NAME
OpenAPI::Linter - Validate and lint OpenAPI 3.x specification files
VERSION
Version 0.19
SYNOPSIS
use OpenAPI::Linter;
# Load from a YAML or JSON file
my $linter = OpenAPI::Linter->new(spec => 'openapi.yaml');
# Or pass a pre-parsed spec as a hashref
my $linter = OpenAPI::Linter->new(spec => \%spec_data);
# Run all checks (schema + semantic)
my @issues = $linter->find_issues;
# Run structural/schema checks only
my @errors = $linter->validate_schema;
# Filter by severity level
my @errors_only = $linter->find_issues(level => 'ERROR');
# Filter by message pattern
my @security = $linter->find_issues(pattern => qr/security/i);
# Inspect results
for my $issue (@issues) {
my $loc = $issue->{location};
# Location stringifies to the dot-separated spec path
printf "[%s] %s (at %s)\n",
$issue->{level},
$issue->{message},
$loc;
# For file-based specs, precise line/column is also available
if ($loc->line) {
printf " => %s\n", $loc->position; # e.g. openapi.yaml:42:5
}
}
DESCRIPTION
OpenAPI::Linter validates OpenAPI 3.0.x and 3.1.x specification files against both the official OpenAPI JSON Schema and a curated set of semantic and documentation rules.
Checks are organised into two phases:
- 1. Structural validation - the spec is validated against the official OpenAPI schema published at https://spec.openapis.org. If structural errors are found, further checks are skipped.
- 2. Semantic checks - a suite of opinionated rules is applied covering documentation completeness, naming conventions, security, HTTP method semantics, unused components, and more.
Results are returned as a list of issue hashrefs; see "ISSUE STRUCTURE" for the full field reference.
CONSTRUCTOR
new
my $linter = OpenAPI::Linter->new(%args);
Creates and returns a new linter instance.
Arguments
spec(required)-
Either a filesystem path to a YAML or JSON OpenAPI file, or a hashref containing a pre-parsed specification. A path that does not exist causes a fatal error via
croak. schema_url(optional)-
Override the OpenAPI meta-schema URL used for structural validation. By default the correct URL is chosen automatically based on the
openapiversion field in the spec:3.0.x -
https://spec.openapis.org/oas/3.0/schema/2021-09-283.1.x -
https://spec.openapis.org/oas/3.1/schema/2022-10-07
Overriding this is primarily useful in tests or air-gapped environments.
Exceptions
new will croak if:
The
specargument is not provided.specis a string that does not refer to an existing file.
Parse errors (malformed YAML/JSON) and schema-download failures are captured and reported as ERROR-level issues on the first call to find_issues or validate_schema, rather than causing a fatal exception.
METHODS
validate_schema
my @issues = $linter->validate_schema;
my $issues = $linter->validate_schema; # scalar context -> arrayref
Validates the spec against the official OpenAPI JSON Schema and checks the openapi version field. Returns a (possibly empty) list of issue hashrefs.
In scalar context returns an arrayref.
If a parse error was encountered during construction it is reported here and no further schema validation is attempted.
find_issues
my @issues = $linter->find_issues(%args);
my $issues = $linter->find_issues(%args); # scalar context -> arrayref
Runs the full linting pipeline: structural validation first, then all semantic checks. If structural errors are found the semantic checks are skipped and only those errors are returned (after any filtering).
In scalar context returns an arrayref.
Optional arguments
level-
Return only issues whose
levelfield exactly matches the given string. Valid values areERROR,WARN, andINFO. pattern-
A compiled or uncompiled regular expression. Only issues whose
messagefield matches the pattern are returned.
Both filters may be combined; an issue must satisfy both to be included.
ISSUE STRUCTURE
Each issue is a plain hashref with the following keys:
level-
Severity of the issue. One of:
ERROR- the spec is invalid or will cause interoperability failures.WARN- a best-practice violation; the spec may still be functional.INFO- informational; e.g. duplicate descriptions.
message-
A human-readable description of the problem.
path-
A dot-separated or
#/-prefixed location within the spec indicating where the issue was found. May be absent for top-level parse errors. type-
A machine-readable category string. Common values:
documentation Missing description, summary, example, or licence. semantic HTTP method misuse, missing operationId, etc. schema Violation of the OpenAPI JSON Schema. syntax YAML/JSON parse failure. security Missing or incomplete security definitions. naming Path segment or property naming convention violations. validation Server variable, version format, or other validation. maintainability Unused components, duplicate descriptions, etc. rule(optional)-
A short rule identifier, present on selected checks (e.g.
info-description,security-defined,no-undefined-server-variable).
CHECKS PERFORMED
Structural
openapiversion field format must beX.Y.Z.openapiversion must be3.0.xor3.1.x.Full validation against the official OpenAPI JSON Schema.
Info section
info.descriptionis present.info.licenseis present.
Operations (paths)
Every operation has a
description,summary, andoperationId.GETandDELETEoperations do not include arequestBody.requestBodyhas adescription.Non-path parameters have a
description.Path parameters declare
required: true.Every operation has at least one 2xx response.
Every response object has a
description.Response schemas that include content have a
typeor$ref.POSToperations should return201 Created.204 No Contentresponses must not include a body.GEToperations should not return201.
Security
Checked only when the spec defines components.securitySchemes.
A root-level
securityfield is present.Each operation either has its own
securityfield or inherits one from the root.
Servers
Every
{variable}placeholder in a server URL is declared inservers[n].variables.A server URL that consists entirely of a single bare placeholder (e.g.
"{siteUrl}"), with no variables block, is downgraded toWARNrather thanERROR. This pattern is common in published specs to indicate that the consumer must supply the base URL. It is still reported so the spec author is aware, but it is not treated as a hard error. Any other URL that mixes literal text with an undefined variable (e.g."https://{host}/api") remains anERROR.
Components
Every schema in
components.schemashas adescription,type, and (for object schemas) anexampleorexamples.Every schema property has a
descriptionand uses camelCase naming.Every entry in
components.parametershas adescription.Every entry in
components.responseshas adescription.Every entry in
components.requestBodieshas adescription.Schemas, responses, and request bodies defined in
componentsbut never referenced via$refare reported as unused.
Path naming
Static path segments must be kebab-case (
[a-z][a-z0-9]*(-[a-z][a-z0-9]*)*).
Duplicate descriptions
Identical descriptions across multiple component schemas are flagged at
INFOlevel.
ENVIRONMENT
DEBUG-
Set to a true value to emit verbose diagnostic output to
STDERRtracing the linter's internal execution.DEBUG=1 perl myscript.pl MOJO_CONNECT_TIMEOUT/MOJO_INACTIVITY_TIMEOUT-
Temporarily set to
10seconds during schema download to prevent indefinite hangs on network failures.
DEPENDENCIES
- JSON::Validator
- JSON
- YAML::XS
- File::Slurp
- URI (optional - used for stricter
uri-referenceformat validation when available)
DIAGNOSTICS
A 'spec' file path or data is required-
You called
newwithout supplying thespecargument. Spec path '%s' does not exist-
The string passed as
specis not a path to an existing file. Parsing error: ...-
The file could not be parsed as YAML or JSON. The underlying error from YAML::XS is included in the message.
BUGS AND LIMITATIONS
$refresolution is shallow: only direct$refusage in operationrequestBody,responses,parameters, and responsecontentschemas is tracked for the unused-components check. Nested or recursive$refusage is not followed.The unused-components check for
components.parametersonly reports a parameter namedfragment(for Redocly toolchain compatibility). All other unused parameters are silently ignored.Examples, headers, links, and callbacks inside
componentsare not checked.The schema is downloaded from
spec.openapis.orgon first use and cached by JSON::Validator. Subsequent instantiations reuse the cache. In environments without internet access, pass a localschema_url.
SEE ALSO
JSON::Validator - the underlying schema-validation engine.
Mojolicious::Plugin::OpenAPI - runtime OpenAPI validation for Mojolicious applications.
OpenAPI::Modern - another OpenAPI validation toolkit.
The OpenAPI Specification - https://spec.openapis.org/oas/latest.html
AUTHOR
Mohammad Sajid Anwar, <mohammad.anwar at yahoo.com>
REPOSITORY
https://github.com/manwar/OpenAPI-Linter
BUGS
Please report any bugs or feature requests through the web interface at https://github.com/manwar/OpenAPI-Linter/issues. I will be notified and then you'll automatically be notified of progress on your bug as I make changes.
SUPPORT
You can find documentation for this module with the perldoc command.
perldoc OpenAPI::Linter
You can also look for information at:
BUG Report
CPAN Ratings
Search MetaCPAN
LICENSE AND COPYRIGHT
Copyright (C) 2026 Mohammad Sajid Anwar.
This program is free software; you can redistribute it and / or modify it under the terms of the the Artistic License (2.0). You may obtain a copy of the full license at: http://www.perlfoundation.org/artistic_license_2_0 Any use, modification, and distribution of the Standard or Modified Versions is governed by this Artistic License.By using, modifying or distributing the Package, you accept this license. Do not use, modify, or distribute the Package, if you do not accept this license. If your Modified Version has been derived from a Modified Version made by someone other than you,you are nevertheless required to ensure that your Modified Version complies with the requirements of this license. This license does not grant you the right to use any trademark, service mark, tradename, or logo of the Copyright Holder. This license includes the non-exclusive, worldwide, free-of-charge patent license to make, have made, use, offer to sell, sell, import and otherwise transfer the Package with respect to any patent claims licensable by the Copyright Holder that are necessarily infringed by the Package. If you institute patent litigation (including a cross-claim or counterclaim) against any party alleging that the Package constitutes direct or contributory patent infringement,then this Artistic License to you shall terminate on the date that such litigation is filed. Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.