allOf schema composition
When the fglrestful tool reads an allOf composition in the OpenAPI description of a web service, it generates the BDL types for it.
The JSON Schema allOf keyword combines several schemas, called subschemas, into a
single composition. A JSON value must satisfy every subschema.
To expose a record type of your own web service as a composition, set the JSONAllOf attribute on it. The attribute composes record types only.
Types generated from a composition
The type fglrestful generates depends on what the subschemas are:
| Case | If the allOf composition contains |
fglrestful generates |
|---|---|---|
| Objects | Only object schemas | A single RECORD containing the merged properties. |
| Primitives | Only schemas of primitive JSON types, such as strings, numbers, integers, or booleans | A single primitive data type containing the merged constraints. |
| Differing primitives | Schemas of different primitive JSON types, such as a string and an integer, or where no subschema declares a type | A util.JSONObject. The
tool outputs a warning. |
| Mixed | A mixture of object schemas and schemas of primitive JSON types | A RECORD built from the object schemas. The tool skips the
other schemas and outputs a warning. |
Objects. Where the composition contains only object schemas, the tool merges their
properties into one record. For an Employee composition of the
Person and Address schemas, it generates the following record. The
field types come from those two schemas, which are not shown:
PUBLIC TYPE Employee RECORD
id INTEGER,
name VARCHAR(40),
street VARCHAR(60),
city VARCHAR(30)
END RECORD
Primitives. Where the composition contains only schemas of primitive JSON types, the tool
merges them into a single primitive data type. A common use of allOf is to restrict an existing type
further. In the following schemas, OpenOrderStatus references the
OrderStatus enumeration and adds a subschema that lists two of its three values and
sets a shorter length limit:
"OrderStatus": {
"type": "string",
"enum": ["NEW", "SHIPPED", "CANCELLED"],
"maxLength": 16
},
"OpenOrderStatus": {
"allOf": [
{ "$ref": "#/components/schemas/OrderStatus" },
{ "enum": ["NEW", "SHIPPED"], "maxLength": 10 }
]
}
The second subschema does not list CANCELLED, so the type allows only
NEW and SHIPPED. It also sets the shorter length limit, resulting
in a VARCHAR(10) type:
PUBLIC TYPE OpenOrderStatus VARCHAR(10) ATTRIBUTE(JSONEnum = '"NEW","SHIPPED"')
Differing primitives. Where the composition contains schemas of different primitive JSON types, no single BDL type can hold the value. For example, from the following schema:
"OrderRef": {
"allOf": [
{ "type": "string", "maxLength": 12 },
{ "type": "integer" }
]
}
The tool generates a util.JSONObject:
PUBLIC TYPE OrderRef util.JSONObject {fallback type}
It outputs a warning for each subschema it skips, and then the fallback warning.
Mixed. Where the composition contains a mixture of object schemas and schemas of primitive
JSON types, the tool builds
the record from the object schemas only. For example, from a composition of a Customer object schema
with id and name properties and a string:
"LabeledCustomer": {
"allOf": [
{ "$ref": "#/components/schemas/Customer" },
{ "type": "string", "maxLength": 10 }
]
}
The tool generates the Customer properties only, and outputs a warning that the
type and maxLength keywords of the string are not mapped:
PUBLIC TYPE LabeledCustomer RECORD
id INTEGER,
name VARCHAR(40)
END RECORD
Where the composition contains an object schema and a subschema with only keywords such as
nullable or description, the tool also skips that subschema and
outputs the same warning. For example,
OpenAPI 3.0 ignores keywords placed next to a $ref, such as
nullable, so a specification often wraps the reference in a composition:
"OptionalCustomer": {
"allOf": [
{ "$ref": "#/components/schemas/Customer" },
{ "nullable": true }
]
}
The tool generates the Customer properties only, and the
nullable keyword is not mapped:
PUBLIC TYPE OptionalCustomer RECORD
id INTEGER,
name VARCHAR(40)
END RECORD
Separate types for object subschemas (--no-merge-allof)
The default merge gives one record for each composition, so a schema that several compositions
reference has its properties copied into each one, as Person and
Address are in the Employee record. To keep those schemas as types
of their own, run --no-merge-allof. The tool then generates a type for each
$ref subschema, and a composing type that holds those types as members, with the JSONAllOf attribute set on it:
PUBLIC TYPE Person RECORD
id INTEGER,
name VARCHAR(40)
END RECORD
PUBLIC TYPE Address RECORD
street VARCHAR(60),
city VARCHAR(30)
END RECORD
PUBLIC TYPE Employee RECORD ATTRIBUTE(JSONAllOf)
Employee_Person Person,
Employee_Address Address
END RECORD
The tool names each member for the composing type and the subschema it came from. An inline
object subschema has no type of its own, so it becomes an anonymous record member named
Type_specific, Type_specific2,
and so on, where Type is the composing type.
The option changes nothing for a composition of schemas of primitive JSON types, because the attribute applies to record members only.
How constraints are merged
The tool merges the constraints of all the subschemas as follows:
enum: only the values allowed by every subschema. Where the subschemas have no value in common, the tool drops the constraint and outputs a warning.maxLength: the smallest value.minimumandmaximum: the narrowest range.multipleOf: the first value found.pattern: the first regular expression found. BDL supports one JSONPattern attribute per field, so the tool drops any further expression and outputs a warning.
For the BDL types fglrestful generates from these keywords, go to Converting OpenAPI types to BDL in client stubs.
Duplicate properties in object subschemas
These rules apply to a schema fglrestful reads. Where you set the JSONAllOf attribute on a record type of your own, the composed records cannot repeat a property name.
Where several object subschemas define the same property, what the tool generates depends on the mode.
- Compatible definitions: the tool merges them into a single field and combines their
constraints, such as
enum,maxLength, andpattern, by the constraint rules. The field is mandatory if any subschema lists it as arequiredproperty. - Incompatible definitions (a different type, an object, or an array): the tool keeps the last one and outputs a warning.
--no-merge-allof, the tool merges nothing:- One
$refdefinition at most: the tool keeps a single definition, the$refone if there is one, otherwise the last inline one. It drops the others, whatever their type.Note: The tool keeps one definition and drops the others, so the generated field can lose constraints. For example, where two inline subschemas setmaxLengthto 5 and to 20, the effective maximum length is 5 characters, but the generated field isVARCHAR(20). The default merge generatesVARCHAR(5). If you need the generated field to reflect all the constraints of the subschemas, you use the default merge. - Two or more
$refdefinitions: the tool keeps them all. Each member record then defines the property, so the generated code does not compile and returns error -9154. You correct the generated code by hand.
Warnings
The tool outputs a warning wherever BDL cannot represent a constraint of the description. Warnings
are on by default, and you run -W no to silence them. Each message begins with the
path of the schema that caused it.
If the allOf composition contains |
fglrestful reports |
|---|---|
| Enumerations with no value in common | allOf enum intersection is empty; the enum constraint was dropped |
More than one pattern |
allOf subschema #1 pattern '^[A-Z0-9-]+$' dropped: 4GL supports a single JSONPattern, keeping '^ORD-[0-9]{6}$' |
| Object and primitive subschemas together | allOf subschema #1 has no identifiable type; it was skipped and its keys (type, maxLength) are not mapped |
| Schemas of different primitive JSON types, or where no subschema declares a type | allOf schema does not have a subschema with identifiable types, using util.JSONObject fallback |
The same property in several subschemas, where the default merge cannot merge
it, or with --no-merge-allof where at most one definition is a $ref
subschema |
allOf property 'quantity' is defined by several subschemas; only the definition of subschema #1 is generated |
The same property in several referenced subschemas, with
--no-merge-allof |
allOf property 'label' is defined by several referenced subschemas, which is not supported by the compiler. Please correct the code directly by hand. |
The messages name the values the tool found, so your own output differs in the pattern text, the property names, and the subschema numbers.