{"id":15437178,"url":"https://github.com/karenetheridge/openapi-modern","last_synced_at":"2025-04-12T17:10:56.447Z","repository":{"id":56824303,"uuid":"422955010","full_name":"karenetheridge/OpenAPI-Modern","owner":"karenetheridge","description":"Validate HTTP requests and responses against an OpenAPI v3.1 document","archived":false,"fork":false,"pushed_at":"2024-10-24T16:37:27.000Z","size":1003,"stargazers_count":5,"open_issues_count":49,"forks_count":3,"subscribers_count":5,"default_branch":"master","last_synced_at":"2024-10-25T23:30:22.092Z","etag":null,"topics":["json-schema","openapi"],"latest_commit_sha":null,"homepage":"https://metacpan.org/release/OpenAPI-Modern/","language":"Perl","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/karenetheridge.png","metadata":{"files":{"readme":"README.pod","changelog":"Changes","contributing":"CONTRIBUTING","funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-10-30T18:01:46.000Z","updated_at":"2024-10-24T16:37:32.000Z","dependencies_parsed_at":"2023-02-09T23:15:25.219Z","dependency_job_id":"1aea52a5-7ab0-4885-835b-7602315a9e06","html_url":"https://github.com/karenetheridge/OpenAPI-Modern","commit_stats":{"total_commits":596,"total_committers":1,"mean_commits":596.0,"dds":0.0,"last_synced_commit":"00aaaee1095fac66b51e140c8d11ce581a7e15fa"},"previous_names":[],"tags_count":72,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/karenetheridge%2FOpenAPI-Modern","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/karenetheridge%2FOpenAPI-Modern/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/karenetheridge%2FOpenAPI-Modern/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/karenetheridge%2FOpenAPI-Modern/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/karenetheridge","download_url":"https://codeload.github.com/karenetheridge/OpenAPI-Modern/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":222422258,"owners_count":16981940,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["json-schema","openapi"],"created_at":"2024-10-01T18:55:32.677Z","updated_at":"2025-04-12T17:10:56.439Z","avatar_url":"https://github.com/karenetheridge.png","language":"Perl","funding_links":[],"categories":[],"sub_categories":[],"readme":"=pod\n\n=encoding UTF-8\n\n=head1 NAME\n\nOpenAPI::Modern - Validate HTTP requests and responses against an OpenAPI v3.1 document\n\n=head1 VERSION\n\nversion 0.084\n\n=head1 SYNOPSIS\n\n  my $openapi = OpenAPI::Modern-\u003enew(\n    openapi_uri =\u003e '/api',\n    openapi_schema =\u003e YAML::PP-\u003enew(boolean =\u003e 'JSON::PP')-\u003eload_string(\u003c\u003c'YAML'));\n  openapi: 3.1.1\n  info:\n    title: Test API\n    version: 1.2.3\n  paths:\n    /foo/{foo_id}:\n      parameters:\n      - name: foo_id\n        in: path\n        required: true\n        schema:\n          pattern: ^[a-z]+$\n      post:\n        operationId: my_foo_request\n        parameters:\n        - name: My-Request-Header\n          in: header\n          required: true\n          schema:\n            pattern: ^[0-9]+$\n        requestBody:\n          required: true\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  hello:\n                    type: string\n                    pattern: ^[0-9]+$\n        responses:\n          200:\n            description: success\n            headers:\n              My-Response-Header:\n                required: true\n                schema:\n                  pattern: ^[0-9]+$\n            content:\n              application/json:\n                schema:\n                  type: object\n                  required: [ status ]\n                  properties:\n                    status:\n                      const: ok\n  YAML\n\n  say 'request:';\n  my $request = POST '/foo/bar',\n    'My-Request-Header' =\u003e '123', 'Content-Type' =\u003e 'application/json', Host =\u003e 'example.com',\n    Content =\u003e '{\"hello\": 123}';\n  my $results = $openapi-\u003evalidate_request($request);\n  say $results;\n  say ''; # newline\n  say JSON::MaybeXS-\u003enew(convert_blessed =\u003e 1, canonical =\u003e 1, pretty =\u003e 1, indent_length =\u003e 2)-\u003eencode($results);\n\n  say 'response:';\n  my $response = Mojo::Message::Response-\u003enew(code =\u003e 200, message =\u003e 'OK');\n  $response-\u003eheaders-\u003econtent_type('application/json');\n  $response-\u003eheaders-\u003eheader('My-Response-Header', '123');\n  $response-\u003ebody('{\"status\": \"ok\"}');\n  $results = $openapi-\u003evalidate_response($response, { request =\u003e $request });\n  say $results;\n  say ''; # newline\n  say JSON::MaybeXS-\u003enew(convert_blessed =\u003e 1, canonical =\u003e 1, pretty =\u003e 1, indent_length =\u003e 2)-\u003eencode($results);\n\nprints:\n\n  request:\n  '/request/body/hello': got integer, not string\n  '/request/body': not all properties are valid\n\n  {\n    \"errors\" : [\n      {\n        \"absoluteKeywordLocation\" : \"https://example.com/api#/paths/~1foo~1%7Bfoo_id%7D/post/requestBody/content/application~1json/schema/properties/hello/type\",\n        \"error\" : \"got integer, not string\",\n        \"instanceLocation\" : \"/request/body/hello\",\n        \"keywordLocation\" : \"/paths/~1foo~1{foo_id}/post/requestBody/content/application~1json/schema/properties/hello/type\"\n      },\n      {\n        \"absoluteKeywordLocation\" : \"https://example.com/api#/paths/~1foo~1%7Bfoo_id%7D/post/requestBody/content/application~1json/schema/properties\",\n        \"error\" : \"not all properties are valid\",\n        \"instanceLocation\" : \"/request/body\",\n        \"keywordLocation\" : \"/paths/~1foo~1{foo_id}/post/requestBody/content/application~1json/schema/properties\"\n      }\n    ],\n    \"valid\" : false\n  }\n\n  response:\n  valid\n\n  {\n    \"valid\" : true\n  }\n\n=head1 DESCRIPTION\n\nThis module provides various tools for working with an\nL\u003cOpenAPI Specification v3.1 document|https://spec.openapis.org/oas/v3.1#openapi-document\u003e within\nyour application. The JSON Schema evaluator is fully specification-compliant; the OpenAPI evaluator\naims to be but some features are not yet available. My belief is that missing features are better\nthan features that seem to work but actually cut corners for simplicity.\n\n=for Pod::Coverage BUILDARGS FREEZE THAW\n\n=for stopwords schemas jsonSchemaDialect metaschema subschema perlish operationId openapi Mojolicious\n\n=head1 CONSTRUCTOR ARGUMENTS\n\nIf construction of the object is not successful, for example the document has a syntax error, the\ncall to C\u003cnew()\u003e will throw an exception, which will likely be a L\u003cJSON::Schema::Modern::Result\u003e\nobject containing details.\n\n=head2 openapi_uri\n\nThe URI that identifies the OpenAPI document.\nIgnored if L\u003c/openapi_document\u003e is provided.\n\nIt is used at runtime as the base for absolute URIs used in L\u003cJSON::Schema::Modern::Result\u003e objects,\nalong with the request's C\u003cHost\u003e header and scheme (e.g. C\u003chttps\u003e), when available.\n\n=head2 openapi_schema\n\nThe data structure describing the OpenAPI v3.1 document (as specified at\nL\u003chttps://spec.openapis.org/oas/v3.1\u003e). Ignored if L\u003c/openapi_document\u003e is provided.\n\n=head2 openapi_document\n\nThe L\u003cJSON::Schema::Modern::Document::OpenAPI\u003e document that holds the OpenAPI information to be\nused for validation. If it is not provided to the constructor, then both L\u003c/openapi_uri\u003e and\nL\u003c/openapi_schema\u003e B\u003cMUST\u003e be provided, and L\u003c/evaluator\u003e will also be used if provided.\n\n=head2 evaluator\n\nThe L\u003cJSON::Schema::Modern\u003e object to use for all URI resolution and JSON Schema evaluation.\nIgnored if L\u003c/openapi_document\u003e is provided. Optional.\n\n=head1 ACCESSORS/METHODS\n\n=head2 openapi_uri\n\nThe URI that identifies the OpenAPI document. This URI will be used to resolve relative URIs used in\nthe OpenAPI document, such as for C\u003cjsonSchemaDialect\u003e or C\u003cservers url\u003e values, as well as used\nfor locations in L\u003cJSON::Schema::Modern::Result\u003e objects (see below).\n\n=head2 openapi_schema\n\nThe data structure describing the OpenAPI document. See L\u003cthe specification/https://spec.openapis.org/oas/v3.1\u003e.\n\n=head2 openapi_document\n\nThe L\u003cJSON::Schema::Modern::Document::OpenAPI\u003e document that holds the OpenAPI information to be\nused for validation.\n\n=head2 document_get\n\n  my $parameter_data = $openapi-\u003edocument_get('/paths/~1foo~1{foo_id}/get/parameters/0');\n\nFetches the subschema at the provided JSON pointer.\nProxies to L\u003cJSON::Schema::Modern::Document::OpenAPI/get\u003e.\nThis is not recursive (does not follow C\u003c$ref\u003e chains) -- for that, use\nC\u003c\u003c $openapi-\u003erecursive_get(Mojo::URL-\u003enew-\u003efragment($json_pointer)) \u003e\u003e, see\nL\u003c/recursive_get\u003e.\n\n=head2 evaluator\n\nThe L\u003cJSON::Schema::Modern\u003e object to use for all URI retrieval and JSON Schema evaluation.\n\n=head2 validate_request\n\n  $result = $openapi-\u003evalidate_request(\n    $request,\n    # optional second argument can contain any combination of:\n    my $options = {\n      path_template =\u003e '/foo/{arg1}/bar/{arg2}',\n      operation_id =\u003e 'my_operation_id',\n      path_captures =\u003e { arg1 =\u003e 1, arg2 =\u003e 2 },\n      method =\u003e 'get',\n    },\n  );\n\nValidates an L\u003cHTTP::Request\u003e, L\u003cPlack::Request\u003e, L\u003cCatalyst::Request\u003e or L\u003cMojo::Message::Request\u003e\nobject against the corresponding OpenAPI v3.1 document, returning a\nL\u003cJSON::Schema::Modern::Result\u003e object.\n\nAbsolute URIs in the result object are constructed by resolving the openapi document path against\nthe L\u003c/openapi_uri\u003e, as well as the C\u003cHost\u003e header of the request if a host component is not\nincluded in the L\u003c/openapi_uri\u003e.\n\nThe second argument is an optional hashref that contains extra information about the request,\ncorresponding to the values expected by L\u003c/find_path\u003e below. It is populated with some information\nabout the request:\nsave it and pass it to a later L\u003c/validate_response\u003e (corresponding to a response for this request)\nto improve performance.\n\n=head2 validate_response\n\n  $result = $openapi-\u003evalidate_response(\n    $response,\n    {\n      path_template =\u003e '/foo/{arg1}/bar/{arg2}',\n      request =\u003e $request,\n    },\n  );\n\nValidates an L\u003cHTTP::Response\u003e, L\u003cPlack::Response\u003e, L\u003cCatalyst::Response\u003e or L\u003cMojo::Message::Response\u003e\nobject against the corresponding OpenAPI v3.1 document, returning a\nL\u003cJSON::Schema::Modern::Result\u003e object.\n\nAbsolute URIs in the result object are constructed by resolving the openapi document path against\nthe L\u003c/openapi_uri\u003e, as well as the C\u003cHost\u003e header of the request if the request is provided and if a\nhost component is not included in the L\u003c/openapi_uri\u003e.\n\nThe second argument is an optional hashref that contains extra information about the request\ncorresponding to the response, as in L\u003c/find_path\u003e.\n\nC\u003crequest\u003e is also accepted as a key in the hashref, representing the original request object that\ncorresponds to this response (as not all HTTP libraries link to the request in the response object).\n\n=head2 find_path\n\n  $result = $self-\u003efind_path($options);\n\nUses information in the request to determine the relevant parts of the OpenAPI specification.\nC\u003crequest\u003e should be provided if available, but additional data can be used instead\n(which is populated by earlier L\u003c/validate_request\u003e or L\u003c/find_path\u003e calls to the same request).\n\nThe single argument is a hashref that contains information about the request. Possible values\ninclude:\n\n=over 4\n\n=item *\n\nC\u003crequest\u003e: the object representing the HTTP request. Should be provided when available.\n\n=item *\n\nC\u003cpath_template\u003e: a string representing the request URI, with placeholders in braces (e.g. C\u003c/pets/{petId}\u003e); see L\u003chttps://spec.openapis.org/oas/v3.1#paths-object\u003e.\n\n=item *\n\nC\u003coperation_id\u003e: a string corresponding to the L\u003coperationId|https://learn.openapis.org/specification/paths.html#the-endpoints-list\u003e at a particular path-template and HTTP location under C\u003c/paths\u003e\n\n=item *\n\nC\u003cpath_captures\u003e: a hashref mapping placeholders in the path template to their actual values in the request URI\n\n=item *\n\nC\u003cmethod\u003e: the HTTP method used by the request (used case-insensitively)\n\n=back\n\nAll of these values are optional (unless C\u003crequest\u003e is omitted), and will be derived from the\nrequest URI as needed (albeit less\nefficiently than if they were provided). All passed-in values MUST be consistent with each other and\nthe request URI.\n\nWhen successful, the options hash will be populated (or updated) with keys C\u003cpath_template\u003e,\nC\u003cpath_captures\u003e, C\u003cmethod\u003e, C\u003coperation_id\u003e and C\u003coperation_uri\u003e (see below), and the return value\nis true.\nWhen not successful, the options hash will be populated with key C\u003cerrors\u003e, an arrayref containing\na L\u003cJSON::Schema::Modern::Error\u003e object, and the return value is false.\n\nIn addition, these values are populated in the options hash (when available):\n\n=over 4\n\n=item *\n\nC\u003coperation_uri\u003e: a URI indicating the document location of the operation object for the request, after following any references (usually something under C\u003c/paths/\u003e, but may be in another document). Use C\u003c\u003c $openapi-\u003eevaluator-\u003eget($uri) \u003e\u003e to fetch this content (see L\u003cJSON::Schema::Modern/get\u003e). Note that this is the same as C\u003c\u003c $openapi-\u003erecursive_get(Mojo::URL-\u003enew-\u003efragment(JSON::Schema::Modern::Utilities::jsonp('/paths', $options-\u003e{path_template}{$options-\u003e{method}}))) \u003e\u003e. (See the documentation for an operation at L\u003chttps://learn.openapis.org/specification/paths.html#the-endpoints-list\u003e or in the specification at L\u003c§4.8.10 of the specification|https://spec.openapis.org/oas/v3.1#operation-object\u003e.)\n\n=item *\n\nC\u003crequest\u003e (not necessarily what was passed in: this is always a L\u003cMojo::Message::Request\u003e)\n\n=back\n\nYou can find the associated operation object by using either C\u003coperation_uri\u003e,\nor by calling C\u003c\u003c $openapi-\u003eopenapi_document-\u003eget_operationId_path($operation_id) \u003e\u003e\n(see L\u003cJSON::Schema::Modern::Document::OpenAPI/get_operationId_path\u003e) (note that the latter will\nbe removed in a subsequent release, in order to support operations existing in other documents).\n\nNote that the L\u003cC\u003c/servers\u003e|https://spec.openapis.org/oas/v3.1#server-object\u003e section of the\nOpenAPI document is not used for path matching at this time, for either scheme and host matching nor\npath prefixes. For now, if you use a path prefix in C\u003cservers\u003e entries you will need to add this to\nthe path templates under `/paths`.\n\n=head2 recursive_get\n\nGiven a uri or uri-reference, get the definition at that location, following any C\u003c$ref\u003es along the\nway. Include the expected definition type\n(one of C\u003cschema\u003e, C\u003cresponse\u003e, C\u003cparameter\u003e, C\u003cexample\u003e, C\u003crequest-body\u003e, C\u003cheader\u003e,\nC\u003csecurity-scheme\u003e, C\u003clink\u003e, C\u003ccallbacks\u003e, or C\u003cpath-item\u003e)\nfor validation of the entire reference chain.\n\nReturns the data in scalar context, or a tuple of the data and the canonical URI of the\nreferenced location in list context.\n\nIf the provided location is relative, the main openapi document is used for the base URI.\nIf you have a local json pointer you want to resolve, you can turn it into a uri-reference by\nprepending C\u003c#\u003e.\n\n  my $schema = $openapi-\u003erecursive_get('#/components/parameters/Content-Encoding', 'parameter');\n\n  # starts with a JSON::Schema::Modern object (TODO)\n  my $schema = $js-\u003erecursive_get('https:///openapi_doc.yaml#/components/schemas/my_object')\n  my $schema = $js-\u003erecursive_get('https://localhost:1234/my_spec#/$defs/my_object')\n\n=head2 canonical_uri\n\nAn accessor that delegates to L\u003cJSON::Schema::Modern::Document/canonical_uri\u003e.\n\n=head2 schema\n\nAn accessor that delegates to L\u003cJSON::Schema::Modern::Document/schema\u003e.\n\n=head2 get_media_type\n\nAn accessor that delegates to L\u003cJSON::Schema::Modern/get_media_type\u003e.\n\n=head2 add_media_type\n\nA setter that delegates to L\u003cJSON::Schema::Modern/add_media_type\u003e.\n\n=head1 CACHING\n\n=for stopwords preforking\n\nVery large OpenAPI documents may take a noticeable time to be\nloaded and parsed. You can reduce the impact to your preforking application by loading all necessary\ndocuments at startup, and impact can be further reduced by saving objects to cache and then\nreloading them (perhaps by using a timestamp or checksum to determine if a fresh reload is needed).\n\n  sub get_openapi (...) {\n    my $serialized_file = Path::Tiny::path($serialized_filename);\n    my $openapi_file = Path::Tiny::path($openapi_filename);\n    my $openapi;\n    if ($serialized_file-\u003estat-\u003emtime \u003c $openapi_file-\u003estat-\u003emtime)) {\n      $openapi = OpenAPI::Modern-\u003enew(\n        openapi_uri =\u003e '/api',\n        openapi_schema =\u003e decode_json($openapi_file-\u003eslurp_raw), # your openapi document\n      );\n      $openapi-\u003eevaluator-\u003eadd_schema(decode_json(...));  # any other needed schemas\n      my $frozen = Sereal::Encoder-\u003enew({ freeze_callbacks =\u003e 1 })-\u003eencode($openapi);\n      $serialized_file-\u003espew_raw($frozen);\n    }\n    else {\n      my $frozen = $serialized_file-\u003eslurp_raw;\n      $openapi = Sereal::Decoder-\u003enew-\u003edecode($frozen);\n    }\n\n    # add custom format validations, media types and encodings here\n    $openapi-\u003eevaluator-\u003eadd_media_type(...);\n\n    return $openapi;\n  }\n\nSee also L\u003cJSON::Schema::Modern/CACHING\u003e.\n\n=head1 ON THE USE OF JSON SCHEMAS\n\nEmbedded JSON Schemas, through the use of the C\u003cschema\u003e keyword, are fully draft2020-12-compliant,\nas per the spec, and implemented with L\u003cJSON::Schema::Modern\u003e. Unless overridden with the use of the\nL\u003cjsonSchemaDialect|https://spec.openapis.org/oas/v3.1#specifying-schema-dialects\u003e keyword, their\nmetaschema is L\u003chttps://spec.openapis.org/oas/3.1/dialect/base\u003e, which allows for use of the\nOpenAPI-specific keywords (C\u003cdiscriminator\u003e, C\u003cxml\u003e, C\u003cexternalDocs\u003e, and C\u003cexample\u003e), as defined in\nL\u003cthe specification/https://spec.openapis.org/oas/v3.1#schema-object\u003e. Format validation is turned\nB\u003con\u003e, and the use of content* keywords is off (see\nL\u003cJSON::Schema::Modern/validate_content_schemas\u003e).\n\nReferences (with the C\u003c$ref\u003e) keyword may reference any position within the entire OpenAPI document;\nas such, json pointers are relative to the B\u003croot\u003e of the document, not the root of the subschema\nitself. References to other documents are also permitted, provided those documents have been loaded\ninto the evaluator in advance (see L\u003cJSON::Schema::Modern/add_schema\u003e).\n\nValues are generally treated as strings for the purpose of schema evaluation. However, if the top\nlevel of the schema contains C\u003c\"type\": \"number\"\u003e or C\u003c\"type\": \"integer\"\u003e, then the value will be\n(attempted to be) coerced into a number before being passed to the JSON Schema evaluator.\nType coercion will B\u003cnot\u003e be done if the C\u003ctype\u003e keyword is omitted.\nThis lets you use numeric keywords such as C\u003cmaximum\u003e and C\u003cmultipleOf\u003e in your schemas.\nIt also resolves inconsistencies that can arise when request and response objects are created\nmanually in a test environment (as opposed to being parsed from incoming network traffic) and can\ntherefore inadvertently contain perlish numbers rather than strings.\n\n=head1 LIMITATIONS\n\nAll message validation is done using L\u003cMojolicious\u003e objects (L\u003cMojo::Message::Request\u003e and\nL\u003cMojo::Message::Response\u003e). If messages of other types are passed, conversion is done on a\nbest-effort basis, but since different implementations have different levels of adherence to the RFC\nspecs, some validation errors may occur e.g. if a certain required header is missing on the\noriginal. For best results in validating real messages from the network, parse them directly into\nMojolicious messages (see L\u003cMojo::Message/parse\u003e).\n\nOnly certain permutations of OpenAPI documents are supported at this time:\n\n=over 4\n\n=item *\n\nfor path parameters, only C\u003cstyle: simple\u003e and C\u003cexplode: false\u003e is supported\n\n=item *\n\nfor query parameters, only C\u003cstyle: form\u003e and C\u003cexplode: true\u003e is supported, only the first value of each parameter name is considered, and C\u003callowEmptyValue\u003e and C\u003callowReserved\u003e are not checked\n\n=item *\n\ncookie parameters are not checked at all yet\n\n=item *\n\nC\u003capplication/x-www-form-urlencoded\u003e and C\u003cmultipart/*\u003e messages are not yet supported\n\n=item *\n\nC\u003cserver\u003e fields in definitions are completely ignored, and not considered when parsing request URIs.\n\n=item *\n\nOpenAPI descriptions must be contained in a single document; C\u003c$ref\u003eerences to other documents are not fully supported at this time.\n\n=item *\n\nThe use of C\u003c$ref\u003e within a path-item object is only allowed when not adjacent to any other path-item properties ('parameters', 'servers', request methods)\n\n=item *\n\nSecurity schemes in the OpenAPI description, and the use of any C\u003cAuthorization\u003e headers in requests, are not currently supported.\n\n=back\n\n=head1 SEE ALSO\n\n=over 4\n\n=item *\n\nL\u003cMojolicious::Plugin::OpenAPI::Modern\u003e\n\n=item *\n\nL\u003cTest::Mojo::Role::OpenAPI::Modern\u003e\n\n=item *\n\nL\u003cJSON::Schema::Modern::Document::OpenAPI\u003e\n\n=item *\n\nL\u003cJSON::Schema::Modern\u003e\n\n=item *\n\nL\u003chttps://json-schema.org\u003e\n\n=item *\n\nL\u003chttps://www.openapis.org/\u003e\n\n=item *\n\nL\u003chttps://learn.openapis.org/\u003e\n\n=item *\n\nL\u003chttps://spec.openapis.org/oas/v3.1\u003e\n\n=back\n\n=head1 SUPPORT\n\nBugs may be submitted through L\u003chttps://github.com/karenetheridge/OpenAPI-Modern/issues\u003e.\n\nI am also usually active on irc, as 'ether' at C\u003circ.perl.org\u003e and C\u003circ.libera.chat\u003e.\n\n=for stopwords OpenAPI\n\nYou can also find me on the L\u003cJSON Schema Slack server|https://json-schema.slack.com\u003e and L\u003cOpenAPI\nSlack server|https://open-api.slack.com\u003e, which are also great resources for finding help.\n\n=head1 AUTHOR\n\nKaren Etheridge \u003cether@cpan.org\u003e\n\n=head1 COPYRIGHT AND LICENCE\n\nThis software is copyright (c) 2021 by Karen Etheridge.\n\nThis is free software; you can redistribute it and/or modify it under\nthe same terms as the Perl 5 programming language system itself.\n\nSome schema files have their own licence, in share/oas/LICENSE.\n\n=cut\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkarenetheridge%2Fopenapi-modern","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkarenetheridge%2Fopenapi-modern","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkarenetheridge%2Fopenapi-modern/lists"}