feat: support schemas combining 'properties' and 'additionalProperties'#141
Merged
Conversation
Schemas that declare both 'properties' and 'additionalProperties' were
previously rejected with a GeneratorException. Such schemas are now
generated as regular classes in which all declared properties work as
before, and undeclared input properties are collected into an
additional-properties map:
- buildFromInput() maps undeclared input keys into the map, converting
each value according to the 'additionalProperties' schema
- toJson() serializes the map back, with declared properties taking
precedence on key collisions
- getAdditionalProperties()/withAdditionalProperties() expose the map
- __clone() deep-clones map values where necessary
- object-typed 'additionalProperties' values get their own generated
class ({Class}AdditionalPropertiesItem)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
NamespaceInferrer joined the working directory and a relative target
directory with PATH_SEPARATOR (':' on Unix), which broke namespace
inference for all relative paths, including 'generate:fromspec' with
the repository's own .s2c.yaml.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Optional properties with a schema default are declared non-nullable and initialized with their default, so the isset() guards emitted around their toJson() serialization and __clone() handling were always true. PHPStan >= 2.2 rejects isset() on non-nullable properties (isset.property), which broke CI on every regeneration of the example classes. The Spec classes are regenerated with the fixed generator. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The additional-properties map is string-keyed, so plain 'T[]' was imprecise; use PHPStan-style 'array<string, T>' in the property, getter, and wither annotations. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Schemas that declare both
propertiesandadditionalPropertieswere previously rejected with aGeneratorException("using 'properties' and 'additionalProperties' in the same schema is currently not supported"). This broke code generation for the mittwald mStudio API, whose spec now contains such a schema (de.mittwald.v1.domainmigration.DomainNotMigratableValidationError'scontextproperty).Such schemas are now generated as regular classes in which all declared properties work as before, while undeclared input properties are collected into an
$additionalPropertiesmap:buildFromInput()maps undeclared input keys into the map, converting each value according to theadditionalPropertiesschema (including$validatepassthrough for object-typed values)toJson()serializes the map back out; it is written before the declared properties, so declared properties win on key collisionsgetAdditionalProperties()/withAdditionalProperties()expose the map__clone()deep-clones map values where the value type requires itadditionalPropertiesvalues get their own generated class ({Class}AdditionalPropertiesItem)Implementation notes
PropertyBuilderis removed;NestedObjectPropertynow claims schemas that have declared properties even whenadditionalPropertiesis also present. Purely map-like schemas (onlyadditionalProperties) are still handled by the array property types, so their behavior is unchanged.additionalPropertiesis modeled as a pseudo-property built viaPropertyBuilder, so all existing mapping/serialization/clone expression machinery is reused.additionalPropertieswhile also using schema-leveladditionalPropertiesstill throw, since the generated member would collide.🤖 Generated with Claude Code