Laravel route parity
gesso:routes compares Laravel's registered routes with the operations in one or more OpenAPI specs. It catches drift before runtime coverage can: a documented operation may have no application route, or an application route may never have been documented.
Route parity complements contract coverage. Parity checks that a route and an operation exist; runtime coverage still proves that tests exercised the operation and validated its responses.
Configuration
Publish the Laravel config if you have not already:
php artisan vendor:publish --tag=gessoConfigure the spec directory, default spec, and the same prefixes used by the PHPUnit extension:
return [
'default_spec' => 'front',
'spec_base_path' => base_path('openapi/bundled'),
'strip_prefixes' => ['/api'],
'route_parity' => [
'external_operation_ids' => ['forms.*'],
'external_openapi_paths' => ['/v2/proxy/*'],
],
];spec_base_path is the directory searched for front.json, front.yaml, or front.yml. Keep strip_prefixes aligned with the PHPUnit extension. The command applies the runtime matcher's first-match prefix and trailing-slash rules, so the static result matches request/response validation.
Usage
Use the configured default_spec:
php artisan gesso:routesSelect multiple specs and narrow the Laravel route collection:
php artisan gesso:routes \
--spec=front \
--spec=admin \
--prefix=api/v2 \
--middleware=api \
--domain=api.example.com \
--exclude-route='internal.*' \
--exclude-operation='forms.*' \
--exclude-openapi-path='/v2/proxy/*'Filters are combined with AND semantics:
--prefixmatches a complete Laravel URI prefix segment.- Repeat
--middlewareto require every listed middleware name. - Repeat
--domainto allow any listed exact route domain. - Repeat
--exclude-routeto exclude named routes;*wildcards are supported. Unnamed routes are not excluded by this option.
Documented-side exclusions classify operations implemented by another service in a gateway topology. Repeat --exclude-operation to match operationId, or --exclude-openapi-path to match the OpenAPI path template. Both support * wildcards and are merged with route_parity.external_operation_ids and route_parity.external_openapi_paths from the Laravel config. An exclusion is applied only when the operation has no matching Laravel route: implemented operations remain in matched. External operations are reported separately instead of being silently removed.
Laravel parameter names do not need to equal OpenAPI parameter names: /pets/{pet} matches /pets/{petId}. A trailing optional Laravel parameter is compared in both forms, so /users/{user?} can implement both /users and /users/{userId}. Laravel's implicit HEAD on a GET route is ignored when the spec omits head, but it implements and matches an explicitly documented OpenAPI head operation on the same path.
Fallback routes are reported as ambiguous because they cannot prove that one specific OpenAPI path is implemented. A custom HTTP method is supported when the selected OpenAPI 3.2 spec declares the same, case-sensitive key under additionalOperations; otherwise it is reported as unsupported.
CI exit codes
By default, discovered differences are reported but the command exits 0. Enable either gate independently:
php artisan gesso:routes --fail-on-undocumented
php artisan gesso:routes --fail-on-unimplemented--fail-on-undocumentedexits1for registered-but-undocumented routes or unsupported route methods.--fail-on-unimplementedexits1for documented-but-not-registered operations, excluding those classified underexternal_operations.- Invalid command options exit
2; load/configuration failures exit1.
JSON output
Use stable machine-readable output in CI:
php artisan gesso:routes --format=jsonFor large applications, capture the formatted JSON as a CI artifact instead of relying only on the job log:
- name: Compare Laravel routes with OpenAPI
run: php artisan gesso:routes --format=json > route-parity.json
- name: Upload route parity report
uses: actions/upload-artifact@v4
with:
name: openapi-route-parity
path: route-parity.jsonThe top-level schema_version is currently 2. Version 2 adds the external_operations result and summary count. The payload contains the selected specs, a summary, and these result arrays:
matcheddocumented_but_not_registeredexternal_operationsregistered_but_undocumentedambiguousunsupported
Paths and route names are emitted, but absolute filesystem paths and spec contents are not.