adachsoft/public-api-spec

Public API specification validator for PHP libraries

Maintainers

Package info

gitlab.com/a.adach/public-api-spec

Issues

pkg:composer/adachsoft/public-api-spec

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

v0.1.0 2026-03-28 10:41 UTC

This package is not auto-updated.

Last update: 2026-03-28 12:58:03 UTC


README

public-api.json is a small JSON document that describes the public surface of a PHP library:

  • which Composer package it represents,
  • since which version the public API is considered stable,
  • which contracts from other packages it implements,
  • which classes are the main entry points for users,
  • whether the entire codebase should be treated as public.

The goal is to provide a tool‑friendly, machine‑readable contract that can be used by humans, static analysis, IDEs and automation (e.g. BC checks, dependency mapping, integration tests).

This repository contains:

  • the JSON Schema for the format: schema/v1/public-api.json,
  • a PHP validator implementation (under the namespace AdachSoft\\PublicApiSpec),
  • a CLI validator exposed as vendor/bin/public-api-validate,
  • this documentation explaining how to describe your library using public-api.json.

1. Where to put public-api.json

Convention:

  • File name: public-api.json
  • Location in a library repository: repository root (next to composer.json).

Example project layout:

my-library/
├── composer.json
├── public-api.json   <— this file (spec document)
├── src/
└── tests/

This is a convention, not a hard requirement of the schema, but tools will typically assume that public-api.json lives in the project root.

2. Top‑level structure of public-api.json

The JSON Schema for version 1.0 lives in schema/v1/public-api.json. At the top level, the document is an object with the following fields:

FieldTypeRequiredDescription
$schemastringnoOptional self‑reference to the JSON Schema URL; recommended but not required.
format_versionstringyesVersion of this specification format (currently only "1.0" is allowed).
packagestringyesComposer package name in vendor/package form.
stable_sincestringyesFirst version where the public API is considered stable (SemVer).
contractsarraynoList of external contracts that the library implements (see §4).
entry_pointsarrayyesMain entry points of the library (see §5).
all_code_is_publicbooleannoIndicates if the entire codebase should be treated as public API.
notesstringnoFree‑form notes for humans and AI tools.

The schema forbids additional properties: you cannot add arbitrary top‑level keys; everything must fit the fields above.

2.1. $schema

Optional, but recommended self‑reference to the schema used to validate the document.

"$schema": "https://gitlab.com/a.adach/public-api-spec/-/raw/main/schema/v1/public-api.json"

This helps tools discover the schema automatically and enables better editor/IDE support.

2.2. format_version

"format_version": "1.0"
  • Type: string
  • Constraint: must be exactly "1.0" for the current schema.

This is not your library version. It is the version of the specification format. Future versions of the spec will use different format_version values ("2.0", etc.).

2.3. package

"package": "vendor/package"
  • Type: string
  • Pattern: ^[a-z0-9-]+/[a-z0-9-]+$

This is your Composer package name – the same string that appears in composer.json under the name key.

2.4. stable_since

"stable_since": "1.2.0"
  • Type: string
  • Pattern: ^\d+\.\d+\.\d+(-[a-z0-9]+)?$

This is the first version where your public API is considered stable according to semantic versioning. It is useful for tools that:

  • detect BC breaks across versions,
  • generate upgrade guides,
  • understand which historical versions can be ignored for compatibility checks.

Examples:

  • "1.0.0" – first stable release.
  • "2.1.0-beta" – pre‑release that you still want to treat as the reference for the public surface.

2.5. contracts

"contracts": [
  {
    "package": "adachsoft/ai-image-contract",
    "mappings": [
      {
        "interface": "AdachSoft\\AiImageContract\\GenerateImageInterface",
        "implementation": "Vendor\\ImageLib\\Service\\GenerateImageService"
      }
    ]
  }
]

Each contract object has:

  • package (required):
    • Type: string
    • Meaning: Composer package name of the contract library (interfaces / abstractions).
  • mappings (optional):
    • Type: array of objects with required fields:
      • interface: FQCN of the contract interface the library implements.
      • implementation: FQCN of the concrete class that implements the contract.
    • additionalProperties: false – no extra fields allowed.

Use contracts when your library implements contracts from other packages (e.g. PSR interfaces, domain contracts, plugin APIs). This allows tooling to answer questions like:

“Given contract X, which concrete classes implement it in my dependency tree?”

If your library does not implement any external contracts, you can omit contracts or set it to an empty array.

2.6. entry_points

"entry_points": [
  {
    "type": "service",
    "fqn": "Vendor\\Package\\Service\\MyMainService"
  },
  {
    "type": "factory",
    "fqn": "Vendor\\Package\\Factory\\ClientFactory"
  },
  {
    "type": "dto",
    "fqn": "Vendor\\Package\\Dto\\ImportantResultDto"
  }
]

Each entry point object has:

  • type (required): one of
    • "service"
    • "factory"
    • "dto"
    • "interface"
    • "value_object"
    • "enum"
    • "helper"
    • "facade"
  • fqn (required):
    • Type: string
    • Meaning: Fully qualified class name or static method, e.g.:
      • "Vendor\\Package\\Service\\MyService"
      • "Vendor\\Package\\Factory\\ClientFactory::create"

You should list only the most important entry points – the classes that users are expected to construct, inject or call directly. This helps both humans and tooling to understand:

  • which APIs are intended for public use,
  • where to start reading the code,
  • which symbols should be highlighted in documentation.

2.7. all_code_is_public

"all_code_is_public": true
  • Type: boolean
  • Meaning:
    • true – treat the entire library as public API.
    • false or omitted – only the explicitly listed entry points (and the types they expose in public signatures) are public.

Typical usage:

  • Contract libraries (interfaces‑only) often set this to true.
  • Implementation libraries normally keep it false and use entry_points to mark the public surface.

2.8. notes

"notes": "This library is designed for AI agents. Breaking changes are announced in the changelog and reflected in public-api.json."

Free‑form text for humans and AI. Use it for:

  • high‑level guidance (“This package is low‑level, prefer using X instead”),
  • migration notes (“Deprecated methods will be kept until 2.0.0”),
  • anything that does not fit into the structured fields.

3. Minimal example

A minimal valid public-api.json that only uses required fields:

{
  "$schema": "https://gitlab.com/a.adach/public-api-spec/-/raw/main/schema/v1/public-api.json",
  "format_version": "1.0",
  "package": "vendor/package-name",
  "stable_since": "1.0.0",
  "entry_points": [
    {
      "type": "service",
      "fqn": "Vendor\\Package\\Service\\MainService"
    }
  ]
}

This is already useful for tools that want to:

  • discover the main service of the library,
  • know when the API became stable,
  • verify that the document is structurally valid.

4. Rich example with contracts and multiple entry points

The following example shows a more complete description of a library that implements a contract package and exposes several entry points:

{
  "$schema": "https://gitlab.com/a.adach/public-api-spec/-/raw/main/schema/v1/public-api.json",
  "format_version": "1.0",
  "package": "acme/image-resizer",
  "stable_since": "1.2.0",

  "contracts": [
    {
      "package": "adachsoft/ai-image-contract",
      "mappings": [
        {
          "interface": "AdachSoft\\AiImageContract\\ResizeImageInterface",
          "implementation": "Acme\\ImageResizer\\Service\\ResizeImageService"
        }
      ]
    }
  ],

  "entry_points": [
    {
      "type": "service",
      "fqn": "Acme\\ImageResizer\\Service\\ResizeImageService"
    },
    {
      "type": "dto",
      "fqn": "Acme\\ImageResizer\\Dto\\ResizeOptions"
    },
    {
      "type": "value_object",
      "fqn": "Acme\\ImageResizer\\Value\\Size"
    },
    {
      "type": "facade",
      "fqn": "Acme\\ImageResizer\\ImageResizerFacade"
    }
  ],

  "all_code_is_public": false,

  "notes": "The primary entry point is ImageResizerFacade. Low-level services and DTOs may change between minor versions, unless listed here."
}

This document tells tools and readers:

  • which contract package the library implements and how interfaces map to implementations,
  • which classes represent the public boundary of the library,
  • that only those entry points (and their public signatures) should be considered stable,
  • since which version (1.2.0) this guarantee holds.

5. Step‑by‑step: documenting your library

  1. Create public-api.json in the project root.
    Start with the minimal structure from §3.

  2. Fill in the required metadata:

    • format_version: always "1.0" for this schema.
    • package: copy from composer.json.
    • stable_since: pick the first version where you are ready to promise API stability.
  3. List your main entry points (entry_points).

    • Include the service/facade that most users interact with.
    • Add important DTOs, value objects and enums that appear in public signatures.
    • Avoid listing every internal helper – keep the list focused.
  4. Describe implemented contracts (contracts).

    • For each external contract package you implement, add one object to contracts.
    • For each interface you implement from that package, add a mapping with:
      • interface: contract FQCN,
      • implementation: your concrete FQCN.
  5. Decide on all_code_is_public.

    • For contract libraries and thin wrappers, true may be appropriate.
    • For complex implementation libraries, keep it false and rely on entry_points.
  6. Add notes for extra guidance.

    • Clarify deprecation policy, long‑term support, or special integration rules.
  7. Validate the document.
    Use the JSON Schema (or the PHP validator from this package) to ensure that public-api.json is valid and consistent.

6. Validating public-api.json with PHP

This repository ships a small PHP library under the namespace AdachSoft\\PublicApiSpec that can be used to validate public-api.json files.

CLI validation

If you install this package as a dependency, you also get a small CLI wrapper around the validator:

vendor/bin/public-api-validate [path/to/public-api.json]

Behavior:

  • If you pass a path argument, that file will be validated.
  • If you omit the argument, the tool looks for public-api.json in the current working directory.
  • Output is printed using adachsoft/console-io with colors and icons when supported by the terminal.

Exit codes:

  • 0 – the file is valid according to the schema.
  • 1 – invalid usage or configuration error (malformed JSON, unsupported format_version, missing required structural fields).
  • 2 – validation errors (the document is well-formed JSON but does not satisfy the schema).
  • 3 – unexpected internal error during validation (I/O problems, unexpected exceptions, etc.).

The rest of this section shows how to call the underlying PHP validator directly.

6.1. Installation

If the package is published on Packagist, you can install it as a normal Composer dependency:

composer require adachsoft/public-api-spec

The library uses opis/json-schema under the hood.

6.2. Basic usage

<?php

declare(strict_types=1);

use AdachSoft\PublicApiSpec\SchemaResolver\LocalSchemaResolver;
use AdachSoft\PublicApiSpec\Validator\OpisSchemaValidator;
use AdachSoft\PublicApiSpec\Exception\InvalidJsonException;
use AdachSoft\PublicApiSpec\Exception\UnsupportedSchemaVersionException;

// Base path where the `schema/` directory from this repo is located
$basePath = __DIR__ . '/vendor/adachsoft/public-api-spec';

$schemaResolver = new LocalSchemaResolver($basePath);
$validator = new OpisSchemaValidator($schemaResolver);

$json = file_get_contents(__DIR__ . '/public-api.json');

try {
    $result = $validator->validate($json);
} catch (InvalidJsonException|UnsupportedSchemaVersionException $e) {
    // JSON is malformed or uses an unsupported format_version
    echo 'Configuration error: ' . $e->getMessage() . PHP_EOL;
    exit(1);
}

if (!$result->isValid) {
    foreach ($result->errors as $error) {
        // $error is an instance of ValidationErrorDto
        echo $error->propertyPath . ' : ' . $error->message . PHP_EOL;
    }

    exit(1);
}

echo "public-api.json is valid.\n";

Key types used above (from this library):

  • SchemaVersionEnum – internal enum for known format_version values (currently only V1 = "1.0").
  • LocalSchemaResolver – resolves the JSON Schema from schema/vN/public-api.json on disk.
  • OpisSchemaValidator – validates a public-api.json string against the appropriate schema version.
  • ValidationResultDto – returned from validate(), with:
    • bool $isValid – overall result,
    • ValidationErrorCollection $errors – typed collection of validation errors.
  • ValidationErrorDto – contains:
    • string $propertyPath – JSON Pointer path like /entry_points/0/type, /package,
    • string $message – human‑readable description of the problem.

6.3. Error handling

The validator can throw the following exceptions:

  • InvalidJsonException – the provided string is not a valid JSON document, or the root is not an object, or required structural fields are missing/invalid before schema validation.
  • UnsupportedSchemaVersionException – the format_version field refers to a spec version that this library does not support.
  • SchemaFileNotFoundException (from LocalSchemaResolver) – the JSON Schema file cannot be found/read on disk.

In all other cases, validation errors are returned via ValidationResultDto and its errors collection.

7. Versioning and compatibility

  • The spec format is versioned via format_version (currently "1.0").
  • New spec versions will introduce new JSON Schemas in schema/v{N}/public-api.json and new enum cases in SchemaVersionEnum.
  • The PHP validator is designed so that adding a new spec version does not require changes in consumer code – it is handled internally based on format_version.

When you update your library:

  1. Keep format_version at "1.0" as long as you are compatible with this schema.
  2. Update stable_since only when you change the stability guarantee (e.g. first stable after a beta phase).
  3. Evolve contracts and entry_points as your public API grows, but avoid removing items without a major version bump.

By keeping public-api.json up to date, you give downstream tools and users a clear, machine‑readable picture of your public surface and compatibility promises.