Skip to content

feat(database): expose 422 validation errors from Model.save()#109

Merged
anilcancakir merged 2 commits into
masterfrom
feat/orm-validation-errors
Jul 25, 2026
Merged

feat(database): expose 422 validation errors from Model.save()#109
anilcancakir merged 2 commits into
masterfrom
feat/orm-validation-errors

Conversation

@anilcancakir

Copy link
Copy Markdown
Contributor

What

InteractsWithPersistence.save() returned only a bool and threw away the server's 422 response body, so a consumer could not show per-field validation errors after a failed save (only a generic "save failed").

Change

  • Adds Map<String, List<String>> validationErrors (unmodifiable getter) and String? validationError(String field) on the persistence mixin.
  • save() clears them at the start of the remote block and populates them from a non-successful response (Laravel {message, errors: {field: [...]}} shape) via MagicResponse.errors.
  • The Future<bool> return contract is unchanged; a thrown transport error leaves the map empty, so callers distinguish a field-validation failure (422) from a network/500 failure.

Why

Enables ORM-backed forms to render server field errors inline (the uptizm monitoring forms need this). Backward compatible and additive.

Tests

test/database/eloquent/model_test.dart: a faked 422 populates validationErrors and a subsequent success clears them; a 500 leaves them empty. Full flutter test test/database green.

Model.save() returned only a bool and discarded the server's 422 error body, so callers could not surface per-field validation messages. Capture the Laravel-shape errors into validationErrors (with a validationError(field) convenience), cleared at the start of each save and populated on a non-successful response. The bool return is unchanged (backward compatible); a thrown transport error leaves the map empty so callers can distinguish a field-validation failure from a network failure.
Copilot AI review requested due to automatic review settings July 21, 2026 19:36
@codecov

codecov Bot commented Jul 21, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a small, additive validation-error surface to the Eloquent persistence layer so callers can read Laravel-style 422 field errors after Model.save() fails, enabling ORM-backed forms to render inline per-field messages.

Changes:

  • Added validationErrors (map) and validationError(field) helpers to InteractsWithPersistence.
  • Updated remote save() flow to clear prior errors and populate them from a failed response via MagicResponse.errors.
  • Added tests covering 422 population, clearing on subsequent success, and ensuring non-field failures (500) don’t populate field errors.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
lib/src/database/eloquent/concerns/interacts_with_persistence.dart Adds validation error state + accessors and wires population/clearing into the remote save path.
test/database/eloquent/model_test.dart Adds regression tests for 422 error extraction/clearing behavior on save().

Comment thread lib/src/database/eloquent/concerns/interacts_with_persistence.dart Outdated
Comment thread lib/src/database/eloquent/concerns/interacts_with_persistence.dart Outdated
Comment thread lib/src/database/eloquent/concerns/interacts_with_persistence.dart Outdated
… contract

Map.unmodifiable is shallow: MagicResponse.errors builds a fresh map of
growable lists, so a caller could still mutate the per-field messages the
getter handed out. _extractValidationErrors now freezes the map and every
list inside it, the field holds that frozen map (const {} when empty), and
the getter returns it directly instead of re-wrapping on each read.

The doc comment also claimed a successful save always leaves the map
empty. It does not: the map tracks the REMOTE leg, so a hybrid model whose
remote save 422s while its local write succeeds returns true with the
errors filled. Say so, and drop the '?.isNotEmpty == true' plus bang from
validationError() for a plain null-and-empty guard.

Adds an unmodifiable-at-both-levels test, the CHANGELOG entry the
contributing checklist asks for, and the doc + skill pages for the new
public members.
@anilcancakir
anilcancakir merged commit d560c0a into master Jul 25, 2026
4 checks passed
@anilcancakir
anilcancakir deleted the feat/orm-validation-errors branch July 25, 2026 17:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants