Repository navigation
Use nullable for Maybe schemas - #113
isaac-uptrust wants to merge 4 commits into
Conversation
77fbf18 to
f5075ee
Compare
The `Generic`-derived schema for polymorphic datatypes is incorrect. The schema for a datatype like
`data X a = X{ field :: a }` has `field` as a required property.
This means that the schema for `X (Maybe something)` also has `field` marked
as a required field.
The optionality of the `Maybe` is forgotten. A service relying on the erroneous schema would
expect a non-nullable value, but
The service sending `X (Maybe something)` values can send `null` in the `field` propety, but the
service relying on the erroneous schema will expect no `null` in `field`, leading to a runtime
error.
Given `data X a = X{ field :: a }`, the `Generic`-derived schema *has* to mark `field` as required,
because it must work for all `a`. Therefore, `instance ToSchema a => ToSchema (Maybe a)` has to
generate a `nullable` schema to be correct. Then the schema for `X (Maybe something)` will have
required property `field` with a `nullable` schema for `something`.
See the previous commit for an explanation of the problem. This change also rules out `ToSchema (Maybe (Maybe _))` for correctness. --- The schema for `Maybe a` is prefixed with `Maybe_` to distinguish it from the schema for `a`. Without this, `declareSchemaRef` will generate the same reference for an `a` or a `Maybe a`. When an API uses an `a` and a `Maybe a`, only one of the two schemas will make it into the `components.schemas` section (because of the name conflict). This can lead to a schema that says it produces non-nullable values, but may actually produce nulls. This problem could also be solved by changing `declareSchemaRef` to strip the `nullable` property from the declared schema and return the nullability value alongside the `Referenced Schema`. This would increase sharing (you wouldn't have near duplicate schemas `X` and `Maybe_X`), with the cost of forcing downstream libraries (such as `servant-openapi3`) to explicitly handle that nullability information. I chose the solution that's compatible with current downstream code.
f5075ee to
6111955
Compare
|
Clever use of overlapping instances. Love it 🚀 |
|
AFAIR |
|
any plan to fix this ? |
|
@vcombey the maintainer is only minimally available, about once a year. I have been trying to get somebody (like e.g. Rick Owens or Simon Michael) to fork this library and make improvements, I think it should be pretty easy to make something significantly better. You could say I should do it myself, but our issues haven't been severe enough. |
# Conflicts: # CHANGELOG.md # openapi3.cabal
|
@stevladimir the library only accepts spec versions 3.0.0 to 3.0.3 ( @maksbotan I've merged master to resolve the conflicts; the test suite and doctests pass on GHC 9.10.3. Could you review this when you have a moment? |
The
Generic-derived schema for polymorphic datatypes is incorrect. The schema for a datatype likedata X a = X{ field :: a }hasfieldas a required property. This means that the schema forX (Maybe something)also hasfieldmarked as a required field. The optionality of theMaybeis forgotten. A service relying on the erroneous schema would expect a non-nullable value, but a service sendingX (Maybe something)values can sendnullin thefieldpropety, but a service relying on the erroneous schema will expect nonullinfield, leading to a runtime error.Given
data X a = X{ field :: a }, theGeneric-derived schema must markfieldas required, because it needs to work for alla. Therefore,instance ToSchema a => ToSchema (Maybe a)has to generate anullableschema to be correct. Then the schema forX (Maybe something)will have required propertyfieldwith anullableschema forsomething.This change changes the
ToSchemainstance forMaybe a. The schema forMaybe ais prefixed withMaybe_to distinguish it from the schema fora. Without this,declareSchemaRefwill generate the same reference for anaor aMaybe a. When an API uses anaand aMaybe a, only one of the two schemas will make it into thecomponents.schemassection (because of the name conflict). This can lead to a schema that says it produces non-nullable values, but may actually produce nulls.This problem could also be solved by changing
declareSchemaRefto strip thenullableproperty from the declared schema and return the nullability value alongside theReferenced Schema. This would increase sharing (you wouldn't have near duplicate schemasXandMaybe_X), and I think it would be cleaner, but at the cost of forcing downstream libraries (such asservant-openapi3) to explicitly handle that nullability information. I chose the solution that's compatible with current downstream code.I think this supercedes the previous attempts at fixing
Maybe:Maybe aschemas #103