package Open::API;

use 5.008003;
use strict;
use warnings;
use Carp ();

our $VERSION = '0.04';

require XSLoader;
XSLoader::load('Open::API', $VERSION);

Carp::croak(
	"Open::API requires JSON::Schema::Fast built with its C ABI "
	. "(JSON::Schema::Fast 0.04 or later); please upgrade or reinstall it"
) unless _abi_ok();

1;

__END__

=head1 NAME

Open::API - OpenAPI 3.1 server and client

=head1 VERSION

Version 0.04

=head1 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;

=head1 DESCRIPTION

C<Open::API> loads an OpenAPI 3.1 document once and compiles every parameter,
header, cookie and body schema through L<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: L<Open::API::Plack>
serves it as a PSGI app (routing, validation, security, CSRF, CORS - all in
C before a handler runs), and L<Open::API::Client> is a spec-driven HTTP
client on L<Fetch>'s C ABI, so one document defines both sides of the wire.
The L</match> and L</validate_request> methods below expose the router and
validator directly for any other framework adapter.

OpenAPI B<3.1 only>: 3.1 schemas are native JSON Schema 2020-12, which is what
JSON::Schema::Fast validates. A document with any other C<openapi> version
croaks at load.

=head1 CONSTRUCTOR

=head2 new

    my $api = Open::API->new(spec => $spec);

C<spec> is required and may be:

=over 4

=item * a hashref (an already-decoded document),

=item * a string of JSON text,

=item * a string of YAML text (decoded with L<YAML::XS>, loaded lazily -
only YAML specs need it), or

=item * a filename (C<.json>, C<.yaml> or C<.yml>; anything else is sniffed by
content).

=back

Compilation walks C<paths> once: every operation needs a unique
C<operationId> (it is the dispatch key), path templates are pre-split,
path-item and operation C<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 C<default>-filling on. C<$ref>s to C<#/components/schemas/...> are
resolved at compile time. A malformed document, a missing or duplicate
operationId, or an unresolvable reference croaks here, at startup.

=head1 METHODS

=head2 spec

The decoded OpenAPI document.

=head2 operations

    my $ops = $api->operations;   # [ { operationId, method, path }, ... ]

=head2 operation

    my $info = $api->operation('getPet');

A description of one operation: C<params> by location (name + required),
C<body> (required flag + content types) and C<responses> (statuses with
compiled schemas). C<undef> for an unknown id.

=head2 match

    my ($opId, $captures) = $api->match($method => $path);

The router alone: C<($operationId, \%raw_path_captures)> on a match; an empty
list for a 404; C<(undef, \@allow)> when the path exists but the method does
not (a 405 and its Allow list). For framework adapters.

=head2 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: C<(1, \%params)> or C<(0, \@errors)>. Header names must
be lowercased by the caller; cookies are parsed from the C<cookie> header when
the operation declares cookie parameters. For framework adapters - together
with L</match> this is the complete integration surface, everything heavy
stays in C.

=head1 ERRORS

Validation errors are hashrefs: the L<JSON::Schema::Fast> output fields
(C<instanceLocation>, C<keyword>, C<schemaLocation>, C<message>) augmented
with C<in> (path / query / header / cookie / body / response) and C<name>
(the parameter name). Missing required inputs use C<< keyword => 'required' >>;
an undecodable JSON body uses C<< keyword => 'json' >>.

=head1 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:

=over 4

=item * L<JSON::Schema::Fast> (required) - schemas are compiled once through
C<jsf_abi.h> and every validation on the request path is a direct C call.
Resolved and version-checked at load; absence is a hard error.

=item * L<Fetch> (optional) - L<Open::API::Client> fires requests through
C<fetch_abi.h>. Resolved lazily on first client construction; the server side
never loads it.

=back

It is also a B<provider> of one: C<include/oa_abi.h> is a versioned
function-pointer table (C<OA_ABI_VERSION>) reached through
C<Open::API::_abi_ptr>, exposing the router and validator - C<api_of>,
C<route>, C<op_by_id>, C<validate>, C<op_id> - so a consumer's own C
dispatcher (L<Punk>) routes and validates an operation with no Perl frame
between the socket and the controller. It is the C form of L</match> and
L</validate_request>. The header installs through L<ExtUtils::Depends> (a
dependent adds C<< ExtUtils::Depends->new($pkg, 'Open::API') >> and gets the
include path with no vendoring) or is vendored at a pinned
C<OA_ABI_VERSION>; a consumer checks C<abi_version> at boot and falls back to
the Perl-visible methods on any mismatch.

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 on
the consumer side, t/25-abi.t for the provider table).

=head1 SEE ALSO

L<Open::API::Plack>, L<Open::API::Client>, L<JSON::Schema::Fast>, L<Fetch>,
L<Hyperman>.

=head1 AUTHOR

LNATION <email@lnation.org>

=head1 BUGS

Please report any bugs or feature requests to C<bug-open-api at rt.cpan.org>, or through
the web interface at L<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.

=head1 SUPPORT

You can find documentation for this module with the perldoc command.

    perldoc Open::API

You can also look for information at:

=over 4

=item * RT: CPAN's request tracker (report bugs here)

L<https://rt.cpan.org/NoAuth/Bugs.html?Dist=Open-API>

=item * Search CPAN

L<https://metacpan.org/release/Open-API>

=back

=head1 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)

=cut
