add email metadata and relational validations - #5
Conversation
| if isinstance(self.load_default, enum.Enum): | ||
| self.metadata["default"] = self.load_default.value |
There was a problem hiding this comment.
From a quick test, I think this is only an issue on the enum class. If I do:
my_field = fields.String(
load_default=ResourceType.INTERNAL
)It generates fine, it's only if I set a load_default in fields.Enum. What I'd probably do here is just put this logic in the Enum class which already has some special logic (we don't use the enum class from Marshmallow because it did not work as expected).
| if isinstance(example, enum.Enum): | ||
| self.metadata["example"] = example.value | ||
| elif isinstance(example, list): | ||
| self.metadata["example"] = [ | ||
| item.value if isinstance(item, enum.Enum) else item for item in example | ||
| ] |
There was a problem hiding this comment.
This however does appear to be right as example always has this behavior.
| def __init__(self, **kwargs: typing.Any) -> None: | ||
| super().__init__(**kwargs) | ||
|
|
||
| if any(isinstance(validator, CustomEmail) for validator in self.validators): | ||
| self.metadata["format"] = "email" | ||
|
|
There was a problem hiding this comment.
I tried digging into the APISpec docs https://apispec.readthedocs.io/en/latest/index.html to see if there was a better way to do it, and honestly got a bit lost. I looked at what Regexp does in order to get pattern set and it seemed to just be a custom case that didn't have a great parallel for the format field.
I don't think we'd want to do it quite like this since it would mean if I update a validator to have a format I have to also remember to come change this to make it work.
What if instead we had something like this in the base MixinField init:
...
validators=kwargs.get("validate", [])
for validator in validators:
format_override = validator.get_format_override()
# We would have to figure out what happens in the event two validators had a format override and probably error:
if format_override:
self.metadata["format"] = format_override
then in the validators we can add overrides to the metadata for a given type.
Email would return "email" and we'd have a mixin that the rest have that defaults to just None.
This way we make the field ask the validator if it has any special behavior.
Summary
Work for #11587
Adds shared schema/OpenAPI functionality needed to expose structured validation metadata to API consumers.
This is the
grants-sharedportion of the validation proof of concept being explored in #11587. The accompanyingsimpler-grants-govPR uses this metadata to generate frontend Zod validation schemas and map validation failures to user-friendly translated messages.Changes proposed
!!python/...) in the generated specification.x-relational-validationsOpenAPI metadata.example output of a relational validator in the openapi spec
Context for reviewers
The goal of #11587 is to investigate a less brittle way for the frontend to handle backend validation errors rather than mapping user-facing messages to raw backend error strings.
This PR provides the shared backend/OpenAPI pieces of that experiment.
The larger approach treats the backend schema as the source of truth for validation rules.
grants-sharedprovides the validation behavior and exposes enough structured metadata through OpenAPI for API consumers to understand those rules without duplicating their definitions.For example, a schema can define a relationship such as:
The generated OpenAPI specification can then describe that relationship through
x-relational-validations.An accompanying PR in
simpler-grants-govconsumes this metadata during Zod generation. That allows the same backend-defined rules to be used for frontend field validation, submit-time validation, and mapping API422responses to translated frontend messages.This is part of a broader proof of concept and is not intended to establish the final architecture by itself.
Validation steps
Install/use this branch of
grants-sharedfrom thesimpler-grants-govAPI project.Generate the OpenAPI specification:
cd api make openapi-spec