-
Notifications
You must be signed in to change notification settings - Fork 4
required
Most projection types support "required" flag (+
) on fields and tags; in addition map keys in operation
projections may be marked as required
. This page summarizes different usages and semantics of this flag.
Before discussing what does it mean for something to be required let's see how Epigraph data is structured.
(Entity)Data
is essentially a map from tags to their Value
s, where Value
holds either an ErrorValue
or a Datum
.
This means that every tag may be in one of the following states:
- Absent
- Contains a
Value
withErrorValue
- Contains a
Value
with (bothErrorValue
and)Datum
set tonull
(NullValue
) - Contains a
Value
with non-nullDatum
Datum
is a parent for (instances of) all the other types: maps, lists, records, primitives, and enums.
RecordDatum
is a map from fields to Data
instances. If field type is a Datum
type, then Data
instance
will have only one special self
tag containing Datum
instance. This leads to the following options for a field:
- Absent
- is of
Data
type and contains arbitraryData
instance - is of
Datum
type andself
tag containsErrorValue
- is of
Datum
type andself
tag containsNullValue
- is of
Datum
type andself
tag contains non-nullDatum
instance
This implies that Datum
field can be set to null
(self
tag contains NullValue
), but Data
fields can not:
setting them to null
removes the field. It goes in line with the philosophy of the Data
type as null
value
wouldn't have any meaning for it.
Map and list values are structured the same way as field values.
Fields and tags may be marked as required, for instance:
create {
inputType list[BookRecord]
inputProjection *(
+title, // title required
+author:+id, // author id required
+text:+plain // text required
)
}
Required on Data
tag mean that
- tag must be present
- it must not be an error
- its value must not be null
Required on a field mean that
- field must be present
- if it's a
DatumType
then it must not be a null or an error (i.e.self
tag is required)
Required map keys mean that map keys must be provided, *
is not accepted in request projection.
Operation delete and output projections don't have 'required' on fields and tags (although delete projection
uses +
on data projections to mark deletable entities), but they can still mark map keys as required
with the
same meaning as input and update projections, i.e. to force clients to pass map keys in the request.
'Required' only applies to request output projections and enforces operation to somehow fulfill its contract: required but missing field is an operation error.
'Required' can be put on Data
tags and RecordDatum
fields. Guarantees provided to response consumers by required
are the same as for the operation projections: data must be present, it must not be an error or null. Constraints are a
bit more relaxed for operation implementations: errors and null values are allowed, and framework will try to
get rid of them by sweeping them under the carpet data tree pruning.
Object | State | Action |
---|---|---|
required tag | missing | operation error, fail request |
required tag | set to error | remove Data (field, map or list entry) |
optional self tag | set to error | remove Data (field, map or list entry) |
optional tag | * | keep as is |
required field | missing | operation error, fail request |
required field | set to error | replace whole record with error |
required field | set to null | replace whole record with error (412) |
optional field | * | keep as is |
required key | missing | operation error, fail request |
required key | set to error | replace whole map with error |
required key | set to null | replace whole map with error (412) |
optional key | * | keep as is |
list entry | * | keep as is |
Rules are applied recursively, bottom to top. This means that nulls and errors will bubble up until non-required field or tag is found and will become its value. If they bubble all the way up to the root then request fails.
More technically, this can be described as two functions:
pruneData(data, projection) -> Keep | Fail | Replace(newData) | Remove
pruneDatum(datum, projection) -> Keep | Fail | Replace(newDatum) | UseError(error)
with the following logic (first match wins)
Object | State | Pruning result | Action |
---|---|---|---|
required tag/field/key | missing | return Fail
|
|
required tag | set to error | return Remove
|
|
required tag | set to null
|
return Remove
|
|
tag | Replace |
replace | |
required or $self tag |
UseError |
return Remove
|
|
tag | UseError |
replace with error | |
required field/key datum | set to error | return UseError
|
|
required field/key datum | set to null
|
return UseError(412)
|
|
required field/key | Remove |
return UseError(412)
|
|
field/key/list item | Replace |
replace | |
field/key | Remove |
remove | |
list item | Remove |
remove item (changes indices!) | |
* | Keep |
keep | |
* | Fail |
fail request |