NAME
Open::API - OpenAPI 3.1 server and client
VERSION
Version 0.03
SYNOPSIS
use Open::API;
# a spec: hashref, JSON text, YAML text, or a filename
my $api = Open::API->new(spec => 'openapi.json');
# the compiled spec drives a PSGI app (see Open::API::Plack) ...
my $app = Open::API::Plack->new(
api => $api,
handlers => {
listPets => 'MyApp::Pets::list',
getPet => sub { ... },
},
)->to_app;
# ... and an HTTP client (see Open::API::Client)
my $client = Open::API::Client->new(
api => $api,
base_url => 'http://127.0.0.1:5000'
);
my $res = $client->getPet(petId => 42)->get;
DESCRIPTION
Open::API loads an OpenAPI 3.1 document once and compiles every parameter, header, cookie and body schema through JSON::Schema::Fast at startup. Each request is then routed and validated on a C hot path.
The compiled object is the shared core of two consumers: Open::API::Plack serves it as a PSGI app (routing, validation, security, CSRF, CORS - all in C before a handler runs), and Open::API::Client is a spec-driven HTTP client on Fetch's C ABI, so one document defines both sides of the wire. The "match" and "validate_request" methods below expose the router and validator directly for any other framework adapter.
OpenAPI 3.1 only: 3.1 schemas are native JSON Schema 2020-12, which is what JSON::Schema::Fast validates. A document with any other openapi version croaks at load.
CONSTRUCTOR
new
my $api = Open::API->new(spec => $spec);
spec is required and may be:
a hashref (an already-decoded document),
a string of JSON text,
a string of YAML text (decoded with YAML::XS, loaded lazily - only YAML specs need it), or
a filename (
.json,.yamlor.yml; anything else is sniffed by content).
Compilation walks paths once: every operation needs a unique operationId (it is the dispatch key), path templates are pre-split, path-item and operation parameters are merged (operation wins), and every schema is compiled through the JSF ABI - parameters with coercion enabled (string sources satisfy typed schemas, exactly what OpenAPI parameters need) and default-filling on. $refs to #/components/schemas/... are resolved at compile time. A malformed document, a missing or duplicate operationId, or an unresolvable reference croaks here, at startup.
METHODS
spec
The decoded OpenAPI document.
operations
my $ops = $api->operations; # [ { operationId, method, path }, ... ]
operation
my $info = $api->operation('getPet');
A description of one operation: params by location (name + required), body (required flag + content types) and responses (statuses with compiled schemas). undef for an unknown id.
match
my ($opId, $captures) = $api->match($method => $path);
The router alone: ($operationId, \%raw_path_captures) on a match; an empty list for a 404; (undef, \@allow) when the path exists but the method does not (a 405 and its Allow list). For framework adapters.
validate_request
my ($ok, $result) = $api->validate_request($opId, {
path => \%raw_captures,
query => $query_string_or_hashref,
header => \%lowercased_headers,
body => $raw_body_or_decoded_ref,
});
The validator alone: (1, \%params) or (0, \@errors). Header names must be lowercased by the caller; cookies are parsed from the cookie header when the operation declares cookie parameters. For framework adapters - together with "match" this is the complete integration surface, everything heavy stays in C.
ERRORS
Validation errors are hashrefs: the JSON::Schema::Fast output fields (instanceLocation, keyword, schemaLocation, message) augmented with in (path / query / header / cookie / body / response) and name (the parameter name). Missing required inputs use keyword => 'required'; an undecodable JSON body uses keyword => 'json'.
ARCHITECTURE
Open::API is a consumer of two runtime-resolved C ABIs, the same DBI-style versioned function-pointer tables used across the Semantic stack:
JSON::Schema::Fast (required) - schemas are compiled once through
jsf_abi.hand every validation on the request path is a direct C call. Resolved and version-checked at load; absence is a hard error.Fetch (optional) - Open::API::Client fires requests through
fetch_abi.h. Resolved lazily on first client construction; the server side never loads it.
There is no link-time coupling: each distribution builds and upgrades independently, and a version skew fails cleanly at boot (t/12-abi-guard.t).
SEE ALSO
Open::API::Plack, Open::API::Client, JSON::Schema::Fast, Fetch, Hyperman.
AUTHOR
LNATION <email@lnation.org>
BUGS
Please report any bugs or feature requests to bug-open-api at rt.cpan.org, or through the web interface at https://rt.cpan.org/NoAuth/ReportBug.html?Queue=Open-API. 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 Open::API
You can also look for information at:
RT: CPAN's request tracker (report bugs here)
Search CPAN
LICENSE AND COPYRIGHT
This software is Copyright (c) 2026 by LNATION <email@lnation.org>.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)