docuccino / core
OpenAPI 3.2, 3.1 and 3.0 emitters, semantic API diffing and breaking-change detection: the framework-agnostic engine behind Docuccino.
Requires
- php: ^8.3
- composer-runtime-api: ^2
- docuccino/attributes: v0.23.4
- nikic/php-parser: ^5
- opis/json-schema: ^2.4
- phpstan/phpdoc-parser: ^2
- psr/container: ^1.1 || ^2.0
- symfony/yaml: ^7.4.18 || ^8.1.6
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
- dev-main
- v0.23.4
- v0.23.3
- v0.23.2
- v0.23.1
- v0.23.0
- v0.22.1
- v0.22.0
- v0.21.2
- v0.21.1
- v0.21.0
- v0.20.4
- v0.20.3
- v0.20.2
- v0.20.1
- v0.20.0
- v0.19.1
- v0.19.0
- v0.18.1
- v0.18.0
- v0.17.0
- v0.16.0
- v0.15.0
- v0.14.1
- v0.14.0
- v0.13.1
- v0.13.0
- v0.12.0
- v0.11.0
- v0.10.5
- v0.10.4
- v0.10.3
- v0.10.2
- v0.10.1
- v0.10.0
- v0.9.1
- v0.9.0
- v0.8.5
- v0.8.4
- v0.8.3
- v0.8.2
- v0.8.1
- v0.8.0
- v0.7.0
- v0.6.1
- v0.6.0
- v0.5.1
- v0.5.0
- v0.4.0
- v0.3.0
- v0.2.1
- v0.2.0
- v0.1.2
- v0.1.1
- v0.1.0
This package is auto-updated.
Last update: 2026-11-01 18:08:05 UTC
README
The framework-agnostic engine behind Docuccino — the document model, canonicalizer, identities, OpenAPI emitters and semantic diff.
Core compiles an application into an OpenAPI 3.2-shaped JSON document that is deterministic and
carries, under one reserved x-docuccino member, a stable identity and provenance for every
operation, schema and parameter. From it, core emits OpenAPI 3.2 and 3.1, validates against the
published JSON Schema, and answers what changed between two documents rather than only what the
endpoints are.
It has no framework dependency and no static-analysis dependency. Framework adapters consume it —
if you are documenting a Laravel application, you want docuccino/laravel,
which pulls this in for you.
Install
composer require docuccino/core
Usage
Emit a canonical OpenAPI 3.2 document from a built document:
use Docuccino\Core\Emit\EmitOptions; use Docuccino\Core\Emit\OpenApi32Emitter; $json = (new OpenApi32Emitter())->emit($uirDocument, new EmitOptions());
Compare two documents over their identities, and classify what moved:
use Docuccino\Core\Diff\DocumentDiffer; $changes = (new DocumentDiffer())->diff($oldDocument, $newDocument); foreach ($changes->changes as $change) { $change->path; // 'GET /pets/{id} responses 200 application/json schema.properties.name' $change->subject?->toArray(); // ['kind' => 'operation', 'method' => 'GET', 'path' => '/pets/{id}'] }
A change's subject is what it is about — an operation, a component, a page — as members, so changes
group without taking path apart, where a dot in a component name reads like a step into it.
Read a document someone else wrote — a full artifact, an export, or any OpenAPI 3.x description, as JSON or YAML — and ask what it is, where it fails its specification, and what it holds:
use Docuccino\Core\Reader\DocumentReader; $document = (new DocumentReader())->read($bytes, 'application/yaml'); $document->inspect()->capability; // Capability::Full, ::Identities or ::Plain $document->inspect()->info->version; // what `info` says: '1.4.0', or null where it states none $document->validate()->isValid(); // OpenAPI meta-schema, plus the extension schema for a full document $document->nodes(); // one DocumentNode per operation, parameter, response and schema $document->contentHash(); $document->json(); // the document as read, as JSON: what every node pointer points into $document->position('/paths'); // SourcePosition: where it is written, line and column, or null
Every finding validate() returns carries its position too, so a CI annotation can point at the line
rather than the file, in JSON and YAML alike. Positions are worked out from the text only when asked for.
read() throws UnreadableDocumentException, with a reason you can show whoever uploaded the bytes
and a ReadFailure to branch on, when they are not a document it can read — including one that only
grows past its own size once walked, through YAML aliases or paths sharing a path item. Where the fault
is in one place, such as where JSON stops parsing, it says where. The differ
refuses the same way: IncomparableDocumentsException::$failure is DiffFailure::ReferenceExpansion
for two documents whose comparison would read their shared nodes far more often than any document
needs.
An Arazzo description, 1.0 or 1.1, reads the same way, under the same bounds:
use Docuccino\Core\Reader\ArazzoReader; $arazzo = (new ArazzoReader())->read($bytes, 'application/yaml'); $arazzo->version(); // ArazzoVersion::V1_1, or null where no schema ships for it $arazzo->validate()->findings; // against the Arazzo schema for that version, each with its position $arazzo->description()->workflows; // list<Workflow>, the model declared workflows use too $arazzo->description()->workflows[0]->outcomes(0); // what the first step does next, on success and failure $arazzo->positions(['/workflows/0/steps/1']); // where each is written
Part of Docuccino
| Package | Role |
|---|---|
docuccino/laravel |
The Laravel adapter: provider, config, commands, viewer, integrations. Start here. |
docuccino/core ← you are here |
Framework-agnostic document model, canonicalizer, identities, emitters, diff. |
docuccino/inference-phpstan |
PHPStan + Larastan type inference. Install as a dev dependency. |
docuccino/attributes |
Dependency-free PHP attribute classes. |
Documentation
Full documentation is at docs.docuccino.app. See especially the the Docuccino extension, schema hosting and writing an extension. The versioned JSON Schema is served at https://spec.docuccino.app/uir/2.0/schema.json, with the extension half at https://spec.docuccino.app/uir/2.0/extension.schema.json.
Issues and contributing
This repository is a read-only subtree split of docuccino/docuccino. Open issues and pull requests on the monorepo — commits pushed here are overwritten. See CONTRIBUTING.md.
License
MIT. See LICENSE.
This package redistributes the OpenAPI Initiative's specification JSON Schemas under the Apache License 2.0, unmodified, and validates every document it emits against them. See NOTICE.