NAME
OpenAPI::Modern - Validate HTTP requests and responses against an OpenAPI document
VERSION
version 0.029
SYNOPSIS
my $openapi = OpenAPI::Modern->new(
openapi_uri => '/api',
openapi_schema => YAML::PP->new(boolean => 'JSON::PP')->load_string(<<'YAML'));
openapi: 3.1.0
info:
title: Test API
version: 1.2.3
paths:
/foo/{foo_id}:
parameters:
- name: foo_id
in: path
required: true
schema:
pattern: ^[a-z]+$
post:
operationId: my_foo_request
parameters:
- name: My-Request-Header
in: header
required: true
schema:
pattern: ^[0-9]+$
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
hello:
type: string
pattern: ^[0-9]+$
responses:
200:
description: success
headers:
My-Response-Header:
required: true
schema:
pattern: ^[0-9]+$
content:
application/json:
schema:
type: object
required: [ status ]
properties:
status:
const: ok
YAML
say 'request:';
my $request = POST '/foo/bar',
'My-Request-Header' => '123', 'Content-Type' => 'application/json', Host => 'example.com',
Content => '{"hello": 123}';
my $results = $openapi->validate_request($request);
say $results;
say ''; # newline
say JSON::MaybeXS->new(convert_blessed => 1, canonical => 1, pretty => 1, indent_length => 2)->encode($results);
say 'response:';
my $response = Mojo::Message::Response->new(code => 200, message => 'OK');
$response->headers->header('Content-Type', 'application/json');
$response->headers->header('My-Response-Header', '123');
$response->body('{"status": "ok"}');
$results = $openapi->validate_response($response, { request => $request });
say $results;
say ''; # newline
say JSON::MaybeXS->new(convert_blessed => 1, canonical => 1, pretty => 1, indent_length => 2)->encode($results);
prints:
request:
at '/request/body/hello': got integer, not string
at '/request/body': not all properties are valid
{
"errors" : [
{
"absoluteKeywordLocation" : "https://example.com/api#/paths/~1foo~1%7Bfoo_id%7D/post/requestBody/content/application~1json/schema/properties/hello/type",
"error" : "got integer, not string",
"instanceLocation" : "/request/body/hello",
"keywordLocation" : "/paths/~1foo~1{foo_id}/post/requestBody/content/application~1json/schema/properties/hello/type"
},
{
"absoluteKeywordLocation" : "https://example.com/api#/paths/~1foo~1%7Bfoo_id%7D/post/requestBody/content/application~1json/schema/properties",
"error" : "not all properties are valid",
"instanceLocation" : "/request/body",
"keywordLocation" : "/paths/~1foo~1{foo_id}/post/requestBody/content/application~1json/schema/properties"
}
],
"valid" : false
}
response:
valid
{
"valid" : true
}
DESCRIPTION
This module provides various tools for working with an OpenAPI Specification v3.1 document within your application. The JSON Schema evaluator is fully specification-compliant; the OpenAPI evaluator aims to be but some features are not yet available. My belief is that missing features are better than features that seem to work but actually cut corners for simplicity.
CONSTRUCTOR ARGUMENTS
If construction of the object is not successful, for example the document has a syntax error, the call to new()
will throw an exception. Be careful about examining this exception, for it might be a JSON::Schema::Modern::Result object, which has a boolean overload of false when it contains errors! But you never do if ($@) { ... }
, right?
openapi_uri
The URI that identifies the OpenAPI document. Ignored if "openapi_document" is provided.
If it is not absolute, it is resolved at runtime against the request's Host
header (when available) and the https scheme is assumed.
openapi_schema
The data structure describing the OpenAPI v3.1 document (as specified at https://spec.openapis.org/oas/v3.1.0). Ignored if "openapi_document" is provided.
openapi_document
The JSON::Schema::Modern::Document::OpenAPI document that holds the OpenAPI information to be used for validation. If it is not provided to the constructor, then "openapi_uri" and "openapi_schema" MUST be provided, and "evaluator" will also be used if provided.
evaluator
The JSON::Schema::Modern object to use for all URI resolution and JSON Schema evaluation. Ignored if "openapi_document" is provided. Optional.
ACCESSORS/METHODS
openapi_uri
The URI that identifies the OpenAPI document.
openapi_schema
The data structure describing the OpenAPI document. See "https://spec.openapis.org/oas/v3.1.0" in the specification.
openapi_document
The JSON::Schema::Modern::Document::OpenAPI document that holds the OpenAPI information to be used for validation.
evaluator
The JSON::Schema::Modern object to use for all URI resolution and JSON Schema evaluation.
validate_request
$result = $openapi->validate_request(
$request,
# optional second argument can contain any combination of:
my $options = {
path_template => '/foo/{arg1}/bar/{arg2}',
operation_id => 'my_operation_id',
path_captures => { arg1 => 1, arg2 => 2 },
method => 'get',
},
);
Validates an HTTP::Request or Mojo::Message::Request object against the corresponding OpenAPI v3.1 document, returning a JSON::Schema::Modern::Result object.
The second argument is an optional hashref that contains extra information about the request, corresponding to the values expected by "find_path" below. It is populated with some information about the request: pass it to a later "validate_response" to improve performance.
validate_response
$result = $openapi->validate_response(
$response,
{
path_template => '/foo/{arg1}/bar/{arg2}',
request => $request,
},
);
Validates an HTTP::Response or Mojo::Message::Response object against the corresponding OpenAPI v3.1 document, returning a JSON::Schema::Modern::Result object.
The second argument is an optional hashref that contains extra information about the request corresponding to the response, as in "find_path".
request
is also accepted as a key in the hashref, representing the original request object that corresponds to this response (as not all HTTP libraries link to the request in the response object).
find_path
$result = $self->find_path($request, $options);
Uses information in the request to determine the relevant parts of the OpenAPI specification. $request
should be provided if available, but data in the second argument can be used instead (which is populated by earlier "validate_request" or "find_path" calls to the same request).
The second argument is a hashref that contains extra information about the request. Possible values include:
path_template
: a string representing the request URI, with placeholders in braces (e.g./pets/{petId}
); see https://spec.openapis.org/oas/v3.1.0#paths-object.operation_id
: a string corresponding to the operationId at a particular path-template and HTTP location under/paths
path_captures
: a hashref mapping placeholders in the path to their actual values in the request URImethod
: the HTTP method used by the request (used case-insensitively)
All of these values are optional (unless $request
is omitted), and will be derived from the request URI as needed (albeit less efficiently than if they were provided). All passed-in values MUST be consistent with each other and the request URI.
When successful, the options hash will be populated with keys path_template
, path_captures
, method
, and operation_id
, and the return value is true. When not successful, the options hash will be populated with key errors
, an arrayref containing a JSON::Schema::Modern::Error object, and the return value is false.
Note that the /servers
section of the OpenAPI document is not used for path matching at this time, for either scheme and host matching nor path prefixes.
canonical_uri
An accessor that delegates to "canonical_uri" in JSON::Schema::Modern::Document.
schema
An accessor that delegates to "schema" in JSON::Schema::Modern::Document.
get_media_type
An accessor that delegates to "get_media_type" in JSON::Schema::Modern.
add_media_type
A setter that delegates to "add_media_type" in JSON::Schema::Modern.
ON THE USE OF JSON SCHEMAS
Embedded JSON Schemas, through the use of the schema
keyword, are fully draft2020-12-compliant, as per the spec, and implemented with JSON::Schema::Modern. Unless overridden with the use of the jsonSchemaDialect keyword, their metaschema is https://spec.openapis.org/oas/3.1/dialect/base, which allows for use of the OpenAPI-specific keywords (discriminator
, xml
, externalDocs
, and example
), as defined in "https://spec.openapis.org/oas/v3.1.0#schema-object" in the specification. Format validation is turned on, and the use of content* keywords is off (see "validate_content_schemas" in JSON::Schema::Modern).
References (with the $ref
) keyword may reference any position within the entire OpenAPI document; as such, json pointers are relative to the root of the document, not the root of the subschema itself. References to other documents are also permitted, provided those documents have been loaded into the evaluator in advance (see "add_schema" in JSON::Schema::Modern).
Values are generally treated as strings for the purpose of schema evaluation. However, if the top level of the schema contains "type": "number"
or "type": "integer"
, then the value will be (attempted to be) coerced into a number before being passed to the JSON Schema evaluator. Type coercion will not be done if the type
keyword is omitted. This lets you use numeric keywords such as maximum
and multipleOf
in your schemas. It also resolves inconsistencies that can arise when request and response objects are created manually in a test environment (as opposed to being parsed from incoming network traffic) and can therefore inadvertently contain perlish numbers rather than strings.
LIMITATIONS
Only certain permutations of OpenAPI documents are supported at this time:
for all parameters types, only
explode: true
is supportedfor path parameters, only
style: simple
is supportedfor query parameters, only
style: form
is supportedcookie parameters are not checked at all yet
for query and header parameters, only the first value of each name is considered
SEE ALSO
SUPPORT
Bugs may be submitted through https://github.com/karenetheridge/OpenAPI-Modern/issues.
I am also usually active on irc, as 'ether' at irc.perl.org
and irc.libera.chat
.
You can also find me on the JSON Schema Slack server and OpenAPI Slack server, which are also great resources for finding help.
AUTHOR
Karen Etheridge <ether@cpan.org>
COPYRIGHT AND LICENCE
This software is copyright (c) 2021 by Karen Etheridge.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.
Some schema files have their own licence, in share/oas/LICENSE.