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:

Table 1. allOf composition mapping
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.
  • minimum and maximum: 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.

In the default merge mode:
  • Compatible definitions: the tool merges them into a single field and combines their constraints, such as enum, maxLength, and pattern, by the constraint rules. The field is mandatory if any subschema lists it as a required property.
  • Incompatible definitions (a different type, an object, or an array): the tool keeps the last one and outputs a warning.
With --no-merge-allof, the tool merges nothing:
  • One $ref definition at most: the tool keeps a single definition, the $ref one 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 set maxLength to 5 and to 20, the effective maximum length is 5 characters, but the generated field is VARCHAR(20). The default merge generates VARCHAR(5). If you need the generated field to reflect all the constraints of the subschemas, you use the default merge.
  • Two or more $ref definitions: 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.

Table 2. Sample warnings
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.

Note: For the format of the message path and the commands that write warnings to standard output, go to Output warnings (-W).