Skip to content

Commit

Permalink
Document optField combinator. (#1624)
Browse files Browse the repository at this point in the history
* Document optField combinator.

This adds some extra explanations to the section about optional fields, and introduces the new `optField` combinator, added to work around the parser leniency issue described in #1621.

* Update CHANGELOG
  • Loading branch information
pcapriotti authored Jun 24, 2021
1 parent 467af68 commit 0155064
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 1 deletion.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@
-->

# Unreleased

## Internal changes

* schema-profunctor: add `optField` combinator and corresponding documentation (#1621, #1624).

# 2021-06-23

## API Changes
Expand Down
37 changes: 36 additions & 1 deletion libs/schema-profunctor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,10 @@ will return `Nothing` when the field is missing (or is `null`), and
correspondingly the serialiser will not produce the field at all when its value
is `Nothing`.

Another possibility is a field that, when missing, is assumed to have a given default value. Most likely, in this case we do not want the field to be omitted when serialising. The schema can then be defined simply by using the `Alternative` instance of `SchemaP` to provide the default value:
Another possibility is a field that, when missing, is assumed to have a given
default value. Most likely, in this case we do not want the field to be omitted
when serialising. The schema can then be defined simply by using the
`Alternative` instance of `SchemaP` to provide the default value:

```haskell
userSchemaWithDefaultName :: ValueSchema NamedSwaggerDoc User
Expand Down Expand Up @@ -436,6 +439,38 @@ that the serialiser should output the default when the value of the field is
`Nothing`. That means we need to also change the input type to a `Maybe`, which
is what `opt` and `optWithDefault` do.

There is a subtlety here related to error messages, which can sometimes result
in surprising behaviour when parsing optional fields with default values.
Namely, given a field of the form

```haskell
opt (field "name" schema)
```

the corresponding parser will return `Nothing` not only in the case where the
`"name"` field is missing, but also if it is fails to parse correctly (for
example, if it has an unexpected type). This behaviour is caused by the fact
that `opt` (and the `optWithDefault` / `lax` combo described above) are
implemented in terms of the `Alternative` instance for `Aeson.Parser`, which
cannot distinguish between "recoverable" and "unrecoverable" failures.

There are plans to improve on this behaviour in the future by directly changing
the `Alternative` instance that `SchemaP` relies on, but for the moment, if
this behaviour is not desirable, then one can use the ad-hoc `optField`
combinator to introduce optional fields.

For exapmle, the above schema can be implemented using `optField` as follow:

```haskell
userSchema'' :: ValueSchema NamedSwaggerDoc User
userSchema'' = object "User" $ User
<$> field "name" schema
<*> optField "handle" (Just Aeson.Null) schema
<*> optField "expire" Nothing schema
```

The argument after the field name determines how the `Nothing` case is rendered in the generated JSON. If it is itself `Nothing`, that means that the field is completely omitted in that case.

### Redundant fields

Sometimes, JSON encoding of haskell types is not as straightfoward as
Expand Down

0 comments on commit 0155064

Please sign in to comment.