OpenAPI Documentation
The package documents its endpoints through Scramble. It builds the document from the declarations of your resources: fields, relations, scopes, instructions, actions, metrics, limits and gates.
Installation
composer require dedoc/scramble:^0.13.48
The package registers its documentation in the boot() method of its service provider: config/scramble.php needs no change. Visit /docs/api to read the document, or export it:
php artisan scramble:export
An API that your application registers with Scramble::registerApi() after the package boots gets the package documentation too. For an API registered before, add the transformers:
Scramble::registerApi('v2', ['api_path' => 'api/v2'])
->withOperationTransformers(\Lomkit\Rest\Scramble\RestOperations::class)
->withDocumentTransformers(\Lomkit\Rest\Scramble\RestDocument::class);
What the document holds
The document is the same for every reader. The package reads the declarations of each resource as a guest: a field, an action or a metric declared for some users only is documented as a guest sees it.
| Operation | Summary | Body | Response |
|---|---|---|---|
GET /{resource} | Get {Resource} details | the with parameter | {Resource}Details |
POST /{resource}/search | Search {Resource} | {Resource}Search | {Resource}Paginated |
POST /{resource}/mutate | Mutate {Resource} | {Resource}Mutation entries | {Resource}MutateResult |
POST /{resource}/actions/{uriKey} | the name of the action | search or resources, as the target requires, and fields | RestActionResponse |
POST /{resource}/metrics/{uriKey} | the name of the metric | search, range, timezone | data with the result of the metric |
DELETE /{resource}, POST /{resource}/restore, DELETE /{resource}/force | Delete {Resource}, Restore {Resource}, Force delete {Resource} | resources | {Resource}Rows |
GET /{resource}/actions/{action}/batches/{batch} | Batch progress of {Resource} actions | RestBatchProgress |
{Resource} is the class name of the resource without its Resource suffix, also used as the tag.
- Records. Each resource has a record, named like its tag (
Post): its selectable fields with their type, the gates of a row, and each relation under its key. The morph type column of aMorphTorelation lists its morph types as an enum. AMorphTorelation isoneOfthe record of each target, with adiscriminatoron@type. - Search. Filters list one condition per filterable field, with the operators of the field and the value that each one takes, plus the fields of each related resource one level deep and the pivot fields. Includes, aggregates, scopes and instructions have one schema per relation or name. Lists hold at most their
rest.limits.sizesentries.sortslists the key of each aggregate that sorts: an aggregate over a relation to many, with no field or on a field that is not a key, see Sorts and selects. - Mutations.
attributeslists the fields that the operation writes, with their rules in the description. AnEnumfield lists the cases that writes accept. Each relation lists the operations that it accepts. - Actions. The body follows the target of the action:
searchis required on theSearchtarget,resourceson theResourcestarget. A key that the target refuses is documented asfalse.fieldslists typed{name, value}entries. The result isoneOfthe types ofActionResponse, with adiscriminatorontype. A batched action answersRestBatchableActionResponse, withbatch. See Actions and writes. - Metrics. The built-in types share
RestValueResult,RestTrendResult,RestPartitionResult(withpreviouson each part and onotherwhen the partition compares two periods),RestTableResult(withpercentileon a percentile column) andRestProgressResult.partsandsplitshold at mostrest.limits.sizes.groupsentries; a partition or a trend with its ownlimit()has its schema, with that maximum, in its operation. A metric of your own documents itstypealone until it declaresschema(), see Metrics. - Details.
{Resource}Detailslists the names of the resource as enums. The base keys are required, the sections and parts optional.metaaccepts any key when the resource overridesdetailsMeta().
Actions and writes
{
"/shows/actions/cancel-shows": {
"post": {
"description": "The action is destructive: a hint for clients, the endpoint runs it the same way.\n\nThe text that a client shows before it runs the action: Cancel the selected shows?\n\nThe action can answer 428: send the same body again with the token of the answer in `confirmation`.",
"summary": "Cancel Shows",
"x-lomkit-destructive": true,
"x-lomkit-confirm": "Cancel the selected shows?"
}
}
}
- The operation of a
destructive()action carries"x-lomkit-destructive": true, and the operation of an action withconfirm()carries its text underx-lomkit-confirm. Its description says both. - An action that declares
confirmation()documentsconfirmationin its body and the428response. So do themutate,destroy,restoreandforceDestroyoperations of a resource that declares the matching hook, see Confirmation. - A key that an operation refuses is written
"<key>": falsein itsproperties, such as"search": falseon theResourcestarget,"range": falseon a metric without ranges, or"text": falseon a search of a resource without a text search: the endpoint answers422when the request sends it.
Errors
components.responses holds the shared error responses, listed on the operations of the package:
| Response | Status | Listed on |
|---|---|---|
RestUnauthenticated | 401 | the operations whose route runs auth |
RestForbidden | 403 | every operation; with denials when the application registers DenialsRenderer |
RestNotFound | 404 | every operation |
RestConflict | 409 | the mutate operations that write a versioned resource, at the root or under a relation |
RestValidationFailed | 422 | every operation but batch progress |
RestConfirmationRequired | 428 | the actions that declare confirmation(), and the writes of a resource that declares the matching confirmation hook |
An operation whose route does not run auth has "security": []. The package declares no authentication scheme: declare yours in a document transformer.
use Dedoc\Scramble\Scramble;
use Dedoc\Scramble\Support\Generator\OpenApi;
use Dedoc\Scramble\Support\Generator\SecurityScheme;
Scramble::configure()->withDocumentTransformers(function (OpenApi $openApi): void {
$openApi->secure(SecurityScheme::http('bearer'));
});
Types and examples
Each field documents its type:
| Field | Schema |
|---|---|
ID | an integer or a string, from the key type |
Text | string |
Number, Integer | number, integer |
Boolean | boolean |
Date, DateTime | string with format: date or format: date-time |
Enum | an enum of its backing values |
Json | object |
nullable() adds null, and meta() goes under x-lomkit-meta. A field type of your own overrides toSchema(), and toWriteSchema() when writes accept fewer values, as an Enum with offered() does.
- The responses follow these types: a
Numberfield answers a number, even from adecimalcast, and an emptyJsonfield answers{}. See Fields. - The rules of a field go in its description, such as
Rules: string, max:80, required, on the fields ofmutate, of actions, of instructions and of scopes. - An action field, an instruction field, a scope parameter or a metric
rangewith a default gives it underdefault, such as"default": trueor"default": 30.
examples() lists example values on a field. The request examples of each operation use the first value:
Text::make('title')->examples('Weekly report', 'Daily report'),
Without examples(), an example takes a value of the field's type that passes its simple rules: the length of min, max, size and between on a string (aaaaaaaa for min:8), their bounds and those of gt, gte, lt and lte on a number, the first value of in, and a value for email, url, uuid, ulid, ip, date, numeric, digits, accepted and declined.
- A request example never sends a default: a field with a default is left out of an action example, and a metric example is
{}. - The mutate example adds each relation declared
requiredOnCreation(), with anattachof a key.
examples() on the fields whose other rules the generated value does not pass, so that the request examples pass your rules.date cast of Laravel renders a date and a time. Cast a Date attribute with date:Y-m-d so that the response matches the documented format: date.Keeping the document under test
The package tests its own document in two ways, and you can do the same in your application.
- Contract tests. The package sends real requests to each documented operation and validates each response against the schema of the document: a response that does not match fails the test.
- A snapshot. The package generates the document of a fixture application and compares it with a committed JSON file. A change of a declaration shows as a diff of that file.
A snapshot test in your application can compare the generated document with a committed file:
use Dedoc\Scramble\Generator;
public function test_the_api_document_matches_the_snapshot(): void
{
$document = app(Generator::class)();
unset($document['info'], $document['servers']);
$json = json_encode($document, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR)."\n";
$this->assertSame(file_get_contents(base_path('tests/api.json')), $json);
}
Write the file once from the same code, and commit it. When a change to a resource changes the API, the test fails and the diff of tests/api.json shows what clients see.