Align the docs with the implementation, and centralise references on Reference:: - #22
Merged
Merged
Conversation
Correct schema title embedding, version constructors, Tag|string operation tags, and samples that passed Reference into JsonSchema or Operation::responses(). Co-authored-by: Sean Tymon <tymondesigns@users.noreply.github.com>
Drop keyword()/property() (not in cortexphp/json-schema 1.3.0). Document that nested items() still emit $schema, and attach discriminator/xml via raw array schema slots. Co-authored-by: Sean Tymon <tymondesigns@users.noreply.github.com>
Clarify that $ref composition uses Schema::object()->ref(), not a static Schema::ref(). Correct README to describe bundled opis meta-schema validation. Co-authored-by: Sean Tymon <tymondesigns@users.noreply.github.com>
Inline OpenAPI schemas must not carry the JSON Schema $schema URI, but the rule was only applied to the schema handed to a slot. Subschemas that cortexphp/json-schema serializes itself (items, and anything below it) still emitted the URI, so Schema::array()->items(...) leaked it into the document. Strip it at every depth, skipping maps whose keys are user-chosen names so a property literally called $schema survives. Raw array schemas are left alone as an escape hatch for declaring a dialect deliberately. Co-authored-by: Sean Tymon <tymondesigns@users.noreply.github.com>
Two feature cards linked to /openapi/validation-and-output, which was never written. Document validate(), the structured errors() payload, what the meta-schema does and does not catch, and the three output formats, instead of pointing the cards somewhere else. Co-authored-by: Sean Tymon <tymondesigns@users.noreply.github.com>
The earlier pass explained failure modes ("cannot be passed to", "returns a
Reference, which responses() does not accept") where the reader only needs the
working pattern. Lead with that instead, drop the embedding caveats the nested
$schema fix made obsolete, and keep the quickstart free of asides.
Co-authored-by: Sean Tymon <tymondesigns@users.noreply.github.com>
…chema JSON Schema 2020-12 core 8.1.1 allows $schema only at a schema resource root, so every nested occurrence the builder can produce is invalid. items and contains are the two call sites in cortexphp/json-schema 1.3.0 that emit it. Co-authored-by: Sean Tymon <tymondesigns@users.noreply.github.com>
Reference::response() and Response::ref() return the same object; the target class shortcut says which registry it points at without reading the pointer. The equivalence is already spelled out in the quickstart cheat sheet. Co-authored-by: Sean Tymon <tymondesigns@users.noreply.github.com>
…fluent form (#23) * feat: add Response::refTo() so referenced responses work in responses() Operation::responses() keys each response from getStatusCode(), so the static Response::ref() — a Reference, which has no status code — could not be passed to it. Every other variadic takes Parameter|Reference because it builds a list and derives no key; responses() is the only one that does. refTo() sets the reference on the response itself, keeping the status code from the named constructor, and serializes to the reference alone. Output is byte-identical to ->response(404, Response::ref('NotFound')). Co-authored-by: Sean Tymon <tymondesigns@users.noreply.github.com> * refactor!: build every reference through Reference:: The X::ref() shortcuts covered nine of the ten component registries and could never cover the tenth: schemas are referenced through Reference::schema() because Schema belongs to cortexphp/json-schema, which knows nothing about #/components. A family that advertises uniformity while omitting the most-used member invites calls like Schema::ref('User'), which cannot exist. Drop the nine delegating statics. Reference:: already has a named constructor for every registry and was the dominant spelling anyway. With the static gone, Response can use the name for the fluent form, so the refTo() added earlier on this branch becomes ref(). Co-authored-by: Sean Tymon <tymondesigns@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Sean Tymon <tymondesigns@users.noreply.github.com>
json-schema 2.0 fixed the items leak but not contains, so the assertions that documented upstream's behaviour as a precondition broke. Assert only that no nested $schema survives, which holds whichever side strips it, and compare whole arrays so the checks do not drill into mixed. Co-authored-by: Sean Tymon <tymondesigns@users.noreply.github.com>
tymondesigns
marked this pull request as ready for review
September 16, 2026 22:48
There was a problem hiding this comment.
🟡 Changes recommended
Recursive schema sanitization can corrupt arbitrary values containing a $schema property.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Aligns documentation with the implemented API, centralizes component references, supports referenced responses, and sanitizes nested schemas.
Changes:
- Replaces object-specific reference shortcuts with
Reference::*. - Adds fluent referenced responses while retaining status codes.
- Expands documentation and nested-schema handling.
File summaries
| File | Description |
|---|---|
README.md |
Corrects validation and Info examples. |
src/Concerns/BuildsArray.php |
Recursively removes nested $schema keys. |
src/Objects/Callback.php |
Removes the callback reference shortcut. |
src/Objects/Example.php |
Removes the example reference shortcut. |
src/Objects/Header.php |
Removes the header reference shortcut. |
src/Objects/Link.php |
Removes the link reference shortcut. |
src/Objects/Parameter.php |
Removes the parameter reference shortcut. |
src/Objects/PathItem.php |
Removes the path-item reference shortcut. |
src/Objects/RequestBody.php |
Removes the request-body reference shortcut. |
src/Objects/Response.php |
Adds status-preserving fluent references. |
src/Objects/SecurityScheme.php |
Removes the security-scheme reference shortcut. |
tests/Unit/Concerns/BuildsArrayTest.php |
Tests nested schema sanitization. |
tests/Unit/Objects/CallbackTest.php |
Removes shortcut coverage. |
tests/Unit/Objects/ExampleTest.php |
Removes shortcut coverage. |
tests/Unit/Objects/HeaderTest.php |
Removes shortcut coverage. |
tests/Unit/Objects/LinkTest.php |
Removes shortcut coverage. |
tests/Unit/Objects/OperationTest.php |
Tests referenced responses and callbacks. |
tests/Unit/Objects/ParameterTest.php |
Removes shortcut coverage. |
tests/Unit/Objects/PathItemTest.php |
Removes shortcut coverage. |
tests/Unit/Objects/RequestBodyTest.php |
Removes shortcut coverage. |
tests/Unit/Objects/ResponseTest.php |
Tests fluent response references. |
tests/Unit/Objects/SecuritySchemeTest.php |
Removes shortcut coverage. |
docs/docs.json |
Adds the validation page to navigation. |
docs/openapi/components.mdx |
Updates component reference guidance. |
docs/openapi/info-and-metadata.mdx |
Clarifies operation tag inputs. |
docs/openapi/installation.mdx |
Documents the optional YAML dependency. |
docs/openapi/introduction.mdx |
Corrects schema and version guidance. |
docs/openapi/paths-and-operations.mdx |
Documents tags and response attachment. |
docs/openapi/quickstart.mdx |
Centralizes reference documentation. |
docs/openapi/request-bodies.mdx |
Updates styles and references. |
docs/openapi/responses.mdx |
Documents fluent referenced responses. |
docs/openapi/security.mdx |
Adds a missing import. |
docs/openapi/validation-and-output.mdx |
Adds validation and serialization guidance. |
docs/openapi/webhooks-and-callbacks.mdx |
Corrects schema references and callback examples. |
Review details
Suppressed comments (3)
tests/Unit/Objects/ResponseTest.php:94
- The test description still names
refTo()even though the exercised API isref().
tests/Unit/Objects/ResponseTest.php:98 - Rename the stale
refTo()reference toref()so test output identifies the public method correctly.
tests/Unit/Objects/ResponseTest.php:107 - This test description refers to the discarded
refTo()name rather than the implementedref()method.
- Files reviewed: 34/34 changed files
- Comments generated: 2
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Recursing into every nested array treated default/examples/const/enum as schemas, so an instance payload like default(['$schema' => 'literal']) lost that key. Limit recursion to JSON Schema applicator and definition keywords. Also rename the Response tests that still said refTo(). Co-authored-by: Sean Tymon <tymondesigns@users.noreply.github.com>
The Copilot-driven allowlist of every applicator created ~20 untested RemoveArrayItem mutants and dropped mutation score to 79.6%, below the CI gate. Skipping default, enum, and examples is the actual constraint: those hold instance data. Everything else that is an array is walked as a nested schema or a list of them. Co-authored-by: Sean Tymon <tymondesigns@users.noreply.github.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.
Audit of the docs against the current implementation. Where the honest answer would have been a workaround, I fixed the thing that made the workaround necessary instead of writing it down.
Includes #23, merged into this branch, plus
mainmerged in for PHP 8.4, Pest 5, and json-schema 2.Code
Every reference is built through
Reference::. TheX::ref()shortcuts covered nine of the ten component registries and could never cover the tenth: schemas go throughReference::schema(), becauseSchemabelongs tocortexphp/json-schema, which knows nothing about#/components. A family that advertises uniformity while omitting its most-used member is what makesSchema::ref('User')look reasonable when it cannot exist. Before the change there were 25X::ref()call sites against 97Reference::ones, 61 of thoseReference::schema(). All nine shortcuts were one-line delegations, so nothing is lost:Breaking, done now rather than after 1.0. The nine per-class tests went with them, since
ReferenceTestalready asserts all ten registries.responses()accepts a referenced response. It keys each entry fromgetStatusCode(), and a bareReferencehas none, so a referenced response could not go in. This is the only place in the package with that problem —Operation::parameters()andPathItem::parameters()takeParameter|Referencehappily, because they build a list and derive no key. Removing the static freed the name for the fluent form:Byte-identical to
->response(404, Reference::response('NotFound')), asserted in a test rather than eyeballed. A referenced response emits the$refalone, because a Reference Object has nowhere to put content — last-call-wins, matchingresponses()andcontent(), and pinned by a test.Nested schemas leaked the JSON Schema
$schemaURI. JSON Schema 2020-12 core §8.1.1 allows it only at the root of a schema resource, and OAS 3.1 §Schema Object repeats the restriction, so every nested occurrence the builder can produce is invalid output.BuildsArraynow strips it at every depth, skipping maps whose keys are user-chosen so a property literally named$schemasurvives, and leaving raw array schemas alone as the way to declare a dialect deliberately.One for the json-schema side: 2.0 fixed this for
itemsbutcontainsstill emits the URI. Of its ~20 nested serialization call sites those two were the only ones not passingincludeSchemaRef: falseby hand, and the fix reached one of them. A propagating rule rather than an argument repeated per keyword would have caught both.Docs
New page: Validation & Output. The "Built-in Validation" and "Multiple Output Formats" cards both linked to
/openapi/validation-and-output, which was never written. It coversvalidate(), the structurederrors()payload keyed by JSON pointer, what the meta-schema does and does not catch, and the three output formats. It also decodes the anyOf error that reports one rule as three missing-property lines.OpenApi::v310()/v311()OpenApi::create(OpenApiVersion::V3_1_1)$schemaandtitleare stripped"->title()is published,$schemanever appearsallOf(Reference::schema('BaseEntity'))— a TypeErrorallOf(Schema::typeless()->ref(...));Schema::object()->ref()emits a stray"type": "object"beside the$reftags()takesTagobjects, strings, or a mix->style(string)->style()also takes theStyleenumsymfony/yaml;toYaml()throws without itInfo::create()->title(...)->version(...)Info::create('Example API', '1.0.0')Also added:
Operation::callback(),jsonSchemaDialect(), andReference::example(), plus missingusestatements in the Security and Webhooks samples. The quickstart's "Working with References" section is now the single place references are taught.Left alone
discriminatorandxmlhave no builder method. They are OpenAPI fields rather than JSON Schema ones. Documented as spreading a built schema into the array form, instead of hand-writing the whole array.->response(200, Reference::schema('User'))type-checks, since every reference is the same class, andvalidate()passes it because pointers are never resolved. Catching that needs a reference-integrity pass over the finished document — separate work, and the same pass would close the dangling-$refgap the new page has to document as a blind spot.$id-aware exception in the$schemastrip. Unreachable through the builder, and it would survive mutation testing as dead code.Verification
All 13 CI checks pass: PHP 8.4 and 8.5, prefer-lowest and prefer-stable, Linux and Windows, plus format, phpstan, type-coverage, and CodeQL.
Locally on PHP 8.4 with json-schema 2.0: 193 tests, PHPStan level 10, ECS and Rector clean, 100% type coverage, mutation score 83.46%. Every sample on the touched pages was executed against the package, and the README example builds and validates.
The
$schematests assert only that no nested URI survives, rather than asserting upstream's bug as a precondition — which is precisely what broke when json-schema 2.0 fixeditems.