Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Serializer] Add ability to collect denormalization errors #38165

Closed

Conversation

julienfalque
Copy link
Contributor

@julienfalque julienfalque commented Sep 12, 2020

Q A
Branch? master
Bug fix? no
New feature? yes
Deprecations? no
Tickets #37419
License MIT
Doc PR -

This PR implements #37419 and replaces #27136 (and #28358 maybe).

With these changes, the feature would require all involved third party denormalizers to check the new collect_invariant_violations context option and transform any relevant domain error into an InvariantViolationException exception that contains information needed to build e.g. a nice 400 Bad Request response.

Maybe I can go further with something similar to ConstraintViolationBuilder to ease creating InvariantViolationException instances with symfony/translation integration. But I'm not sure about the current direction so I'd like to get some feedback first :)

TODO:

  • tests
  • builder similar to ConstraintViolationBuilder?
  • integration with symfony/translation?

Copy link
Member

@weaverryan weaverryan left a comment

Choose a reason for hiding this comment

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

I don't know the serializer internals well, but I would LOVE this for 5.2 - it would help API Platform return denormalization errors as validation errors instead of one-off 400 errors.

src/Symfony/Component/Serializer/InvariantViolation.php Outdated Show resolved Hide resolved
src/Symfony/Component/Serializer/InvariantViolation.php Outdated Show resolved Hide resolved
$this->setAttributeValue($object, $attribute, $denormalizedValue, $format, $context);
} catch (InvalidArgumentException $e) {
throw new NotNormalizableValueException(sprintf('Failed to denormalize attribute "%s" value for class "%s": '.$e->getMessage(), $attribute, $type), $e->getCode(), $e);
}
Copy link
Member

Choose a reason for hiding this comment

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

I don't know the internals well here. Do we need BOTH the $this->validateAndDenormalize() and $this->setAttributeValue() to be inside the try-catch for InvariantViolationException?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually if we can denormalize the value (which means it is valid) but can't write it, I don't think this is an invariant violation. I moved that parted out of the new try-catch.

} catch (InvalidArgumentException $e) {
throw new NotNormalizableValueException(sprintf('Failed to denormalize attribute "%s" value for class "%s": '.$e->getMessage(), $attribute, $type), $e->getCode(), $e);
}
} catch (InvariantViolationException $exception) {
Copy link
Member

Choose a reason for hiding this comment

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

So, the idea is that all denormalizers need to be aware of the COLLECT_INVARIANT_VIOLATIONS context key and behave differently based on this? Either throwing a NotNormalizableValueException or a InvariantViolationException? Is that correct?

Could we just catch the existing NotNormalizableValueException? Or is the problem that these error messages are currently a bit "internal" (e.g. Failed to denormalize attribute foo for class Foo\Bar) and so the new exception message allows denormalizers to advertise a safer, "validation" message? Or is there another purpose?

If the only purpose is to advertise a validation-safe message, perhaps we could add a new optional property+method (thinking out loud) to NotNormalizableValueException that contains this - e.g. getValidationMessage()? Or maybe I'm way off understanding the mechanics :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So, the idea is that all denormalizers need to be aware of the COLLECT_INVARIANT_VIOLATIONS context key and behave differently based on this? Either throwing a NotNormalizableValueException or a InvariantViolationException? Is that correct?

Yes, I think collecting denormalization errors should be opt-in. Though throwing InvariantViolationException is the only implemenation I managed to get working for now.

My first attempt was to wrap all denormalizations using an interface (e.g. InvariantViolationCatcherInterface). One implementation would just throw immediatly (current behavior) and another would collect errors instead. But I faced too many issues and gave up with this approach. Maybe I should give it another try?

Could we just catch the existing NotNormalizableValueException?

Here we catch InvariantViolationException because we rely on nested denormalizers calls to denormalize nested values. Those denormalizers will only throw this kind of exception for each invariant violation if COLLECT_INVARIANT_VIOLATIONS is enabled, so if we catch one here, we can assume it's enabled without actually checking the context in this method.

I'd like to introduce an interface for exceptions that would simplify this maybe, but I'll wait until we're sure the current approach is correct.

Or is the problem that these error messages are currently a bit "internal" (e.g. Failed to denormalize attribute foo for class Foo\Bar) and so the new exception message allows denormalizers to advertise a safer, "validation" message? Or is there another purpose?

If the only purpose is to advertise a validation-safe message, perhaps we could add a new optional property+method (thinking out loud) to NotNormalizableValueException that contains this - e.g. getValidationMessage()? Or maybe I'm way off understanding the mechanics :)

Indeed, the purpose is to advertise validation-safe messages (and maybe more validation related data later). Not sure NotNormalizableValueException is suitable for that though: currently, denormalizers might throw other exceptions (e.g. UnexpectedValueException). We could either:

  • always throw NotNormalizableValueException, but replacing a thrown exception with another would be a BC break I guess;
  • add the methods you suggest to all possible exceptions.

WDYT?

@dkarlovi
Copy link
Contributor

dkarlovi commented Oct 5, 2020

Discussed for a bit in #25729

@fabpot fabpot closed this Oct 7, 2020
@fabpot
Copy link
Member

fabpot commented Oct 7, 2020

We've just moved away from master as the main branch to use 5.x instead. Unfortunately, I cannot reopen the PR and change the target branch to 5.x. Can you open a new PR referencing this one to not loose the discussion? Thank you for your understanding and for your help.

@julienfalque
Copy link
Contributor Author

Sure, see #38472 😊

@nicolas-grekas nicolas-grekas modified the milestones: 5.x, 5.2 Oct 14, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants