wol-soft / wol-soft/php-json-schema-model-generator

A multi-type property including object with a composition rejects every object value

Open
#191 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
PHP
Stars
83
Forks
21
PR merge metrics
No merged PRs in 30d

Description

Summary

A property whose declared type is a multi-type list containing object, combined with a
composition (anyOf / allOf / oneOf / if) whose branch resolves to an object, rejects every
object value at runtime — with an error comparing two generated class names:

Invalid class for 'p': requires 'Root_Root_P5d2593f4976038d6db2cbff598e75752', got 'Root_P'

The other declared types still work; only object values are affected. This includes
["object", "null"], the ordinary way to spell a nullable object, which makes the shape easy to hit
by accident.

Pre-existing on master. Found while working on #181.

Reproduction

{
  "type": "object",
  "properties": {
    "p": {
      "type": ["object", "null"],
      "anyOf": [
        {
          "type": "object",
          "required": ["a"]
        }
      ]
    }
  }
}
new Root(['p' => ['a' => 1]]);
Invalid value for 'p' declined by composition constraint
  Requires to match at least one composition element
  - Composition element #1: Failed
    * Invalid class for 'p': requires 'Root_Root_P5d2593f…', got 'Root_P'

{"a": 1} satisfies the branch: it is an object and it has a. It must be accepted.

Boundary

Only the parent property's declared type matters. The branch below is the same object-typed branch
in every row:

parent type object value
"object" accepted
["object"] rejected
["object", "string"] rejected
["object", "null"] rejected
(absent) accepted

The branch's own spelling makes no difference — a branch declaring "type": "object" and a branch
declaring "type": ["object", "string"] both fail. Applies to anyOf, allOf and if/then/else
alike.

Note the ["object"] row is a separate defect (#189) rather than part of this one, and is already
fixed on the #181 branch; the two remaining multi-type rows are what this issue is about.

Cause

PropertyFactory::createMultiTypeProperty() builds one sub-property per listed type and routes the
object entry through createObjectProperty(), which generates a nested class and attaches an
instantiation decorator. The value handed to the property is therefore instantiated into the
property's own generated class before the composition validator runs.

The composition branch is processed independently and generates a second class of its own, with an
InstanceOfValidator against it. That validator then sees an instance of the property's class and
fails — the two classes describe the same data but are unrelated types.

With a single "object" type, or with no type at all, only one class is involved and nothing
compares them.

Suggested direction

The branch check needs to compare against the value as the branch understands it, rather than
against whichever class instantiated it first. Options worth evaluating:

  • Have the multi-type object sub-property and the composition branch share one generated class where
    the branch's constraints permit it.
  • Reset the branch's input to the raw model data (as ComposedItem.phptpl already does between
    branches) so the branch validates the array rather than an instance.
  • Skip the branch's InstanceOfValidator when the enclosing property is multi-type, and rely on the
    branch's own structural constraints instead.

Test coverage to add

No existing fixture combines a multi-type property with a composition, which is why this has gone
unnoticed. Worth covering:

  • ["object", "null"] and ["object", "string"] parents, each with anyOf, allOf and
    if/then/else, asserting that a satisfying object is accepted;
  • a non-satisfying object still rejected, with the branch's real reason rather than a class
    mismatch;
  • the non-object member of the union still accepted;
  • the single-type "object" parent as the control that must keep working.

Related

Fixed on the #181 branch, in the same area but a different mechanism: the same shape with an
untyped branch ({"required": ["a"]} with no type) also rejected every object value, because
the parent's type was injected into the branch and turned it into a multi-type branch with its own
class. That injection is gone, so untyped branches now work; an explicitly object-typed branch still
does not, which is this issue.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in PropertyFactory::createMultiTypeProperty() and inspect how its object sub-property reaches composition validation. Compare that flow with the raw-data reset described for ComposedItem.phptpl, then add coverage for the listed union and composition cases. Done means satisfying objects are accepted, failing objects report the branch reason, non-object union values still work, and the single-type object control remains passing.

Written by the indexing model from the issue text.

Assessment

Tech stack
json, php
Domain
tooling
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.