diff --git a/CMakeLists.txt b/CMakeLists.txt index 081371c42..cb72c1c20 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,6 +39,7 @@ option(SOURCEMETA_CORE_JSONRPC "Build the Sourcemeta Core JSON-RPC library" ON) option(SOURCEMETA_CORE_MCP "Build the Sourcemeta Core MCP library" ON) option(SOURCEMETA_CORE_HTTP "Build the Sourcemeta Core HTTP library" ON) option(SOURCEMETA_CORE_HTTP_USE_SYSTEM_CURL "Use system cURL for the Sourcemeta Core HTTP library" OFF) +option(SOURCEMETA_CORE_OPENAPI "Build the Sourcemeta Core OpenAPI library" ON) option(SOURCEMETA_CORE_JOSE "Build the Sourcemeta Core JOSE library" ON) option(SOURCEMETA_CORE_OAUTH "Build the Sourcemeta Core OAuth library" ON) option(SOURCEMETA_CORE_OIDC "Build the Sourcemeta Core OIDC library" ON) @@ -251,6 +252,10 @@ if(SOURCEMETA_CORE_HTTP) add_subdirectory(src/core/http) endif() +if(SOURCEMETA_CORE_OPENAPI) + add_subdirectory(src/core/openapi) +endif() + if(SOURCEMETA_CORE_JOSE) add_subdirectory(src/core/jose) endif() @@ -447,6 +452,10 @@ if(SOURCEMETA_CORE_TESTS) add_subdirectory(test/http) endif() + if(SOURCEMETA_CORE_OPENAPI) + add_subdirectory(test/openapi) + endif() + if(SOURCEMETA_CORE_JOSE) add_subdirectory(test/jose) endif() diff --git a/DEPENDENCIES b/DEPENDENCIES index a3296a9a4..0e7ded695 100644 --- a/DEPENDENCIES +++ b/DEPENDENCIES @@ -24,6 +24,7 @@ jsonschema-draft2 https://github.com/json-schema-org/json-schema-spec 707f65070d jsonschema-draft1 https://github.com/json-schema-org/json-schema-spec 2072feec9fc7a7ff0b2bb5b02c2d6742c554cc4a jsonschema-draft0 https://github.com/json-schema-org/json-schema-spec 7ea575aef8d5c0183acbe6ff65b4c98ee9c236ec openapi https://github.com/OAI/OpenAPI-Specification 74906beddddab9e555337031b2a8d8e9338c4972 +openapi-test-suite-3-1 https://github.com/OAI/OpenAPI-Specification 8df69dd6b8c12f50c99e28a864e500aadf3394e5 referencing-suite https://github.com/python-jsonschema/referencing-suite 61c4cc202b1e96ed5adcaf4842a595f68d659212 iana-oauth/parameters.csv https://www.iana.org/assignments/oauth-parameters/parameters.csv acfe19a091a15279587e761a399704e886447eec906b19a41528cb37dc2afc35 iana-oauth/extensions-error.csv https://www.iana.org/assignments/oauth-parameters/extensions-error.csv b49d3e1c9904667551170d12d0943ae9613751f42581f6438a4714c774d64838 diff --git a/config.cmake.in b/config.cmake.in index 42b5aa5ef..724aac47b 100644 --- a/config.cmake.in +++ b/config.cmake.in @@ -33,6 +33,7 @@ if(NOT SOURCEMETA_CORE_COMPONENTS) list(APPEND SOURCEMETA_CORE_COMPONENTS jsonrpc) list(APPEND SOURCEMETA_CORE_COMPONENTS mcp) list(APPEND SOURCEMETA_CORE_COMPONENTS http) + list(APPEND SOURCEMETA_CORE_COMPONENTS openapi) list(APPEND SOURCEMETA_CORE_COMPONENTS jose) list(APPEND SOURCEMETA_CORE_COMPONENTS oauth) list(APPEND SOURCEMETA_CORE_COMPONENTS oidc) @@ -249,6 +250,23 @@ foreach(component ${SOURCEMETA_CORE_COMPONENTS}) include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_ip.cmake") include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_uri.cmake") include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_http.cmake") + elseif(component STREQUAL "openapi") + include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_io.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_preprocessor.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_numeric.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_text.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_unicode.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_json.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_regex.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_ip.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_uri.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_jsonpointer.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_memory.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_punycode.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_idna.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_dns.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_email.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_openapi.cmake") elseif(component STREQUAL "jose") if(@SOURCEMETA_CORE_CRYPTO_USE_SYSTEM_OPENSSL@) find_dependency(OpenSSL 3.0) diff --git a/src/core/openapi/CMakeLists.txt b/src/core/openapi/CMakeLists.txt new file mode 100644 index 000000000..e14b6803f --- /dev/null +++ b/src/core/openapi/CMakeLists.txt @@ -0,0 +1,18 @@ +sourcemeta_library(NAMESPACE sourcemeta PROJECT core NAME openapi + PRIVATE_HEADERS error.h + SOURCES helpers.h reference.h example.h content.h link.h response.h + parameter.h request_body.h security.h path_item.h paths.h + components.h external_documentation.h info.h server.h tag.h + document.h frame.cc version.cc) + +if(SOURCEMETA_CORE_INSTALL) + sourcemeta_library_install(NAMESPACE sourcemeta PROJECT core NAME openapi) +endif() + +target_link_libraries(sourcemeta_core_openapi PUBLIC sourcemeta::core::json) +target_link_libraries(sourcemeta_core_openapi PUBLIC + sourcemeta::core::jsonpointer) +target_link_libraries(sourcemeta_core_openapi PUBLIC sourcemeta::core::memory) +target_link_libraries(sourcemeta_core_openapi PRIVATE sourcemeta::core::uri) +target_link_libraries(sourcemeta_core_openapi PRIVATE sourcemeta::core::email) +target_link_libraries(sourcemeta_core_openapi PRIVATE sourcemeta::core::text) diff --git a/src/core/openapi/components.h b/src/core/openapi/components.h new file mode 100644 index 000000000..f0e6bcac9 --- /dev/null +++ b/src/core/openapi/components.h @@ -0,0 +1,181 @@ +#ifndef SOURCEMETA_CORE_OPENAPI_COMPONENTS_H_ +#define SOURCEMETA_CORE_OPENAPI_COMPONENTS_H_ + +#include + +#include "content.h" +#include "example.h" +#include "helpers.h" +#include "link.h" +#include "parameter.h" +#include "path_item.h" +#include "request_body.h" +#include "response.h" +#include "security.h" + +#include // std::array +#include // std::set +#include // std::string_view + +namespace sourcemeta::core { + +constexpr auto OPENAPI_HASH_COMPONENTS{JSON::Object::hash("components"sv)}; +constexpr auto OPENAPI_HASH_SECURITY_SCHEMES{ + JSON::Object::hash("securitySchemes"sv)}; + +constexpr std::array OPENAPI_COMPONENTS_FIELDS{ + {"schemas"sv, "responses"sv, "parameters"sv, "examples"sv, + "requestBodies"sv, "headers"sv, "securitySchemes"sv, "links"sv, + "callbacks"sv, "pathItems"sv}}; + +// The names the entry document declares as security schemes, read before the +// walk goes anywhere so that the order documents are read in cannot decide +// what a Security Requirement Object may name. This runs before the Components +// Object has been checked, so it takes what is there and leaves being strict +// about the shape to that check +inline auto openapi_collect_security_schemes(const JSON &document, + OpenAPIWalk &walk) -> void { + const auto *components{ + document.try_at("components", OPENAPI_HASH_COMPONENTS)}; + if (components == nullptr || !components->is_object()) { + return; + } + + const auto *schemes{ + components->try_at("securitySchemes", OPENAPI_HASH_SECURITY_SCHEMES)}; + if (schemes == nullptr || !schemes->is_object()) { + return; + } + + for (const auto &entry : schemes->as_object()) { + walk.security_schemes.insert(entry.first); + } +} + +// OpenAPI Specification 3.1.1, Section 4.8.30: "Lists the required security +// schemes to execute this operation". Every field is patterned, so unlike +// almost every other Object this one has no extension carve-out and a member +// named `x-` is a scheme name +// OpenAPI Specification 3.1.1, Section 4.8.7: "All the fixed fields declared +// above are objects that MUST use keys that match the regular expression: +// `^[a-zA-Z0-9\.\-_]+$`". Every member of that character class is ASCII, so +// reading the key one byte at a time turns down any other code point too +inline auto openapi_is_component_key(const JSON::StringView key) noexcept + -> bool { + for (const auto character : key) { + if ((character >= 'a' && character <= 'z') || + (character >= 'A' && character <= 'Z') || + (character >= '0' && character <= '9') || character == '.' || + character == '-' || character == '_') { + continue; + } + + return false; + } + + return !key.empty(); +} + +// OpenAPI Specification 3.1.1, Section 4.8.7: "Holds a set of reusable objects +// for different aspects of the OAS". What each entry of those maps holds is +// not read here, and the Schema Objects under `schemas` are never read at all, +// as their semantics belong to a JSON Schema implementation +inline auto openapi_check_components(const JSON &document, OpenAPIWalk &walk) + -> void { + const auto *components{ + document.try_at("components", OPENAPI_HASH_COMPONENTS)}; + if (components == nullptr) { + return; + } + + const Pointer base{"components"}; + openapi_record(walk, base, OpenAPIObjectKind::Components); + if (!components->is_object()) { + throw OpenAPIError{base, "The Components Object must be an object"}; + } + + openapi_reject_unknown_fields( + *components, OPENAPI_COMPONENTS_FIELDS, base, + "The Components Object does not define this field"); + + for (const auto &entry : components->as_object()) { + if (entry.first.starts_with(OPENAPI_EXTENSION_PREFIX)) { + continue; + } + + const auto location{openapi_child(base, entry.first)}; + if (!entry.second.is_object()) { + throw OpenAPIError{location, + "The Components Object fields must each be an object"}; + } + + // Every entry of every map but one is an Object or a Reference Object, + // and the specification types both as objects. What sits under `schemas` + // is a Schema Object, and Section 4.8.24 states that "The empty schema + // [...] MAY be represented by the boolean value `true` and a schema which + // allows no instance to validate MAY be represented by the boolean value + // `false`", which is also all that the meta-schema asserts of a Schema + // Object position when it leaves those unvalidated + const auto holds_schemas{entry.first == "schemas"sv}; + + for (const auto &component : entry.second.as_object()) { + if (component.first.empty()) { + throw OpenAPIError{openapi_child(location, component.first), + "The Components Object keys must not be empty"}; + } + + if (!openapi_is_component_key(component.first)) { + throw OpenAPIError{openapi_child(location, component.first), + "The Components Object keys may only hold letters, " + "digits, dots, hyphens and underscores"}; + } + + const auto entry_location{openapi_child(location, component.first)}; + if (holds_schemas) { + openapi_expect_schema(component.second, entry_location, + "A Schema Object must be an object or a boolean", + walk); + continue; + } + + openapi_expect_object(component.second, entry_location, + "The Components Object entries must be objects"); + + if (entry.first == "responses"sv) { + openapi_check_response_or_reference(component.second, entry_location, + walk); + } else if (entry.first == "parameters"sv) { + if (openapi_is_reference(component.second)) { + openapi_check_reference(component.second, entry_location, + OpenAPIObjectKind::Parameter, walk); + } else { + [[maybe_unused]] const auto identity{ + openapi_check_parameter(component.second, entry_location, walk)}; + } + } else if (entry.first == "examples"sv) { + openapi_check_example_or_reference(component.second, entry_location, + walk); + } else if (entry.first == "requestBodies"sv) { + openapi_check_request_body_or_reference(component.second, + entry_location, walk); + } else if (entry.first == "headers"sv) { + openapi_check_header_or_reference(component.second, entry_location, + walk); + } else if (entry.first == "securitySchemes"sv) { + openapi_check_security_scheme_or_reference(component.second, + entry_location, walk); + } else if (entry.first == "links"sv) { + openapi_check_link_or_reference(component.second, entry_location, walk); + } else if (entry.first == "callbacks"sv) { + openapi_check_callbacks_or_reference(component.second, entry_location, + walk); + } else { + openapi_check_path_item(component.second, entry_location, walk); + } + } + } +} + +} // namespace sourcemeta::core + +#endif diff --git a/src/core/openapi/content.h b/src/core/openapi/content.h new file mode 100644 index 000000000..a2f1ceb2c --- /dev/null +++ b/src/core/openapi/content.h @@ -0,0 +1,249 @@ +#ifndef SOURCEMETA_CORE_OPENAPI_CONTENT_H_ +#define SOURCEMETA_CORE_OPENAPI_CONTENT_H_ + +#include + +#include "example.h" +#include "helpers.h" +#include "reference.h" + +#include // std::array +#include // std::string_view + +namespace sourcemeta::core { + +constexpr auto OPENAPI_HASH_SCHEMA{JSON::Object::hash("schema"sv)}; +constexpr auto OPENAPI_HASH_CONTENT{JSON::Object::hash("content"sv)}; +constexpr auto OPENAPI_HASH_ENCODING{JSON::Object::hash("encoding"sv)}; +constexpr auto OPENAPI_HASH_CONTENT_TYPE{JSON::Object::hash("contentType"sv)}; +constexpr auto OPENAPI_HASH_HEADERS{JSON::Object::hash("headers"sv)}; +constexpr auto OPENAPI_HASH_STYLE{JSON::Object::hash("style"sv)}; +constexpr auto OPENAPI_HASH_EXPLODE{JSON::Object::hash("explode"sv)}; +constexpr auto OPENAPI_HASH_ALLOW_RESERVED{ + JSON::Object::hash("allowReserved"sv)}; +constexpr auto OPENAPI_HASH_REQUIRED{JSON::Object::hash("required"sv)}; +constexpr auto OPENAPI_HASH_DEPRECATED{JSON::Object::hash("deprecated"sv)}; + +constexpr std::array OPENAPI_ENCODING_FIELDS{ + {"contentType"sv, "headers"sv, "style"sv, "explode"sv, "allowReserved"sv}}; + +constexpr std::array OPENAPI_MEDIA_TYPE_FIELDS{ + {"schema"sv, "example"sv, "examples"sv, "encoding"sv}}; + +// A Header Object only admits the serialisation fields alongside a `schema`, +// which is how the meta-schema reads it, holding them behind a dependent +// schema that a `content` form never reaches +constexpr std::array OPENAPI_HEADER_SCHEMA_FIELDS{ + {"description"sv, "required"sv, "deprecated"sv, "schema"sv, "style"sv, + "explode"sv, "example"sv, "examples"sv}}; + +constexpr std::array OPENAPI_HEADER_CONTENT_FIELDS{ + {"description"sv, "required"sv, "deprecated"sv, "content"sv}}; + +inline auto openapi_check_header_or_reference(const JSON &value, + const Pointer &base, + OpenAPIWalk &walk) -> void; + +// OpenAPI Specification 3.1.1, Section 4.8.15: "A single encoding definition +// applied to a single schema property" +inline auto openapi_check_encoding(const JSON &value, const Pointer &base, + OpenAPIWalk &walk) -> void { + openapi_record(walk, base, OpenAPIObjectKind::Encoding); + openapi_expect_object(value, base, "The Encoding Object must be an object"); + openapi_reject_unknown_fields( + value, OPENAPI_ENCODING_FIELDS, base, + "The Encoding Object does not define this field"); + + // The specification says media type definitions "SHOULD be in compliance + // with RFC6838", which is not a requirement, so only the type is checked + const auto *content_type{ + value.try_at("contentType", OPENAPI_HASH_CONTENT_TYPE)}; + if (content_type != nullptr) { + openapi_expect_string(*content_type, base, "contentType"sv, + "The Encoding Object content type must be a string"); + } + + const auto *headers{value.try_at("headers", OPENAPI_HASH_HEADERS)}; + if (headers != nullptr) { + const auto location{openapi_child(base, "headers"sv)}; + openapi_expect_object(*headers, location, + "The Encoding Object headers must be an object"); + for (const auto &entry : headers->as_object()) { + openapi_check_header_or_reference( + entry.second, openapi_child(location, entry.first), walk); + } + } + + const auto *style{value.try_at("style", OPENAPI_HASH_STYLE)}; + if (style != nullptr) { + openapi_expect_enumeration( + *style, base, "style"sv, + {"form"sv, "spaceDelimited"sv, "pipeDelimited"sv, "deepObject"sv}, + "The Encoding Object style must be a string", + "The Encoding Object style is not one this specification defines"); + } + + const auto *explode{value.try_at("explode", OPENAPI_HASH_EXPLODE)}; + if (explode != nullptr) { + openapi_expect_boolean(*explode, base, "explode"sv, + "The Encoding Object explode must be a boolean"); + } + + const auto *allow_reserved{ + value.try_at("allowReserved", OPENAPI_HASH_ALLOW_RESERVED)}; + if (allow_reserved != nullptr) { + openapi_expect_boolean( + *allow_reserved, base, "allowReserved"sv, + "The Encoding Object allowReserved must be a boolean"); + } +} + +// OpenAPI Specification 3.1.1, Section 4.8.14: "Each Media Type Object +// provides schema and examples for the media type identified by its key" +inline auto openapi_check_media_type(const JSON &value, const Pointer &base, + OpenAPIWalk &walk) -> void { + openapi_record(walk, base, OpenAPIObjectKind::MediaType); + openapi_expect_object(value, base, "The Media Type Object must be an object"); + openapi_reject_unknown_fields( + value, OPENAPI_MEDIA_TYPE_FIELDS, base, + "The Media Type Object does not define this field"); + + const auto *schema{value.try_at("schema", OPENAPI_HASH_SCHEMA)}; + if (schema != nullptr) { + openapi_expect_schema(*schema, openapi_child(base, "schema"sv), + "A Schema Object must be an object or a boolean", + walk); + } + + openapi_check_examples( + value, base, + "The Media Type Object example and examples are mutually exclusive", + "The Media Type Object examples must be an object", walk); + + const auto *encoding{value.try_at("encoding", OPENAPI_HASH_ENCODING)}; + if (encoding != nullptr) { + const auto location{openapi_child(base, "encoding"sv)}; + openapi_expect_object(*encoding, location, + "The Media Type Object encoding must be an object"); + for (const auto &entry : encoding->as_object()) { + openapi_check_encoding(entry.second, openapi_child(location, entry.first), + walk); + } + } +} + +// The map that the Request Body, Response, Parameter and Header Objects all +// key by media type. Section 4.5 says those definitions "SHOULD be in +// compliance with RFC6838", so the keys carry no requirement to enforce +inline auto openapi_check_content(const JSON &value, const Pointer &location, + const char *type_message, OpenAPIWalk &walk) + -> void { + openapi_expect_object(value, location, type_message); + for (const auto &entry : value.as_object()) { + openapi_check_media_type(entry.second, openapi_child(location, entry.first), + walk); + } +} + +// OpenAPI Specification 3.1.1, Section 4.8.21: "Describes a single header for +// HTTP responses and for individual parts in `multipart` representations" +inline auto openapi_check_header(const JSON &value, const Pointer &base, + OpenAPIWalk &walk) -> void { + openapi_record(walk, base, OpenAPIObjectKind::Header); + openapi_expect_object(value, base, "The Header Object must be an object"); + + const auto *schema{value.try_at("schema", OPENAPI_HASH_SCHEMA)}; + const auto *content{value.try_at("content", OPENAPI_HASH_CONTENT)}; + + // OpenAPI Specification 3.1.1, Section 4.8.21: "The `schema` field and + // `content` field are mutually exclusive", and one of them has to be there + if (schema != nullptr && content != nullptr) { + throw OpenAPIError{ + base, "The Header Object schema and content are mutually exclusive"}; + } + + if (schema == nullptr && content == nullptr) { + throw OpenAPIError{base, + "The Header Object must declare a schema or a content"}; + } + + if (schema == nullptr) { + openapi_reject_unknown_fields( + value, OPENAPI_HEADER_CONTENT_FIELDS, base, + "The Header Object does not define this field"); + } else { + openapi_reject_unknown_fields( + value, OPENAPI_HEADER_SCHEMA_FIELDS, base, + "The Header Object does not define this field"); + } + + const auto *description{ + value.try_at("description", OPENAPI_HASH_DESCRIPTION)}; + if (description != nullptr) { + openapi_expect_string(*description, base, "description"sv, + "The Header Object description must be a string"); + } + + const auto *required{value.try_at("required", OPENAPI_HASH_REQUIRED)}; + if (required != nullptr) { + openapi_expect_boolean(*required, base, "required"sv, + "The Header Object required must be a boolean"); + } + + const auto *deprecated{value.try_at("deprecated", OPENAPI_HASH_DEPRECATED)}; + if (deprecated != nullptr) { + openapi_expect_boolean(*deprecated, base, "deprecated"sv, + "The Header Object deprecated must be a boolean"); + } + + if (content != nullptr) { + const auto location{openapi_child(base, "content"sv)}; + openapi_check_content(*content, location, + "The Header Object content must be an object", walk); + // The meta-schema bounds this map at one entry in both directions + if (content->size() != 1) { + throw OpenAPIError{ + location, "The Header Object content must hold exactly one entry"}; + } + + return; + } + + openapi_expect_schema(*schema, openapi_child(base, "schema"sv), + "A Schema Object must be an object or a boolean", walk); + + // OpenAPI Specification 3.1.1, Section 4.8.21 fixes the only style a Header + // Object may name + const auto *style{value.try_at("style", OPENAPI_HASH_STYLE)}; + if (style != nullptr) { + openapi_expect_enumeration(*style, base, "style"sv, {"simple"sv}, + "The Header Object style must be a string", + "The Header Object style must be simple"); + } + + const auto *explode{value.try_at("explode", OPENAPI_HASH_EXPLODE)}; + if (explode != nullptr) { + openapi_expect_boolean(*explode, base, "explode"sv, + "The Header Object explode must be a boolean"); + } + + openapi_check_examples( + value, base, + "The Header Object example and examples are mutually exclusive", + "The Header Object examples must be an object", walk); +} + +inline auto openapi_check_header_or_reference(const JSON &value, + const Pointer &base, + OpenAPIWalk &walk) -> void { + if (openapi_is_reference(value)) { + openapi_check_reference(value, base, OpenAPIObjectKind::Header, walk); + return; + } + + openapi_check_header(value, base, walk); +} + +} // namespace sourcemeta::core + +#endif diff --git a/src/core/openapi/document.h b/src/core/openapi/document.h new file mode 100644 index 000000000..753a00b4e --- /dev/null +++ b/src/core/openapi/document.h @@ -0,0 +1,493 @@ +#ifndef SOURCEMETA_CORE_OPENAPI_DOCUMENT_H_ +#define SOURCEMETA_CORE_OPENAPI_DOCUMENT_H_ + +#include + +#include + +#include "components.h" +#include "content.h" +#include "example.h" +#include "external_documentation.h" +#include "helpers.h" +#include "info.h" +#include "link.h" +#include "parameter.h" +#include "path_item.h" +#include "paths.h" +#include "reference.h" +#include "request_body.h" +#include "response.h" +#include "security.h" +#include "server.h" +#include "tag.h" + +#include // std::array +#include // std::optional +#include // std::string_view +#include // std::move, std::swap, std::unreachable + +namespace sourcemeta::core { + +// OpenAPI Specification 3.1.1, Section 4.10: "The OpenAPI Schema Object +// dialect for this version of the specification is identified by the URI +// `https://spec.openapis.org/oas/3.1/dialect/base`" +constexpr auto OPENAPI_DIALECT{ + "https://spec.openapis.org/oas/3.1/dialect/base"sv}; + +constexpr auto OPENAPI_HASH_OPENAPI{JSON::Object::hash("openapi"sv)}; +constexpr auto OPENAPI_HASH_JSON_SCHEMA_DIALECT{ + JSON::Object::hash("jsonSchemaDialect"sv)}; + +constexpr std::array OPENAPI_ROOT_FIELDS{ + {"openapi"sv, "info"sv, "jsonSchemaDialect"sv, "servers"sv, "paths"sv, + "webhooks"sv, "components"sv, "security"sv, "tags"sv, "externalDocs"sv}}; + +inline auto openapi_check_document(const JSON &document, OpenAPIWalk &walk) + -> void; + +// OpenAPI Specification 3.1.1, Section 3 lists five ways an implementation MAY +// tell what a referenced document is, and this takes the second of them, +// "Detecting OpenAPI documents through the root `openapi` field". A document +// without one may be a bare holder of referenceable Objects or a Schema +// Object, and 3.1 gives no ground to turn either down +inline auto openapi_is_document(const JSON &document) -> bool { + return document.is_object() && + document.try_at("openapi", OPENAPI_HASH_OPENAPI) != nullptr; +} + +// Check a document whose root is the Object a reference expected to find +// there, rather than a whole OpenAPI Description +// The base pointer is where the Object sits in the document being read, which +// is the root for a document a reference brought in, and the pointer a +// reference names for one that stays within the document at hand. Getting that +// right is what keeps reading the same Object twice from recording it twice +inline auto openapi_check_object(const OpenAPIObjectKind expected, + const JSON &value, const Pointer &base, + OpenAPIWalk &walk) -> void { + switch (expected) { + case OpenAPIObjectKind::Document: + openapi_check_document(value, walk); + return; + case OpenAPIObjectKind::PathItem: + openapi_check_path_item(value, base, walk); + return; + case OpenAPIObjectKind::Parameter: + openapi_check_parameters_entry(value, base, walk); + return; + case OpenAPIObjectKind::RequestBody: + openapi_check_request_body_or_reference(value, base, walk); + return; + case OpenAPIObjectKind::Response: + openapi_check_response_or_reference(value, base, walk); + return; + case OpenAPIObjectKind::Example: + openapi_check_example_or_reference(value, base, walk); + return; + case OpenAPIObjectKind::Header: + openapi_check_header_or_reference(value, base, walk); + return; + case OpenAPIObjectKind::Link: + openapi_check_link_or_reference(value, base, walk); + return; + case OpenAPIObjectKind::Callbacks: + openapi_check_callbacks_or_reference(value, base, walk); + return; + case OpenAPIObjectKind::SecurityScheme: + openapi_check_security_scheme_or_reference(value, base, walk); + return; + // Not what a `$ref` ever expects, but what a Link Object's + // `operationRef` always does + case OpenAPIObjectKind::Operation: + openapi_check_operation(value, base, walk); + return; + default: + // No other kind is ever what a reference expects to find + std::unreachable(); + } +} + +// A reference that stays within the document it was written in. There is +// nothing to resolve, since the document is already at hand, but the fragment +// still has to land on something and what it lands on still has to be the +// Object the reference position expects +inline auto openapi_follow_internal_reference(const URI &target, + const bool names_a_fragment, + const OpenAPIObjectKind expected, + OpenAPIWalk &walk) -> void { + // A reference naming no fragment names the document itself, which is an + // OpenAPI Description and has already been read + if (!names_a_fragment || walk.document == nullptr) { + return; + } + + // OpenAPI Specification 3.1.1, Section 4.6: "If the representation of the + // referenced document is JSON or YAML, then the fragment identifier SHOULD + // be interpreted as a JSON-Pointer as per RFC6901". That is a SHOULD rather + // than a MUST, so a fragment shaped like anything else is left alone + const auto pointer{fragment_to_pointer(target)}; + if (!pointer.has_value()) { + return; + } + + // A reference may point at another reference, and round again, so where this + // has already been is remembered by the whole URI rather than by the + // document alone + if (!walk.visited.insert({target.recompose(), expected}).second) { + return; + } + + // Section 4.8.11 requires a `$ref` to "be in the form of a URI" and says + // nothing about it having to resolve, so a fragment that lands on nothing is + // not an error this specification lets us report. The reference is recorded + // either way, and a frame holding one that lands nowhere does not stand + // alone + const auto *destination{try_get(*walk.document, pointer.value())}; + if (destination == nullptr) { + return; + } + + // What it lands on has to hold up as the Object the reference expected, + // which is what catches a reference pointing at the wrong kind of thing + openapi_check_object(expected, *destination, pointer.value(), walk); +} + +inline auto openapi_follow_reference(const JSON::StringView reference, + const Pointer &origin, + const OpenAPIObjectKind expected, + OpenAPIWalk &walk) -> void { + std::optional target; + try { + target.emplace(JSON::String{reference}); + if (!walk.base.empty()) { + target.value().resolve_from(URI{walk.base}); + } + + // Canonicalising here is what makes two spellings of one document one + // document, both to the resolver that is asked for it and to the set that + // remembers it was already read + target.value().canonicalize(); + + } catch (const URIParseError &) { + // A reference that does not parse was already turned down where it was + // read, so reaching here means it cannot name another document either + return; + } + + const auto identifier{target.value().recompose_without_fragment()}; + const auto names_a_fragment{target.value().fragment().has_value()}; + + // Recorded before anything is decided about it, so that a reference nobody + // follows is still one the description makes. It is keyed by the Object that + // makes it rather than by its `$ref` member, so that where a reference comes + // from is a location like any other, while an error still points at the + // member that holds the problem + walk.references.insert_or_assign( + openapi_location_uri(walk.base, origin.initial()), + OpenAPIReference{.original = JSON::String{reference}, + .destination = target.value().recompose()}); + + // Section 4.8.9, of a Path Item Object's `$ref`: "the referenced structure + // MUST be in the form of a Path Item Object", and Section 4.8.20, of a Link + // Object's `operationRef`: it "MUST point to an Operation Object". A + // document that declares a root `openapi` field is an OpenAPI Description + // and neither of those, so a reference from one of those two positions that + // names such a document whole has landed on the wrong thing. Section 3's + // detection settles how a document is read, which is a separate question + // from whether a reference was allowed to point at it. A Reference Object is + // held to no such requirement, so nothing here applies to one + const auto demands_its_own_kind{expected == OpenAPIObjectKind::PathItem || + expected == OpenAPIObjectKind::Operation}; + + // A reference that keeps the document it was written in needs no resolver, + // however it was spelled. Both a bare fragment and the document's own URI + // written out in full land here + if (!identifier.has_value() || identifier.value() == walk.base) { + if (demands_its_own_kind && !names_a_fragment && walk.document != nullptr && + openapi_is_document(*walk.document)) { + throw OpenAPIError{walk.base, origin, + "This reference must name a document that holds only " + "what the reference expects"}; + } + + openapi_follow_internal_reference(target.value(), names_a_fragment, + expected, walk); + return; + } + + // Only a reference that leaves the document needs one, so this is the first + // point where a resolver that was never supplied matters. Going without one + // is the caller opting out of a description that spans more than one + if (!walk.resolver) { + return; + } + + // Whether this document has been read already has to be settled before the + // resolver is asked for it, so that a reference coming back round to one we + // hold is not put to a resolver that has never heard of it. Which of the two + // keys it landed under is not known yet, so both are consulted + if (demands_its_own_kind && !names_a_fragment && + walk.visited.contains( + {identifier.value(), OpenAPIObjectKind::Document})) { + throw OpenAPIError{walk.base, origin, + "This reference must name a document that holds only " + "what the reference expects"}; + } + + // A document is read once, but a second reference into it names a fragment + // of its own, and that fragment has been checked by nobody. Section 3 has + // the whole document parsed rather than the fragment, which already + // happened, so what is left is the question an internal reference asks + if (names_a_fragment && + walk.visited.contains( + {identifier.value(), OpenAPIObjectKind::Document})) { + const auto known{walk.documents_by_uri.find(identifier.value())}; + if (known != walk.documents_by_uri.cend()) { + auto base{identifier.value()}; + const auto *document{known->second}; + bool entry{false}; + + // Section 4.10 scopes `jsonSchemaDialect` to "all Schema Objects + // contained within an OAS document", so a Schema Object inside this + // fragment hands on the dialect of the document it sits in rather than + // the one in force where the reference was written. Reading that + // document again would settle it, and it was read once already, so what + // it settled on is taken from the location of its root + const auto root{walk.locations.find(identifier.value())}; + auto dialect{root == walk.locations.cend() ? walk.dialect + : root->second.dialect}; + std::swap(walk.dialect, dialect); + std::swap(walk.base, base); + std::swap(walk.document, document); + std::swap(walk.entry, entry); + openapi_follow_internal_reference(target.value(), true, expected, walk); + std::swap(walk.base, base); + std::swap(walk.document, document); + std::swap(walk.entry, entry); + std::swap(walk.dialect, dialect); + } + + return; + } + + if (walk.visited.contains( + {identifier.value(), OpenAPIObjectKind::Document}) || + walk.visited.contains({identifier.value(), expected})) { + return; + } + + // A document nobody can hand us is not an error either, for the same reason. + // Going without part of a description is the caller's business, and the + // frame reports that it does not stand alone + auto resolved{walk.resolver(identifier.value())}; + if (!resolved.has_value()) { + return; + } + + // The resolver may have handed back a document it owns, which reading it + // would otherwise be the last thing to happen to + const auto &contents{ + walk.documents.emplace_back(std::move(resolved)).value()}; + walk.documents_by_uri.insert_or_assign(identifier.value(), &contents); + + // Section 3 lists five ways to tell what a referenced document is. A root + // `openapi` field settles it whatever the reference expected, and otherwise + // the expected type stands in, which Section 3 also allows + const auto document_kind{ + openapi_is_document(contents) ? OpenAPIObjectKind::Document : expected}; + + if (demands_its_own_kind && !names_a_fragment && + document_kind == OpenAPIObjectKind::Document) { + throw OpenAPIError{walk.base, origin, + "This reference must name a document that holds only " + "what the reference expects"}; + } + + walk.visited.insert({identifier.value(), document_kind}); + + // Section 3 requires complete documents to be parsed rather than the + // fragment a reference points at, as "the result of parsing fragments in + // isolation is undefined". A reference naming a fragment of a document that + // is not an OpenAPI Description is asking for exactly that, so it is left + // alone rather than read as something it may not be + if (document_kind != OpenAPIObjectKind::Document && names_a_fragment) { + return; + } + + auto base{identifier.value()}; + const auto *document{&contents}; + bool entry{false}; + + // Section 4.10 sets `jsonSchemaDialect` "within the OpenAPI Object", and + // "if this default is not set, then the OAS dialect schema id MUST be used". + // A document holding a referenceable Object rather than a Description has no + // OpenAPI Object and so sets none, which makes the OAS dialect the one in + // force for its Schema Objects rather than whatever the referring document + // happened to declare. A document that is a Description settles its own + // below, when it is read + auto dialect{document_kind == OpenAPIObjectKind::Document + ? walk.dialect + : JSON::String{OPENAPI_DIALECT}}; + std::swap(walk.dialect, dialect); + std::swap(walk.base, base); + std::swap(walk.document, document); + std::swap(walk.entry, entry); + openapi_check_object(document_kind, contents, EMPTY_POINTER, walk); + + // A reference into a document that turned out to be a whole Description has + // had that Description read, which is what Section 3 asks for, but nothing + // has yet asked whether the fragment lands on the Object the reference + // expected. That is the same question an internal reference answers, so it + // is answered the same way, now that the document being read is the one the + // fragment belongs to + if (names_a_fragment && document_kind == OpenAPIObjectKind::Document) { + openapi_follow_internal_reference(target.value(), true, expected, walk); + } + + std::swap(walk.base, base); + std::swap(walk.document, document); + std::swap(walk.entry, entry); + std::swap(walk.dialect, dialect); +} + +// OpenAPI Specification 3.1.1, Section 4.8.1: "This is the root object of the +// OpenAPI Description" +inline auto openapi_check_document(const JSON &document, OpenAPIWalk &walk) + -> void { + try { + // Section 4.2: "An OpenAPI Document that conforms to the OpenAPI + // Specification is itself a JSON object" + if (!document.is_object()) { + throw OpenAPIError{EMPTY_POINTER, + "The OpenAPI Description must be an object"}; + } + + // Section 4.8.1: "openapi | string | REQUIRED" + const auto *version{document.try_at("openapi", OPENAPI_HASH_OPENAPI)}; + if (version == nullptr) { + throw OpenAPIError{EMPTY_POINTER, + "The OpenAPI Description must declare its version"}; + } + + if (!version->is_string()) { + throw OpenAPIError{Pointer{"openapi"}, + "The OpenAPI version must be a string"}; + } + + if (!openapi_version(document).has_value()) { + throw OpenAPIError{Pointer{"openapi"}, + "Unsupported OpenAPI Specification version"}; + } + + // The version comes before the field table on purpose, so that a document + // of another OpenAPI revision is told what it is rather than being + // reported field by field + openapi_reject_unknown_fields( + document, OPENAPI_ROOT_FIELDS, EMPTY_POINTER, + "The OpenAPI Object does not define this field"); + + // Section 4.8.30: "The name used for each property MUST correspond to a + // security scheme declared in the Security Schemes under the Components + // Object". Section 3 leaves which document's Components Object that is to + // the implementation and says "it is RECOMMENDED that tools resolve from + // the entry document, rather than the current document", so the names come + // from there. They are taken before anything else is read, as a document + // a reference brings in may declare a requirement long before the entry + // document's own components have been walked + // Section 4.8.10 has an Operation Object's tags name Tag Objects "found + // under the root OpenAPI Object", and Section 3 recommends the entry + // document for the same reason it does for security schemes, so both sets + // of names come from there and both come before anything else is read + if (walk.entry) { + openapi_collect_security_schemes(document, walk); + openapi_collect_tags(document, walk); + } + + // Section 3: an OpenAPI Description "MUST contain at least one paths + // field, components field, or webhooks field" + if (document.try_at("paths", OPENAPI_HASH_PATHS) == nullptr && + document.try_at("components", OPENAPI_HASH_COMPONENTS) == nullptr && + document.try_at("webhooks", OPENAPI_HASH_WEBHOOKS) == nullptr) { + throw OpenAPIError{ + EMPTY_POINTER, + "The OpenAPI Description must declare paths, components or webhooks"}; + } + + // What the frame reports is what the entry document says, and an entry + // document whose title and version are both the empty string is a legal + // one, so which document this is has to be asked rather than inferred from + // the values already held + const auto info{openapi_parse_info(document, walk)}; + if (walk.entry) { + walk.info = info; + } + + // Section 4.8.1: "jsonSchemaDialect | string | The default value for the + // `$schema` keyword within Schema Objects [...] This MUST be in the form + // of a URI". It is checked rather than applied, as applying it is what + // framing a Schema Object will do + const auto *dialect{ + document.try_at("jsonSchemaDialect", OPENAPI_HASH_JSON_SCHEMA_DIALECT)}; + if (dialect != nullptr) { + openapi_expect_uri_reference( + *dialect, EMPTY_POINTER, "jsonSchemaDialect"sv, + "The OpenAPI dialect must be a string", + "The OpenAPI dialect must be a URI reference"); + } + + // Section 4.10: "To allow use of a different default `$schema` value for + // all Schema Objects contained within an OAS document, a + // `jsonSchemaDialect` value may be set within the OpenAPI Object. If this + // default is not set, then the OAS dialect schema id MUST be used". What a + // Schema Object says about itself overrides this, which is a matter for + // whatever reads inside one + JSON::String effective_dialect{OPENAPI_DIALECT}; + if (dialect != nullptr) { + // Already checked above, so this parses + URI resolved{dialect->to_string()}; + if (!walk.base.empty()) { + resolved.resolve_from(URI{walk.base}); + } + + resolved.canonicalize(); + effective_dialect = resolved.recompose(); + } + + walk.dialect = effective_dialect; + openapi_record(walk, EMPTY_POINTER, OpenAPIObjectKind::Document, + std::move(effective_dialect)); + + openapi_check_servers(document, walk); + openapi_check_root_external_documentation(document, walk); + openapi_check_tags(document, walk); + openapi_check_components(document, walk); + openapi_check_paths(document, walk); + openapi_check_webhooks(document, walk); + + const auto *security{document.try_at("security", OPENAPI_HASH_SECURITY)}; + if (security != nullptr) { + auto locations{openapi_check_security( + *security, Pointer{"security"}, + "The OpenAPI Description security must be an array", walk)}; + + // Section 4.8.10 has an Operation Object override "any declared + // top-level security", and Section 3 makes the entry document the one + // that describes the API, so this is the declaration it overrides + if (walk.entry) { + walk.security = std::move(locations); + } + } + } catch (const OpenAPIError &error) { + // Every check reports where in its own document the problem is, and this + // is the only place that knows which document that was + if (!error.base().empty() || walk.base.empty()) { + throw; + } + + throw OpenAPIError{walk.base, error.location(), error.what()}; + } +} + +} // namespace sourcemeta::core + +#endif diff --git a/src/core/openapi/example.h b/src/core/openapi/example.h new file mode 100644 index 000000000..bb7ebe167 --- /dev/null +++ b/src/core/openapi/example.h @@ -0,0 +1,106 @@ +#ifndef SOURCEMETA_CORE_OPENAPI_EXAMPLE_H_ +#define SOURCEMETA_CORE_OPENAPI_EXAMPLE_H_ + +#include + +#include "helpers.h" +#include "reference.h" + +#include // std::array +#include // std::string_view + +namespace sourcemeta::core { + +constexpr auto OPENAPI_HASH_VALUE{JSON::Object::hash("value"sv)}; +constexpr auto OPENAPI_HASH_EXTERNAL_VALUE{ + JSON::Object::hash("externalValue"sv)}; +constexpr auto OPENAPI_HASH_EXAMPLE{JSON::Object::hash("example"sv)}; +constexpr auto OPENAPI_HASH_EXAMPLES{JSON::Object::hash("examples"sv)}; + +constexpr std::array OPENAPI_EXAMPLE_FIELDS{ + {"summary"sv, "description"sv, "value"sv, "externalValue"sv}}; + +// OpenAPI Specification 3.1.1, Section 4.8.19: "An object grouping an internal +// or external example value with basic `summary` and `description` metadata" +inline auto openapi_check_example(const JSON &value, const Pointer &base, + OpenAPIWalk &walk) -> void { + openapi_record(walk, base, OpenAPIObjectKind::Example); + openapi_expect_object(value, base, "The Example Object must be an object"); + openapi_reject_unknown_fields( + value, OPENAPI_EXAMPLE_FIELDS, base, + "The Example Object does not define this field"); + + const auto *summary{value.try_at("summary", OPENAPI_HASH_SUMMARY)}; + if (summary != nullptr) { + openapi_expect_string(*summary, base, "summary"sv, + "The Example Object summary must be a string"); + } + + const auto *description{ + value.try_at("description", OPENAPI_HASH_DESCRIPTION)}; + if (description != nullptr) { + openapi_expect_string(*description, base, "description"sv, + "The Example Object description must be a string"); + } + + // OpenAPI Specification 3.1.1, Section 4.8.19: "externalValue | string | A + // URI that identifies the literal example" + const auto *external{ + value.try_at("externalValue", OPENAPI_HASH_EXTERNAL_VALUE)}; + if (external != nullptr) { + openapi_expect_uri_reference( + *external, base, "externalValue"sv, + "The Example Object external value must be a string", + "The Example Object external value must be a URI reference"); + } + + // OpenAPI Specification 3.1.1, Section 4.8.19: "The `value` field and + // `externalValue` field are mutually exclusive". Any JSON value is a legal + // `value`, so there is nothing else to check about it + if (external != nullptr && + value.try_at("value", OPENAPI_HASH_VALUE) != nullptr) { + throw OpenAPIError{ + base, "The Example Object value and external value are mutually " + "exclusive"}; + } +} + +inline auto openapi_check_example_or_reference(const JSON &value, + const Pointer &base, + OpenAPIWalk &walk) -> void { + if (openapi_is_reference(value)) { + openapi_check_reference(value, base, OpenAPIObjectKind::Example, walk); + return; + } + + openapi_check_example(value, base, walk); +} + +// The Parameter, Media Type and Header Objects all carry this pair, and all +// three state that "The `example` field is mutually exclusive of the +// `examples` field" +inline auto openapi_check_examples(const JSON &value, const Pointer &base, + const char *exclusive_message, + const char *type_message, OpenAPIWalk &walk) + -> void { + const auto *single{value.try_at("example", OPENAPI_HASH_EXAMPLE)}; + const auto *multiple{value.try_at("examples", OPENAPI_HASH_EXAMPLES)}; + if (single != nullptr && multiple != nullptr) { + throw OpenAPIError{base, exclusive_message}; + } + + if (multiple == nullptr) { + return; + } + + const auto location{openapi_child(base, "examples"sv)}; + openapi_expect_object(*multiple, location, type_message); + for (const auto &entry : multiple->as_object()) { + openapi_check_example_or_reference( + entry.second, openapi_child(location, entry.first), walk); + } +} + +} // namespace sourcemeta::core + +#endif diff --git a/src/core/openapi/external_documentation.h b/src/core/openapi/external_documentation.h new file mode 100644 index 000000000..60c20d9c1 --- /dev/null +++ b/src/core/openapi/external_documentation.h @@ -0,0 +1,73 @@ +#ifndef SOURCEMETA_CORE_OPENAPI_EXTERNAL_DOCUMENTATION_H_ +#define SOURCEMETA_CORE_OPENAPI_EXTERNAL_DOCUMENTATION_H_ + +#include + +#include "helpers.h" + +#include // std::array +#include // std::string_view + +namespace sourcemeta::core { + +constexpr auto OPENAPI_HASH_EXTERNAL_DOCS{JSON::Object::hash("externalDocs"sv)}; + +constexpr std::array OPENAPI_EXTERNAL_DOCS_FIELDS{ + {"description"sv, "url"sv}}; + +// OpenAPI Specification 3.1.1, Section 4.8.11: "Allows referencing an external +// resource for extended documentation" +inline auto openapi_check_external_documentation(const JSON &value, + const Pointer &base, + OpenAPIWalk &walk) -> void { + openapi_record(walk, base, OpenAPIObjectKind::ExternalDocumentation); + if (!value.is_object()) { + throw OpenAPIError{base, + "The External Documentation Object must be an object"}; + } + + openapi_reject_unknown_fields( + value, OPENAPI_EXTERNAL_DOCS_FIELDS, base, + "The External Documentation Object does not define this field"); + + // OpenAPI Specification 3.1.1, Section 4.8.11: "url | string | REQUIRED. The + // URI for the target documentation. This MUST be in the form of a URI". + // Unlike the Server Object, this one does state a requirement on its form, + // and it names no template variables, so it is checked + const auto *url{value.try_at("url", OPENAPI_HASH_URL)}; + if (url == nullptr) { + throw OpenAPIError{base, + "The External Documentation Object must declare a URI"}; + } + + openapi_expect_uri_reference( + *url, base, "url"sv, + "The External Documentation Object URI must be a string", + "The External Documentation Object URI must be a URI reference"); + + const auto *description{ + value.try_at("description", OPENAPI_HASH_DESCRIPTION)}; + if (description != nullptr) { + openapi_expect_string( + *description, base, "description"sv, + "The External Documentation Object description must be a string"); + } +} + +// OpenAPI Specification 3.1.1, Section 4.8.1: "externalDocs | External +// Documentation Object | Additional external documentation" +inline auto openapi_check_root_external_documentation(const JSON &document, + OpenAPIWalk &walk) + -> void { + const auto *value{ + document.try_at("externalDocs", OPENAPI_HASH_EXTERNAL_DOCS)}; + if (value == nullptr) { + return; + } + + openapi_check_external_documentation(*value, Pointer{"externalDocs"}, walk); +} + +} // namespace sourcemeta::core + +#endif diff --git a/src/core/openapi/frame.cc b/src/core/openapi/frame.cc new file mode 100644 index 000000000..53e88d37f --- /dev/null +++ b/src/core/openapi/frame.cc @@ -0,0 +1,617 @@ +#include + +#include + +#include "document.h" +#include "helpers.h" +#include "info.h" + +#include // std::ranges::all_of +#include // std::size_t +#include // std::deque +#include // std::map +#include // std::make_unique +#include // std::optional +#include // std::set +#include // std::string_view +#include // std::move, std::pair, std::unreachable +#include // std::vector + +namespace { +using namespace std::string_view_literals; + +// The document a location sits in is the key up to its fragment, which is why +// nothing repeats it on the entry itself. A parent is given as one of these +// keys rather than as a bare pointer, so that following it is a lookup in the +// same map rather than a key the reader has to rebuild +auto document_of(const sourcemeta::core::JSON::String &uri) + -> sourcemeta::core::JSON::String { + const auto fragment{uri.find('#')}; + return fragment == sourcemeta::core::JSON::String::npos + ? uri + : uri.substr(0, fragment); +} + +auto parent_of(const std::map &locations, + const sourcemeta::core::JSON::String &uri, + const sourcemeta::core::OpenAPILocation &location) + -> sourcemeta::core::JSON { + if (location.pointer.empty()) { + return sourcemeta::core::JSON{nullptr}; + } + + const auto document{document_of(uri)}; + auto pointer{location.pointer}; + while (!pointer.empty()) { + pointer.pop_back(); + auto candidate{sourcemeta::core::openapi_location_uri(document, pointer)}; + if (locations.contains(candidate)) { + return sourcemeta::core::JSON{std::move(candidate)}; + } + } + + return sourcemeta::core::JSON{document}; +} + +// Where a problem found once the walk is over belongs. A location says which +// document it is in and where in it, and a field hangs off that when the +// problem is with one rather than with the Object holding it +auto error_at(const sourcemeta::core::OpenAPIWalk &walk, + const sourcemeta::core::JSON::String &location, + const char *message, + const sourcemeta::core::JSON::StringView field = {}) + -> sourcemeta::core::OpenAPIError { + const auto match{walk.locations.find(location)}; + auto pointer{match == walk.locations.cend() ? sourcemeta::core::EMPTY_POINTER + : match->second.pointer}; + if (!field.empty()) { + pointer = pointer.concat(sourcemeta::core::JSON::String{field}); + } + + return {document_of(location), std::move(pointer), message}; +} + +// OpenAPI Specification 3.1.1, Section 4.8.20: "The identified or reference +// operation MUST be unique, and in the case of an `operationId`, it MUST be +// resolved within the scope of the OpenAPI Description". Section 3 recommends +// resolving one "considering all Operation Objects from all parsed documents", +// which is the set the walk already keeps for the uniqueness requirement +auto check_operation_id_links(const sourcemeta::core::OpenAPIWalk &walk) + -> void { + for (const auto &[location, identifier] : walk.operation_id_links) { + // Section 4.8.20 goes on to say that an operation reached through a Path + // Item referenced more than once "cannot be resolved unambiguously", and + // that "in such ambiguous cases, the resulting behavior is + // implementation-defined and MAY result in an error". So naming nothing at + // all is the violation, and naming something twice over is not + if (!walk.operation_ids.contains(identifier)) { + throw error_at(walk, location, + "The Link Object operation identifier must name an " + "operation the OpenAPI Description declares", + "operationId"); + } + } +} + +auto optional_string( + const std::optional &value) + -> sourcemeta::core::JSON { + return value.has_value() ? sourcemeta::core::JSON{value.value()} + : sourcemeta::core::JSON{nullptr}; +} + +auto info_json(const sourcemeta::core::OpenAPIInfo &info) + -> sourcemeta::core::JSON { + auto result{sourcemeta::core::JSON::make_object()}; + result.assign_assume_new("title", sourcemeta::core::JSON{info.title}); + result.assign_assume_new("version", sourcemeta::core::JSON{info.version}); + result.assign_assume_new("summary", optional_string(info.summary)); + result.assign_assume_new("description", optional_string(info.description)); + result.assign_assume_new("termsOfService", + optional_string(info.terms_of_service)); + + if (info.contact.has_value()) { + auto contact{sourcemeta::core::JSON::make_object()}; + contact.assign_assume_new("name", optional_string(info.contact->name)); + contact.assign_assume_new("url", optional_string(info.contact->url)); + contact.assign_assume_new("email", optional_string(info.contact->email)); + result.assign_assume_new("contact", std::move(contact)); + } else { + result.assign_assume_new("contact", sourcemeta::core::JSON{nullptr}); + } + + if (info.license.has_value()) { + auto license{sourcemeta::core::JSON::make_object()}; + license.assign_assume_new("name", + sourcemeta::core::JSON{info.license->name}); + license.assign_assume_new("identifier", + optional_string(info.license->identifier)); + license.assign_assume_new("url", optional_string(info.license->url)); + result.assign_assume_new("license", std::move(license)); + } else { + result.assign_assume_new("license", sourcemeta::core::JSON{nullptr}); + } + + return result; +} + +auto version_string(const sourcemeta::core::OpenAPIVersion version) + -> sourcemeta::core::JSON::StringView { + switch (version) { + // OpenAPI Specification 3.1.1, Section 4.1: "The `major`.`minor` portion + // of the version string (for example `3.1`) SHALL designate the OAS + // feature set" + case sourcemeta::core::OpenAPIVersion::OPENAPI_3_1: + return "3.1"sv; + } + + std::unreachable(); +} + +// OpenAPI Specification 3.1.1, Section 4.6 determines a document's base URI +// "in accordance with RFC3986 Section 5.1.2 - 5.1.4", a range that starts at +// 5.1.2 and so leaves out 5.1.1, "Base URI Embedded in Content". A 3.1 +// document therefore has no way of declaring its own base, and what remains is +// 5.1.3, "Base URI from the Retrieval URI", which only the caller can supply. +// Section 4.6 says as much: implementations "SHOULD allow users to provide +// documents with their intended retrieval URIs" +auto canonical_base(const std::string_view input) + -> sourcemeta::core::JSON::String { + if (input.empty()) { + return {}; + } + + std::optional base; + try { + base.emplace(input); + } catch (const sourcemeta::core::URIParseError &) { + base.reset(); + } + + // RFC 3986 Section 5.2.1: "only the scheme component is required to be + // present in a base URI". Anything without one cannot resolve a reference + // RFC 3986 Section 5.2.2 resolves a reference against a base's scheme, + // authority, path and query, and never against its fragment, so a fragment + // is no part of what a base is. Keeping one would also put two of them in + // every location this frame reports + if (base.has_value() && base.value().scheme().has_value()) { + base.value().canonicalize(); + const auto result{base.value().recompose_without_fragment()}; + if (result.has_value()) { + return result.value(); + } + } + + throw sourcemeta::core::OpenAPIError{ + sourcemeta::core::EMPTY_POINTER, + "The OpenAPI Description base must be a URI with a scheme"}; +} + +// A Reference Object, and a Path Item Object that declares a `$ref`, stand in +// for what they lead to. OpenAPI Specification 3.1.1, Section 4.8.9 has `$ref` +// "allow for a referenced definition of this path item" and leaves what a +// sibling field means undefined, so the definition is what the reference leads +// to rather than anything written alongside it +auto follow_aliases(const sourcemeta::core::OpenAPIWalk &walk, + const sourcemeta::core::JSON::String &position) + -> sourcemeta::core::JSON::String { + return sourcemeta::core::openapi_resolve_position(walk, position); +} + +auto identity_of(const sourcemeta::core::OpenAPIWalk &walk, + const sourcemeta::core::JSON::String &position) + -> const std::pair * { + return sourcemeta::core::openapi_parameter_identity(walk, position); +} + +// The parameters in force where an operation sits. Section 4.8.9 has the ones +// a Path Item Object declares "applicable for all the operations described +// under this path. These parameters can be overridden at the operation level, +// but cannot be removed there", so what the Path Item declares stands unless +// the operation declares one of the same name and location +auto parameters_of(const sourcemeta::core::OpenAPIWalk &walk, + const std::vector &operation, + const std::vector &path_item) + -> std::vector { + std::vector result; + result.reserve(operation.size() + path_item.size()); + std::set< + std::pair> + claimed; + for (const auto &position : operation) { + const auto *identity{identity_of(walk, position)}; + if (identity != nullptr) { + claimed.insert(*identity); + } + } + + for (const auto &position : path_item) { + // A Reference Object that was never followed names no parameter, so + // nothing can be said to override it + const auto *identity{identity_of(walk, position)}; + if (identity == nullptr || !claimed.contains(*identity)) { + result.push_back(position); + } + } + + result.insert(result.cend(), operation.cbegin(), operation.cend()); + return result; +} + +// Which Tag Object each of an operation's tags names. Section 3 lists this +// among the connections a description makes by name rather than by pointer, +// and it is the one that carries no requirement, since Section 4.8.1 says +// "not all tags that are used by the Operation Object must be declared". So a +// name nothing declares is reported rather than turned down +auto tags_of(const sourcemeta::core::OpenAPIWalk &walk, + const std::vector &names) + -> std::vector> { + std::vector> result; + result.reserve(names.size()); + for (const auto &name : names) { + const auto match{walk.tags.find(name)}; + result.push_back(match == walk.tags.cend() ? std::nullopt + : std::optional{match->second}); + } + + return result; +} + +// The servers in force where an operation sits. OpenAPI Specification 3.1.1, +// Section 4.8.10 has an Operation Object's servers override those of "the Path +// Item Object or OpenAPI Object level", and Section 4.8.1 makes an empty array +// there stand for none being given at all, so an empty array carries on up +auto servers_of(const std::vector &operation, + const std::vector &path_item, + const std::vector &document) + -> std::vector { + if (!operation.empty()) { + return operation; + } + + return path_item.empty() ? document : path_item; +} + +// OpenAPI Specification 3.1.1, Section 4.8.12, of a parameter whose location +// is `path`: its "`name` field MUST correspond to a template expression +// occurring within the path field in the Paths Object". This direction holds +// wherever such a parameter is written, including a Path Item Object that +// declares no operation at all +auto check_path_parameters( + const sourcemeta::core::OpenAPIWalk &walk, + const std::vector &templates, + const std::vector ¶meters) -> void { + for (const auto &position : parameters) { + const auto *identity{identity_of(walk, position)}; + // A Reference Object that was never followed names no parameter, so + // nothing can be said about what it corresponds to + if (identity == nullptr || identity->second != "path") { + continue; + } + + if (std::ranges::find(templates, identity->first) == templates.cend()) { + throw error_at(walk, position, + "A path Parameter Object must name a template expression " + "of the path it is under"); + } + } +} + +// Section 4.3, the other direction: "Each template expression in the path MUST +// correspond to a path parameter that is included in the Path Item itself +// and/or in each of the Path Item's Operations". So this holds of the +// parameters in force for one operation rather than of either level alone, +// and Section 4.3 excuses an empty Path Item from it, which is why nothing +// checks it until there is an operation to check +auto check_path_templates( + const sourcemeta::core::OpenAPIWalk &walk, + const sourcemeta::core::JSON::String &endpoint, + const std::vector &templates, + const std::vector ¶meters) -> void { + std::set named; + for (const auto &position : parameters) { + const auto *identity{identity_of(walk, position)}; + + // A reference the walk could not follow may be the very parameter a + // template expression is looking for, and a description we do not hold in + // full is one we cannot call incomplete. This is the same restraint + // Section 8.7.1 applies to a Link Object's operation identifier + if (identity == nullptr) { + return; + } + + if (identity->second == "path") { + named.insert(identity->first); + } + } + + for (const auto &expression : templates) { + if (!named.contains(expression)) { + throw error_at(walk, endpoint, + "A templated path must declare a path parameter for each " + "of its template expressions"); + } + } +} + +// Every operation the description exposes, which Section 3 confines to what +// the entry document reaches: "only the entry document's Paths Object +// contributes URLs to the described API". A Callback Object holds Path Item +// Objects of its own, so what an endpoint reaches may expose further endpoints +auto project(const sourcemeta::core::OpenAPIWalk &walk) + -> std::vector { + std::vector result; + std::vector pending{walk.endpoints}; + std::set seen; + for (std::size_t index = 0; index < pending.size(); index += 1) { + const auto kind{pending[index].kind}; + const auto path{pending[index].path}; + const auto endpoint{pending[index].path_item}; + auto position{follow_aliases(walk, endpoint)}; + + // A Path Item that leads back to one already exposed the same way exposes + // nothing further, which is what stops a cycle of them + sourcemeta::core::JSON::String key{ + sourcemeta::core::openapi_operation_kind_name(kind)}; + key.append("#").append(path).append("#").append(position); + if (!seen.insert(std::move(key)).second) { + continue; + } + + const auto entry{walk.path_items.find(position)}; + if (entry == walk.path_items.cend()) { + continue; + } + + // A webhook name and a callback expression are not templated paths, so + // only what the Paths Object exposes has any templating to correspond to + const auto templated{kind == sourcemeta::core::OpenAPIOperationKind::Path}; + const auto templates{ + templated ? sourcemeta::core::openapi_path_templates(path) + : std::vector{}}; + if (templated) { + check_path_parameters(walk, templates, entry->second.parameters); + } + + for (const auto &[method, origin] : entry->second.operations) { + const auto operation{walk.operation_records.find(origin)}; + if (operation == walk.operation_records.cend()) { + continue; + } + + auto parameters{parameters_of(walk, operation->second.parameters, + entry->second.parameters)}; + if (templated) { + check_path_parameters(walk, templates, parameters); + check_path_templates(walk, endpoint, templates, parameters); + } + + result.push_back( + {.kind = kind, + .path = path, + .method = method, + .origin = origin, + .endpoint = endpoint, + .servers = servers_of(operation->second.servers, + entry->second.servers, walk.servers), + // Section 4.8.10: "This definition overrides any declared top-level + // security. To remove a top-level security declaration, an empty + // array can be used", which is why declaring none and declaring an + // empty array are not the same thing here + .security = operation->second.security.has_value() + ? operation->second.security.value() + : walk.security.value_or( + std::vector{}), + .parameters = std::move(parameters), + .tags = tags_of(walk, operation->second.tags)}); + + for (const auto &callback : operation->second.callbacks) { + const auto entries{walk.callbacks.find(follow_aliases(walk, callback))}; + if (entries == walk.callbacks.cend()) { + continue; + } + + for (const auto &[expression, path_item] : entries->second) { + pending.push_back( + {.kind = sourcemeta::core::OpenAPIOperationKind::Callback, + .path = expression, + .path_item = path_item}); + } + } + } + } + + return result; +} + +auto analyse(const sourcemeta::core::JSON &document, + const sourcemeta::core::OpenAPIResolver &resolver, + sourcemeta::core::JSON::String base) + -> sourcemeta::core::OpenAPIWalk { + sourcemeta::core::OpenAPIWalk walk{.resolver = resolver, + .base = base, + .documents = {}, + .documents_by_uri = {}, + .document = &document, + .operation_ids = {}, + .visited = {}, + .locations = {}, + .references = {}, + .parameters = {}, + .path_items = {}, + .operation_records = {}, + .callbacks = {}, + .endpoints = {}, + .servers = {}, + .security = {}, + .security_schemes = {}, + .tags = {}, + .operation_id_links = {}, + .entry = true, + .dialect = {}, + .info = {}}; + // The entry document is one we already hold, so a reference that comes back + // round to it is not a document anybody needs to resolve + if (!base.empty()) { + walk.visited.insert( + {std::move(base), sourcemeta::core::OpenAPIObjectKind::Document}); + } + + sourcemeta::core::openapi_check_document(document, walk); + return walk; +} + +} // namespace + +namespace sourcemeta::core { + +struct OpenAPIFrame::Internal { + OpenAPIVersion version; + OpenAPIInfo info; + // Canonicalising means this no longer borrows from what the caller passed + JSON::String base; + // Held for as long as the frame is, as a resolver may hand back a document + // it owns and nothing else would keep it alive + std::deque documents; + bool standalone; + std::map locations; + std::map references; + std::vector operations; +}; + +OpenAPIFrame::OpenAPIFrame(const JSON &document, + const OpenAPIResolver &resolver, + const std::string_view default_base) + : internal_{std::make_unique()} { + auto base{canonical_base(default_base)}; + auto walk{analyse(document, resolver, base)}; + this->internal_->version = openapi_version(document).value(); + this->internal_->info = walk.info; + this->internal_->base = std::move(base); + // A frame stands alone when everything it references is inside it, which is + // what a caller asks before deciding whether it has the whole description + this->internal_->standalone = std::ranges::all_of( + walk.references, [&walk](const auto &reference) -> bool { + return walk.locations.contains(reference.second.destination); + }); + + // Section 3 has resolving a Link Object `operationId` require "parsing all + // referenced documents prior to determining an `operationId` to be + // unresolvable". A description we do not hold in full is one we cannot say + // that of, so a frame that does not stand alone says nothing here + if (this->internal_->standalone) { + check_operation_id_links(walk); + } + + // Projecting reads the whole walk, so nothing is taken out of it until after + this->internal_->operations = project(walk); + this->internal_->documents = std::move(walk.documents); + this->internal_->locations = std::move(walk.locations); + this->internal_->references = std::move(walk.references); +} + +OpenAPIFrame::~OpenAPIFrame() = default; + +auto OpenAPIFrame::version() const noexcept -> OpenAPIVersion { + return this->internal_->version; +} + +auto OpenAPIFrame::info() const noexcept -> const OpenAPIInfo & { + return this->internal_->info; +} + +auto OpenAPIFrame::base() const noexcept -> JSON::StringView { + return this->internal_->base; +} + +auto OpenAPIFrame::standalone() const noexcept -> bool { + return this->internal_->standalone; +} + +auto OpenAPIFrame::to_json() const -> JSON { + // Read through the accessors rather than the internal state, so that what + // this reports and what a caller can observe cannot drift apart + auto result{JSON::make_object()}; + result.assign_assume_new("version", JSON{version_string(this->version())}); + result.assign_assume_new("base", JSON{this->base()}); + result.assign_assume_new("standalone", JSON{this->standalone()}); + result.assign_assume_new("info", info_json(this->info())); + + auto locations{JSON::make_object()}; + for (const auto &location : this->internal_->locations) { + auto entry{JSON::make_object()}; + entry.assign_assume_new("type", + JSON{openapi_kind_name(location.second.type)}); + entry.assign_assume_new("pointer", + JSON{to_string(location.second.pointer)}); + // The nearest recorded ancestor, which every Object but the root of a + // document has, since an Object is always recorded before its contents + entry.assign_assume_new( + "parent", + parent_of(this->internal_->locations, location.first, location.second)); + // Only the root of a document and a Schema Object carry one + if (!location.second.dialect.empty()) { + entry.assign_assume_new("dialect", JSON{location.second.dialect}); + } + + // Only an Object that declares a `$ref` carries these, which is a + // Reference Object or a Path Item Object standing in for another + const auto reference{this->internal_->references.find(location.first)}; + if (reference != this->internal_->references.cend()) { + entry.assign_assume_new("original", JSON{reference->second.original}); + entry.assign_assume_new("destination", + JSON{reference->second.destination}); + } + + locations.assign_assume_new(location.first, std::move(entry)); + } + + result.assign_assume_new("locations", std::move(locations)); + + auto operations{JSON::make_array()}; + for (const auto &operation : this->internal_->operations) { + auto entry{JSON::make_object()}; + entry.assign_assume_new("type", + JSON{openapi_operation_kind_name(operation.kind)}); + entry.assign_assume_new("path", JSON{operation.path}); + entry.assign_assume_new("method", JSON{operation.method}); + entry.assign_assume_new("origin", JSON{operation.origin}); + entry.assign_assume_new("endpoint", JSON{operation.endpoint}); + + auto tags{JSON::make_array()}; + for (const auto &tag : operation.tags) { + tags.push_back(tag.has_value() ? JSON{tag.value()} : JSON{nullptr}); + } + + entry.assign_assume_new("tags", std::move(tags)); + + auto servers{JSON::make_array()}; + for (const auto &server : operation.servers) { + servers.push_back(JSON{server}); + } + + entry.assign_assume_new("servers", std::move(servers)); + + auto security{JSON::make_array()}; + for (const auto &requirement : operation.security) { + security.push_back(JSON{requirement}); + } + + entry.assign_assume_new("security", std::move(security)); + + auto parameters{JSON::make_array()}; + for (const auto ¶meter : operation.parameters) { + parameters.push_back(JSON{parameter}); + } + + entry.assign_assume_new("parameters", std::move(parameters)); + operations.push_back(std::move(entry)); + } + + result.assign_assume_new("operations", std::move(operations)); + return result; +} + +} // namespace sourcemeta::core diff --git a/src/core/openapi/helpers.h b/src/core/openapi/helpers.h new file mode 100644 index 000000000..6cc0e959d --- /dev/null +++ b/src/core/openapi/helpers.h @@ -0,0 +1,562 @@ +#ifndef SOURCEMETA_CORE_OPENAPI_HELPERS_H_ +#define SOURCEMETA_CORE_OPENAPI_HELPERS_H_ + +#include + +#include + +#include // std::ranges::find +#include // std::array +#include // std::size_t +#include // std::uint8_t +#include // std::deque +#include // std::initializer_list +#include // std::map +#include // std::optional +#include // std::set +#include // std::string_view +#include // std::pair, std::unreachable +#include // std::vector + +namespace sourcemeta::core { + +using namespace std::string_view_literals; + +// OpenAPI Specification 3.1.1, Section 4.9: "The field name MUST begin with +// `x-`, for example, `x-internal-id`" +constexpr auto OPENAPI_EXTENSION_PREFIX{"x-"sv}; + +constexpr auto OPENAPI_HASH_DESCRIPTION{JSON::Object::hash("description"sv)}; +constexpr auto OPENAPI_HASH_SUMMARY{JSON::Object::hash("summary"sv)}; +constexpr auto OPENAPI_HASH_URL{JSON::Object::hash("url"sv)}; +constexpr auto OPENAPI_HASH_NAME{JSON::Object::hash("name"sv)}; +constexpr auto OPENAPI_HASH_IN{JSON::Object::hash("in"sv)}; +constexpr auto OPENAPI_HASH_TAGS{JSON::Object::hash("tags"sv)}; +constexpr auto OPENAPI_HASH_SERVERS{JSON::Object::hash("servers"sv)}; + +// What a reference expects to find at the far end of itself, which is fixed by +// where the reference sits rather than by anything the target says about +// itself. OpenAPI Specification 3.1.1, Section 3 lists "Detecting a document +// containing a referenceable Object at its root based on the expected type of +// the reference" among the ways to tell what a referenced document is, and +// this is that expected type +enum class OpenAPIObjectKind : std::uint8_t { + /// A whole OpenAPI Description, which is what a document declaring a root + /// `openapi` field is read as no matter where the reference sat + Document, + // The ten a reference may expect to find + PathItem, + Parameter, + RequestBody, + Response, + Example, + Header, + Link, + Callbacks, + SecurityScheme, + // The rest are never referenced, but every Object gets a location + Info, + Contact, + License, + Server, + ServerVariable, + Components, + Paths, + Operation, + ExternalDocumentation, + MediaType, + Encoding, + Responses, + Tag, + Reference, + Schema, + OAuthFlows, + OAuthFlow, + SecurityRequirement +}; + +inline auto openapi_kind_name(const OpenAPIObjectKind kind) noexcept + -> JSON::StringView { + switch (kind) { + case OpenAPIObjectKind::Document: + return "openapi"sv; + case OpenAPIObjectKind::PathItem: + return "path-item"sv; + case OpenAPIObjectKind::Parameter: + return "parameter"sv; + case OpenAPIObjectKind::RequestBody: + return "request-body"sv; + case OpenAPIObjectKind::Response: + return "response"sv; + case OpenAPIObjectKind::Example: + return "example"sv; + case OpenAPIObjectKind::Header: + return "header"sv; + case OpenAPIObjectKind::Link: + return "link"sv; + case OpenAPIObjectKind::Callbacks: + return "callback"sv; + case OpenAPIObjectKind::SecurityScheme: + return "security-scheme"sv; + case OpenAPIObjectKind::Info: + return "info"sv; + case OpenAPIObjectKind::Contact: + return "contact"sv; + case OpenAPIObjectKind::License: + return "license"sv; + case OpenAPIObjectKind::Server: + return "server"sv; + case OpenAPIObjectKind::ServerVariable: + return "server-variable"sv; + case OpenAPIObjectKind::Components: + return "components"sv; + case OpenAPIObjectKind::Paths: + return "paths"sv; + case OpenAPIObjectKind::Operation: + return "operation"sv; + case OpenAPIObjectKind::ExternalDocumentation: + return "external-documentation"sv; + case OpenAPIObjectKind::MediaType: + return "media-type"sv; + case OpenAPIObjectKind::Encoding: + return "encoding"sv; + case OpenAPIObjectKind::Responses: + return "responses"sv; + case OpenAPIObjectKind::Tag: + return "tag"sv; + case OpenAPIObjectKind::Reference: + return "reference"sv; + case OpenAPIObjectKind::Schema: + return "schema"sv; + case OpenAPIObjectKind::OAuthFlows: + return "oauth-flows"sv; + case OpenAPIObjectKind::OAuthFlow: + return "oauth-flow"sv; + case OpenAPIObjectKind::SecurityRequirement: + return "security-requirement"sv; + } + + std::unreachable(); +} + +/// Where an Object that stands in for another leads. OpenAPI Specification +/// 3.1.1 has a Reference Object and a Path Item Object each declare at most +/// one `$ref`, and a Schema Object's `$ref` never reaches here, so this is a +/// field of the Object that makes it rather than a table of its own +struct OpenAPIReference { + /// The value as the document wrote it + JSON::String original; + /// Where it points, resolved against the base and canonicalised. The + /// document it names and the fragment it carries are that string either side + /// of its `#`, so neither is repeated here + JSON::String destination; +}; + +/// How an Operation Object is reached from the entry document. OpenAPI +/// Specification 3.1.1, Section 3: "only the entry document's Paths Object +/// contributes URLs to the described API", so what an operation is reached +/// through is a property of that document rather than of the one it sits in +enum class OpenAPIOperationKind : std::uint8_t { Path, Webhook, Callback }; + +inline auto +openapi_operation_kind_name(const OpenAPIOperationKind kind) noexcept + -> JSON::StringView { + switch (kind) { + case OpenAPIOperationKind::Path: + return "path"sv; + case OpenAPIOperationKind::Webhook: + return "webhook"sv; + case OpenAPIOperationKind::Callback: + return "callback"sv; + } + + std::unreachable(); +} + +/// What a Path Item Object declares that the endpoints reaching it need. Two +/// endpoints may reach one Path Item, and following a reference reads its +/// target once, so this is kept rather than read again +struct OpenAPIPathItemRecord { + /// The methods it declares, in the order this specification lists them, each + /// against the location of the Operation Object it holds + std::vector> operations; + /// Where the Server Objects it declares sit + std::vector servers; + /// Where the Parameter Objects it declares sit, in the order it writes them. + /// A position may hold a Reference Object rather than a Parameter Object + std::vector parameters; +}; + +/// What an Operation Object declares that the endpoint reaching it needs +struct OpenAPIOperationRecord { + /// Where the Server Objects it declares sit + std::vector servers; + /// Where the Security Requirement Objects it declares sit. Declaring none + /// and declaring an empty array differ, as Section 4.8.10 makes the latter + /// the way "to remove a top-level security declaration" + std::optional> security; + /// Where each Callback Object it declares sits, which is a Reference Object + /// rather than a Callback Object when the description wrote one there + std::vector callbacks; + /// Where the Parameter Objects it declares sit, in the order it writes them + std::vector parameters; + /// The tags it names, in the order it writes them + std::vector tags; +}; + +/// A Path Item Object that the entry document exposes, and what it is exposed +/// as: a path template, a webhook name, or a runtime expression +struct OpenAPIEndpoint { + OpenAPIOperationKind kind; + JSON::String path; + JSON::String path_item; +}; + +/// One operation of the described API, which is what an endpoint and the Path +/// Item it reaches come to between them +struct OpenAPIOperation { + OpenAPIOperationKind kind; + JSON::String path; + JSON::StringView method; + /// Where the Operation Object sits + JSON::String origin; + /// Where the Path Item Object that exposes it sits, which is the position + /// that gives it a URL rather than the one that defines it. The two differ + /// whenever a reference stands between them + JSON::String endpoint; + /// Where the Server Objects in force sit, empty when nothing declares any, + /// in which case Section 4.8.1 puts a single Server Object with a `url` of + /// `/` in their place + std::vector servers; + /// Where the Security Requirement Objects in force sit + std::vector security; + /// Where the Parameter Objects in force sit, which is what the Path Item + /// Object declares once anything the Operation Object overrides is taken + /// out, followed by what the Operation Object declares itself + std::vector parameters; + /// Where the Tag Object each of its tags names sits, in the order the + /// operation wrote them, with no value where the entry document declares no + /// tag by that name. Section 4.8.1 permits exactly that: "Not all tags that + /// are used by the Operation Object must be declared" + std::vector> tags; +}; + +struct OpenAPILocation { + OpenAPIObjectKind type; + Pointer pointer; + /// Set on the root of a document alone: the default `$schema` in force for + /// the Schema Objects it holds, resolved against its base. A Schema Object + /// that declares its own overrides it, which is a matter for whatever reads + /// inside one + JSON::String dialect; +}; + +// What every check needs to reach beyond the Object in front of it: the +// document it is reading, so an error can name it, and the means to follow a +// reference out of it. OpenAPI Specification 3.1.1, Section 4.8.10 makes +// operation identifiers unique across the whole description rather than one +// document, so the set that tracks them spans every document too +struct OpenAPIWalk { + const OpenAPIResolver &resolver; + JSON::String base; + /// Every document a reference brought in. A resolver may hand back a + /// document it owns rather than one the caller keeps alive, so reading one + /// must not be the last thing that happens to it. This is a deque rather + /// than a vector because the walk points into these while it reads them + std::deque documents; + /// Every one of those by the URI it was asked for, so that a second + /// reference into a document already read can still have its own fragment + /// checked without the resolver being asked again + std::map documents_by_uri; + // The document the checks are reading, which a reference that stays inside + // it resolves its fragment against + const JSON *document{nullptr}; + // Kept against where each identifier was read, so that reaching one Operation + // Object twice, which following a reference into the document being read + // does, is told apart from two Operation Objects claiming one identifier. + // These own their strings, as a resolver that hands back a document it owns + // has that document destroyed once it has been read + std::map operation_ids; + // A description may reference the same document twice, or reference its way + // back to one already read. This is keyed by the type expected of a document + // as well as by the document itself, because the same file referenced from + // two positions is two different Objects, and reading it once as whichever + // reference happened to be walked first would let read order decide what it + // is. Section 3.2 of OAS 3.2 names this hazard and says the behaviour "MAY + // be treated as an error if detected", so checking it as each type in turn + // surfaces a genuine conflict rather than hiding it + std::set> visited; + + /// Keyed the way a schema frame keys its own, by the base with the pointer + /// as a fragment, or by the pointer alone when no base was established. The + /// document an Object sits in is that key up to its fragment, so nothing + /// records it a second time + std::map locations; + /// Every reference the description makes, whether or not it was followed, + /// keyed by the location of the Object that makes it. Kept apart from the + /// locations themselves only because reading one Object twice records it + /// twice, and what it stands in for must survive that + std::map references; + /// Every Path Item Object and Operation Object read, along with the Callback + /// Objects that hold more of them, all keyed by where they sit. The + /// projection that turns these into operations runs once the walk is over, + /// as a Path Item may be reached before the endpoint that exposes it + /// + /// What every Parameter Object read is called and where it goes, keyed by + /// where it sits. Section 4.8.9 identifies a parameter "by a combination of a + /// name and location", which is what tells an override from an addition. It + /// owns its strings, as the document it was read from may be gone by the + /// time an operation is projected + std::map> parameters; + /// Every Path Item Object read, keyed by where it sits + std::map path_items; + /// Every Operation Object read, keyed by where it sits + std::map operation_records; + /// Every Callback Object read, keyed by where it sits, holding the + /// expression each of its entries is named by and the Path Item it carries + std::map>> + callbacks; + /// What the entry document exposes, in the order it writes it + std::vector endpoints; + /// Where the entry document's own Server Objects and Security Requirement + /// Objects sit, which is what an operation declaring neither falls back on + std::vector servers; + std::optional> security; + /// The names the entry document declares as security schemes, which is what + /// a Security Requirement Object anywhere in the description may name + std::set security_schemes; + /// Where the entry document declares each Tag Object, by the name it gave + /// it, which is the name an Operation Object's tags resolve against + std::map tags; + /// The operation each Link Object names, keyed by where that Link Object + /// sits. Section 3 has resolving one of these require "parsing all + /// referenced documents prior to determining an `operationId` to be + /// unresolvable", so nothing is decided about them until the walk is over + std::map operation_id_links; + /// Whether the document being read is the entry document, which is the only + /// one whose Paths Object describes the API + bool entry{true}; + /// The default `$schema` in force for the document being read, which every + /// Schema Object position in it hands on + JSON::String dialect; + /// What the entry document says about the API, kept so that reading it once + /// serves both the walk and the caller + OpenAPIInfo info; +}; + +// Where a position that stands in for another leads, following as far as the +// chain goes. A Reference Object may name another one, so this is a walk +// rather than a lookup, and a position that stands in for nothing is itself +inline auto openapi_resolve_position(const OpenAPIWalk &walk, + JSON::String position) -> JSON::String; + +// What the Parameter Object at a position is called and where it goes, or +// nothing when a reference the walk never followed stands in the way +inline auto openapi_parameter_identity(const OpenAPIWalk &walk, + const JSON::String &position) + -> const std::pair *; + +// Follow a reference that leaves the document being read. Defined alongside +// the document-level checks, as those are what a referenced document goes +// through, and declared here because a Reference Object is the thing that +// triggers it +inline auto openapi_follow_reference(JSON::StringView reference, + const Pointer &origin, + OpenAPIObjectKind expected, + OpenAPIWalk &walk) -> void; + +inline auto openapi_resolve_position(const OpenAPIWalk &walk, + JSON::String position) -> JSON::String { + std::set seen; + while (seen.insert(position).second) { + const auto alias{walk.references.find(position)}; + if (alias == walk.references.cend()) { + break; + } + + position = alias->second.destination; + } + + return position; +} + +inline auto openapi_parameter_identity(const OpenAPIWalk &walk, + const JSON::String &position) + -> const std::pair * { + const auto match{ + walk.parameters.find(openapi_resolve_position(walk, position))}; + return match == walk.parameters.cend() ? nullptr : &match->second; +} + +inline auto openapi_child(const Pointer &base, const JSON::StringView field) + -> Pointer { + return base.concat(JSON::String{field}); +} + +inline auto openapi_child(const Pointer &base, const std::size_t index) + -> Pointer { + return base.concat(index); +} + +// OpenAPI Specification 3.1.1, Section 4.2: "The schema exposes two types of +// fields: fixed fields, which have a declared name, and patterned fields, +// which have a declared pattern for the field name". These objects declare +// `^x-` as their only pattern, so a member that is neither is not a field that +// this specification defines +template +auto openapi_reject_unknown_fields( + const JSON &object, const std::array &fields, + const Pointer &base, const char *message) -> void { + for (const auto &entry : object.as_object()) { + if (entry.first.starts_with(OPENAPI_EXTENSION_PREFIX) || + std::ranges::find(fields, entry.first) != fields.cend()) { + continue; + } + + throw OpenAPIError{openapi_child(base, entry.first), message}; + } +} + +// A location is keyed the way a schema frame keys its own: the document base +// with the pointer hung off it as a fragment, or the pointer alone when no +// base was established, and the base by itself for the root of a document. +// +// RFC 6901 Section 6: "A JSON Pointer can be represented in a URI fragment +// identifier by encoding it into octets using UTF-8, while percent-encoding +// those characters not allowed by the fragment rule in [RFC3986]". A path +// template holds braces and a callback expression holds a `#`, neither of +// which a fragment admits, so writing the pointer out as it stands would give +// a key that is no URI and that a reference to the same place could never +// match +inline auto openapi_location_uri(const JSON::String &base, + const Pointer &pointer) -> JSON::String { + if (pointer.empty()) { + return base; + } + + // RFC 3986 Section 5.2.2 resolves a fragment-only reference by keeping every + // other component of the base as it stands, and a base here carries no + // fragment of its own, so appending is that resolution without parsing the + // base again for every Object recorded + JSON::String result{base}; + result.append(to_uri(pointer).recompose()); + return result; +} + +// Every Object gets one of these. The nearest recorded ancestor is the parent, +// which holds because an Object is always recorded before anything inside it +inline auto openapi_record(OpenAPIWalk &walk, const Pointer &pointer, + const OpenAPIObjectKind kind, + JSON::String dialect = {}) -> void { + walk.locations.insert_or_assign( + openapi_location_uri(walk.base, pointer), + OpenAPILocation{ + .type = kind, .pointer = pointer, .dialect = std::move(dialect)}); +} + +inline auto openapi_expect_object(const JSON &value, const Pointer &location, + const char *message) -> void { + if (!value.is_object()) { + throw OpenAPIError{location, message}; + } +} + +inline auto openapi_expect_array(const JSON &value, const Pointer &location, + const char *message) -> void { + if (!value.is_array()) { + throw OpenAPIError{location, message}; + } +} + +inline auto openapi_expect_boolean(const JSON &value, const Pointer &base, + const JSON::StringView field, + const char *message) -> void { + if (!value.is_boolean()) { + throw OpenAPIError{openapi_child(base, field), message}; + } +} + +// A Schema Object is where framing stops, and Section 4.8.24 permits it to be +// "the boolean value `true`" or "the boolean value `false`" as well as an +// object, which is all the meta-schema asserts of one it leaves unvalidated. +// Its location is the handoff: a pointer, the base its key carries, and the +// dialect in force, which is everything a JSON Schema implementation needs to +// take it from here +inline auto openapi_expect_schema(const JSON &value, const Pointer &location, + const char *message, OpenAPIWalk &walk) + -> void { + if (!value.is_object() && !value.is_boolean()) { + throw OpenAPIError{location, message}; + } + + openapi_record(walk, location, OpenAPIObjectKind::Schema, walk.dialect); +} + +inline auto openapi_check_map_of_strings(const JSON &value, + const Pointer &location, + const char *type_message, + const char *entry_message) -> void { + openapi_expect_object(value, location, type_message); + for (const auto &entry : value.as_object()) { + if (!entry.second.is_string()) { + throw OpenAPIError{openapi_child(location, entry.first), entry_message}; + } + } +} + +inline auto openapi_check_array_of_strings(const JSON &value, + const Pointer &location, + const char *type_message, + const char *entry_message) -> void { + openapi_expect_array(value, location, type_message); + std::size_t index{0}; + for (const auto &entry : value.as_array()) { + if (!entry.is_string()) { + throw OpenAPIError{openapi_child(location, index), entry_message}; + } + + index += 1; + } +} + +inline auto openapi_expect_string(const JSON &value, const Pointer &base, + const JSON::StringView field, + const char *message) -> JSON::StringView { + if (!value.is_string()) { + throw OpenAPIError{openapi_child(base, field), message}; + } + + return value.to_string(); +} + +// OpenAPI Specification 3.1.1, Section 4.6: "Unless specified otherwise, all +// fields that are URIs MAY be relative references as defined by RFC3986", so +// what these fields hold is a URI reference rather than an absolute URI +inline auto openapi_expect_uri_reference(const JSON &value, const Pointer &base, + const JSON::StringView field, + const char *type_message, + const char *syntax_message) + -> JSON::StringView { + const auto result{openapi_expect_string(value, base, field, type_message)}; + if (!URI::is_uri_reference(result)) { + throw OpenAPIError{openapi_child(base, field), syntax_message}; + } + + return result; +} + +inline auto openapi_expect_enumeration( + const JSON &value, const Pointer &base, const JSON::StringView field, + const std::initializer_list options, + const char *type_message, const char *value_message) -> JSON::StringView { + const auto result{openapi_expect_string(value, base, field, type_message)}; + if (std::ranges::find(options, result) == options.end()) { + throw OpenAPIError{openapi_child(base, field), value_message}; + } + + return result; +} + +} // namespace sourcemeta::core + +#endif diff --git a/src/core/openapi/include/sourcemeta/core/openapi.h b/src/core/openapi/include/sourcemeta/core/openapi.h new file mode 100644 index 000000000..229dcd520 --- /dev/null +++ b/src/core/openapi/include/sourcemeta/core/openapi.h @@ -0,0 +1,283 @@ +#ifndef SOURCEMETA_CORE_OPENAPI_H_ +#define SOURCEMETA_CORE_OPENAPI_H_ + +#ifndef SOURCEMETA_CORE_OPENAPI_EXPORT +#include +#endif + +#include +#include + +// NOLINTBEGIN(misc-include-cleaner) +#include +// NOLINTEND(misc-include-cleaner) + +#include // std::uint8_t +#include // std::function +#include // std::unique_ptr +#include // std::optional, std::nullopt +#include // std::string_view + +/// @defgroup openapi OpenAPI +/// @brief A growing implementation of the OpenAPI Specification 3.1. +/// +/// This module reports where an OpenAPI Description declares its JSON Schemas +/// and leaves what is inside them to a JSON Schema implementation. +/// +/// This functionality is included as follows: +/// +/// ```cpp +/// #include +/// ``` + +namespace sourcemeta::core { + +/// @ingroup openapi +/// The OpenAPI Description versions that this module recognises +enum class OpenAPIVersion : std::uint8_t { + /// The OpenAPI Specification 3.1 revision + OPENAPI_3_1 +}; + +/// @ingroup openapi +/// Determine the version of an OpenAPI Description from its `openapi` field +/// without framing it, returning no value for a version we do not recognise. +/// The patch component of the field carries no meaning, so every `3.1.x` +/// release maps to the same result. For example: +/// +/// ```cpp +/// #include +/// #include +/// #include +/// +/// const auto document{sourcemeta::core::parse_json(R"({ +/// "openapi": "3.1.1", +/// "info": { "title": "Example", "version": "1.0.0" }, +/// "paths": {} +/// })")}; +/// +/// assert(sourcemeta::core::openapi_version(document).value() == +/// sourcemeta::core::OpenAPIVersion::OPENAPI_3_1); +/// ``` +SOURCEMETA_CORE_OPENAPI_EXPORT +auto openapi_version(const JSON &document) -> std::optional; + +/// @ingroup openapi +/// The contact information that an OpenAPI Description declares for the API. +/// Every value borrows from the document it was read from, so that document +/// must outlive this +struct OpenAPIContact { + /// The identifying name of the contact person or organisation + std::optional name{std::nullopt}; + /// Where to find the contact information, as a URI reference + std::optional url{std::nullopt}; + /// The email address of the contact person or organisation + std::optional email{std::nullopt}; +}; + +/// @ingroup openapi +/// The license information that an OpenAPI Description declares for the API. +/// Every value borrows from the document it was read from, so that document +/// must outlive this +struct OpenAPILicense { + /// The license name used for the API + JSON::StringView name{}; + /// The SPDX license expression for the API, recorded as it was written, as + /// the specification states no requirement on its syntax + std::optional identifier{std::nullopt}; + /// Where to find the license used for the API, as a URI reference + std::optional url{std::nullopt}; +}; + +/// @ingroup openapi +/// The metadata that an OpenAPI Description declares about the API it +/// describes. Every value borrows from the document it was read from, so that +/// document must outlive this +struct OpenAPIInfo { + /// The title of the API + JSON::StringView title{}; + /// The version of the document, which is unrelated to the version of the + /// OpenAPI Specification that it declares + JSON::StringView version{}; + /// A short summary of the API + std::optional summary{std::nullopt}; + /// A description of the API, which may be written in CommonMark + std::optional description{std::nullopt}; + /// Where to find the terms of service for the API, as a URI reference + std::optional terms_of_service{std::nullopt}; + /// The contact information for the API + std::optional contact{std::nullopt}; + /// The license information for the API + std::optional license{std::nullopt}; +}; + +/// @ingroup openapi +/// What a resolver hands back: either a document it owns or one that the +/// caller keeps alive. The root of the result may be an OpenAPI Object or a +/// Schema Object, as an OpenAPI Description may span both +using OpenAPIResolverResult = OwnedOrReference; + +/// @ingroup openapi +/// Resolve a URI to a document that forms part of an OpenAPI Description. A +/// resolver that never hands back a document confines framing to the entry +/// document alone +using OpenAPIResolver = std::function; + +/// @ingroup openapi +/// A static analysis pass over an OpenAPI Description that computes the +/// locations it exposes, the references between them, the operations it +/// describes, and where its JSON Schemas begin. It does not look inside those +/// schemas. For example: +/// +/// ```cpp +/// #include +/// #include +/// #include +/// +/// const auto document{sourcemeta::core::parse_json(R"({ +/// "openapi": "3.1.1", +/// "info": { "title": "Example", "version": "1.0.0" }, +/// "paths": {} +/// })")}; +/// +/// const sourcemeta::core::OpenAPIFrame frame{document, nullptr}; +/// sourcemeta::core::prettify(frame.to_json(), std::cout); +/// std::cout << std::endl; +/// ``` +/// +/// A frame is analysed once, on construction, and is immutable afterwards. +class SOURCEMETA_CORE_OPENAPI_EXPORT OpenAPIFrame { +public: + /// Frame an OpenAPI Description from a given entry document. The entry + /// document must outlive the frame, as the metadata it reports borrows from + /// it. The given base need not, as the frame canonicalises it into a string + /// of its own, and neither must a document that a reference brought in, as + /// the frame keeps whatever the resolver handed it. + /// + /// The base is the retrieval URI of the entry document. OpenAPI 3.1 offers + /// a document no way of declaring an identity of its own, so this is the + /// only way to give the description one. A referenced document whose root + /// is a Schema Object may still override it through `$id` + /// + /// A document that does not conform to the specification is rejected here + /// rather than reported back + OpenAPIFrame(const JSON &document, const OpenAPIResolver &resolver, + std::string_view default_base = ""); + + ~OpenAPIFrame(); + + // We rely on internal caches that would be dangling otherwise + OpenAPIFrame(const OpenAPIFrame &) = delete; + auto operator=(const OpenAPIFrame &) -> OpenAPIFrame & = delete; + OpenAPIFrame(OpenAPIFrame &&) = delete; + auto operator=(OpenAPIFrame &&) -> OpenAPIFrame & = delete; + + /// Get the version of the OpenAPI Specification that the entry document + /// declares. The patch component of that declaration carries no meaning, so + /// every `3.1.x` release reports the same version. For example: + /// + /// ```cpp + /// #include + /// #include + /// #include + /// + /// const auto document{sourcemeta::core::parse_json(R"({ + /// "openapi": "3.1.1", + /// "info": { "title": "Example", "version": "1.0.0" }, + /// "paths": {} + /// })")}; + /// + /// const sourcemeta::core::OpenAPIFrame frame{document, nullptr}; + /// assert(frame.version() == + /// sourcemeta::core::OpenAPIVersion::OPENAPI_3_1); + /// ``` + [[nodiscard]] auto version() const noexcept -> OpenAPIVersion; + + /// Get the metadata that the entry document declares about the API. For + /// example: + /// + /// ```cpp + /// #include + /// #include + /// #include + /// + /// const auto document{sourcemeta::core::parse_json(R"({ + /// "openapi": "3.1.1", + /// "info": { "title": "Example", "version": "1.0.0" }, + /// "paths": {} + /// })")}; + /// + /// const sourcemeta::core::OpenAPIFrame frame{document, nullptr}; + /// assert(frame.info().title == "Example"); + /// assert(frame.info().version == "1.0.0"); + /// assert(!frame.info().license.has_value()); + /// ``` + [[nodiscard]] auto info() const noexcept -> const OpenAPIInfo &; + + /// Get the base URI that relative references in this description resolve + /// against, canonicalised, or the empty URI reference when the caller + /// established none, which leaves those references relative. OpenAPI 3.1 + /// gives a document no way of declaring this itself, so it is the retrieval + /// URI the caller supplied and nothing else. For example: + /// + /// ```cpp + /// #include + /// #include + /// #include + /// + /// const auto document{sourcemeta::core::parse_json(R"({ + /// "openapi": "3.1.1", + /// "info": { "title": "Example", "version": "1.0.0" }, + /// "paths": {} + /// })")}; + /// + /// const sourcemeta::core::OpenAPIFrame frame{ + /// document, nullptr, "https://example.com/openapi.json"}; + /// assert(frame.base() == "https://example.com/openapi.json"); + /// ``` + [[nodiscard]] auto base() const noexcept -> JSON::StringView; + + /// Check whether everything this description references is inside what was + /// framed. A frame that does not stand alone is missing part of the + /// description, either because no resolver was supplied, because one could + /// not hand back a document, or because a reference lands on nothing. For + /// example: + /// + /// ```cpp + /// #include + /// #include + /// #include + /// + /// const auto document{sourcemeta::core::parse_json(R"({ + /// "openapi": "3.1.1", + /// "info": { "title": "Example", "version": "1.0.0" }, + /// "paths": {} + /// })")}; + /// + /// const sourcemeta::core::OpenAPIFrame frame{document, nullptr}; + /// assert(frame.standalone()); + /// ``` + [[nodiscard]] auto standalone() const noexcept -> bool; + + /// Export the frame as JSON. This is the complete state of the frame, and + /// for now its only window + [[nodiscard]] auto to_json() const -> JSON; + +private: +// Exporting symbols that depends on the standard C++ library is considered +// safe. +// https://learn.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-2-c4275?view=msvc-170&redirectedfrom=MSDN +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable : 4251) +#endif + struct Internal; + std::unique_ptr internal_; +#if defined(_MSC_VER) +#pragma warning(pop) +#endif +}; + +} // namespace sourcemeta::core + +#endif diff --git a/src/core/openapi/include/sourcemeta/core/openapi_error.h b/src/core/openapi/include/sourcemeta/core/openapi_error.h new file mode 100644 index 000000000..923d778b1 --- /dev/null +++ b/src/core/openapi/include/sourcemeta/core/openapi_error.h @@ -0,0 +1,88 @@ +#ifndef SOURCEMETA_CORE_OPENAPI_ERROR_H_ +#define SOURCEMETA_CORE_OPENAPI_ERROR_H_ + +#ifndef SOURCEMETA_CORE_OPENAPI_EXPORT +#include +#endif + +#include +#include + +#include // std::exception +#include // std::string +#include // std::string_view +#include // std::move + +namespace sourcemeta::core { + +// Exporting symbols that depends on the standard C++ library is considered +// safe. +// https://learn.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-2-c4275?view=msvc-170&redirectedfrom=MSDN +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable : 4251 4275) +#endif + +/// @ingroup openapi +/// An error that represents an OpenAPI Description that does not conform to +/// the specification. For example: +/// +/// ```cpp +/// #include +/// #include +/// #include +/// +/// const sourcemeta::core::OpenAPIError error{ +/// sourcemeta::core::Pointer{"info"}, "The Info Object is required"}; +/// assert(error.location() == sourcemeta::core::Pointer{"info"}); +/// ``` +class SOURCEMETA_CORE_OPENAPI_EXPORT OpenAPIError : public std::exception { +public: + /// Construct an error from where the problem is and a message + OpenAPIError(Pointer location, const char *message) + : location_{std::move(location)}, message_{message} {} + OpenAPIError(Pointer location, std::string message) = delete; + OpenAPIError(Pointer location, std::string &&message) = delete; + OpenAPIError(Pointer location, std::string_view message) = delete; + + /// Construct an error that names the document the problem is in, for a + /// description that spans more than one document + OpenAPIError(JSON::String base, Pointer location, const char *message) + : base_{std::move(base)}, location_{std::move(location)}, + message_{message} {} + OpenAPIError(JSON::String base, Pointer location, + std::string message) = delete; + OpenAPIError(JSON::String base, Pointer location, + std::string &&message) = delete; + OpenAPIError(JSON::String base, Pointer location, + std::string_view message) = delete; + + [[nodiscard]] auto what() const noexcept -> const char * override { + return this->message_; + } + + /// Get where the problem is, as a pointer from the root of the document + /// that holds it + [[nodiscard]] auto location() const noexcept -> const Pointer & { + return this->location_; + } + + /// Get the base URI of the document that holds the problem, or the empty + /// URI reference when the caller established none + [[nodiscard]] auto base() const noexcept -> JSON::StringView { + return this->base_; + } + +private: + JSON::String base_; + Pointer location_; + const char *message_; +}; + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +} // namespace sourcemeta::core + +#endif diff --git a/src/core/openapi/info.h b/src/core/openapi/info.h new file mode 100644 index 000000000..690e533d9 --- /dev/null +++ b/src/core/openapi/info.h @@ -0,0 +1,220 @@ +#ifndef SOURCEMETA_CORE_OPENAPI_INFO_H_ +#define SOURCEMETA_CORE_OPENAPI_INFO_H_ + +#include + +#include "helpers.h" + +#include + +#include // std::array +#include // std::string_view + +namespace sourcemeta::core { + +constexpr auto OPENAPI_HASH_INFO{JSON::Object::hash("info"sv)}; +constexpr auto OPENAPI_HASH_TITLE{JSON::Object::hash("title"sv)}; +constexpr auto OPENAPI_HASH_TERMS_OF_SERVICE{ + JSON::Object::hash("termsOfService"sv)}; +constexpr auto OPENAPI_HASH_CONTACT{JSON::Object::hash("contact"sv)}; +constexpr auto OPENAPI_HASH_LICENSE{JSON::Object::hash("license"sv)}; +constexpr auto OPENAPI_HASH_VERSION{JSON::Object::hash("version"sv)}; +constexpr auto OPENAPI_HASH_EMAIL{JSON::Object::hash("email"sv)}; +constexpr auto OPENAPI_HASH_IDENTIFIER{JSON::Object::hash("identifier"sv)}; + +constexpr std::array OPENAPI_INFO_FIELDS{ + {"title"sv, "summary"sv, "description"sv, "termsOfService"sv, "contact"sv, + "license"sv, "version"sv}}; + +constexpr std::array OPENAPI_CONTACT_FIELDS{ + {"name"sv, "url"sv, "email"sv}}; + +constexpr std::array OPENAPI_LICENSE_FIELDS{ + {"name"sv, "identifier"sv, "url"sv}}; + +// OpenAPI Specification 3.1.1, Section 4.8.3: "Contact information for the +// exposed API" +inline auto openapi_parse_contact(const JSON &value, const Pointer &base, + OpenAPIWalk &walk) -> OpenAPIContact { + openapi_record(walk, base, OpenAPIObjectKind::Contact); + if (!value.is_object()) { + throw OpenAPIError{base, "The Contact Object must be an object"}; + } + + openapi_reject_unknown_fields( + value, OPENAPI_CONTACT_FIELDS, base, + "The Contact Object does not define this field"); + + OpenAPIContact result; + + const auto *name{value.try_at("name", OPENAPI_HASH_NAME)}; + if (name != nullptr) { + result.name = openapi_expect_string( + *name, base, "name"sv, "The Contact Object name must be a string"); + } + + const auto *url{value.try_at("url", OPENAPI_HASH_URL)}; + if (url != nullptr) { + result.url = openapi_expect_uri_reference( + *url, base, "url"sv, "The Contact Object URI must be a string", + "The Contact Object URI must be a URI reference"); + } + + // OpenAPI Specification 3.1.1, Section 4.8.3: "The email address of the + // contact person/organization. This MUST be in the form of an email address" + const auto *email{value.try_at("email", OPENAPI_HASH_EMAIL)}; + if (email != nullptr) { + const auto address{openapi_expect_string( + *email, base, "email"sv, "The Contact Object email must be a string")}; + if (!is_email(address)) { + throw OpenAPIError{openapi_child(base, "email"sv), + "The Contact Object email must be an email address"}; + } + + result.email = address; + } + + return result; +} + +// OpenAPI Specification 3.1.1, Section 4.8.4: "License information for the +// exposed API" +inline auto openapi_parse_license(const JSON &value, const Pointer &base, + OpenAPIWalk &walk) -> OpenAPILicense { + openapi_record(walk, base, OpenAPIObjectKind::License); + if (!value.is_object()) { + throw OpenAPIError{base, "The License Object must be an object"}; + } + + openapi_reject_unknown_fields( + value, OPENAPI_LICENSE_FIELDS, base, + "The License Object does not define this field"); + + OpenAPILicense result; + + // OpenAPI Specification 3.1.1, Section 4.8.4: "name | string | REQUIRED. The + // license name used for the API" + const auto *name{value.try_at("name", OPENAPI_HASH_NAME)}; + if (name == nullptr) { + throw OpenAPIError{base, "The License Object must declare a name"}; + } + + result.name = openapi_expect_string( + *name, base, "name"sv, "The License Object name must be a string"); + + // The specification states no requirement on the syntax of this field, only + // that it is "An SPDX license expression for the API", so it is recorded as + // it was written + const auto *identifier{value.try_at("identifier", OPENAPI_HASH_IDENTIFIER)}; + if (identifier != nullptr) { + result.identifier = + openapi_expect_string(*identifier, base, "identifier"sv, + "The License Object identifier must be a string"); + } + + const auto *url{value.try_at("url", OPENAPI_HASH_URL)}; + if (url != nullptr) { + result.url = openapi_expect_uri_reference( + *url, base, "url"sv, "The License Object URI must be a string", + "The License Object URI must be a URI reference"); + } + + // OpenAPI Specification 3.1.1, Section 4.8.4: "The `identifier` field is + // mutually exclusive of the `url` field" + if (result.identifier.has_value() && result.url.has_value()) { + throw OpenAPIError{ + base, "The License Object identifier and URI are mutually exclusive"}; + } + + return result; +} + +// Read the Info Object of an OpenAPI Description, turning down anything the +// specification does not permit. The result borrows from the given document +inline auto openapi_parse_info(const JSON &document, OpenAPIWalk &walk) + -> OpenAPIInfo { + // OpenAPI Specification 3.1.1, Section 4.8.1: "info | Info Object | + // REQUIRED. Provides metadata about the API" + const auto *value{document.try_at("info", OPENAPI_HASH_INFO)}; + if (value == nullptr) { + throw OpenAPIError{EMPTY_POINTER, + "The OpenAPI Description must provide an Info Object"}; + } + + const Pointer base{"info"}; + openapi_record(walk, base, OpenAPIObjectKind::Info); + if (!value->is_object()) { + throw OpenAPIError{base, "The Info Object must be an object"}; + } + + openapi_reject_unknown_fields(*value, OPENAPI_INFO_FIELDS, base, + "The Info Object does not define this field"); + + OpenAPIInfo result; + + // OpenAPI Specification 3.1.1, Section 4.8.2: "title | string | REQUIRED. + // The title of the API" + const auto *title{value->try_at("title", OPENAPI_HASH_TITLE)}; + if (title == nullptr) { + throw OpenAPIError{base, "The Info Object must declare a title"}; + } + + result.title = openapi_expect_string( + *title, base, "title"sv, "The Info Object title must be a string"); + + // OpenAPI Specification 3.1.1, Section 4.8.2: "version | string | REQUIRED. + // The version of the OpenAPI Document" + const auto *version{value->try_at("version", OPENAPI_HASH_VERSION)}; + if (version == nullptr) { + throw OpenAPIError{base, "The Info Object must declare a version"}; + } + + result.version = openapi_expect_string( + *version, base, "version"sv, "The Info Object version must be a string"); + + const auto *summary{value->try_at("summary", OPENAPI_HASH_SUMMARY)}; + if (summary != nullptr) { + result.summary = + openapi_expect_string(*summary, base, "summary"sv, + "The Info Object summary must be a string"); + } + + // The specification permits CommonMark here but does not require it, so + // there is nothing to check beyond the type + const auto *description{ + value->try_at("description", OPENAPI_HASH_DESCRIPTION)}; + if (description != nullptr) { + result.description = + openapi_expect_string(*description, base, "description"sv, + "The Info Object description must be a string"); + } + + // OpenAPI Specification 3.1.1, Section 4.8.2: "A URI for the Terms of + // Service for the API. This MUST be in the form of a URI" + const auto *terms{ + value->try_at("termsOfService", OPENAPI_HASH_TERMS_OF_SERVICE)}; + if (terms != nullptr) { + result.terms_of_service = openapi_expect_uri_reference( + *terms, base, "termsOfService"sv, + "The Info Object terms of service must be a string", + "The Info Object terms of service must be a URI reference"); + } + + const auto *contact{value->try_at("contact", OPENAPI_HASH_CONTACT)}; + if (contact != nullptr) { + result.contact = + openapi_parse_contact(*contact, openapi_child(base, "contact"sv), walk); + } + + const auto *license{value->try_at("license", OPENAPI_HASH_LICENSE)}; + if (license != nullptr) { + result.license = + openapi_parse_license(*license, openapi_child(base, "license"sv), walk); + } + + return result; +} + +} // namespace sourcemeta::core + +#endif diff --git a/src/core/openapi/link.h b/src/core/openapi/link.h new file mode 100644 index 000000000..d223bc1aa --- /dev/null +++ b/src/core/openapi/link.h @@ -0,0 +1,117 @@ +#ifndef SOURCEMETA_CORE_OPENAPI_LINK_H_ +#define SOURCEMETA_CORE_OPENAPI_LINK_H_ + +#include + +#include "helpers.h" +#include "reference.h" +#include "server.h" + +#include // std::array +#include // std::string_view + +namespace sourcemeta::core { + +constexpr auto OPENAPI_HASH_OPERATION_REF{JSON::Object::hash("operationRef"sv)}; +constexpr auto OPENAPI_HASH_OPERATION_ID{JSON::Object::hash("operationId"sv)}; +constexpr auto OPENAPI_HASH_PARAMETERS{JSON::Object::hash("parameters"sv)}; +constexpr auto OPENAPI_HASH_REQUEST_BODY{JSON::Object::hash("requestBody"sv)}; +constexpr auto OPENAPI_HASH_SERVER{JSON::Object::hash("server"sv)}; + +constexpr std::array OPENAPI_LINK_FIELDS{ + {"operationRef"sv, "operationId"sv, "parameters"sv, "requestBody"sv, + "description"sv, "server"sv}}; + +// OpenAPI Specification 3.1.1, Section 4.8.20: "The Link Object represents a +// possible design-time link for a response" +inline auto openapi_check_link(const JSON &value, const Pointer &base, + OpenAPIWalk &walk) -> void { + openapi_record(walk, base, OpenAPIObjectKind::Link); + openapi_expect_object(value, base, "The Link Object must be an object"); + openapi_reject_unknown_fields(value, OPENAPI_LINK_FIELDS, base, + "The Link Object does not define this field"); + + // OpenAPI Specification 3.1.1, Section 4.8.20: "operationRef | string | A + // URI reference to an OAS operation. This field is mutually exclusive of the + // `operationId` field" + const auto *operation_reference{ + value.try_at("operationRef", OPENAPI_HASH_OPERATION_REF)}; + const auto *operation_identifier{ + value.try_at("operationId", OPENAPI_HASH_OPERATION_ID)}; + + if (operation_reference != nullptr && operation_identifier != nullptr) { + throw OpenAPIError{base, + "The Link Object operation reference and operation " + "identifier are mutually exclusive"}; + } + + if (operation_reference == nullptr && operation_identifier == nullptr) { + throw OpenAPIError{base, + "The Link Object must declare an operation reference or " + "an operation identifier"}; + } + + // Section 4.8.20: "The identified or reference operation MUST be unique, and + // in the case of an `operationId`, it MUST be resolved within the scope of + // the OpenAPI Description". What operations the description declares is not + // known until every document has been read, so this is only written down + if (operation_identifier != nullptr) { + const auto identifier{openapi_expect_string( + *operation_identifier, base, "operationId"sv, + "The Link Object operation identifier must be a string")}; + walk.operation_id_links.insert_or_assign( + openapi_location_uri(walk.base, base), JSON::String{identifier}); + } + + // OpenAPI Specification 3.1.1, Section 4.8.20: "parameters | Map[string, + // Any | {expression}]". Any is any JSON value, so a constant of any type is + // as legal as a runtime expression and only the map itself is checked + const auto *parameters{value.try_at("parameters", OPENAPI_HASH_PARAMETERS)}; + if (parameters != nullptr) { + openapi_expect_object(*parameters, openapi_child(base, "parameters"sv), + "The Link Object parameters must be an object"); + } + + const auto *description{ + value.try_at("description", OPENAPI_HASH_DESCRIPTION)}; + if (description != nullptr) { + openapi_expect_string(*description, base, "description"sv, + "The Link Object description must be a string"); + } + + const auto *server{value.try_at("server", OPENAPI_HASH_SERVER)}; + if (server != nullptr) { + openapi_check_server(*server, openapi_child(base, "server"sv), walk); + } + + // Any value at all is a legal `requestBody`, so there is nothing to check + + // Section 4.8.20: "operationRef | string | A URI reference to an OAS + // operation [...] and MUST point to an Operation Object". So unlike every + // other URI-valued field this one is a reference to follow, and what it + // lands on has to hold up as an Operation Object. Followed last, so that the + // Link Object's own fields are settled before anything it points at is read + if (operation_reference != nullptr) { + const auto reference{openapi_expect_uri_reference( + *operation_reference, base, "operationRef"sv, + "The Link Object operation reference must be a string", + "The Link Object operation reference must be a URI reference")}; + openapi_follow_reference(reference, openapi_child(base, "operationRef"sv), + OpenAPIObjectKind::Operation, walk); + } +} + +inline auto openapi_check_link_or_reference(const JSON &value, + const Pointer &base, + OpenAPIWalk &walk) -> void { + if (openapi_is_reference(value)) { + openapi_check_reference(value, base, OpenAPIObjectKind::Link, walk); + return; + } + + openapi_check_link(value, base, walk); +} + +} // namespace sourcemeta::core + +#endif diff --git a/src/core/openapi/parameter.h b/src/core/openapi/parameter.h new file mode 100644 index 000000000..a6673dea3 --- /dev/null +++ b/src/core/openapi/parameter.h @@ -0,0 +1,298 @@ +#ifndef SOURCEMETA_CORE_OPENAPI_PARAMETER_H_ +#define SOURCEMETA_CORE_OPENAPI_PARAMETER_H_ + +#include + +#include "content.h" +#include "example.h" +#include "helpers.h" +#include "reference.h" + +#include // std::array +#include // std::size_t +#include // std::set +#include // std::string_view +#include // std::pair +#include // std::vector + +namespace sourcemeta::core { + +constexpr auto OPENAPI_HASH_ALLOW_EMPTY_VALUE{ + JSON::Object::hash("allowEmptyValue"sv)}; + +// The fields a Parameter Object admits depend on how it serialises and on +// where it sits, which is how the meta-schema reads it, holding the +// serialisation fields behind a dependent schema on `schema` and +// `allowEmptyValue` and `allowReserved` behind the location +constexpr std::array OPENAPI_PARAMETER_CONTENT_FIELDS{ + {"name"sv, "in"sv, "description"sv, "required"sv, "deprecated"sv, + "content"sv}}; + +constexpr std::array OPENAPI_PARAMETER_SCHEMA_FIELDS{ + {"name"sv, "in"sv, "description"sv, "required"sv, "deprecated"sv, + "schema"sv, "style"sv, "explode"sv, "example"sv, "examples"sv}}; + +constexpr std::array + OPENAPI_PARAMETER_QUERY_SCHEMA_FIELDS{ + {"name"sv, "in"sv, "description"sv, "required"sv, "deprecated"sv, + "schema"sv, "style"sv, "explode"sv, "example"sv, "examples"sv, + "allowEmptyValue"sv, "allowReserved"sv}}; + +constexpr std::array + OPENAPI_PARAMETER_QUERY_CONTENT_FIELDS{{"name"sv, "in"sv, "description"sv, + "required"sv, "deprecated"sv, + "content"sv, "allowEmptyValue"sv}}; + +// OpenAPI Specification 3.1.1, Section 4.8.12: "Describes a single operation +// parameter" +inline auto openapi_check_parameter(const JSON &value, const Pointer &base, + OpenAPIWalk &walk) + -> std::pair { + openapi_record(walk, base, OpenAPIObjectKind::Parameter); + openapi_expect_object(value, base, "The Parameter Object must be an object"); + const auto location{openapi_location_uri(walk.base, base)}; + + // OpenAPI Specification 3.1.1, Section 4.8.12: "name | string | REQUIRED. + // The name of the parameter" + const auto *name{value.try_at("name", OPENAPI_HASH_NAME)}; + if (name == nullptr) { + throw OpenAPIError{base, "The Parameter Object must declare a name"}; + } + + const auto parameter_name{openapi_expect_string( + *name, base, "name"sv, "The Parameter Object name must be a string")}; + + // OpenAPI Specification 3.1.1, Section 4.8.12: "in | string | REQUIRED. The + // location of the parameter. Possible values are `"query"`, `"header"`, + // `"path"` or `"cookie"`" + const auto *location_field{value.try_at("in", OPENAPI_HASH_IN)}; + if (location_field == nullptr) { + throw OpenAPIError{base, "The Parameter Object must declare a location"}; + } + + const auto parameter_location{openapi_expect_enumeration( + *location_field, base, "in"sv, + {"query"sv, "header"sv, "path"sv, "cookie"sv}, + "The Parameter Object location must be a string", + "The Parameter Object location is not one this specification defines")}; + + const auto *schema{value.try_at("schema", OPENAPI_HASH_SCHEMA)}; + const auto *content{value.try_at("content", OPENAPI_HASH_CONTENT)}; + + if (schema != nullptr && content != nullptr) { + throw OpenAPIError{ + base, "The Parameter Object schema and content are mutually exclusive"}; + } + + if (schema == nullptr && content == nullptr) { + throw OpenAPIError{ + base, "The Parameter Object must declare a schema or a content"}; + } + + const auto in_query{parameter_location == "query"sv}; + if (schema == nullptr) { + if (in_query) { + openapi_reject_unknown_fields( + value, OPENAPI_PARAMETER_QUERY_CONTENT_FIELDS, base, + "The Parameter Object does not define this field"); + } else { + openapi_reject_unknown_fields( + value, OPENAPI_PARAMETER_CONTENT_FIELDS, base, + "The Parameter Object does not define this field"); + } + } else if (in_query) { + openapi_reject_unknown_fields( + value, OPENAPI_PARAMETER_QUERY_SCHEMA_FIELDS, base, + "The Parameter Object does not define this field"); + } else { + openapi_reject_unknown_fields( + value, OPENAPI_PARAMETER_SCHEMA_FIELDS, base, + "The Parameter Object does not define this field"); + } + + const auto *description{ + value.try_at("description", OPENAPI_HASH_DESCRIPTION)}; + if (description != nullptr) { + openapi_expect_string(*description, base, "description"sv, + "The Parameter Object description must be a string"); + } + + const auto *deprecated{value.try_at("deprecated", OPENAPI_HASH_DEPRECATED)}; + if (deprecated != nullptr) { + openapi_expect_boolean(*deprecated, base, "deprecated"sv, + "The Parameter Object deprecated must be a boolean"); + } + + const auto *required{value.try_at("required", OPENAPI_HASH_REQUIRED)}; + if (required != nullptr) { + openapi_expect_boolean(*required, base, "required"sv, + "The Parameter Object required must be a boolean"); + } + + // OpenAPI Specification 3.1.1, Section 4.8.12: "If the parameter location is + // `"path"`, this field is REQUIRED and its value MUST be `true`" + if (parameter_location == "path"sv) { + if (required == nullptr) { + throw OpenAPIError{ + base, "A path Parameter Object must declare that it is required"}; + } + + if (!required->to_boolean()) { + throw OpenAPIError{openapi_child(base, "required"sv), + "A path Parameter Object must be required"}; + } + + // OpenAPI Specification 3.1.1, Section 4.8.12: "If `in` is `"path"`, the + // `name` field MUST correspond to a template expression occurring within + // the path field in the Paths Object", and a template expression is + // delimited by braces, so its name can hold neither + if (parameter_name.find('{') != JSON::StringView::npos || + parameter_name.find('}') != JSON::StringView::npos) { + throw OpenAPIError{openapi_child(base, "name"sv), + "A path Parameter Object name must not hold braces"}; + } + } + + const auto *allow_empty{ + value.try_at("allowEmptyValue", OPENAPI_HASH_ALLOW_EMPTY_VALUE)}; + if (allow_empty != nullptr) { + openapi_expect_boolean( + *allow_empty, base, "allowEmptyValue"sv, + "The Parameter Object allowEmptyValue must be a boolean"); + } + + if (content != nullptr) { + const auto content_location{openapi_child(base, "content"sv)}; + openapi_check_content(*content, content_location, + "The Parameter Object content must be an object", + walk); + // OpenAPI Specification 3.1.1, Section 4.8.12: "The map MUST only contain + // one entry" + if (content->size() != 1) { + throw OpenAPIError{ + content_location, + "The Parameter Object content must hold exactly one entry"}; + } + + walk.parameters.insert_or_assign( + location, std::pair{JSON::String{parameter_name}, + JSON::String{parameter_location}}); + return {parameter_name, parameter_location}; + } + + openapi_expect_schema(*schema, openapi_child(base, "schema"sv), + "A Schema Object must be an object or a boolean", walk); + + // OpenAPI Specification 3.1.1, Section 4.8.12 lists the styles each location + // admits, and the meta-schema enumerates them per location + const auto *style{value.try_at("style", OPENAPI_HASH_STYLE)}; + if (style != nullptr) { + if (parameter_location == "path"sv) { + openapi_expect_enumeration( + *style, base, "style"sv, {"matrix"sv, "label"sv, "simple"sv}, + "The Parameter Object style must be a string", + "The Parameter Object style is not one a path parameter admits"); + } else if (parameter_location == "header"sv) { + openapi_expect_enumeration( + *style, base, "style"sv, {"simple"sv}, + "The Parameter Object style must be a string", + "The Parameter Object style is not one a header parameter admits"); + } else if (in_query) { + openapi_expect_enumeration( + *style, base, "style"sv, + {"form"sv, "spaceDelimited"sv, "pipeDelimited"sv, "deepObject"sv}, + "The Parameter Object style must be a string", + "The Parameter Object style is not one a query parameter admits"); + } else { + openapi_expect_enumeration( + *style, base, "style"sv, {"form"sv}, + "The Parameter Object style must be a string", + "The Parameter Object style is not one a cookie parameter admits"); + } + } + + const auto *explode{value.try_at("explode", OPENAPI_HASH_EXPLODE)}; + if (explode != nullptr) { + openapi_expect_boolean(*explode, base, "explode"sv, + "The Parameter Object explode must be a boolean"); + } + + const auto *allow_reserved{ + value.try_at("allowReserved", OPENAPI_HASH_ALLOW_RESERVED)}; + if (allow_reserved != nullptr) { + openapi_expect_boolean( + *allow_reserved, base, "allowReserved"sv, + "The Parameter Object allowReserved must be a boolean"); + } + + openapi_check_examples( + value, base, + "The Parameter Object example and examples are mutually exclusive", + "The Parameter Object examples must be an object", walk); + + walk.parameters.insert_or_assign(location, + std::pair{JSON::String{parameter_name}, + JSON::String{parameter_location}}); + return {parameter_name, parameter_location}; +} + +inline auto openapi_check_parameters_entry(const JSON &value, + const Pointer &base, + OpenAPIWalk &walk) -> void { + if (openapi_is_reference(value)) { + openapi_check_reference(value, base, OpenAPIObjectKind::Parameter, walk); + return; + } + + [[maybe_unused]] const auto identity{ + openapi_check_parameter(value, base, walk)}; +} + +// OpenAPI Specification 3.1.1, Sections 4.8.9 and 4.8.10: "The list MUST NOT +// include duplicated parameters. A unique parameter is defined by a +// combination of a name and location". A Reference Object names neither until +// it is followed, and following is eager, so what it leads to is compared +// alongside the parameters written out in place. One that could not be +// followed names nothing and is left out of the comparison +inline auto openapi_check_parameters(const JSON &value, const Pointer &base, + const char *type_message, + const char *duplicate_message, + OpenAPIWalk &walk) + -> std::vector { + openapi_expect_array(value, base, type_message); + + std::vector result; + result.reserve(value.size()); + // These own their strings, as an identity read back through a reference + // borrows from a map the walk keeps writing to + std::set> seen; + std::size_t index{0}; + for (const auto ¶meter : value.as_array()) { + const auto location{openapi_child(base, index)}; + if (openapi_is_reference(parameter)) { + openapi_check_reference(parameter, location, OpenAPIObjectKind::Parameter, + walk); + const auto *identity{openapi_parameter_identity( + walk, openapi_location_uri(walk.base, location))}; + if (identity != nullptr && !seen.insert(*identity).second) { + throw OpenAPIError{location, duplicate_message}; + } + } else { + const auto identity{openapi_check_parameter(parameter, location, walk)}; + if (!seen.insert({JSON::String{identity.first}, + JSON::String{identity.second}}) + .second) { + throw OpenAPIError{location, duplicate_message}; + } + } + + result.push_back(openapi_location_uri(walk.base, location)); + index += 1; + } + + return result; +} + +} // namespace sourcemeta::core + +#endif diff --git a/src/core/openapi/path_item.h b/src/core/openapi/path_item.h new file mode 100644 index 000000000..4b2371f79 --- /dev/null +++ b/src/core/openapi/path_item.h @@ -0,0 +1,275 @@ +#ifndef SOURCEMETA_CORE_OPENAPI_PATH_ITEM_H_ +#define SOURCEMETA_CORE_OPENAPI_PATH_ITEM_H_ + +#include + +#include "external_documentation.h" +#include "helpers.h" +#include "parameter.h" +#include "reference.h" +#include "request_body.h" +#include "response.h" +#include "security.h" +#include "server.h" + +#include // std::array +#include // std::set +#include // std::string_view +#include // std::move, std::pair +#include // std::vector + +namespace sourcemeta::core { + +constexpr auto OPENAPI_HASH_RESPONSES{JSON::Object::hash("responses"sv)}; +constexpr auto OPENAPI_HASH_CALLBACKS{JSON::Object::hash("callbacks"sv)}; + +constexpr std::array OPENAPI_PATH_ITEM_FIELDS{ + {"$ref"sv, "summary"sv, "description"sv, "servers"sv, "parameters"sv, + "get"sv, "put"sv, "post"sv, "delete"sv, "options"sv, "head"sv, "patch"sv, + "trace"sv}}; + +constexpr std::array OPENAPI_PATH_ITEM_METHODS{ + {"get"sv, "put"sv, "post"sv, "delete"sv, "options"sv, "head"sv, "patch"sv, + "trace"sv}}; + +constexpr std::array OPENAPI_OPERATION_FIELDS{ + {"tags"sv, "summary"sv, "description"sv, "externalDocs"sv, "operationId"sv, + "parameters"sv, "requestBody"sv, "responses"sv, "callbacks"sv, + "deprecated"sv, "security"sv, "servers"sv}}; + +// A Callback Object holds Path Item Objects, and a Path Item Object reaches +// back here through its Operations, so the two have to be declared apart +inline auto openapi_check_path_item(const JSON &value, const Pointer &base, + OpenAPIWalk &walk) -> void; + +// OpenAPI Specification 3.1.1, Section 4.8.18: "A map of possible out-of band +// callbacks related to the parent operation". Its keys are runtime +// expressions, which carry no requirement this Object can check +inline auto openapi_check_callbacks(const JSON &value, const Pointer &base, + OpenAPIWalk &walk) -> void { + openapi_record(walk, base, OpenAPIObjectKind::Callbacks); + openapi_expect_object(value, base, "The Callback Object must be an object"); + std::vector> entries; + for (const auto &entry : value.as_object()) { + if (entry.first.starts_with(OPENAPI_EXTENSION_PREFIX)) { + continue; + } + + const auto location{openapi_child(base, entry.first)}; + openapi_check_path_item(entry.second, location, walk); + entries.emplace_back(entry.first, + openapi_location_uri(walk.base, location)); + } + + walk.callbacks.insert_or_assign(openapi_location_uri(walk.base, base), + std::move(entries)); +} + +inline auto openapi_check_callbacks_or_reference(const JSON &value, + const Pointer &base, + OpenAPIWalk &walk) -> void { + if (openapi_is_reference(value)) { + openapi_check_reference(value, base, OpenAPIObjectKind::Callbacks, walk); + return; + } + + openapi_check_callbacks(value, base, walk); +} + +// OpenAPI Specification 3.1.1, Section 4.8.10: "Describes a single API +// operation on a path" +inline auto openapi_check_operation(const JSON &value, const Pointer &base, + OpenAPIWalk &walk) -> void { + openapi_record(walk, base, OpenAPIObjectKind::Operation); + openapi_expect_object(value, base, "The Operation Object must be an object"); + openapi_reject_unknown_fields( + value, OPENAPI_OPERATION_FIELDS, base, + "The Operation Object does not define this field"); + + OpenAPIOperationRecord record; + + const auto *tags{value.try_at("tags", OPENAPI_HASH_TAGS)}; + if (tags != nullptr) { + openapi_check_array_of_strings( + *tags, openapi_child(base, "tags"sv), + "The Operation Object tags must be an array", + "The Operation Object tags must hold strings"); + record.tags.reserve(tags->size()); + for (const auto &tag : tags->as_array()) { + record.tags.emplace_back(tag.to_string()); + } + } + + const auto *summary{value.try_at("summary", OPENAPI_HASH_SUMMARY)}; + if (summary != nullptr) { + openapi_expect_string(*summary, base, "summary"sv, + "The Operation Object summary must be a string"); + } + + const auto *description{ + value.try_at("description", OPENAPI_HASH_DESCRIPTION)}; + if (description != nullptr) { + openapi_expect_string(*description, base, "description"sv, + "The Operation Object description must be a string"); + } + + const auto *external_documentation{ + value.try_at("externalDocs", OPENAPI_HASH_EXTERNAL_DOCS)}; + if (external_documentation != nullptr) { + openapi_check_external_documentation( + *external_documentation, openapi_child(base, "externalDocs"sv), walk); + } + + // OpenAPI Specification 3.1.1, Section 4.8.10: "operationId | string | + // Unique string used to identify the operation. The id MUST be unique among + // all operations described in the API" + const auto *operation_id{ + value.try_at("operationId", OPENAPI_HASH_OPERATION_ID)}; + if (operation_id != nullptr) { + const auto identifier{openapi_expect_string( + *operation_id, base, "operationId"sv, + "The Operation Object identifier must be a string")}; + const auto location{openapi_location_uri(walk.base, base)}; + const auto match{ + walk.operation_ids.try_emplace(JSON::String{identifier}, location)}; + if (!match.second && match.first->second != location) { + throw OpenAPIError{openapi_child(base, "operationId"sv), + "The Operation Object identifiers must be unique"}; + } + } + + const auto *parameters{value.try_at("parameters", OPENAPI_HASH_PARAMETERS)}; + if (parameters != nullptr) { + record.parameters = openapi_check_parameters( + *parameters, openapi_child(base, "parameters"sv), + "The Operation Object parameters must be an array", + "The Operation Object parameters must not repeat a name and location", + walk); + } + + const auto *request_body{ + value.try_at("requestBody", OPENAPI_HASH_REQUEST_BODY)}; + if (request_body != nullptr) { + openapi_check_request_body_or_reference( + *request_body, openapi_child(base, "requestBody"sv), walk); + } + + const auto *responses{value.try_at("responses", OPENAPI_HASH_RESPONSES)}; + if (responses != nullptr) { + openapi_check_responses(*responses, openapi_child(base, "responses"sv), + walk); + } + + const auto *callbacks{value.try_at("callbacks", OPENAPI_HASH_CALLBACKS)}; + if (callbacks != nullptr) { + const auto location{openapi_child(base, "callbacks"sv)}; + openapi_expect_object(*callbacks, location, + "The Operation Object callbacks must be an object"); + for (const auto &entry : callbacks->as_object()) { + if (entry.first.starts_with(OPENAPI_EXTENSION_PREFIX)) { + continue; + } + + const auto callback{openapi_child(location, entry.first)}; + openapi_check_callbacks_or_reference(entry.second, callback, walk); + record.callbacks.push_back(openapi_location_uri(walk.base, callback)); + } + } + + const auto *deprecated{value.try_at("deprecated", OPENAPI_HASH_DEPRECATED)}; + if (deprecated != nullptr) { + openapi_expect_boolean(*deprecated, base, "deprecated"sv, + "The Operation Object deprecated must be a boolean"); + } + + const auto *security{value.try_at("security", OPENAPI_HASH_SECURITY)}; + if (security != nullptr) { + record.security = openapi_check_security( + *security, openapi_child(base, "security"sv), + "The Operation Object security must be an array", walk); + } + + const auto *servers{value.try_at("servers", OPENAPI_HASH_SERVERS)}; + if (servers != nullptr) { + record.servers = openapi_check_server_array( + *servers, openapi_child(base, "servers"sv), + "The Operation Object servers must be an array", walk); + } + + walk.operation_records.insert_or_assign(openapi_location_uri(walk.base, base), + std::move(record)); +} + +// OpenAPI Specification 3.1.1, Section 4.8.9: "Describes the operations +// available on a single path" +inline auto openapi_check_path_item(const JSON &value, const Pointer &base, + OpenAPIWalk &walk) -> void { + openapi_record(walk, base, OpenAPIObjectKind::PathItem); + openapi_expect_object(value, base, "The Path Item Object must be an object"); + openapi_reject_unknown_fields( + value, OPENAPI_PATH_ITEM_FIELDS, base, + "The Path Item Object does not define this field"); + + // OpenAPI Specification 3.1.1, Section 4.8.9: "$ref | string | Allows for a + // referenced definition of this path item. The value MUST be in the form of + // a URI". Unlike a Reference Object this is a plain field, and the + // specification leaves the behavior of its siblings undefined rather than + // forbidding them + const auto *target{value.try_at("$ref", OPENAPI_HASH_REF)}; + if (target != nullptr) { + const auto reference{openapi_expect_uri_reference( + *target, base, "$ref"sv, + "The Path Item Object reference must be a string", + "The Path Item Object reference must be a URI reference")}; + openapi_follow_reference(reference, openapi_child(base, "$ref"sv), + OpenAPIObjectKind::PathItem, walk); + } + + const auto *summary{value.try_at("summary", OPENAPI_HASH_SUMMARY)}; + if (summary != nullptr) { + openapi_expect_string(*summary, base, "summary"sv, + "The Path Item Object summary must be a string"); + } + + const auto *description{ + value.try_at("description", OPENAPI_HASH_DESCRIPTION)}; + if (description != nullptr) { + openapi_expect_string(*description, base, "description"sv, + "The Path Item Object description must be a string"); + } + + OpenAPIPathItemRecord record; + + const auto *servers{value.try_at("servers", OPENAPI_HASH_SERVERS)}; + if (servers != nullptr) { + record.servers = openapi_check_server_array( + *servers, openapi_child(base, "servers"sv), + "The Path Item Object servers must be an array", walk); + } + + const auto *parameters{value.try_at("parameters", OPENAPI_HASH_PARAMETERS)}; + if (parameters != nullptr) { + record.parameters = openapi_check_parameters( + *parameters, openapi_child(base, "parameters"sv), + "The Path Item Object parameters must be an array", + "The Path Item Object parameters must not repeat a name and location", + walk); + } + + for (const auto &method : OPENAPI_PATH_ITEM_METHODS) { + const auto *operation{value.try_at(JSON::String{method})}; + if (operation != nullptr) { + const auto location{openapi_child(base, method)}; + openapi_check_operation(*operation, location, walk); + record.operations.emplace_back(method, + openapi_location_uri(walk.base, location)); + } + } + + walk.path_items.insert_or_assign(openapi_location_uri(walk.base, base), + std::move(record)); +} + +} // namespace sourcemeta::core + +#endif diff --git a/src/core/openapi/paths.h b/src/core/openapi/paths.h new file mode 100644 index 000000000..a643ddb08 --- /dev/null +++ b/src/core/openapi/paths.h @@ -0,0 +1,155 @@ +#ifndef SOURCEMETA_CORE_OPENAPI_PATHS_H_ +#define SOURCEMETA_CORE_OPENAPI_PATHS_H_ + +#include + +#include "helpers.h" +#include "path_item.h" + +#include // std::size_t +#include // std::set +#include // std::string_view +#include // std::vector + +namespace sourcemeta::core { + +constexpr auto OPENAPI_HASH_PATHS{JSON::Object::hash("paths"sv)}; +constexpr auto OPENAPI_HASH_WEBHOOKS{JSON::Object::hash("webhooks"sv)}; + +// Two paths are "identical" when they differ only in what their template +// expressions are called, so comparing them means dropping those names while +// leaving every other byte in place +inline auto openapi_path_shape(const JSON::StringView path) -> JSON::String { + JSON::String result; + result.reserve(path.size()); + std::size_t cursor{0}; + while (cursor < path.size()) { + const auto open{path.find('{', cursor)}; + if (open == JSON::StringView::npos) { + break; + } + + // A brace that never closes opens no expression, so what follows it is + // ordinary text. Dropping it instead would make two paths that differ only + // after an unclosed brace compare as one, and 3.1 constrains neither + const auto close{path.find('}', open)}; + if (close == JSON::StringView::npos) { + break; + } + + result.append(path.substr(cursor, open - cursor)).append("{}"); + cursor = close + 1; + } + + result.append(path.substr(cursor)); + return result; +} + +// The template expressions a path declares. OpenAPI Specification 3.1.1, +// Section 4.3: "Path templating refers to the usage of template expressions, +// delimited by curly braces (`{}`), to mark a section of a URL path as +// replaceable using path parameters". Nothing there says what an unbalanced +// brace means, so a run that never closes is no expression +inline auto openapi_path_templates(const JSON::StringView path) + -> std::vector { + std::vector result; + std::size_t cursor{0}; + while (cursor < path.size()) { + const auto open{path.find('{', cursor)}; + if (open == JSON::StringView::npos) { + break; + } + + const auto close{path.find('}', open)}; + if (close == JSON::StringView::npos) { + break; + } + + result.push_back(path.substr(open + 1, close - open - 1)); + cursor = close + 1; + } + + return result; +} + +// OpenAPI Specification 3.1.1, Section 4.8.8: "Holds the relative paths to the +// individual endpoints and their operations" +inline auto openapi_check_paths(const JSON &document, OpenAPIWalk &walk) + -> void { + const auto *paths{document.try_at("paths", OPENAPI_HASH_PATHS)}; + if (paths == nullptr) { + return; + } + + const Pointer base{"paths"}; + openapi_record(walk, base, OpenAPIObjectKind::Paths); + openapi_expect_object(*paths, base, "The Paths Object must be an object"); + + std::set shapes; + for (const auto &entry : paths->as_object()) { + if (entry.first.starts_with(OPENAPI_EXTENSION_PREFIX)) { + continue; + } + + const auto location{openapi_child(base, entry.first)}; + + // OpenAPI Specification 3.1.1, Section 4.8.8: "The field name MUST begin + // with a forward slash (`/`)" + if (!entry.first.starts_with('/')) { + throw OpenAPIError{location, + "The Paths Object keys must begin with a slash"}; + } + + // OpenAPI Specification 3.1.1, Section 4.8.8: "Templated paths with the + // same hierarchy but different templated names MUST NOT exist as they are + // identical". The published meta-schema cannot state this, as no keyword + // there compares one property name against another + if (!shapes.insert(openapi_path_shape(entry.first)).second) { + throw OpenAPIError{ + location, "The Paths Object keys must not repeat a templated path"}; + } + + openapi_check_path_item(entry.second, location, walk); + + // Section 3: "only the entry document's Paths Object contributes URLs to + // the described API", so a Paths Object in a document a reference brought + // in describes nothing + if (walk.entry) { + walk.endpoints.push_back( + {.kind = OpenAPIOperationKind::Path, + .path = entry.first, + .path_item = openapi_location_uri(walk.base, location)}); + } + } +} + +// OpenAPI Specification 3.1.1, Section 4.8.1: "webhooks | Map[string, Path +// Item Object] | The incoming webhooks that MAY be received as part of this +// API". Its keys are names rather than paths, and unlike the Paths Object this +// map carries no extension carve-out, so a member named `x-` is a webhook +inline auto openapi_check_webhooks(const JSON &document, OpenAPIWalk &walk) + -> void { + const auto *webhooks{document.try_at("webhooks", OPENAPI_HASH_WEBHOOKS)}; + if (webhooks == nullptr) { + return; + } + + const Pointer base{"webhooks"}; + openapi_expect_object(*webhooks, base, + "The OpenAPI Description webhooks must be an object"); + + for (const auto &entry : webhooks->as_object()) { + const auto location{openapi_child(base, entry.first)}; + openapi_check_path_item(entry.second, location, walk); + if (walk.entry) { + walk.endpoints.push_back( + {.kind = OpenAPIOperationKind::Webhook, + .path = entry.first, + .path_item = openapi_location_uri(walk.base, location)}); + } + } +} + +} // namespace sourcemeta::core + +#endif diff --git a/src/core/openapi/reference.h b/src/core/openapi/reference.h new file mode 100644 index 000000000..dec84290b --- /dev/null +++ b/src/core/openapi/reference.h @@ -0,0 +1,61 @@ +#ifndef SOURCEMETA_CORE_OPENAPI_REFERENCE_H_ +#define SOURCEMETA_CORE_OPENAPI_REFERENCE_H_ + +#include + +#include "helpers.h" + +#include // std::string_view + +namespace sourcemeta::core { + +constexpr auto OPENAPI_HASH_REF{JSON::Object::hash("$ref"sv)}; + +// OpenAPI Specification 3.1.1, Section 4.8.23: "A simple object to allow +// referencing other components in the OpenAPI Description" +inline auto openapi_check_reference(const JSON &value, const Pointer &base, + const OpenAPIObjectKind expected, + OpenAPIWalk &walk) -> void { + openapi_record(walk, base, OpenAPIObjectKind::Reference); + // OpenAPI Specification 3.1.1, Section 4.8.23: "$ref | string | REQUIRED. + // The reference identifier. This MUST be in the form of a URI" + const auto *target{value.try_at("$ref", OPENAPI_HASH_REF)}; + const auto reference{openapi_expect_uri_reference( + *target, base, "$ref"sv, + "The Reference Object identifier must be a " + "string", + "The Reference Object identifier must be a URI reference")}; + + const auto *summary{value.try_at("summary", OPENAPI_HASH_SUMMARY)}; + if (summary != nullptr) { + openapi_expect_string(*summary, base, "summary"sv, + "The Reference Object summary must be a string"); + } + + const auto *description{ + value.try_at("description", OPENAPI_HASH_DESCRIPTION)}; + if (description != nullptr) { + openapi_expect_string(*description, base, "description"sv, + "The Reference Object description must be a string"); + } + + // OpenAPI Specification 3.1.1, Section 4.8.23: "This object cannot be + // extended with additional properties, and any properties added SHALL be + // ignored". Ignoring is what the specification asks for, so no member beyond + // the three above is read and none is turned down either + + // Following comes last, so that what is wrong with this Object is reported + // before whatever may be wrong at the far end of it + openapi_follow_reference(reference, openapi_child(base, "$ref"sv), expected, + walk); +} + +// A Reference Object stands in for most of the Objects the Components Object +// holds, and the meta-schema tells the two apart by whether `$ref` is present +inline auto openapi_is_reference(const JSON &value) -> bool { + return value.is_object() && value.try_at("$ref", OPENAPI_HASH_REF) != nullptr; +} + +} // namespace sourcemeta::core + +#endif diff --git a/src/core/openapi/request_body.h b/src/core/openapi/request_body.h new file mode 100644 index 000000000..9b56c8ca2 --- /dev/null +++ b/src/core/openapi/request_body.h @@ -0,0 +1,69 @@ +#ifndef SOURCEMETA_CORE_OPENAPI_REQUEST_BODY_H_ +#define SOURCEMETA_CORE_OPENAPI_REQUEST_BODY_H_ + +#include + +#include "content.h" +#include "helpers.h" +#include "reference.h" + +#include // std::array +#include // std::string_view + +namespace sourcemeta::core { + +constexpr std::array OPENAPI_REQUEST_BODY_FIELDS{ + {"description"sv, "content"sv, "required"sv}}; + +// OpenAPI Specification 3.1.1, Section 4.8.13: "Describes a single request +// body" +inline auto openapi_check_request_body(const JSON &value, const Pointer &base, + OpenAPIWalk &walk) -> void { + openapi_record(walk, base, OpenAPIObjectKind::RequestBody); + openapi_expect_object(value, base, + "The Request Body Object must be an object"); + openapi_reject_unknown_fields( + value, OPENAPI_REQUEST_BODY_FIELDS, base, + "The Request Body Object does not define this field"); + + const auto *description{ + value.try_at("description", OPENAPI_HASH_DESCRIPTION)}; + if (description != nullptr) { + openapi_expect_string( + *description, base, "description"sv, + "The Request Body Object description must be a string"); + } + + const auto *required{value.try_at("required", OPENAPI_HASH_REQUIRED)}; + if (required != nullptr) { + openapi_expect_boolean( + *required, base, "required"sv, + "The Request Body Object required must be a boolean"); + } + + // OpenAPI Specification 3.1.1, Section 4.8.13: "content | Map[string, Media + // Type Object] | REQUIRED. The content of the request body" + const auto *content{value.try_at("content", OPENAPI_HASH_CONTENT)}; + if (content == nullptr) { + throw OpenAPIError{base, "The Request Body Object must declare a content"}; + } + + openapi_check_content(*content, openapi_child(base, "content"sv), + "The Request Body Object content must be an object", + walk); +} + +inline auto openapi_check_request_body_or_reference(const JSON &value, + const Pointer &base, + OpenAPIWalk &walk) -> void { + if (openapi_is_reference(value)) { + openapi_check_reference(value, base, OpenAPIObjectKind::RequestBody, walk); + return; + } + + openapi_check_request_body(value, base, walk); +} + +} // namespace sourcemeta::core + +#endif diff --git a/src/core/openapi/response.h b/src/core/openapi/response.h new file mode 100644 index 000000000..a1929b467 --- /dev/null +++ b/src/core/openapi/response.h @@ -0,0 +1,144 @@ +#ifndef SOURCEMETA_CORE_OPENAPI_RESPONSE_H_ +#define SOURCEMETA_CORE_OPENAPI_RESPONSE_H_ + +#include + +#include "content.h" +#include "helpers.h" +#include "link.h" +#include "reference.h" + +#include // std::array +#include // std::string_view + +namespace sourcemeta::core { + +constexpr auto OPENAPI_HASH_LINKS{JSON::Object::hash("links"sv)}; +constexpr auto OPENAPI_HASH_DEFAULT{JSON::Object::hash("default"sv)}; + +constexpr std::array OPENAPI_RESPONSE_FIELDS{ + {"description"sv, "headers"sv, "content"sv, "links"sv}}; + +// OpenAPI Specification 3.1.1, Section 4.8.17: "Describes a single response +// from an API operation" +inline auto openapi_check_response(const JSON &value, const Pointer &base, + OpenAPIWalk &walk) -> void { + openapi_record(walk, base, OpenAPIObjectKind::Response); + openapi_expect_object(value, base, "The Response Object must be an object"); + openapi_reject_unknown_fields( + value, OPENAPI_RESPONSE_FIELDS, base, + "The Response Object does not define this field"); + + // OpenAPI Specification 3.1.1, Section 4.8.17: "description | string | + // REQUIRED. A description of the response" + const auto *description{ + value.try_at("description", OPENAPI_HASH_DESCRIPTION)}; + if (description == nullptr) { + throw OpenAPIError{base, "The Response Object must declare a description"}; + } + + openapi_expect_string(*description, base, "description"sv, + "The Response Object description must be a string"); + + const auto *headers{value.try_at("headers", OPENAPI_HASH_HEADERS)}; + if (headers != nullptr) { + const auto location{openapi_child(base, "headers"sv)}; + openapi_expect_object(*headers, location, + "The Response Object headers must be an object"); + for (const auto &entry : headers->as_object()) { + openapi_check_header_or_reference( + entry.second, openapi_child(location, entry.first), walk); + } + } + + const auto *content{value.try_at("content", OPENAPI_HASH_CONTENT)}; + if (content != nullptr) { + openapi_check_content(*content, openapi_child(base, "content"sv), + "The Response Object content must be an object", + walk); + } + + const auto *links{value.try_at("links", OPENAPI_HASH_LINKS)}; + if (links != nullptr) { + const auto location{openapi_child(base, "links"sv)}; + openapi_expect_object(*links, location, + "The Response Object links must be an object"); + for (const auto &entry : links->as_object()) { + openapi_check_link_or_reference( + entry.second, openapi_child(location, entry.first), walk); + } + } +} + +inline auto openapi_check_response_or_reference(const JSON &value, + const Pointer &base, + OpenAPIWalk &walk) -> void { + if (openapi_is_reference(value)) { + openapi_check_reference(value, base, OpenAPIObjectKind::Response, walk); + return; + } + + openapi_check_response(value, base, walk); +} + +// OpenAPI Specification 3.1.1, Section 4.8.16 keys this map by HTTP status +// code, and the meta-schema reads the allowed shapes as three digits from +// `100` through `599`, or a leading digit followed by `XX` for a whole range +inline auto openapi_is_status_code(const JSON::StringView code) noexcept + -> bool { + if (code.size() != 3 || code.front() < '1' || code.front() > '5') { + return false; + } + + if (code[1] == 'X' && code[2] == 'X') { + return true; + } + + return code[1] >= '0' && code[1] <= '9' && code[2] >= '0' && code[2] <= '9'; +} + +// OpenAPI Specification 3.1.1, Section 4.8.16: "A container for the expected +// responses of an operation" +inline auto openapi_check_responses(const JSON &value, const Pointer &base, + OpenAPIWalk &walk) -> void { + openapi_record(walk, base, OpenAPIObjectKind::Responses); + openapi_expect_object(value, base, "The Responses Object must be an object"); + + // The meta-schema bounds this map from below, and requires a `default` when + // no status code is named + if (value.empty()) { + throw OpenAPIError{base, "The Responses Object must not be empty"}; + } + + bool names_a_status_code{false}; + for (const auto &entry : value.as_object()) { + if (entry.first.starts_with(OPENAPI_EXTENSION_PREFIX)) { + continue; + } + + const auto location{openapi_child(base, entry.first)}; + if (entry.first == "default"sv) { + openapi_check_response_or_reference(entry.second, location, walk); + continue; + } + + if (!openapi_is_status_code(entry.first)) { + throw OpenAPIError{location, + "The Responses Object keys must be an HTTP status " + "code or a status code range"}; + } + + names_a_status_code = true; + openapi_check_response_or_reference(entry.second, location, walk); + } + + if (!names_a_status_code && + value.try_at("default", OPENAPI_HASH_DEFAULT) == nullptr) { + throw OpenAPIError{ + base, "The Responses Object must declare a default or a status code"}; + } +} + +} // namespace sourcemeta::core + +#endif diff --git a/src/core/openapi/security.h b/src/core/openapi/security.h new file mode 100644 index 000000000..feecd502c --- /dev/null +++ b/src/core/openapi/security.h @@ -0,0 +1,369 @@ +#ifndef SOURCEMETA_CORE_OPENAPI_SECURITY_H_ +#define SOURCEMETA_CORE_OPENAPI_SECURITY_H_ + +#include + +#include "helpers.h" +#include "reference.h" + +#include + +#include // std::array +#include // std::size_t +#include // std::string_view +#include // std::vector + +namespace sourcemeta::core { + +constexpr auto OPENAPI_HASH_SECURITY{JSON::Object::hash("security"sv)}; +constexpr auto OPENAPI_HASH_TYPE{JSON::Object::hash("type"sv)}; +constexpr auto OPENAPI_HASH_SCHEME{JSON::Object::hash("scheme"sv)}; +constexpr auto OPENAPI_HASH_BEARER_FORMAT{JSON::Object::hash("bearerFormat"sv)}; +constexpr auto OPENAPI_HASH_FLOWS{JSON::Object::hash("flows"sv)}; +constexpr auto OPENAPI_HASH_OPENID_CONNECT_URL{ + JSON::Object::hash("openIdConnectUrl"sv)}; +constexpr auto OPENAPI_HASH_SCOPES{JSON::Object::hash("scopes"sv)}; +constexpr auto OPENAPI_HASH_AUTHORIZATION_URL{ + JSON::Object::hash("authorizationUrl"sv)}; +constexpr auto OPENAPI_HASH_TOKEN_URL{JSON::Object::hash("tokenUrl"sv)}; +constexpr auto OPENAPI_HASH_REFRESH_URL{JSON::Object::hash("refreshUrl"sv)}; + +constexpr std::array OPENAPI_SECURITY_SCHEME_APIKEY_FIELDS{ + {"type"sv, "description"sv, "name"sv, "in"sv}}; +// OpenAPI Specification 3.1.1, Section 4.8.27 scopes `bearerFormat` to `http +// ("bearer")` in its "Applies To" column, so an HTTP scheme that is not bearer +// does not define it. The published meta-schema draws the same line, admitting +// the field only under a `scheme` matching `^[Bb][Ee][Aa][Rr][Ee][Rr]$` +constexpr std::array OPENAPI_SECURITY_SCHEME_HTTP_FIELDS{ + {"type"sv, "description"sv, "scheme"sv}}; +constexpr std::array + OPENAPI_SECURITY_SCHEME_HTTP_BEARER_FIELDS{ + {"type"sv, "description"sv, "scheme"sv, "bearerFormat"sv}}; +constexpr std::array OPENAPI_SECURITY_SCHEME_OAUTH2_FIELDS{ + {"type"sv, "description"sv, "flows"sv}}; +constexpr std::array OPENAPI_SECURITY_SCHEME_OIDC_FIELDS{ + {"type"sv, "description"sv, "openIdConnectUrl"sv}}; +constexpr std::array OPENAPI_SECURITY_SCHEME_COMMON_FIELDS{ + {"type"sv, "description"sv}}; + +constexpr std::array OPENAPI_OAUTH_FLOWS_FIELDS{ + {"implicit"sv, "password"sv, "clientCredentials"sv, "authorizationCode"sv}}; + +constexpr std::array OPENAPI_OAUTH_FLOW_FIELDS{ + {"authorizationUrl"sv, "tokenUrl"sv, "refreshUrl"sv, "scopes"sv}}; + +// OpenAPI Specification 3.1.1, Section 4.8.29: "Configuration details for a +// supported OAuth Flow". Which of the two URLs a flow must carry depends on +// which flow it is, and `scopes` is required by every one of them +inline auto openapi_check_oauth_flow(const JSON &value, const Pointer &base, + const bool needs_authorization_url, + const bool needs_token_url, + OpenAPIWalk &walk) -> void { + openapi_record(walk, base, OpenAPIObjectKind::OAuthFlow); + openapi_expect_object(value, base, "The OAuth Flow Object must be an object"); + openapi_reject_unknown_fields( + value, OPENAPI_OAUTH_FLOW_FIELDS, base, + "The OAuth Flow Object does not define this field"); + + // Section 4.8.29 scopes each URL to the flows it names in its "Applies To" + // column, and it names exactly the flows that require it. So a flow does not + // define the URL it does not require, which the published meta-schema draws + // the same way, giving each flow its own property set + if (!needs_authorization_url && + value.try_at("authorizationUrl", OPENAPI_HASH_AUTHORIZATION_URL) != + nullptr) { + throw OpenAPIError{ + openapi_child(base, "authorizationUrl"sv), + "This OAuth Flow Object does not define an authorization URI"}; + } + + if (!needs_token_url && + value.try_at("tokenUrl", OPENAPI_HASH_TOKEN_URL) != nullptr) { + throw OpenAPIError{openapi_child(base, "tokenUrl"sv), + "This OAuth Flow Object does not define a token URI"}; + } + + const auto *authorization{ + value.try_at("authorizationUrl", OPENAPI_HASH_AUTHORIZATION_URL)}; + if (needs_authorization_url && authorization == nullptr) { + throw OpenAPIError{ + base, "This OAuth Flow Object must declare an authorization URI"}; + } + + if (authorization != nullptr) { + openapi_expect_uri_reference( + *authorization, base, "authorizationUrl"sv, + "The OAuth Flow Object authorization URI must be a string", + "The OAuth Flow Object authorization URI must be a URI reference"); + } + + const auto *token{value.try_at("tokenUrl", OPENAPI_HASH_TOKEN_URL)}; + if (needs_token_url && token == nullptr) { + throw OpenAPIError{base, "This OAuth Flow Object must declare a token URI"}; + } + + if (token != nullptr) { + openapi_expect_uri_reference( + *token, base, "tokenUrl"sv, + "The OAuth Flow Object token URI must be a string", + "The OAuth Flow Object token URI must be a URI reference"); + } + + const auto *refresh{value.try_at("refreshUrl", OPENAPI_HASH_REFRESH_URL)}; + if (refresh != nullptr) { + openapi_expect_uri_reference( + *refresh, base, "refreshUrl"sv, + "The OAuth Flow Object refresh URI must be a string", + "The OAuth Flow Object refresh URI must be a URI reference"); + } + + // OpenAPI Specification 3.1.1, Section 4.8.29: "scopes | Map[string, string] + // | REQUIRED. The available scopes for the OAuth2 security scheme" + const auto *scopes{value.try_at("scopes", OPENAPI_HASH_SCOPES)}; + if (scopes == nullptr) { + throw OpenAPIError{base, "The OAuth Flow Object must declare its scopes"}; + } + + openapi_check_map_of_strings( + *scopes, openapi_child(base, "scopes"sv), + "The OAuth Flow Object scopes must be an object", + "The OAuth Flow Object scopes must hold strings"); +} + +// OpenAPI Specification 3.1.1, Section 4.8.28: "Allows configuration of the +// supported OAuth Flows" +inline auto openapi_check_oauth_flows(const JSON &value, const Pointer &base, + OpenAPIWalk &walk) -> void { + openapi_record(walk, base, OpenAPIObjectKind::OAuthFlows); + openapi_expect_object(value, base, + "The OAuth Flows Object must be an object"); + openapi_reject_unknown_fields( + value, OPENAPI_OAUTH_FLOWS_FIELDS, base, + "The OAuth Flows Object does not define this field"); + + const auto *implicit{ + value.try_at("implicit", JSON::Object::hash("implicit"sv))}; + if (implicit != nullptr) { + openapi_check_oauth_flow(*implicit, openapi_child(base, "implicit"sv), true, + false, walk); + } + + const auto *password{ + value.try_at("password", JSON::Object::hash("password"sv))}; + if (password != nullptr) { + openapi_check_oauth_flow(*password, openapi_child(base, "password"sv), + false, true, walk); + } + + const auto *client_credentials{value.try_at( + "clientCredentials", JSON::Object::hash("clientCredentials"sv))}; + if (client_credentials != nullptr) { + openapi_check_oauth_flow(*client_credentials, + openapi_child(base, "clientCredentials"sv), false, + true, walk); + } + + const auto *authorization_code{value.try_at( + "authorizationCode", JSON::Object::hash("authorizationCode"sv))}; + if (authorization_code != nullptr) { + openapi_check_oauth_flow(*authorization_code, + openapi_child(base, "authorizationCode"sv), true, + true, walk); + } +} + +// OpenAPI Specification 3.1.1, Section 4.8.27: "Defines a security scheme that +// can be used by the operations". Its field table carries an "Applies To" +// column, so which fields are required depends on the type it declares +inline auto openapi_check_security_scheme(const JSON &value, + const Pointer &base, + OpenAPIWalk &walk) -> void { + openapi_record(walk, base, OpenAPIObjectKind::SecurityScheme); + openapi_expect_object(value, base, + "The Security Scheme Object must be an object"); + + // OpenAPI Specification 3.1.1, Section 4.8.27: "type | string | Any | + // REQUIRED. The type of the security scheme" + const auto *type{value.try_at("type", OPENAPI_HASH_TYPE)}; + if (type == nullptr) { + throw OpenAPIError{base, + "The Security Scheme Object must declare its type"}; + } + + const auto scheme_type{openapi_expect_enumeration( + *type, base, "type"sv, + {"apiKey"sv, "http"sv, "mutualTLS"sv, "oauth2"sv, "openIdConnect"sv}, + "The Security Scheme Object type must be a string", + "The Security Scheme Object type is not one this specification " + "defines")}; + + const auto *description{ + value.try_at("description", OPENAPI_HASH_DESCRIPTION)}; + if (description != nullptr) { + openapi_expect_string( + *description, base, "description"sv, + "The Security Scheme Object description must be a string"); + } + + if (scheme_type == "apiKey"sv) { + openapi_reject_unknown_fields( + value, OPENAPI_SECURITY_SCHEME_APIKEY_FIELDS, base, + "The Security Scheme Object does not define this field"); + + const auto *name{value.try_at("name", OPENAPI_HASH_NAME)}; + if (name == nullptr) { + throw OpenAPIError{ + base, "An apiKey Security Scheme Object must declare a name"}; + } + + openapi_expect_string(*name, base, "name"sv, + "The Security Scheme Object name must be a string"); + + const auto *location{value.try_at("in", OPENAPI_HASH_IN)}; + if (location == nullptr) { + throw OpenAPIError{ + base, "An apiKey Security Scheme Object must declare a location"}; + } + + openapi_expect_enumeration( + *location, base, "in"sv, {"query"sv, "header"sv, "cookie"sv}, + "The Security Scheme Object location must be a string", + "The Security Scheme Object location is not one an apiKey admits"); + return; + } + + if (scheme_type == "http"sv) { + // RFC 7235 Section 2.1 makes an authentication scheme name + // case-insensitive, which is why the meta-schema spells bearer as a + // pattern rather than a constant + const auto *scheme{value.try_at("scheme", OPENAPI_HASH_SCHEME)}; + if (scheme != nullptr && scheme->is_string() && + equals_ignore_case(scheme->to_string(), "bearer"sv)) { + openapi_reject_unknown_fields( + value, OPENAPI_SECURITY_SCHEME_HTTP_BEARER_FIELDS, base, + "The Security Scheme Object does not define this field"); + } else { + openapi_reject_unknown_fields( + value, OPENAPI_SECURITY_SCHEME_HTTP_FIELDS, base, + "The Security Scheme Object does not define this field"); + } + + if (scheme == nullptr) { + throw OpenAPIError{ + base, "An http Security Scheme Object must declare a scheme"}; + } + + openapi_expect_string(*scheme, base, "scheme"sv, + "The Security Scheme Object scheme must be a string"); + + const auto *bearer{ + value.try_at("bearerFormat", OPENAPI_HASH_BEARER_FORMAT)}; + if (bearer != nullptr) { + openapi_expect_string( + *bearer, base, "bearerFormat"sv, + "The Security Scheme Object bearer format must be a string"); + } + + return; + } + + if (scheme_type == "oauth2"sv) { + openapi_reject_unknown_fields( + value, OPENAPI_SECURITY_SCHEME_OAUTH2_FIELDS, base, + "The Security Scheme Object does not define this field"); + + const auto *flows{value.try_at("flows", OPENAPI_HASH_FLOWS)}; + if (flows == nullptr) { + throw OpenAPIError{ + base, "An oauth2 Security Scheme Object must declare its flows"}; + } + + openapi_check_oauth_flows(*flows, openapi_child(base, "flows"sv), walk); + return; + } + + if (scheme_type == "openIdConnect"sv) { + openapi_reject_unknown_fields( + value, OPENAPI_SECURITY_SCHEME_OIDC_FIELDS, base, + "The Security Scheme Object does not define this field"); + + const auto *url{ + value.try_at("openIdConnectUrl", OPENAPI_HASH_OPENID_CONNECT_URL)}; + if (url == nullptr) { + throw OpenAPIError{base, "An openIdConnect Security Scheme Object must " + "declare its discovery URI"}; + } + + openapi_expect_uri_reference( + *url, base, "openIdConnectUrl"sv, + "The Security Scheme Object discovery URI must be a string", + "The Security Scheme Object discovery URI must be a URI reference"); + return; + } + + // A mutualTLS scheme carries nothing beyond the two fields every type has + openapi_reject_unknown_fields( + value, OPENAPI_SECURITY_SCHEME_COMMON_FIELDS, base, + "The Security Scheme Object does not define this field"); +} + +inline auto openapi_check_security_scheme_or_reference(const JSON &value, + const Pointer &base, + OpenAPIWalk &walk) + -> void { + if (openapi_is_reference(value)) { + openapi_check_reference(value, base, OpenAPIObjectKind::SecurityScheme, + walk); + return; + } + + openapi_check_security_scheme(value, base, walk); +} + +inline auto openapi_check_security_requirement(const JSON &value, + const Pointer &base, + OpenAPIWalk &walk) -> void { + openapi_record(walk, base, OpenAPIObjectKind::SecurityRequirement); + openapi_expect_object(value, base, + "The Security Requirement Object must be an object"); + for (const auto &entry : value.as_object()) { + // Section 4.8.30: "Each name MUST correspond to a security scheme which is + // declared in the Security Schemes under the Components Object". This + // Object declares no pattern but its names, so a member called `x-` is a + // scheme name and is held to the same requirement + if (!walk.security_schemes.contains(entry.first)) { + throw OpenAPIError{ + openapi_child(base, entry.first), + "The Security Requirement Object must name a declared security " + "scheme"}; + } + + openapi_check_array_of_strings( + entry.second, openapi_child(base, entry.first), + "The Security Requirement Object entries must be arrays", + "The Security Requirement Object entries must hold strings"); + } +} + +// What comes back is where each Security Requirement Object was recorded, so +// that an operation can name the requirements in force without repeating them +inline auto openapi_check_security(const JSON &value, const Pointer &base, + const char *type_message, OpenAPIWalk &walk) + -> std::vector { + openapi_expect_array(value, base, type_message); + std::vector result; + result.reserve(value.size()); + std::size_t index{0}; + for (const auto &requirement : value.as_array()) { + const auto location{openapi_child(base, index)}; + openapi_check_security_requirement(requirement, location, walk); + result.push_back(openapi_location_uri(walk.base, location)); + index += 1; + } + + return result; +} + +} // namespace sourcemeta::core + +#endif diff --git a/src/core/openapi/server.h b/src/core/openapi/server.h new file mode 100644 index 000000000..48f42acdd --- /dev/null +++ b/src/core/openapi/server.h @@ -0,0 +1,211 @@ +#ifndef SOURCEMETA_CORE_OPENAPI_SERVER_H_ +#define SOURCEMETA_CORE_OPENAPI_SERVER_H_ + +#include + +#include "helpers.h" + +#include // std::ranges::any_of +#include // std::array +#include // std::size_t +#include // std::string_view +#include // std::move +#include // std::vector + +namespace sourcemeta::core { + +constexpr auto OPENAPI_HASH_SERVER_VARIABLES{JSON::Object::hash("variables"sv)}; +constexpr auto OPENAPI_HASH_SERVER_ENUM{JSON::Object::hash("enum"sv)}; +constexpr auto OPENAPI_HASH_SERVER_DEFAULT{JSON::Object::hash("default"sv)}; + +constexpr std::array OPENAPI_SERVER_FIELDS{ + {"url"sv, "description"sv, "variables"sv}}; + +constexpr std::array OPENAPI_SERVER_VARIABLE_FIELDS{ + {"enum"sv, "default"sv, "description"sv}}; + +// OpenAPI Specification 3.1.1, Section 4.8.6: "An object representing a Server +// Variable for server URL template substitution" +inline auto openapi_check_server_variable(const JSON &value, + const Pointer &base, + OpenAPIWalk &walk) -> void { + openapi_record(walk, base, OpenAPIObjectKind::ServerVariable); + if (!value.is_object()) { + throw OpenAPIError{base, "The Server Variable Object must be an object"}; + } + + openapi_reject_unknown_fields( + value, OPENAPI_SERVER_VARIABLE_FIELDS, base, + "The Server Variable Object does not define this field"); + + // OpenAPI Specification 3.1.1, Section 4.8.6: "enum | [string] | An + // enumeration of string values to be used if the substitution options are + // from a limited set. The array MUST NOT be empty" + const auto *enumeration{value.try_at("enum", OPENAPI_HASH_SERVER_ENUM)}; + if (enumeration != nullptr) { + if (!enumeration->is_array()) { + throw OpenAPIError{ + openapi_child(base, "enum"sv), + "The Server Variable Object enumeration must be an array"}; + } + + if (enumeration->empty()) { + throw OpenAPIError{ + openapi_child(base, "enum"sv), + "The Server Variable Object enumeration must not be empty"}; + } + + const auto enumeration_base{openapi_child(base, "enum"sv)}; + std::size_t index{0}; + for (const auto &option : enumeration->as_array()) { + if (!option.is_string()) { + throw OpenAPIError{ + openapi_child(enumeration_base, index), + "The Server Variable Object enumeration must only hold strings"}; + } + + index += 1; + } + } + + // OpenAPI Specification 3.1.1, Section 4.8.6: "default | string | REQUIRED. + // The default value to use for substitution" + const auto *fallback{value.try_at("default", OPENAPI_HASH_SERVER_DEFAULT)}; + if (fallback == nullptr) { + throw OpenAPIError{base, + "The Server Variable Object must declare a default"}; + } + + const auto default_value{openapi_expect_string( + *fallback, base, "default"sv, + "The Server Variable Object default must be a string")}; + + const auto *description{ + value.try_at("description", OPENAPI_HASH_DESCRIPTION)}; + if (description != nullptr) { + openapi_expect_string( + *description, base, "description"sv, + "The Server Variable Object description must be a string"); + } + + // OpenAPI Specification 3.1.1, Section 4.8.6: "If the `enum` is defined, the + // value MUST exist in the enum's values". The published meta-schema does not + // check this, and the fixture the OpenAPI Initiative ships for an empty + // enumeration says so in a comment, so the prose is what this follows + if (enumeration != nullptr && + !std::ranges::any_of(enumeration->as_array(), + [default_value](const JSON &option) -> bool { + return option.to_string() == default_value; + })) { + throw OpenAPIError{ + openapi_child(base, "default"sv), + "The Server Variable Object default must exist in its enumeration"}; + } +} + +// OpenAPI Specification 3.1.1, Section 4.8.5: "An object representing a Server" +inline auto openapi_check_server(const JSON &value, const Pointer &base, + OpenAPIWalk &walk) -> void { + openapi_record(walk, base, OpenAPIObjectKind::Server); + if (!value.is_object()) { + throw OpenAPIError{base, "The Server Object must be an object"}; + } + + openapi_reject_unknown_fields(value, OPENAPI_SERVER_FIELDS, base, + "The Server Object does not define this field"); + + // OpenAPI Specification 3.1.1, Section 4.8.5: "url | string | REQUIRED. A + // URL to the target host. This URL supports Server Variables and MAY be + // relative". Beyond its type there is little to check, as it is a template + // rather than a URL once a variable is named in braces + const auto *url{value.try_at("url", OPENAPI_HASH_URL)}; + if (url == nullptr) { + throw OpenAPIError{base, "The Server Object must declare a URL"}; + } + + const auto address{openapi_expect_string( + *url, base, "url"sv, "The Server Object URL must be a string")}; + + // OpenAPI Specification 3.1.2, Section 4.8.5 adds to that row: "Query and + // fragment MUST NOT be part of this URL". A query begins at the first `?` + // and a fragment at the first `#`, so either character in the template + // starts one, whether or not it sits inside a variable expression. A + // percent-encoded one is neither and is left alone + if (address.find('?') != JSON::StringView::npos || + address.find('#') != JSON::StringView::npos) { + throw OpenAPIError{ + openapi_child(base, "url"sv), + "The Server Object URL must carry no query and no fragment"}; + } + + const auto *description{ + value.try_at("description", OPENAPI_HASH_DESCRIPTION)}; + if (description != nullptr) { + openapi_expect_string(*description, base, "description"sv, + "The Server Object description must be a string"); + } + + // OpenAPI Specification 3.1.1, Section 4.8.5: "variables | Map[string, + // Server Variable Object] | A map between a variable name and its value" + const auto *variables{ + value.try_at("variables", OPENAPI_HASH_SERVER_VARIABLES)}; + if (variables != nullptr) { + if (!variables->is_object()) { + throw OpenAPIError{openapi_child(base, "variables"sv), + "The Server Object variables must be an object"}; + } + + const auto variables_base{openapi_child(base, "variables"sv)}; + for (const auto &entry : variables->as_object()) { + openapi_check_server_variable( + entry.second, openapi_child(variables_base, entry.first), walk); + } + } +} + +// The three Objects that may declare servers do so the same way. What comes +// back is where each Server Object was recorded, which is how an operation +// names the servers in force where it sits without repeating them +inline auto openapi_check_server_array(const JSON &value, const Pointer &base, + const char *message, OpenAPIWalk &walk) + -> std::vector { + openapi_expect_array(value, base, message); + std::vector result; + result.reserve(value.size()); + std::size_t index{0}; + for (const auto &server : value.as_array()) { + const auto location{openapi_child(base, index)}; + openapi_check_server(server, location, walk); + result.push_back(openapi_location_uri(walk.base, location)); + index += 1; + } + + return result; +} + +// OpenAPI Specification 3.1.1, Section 4.8.1: "servers | [Server Object] | An +// array of Server Objects, which provide connectivity information to a target +// server". An absent or empty array is legal, as the same row states that both +// stand for a single server whose URL is a slash +inline auto openapi_check_servers(const JSON &document, OpenAPIWalk &walk) + -> void { + const auto *servers{document.try_at("servers", OPENAPI_HASH_SERVERS)}; + if (servers == nullptr) { + return; + } + + auto locations{openapi_check_server_array( + *servers, Pointer{"servers"}, + "The OpenAPI Description servers must be an array", walk)}; + + // Section 3: "only the entry document's Paths Object contributes URLs to the + // described API", which makes the entry document's servers the deployment + // information that every operation falls back on + if (walk.entry) { + walk.servers = std::move(locations); + } +} + +} // namespace sourcemeta::core + +#endif diff --git a/src/core/openapi/tag.h b/src/core/openapi/tag.h new file mode 100644 index 000000000..e12916283 --- /dev/null +++ b/src/core/openapi/tag.h @@ -0,0 +1,119 @@ +#ifndef SOURCEMETA_CORE_OPENAPI_TAG_H_ +#define SOURCEMETA_CORE_OPENAPI_TAG_H_ + +#include + +#include "external_documentation.h" +#include "helpers.h" + +#include // std::array +#include // std::size_t +#include // std::set +#include // std::string_view + +namespace sourcemeta::core { + +constexpr std::array OPENAPI_TAG_FIELDS{ + {"name"sv, "description"sv, "externalDocs"sv}}; + +// OpenAPI Specification 3.1.1, Section 4.8.22: "Adds metadata to a single tag +// that is used by the Operation Object" +inline auto openapi_check_tag(const JSON &value, const Pointer &base, + OpenAPIWalk &walk) -> JSON::StringView { + openapi_record(walk, base, OpenAPIObjectKind::Tag); + if (!value.is_object()) { + throw OpenAPIError{base, "The Tag Object must be an object"}; + } + + openapi_reject_unknown_fields(value, OPENAPI_TAG_FIELDS, base, + "The Tag Object does not define this field"); + + // OpenAPI Specification 3.1.1, Section 4.8.22: "name | string | REQUIRED. + // The name of the tag" + const auto *name{value.try_at("name", OPENAPI_HASH_NAME)}; + if (name == nullptr) { + throw OpenAPIError{base, "The Tag Object must declare a name"}; + } + + const auto result{openapi_expect_string( + *name, base, "name"sv, "The Tag Object name must be a string")}; + + const auto *description{ + value.try_at("description", OPENAPI_HASH_DESCRIPTION)}; + if (description != nullptr) { + openapi_expect_string(*description, base, "description"sv, + "The Tag Object description must be a string"); + } + + const auto *external_documentation{ + value.try_at("externalDocs", OPENAPI_HASH_EXTERNAL_DOCS)}; + if (external_documentation != nullptr) { + openapi_check_external_documentation( + *external_documentation, openapi_child(base, "externalDocs"sv), walk); + } + + return result; +} + +// OpenAPI Specification 3.1.1, Section 4.8.1: "tags | [Tag Object] | A list of +// tags used by the OpenAPI Description with additional metadata [...] Each tag +// name in the list MUST be unique" +// The tags the entry document declares, read before the walk goes anywhere so +// that the order documents are read in cannot decide what an Operation Object +// resolves against. Like the security schemes, this runs before the tags +// themselves have been checked, so it takes what is there and leaves being +// strict about the shape to the check that owns it +inline auto openapi_collect_tags(const JSON &document, OpenAPIWalk &walk) + -> void { + const auto *tags{document.try_at("tags", OPENAPI_HASH_TAGS)}; + if (tags == nullptr || !tags->is_array()) { + return; + } + + const Pointer base{"tags"}; + std::size_t index{0}; + for (const auto &tag : tags->as_array()) { + const auto *name{tag.is_object() ? tag.try_at("name", OPENAPI_HASH_NAME) + : nullptr}; + if (name != nullptr && name->is_string()) { + walk.tags.emplace( + name->to_string(), + openapi_location_uri(walk.base, openapi_child(base, index))); + } + + index += 1; + } +} + +inline auto openapi_check_tags(const JSON &document, OpenAPIWalk &walk) + -> void { + const auto *tags{document.try_at("tags", OPENAPI_HASH_TAGS)}; + if (tags == nullptr) { + return; + } + + const Pointer base{"tags"}; + if (!tags->is_array()) { + throw OpenAPIError{base, "The OpenAPI Description tags must be an array"}; + } + + // Uniqueness is stated in the prose alone. The published meta-schema says + // nothing about it, as no keyword there can compare a property across the + // members of an array + std::set names; + std::size_t index{0}; + for (const auto &tag : tags->as_array()) { + const auto location{openapi_child(base, index)}; + const auto name{openapi_check_tag(tag, location, walk)}; + if (!names.insert(name).second) { + throw OpenAPIError{openapi_child(location, "name"sv), + "The OpenAPI Description tag names must be unique"}; + } + + index += 1; + } +} + +} // namespace sourcemeta::core + +#endif diff --git a/src/core/openapi/version.cc b/src/core/openapi/version.cc new file mode 100644 index 000000000..63e6d6b09 --- /dev/null +++ b/src/core/openapi/version.cc @@ -0,0 +1,82 @@ +#include + +#include // std::optional, std::nullopt +#include // std::string_view + +namespace { +using namespace std::string_view_literals; + +constexpr auto HASH_OPENAPI{sourcemeta::core::JSON::Object::hash("openapi"sv)}; + +// ECMA-262 Section 22.2.2.7 compiles `Atom :: .` by taking every character and, +// when the `dotAll` flag is unset, "Remove from charSet all characters +// corresponding to a code point on the right-hand side of the LineTerminator +// production", which Table 32 lists as U+000A, U+000D, U+2028 and U+2029. JSON +// Schema Validation Section 6.3.3 reads `pattern` "according to the ECMA-262 +// regular expression dialect", so the `.` of the meta-schema pattern below +// matches everything except those four +auto contains_line_terminator(const sourcemeta::core::JSON::StringView value) + -> bool { + if (value.find_first_of("\x0A\x0D"sv) != + sourcemeta::core::JSON::StringView::npos) { + return true; + } + + // UTF-8 is self-synchronising, so neither of these sequences can occur + // within the encoding of any other code point + return value.find("\xE2\x80\xA8"sv) != + sourcemeta::core::JSON::StringView::npos || + value.find("\xE2\x80\xA9"sv) != + sourcemeta::core::JSON::StringView::npos; +} + +// OpenAPI Specification 3.1.1, Section 4.1: "The `major`.`minor` portion of +// the version string (for example `3.1`) SHALL designate the OAS feature set +// [...] The patch version SHOULD NOT be considered by tooling, making no +// distinction between `3.1.0` and `3.1.1` for example". The published +// meta-schema states the same rule as `^3\.1\.\d+(-.+)?$`, so a patch +// component is mandatory, its value carries no meaning, and a pre-release +// suffix of at least one non line terminator character is permitted +auto is_openapi_3_1(const sourcemeta::core::JSON::StringView version) -> bool { + constexpr auto PREFIX{"3.1."sv}; + if (!version.starts_with(PREFIX)) { + return false; + } + + const auto patch{version.substr(PREFIX.size())}; + const auto boundary{patch.find_first_not_of("0123456789"sv)}; + if (boundary == 0) { + return false; + } + + if (boundary == sourcemeta::core::JSON::StringView::npos) { + return !patch.empty(); + } + + const auto suffix{patch.substr(boundary)}; + return suffix.starts_with('-') && suffix.size() > 1 && + !contains_line_terminator(suffix.substr(1)); +} + +} // namespace + +namespace sourcemeta::core { + +auto openapi_version(const JSON &document) -> std::optional { + if (!document.is_object()) { + return std::nullopt; + } + + const auto *version{document.try_at("openapi", HASH_OPENAPI)}; + if (version == nullptr || !version->is_string()) { + return std::nullopt; + } + + if (is_openapi_3_1(version->to_string())) { + return OpenAPIVersion::OPENAPI_3_1; + } + + return std::nullopt; +} + +} // namespace sourcemeta::core diff --git a/test/jsonpointer/jsonpointer_to_uri_test.cc b/test/jsonpointer/jsonpointer_to_uri_test.cc index 46ba0d362..a550763b9 100644 --- a/test/jsonpointer/jsonpointer_to_uri_test.cc +++ b/test/jsonpointer/jsonpointer_to_uri_test.cc @@ -332,3 +332,17 @@ TEST(with_at_sign) { const auto fragment{sourcemeta::core::to_uri(pointer)}; EXPECT_EQ(fragment.recompose(), "#/@foo/bar"); } + +TEST(with_braces) { + const sourcemeta::core::Pointer pointer{"/pets/{petId}"}; + const sourcemeta::core::URI base{"https://www.example.com/openapi.json"}; + const sourcemeta::core::URI fragment{sourcemeta::core::to_uri(pointer, base)}; + EXPECT_EQ(fragment.recompose(), + "https://www.example.com/openapi.json#/~1pets~1%7BpetId%7D"); +} + +TEST(with_a_hash) { + const sourcemeta::core::Pointer pointer{"{$request.body#/url}"}; + const auto fragment{sourcemeta::core::to_uri(pointer)}; + EXPECT_EQ(fragment.recompose(), "#/%7B$request.body%23~1url%7D"); +} diff --git a/test/openapi/CMakeLists.txt b/test/openapi/CMakeLists.txt new file mode 100644 index 000000000..e812eb6cb --- /dev/null +++ b/test/openapi/CMakeLists.txt @@ -0,0 +1,32 @@ +sourcemeta_test(NAMESPACE sourcemeta PROJECT core NAME openapi + SOURCES openapi_frame_test.cc openapi_version_test.cc) + +target_link_libraries(sourcemeta_core_openapi_unit + PRIVATE sourcemeta::core::openapi) +target_link_libraries(sourcemeta_core_openapi_unit + PRIVATE sourcemeta::core::json) +target_link_libraries(sourcemeta_core_openapi_unit + PRIVATE sourcemeta::core::jsonpointer) + +# Complete frame tests, expressed as JSON fixtures +sourcemeta_test(NAMESPACE sourcemeta PROJECT core NAME openapi_frame_suite + SOURCES framesuite.cc) +target_compile_definitions(sourcemeta_core_openapi_frame_suite_unit + PRIVATE FRAME_SUITE_PATH="${PROJECT_SOURCE_DIR}/test/openapi/frame") +target_link_libraries(sourcemeta_core_openapi_frame_suite_unit + PRIVATE sourcemeta::core::openapi) +target_link_libraries(sourcemeta_core_openapi_frame_suite_unit + PRIVATE sourcemeta::core::json) + +# The OpenAPI Initiative's own meta-schema fixtures +# See https://github.com/OAI/OpenAPI-Specification/tree/v3.1-dev/tests/schema +sourcemeta_test(NAMESPACE sourcemeta PROJECT core NAME openapi_suite + SOURCES oaisuite.cc) +target_compile_definitions(sourcemeta_core_openapi_suite_unit + PRIVATE OPENAPI_SUITE_PATH="${PROJECT_SOURCE_DIR}/vendor/openapi-test-suite-3-1/tests/schema") +target_link_libraries(sourcemeta_core_openapi_suite_unit + PRIVATE sourcemeta::core::openapi) +target_link_libraries(sourcemeta_core_openapi_suite_unit + PRIVATE sourcemeta::core::json) +target_link_libraries(sourcemeta_core_openapi_suite_unit + PRIVATE sourcemeta::core::yaml) diff --git a/test/openapi/frame/3.1/fail/base_is_a_fragment.json b/test/openapi/frame/3.1/fail/base_is_a_fragment.json new file mode 100644 index 000000000..fb80f1032 --- /dev/null +++ b/test/openapi/frame/3.1/fail/base_is_a_fragment.json @@ -0,0 +1,16 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": {} + }, + "defaultBase": "#/paths", + "error": { + "message": "The OpenAPI Description base must be a URI with a scheme", + "location": "", + "base": "" + } +} diff --git a/test/openapi/frame/3.1/fail/base_is_a_relative_reference.json b/test/openapi/frame/3.1/fail/base_is_a_relative_reference.json new file mode 100644 index 000000000..3117695bc --- /dev/null +++ b/test/openapi/frame/3.1/fail/base_is_a_relative_reference.json @@ -0,0 +1,16 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": {} + }, + "defaultBase": "openapi.json", + "error": { + "message": "The OpenAPI Description base must be a URI with a scheme", + "location": "", + "base": "" + } +} diff --git a/test/openapi/frame/3.1/fail/base_is_an_absolute_path.json b/test/openapi/frame/3.1/fail/base_is_an_absolute_path.json new file mode 100644 index 000000000..2b5629fd4 --- /dev/null +++ b/test/openapi/frame/3.1/fail/base_is_an_absolute_path.json @@ -0,0 +1,16 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": {} + }, + "defaultBase": "/schemas/openapi.json", + "error": { + "message": "The OpenAPI Description base must be a URI with a scheme", + "location": "", + "base": "" + } +} diff --git a/test/openapi/frame/3.1/fail/base_is_malformed.json b/test/openapi/frame/3.1/fail/base_is_malformed.json new file mode 100644 index 000000000..5f282d0d8 --- /dev/null +++ b/test/openapi/frame/3.1/fail/base_is_malformed.json @@ -0,0 +1,16 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": {} + }, + "defaultBase": "://example.com", + "error": { + "message": "The OpenAPI Description base must be a URI with a scheme", + "location": "", + "base": "" + } +} diff --git a/test/openapi/frame/3.1/fail/base_is_not_a_uri.json b/test/openapi/frame/3.1/fail/base_is_not_a_uri.json new file mode 100644 index 000000000..7ade80894 --- /dev/null +++ b/test/openapi/frame/3.1/fail/base_is_not_a_uri.json @@ -0,0 +1,16 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": {} + }, + "defaultBase": "https://example.com/a b", + "error": { + "message": "The OpenAPI Description base must be a URI with a scheme", + "location": "", + "base": "" + } +} diff --git a/test/openapi/frame/3.1/fail/components_definitions_is_not_a_field.json b/test/openapi/frame/3.1/fail/components_definitions_is_not_a_field.json new file mode 100644 index 000000000..28cccf57c --- /dev/null +++ b/test/openapi/frame/3.1/fail/components_definitions_is_not_a_field.json @@ -0,0 +1,16 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "definitions": {} + } + }, + "error": { + "message": "The Components Object does not define this field", + "location": "/components/definitions" + } +} diff --git a/test/openapi/frame/3.1/fail/components_example_is_a_string.json b/test/openapi/frame/3.1/fail/components_example_is_a_string.json new file mode 100644 index 000000000..e48cfb83c --- /dev/null +++ b/test/openapi/frame/3.1/fail/components_example_is_a_string.json @@ -0,0 +1,18 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "examples": { + "Sample": "value" + } + } + }, + "error": { + "message": "The Components Object entries must be objects", + "location": "/components/examples/Sample" + } +} diff --git a/test/openapi/frame/3.1/fail/components_is_an_array.json b/test/openapi/frame/3.1/fail/components_is_an_array.json new file mode 100644 index 000000000..3842dbd62 --- /dev/null +++ b/test/openapi/frame/3.1/fail/components_is_an_array.json @@ -0,0 +1,14 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": [] + }, + "error": { + "message": "The Components Object must be an object", + "location": "/components" + } +} diff --git a/test/openapi/frame/3.1/fail/components_is_not_an_object.json b/test/openapi/frame/3.1/fail/components_is_not_an_object.json new file mode 100644 index 000000000..c4b7790c9 --- /dev/null +++ b/test/openapi/frame/3.1/fail/components_is_not_an_object.json @@ -0,0 +1,14 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": "schemas" + }, + "error": { + "message": "The Components Object must be an object", + "location": "/components" + } +} diff --git a/test/openapi/frame/3.1/fail/components_key_in_path_items.json b/test/openapi/frame/3.1/fail/components_key_in_path_items.json new file mode 100644 index 000000000..ea18d83b7 --- /dev/null +++ b/test/openapi/frame/3.1/fail/components_key_in_path_items.json @@ -0,0 +1,18 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "pathItems": { + "/pets": {} + } + } + }, + "error": { + "message": "The Components Object keys may only hold letters, digits, dots, hyphens and underscores", + "location": "/components/pathItems/~1pets" + } +} diff --git a/test/openapi/frame/3.1/fail/components_key_in_security_schemes.json b/test/openapi/frame/3.1/fail/components_key_in_security_schemes.json new file mode 100644 index 000000000..96c9eef7a --- /dev/null +++ b/test/openapi/frame/3.1/fail/components_key_in_security_schemes.json @@ -0,0 +1,18 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "securitySchemes": { + "My Key": {} + } + } + }, + "error": { + "message": "The Components Object keys may only hold letters, digits, dots, hyphens and underscores", + "location": "/components/securitySchemes/My Key" + } +} diff --git a/test/openapi/frame/3.1/fail/components_key_is_empty.json b/test/openapi/frame/3.1/fail/components_key_is_empty.json new file mode 100644 index 000000000..f87017df9 --- /dev/null +++ b/test/openapi/frame/3.1/fail/components_key_is_empty.json @@ -0,0 +1,18 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "schemas": { + "": {} + } + } + }, + "error": { + "message": "The Components Object keys must not be empty", + "location": "/components/schemas/" + } +} diff --git a/test/openapi/frame/3.1/fail/components_key_outside_ascii.json b/test/openapi/frame/3.1/fail/components_key_outside_ascii.json new file mode 100644 index 000000000..33e021591 --- /dev/null +++ b/test/openapi/frame/3.1/fail/components_key_outside_ascii.json @@ -0,0 +1,18 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "schemas": { + "Usu\u00e1rio": {} + } + } + }, + "error": { + "message": "The Components Object keys may only hold letters, digits, dots, hyphens and underscores", + "location": "/components/schemas/Usu\u00e1rio" + } +} diff --git a/test/openapi/frame/3.1/fail/components_key_with_a_dollar.json b/test/openapi/frame/3.1/fail/components_key_with_a_dollar.json new file mode 100644 index 000000000..31cde4c0a --- /dev/null +++ b/test/openapi/frame/3.1/fail/components_key_with_a_dollar.json @@ -0,0 +1,18 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "schemas": { + "$User": {} + } + } + }, + "error": { + "message": "The Components Object keys may only hold letters, digits, dots, hyphens and underscores", + "location": "/components/schemas/$User" + } +} diff --git a/test/openapi/frame/3.1/fail/components_key_with_a_slash.json b/test/openapi/frame/3.1/fail/components_key_with_a_slash.json new file mode 100644 index 000000000..87aa65262 --- /dev/null +++ b/test/openapi/frame/3.1/fail/components_key_with_a_slash.json @@ -0,0 +1,18 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "schemas": { + "my/User": {} + } + } + }, + "error": { + "message": "The Components Object keys may only hold letters, digits, dots, hyphens and underscores", + "location": "/components/schemas/my~1User" + } +} diff --git a/test/openapi/frame/3.1/fail/components_key_with_a_space.json b/test/openapi/frame/3.1/fail/components_key_with_a_space.json new file mode 100644 index 000000000..6f38229af --- /dev/null +++ b/test/openapi/frame/3.1/fail/components_key_with_a_space.json @@ -0,0 +1,18 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "schemas": { + "My User": {} + } + } + }, + "error": { + "message": "The Components Object keys may only hold letters, digits, dots, hyphens and underscores", + "location": "/components/schemas/My User" + } +} diff --git a/test/openapi/frame/3.1/fail/components_parameter_is_a_number.json b/test/openapi/frame/3.1/fail/components_parameter_is_a_number.json new file mode 100644 index 000000000..eb62e3a66 --- /dev/null +++ b/test/openapi/frame/3.1/fail/components_parameter_is_a_number.json @@ -0,0 +1,18 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "parameters": { + "Limit": 1 + } + } + }, + "error": { + "message": "The Components Object entries must be objects", + "location": "/components/parameters/Limit" + } +} diff --git a/test/openapi/frame/3.1/fail/components_path_item_is_an_array.json b/test/openapi/frame/3.1/fail/components_path_item_is_an_array.json new file mode 100644 index 000000000..cab2bbf01 --- /dev/null +++ b/test/openapi/frame/3.1/fail/components_path_item_is_an_array.json @@ -0,0 +1,18 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "pathItems": { + "Pets": [] + } + } + }, + "error": { + "message": "The Components Object entries must be objects", + "location": "/components/pathItems/Pets" + } +} diff --git a/test/openapi/frame/3.1/fail/components_path_items_is_not_an_object.json b/test/openapi/frame/3.1/fail/components_path_items_is_not_an_object.json new file mode 100644 index 000000000..bd9237109 --- /dev/null +++ b/test/openapi/frame/3.1/fail/components_path_items_is_not_an_object.json @@ -0,0 +1,16 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "pathItems": null + } + }, + "error": { + "message": "The Components Object fields must each be an object", + "location": "/components/pathItems" + } +} diff --git a/test/openapi/frame/3.1/fail/components_response_is_a_boolean.json b/test/openapi/frame/3.1/fail/components_response_is_a_boolean.json new file mode 100644 index 000000000..8de4d2c53 --- /dev/null +++ b/test/openapi/frame/3.1/fail/components_response_is_a_boolean.json @@ -0,0 +1,18 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "NotFound": true + } + } + }, + "error": { + "message": "The Components Object entries must be objects", + "location": "/components/responses/NotFound" + } +} diff --git a/test/openapi/frame/3.1/fail/components_responses_is_not_an_object.json b/test/openapi/frame/3.1/fail/components_responses_is_not_an_object.json new file mode 100644 index 000000000..a2d0f99ed --- /dev/null +++ b/test/openapi/frame/3.1/fail/components_responses_is_not_an_object.json @@ -0,0 +1,16 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": "none" + } + }, + "error": { + "message": "The Components Object fields must each be an object", + "location": "/components/responses" + } +} diff --git a/test/openapi/frame/3.1/fail/components_schema_is_a_number.json b/test/openapi/frame/3.1/fail/components_schema_is_a_number.json new file mode 100644 index 000000000..f62434a48 --- /dev/null +++ b/test/openapi/frame/3.1/fail/components_schema_is_a_number.json @@ -0,0 +1,18 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "schemas": { + "User": 1 + } + } + }, + "error": { + "message": "A Schema Object must be an object or a boolean", + "location": "/components/schemas/User" + } +} diff --git a/test/openapi/frame/3.1/fail/components_schema_is_a_string.json b/test/openapi/frame/3.1/fail/components_schema_is_a_string.json new file mode 100644 index 000000000..6baa19d8f --- /dev/null +++ b/test/openapi/frame/3.1/fail/components_schema_is_a_string.json @@ -0,0 +1,18 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "schemas": { + "User": "object" + } + } + }, + "error": { + "message": "A Schema Object must be an object or a boolean", + "location": "/components/schemas/User" + } +} diff --git a/test/openapi/frame/3.1/fail/components_schema_is_an_array.json b/test/openapi/frame/3.1/fail/components_schema_is_an_array.json new file mode 100644 index 000000000..dd5eb324f --- /dev/null +++ b/test/openapi/frame/3.1/fail/components_schema_is_an_array.json @@ -0,0 +1,18 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "schemas": { + "User": [] + } + } + }, + "error": { + "message": "A Schema Object must be an object or a boolean", + "location": "/components/schemas/User" + } +} diff --git a/test/openapi/frame/3.1/fail/components_schema_is_null.json b/test/openapi/frame/3.1/fail/components_schema_is_null.json new file mode 100644 index 000000000..fef00d963 --- /dev/null +++ b/test/openapi/frame/3.1/fail/components_schema_is_null.json @@ -0,0 +1,18 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "schemas": { + "User": null + } + } + }, + "error": { + "message": "A Schema Object must be an object or a boolean", + "location": "/components/schemas/User" + } +} diff --git a/test/openapi/frame/3.1/fail/components_schemas_is_not_an_object.json b/test/openapi/frame/3.1/fail/components_schemas_is_not_an_object.json new file mode 100644 index 000000000..7768efd5d --- /dev/null +++ b/test/openapi/frame/3.1/fail/components_schemas_is_not_an_object.json @@ -0,0 +1,16 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "schemas": [] + } + }, + "error": { + "message": "The Components Object fields must each be an object", + "location": "/components/schemas" + } +} diff --git a/test/openapi/frame/3.1/fail/components_security_scheme_is_null.json b/test/openapi/frame/3.1/fail/components_security_scheme_is_null.json new file mode 100644 index 000000000..d7832809b --- /dev/null +++ b/test/openapi/frame/3.1/fail/components_security_scheme_is_null.json @@ -0,0 +1,18 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "securitySchemes": { + "ApiKey": null + } + } + }, + "error": { + "message": "The Components Object entries must be objects", + "location": "/components/securitySchemes/ApiKey" + } +} diff --git a/test/openapi/frame/3.1/fail/components_unknown_field.json b/test/openapi/frame/3.1/fail/components_unknown_field.json new file mode 100644 index 000000000..ada7cac2e --- /dev/null +++ b/test/openapi/frame/3.1/fail/components_unknown_field.json @@ -0,0 +1,16 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "schema": {} + } + }, + "error": { + "message": "The Components Object does not define this field", + "location": "/components/schema" + } +} diff --git a/test/openapi/frame/3.1/fail/contact_email_is_not_a_string.json b/test/openapi/frame/3.1/fail/contact_email_is_not_a_string.json new file mode 100644 index 000000000..7de36b8e7 --- /dev/null +++ b/test/openapi/frame/3.1/fail/contact_email_is_not_a_string.json @@ -0,0 +1,17 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0", + "contact": { + "email": 1 + } + }, + "paths": {} + }, + "error": { + "message": "The Contact Object email must be a string", + "location": "/info/contact/email" + } +} diff --git a/test/openapi/frame/3.1/fail/contact_email_is_not_an_email_address.json b/test/openapi/frame/3.1/fail/contact_email_is_not_an_email_address.json new file mode 100644 index 000000000..683736e14 --- /dev/null +++ b/test/openapi/frame/3.1/fail/contact_email_is_not_an_email_address.json @@ -0,0 +1,17 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0", + "contact": { + "email": "support" + } + }, + "paths": {} + }, + "error": { + "message": "The Contact Object email must be an email address", + "location": "/info/contact/email" + } +} diff --git a/test/openapi/frame/3.1/fail/contact_is_not_an_object.json b/test/openapi/frame/3.1/fail/contact_is_not_an_object.json new file mode 100644 index 000000000..571164816 --- /dev/null +++ b/test/openapi/frame/3.1/fail/contact_is_not_an_object.json @@ -0,0 +1,15 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0", + "contact": "support@example.com" + }, + "paths": {} + }, + "error": { + "message": "The Contact Object must be an object", + "location": "/info/contact" + } +} diff --git a/test/openapi/frame/3.1/fail/contact_name_is_not_a_string.json b/test/openapi/frame/3.1/fail/contact_name_is_not_a_string.json new file mode 100644 index 000000000..45695c0df --- /dev/null +++ b/test/openapi/frame/3.1/fail/contact_name_is_not_a_string.json @@ -0,0 +1,17 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0", + "contact": { + "name": 1 + } + }, + "paths": {} + }, + "error": { + "message": "The Contact Object name must be a string", + "location": "/info/contact/name" + } +} diff --git a/test/openapi/frame/3.1/fail/contact_unknown_field.json b/test/openapi/frame/3.1/fail/contact_unknown_field.json new file mode 100644 index 000000000..d7b969021 --- /dev/null +++ b/test/openapi/frame/3.1/fail/contact_unknown_field.json @@ -0,0 +1,17 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0", + "contact": { + "phone": "555" + } + }, + "paths": {} + }, + "error": { + "message": "The Contact Object does not define this field", + "location": "/info/contact/phone" + } +} diff --git a/test/openapi/frame/3.1/fail/contact_url_is_not_a_string.json b/test/openapi/frame/3.1/fail/contact_url_is_not_a_string.json new file mode 100644 index 000000000..4332f98d9 --- /dev/null +++ b/test/openapi/frame/3.1/fail/contact_url_is_not_a_string.json @@ -0,0 +1,17 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0", + "contact": { + "url": 1 + } + }, + "paths": {} + }, + "error": { + "message": "The Contact Object URI must be a string", + "location": "/info/contact/url" + } +} diff --git a/test/openapi/frame/3.1/fail/contact_url_is_not_a_uri_reference.json b/test/openapi/frame/3.1/fail/contact_url_is_not_a_uri_reference.json new file mode 100644 index 000000000..09b00237a --- /dev/null +++ b/test/openapi/frame/3.1/fail/contact_url_is_not_a_uri_reference.json @@ -0,0 +1,17 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0", + "contact": { + "url": "https://example.com/a b" + } + }, + "paths": {} + }, + "error": { + "message": "The Contact Object URI must be a URI reference", + "location": "/info/contact/url" + } +} diff --git a/test/openapi/frame/3.1/fail/document_array.json b/test/openapi/frame/3.1/fail/document_array.json new file mode 100644 index 000000000..841275565 --- /dev/null +++ b/test/openapi/frame/3.1/fail/document_array.json @@ -0,0 +1,11 @@ +{ + "document": [ + { + "openapi": "3.1.1" + } + ], + "error": { + "message": "The OpenAPI Description must be an object", + "location": "" + } +} diff --git a/test/openapi/frame/3.1/fail/document_boolean.json b/test/openapi/frame/3.1/fail/document_boolean.json new file mode 100644 index 000000000..52bd71067 --- /dev/null +++ b/test/openapi/frame/3.1/fail/document_boolean.json @@ -0,0 +1,7 @@ +{ + "document": true, + "error": { + "message": "The OpenAPI Description must be an object", + "location": "" + } +} diff --git a/test/openapi/frame/3.1/fail/document_integer.json b/test/openapi/frame/3.1/fail/document_integer.json new file mode 100644 index 000000000..00c25f912 --- /dev/null +++ b/test/openapi/frame/3.1/fail/document_integer.json @@ -0,0 +1,7 @@ +{ + "document": 3, + "error": { + "message": "The OpenAPI Description must be an object", + "location": "" + } +} diff --git a/test/openapi/frame/3.1/fail/document_null.json b/test/openapi/frame/3.1/fail/document_null.json new file mode 100644 index 000000000..5b73426b9 --- /dev/null +++ b/test/openapi/frame/3.1/fail/document_null.json @@ -0,0 +1,7 @@ +{ + "document": null, + "error": { + "message": "The OpenAPI Description must be an object", + "location": "" + } +} diff --git a/test/openapi/frame/3.1/fail/document_real.json b/test/openapi/frame/3.1/fail/document_real.json new file mode 100644 index 000000000..728d32bb8 --- /dev/null +++ b/test/openapi/frame/3.1/fail/document_real.json @@ -0,0 +1,7 @@ +{ + "document": 3.1, + "error": { + "message": "The OpenAPI Description must be an object", + "location": "" + } +} diff --git a/test/openapi/frame/3.1/fail/document_string.json b/test/openapi/frame/3.1/fail/document_string.json new file mode 100644 index 000000000..9fdd99ad0 --- /dev/null +++ b/test/openapi/frame/3.1/fail/document_string.json @@ -0,0 +1,7 @@ +{ + "document": "3.1.1", + "error": { + "message": "The OpenAPI Description must be an object", + "location": "" + } +} diff --git a/test/openapi/frame/3.1/fail/encoding_explode_is_not_a_boolean.json b/test/openapi/frame/3.1/fail/encoding_explode_is_not_a_boolean.json new file mode 100644 index 000000000..3bec153c6 --- /dev/null +++ b/test/openapi/frame/3.1/fail/encoding_explode_is_not_a_boolean.json @@ -0,0 +1,29 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "Item": { + "description": "ok", + "content": { + "application/json": { + "encoding": { + "part": { + "explode": 1 + } + } + } + } + } + } + } + }, + "error": { + "message": "The Encoding Object explode must be a boolean", + "location": "/components/responses/Item/content/application~1json/encoding/part/explode" + } +} diff --git a/test/openapi/frame/3.1/fail/encoding_style_is_unknown.json b/test/openapi/frame/3.1/fail/encoding_style_is_unknown.json new file mode 100644 index 000000000..da4dd593c --- /dev/null +++ b/test/openapi/frame/3.1/fail/encoding_style_is_unknown.json @@ -0,0 +1,29 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "Item": { + "description": "ok", + "content": { + "application/json": { + "encoding": { + "part": { + "style": "matrix" + } + } + } + } + } + } + } + }, + "error": { + "message": "The Encoding Object style is not one this specification defines", + "location": "/components/responses/Item/content/application~1json/encoding/part/style" + } +} diff --git a/test/openapi/frame/3.1/fail/example_external_value_is_not_a_uri_reference.json b/test/openapi/frame/3.1/fail/example_external_value_is_not_a_uri_reference.json new file mode 100644 index 000000000..3fa9cd2a5 --- /dev/null +++ b/test/openapi/frame/3.1/fail/example_external_value_is_not_a_uri_reference.json @@ -0,0 +1,20 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "examples": { + "Item": { + "externalValue": "https://example.com/a b" + } + } + } + }, + "error": { + "message": "The Example Object external value must be a URI reference", + "location": "/components/examples/Item/externalValue" + } +} diff --git a/test/openapi/frame/3.1/fail/example_unknown_field.json b/test/openapi/frame/3.1/fail/example_unknown_field.json new file mode 100644 index 000000000..9977d944e --- /dev/null +++ b/test/openapi/frame/3.1/fail/example_unknown_field.json @@ -0,0 +1,20 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "examples": { + "Item": { + "values": 1 + } + } + } + }, + "error": { + "message": "The Example Object does not define this field", + "location": "/components/examples/Item/values" + } +} diff --git a/test/openapi/frame/3.1/fail/example_value_and_external_value.json b/test/openapi/frame/3.1/fail/example_value_and_external_value.json new file mode 100644 index 000000000..2f567dd35 --- /dev/null +++ b/test/openapi/frame/3.1/fail/example_value_and_external_value.json @@ -0,0 +1,21 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "examples": { + "Item": { + "value": 1, + "externalValue": "https://example.com" + } + } + } + }, + "error": { + "message": "The Example Object value and external value are mutually exclusive", + "location": "/components/examples/Item" + } +} diff --git a/test/openapi/frame/3.1/fail/external_docs_description_is_not_a_string.json b/test/openapi/frame/3.1/fail/external_docs_description_is_not_a_string.json new file mode 100644 index 000000000..f6e1434c0 --- /dev/null +++ b/test/openapi/frame/3.1/fail/external_docs_description_is_not_a_string.json @@ -0,0 +1,18 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "externalDocs": { + "url": "https://example.com", + "description": 1 + }, + "paths": {} + }, + "error": { + "message": "The External Documentation Object description must be a string", + "location": "/externalDocs/description" + } +} diff --git a/test/openapi/frame/3.1/fail/external_docs_is_an_array.json b/test/openapi/frame/3.1/fail/external_docs_is_an_array.json new file mode 100644 index 000000000..132676ea2 --- /dev/null +++ b/test/openapi/frame/3.1/fail/external_docs_is_an_array.json @@ -0,0 +1,15 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "externalDocs": [], + "paths": {} + }, + "error": { + "message": "The External Documentation Object must be an object", + "location": "/externalDocs" + } +} diff --git a/test/openapi/frame/3.1/fail/external_docs_is_not_an_object.json b/test/openapi/frame/3.1/fail/external_docs_is_not_an_object.json new file mode 100644 index 000000000..a41e6971f --- /dev/null +++ b/test/openapi/frame/3.1/fail/external_docs_is_not_an_object.json @@ -0,0 +1,15 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "externalDocs": "https://example.com", + "paths": {} + }, + "error": { + "message": "The External Documentation Object must be an object", + "location": "/externalDocs" + } +} diff --git a/test/openapi/frame/3.1/fail/external_docs_missing_url.json b/test/openapi/frame/3.1/fail/external_docs_missing_url.json new file mode 100644 index 000000000..3912e08e1 --- /dev/null +++ b/test/openapi/frame/3.1/fail/external_docs_missing_url.json @@ -0,0 +1,17 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "externalDocs": { + "description": "Find more info here" + }, + "paths": {} + }, + "error": { + "message": "The External Documentation Object must declare a URI", + "location": "/externalDocs" + } +} diff --git a/test/openapi/frame/3.1/fail/external_docs_unknown_field.json b/test/openapi/frame/3.1/fail/external_docs_unknown_field.json new file mode 100644 index 000000000..cfbbcc969 --- /dev/null +++ b/test/openapi/frame/3.1/fail/external_docs_unknown_field.json @@ -0,0 +1,18 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "externalDocs": { + "url": "https://example.com", + "title": "Docs" + }, + "paths": {} + }, + "error": { + "message": "The External Documentation Object does not define this field", + "location": "/externalDocs/title" + } +} diff --git a/test/openapi/frame/3.1/fail/external_docs_url_is_not_a_string.json b/test/openapi/frame/3.1/fail/external_docs_url_is_not_a_string.json new file mode 100644 index 000000000..8acf13d39 --- /dev/null +++ b/test/openapi/frame/3.1/fail/external_docs_url_is_not_a_string.json @@ -0,0 +1,17 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "externalDocs": { + "url": 1 + }, + "paths": {} + }, + "error": { + "message": "The External Documentation Object URI must be a string", + "location": "/externalDocs/url" + } +} diff --git a/test/openapi/frame/3.1/fail/external_docs_url_is_not_a_uri_reference.json b/test/openapi/frame/3.1/fail/external_docs_url_is_not_a_uri_reference.json new file mode 100644 index 000000000..1a9a54fd3 --- /dev/null +++ b/test/openapi/frame/3.1/fail/external_docs_url_is_not_a_uri_reference.json @@ -0,0 +1,17 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "externalDocs": { + "url": "https://example.com/a b" + }, + "paths": {} + }, + "error": { + "message": "The External Documentation Object URI must be a URI reference", + "location": "/externalDocs/url" + } +} diff --git a/test/openapi/frame/3.1/fail/external_document_has_an_unsupported_version.json b/test/openapi/frame/3.1/fail/external_document_has_an_unsupported_version.json new file mode 100644 index 000000000..3e13a7932 --- /dev/null +++ b/test/openapi/frame/3.1/fail/external_document_has_an_unsupported_version.json @@ -0,0 +1,32 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "R": { + "$ref": "https://example.com/common.json#/components/responses/NotFound" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "error": { + "message": "Unsupported OpenAPI Specification version", + "location": "/openapi", + "base": "https://example.com/common.json" + }, + "resolver": { + "https://example.com/common.json": { + "openapi": "3.0.4", + "info": { + "title": "C", + "version": "1" + }, + "components": {} + } + } +} diff --git a/test/openapi/frame/3.1/fail/external_document_is_invalid.json b/test/openapi/frame/3.1/fail/external_document_is_invalid.json new file mode 100644 index 000000000..896386b1f --- /dev/null +++ b/test/openapi/frame/3.1/fail/external_document_is_invalid.json @@ -0,0 +1,36 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "R": { + "$ref": "https://example.com/common.json#/components/responses/NotFound" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "error": { + "message": "The Response Object must declare a description", + "location": "/components/responses/NotFound", + "base": "https://example.com/common.json" + }, + "resolver": { + "https://example.com/common.json": { + "openapi": "3.1.1", + "info": { + "title": "Common", + "version": "1.0.0" + }, + "components": { + "responses": { + "NotFound": {} + } + } + } + } +} diff --git a/test/openapi/frame/3.1/fail/external_document_repeats_an_operation_id.json b/test/openapi/frame/3.1/fail/external_document_repeats_an_operation_id.json new file mode 100644 index 000000000..8ee3097d3 --- /dev/null +++ b/test/openapi/frame/3.1/fail/external_document_repeats_an_operation_id.json @@ -0,0 +1,62 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets": { + "get": { + "operationId": "listPets", + "responses": { + "200": { + "description": "ok" + } + } + } + } + }, + "components": { + "responses": { + "R": { + "$ref": "https://example.com/common.json#/components/responses/NotFound" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "error": { + "message": "The Operation Object identifiers must be unique", + "location": "/paths/~1pets/get/operationId", + "base": "https://example.com/openapi.json" + }, + "resolver": { + "https://example.com/common.json": { + "openapi": "3.1.1", + "info": { + "title": "Common", + "version": "1.0.0" + }, + "components": { + "responses": { + "NotFound": { + "description": "ok" + } + } + }, + "paths": { + "/pets": { + "get": { + "operationId": "listPets", + "responses": { + "200": { + "description": "ok" + } + } + } + } + } + } + } +} diff --git a/test/openapi/frame/3.1/fail/external_document_two_deep_is_invalid.json b/test/openapi/frame/3.1/fail/external_document_two_deep_is_invalid.json new file mode 100644 index 000000000..6c8913453 --- /dev/null +++ b/test/openapi/frame/3.1/fail/external_document_two_deep_is_invalid.json @@ -0,0 +1,50 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "R": { + "$ref": "https://example.com/first.json#/components/responses/Onward" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/first.json": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "Onward": { + "$ref": "https://example.com/second.json#/components/responses/Ok" + } + } + } + }, + "https://example.com/second.json": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "Ok": {} + } + } + } + }, + "error": { + "message": "The Response Object must declare a description", + "location": "/components/responses/Ok", + "base": "https://example.com/second.json" + } +} diff --git a/test/openapi/frame/3.1/fail/external_reference_then_an_entry_document_error.json b/test/openapi/frame/3.1/fail/external_reference_then_an_entry_document_error.json new file mode 100644 index 000000000..977be1c10 --- /dev/null +++ b/test/openapi/frame/3.1/fail/external_reference_then_an_entry_document_error.json @@ -0,0 +1,46 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "R": { + "$ref": "https://example.com/first.json#/components/responses/Ok" + } + } + }, + "tags": [ + { + "name": "pet" + }, + { + "name": "pet" + } + ] + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/first.json": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "Ok": { + "description": "ok" + } + } + } + } + }, + "error": { + "message": "The OpenAPI Description tag names must be unique", + "location": "/tags/1/name", + "base": "https://example.com/openapi.json" + } +} diff --git a/test/openapi/frame/3.1/fail/header_allow_reserved_is_not_a_field.json b/test/openapi/frame/3.1/fail/header_allow_reserved_is_not_a_field.json new file mode 100644 index 000000000..95709bc94 --- /dev/null +++ b/test/openapi/frame/3.1/fail/header_allow_reserved_is_not_a_field.json @@ -0,0 +1,21 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "headers": { + "Item": { + "schema": {}, + "allowReserved": true + } + } + } + }, + "error": { + "message": "The Header Object does not define this field", + "location": "/components/headers/Item/allowReserved" + } +} diff --git a/test/openapi/frame/3.1/fail/header_content_with_two_entries.json b/test/openapi/frame/3.1/fail/header_content_with_two_entries.json new file mode 100644 index 000000000..bcb1c5a5f --- /dev/null +++ b/test/openapi/frame/3.1/fail/header_content_with_two_entries.json @@ -0,0 +1,23 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "headers": { + "Item": { + "content": { + "text/plain": {}, + "text/html": {} + } + } + } + } + }, + "error": { + "message": "The Header Object content must hold exactly one entry", + "location": "/components/headers/Item/content" + } +} diff --git a/test/openapi/frame/3.1/fail/header_style_is_not_simple.json b/test/openapi/frame/3.1/fail/header_style_is_not_simple.json new file mode 100644 index 000000000..85ea64dac --- /dev/null +++ b/test/openapi/frame/3.1/fail/header_style_is_not_simple.json @@ -0,0 +1,21 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "headers": { + "Item": { + "schema": {}, + "style": "form" + } + } + } + }, + "error": { + "message": "The Header Object style must be simple", + "location": "/components/headers/Item/style" + } +} diff --git a/test/openapi/frame/3.1/fail/header_style_with_content.json b/test/openapi/frame/3.1/fail/header_style_with_content.json new file mode 100644 index 000000000..327bedcaa --- /dev/null +++ b/test/openapi/frame/3.1/fail/header_style_with_content.json @@ -0,0 +1,23 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "headers": { + "Item": { + "content": { + "text/plain": {} + }, + "style": "simple" + } + } + } + }, + "error": { + "message": "The Header Object does not define this field", + "location": "/components/headers/Item/style" + } +} diff --git a/test/openapi/frame/3.1/fail/header_with_schema_and_content.json b/test/openapi/frame/3.1/fail/header_with_schema_and_content.json new file mode 100644 index 000000000..a1aa75279 --- /dev/null +++ b/test/openapi/frame/3.1/fail/header_with_schema_and_content.json @@ -0,0 +1,23 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "headers": { + "Item": { + "schema": {}, + "content": { + "text/plain": {} + } + } + } + } + }, + "error": { + "message": "The Header Object schema and content are mutually exclusive", + "location": "/components/headers/Item" + } +} diff --git a/test/openapi/frame/3.1/fail/header_without_schema_or_content.json b/test/openapi/frame/3.1/fail/header_without_schema_or_content.json new file mode 100644 index 000000000..19e9c8682 --- /dev/null +++ b/test/openapi/frame/3.1/fail/header_without_schema_or_content.json @@ -0,0 +1,18 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "headers": { + "Item": {} + } + } + }, + "error": { + "message": "The Header Object must declare a schema or a content", + "location": "/components/headers/Item" + } +} diff --git a/test/openapi/frame/3.1/fail/info_description_is_not_a_string.json b/test/openapi/frame/3.1/fail/info_description_is_not_a_string.json new file mode 100644 index 000000000..572b26ab4 --- /dev/null +++ b/test/openapi/frame/3.1/fail/info_description_is_not_a_string.json @@ -0,0 +1,15 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0", + "description": false + }, + "paths": {} + }, + "error": { + "message": "The Info Object description must be a string", + "location": "/info/description" + } +} diff --git a/test/openapi/frame/3.1/fail/info_extension_without_the_prefix.json b/test/openapi/frame/3.1/fail/info_extension_without_the_prefix.json new file mode 100644 index 000000000..b60f95806 --- /dev/null +++ b/test/openapi/frame/3.1/fail/info_extension_without_the_prefix.json @@ -0,0 +1,15 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0", + "internal-id": 1 + }, + "paths": {} + }, + "error": { + "message": "The Info Object does not define this field", + "location": "/info/internal-id" + } +} diff --git a/test/openapi/frame/3.1/fail/info_is_a_string.json b/test/openapi/frame/3.1/fail/info_is_a_string.json new file mode 100644 index 000000000..ebce02c77 --- /dev/null +++ b/test/openapi/frame/3.1/fail/info_is_a_string.json @@ -0,0 +1,11 @@ +{ + "document": { + "openapi": "3.1.1", + "info": "Example", + "paths": {} + }, + "error": { + "message": "The Info Object must be an object", + "location": "/info" + } +} diff --git a/test/openapi/frame/3.1/fail/info_is_an_array.json b/test/openapi/frame/3.1/fail/info_is_an_array.json new file mode 100644 index 000000000..dea044f09 --- /dev/null +++ b/test/openapi/frame/3.1/fail/info_is_an_array.json @@ -0,0 +1,11 @@ +{ + "document": { + "openapi": "3.1.1", + "info": [], + "paths": {} + }, + "error": { + "message": "The Info Object must be an object", + "location": "/info" + } +} diff --git a/test/openapi/frame/3.1/fail/info_is_null.json b/test/openapi/frame/3.1/fail/info_is_null.json new file mode 100644 index 000000000..04918ec99 --- /dev/null +++ b/test/openapi/frame/3.1/fail/info_is_null.json @@ -0,0 +1,11 @@ +{ + "document": { + "openapi": "3.1.1", + "info": null, + "paths": {} + }, + "error": { + "message": "The Info Object must be an object", + "location": "/info" + } +} diff --git a/test/openapi/frame/3.1/fail/info_missing.json b/test/openapi/frame/3.1/fail/info_missing.json new file mode 100644 index 000000000..4c0411d39 --- /dev/null +++ b/test/openapi/frame/3.1/fail/info_missing.json @@ -0,0 +1,10 @@ +{ + "document": { + "openapi": "3.1.1", + "paths": {} + }, + "error": { + "message": "The OpenAPI Description must provide an Info Object", + "location": "" + } +} diff --git a/test/openapi/frame/3.1/fail/info_missing_title.json b/test/openapi/frame/3.1/fail/info_missing_title.json new file mode 100644 index 000000000..f90a74d59 --- /dev/null +++ b/test/openapi/frame/3.1/fail/info_missing_title.json @@ -0,0 +1,13 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "version": "1.0.0" + }, + "paths": {} + }, + "error": { + "message": "The Info Object must declare a title", + "location": "/info" + } +} diff --git a/test/openapi/frame/3.1/fail/info_missing_version.json b/test/openapi/frame/3.1/fail/info_missing_version.json new file mode 100644 index 000000000..d10da6b9a --- /dev/null +++ b/test/openapi/frame/3.1/fail/info_missing_version.json @@ -0,0 +1,13 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example" + }, + "paths": {} + }, + "error": { + "message": "The Info Object must declare a version", + "location": "/info" + } +} diff --git a/test/openapi/frame/3.1/fail/info_summary_is_not_a_string.json b/test/openapi/frame/3.1/fail/info_summary_is_not_a_string.json new file mode 100644 index 000000000..fd894013a --- /dev/null +++ b/test/openapi/frame/3.1/fail/info_summary_is_not_a_string.json @@ -0,0 +1,15 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0", + "summary": [] + }, + "paths": {} + }, + "error": { + "message": "The Info Object summary must be a string", + "location": "/info/summary" + } +} diff --git a/test/openapi/frame/3.1/fail/info_terms_of_service_is_not_a_string.json b/test/openapi/frame/3.1/fail/info_terms_of_service_is_not_a_string.json new file mode 100644 index 000000000..bc5c52b48 --- /dev/null +++ b/test/openapi/frame/3.1/fail/info_terms_of_service_is_not_a_string.json @@ -0,0 +1,15 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0", + "termsOfService": 1 + }, + "paths": {} + }, + "error": { + "message": "The Info Object terms of service must be a string", + "location": "/info/termsOfService" + } +} diff --git a/test/openapi/frame/3.1/fail/info_terms_of_service_is_not_a_uri_reference.json b/test/openapi/frame/3.1/fail/info_terms_of_service_is_not_a_uri_reference.json new file mode 100644 index 000000000..fb098caf9 --- /dev/null +++ b/test/openapi/frame/3.1/fail/info_terms_of_service_is_not_a_uri_reference.json @@ -0,0 +1,15 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0", + "termsOfService": "https://example.com/ terms" + }, + "paths": {} + }, + "error": { + "message": "The Info Object terms of service must be a URI reference", + "location": "/info/termsOfService" + } +} diff --git a/test/openapi/frame/3.1/fail/info_title_is_not_a_string.json b/test/openapi/frame/3.1/fail/info_title_is_not_a_string.json new file mode 100644 index 000000000..823cf595a --- /dev/null +++ b/test/openapi/frame/3.1/fail/info_title_is_not_a_string.json @@ -0,0 +1,14 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": 1, + "version": "1.0.0" + }, + "paths": {} + }, + "error": { + "message": "The Info Object title must be a string", + "location": "/info/title" + } +} diff --git a/test/openapi/frame/3.1/fail/info_unknown_field.json b/test/openapi/frame/3.1/fail/info_unknown_field.json new file mode 100644 index 000000000..d29909352 --- /dev/null +++ b/test/openapi/frame/3.1/fail/info_unknown_field.json @@ -0,0 +1,15 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0", + "licence": {} + }, + "paths": {} + }, + "error": { + "message": "The Info Object does not define this field", + "location": "/info/licence" + } +} diff --git a/test/openapi/frame/3.1/fail/info_version_is_not_a_string.json b/test/openapi/frame/3.1/fail/info_version_is_not_a_string.json new file mode 100644 index 000000000..bbfa287f8 --- /dev/null +++ b/test/openapi/frame/3.1/fail/info_version_is_not_a_string.json @@ -0,0 +1,14 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": 1 + }, + "paths": {} + }, + "error": { + "message": "The Info Object version must be a string", + "location": "/info/version" + } +} diff --git a/test/openapi/frame/3.1/fail/internal_reference_to_an_invalid_target.json b/test/openapi/frame/3.1/fail/internal_reference_to_an_invalid_target.json new file mode 100644 index 000000000..628132c1f --- /dev/null +++ b/test/openapi/frame/3.1/fail/internal_reference_to_an_invalid_target.json @@ -0,0 +1,25 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "R": { + "$ref": "#/components/responses/Broken" + }, + "Broken": { + "description": 1 + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "error": { + "message": "The Response Object description must be a string", + "base": "https://example.com/openapi.json", + "location": "/components/responses/Broken/description" + } +} diff --git a/test/openapi/frame/3.1/fail/internal_reference_to_the_wrong_kind.json b/test/openapi/frame/3.1/fail/internal_reference_to_the_wrong_kind.json new file mode 100644 index 000000000..4fa57b814 --- /dev/null +++ b/test/openapi/frame/3.1/fail/internal_reference_to_the_wrong_kind.json @@ -0,0 +1,29 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "R": { + "$ref": "#/components/parameters/P" + } + }, + "parameters": { + "P": { + "name": "limit", + "in": "query", + "schema": {} + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "error": { + "message": "The Response Object does not define this field", + "location": "/components/parameters/P/name", + "base": "https://example.com/openapi.json" + } +} diff --git a/test/openapi/frame/3.1/fail/json_schema_dialect_is_a_number.json b/test/openapi/frame/3.1/fail/json_schema_dialect_is_a_number.json new file mode 100644 index 000000000..f25fadf9f --- /dev/null +++ b/test/openapi/frame/3.1/fail/json_schema_dialect_is_a_number.json @@ -0,0 +1,15 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "jsonSchemaDialect": 1, + "paths": {} + }, + "error": { + "message": "The OpenAPI dialect must be a string", + "location": "/jsonSchemaDialect" + } +} diff --git a/test/openapi/frame/3.1/fail/json_schema_dialect_is_an_array.json b/test/openapi/frame/3.1/fail/json_schema_dialect_is_an_array.json new file mode 100644 index 000000000..bad82b74a --- /dev/null +++ b/test/openapi/frame/3.1/fail/json_schema_dialect_is_an_array.json @@ -0,0 +1,15 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "jsonSchemaDialect": [], + "paths": {} + }, + "error": { + "message": "The OpenAPI dialect must be a string", + "location": "/jsonSchemaDialect" + } +} diff --git a/test/openapi/frame/3.1/fail/json_schema_dialect_is_an_object.json b/test/openapi/frame/3.1/fail/json_schema_dialect_is_an_object.json new file mode 100644 index 000000000..7ffc21305 --- /dev/null +++ b/test/openapi/frame/3.1/fail/json_schema_dialect_is_an_object.json @@ -0,0 +1,15 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "jsonSchemaDialect": {}, + "paths": {} + }, + "error": { + "message": "The OpenAPI dialect must be a string", + "location": "/jsonSchemaDialect" + } +} diff --git a/test/openapi/frame/3.1/fail/json_schema_dialect_is_null.json b/test/openapi/frame/3.1/fail/json_schema_dialect_is_null.json new file mode 100644 index 000000000..835951a11 --- /dev/null +++ b/test/openapi/frame/3.1/fail/json_schema_dialect_is_null.json @@ -0,0 +1,15 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "jsonSchemaDialect": null, + "paths": {} + }, + "error": { + "message": "The OpenAPI dialect must be a string", + "location": "/jsonSchemaDialect" + } +} diff --git a/test/openapi/frame/3.1/fail/json_schema_dialect_with_a_space.json b/test/openapi/frame/3.1/fail/json_schema_dialect_with_a_space.json new file mode 100644 index 000000000..4d007a209 --- /dev/null +++ b/test/openapi/frame/3.1/fail/json_schema_dialect_with_a_space.json @@ -0,0 +1,15 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "jsonSchemaDialect": "https://spec.openapis.org/oas/3.1/dialect /base", + "paths": {} + }, + "error": { + "message": "The OpenAPI dialect must be a URI reference", + "location": "/jsonSchemaDialect" + } +} diff --git a/test/openapi/frame/3.1/fail/json_schema_dialect_without_a_scheme_body.json b/test/openapi/frame/3.1/fail/json_schema_dialect_without_a_scheme_body.json new file mode 100644 index 000000000..83b040fb2 --- /dev/null +++ b/test/openapi/frame/3.1/fail/json_schema_dialect_without_a_scheme_body.json @@ -0,0 +1,15 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "jsonSchemaDialect": "://base", + "paths": {} + }, + "error": { + "message": "The OpenAPI dialect must be a URI reference", + "location": "/jsonSchemaDialect" + } +} diff --git a/test/openapi/frame/3.1/fail/license_identifier_and_url_together.json b/test/openapi/frame/3.1/fail/license_identifier_and_url_together.json new file mode 100644 index 000000000..565aa53a7 --- /dev/null +++ b/test/openapi/frame/3.1/fail/license_identifier_and_url_together.json @@ -0,0 +1,19 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0", + "license": { + "name": "MIT", + "identifier": "MIT", + "url": "https://example.com" + } + }, + "paths": {} + }, + "error": { + "message": "The License Object identifier and URI are mutually exclusive", + "location": "/info/license" + } +} diff --git a/test/openapi/frame/3.1/fail/license_identifier_is_not_a_string.json b/test/openapi/frame/3.1/fail/license_identifier_is_not_a_string.json new file mode 100644 index 000000000..847d1b446 --- /dev/null +++ b/test/openapi/frame/3.1/fail/license_identifier_is_not_a_string.json @@ -0,0 +1,18 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0", + "license": { + "name": "MIT", + "identifier": 1 + } + }, + "paths": {} + }, + "error": { + "message": "The License Object identifier must be a string", + "location": "/info/license/identifier" + } +} diff --git a/test/openapi/frame/3.1/fail/license_is_not_an_object.json b/test/openapi/frame/3.1/fail/license_is_not_an_object.json new file mode 100644 index 000000000..6eed31068 --- /dev/null +++ b/test/openapi/frame/3.1/fail/license_is_not_an_object.json @@ -0,0 +1,15 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0", + "license": "MIT" + }, + "paths": {} + }, + "error": { + "message": "The License Object must be an object", + "location": "/info/license" + } +} diff --git a/test/openapi/frame/3.1/fail/license_missing_name.json b/test/openapi/frame/3.1/fail/license_missing_name.json new file mode 100644 index 000000000..edc11ad9f --- /dev/null +++ b/test/openapi/frame/3.1/fail/license_missing_name.json @@ -0,0 +1,17 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0", + "license": { + "identifier": "MIT" + } + }, + "paths": {} + }, + "error": { + "message": "The License Object must declare a name", + "location": "/info/license" + } +} diff --git a/test/openapi/frame/3.1/fail/license_name_is_not_a_string.json b/test/openapi/frame/3.1/fail/license_name_is_not_a_string.json new file mode 100644 index 000000000..7be0d52c1 --- /dev/null +++ b/test/openapi/frame/3.1/fail/license_name_is_not_a_string.json @@ -0,0 +1,17 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0", + "license": { + "name": 1 + } + }, + "paths": {} + }, + "error": { + "message": "The License Object name must be a string", + "location": "/info/license/name" + } +} diff --git a/test/openapi/frame/3.1/fail/license_unknown_field.json b/test/openapi/frame/3.1/fail/license_unknown_field.json new file mode 100644 index 000000000..1810c9ae6 --- /dev/null +++ b/test/openapi/frame/3.1/fail/license_unknown_field.json @@ -0,0 +1,18 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0", + "license": { + "name": "MIT", + "spdx": "MIT" + } + }, + "paths": {} + }, + "error": { + "message": "The License Object does not define this field", + "location": "/info/license/spdx" + } +} diff --git a/test/openapi/frame/3.1/fail/license_url_is_not_a_string.json b/test/openapi/frame/3.1/fail/license_url_is_not_a_string.json new file mode 100644 index 000000000..b27f5bdfb --- /dev/null +++ b/test/openapi/frame/3.1/fail/license_url_is_not_a_string.json @@ -0,0 +1,18 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0", + "license": { + "name": "MIT", + "url": 1 + } + }, + "paths": {} + }, + "error": { + "message": "The License Object URI must be a string", + "location": "/info/license/url" + } +} diff --git a/test/openapi/frame/3.1/fail/license_url_is_not_a_uri_reference.json b/test/openapi/frame/3.1/fail/license_url_is_not_a_uri_reference.json new file mode 100644 index 000000000..1fad2e340 --- /dev/null +++ b/test/openapi/frame/3.1/fail/license_url_is_not_a_uri_reference.json @@ -0,0 +1,18 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0", + "license": { + "name": "MIT", + "url": "https://example.com/a b" + } + }, + "paths": {} + }, + "error": { + "message": "The License Object URI must be a URI reference", + "location": "/info/license/url" + } +} diff --git a/test/openapi/frame/3.1/fail/link_operation_id_names_no_operation.json b/test/openapi/frame/3.1/fail/link_operation_id_names_no_operation.json new file mode 100644 index 000000000..6b24d13f9 --- /dev/null +++ b/test/openapi/frame/3.1/fail/link_operation_id_names_no_operation.json @@ -0,0 +1,33 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "links": { + "L": { + "operationId": "listPets" + } + } + }, + "paths": { + "/pets": { + "get": { + "responses": { + "200": { + "description": "ok" + } + } + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "error": { + "message": "The Link Object operation identifier must name an operation the OpenAPI Description declares", + "location": "/components/links/L/operationId", + "base": "https://example.com/openapi.json" + } +} diff --git a/test/openapi/frame/3.1/fail/link_operation_ref_to_a_description_by_empty_fragment.json b/test/openapi/frame/3.1/fail/link_operation_ref_to_a_description_by_empty_fragment.json new file mode 100644 index 000000000..dbb1f3706 --- /dev/null +++ b/test/openapi/frame/3.1/fail/link_operation_ref_to_a_description_by_empty_fragment.json @@ -0,0 +1,43 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "E", + "version": "1" + }, + "components": { + "links": { + "L": { + "operationRef": "https://example.com/target.json#" + } + } + }, + "paths": {} + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/target.json": { + "openapi": "3.1.1", + "info": { + "title": "T", + "version": "1" + }, + "paths": { + "/bar": { + "get": { + "responses": { + "200": { + "description": "ok" + } + } + } + } + } + } + }, + "error": { + "message": "This reference must name a document that holds only what the reference expects", + "location": "/components/links/L/operationRef", + "base": "https://example.com/openapi.json" + } +} diff --git a/test/openapi/frame/3.1/fail/link_operation_ref_to_a_fragment_that_is_not_an_operation.json b/test/openapi/frame/3.1/fail/link_operation_ref_to_a_fragment_that_is_not_an_operation.json new file mode 100644 index 000000000..99c89be13 --- /dev/null +++ b/test/openapi/frame/3.1/fail/link_operation_ref_to_a_fragment_that_is_not_an_operation.json @@ -0,0 +1,50 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "E", + "version": "1" + }, + "components": { + "links": { + "L": { + "operationRef": "https://example.com/common.json#/info" + } + } + }, + "paths": {} + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/common.json": { + "openapi": "3.1.1", + "info": { + "title": "Common", + "version": "1.0.0" + }, + "components": { + "responses": { + "Ok": { + "description": "ok" + } + } + }, + "paths": { + "/pets": { + "get": { + "responses": { + "200": { + "description": "ok" + } + } + } + } + } + } + }, + "error": { + "message": "The Operation Object does not define this field", + "location": "/info/title", + "base": "https://example.com/common.json" + } +} diff --git a/test/openapi/frame/3.1/fail/link_operation_ref_to_a_parameter.json b/test/openapi/frame/3.1/fail/link_operation_ref_to_a_parameter.json new file mode 100644 index 000000000..6d8cf9780 --- /dev/null +++ b/test/openapi/frame/3.1/fail/link_operation_ref_to_a_parameter.json @@ -0,0 +1,29 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "links": { + "ByRef": { + "operationRef": "#/components/parameters/Limit" + } + }, + "parameters": { + "Limit": { + "name": "limit", + "in": "query", + "schema": {} + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "error": { + "message": "The Operation Object does not define this field", + "location": "/components/parameters/Limit/name", + "base": "https://example.com/openapi.json" + } +} diff --git a/test/openapi/frame/3.1/fail/link_operation_ref_to_a_whole_description.json b/test/openapi/frame/3.1/fail/link_operation_ref_to_a_whole_description.json new file mode 100644 index 000000000..630604244 --- /dev/null +++ b/test/openapi/frame/3.1/fail/link_operation_ref_to_a_whole_description.json @@ -0,0 +1,43 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "links": { + "L": { + "operationRef": "https://example.com/target.json" + } + } + }, + "paths": {} + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/target.json": { + "openapi": "3.1.1", + "info": { + "title": "Target", + "version": "1.0.0" + }, + "paths": { + "/bar": { + "get": { + "responses": { + "200": { + "description": "ok" + } + } + } + } + } + } + }, + "error": { + "message": "This reference must name a document that holds only what the reference expects", + "location": "/components/links/L/operationRef", + "base": "https://example.com/openapi.json" + } +} diff --git a/test/openapi/frame/3.1/fail/link_unknown_field.json b/test/openapi/frame/3.1/fail/link_unknown_field.json new file mode 100644 index 000000000..d8e9e07bd --- /dev/null +++ b/test/openapi/frame/3.1/fail/link_unknown_field.json @@ -0,0 +1,21 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "links": { + "Item": { + "operationId": "list", + "headers": {} + } + } + } + }, + "error": { + "message": "The Link Object does not define this field", + "location": "/components/links/Item/headers" + } +} diff --git a/test/openapi/frame/3.1/fail/link_with_both_operations.json b/test/openapi/frame/3.1/fail/link_with_both_operations.json new file mode 100644 index 000000000..6ab16e7e6 --- /dev/null +++ b/test/openapi/frame/3.1/fail/link_with_both_operations.json @@ -0,0 +1,21 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "links": { + "Item": { + "operationRef": "#/x", + "operationId": "list" + } + } + } + }, + "error": { + "message": "The Link Object operation reference and operation identifier are mutually exclusive", + "location": "/components/links/Item" + } +} diff --git a/test/openapi/frame/3.1/fail/link_without_operation.json b/test/openapi/frame/3.1/fail/link_without_operation.json new file mode 100644 index 000000000..af3fb7b00 --- /dev/null +++ b/test/openapi/frame/3.1/fail/link_without_operation.json @@ -0,0 +1,18 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "links": { + "Item": {} + } + } + }, + "error": { + "message": "The Link Object must declare an operation reference or an operation identifier", + "location": "/components/links/Item" + } +} diff --git a/test/openapi/frame/3.1/fail/media_type_example_and_examples.json b/test/openapi/frame/3.1/fail/media_type_example_and_examples.json new file mode 100644 index 000000000..67abdc148 --- /dev/null +++ b/test/openapi/frame/3.1/fail/media_type_example_and_examples.json @@ -0,0 +1,26 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "Item": { + "description": "ok", + "content": { + "application/json": { + "example": 1, + "examples": {} + } + } + } + } + } + }, + "error": { + "message": "The Media Type Object example and examples are mutually exclusive", + "location": "/components/responses/Item/content/application~1json" + } +} diff --git a/test/openapi/frame/3.1/fail/media_type_schema_is_a_number.json b/test/openapi/frame/3.1/fail/media_type_schema_is_a_number.json new file mode 100644 index 000000000..44e69f55d --- /dev/null +++ b/test/openapi/frame/3.1/fail/media_type_schema_is_a_number.json @@ -0,0 +1,25 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "Item": { + "description": "ok", + "content": { + "application/json": { + "schema": 1 + } + } + } + } + } + }, + "error": { + "message": "A Schema Object must be an object or a boolean", + "location": "/components/responses/Item/content/application~1json/schema" + } +} diff --git a/test/openapi/frame/3.1/fail/media_type_unknown_field.json b/test/openapi/frame/3.1/fail/media_type_unknown_field.json new file mode 100644 index 000000000..dd2057223 --- /dev/null +++ b/test/openapi/frame/3.1/fail/media_type_unknown_field.json @@ -0,0 +1,25 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "Item": { + "description": "ok", + "content": { + "application/json": { + "style": "form" + } + } + } + } + } + }, + "error": { + "message": "The Media Type Object does not define this field", + "location": "/components/responses/Item/content/application~1json/style" + } +} diff --git a/test/openapi/frame/3.1/fail/missing_version.json b/test/openapi/frame/3.1/fail/missing_version.json new file mode 100644 index 000000000..f86a7904d --- /dev/null +++ b/test/openapi/frame/3.1/fail/missing_version.json @@ -0,0 +1,13 @@ +{ + "document": { + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": {} + }, + "error": { + "message": "The OpenAPI Description must declare its version", + "location": "" + } +} diff --git a/test/openapi/frame/3.1/fail/oauth_flow_authorization_code_missing_token_url.json b/test/openapi/frame/3.1/fail/oauth_flow_authorization_code_missing_token_url.json new file mode 100644 index 000000000..e78d8772c --- /dev/null +++ b/test/openapi/frame/3.1/fail/oauth_flow_authorization_code_missing_token_url.json @@ -0,0 +1,26 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "securitySchemes": { + "Item": { + "type": "oauth2", + "flows": { + "authorizationCode": { + "authorizationUrl": "/auth", + "scopes": {} + } + } + } + } + } + }, + "error": { + "message": "This OAuth Flow Object must declare a token URI", + "location": "/components/securitySchemes/Item/flows/authorizationCode" + } +} diff --git a/test/openapi/frame/3.1/fail/oauth_flow_client_credentials_missing_token_url.json b/test/openapi/frame/3.1/fail/oauth_flow_client_credentials_missing_token_url.json new file mode 100644 index 000000000..ff8b257d8 --- /dev/null +++ b/test/openapi/frame/3.1/fail/oauth_flow_client_credentials_missing_token_url.json @@ -0,0 +1,25 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "securitySchemes": { + "Item": { + "type": "oauth2", + "flows": { + "clientCredentials": { + "scopes": {} + } + } + } + } + } + }, + "error": { + "message": "This OAuth Flow Object must declare a token URI", + "location": "/components/securitySchemes/Item/flows/clientCredentials" + } +} diff --git a/test/openapi/frame/3.1/fail/oauth_flow_implicit_missing_authorization_url.json b/test/openapi/frame/3.1/fail/oauth_flow_implicit_missing_authorization_url.json new file mode 100644 index 000000000..803a78347 --- /dev/null +++ b/test/openapi/frame/3.1/fail/oauth_flow_implicit_missing_authorization_url.json @@ -0,0 +1,25 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "securitySchemes": { + "Item": { + "type": "oauth2", + "flows": { + "implicit": { + "scopes": {} + } + } + } + } + } + }, + "error": { + "message": "This OAuth Flow Object must declare an authorization URI", + "location": "/components/securitySchemes/Item/flows/implicit" + } +} diff --git a/test/openapi/frame/3.1/fail/oauth_flow_implicit_missing_scopes.json b/test/openapi/frame/3.1/fail/oauth_flow_implicit_missing_scopes.json new file mode 100644 index 000000000..c3419c79c --- /dev/null +++ b/test/openapi/frame/3.1/fail/oauth_flow_implicit_missing_scopes.json @@ -0,0 +1,25 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "securitySchemes": { + "Item": { + "type": "oauth2", + "flows": { + "implicit": { + "authorizationUrl": "/auth" + } + } + } + } + } + }, + "error": { + "message": "The OAuth Flow Object must declare its scopes", + "location": "/components/securitySchemes/Item/flows/implicit" + } +} diff --git a/test/openapi/frame/3.1/fail/oauth_flow_implicit_with_a_token_url.json b/test/openapi/frame/3.1/fail/oauth_flow_implicit_with_a_token_url.json new file mode 100644 index 000000000..858da633c --- /dev/null +++ b/test/openapi/frame/3.1/fail/oauth_flow_implicit_with_a_token_url.json @@ -0,0 +1,29 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "securitySchemes": { + "S": { + "type": "oauth2", + "flows": { + "implicit": { + "authorizationUrl": "/auth", + "tokenUrl": "/token", + "scopes": {} + } + } + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "error": { + "message": "This OAuth Flow Object does not define a token URI", + "location": "/components/securitySchemes/S/flows/implicit/tokenUrl", + "base": "https://example.com/openapi.json" + } +} diff --git a/test/openapi/frame/3.1/fail/oauth_flow_password_missing_token_url.json b/test/openapi/frame/3.1/fail/oauth_flow_password_missing_token_url.json new file mode 100644 index 000000000..9abb9d862 --- /dev/null +++ b/test/openapi/frame/3.1/fail/oauth_flow_password_missing_token_url.json @@ -0,0 +1,25 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "securitySchemes": { + "Item": { + "type": "oauth2", + "flows": { + "password": { + "scopes": {} + } + } + } + } + } + }, + "error": { + "message": "This OAuth Flow Object must declare a token URI", + "location": "/components/securitySchemes/Item/flows/password" + } +} diff --git a/test/openapi/frame/3.1/fail/oauth_flow_password_with_an_authorization_url.json b/test/openapi/frame/3.1/fail/oauth_flow_password_with_an_authorization_url.json new file mode 100644 index 000000000..59b4c1510 --- /dev/null +++ b/test/openapi/frame/3.1/fail/oauth_flow_password_with_an_authorization_url.json @@ -0,0 +1,29 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "securitySchemes": { + "S": { + "type": "oauth2", + "flows": { + "password": { + "tokenUrl": "/token", + "authorizationUrl": "/auth", + "scopes": {} + } + } + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "error": { + "message": "This OAuth Flow Object does not define an authorization URI", + "location": "/components/securitySchemes/S/flows/password/authorizationUrl", + "base": "https://example.com/openapi.json" + } +} diff --git a/test/openapi/frame/3.1/fail/oauth_flow_scopes_hold_a_number.json b/test/openapi/frame/3.1/fail/oauth_flow_scopes_hold_a_number.json new file mode 100644 index 000000000..7b866ff8f --- /dev/null +++ b/test/openapi/frame/3.1/fail/oauth_flow_scopes_hold_a_number.json @@ -0,0 +1,28 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "securitySchemes": { + "Item": { + "type": "oauth2", + "flows": { + "implicit": { + "authorizationUrl": "/auth", + "scopes": { + "read": 1 + } + } + } + } + } + } + }, + "error": { + "message": "The OAuth Flow Object scopes must hold strings", + "location": "/components/securitySchemes/Item/flows/implicit/scopes/read" + } +} diff --git a/test/openapi/frame/3.1/fail/oauth_flow_unknown_field.json b/test/openapi/frame/3.1/fail/oauth_flow_unknown_field.json new file mode 100644 index 000000000..eb6b9acb3 --- /dev/null +++ b/test/openapi/frame/3.1/fail/oauth_flow_unknown_field.json @@ -0,0 +1,28 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "securitySchemes": { + "Item": { + "type": "oauth2", + "flows": { + "implicit": { + "authorizationUrl": "/auth", + "scopes": {}, + "tokenUrl": "/token", + "audience": "x" + } + } + } + } + } + }, + "error": { + "message": "The OAuth Flow Object does not define this field", + "location": "/components/securitySchemes/Item/flows/implicit/audience" + } +} diff --git a/test/openapi/frame/3.1/fail/oauth_flows_unknown_field.json b/test/openapi/frame/3.1/fail/oauth_flows_unknown_field.json new file mode 100644 index 000000000..8c821fecf --- /dev/null +++ b/test/openapi/frame/3.1/fail/oauth_flows_unknown_field.json @@ -0,0 +1,23 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "securitySchemes": { + "Item": { + "type": "oauth2", + "flows": { + "application": {} + } + } + } + } + }, + "error": { + "message": "The OAuth Flows Object does not define this field", + "location": "/components/securitySchemes/Item/flows/application" + } +} diff --git a/test/openapi/frame/3.1/fail/operation_deprecated_is_not_a_boolean.json b/test/openapi/frame/3.1/fail/operation_deprecated_is_not_a_boolean.json new file mode 100644 index 000000000..165c91748 --- /dev/null +++ b/test/openapi/frame/3.1/fail/operation_deprecated_is_not_a_boolean.json @@ -0,0 +1,20 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets": { + "get": { + "deprecated": "yes" + } + } + } + }, + "error": { + "message": "The Operation Object deprecated must be a boolean", + "location": "/paths/~1pets/get/deprecated" + } +} diff --git a/test/openapi/frame/3.1/fail/operation_id_is_not_a_string.json b/test/openapi/frame/3.1/fail/operation_id_is_not_a_string.json new file mode 100644 index 000000000..c790ebf21 --- /dev/null +++ b/test/openapi/frame/3.1/fail/operation_id_is_not_a_string.json @@ -0,0 +1,20 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets": { + "get": { + "operationId": 1 + } + } + } + }, + "error": { + "message": "The Operation Object identifier must be a string", + "location": "/paths/~1pets/get/operationId" + } +} diff --git a/test/openapi/frame/3.1/fail/operation_ids_repeat.json b/test/openapi/frame/3.1/fail/operation_ids_repeat.json new file mode 100644 index 000000000..71ffaa8d9 --- /dev/null +++ b/test/openapi/frame/3.1/fail/operation_ids_repeat.json @@ -0,0 +1,23 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets": { + "get": { + "operationId": "list" + }, + "post": { + "operationId": "list" + } + } + } + }, + "error": { + "message": "The Operation Object identifiers must be unique", + "location": "/paths/~1pets/post/operationId" + } +} diff --git a/test/openapi/frame/3.1/fail/operation_is_not_an_object.json b/test/openapi/frame/3.1/fail/operation_is_not_an_object.json new file mode 100644 index 000000000..7faf2a68a --- /dev/null +++ b/test/openapi/frame/3.1/fail/operation_is_not_an_object.json @@ -0,0 +1,18 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets": { + "get": [] + } + } + }, + "error": { + "message": "The Operation Object must be an object", + "location": "/paths/~1pets/get" + } +} diff --git a/test/openapi/frame/3.1/fail/operation_security_is_not_an_array.json b/test/openapi/frame/3.1/fail/operation_security_is_not_an_array.json new file mode 100644 index 000000000..9b46f5c5a --- /dev/null +++ b/test/openapi/frame/3.1/fail/operation_security_is_not_an_array.json @@ -0,0 +1,20 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets": { + "get": { + "security": {} + } + } + } + }, + "error": { + "message": "The Operation Object security must be an array", + "location": "/paths/~1pets/get/security" + } +} diff --git a/test/openapi/frame/3.1/fail/operation_tags_hold_a_number.json b/test/openapi/frame/3.1/fail/operation_tags_hold_a_number.json new file mode 100644 index 000000000..6fa1d6293 --- /dev/null +++ b/test/openapi/frame/3.1/fail/operation_tags_hold_a_number.json @@ -0,0 +1,23 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets": { + "get": { + "tags": [ + "pet", + 1 + ] + } + } + } + }, + "error": { + "message": "The Operation Object tags must hold strings", + "location": "/paths/~1pets/get/tags/1" + } +} diff --git a/test/openapi/frame/3.1/fail/operation_tags_is_not_an_array.json b/test/openapi/frame/3.1/fail/operation_tags_is_not_an_array.json new file mode 100644 index 000000000..28480f8f1 --- /dev/null +++ b/test/openapi/frame/3.1/fail/operation_tags_is_not_an_array.json @@ -0,0 +1,20 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets": { + "get": { + "tags": "pet" + } + } + } + }, + "error": { + "message": "The Operation Object tags must be an array", + "location": "/paths/~1pets/get/tags" + } +} diff --git a/test/openapi/frame/3.1/fail/operation_unknown_field.json b/test/openapi/frame/3.1/fail/operation_unknown_field.json new file mode 100644 index 000000000..1e31fca28 --- /dev/null +++ b/test/openapi/frame/3.1/fail/operation_unknown_field.json @@ -0,0 +1,20 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets": { + "get": { + "produces": [] + } + } + } + }, + "error": { + "message": "The Operation Object does not define this field", + "location": "/paths/~1pets/get/produces" + } +} diff --git a/test/openapi/frame/3.1/fail/parameter_allow_empty_value_outside_query.json b/test/openapi/frame/3.1/fail/parameter_allow_empty_value_outside_query.json new file mode 100644 index 000000000..0e7660889 --- /dev/null +++ b/test/openapi/frame/3.1/fail/parameter_allow_empty_value_outside_query.json @@ -0,0 +1,23 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "parameters": { + "Item": { + "name": "id", + "in": "header", + "schema": {}, + "allowEmptyValue": true + } + } + } + }, + "error": { + "message": "The Parameter Object does not define this field", + "location": "/components/parameters/Item/allowEmptyValue" + } +} diff --git a/test/openapi/frame/3.1/fail/parameter_allow_reserved_outside_query.json b/test/openapi/frame/3.1/fail/parameter_allow_reserved_outside_query.json new file mode 100644 index 000000000..91bf07209 --- /dev/null +++ b/test/openapi/frame/3.1/fail/parameter_allow_reserved_outside_query.json @@ -0,0 +1,23 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "parameters": { + "Item": { + "name": "id", + "in": "header", + "schema": {}, + "allowReserved": true + } + } + } + }, + "error": { + "message": "The Parameter Object does not define this field", + "location": "/components/parameters/Item/allowReserved" + } +} diff --git a/test/openapi/frame/3.1/fail/parameter_content_with_two_entries.json b/test/openapi/frame/3.1/fail/parameter_content_with_two_entries.json new file mode 100644 index 000000000..f46059b17 --- /dev/null +++ b/test/openapi/frame/3.1/fail/parameter_content_with_two_entries.json @@ -0,0 +1,25 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "parameters": { + "Item": { + "name": "id", + "in": "query", + "content": { + "text/plain": {}, + "text/html": {} + } + } + } + } + }, + "error": { + "message": "The Parameter Object content must hold exactly one entry", + "location": "/components/parameters/Item/content" + } +} diff --git a/test/openapi/frame/3.1/fail/parameter_cookie_style_is_unknown.json b/test/openapi/frame/3.1/fail/parameter_cookie_style_is_unknown.json new file mode 100644 index 000000000..96495af07 --- /dev/null +++ b/test/openapi/frame/3.1/fail/parameter_cookie_style_is_unknown.json @@ -0,0 +1,23 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "parameters": { + "Item": { + "name": "id", + "in": "cookie", + "schema": {}, + "style": "simple" + } + } + } + }, + "error": { + "message": "The Parameter Object style is not one a cookie parameter admits", + "location": "/components/parameters/Item/style" + } +} diff --git a/test/openapi/frame/3.1/fail/parameter_example_and_examples.json b/test/openapi/frame/3.1/fail/parameter_example_and_examples.json new file mode 100644 index 000000000..f90b8960e --- /dev/null +++ b/test/openapi/frame/3.1/fail/parameter_example_and_examples.json @@ -0,0 +1,24 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "parameters": { + "Item": { + "name": "id", + "in": "query", + "schema": {}, + "example": 1, + "examples": {} + } + } + } + }, + "error": { + "message": "The Parameter Object example and examples are mutually exclusive", + "location": "/components/parameters/Item" + } +} diff --git a/test/openapi/frame/3.1/fail/parameter_header_style_is_unknown.json b/test/openapi/frame/3.1/fail/parameter_header_style_is_unknown.json new file mode 100644 index 000000000..ab9614605 --- /dev/null +++ b/test/openapi/frame/3.1/fail/parameter_header_style_is_unknown.json @@ -0,0 +1,23 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "parameters": { + "Item": { + "name": "id", + "in": "header", + "schema": {}, + "style": "form" + } + } + } + }, + "error": { + "message": "The Parameter Object style is not one a header parameter admits", + "location": "/components/parameters/Item/style" + } +} diff --git a/test/openapi/frame/3.1/fail/parameter_location_is_unknown.json b/test/openapi/frame/3.1/fail/parameter_location_is_unknown.json new file mode 100644 index 000000000..0d06deb71 --- /dev/null +++ b/test/openapi/frame/3.1/fail/parameter_location_is_unknown.json @@ -0,0 +1,21 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "parameters": { + "Item": { + "name": "id", + "in": "body" + } + } + } + }, + "error": { + "message": "The Parameter Object location is not one this specification defines", + "location": "/components/parameters/Item/in" + } +} diff --git a/test/openapi/frame/3.1/fail/parameter_missing_location.json b/test/openapi/frame/3.1/fail/parameter_missing_location.json new file mode 100644 index 000000000..fb45ed9bb --- /dev/null +++ b/test/openapi/frame/3.1/fail/parameter_missing_location.json @@ -0,0 +1,20 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "parameters": { + "Item": { + "name": "id" + } + } + } + }, + "error": { + "message": "The Parameter Object must declare a location", + "location": "/components/parameters/Item" + } +} diff --git a/test/openapi/frame/3.1/fail/parameter_missing_name.json b/test/openapi/frame/3.1/fail/parameter_missing_name.json new file mode 100644 index 000000000..1f729d0de --- /dev/null +++ b/test/openapi/frame/3.1/fail/parameter_missing_name.json @@ -0,0 +1,20 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "parameters": { + "Item": { + "in": "query" + } + } + } + }, + "error": { + "message": "The Parameter Object must declare a name", + "location": "/components/parameters/Item" + } +} diff --git a/test/openapi/frame/3.1/fail/parameter_path_name_with_braces.json b/test/openapi/frame/3.1/fail/parameter_path_name_with_braces.json new file mode 100644 index 000000000..9458aa8b8 --- /dev/null +++ b/test/openapi/frame/3.1/fail/parameter_path_name_with_braces.json @@ -0,0 +1,23 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "parameters": { + "Item": { + "name": "{id}", + "in": "path", + "required": true, + "schema": {} + } + } + } + }, + "error": { + "message": "A path Parameter Object name must not hold braces", + "location": "/components/parameters/Item/name" + } +} diff --git a/test/openapi/frame/3.1/fail/parameter_path_not_required.json b/test/openapi/frame/3.1/fail/parameter_path_not_required.json new file mode 100644 index 000000000..afec58a8d --- /dev/null +++ b/test/openapi/frame/3.1/fail/parameter_path_not_required.json @@ -0,0 +1,23 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "parameters": { + "Item": { + "name": "id", + "in": "path", + "required": false, + "schema": {} + } + } + } + }, + "error": { + "message": "A path Parameter Object must be required", + "location": "/components/parameters/Item/required" + } +} diff --git a/test/openapi/frame/3.1/fail/parameter_path_style_is_unknown.json b/test/openapi/frame/3.1/fail/parameter_path_style_is_unknown.json new file mode 100644 index 000000000..d04e62673 --- /dev/null +++ b/test/openapi/frame/3.1/fail/parameter_path_style_is_unknown.json @@ -0,0 +1,24 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "parameters": { + "Item": { + "name": "id", + "in": "path", + "required": true, + "schema": {}, + "style": "form" + } + } + } + }, + "error": { + "message": "The Parameter Object style is not one a path parameter admits", + "location": "/components/parameters/Item/style" + } +} diff --git a/test/openapi/frame/3.1/fail/parameter_path_without_required.json b/test/openapi/frame/3.1/fail/parameter_path_without_required.json new file mode 100644 index 000000000..d91486669 --- /dev/null +++ b/test/openapi/frame/3.1/fail/parameter_path_without_required.json @@ -0,0 +1,22 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "parameters": { + "Item": { + "name": "id", + "in": "path", + "schema": {} + } + } + } + }, + "error": { + "message": "A path Parameter Object must declare that it is required", + "location": "/components/parameters/Item" + } +} diff --git a/test/openapi/frame/3.1/fail/parameter_query_style_is_unknown.json b/test/openapi/frame/3.1/fail/parameter_query_style_is_unknown.json new file mode 100644 index 000000000..712373f35 --- /dev/null +++ b/test/openapi/frame/3.1/fail/parameter_query_style_is_unknown.json @@ -0,0 +1,23 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "parameters": { + "Item": { + "name": "id", + "in": "query", + "schema": {}, + "style": "matrix" + } + } + } + }, + "error": { + "message": "The Parameter Object style is not one a query parameter admits", + "location": "/components/parameters/Item/style" + } +} diff --git a/test/openapi/frame/3.1/fail/parameter_style_with_content.json b/test/openapi/frame/3.1/fail/parameter_style_with_content.json new file mode 100644 index 000000000..89500e6f3 --- /dev/null +++ b/test/openapi/frame/3.1/fail/parameter_style_with_content.json @@ -0,0 +1,25 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "parameters": { + "Item": { + "name": "id", + "in": "query", + "content": { + "text/plain": {} + }, + "style": "form" + } + } + } + }, + "error": { + "message": "The Parameter Object does not define this field", + "location": "/components/parameters/Item/style" + } +} diff --git a/test/openapi/frame/3.1/fail/parameter_unknown_field.json b/test/openapi/frame/3.1/fail/parameter_unknown_field.json new file mode 100644 index 000000000..c0ec276dc --- /dev/null +++ b/test/openapi/frame/3.1/fail/parameter_unknown_field.json @@ -0,0 +1,23 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "parameters": { + "Item": { + "name": "id", + "in": "query", + "schema": {}, + "collectionFormat": "csv" + } + } + } + }, + "error": { + "message": "The Parameter Object does not define this field", + "location": "/components/parameters/Item/collectionFormat" + } +} diff --git a/test/openapi/frame/3.1/fail/parameter_with_schema_and_content.json b/test/openapi/frame/3.1/fail/parameter_with_schema_and_content.json new file mode 100644 index 000000000..9b2196023 --- /dev/null +++ b/test/openapi/frame/3.1/fail/parameter_with_schema_and_content.json @@ -0,0 +1,25 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "parameters": { + "Item": { + "name": "id", + "in": "query", + "schema": {}, + "content": { + "text/plain": {} + } + } + } + } + }, + "error": { + "message": "The Parameter Object schema and content are mutually exclusive", + "location": "/components/parameters/Item" + } +} diff --git a/test/openapi/frame/3.1/fail/parameter_without_schema_or_content.json b/test/openapi/frame/3.1/fail/parameter_without_schema_or_content.json new file mode 100644 index 000000000..24dc63844 --- /dev/null +++ b/test/openapi/frame/3.1/fail/parameter_without_schema_or_content.json @@ -0,0 +1,21 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "parameters": { + "Item": { + "name": "id", + "in": "query" + } + } + } + }, + "error": { + "message": "The Parameter Object must declare a schema or a content", + "location": "/components/parameters/Item" + } +} diff --git a/test/openapi/frame/3.1/fail/parameters_repeat_a_name_and_location.json b/test/openapi/frame/3.1/fail/parameters_repeat_a_name_and_location.json new file mode 100644 index 000000000..bce166911 --- /dev/null +++ b/test/openapi/frame/3.1/fail/parameters_repeat_a_name_and_location.json @@ -0,0 +1,29 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets": { + "parameters": [ + { + "name": "id", + "in": "query", + "schema": {} + }, + { + "name": "id", + "in": "query", + "schema": {} + } + ] + } + } + }, + "error": { + "message": "The Path Item Object parameters must not repeat a name and location", + "location": "/paths/~1pets/parameters/1" + } +} diff --git a/test/openapi/frame/3.1/fail/parameters_repeat_through_a_reference.json b/test/openapi/frame/3.1/fail/parameters_repeat_through_a_reference.json new file mode 100644 index 000000000..25b5df6b1 --- /dev/null +++ b/test/openapi/frame/3.1/fail/parameters_repeat_through_a_reference.json @@ -0,0 +1,45 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "E", + "version": "1" + }, + "components": { + "parameters": { + "Limit": { + "name": "limit", + "in": "query", + "schema": {} + } + } + }, + "paths": { + "/pets": { + "parameters": [ + { + "name": "limit", + "in": "query", + "schema": {} + }, + { + "$ref": "#/components/parameters/Limit" + } + ], + "get": { + "responses": { + "200": { + "description": "ok" + } + } + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "error": { + "message": "The Path Item Object parameters must not repeat a name and location", + "location": "/paths/~1pets/parameters/1", + "base": "https://example.com/openapi.json" + } +} diff --git a/test/openapi/frame/3.1/fail/path_item_is_not_an_object.json b/test/openapi/frame/3.1/fail/path_item_is_not_an_object.json new file mode 100644 index 000000000..4437cf569 --- /dev/null +++ b/test/openapi/frame/3.1/fail/path_item_is_not_an_object.json @@ -0,0 +1,16 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets": [] + } + }, + "error": { + "message": "The Path Item Object must be an object", + "location": "/paths/~1pets" + } +} diff --git a/test/openapi/frame/3.1/fail/path_item_parameters_is_not_an_array.json b/test/openapi/frame/3.1/fail/path_item_parameters_is_not_an_array.json new file mode 100644 index 000000000..646ce75f3 --- /dev/null +++ b/test/openapi/frame/3.1/fail/path_item_parameters_is_not_an_array.json @@ -0,0 +1,18 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets": { + "parameters": {} + } + } + }, + "error": { + "message": "The Path Item Object parameters must be an array", + "location": "/paths/~1pets/parameters" + } +} diff --git a/test/openapi/frame/3.1/fail/path_item_ref_is_not_a_string.json b/test/openapi/frame/3.1/fail/path_item_ref_is_not_a_string.json new file mode 100644 index 000000000..042b9ec6b --- /dev/null +++ b/test/openapi/frame/3.1/fail/path_item_ref_is_not_a_string.json @@ -0,0 +1,18 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets": { + "$ref": 1 + } + } + }, + "error": { + "message": "The Path Item Object reference must be a string", + "location": "/paths/~1pets/$ref" + } +} diff --git a/test/openapi/frame/3.1/fail/path_item_reference_to_a_description_by_empty_fragment.json b/test/openapi/frame/3.1/fail/path_item_reference_to_a_description_by_empty_fragment.json new file mode 100644 index 000000000..97f44a71c --- /dev/null +++ b/test/openapi/frame/3.1/fail/path_item_reference_to_a_description_by_empty_fragment.json @@ -0,0 +1,40 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "E", + "version": "1" + }, + "paths": { + "/foo": { + "$ref": "https://example.com/target.json#" + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/target.json": { + "openapi": "3.1.1", + "info": { + "title": "T", + "version": "1" + }, + "paths": { + "/bar": { + "get": { + "responses": { + "200": { + "description": "ok" + } + } + } + } + } + } + }, + "error": { + "message": "This reference must name a document that holds only what the reference expects", + "location": "/paths/~1foo/$ref", + "base": "https://example.com/openapi.json" + } +} diff --git a/test/openapi/frame/3.1/fail/path_item_reference_to_a_whole_description.json b/test/openapi/frame/3.1/fail/path_item_reference_to_a_whole_description.json new file mode 100644 index 000000000..1c10eca81 --- /dev/null +++ b/test/openapi/frame/3.1/fail/path_item_reference_to_a_whole_description.json @@ -0,0 +1,40 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/foo": { + "$ref": "https://example.com/target.json" + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/target.json": { + "openapi": "3.1.1", + "info": { + "title": "Target", + "version": "1.0.0" + }, + "paths": { + "/bar": { + "get": { + "responses": { + "200": { + "description": "ok" + } + } + } + } + } + } + }, + "error": { + "message": "This reference must name a document that holds only what the reference expects", + "location": "/paths/~1foo/$ref", + "base": "https://example.com/openapi.json" + } +} diff --git a/test/openapi/frame/3.1/fail/path_item_reference_to_the_entry_document.json b/test/openapi/frame/3.1/fail/path_item_reference_to_the_entry_document.json new file mode 100644 index 000000000..0612a4bc7 --- /dev/null +++ b/test/openapi/frame/3.1/fail/path_item_reference_to_the_entry_document.json @@ -0,0 +1,20 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/foo": { + "$ref": "" + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "error": { + "message": "This reference must name a document that holds only what the reference expects", + "location": "/paths/~1foo/$ref", + "base": "https://example.com/openapi.json" + } +} diff --git a/test/openapi/frame/3.1/fail/path_item_reference_to_the_entry_document_by_empty_fragment.json b/test/openapi/frame/3.1/fail/path_item_reference_to_the_entry_document_by_empty_fragment.json new file mode 100644 index 000000000..77efa1fe5 --- /dev/null +++ b/test/openapi/frame/3.1/fail/path_item_reference_to_the_entry_document_by_empty_fragment.json @@ -0,0 +1,20 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "E", + "version": "1" + }, + "paths": { + "/foo": { + "$ref": "#" + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "error": { + "message": "This reference must name a document that holds only what the reference expects", + "location": "/paths/~1foo/$ref", + "base": "https://example.com/openapi.json" + } +} diff --git a/test/openapi/frame/3.1/fail/path_item_reference_to_the_entry_document_by_uri.json b/test/openapi/frame/3.1/fail/path_item_reference_to_the_entry_document_by_uri.json new file mode 100644 index 000000000..f91f7a4cc --- /dev/null +++ b/test/openapi/frame/3.1/fail/path_item_reference_to_the_entry_document_by_uri.json @@ -0,0 +1,20 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/foo": { + "$ref": "https://example.com/openapi.json" + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "error": { + "message": "This reference must name a document that holds only what the reference expects", + "location": "/paths/~1foo/$ref", + "base": "https://example.com/openapi.json" + } +} diff --git a/test/openapi/frame/3.1/fail/path_item_servers_is_not_an_array.json b/test/openapi/frame/3.1/fail/path_item_servers_is_not_an_array.json new file mode 100644 index 000000000..00f35221d --- /dev/null +++ b/test/openapi/frame/3.1/fail/path_item_servers_is_not_an_array.json @@ -0,0 +1,18 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets": { + "servers": {} + } + } + }, + "error": { + "message": "The Path Item Object servers must be an array", + "location": "/paths/~1pets/servers" + } +} diff --git a/test/openapi/frame/3.1/fail/path_item_summary_is_not_a_string.json b/test/openapi/frame/3.1/fail/path_item_summary_is_not_a_string.json new file mode 100644 index 000000000..9b6a0d255 --- /dev/null +++ b/test/openapi/frame/3.1/fail/path_item_summary_is_not_a_string.json @@ -0,0 +1,18 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets": { + "summary": 1 + } + } + }, + "error": { + "message": "The Path Item Object summary must be a string", + "location": "/paths/~1pets/summary" + } +} diff --git a/test/openapi/frame/3.1/fail/path_item_unknown_field.json b/test/openapi/frame/3.1/fail/path_item_unknown_field.json new file mode 100644 index 000000000..2e6b72cdc --- /dev/null +++ b/test/openapi/frame/3.1/fail/path_item_unknown_field.json @@ -0,0 +1,18 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets": { + "connect": {} + } + } + }, + "error": { + "message": "The Path Item Object does not define this field", + "location": "/paths/~1pets/connect" + } +} diff --git a/test/openapi/frame/3.1/fail/path_parameter_from_the_path_item_without_a_template.json b/test/openapi/frame/3.1/fail/path_parameter_from_the_path_item_without_a_template.json new file mode 100644 index 000000000..047d10595 --- /dev/null +++ b/test/openapi/frame/3.1/fail/path_parameter_from_the_path_item_without_a_template.json @@ -0,0 +1,33 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets": { + "parameters": [ + { + "name": "petId", + "in": "path", + "required": true, + "schema": {} + } + ], + "get": { + "responses": { + "200": { + "description": "ok" + } + } + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "error": { + "message": "A path Parameter Object must name a template expression of the path it is under", + "location": "/paths/~1pets/parameters/0" + } +} diff --git a/test/openapi/frame/3.1/fail/path_parameter_without_a_template.json b/test/openapi/frame/3.1/fail/path_parameter_without_a_template.json new file mode 100644 index 000000000..16a507342 --- /dev/null +++ b/test/openapi/frame/3.1/fail/path_parameter_without_a_template.json @@ -0,0 +1,33 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets": { + "get": { + "responses": { + "200": { + "description": "ok" + } + }, + "parameters": [ + { + "name": "petId", + "in": "path", + "required": true, + "schema": {} + } + ] + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "error": { + "message": "A path Parameter Object must name a template expression of the path it is under", + "location": "/paths/~1pets/get/parameters/0" + } +} diff --git a/test/openapi/frame/3.1/fail/path_parameter_without_a_template_in_an_empty_path_item.json b/test/openapi/frame/3.1/fail/path_parameter_without_a_template_in_an_empty_path_item.json new file mode 100644 index 000000000..429035caa --- /dev/null +++ b/test/openapi/frame/3.1/fail/path_parameter_without_a_template_in_an_empty_path_item.json @@ -0,0 +1,26 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets": { + "parameters": [ + { + "name": "petId", + "in": "path", + "required": true, + "schema": {} + } + ] + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "error": { + "message": "A path Parameter Object must name a template expression of the path it is under", + "location": "/paths/~1pets/parameters/0" + } +} diff --git a/test/openapi/frame/3.1/fail/path_template_mismatched_name.json b/test/openapi/frame/3.1/fail/path_template_mismatched_name.json new file mode 100644 index 000000000..809c83115 --- /dev/null +++ b/test/openapi/frame/3.1/fail/path_template_mismatched_name.json @@ -0,0 +1,33 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets/{petId}": { + "get": { + "responses": { + "200": { + "description": "ok" + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": {} + } + ] + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "error": { + "message": "A path Parameter Object must name a template expression of the path it is under", + "location": "/paths/~1pets~1{petId}/get/parameters/0" + } +} diff --git a/test/openapi/frame/3.1/fail/path_template_missing_from_one_operation.json b/test/openapi/frame/3.1/fail/path_template_missing_from_one_operation.json new file mode 100644 index 000000000..e58fde5df --- /dev/null +++ b/test/openapi/frame/3.1/fail/path_template_missing_from_one_operation.json @@ -0,0 +1,40 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets/{petId}": { + "get": { + "responses": { + "200": { + "description": "ok" + } + }, + "parameters": [ + { + "name": "petId", + "in": "path", + "required": true, + "schema": {} + } + ] + }, + "delete": { + "responses": { + "200": { + "description": "ok" + } + } + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "error": { + "message": "A templated path must declare a path parameter for each of its template expressions", + "location": "/paths/~1pets~1{petId}" + } +} diff --git a/test/openapi/frame/3.1/fail/path_template_without_a_parameter.json b/test/openapi/frame/3.1/fail/path_template_without_a_parameter.json new file mode 100644 index 000000000..2a8a2d0de --- /dev/null +++ b/test/openapi/frame/3.1/fail/path_template_without_a_parameter.json @@ -0,0 +1,25 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets/{petId}": { + "get": { + "responses": { + "200": { + "description": "ok" + } + } + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "error": { + "message": "A templated path must declare a path parameter for each of its template expressions", + "location": "/paths/~1pets~1{petId}" + } +} diff --git a/test/openapi/frame/3.1/fail/paths_is_not_an_object.json b/test/openapi/frame/3.1/fail/paths_is_not_an_object.json new file mode 100644 index 000000000..874d18228 --- /dev/null +++ b/test/openapi/frame/3.1/fail/paths_is_not_an_object.json @@ -0,0 +1,14 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": [] + }, + "error": { + "message": "The Paths Object must be an object", + "location": "/paths" + } +} diff --git a/test/openapi/frame/3.1/fail/paths_key_without_a_slash.json b/test/openapi/frame/3.1/fail/paths_key_without_a_slash.json new file mode 100644 index 000000000..7f29714df --- /dev/null +++ b/test/openapi/frame/3.1/fail/paths_key_without_a_slash.json @@ -0,0 +1,16 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "pets": {} + } + }, + "error": { + "message": "The Paths Object keys must begin with a slash", + "location": "/paths/pets" + } +} diff --git a/test/openapi/frame/3.1/fail/paths_repeat_a_templated_path.json b/test/openapi/frame/3.1/fail/paths_repeat_a_templated_path.json new file mode 100644 index 000000000..4f56c5211 --- /dev/null +++ b/test/openapi/frame/3.1/fail/paths_repeat_a_templated_path.json @@ -0,0 +1,17 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets/{petId}": {}, + "/pets/{name}": {} + } + }, + "error": { + "message": "The Paths Object keys must not repeat a templated path", + "location": "/paths/~1pets~1{name}" + } +} diff --git a/test/openapi/frame/3.1/fail/reference_identifier_is_not_a_string.json b/test/openapi/frame/3.1/fail/reference_identifier_is_not_a_string.json new file mode 100644 index 000000000..960acd752 --- /dev/null +++ b/test/openapi/frame/3.1/fail/reference_identifier_is_not_a_string.json @@ -0,0 +1,20 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "Item": { + "$ref": 1 + } + } + } + }, + "error": { + "message": "The Reference Object identifier must be a string", + "location": "/components/responses/Item/$ref" + } +} diff --git a/test/openapi/frame/3.1/fail/reference_identifier_is_not_a_uri_reference.json b/test/openapi/frame/3.1/fail/reference_identifier_is_not_a_uri_reference.json new file mode 100644 index 000000000..875dd427b --- /dev/null +++ b/test/openapi/frame/3.1/fail/reference_identifier_is_not_a_uri_reference.json @@ -0,0 +1,20 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "Item": { + "$ref": "https://example.com/a b" + } + } + } + }, + "error": { + "message": "The Reference Object identifier must be a URI reference", + "location": "/components/responses/Item/$ref" + } +} diff --git a/test/openapi/frame/3.1/fail/reference_into_a_templated_path_unescaped.json b/test/openapi/frame/3.1/fail/reference_into_a_templated_path_unescaped.json new file mode 100644 index 000000000..0db5f1523 --- /dev/null +++ b/test/openapi/frame/3.1/fail/reference_into_a_templated_path_unescaped.json @@ -0,0 +1,41 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "A": { + "$ref": "#/paths/~1pets~1{petId}/get/responses/200" + } + } + }, + "paths": { + "/pets/{petId}": { + "parameters": [ + { + "name": "petId", + "in": "path", + "required": true, + "schema": {} + } + ], + "get": { + "responses": { + "200": { + "description": "ok" + } + } + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "error": { + "message": "The Reference Object identifier must be a URI reference", + "location": "/components/responses/A/$ref", + "base": "https://example.com/openapi.json" + } +} diff --git a/test/openapi/frame/3.1/fail/reference_not_admitted_as_a_paths_key.json b/test/openapi/frame/3.1/fail/reference_not_admitted_as_a_paths_key.json new file mode 100644 index 000000000..2c07baf15 --- /dev/null +++ b/test/openapi/frame/3.1/fail/reference_not_admitted_as_a_paths_key.json @@ -0,0 +1,16 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "$ref": "#/components/schemas/Pet" + } + }, + "error": { + "message": "The Paths Object keys must begin with a slash", + "location": "/paths/$ref" + } +} diff --git a/test/openapi/frame/3.1/fail/reference_not_admitted_as_a_responses_key.json b/test/openapi/frame/3.1/fail/reference_not_admitted_as_a_responses_key.json new file mode 100644 index 000000000..42054519c --- /dev/null +++ b/test/openapi/frame/3.1/fail/reference_not_admitted_as_a_responses_key.json @@ -0,0 +1,22 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets": { + "get": { + "responses": { + "$ref": "#/components/schemas/Pet" + } + } + } + } + }, + "error": { + "message": "The Responses Object keys must be an HTTP status code or a status code range", + "location": "/paths/~1pets/get/responses/$ref" + } +} diff --git a/test/openapi/frame/3.1/fail/reference_not_admitted_in_components.json b/test/openapi/frame/3.1/fail/reference_not_admitted_in_components.json new file mode 100644 index 000000000..e70261a4b --- /dev/null +++ b/test/openapi/frame/3.1/fail/reference_not_admitted_in_components.json @@ -0,0 +1,16 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "$ref": "#/components/schemas/Pet" + } + }, + "error": { + "message": "The Components Object does not define this field", + "location": "/components/$ref" + } +} diff --git a/test/openapi/frame/3.1/fail/reference_not_admitted_in_contact.json b/test/openapi/frame/3.1/fail/reference_not_admitted_in_contact.json new file mode 100644 index 000000000..624d62444 --- /dev/null +++ b/test/openapi/frame/3.1/fail/reference_not_admitted_in_contact.json @@ -0,0 +1,17 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "E", + "version": "1", + "contact": { + "$ref": "#/components/schemas/Pet" + } + }, + "paths": {} + }, + "error": { + "message": "The Contact Object does not define this field", + "location": "/info/contact/$ref" + } +} diff --git a/test/openapi/frame/3.1/fail/reference_not_admitted_in_encoding.json b/test/openapi/frame/3.1/fail/reference_not_admitted_in_encoding.json new file mode 100644 index 000000000..9f8664d0b --- /dev/null +++ b/test/openapi/frame/3.1/fail/reference_not_admitted_in_encoding.json @@ -0,0 +1,28 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "requestBodies": { + "R": { + "content": { + "multipart/form-data": { + "encoding": { + "part": { + "$ref": "#/components/schemas/Pet" + } + } + } + } + } + } + } + }, + "error": { + "message": "The Encoding Object does not define this field", + "location": "/components/requestBodies/R/content/multipart~1form-data/encoding/part/$ref" + } +} diff --git a/test/openapi/frame/3.1/fail/reference_not_admitted_in_external_documentation.json b/test/openapi/frame/3.1/fail/reference_not_admitted_in_external_documentation.json new file mode 100644 index 000000000..82aa3a654 --- /dev/null +++ b/test/openapi/frame/3.1/fail/reference_not_admitted_in_external_documentation.json @@ -0,0 +1,18 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": {}, + "externalDocs": { + "url": "/docs", + "$ref": "#/components/schemas/Pet" + } + }, + "error": { + "message": "The External Documentation Object does not define this field", + "location": "/externalDocs/$ref" + } +} diff --git a/test/openapi/frame/3.1/fail/reference_not_admitted_in_info.json b/test/openapi/frame/3.1/fail/reference_not_admitted_in_info.json new file mode 100644 index 000000000..0129445b9 --- /dev/null +++ b/test/openapi/frame/3.1/fail/reference_not_admitted_in_info.json @@ -0,0 +1,15 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "E", + "version": "1", + "$ref": "#/components/schemas/Pet" + }, + "paths": {} + }, + "error": { + "message": "The Info Object does not define this field", + "location": "/info/$ref" + } +} diff --git a/test/openapi/frame/3.1/fail/reference_not_admitted_in_license.json b/test/openapi/frame/3.1/fail/reference_not_admitted_in_license.json new file mode 100644 index 000000000..bbdaf8dd5 --- /dev/null +++ b/test/openapi/frame/3.1/fail/reference_not_admitted_in_license.json @@ -0,0 +1,18 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "E", + "version": "1", + "license": { + "name": "MIT", + "$ref": "#/components/schemas/Pet" + } + }, + "paths": {} + }, + "error": { + "message": "The License Object does not define this field", + "location": "/info/license/$ref" + } +} diff --git a/test/openapi/frame/3.1/fail/reference_not_admitted_in_media_type.json b/test/openapi/frame/3.1/fail/reference_not_admitted_in_media_type.json new file mode 100644 index 000000000..932815333 --- /dev/null +++ b/test/openapi/frame/3.1/fail/reference_not_admitted_in_media_type.json @@ -0,0 +1,25 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "R": { + "description": "ok", + "content": { + "application/json": { + "$ref": "#/components/schemas/Pet" + } + } + } + } + } + }, + "error": { + "message": "The Media Type Object does not define this field", + "location": "/components/responses/R/content/application~1json/$ref" + } +} diff --git a/test/openapi/frame/3.1/fail/reference_not_admitted_in_oauth_flow.json b/test/openapi/frame/3.1/fail/reference_not_admitted_in_oauth_flow.json new file mode 100644 index 000000000..fcf4fbb8d --- /dev/null +++ b/test/openapi/frame/3.1/fail/reference_not_admitted_in_oauth_flow.json @@ -0,0 +1,27 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "securitySchemes": { + "S": { + "type": "oauth2", + "flows": { + "implicit": { + "authorizationUrl": "/a", + "scopes": {}, + "$ref": "#/components/schemas/Pet" + } + } + } + } + } + }, + "error": { + "message": "The OAuth Flow Object does not define this field", + "location": "/components/securitySchemes/S/flows/implicit/$ref" + } +} diff --git a/test/openapi/frame/3.1/fail/reference_not_admitted_in_oauth_flows.json b/test/openapi/frame/3.1/fail/reference_not_admitted_in_oauth_flows.json new file mode 100644 index 000000000..ff0626615 --- /dev/null +++ b/test/openapi/frame/3.1/fail/reference_not_admitted_in_oauth_flows.json @@ -0,0 +1,23 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "securitySchemes": { + "S": { + "type": "oauth2", + "flows": { + "$ref": "#/components/schemas/Pet" + } + } + } + } + }, + "error": { + "message": "The OAuth Flows Object does not define this field", + "location": "/components/securitySchemes/S/flows/$ref" + } +} diff --git a/test/openapi/frame/3.1/fail/reference_not_admitted_in_operation.json b/test/openapi/frame/3.1/fail/reference_not_admitted_in_operation.json new file mode 100644 index 000000000..76cf6feb9 --- /dev/null +++ b/test/openapi/frame/3.1/fail/reference_not_admitted_in_operation.json @@ -0,0 +1,20 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets": { + "get": { + "$ref": "#/components/schemas/Pet" + } + } + } + }, + "error": { + "message": "The Operation Object does not define this field", + "location": "/paths/~1pets/get/$ref" + } +} diff --git a/test/openapi/frame/3.1/fail/reference_not_admitted_in_server.json b/test/openapi/frame/3.1/fail/reference_not_admitted_in_server.json new file mode 100644 index 000000000..d96443696 --- /dev/null +++ b/test/openapi/frame/3.1/fail/reference_not_admitted_in_server.json @@ -0,0 +1,20 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": {}, + "servers": [ + { + "url": "/v1", + "$ref": "#/components/schemas/Pet" + } + ] + }, + "error": { + "message": "The Server Object does not define this field", + "location": "/servers/0/$ref" + } +} diff --git a/test/openapi/frame/3.1/fail/reference_not_admitted_in_server_variable.json b/test/openapi/frame/3.1/fail/reference_not_admitted_in_server_variable.json new file mode 100644 index 000000000..808c0ee40 --- /dev/null +++ b/test/openapi/frame/3.1/fail/reference_not_admitted_in_server_variable.json @@ -0,0 +1,25 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": {}, + "servers": [ + { + "url": "/{v}", + "variables": { + "v": { + "default": "1", + "$ref": "#/components/schemas/Pet" + } + } + } + ] + }, + "error": { + "message": "The Server Variable Object does not define this field", + "location": "/servers/0/variables/v/$ref" + } +} diff --git a/test/openapi/frame/3.1/fail/reference_not_admitted_in_tag.json b/test/openapi/frame/3.1/fail/reference_not_admitted_in_tag.json new file mode 100644 index 000000000..be9b521c6 --- /dev/null +++ b/test/openapi/frame/3.1/fail/reference_not_admitted_in_tag.json @@ -0,0 +1,20 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": {}, + "tags": [ + { + "name": "pet", + "$ref": "#/components/schemas/Pet" + } + ] + }, + "error": { + "message": "The Tag Object does not define this field", + "location": "/tags/0/$ref" + } +} diff --git a/test/openapi/frame/3.1/fail/reference_summary_is_not_a_string.json b/test/openapi/frame/3.1/fail/reference_summary_is_not_a_string.json new file mode 100644 index 000000000..e700ec5bc --- /dev/null +++ b/test/openapi/frame/3.1/fail/reference_summary_is_not_a_string.json @@ -0,0 +1,21 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "Item": { + "$ref": "#/x", + "summary": 1 + } + } + } + }, + "error": { + "message": "The Reference Object summary must be a string", + "location": "/components/responses/Item/summary" + } +} diff --git a/test/openapi/frame/3.1/fail/request_body_missing_content.json b/test/openapi/frame/3.1/fail/request_body_missing_content.json new file mode 100644 index 000000000..e6cb51c9f --- /dev/null +++ b/test/openapi/frame/3.1/fail/request_body_missing_content.json @@ -0,0 +1,18 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "requestBodies": { + "Item": {} + } + } + }, + "error": { + "message": "The Request Body Object must declare a content", + "location": "/components/requestBodies/Item" + } +} diff --git a/test/openapi/frame/3.1/fail/request_body_required_is_not_a_boolean.json b/test/openapi/frame/3.1/fail/request_body_required_is_not_a_boolean.json new file mode 100644 index 000000000..d9bb260c6 --- /dev/null +++ b/test/openapi/frame/3.1/fail/request_body_required_is_not_a_boolean.json @@ -0,0 +1,21 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "requestBodies": { + "Item": { + "content": {}, + "required": "yes" + } + } + } + }, + "error": { + "message": "The Request Body Object required must be a boolean", + "location": "/components/requestBodies/Item/required" + } +} diff --git a/test/openapi/frame/3.1/fail/response_content_is_not_an_object.json b/test/openapi/frame/3.1/fail/response_content_is_not_an_object.json new file mode 100644 index 000000000..8b77973a8 --- /dev/null +++ b/test/openapi/frame/3.1/fail/response_content_is_not_an_object.json @@ -0,0 +1,21 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "Item": { + "description": "ok", + "content": [] + } + } + } + }, + "error": { + "message": "The Response Object content must be an object", + "location": "/components/responses/Item/content" + } +} diff --git a/test/openapi/frame/3.1/fail/response_description_is_not_a_string.json b/test/openapi/frame/3.1/fail/response_description_is_not_a_string.json new file mode 100644 index 000000000..d6c7c2004 --- /dev/null +++ b/test/openapi/frame/3.1/fail/response_description_is_not_a_string.json @@ -0,0 +1,20 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "Item": { + "description": 1 + } + } + } + }, + "error": { + "message": "The Response Object description must be a string", + "location": "/components/responses/Item/description" + } +} diff --git a/test/openapi/frame/3.1/fail/response_headers_is_not_an_object.json b/test/openapi/frame/3.1/fail/response_headers_is_not_an_object.json new file mode 100644 index 000000000..49ea02a93 --- /dev/null +++ b/test/openapi/frame/3.1/fail/response_headers_is_not_an_object.json @@ -0,0 +1,21 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "Item": { + "description": "ok", + "headers": [] + } + } + } + }, + "error": { + "message": "The Response Object headers must be an object", + "location": "/components/responses/Item/headers" + } +} diff --git a/test/openapi/frame/3.1/fail/response_links_is_not_an_object.json b/test/openapi/frame/3.1/fail/response_links_is_not_an_object.json new file mode 100644 index 000000000..c6ef79022 --- /dev/null +++ b/test/openapi/frame/3.1/fail/response_links_is_not_an_object.json @@ -0,0 +1,21 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "Item": { + "description": "ok", + "links": [] + } + } + } + }, + "error": { + "message": "The Response Object links must be an object", + "location": "/components/responses/Item/links" + } +} diff --git a/test/openapi/frame/3.1/fail/response_missing_description.json b/test/openapi/frame/3.1/fail/response_missing_description.json new file mode 100644 index 000000000..111634631 --- /dev/null +++ b/test/openapi/frame/3.1/fail/response_missing_description.json @@ -0,0 +1,18 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "Item": {} + } + } + }, + "error": { + "message": "The Response Object must declare a description", + "location": "/components/responses/Item" + } +} diff --git a/test/openapi/frame/3.1/fail/response_unknown_field.json b/test/openapi/frame/3.1/fail/response_unknown_field.json new file mode 100644 index 000000000..87f9b5ed5 --- /dev/null +++ b/test/openapi/frame/3.1/fail/response_unknown_field.json @@ -0,0 +1,21 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "Item": { + "description": "ok", + "schema": {} + } + } + } + }, + "error": { + "message": "The Response Object does not define this field", + "location": "/components/responses/Item/schema" + } +} diff --git a/test/openapi/frame/3.1/fail/responses_is_empty.json b/test/openapi/frame/3.1/fail/responses_is_empty.json new file mode 100644 index 000000000..880e36161 --- /dev/null +++ b/test/openapi/frame/3.1/fail/responses_is_empty.json @@ -0,0 +1,20 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets": { + "get": { + "responses": {} + } + } + } + }, + "error": { + "message": "The Responses Object must not be empty", + "location": "/paths/~1pets/get/responses" + } +} diff --git a/test/openapi/frame/3.1/fail/responses_is_not_an_object.json b/test/openapi/frame/3.1/fail/responses_is_not_an_object.json new file mode 100644 index 000000000..24b86c49d --- /dev/null +++ b/test/openapi/frame/3.1/fail/responses_is_not_an_object.json @@ -0,0 +1,20 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets": { + "get": { + "responses": [] + } + } + } + }, + "error": { + "message": "The Responses Object must be an object", + "location": "/paths/~1pets/get/responses" + } +} diff --git a/test/openapi/frame/3.1/fail/responses_key_is_not_a_status_code.json b/test/openapi/frame/3.1/fail/responses_key_is_not_a_status_code.json new file mode 100644 index 000000000..a8320ccc7 --- /dev/null +++ b/test/openapi/frame/3.1/fail/responses_key_is_not_a_status_code.json @@ -0,0 +1,24 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets": { + "get": { + "responses": { + "okay": { + "description": "ok" + } + } + } + } + } + }, + "error": { + "message": "The Responses Object keys must be an HTTP status code or a status code range", + "location": "/paths/~1pets/get/responses/okay" + } +} diff --git a/test/openapi/frame/3.1/fail/responses_only_extensions.json b/test/openapi/frame/3.1/fail/responses_only_extensions.json new file mode 100644 index 000000000..e1d3d0f6c --- /dev/null +++ b/test/openapi/frame/3.1/fail/responses_only_extensions.json @@ -0,0 +1,22 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets": { + "get": { + "responses": { + "x-note": true + } + } + } + } + }, + "error": { + "message": "The Responses Object must declare a default or a status code", + "location": "/paths/~1pets/get/responses" + } +} diff --git a/test/openapi/frame/3.1/fail/responses_status_code_out_of_range.json b/test/openapi/frame/3.1/fail/responses_status_code_out_of_range.json new file mode 100644 index 000000000..746d57d8c --- /dev/null +++ b/test/openapi/frame/3.1/fail/responses_status_code_out_of_range.json @@ -0,0 +1,24 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets": { + "get": { + "responses": { + "600": { + "description": "ok" + } + } + } + } + } + }, + "error": { + "message": "The Responses Object keys must be an HTTP status code or a status code range", + "location": "/paths/~1pets/get/responses/600" + } +} diff --git a/test/openapi/frame/3.1/fail/root_definitions_is_not_a_field.json b/test/openapi/frame/3.1/fail/root_definitions_is_not_a_field.json new file mode 100644 index 000000000..ef3de190f --- /dev/null +++ b/test/openapi/frame/3.1/fail/root_definitions_is_not_a_field.json @@ -0,0 +1,15 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": {}, + "definitions": {} + }, + "error": { + "message": "The OpenAPI Object does not define this field", + "location": "/definitions" + } +} diff --git a/test/openapi/frame/3.1/fail/root_security_is_not_an_array.json b/test/openapi/frame/3.1/fail/root_security_is_not_an_array.json new file mode 100644 index 000000000..a04705528 --- /dev/null +++ b/test/openapi/frame/3.1/fail/root_security_is_not_an_array.json @@ -0,0 +1,15 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": {}, + "security": {} + }, + "error": { + "message": "The OpenAPI Description security must be an array", + "location": "/security" + } +} diff --git a/test/openapi/frame/3.1/fail/root_unknown_field.json b/test/openapi/frame/3.1/fail/root_unknown_field.json new file mode 100644 index 000000000..cd7b4f880 --- /dev/null +++ b/test/openapi/frame/3.1/fail/root_unknown_field.json @@ -0,0 +1,15 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": {}, + "swagger": "2.0" + }, + "error": { + "message": "The OpenAPI Object does not define this field", + "location": "/swagger" + } +} diff --git a/test/openapi/frame/3.1/fail/root_without_any_container.json b/test/openapi/frame/3.1/fail/root_without_any_container.json new file mode 100644 index 000000000..3fcb11f89 --- /dev/null +++ b/test/openapi/frame/3.1/fail/root_without_any_container.json @@ -0,0 +1,13 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + } + }, + "error": { + "message": "The OpenAPI Description must declare paths, components or webhooks", + "location": "" + } +} diff --git a/test/openapi/frame/3.1/fail/second_reference_into_one_document_checks_its_own_fragment.json b/test/openapi/frame/3.1/fail/second_reference_into_one_document_checks_its_own_fragment.json new file mode 100644 index 000000000..7d2fcfbd5 --- /dev/null +++ b/test/openapi/frame/3.1/fail/second_reference_into_one_document_checks_its_own_fragment.json @@ -0,0 +1,49 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "E", + "version": "1" + }, + "components": { + "responses": { + "First": { + "$ref": "https://example.com/common.json#/components/responses/Ok" + }, + "Second": { + "$ref": "https://example.com/common.json#/components/parameters/Limit" + } + } + }, + "paths": {} + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/common.json": { + "openapi": "3.1.1", + "info": { + "title": "Common", + "version": "1.0.0" + }, + "components": { + "responses": { + "Ok": { + "description": "ok" + } + }, + "parameters": { + "Limit": { + "name": "limit", + "in": "query", + "schema": {} + } + } + } + } + }, + "error": { + "message": "The Response Object does not define this field", + "location": "/components/parameters/Limit/name", + "base": "https://example.com/common.json" + } +} diff --git a/test/openapi/frame/3.1/fail/security_requirement_does_not_resolve_from_the_current_document.json b/test/openapi/frame/3.1/fail/security_requirement_does_not_resolve_from_the_current_document.json new file mode 100644 index 000000000..43e123b97 --- /dev/null +++ b/test/openapi/frame/3.1/fail/security_requirement_does_not_resolve_from_the_current_document.json @@ -0,0 +1,54 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/foo": { + "$ref": "https://example.com/other.json#/components/pathItems/Foo" + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/other.json": { + "openapi": "3.1.1", + "info": { + "title": "Other", + "version": "1.0.0" + }, + "components": { + "securitySchemes": { + "MySecurity": { + "type": "apiKey", + "name": "X-Key", + "in": "header" + } + }, + "pathItems": { + "Foo": { + "get": { + "responses": { + "200": { + "description": "ok" + } + }, + "security": [ + { + "MySecurity": [] + } + ] + } + } + } + } + } + }, + "error": { + "message": "The Security Requirement Object must name a declared security scheme", + "location": "/components/pathItems/Foo/get/security/0/MySecurity", + "base": "https://example.com/other.json" + } +} diff --git a/test/openapi/frame/3.1/fail/security_requirement_in_an_operation_names_an_undeclared_scheme.json b/test/openapi/frame/3.1/fail/security_requirement_in_an_operation_names_an_undeclared_scheme.json new file mode 100644 index 000000000..62a292999 --- /dev/null +++ b/test/openapi/frame/3.1/fail/security_requirement_in_an_operation_names_an_undeclared_scheme.json @@ -0,0 +1,29 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets": { + "get": { + "responses": { + "200": { + "description": "ok" + } + }, + "security": [ + { + "ApiKey": [] + } + ] + } + } + } + }, + "error": { + "message": "The Security Requirement Object must name a declared security scheme", + "location": "/paths/~1pets/get/security/0/ApiKey" + } +} diff --git a/test/openapi/frame/3.1/fail/security_requirement_is_not_an_object.json b/test/openapi/frame/3.1/fail/security_requirement_is_not_an_object.json new file mode 100644 index 000000000..62442ab61 --- /dev/null +++ b/test/openapi/frame/3.1/fail/security_requirement_is_not_an_object.json @@ -0,0 +1,17 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": {}, + "security": [ + "ApiKey" + ] + }, + "error": { + "message": "The Security Requirement Object must be an object", + "location": "/security/0" + } +} diff --git a/test/openapi/frame/3.1/fail/security_requirement_named_ref.json b/test/openapi/frame/3.1/fail/security_requirement_named_ref.json new file mode 100644 index 000000000..eb1a54af3 --- /dev/null +++ b/test/openapi/frame/3.1/fail/security_requirement_named_ref.json @@ -0,0 +1,21 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "security": [ + { + "$ref": [ + "read" + ] + } + ], + "paths": {} + }, + "error": { + "message": "The Security Requirement Object must name a declared security scheme", + "location": "/security/0/$ref" + } +} diff --git a/test/openapi/frame/3.1/fail/security_requirement_names_an_undeclared_scheme.json b/test/openapi/frame/3.1/fail/security_requirement_names_an_undeclared_scheme.json new file mode 100644 index 000000000..a8a40a807 --- /dev/null +++ b/test/openapi/frame/3.1/fail/security_requirement_names_an_undeclared_scheme.json @@ -0,0 +1,19 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": {}, + "security": [ + { + "ApiKey": [] + } + ] + }, + "error": { + "message": "The Security Requirement Object must name a declared security scheme", + "location": "/security/0/ApiKey" + } +} diff --git a/test/openapi/frame/3.1/fail/security_requirement_scope_is_not_a_string.json b/test/openapi/frame/3.1/fail/security_requirement_scope_is_not_a_string.json new file mode 100644 index 000000000..a4977fa90 --- /dev/null +++ b/test/openapi/frame/3.1/fail/security_requirement_scope_is_not_a_string.json @@ -0,0 +1,30 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": {}, + "security": [ + { + "ApiKey": [ + 1 + ] + } + ], + "components": { + "securitySchemes": { + "ApiKey": { + "type": "apiKey", + "name": "X-Key", + "in": "header" + } + } + } + }, + "error": { + "message": "The Security Requirement Object entries must hold strings", + "location": "/security/0/ApiKey/0" + } +} diff --git a/test/openapi/frame/3.1/fail/security_requirement_value_is_not_an_array.json b/test/openapi/frame/3.1/fail/security_requirement_value_is_not_an_array.json new file mode 100644 index 000000000..73e478ad9 --- /dev/null +++ b/test/openapi/frame/3.1/fail/security_requirement_value_is_not_an_array.json @@ -0,0 +1,28 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": {}, + "security": [ + { + "ApiKey": "read" + } + ], + "components": { + "securitySchemes": { + "ApiKey": { + "type": "apiKey", + "name": "X-Key", + "in": "header" + } + } + } + }, + "error": { + "message": "The Security Requirement Object entries must be arrays", + "location": "/security/0/ApiKey" + } +} diff --git a/test/openapi/frame/3.1/fail/security_scheme_apikey_location_is_unknown.json b/test/openapi/frame/3.1/fail/security_scheme_apikey_location_is_unknown.json new file mode 100644 index 000000000..33223e829 --- /dev/null +++ b/test/openapi/frame/3.1/fail/security_scheme_apikey_location_is_unknown.json @@ -0,0 +1,22 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "securitySchemes": { + "Item": { + "type": "apiKey", + "name": "X-Key", + "in": "body" + } + } + } + }, + "error": { + "message": "The Security Scheme Object location is not one an apiKey admits", + "location": "/components/securitySchemes/Item/in" + } +} diff --git a/test/openapi/frame/3.1/fail/security_scheme_apikey_missing_location.json b/test/openapi/frame/3.1/fail/security_scheme_apikey_missing_location.json new file mode 100644 index 000000000..79b2297fa --- /dev/null +++ b/test/openapi/frame/3.1/fail/security_scheme_apikey_missing_location.json @@ -0,0 +1,21 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "securitySchemes": { + "Item": { + "type": "apiKey", + "name": "X-Key" + } + } + } + }, + "error": { + "message": "An apiKey Security Scheme Object must declare a location", + "location": "/components/securitySchemes/Item" + } +} diff --git a/test/openapi/frame/3.1/fail/security_scheme_apikey_missing_name.json b/test/openapi/frame/3.1/fail/security_scheme_apikey_missing_name.json new file mode 100644 index 000000000..9774f9e94 --- /dev/null +++ b/test/openapi/frame/3.1/fail/security_scheme_apikey_missing_name.json @@ -0,0 +1,21 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "securitySchemes": { + "Item": { + "type": "apiKey", + "in": "header" + } + } + } + }, + "error": { + "message": "An apiKey Security Scheme Object must declare a name", + "location": "/components/securitySchemes/Item" + } +} diff --git a/test/openapi/frame/3.1/fail/security_scheme_apikey_with_scheme.json b/test/openapi/frame/3.1/fail/security_scheme_apikey_with_scheme.json new file mode 100644 index 000000000..0f3957913 --- /dev/null +++ b/test/openapi/frame/3.1/fail/security_scheme_apikey_with_scheme.json @@ -0,0 +1,23 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "securitySchemes": { + "Item": { + "type": "apiKey", + "name": "X-Key", + "in": "header", + "scheme": "bearer" + } + } + } + }, + "error": { + "message": "The Security Scheme Object does not define this field", + "location": "/components/securitySchemes/Item/scheme" + } +} diff --git a/test/openapi/frame/3.1/fail/security_scheme_http_bearer_format_without_bearer.json b/test/openapi/frame/3.1/fail/security_scheme_http_bearer_format_without_bearer.json new file mode 100644 index 000000000..7460e2394 --- /dev/null +++ b/test/openapi/frame/3.1/fail/security_scheme_http_bearer_format_without_bearer.json @@ -0,0 +1,24 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "securitySchemes": { + "S": { + "type": "http", + "scheme": "basic", + "bearerFormat": "JWT" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "error": { + "message": "The Security Scheme Object does not define this field", + "location": "/components/securitySchemes/S/bearerFormat", + "base": "https://example.com/openapi.json" + } +} diff --git a/test/openapi/frame/3.1/fail/security_scheme_http_missing_scheme.json b/test/openapi/frame/3.1/fail/security_scheme_http_missing_scheme.json new file mode 100644 index 000000000..c4b123c35 --- /dev/null +++ b/test/openapi/frame/3.1/fail/security_scheme_http_missing_scheme.json @@ -0,0 +1,20 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "securitySchemes": { + "Item": { + "type": "http" + } + } + } + }, + "error": { + "message": "An http Security Scheme Object must declare a scheme", + "location": "/components/securitySchemes/Item" + } +} diff --git a/test/openapi/frame/3.1/fail/security_scheme_missing_type.json b/test/openapi/frame/3.1/fail/security_scheme_missing_type.json new file mode 100644 index 000000000..ba1bba9bb --- /dev/null +++ b/test/openapi/frame/3.1/fail/security_scheme_missing_type.json @@ -0,0 +1,18 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "securitySchemes": { + "Item": {} + } + } + }, + "error": { + "message": "The Security Scheme Object must declare its type", + "location": "/components/securitySchemes/Item" + } +} diff --git a/test/openapi/frame/3.1/fail/security_scheme_mutual_tls_with_scheme.json b/test/openapi/frame/3.1/fail/security_scheme_mutual_tls_with_scheme.json new file mode 100644 index 000000000..666eff865 --- /dev/null +++ b/test/openapi/frame/3.1/fail/security_scheme_mutual_tls_with_scheme.json @@ -0,0 +1,21 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "securitySchemes": { + "Item": { + "type": "mutualTLS", + "scheme": "bearer" + } + } + } + }, + "error": { + "message": "The Security Scheme Object does not define this field", + "location": "/components/securitySchemes/Item/scheme" + } +} diff --git a/test/openapi/frame/3.1/fail/security_scheme_oauth2_missing_flows.json b/test/openapi/frame/3.1/fail/security_scheme_oauth2_missing_flows.json new file mode 100644 index 000000000..640fd9cad --- /dev/null +++ b/test/openapi/frame/3.1/fail/security_scheme_oauth2_missing_flows.json @@ -0,0 +1,20 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "securitySchemes": { + "Item": { + "type": "oauth2" + } + } + } + }, + "error": { + "message": "An oauth2 Security Scheme Object must declare its flows", + "location": "/components/securitySchemes/Item" + } +} diff --git a/test/openapi/frame/3.1/fail/security_scheme_oidc_missing_url.json b/test/openapi/frame/3.1/fail/security_scheme_oidc_missing_url.json new file mode 100644 index 000000000..affe9f477 --- /dev/null +++ b/test/openapi/frame/3.1/fail/security_scheme_oidc_missing_url.json @@ -0,0 +1,20 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "securitySchemes": { + "Item": { + "type": "openIdConnect" + } + } + } + }, + "error": { + "message": "An openIdConnect Security Scheme Object must declare its discovery URI", + "location": "/components/securitySchemes/Item" + } +} diff --git a/test/openapi/frame/3.1/fail/security_scheme_oidc_url_is_not_a_uri_reference.json b/test/openapi/frame/3.1/fail/security_scheme_oidc_url_is_not_a_uri_reference.json new file mode 100644 index 000000000..d7011c55d --- /dev/null +++ b/test/openapi/frame/3.1/fail/security_scheme_oidc_url_is_not_a_uri_reference.json @@ -0,0 +1,21 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "securitySchemes": { + "Item": { + "type": "openIdConnect", + "openIdConnectUrl": "https://example.com/a b" + } + } + } + }, + "error": { + "message": "The Security Scheme Object discovery URI must be a URI reference", + "location": "/components/securitySchemes/Item/openIdConnectUrl" + } +} diff --git a/test/openapi/frame/3.1/fail/security_scheme_type_is_unknown.json b/test/openapi/frame/3.1/fail/security_scheme_type_is_unknown.json new file mode 100644 index 000000000..3a338c9ef --- /dev/null +++ b/test/openapi/frame/3.1/fail/security_scheme_type_is_unknown.json @@ -0,0 +1,20 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "securitySchemes": { + "Item": { + "type": "basic" + } + } + } + }, + "error": { + "message": "The Security Scheme Object type is not one this specification defines", + "location": "/components/securitySchemes/Item/type" + } +} diff --git a/test/openapi/frame/3.1/fail/server_description_is_not_a_string.json b/test/openapi/frame/3.1/fail/server_description_is_not_a_string.json new file mode 100644 index 000000000..fb17a9c05 --- /dev/null +++ b/test/openapi/frame/3.1/fail/server_description_is_not_a_string.json @@ -0,0 +1,20 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "servers": [ + { + "url": "/v1", + "description": 1 + } + ], + "paths": {} + }, + "error": { + "message": "The Server Object description must be a string", + "location": "/servers/0/description" + } +} diff --git a/test/openapi/frame/3.1/fail/server_is_not_an_object.json b/test/openapi/frame/3.1/fail/server_is_not_an_object.json new file mode 100644 index 000000000..b05791afd --- /dev/null +++ b/test/openapi/frame/3.1/fail/server_is_not_an_object.json @@ -0,0 +1,17 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "servers": [ + "https://example.com" + ], + "paths": {} + }, + "error": { + "message": "The Server Object must be an object", + "location": "/servers/0" + } +} diff --git a/test/openapi/frame/3.1/fail/server_missing_url.json b/test/openapi/frame/3.1/fail/server_missing_url.json new file mode 100644 index 000000000..ddef90c0e --- /dev/null +++ b/test/openapi/frame/3.1/fail/server_missing_url.json @@ -0,0 +1,19 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "servers": [ + { + "description": "Production" + } + ], + "paths": {} + }, + "error": { + "message": "The Server Object must declare a URL", + "location": "/servers/0" + } +} diff --git a/test/openapi/frame/3.1/fail/server_second_entry_is_invalid.json b/test/openapi/frame/3.1/fail/server_second_entry_is_invalid.json new file mode 100644 index 000000000..4f48ddddf --- /dev/null +++ b/test/openapi/frame/3.1/fail/server_second_entry_is_invalid.json @@ -0,0 +1,22 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "servers": [ + { + "url": "/v1" + }, + { + "description": "No URL" + } + ], + "paths": {} + }, + "error": { + "message": "The Server Object must declare a URL", + "location": "/servers/1" + } +} diff --git a/test/openapi/frame/3.1/fail/server_unknown_field.json b/test/openapi/frame/3.1/fail/server_unknown_field.json new file mode 100644 index 000000000..39d9a12cc --- /dev/null +++ b/test/openapi/frame/3.1/fail/server_unknown_field.json @@ -0,0 +1,20 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "servers": [ + { + "url": "/v1", + "variable": {} + } + ], + "paths": {} + }, + "error": { + "message": "The Server Object does not define this field", + "location": "/servers/0/variable" + } +} diff --git a/test/openapi/frame/3.1/fail/server_url_is_not_a_string.json b/test/openapi/frame/3.1/fail/server_url_is_not_a_string.json new file mode 100644 index 000000000..21d448829 --- /dev/null +++ b/test/openapi/frame/3.1/fail/server_url_is_not_a_string.json @@ -0,0 +1,19 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "servers": [ + { + "url": 1 + } + ], + "paths": {} + }, + "error": { + "message": "The Server Object URL must be a string", + "location": "/servers/0/url" + } +} diff --git a/test/openapi/frame/3.1/fail/server_url_with_a_fragment.json b/test/openapi/frame/3.1/fail/server_url_with_a_fragment.json new file mode 100644 index 000000000..ed28bf183 --- /dev/null +++ b/test/openapi/frame/3.1/fail/server_url_with_a_fragment.json @@ -0,0 +1,21 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "servers": [ + { + "url": "https://api.example.com/v1#top" + } + ], + "paths": {} + }, + "defaultBase": "https://example.com/openapi.json", + "error": { + "message": "The Server Object URL must carry no query and no fragment", + "location": "/servers/0/url", + "base": "https://example.com/openapi.json" + } +} diff --git a/test/openapi/frame/3.1/fail/server_url_with_a_query.json b/test/openapi/frame/3.1/fail/server_url_with_a_query.json new file mode 100644 index 000000000..a9e6385d9 --- /dev/null +++ b/test/openapi/frame/3.1/fail/server_url_with_a_query.json @@ -0,0 +1,21 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "servers": [ + { + "url": "https://api.example.com/v1?debug=1" + } + ], + "paths": {} + }, + "defaultBase": "https://example.com/openapi.json", + "error": { + "message": "The Server Object URL must carry no query and no fragment", + "location": "/servers/0/url", + "base": "https://example.com/openapi.json" + } +} diff --git a/test/openapi/frame/3.1/fail/server_url_with_a_query_inside_a_variable.json b/test/openapi/frame/3.1/fail/server_url_with_a_query_inside_a_variable.json new file mode 100644 index 000000000..67e4c2470 --- /dev/null +++ b/test/openapi/frame/3.1/fail/server_url_with_a_query_inside_a_variable.json @@ -0,0 +1,21 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "servers": [ + { + "url": "https://api.example.com/{path?x}" + } + ], + "paths": {} + }, + "defaultBase": "https://example.com/openapi.json", + "error": { + "message": "The Server Object URL must carry no query and no fragment", + "location": "/servers/0/url", + "base": "https://example.com/openapi.json" + } +} diff --git a/test/openapi/frame/3.1/fail/server_variable_default_is_not_a_string.json b/test/openapi/frame/3.1/fail/server_variable_default_is_not_a_string.json new file mode 100644 index 000000000..fddc5c355 --- /dev/null +++ b/test/openapi/frame/3.1/fail/server_variable_default_is_not_a_string.json @@ -0,0 +1,24 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "servers": [ + { + "url": "/{version}", + "variables": { + "version": { + "default": 1 + } + } + } + ], + "paths": {} + }, + "error": { + "message": "The Server Variable Object default must be a string", + "location": "/servers/0/variables/version/default" + } +} diff --git a/test/openapi/frame/3.1/fail/server_variable_default_outside_enum.json b/test/openapi/frame/3.1/fail/server_variable_default_outside_enum.json new file mode 100644 index 000000000..e596a86fa --- /dev/null +++ b/test/openapi/frame/3.1/fail/server_variable_default_outside_enum.json @@ -0,0 +1,28 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "servers": [ + { + "url": "/{version}", + "variables": { + "version": { + "default": "v3", + "enum": [ + "v1", + "v2" + ] + } + } + } + ], + "paths": {} + }, + "error": { + "message": "The Server Variable Object default must exist in its enumeration", + "location": "/servers/0/variables/version/default" + } +} diff --git a/test/openapi/frame/3.1/fail/server_variable_description_is_not_a_string.json b/test/openapi/frame/3.1/fail/server_variable_description_is_not_a_string.json new file mode 100644 index 000000000..272c104d3 --- /dev/null +++ b/test/openapi/frame/3.1/fail/server_variable_description_is_not_a_string.json @@ -0,0 +1,25 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "servers": [ + { + "url": "/{version}", + "variables": { + "version": { + "default": "v1", + "description": 1 + } + } + } + ], + "paths": {} + }, + "error": { + "message": "The Server Variable Object description must be a string", + "location": "/servers/0/variables/version/description" + } +} diff --git a/test/openapi/frame/3.1/fail/server_variable_enum_holds_a_number.json b/test/openapi/frame/3.1/fail/server_variable_enum_holds_a_number.json new file mode 100644 index 000000000..30a4b15d6 --- /dev/null +++ b/test/openapi/frame/3.1/fail/server_variable_enum_holds_a_number.json @@ -0,0 +1,28 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "servers": [ + { + "url": "/{version}", + "variables": { + "version": { + "default": "v1", + "enum": [ + "v1", + 2 + ] + } + } + } + ], + "paths": {} + }, + "error": { + "message": "The Server Variable Object enumeration must only hold strings", + "location": "/servers/0/variables/version/enum/1" + } +} diff --git a/test/openapi/frame/3.1/fail/server_variable_enum_is_empty.json b/test/openapi/frame/3.1/fail/server_variable_enum_is_empty.json new file mode 100644 index 000000000..41d715c46 --- /dev/null +++ b/test/openapi/frame/3.1/fail/server_variable_enum_is_empty.json @@ -0,0 +1,25 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "servers": [ + { + "url": "/{version}", + "variables": { + "version": { + "default": "v1", + "enum": [] + } + } + } + ], + "paths": {} + }, + "error": { + "message": "The Server Variable Object enumeration must not be empty", + "location": "/servers/0/variables/version/enum" + } +} diff --git a/test/openapi/frame/3.1/fail/server_variable_enum_is_not_an_array.json b/test/openapi/frame/3.1/fail/server_variable_enum_is_not_an_array.json new file mode 100644 index 000000000..d2fb87602 --- /dev/null +++ b/test/openapi/frame/3.1/fail/server_variable_enum_is_not_an_array.json @@ -0,0 +1,25 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "servers": [ + { + "url": "/{version}", + "variables": { + "version": { + "default": "v1", + "enum": "v1" + } + } + } + ], + "paths": {} + }, + "error": { + "message": "The Server Variable Object enumeration must be an array", + "location": "/servers/0/variables/version/enum" + } +} diff --git a/test/openapi/frame/3.1/fail/server_variable_is_not_an_object.json b/test/openapi/frame/3.1/fail/server_variable_is_not_an_object.json new file mode 100644 index 000000000..b64ceea91 --- /dev/null +++ b/test/openapi/frame/3.1/fail/server_variable_is_not_an_object.json @@ -0,0 +1,22 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "servers": [ + { + "url": "/{version}", + "variables": { + "version": "v1" + } + } + ], + "paths": {} + }, + "error": { + "message": "The Server Variable Object must be an object", + "location": "/servers/0/variables/version" + } +} diff --git a/test/openapi/frame/3.1/fail/server_variable_missing_default.json b/test/openapi/frame/3.1/fail/server_variable_missing_default.json new file mode 100644 index 000000000..59f33af24 --- /dev/null +++ b/test/openapi/frame/3.1/fail/server_variable_missing_default.json @@ -0,0 +1,22 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "servers": [ + { + "url": "/{version}", + "variables": { + "version": {} + } + } + ], + "paths": {} + }, + "error": { + "message": "The Server Variable Object must declare a default", + "location": "/servers/0/variables/version" + } +} diff --git a/test/openapi/frame/3.1/fail/server_variable_unknown_field.json b/test/openapi/frame/3.1/fail/server_variable_unknown_field.json new file mode 100644 index 000000000..ba367b445 --- /dev/null +++ b/test/openapi/frame/3.1/fail/server_variable_unknown_field.json @@ -0,0 +1,25 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "servers": [ + { + "url": "/{version}", + "variables": { + "version": { + "default": "v1", + "examples": [] + } + } + } + ], + "paths": {} + }, + "error": { + "message": "The Server Variable Object does not define this field", + "location": "/servers/0/variables/version/examples" + } +} diff --git a/test/openapi/frame/3.1/fail/server_variables_is_not_an_object.json b/test/openapi/frame/3.1/fail/server_variables_is_not_an_object.json new file mode 100644 index 000000000..4670f0c51 --- /dev/null +++ b/test/openapi/frame/3.1/fail/server_variables_is_not_an_object.json @@ -0,0 +1,20 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "servers": [ + { + "url": "/{version}", + "variables": [] + } + ], + "paths": {} + }, + "error": { + "message": "The Server Object variables must be an object", + "location": "/servers/0/variables" + } +} diff --git a/test/openapi/frame/3.1/fail/servers_is_an_object.json b/test/openapi/frame/3.1/fail/servers_is_an_object.json new file mode 100644 index 000000000..808f3aa6a --- /dev/null +++ b/test/openapi/frame/3.1/fail/servers_is_an_object.json @@ -0,0 +1,17 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "servers": { + "url": "/v1" + }, + "paths": {} + }, + "error": { + "message": "The OpenAPI Description servers must be an array", + "location": "/servers" + } +} diff --git a/test/openapi/frame/3.1/fail/servers_is_not_an_array.json b/test/openapi/frame/3.1/fail/servers_is_not_an_array.json new file mode 100644 index 000000000..63258b002 --- /dev/null +++ b/test/openapi/frame/3.1/fail/servers_is_not_an_array.json @@ -0,0 +1,15 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "servers": "https://example.com", + "paths": {} + }, + "error": { + "message": "The OpenAPI Description servers must be an array", + "location": "/servers" + } +} diff --git a/test/openapi/frame/3.1/fail/swagger_document.json b/test/openapi/frame/3.1/fail/swagger_document.json new file mode 100644 index 000000000..3a14a07cf --- /dev/null +++ b/test/openapi/frame/3.1/fail/swagger_document.json @@ -0,0 +1,14 @@ +{ + "document": { + "swagger": "2.0", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": {} + }, + "error": { + "message": "The OpenAPI Description must declare its version", + "location": "" + } +} diff --git a/test/openapi/frame/3.1/fail/tag_description_is_not_a_string.json b/test/openapi/frame/3.1/fail/tag_description_is_not_a_string.json new file mode 100644 index 000000000..29090e4cb --- /dev/null +++ b/test/openapi/frame/3.1/fail/tag_description_is_not_a_string.json @@ -0,0 +1,20 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "tags": [ + { + "name": "pet", + "description": 1 + } + ], + "paths": {} + }, + "error": { + "message": "The Tag Object description must be a string", + "location": "/tags/0/description" + } +} diff --git a/test/openapi/frame/3.1/fail/tag_external_docs_is_not_an_object.json b/test/openapi/frame/3.1/fail/tag_external_docs_is_not_an_object.json new file mode 100644 index 000000000..bb1b7f9ae --- /dev/null +++ b/test/openapi/frame/3.1/fail/tag_external_docs_is_not_an_object.json @@ -0,0 +1,20 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "tags": [ + { + "name": "pet", + "externalDocs": "https://example.com" + } + ], + "paths": {} + }, + "error": { + "message": "The External Documentation Object must be an object", + "location": "/tags/0/externalDocs" + } +} diff --git a/test/openapi/frame/3.1/fail/tag_external_docs_missing_url.json b/test/openapi/frame/3.1/fail/tag_external_docs_missing_url.json new file mode 100644 index 000000000..83da34094 --- /dev/null +++ b/test/openapi/frame/3.1/fail/tag_external_docs_missing_url.json @@ -0,0 +1,22 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "tags": [ + { + "name": "pet", + "externalDocs": { + "description": "Docs" + } + } + ], + "paths": {} + }, + "error": { + "message": "The External Documentation Object must declare a URI", + "location": "/tags/0/externalDocs" + } +} diff --git a/test/openapi/frame/3.1/fail/tag_external_docs_url_is_not_a_uri_reference.json b/test/openapi/frame/3.1/fail/tag_external_docs_url_is_not_a_uri_reference.json new file mode 100644 index 000000000..c26d050f0 --- /dev/null +++ b/test/openapi/frame/3.1/fail/tag_external_docs_url_is_not_a_uri_reference.json @@ -0,0 +1,22 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "tags": [ + { + "name": "pet", + "externalDocs": { + "url": "https://example.com/a b" + } + } + ], + "paths": {} + }, + "error": { + "message": "The External Documentation Object URI must be a URI reference", + "location": "/tags/0/externalDocs/url" + } +} diff --git a/test/openapi/frame/3.1/fail/tag_is_not_an_object.json b/test/openapi/frame/3.1/fail/tag_is_not_an_object.json new file mode 100644 index 000000000..25f7503b4 --- /dev/null +++ b/test/openapi/frame/3.1/fail/tag_is_not_an_object.json @@ -0,0 +1,17 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "tags": [ + "pet" + ], + "paths": {} + }, + "error": { + "message": "The Tag Object must be an object", + "location": "/tags/0" + } +} diff --git a/test/openapi/frame/3.1/fail/tag_missing_name.json b/test/openapi/frame/3.1/fail/tag_missing_name.json new file mode 100644 index 000000000..0a9c07934 --- /dev/null +++ b/test/openapi/frame/3.1/fail/tag_missing_name.json @@ -0,0 +1,19 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "tags": [ + { + "description": "Pets operations" + } + ], + "paths": {} + }, + "error": { + "message": "The Tag Object must declare a name", + "location": "/tags/0" + } +} diff --git a/test/openapi/frame/3.1/fail/tag_name_is_not_a_string.json b/test/openapi/frame/3.1/fail/tag_name_is_not_a_string.json new file mode 100644 index 000000000..919d6f5c5 --- /dev/null +++ b/test/openapi/frame/3.1/fail/tag_name_is_not_a_string.json @@ -0,0 +1,19 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "tags": [ + { + "name": 1 + } + ], + "paths": {} + }, + "error": { + "message": "The Tag Object name must be a string", + "location": "/tags/0/name" + } +} diff --git a/test/openapi/frame/3.1/fail/tag_names_are_duplicated.json b/test/openapi/frame/3.1/fail/tag_names_are_duplicated.json new file mode 100644 index 000000000..f86ebf57e --- /dev/null +++ b/test/openapi/frame/3.1/fail/tag_names_are_duplicated.json @@ -0,0 +1,25 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "tags": [ + { + "name": "pet" + }, + { + "name": "store" + }, + { + "name": "pet" + } + ], + "paths": {} + }, + "error": { + "message": "The OpenAPI Description tag names must be unique", + "location": "/tags/2/name" + } +} diff --git a/test/openapi/frame/3.1/fail/tag_names_are_duplicated_adjacently.json b/test/openapi/frame/3.1/fail/tag_names_are_duplicated_adjacently.json new file mode 100644 index 000000000..963a711e9 --- /dev/null +++ b/test/openapi/frame/3.1/fail/tag_names_are_duplicated_adjacently.json @@ -0,0 +1,22 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "tags": [ + { + "name": "pet" + }, + { + "name": "pet" + } + ], + "paths": {} + }, + "error": { + "message": "The OpenAPI Description tag names must be unique", + "location": "/tags/1/name" + } +} diff --git a/test/openapi/frame/3.1/fail/tag_unknown_field.json b/test/openapi/frame/3.1/fail/tag_unknown_field.json new file mode 100644 index 000000000..17ef76efe --- /dev/null +++ b/test/openapi/frame/3.1/fail/tag_unknown_field.json @@ -0,0 +1,20 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "tags": [ + { + "name": "pet", + "parent": "animal" + } + ], + "paths": {} + }, + "error": { + "message": "The Tag Object does not define this field", + "location": "/tags/0/parent" + } +} diff --git a/test/openapi/frame/3.1/fail/tags_is_an_object.json b/test/openapi/frame/3.1/fail/tags_is_an_object.json new file mode 100644 index 000000000..0f67ebc5f --- /dev/null +++ b/test/openapi/frame/3.1/fail/tags_is_an_object.json @@ -0,0 +1,17 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "tags": { + "name": "pet" + }, + "paths": {} + }, + "error": { + "message": "The OpenAPI Description tags must be an array", + "location": "/tags" + } +} diff --git a/test/openapi/frame/3.1/fail/tags_is_not_an_array.json b/test/openapi/frame/3.1/fail/tags_is_not_an_array.json new file mode 100644 index 000000000..60caa3af4 --- /dev/null +++ b/test/openapi/frame/3.1/fail/tags_is_not_an_array.json @@ -0,0 +1,15 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "tags": "pet", + "paths": {} + }, + "error": { + "message": "The OpenAPI Description tags must be an array", + "location": "/tags" + } +} diff --git a/test/openapi/frame/3.1/fail/version_earlier_minor.json b/test/openapi/frame/3.1/fail/version_earlier_minor.json new file mode 100644 index 000000000..1e7001fee --- /dev/null +++ b/test/openapi/frame/3.1/fail/version_earlier_minor.json @@ -0,0 +1,14 @@ +{ + "document": { + "openapi": "3.0.4", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": {} + }, + "error": { + "message": "Unsupported OpenAPI Specification version", + "location": "/openapi" + } +} diff --git a/test/openapi/frame/3.1/fail/version_is_a_number.json b/test/openapi/frame/3.1/fail/version_is_a_number.json new file mode 100644 index 000000000..8dc64cafa --- /dev/null +++ b/test/openapi/frame/3.1/fail/version_is_a_number.json @@ -0,0 +1,14 @@ +{ + "document": { + "openapi": 3.1, + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": {} + }, + "error": { + "message": "The OpenAPI version must be a string", + "location": "/openapi" + } +} diff --git a/test/openapi/frame/3.1/fail/version_is_null.json b/test/openapi/frame/3.1/fail/version_is_null.json new file mode 100644 index 000000000..11e153426 --- /dev/null +++ b/test/openapi/frame/3.1/fail/version_is_null.json @@ -0,0 +1,14 @@ +{ + "document": { + "openapi": null, + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": {} + }, + "error": { + "message": "The OpenAPI version must be a string", + "location": "/openapi" + } +} diff --git a/test/openapi/frame/3.1/fail/version_later_minor.json b/test/openapi/frame/3.1/fail/version_later_minor.json new file mode 100644 index 000000000..fcc800e7c --- /dev/null +++ b/test/openapi/frame/3.1/fail/version_later_minor.json @@ -0,0 +1,14 @@ +{ + "document": { + "openapi": "3.2.0", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": {} + }, + "error": { + "message": "Unsupported OpenAPI Specification version", + "location": "/openapi" + } +} diff --git a/test/openapi/frame/3.1/fail/version_without_patch_component.json b/test/openapi/frame/3.1/fail/version_without_patch_component.json new file mode 100644 index 000000000..55656cf7b --- /dev/null +++ b/test/openapi/frame/3.1/fail/version_without_patch_component.json @@ -0,0 +1,14 @@ +{ + "document": { + "openapi": "3.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": {} + }, + "error": { + "message": "Unsupported OpenAPI Specification version", + "location": "/openapi" + } +} diff --git a/test/openapi/frame/3.1/fail/webhook_is_not_an_object.json b/test/openapi/frame/3.1/fail/webhook_is_not_an_object.json new file mode 100644 index 000000000..8d10126cb --- /dev/null +++ b/test/openapi/frame/3.1/fail/webhook_is_not_an_object.json @@ -0,0 +1,16 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "webhooks": { + "newPet": 1 + } + }, + "error": { + "message": "The Path Item Object must be an object", + "location": "/webhooks/newPet" + } +} diff --git a/test/openapi/frame/3.1/fail/webhooks_is_not_an_object.json b/test/openapi/frame/3.1/fail/webhooks_is_not_an_object.json new file mode 100644 index 000000000..5628b52ae --- /dev/null +++ b/test/openapi/frame/3.1/fail/webhooks_is_not_an_object.json @@ -0,0 +1,14 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "webhooks": [] + }, + "error": { + "message": "The OpenAPI Description webhooks must be an object", + "location": "/webhooks" + } +} diff --git a/test/openapi/frame/3.1/fail/whole_file_reference_from_an_operation_is_invalid.json b/test/openapi/frame/3.1/fail/whole_file_reference_from_an_operation_is_invalid.json new file mode 100644 index 000000000..6708451e7 --- /dev/null +++ b/test/openapi/frame/3.1/fail/whole_file_reference_from_an_operation_is_invalid.json @@ -0,0 +1,38 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets": { + "get": { + "parameters": [ + { + "$ref": "https://example.com/target.json" + } + ], + "responses": { + "200": { + "description": "ok" + } + } + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/target.json": { + "name": "limit", + "in": "body", + "schema": {} + } + }, + "error": { + "message": "The Parameter Object location is not one this specification defines", + "location": "/in", + "base": "https://example.com/target.json" + } +} diff --git a/test/openapi/frame/3.1/fail/whole_file_reference_to_an_invalid_callbacks.json b/test/openapi/frame/3.1/fail/whole_file_reference_to_an_invalid_callbacks.json new file mode 100644 index 000000000..d691b717f --- /dev/null +++ b/test/openapi/frame/3.1/fail/whole_file_reference_to_an_invalid_callbacks.json @@ -0,0 +1,29 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "callbacks": { + "C": { + "$ref": "https://example.com/target.json" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/target.json": { + "{$request.body#/url}": { + "connect": {} + } + } + }, + "error": { + "message": "The Path Item Object does not define this field", + "location": "/{$request.body#~1url}/connect", + "base": "https://example.com/target.json" + } +} diff --git a/test/openapi/frame/3.1/fail/whole_file_reference_to_an_invalid_example.json b/test/openapi/frame/3.1/fail/whole_file_reference_to_an_invalid_example.json new file mode 100644 index 000000000..226204def --- /dev/null +++ b/test/openapi/frame/3.1/fail/whole_file_reference_to_an_invalid_example.json @@ -0,0 +1,28 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "examples": { + "E": { + "$ref": "https://example.com/target.json" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/target.json": { + "value": 1, + "externalValue": "/x" + } + }, + "error": { + "message": "The Example Object value and external value are mutually exclusive", + "location": "", + "base": "https://example.com/target.json" + } +} diff --git a/test/openapi/frame/3.1/fail/whole_file_reference_to_an_invalid_header.json b/test/openapi/frame/3.1/fail/whole_file_reference_to_an_invalid_header.json new file mode 100644 index 000000000..3e33dd6b4 --- /dev/null +++ b/test/openapi/frame/3.1/fail/whole_file_reference_to_an_invalid_header.json @@ -0,0 +1,25 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "headers": { + "H": { + "$ref": "https://example.com/target.json" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/target.json": {} + }, + "error": { + "message": "The Header Object must declare a schema or a content", + "location": "", + "base": "https://example.com/target.json" + } +} diff --git a/test/openapi/frame/3.1/fail/whole_file_reference_to_an_invalid_link.json b/test/openapi/frame/3.1/fail/whole_file_reference_to_an_invalid_link.json new file mode 100644 index 000000000..b8cbc5ae4 --- /dev/null +++ b/test/openapi/frame/3.1/fail/whole_file_reference_to_an_invalid_link.json @@ -0,0 +1,25 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "links": { + "L": { + "$ref": "https://example.com/target.json" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/target.json": {} + }, + "error": { + "message": "The Link Object must declare an operation reference or an operation identifier", + "location": "", + "base": "https://example.com/target.json" + } +} diff --git a/test/openapi/frame/3.1/fail/whole_file_reference_to_an_invalid_parameter.json b/test/openapi/frame/3.1/fail/whole_file_reference_to_an_invalid_parameter.json new file mode 100644 index 000000000..af55437f9 --- /dev/null +++ b/test/openapi/frame/3.1/fail/whole_file_reference_to_an_invalid_parameter.json @@ -0,0 +1,28 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "parameters": { + "P": { + "$ref": "https://example.com/target.json" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/target.json": { + "in": "query", + "schema": {} + } + }, + "error": { + "message": "The Parameter Object must declare a name", + "location": "", + "base": "https://example.com/target.json" + } +} diff --git a/test/openapi/frame/3.1/fail/whole_file_reference_to_an_invalid_path_item.json b/test/openapi/frame/3.1/fail/whole_file_reference_to_an_invalid_path_item.json new file mode 100644 index 000000000..334ac4f80 --- /dev/null +++ b/test/openapi/frame/3.1/fail/whole_file_reference_to_an_invalid_path_item.json @@ -0,0 +1,25 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets": { + "$ref": "https://example.com/target.json" + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/target.json": { + "connect": {} + } + }, + "error": { + "message": "The Path Item Object does not define this field", + "location": "/connect", + "base": "https://example.com/target.json" + } +} diff --git a/test/openapi/frame/3.1/fail/whole_file_reference_to_an_invalid_request_body.json b/test/openapi/frame/3.1/fail/whole_file_reference_to_an_invalid_request_body.json new file mode 100644 index 000000000..93eb6c2ef --- /dev/null +++ b/test/openapi/frame/3.1/fail/whole_file_reference_to_an_invalid_request_body.json @@ -0,0 +1,25 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "requestBodies": { + "B": { + "$ref": "https://example.com/target.json" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/target.json": {} + }, + "error": { + "message": "The Request Body Object must declare a content", + "location": "", + "base": "https://example.com/target.json" + } +} diff --git a/test/openapi/frame/3.1/fail/whole_file_reference_to_an_invalid_response.json b/test/openapi/frame/3.1/fail/whole_file_reference_to_an_invalid_response.json new file mode 100644 index 000000000..4406ece5b --- /dev/null +++ b/test/openapi/frame/3.1/fail/whole_file_reference_to_an_invalid_response.json @@ -0,0 +1,25 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "R": { + "$ref": "https://example.com/target.json" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/target.json": {} + }, + "error": { + "message": "The Response Object must declare a description", + "location": "", + "base": "https://example.com/target.json" + } +} diff --git a/test/openapi/frame/3.1/fail/whole_file_reference_to_an_invalid_security_scheme.json b/test/openapi/frame/3.1/fail/whole_file_reference_to_an_invalid_security_scheme.json new file mode 100644 index 000000000..169e5f132 --- /dev/null +++ b/test/openapi/frame/3.1/fail/whole_file_reference_to_an_invalid_security_scheme.json @@ -0,0 +1,25 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "securitySchemes": { + "S": { + "$ref": "https://example.com/target.json" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/target.json": {} + }, + "error": { + "message": "The Security Scheme Object must declare its type", + "location": "", + "base": "https://example.com/target.json" + } +} diff --git a/test/openapi/frame/3.1/fail/whole_file_referenced_as_two_kinds_conflicts.json b/test/openapi/frame/3.1/fail/whole_file_referenced_as_two_kinds_conflicts.json new file mode 100644 index 000000000..d68d9ee71 --- /dev/null +++ b/test/openapi/frame/3.1/fail/whole_file_referenced_as_two_kinds_conflicts.json @@ -0,0 +1,32 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "R": { + "$ref": "https://example.com/target.json" + } + }, + "headers": { + "H": { + "$ref": "https://example.com/target.json" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/target.json": { + "description": "Not found" + } + }, + "error": { + "message": "The Header Object must declare a schema or a content", + "location": "", + "base": "https://example.com/target.json" + } +} diff --git a/test/openapi/frame/3.1/fail/whole_file_referenced_as_two_kinds_conflicts_reversed.json b/test/openapi/frame/3.1/fail/whole_file_referenced_as_two_kinds_conflicts_reversed.json new file mode 100644 index 000000000..528cd7b24 --- /dev/null +++ b/test/openapi/frame/3.1/fail/whole_file_referenced_as_two_kinds_conflicts_reversed.json @@ -0,0 +1,32 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "R": { + "$ref": "https://example.com/target.json" + } + }, + "headers": { + "H": { + "$ref": "https://example.com/target.json" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/target.json": { + "schema": {} + } + }, + "error": { + "message": "The Response Object does not define this field", + "location": "/schema", + "base": "https://example.com/target.json" + } +} diff --git a/test/openapi/frame/3.1/fail/whole_file_with_a_version_is_read_as_a_document.json b/test/openapi/frame/3.1/fail/whole_file_with_a_version_is_read_as_a_document.json new file mode 100644 index 000000000..b6b190e1b --- /dev/null +++ b/test/openapi/frame/3.1/fail/whole_file_with_a_version_is_read_as_a_document.json @@ -0,0 +1,32 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "R": { + "$ref": "https://example.com/target.json" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/target.json": { + "openapi": "3.0.4", + "info": { + "title": "T", + "version": "1" + }, + "paths": {} + } + }, + "error": { + "message": "Unsupported OpenAPI Specification version", + "location": "/openapi", + "base": "https://example.com/target.json" + } +} diff --git a/test/openapi/frame/3.1/pass/base_absent.json b/test/openapi/frame/3.1/pass/base_absent.json new file mode 100644 index 000000000..777ec09d8 --- /dev/null +++ b/test/openapi/frame/3.1/pass/base_absent.json @@ -0,0 +1,43 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": {} + }, + "frame": { + "version": "3.1", + "base": "", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/info": { + "type": "info", + "pointer": "/info", + "parent": "" + }, + "#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/base_empty.json b/test/openapi/frame/3.1/pass/base_empty.json new file mode 100644 index 000000000..bd8104b13 --- /dev/null +++ b/test/openapi/frame/3.1/pass/base_empty.json @@ -0,0 +1,44 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": {} + }, + "frame": { + "version": "3.1", + "base": "", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/info": { + "type": "info", + "pointer": "/info", + "parent": "" + }, + "#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "" + } + }, + "operations": [] + }, + "defaultBase": "" +} diff --git a/test/openapi/frame/3.1/pass/base_from_the_file_it_was_read_from.json b/test/openapi/frame/3.1/pass/base_from_the_file_it_was_read_from.json new file mode 100644 index 000000000..9ac8e346f --- /dev/null +++ b/test/openapi/frame/3.1/pass/base_from_the_file_it_was_read_from.json @@ -0,0 +1,44 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": {} + }, + "frame": { + "version": "3.1", + "base": "[PATH]/base_from_the_file_it_was_read_from.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "[PATH]/base_from_the_file_it_was_read_from.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "[PATH]/base_from_the_file_it_was_read_from.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "[PATH]/base_from_the_file_it_was_read_from.json" + }, + "[PATH]/base_from_the_file_it_was_read_from.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "[PATH]/base_from_the_file_it_was_read_from.json" + } + }, + "operations": [] + }, + "defaultBase": "[PATH]/base_from_the_file_it_was_read_from.json" +} diff --git a/test/openapi/frame/3.1/pass/base_https.json b/test/openapi/frame/3.1/pass/base_https.json new file mode 100644 index 000000000..4c7508ddc --- /dev/null +++ b/test/openapi/frame/3.1/pass/base_https.json @@ -0,0 +1,44 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": {} + }, + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [] + }, + "defaultBase": "https://example.com/openapi.json" +} diff --git a/test/openapi/frame/3.1/pass/base_is_canonicalised.json b/test/openapi/frame/3.1/pass/base_is_canonicalised.json new file mode 100644 index 000000000..021e9b7b8 --- /dev/null +++ b/test/openapi/frame/3.1/pass/base_is_canonicalised.json @@ -0,0 +1,44 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": {} + }, + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [] + }, + "defaultBase": "HTTPS://Example.COM:443/a/../openapi.json" +} diff --git a/test/openapi/frame/3.1/pass/base_scheme_only.json b/test/openapi/frame/3.1/pass/base_scheme_only.json new file mode 100644 index 000000000..b111c95ef --- /dev/null +++ b/test/openapi/frame/3.1/pass/base_scheme_only.json @@ -0,0 +1,44 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": {} + }, + "frame": { + "version": "3.1", + "base": "urn:example:openapi", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "urn:example:openapi": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "urn:example:openapi#/info": { + "type": "info", + "pointer": "/info", + "parent": "urn:example:openapi" + }, + "urn:example:openapi#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "urn:example:openapi" + } + }, + "operations": [] + }, + "defaultBase": "urn:example:openapi" +} diff --git a/test/openapi/frame/3.1/pass/base_with_a_fragment.json b/test/openapi/frame/3.1/pass/base_with_a_fragment.json new file mode 100644 index 000000000..23aa3d087 --- /dev/null +++ b/test/openapi/frame/3.1/pass/base_with_a_fragment.json @@ -0,0 +1,44 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": {} + }, + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [] + }, + "defaultBase": "https://example.com/openapi.json#/paths" +} diff --git a/test/openapi/frame/3.1/pass/components_all_fields_empty.json b/test/openapi/frame/3.1/pass/components_all_fields_empty.json new file mode 100644 index 000000000..3603b690c --- /dev/null +++ b/test/openapi/frame/3.1/pass/components_all_fields_empty.json @@ -0,0 +1,54 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "schemas": {}, + "responses": {}, + "parameters": {}, + "examples": {}, + "requestBodies": {}, + "headers": {}, + "securitySchemes": {}, + "links": {}, + "callbacks": {}, + "pathItems": {} + } + }, + "frame": { + "version": "3.1", + "base": "", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/components": { + "type": "components", + "pointer": "/components", + "parent": "" + }, + "#/info": { + "type": "info", + "pointer": "/info", + "parent": "" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/components_empty.json b/test/openapi/frame/3.1/pass/components_empty.json new file mode 100644 index 000000000..da28c6fa5 --- /dev/null +++ b/test/openapi/frame/3.1/pass/components_empty.json @@ -0,0 +1,43 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": {} + }, + "frame": { + "version": "3.1", + "base": "", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/components": { + "type": "components", + "pointer": "/components", + "parent": "" + }, + "#/info": { + "type": "info", + "pointer": "/info", + "parent": "" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/components_every_map.json b/test/openapi/frame/3.1/pass/components_every_map.json new file mode 100644 index 000000000..9b10efcf9 --- /dev/null +++ b/test/openapi/frame/3.1/pass/components_every_map.json @@ -0,0 +1,282 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "schemas": { + "Pet": { + "type": "object" + } + }, + "responses": { + "NotFound": { + "description": "Not found" + }, + "Ref": { + "$ref": "#/components/responses/NotFound" + } + }, + "parameters": { + "Limit": { + "name": "limit", + "in": "query", + "schema": {} + } + }, + "examples": { + "Sample": { + "summary": "A sample", + "value": { + "id": 1 + } + }, + "External": { + "externalValue": "https://example.com/sample.json" + } + }, + "requestBodies": { + "Pet": { + "content": { + "application/json": { + "schema": {} + } + }, + "required": true + } + }, + "headers": { + "Rate": { + "description": "Rate limit", + "schema": {}, + "style": "simple", + "explode": false + }, + "Complex": { + "content": { + "text/plain": {} + } + } + }, + "securitySchemes": { + "Key": { + "type": "apiKey", + "name": "X-Key", + "in": "cookie" + } + }, + "links": { + "ById": { + "operationId": "getPet", + "parameters": { + "id": "$response.body#/id" + } + }, + "ByRef": { + "operationRef": "#/paths/~1pets/get", + "description": "By ref" + } + }, + "callbacks": { + "OnData": { + "{$request.body#/url}": { + "post": { + "responses": { + "200": { + "description": "ok" + } + } + } + } + } + }, + "pathItems": { + "Pets": { + "get": { + "responses": { + "200": { + "description": "ok" + } + } + } + } + } + } + }, + "frame": { + "version": "3.1", + "base": "", + "standalone": false, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/components": { + "type": "components", + "pointer": "/components", + "parent": "" + }, + "#/components/callbacks/OnData": { + "type": "callback", + "pointer": "/components/callbacks/OnData", + "parent": "#/components" + }, + "#/components/callbacks/OnData/%7B$request.body%23~1url%7D": { + "type": "path-item", + "pointer": "/components/callbacks/OnData/{$request.body#~1url}", + "parent": "#/components/callbacks/OnData" + }, + "#/components/callbacks/OnData/%7B$request.body%23~1url%7D/post": { + "type": "operation", + "pointer": "/components/callbacks/OnData/{$request.body#~1url}/post", + "parent": "#/components/callbacks/OnData/%7B$request.body%23~1url%7D" + }, + "#/components/callbacks/OnData/%7B$request.body%23~1url%7D/post/responses": { + "type": "responses", + "pointer": "/components/callbacks/OnData/{$request.body#~1url}/post/responses", + "parent": "#/components/callbacks/OnData/%7B$request.body%23~1url%7D/post" + }, + "#/components/callbacks/OnData/%7B$request.body%23~1url%7D/post/responses/200": { + "type": "response", + "pointer": "/components/callbacks/OnData/{$request.body#~1url}/post/responses/200", + "parent": "#/components/callbacks/OnData/%7B$request.body%23~1url%7D/post/responses" + }, + "#/components/examples/External": { + "type": "example", + "pointer": "/components/examples/External", + "parent": "#/components" + }, + "#/components/examples/Sample": { + "type": "example", + "pointer": "/components/examples/Sample", + "parent": "#/components" + }, + "#/components/headers/Complex": { + "type": "header", + "pointer": "/components/headers/Complex", + "parent": "#/components" + }, + "#/components/headers/Complex/content/text~1plain": { + "type": "media-type", + "pointer": "/components/headers/Complex/content/text~1plain", + "parent": "#/components/headers/Complex" + }, + "#/components/headers/Rate": { + "type": "header", + "pointer": "/components/headers/Rate", + "parent": "#/components" + }, + "#/components/headers/Rate/schema": { + "type": "schema", + "pointer": "/components/headers/Rate/schema", + "parent": "#/components/headers/Rate", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/components/links/ById": { + "type": "link", + "pointer": "/components/links/ById", + "parent": "#/components" + }, + "#/components/links/ByRef": { + "type": "link", + "pointer": "/components/links/ByRef", + "parent": "#/components", + "original": "#/paths/~1pets/get", + "destination": "#/paths/~1pets/get" + }, + "#/components/parameters/Limit": { + "type": "parameter", + "pointer": "/components/parameters/Limit", + "parent": "#/components" + }, + "#/components/parameters/Limit/schema": { + "type": "schema", + "pointer": "/components/parameters/Limit/schema", + "parent": "#/components/parameters/Limit", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/components/pathItems/Pets": { + "type": "path-item", + "pointer": "/components/pathItems/Pets", + "parent": "#/components" + }, + "#/components/pathItems/Pets/get": { + "type": "operation", + "pointer": "/components/pathItems/Pets/get", + "parent": "#/components/pathItems/Pets" + }, + "#/components/pathItems/Pets/get/responses": { + "type": "responses", + "pointer": "/components/pathItems/Pets/get/responses", + "parent": "#/components/pathItems/Pets/get" + }, + "#/components/pathItems/Pets/get/responses/200": { + "type": "response", + "pointer": "/components/pathItems/Pets/get/responses/200", + "parent": "#/components/pathItems/Pets/get/responses" + }, + "#/components/requestBodies/Pet": { + "type": "request-body", + "pointer": "/components/requestBodies/Pet", + "parent": "#/components" + }, + "#/components/requestBodies/Pet/content/application~1json": { + "type": "media-type", + "pointer": "/components/requestBodies/Pet/content/application~1json", + "parent": "#/components/requestBodies/Pet" + }, + "#/components/requestBodies/Pet/content/application~1json/schema": { + "type": "schema", + "pointer": "/components/requestBodies/Pet/content/application~1json/schema", + "parent": "#/components/requestBodies/Pet/content/application~1json", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/components/responses/NotFound": { + "type": "response", + "pointer": "/components/responses/NotFound", + "parent": "#/components" + }, + "#/components/responses/Ref": { + "type": "reference", + "pointer": "/components/responses/Ref", + "parent": "#/components", + "original": "#/components/responses/NotFound", + "destination": "#/components/responses/NotFound" + }, + "#/components/schemas/Pet": { + "type": "schema", + "pointer": "/components/schemas/Pet", + "parent": "#/components", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/components/securitySchemes/Key": { + "type": "security-scheme", + "pointer": "/components/securitySchemes/Key", + "parent": "#/components" + }, + "#/info": { + "type": "info", + "pointer": "/info", + "parent": "" + } + }, + "operations": [] + }, + "dangling": [ + "#/paths/~1pets/get" + ] +} diff --git a/test/openapi/frame/3.1/pass/components_key_examples.json b/test/openapi/frame/3.1/pass/components_key_examples.json new file mode 100644 index 000000000..166d2b615 --- /dev/null +++ b/test/openapi/frame/3.1/pass/components_key_examples.json @@ -0,0 +1,81 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "schemas": { + "User": {}, + "User_1": {}, + "User_Name": {}, + "user-name": {}, + "my.org.User": {} + } + } + }, + "frame": { + "version": "3.1", + "base": "", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/components": { + "type": "components", + "pointer": "/components", + "parent": "" + }, + "#/components/schemas/User": { + "type": "schema", + "pointer": "/components/schemas/User", + "parent": "#/components", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/components/schemas/User_1": { + "type": "schema", + "pointer": "/components/schemas/User_1", + "parent": "#/components", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/components/schemas/User_Name": { + "type": "schema", + "pointer": "/components/schemas/User_Name", + "parent": "#/components", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/components/schemas/my.org.User": { + "type": "schema", + "pointer": "/components/schemas/my.org.User", + "parent": "#/components", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/components/schemas/user-name": { + "type": "schema", + "pointer": "/components/schemas/user-name", + "parent": "#/components", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/info": { + "type": "info", + "pointer": "/info", + "parent": "" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/components_schemas_are_not_read.json b/test/openapi/frame/3.1/pass/components_schemas_are_not_read.json new file mode 100644 index 000000000..1496677e4 --- /dev/null +++ b/test/openapi/frame/3.1/pass/components_schemas_are_not_read.json @@ -0,0 +1,87 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "schemas": { + "Anything": true, + "Nothing": false, + "Nonsense": { + "type": "not-a-type", + "$ref": 42 + }, + "Deeply": { + "properties": { + "nested": { + "type": 42 + } + }, + "items": [ + true, + false + ] + } + } + } + }, + "frame": { + "version": "3.1", + "base": "", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/components": { + "type": "components", + "pointer": "/components", + "parent": "" + }, + "#/components/schemas/Anything": { + "type": "schema", + "pointer": "/components/schemas/Anything", + "parent": "#/components", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/components/schemas/Deeply": { + "type": "schema", + "pointer": "/components/schemas/Deeply", + "parent": "#/components", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/components/schemas/Nonsense": { + "type": "schema", + "pointer": "/components/schemas/Nonsense", + "parent": "#/components", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/components/schemas/Nothing": { + "type": "schema", + "pointer": "/components/schemas/Nothing", + "parent": "#/components", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/info": { + "type": "info", + "pointer": "/info", + "parent": "" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/components_with_extensions.json b/test/openapi/frame/3.1/pass/components_with_extensions.json new file mode 100644 index 000000000..f19422a6b --- /dev/null +++ b/test/openapi/frame/3.1/pass/components_with_extensions.json @@ -0,0 +1,48 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "schemas": {}, + "x-internal": { + "anything": true + } + } + }, + "frame": { + "version": "3.1", + "base": "", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/components": { + "type": "components", + "pointer": "/components", + "parent": "" + }, + "#/info": { + "type": "info", + "pointer": "/info", + "parent": "" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/dialect_of_a_document_that_is_not_a_description.json b/test/openapi/frame/3.1/pass/dialect_of_a_document_that_is_not_a_description.json new file mode 100644 index 000000000..06b1d65b2 --- /dev/null +++ b/test/openapi/frame/3.1/pass/dialect_of_a_document_that_is_not_a_description.json @@ -0,0 +1,90 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "E", + "version": "1" + }, + "jsonSchemaDialect": "https://example.com/entry-dialect", + "components": { + "responses": { + "R": { + "$ref": "https://example.com/common.json" + } + } + }, + "paths": {} + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/common.json": { + "description": "ok", + "content": { + "application/json": { + "schema": {} + } + } + } + }, + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "E", + "version": "1", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/common.json": { + "type": "response", + "pointer": "", + "parent": null + }, + "https://example.com/common.json#/content/application~1json": { + "type": "media-type", + "pointer": "/content/application~1json", + "parent": "https://example.com/common.json" + }, + "https://example.com/common.json#/content/application~1json/schema": { + "type": "schema", + "pointer": "/content/application~1json/schema", + "parent": "https://example.com/common.json#/content/application~1json", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://example.com/entry-dialect" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/responses/R": { + "type": "reference", + "pointer": "/components/responses/R", + "parent": "https://example.com/openapi.json#/components", + "original": "https://example.com/common.json", + "destination": "https://example.com/common.json" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/dialect_of_a_second_fragment_in_one_document.json b/test/openapi/frame/3.1/pass/dialect_of_a_second_fragment_in_one_document.json new file mode 100644 index 000000000..d5444fd77 --- /dev/null +++ b/test/openapi/frame/3.1/pass/dialect_of_a_second_fragment_in_one_document.json @@ -0,0 +1,136 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "E", + "version": "1" + }, + "jsonSchemaDialect": "https://example.com/entry-dialect", + "components": { + "responses": { + "First": { + "$ref": "https://example.com/common.json#/components/responses/Plain" + }, + "Second": { + "$ref": "https://example.com/common.json#/components/responses/WithSchema" + } + } + }, + "paths": {} + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/common.json": { + "openapi": "3.1.1", + "info": { + "title": "C", + "version": "1" + }, + "jsonSchemaDialect": "https://example.com/target-dialect", + "components": { + "responses": { + "Plain": { + "description": "ok" + }, + "WithSchema": { + "description": "ok", + "content": { + "application/json": { + "schema": {} + } + } + } + } + } + } + }, + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "E", + "version": "1", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/common.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://example.com/target-dialect" + }, + "https://example.com/common.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/common.json" + }, + "https://example.com/common.json#/components/responses/Plain": { + "type": "response", + "pointer": "/components/responses/Plain", + "parent": "https://example.com/common.json#/components" + }, + "https://example.com/common.json#/components/responses/WithSchema": { + "type": "response", + "pointer": "/components/responses/WithSchema", + "parent": "https://example.com/common.json#/components" + }, + "https://example.com/common.json#/components/responses/WithSchema/content/application~1json": { + "type": "media-type", + "pointer": "/components/responses/WithSchema/content/application~1json", + "parent": "https://example.com/common.json#/components/responses/WithSchema" + }, + "https://example.com/common.json#/components/responses/WithSchema/content/application~1json/schema": { + "type": "schema", + "pointer": "/components/responses/WithSchema/content/application~1json/schema", + "parent": "https://example.com/common.json#/components/responses/WithSchema/content/application~1json", + "dialect": "https://example.com/target-dialect" + }, + "https://example.com/common.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/common.json" + }, + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://example.com/entry-dialect" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/responses/First": { + "type": "reference", + "pointer": "/components/responses/First", + "parent": "https://example.com/openapi.json#/components", + "original": "https://example.com/common.json#/components/responses/Plain", + "destination": "https://example.com/common.json#/components/responses/Plain" + }, + "https://example.com/openapi.json#/components/responses/Second": { + "type": "reference", + "pointer": "/components/responses/Second", + "parent": "https://example.com/openapi.json#/components", + "original": "https://example.com/common.json#/components/responses/WithSchema", + "destination": "https://example.com/common.json#/components/responses/WithSchema" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/external_docs_example.json b/test/openapi/frame/3.1/pass/external_docs_example.json new file mode 100644 index 000000000..535a04933 --- /dev/null +++ b/test/openapi/frame/3.1/pass/external_docs_example.json @@ -0,0 +1,52 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "externalDocs": { + "description": "Find more info here", + "url": "https://example.com" + }, + "paths": {} + }, + "frame": { + "version": "3.1", + "base": "", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/externalDocs": { + "type": "external-documentation", + "pointer": "/externalDocs", + "parent": "" + }, + "#/info": { + "type": "info", + "pointer": "/info", + "parent": "" + }, + "#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/external_docs_relative_url.json b/test/openapi/frame/3.1/pass/external_docs_relative_url.json new file mode 100644 index 000000000..9946797c7 --- /dev/null +++ b/test/openapi/frame/3.1/pass/external_docs_relative_url.json @@ -0,0 +1,51 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "externalDocs": { + "url": "docs/index.html" + }, + "paths": {} + }, + "frame": { + "version": "3.1", + "base": "", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/externalDocs": { + "type": "external-documentation", + "pointer": "/externalDocs", + "parent": "" + }, + "#/info": { + "type": "info", + "pointer": "/info", + "parent": "" + }, + "#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/external_docs_url_only.json b/test/openapi/frame/3.1/pass/external_docs_url_only.json new file mode 100644 index 000000000..c7acc4c48 --- /dev/null +++ b/test/openapi/frame/3.1/pass/external_docs_url_only.json @@ -0,0 +1,51 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "externalDocs": { + "url": "https://example.com" + }, + "paths": {} + }, + "frame": { + "version": "3.1", + "base": "", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/externalDocs": { + "type": "external-documentation", + "pointer": "/externalDocs", + "parent": "" + }, + "#/info": { + "type": "info", + "pointer": "/info", + "parent": "" + }, + "#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/external_docs_with_extensions.json b/test/openapi/frame/3.1/pass/external_docs_with_extensions.json new file mode 100644 index 000000000..6d85b1a30 --- /dev/null +++ b/test/openapi/frame/3.1/pass/external_docs_with_extensions.json @@ -0,0 +1,52 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "externalDocs": { + "url": "https://example.com", + "x-internal": true + }, + "paths": {} + }, + "frame": { + "version": "3.1", + "base": "", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/externalDocs": { + "type": "external-documentation", + "pointer": "/externalDocs", + "parent": "" + }, + "#/info": { + "type": "info", + "pointer": "/info", + "parent": "" + }, + "#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/external_document_with_its_own_dialect.json b/test/openapi/frame/3.1/pass/external_document_with_its_own_dialect.json new file mode 100644 index 000000000..e3d33b128 --- /dev/null +++ b/test/openapi/frame/3.1/pass/external_document_with_its_own_dialect.json @@ -0,0 +1,114 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "jsonSchemaDialect": "https://example.com/entry-dialect", + "components": { + "schemas": { + "Local": {} + }, + "responses": { + "R": { + "$ref": "https://example.com/common.json#/components/responses/NotFound" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/common.json": { + "openapi": "3.1.1", + "info": { + "title": "Common", + "version": "1.0.0" + }, + "jsonSchemaDialect": "https://example.com/common-dialect", + "components": { + "schemas": { + "Remote": {} + }, + "responses": { + "NotFound": { + "description": "ok" + } + } + } + } + }, + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/common.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://example.com/common-dialect" + }, + "https://example.com/common.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/common.json" + }, + "https://example.com/common.json#/components/responses/NotFound": { + "type": "response", + "pointer": "/components/responses/NotFound", + "parent": "https://example.com/common.json#/components" + }, + "https://example.com/common.json#/components/schemas/Remote": { + "type": "schema", + "pointer": "/components/schemas/Remote", + "parent": "https://example.com/common.json#/components", + "dialect": "https://example.com/common-dialect" + }, + "https://example.com/common.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/common.json" + }, + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://example.com/entry-dialect" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/responses/R": { + "type": "reference", + "pointer": "/components/responses/R", + "parent": "https://example.com/openapi.json#/components", + "original": "https://example.com/common.json#/components/responses/NotFound", + "destination": "https://example.com/common.json#/components/responses/NotFound" + }, + "https://example.com/openapi.json#/components/schemas/Local": { + "type": "schema", + "pointer": "/components/schemas/Local", + "parent": "https://example.com/openapi.json#/components", + "dialect": "https://example.com/entry-dialect" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/external_reference_cycle.json b/test/openapi/frame/3.1/pass/external_reference_cycle.json new file mode 100644 index 000000000..6868228aa --- /dev/null +++ b/test/openapi/frame/3.1/pass/external_reference_cycle.json @@ -0,0 +1,104 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "R": { + "$ref": "https://example.com/common.json#/components/responses/NotFound" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/common.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/common.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/common.json" + }, + "https://example.com/common.json#/components/responses/Back": { + "type": "reference", + "pointer": "/components/responses/Back", + "parent": "https://example.com/common.json#/components", + "original": "https://example.com/openapi.json#/components/responses/R", + "destination": "https://example.com/openapi.json#/components/responses/R" + }, + "https://example.com/common.json#/components/responses/NotFound": { + "type": "response", + "pointer": "/components/responses/NotFound", + "parent": "https://example.com/common.json#/components" + }, + "https://example.com/common.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/common.json" + }, + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/responses/R": { + "type": "reference", + "pointer": "/components/responses/R", + "parent": "https://example.com/openapi.json#/components", + "original": "https://example.com/common.json#/components/responses/NotFound", + "destination": "https://example.com/common.json#/components/responses/NotFound" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [] + }, + "resolver": { + "https://example.com/common.json": { + "openapi": "3.1.1", + "info": { + "title": "Common", + "version": "1.0.0" + }, + "components": { + "responses": { + "NotFound": { + "description": "Not found" + }, + "Back": { + "$ref": "https://example.com/openapi.json#/components/responses/R" + } + } + } + } + } +} diff --git a/test/openapi/frame/3.1/pass/external_reference_cycle_between_referenced_documents.json b/test/openapi/frame/3.1/pass/external_reference_cycle_between_referenced_documents.json new file mode 100644 index 000000000..a13ce94eb --- /dev/null +++ b/test/openapi/frame/3.1/pass/external_reference_cycle_between_referenced_documents.json @@ -0,0 +1,149 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "R": { + "$ref": "https://example.com/first.json#/components/responses/Ok" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/first.json": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "Ok": { + "description": "ok" + }, + "Onward": { + "$ref": "https://example.com/second.json#/components/responses/Ok" + } + } + } + }, + "https://example.com/second.json": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "Ok": { + "description": "ok" + }, + "Back": { + "$ref": "https://example.com/first.json#/components/responses/Ok" + } + } + } + } + }, + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/first.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/first.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/first.json" + }, + "https://example.com/first.json#/components/responses/Ok": { + "type": "response", + "pointer": "/components/responses/Ok", + "parent": "https://example.com/first.json#/components" + }, + "https://example.com/first.json#/components/responses/Onward": { + "type": "reference", + "pointer": "/components/responses/Onward", + "parent": "https://example.com/first.json#/components", + "original": "https://example.com/second.json#/components/responses/Ok", + "destination": "https://example.com/second.json#/components/responses/Ok" + }, + "https://example.com/first.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/first.json" + }, + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/responses/R": { + "type": "reference", + "pointer": "/components/responses/R", + "parent": "https://example.com/openapi.json#/components", + "original": "https://example.com/first.json#/components/responses/Ok", + "destination": "https://example.com/first.json#/components/responses/Ok" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/second.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/second.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/second.json" + }, + "https://example.com/second.json#/components/responses/Back": { + "type": "reference", + "pointer": "/components/responses/Back", + "parent": "https://example.com/second.json#/components", + "original": "https://example.com/first.json#/components/responses/Ok", + "destination": "https://example.com/first.json#/components/responses/Ok" + }, + "https://example.com/second.json#/components/responses/Ok": { + "type": "response", + "pointer": "/components/responses/Ok", + "parent": "https://example.com/second.json#/components" + }, + "https://example.com/second.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/second.json" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/external_reference_from_a_path_item.json b/test/openapi/frame/3.1/pass/external_reference_from_a_path_item.json new file mode 100644 index 000000000..6e233d981 --- /dev/null +++ b/test/openapi/frame/3.1/pass/external_reference_from_a_path_item.json @@ -0,0 +1,90 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets": { + "$ref": "https://example.com/common.json#/components/pathItems/Pets" + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/common.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/common.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/common.json" + }, + "https://example.com/common.json#/components/pathItems/Pets": { + "type": "path-item", + "pointer": "/components/pathItems/Pets", + "parent": "https://example.com/common.json#/components" + }, + "https://example.com/common.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/common.json" + }, + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1pets": { + "type": "path-item", + "pointer": "/paths/~1pets", + "parent": "https://example.com/openapi.json#/paths", + "original": "https://example.com/common.json#/components/pathItems/Pets", + "destination": "https://example.com/common.json#/components/pathItems/Pets" + } + }, + "operations": [] + }, + "resolver": { + "https://example.com/common.json": { + "openapi": "3.1.1", + "info": { + "title": "Common", + "version": "1.0.0" + }, + "components": { + "pathItems": { + "Pets": {} + } + } + } + } +} diff --git a/test/openapi/frame/3.1/pass/external_reference_inside_a_schema_is_not_followed.json b/test/openapi/frame/3.1/pass/external_reference_inside_a_schema_is_not_followed.json new file mode 100644 index 000000000..0fbae2482 --- /dev/null +++ b/test/openapi/frame/3.1/pass/external_reference_inside_a_schema_is_not_followed.json @@ -0,0 +1,56 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "schemas": { + "Pet": { + "$ref": "https://example.com/missing.json" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/schemas/Pet": { + "type": "schema", + "pointer": "/components/schemas/Pet", + "parent": "https://example.com/openapi.json#/components", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/external_reference_relative.json b/test/openapi/frame/3.1/pass/external_reference_relative.json new file mode 100644 index 000000000..faab99b28 --- /dev/null +++ b/test/openapi/frame/3.1/pass/external_reference_relative.json @@ -0,0 +1,94 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "R": { + "$ref": "common.json#/components/responses/NotFound" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/common.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/common.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/common.json" + }, + "https://example.com/common.json#/components/responses/NotFound": { + "type": "response", + "pointer": "/components/responses/NotFound", + "parent": "https://example.com/common.json#/components" + }, + "https://example.com/common.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/common.json" + }, + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/responses/R": { + "type": "reference", + "pointer": "/components/responses/R", + "parent": "https://example.com/openapi.json#/components", + "original": "common.json#/components/responses/NotFound", + "destination": "https://example.com/common.json#/components/responses/NotFound" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [] + }, + "resolver": { + "https://example.com/common.json": { + "openapi": "3.1.1", + "info": { + "title": "Common", + "version": "1.0.0" + }, + "components": { + "responses": { + "NotFound": { + "description": "Not found" + } + } + } + } + } +} diff --git a/test/openapi/frame/3.1/pass/external_reference_repeated.json b/test/openapi/frame/3.1/pass/external_reference_repeated.json new file mode 100644 index 000000000..8ce3b86b1 --- /dev/null +++ b/test/openapi/frame/3.1/pass/external_reference_repeated.json @@ -0,0 +1,112 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "A": { + "$ref": "https://example.com/common.json#/components/responses/NotFound" + }, + "B": { + "$ref": "https://example.com/common.json#/components/responses/Gone" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/common.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/common.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/common.json" + }, + "https://example.com/common.json#/components/responses/Gone": { + "type": "response", + "pointer": "/components/responses/Gone", + "parent": "https://example.com/common.json#/components" + }, + "https://example.com/common.json#/components/responses/NotFound": { + "type": "response", + "pointer": "/components/responses/NotFound", + "parent": "https://example.com/common.json#/components" + }, + "https://example.com/common.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/common.json" + }, + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/responses/A": { + "type": "reference", + "pointer": "/components/responses/A", + "parent": "https://example.com/openapi.json#/components", + "original": "https://example.com/common.json#/components/responses/NotFound", + "destination": "https://example.com/common.json#/components/responses/NotFound" + }, + "https://example.com/openapi.json#/components/responses/B": { + "type": "reference", + "pointer": "/components/responses/B", + "parent": "https://example.com/openapi.json#/components", + "original": "https://example.com/common.json#/components/responses/Gone", + "destination": "https://example.com/common.json#/components/responses/Gone" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [] + }, + "resolver": { + "https://example.com/common.json": { + "openapi": "3.1.1", + "info": { + "title": "Common", + "version": "1.0.0" + }, + "components": { + "responses": { + "NotFound": { + "description": "Not found" + }, + "Gone": { + "description": "Gone" + } + } + } + } + } +} diff --git a/test/openapi/frame/3.1/pass/external_reference_resolved.json b/test/openapi/frame/3.1/pass/external_reference_resolved.json new file mode 100644 index 000000000..df49073a4 --- /dev/null +++ b/test/openapi/frame/3.1/pass/external_reference_resolved.json @@ -0,0 +1,94 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "R": { + "$ref": "https://example.com/common.json#/components/responses/NotFound" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/common.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/common.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/common.json" + }, + "https://example.com/common.json#/components/responses/NotFound": { + "type": "response", + "pointer": "/components/responses/NotFound", + "parent": "https://example.com/common.json#/components" + }, + "https://example.com/common.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/common.json" + }, + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/responses/R": { + "type": "reference", + "pointer": "/components/responses/R", + "parent": "https://example.com/openapi.json#/components", + "original": "https://example.com/common.json#/components/responses/NotFound", + "destination": "https://example.com/common.json#/components/responses/NotFound" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [] + }, + "resolver": { + "https://example.com/common.json": { + "openapi": "3.1.1", + "info": { + "title": "Common", + "version": "1.0.0" + }, + "components": { + "responses": { + "NotFound": { + "description": "Not found" + } + } + } + } + } +} diff --git a/test/openapi/frame/3.1/pass/external_reference_to_a_document_that_is_not_openapi.json b/test/openapi/frame/3.1/pass/external_reference_to_a_document_that_is_not_openapi.json new file mode 100644 index 000000000..fa557d699 --- /dev/null +++ b/test/openapi/frame/3.1/pass/external_reference_to_a_document_that_is_not_openapi.json @@ -0,0 +1,71 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "R": { + "$ref": "https://example.com/common.json#/components/responses/NotFound" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": false, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/responses/R": { + "type": "reference", + "pointer": "/components/responses/R", + "parent": "https://example.com/openapi.json#/components", + "original": "https://example.com/common.json#/components/responses/NotFound", + "destination": "https://example.com/common.json#/components/responses/NotFound" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [] + }, + "dangling": [ + "https://example.com/common.json#/components/responses/NotFound" + ], + "resolver": { + "https://example.com/common.json": { + "components": { + "responses": { + "NotFound": { + "nonsense": true + } + } + } + } + } +} diff --git a/test/openapi/frame/3.1/pass/external_reference_two_documents_deep.json b/test/openapi/frame/3.1/pass/external_reference_two_documents_deep.json new file mode 100644 index 000000000..403de2c4f --- /dev/null +++ b/test/openapi/frame/3.1/pass/external_reference_two_documents_deep.json @@ -0,0 +1,131 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "R": { + "$ref": "https://example.com/first.json#/components/responses/Onward" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/first.json": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "Onward": { + "$ref": "https://example.com/second.json#/components/responses/Ok" + } + } + } + }, + "https://example.com/second.json": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "Ok": { + "description": "ok" + } + } + } + } + }, + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/first.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/first.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/first.json" + }, + "https://example.com/first.json#/components/responses/Onward": { + "type": "reference", + "pointer": "/components/responses/Onward", + "parent": "https://example.com/first.json#/components", + "original": "https://example.com/second.json#/components/responses/Ok", + "destination": "https://example.com/second.json#/components/responses/Ok" + }, + "https://example.com/first.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/first.json" + }, + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/responses/R": { + "type": "reference", + "pointer": "/components/responses/R", + "parent": "https://example.com/openapi.json#/components", + "original": "https://example.com/first.json#/components/responses/Onward", + "destination": "https://example.com/first.json#/components/responses/Onward" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/second.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/second.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/second.json" + }, + "https://example.com/second.json#/components/responses/Ok": { + "type": "response", + "pointer": "/components/responses/Ok", + "parent": "https://example.com/second.json#/components" + }, + "https://example.com/second.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/second.json" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/external_reference_unresolvable.json b/test/openapi/frame/3.1/pass/external_reference_unresolvable.json new file mode 100644 index 000000000..eb08d454a --- /dev/null +++ b/test/openapi/frame/3.1/pass/external_reference_unresolvable.json @@ -0,0 +1,70 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "R": { + "$ref": "https://example.com/common.json#/components/responses/NotFound" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/other.json": { + "openapi": "3.1.1", + "info": { + "title": "Common", + "version": "1.0.0" + }, + "components": {} + } + }, + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": false, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/responses/R": { + "type": "reference", + "pointer": "/components/responses/R", + "parent": "https://example.com/openapi.json#/components", + "original": "https://example.com/common.json#/components/responses/NotFound", + "destination": "https://example.com/common.json#/components/responses/NotFound" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [] + }, + "dangling": [ + "https://example.com/common.json#/components/responses/NotFound" + ] +} diff --git a/test/openapi/frame/3.1/pass/external_reference_unresolvable_from_a_path_item.json b/test/openapi/frame/3.1/pass/external_reference_unresolvable_from_a_path_item.json new file mode 100644 index 000000000..5db7f5f6b --- /dev/null +++ b/test/openapi/frame/3.1/pass/external_reference_unresolvable_from_a_path_item.json @@ -0,0 +1,68 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets": { + "$ref": "https://example.com/common.json#/components/pathItems/Pets" + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/other.json": { + "openapi": "3.1.1", + "info": { + "title": "Common", + "version": "1.0.0" + }, + "components": {} + } + }, + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": false, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1pets": { + "type": "path-item", + "pointer": "/paths/~1pets", + "parent": "https://example.com/openapi.json#/paths", + "original": "https://example.com/common.json#/components/pathItems/Pets", + "destination": "https://example.com/common.json#/components/pathItems/Pets" + } + }, + "operations": [] + }, + "dangling": [ + "https://example.com/common.json#/components/pathItems/Pets" + ] +} diff --git a/test/openapi/frame/3.1/pass/external_reference_without_a_resolver.json b/test/openapi/frame/3.1/pass/external_reference_without_a_resolver.json new file mode 100644 index 000000000..6e2b55b5e --- /dev/null +++ b/test/openapi/frame/3.1/pass/external_reference_without_a_resolver.json @@ -0,0 +1,60 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "R": { + "$ref": "https://example.com/common.json#/components/responses/NotFound" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": false, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/responses/R": { + "type": "reference", + "pointer": "/components/responses/R", + "parent": "https://example.com/openapi.json#/components", + "original": "https://example.com/common.json#/components/responses/NotFound", + "destination": "https://example.com/common.json#/components/responses/NotFound" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [] + }, + "dangling": [ + "https://example.com/common.json#/components/responses/NotFound" + ] +} diff --git a/test/openapi/frame/3.1/pass/fragment_reference_to_a_callbacks_is_not_read.json b/test/openapi/frame/3.1/pass/fragment_reference_to_a_callbacks_is_not_read.json new file mode 100644 index 000000000..6785b171f --- /dev/null +++ b/test/openapi/frame/3.1/pass/fragment_reference_to_a_callbacks_is_not_read.json @@ -0,0 +1,67 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "callbacks": { + "C": { + "$ref": "https://example.com/target.json#/anything" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/target.json": { + "{$request.body#/url}": { + "connect": {} + } + } + }, + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": false, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/callbacks/C": { + "type": "reference", + "pointer": "/components/callbacks/C", + "parent": "https://example.com/openapi.json#/components", + "original": "https://example.com/target.json#/anything", + "destination": "https://example.com/target.json#/anything" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [] + }, + "dangling": [ + "https://example.com/target.json#/anything" + ] +} diff --git a/test/openapi/frame/3.1/pass/fragment_reference_to_a_example_is_not_read.json b/test/openapi/frame/3.1/pass/fragment_reference_to_a_example_is_not_read.json new file mode 100644 index 000000000..f4c43697d --- /dev/null +++ b/test/openapi/frame/3.1/pass/fragment_reference_to_a_example_is_not_read.json @@ -0,0 +1,66 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "examples": { + "E": { + "$ref": "https://example.com/target.json#/anything" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/target.json": { + "value": 1, + "externalValue": "/x" + } + }, + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": false, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/examples/E": { + "type": "reference", + "pointer": "/components/examples/E", + "parent": "https://example.com/openapi.json#/components", + "original": "https://example.com/target.json#/anything", + "destination": "https://example.com/target.json#/anything" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [] + }, + "dangling": [ + "https://example.com/target.json#/anything" + ] +} diff --git a/test/openapi/frame/3.1/pass/fragment_reference_to_a_header_is_not_read.json b/test/openapi/frame/3.1/pass/fragment_reference_to_a_header_is_not_read.json new file mode 100644 index 000000000..74031675a --- /dev/null +++ b/test/openapi/frame/3.1/pass/fragment_reference_to_a_header_is_not_read.json @@ -0,0 +1,63 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "headers": { + "H": { + "$ref": "https://example.com/target.json#/anything" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/target.json": {} + }, + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": false, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/headers/H": { + "type": "reference", + "pointer": "/components/headers/H", + "parent": "https://example.com/openapi.json#/components", + "original": "https://example.com/target.json#/anything", + "destination": "https://example.com/target.json#/anything" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [] + }, + "dangling": [ + "https://example.com/target.json#/anything" + ] +} diff --git a/test/openapi/frame/3.1/pass/fragment_reference_to_a_link_is_not_read.json b/test/openapi/frame/3.1/pass/fragment_reference_to_a_link_is_not_read.json new file mode 100644 index 000000000..bf83af482 --- /dev/null +++ b/test/openapi/frame/3.1/pass/fragment_reference_to_a_link_is_not_read.json @@ -0,0 +1,63 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "links": { + "L": { + "$ref": "https://example.com/target.json#/anything" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/target.json": {} + }, + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": false, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/links/L": { + "type": "reference", + "pointer": "/components/links/L", + "parent": "https://example.com/openapi.json#/components", + "original": "https://example.com/target.json#/anything", + "destination": "https://example.com/target.json#/anything" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [] + }, + "dangling": [ + "https://example.com/target.json#/anything" + ] +} diff --git a/test/openapi/frame/3.1/pass/fragment_reference_to_a_parameter_is_not_read.json b/test/openapi/frame/3.1/pass/fragment_reference_to_a_parameter_is_not_read.json new file mode 100644 index 000000000..080423f97 --- /dev/null +++ b/test/openapi/frame/3.1/pass/fragment_reference_to_a_parameter_is_not_read.json @@ -0,0 +1,66 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "parameters": { + "P": { + "$ref": "https://example.com/target.json#/anything" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/target.json": { + "in": "query", + "schema": {} + } + }, + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": false, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/parameters/P": { + "type": "reference", + "pointer": "/components/parameters/P", + "parent": "https://example.com/openapi.json#/components", + "original": "https://example.com/target.json#/anything", + "destination": "https://example.com/target.json#/anything" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [] + }, + "dangling": [ + "https://example.com/target.json#/anything" + ] +} diff --git a/test/openapi/frame/3.1/pass/fragment_reference_to_a_path_item_is_not_read.json b/test/openapi/frame/3.1/pass/fragment_reference_to_a_path_item_is_not_read.json new file mode 100644 index 000000000..7bb5dee01 --- /dev/null +++ b/test/openapi/frame/3.1/pass/fragment_reference_to_a_path_item_is_not_read.json @@ -0,0 +1,63 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets": { + "$ref": "https://example.com/target.json#/anything" + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/target.json": { + "connect": {} + } + }, + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": false, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1pets": { + "type": "path-item", + "pointer": "/paths/~1pets", + "parent": "https://example.com/openapi.json#/paths", + "original": "https://example.com/target.json#/anything", + "destination": "https://example.com/target.json#/anything" + } + }, + "operations": [] + }, + "dangling": [ + "https://example.com/target.json#/anything" + ] +} diff --git a/test/openapi/frame/3.1/pass/fragment_reference_to_a_request_body_is_not_read.json b/test/openapi/frame/3.1/pass/fragment_reference_to_a_request_body_is_not_read.json new file mode 100644 index 000000000..5b2e42b84 --- /dev/null +++ b/test/openapi/frame/3.1/pass/fragment_reference_to_a_request_body_is_not_read.json @@ -0,0 +1,63 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "requestBodies": { + "B": { + "$ref": "https://example.com/target.json#/anything" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/target.json": {} + }, + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": false, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/requestBodies/B": { + "type": "reference", + "pointer": "/components/requestBodies/B", + "parent": "https://example.com/openapi.json#/components", + "original": "https://example.com/target.json#/anything", + "destination": "https://example.com/target.json#/anything" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [] + }, + "dangling": [ + "https://example.com/target.json#/anything" + ] +} diff --git a/test/openapi/frame/3.1/pass/fragment_reference_to_a_response_is_not_read.json b/test/openapi/frame/3.1/pass/fragment_reference_to_a_response_is_not_read.json new file mode 100644 index 000000000..f9414b15e --- /dev/null +++ b/test/openapi/frame/3.1/pass/fragment_reference_to_a_response_is_not_read.json @@ -0,0 +1,63 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "R": { + "$ref": "https://example.com/target.json#/anything" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/target.json": {} + }, + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": false, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/responses/R": { + "type": "reference", + "pointer": "/components/responses/R", + "parent": "https://example.com/openapi.json#/components", + "original": "https://example.com/target.json#/anything", + "destination": "https://example.com/target.json#/anything" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [] + }, + "dangling": [ + "https://example.com/target.json#/anything" + ] +} diff --git a/test/openapi/frame/3.1/pass/fragment_reference_to_a_security_scheme_is_not_read.json b/test/openapi/frame/3.1/pass/fragment_reference_to_a_security_scheme_is_not_read.json new file mode 100644 index 000000000..f0aba8202 --- /dev/null +++ b/test/openapi/frame/3.1/pass/fragment_reference_to_a_security_scheme_is_not_read.json @@ -0,0 +1,63 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "securitySchemes": { + "S": { + "$ref": "https://example.com/target.json#/anything" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/target.json": {} + }, + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": false, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/securitySchemes/S": { + "type": "reference", + "pointer": "/components/securitySchemes/S", + "parent": "https://example.com/openapi.json#/components", + "original": "https://example.com/target.json#/anything", + "destination": "https://example.com/target.json#/anything" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [] + }, + "dangling": [ + "https://example.com/target.json#/anything" + ] +} diff --git a/test/openapi/frame/3.1/pass/info_from_the_entry_document_when_it_is_empty.json b/test/openapi/frame/3.1/pass/info_from_the_entry_document_when_it_is_empty.json new file mode 100644 index 000000000..571bb3482 --- /dev/null +++ b/test/openapi/frame/3.1/pass/info_from_the_entry_document_when_it_is_empty.json @@ -0,0 +1,101 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "", + "version": "" + }, + "components": { + "responses": { + "R": { + "$ref": "https://example.com/common.json#/components/responses/Ok" + } + } + }, + "paths": {} + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/common.json": { + "openapi": "3.1.1", + "info": { + "title": "Common", + "version": "9.9.9", + "summary": "should not leak" + }, + "components": { + "responses": { + "Ok": { + "description": "ok" + } + } + } + } + }, + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "", + "version": "", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/common.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/common.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/common.json" + }, + "https://example.com/common.json#/components/responses/Ok": { + "type": "response", + "pointer": "/components/responses/Ok", + "parent": "https://example.com/common.json#/components" + }, + "https://example.com/common.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/common.json" + }, + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/responses/R": { + "type": "reference", + "pointer": "/components/responses/R", + "parent": "https://example.com/openapi.json#/components", + "original": "https://example.com/common.json#/components/responses/Ok", + "destination": "https://example.com/common.json#/components/responses/Ok" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/internal_reference_cycle.json b/test/openapi/frame/3.1/pass/internal_reference_cycle.json new file mode 100644 index 000000000..43ef8726a --- /dev/null +++ b/test/openapi/frame/3.1/pass/internal_reference_cycle.json @@ -0,0 +1,67 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "A": { + "$ref": "#/components/responses/B" + }, + "B": { + "$ref": "#/components/responses/A" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/responses/A": { + "type": "reference", + "pointer": "/components/responses/A", + "parent": "https://example.com/openapi.json#/components", + "original": "#/components/responses/B", + "destination": "https://example.com/openapi.json#/components/responses/B" + }, + "https://example.com/openapi.json#/components/responses/B": { + "type": "reference", + "pointer": "/components/responses/B", + "parent": "https://example.com/openapi.json#/components", + "original": "#/components/responses/A", + "destination": "https://example.com/openapi.json#/components/responses/A" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/internal_reference_fragment_is_not_a_pointer.json b/test/openapi/frame/3.1/pass/internal_reference_fragment_is_not_a_pointer.json new file mode 100644 index 000000000..a3d42637f --- /dev/null +++ b/test/openapi/frame/3.1/pass/internal_reference_fragment_is_not_a_pointer.json @@ -0,0 +1,68 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "NotFound": { + "description": "Not found" + }, + "R": { + "$ref": "#NotFound" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": false, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/responses/NotFound": { + "type": "response", + "pointer": "/components/responses/NotFound", + "parent": "https://example.com/openapi.json#/components" + }, + "https://example.com/openapi.json#/components/responses/R": { + "type": "reference", + "pointer": "/components/responses/R", + "parent": "https://example.com/openapi.json#/components", + "original": "#NotFound", + "destination": "https://example.com/openapi.json#NotFound" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [] + }, + "dangling": [ + "https://example.com/openapi.json#NotFound" + ] +} diff --git a/test/openapi/frame/3.1/pass/internal_reference_fragment_only.json b/test/openapi/frame/3.1/pass/internal_reference_fragment_only.json new file mode 100644 index 000000000..d5ed54a63 --- /dev/null +++ b/test/openapi/frame/3.1/pass/internal_reference_fragment_only.json @@ -0,0 +1,65 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "NotFound": { + "description": "Not found" + }, + "R": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/responses/NotFound": { + "type": "response", + "pointer": "/components/responses/NotFound", + "parent": "https://example.com/openapi.json#/components" + }, + "https://example.com/openapi.json#/components/responses/R": { + "type": "reference", + "pointer": "/components/responses/R", + "parent": "https://example.com/openapi.json#/components", + "original": "#/components/responses/NotFound", + "destination": "https://example.com/openapi.json#/components/responses/NotFound" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/internal_reference_to_a_missing_container.json b/test/openapi/frame/3.1/pass/internal_reference_to_a_missing_container.json new file mode 100644 index 000000000..8fbd9f315 --- /dev/null +++ b/test/openapi/frame/3.1/pass/internal_reference_to_a_missing_container.json @@ -0,0 +1,68 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "NotFound": { + "description": "Not found" + }, + "R": { + "$ref": "#/components/schemas/Pet" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": false, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/responses/NotFound": { + "type": "response", + "pointer": "/components/responses/NotFound", + "parent": "https://example.com/openapi.json#/components" + }, + "https://example.com/openapi.json#/components/responses/R": { + "type": "reference", + "pointer": "/components/responses/R", + "parent": "https://example.com/openapi.json#/components", + "original": "#/components/schemas/Pet", + "destination": "https://example.com/openapi.json#/components/schemas/Pet" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [] + }, + "dangling": [ + "https://example.com/openapi.json#/components/schemas/Pet" + ] +} diff --git a/test/openapi/frame/3.1/pass/internal_reference_to_a_missing_path_item.json b/test/openapi/frame/3.1/pass/internal_reference_to_a_missing_path_item.json new file mode 100644 index 000000000..19c699606 --- /dev/null +++ b/test/openapi/frame/3.1/pass/internal_reference_to_a_missing_path_item.json @@ -0,0 +1,66 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "pathItems": {} + }, + "paths": { + "/pets": { + "$ref": "#/components/pathItems/Missing" + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": false, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1pets": { + "type": "path-item", + "pointer": "/paths/~1pets", + "parent": "https://example.com/openapi.json#/paths", + "original": "#/components/pathItems/Missing", + "destination": "https://example.com/openapi.json#/components/pathItems/Missing" + } + }, + "operations": [] + }, + "dangling": [ + "https://example.com/openapi.json#/components/pathItems/Missing" + ] +} diff --git a/test/openapi/frame/3.1/pass/internal_reference_to_a_missing_target.json b/test/openapi/frame/3.1/pass/internal_reference_to_a_missing_target.json new file mode 100644 index 000000000..7ea92d0ea --- /dev/null +++ b/test/openapi/frame/3.1/pass/internal_reference_to_a_missing_target.json @@ -0,0 +1,68 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "NotFound": { + "description": "Not found" + }, + "R": { + "$ref": "#/components/responses/Missing" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": false, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/responses/NotFound": { + "type": "response", + "pointer": "/components/responses/NotFound", + "parent": "https://example.com/openapi.json#/components" + }, + "https://example.com/openapi.json#/components/responses/R": { + "type": "reference", + "pointer": "/components/responses/R", + "parent": "https://example.com/openapi.json#/components", + "original": "#/components/responses/Missing", + "destination": "https://example.com/openapi.json#/components/responses/Missing" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [] + }, + "dangling": [ + "https://example.com/openapi.json#/components/responses/Missing" + ] +} diff --git a/test/openapi/frame/3.1/pass/internal_reference_to_a_missing_target_by_full_uri.json b/test/openapi/frame/3.1/pass/internal_reference_to_a_missing_target_by_full_uri.json new file mode 100644 index 000000000..31e61b5fc --- /dev/null +++ b/test/openapi/frame/3.1/pass/internal_reference_to_a_missing_target_by_full_uri.json @@ -0,0 +1,69 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "NotFound": { + "description": "Not found" + }, + "R": { + "$ref": "https://example.com/openapi.json#/components/responses/Missing" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": {}, + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": false, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/responses/NotFound": { + "type": "response", + "pointer": "/components/responses/NotFound", + "parent": "https://example.com/openapi.json#/components" + }, + "https://example.com/openapi.json#/components/responses/R": { + "type": "reference", + "pointer": "/components/responses/R", + "parent": "https://example.com/openapi.json#/components", + "original": "https://example.com/openapi.json#/components/responses/Missing", + "destination": "https://example.com/openapi.json#/components/responses/Missing" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [] + }, + "dangling": [ + "https://example.com/openapi.json#/components/responses/Missing" + ] +} diff --git a/test/openapi/frame/3.1/pass/internal_reference_to_a_missing_target_non_canonical.json b/test/openapi/frame/3.1/pass/internal_reference_to_a_missing_target_non_canonical.json new file mode 100644 index 000000000..ac3f9b2d2 --- /dev/null +++ b/test/openapi/frame/3.1/pass/internal_reference_to_a_missing_target_non_canonical.json @@ -0,0 +1,60 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "R": { + "$ref": "HTTPS://Example.COM:443/openapi.json#/components/responses/Missing" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": false, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/responses/R": { + "type": "reference", + "pointer": "/components/responses/R", + "parent": "https://example.com/openapi.json#/components", + "original": "HTTPS://Example.COM:443/openapi.json#/components/responses/Missing", + "destination": "https://example.com/openapi.json#/components/responses/Missing" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [] + }, + "dangling": [ + "https://example.com/openapi.json#/components/responses/Missing" + ] +} diff --git a/test/openapi/frame/3.1/pass/internal_reference_to_a_missing_target_without_a_base.json b/test/openapi/frame/3.1/pass/internal_reference_to_a_missing_target_without_a_base.json new file mode 100644 index 000000000..96f53ec09 --- /dev/null +++ b/test/openapi/frame/3.1/pass/internal_reference_to_a_missing_target_without_a_base.json @@ -0,0 +1,59 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "R": { + "$ref": "#/components/responses/Missing" + } + } + } + }, + "frame": { + "version": "3.1", + "base": "", + "standalone": false, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/components": { + "type": "components", + "pointer": "/components", + "parent": "" + }, + "#/components/responses/R": { + "type": "reference", + "pointer": "/components/responses/R", + "parent": "#/components", + "original": "#/components/responses/Missing", + "destination": "#/components/responses/Missing" + }, + "#/info": { + "type": "info", + "pointer": "/info", + "parent": "" + } + }, + "operations": [] + }, + "dangling": [ + "#/components/responses/Missing" + ] +} diff --git a/test/openapi/frame/3.1/pass/internal_reference_to_a_path_item.json b/test/openapi/frame/3.1/pass/internal_reference_to_a_path_item.json new file mode 100644 index 000000000..231de47ce --- /dev/null +++ b/test/openapi/frame/3.1/pass/internal_reference_to_a_path_item.json @@ -0,0 +1,105 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "pathItems": { + "Pets": { + "get": { + "responses": { + "200": { + "description": "ok" + } + } + } + } + } + }, + "paths": { + "/pets": { + "$ref": "#/components/pathItems/Pets" + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/pathItems/Pets": { + "type": "path-item", + "pointer": "/components/pathItems/Pets", + "parent": "https://example.com/openapi.json#/components" + }, + "https://example.com/openapi.json#/components/pathItems/Pets/get": { + "type": "operation", + "pointer": "/components/pathItems/Pets/get", + "parent": "https://example.com/openapi.json#/components/pathItems/Pets" + }, + "https://example.com/openapi.json#/components/pathItems/Pets/get/responses": { + "type": "responses", + "pointer": "/components/pathItems/Pets/get/responses", + "parent": "https://example.com/openapi.json#/components/pathItems/Pets/get" + }, + "https://example.com/openapi.json#/components/pathItems/Pets/get/responses/200": { + "type": "response", + "pointer": "/components/pathItems/Pets/get/responses/200", + "parent": "https://example.com/openapi.json#/components/pathItems/Pets/get/responses" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1pets": { + "type": "path-item", + "pointer": "/paths/~1pets", + "parent": "https://example.com/openapi.json#/paths", + "original": "#/components/pathItems/Pets", + "destination": "https://example.com/openapi.json#/components/pathItems/Pets" + } + }, + "operations": [ + { + "type": "path", + "path": "/pets", + "method": "get", + "origin": "https://example.com/openapi.json#/components/pathItems/Pets/get", + "endpoint": "https://example.com/openapi.json#/paths/~1pets", + "tags": [], + "servers": [], + "security": [], + "parameters": [] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/internal_reference_to_a_path_item_that_declares_an_operation_id.json b/test/openapi/frame/3.1/pass/internal_reference_to_a_path_item_that_declares_an_operation_id.json new file mode 100644 index 000000000..655c33b60 --- /dev/null +++ b/test/openapi/frame/3.1/pass/internal_reference_to_a_path_item_that_declares_an_operation_id.json @@ -0,0 +1,106 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "pathItems": { + "Pets": { + "get": { + "operationId": "listPets", + "responses": { + "200": { + "description": "ok" + } + } + } + } + } + }, + "paths": { + "/pets": { + "$ref": "#/components/pathItems/Pets" + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/pathItems/Pets": { + "type": "path-item", + "pointer": "/components/pathItems/Pets", + "parent": "https://example.com/openapi.json#/components" + }, + "https://example.com/openapi.json#/components/pathItems/Pets/get": { + "type": "operation", + "pointer": "/components/pathItems/Pets/get", + "parent": "https://example.com/openapi.json#/components/pathItems/Pets" + }, + "https://example.com/openapi.json#/components/pathItems/Pets/get/responses": { + "type": "responses", + "pointer": "/components/pathItems/Pets/get/responses", + "parent": "https://example.com/openapi.json#/components/pathItems/Pets/get" + }, + "https://example.com/openapi.json#/components/pathItems/Pets/get/responses/200": { + "type": "response", + "pointer": "/components/pathItems/Pets/get/responses/200", + "parent": "https://example.com/openapi.json#/components/pathItems/Pets/get/responses" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1pets": { + "type": "path-item", + "pointer": "/paths/~1pets", + "parent": "https://example.com/openapi.json#/paths", + "original": "#/components/pathItems/Pets", + "destination": "https://example.com/openapi.json#/components/pathItems/Pets" + } + }, + "operations": [ + { + "type": "path", + "path": "/pets", + "method": "get", + "origin": "https://example.com/openapi.json#/components/pathItems/Pets/get", + "endpoint": "https://example.com/openapi.json#/paths/~1pets", + "tags": [], + "servers": [], + "security": [], + "parameters": [] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/internal_reference_to_another_reference.json b/test/openapi/frame/3.1/pass/internal_reference_to_another_reference.json new file mode 100644 index 000000000..df7756079 --- /dev/null +++ b/test/openapi/frame/3.1/pass/internal_reference_to_another_reference.json @@ -0,0 +1,75 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "NotFound": { + "description": "Not found" + }, + "A": { + "$ref": "#/components/responses/B" + }, + "B": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/responses/A": { + "type": "reference", + "pointer": "/components/responses/A", + "parent": "https://example.com/openapi.json#/components", + "original": "#/components/responses/B", + "destination": "https://example.com/openapi.json#/components/responses/B" + }, + "https://example.com/openapi.json#/components/responses/B": { + "type": "reference", + "pointer": "/components/responses/B", + "parent": "https://example.com/openapi.json#/components", + "original": "#/components/responses/NotFound", + "destination": "https://example.com/openapi.json#/components/responses/NotFound" + }, + "https://example.com/openapi.json#/components/responses/NotFound": { + "type": "response", + "pointer": "/components/responses/NotFound", + "parent": "https://example.com/openapi.json#/components" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/internal_reference_to_itself.json b/test/openapi/frame/3.1/pass/internal_reference_to_itself.json new file mode 100644 index 000000000..3f53de8cb --- /dev/null +++ b/test/openapi/frame/3.1/pass/internal_reference_to_itself.json @@ -0,0 +1,57 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "A": { + "$ref": "#/components/responses/A" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/responses/A": { + "type": "reference", + "pointer": "/components/responses/A", + "parent": "https://example.com/openapi.json#/components", + "original": "#/components/responses/A", + "destination": "https://example.com/openapi.json#/components/responses/A" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/internal_reference_with_an_escaped_token.json b/test/openapi/frame/3.1/pass/internal_reference_with_an_escaped_token.json new file mode 100644 index 000000000..64dc3124a --- /dev/null +++ b/test/openapi/frame/3.1/pass/internal_reference_with_an_escaped_token.json @@ -0,0 +1,105 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "R": { + "$ref": "#/paths/~1pets/get/responses/200" + } + } + }, + "paths": { + "/pets": { + "get": { + "responses": { + "200": { + "description": "ok" + } + } + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/responses/R": { + "type": "reference", + "pointer": "/components/responses/R", + "parent": "https://example.com/openapi.json#/components", + "original": "#/paths/~1pets/get/responses/200", + "destination": "https://example.com/openapi.json#/paths/~1pets/get/responses/200" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1pets": { + "type": "path-item", + "pointer": "/paths/~1pets", + "parent": "https://example.com/openapi.json#/paths" + }, + "https://example.com/openapi.json#/paths/~1pets/get": { + "type": "operation", + "pointer": "/paths/~1pets/get", + "parent": "https://example.com/openapi.json#/paths/~1pets" + }, + "https://example.com/openapi.json#/paths/~1pets/get/responses": { + "type": "responses", + "pointer": "/paths/~1pets/get/responses", + "parent": "https://example.com/openapi.json#/paths/~1pets/get" + }, + "https://example.com/openapi.json#/paths/~1pets/get/responses/200": { + "type": "response", + "pointer": "/paths/~1pets/get/responses/200", + "parent": "https://example.com/openapi.json#/paths/~1pets/get/responses" + } + }, + "operations": [ + { + "type": "path", + "path": "/pets", + "method": "get", + "origin": "https://example.com/openapi.json#/paths/~1pets/get", + "endpoint": "https://example.com/openapi.json#/paths/~1pets", + "tags": [], + "servers": [], + "security": [], + "parameters": [] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/internal_reference_without_a_base.json b/test/openapi/frame/3.1/pass/internal_reference_without_a_base.json new file mode 100644 index 000000000..5fdd5f212 --- /dev/null +++ b/test/openapi/frame/3.1/pass/internal_reference_without_a_base.json @@ -0,0 +1,65 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "NotFound": { + "description": "Not found" + }, + "R": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "resolver": {}, + "frame": { + "version": "3.1", + "base": "", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/components": { + "type": "components", + "pointer": "/components", + "parent": "" + }, + "#/components/responses/NotFound": { + "type": "response", + "pointer": "/components/responses/NotFound", + "parent": "#/components" + }, + "#/components/responses/R": { + "type": "reference", + "pointer": "/components/responses/R", + "parent": "#/components", + "original": "#/components/responses/NotFound", + "destination": "#/components/responses/NotFound" + }, + "#/info": { + "type": "info", + "pointer": "/info", + "parent": "" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/internal_reference_without_a_fragment.json b/test/openapi/frame/3.1/pass/internal_reference_without_a_fragment.json new file mode 100644 index 000000000..e1d6b1c79 --- /dev/null +++ b/test/openapi/frame/3.1/pass/internal_reference_without_a_fragment.json @@ -0,0 +1,65 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "NotFound": { + "description": "Not found" + }, + "R": { + "$ref": "" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/responses/NotFound": { + "type": "response", + "pointer": "/components/responses/NotFound", + "parent": "https://example.com/openapi.json#/components" + }, + "https://example.com/openapi.json#/components/responses/R": { + "type": "reference", + "pointer": "/components/responses/R", + "parent": "https://example.com/openapi.json#/components", + "original": "", + "destination": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/internal_reference_written_as_a_full_uri.json b/test/openapi/frame/3.1/pass/internal_reference_written_as_a_full_uri.json new file mode 100644 index 000000000..003deac80 --- /dev/null +++ b/test/openapi/frame/3.1/pass/internal_reference_written_as_a_full_uri.json @@ -0,0 +1,66 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "NotFound": { + "description": "Not found" + }, + "R": { + "$ref": "https://example.com/openapi.json#/components/responses/NotFound" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": {}, + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/responses/NotFound": { + "type": "response", + "pointer": "/components/responses/NotFound", + "parent": "https://example.com/openapi.json#/components" + }, + "https://example.com/openapi.json#/components/responses/R": { + "type": "reference", + "pointer": "/components/responses/R", + "parent": "https://example.com/openapi.json#/components", + "original": "https://example.com/openapi.json#/components/responses/NotFound", + "destination": "https://example.com/openapi.json#/components/responses/NotFound" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/internal_reference_written_as_a_non_canonical_uri.json b/test/openapi/frame/3.1/pass/internal_reference_written_as_a_non_canonical_uri.json new file mode 100644 index 000000000..b74a534ba --- /dev/null +++ b/test/openapi/frame/3.1/pass/internal_reference_written_as_a_non_canonical_uri.json @@ -0,0 +1,66 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "NotFound": { + "description": "Not found" + }, + "R": { + "$ref": "HTTPS://Example.COM:443/openapi.json#/components/responses/NotFound" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": {}, + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/responses/NotFound": { + "type": "response", + "pointer": "/components/responses/NotFound", + "parent": "https://example.com/openapi.json#/components" + }, + "https://example.com/openapi.json#/components/responses/R": { + "type": "reference", + "pointer": "/components/responses/R", + "parent": "https://example.com/openapi.json#/components", + "original": "HTTPS://Example.COM:443/openapi.json#/components/responses/NotFound", + "destination": "https://example.com/openapi.json#/components/responses/NotFound" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/json_schema_dialect_oas.json b/test/openapi/frame/3.1/pass/json_schema_dialect_oas.json new file mode 100644 index 000000000..5c764437d --- /dev/null +++ b/test/openapi/frame/3.1/pass/json_schema_dialect_oas.json @@ -0,0 +1,44 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "jsonSchemaDialect": "https://spec.openapis.org/oas/3.1/dialect/base", + "paths": {} + }, + "frame": { + "version": "3.1", + "base": "", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/info": { + "type": "info", + "pointer": "/info", + "parent": "" + }, + "#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/json_schema_dialect_relative.json b/test/openapi/frame/3.1/pass/json_schema_dialect_relative.json new file mode 100644 index 000000000..b820a3b58 --- /dev/null +++ b/test/openapi/frame/3.1/pass/json_schema_dialect_relative.json @@ -0,0 +1,44 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "jsonSchemaDialect": "dialects/custom.json", + "paths": {} + }, + "frame": { + "version": "3.1", + "base": "", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "dialects/custom.json" + }, + "#/info": { + "type": "info", + "pointer": "/info", + "parent": "" + }, + "#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/json_schema_dialect_relative_to_the_base.json b/test/openapi/frame/3.1/pass/json_schema_dialect_relative_to_the_base.json new file mode 100644 index 000000000..9b2e21abc --- /dev/null +++ b/test/openapi/frame/3.1/pass/json_schema_dialect_relative_to_the_base.json @@ -0,0 +1,45 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "jsonSchemaDialect": "dialects/../custom.json", + "paths": {} + }, + "defaultBase": "https://example.com/schemas/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/schemas/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/schemas/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://example.com/schemas/custom.json" + }, + "https://example.com/schemas/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/schemas/openapi.json" + }, + "https://example.com/schemas/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/schemas/openapi.json" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/json_schema_dialect_unknown.json b/test/openapi/frame/3.1/pass/json_schema_dialect_unknown.json new file mode 100644 index 000000000..0675e3010 --- /dev/null +++ b/test/openapi/frame/3.1/pass/json_schema_dialect_unknown.json @@ -0,0 +1,44 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "jsonSchemaDialect": "https://example.com/whatever", + "paths": {} + }, + "frame": { + "version": "3.1", + "base": "", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://example.com/whatever" + }, + "#/info": { + "type": "info", + "pointer": "/info", + "parent": "" + }, + "#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/link_operation_id_is_not_resolved_without_the_whole_description.json b/test/openapi/frame/3.1/pass/link_operation_id_is_not_resolved_without_the_whole_description.json new file mode 100644 index 000000000..23d9ff3e9 --- /dev/null +++ b/test/openapi/frame/3.1/pass/link_operation_id_is_not_resolved_without_the_whole_description.json @@ -0,0 +1,76 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "links": { + "L": { + "operationId": "listPets" + } + }, + "responses": { + "R": { + "$ref": "https://example.com/common.json#/components/responses/Ok" + } + } + }, + "paths": {} + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": false, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/links/L": { + "type": "link", + "pointer": "/components/links/L", + "parent": "https://example.com/openapi.json#/components" + }, + "https://example.com/openapi.json#/components/responses/R": { + "type": "reference", + "pointer": "/components/responses/R", + "parent": "https://example.com/openapi.json#/components", + "original": "https://example.com/common.json#/components/responses/Ok", + "destination": "https://example.com/common.json#/components/responses/Ok" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [] + }, + "dangling": [ + "https://example.com/common.json#/components/responses/Ok" + ] +} diff --git a/test/openapi/frame/3.1/pass/link_operation_id_named_by_two_paths.json b/test/openapi/frame/3.1/pass/link_operation_id_named_by_two_paths.json new file mode 100644 index 000000000..78bd19f99 --- /dev/null +++ b/test/openapi/frame/3.1/pass/link_operation_id_named_by_two_paths.json @@ -0,0 +1,137 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "links": { + "L": { + "operationId": "listPets" + } + }, + "pathItems": { + "Pets": { + "get": { + "responses": { + "200": { + "description": "ok" + } + }, + "operationId": "listPets" + } + } + } + }, + "paths": { + "/pets": { + "$ref": "#/components/pathItems/Pets" + }, + "/animals": { + "$ref": "#/components/pathItems/Pets" + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/links/L": { + "type": "link", + "pointer": "/components/links/L", + "parent": "https://example.com/openapi.json#/components" + }, + "https://example.com/openapi.json#/components/pathItems/Pets": { + "type": "path-item", + "pointer": "/components/pathItems/Pets", + "parent": "https://example.com/openapi.json#/components" + }, + "https://example.com/openapi.json#/components/pathItems/Pets/get": { + "type": "operation", + "pointer": "/components/pathItems/Pets/get", + "parent": "https://example.com/openapi.json#/components/pathItems/Pets" + }, + "https://example.com/openapi.json#/components/pathItems/Pets/get/responses": { + "type": "responses", + "pointer": "/components/pathItems/Pets/get/responses", + "parent": "https://example.com/openapi.json#/components/pathItems/Pets/get" + }, + "https://example.com/openapi.json#/components/pathItems/Pets/get/responses/200": { + "type": "response", + "pointer": "/components/pathItems/Pets/get/responses/200", + "parent": "https://example.com/openapi.json#/components/pathItems/Pets/get/responses" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1animals": { + "type": "path-item", + "pointer": "/paths/~1animals", + "parent": "https://example.com/openapi.json#/paths", + "original": "#/components/pathItems/Pets", + "destination": "https://example.com/openapi.json#/components/pathItems/Pets" + }, + "https://example.com/openapi.json#/paths/~1pets": { + "type": "path-item", + "pointer": "/paths/~1pets", + "parent": "https://example.com/openapi.json#/paths", + "original": "#/components/pathItems/Pets", + "destination": "https://example.com/openapi.json#/components/pathItems/Pets" + } + }, + "operations": [ + { + "type": "path", + "path": "/pets", + "method": "get", + "origin": "https://example.com/openapi.json#/components/pathItems/Pets/get", + "endpoint": "https://example.com/openapi.json#/paths/~1pets", + "tags": [], + "servers": [], + "security": [], + "parameters": [] + }, + { + "type": "path", + "path": "/animals", + "method": "get", + "origin": "https://example.com/openapi.json#/components/pathItems/Pets/get", + "endpoint": "https://example.com/openapi.json#/paths/~1animals", + "tags": [], + "servers": [], + "security": [], + "parameters": [] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/link_operation_id_names_an_operation.json b/test/openapi/frame/3.1/pass/link_operation_id_names_an_operation.json new file mode 100644 index 000000000..b60a43146 --- /dev/null +++ b/test/openapi/frame/3.1/pass/link_operation_id_names_an_operation.json @@ -0,0 +1,104 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "links": { + "L": { + "operationId": "listPets" + } + } + }, + "paths": { + "/pets": { + "get": { + "responses": { + "200": { + "description": "ok" + } + }, + "operationId": "listPets" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/links/L": { + "type": "link", + "pointer": "/components/links/L", + "parent": "https://example.com/openapi.json#/components" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1pets": { + "type": "path-item", + "pointer": "/paths/~1pets", + "parent": "https://example.com/openapi.json#/paths" + }, + "https://example.com/openapi.json#/paths/~1pets/get": { + "type": "operation", + "pointer": "/paths/~1pets/get", + "parent": "https://example.com/openapi.json#/paths/~1pets" + }, + "https://example.com/openapi.json#/paths/~1pets/get/responses": { + "type": "responses", + "pointer": "/paths/~1pets/get/responses", + "parent": "https://example.com/openapi.json#/paths/~1pets/get" + }, + "https://example.com/openapi.json#/paths/~1pets/get/responses/200": { + "type": "response", + "pointer": "/paths/~1pets/get/responses/200", + "parent": "https://example.com/openapi.json#/paths/~1pets/get/responses" + } + }, + "operations": [ + { + "type": "path", + "path": "/pets", + "method": "get", + "origin": "https://example.com/openapi.json#/paths/~1pets/get", + "endpoint": "https://example.com/openapi.json#/paths/~1pets", + "tags": [], + "servers": [], + "security": [], + "parameters": [] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/link_operation_id_resolves_across_documents.json b/test/openapi/frame/3.1/pass/link_operation_id_resolves_across_documents.json new file mode 100644 index 000000000..91f309e5c --- /dev/null +++ b/test/openapi/frame/3.1/pass/link_operation_id_resolves_across_documents.json @@ -0,0 +1,143 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "links": { + "L": { + "operationId": "listPets" + } + } + }, + "paths": { + "/pets": { + "$ref": "https://example.com/common.json#/components/pathItems/Pets" + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/common.json": { + "openapi": "3.1.1", + "info": { + "title": "Common", + "version": "1.0.0" + }, + "components": { + "pathItems": { + "Pets": { + "get": { + "responses": { + "200": { + "description": "ok" + } + }, + "operationId": "listPets" + } + } + } + } + } + }, + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/common.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/common.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/common.json" + }, + "https://example.com/common.json#/components/pathItems/Pets": { + "type": "path-item", + "pointer": "/components/pathItems/Pets", + "parent": "https://example.com/common.json#/components" + }, + "https://example.com/common.json#/components/pathItems/Pets/get": { + "type": "operation", + "pointer": "/components/pathItems/Pets/get", + "parent": "https://example.com/common.json#/components/pathItems/Pets" + }, + "https://example.com/common.json#/components/pathItems/Pets/get/responses": { + "type": "responses", + "pointer": "/components/pathItems/Pets/get/responses", + "parent": "https://example.com/common.json#/components/pathItems/Pets/get" + }, + "https://example.com/common.json#/components/pathItems/Pets/get/responses/200": { + "type": "response", + "pointer": "/components/pathItems/Pets/get/responses/200", + "parent": "https://example.com/common.json#/components/pathItems/Pets/get/responses" + }, + "https://example.com/common.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/common.json" + }, + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/links/L": { + "type": "link", + "pointer": "/components/links/L", + "parent": "https://example.com/openapi.json#/components" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1pets": { + "type": "path-item", + "pointer": "/paths/~1pets", + "parent": "https://example.com/openapi.json#/paths", + "original": "https://example.com/common.json#/components/pathItems/Pets", + "destination": "https://example.com/common.json#/components/pathItems/Pets" + } + }, + "operations": [ + { + "type": "path", + "path": "/pets", + "method": "get", + "origin": "https://example.com/common.json#/components/pathItems/Pets/get", + "endpoint": "https://example.com/openapi.json#/paths/~1pets", + "tags": [], + "servers": [], + "security": [], + "parameters": [] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/link_operation_ref_that_lands_nowhere.json b/test/openapi/frame/3.1/pass/link_operation_ref_that_lands_nowhere.json new file mode 100644 index 000000000..338914338 --- /dev/null +++ b/test/openapi/frame/3.1/pass/link_operation_ref_that_lands_nowhere.json @@ -0,0 +1,60 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "links": { + "ByRef": { + "operationRef": "#/paths/~1pets/get" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": false, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/links/ByRef": { + "type": "link", + "pointer": "/components/links/ByRef", + "parent": "https://example.com/openapi.json#/components", + "original": "#/paths/~1pets/get", + "destination": "https://example.com/openapi.json#/paths/~1pets/get" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [] + }, + "dangling": [ + "https://example.com/openapi.json#/paths/~1pets/get" + ] +} diff --git a/test/openapi/frame/3.1/pass/link_operation_ref_to_an_operation.json b/test/openapi/frame/3.1/pass/link_operation_ref_to_an_operation.json new file mode 100644 index 000000000..e094b272f --- /dev/null +++ b/test/openapi/frame/3.1/pass/link_operation_ref_to_an_operation.json @@ -0,0 +1,105 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "links": { + "ByRef": { + "operationRef": "#/paths/~1pets/get" + } + } + }, + "paths": { + "/pets": { + "get": { + "responses": { + "200": { + "description": "ok" + } + } + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/links/ByRef": { + "type": "link", + "pointer": "/components/links/ByRef", + "parent": "https://example.com/openapi.json#/components", + "original": "#/paths/~1pets/get", + "destination": "https://example.com/openapi.json#/paths/~1pets/get" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1pets": { + "type": "path-item", + "pointer": "/paths/~1pets", + "parent": "https://example.com/openapi.json#/paths" + }, + "https://example.com/openapi.json#/paths/~1pets/get": { + "type": "operation", + "pointer": "/paths/~1pets/get", + "parent": "https://example.com/openapi.json#/paths/~1pets" + }, + "https://example.com/openapi.json#/paths/~1pets/get/responses": { + "type": "responses", + "pointer": "/paths/~1pets/get/responses", + "parent": "https://example.com/openapi.json#/paths/~1pets/get" + }, + "https://example.com/openapi.json#/paths/~1pets/get/responses/200": { + "type": "response", + "pointer": "/paths/~1pets/get/responses/200", + "parent": "https://example.com/openapi.json#/paths/~1pets/get/responses" + } + }, + "operations": [ + { + "type": "path", + "path": "/pets", + "method": "get", + "origin": "https://example.com/openapi.json#/paths/~1pets/get", + "endpoint": "https://example.com/openapi.json#/paths/~1pets", + "tags": [], + "servers": [], + "security": [], + "parameters": [] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/link_operation_ref_to_another_document.json b/test/openapi/frame/3.1/pass/link_operation_ref_to_another_document.json new file mode 100644 index 000000000..0ae337bd5 --- /dev/null +++ b/test/openapi/frame/3.1/pass/link_operation_ref_to_another_document.json @@ -0,0 +1,119 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "links": { + "ByRef": { + "operationRef": "https://example.com/common.json#/paths/~1pets/get" + } + } + }, + "paths": {} + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/common.json": { + "openapi": "3.1.1", + "info": { + "title": "Common", + "version": "1.0.0" + }, + "paths": { + "/pets": { + "get": { + "responses": { + "200": { + "description": "ok" + } + } + } + } + } + } + }, + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/common.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/common.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/common.json" + }, + "https://example.com/common.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/common.json" + }, + "https://example.com/common.json#/paths/~1pets": { + "type": "path-item", + "pointer": "/paths/~1pets", + "parent": "https://example.com/common.json#/paths" + }, + "https://example.com/common.json#/paths/~1pets/get": { + "type": "operation", + "pointer": "/paths/~1pets/get", + "parent": "https://example.com/common.json#/paths/~1pets" + }, + "https://example.com/common.json#/paths/~1pets/get/responses": { + "type": "responses", + "pointer": "/paths/~1pets/get/responses", + "parent": "https://example.com/common.json#/paths/~1pets/get" + }, + "https://example.com/common.json#/paths/~1pets/get/responses/200": { + "type": "response", + "pointer": "/paths/~1pets/get/responses/200", + "parent": "https://example.com/common.json#/paths/~1pets/get/responses" + }, + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/links/ByRef": { + "type": "link", + "pointer": "/components/links/ByRef", + "parent": "https://example.com/openapi.json#/components", + "original": "https://example.com/common.json#/paths/~1pets/get", + "destination": "https://example.com/common.json#/paths/~1pets/get" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/link_parameters_hold_any_value.json b/test/openapi/frame/3.1/pass/link_parameters_hold_any_value.json new file mode 100644 index 000000000..c2aca65af --- /dev/null +++ b/test/openapi/frame/3.1/pass/link_parameters_hold_any_value.json @@ -0,0 +1,117 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "links": { + "Item": { + "operationId": "list", + "parameters": { + "number": 1, + "boolean": true, + "null": null, + "array": [ + 1, + 2 + ], + "object": { + "a": 1 + }, + "expression": "$response.body#/id" + } + } + } + }, + "paths": { + "/pets": { + "get": { + "responses": { + "200": { + "description": "ok" + } + }, + "operationId": "list" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/links/Item": { + "type": "link", + "pointer": "/components/links/Item", + "parent": "https://example.com/openapi.json#/components" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1pets": { + "type": "path-item", + "pointer": "/paths/~1pets", + "parent": "https://example.com/openapi.json#/paths" + }, + "https://example.com/openapi.json#/paths/~1pets/get": { + "type": "operation", + "pointer": "/paths/~1pets/get", + "parent": "https://example.com/openapi.json#/paths/~1pets" + }, + "https://example.com/openapi.json#/paths/~1pets/get/responses": { + "type": "responses", + "pointer": "/paths/~1pets/get/responses", + "parent": "https://example.com/openapi.json#/paths/~1pets/get" + }, + "https://example.com/openapi.json#/paths/~1pets/get/responses/200": { + "type": "response", + "pointer": "/paths/~1pets/get/responses/200", + "parent": "https://example.com/openapi.json#/paths/~1pets/get/responses" + } + }, + "operations": [ + { + "type": "path", + "path": "/pets", + "method": "get", + "origin": "https://example.com/openapi.json#/paths/~1pets/get", + "endpoint": "https://example.com/openapi.json#/paths/~1pets", + "tags": [], + "servers": [], + "security": [], + "parameters": [] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/media_type_encoding.json b/test/openapi/frame/3.1/pass/media_type_encoding.json new file mode 100644 index 000000000..79d4d8e3a --- /dev/null +++ b/test/openapi/frame/3.1/pass/media_type_encoding.json @@ -0,0 +1,98 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "requestBodies": { + "Upload": { + "content": { + "multipart/form-data": { + "schema": {}, + "encoding": { + "profile": { + "contentType": "image/png", + "headers": { + "X-Rate": { + "schema": {} + } + }, + "style": "form", + "explode": true, + "allowReserved": false + } + } + } + } + } + } + } + }, + "frame": { + "version": "3.1", + "base": "", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/components": { + "type": "components", + "pointer": "/components", + "parent": "" + }, + "#/components/requestBodies/Upload": { + "type": "request-body", + "pointer": "/components/requestBodies/Upload", + "parent": "#/components" + }, + "#/components/requestBodies/Upload/content/multipart~1form-data": { + "type": "media-type", + "pointer": "/components/requestBodies/Upload/content/multipart~1form-data", + "parent": "#/components/requestBodies/Upload" + }, + "#/components/requestBodies/Upload/content/multipart~1form-data/encoding/profile": { + "type": "encoding", + "pointer": "/components/requestBodies/Upload/content/multipart~1form-data/encoding/profile", + "parent": "#/components/requestBodies/Upload/content/multipart~1form-data" + }, + "#/components/requestBodies/Upload/content/multipart~1form-data/encoding/profile/headers/X-Rate": { + "type": "header", + "pointer": "/components/requestBodies/Upload/content/multipart~1form-data/encoding/profile/headers/X-Rate", + "parent": "#/components/requestBodies/Upload/content/multipart~1form-data/encoding/profile" + }, + "#/components/requestBodies/Upload/content/multipart~1form-data/encoding/profile/headers/X-Rate/schema": { + "type": "schema", + "pointer": "/components/requestBodies/Upload/content/multipart~1form-data/encoding/profile/headers/X-Rate/schema", + "parent": "#/components/requestBodies/Upload/content/multipart~1form-data/encoding/profile/headers/X-Rate", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/components/requestBodies/Upload/content/multipart~1form-data/schema": { + "type": "schema", + "pointer": "/components/requestBodies/Upload/content/multipart~1form-data/schema", + "parent": "#/components/requestBodies/Upload/content/multipart~1form-data", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/info": { + "type": "info", + "pointer": "/info", + "parent": "" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/minimal.json b/test/openapi/frame/3.1/pass/minimal.json new file mode 100644 index 000000000..777ec09d8 --- /dev/null +++ b/test/openapi/frame/3.1/pass/minimal.json @@ -0,0 +1,43 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": {} + }, + "frame": { + "version": "3.1", + "base": "", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/info": { + "type": "info", + "pointer": "/info", + "parent": "" + }, + "#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/oai_info_object_example.json b/test/openapi/frame/3.1/pass/oai_info_object_example.json new file mode 100644 index 000000000..1e4a5c7c1 --- /dev/null +++ b/test/openapi/frame/3.1/pass/oai_info_object_example.json @@ -0,0 +1,82 @@ +{ + "document": { + "openapi": "3.1.0", + "info": { + "title": "Example Pet Store App", + "summary": "A pet store manager.", + "description": "This is an example server for a pet store.", + "termsOfService": "https://example.com/terms/", + "contact": { + "name": "API Support", + "url": "https://www.example.com/support", + "email": "support@example.com" + }, + "license": { + "name": "Apache 2.0", + "url": "https://www.apache.org/licenses/LICENSE-2.0.html" + }, + "version": "1.0.1" + }, + "externalDocs": { + "description": "Find more info here", + "url": "https://example.com" + }, + "components": {} + }, + "frame": { + "version": "3.1", + "base": "", + "standalone": true, + "info": { + "title": "Example Pet Store App", + "version": "1.0.1", + "summary": "A pet store manager.", + "description": "This is an example server for a pet store.", + "termsOfService": "https://example.com/terms/", + "contact": { + "name": "API Support", + "url": "https://www.example.com/support", + "email": "support@example.com" + }, + "license": { + "name": "Apache 2.0", + "identifier": null, + "url": "https://www.apache.org/licenses/LICENSE-2.0.html" + } + }, + "locations": { + "": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/components": { + "type": "components", + "pointer": "/components", + "parent": "" + }, + "#/externalDocs": { + "type": "external-documentation", + "pointer": "/externalDocs", + "parent": "" + }, + "#/info": { + "type": "info", + "pointer": "/info", + "parent": "" + }, + "#/info/contact": { + "type": "contact", + "pointer": "/info/contact", + "parent": "#/info" + }, + "#/info/license": { + "type": "license", + "pointer": "/info/license", + "parent": "#/info" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/oai_info_summary.json b/test/openapi/frame/3.1/pass/oai_info_summary.json new file mode 100644 index 000000000..5ad42b324 --- /dev/null +++ b/test/openapi/frame/3.1/pass/oai_info_summary.json @@ -0,0 +1,44 @@ +{ + "document": { + "openapi": "3.1.0", + "info": { + "title": "API", + "summary": "My lovely API", + "version": "1.0.0" + }, + "components": {} + }, + "frame": { + "version": "3.1", + "base": "", + "standalone": true, + "info": { + "title": "API", + "version": "1.0.0", + "summary": "My lovely API", + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/components": { + "type": "components", + "pointer": "/components", + "parent": "" + }, + "#/info": { + "type": "info", + "pointer": "/info", + "parent": "" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/oai_json_schema_dialect.json b/test/openapi/frame/3.1/pass/oai_json_schema_dialect.json new file mode 100644 index 000000000..6ef6a5ab1 --- /dev/null +++ b/test/openapi/frame/3.1/pass/oai_json_schema_dialect.json @@ -0,0 +1,77 @@ +{ + "document": { + "openapi": "3.1.0", + "info": { + "summary": "Testing jsonSchemaDialect", + "title": "My API", + "version": "1.0.0", + "license": { + "name": "Apache 2.0", + "identifier": "Apache-2.0" + } + }, + "jsonSchemaDialect": "https://spec.openapis.org/oas/3.1/dialect/WORK-IN-PROGRESS", + "components": { + "schemas": { + "WithDollarSchema": { + "$id": "locked-metaschema", + "$schema": "https://spec.openapis.org/oas/3.1/dialect/WORK-IN-PROGRESS" + } + } + }, + "paths": {} + }, + "frame": { + "version": "3.1", + "base": "", + "standalone": true, + "info": { + "title": "My API", + "version": "1.0.0", + "summary": "Testing jsonSchemaDialect", + "description": null, + "termsOfService": null, + "contact": null, + "license": { + "name": "Apache 2.0", + "identifier": "Apache-2.0", + "url": null + } + }, + "locations": { + "": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/WORK-IN-PROGRESS" + }, + "#/components": { + "type": "components", + "pointer": "/components", + "parent": "" + }, + "#/components/schemas/WithDollarSchema": { + "type": "schema", + "pointer": "/components/schemas/WithDollarSchema", + "parent": "#/components", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/WORK-IN-PROGRESS" + }, + "#/info": { + "type": "info", + "pointer": "/info", + "parent": "" + }, + "#/info/license": { + "type": "license", + "pointer": "/info/license", + "parent": "#/info" + }, + "#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/oai_license_identifier.json b/test/openapi/frame/3.1/pass/oai_license_identifier.json new file mode 100644 index 000000000..34d376c63 --- /dev/null +++ b/test/openapi/frame/3.1/pass/oai_license_identifier.json @@ -0,0 +1,57 @@ +{ + "document": { + "openapi": "3.1.0", + "info": { + "title": "API", + "summary": "My lovely API", + "version": "1.0.0", + "license": { + "name": "Apache", + "identifier": "Apache-2.0" + } + }, + "components": {} + }, + "frame": { + "version": "3.1", + "base": "", + "standalone": true, + "info": { + "title": "API", + "version": "1.0.0", + "summary": "My lovely API", + "description": null, + "termsOfService": null, + "contact": null, + "license": { + "name": "Apache", + "identifier": "Apache-2.0", + "url": null + } + }, + "locations": { + "": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/components": { + "type": "components", + "pointer": "/components", + "parent": "" + }, + "#/info": { + "type": "info", + "pointer": "/info", + "parent": "" + }, + "#/info/license": { + "type": "license", + "pointer": "/info/license", + "parent": "#/info" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/operation_every_field.json b/test/openapi/frame/3.1/pass/operation_every_field.json new file mode 100644 index 000000000..0f093a948 --- /dev/null +++ b/test/openapi/frame/3.1/pass/operation_every_field.json @@ -0,0 +1,306 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets": { + "get": { + "tags": [ + "pets" + ], + "summary": "List pets", + "description": "Returns pets", + "externalDocs": { + "url": "https://example.com/docs" + }, + "operationId": "listPets", + "parameters": [ + { + "name": "limit", + "in": "query", + "schema": {} + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": {} + } + } + }, + "responses": { + "200": { + "description": "ok", + "headers": { + "X-Rate": { + "schema": {} + } + }, + "content": { + "application/json": { + "schema": {} + } + }, + "links": { + "Self": { + "operationId": "listPets" + } + } + }, + "4XX": { + "description": "client error" + }, + "default": { + "description": "unexpected" + } + }, + "callbacks": { + "onData": { + "{$request.body#/url}": { + "post": { + "responses": { + "200": { + "description": "ok" + } + } + } + } + } + }, + "deprecated": true, + "security": [ + { + "Key": [] + } + ], + "servers": [ + { + "url": "/v1" + } + ], + "x-internal": true + } + } + }, + "tags": [ + { + "name": "pets" + } + ], + "components": { + "securitySchemes": { + "Key": { + "type": "apiKey", + "name": "X-Key", + "in": "header" + } + } + } + }, + "frame": { + "version": "3.1", + "base": "", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/components": { + "type": "components", + "pointer": "/components", + "parent": "" + }, + "#/components/securitySchemes/Key": { + "type": "security-scheme", + "pointer": "/components/securitySchemes/Key", + "parent": "#/components" + }, + "#/info": { + "type": "info", + "pointer": "/info", + "parent": "" + }, + "#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "" + }, + "#/paths/~1pets": { + "type": "path-item", + "pointer": "/paths/~1pets", + "parent": "#/paths" + }, + "#/paths/~1pets/get": { + "type": "operation", + "pointer": "/paths/~1pets/get", + "parent": "#/paths/~1pets" + }, + "#/paths/~1pets/get/callbacks/onData": { + "type": "callback", + "pointer": "/paths/~1pets/get/callbacks/onData", + "parent": "#/paths/~1pets/get" + }, + "#/paths/~1pets/get/callbacks/onData/%7B$request.body%23~1url%7D": { + "type": "path-item", + "pointer": "/paths/~1pets/get/callbacks/onData/{$request.body#~1url}", + "parent": "#/paths/~1pets/get/callbacks/onData" + }, + "#/paths/~1pets/get/callbacks/onData/%7B$request.body%23~1url%7D/post": { + "type": "operation", + "pointer": "/paths/~1pets/get/callbacks/onData/{$request.body#~1url}/post", + "parent": "#/paths/~1pets/get/callbacks/onData/%7B$request.body%23~1url%7D" + }, + "#/paths/~1pets/get/callbacks/onData/%7B$request.body%23~1url%7D/post/responses": { + "type": "responses", + "pointer": "/paths/~1pets/get/callbacks/onData/{$request.body#~1url}/post/responses", + "parent": "#/paths/~1pets/get/callbacks/onData/%7B$request.body%23~1url%7D/post" + }, + "#/paths/~1pets/get/callbacks/onData/%7B$request.body%23~1url%7D/post/responses/200": { + "type": "response", + "pointer": "/paths/~1pets/get/callbacks/onData/{$request.body#~1url}/post/responses/200", + "parent": "#/paths/~1pets/get/callbacks/onData/%7B$request.body%23~1url%7D/post/responses" + }, + "#/paths/~1pets/get/externalDocs": { + "type": "external-documentation", + "pointer": "/paths/~1pets/get/externalDocs", + "parent": "#/paths/~1pets/get" + }, + "#/paths/~1pets/get/parameters/0": { + "type": "parameter", + "pointer": "/paths/~1pets/get/parameters/0", + "parent": "#/paths/~1pets/get" + }, + "#/paths/~1pets/get/parameters/0/schema": { + "type": "schema", + "pointer": "/paths/~1pets/get/parameters/0/schema", + "parent": "#/paths/~1pets/get/parameters/0", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/paths/~1pets/get/requestBody": { + "type": "request-body", + "pointer": "/paths/~1pets/get/requestBody", + "parent": "#/paths/~1pets/get" + }, + "#/paths/~1pets/get/requestBody/content/application~1json": { + "type": "media-type", + "pointer": "/paths/~1pets/get/requestBody/content/application~1json", + "parent": "#/paths/~1pets/get/requestBody" + }, + "#/paths/~1pets/get/requestBody/content/application~1json/schema": { + "type": "schema", + "pointer": "/paths/~1pets/get/requestBody/content/application~1json/schema", + "parent": "#/paths/~1pets/get/requestBody/content/application~1json", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/paths/~1pets/get/responses": { + "type": "responses", + "pointer": "/paths/~1pets/get/responses", + "parent": "#/paths/~1pets/get" + }, + "#/paths/~1pets/get/responses/200": { + "type": "response", + "pointer": "/paths/~1pets/get/responses/200", + "parent": "#/paths/~1pets/get/responses" + }, + "#/paths/~1pets/get/responses/200/content/application~1json": { + "type": "media-type", + "pointer": "/paths/~1pets/get/responses/200/content/application~1json", + "parent": "#/paths/~1pets/get/responses/200" + }, + "#/paths/~1pets/get/responses/200/content/application~1json/schema": { + "type": "schema", + "pointer": "/paths/~1pets/get/responses/200/content/application~1json/schema", + "parent": "#/paths/~1pets/get/responses/200/content/application~1json", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/paths/~1pets/get/responses/200/headers/X-Rate": { + "type": "header", + "pointer": "/paths/~1pets/get/responses/200/headers/X-Rate", + "parent": "#/paths/~1pets/get/responses/200" + }, + "#/paths/~1pets/get/responses/200/headers/X-Rate/schema": { + "type": "schema", + "pointer": "/paths/~1pets/get/responses/200/headers/X-Rate/schema", + "parent": "#/paths/~1pets/get/responses/200/headers/X-Rate", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/paths/~1pets/get/responses/200/links/Self": { + "type": "link", + "pointer": "/paths/~1pets/get/responses/200/links/Self", + "parent": "#/paths/~1pets/get/responses/200" + }, + "#/paths/~1pets/get/responses/4XX": { + "type": "response", + "pointer": "/paths/~1pets/get/responses/4XX", + "parent": "#/paths/~1pets/get/responses" + }, + "#/paths/~1pets/get/responses/default": { + "type": "response", + "pointer": "/paths/~1pets/get/responses/default", + "parent": "#/paths/~1pets/get/responses" + }, + "#/paths/~1pets/get/security/0": { + "type": "security-requirement", + "pointer": "/paths/~1pets/get/security/0", + "parent": "#/paths/~1pets/get" + }, + "#/paths/~1pets/get/servers/0": { + "type": "server", + "pointer": "/paths/~1pets/get/servers/0", + "parent": "#/paths/~1pets/get" + }, + "#/tags/0": { + "type": "tag", + "pointer": "/tags/0", + "parent": "" + } + }, + "operations": [ + { + "type": "path", + "path": "/pets", + "method": "get", + "origin": "#/paths/~1pets/get", + "endpoint": "#/paths/~1pets", + "tags": [ + "#/tags/0" + ], + "servers": [ + "#/paths/~1pets/get/servers/0" + ], + "security": [ + "#/paths/~1pets/get/security/0" + ], + "parameters": [ + "#/paths/~1pets/get/parameters/0" + ] + }, + { + "type": "callback", + "path": "{$request.body#/url}", + "method": "post", + "origin": "#/paths/~1pets/get/callbacks/onData/%7B$request.body%23~1url%7D/post", + "endpoint": "#/paths/~1pets/get/callbacks/onData/%7B$request.body%23~1url%7D", + "tags": [], + "servers": [], + "security": [], + "parameters": [] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/operations_from_a_callback_behind_a_reference.json b/test/openapi/frame/3.1/pass/operations_from_a_callback_behind_a_reference.json new file mode 100644 index 000000000..684429da2 --- /dev/null +++ b/test/openapi/frame/3.1/pass/operations_from_a_callback_behind_a_reference.json @@ -0,0 +1,154 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "callbacks": { + "OnData": { + "{$request.body#/url}": { + "post": { + "responses": { + "200": { + "description": "ok" + } + } + } + } + } + } + }, + "paths": { + "/pets": { + "get": { + "responses": { + "200": { + "description": "ok" + } + }, + "callbacks": { + "onData": { + "$ref": "#/components/callbacks/OnData" + } + } + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/callbacks/OnData": { + "type": "callback", + "pointer": "/components/callbacks/OnData", + "parent": "https://example.com/openapi.json#/components" + }, + "https://example.com/openapi.json#/components/callbacks/OnData/%7B$request.body%23~1url%7D": { + "type": "path-item", + "pointer": "/components/callbacks/OnData/{$request.body#~1url}", + "parent": "https://example.com/openapi.json#/components/callbacks/OnData" + }, + "https://example.com/openapi.json#/components/callbacks/OnData/%7B$request.body%23~1url%7D/post": { + "type": "operation", + "pointer": "/components/callbacks/OnData/{$request.body#~1url}/post", + "parent": "https://example.com/openapi.json#/components/callbacks/OnData/%7B$request.body%23~1url%7D" + }, + "https://example.com/openapi.json#/components/callbacks/OnData/%7B$request.body%23~1url%7D/post/responses": { + "type": "responses", + "pointer": "/components/callbacks/OnData/{$request.body#~1url}/post/responses", + "parent": "https://example.com/openapi.json#/components/callbacks/OnData/%7B$request.body%23~1url%7D/post" + }, + "https://example.com/openapi.json#/components/callbacks/OnData/%7B$request.body%23~1url%7D/post/responses/200": { + "type": "response", + "pointer": "/components/callbacks/OnData/{$request.body#~1url}/post/responses/200", + "parent": "https://example.com/openapi.json#/components/callbacks/OnData/%7B$request.body%23~1url%7D/post/responses" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1pets": { + "type": "path-item", + "pointer": "/paths/~1pets", + "parent": "https://example.com/openapi.json#/paths" + }, + "https://example.com/openapi.json#/paths/~1pets/get": { + "type": "operation", + "pointer": "/paths/~1pets/get", + "parent": "https://example.com/openapi.json#/paths/~1pets" + }, + "https://example.com/openapi.json#/paths/~1pets/get/callbacks/onData": { + "type": "reference", + "pointer": "/paths/~1pets/get/callbacks/onData", + "parent": "https://example.com/openapi.json#/paths/~1pets/get", + "original": "#/components/callbacks/OnData", + "destination": "https://example.com/openapi.json#/components/callbacks/OnData" + }, + "https://example.com/openapi.json#/paths/~1pets/get/responses": { + "type": "responses", + "pointer": "/paths/~1pets/get/responses", + "parent": "https://example.com/openapi.json#/paths/~1pets/get" + }, + "https://example.com/openapi.json#/paths/~1pets/get/responses/200": { + "type": "response", + "pointer": "/paths/~1pets/get/responses/200", + "parent": "https://example.com/openapi.json#/paths/~1pets/get/responses" + } + }, + "operations": [ + { + "type": "path", + "path": "/pets", + "method": "get", + "origin": "https://example.com/openapi.json#/paths/~1pets/get", + "endpoint": "https://example.com/openapi.json#/paths/~1pets", + "tags": [], + "servers": [], + "security": [], + "parameters": [] + }, + { + "type": "callback", + "path": "{$request.body#/url}", + "method": "post", + "origin": "https://example.com/openapi.json#/components/callbacks/OnData/%7B$request.body%23~1url%7D/post", + "endpoint": "https://example.com/openapi.json#/components/callbacks/OnData/%7B$request.body%23~1url%7D", + "tags": [], + "servers": [], + "security": [], + "parameters": [] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/operations_from_a_path_item_reference_cycle.json b/test/openapi/frame/3.1/pass/operations_from_a_path_item_reference_cycle.json new file mode 100644 index 000000000..b0d783f74 --- /dev/null +++ b/test/openapi/frame/3.1/pass/operations_from_a_path_item_reference_cycle.json @@ -0,0 +1,84 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "pathItems": { + "A": { + "$ref": "#/components/pathItems/B" + }, + "B": { + "$ref": "#/components/pathItems/A" + } + } + }, + "paths": { + "/pets": { + "$ref": "#/components/pathItems/A" + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/pathItems/A": { + "type": "path-item", + "pointer": "/components/pathItems/A", + "parent": "https://example.com/openapi.json#/components", + "original": "#/components/pathItems/B", + "destination": "https://example.com/openapi.json#/components/pathItems/B" + }, + "https://example.com/openapi.json#/components/pathItems/B": { + "type": "path-item", + "pointer": "/components/pathItems/B", + "parent": "https://example.com/openapi.json#/components", + "original": "#/components/pathItems/A", + "destination": "https://example.com/openapi.json#/components/pathItems/A" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1pets": { + "type": "path-item", + "pointer": "/paths/~1pets", + "parent": "https://example.com/openapi.json#/paths", + "original": "#/components/pathItems/A", + "destination": "https://example.com/openapi.json#/components/pathItems/A" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/operations_from_a_shared_path_item.json b/test/openapi/frame/3.1/pass/operations_from_a_shared_path_item.json new file mode 100644 index 000000000..64426e120 --- /dev/null +++ b/test/openapi/frame/3.1/pass/operations_from_a_shared_path_item.json @@ -0,0 +1,126 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "pathItems": { + "Shared": { + "get": { + "responses": { + "200": { + "description": "ok" + } + } + } + } + } + }, + "paths": { + "/a": { + "$ref": "#/components/pathItems/Shared" + }, + "/b": { + "$ref": "#/components/pathItems/Shared" + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/pathItems/Shared": { + "type": "path-item", + "pointer": "/components/pathItems/Shared", + "parent": "https://example.com/openapi.json#/components" + }, + "https://example.com/openapi.json#/components/pathItems/Shared/get": { + "type": "operation", + "pointer": "/components/pathItems/Shared/get", + "parent": "https://example.com/openapi.json#/components/pathItems/Shared" + }, + "https://example.com/openapi.json#/components/pathItems/Shared/get/responses": { + "type": "responses", + "pointer": "/components/pathItems/Shared/get/responses", + "parent": "https://example.com/openapi.json#/components/pathItems/Shared/get" + }, + "https://example.com/openapi.json#/components/pathItems/Shared/get/responses/200": { + "type": "response", + "pointer": "/components/pathItems/Shared/get/responses/200", + "parent": "https://example.com/openapi.json#/components/pathItems/Shared/get/responses" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1a": { + "type": "path-item", + "pointer": "/paths/~1a", + "parent": "https://example.com/openapi.json#/paths", + "original": "#/components/pathItems/Shared", + "destination": "https://example.com/openapi.json#/components/pathItems/Shared" + }, + "https://example.com/openapi.json#/paths/~1b": { + "type": "path-item", + "pointer": "/paths/~1b", + "parent": "https://example.com/openapi.json#/paths", + "original": "#/components/pathItems/Shared", + "destination": "https://example.com/openapi.json#/components/pathItems/Shared" + } + }, + "operations": [ + { + "type": "path", + "path": "/a", + "method": "get", + "origin": "https://example.com/openapi.json#/components/pathItems/Shared/get", + "endpoint": "https://example.com/openapi.json#/paths/~1a", + "tags": [], + "servers": [], + "security": [], + "parameters": [] + }, + { + "type": "path", + "path": "/b", + "method": "get", + "origin": "https://example.com/openapi.json#/components/pathItems/Shared/get", + "endpoint": "https://example.com/openapi.json#/paths/~1b", + "tags": [], + "servers": [], + "security": [], + "parameters": [] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/operations_from_an_external_path_item.json b/test/openapi/frame/3.1/pass/operations_from_an_external_path_item.json new file mode 100644 index 000000000..39fe99350 --- /dev/null +++ b/test/openapi/frame/3.1/pass/operations_from_an_external_path_item.json @@ -0,0 +1,158 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets": { + "$ref": "https://example.com/common.json#/components/pathItems/Pets" + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/common.json": { + "openapi": "3.1.1", + "info": { + "title": "Common", + "version": "1.0.0" + }, + "components": { + "pathItems": { + "Pets": { + "get": { + "responses": { + "200": { + "description": "ok" + } + } + }, + "post": { + "responses": { + "200": { + "description": "ok" + } + } + } + } + } + } + } + }, + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/common.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/common.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/common.json" + }, + "https://example.com/common.json#/components/pathItems/Pets": { + "type": "path-item", + "pointer": "/components/pathItems/Pets", + "parent": "https://example.com/common.json#/components" + }, + "https://example.com/common.json#/components/pathItems/Pets/get": { + "type": "operation", + "pointer": "/components/pathItems/Pets/get", + "parent": "https://example.com/common.json#/components/pathItems/Pets" + }, + "https://example.com/common.json#/components/pathItems/Pets/get/responses": { + "type": "responses", + "pointer": "/components/pathItems/Pets/get/responses", + "parent": "https://example.com/common.json#/components/pathItems/Pets/get" + }, + "https://example.com/common.json#/components/pathItems/Pets/get/responses/200": { + "type": "response", + "pointer": "/components/pathItems/Pets/get/responses/200", + "parent": "https://example.com/common.json#/components/pathItems/Pets/get/responses" + }, + "https://example.com/common.json#/components/pathItems/Pets/post": { + "type": "operation", + "pointer": "/components/pathItems/Pets/post", + "parent": "https://example.com/common.json#/components/pathItems/Pets" + }, + "https://example.com/common.json#/components/pathItems/Pets/post/responses": { + "type": "responses", + "pointer": "/components/pathItems/Pets/post/responses", + "parent": "https://example.com/common.json#/components/pathItems/Pets/post" + }, + "https://example.com/common.json#/components/pathItems/Pets/post/responses/200": { + "type": "response", + "pointer": "/components/pathItems/Pets/post/responses/200", + "parent": "https://example.com/common.json#/components/pathItems/Pets/post/responses" + }, + "https://example.com/common.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/common.json" + }, + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1pets": { + "type": "path-item", + "pointer": "/paths/~1pets", + "parent": "https://example.com/openapi.json#/paths", + "original": "https://example.com/common.json#/components/pathItems/Pets", + "destination": "https://example.com/common.json#/components/pathItems/Pets" + } + }, + "operations": [ + { + "type": "path", + "path": "/pets", + "method": "get", + "origin": "https://example.com/common.json#/components/pathItems/Pets/get", + "endpoint": "https://example.com/openapi.json#/paths/~1pets", + "tags": [], + "servers": [], + "security": [], + "parameters": [] + }, + { + "type": "path", + "path": "/pets", + "method": "post", + "origin": "https://example.com/common.json#/components/pathItems/Pets/post", + "endpoint": "https://example.com/openapi.json#/paths/~1pets", + "tags": [], + "servers": [], + "security": [], + "parameters": [] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/operations_in_a_referenced_document_are_not_endpoints.json b/test/openapi/frame/3.1/pass/operations_in_a_referenced_document_are_not_endpoints.json new file mode 100644 index 000000000..a8f528e04 --- /dev/null +++ b/test/openapi/frame/3.1/pass/operations_in_a_referenced_document_are_not_endpoints.json @@ -0,0 +1,178 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "R": { + "$ref": "https://example.com/common.json#/components/responses/NotFound" + } + } + }, + "paths": { + "/pets": { + "get": { + "responses": { + "200": { + "description": "ok" + } + } + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/common.json": { + "openapi": "3.1.1", + "info": { + "title": "Common", + "version": "1.0.0" + }, + "components": { + "responses": { + "NotFound": { + "description": "ok" + } + } + }, + "paths": { + "/other": { + "get": { + "responses": { + "200": { + "description": "ok" + } + } + } + } + } + } + }, + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/common.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/common.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/common.json" + }, + "https://example.com/common.json#/components/responses/NotFound": { + "type": "response", + "pointer": "/components/responses/NotFound", + "parent": "https://example.com/common.json#/components" + }, + "https://example.com/common.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/common.json" + }, + "https://example.com/common.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/common.json" + }, + "https://example.com/common.json#/paths/~1other": { + "type": "path-item", + "pointer": "/paths/~1other", + "parent": "https://example.com/common.json#/paths" + }, + "https://example.com/common.json#/paths/~1other/get": { + "type": "operation", + "pointer": "/paths/~1other/get", + "parent": "https://example.com/common.json#/paths/~1other" + }, + "https://example.com/common.json#/paths/~1other/get/responses": { + "type": "responses", + "pointer": "/paths/~1other/get/responses", + "parent": "https://example.com/common.json#/paths/~1other/get" + }, + "https://example.com/common.json#/paths/~1other/get/responses/200": { + "type": "response", + "pointer": "/paths/~1other/get/responses/200", + "parent": "https://example.com/common.json#/paths/~1other/get/responses" + }, + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/responses/R": { + "type": "reference", + "pointer": "/components/responses/R", + "parent": "https://example.com/openapi.json#/components", + "original": "https://example.com/common.json#/components/responses/NotFound", + "destination": "https://example.com/common.json#/components/responses/NotFound" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1pets": { + "type": "path-item", + "pointer": "/paths/~1pets", + "parent": "https://example.com/openapi.json#/paths" + }, + "https://example.com/openapi.json#/paths/~1pets/get": { + "type": "operation", + "pointer": "/paths/~1pets/get", + "parent": "https://example.com/openapi.json#/paths/~1pets" + }, + "https://example.com/openapi.json#/paths/~1pets/get/responses": { + "type": "responses", + "pointer": "/paths/~1pets/get/responses", + "parent": "https://example.com/openapi.json#/paths/~1pets/get" + }, + "https://example.com/openapi.json#/paths/~1pets/get/responses/200": { + "type": "response", + "pointer": "/paths/~1pets/get/responses/200", + "parent": "https://example.com/openapi.json#/paths/~1pets/get/responses" + } + }, + "operations": [ + { + "type": "path", + "path": "/pets", + "method": "get", + "origin": "https://example.com/openapi.json#/paths/~1pets/get", + "endpoint": "https://example.com/openapi.json#/paths/~1pets", + "tags": [], + "servers": [], + "security": [], + "parameters": [] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/operations_parameters_cannot_be_removed.json b/test/openapi/frame/3.1/pass/operations_parameters_cannot_be_removed.json new file mode 100644 index 000000000..9a3e3b962 --- /dev/null +++ b/test/openapi/frame/3.1/pass/operations_parameters_cannot_be_removed.json @@ -0,0 +1,107 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets": { + "parameters": [ + { + "name": "limit", + "in": "query", + "schema": {} + } + ], + "get": { + "responses": { + "200": { + "description": "ok" + } + }, + "parameters": [] + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1pets": { + "type": "path-item", + "pointer": "/paths/~1pets", + "parent": "https://example.com/openapi.json#/paths" + }, + "https://example.com/openapi.json#/paths/~1pets/get": { + "type": "operation", + "pointer": "/paths/~1pets/get", + "parent": "https://example.com/openapi.json#/paths/~1pets" + }, + "https://example.com/openapi.json#/paths/~1pets/get/responses": { + "type": "responses", + "pointer": "/paths/~1pets/get/responses", + "parent": "https://example.com/openapi.json#/paths/~1pets/get" + }, + "https://example.com/openapi.json#/paths/~1pets/get/responses/200": { + "type": "response", + "pointer": "/paths/~1pets/get/responses/200", + "parent": "https://example.com/openapi.json#/paths/~1pets/get/responses" + }, + "https://example.com/openapi.json#/paths/~1pets/parameters/0": { + "type": "parameter", + "pointer": "/paths/~1pets/parameters/0", + "parent": "https://example.com/openapi.json#/paths/~1pets" + }, + "https://example.com/openapi.json#/paths/~1pets/parameters/0/schema": { + "type": "schema", + "pointer": "/paths/~1pets/parameters/0/schema", + "parent": "https://example.com/openapi.json#/paths/~1pets/parameters/0", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + } + }, + "operations": [ + { + "type": "path", + "path": "/pets", + "method": "get", + "origin": "https://example.com/openapi.json#/paths/~1pets/get", + "endpoint": "https://example.com/openapi.json#/paths/~1pets", + "tags": [], + "servers": [], + "security": [], + "parameters": [ + "https://example.com/openapi.json#/paths/~1pets/parameters/0" + ] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/operations_parameters_differ_by_location.json b/test/openapi/frame/3.1/pass/operations_parameters_differ_by_location.json new file mode 100644 index 000000000..48325af17 --- /dev/null +++ b/test/openapi/frame/3.1/pass/operations_parameters_differ_by_location.json @@ -0,0 +1,125 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets": { + "parameters": [ + { + "name": "token", + "in": "query", + "schema": {} + } + ], + "get": { + "responses": { + "200": { + "description": "ok" + } + }, + "parameters": [ + { + "name": "token", + "in": "header", + "schema": {} + } + ] + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1pets": { + "type": "path-item", + "pointer": "/paths/~1pets", + "parent": "https://example.com/openapi.json#/paths" + }, + "https://example.com/openapi.json#/paths/~1pets/get": { + "type": "operation", + "pointer": "/paths/~1pets/get", + "parent": "https://example.com/openapi.json#/paths/~1pets" + }, + "https://example.com/openapi.json#/paths/~1pets/get/parameters/0": { + "type": "parameter", + "pointer": "/paths/~1pets/get/parameters/0", + "parent": "https://example.com/openapi.json#/paths/~1pets/get" + }, + "https://example.com/openapi.json#/paths/~1pets/get/parameters/0/schema": { + "type": "schema", + "pointer": "/paths/~1pets/get/parameters/0/schema", + "parent": "https://example.com/openapi.json#/paths/~1pets/get/parameters/0", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/paths/~1pets/get/responses": { + "type": "responses", + "pointer": "/paths/~1pets/get/responses", + "parent": "https://example.com/openapi.json#/paths/~1pets/get" + }, + "https://example.com/openapi.json#/paths/~1pets/get/responses/200": { + "type": "response", + "pointer": "/paths/~1pets/get/responses/200", + "parent": "https://example.com/openapi.json#/paths/~1pets/get/responses" + }, + "https://example.com/openapi.json#/paths/~1pets/parameters/0": { + "type": "parameter", + "pointer": "/paths/~1pets/parameters/0", + "parent": "https://example.com/openapi.json#/paths/~1pets" + }, + "https://example.com/openapi.json#/paths/~1pets/parameters/0/schema": { + "type": "schema", + "pointer": "/paths/~1pets/parameters/0/schema", + "parent": "https://example.com/openapi.json#/paths/~1pets/parameters/0", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + } + }, + "operations": [ + { + "type": "path", + "path": "/pets", + "method": "get", + "origin": "https://example.com/openapi.json#/paths/~1pets/get", + "endpoint": "https://example.com/openapi.json#/paths/~1pets", + "tags": [], + "servers": [], + "security": [], + "parameters": [ + "https://example.com/openapi.json#/paths/~1pets/parameters/0", + "https://example.com/openapi.json#/paths/~1pets/get/parameters/0" + ] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/operations_parameters_from_an_unresolved_reference.json b/test/openapi/frame/3.1/pass/operations_parameters_from_an_unresolved_reference.json new file mode 100644 index 000000000..5813472f0 --- /dev/null +++ b/test/openapi/frame/3.1/pass/operations_parameters_from_an_unresolved_reference.json @@ -0,0 +1,122 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets": { + "parameters": [ + { + "$ref": "https://example.com/common.json#/components/parameters/Limit" + } + ], + "get": { + "responses": { + "200": { + "description": "ok" + } + }, + "parameters": [ + { + "name": "limit", + "in": "query", + "schema": {} + } + ] + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": false, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1pets": { + "type": "path-item", + "pointer": "/paths/~1pets", + "parent": "https://example.com/openapi.json#/paths" + }, + "https://example.com/openapi.json#/paths/~1pets/get": { + "type": "operation", + "pointer": "/paths/~1pets/get", + "parent": "https://example.com/openapi.json#/paths/~1pets" + }, + "https://example.com/openapi.json#/paths/~1pets/get/parameters/0": { + "type": "parameter", + "pointer": "/paths/~1pets/get/parameters/0", + "parent": "https://example.com/openapi.json#/paths/~1pets/get" + }, + "https://example.com/openapi.json#/paths/~1pets/get/parameters/0/schema": { + "type": "schema", + "pointer": "/paths/~1pets/get/parameters/0/schema", + "parent": "https://example.com/openapi.json#/paths/~1pets/get/parameters/0", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/paths/~1pets/get/responses": { + "type": "responses", + "pointer": "/paths/~1pets/get/responses", + "parent": "https://example.com/openapi.json#/paths/~1pets/get" + }, + "https://example.com/openapi.json#/paths/~1pets/get/responses/200": { + "type": "response", + "pointer": "/paths/~1pets/get/responses/200", + "parent": "https://example.com/openapi.json#/paths/~1pets/get/responses" + }, + "https://example.com/openapi.json#/paths/~1pets/parameters/0": { + "type": "reference", + "pointer": "/paths/~1pets/parameters/0", + "parent": "https://example.com/openapi.json#/paths/~1pets", + "original": "https://example.com/common.json#/components/parameters/Limit", + "destination": "https://example.com/common.json#/components/parameters/Limit" + } + }, + "operations": [ + { + "type": "path", + "path": "/pets", + "method": "get", + "origin": "https://example.com/openapi.json#/paths/~1pets/get", + "endpoint": "https://example.com/openapi.json#/paths/~1pets", + "tags": [], + "servers": [], + "security": [], + "parameters": [ + "https://example.com/openapi.json#/paths/~1pets/parameters/0", + "https://example.com/openapi.json#/paths/~1pets/get/parameters/0" + ] + } + ] + }, + "dangling": [ + "https://example.com/common.json#/components/parameters/Limit" + ] +} diff --git a/test/openapi/frame/3.1/pass/operations_parameters_from_the_path_item.json b/test/openapi/frame/3.1/pass/operations_parameters_from_the_path_item.json new file mode 100644 index 000000000..1cd2ea6b9 --- /dev/null +++ b/test/openapi/frame/3.1/pass/operations_parameters_from_the_path_item.json @@ -0,0 +1,123 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets": { + "parameters": [ + { + "name": "limit", + "in": "query", + "schema": {} + }, + { + "name": "offset", + "in": "query", + "schema": {} + } + ], + "get": { + "responses": { + "200": { + "description": "ok" + } + } + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1pets": { + "type": "path-item", + "pointer": "/paths/~1pets", + "parent": "https://example.com/openapi.json#/paths" + }, + "https://example.com/openapi.json#/paths/~1pets/get": { + "type": "operation", + "pointer": "/paths/~1pets/get", + "parent": "https://example.com/openapi.json#/paths/~1pets" + }, + "https://example.com/openapi.json#/paths/~1pets/get/responses": { + "type": "responses", + "pointer": "/paths/~1pets/get/responses", + "parent": "https://example.com/openapi.json#/paths/~1pets/get" + }, + "https://example.com/openapi.json#/paths/~1pets/get/responses/200": { + "type": "response", + "pointer": "/paths/~1pets/get/responses/200", + "parent": "https://example.com/openapi.json#/paths/~1pets/get/responses" + }, + "https://example.com/openapi.json#/paths/~1pets/parameters/0": { + "type": "parameter", + "pointer": "/paths/~1pets/parameters/0", + "parent": "https://example.com/openapi.json#/paths/~1pets" + }, + "https://example.com/openapi.json#/paths/~1pets/parameters/0/schema": { + "type": "schema", + "pointer": "/paths/~1pets/parameters/0/schema", + "parent": "https://example.com/openapi.json#/paths/~1pets/parameters/0", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/paths/~1pets/parameters/1": { + "type": "parameter", + "pointer": "/paths/~1pets/parameters/1", + "parent": "https://example.com/openapi.json#/paths/~1pets" + }, + "https://example.com/openapi.json#/paths/~1pets/parameters/1/schema": { + "type": "schema", + "pointer": "/paths/~1pets/parameters/1/schema", + "parent": "https://example.com/openapi.json#/paths/~1pets/parameters/1", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + } + }, + "operations": [ + { + "type": "path", + "path": "/pets", + "method": "get", + "origin": "https://example.com/openapi.json#/paths/~1pets/get", + "endpoint": "https://example.com/openapi.json#/paths/~1pets", + "tags": [], + "servers": [], + "security": [], + "parameters": [ + "https://example.com/openapi.json#/paths/~1pets/parameters/0", + "https://example.com/openapi.json#/paths/~1pets/parameters/1" + ] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/operations_parameters_overridden_by_the_operation.json b/test/openapi/frame/3.1/pass/operations_parameters_overridden_by_the_operation.json new file mode 100644 index 000000000..b71bb80bd --- /dev/null +++ b/test/openapi/frame/3.1/pass/operations_parameters_overridden_by_the_operation.json @@ -0,0 +1,142 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets": { + "parameters": [ + { + "name": "limit", + "in": "query", + "schema": {} + }, + { + "name": "offset", + "in": "query", + "schema": {} + } + ], + "get": { + "responses": { + "200": { + "description": "ok" + } + }, + "parameters": [ + { + "name": "limit", + "in": "query", + "schema": {}, + "description": "Narrower" + } + ] + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1pets": { + "type": "path-item", + "pointer": "/paths/~1pets", + "parent": "https://example.com/openapi.json#/paths" + }, + "https://example.com/openapi.json#/paths/~1pets/get": { + "type": "operation", + "pointer": "/paths/~1pets/get", + "parent": "https://example.com/openapi.json#/paths/~1pets" + }, + "https://example.com/openapi.json#/paths/~1pets/get/parameters/0": { + "type": "parameter", + "pointer": "/paths/~1pets/get/parameters/0", + "parent": "https://example.com/openapi.json#/paths/~1pets/get" + }, + "https://example.com/openapi.json#/paths/~1pets/get/parameters/0/schema": { + "type": "schema", + "pointer": "/paths/~1pets/get/parameters/0/schema", + "parent": "https://example.com/openapi.json#/paths/~1pets/get/parameters/0", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/paths/~1pets/get/responses": { + "type": "responses", + "pointer": "/paths/~1pets/get/responses", + "parent": "https://example.com/openapi.json#/paths/~1pets/get" + }, + "https://example.com/openapi.json#/paths/~1pets/get/responses/200": { + "type": "response", + "pointer": "/paths/~1pets/get/responses/200", + "parent": "https://example.com/openapi.json#/paths/~1pets/get/responses" + }, + "https://example.com/openapi.json#/paths/~1pets/parameters/0": { + "type": "parameter", + "pointer": "/paths/~1pets/parameters/0", + "parent": "https://example.com/openapi.json#/paths/~1pets" + }, + "https://example.com/openapi.json#/paths/~1pets/parameters/0/schema": { + "type": "schema", + "pointer": "/paths/~1pets/parameters/0/schema", + "parent": "https://example.com/openapi.json#/paths/~1pets/parameters/0", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/paths/~1pets/parameters/1": { + "type": "parameter", + "pointer": "/paths/~1pets/parameters/1", + "parent": "https://example.com/openapi.json#/paths/~1pets" + }, + "https://example.com/openapi.json#/paths/~1pets/parameters/1/schema": { + "type": "schema", + "pointer": "/paths/~1pets/parameters/1/schema", + "parent": "https://example.com/openapi.json#/paths/~1pets/parameters/1", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + } + }, + "operations": [ + { + "type": "path", + "path": "/pets", + "method": "get", + "origin": "https://example.com/openapi.json#/paths/~1pets/get", + "endpoint": "https://example.com/openapi.json#/paths/~1pets", + "tags": [], + "servers": [], + "security": [], + "parameters": [ + "https://example.com/openapi.json#/paths/~1pets/parameters/1", + "https://example.com/openapi.json#/paths/~1pets/get/parameters/0" + ] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/operations_parameters_overridden_through_a_reference.json b/test/openapi/frame/3.1/pass/operations_parameters_overridden_through_a_reference.json new file mode 100644 index 000000000..99b489956 --- /dev/null +++ b/test/openapi/frame/3.1/pass/operations_parameters_overridden_through_a_reference.json @@ -0,0 +1,144 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "parameters": { + "Limit": { + "name": "limit", + "in": "query", + "schema": {} + } + } + }, + "paths": { + "/pets": { + "parameters": [ + { + "$ref": "#/components/parameters/Limit" + } + ], + "get": { + "responses": { + "200": { + "description": "ok" + } + }, + "parameters": [ + { + "name": "limit", + "in": "query", + "schema": {}, + "description": "Narrower" + } + ] + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/parameters/Limit": { + "type": "parameter", + "pointer": "/components/parameters/Limit", + "parent": "https://example.com/openapi.json#/components" + }, + "https://example.com/openapi.json#/components/parameters/Limit/schema": { + "type": "schema", + "pointer": "/components/parameters/Limit/schema", + "parent": "https://example.com/openapi.json#/components/parameters/Limit", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1pets": { + "type": "path-item", + "pointer": "/paths/~1pets", + "parent": "https://example.com/openapi.json#/paths" + }, + "https://example.com/openapi.json#/paths/~1pets/get": { + "type": "operation", + "pointer": "/paths/~1pets/get", + "parent": "https://example.com/openapi.json#/paths/~1pets" + }, + "https://example.com/openapi.json#/paths/~1pets/get/parameters/0": { + "type": "parameter", + "pointer": "/paths/~1pets/get/parameters/0", + "parent": "https://example.com/openapi.json#/paths/~1pets/get" + }, + "https://example.com/openapi.json#/paths/~1pets/get/parameters/0/schema": { + "type": "schema", + "pointer": "/paths/~1pets/get/parameters/0/schema", + "parent": "https://example.com/openapi.json#/paths/~1pets/get/parameters/0", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/paths/~1pets/get/responses": { + "type": "responses", + "pointer": "/paths/~1pets/get/responses", + "parent": "https://example.com/openapi.json#/paths/~1pets/get" + }, + "https://example.com/openapi.json#/paths/~1pets/get/responses/200": { + "type": "response", + "pointer": "/paths/~1pets/get/responses/200", + "parent": "https://example.com/openapi.json#/paths/~1pets/get/responses" + }, + "https://example.com/openapi.json#/paths/~1pets/parameters/0": { + "type": "reference", + "pointer": "/paths/~1pets/parameters/0", + "parent": "https://example.com/openapi.json#/paths/~1pets", + "original": "#/components/parameters/Limit", + "destination": "https://example.com/openapi.json#/components/parameters/Limit" + } + }, + "operations": [ + { + "type": "path", + "path": "/pets", + "method": "get", + "origin": "https://example.com/openapi.json#/paths/~1pets/get", + "endpoint": "https://example.com/openapi.json#/paths/~1pets", + "tags": [], + "servers": [], + "security": [], + "parameters": [ + "https://example.com/openapi.json#/paths/~1pets/get/parameters/0" + ] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/operations_parameters_overriding_through_a_reference.json b/test/openapi/frame/3.1/pass/operations_parameters_overriding_through_a_reference.json new file mode 100644 index 000000000..4587b03ed --- /dev/null +++ b/test/openapi/frame/3.1/pass/operations_parameters_overriding_through_a_reference.json @@ -0,0 +1,144 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "parameters": { + "Limit": { + "name": "limit", + "in": "query", + "schema": {}, + "description": "Shared" + } + } + }, + "paths": { + "/pets": { + "parameters": [ + { + "name": "limit", + "in": "query", + "schema": {} + } + ], + "get": { + "responses": { + "200": { + "description": "ok" + } + }, + "parameters": [ + { + "$ref": "#/components/parameters/Limit" + } + ] + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/parameters/Limit": { + "type": "parameter", + "pointer": "/components/parameters/Limit", + "parent": "https://example.com/openapi.json#/components" + }, + "https://example.com/openapi.json#/components/parameters/Limit/schema": { + "type": "schema", + "pointer": "/components/parameters/Limit/schema", + "parent": "https://example.com/openapi.json#/components/parameters/Limit", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1pets": { + "type": "path-item", + "pointer": "/paths/~1pets", + "parent": "https://example.com/openapi.json#/paths" + }, + "https://example.com/openapi.json#/paths/~1pets/get": { + "type": "operation", + "pointer": "/paths/~1pets/get", + "parent": "https://example.com/openapi.json#/paths/~1pets" + }, + "https://example.com/openapi.json#/paths/~1pets/get/parameters/0": { + "type": "reference", + "pointer": "/paths/~1pets/get/parameters/0", + "parent": "https://example.com/openapi.json#/paths/~1pets/get", + "original": "#/components/parameters/Limit", + "destination": "https://example.com/openapi.json#/components/parameters/Limit" + }, + "https://example.com/openapi.json#/paths/~1pets/get/responses": { + "type": "responses", + "pointer": "/paths/~1pets/get/responses", + "parent": "https://example.com/openapi.json#/paths/~1pets/get" + }, + "https://example.com/openapi.json#/paths/~1pets/get/responses/200": { + "type": "response", + "pointer": "/paths/~1pets/get/responses/200", + "parent": "https://example.com/openapi.json#/paths/~1pets/get/responses" + }, + "https://example.com/openapi.json#/paths/~1pets/parameters/0": { + "type": "parameter", + "pointer": "/paths/~1pets/parameters/0", + "parent": "https://example.com/openapi.json#/paths/~1pets" + }, + "https://example.com/openapi.json#/paths/~1pets/parameters/0/schema": { + "type": "schema", + "pointer": "/paths/~1pets/parameters/0/schema", + "parent": "https://example.com/openapi.json#/paths/~1pets/parameters/0", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + } + }, + "operations": [ + { + "type": "path", + "path": "/pets", + "method": "get", + "origin": "https://example.com/openapi.json#/paths/~1pets/get", + "endpoint": "https://example.com/openapi.json#/paths/~1pets", + "tags": [], + "servers": [], + "security": [], + "parameters": [ + "https://example.com/openapi.json#/paths/~1pets/get/parameters/0" + ] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/operations_security_from_the_document.json b/test/openapi/frame/3.1/pass/operations_security_from_the_document.json new file mode 100644 index 000000000..397753116 --- /dev/null +++ b/test/openapi/frame/3.1/pass/operations_security_from_the_document.json @@ -0,0 +1,117 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "security": [ + { + "ApiKey": [] + } + ], + "components": { + "securitySchemes": { + "ApiKey": { + "type": "apiKey", + "name": "X-Key", + "in": "header" + } + } + }, + "paths": { + "/pets": { + "get": { + "responses": { + "200": { + "description": "ok" + } + } + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/securitySchemes/ApiKey": { + "type": "security-scheme", + "pointer": "/components/securitySchemes/ApiKey", + "parent": "https://example.com/openapi.json#/components" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1pets": { + "type": "path-item", + "pointer": "/paths/~1pets", + "parent": "https://example.com/openapi.json#/paths" + }, + "https://example.com/openapi.json#/paths/~1pets/get": { + "type": "operation", + "pointer": "/paths/~1pets/get", + "parent": "https://example.com/openapi.json#/paths/~1pets" + }, + "https://example.com/openapi.json#/paths/~1pets/get/responses": { + "type": "responses", + "pointer": "/paths/~1pets/get/responses", + "parent": "https://example.com/openapi.json#/paths/~1pets/get" + }, + "https://example.com/openapi.json#/paths/~1pets/get/responses/200": { + "type": "response", + "pointer": "/paths/~1pets/get/responses/200", + "parent": "https://example.com/openapi.json#/paths/~1pets/get/responses" + }, + "https://example.com/openapi.json#/security/0": { + "type": "security-requirement", + "pointer": "/security/0", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [ + { + "type": "path", + "path": "/pets", + "method": "get", + "origin": "https://example.com/openapi.json#/paths/~1pets/get", + "endpoint": "https://example.com/openapi.json#/paths/~1pets", + "tags": [], + "servers": [], + "security": [ + "https://example.com/openapi.json#/security/0" + ], + "parameters": [] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/operations_security_from_the_operation.json b/test/openapi/frame/3.1/pass/operations_security_from_the_operation.json new file mode 100644 index 000000000..ce959109c --- /dev/null +++ b/test/openapi/frame/3.1/pass/operations_security_from_the_operation.json @@ -0,0 +1,136 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "security": [ + { + "ApiKey": [] + } + ], + "components": { + "securitySchemes": { + "ApiKey": { + "type": "apiKey", + "name": "X-Key", + "in": "header" + }, + "Other": { + "type": "http", + "scheme": "basic" + } + } + }, + "paths": { + "/pets": { + "get": { + "responses": { + "200": { + "description": "ok" + } + }, + "security": [ + { + "Other": [] + } + ] + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/securitySchemes/ApiKey": { + "type": "security-scheme", + "pointer": "/components/securitySchemes/ApiKey", + "parent": "https://example.com/openapi.json#/components" + }, + "https://example.com/openapi.json#/components/securitySchemes/Other": { + "type": "security-scheme", + "pointer": "/components/securitySchemes/Other", + "parent": "https://example.com/openapi.json#/components" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1pets": { + "type": "path-item", + "pointer": "/paths/~1pets", + "parent": "https://example.com/openapi.json#/paths" + }, + "https://example.com/openapi.json#/paths/~1pets/get": { + "type": "operation", + "pointer": "/paths/~1pets/get", + "parent": "https://example.com/openapi.json#/paths/~1pets" + }, + "https://example.com/openapi.json#/paths/~1pets/get/responses": { + "type": "responses", + "pointer": "/paths/~1pets/get/responses", + "parent": "https://example.com/openapi.json#/paths/~1pets/get" + }, + "https://example.com/openapi.json#/paths/~1pets/get/responses/200": { + "type": "response", + "pointer": "/paths/~1pets/get/responses/200", + "parent": "https://example.com/openapi.json#/paths/~1pets/get/responses" + }, + "https://example.com/openapi.json#/paths/~1pets/get/security/0": { + "type": "security-requirement", + "pointer": "/paths/~1pets/get/security/0", + "parent": "https://example.com/openapi.json#/paths/~1pets/get" + }, + "https://example.com/openapi.json#/security/0": { + "type": "security-requirement", + "pointer": "/security/0", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [ + { + "type": "path", + "path": "/pets", + "method": "get", + "origin": "https://example.com/openapi.json#/paths/~1pets/get", + "endpoint": "https://example.com/openapi.json#/paths/~1pets", + "tags": [], + "servers": [], + "security": [ + "https://example.com/openapi.json#/paths/~1pets/get/security/0" + ], + "parameters": [] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/operations_security_removed_by_an_empty_array.json b/test/openapi/frame/3.1/pass/operations_security_removed_by_an_empty_array.json new file mode 100644 index 000000000..738b4dd7f --- /dev/null +++ b/test/openapi/frame/3.1/pass/operations_security_removed_by_an_empty_array.json @@ -0,0 +1,116 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "security": [ + { + "ApiKey": [] + } + ], + "components": { + "securitySchemes": { + "ApiKey": { + "type": "apiKey", + "name": "X-Key", + "in": "header" + } + } + }, + "paths": { + "/pets": { + "get": { + "responses": { + "200": { + "description": "ok" + } + }, + "security": [] + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/securitySchemes/ApiKey": { + "type": "security-scheme", + "pointer": "/components/securitySchemes/ApiKey", + "parent": "https://example.com/openapi.json#/components" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1pets": { + "type": "path-item", + "pointer": "/paths/~1pets", + "parent": "https://example.com/openapi.json#/paths" + }, + "https://example.com/openapi.json#/paths/~1pets/get": { + "type": "operation", + "pointer": "/paths/~1pets/get", + "parent": "https://example.com/openapi.json#/paths/~1pets" + }, + "https://example.com/openapi.json#/paths/~1pets/get/responses": { + "type": "responses", + "pointer": "/paths/~1pets/get/responses", + "parent": "https://example.com/openapi.json#/paths/~1pets/get" + }, + "https://example.com/openapi.json#/paths/~1pets/get/responses/200": { + "type": "response", + "pointer": "/paths/~1pets/get/responses/200", + "parent": "https://example.com/openapi.json#/paths/~1pets/get/responses" + }, + "https://example.com/openapi.json#/security/0": { + "type": "security-requirement", + "pointer": "/security/0", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [ + { + "type": "path", + "path": "/pets", + "method": "get", + "origin": "https://example.com/openapi.json#/paths/~1pets/get", + "endpoint": "https://example.com/openapi.json#/paths/~1pets", + "tags": [], + "servers": [], + "security": [], + "parameters": [] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/operations_servers_empty_array_falls_back.json b/test/openapi/frame/3.1/pass/operations_servers_empty_array_falls_back.json new file mode 100644 index 000000000..c69450060 --- /dev/null +++ b/test/openapi/frame/3.1/pass/operations_servers_empty_array_falls_back.json @@ -0,0 +1,100 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "servers": [ + { + "url": "https://api.example.com" + } + ], + "paths": { + "/pets": { + "servers": [], + "get": { + "responses": { + "200": { + "description": "ok" + } + }, + "servers": [] + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1pets": { + "type": "path-item", + "pointer": "/paths/~1pets", + "parent": "https://example.com/openapi.json#/paths" + }, + "https://example.com/openapi.json#/paths/~1pets/get": { + "type": "operation", + "pointer": "/paths/~1pets/get", + "parent": "https://example.com/openapi.json#/paths/~1pets" + }, + "https://example.com/openapi.json#/paths/~1pets/get/responses": { + "type": "responses", + "pointer": "/paths/~1pets/get/responses", + "parent": "https://example.com/openapi.json#/paths/~1pets/get" + }, + "https://example.com/openapi.json#/paths/~1pets/get/responses/200": { + "type": "response", + "pointer": "/paths/~1pets/get/responses/200", + "parent": "https://example.com/openapi.json#/paths/~1pets/get/responses" + }, + "https://example.com/openapi.json#/servers/0": { + "type": "server", + "pointer": "/servers/0", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [ + { + "type": "path", + "path": "/pets", + "method": "get", + "origin": "https://example.com/openapi.json#/paths/~1pets/get", + "endpoint": "https://example.com/openapi.json#/paths/~1pets", + "tags": [], + "servers": [ + "https://example.com/openapi.json#/servers/0" + ], + "security": [], + "parameters": [] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/operations_servers_from_the_document.json b/test/openapi/frame/3.1/pass/operations_servers_from_the_document.json new file mode 100644 index 000000000..e9f71e6dd --- /dev/null +++ b/test/openapi/frame/3.1/pass/operations_servers_from_the_document.json @@ -0,0 +1,107 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "servers": [ + { + "url": "https://api.example.com" + }, + { + "url": "https://backup.example.com" + } + ], + "paths": { + "/pets": { + "get": { + "responses": { + "200": { + "description": "ok" + } + } + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1pets": { + "type": "path-item", + "pointer": "/paths/~1pets", + "parent": "https://example.com/openapi.json#/paths" + }, + "https://example.com/openapi.json#/paths/~1pets/get": { + "type": "operation", + "pointer": "/paths/~1pets/get", + "parent": "https://example.com/openapi.json#/paths/~1pets" + }, + "https://example.com/openapi.json#/paths/~1pets/get/responses": { + "type": "responses", + "pointer": "/paths/~1pets/get/responses", + "parent": "https://example.com/openapi.json#/paths/~1pets/get" + }, + "https://example.com/openapi.json#/paths/~1pets/get/responses/200": { + "type": "response", + "pointer": "/paths/~1pets/get/responses/200", + "parent": "https://example.com/openapi.json#/paths/~1pets/get/responses" + }, + "https://example.com/openapi.json#/servers/0": { + "type": "server", + "pointer": "/servers/0", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/servers/1": { + "type": "server", + "pointer": "/servers/1", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [ + { + "type": "path", + "path": "/pets", + "method": "get", + "origin": "https://example.com/openapi.json#/paths/~1pets/get", + "endpoint": "https://example.com/openapi.json#/paths/~1pets", + "tags": [], + "servers": [ + "https://example.com/openapi.json#/servers/0", + "https://example.com/openapi.json#/servers/1" + ], + "security": [], + "parameters": [] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/operations_servers_from_the_operation.json b/test/openapi/frame/3.1/pass/operations_servers_from_the_operation.json new file mode 100644 index 000000000..4f5fa2d4e --- /dev/null +++ b/test/openapi/frame/3.1/pass/operations_servers_from_the_operation.json @@ -0,0 +1,118 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "servers": [ + { + "url": "https://api.example.com" + } + ], + "paths": { + "/pets": { + "servers": [ + { + "url": "https://pets.example.com" + } + ], + "get": { + "responses": { + "200": { + "description": "ok" + } + }, + "servers": [ + { + "url": "https://one.example.com" + } + ] + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1pets": { + "type": "path-item", + "pointer": "/paths/~1pets", + "parent": "https://example.com/openapi.json#/paths" + }, + "https://example.com/openapi.json#/paths/~1pets/get": { + "type": "operation", + "pointer": "/paths/~1pets/get", + "parent": "https://example.com/openapi.json#/paths/~1pets" + }, + "https://example.com/openapi.json#/paths/~1pets/get/responses": { + "type": "responses", + "pointer": "/paths/~1pets/get/responses", + "parent": "https://example.com/openapi.json#/paths/~1pets/get" + }, + "https://example.com/openapi.json#/paths/~1pets/get/responses/200": { + "type": "response", + "pointer": "/paths/~1pets/get/responses/200", + "parent": "https://example.com/openapi.json#/paths/~1pets/get/responses" + }, + "https://example.com/openapi.json#/paths/~1pets/get/servers/0": { + "type": "server", + "pointer": "/paths/~1pets/get/servers/0", + "parent": "https://example.com/openapi.json#/paths/~1pets/get" + }, + "https://example.com/openapi.json#/paths/~1pets/servers/0": { + "type": "server", + "pointer": "/paths/~1pets/servers/0", + "parent": "https://example.com/openapi.json#/paths/~1pets" + }, + "https://example.com/openapi.json#/servers/0": { + "type": "server", + "pointer": "/servers/0", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [ + { + "type": "path", + "path": "/pets", + "method": "get", + "origin": "https://example.com/openapi.json#/paths/~1pets/get", + "endpoint": "https://example.com/openapi.json#/paths/~1pets", + "tags": [], + "servers": [ + "https://example.com/openapi.json#/paths/~1pets/get/servers/0" + ], + "security": [], + "parameters": [] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/operations_servers_from_the_path_item.json b/test/openapi/frame/3.1/pass/operations_servers_from_the_path_item.json new file mode 100644 index 000000000..6ff98f879 --- /dev/null +++ b/test/openapi/frame/3.1/pass/operations_servers_from_the_path_item.json @@ -0,0 +1,108 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "servers": [ + { + "url": "https://api.example.com" + } + ], + "paths": { + "/pets": { + "servers": [ + { + "url": "https://pets.example.com" + } + ], + "get": { + "responses": { + "200": { + "description": "ok" + } + } + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1pets": { + "type": "path-item", + "pointer": "/paths/~1pets", + "parent": "https://example.com/openapi.json#/paths" + }, + "https://example.com/openapi.json#/paths/~1pets/get": { + "type": "operation", + "pointer": "/paths/~1pets/get", + "parent": "https://example.com/openapi.json#/paths/~1pets" + }, + "https://example.com/openapi.json#/paths/~1pets/get/responses": { + "type": "responses", + "pointer": "/paths/~1pets/get/responses", + "parent": "https://example.com/openapi.json#/paths/~1pets/get" + }, + "https://example.com/openapi.json#/paths/~1pets/get/responses/200": { + "type": "response", + "pointer": "/paths/~1pets/get/responses/200", + "parent": "https://example.com/openapi.json#/paths/~1pets/get/responses" + }, + "https://example.com/openapi.json#/paths/~1pets/servers/0": { + "type": "server", + "pointer": "/paths/~1pets/servers/0", + "parent": "https://example.com/openapi.json#/paths/~1pets" + }, + "https://example.com/openapi.json#/servers/0": { + "type": "server", + "pointer": "/servers/0", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [ + { + "type": "path", + "path": "/pets", + "method": "get", + "origin": "https://example.com/openapi.json#/paths/~1pets/get", + "endpoint": "https://example.com/openapi.json#/paths/~1pets", + "tags": [], + "servers": [ + "https://example.com/openapi.json#/paths/~1pets/servers/0" + ], + "security": [], + "parameters": [] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/operations_tags_name_declared_tags.json b/test/openapi/frame/3.1/pass/operations_tags_name_declared_tags.json new file mode 100644 index 000000000..f50899fda --- /dev/null +++ b/test/openapi/frame/3.1/pass/operations_tags_name_declared_tags.json @@ -0,0 +1,111 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "tags": [ + { + "name": "pets" + }, + { + "name": "store" + } + ], + "paths": { + "/pets": { + "get": { + "responses": { + "200": { + "description": "ok" + } + }, + "tags": [ + "store", + "pets" + ] + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1pets": { + "type": "path-item", + "pointer": "/paths/~1pets", + "parent": "https://example.com/openapi.json#/paths" + }, + "https://example.com/openapi.json#/paths/~1pets/get": { + "type": "operation", + "pointer": "/paths/~1pets/get", + "parent": "https://example.com/openapi.json#/paths/~1pets" + }, + "https://example.com/openapi.json#/paths/~1pets/get/responses": { + "type": "responses", + "pointer": "/paths/~1pets/get/responses", + "parent": "https://example.com/openapi.json#/paths/~1pets/get" + }, + "https://example.com/openapi.json#/paths/~1pets/get/responses/200": { + "type": "response", + "pointer": "/paths/~1pets/get/responses/200", + "parent": "https://example.com/openapi.json#/paths/~1pets/get/responses" + }, + "https://example.com/openapi.json#/tags/0": { + "type": "tag", + "pointer": "/tags/0", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/tags/1": { + "type": "tag", + "pointer": "/tags/1", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [ + { + "type": "path", + "path": "/pets", + "method": "get", + "origin": "https://example.com/openapi.json#/paths/~1pets/get", + "endpoint": "https://example.com/openapi.json#/paths/~1pets", + "tags": [ + "https://example.com/openapi.json#/tags/1", + "https://example.com/openapi.json#/tags/0" + ], + "servers": [], + "security": [], + "parameters": [] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/operations_tags_resolve_from_the_entry_document.json b/test/openapi/frame/3.1/pass/operations_tags_resolve_from_the_entry_document.json new file mode 100644 index 000000000..41ea9243f --- /dev/null +++ b/test/openapi/frame/3.1/pass/operations_tags_resolve_from_the_entry_document.json @@ -0,0 +1,152 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "tags": [ + { + "name": "pets" + } + ], + "paths": { + "/pets": { + "$ref": "https://example.com/common.json#/components/pathItems/Pets" + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/common.json": { + "openapi": "3.1.1", + "info": { + "title": "Common", + "version": "1.0.0" + }, + "tags": [ + { + "name": "other" + } + ], + "components": { + "pathItems": { + "Pets": { + "get": { + "responses": { + "200": { + "description": "ok" + } + }, + "tags": [ + "pets", + "other" + ] + } + } + } + } + } + }, + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/common.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/common.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/common.json" + }, + "https://example.com/common.json#/components/pathItems/Pets": { + "type": "path-item", + "pointer": "/components/pathItems/Pets", + "parent": "https://example.com/common.json#/components" + }, + "https://example.com/common.json#/components/pathItems/Pets/get": { + "type": "operation", + "pointer": "/components/pathItems/Pets/get", + "parent": "https://example.com/common.json#/components/pathItems/Pets" + }, + "https://example.com/common.json#/components/pathItems/Pets/get/responses": { + "type": "responses", + "pointer": "/components/pathItems/Pets/get/responses", + "parent": "https://example.com/common.json#/components/pathItems/Pets/get" + }, + "https://example.com/common.json#/components/pathItems/Pets/get/responses/200": { + "type": "response", + "pointer": "/components/pathItems/Pets/get/responses/200", + "parent": "https://example.com/common.json#/components/pathItems/Pets/get/responses" + }, + "https://example.com/common.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/common.json" + }, + "https://example.com/common.json#/tags/0": { + "type": "tag", + "pointer": "/tags/0", + "parent": "https://example.com/common.json" + }, + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1pets": { + "type": "path-item", + "pointer": "/paths/~1pets", + "parent": "https://example.com/openapi.json#/paths", + "original": "https://example.com/common.json#/components/pathItems/Pets", + "destination": "https://example.com/common.json#/components/pathItems/Pets" + }, + "https://example.com/openapi.json#/tags/0": { + "type": "tag", + "pointer": "/tags/0", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [ + { + "type": "path", + "path": "/pets", + "method": "get", + "origin": "https://example.com/common.json#/components/pathItems/Pets/get", + "endpoint": "https://example.com/openapi.json#/paths/~1pets", + "tags": [ + "https://example.com/openapi.json#/tags/0", + null + ], + "servers": [], + "security": [], + "parameters": [] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/operations_tags_undeclared_are_reported.json b/test/openapi/frame/3.1/pass/operations_tags_undeclared_are_reported.json new file mode 100644 index 000000000..d4ee6c40f --- /dev/null +++ b/test/openapi/frame/3.1/pass/operations_tags_undeclared_are_reported.json @@ -0,0 +1,103 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "tags": [ + { + "name": "pets" + } + ], + "paths": { + "/pets": { + "get": { + "responses": { + "200": { + "description": "ok" + } + }, + "tags": [ + "pets", + "unknown" + ] + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1pets": { + "type": "path-item", + "pointer": "/paths/~1pets", + "parent": "https://example.com/openapi.json#/paths" + }, + "https://example.com/openapi.json#/paths/~1pets/get": { + "type": "operation", + "pointer": "/paths/~1pets/get", + "parent": "https://example.com/openapi.json#/paths/~1pets" + }, + "https://example.com/openapi.json#/paths/~1pets/get/responses": { + "type": "responses", + "pointer": "/paths/~1pets/get/responses", + "parent": "https://example.com/openapi.json#/paths/~1pets/get" + }, + "https://example.com/openapi.json#/paths/~1pets/get/responses/200": { + "type": "response", + "pointer": "/paths/~1pets/get/responses/200", + "parent": "https://example.com/openapi.json#/paths/~1pets/get/responses" + }, + "https://example.com/openapi.json#/tags/0": { + "type": "tag", + "pointer": "/tags/0", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [ + { + "type": "path", + "path": "/pets", + "method": "get", + "origin": "https://example.com/openapi.json#/paths/~1pets/get", + "endpoint": "https://example.com/openapi.json#/paths/~1pets", + "tags": [ + "https://example.com/openapi.json#/tags/0", + null + ], + "servers": [], + "security": [], + "parameters": [] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/operations_tags_without_a_tags_array.json b/test/openapi/frame/3.1/pass/operations_tags_without_a_tags_array.json new file mode 100644 index 000000000..bc519ca15 --- /dev/null +++ b/test/openapi/frame/3.1/pass/operations_tags_without_a_tags_array.json @@ -0,0 +1,91 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets": { + "get": { + "responses": { + "200": { + "description": "ok" + } + }, + "tags": [ + "pets" + ] + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1pets": { + "type": "path-item", + "pointer": "/paths/~1pets", + "parent": "https://example.com/openapi.json#/paths" + }, + "https://example.com/openapi.json#/paths/~1pets/get": { + "type": "operation", + "pointer": "/paths/~1pets/get", + "parent": "https://example.com/openapi.json#/paths/~1pets" + }, + "https://example.com/openapi.json#/paths/~1pets/get/responses": { + "type": "responses", + "pointer": "/paths/~1pets/get/responses", + "parent": "https://example.com/openapi.json#/paths/~1pets/get" + }, + "https://example.com/openapi.json#/paths/~1pets/get/responses/200": { + "type": "response", + "pointer": "/paths/~1pets/get/responses/200", + "parent": "https://example.com/openapi.json#/paths/~1pets/get/responses" + } + }, + "operations": [ + { + "type": "path", + "path": "/pets", + "method": "get", + "origin": "https://example.com/openapi.json#/paths/~1pets/get", + "endpoint": "https://example.com/openapi.json#/paths/~1pets", + "tags": [ + null + ], + "servers": [], + "security": [], + "parameters": [] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/parameters_every_location.json b/test/openapi/frame/3.1/pass/parameters_every_location.json new file mode 100644 index 000000000..0d6faf093 --- /dev/null +++ b/test/openapi/frame/3.1/pass/parameters_every_location.json @@ -0,0 +1,246 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "parameters": { + "Path": { + "name": "id", + "in": "path", + "required": true, + "schema": {}, + "style": "simple", + "explode": false + }, + "PathMatrix": { + "name": "id", + "in": "path", + "required": true, + "schema": {}, + "style": "matrix" + }, + "PathLabel": { + "name": "id", + "in": "path", + "required": true, + "schema": {}, + "style": "label" + }, + "Query": { + "name": "q", + "in": "query", + "schema": {}, + "style": "form", + "explode": true, + "allowReserved": true, + "allowEmptyValue": true, + "deprecated": false, + "required": false, + "description": "A query" + }, + "QueryDeep": { + "name": "q", + "in": "query", + "schema": {}, + "style": "deepObject" + }, + "Header": { + "name": "X-Trace", + "in": "header", + "schema": {}, + "style": "simple" + }, + "Cookie": { + "name": "session", + "in": "cookie", + "schema": {}, + "style": "form" + }, + "Content": { + "name": "filter", + "in": "query", + "content": { + "application/json": { + "schema": {} + } + } + }, + "Examples": { + "name": "e", + "in": "query", + "schema": {}, + "examples": { + "One": { + "value": 1 + }, + "Ref": { + "$ref": "#/components/examples/One" + } + } + } + }, + "examples": { + "One": { + "value": 1 + } + } + } + }, + "frame": { + "version": "3.1", + "base": "", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/components": { + "type": "components", + "pointer": "/components", + "parent": "" + }, + "#/components/examples/One": { + "type": "example", + "pointer": "/components/examples/One", + "parent": "#/components" + }, + "#/components/parameters/Content": { + "type": "parameter", + "pointer": "/components/parameters/Content", + "parent": "#/components" + }, + "#/components/parameters/Content/content/application~1json": { + "type": "media-type", + "pointer": "/components/parameters/Content/content/application~1json", + "parent": "#/components/parameters/Content" + }, + "#/components/parameters/Content/content/application~1json/schema": { + "type": "schema", + "pointer": "/components/parameters/Content/content/application~1json/schema", + "parent": "#/components/parameters/Content/content/application~1json", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/components/parameters/Cookie": { + "type": "parameter", + "pointer": "/components/parameters/Cookie", + "parent": "#/components" + }, + "#/components/parameters/Cookie/schema": { + "type": "schema", + "pointer": "/components/parameters/Cookie/schema", + "parent": "#/components/parameters/Cookie", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/components/parameters/Examples": { + "type": "parameter", + "pointer": "/components/parameters/Examples", + "parent": "#/components" + }, + "#/components/parameters/Examples/examples/One": { + "type": "example", + "pointer": "/components/parameters/Examples/examples/One", + "parent": "#/components/parameters/Examples" + }, + "#/components/parameters/Examples/examples/Ref": { + "type": "reference", + "pointer": "/components/parameters/Examples/examples/Ref", + "parent": "#/components/parameters/Examples", + "original": "#/components/examples/One", + "destination": "#/components/examples/One" + }, + "#/components/parameters/Examples/schema": { + "type": "schema", + "pointer": "/components/parameters/Examples/schema", + "parent": "#/components/parameters/Examples", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/components/parameters/Header": { + "type": "parameter", + "pointer": "/components/parameters/Header", + "parent": "#/components" + }, + "#/components/parameters/Header/schema": { + "type": "schema", + "pointer": "/components/parameters/Header/schema", + "parent": "#/components/parameters/Header", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/components/parameters/Path": { + "type": "parameter", + "pointer": "/components/parameters/Path", + "parent": "#/components" + }, + "#/components/parameters/Path/schema": { + "type": "schema", + "pointer": "/components/parameters/Path/schema", + "parent": "#/components/parameters/Path", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/components/parameters/PathLabel": { + "type": "parameter", + "pointer": "/components/parameters/PathLabel", + "parent": "#/components" + }, + "#/components/parameters/PathLabel/schema": { + "type": "schema", + "pointer": "/components/parameters/PathLabel/schema", + "parent": "#/components/parameters/PathLabel", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/components/parameters/PathMatrix": { + "type": "parameter", + "pointer": "/components/parameters/PathMatrix", + "parent": "#/components" + }, + "#/components/parameters/PathMatrix/schema": { + "type": "schema", + "pointer": "/components/parameters/PathMatrix/schema", + "parent": "#/components/parameters/PathMatrix", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/components/parameters/Query": { + "type": "parameter", + "pointer": "/components/parameters/Query", + "parent": "#/components" + }, + "#/components/parameters/Query/schema": { + "type": "schema", + "pointer": "/components/parameters/Query/schema", + "parent": "#/components/parameters/Query", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/components/parameters/QueryDeep": { + "type": "parameter", + "pointer": "/components/parameters/QueryDeep", + "parent": "#/components" + }, + "#/components/parameters/QueryDeep/schema": { + "type": "schema", + "pointer": "/components/parameters/QueryDeep/schema", + "parent": "#/components/parameters/QueryDeep", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/info": { + "type": "info", + "pointer": "/info", + "parent": "" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/path_item_reference_from_a_webhook.json b/test/openapi/frame/3.1/pass/path_item_reference_from_a_webhook.json new file mode 100644 index 000000000..6676feb54 --- /dev/null +++ b/test/openapi/frame/3.1/pass/path_item_reference_from_a_webhook.json @@ -0,0 +1,100 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "pathItems": { + "NewPet": { + "post": { + "responses": { + "200": { + "description": "ok" + } + } + } + } + } + }, + "webhooks": { + "newPet": { + "$ref": "#/components/pathItems/NewPet" + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/pathItems/NewPet": { + "type": "path-item", + "pointer": "/components/pathItems/NewPet", + "parent": "https://example.com/openapi.json#/components" + }, + "https://example.com/openapi.json#/components/pathItems/NewPet/post": { + "type": "operation", + "pointer": "/components/pathItems/NewPet/post", + "parent": "https://example.com/openapi.json#/components/pathItems/NewPet" + }, + "https://example.com/openapi.json#/components/pathItems/NewPet/post/responses": { + "type": "responses", + "pointer": "/components/pathItems/NewPet/post/responses", + "parent": "https://example.com/openapi.json#/components/pathItems/NewPet/post" + }, + "https://example.com/openapi.json#/components/pathItems/NewPet/post/responses/200": { + "type": "response", + "pointer": "/components/pathItems/NewPet/post/responses/200", + "parent": "https://example.com/openapi.json#/components/pathItems/NewPet/post/responses" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/webhooks/newPet": { + "type": "path-item", + "pointer": "/webhooks/newPet", + "parent": "https://example.com/openapi.json", + "original": "#/components/pathItems/NewPet", + "destination": "https://example.com/openapi.json#/components/pathItems/NewPet" + } + }, + "operations": [ + { + "type": "webhook", + "path": "newPet", + "method": "post", + "origin": "https://example.com/openapi.json#/components/pathItems/NewPet/post", + "endpoint": "https://example.com/openapi.json#/webhooks/newPet", + "tags": [], + "servers": [], + "security": [], + "parameters": [] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/path_item_reference_to_a_document_that_is_only_a_path_item.json b/test/openapi/frame/3.1/pass/path_item_reference_to_a_document_that_is_only_a_path_item.json new file mode 100644 index 000000000..70315efe0 --- /dev/null +++ b/test/openapi/frame/3.1/pass/path_item_reference_to_a_document_that_is_only_a_path_item.json @@ -0,0 +1,98 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/foo": { + "$ref": "https://example.com/target.json" + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/target.json": { + "get": { + "responses": { + "200": { + "description": "ok" + } + } + } + } + }, + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1foo": { + "type": "path-item", + "pointer": "/paths/~1foo", + "parent": "https://example.com/openapi.json#/paths", + "original": "https://example.com/target.json", + "destination": "https://example.com/target.json" + }, + "https://example.com/target.json": { + "type": "path-item", + "pointer": "", + "parent": null + }, + "https://example.com/target.json#/get": { + "type": "operation", + "pointer": "/get", + "parent": "https://example.com/target.json" + }, + "https://example.com/target.json#/get/responses": { + "type": "responses", + "pointer": "/get/responses", + "parent": "https://example.com/target.json#/get" + }, + "https://example.com/target.json#/get/responses/200": { + "type": "response", + "pointer": "/get/responses/200", + "parent": "https://example.com/target.json#/get/responses" + } + }, + "operations": [ + { + "type": "path", + "path": "/foo", + "method": "get", + "origin": "https://example.com/target.json#/get", + "endpoint": "https://example.com/openapi.json#/paths/~1foo", + "tags": [], + "servers": [], + "security": [], + "parameters": [] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/path_item_reference_with_siblings.json b/test/openapi/frame/3.1/pass/path_item_reference_with_siblings.json new file mode 100644 index 000000000..a0a660945 --- /dev/null +++ b/test/openapi/frame/3.1/pass/path_item_reference_with_siblings.json @@ -0,0 +1,92 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets": { + "$ref": "#/components/pathItems/Pets", + "summary": "Alongside a reference", + "get": { + "responses": { + "200": { + "description": "ok" + } + } + } + } + }, + "components": { + "pathItems": { + "Pets": {} + } + } + }, + "frame": { + "version": "3.1", + "base": "", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/components": { + "type": "components", + "pointer": "/components", + "parent": "" + }, + "#/components/pathItems/Pets": { + "type": "path-item", + "pointer": "/components/pathItems/Pets", + "parent": "#/components" + }, + "#/info": { + "type": "info", + "pointer": "/info", + "parent": "" + }, + "#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "" + }, + "#/paths/~1pets": { + "type": "path-item", + "pointer": "/paths/~1pets", + "parent": "#/paths", + "original": "#/components/pathItems/Pets", + "destination": "#/components/pathItems/Pets" + }, + "#/paths/~1pets/get": { + "type": "operation", + "pointer": "/paths/~1pets/get", + "parent": "#/paths/~1pets" + }, + "#/paths/~1pets/get/responses": { + "type": "responses", + "pointer": "/paths/~1pets/get/responses", + "parent": "#/paths/~1pets/get" + }, + "#/paths/~1pets/get/responses/200": { + "type": "response", + "pointer": "/paths/~1pets/get/responses/200", + "parent": "#/paths/~1pets/get/responses" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/path_item_with_ref.json b/test/openapi/frame/3.1/pass/path_item_with_ref.json new file mode 100644 index 000000000..9d76f95db --- /dev/null +++ b/test/openapi/frame/3.1/pass/path_item_with_ref.json @@ -0,0 +1,104 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets": { + "$ref": "#/components/pathItems/Pets" + } + }, + "components": { + "pathItems": { + "Pets": { + "get": { + "responses": { + "200": { + "description": "ok" + } + } + } + } + } + } + }, + "frame": { + "version": "3.1", + "base": "", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/components": { + "type": "components", + "pointer": "/components", + "parent": "" + }, + "#/components/pathItems/Pets": { + "type": "path-item", + "pointer": "/components/pathItems/Pets", + "parent": "#/components" + }, + "#/components/pathItems/Pets/get": { + "type": "operation", + "pointer": "/components/pathItems/Pets/get", + "parent": "#/components/pathItems/Pets" + }, + "#/components/pathItems/Pets/get/responses": { + "type": "responses", + "pointer": "/components/pathItems/Pets/get/responses", + "parent": "#/components/pathItems/Pets/get" + }, + "#/components/pathItems/Pets/get/responses/200": { + "type": "response", + "pointer": "/components/pathItems/Pets/get/responses/200", + "parent": "#/components/pathItems/Pets/get/responses" + }, + "#/info": { + "type": "info", + "pointer": "/info", + "parent": "" + }, + "#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "" + }, + "#/paths/~1pets": { + "type": "path-item", + "pointer": "/paths/~1pets", + "parent": "#/paths", + "original": "#/components/pathItems/Pets", + "destination": "#/components/pathItems/Pets" + } + }, + "operations": [ + { + "type": "path", + "path": "/pets", + "method": "get", + "origin": "#/components/pathItems/Pets/get", + "endpoint": "#/paths/~1pets", + "tags": [], + "servers": [], + "security": [], + "parameters": [] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/path_template_adjacent_expressions.json b/test/openapi/frame/3.1/pass/path_template_adjacent_expressions.json new file mode 100644 index 000000000..e895a00d0 --- /dev/null +++ b/test/openapi/frame/3.1/pass/path_template_adjacent_expressions.json @@ -0,0 +1,125 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/a/{x}{y}": { + "parameters": [ + { + "name": "x", + "in": "path", + "required": true, + "schema": {} + }, + { + "name": "y", + "in": "path", + "required": true, + "schema": {} + } + ], + "get": { + "responses": { + "200": { + "description": "ok" + } + } + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1a~1%7Bx%7D%7By%7D": { + "type": "path-item", + "pointer": "/paths/~1a~1{x}{y}", + "parent": "https://example.com/openapi.json#/paths" + }, + "https://example.com/openapi.json#/paths/~1a~1%7Bx%7D%7By%7D/get": { + "type": "operation", + "pointer": "/paths/~1a~1{x}{y}/get", + "parent": "https://example.com/openapi.json#/paths/~1a~1%7Bx%7D%7By%7D" + }, + "https://example.com/openapi.json#/paths/~1a~1%7Bx%7D%7By%7D/get/responses": { + "type": "responses", + "pointer": "/paths/~1a~1{x}{y}/get/responses", + "parent": "https://example.com/openapi.json#/paths/~1a~1%7Bx%7D%7By%7D/get" + }, + "https://example.com/openapi.json#/paths/~1a~1%7Bx%7D%7By%7D/get/responses/200": { + "type": "response", + "pointer": "/paths/~1a~1{x}{y}/get/responses/200", + "parent": "https://example.com/openapi.json#/paths/~1a~1%7Bx%7D%7By%7D/get/responses" + }, + "https://example.com/openapi.json#/paths/~1a~1%7Bx%7D%7By%7D/parameters/0": { + "type": "parameter", + "pointer": "/paths/~1a~1{x}{y}/parameters/0", + "parent": "https://example.com/openapi.json#/paths/~1a~1%7Bx%7D%7By%7D" + }, + "https://example.com/openapi.json#/paths/~1a~1%7Bx%7D%7By%7D/parameters/0/schema": { + "type": "schema", + "pointer": "/paths/~1a~1{x}{y}/parameters/0/schema", + "parent": "https://example.com/openapi.json#/paths/~1a~1%7Bx%7D%7By%7D/parameters/0", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/paths/~1a~1%7Bx%7D%7By%7D/parameters/1": { + "type": "parameter", + "pointer": "/paths/~1a~1{x}{y}/parameters/1", + "parent": "https://example.com/openapi.json#/paths/~1a~1%7Bx%7D%7By%7D" + }, + "https://example.com/openapi.json#/paths/~1a~1%7Bx%7D%7By%7D/parameters/1/schema": { + "type": "schema", + "pointer": "/paths/~1a~1{x}{y}/parameters/1/schema", + "parent": "https://example.com/openapi.json#/paths/~1a~1%7Bx%7D%7By%7D/parameters/1", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + } + }, + "operations": [ + { + "type": "path", + "path": "/a/{x}{y}", + "method": "get", + "origin": "https://example.com/openapi.json#/paths/~1a~1%7Bx%7D%7By%7D/get", + "endpoint": "https://example.com/openapi.json#/paths/~1a~1%7Bx%7D%7By%7D", + "tags": [], + "servers": [], + "security": [], + "parameters": [ + "https://example.com/openapi.json#/paths/~1a~1%7Bx%7D%7By%7D/parameters/0", + "https://example.com/openapi.json#/paths/~1a~1%7Bx%7D%7By%7D/parameters/1" + ] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/path_template_empty_expression_is_unconstrained.json b/test/openapi/frame/3.1/pass/path_template_empty_expression_is_unconstrained.json new file mode 100644 index 000000000..3e92cfb30 --- /dev/null +++ b/test/openapi/frame/3.1/pass/path_template_empty_expression_is_unconstrained.json @@ -0,0 +1,107 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/a/{}": { + "parameters": [ + { + "name": "", + "in": "path", + "required": true, + "schema": {} + } + ], + "get": { + "responses": { + "200": { + "description": "ok" + } + } + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1a~1%7B%7D": { + "type": "path-item", + "pointer": "/paths/~1a~1{}", + "parent": "https://example.com/openapi.json#/paths" + }, + "https://example.com/openapi.json#/paths/~1a~1%7B%7D/get": { + "type": "operation", + "pointer": "/paths/~1a~1{}/get", + "parent": "https://example.com/openapi.json#/paths/~1a~1%7B%7D" + }, + "https://example.com/openapi.json#/paths/~1a~1%7B%7D/get/responses": { + "type": "responses", + "pointer": "/paths/~1a~1{}/get/responses", + "parent": "https://example.com/openapi.json#/paths/~1a~1%7B%7D/get" + }, + "https://example.com/openapi.json#/paths/~1a~1%7B%7D/get/responses/200": { + "type": "response", + "pointer": "/paths/~1a~1{}/get/responses/200", + "parent": "https://example.com/openapi.json#/paths/~1a~1%7B%7D/get/responses" + }, + "https://example.com/openapi.json#/paths/~1a~1%7B%7D/parameters/0": { + "type": "parameter", + "pointer": "/paths/~1a~1{}/parameters/0", + "parent": "https://example.com/openapi.json#/paths/~1a~1%7B%7D" + }, + "https://example.com/openapi.json#/paths/~1a~1%7B%7D/parameters/0/schema": { + "type": "schema", + "pointer": "/paths/~1a~1{}/parameters/0/schema", + "parent": "https://example.com/openapi.json#/paths/~1a~1%7B%7D/parameters/0", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + } + }, + "operations": [ + { + "type": "path", + "path": "/a/{}", + "method": "get", + "origin": "https://example.com/openapi.json#/paths/~1a~1%7B%7D/get", + "endpoint": "https://example.com/openapi.json#/paths/~1a~1%7B%7D", + "tags": [], + "servers": [], + "security": [], + "parameters": [ + "https://example.com/openapi.json#/paths/~1a~1%7B%7D/parameters/0" + ] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/path_template_empty_segment_is_unconstrained.json b/test/openapi/frame/3.1/pass/path_template_empty_segment_is_unconstrained.json new file mode 100644 index 000000000..d3b5ff303 --- /dev/null +++ b/test/openapi/frame/3.1/pass/path_template_empty_segment_is_unconstrained.json @@ -0,0 +1,86 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "//pets": { + "get": { + "responses": { + "200": { + "description": "ok" + } + } + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1~1pets": { + "type": "path-item", + "pointer": "/paths/~1~1pets", + "parent": "https://example.com/openapi.json#/paths" + }, + "https://example.com/openapi.json#/paths/~1~1pets/get": { + "type": "operation", + "pointer": "/paths/~1~1pets/get", + "parent": "https://example.com/openapi.json#/paths/~1~1pets" + }, + "https://example.com/openapi.json#/paths/~1~1pets/get/responses": { + "type": "responses", + "pointer": "/paths/~1~1pets/get/responses", + "parent": "https://example.com/openapi.json#/paths/~1~1pets/get" + }, + "https://example.com/openapi.json#/paths/~1~1pets/get/responses/200": { + "type": "response", + "pointer": "/paths/~1~1pets/get/responses/200", + "parent": "https://example.com/openapi.json#/paths/~1~1pets/get/responses" + } + }, + "operations": [ + { + "type": "path", + "path": "//pets", + "method": "get", + "origin": "https://example.com/openapi.json#/paths/~1~1pets/get", + "endpoint": "https://example.com/openapi.json#/paths/~1~1pets", + "tags": [], + "servers": [], + "security": [], + "parameters": [] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/path_template_from_the_operation.json b/test/openapi/frame/3.1/pass/path_template_from_the_operation.json new file mode 100644 index 000000000..4edb36c22 --- /dev/null +++ b/test/openapi/frame/3.1/pass/path_template_from_the_operation.json @@ -0,0 +1,107 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets/{petId}": { + "get": { + "responses": { + "200": { + "description": "ok" + } + }, + "parameters": [ + { + "name": "petId", + "in": "path", + "required": true, + "schema": {} + } + ] + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D": { + "type": "path-item", + "pointer": "/paths/~1pets~1{petId}", + "parent": "https://example.com/openapi.json#/paths" + }, + "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/get": { + "type": "operation", + "pointer": "/paths/~1pets~1{petId}/get", + "parent": "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D" + }, + "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/get/parameters/0": { + "type": "parameter", + "pointer": "/paths/~1pets~1{petId}/get/parameters/0", + "parent": "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/get" + }, + "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/get/parameters/0/schema": { + "type": "schema", + "pointer": "/paths/~1pets~1{petId}/get/parameters/0/schema", + "parent": "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/get/parameters/0", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/get/responses": { + "type": "responses", + "pointer": "/paths/~1pets~1{petId}/get/responses", + "parent": "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/get" + }, + "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/get/responses/200": { + "type": "response", + "pointer": "/paths/~1pets~1{petId}/get/responses/200", + "parent": "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/get/responses" + } + }, + "operations": [ + { + "type": "path", + "path": "/pets/{petId}", + "method": "get", + "origin": "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/get", + "endpoint": "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D", + "tags": [], + "servers": [], + "security": [], + "parameters": [ + "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/get/parameters/0" + ] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/path_template_from_the_path_item.json b/test/openapi/frame/3.1/pass/path_template_from_the_path_item.json new file mode 100644 index 000000000..e37cff6d3 --- /dev/null +++ b/test/openapi/frame/3.1/pass/path_template_from_the_path_item.json @@ -0,0 +1,142 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets/{petId}": { + "parameters": [ + { + "name": "petId", + "in": "path", + "required": true, + "schema": {} + } + ], + "get": { + "responses": { + "200": { + "description": "ok" + } + } + }, + "delete": { + "responses": { + "200": { + "description": "ok" + } + } + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D": { + "type": "path-item", + "pointer": "/paths/~1pets~1{petId}", + "parent": "https://example.com/openapi.json#/paths" + }, + "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/delete": { + "type": "operation", + "pointer": "/paths/~1pets~1{petId}/delete", + "parent": "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D" + }, + "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/delete/responses": { + "type": "responses", + "pointer": "/paths/~1pets~1{petId}/delete/responses", + "parent": "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/delete" + }, + "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/delete/responses/200": { + "type": "response", + "pointer": "/paths/~1pets~1{petId}/delete/responses/200", + "parent": "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/delete/responses" + }, + "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/get": { + "type": "operation", + "pointer": "/paths/~1pets~1{petId}/get", + "parent": "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D" + }, + "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/get/responses": { + "type": "responses", + "pointer": "/paths/~1pets~1{petId}/get/responses", + "parent": "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/get" + }, + "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/get/responses/200": { + "type": "response", + "pointer": "/paths/~1pets~1{petId}/get/responses/200", + "parent": "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/get/responses" + }, + "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/parameters/0": { + "type": "parameter", + "pointer": "/paths/~1pets~1{petId}/parameters/0", + "parent": "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D" + }, + "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/parameters/0/schema": { + "type": "schema", + "pointer": "/paths/~1pets~1{petId}/parameters/0/schema", + "parent": "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/parameters/0", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + } + }, + "operations": [ + { + "type": "path", + "path": "/pets/{petId}", + "method": "get", + "origin": "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/get", + "endpoint": "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D", + "tags": [], + "servers": [], + "security": [], + "parameters": [ + "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/parameters/0" + ] + }, + { + "type": "path", + "path": "/pets/{petId}", + "method": "delete", + "origin": "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/delete", + "endpoint": "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D", + "tags": [], + "servers": [], + "security": [], + "parameters": [ + "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/parameters/0" + ] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/path_template_in_a_referenced_path_item.json b/test/openapi/frame/3.1/pass/path_template_in_a_referenced_path_item.json new file mode 100644 index 000000000..5ecb022fc --- /dev/null +++ b/test/openapi/frame/3.1/pass/path_template_in_a_referenced_path_item.json @@ -0,0 +1,126 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "pathItems": { + "Pet": { + "parameters": [ + { + "name": "petId", + "in": "path", + "required": true, + "schema": {} + } + ], + "get": { + "responses": { + "200": { + "description": "ok" + } + } + } + } + } + }, + "paths": { + "/pets/{petId}": { + "$ref": "#/components/pathItems/Pet" + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/pathItems/Pet": { + "type": "path-item", + "pointer": "/components/pathItems/Pet", + "parent": "https://example.com/openapi.json#/components" + }, + "https://example.com/openapi.json#/components/pathItems/Pet/get": { + "type": "operation", + "pointer": "/components/pathItems/Pet/get", + "parent": "https://example.com/openapi.json#/components/pathItems/Pet" + }, + "https://example.com/openapi.json#/components/pathItems/Pet/get/responses": { + "type": "responses", + "pointer": "/components/pathItems/Pet/get/responses", + "parent": "https://example.com/openapi.json#/components/pathItems/Pet/get" + }, + "https://example.com/openapi.json#/components/pathItems/Pet/get/responses/200": { + "type": "response", + "pointer": "/components/pathItems/Pet/get/responses/200", + "parent": "https://example.com/openapi.json#/components/pathItems/Pet/get/responses" + }, + "https://example.com/openapi.json#/components/pathItems/Pet/parameters/0": { + "type": "parameter", + "pointer": "/components/pathItems/Pet/parameters/0", + "parent": "https://example.com/openapi.json#/components/pathItems/Pet" + }, + "https://example.com/openapi.json#/components/pathItems/Pet/parameters/0/schema": { + "type": "schema", + "pointer": "/components/pathItems/Pet/parameters/0/schema", + "parent": "https://example.com/openapi.json#/components/pathItems/Pet/parameters/0", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D": { + "type": "path-item", + "pointer": "/paths/~1pets~1{petId}", + "parent": "https://example.com/openapi.json#/paths", + "original": "#/components/pathItems/Pet", + "destination": "https://example.com/openapi.json#/components/pathItems/Pet" + } + }, + "operations": [ + { + "type": "path", + "path": "/pets/{petId}", + "method": "get", + "origin": "https://example.com/openapi.json#/components/pathItems/Pet/get", + "endpoint": "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D", + "tags": [], + "servers": [], + "security": [], + "parameters": [ + "https://example.com/openapi.json#/components/pathItems/Pet/parameters/0" + ] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/path_template_in_an_empty_path_item.json b/test/openapi/frame/3.1/pass/path_template_in_an_empty_path_item.json new file mode 100644 index 000000000..f07cff7f1 --- /dev/null +++ b/test/openapi/frame/3.1/pass/path_template_in_an_empty_path_item.json @@ -0,0 +1,51 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets/{petId}": {} + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D": { + "type": "path-item", + "pointer": "/paths/~1pets~1{petId}", + "parent": "https://example.com/openapi.json#/paths" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/path_template_literal_outside_pchar_is_unconstrained.json b/test/openapi/frame/3.1/pass/path_template_literal_outside_pchar_is_unconstrained.json new file mode 100644 index 000000000..cdafdc1d4 --- /dev/null +++ b/test/openapi/frame/3.1/pass/path_template_literal_outside_pchar_is_unconstrained.json @@ -0,0 +1,86 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/a b/c": { + "get": { + "responses": { + "200": { + "description": "ok" + } + } + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1a%20b~1c": { + "type": "path-item", + "pointer": "/paths/~1a b~1c", + "parent": "https://example.com/openapi.json#/paths" + }, + "https://example.com/openapi.json#/paths/~1a%20b~1c/get": { + "type": "operation", + "pointer": "/paths/~1a b~1c/get", + "parent": "https://example.com/openapi.json#/paths/~1a%20b~1c" + }, + "https://example.com/openapi.json#/paths/~1a%20b~1c/get/responses": { + "type": "responses", + "pointer": "/paths/~1a b~1c/get/responses", + "parent": "https://example.com/openapi.json#/paths/~1a%20b~1c/get" + }, + "https://example.com/openapi.json#/paths/~1a%20b~1c/get/responses/200": { + "type": "response", + "pointer": "/paths/~1a b~1c/get/responses/200", + "parent": "https://example.com/openapi.json#/paths/~1a%20b~1c/get/responses" + } + }, + "operations": [ + { + "type": "path", + "path": "/a b/c", + "method": "get", + "origin": "https://example.com/openapi.json#/paths/~1a%20b~1c/get", + "endpoint": "https://example.com/openapi.json#/paths/~1a%20b~1c", + "tags": [], + "servers": [], + "security": [], + "parameters": [] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/path_template_partial_segment.json b/test/openapi/frame/3.1/pass/path_template_partial_segment.json new file mode 100644 index 000000000..07812518e --- /dev/null +++ b/test/openapi/frame/3.1/pass/path_template_partial_segment.json @@ -0,0 +1,107 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets/x{petId}y": { + "parameters": [ + { + "name": "petId", + "in": "path", + "required": true, + "schema": {} + } + ], + "get": { + "responses": { + "200": { + "description": "ok" + } + } + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1pets~1x%7BpetId%7Dy": { + "type": "path-item", + "pointer": "/paths/~1pets~1x{petId}y", + "parent": "https://example.com/openapi.json#/paths" + }, + "https://example.com/openapi.json#/paths/~1pets~1x%7BpetId%7Dy/get": { + "type": "operation", + "pointer": "/paths/~1pets~1x{petId}y/get", + "parent": "https://example.com/openapi.json#/paths/~1pets~1x%7BpetId%7Dy" + }, + "https://example.com/openapi.json#/paths/~1pets~1x%7BpetId%7Dy/get/responses": { + "type": "responses", + "pointer": "/paths/~1pets~1x{petId}y/get/responses", + "parent": "https://example.com/openapi.json#/paths/~1pets~1x%7BpetId%7Dy/get" + }, + "https://example.com/openapi.json#/paths/~1pets~1x%7BpetId%7Dy/get/responses/200": { + "type": "response", + "pointer": "/paths/~1pets~1x{petId}y/get/responses/200", + "parent": "https://example.com/openapi.json#/paths/~1pets~1x%7BpetId%7Dy/get/responses" + }, + "https://example.com/openapi.json#/paths/~1pets~1x%7BpetId%7Dy/parameters/0": { + "type": "parameter", + "pointer": "/paths/~1pets~1x{petId}y/parameters/0", + "parent": "https://example.com/openapi.json#/paths/~1pets~1x%7BpetId%7Dy" + }, + "https://example.com/openapi.json#/paths/~1pets~1x%7BpetId%7Dy/parameters/0/schema": { + "type": "schema", + "pointer": "/paths/~1pets~1x{petId}y/parameters/0/schema", + "parent": "https://example.com/openapi.json#/paths/~1pets~1x%7BpetId%7Dy/parameters/0", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + } + }, + "operations": [ + { + "type": "path", + "path": "/pets/x{petId}y", + "method": "get", + "origin": "https://example.com/openapi.json#/paths/~1pets~1x%7BpetId%7Dy/get", + "endpoint": "https://example.com/openapi.json#/paths/~1pets~1x%7BpetId%7Dy", + "tags": [], + "servers": [], + "security": [], + "parameters": [ + "https://example.com/openapi.json#/paths/~1pets~1x%7BpetId%7Dy/parameters/0" + ] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/path_template_repeated_expression_is_unconstrained.json b/test/openapi/frame/3.1/pass/path_template_repeated_expression_is_unconstrained.json new file mode 100644 index 000000000..6c5ec9e7e --- /dev/null +++ b/test/openapi/frame/3.1/pass/path_template_repeated_expression_is_unconstrained.json @@ -0,0 +1,107 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/a/{x}/b/{x}": { + "parameters": [ + { + "name": "x", + "in": "path", + "required": true, + "schema": {} + } + ], + "get": { + "responses": { + "200": { + "description": "ok" + } + } + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1a~1%7Bx%7D~1b~1%7Bx%7D": { + "type": "path-item", + "pointer": "/paths/~1a~1{x}~1b~1{x}", + "parent": "https://example.com/openapi.json#/paths" + }, + "https://example.com/openapi.json#/paths/~1a~1%7Bx%7D~1b~1%7Bx%7D/get": { + "type": "operation", + "pointer": "/paths/~1a~1{x}~1b~1{x}/get", + "parent": "https://example.com/openapi.json#/paths/~1a~1%7Bx%7D~1b~1%7Bx%7D" + }, + "https://example.com/openapi.json#/paths/~1a~1%7Bx%7D~1b~1%7Bx%7D/get/responses": { + "type": "responses", + "pointer": "/paths/~1a~1{x}~1b~1{x}/get/responses", + "parent": "https://example.com/openapi.json#/paths/~1a~1%7Bx%7D~1b~1%7Bx%7D/get" + }, + "https://example.com/openapi.json#/paths/~1a~1%7Bx%7D~1b~1%7Bx%7D/get/responses/200": { + "type": "response", + "pointer": "/paths/~1a~1{x}~1b~1{x}/get/responses/200", + "parent": "https://example.com/openapi.json#/paths/~1a~1%7Bx%7D~1b~1%7Bx%7D/get/responses" + }, + "https://example.com/openapi.json#/paths/~1a~1%7Bx%7D~1b~1%7Bx%7D/parameters/0": { + "type": "parameter", + "pointer": "/paths/~1a~1{x}~1b~1{x}/parameters/0", + "parent": "https://example.com/openapi.json#/paths/~1a~1%7Bx%7D~1b~1%7Bx%7D" + }, + "https://example.com/openapi.json#/paths/~1a~1%7Bx%7D~1b~1%7Bx%7D/parameters/0/schema": { + "type": "schema", + "pointer": "/paths/~1a~1{x}~1b~1{x}/parameters/0/schema", + "parent": "https://example.com/openapi.json#/paths/~1a~1%7Bx%7D~1b~1%7Bx%7D/parameters/0", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + } + }, + "operations": [ + { + "type": "path", + "path": "/a/{x}/b/{x}", + "method": "get", + "origin": "https://example.com/openapi.json#/paths/~1a~1%7Bx%7D~1b~1%7Bx%7D/get", + "endpoint": "https://example.com/openapi.json#/paths/~1a~1%7Bx%7D~1b~1%7Bx%7D", + "tags": [], + "servers": [], + "security": [], + "parameters": [ + "https://example.com/openapi.json#/paths/~1a~1%7Bx%7D~1b~1%7Bx%7D/parameters/0" + ] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/path_template_stray_closing_brace_is_unconstrained.json b/test/openapi/frame/3.1/pass/path_template_stray_closing_brace_is_unconstrained.json new file mode 100644 index 000000000..ae3050b5e --- /dev/null +++ b/test/openapi/frame/3.1/pass/path_template_stray_closing_brace_is_unconstrained.json @@ -0,0 +1,86 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets/petId}": { + "get": { + "responses": { + "200": { + "description": "ok" + } + } + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1pets~1petId%7D": { + "type": "path-item", + "pointer": "/paths/~1pets~1petId}", + "parent": "https://example.com/openapi.json#/paths" + }, + "https://example.com/openapi.json#/paths/~1pets~1petId%7D/get": { + "type": "operation", + "pointer": "/paths/~1pets~1petId}/get", + "parent": "https://example.com/openapi.json#/paths/~1pets~1petId%7D" + }, + "https://example.com/openapi.json#/paths/~1pets~1petId%7D/get/responses": { + "type": "responses", + "pointer": "/paths/~1pets~1petId}/get/responses", + "parent": "https://example.com/openapi.json#/paths/~1pets~1petId%7D/get" + }, + "https://example.com/openapi.json#/paths/~1pets~1petId%7D/get/responses/200": { + "type": "response", + "pointer": "/paths/~1pets~1petId}/get/responses/200", + "parent": "https://example.com/openapi.json#/paths/~1pets~1petId%7D/get/responses" + } + }, + "operations": [ + { + "type": "path", + "path": "/pets/petId}", + "method": "get", + "origin": "https://example.com/openapi.json#/paths/~1pets~1petId%7D/get", + "endpoint": "https://example.com/openapi.json#/paths/~1pets~1petId%7D", + "tags": [], + "servers": [], + "security": [], + "parameters": [] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/path_template_through_a_referenced_parameter.json b/test/openapi/frame/3.1/pass/path_template_through_a_referenced_parameter.json new file mode 100644 index 000000000..2ce8a9181 --- /dev/null +++ b/test/openapi/frame/3.1/pass/path_template_through_a_referenced_parameter.json @@ -0,0 +1,126 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "parameters": { + "PetId": { + "name": "petId", + "in": "path", + "required": true, + "schema": {} + } + } + }, + "paths": { + "/pets/{petId}": { + "parameters": [ + { + "$ref": "#/components/parameters/PetId" + } + ], + "get": { + "responses": { + "200": { + "description": "ok" + } + } + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/parameters/PetId": { + "type": "parameter", + "pointer": "/components/parameters/PetId", + "parent": "https://example.com/openapi.json#/components" + }, + "https://example.com/openapi.json#/components/parameters/PetId/schema": { + "type": "schema", + "pointer": "/components/parameters/PetId/schema", + "parent": "https://example.com/openapi.json#/components/parameters/PetId", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D": { + "type": "path-item", + "pointer": "/paths/~1pets~1{petId}", + "parent": "https://example.com/openapi.json#/paths" + }, + "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/get": { + "type": "operation", + "pointer": "/paths/~1pets~1{petId}/get", + "parent": "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D" + }, + "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/get/responses": { + "type": "responses", + "pointer": "/paths/~1pets~1{petId}/get/responses", + "parent": "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/get" + }, + "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/get/responses/200": { + "type": "response", + "pointer": "/paths/~1pets~1{petId}/get/responses/200", + "parent": "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/get/responses" + }, + "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/parameters/0": { + "type": "reference", + "pointer": "/paths/~1pets~1{petId}/parameters/0", + "parent": "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D", + "original": "#/components/parameters/PetId", + "destination": "https://example.com/openapi.json#/components/parameters/PetId" + } + }, + "operations": [ + { + "type": "path", + "path": "/pets/{petId}", + "method": "get", + "origin": "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/get", + "endpoint": "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D", + "tags": [], + "servers": [], + "security": [], + "parameters": [ + "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/parameters/0" + ] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/path_template_twice_over.json b/test/openapi/frame/3.1/pass/path_template_twice_over.json new file mode 100644 index 000000000..b776727dc --- /dev/null +++ b/test/openapi/frame/3.1/pass/path_template_twice_over.json @@ -0,0 +1,127 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets/{petId}/photos/{photoId}": { + "parameters": [ + { + "name": "petId", + "in": "path", + "required": true, + "schema": {} + } + ], + "get": { + "responses": { + "200": { + "description": "ok" + } + }, + "parameters": [ + { + "name": "photoId", + "in": "path", + "required": true, + "schema": {} + } + ] + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D~1photos~1%7BphotoId%7D": { + "type": "path-item", + "pointer": "/paths/~1pets~1{petId}~1photos~1{photoId}", + "parent": "https://example.com/openapi.json#/paths" + }, + "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D~1photos~1%7BphotoId%7D/get": { + "type": "operation", + "pointer": "/paths/~1pets~1{petId}~1photos~1{photoId}/get", + "parent": "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D~1photos~1%7BphotoId%7D" + }, + "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D~1photos~1%7BphotoId%7D/get/parameters/0": { + "type": "parameter", + "pointer": "/paths/~1pets~1{petId}~1photos~1{photoId}/get/parameters/0", + "parent": "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D~1photos~1%7BphotoId%7D/get" + }, + "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D~1photos~1%7BphotoId%7D/get/parameters/0/schema": { + "type": "schema", + "pointer": "/paths/~1pets~1{petId}~1photos~1{photoId}/get/parameters/0/schema", + "parent": "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D~1photos~1%7BphotoId%7D/get/parameters/0", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D~1photos~1%7BphotoId%7D/get/responses": { + "type": "responses", + "pointer": "/paths/~1pets~1{petId}~1photos~1{photoId}/get/responses", + "parent": "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D~1photos~1%7BphotoId%7D/get" + }, + "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D~1photos~1%7BphotoId%7D/get/responses/200": { + "type": "response", + "pointer": "/paths/~1pets~1{petId}~1photos~1{photoId}/get/responses/200", + "parent": "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D~1photos~1%7BphotoId%7D/get/responses" + }, + "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D~1photos~1%7BphotoId%7D/parameters/0": { + "type": "parameter", + "pointer": "/paths/~1pets~1{petId}~1photos~1{photoId}/parameters/0", + "parent": "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D~1photos~1%7BphotoId%7D" + }, + "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D~1photos~1%7BphotoId%7D/parameters/0/schema": { + "type": "schema", + "pointer": "/paths/~1pets~1{petId}~1photos~1{photoId}/parameters/0/schema", + "parent": "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D~1photos~1%7BphotoId%7D/parameters/0", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + } + }, + "operations": [ + { + "type": "path", + "path": "/pets/{petId}/photos/{photoId}", + "method": "get", + "origin": "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D~1photos~1%7BphotoId%7D/get", + "endpoint": "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D~1photos~1%7BphotoId%7D", + "tags": [], + "servers": [], + "security": [], + "parameters": [ + "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D~1photos~1%7BphotoId%7D/parameters/0", + "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D~1photos~1%7BphotoId%7D/get/parameters/0" + ] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/path_template_two_unbalanced_braces_differ.json b/test/openapi/frame/3.1/pass/path_template_two_unbalanced_braces_differ.json new file mode 100644 index 000000000..2d75408bc --- /dev/null +++ b/test/openapi/frame/3.1/pass/path_template_two_unbalanced_braces_differ.json @@ -0,0 +1,126 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/a/{x": { + "get": { + "responses": { + "200": { + "description": "ok" + } + } + } + }, + "/a/{y": { + "get": { + "responses": { + "200": { + "description": "ok" + } + } + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1a~1%7Bx": { + "type": "path-item", + "pointer": "/paths/~1a~1{x", + "parent": "https://example.com/openapi.json#/paths" + }, + "https://example.com/openapi.json#/paths/~1a~1%7Bx/get": { + "type": "operation", + "pointer": "/paths/~1a~1{x/get", + "parent": "https://example.com/openapi.json#/paths/~1a~1%7Bx" + }, + "https://example.com/openapi.json#/paths/~1a~1%7Bx/get/responses": { + "type": "responses", + "pointer": "/paths/~1a~1{x/get/responses", + "parent": "https://example.com/openapi.json#/paths/~1a~1%7Bx/get" + }, + "https://example.com/openapi.json#/paths/~1a~1%7Bx/get/responses/200": { + "type": "response", + "pointer": "/paths/~1a~1{x/get/responses/200", + "parent": "https://example.com/openapi.json#/paths/~1a~1%7Bx/get/responses" + }, + "https://example.com/openapi.json#/paths/~1a~1%7By": { + "type": "path-item", + "pointer": "/paths/~1a~1{y", + "parent": "https://example.com/openapi.json#/paths" + }, + "https://example.com/openapi.json#/paths/~1a~1%7By/get": { + "type": "operation", + "pointer": "/paths/~1a~1{y/get", + "parent": "https://example.com/openapi.json#/paths/~1a~1%7By" + }, + "https://example.com/openapi.json#/paths/~1a~1%7By/get/responses": { + "type": "responses", + "pointer": "/paths/~1a~1{y/get/responses", + "parent": "https://example.com/openapi.json#/paths/~1a~1%7By/get" + }, + "https://example.com/openapi.json#/paths/~1a~1%7By/get/responses/200": { + "type": "response", + "pointer": "/paths/~1a~1{y/get/responses/200", + "parent": "https://example.com/openapi.json#/paths/~1a~1%7By/get/responses" + } + }, + "operations": [ + { + "type": "path", + "path": "/a/{x", + "method": "get", + "origin": "https://example.com/openapi.json#/paths/~1a~1%7Bx/get", + "endpoint": "https://example.com/openapi.json#/paths/~1a~1%7Bx", + "tags": [], + "servers": [], + "security": [], + "parameters": [] + }, + { + "type": "path", + "path": "/a/{y", + "method": "get", + "origin": "https://example.com/openapi.json#/paths/~1a~1%7By/get", + "endpoint": "https://example.com/openapi.json#/paths/~1a~1%7By", + "tags": [], + "servers": [], + "security": [], + "parameters": [] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/path_template_unbalanced_brace_is_unconstrained.json b/test/openapi/frame/3.1/pass/path_template_unbalanced_brace_is_unconstrained.json new file mode 100644 index 000000000..dbf2b9128 --- /dev/null +++ b/test/openapi/frame/3.1/pass/path_template_unbalanced_brace_is_unconstrained.json @@ -0,0 +1,86 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets/{petId": { + "get": { + "responses": { + "200": { + "description": "ok" + } + } + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1pets~1%7BpetId": { + "type": "path-item", + "pointer": "/paths/~1pets~1{petId", + "parent": "https://example.com/openapi.json#/paths" + }, + "https://example.com/openapi.json#/paths/~1pets~1%7BpetId/get": { + "type": "operation", + "pointer": "/paths/~1pets~1{petId/get", + "parent": "https://example.com/openapi.json#/paths/~1pets~1%7BpetId" + }, + "https://example.com/openapi.json#/paths/~1pets~1%7BpetId/get/responses": { + "type": "responses", + "pointer": "/paths/~1pets~1{petId/get/responses", + "parent": "https://example.com/openapi.json#/paths/~1pets~1%7BpetId/get" + }, + "https://example.com/openapi.json#/paths/~1pets~1%7BpetId/get/responses/200": { + "type": "response", + "pointer": "/paths/~1pets~1{petId/get/responses/200", + "parent": "https://example.com/openapi.json#/paths/~1pets~1%7BpetId/get/responses" + } + }, + "operations": [ + { + "type": "path", + "path": "/pets/{petId", + "method": "get", + "origin": "https://example.com/openapi.json#/paths/~1pets~1%7BpetId/get", + "endpoint": "https://example.com/openapi.json#/paths/~1pets~1%7BpetId", + "tags": [], + "servers": [], + "security": [], + "parameters": [] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/path_template_with_an_unresolved_parameter_reference.json b/test/openapi/frame/3.1/pass/path_template_with_an_unresolved_parameter_reference.json new file mode 100644 index 000000000..aa2949789 --- /dev/null +++ b/test/openapi/frame/3.1/pass/path_template_with_an_unresolved_parameter_reference.json @@ -0,0 +1,103 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets/{petId}": { + "parameters": [ + { + "$ref": "https://example.com/common.json#/components/parameters/PetId" + } + ], + "get": { + "responses": { + "200": { + "description": "ok" + } + } + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": false, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D": { + "type": "path-item", + "pointer": "/paths/~1pets~1{petId}", + "parent": "https://example.com/openapi.json#/paths" + }, + "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/get": { + "type": "operation", + "pointer": "/paths/~1pets~1{petId}/get", + "parent": "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D" + }, + "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/get/responses": { + "type": "responses", + "pointer": "/paths/~1pets~1{petId}/get/responses", + "parent": "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/get" + }, + "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/get/responses/200": { + "type": "response", + "pointer": "/paths/~1pets~1{petId}/get/responses/200", + "parent": "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/get/responses" + }, + "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/parameters/0": { + "type": "reference", + "pointer": "/paths/~1pets~1{petId}/parameters/0", + "parent": "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D", + "original": "https://example.com/common.json#/components/parameters/PetId", + "destination": "https://example.com/common.json#/components/parameters/PetId" + } + }, + "operations": [ + { + "type": "path", + "path": "/pets/{petId}", + "method": "get", + "origin": "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/get", + "endpoint": "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D", + "tags": [], + "servers": [], + "security": [], + "parameters": [ + "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/parameters/0" + ] + } + ] + }, + "dangling": [ + "https://example.com/common.json#/components/parameters/PetId" + ] +} diff --git a/test/openapi/frame/3.1/pass/paths_all_methods.json b/test/openapi/frame/3.1/pass/paths_all_methods.json new file mode 100644 index 000000000..ea9dafd63 --- /dev/null +++ b/test/openapi/frame/3.1/pass/paths_all_methods.json @@ -0,0 +1,421 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets": { + "summary": "Pets", + "description": "Everything about pets", + "servers": [ + { + "url": "/v1" + } + ], + "parameters": [ + { + "name": "trace", + "in": "header", + "schema": {} + }, + { + "$ref": "#/components/parameters/Limit" + } + ], + "get": { + "responses": { + "200": { + "description": "ok" + } + } + }, + "put": { + "responses": { + "200": { + "description": "ok" + } + } + }, + "post": { + "responses": { + "201": { + "description": "created" + } + } + }, + "delete": { + "responses": { + "204": { + "description": "gone" + } + } + }, + "options": { + "responses": { + "200": { + "description": "ok" + } + } + }, + "head": { + "responses": { + "200": { + "description": "ok" + } + } + }, + "patch": { + "responses": { + "200": { + "description": "ok" + } + } + }, + "trace": { + "responses": { + "200": { + "description": "ok" + } + } + } + } + }, + "components": { + "parameters": { + "Limit": { + "name": "limit", + "in": "query", + "schema": {} + } + } + } + }, + "frame": { + "version": "3.1", + "base": "", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/components": { + "type": "components", + "pointer": "/components", + "parent": "" + }, + "#/components/parameters/Limit": { + "type": "parameter", + "pointer": "/components/parameters/Limit", + "parent": "#/components" + }, + "#/components/parameters/Limit/schema": { + "type": "schema", + "pointer": "/components/parameters/Limit/schema", + "parent": "#/components/parameters/Limit", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/info": { + "type": "info", + "pointer": "/info", + "parent": "" + }, + "#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "" + }, + "#/paths/~1pets": { + "type": "path-item", + "pointer": "/paths/~1pets", + "parent": "#/paths" + }, + "#/paths/~1pets/delete": { + "type": "operation", + "pointer": "/paths/~1pets/delete", + "parent": "#/paths/~1pets" + }, + "#/paths/~1pets/delete/responses": { + "type": "responses", + "pointer": "/paths/~1pets/delete/responses", + "parent": "#/paths/~1pets/delete" + }, + "#/paths/~1pets/delete/responses/204": { + "type": "response", + "pointer": "/paths/~1pets/delete/responses/204", + "parent": "#/paths/~1pets/delete/responses" + }, + "#/paths/~1pets/get": { + "type": "operation", + "pointer": "/paths/~1pets/get", + "parent": "#/paths/~1pets" + }, + "#/paths/~1pets/get/responses": { + "type": "responses", + "pointer": "/paths/~1pets/get/responses", + "parent": "#/paths/~1pets/get" + }, + "#/paths/~1pets/get/responses/200": { + "type": "response", + "pointer": "/paths/~1pets/get/responses/200", + "parent": "#/paths/~1pets/get/responses" + }, + "#/paths/~1pets/head": { + "type": "operation", + "pointer": "/paths/~1pets/head", + "parent": "#/paths/~1pets" + }, + "#/paths/~1pets/head/responses": { + "type": "responses", + "pointer": "/paths/~1pets/head/responses", + "parent": "#/paths/~1pets/head" + }, + "#/paths/~1pets/head/responses/200": { + "type": "response", + "pointer": "/paths/~1pets/head/responses/200", + "parent": "#/paths/~1pets/head/responses" + }, + "#/paths/~1pets/options": { + "type": "operation", + "pointer": "/paths/~1pets/options", + "parent": "#/paths/~1pets" + }, + "#/paths/~1pets/options/responses": { + "type": "responses", + "pointer": "/paths/~1pets/options/responses", + "parent": "#/paths/~1pets/options" + }, + "#/paths/~1pets/options/responses/200": { + "type": "response", + "pointer": "/paths/~1pets/options/responses/200", + "parent": "#/paths/~1pets/options/responses" + }, + "#/paths/~1pets/parameters/0": { + "type": "parameter", + "pointer": "/paths/~1pets/parameters/0", + "parent": "#/paths/~1pets" + }, + "#/paths/~1pets/parameters/0/schema": { + "type": "schema", + "pointer": "/paths/~1pets/parameters/0/schema", + "parent": "#/paths/~1pets/parameters/0", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/paths/~1pets/parameters/1": { + "type": "reference", + "pointer": "/paths/~1pets/parameters/1", + "parent": "#/paths/~1pets", + "original": "#/components/parameters/Limit", + "destination": "#/components/parameters/Limit" + }, + "#/paths/~1pets/patch": { + "type": "operation", + "pointer": "/paths/~1pets/patch", + "parent": "#/paths/~1pets" + }, + "#/paths/~1pets/patch/responses": { + "type": "responses", + "pointer": "/paths/~1pets/patch/responses", + "parent": "#/paths/~1pets/patch" + }, + "#/paths/~1pets/patch/responses/200": { + "type": "response", + "pointer": "/paths/~1pets/patch/responses/200", + "parent": "#/paths/~1pets/patch/responses" + }, + "#/paths/~1pets/post": { + "type": "operation", + "pointer": "/paths/~1pets/post", + "parent": "#/paths/~1pets" + }, + "#/paths/~1pets/post/responses": { + "type": "responses", + "pointer": "/paths/~1pets/post/responses", + "parent": "#/paths/~1pets/post" + }, + "#/paths/~1pets/post/responses/201": { + "type": "response", + "pointer": "/paths/~1pets/post/responses/201", + "parent": "#/paths/~1pets/post/responses" + }, + "#/paths/~1pets/put": { + "type": "operation", + "pointer": "/paths/~1pets/put", + "parent": "#/paths/~1pets" + }, + "#/paths/~1pets/put/responses": { + "type": "responses", + "pointer": "/paths/~1pets/put/responses", + "parent": "#/paths/~1pets/put" + }, + "#/paths/~1pets/put/responses/200": { + "type": "response", + "pointer": "/paths/~1pets/put/responses/200", + "parent": "#/paths/~1pets/put/responses" + }, + "#/paths/~1pets/servers/0": { + "type": "server", + "pointer": "/paths/~1pets/servers/0", + "parent": "#/paths/~1pets" + }, + "#/paths/~1pets/trace": { + "type": "operation", + "pointer": "/paths/~1pets/trace", + "parent": "#/paths/~1pets" + }, + "#/paths/~1pets/trace/responses": { + "type": "responses", + "pointer": "/paths/~1pets/trace/responses", + "parent": "#/paths/~1pets/trace" + }, + "#/paths/~1pets/trace/responses/200": { + "type": "response", + "pointer": "/paths/~1pets/trace/responses/200", + "parent": "#/paths/~1pets/trace/responses" + } + }, + "operations": [ + { + "type": "path", + "path": "/pets", + "method": "get", + "origin": "#/paths/~1pets/get", + "endpoint": "#/paths/~1pets", + "tags": [], + "servers": [ + "#/paths/~1pets/servers/0" + ], + "security": [], + "parameters": [ + "#/paths/~1pets/parameters/0", + "#/paths/~1pets/parameters/1" + ] + }, + { + "type": "path", + "path": "/pets", + "method": "put", + "origin": "#/paths/~1pets/put", + "endpoint": "#/paths/~1pets", + "tags": [], + "servers": [ + "#/paths/~1pets/servers/0" + ], + "security": [], + "parameters": [ + "#/paths/~1pets/parameters/0", + "#/paths/~1pets/parameters/1" + ] + }, + { + "type": "path", + "path": "/pets", + "method": "post", + "origin": "#/paths/~1pets/post", + "endpoint": "#/paths/~1pets", + "tags": [], + "servers": [ + "#/paths/~1pets/servers/0" + ], + "security": [], + "parameters": [ + "#/paths/~1pets/parameters/0", + "#/paths/~1pets/parameters/1" + ] + }, + { + "type": "path", + "path": "/pets", + "method": "delete", + "origin": "#/paths/~1pets/delete", + "endpoint": "#/paths/~1pets", + "tags": [], + "servers": [ + "#/paths/~1pets/servers/0" + ], + "security": [], + "parameters": [ + "#/paths/~1pets/parameters/0", + "#/paths/~1pets/parameters/1" + ] + }, + { + "type": "path", + "path": "/pets", + "method": "options", + "origin": "#/paths/~1pets/options", + "endpoint": "#/paths/~1pets", + "tags": [], + "servers": [ + "#/paths/~1pets/servers/0" + ], + "security": [], + "parameters": [ + "#/paths/~1pets/parameters/0", + "#/paths/~1pets/parameters/1" + ] + }, + { + "type": "path", + "path": "/pets", + "method": "head", + "origin": "#/paths/~1pets/head", + "endpoint": "#/paths/~1pets", + "tags": [], + "servers": [ + "#/paths/~1pets/servers/0" + ], + "security": [], + "parameters": [ + "#/paths/~1pets/parameters/0", + "#/paths/~1pets/parameters/1" + ] + }, + { + "type": "path", + "path": "/pets", + "method": "patch", + "origin": "#/paths/~1pets/patch", + "endpoint": "#/paths/~1pets", + "tags": [], + "servers": [ + "#/paths/~1pets/servers/0" + ], + "security": [], + "parameters": [ + "#/paths/~1pets/parameters/0", + "#/paths/~1pets/parameters/1" + ] + }, + { + "type": "path", + "path": "/pets", + "method": "trace", + "origin": "#/paths/~1pets/trace", + "endpoint": "#/paths/~1pets", + "tags": [], + "servers": [ + "#/paths/~1pets/servers/0" + ], + "security": [], + "parameters": [ + "#/paths/~1pets/parameters/0", + "#/paths/~1pets/parameters/1" + ] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/paths_empty.json b/test/openapi/frame/3.1/pass/paths_empty.json new file mode 100644 index 000000000..777ec09d8 --- /dev/null +++ b/test/openapi/frame/3.1/pass/paths_empty.json @@ -0,0 +1,43 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": {} + }, + "frame": { + "version": "3.1", + "base": "", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/info": { + "type": "info", + "pointer": "/info", + "parent": "" + }, + "#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/paths_templated.json b/test/openapi/frame/3.1/pass/paths_templated.json new file mode 100644 index 000000000..932daa984 --- /dev/null +++ b/test/openapi/frame/3.1/pass/paths_templated.json @@ -0,0 +1,214 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets": { + "get": { + "responses": { + "200": { + "description": "ok" + } + } + } + }, + "/{entity}/me": { + "get": { + "responses": { + "default": { + "description": "ok" + } + } + }, + "parameters": [ + { + "name": "entity", + "in": "path", + "required": true, + "schema": {} + } + ] + }, + "/books/{id}": { + "get": { + "responses": { + "2XX": { + "description": "ok" + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": {} + } + ] + } + }, + "/pets/mine": {}, + "x-internal": true + } + }, + "frame": { + "version": "3.1", + "base": "", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/info": { + "type": "info", + "pointer": "/info", + "parent": "" + }, + "#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "" + }, + "#/paths/~1%7Bentity%7D~1me": { + "type": "path-item", + "pointer": "/paths/~1{entity}~1me", + "parent": "#/paths" + }, + "#/paths/~1%7Bentity%7D~1me/get": { + "type": "operation", + "pointer": "/paths/~1{entity}~1me/get", + "parent": "#/paths/~1%7Bentity%7D~1me" + }, + "#/paths/~1%7Bentity%7D~1me/get/responses": { + "type": "responses", + "pointer": "/paths/~1{entity}~1me/get/responses", + "parent": "#/paths/~1%7Bentity%7D~1me/get" + }, + "#/paths/~1%7Bentity%7D~1me/get/responses/default": { + "type": "response", + "pointer": "/paths/~1{entity}~1me/get/responses/default", + "parent": "#/paths/~1%7Bentity%7D~1me/get/responses" + }, + "#/paths/~1%7Bentity%7D~1me/parameters/0": { + "type": "parameter", + "pointer": "/paths/~1{entity}~1me/parameters/0", + "parent": "#/paths/~1%7Bentity%7D~1me" + }, + "#/paths/~1%7Bentity%7D~1me/parameters/0/schema": { + "type": "schema", + "pointer": "/paths/~1{entity}~1me/parameters/0/schema", + "parent": "#/paths/~1%7Bentity%7D~1me/parameters/0", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/paths/~1books~1%7Bid%7D": { + "type": "path-item", + "pointer": "/paths/~1books~1{id}", + "parent": "#/paths" + }, + "#/paths/~1books~1%7Bid%7D/get": { + "type": "operation", + "pointer": "/paths/~1books~1{id}/get", + "parent": "#/paths/~1books~1%7Bid%7D" + }, + "#/paths/~1books~1%7Bid%7D/get/parameters/0": { + "type": "parameter", + "pointer": "/paths/~1books~1{id}/get/parameters/0", + "parent": "#/paths/~1books~1%7Bid%7D/get" + }, + "#/paths/~1books~1%7Bid%7D/get/parameters/0/schema": { + "type": "schema", + "pointer": "/paths/~1books~1{id}/get/parameters/0/schema", + "parent": "#/paths/~1books~1%7Bid%7D/get/parameters/0", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/paths/~1books~1%7Bid%7D/get/responses": { + "type": "responses", + "pointer": "/paths/~1books~1{id}/get/responses", + "parent": "#/paths/~1books~1%7Bid%7D/get" + }, + "#/paths/~1books~1%7Bid%7D/get/responses/2XX": { + "type": "response", + "pointer": "/paths/~1books~1{id}/get/responses/2XX", + "parent": "#/paths/~1books~1%7Bid%7D/get/responses" + }, + "#/paths/~1pets": { + "type": "path-item", + "pointer": "/paths/~1pets", + "parent": "#/paths" + }, + "#/paths/~1pets/get": { + "type": "operation", + "pointer": "/paths/~1pets/get", + "parent": "#/paths/~1pets" + }, + "#/paths/~1pets/get/responses": { + "type": "responses", + "pointer": "/paths/~1pets/get/responses", + "parent": "#/paths/~1pets/get" + }, + "#/paths/~1pets/get/responses/200": { + "type": "response", + "pointer": "/paths/~1pets/get/responses/200", + "parent": "#/paths/~1pets/get/responses" + }, + "#/paths/~1pets~1mine": { + "type": "path-item", + "pointer": "/paths/~1pets~1mine", + "parent": "#/paths" + } + }, + "operations": [ + { + "type": "path", + "path": "/pets", + "method": "get", + "origin": "#/paths/~1pets/get", + "endpoint": "#/paths/~1pets", + "tags": [], + "servers": [], + "security": [], + "parameters": [] + }, + { + "type": "path", + "path": "/{entity}/me", + "method": "get", + "origin": "#/paths/~1%7Bentity%7D~1me/get", + "endpoint": "#/paths/~1%7Bentity%7D~1me", + "tags": [], + "servers": [], + "security": [], + "parameters": [ + "#/paths/~1%7Bentity%7D~1me/parameters/0" + ] + }, + { + "type": "path", + "path": "/books/{id}", + "method": "get", + "origin": "#/paths/~1books~1%7Bid%7D/get", + "endpoint": "#/paths/~1books~1%7Bid%7D", + "tags": [], + "servers": [], + "security": [], + "parameters": [ + "#/paths/~1books~1%7Bid%7D/get/parameters/0" + ] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/paths_with_schema_reference.json b/test/openapi/frame/3.1/pass/paths_with_schema_reference.json new file mode 100644 index 000000000..40c86795e --- /dev/null +++ b/test/openapi/frame/3.1/pass/paths_with_schema_reference.json @@ -0,0 +1,133 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets": { + "get": { + "operationId": "listPets", + "responses": { + "200": { + "description": "A list of pets", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Pet" + } + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "Pet": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string" + } + } + } + } + } + }, + "frame": { + "version": "3.1", + "base": "", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/components": { + "type": "components", + "pointer": "/components", + "parent": "" + }, + "#/components/schemas/Pet": { + "type": "schema", + "pointer": "/components/schemas/Pet", + "parent": "#/components", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/info": { + "type": "info", + "pointer": "/info", + "parent": "" + }, + "#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "" + }, + "#/paths/~1pets": { + "type": "path-item", + "pointer": "/paths/~1pets", + "parent": "#/paths" + }, + "#/paths/~1pets/get": { + "type": "operation", + "pointer": "/paths/~1pets/get", + "parent": "#/paths/~1pets" + }, + "#/paths/~1pets/get/responses": { + "type": "responses", + "pointer": "/paths/~1pets/get/responses", + "parent": "#/paths/~1pets/get" + }, + "#/paths/~1pets/get/responses/200": { + "type": "response", + "pointer": "/paths/~1pets/get/responses/200", + "parent": "#/paths/~1pets/get/responses" + }, + "#/paths/~1pets/get/responses/200/content/application~1json": { + "type": "media-type", + "pointer": "/paths/~1pets/get/responses/200/content/application~1json", + "parent": "#/paths/~1pets/get/responses/200" + }, + "#/paths/~1pets/get/responses/200/content/application~1json/schema": { + "type": "schema", + "pointer": "/paths/~1pets/get/responses/200/content/application~1json/schema", + "parent": "#/paths/~1pets/get/responses/200/content/application~1json", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + } + }, + "operations": [ + { + "type": "path", + "path": "/pets", + "method": "get", + "origin": "#/paths/~1pets/get", + "endpoint": "#/paths/~1pets", + "tags": [], + "servers": [], + "security": [], + "parameters": [] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/reference_canonicalised_current_directory.json b/test/openapi/frame/3.1/pass/reference_canonicalised_current_directory.json new file mode 100644 index 000000000..2f6aaaf92 --- /dev/null +++ b/test/openapi/frame/3.1/pass/reference_canonicalised_current_directory.json @@ -0,0 +1,94 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "R": { + "$ref": "./common.json#/components/responses/NotFound" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/common.json": { + "openapi": "3.1.1", + "info": { + "title": "Common", + "version": "1.0.0" + }, + "components": { + "responses": { + "NotFound": { + "description": "ok" + } + } + } + } + }, + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/common.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/common.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/common.json" + }, + "https://example.com/common.json#/components/responses/NotFound": { + "type": "response", + "pointer": "/components/responses/NotFound", + "parent": "https://example.com/common.json#/components" + }, + "https://example.com/common.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/common.json" + }, + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/responses/R": { + "type": "reference", + "pointer": "/components/responses/R", + "parent": "https://example.com/openapi.json#/components", + "original": "./common.json#/components/responses/NotFound", + "destination": "https://example.com/common.json#/components/responses/NotFound" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/reference_canonicalised_default_port.json b/test/openapi/frame/3.1/pass/reference_canonicalised_default_port.json new file mode 100644 index 000000000..aba4dcd6a --- /dev/null +++ b/test/openapi/frame/3.1/pass/reference_canonicalised_default_port.json @@ -0,0 +1,94 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "R": { + "$ref": "https://example.com:443/common.json#/components/responses/NotFound" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/common.json": { + "openapi": "3.1.1", + "info": { + "title": "Common", + "version": "1.0.0" + }, + "components": { + "responses": { + "NotFound": { + "description": "ok" + } + } + } + } + }, + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/common.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/common.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/common.json" + }, + "https://example.com/common.json#/components/responses/NotFound": { + "type": "response", + "pointer": "/components/responses/NotFound", + "parent": "https://example.com/common.json#/components" + }, + "https://example.com/common.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/common.json" + }, + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/responses/R": { + "type": "reference", + "pointer": "/components/responses/R", + "parent": "https://example.com/openapi.json#/components", + "original": "https://example.com:443/common.json#/components/responses/NotFound", + "destination": "https://example.com/common.json#/components/responses/NotFound" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/reference_canonicalised_dot_segments.json b/test/openapi/frame/3.1/pass/reference_canonicalised_dot_segments.json new file mode 100644 index 000000000..d6a251a77 --- /dev/null +++ b/test/openapi/frame/3.1/pass/reference_canonicalised_dot_segments.json @@ -0,0 +1,94 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "R": { + "$ref": "https://example.com/schemas/../common.json#/components/responses/NotFound" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/common.json": { + "openapi": "3.1.1", + "info": { + "title": "Common", + "version": "1.0.0" + }, + "components": { + "responses": { + "NotFound": { + "description": "ok" + } + } + } + } + }, + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/common.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/common.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/common.json" + }, + "https://example.com/common.json#/components/responses/NotFound": { + "type": "response", + "pointer": "/components/responses/NotFound", + "parent": "https://example.com/common.json#/components" + }, + "https://example.com/common.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/common.json" + }, + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/responses/R": { + "type": "reference", + "pointer": "/components/responses/R", + "parent": "https://example.com/openapi.json#/components", + "original": "https://example.com/schemas/../common.json#/components/responses/NotFound", + "destination": "https://example.com/common.json#/components/responses/NotFound" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/reference_canonicalised_relative_dot_segments.json b/test/openapi/frame/3.1/pass/reference_canonicalised_relative_dot_segments.json new file mode 100644 index 000000000..d58951e49 --- /dev/null +++ b/test/openapi/frame/3.1/pass/reference_canonicalised_relative_dot_segments.json @@ -0,0 +1,94 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "R": { + "$ref": "./schemas/../common.json#/components/responses/NotFound" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/common.json": { + "openapi": "3.1.1", + "info": { + "title": "Common", + "version": "1.0.0" + }, + "components": { + "responses": { + "NotFound": { + "description": "ok" + } + } + } + } + }, + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/common.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/common.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/common.json" + }, + "https://example.com/common.json#/components/responses/NotFound": { + "type": "response", + "pointer": "/components/responses/NotFound", + "parent": "https://example.com/common.json#/components" + }, + "https://example.com/common.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/common.json" + }, + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/responses/R": { + "type": "reference", + "pointer": "/components/responses/R", + "parent": "https://example.com/openapi.json#/components", + "original": "./schemas/../common.json#/components/responses/NotFound", + "destination": "https://example.com/common.json#/components/responses/NotFound" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/reference_canonicalised_scheme_and_host.json b/test/openapi/frame/3.1/pass/reference_canonicalised_scheme_and_host.json new file mode 100644 index 000000000..05155bdd3 --- /dev/null +++ b/test/openapi/frame/3.1/pass/reference_canonicalised_scheme_and_host.json @@ -0,0 +1,94 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "R": { + "$ref": "HTTPS://Example.COM/common.json#/components/responses/NotFound" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/common.json": { + "openapi": "3.1.1", + "info": { + "title": "Common", + "version": "1.0.0" + }, + "components": { + "responses": { + "NotFound": { + "description": "ok" + } + } + } + } + }, + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/common.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/common.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/common.json" + }, + "https://example.com/common.json#/components/responses/NotFound": { + "type": "response", + "pointer": "/components/responses/NotFound", + "parent": "https://example.com/common.json#/components" + }, + "https://example.com/common.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/common.json" + }, + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/responses/R": { + "type": "reference", + "pointer": "/components/responses/R", + "parent": "https://example.com/openapi.json#/components", + "original": "HTTPS://Example.COM/common.json#/components/responses/NotFound", + "destination": "https://example.com/common.json#/components/responses/NotFound" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/reference_canonicalised_when_it_lands_nowhere.json b/test/openapi/frame/3.1/pass/reference_canonicalised_when_it_lands_nowhere.json new file mode 100644 index 000000000..f5f89be90 --- /dev/null +++ b/test/openapi/frame/3.1/pass/reference_canonicalised_when_it_lands_nowhere.json @@ -0,0 +1,65 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "R": { + "$ref": "HTTPS://Example.COM:443/missing.json#/components/responses/NotFound" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/common.json": { + "description": "ok" + } + }, + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": false, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/responses/R": { + "type": "reference", + "pointer": "/components/responses/R", + "parent": "https://example.com/openapi.json#/components", + "original": "HTTPS://Example.COM:443/missing.json#/components/responses/NotFound", + "destination": "https://example.com/missing.json#/components/responses/NotFound" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [] + }, + "dangling": [ + "https://example.com/missing.json#/components/responses/NotFound" + ] +} diff --git a/test/openapi/frame/3.1/pass/reference_canonicalises_to_the_entry_document.json b/test/openapi/frame/3.1/pass/reference_canonicalises_to_the_entry_document.json new file mode 100644 index 000000000..6ea78bc28 --- /dev/null +++ b/test/openapi/frame/3.1/pass/reference_canonicalises_to_the_entry_document.json @@ -0,0 +1,58 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "R": { + "$ref": "HTTPS://Example.COM:443/openapi.json#/components/responses/R" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": {}, + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/responses/R": { + "type": "reference", + "pointer": "/components/responses/R", + "parent": "https://example.com/openapi.json#/components", + "original": "HTTPS://Example.COM:443/openapi.json#/components/responses/R", + "destination": "https://example.com/openapi.json#/components/responses/R" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/reference_from_a_callback_path_item.json b/test/openapi/frame/3.1/pass/reference_from_a_callback_path_item.json new file mode 100644 index 000000000..68d5d7c55 --- /dev/null +++ b/test/openapi/frame/3.1/pass/reference_from_a_callback_path_item.json @@ -0,0 +1,154 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "pathItems": { + "OnData": { + "post": { + "responses": { + "200": { + "description": "ok" + } + } + } + } + } + }, + "paths": { + "/pets": { + "get": { + "responses": { + "200": { + "description": "ok" + } + }, + "callbacks": { + "onData": { + "{$request.body#/url}": { + "$ref": "#/components/pathItems/OnData" + } + } + } + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/pathItems/OnData": { + "type": "path-item", + "pointer": "/components/pathItems/OnData", + "parent": "https://example.com/openapi.json#/components" + }, + "https://example.com/openapi.json#/components/pathItems/OnData/post": { + "type": "operation", + "pointer": "/components/pathItems/OnData/post", + "parent": "https://example.com/openapi.json#/components/pathItems/OnData" + }, + "https://example.com/openapi.json#/components/pathItems/OnData/post/responses": { + "type": "responses", + "pointer": "/components/pathItems/OnData/post/responses", + "parent": "https://example.com/openapi.json#/components/pathItems/OnData/post" + }, + "https://example.com/openapi.json#/components/pathItems/OnData/post/responses/200": { + "type": "response", + "pointer": "/components/pathItems/OnData/post/responses/200", + "parent": "https://example.com/openapi.json#/components/pathItems/OnData/post/responses" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1pets": { + "type": "path-item", + "pointer": "/paths/~1pets", + "parent": "https://example.com/openapi.json#/paths" + }, + "https://example.com/openapi.json#/paths/~1pets/get": { + "type": "operation", + "pointer": "/paths/~1pets/get", + "parent": "https://example.com/openapi.json#/paths/~1pets" + }, + "https://example.com/openapi.json#/paths/~1pets/get/callbacks/onData": { + "type": "callback", + "pointer": "/paths/~1pets/get/callbacks/onData", + "parent": "https://example.com/openapi.json#/paths/~1pets/get" + }, + "https://example.com/openapi.json#/paths/~1pets/get/callbacks/onData/%7B$request.body%23~1url%7D": { + "type": "path-item", + "pointer": "/paths/~1pets/get/callbacks/onData/{$request.body#~1url}", + "parent": "https://example.com/openapi.json#/paths/~1pets/get/callbacks/onData", + "original": "#/components/pathItems/OnData", + "destination": "https://example.com/openapi.json#/components/pathItems/OnData" + }, + "https://example.com/openapi.json#/paths/~1pets/get/responses": { + "type": "responses", + "pointer": "/paths/~1pets/get/responses", + "parent": "https://example.com/openapi.json#/paths/~1pets/get" + }, + "https://example.com/openapi.json#/paths/~1pets/get/responses/200": { + "type": "response", + "pointer": "/paths/~1pets/get/responses/200", + "parent": "https://example.com/openapi.json#/paths/~1pets/get/responses" + } + }, + "operations": [ + { + "type": "path", + "path": "/pets", + "method": "get", + "origin": "https://example.com/openapi.json#/paths/~1pets/get", + "endpoint": "https://example.com/openapi.json#/paths/~1pets", + "tags": [], + "servers": [], + "security": [], + "parameters": [] + }, + { + "type": "callback", + "path": "{$request.body#/url}", + "method": "post", + "origin": "https://example.com/openapi.json#/components/pathItems/OnData/post", + "endpoint": "https://example.com/openapi.json#/paths/~1pets/get/callbacks/onData/%7B$request.body%23~1url%7D", + "tags": [], + "servers": [], + "security": [], + "parameters": [] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/reference_into_a_callback_expression.json b/test/openapi/frame/3.1/pass/reference_into_a_callback_expression.json new file mode 100644 index 000000000..b790b0f75 --- /dev/null +++ b/test/openapi/frame/3.1/pass/reference_into_a_callback_expression.json @@ -0,0 +1,154 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "A": { + "$ref": "#/paths/~1pets/get/callbacks/onData/%7B$request.body%23~1url%7D/post/responses/200" + } + } + }, + "paths": { + "/pets": { + "get": { + "responses": { + "200": { + "description": "ok" + } + }, + "callbacks": { + "onData": { + "{$request.body#/url}": { + "post": { + "responses": { + "200": { + "description": "ok" + } + } + } + } + } + } + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/responses/A": { + "type": "reference", + "pointer": "/components/responses/A", + "parent": "https://example.com/openapi.json#/components", + "original": "#/paths/~1pets/get/callbacks/onData/%7B$request.body%23~1url%7D/post/responses/200", + "destination": "https://example.com/openapi.json#/paths/~1pets/get/callbacks/onData/%7B$request.body%23~1url%7D/post/responses/200" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1pets": { + "type": "path-item", + "pointer": "/paths/~1pets", + "parent": "https://example.com/openapi.json#/paths" + }, + "https://example.com/openapi.json#/paths/~1pets/get": { + "type": "operation", + "pointer": "/paths/~1pets/get", + "parent": "https://example.com/openapi.json#/paths/~1pets" + }, + "https://example.com/openapi.json#/paths/~1pets/get/callbacks/onData": { + "type": "callback", + "pointer": "/paths/~1pets/get/callbacks/onData", + "parent": "https://example.com/openapi.json#/paths/~1pets/get" + }, + "https://example.com/openapi.json#/paths/~1pets/get/callbacks/onData/%7B$request.body%23~1url%7D": { + "type": "path-item", + "pointer": "/paths/~1pets/get/callbacks/onData/{$request.body#~1url}", + "parent": "https://example.com/openapi.json#/paths/~1pets/get/callbacks/onData" + }, + "https://example.com/openapi.json#/paths/~1pets/get/callbacks/onData/%7B$request.body%23~1url%7D/post": { + "type": "operation", + "pointer": "/paths/~1pets/get/callbacks/onData/{$request.body#~1url}/post", + "parent": "https://example.com/openapi.json#/paths/~1pets/get/callbacks/onData/%7B$request.body%23~1url%7D" + }, + "https://example.com/openapi.json#/paths/~1pets/get/callbacks/onData/%7B$request.body%23~1url%7D/post/responses": { + "type": "responses", + "pointer": "/paths/~1pets/get/callbacks/onData/{$request.body#~1url}/post/responses", + "parent": "https://example.com/openapi.json#/paths/~1pets/get/callbacks/onData/%7B$request.body%23~1url%7D/post" + }, + "https://example.com/openapi.json#/paths/~1pets/get/callbacks/onData/%7B$request.body%23~1url%7D/post/responses/200": { + "type": "response", + "pointer": "/paths/~1pets/get/callbacks/onData/{$request.body#~1url}/post/responses/200", + "parent": "https://example.com/openapi.json#/paths/~1pets/get/callbacks/onData/%7B$request.body%23~1url%7D/post/responses" + }, + "https://example.com/openapi.json#/paths/~1pets/get/responses": { + "type": "responses", + "pointer": "/paths/~1pets/get/responses", + "parent": "https://example.com/openapi.json#/paths/~1pets/get" + }, + "https://example.com/openapi.json#/paths/~1pets/get/responses/200": { + "type": "response", + "pointer": "/paths/~1pets/get/responses/200", + "parent": "https://example.com/openapi.json#/paths/~1pets/get/responses" + } + }, + "operations": [ + { + "type": "path", + "path": "/pets", + "method": "get", + "origin": "https://example.com/openapi.json#/paths/~1pets/get", + "endpoint": "https://example.com/openapi.json#/paths/~1pets", + "tags": [], + "servers": [], + "security": [], + "parameters": [] + }, + { + "type": "callback", + "path": "{$request.body#/url}", + "method": "post", + "origin": "https://example.com/openapi.json#/paths/~1pets/get/callbacks/onData/%7B$request.body%23~1url%7D/post", + "endpoint": "https://example.com/openapi.json#/paths/~1pets/get/callbacks/onData/%7B$request.body%23~1url%7D", + "tags": [], + "servers": [], + "security": [], + "parameters": [] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/reference_into_a_templated_path.json b/test/openapi/frame/3.1/pass/reference_into_a_templated_path.json new file mode 100644 index 000000000..04fe107ad --- /dev/null +++ b/test/openapi/frame/3.1/pass/reference_into_a_templated_path.json @@ -0,0 +1,126 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "A": { + "$ref": "#/paths/~1pets~1%7BpetId%7D/get/responses/200" + } + } + }, + "paths": { + "/pets/{petId}": { + "parameters": [ + { + "name": "petId", + "in": "path", + "required": true, + "schema": {} + } + ], + "get": { + "responses": { + "200": { + "description": "ok" + } + } + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/responses/A": { + "type": "reference", + "pointer": "/components/responses/A", + "parent": "https://example.com/openapi.json#/components", + "original": "#/paths/~1pets~1%7BpetId%7D/get/responses/200", + "destination": "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/get/responses/200" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D": { + "type": "path-item", + "pointer": "/paths/~1pets~1{petId}", + "parent": "https://example.com/openapi.json#/paths" + }, + "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/get": { + "type": "operation", + "pointer": "/paths/~1pets~1{petId}/get", + "parent": "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D" + }, + "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/get/responses": { + "type": "responses", + "pointer": "/paths/~1pets~1{petId}/get/responses", + "parent": "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/get" + }, + "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/get/responses/200": { + "type": "response", + "pointer": "/paths/~1pets~1{petId}/get/responses/200", + "parent": "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/get/responses" + }, + "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/parameters/0": { + "type": "parameter", + "pointer": "/paths/~1pets~1{petId}/parameters/0", + "parent": "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D" + }, + "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/parameters/0/schema": { + "type": "schema", + "pointer": "/paths/~1pets~1{petId}/parameters/0/schema", + "parent": "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/parameters/0", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + } + }, + "operations": [ + { + "type": "path", + "path": "/pets/{petId}", + "method": "get", + "origin": "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/get", + "endpoint": "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D", + "tags": [], + "servers": [], + "security": [], + "parameters": [ + "https://example.com/openapi.json#/paths/~1pets~1%7BpetId%7D/parameters/0" + ] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/reference_object_extra_properties_are_ignored.json b/test/openapi/frame/3.1/pass/reference_object_extra_properties_are_ignored.json new file mode 100644 index 000000000..2e0d1657e --- /dev/null +++ b/test/openapi/frame/3.1/pass/reference_object_extra_properties_are_ignored.json @@ -0,0 +1,75 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "R": { + "$ref": "#/components/responses/Other", + "summary": "A summary", + "description": "A description", + "headers": { + "anything": "at all" + }, + "x-note": true, + "nonsense": [ + 1, + 2, + 3 + ] + }, + "Other": { + "description": "ok" + } + } + } + }, + "frame": { + "version": "3.1", + "base": "", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/components": { + "type": "components", + "pointer": "/components", + "parent": "" + }, + "#/components/responses/Other": { + "type": "response", + "pointer": "/components/responses/Other", + "parent": "#/components" + }, + "#/components/responses/R": { + "type": "reference", + "pointer": "/components/responses/R", + "parent": "#/components", + "original": "#/components/responses/Other", + "destination": "#/components/responses/Other" + }, + "#/info": { + "type": "info", + "pointer": "/info", + "parent": "" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/reference_object_uri_forms.json b/test/openapi/frame/3.1/pass/reference_object_uri_forms.json new file mode 100644 index 000000000..b211fbd02 --- /dev/null +++ b/test/openapi/frame/3.1/pass/reference_object_uri_forms.json @@ -0,0 +1,178 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "SameDocument": { + "$ref": "#/components/responses/Other" + }, + "RelativeDocument": { + "$ref": "common.yaml#/components/responses/NotFound" + }, + "AbsolutePath": { + "$ref": "/shared/common.yaml#/components/responses/NotFound" + }, + "Absolute": { + "$ref": "https://example.com/common.yaml#/components/responses/NotFound" + }, + "NoFragment": { + "$ref": "common.yaml" + }, + "Empty": { + "$ref": "" + }, + "Escaped": { + "$ref": "#/paths/~1pets/get/responses/200" + }, + "Other": { + "description": "ok" + } + } + }, + "paths": { + "/pets": { + "get": { + "responses": { + "200": { + "description": "ok" + } + } + } + } + } + }, + "frame": { + "version": "3.1", + "base": "", + "standalone": false, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/components": { + "type": "components", + "pointer": "/components", + "parent": "" + }, + "#/components/responses/Absolute": { + "type": "reference", + "pointer": "/components/responses/Absolute", + "parent": "#/components", + "original": "https://example.com/common.yaml#/components/responses/NotFound", + "destination": "https://example.com/common.yaml#/components/responses/NotFound" + }, + "#/components/responses/AbsolutePath": { + "type": "reference", + "pointer": "/components/responses/AbsolutePath", + "parent": "#/components", + "original": "/shared/common.yaml#/components/responses/NotFound", + "destination": "/shared/common.yaml#/components/responses/NotFound" + }, + "#/components/responses/Empty": { + "type": "reference", + "pointer": "/components/responses/Empty", + "parent": "#/components", + "original": "", + "destination": "" + }, + "#/components/responses/Escaped": { + "type": "reference", + "pointer": "/components/responses/Escaped", + "parent": "#/components", + "original": "#/paths/~1pets/get/responses/200", + "destination": "#/paths/~1pets/get/responses/200" + }, + "#/components/responses/NoFragment": { + "type": "reference", + "pointer": "/components/responses/NoFragment", + "parent": "#/components", + "original": "common.yaml", + "destination": "common.yaml" + }, + "#/components/responses/Other": { + "type": "response", + "pointer": "/components/responses/Other", + "parent": "#/components" + }, + "#/components/responses/RelativeDocument": { + "type": "reference", + "pointer": "/components/responses/RelativeDocument", + "parent": "#/components", + "original": "common.yaml#/components/responses/NotFound", + "destination": "common.yaml#/components/responses/NotFound" + }, + "#/components/responses/SameDocument": { + "type": "reference", + "pointer": "/components/responses/SameDocument", + "parent": "#/components", + "original": "#/components/responses/Other", + "destination": "#/components/responses/Other" + }, + "#/info": { + "type": "info", + "pointer": "/info", + "parent": "" + }, + "#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "" + }, + "#/paths/~1pets": { + "type": "path-item", + "pointer": "/paths/~1pets", + "parent": "#/paths" + }, + "#/paths/~1pets/get": { + "type": "operation", + "pointer": "/paths/~1pets/get", + "parent": "#/paths/~1pets" + }, + "#/paths/~1pets/get/responses": { + "type": "responses", + "pointer": "/paths/~1pets/get/responses", + "parent": "#/paths/~1pets/get" + }, + "#/paths/~1pets/get/responses/200": { + "type": "response", + "pointer": "/paths/~1pets/get/responses/200", + "parent": "#/paths/~1pets/get/responses" + } + }, + "operations": [ + { + "type": "path", + "path": "/pets", + "method": "get", + "origin": "#/paths/~1pets/get", + "endpoint": "#/paths/~1pets", + "tags": [], + "servers": [], + "security": [], + "parameters": [] + } + ] + }, + "dangling": [ + "https://example.com/common.yaml#/components/responses/NotFound", + "/shared/common.yaml#/components/responses/NotFound", + "common.yaml", + "common.yaml#/components/responses/NotFound" + ] +} diff --git a/test/openapi/frame/3.1/pass/reference_objects_everywhere.json b/test/openapi/frame/3.1/pass/reference_objects_everywhere.json new file mode 100644 index 000000000..fe656d857 --- /dev/null +++ b/test/openapi/frame/3.1/pass/reference_objects_everywhere.json @@ -0,0 +1,470 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "R": { + "$ref": "#/components/responses/Other" + }, + "Other": { + "description": "ok" + } + }, + "parameters": { + "P": { + "$ref": "#/components/parameters/Other" + }, + "Other": { + "name": "q", + "in": "query", + "schema": {} + } + }, + "examples": { + "E": { + "$ref": "#/components/examples/Other" + }, + "Other": { + "value": 1 + } + }, + "requestBodies": { + "B": { + "$ref": "#/components/requestBodies/Other" + }, + "Other": { + "content": {} + } + }, + "headers": { + "H": { + "$ref": "#/components/headers/Other" + }, + "Other": { + "schema": {} + } + }, + "securitySchemes": { + "S": { + "$ref": "#/components/securitySchemes/Other" + }, + "Other": { + "type": "mutualTLS" + } + }, + "links": { + "L": { + "$ref": "#/components/links/Other" + }, + "Other": { + "operationId": "listPets" + } + }, + "callbacks": { + "C": { + "$ref": "#/components/callbacks/Other" + }, + "Other": { + "{$request.body#/url}": {} + } + } + }, + "paths": { + "/pets": { + "parameters": [ + { + "$ref": "#/components/parameters/Other" + } + ], + "get": { + "operationId": "listPets", + "parameters": [ + { + "$ref": "#/components/parameters/Other" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/Other" + }, + "callbacks": { + "onData": { + "$ref": "#/components/callbacks/Other" + } + }, + "responses": { + "default": { + "$ref": "#/components/responses/Other" + }, + "200": { + "description": "ok", + "headers": { + "X-Rate": { + "$ref": "#/components/headers/Other" + } + }, + "links": { + "Self": { + "$ref": "#/components/links/Other" + } + }, + "content": { + "multipart/form-data": { + "encoding": { + "part": { + "headers": { + "X-Part": { + "$ref": "#/components/headers/Other" + } + } + } + } + } + } + } + } + }, + "post": { + "parameters": [ + { + "name": "q", + "in": "query", + "schema": {}, + "examples": { + "E": { + "$ref": "#/components/examples/Other" + } + } + } + ], + "responses": { + "200": { + "description": "ok", + "content": { + "application/json": { + "examples": { + "E": { + "$ref": "#/components/examples/Other" + } + } + } + } + } + } + } + } + } + }, + "frame": { + "version": "3.1", + "base": "", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/components": { + "type": "components", + "pointer": "/components", + "parent": "" + }, + "#/components/callbacks/C": { + "type": "reference", + "pointer": "/components/callbacks/C", + "parent": "#/components", + "original": "#/components/callbacks/Other", + "destination": "#/components/callbacks/Other" + }, + "#/components/callbacks/Other": { + "type": "callback", + "pointer": "/components/callbacks/Other", + "parent": "#/components" + }, + "#/components/callbacks/Other/%7B$request.body%23~1url%7D": { + "type": "path-item", + "pointer": "/components/callbacks/Other/{$request.body#~1url}", + "parent": "#/components/callbacks/Other" + }, + "#/components/examples/E": { + "type": "reference", + "pointer": "/components/examples/E", + "parent": "#/components", + "original": "#/components/examples/Other", + "destination": "#/components/examples/Other" + }, + "#/components/examples/Other": { + "type": "example", + "pointer": "/components/examples/Other", + "parent": "#/components" + }, + "#/components/headers/H": { + "type": "reference", + "pointer": "/components/headers/H", + "parent": "#/components", + "original": "#/components/headers/Other", + "destination": "#/components/headers/Other" + }, + "#/components/headers/Other": { + "type": "header", + "pointer": "/components/headers/Other", + "parent": "#/components" + }, + "#/components/headers/Other/schema": { + "type": "schema", + "pointer": "/components/headers/Other/schema", + "parent": "#/components/headers/Other", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/components/links/L": { + "type": "reference", + "pointer": "/components/links/L", + "parent": "#/components", + "original": "#/components/links/Other", + "destination": "#/components/links/Other" + }, + "#/components/links/Other": { + "type": "link", + "pointer": "/components/links/Other", + "parent": "#/components" + }, + "#/components/parameters/Other": { + "type": "parameter", + "pointer": "/components/parameters/Other", + "parent": "#/components" + }, + "#/components/parameters/Other/schema": { + "type": "schema", + "pointer": "/components/parameters/Other/schema", + "parent": "#/components/parameters/Other", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/components/parameters/P": { + "type": "reference", + "pointer": "/components/parameters/P", + "parent": "#/components", + "original": "#/components/parameters/Other", + "destination": "#/components/parameters/Other" + }, + "#/components/requestBodies/B": { + "type": "reference", + "pointer": "/components/requestBodies/B", + "parent": "#/components", + "original": "#/components/requestBodies/Other", + "destination": "#/components/requestBodies/Other" + }, + "#/components/requestBodies/Other": { + "type": "request-body", + "pointer": "/components/requestBodies/Other", + "parent": "#/components" + }, + "#/components/responses/Other": { + "type": "response", + "pointer": "/components/responses/Other", + "parent": "#/components" + }, + "#/components/responses/R": { + "type": "reference", + "pointer": "/components/responses/R", + "parent": "#/components", + "original": "#/components/responses/Other", + "destination": "#/components/responses/Other" + }, + "#/components/securitySchemes/Other": { + "type": "security-scheme", + "pointer": "/components/securitySchemes/Other", + "parent": "#/components" + }, + "#/components/securitySchemes/S": { + "type": "reference", + "pointer": "/components/securitySchemes/S", + "parent": "#/components", + "original": "#/components/securitySchemes/Other", + "destination": "#/components/securitySchemes/Other" + }, + "#/info": { + "type": "info", + "pointer": "/info", + "parent": "" + }, + "#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "" + }, + "#/paths/~1pets": { + "type": "path-item", + "pointer": "/paths/~1pets", + "parent": "#/paths" + }, + "#/paths/~1pets/get": { + "type": "operation", + "pointer": "/paths/~1pets/get", + "parent": "#/paths/~1pets" + }, + "#/paths/~1pets/get/callbacks/onData": { + "type": "reference", + "pointer": "/paths/~1pets/get/callbacks/onData", + "parent": "#/paths/~1pets/get", + "original": "#/components/callbacks/Other", + "destination": "#/components/callbacks/Other" + }, + "#/paths/~1pets/get/parameters/0": { + "type": "reference", + "pointer": "/paths/~1pets/get/parameters/0", + "parent": "#/paths/~1pets/get", + "original": "#/components/parameters/Other", + "destination": "#/components/parameters/Other" + }, + "#/paths/~1pets/get/requestBody": { + "type": "reference", + "pointer": "/paths/~1pets/get/requestBody", + "parent": "#/paths/~1pets/get", + "original": "#/components/requestBodies/Other", + "destination": "#/components/requestBodies/Other" + }, + "#/paths/~1pets/get/responses": { + "type": "responses", + "pointer": "/paths/~1pets/get/responses", + "parent": "#/paths/~1pets/get" + }, + "#/paths/~1pets/get/responses/200": { + "type": "response", + "pointer": "/paths/~1pets/get/responses/200", + "parent": "#/paths/~1pets/get/responses" + }, + "#/paths/~1pets/get/responses/200/content/multipart~1form-data": { + "type": "media-type", + "pointer": "/paths/~1pets/get/responses/200/content/multipart~1form-data", + "parent": "#/paths/~1pets/get/responses/200" + }, + "#/paths/~1pets/get/responses/200/content/multipart~1form-data/encoding/part": { + "type": "encoding", + "pointer": "/paths/~1pets/get/responses/200/content/multipart~1form-data/encoding/part", + "parent": "#/paths/~1pets/get/responses/200/content/multipart~1form-data" + }, + "#/paths/~1pets/get/responses/200/content/multipart~1form-data/encoding/part/headers/X-Part": { + "type": "reference", + "pointer": "/paths/~1pets/get/responses/200/content/multipart~1form-data/encoding/part/headers/X-Part", + "parent": "#/paths/~1pets/get/responses/200/content/multipart~1form-data/encoding/part", + "original": "#/components/headers/Other", + "destination": "#/components/headers/Other" + }, + "#/paths/~1pets/get/responses/200/headers/X-Rate": { + "type": "reference", + "pointer": "/paths/~1pets/get/responses/200/headers/X-Rate", + "parent": "#/paths/~1pets/get/responses/200", + "original": "#/components/headers/Other", + "destination": "#/components/headers/Other" + }, + "#/paths/~1pets/get/responses/200/links/Self": { + "type": "reference", + "pointer": "/paths/~1pets/get/responses/200/links/Self", + "parent": "#/paths/~1pets/get/responses/200", + "original": "#/components/links/Other", + "destination": "#/components/links/Other" + }, + "#/paths/~1pets/get/responses/default": { + "type": "reference", + "pointer": "/paths/~1pets/get/responses/default", + "parent": "#/paths/~1pets/get/responses", + "original": "#/components/responses/Other", + "destination": "#/components/responses/Other" + }, + "#/paths/~1pets/parameters/0": { + "type": "reference", + "pointer": "/paths/~1pets/parameters/0", + "parent": "#/paths/~1pets", + "original": "#/components/parameters/Other", + "destination": "#/components/parameters/Other" + }, + "#/paths/~1pets/post": { + "type": "operation", + "pointer": "/paths/~1pets/post", + "parent": "#/paths/~1pets" + }, + "#/paths/~1pets/post/parameters/0": { + "type": "parameter", + "pointer": "/paths/~1pets/post/parameters/0", + "parent": "#/paths/~1pets/post" + }, + "#/paths/~1pets/post/parameters/0/examples/E": { + "type": "reference", + "pointer": "/paths/~1pets/post/parameters/0/examples/E", + "parent": "#/paths/~1pets/post/parameters/0", + "original": "#/components/examples/Other", + "destination": "#/components/examples/Other" + }, + "#/paths/~1pets/post/parameters/0/schema": { + "type": "schema", + "pointer": "/paths/~1pets/post/parameters/0/schema", + "parent": "#/paths/~1pets/post/parameters/0", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/paths/~1pets/post/responses": { + "type": "responses", + "pointer": "/paths/~1pets/post/responses", + "parent": "#/paths/~1pets/post" + }, + "#/paths/~1pets/post/responses/200": { + "type": "response", + "pointer": "/paths/~1pets/post/responses/200", + "parent": "#/paths/~1pets/post/responses" + }, + "#/paths/~1pets/post/responses/200/content/application~1json": { + "type": "media-type", + "pointer": "/paths/~1pets/post/responses/200/content/application~1json", + "parent": "#/paths/~1pets/post/responses/200" + }, + "#/paths/~1pets/post/responses/200/content/application~1json/examples/E": { + "type": "reference", + "pointer": "/paths/~1pets/post/responses/200/content/application~1json/examples/E", + "parent": "#/paths/~1pets/post/responses/200/content/application~1json", + "original": "#/components/examples/Other", + "destination": "#/components/examples/Other" + } + }, + "operations": [ + { + "type": "path", + "path": "/pets", + "method": "get", + "origin": "#/paths/~1pets/get", + "endpoint": "#/paths/~1pets", + "tags": [], + "servers": [], + "security": [], + "parameters": [ + "#/paths/~1pets/get/parameters/0" + ] + }, + { + "type": "path", + "path": "/pets", + "method": "post", + "origin": "#/paths/~1pets/post", + "endpoint": "#/paths/~1pets", + "tags": [], + "servers": [], + "security": [], + "parameters": [ + "#/paths/~1pets/post/parameters/0" + ] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/reference_spellings_are_one_document.json b/test/openapi/frame/3.1/pass/reference_spellings_are_one_document.json new file mode 100644 index 000000000..c31e787cb --- /dev/null +++ b/test/openapi/frame/3.1/pass/reference_spellings_are_one_document.json @@ -0,0 +1,107 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "A": { + "$ref": "https://example.com/common.json#/components/responses/NotFound" + }, + "B": { + "$ref": "HTTPS://Example.COM:443/common.json#/x" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/common.json": { + "openapi": "3.1.1", + "info": { + "title": "Common", + "version": "1.0.0" + }, + "components": { + "responses": { + "NotFound": { + "description": "ok" + } + } + } + } + }, + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": false, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/common.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/common.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/common.json" + }, + "https://example.com/common.json#/components/responses/NotFound": { + "type": "response", + "pointer": "/components/responses/NotFound", + "parent": "https://example.com/common.json#/components" + }, + "https://example.com/common.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/common.json" + }, + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/responses/A": { + "type": "reference", + "pointer": "/components/responses/A", + "parent": "https://example.com/openapi.json#/components", + "original": "https://example.com/common.json#/components/responses/NotFound", + "destination": "https://example.com/common.json#/components/responses/NotFound" + }, + "https://example.com/openapi.json#/components/responses/B": { + "type": "reference", + "pointer": "/components/responses/B", + "parent": "https://example.com/openapi.json#/components", + "original": "HTTPS://Example.COM:443/common.json#/x", + "destination": "https://example.com/common.json#/x" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [] + }, + "dangling": [ + "https://example.com/common.json#/x" + ] +} diff --git a/test/openapi/frame/3.1/pass/references_are_ordered_by_where_they_were_written.json b/test/openapi/frame/3.1/pass/references_are_ordered_by_where_they_were_written.json new file mode 100644 index 000000000..178fc939d --- /dev/null +++ b/test/openapi/frame/3.1/pass/references_are_ordered_by_where_they_were_written.json @@ -0,0 +1,155 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "pathItems": { + "Zebra": { + "get": { + "responses": { + "200": { + "description": "ok" + } + } + } + }, + "Ant": { + "get": { + "responses": { + "200": { + "description": "ok" + } + } + } + } + } + }, + "paths": { + "/zebra": { + "$ref": "#/components/pathItems/Zebra" + }, + "/ant": { + "$ref": "#/components/pathItems/Ant" + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/pathItems/Ant": { + "type": "path-item", + "pointer": "/components/pathItems/Ant", + "parent": "https://example.com/openapi.json#/components" + }, + "https://example.com/openapi.json#/components/pathItems/Ant/get": { + "type": "operation", + "pointer": "/components/pathItems/Ant/get", + "parent": "https://example.com/openapi.json#/components/pathItems/Ant" + }, + "https://example.com/openapi.json#/components/pathItems/Ant/get/responses": { + "type": "responses", + "pointer": "/components/pathItems/Ant/get/responses", + "parent": "https://example.com/openapi.json#/components/pathItems/Ant/get" + }, + "https://example.com/openapi.json#/components/pathItems/Ant/get/responses/200": { + "type": "response", + "pointer": "/components/pathItems/Ant/get/responses/200", + "parent": "https://example.com/openapi.json#/components/pathItems/Ant/get/responses" + }, + "https://example.com/openapi.json#/components/pathItems/Zebra": { + "type": "path-item", + "pointer": "/components/pathItems/Zebra", + "parent": "https://example.com/openapi.json#/components" + }, + "https://example.com/openapi.json#/components/pathItems/Zebra/get": { + "type": "operation", + "pointer": "/components/pathItems/Zebra/get", + "parent": "https://example.com/openapi.json#/components/pathItems/Zebra" + }, + "https://example.com/openapi.json#/components/pathItems/Zebra/get/responses": { + "type": "responses", + "pointer": "/components/pathItems/Zebra/get/responses", + "parent": "https://example.com/openapi.json#/components/pathItems/Zebra/get" + }, + "https://example.com/openapi.json#/components/pathItems/Zebra/get/responses/200": { + "type": "response", + "pointer": "/components/pathItems/Zebra/get/responses/200", + "parent": "https://example.com/openapi.json#/components/pathItems/Zebra/get/responses" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1ant": { + "type": "path-item", + "pointer": "/paths/~1ant", + "parent": "https://example.com/openapi.json#/paths", + "original": "#/components/pathItems/Ant", + "destination": "https://example.com/openapi.json#/components/pathItems/Ant" + }, + "https://example.com/openapi.json#/paths/~1zebra": { + "type": "path-item", + "pointer": "/paths/~1zebra", + "parent": "https://example.com/openapi.json#/paths", + "original": "#/components/pathItems/Zebra", + "destination": "https://example.com/openapi.json#/components/pathItems/Zebra" + } + }, + "operations": [ + { + "type": "path", + "path": "/zebra", + "method": "get", + "origin": "https://example.com/openapi.json#/components/pathItems/Zebra/get", + "endpoint": "https://example.com/openapi.json#/paths/~1zebra", + "tags": [], + "servers": [], + "security": [], + "parameters": [] + }, + { + "type": "path", + "path": "/ant", + "method": "get", + "origin": "https://example.com/openapi.json#/components/pathItems/Ant/get", + "endpoint": "https://example.com/openapi.json#/paths/~1ant", + "tags": [], + "servers": [], + "security": [], + "parameters": [] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/second_reference_into_one_document_resolves.json b/test/openapi/frame/3.1/pass/second_reference_into_one_document_resolves.json new file mode 100644 index 000000000..302f600f6 --- /dev/null +++ b/test/openapi/frame/3.1/pass/second_reference_into_one_document_resolves.json @@ -0,0 +1,130 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "E", + "version": "1" + }, + "components": { + "responses": { + "First": { + "$ref": "https://example.com/common.json#/components/responses/Ok" + } + }, + "parameters": { + "Second": { + "$ref": "https://example.com/common.json#/components/parameters/Limit" + } + } + }, + "paths": {} + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/common.json": { + "openapi": "3.1.1", + "info": { + "title": "Common", + "version": "1.0.0" + }, + "components": { + "responses": { + "Ok": { + "description": "ok" + } + }, + "parameters": { + "Limit": { + "name": "limit", + "in": "query", + "schema": {} + } + } + } + } + }, + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "E", + "version": "1", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/common.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/common.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/common.json" + }, + "https://example.com/common.json#/components/parameters/Limit": { + "type": "parameter", + "pointer": "/components/parameters/Limit", + "parent": "https://example.com/common.json#/components" + }, + "https://example.com/common.json#/components/parameters/Limit/schema": { + "type": "schema", + "pointer": "/components/parameters/Limit/schema", + "parent": "https://example.com/common.json#/components/parameters/Limit", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/common.json#/components/responses/Ok": { + "type": "response", + "pointer": "/components/responses/Ok", + "parent": "https://example.com/common.json#/components" + }, + "https://example.com/common.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/common.json" + }, + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/parameters/Second": { + "type": "reference", + "pointer": "/components/parameters/Second", + "parent": "https://example.com/openapi.json#/components", + "original": "https://example.com/common.json#/components/parameters/Limit", + "destination": "https://example.com/common.json#/components/parameters/Limit" + }, + "https://example.com/openapi.json#/components/responses/First": { + "type": "reference", + "pointer": "/components/responses/First", + "parent": "https://example.com/openapi.json#/components", + "original": "https://example.com/common.json#/components/responses/Ok", + "destination": "https://example.com/common.json#/components/responses/Ok" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/security_requirement_empty_object_names_nothing.json b/test/openapi/frame/3.1/pass/security_requirement_empty_object_names_nothing.json new file mode 100644 index 000000000..8aeadf320 --- /dev/null +++ b/test/openapi/frame/3.1/pass/security_requirement_empty_object_names_nothing.json @@ -0,0 +1,51 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": {}, + "security": [ + {} + ] + }, + "frame": { + "version": "3.1", + "base": "", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/info": { + "type": "info", + "pointer": "/info", + "parent": "" + }, + "#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "" + }, + "#/security/0": { + "type": "security-requirement", + "pointer": "/security/0", + "parent": "" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/security_requirement_resolves_from_the_entry_document.json b/test/openapi/frame/3.1/pass/security_requirement_resolves_from_the_entry_document.json new file mode 100644 index 000000000..6aed3d635 --- /dev/null +++ b/test/openapi/frame/3.1/pass/security_requirement_resolves_from_the_entry_document.json @@ -0,0 +1,157 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "securitySchemes": { + "MySecurity": { + "type": "apiKey", + "name": "X-Key", + "in": "header" + } + }, + "pathItems": {} + }, + "paths": { + "/foo": { + "$ref": "https://example.com/other.json#/components/pathItems/Foo" + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/other.json": { + "openapi": "3.1.1", + "info": { + "title": "Other", + "version": "1.0.0" + }, + "components": { + "pathItems": { + "Foo": { + "get": { + "responses": { + "200": { + "description": "ok" + } + }, + "security": [ + { + "MySecurity": [] + } + ] + } + } + } + } + } + }, + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/securitySchemes/MySecurity": { + "type": "security-scheme", + "pointer": "/components/securitySchemes/MySecurity", + "parent": "https://example.com/openapi.json#/components" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1foo": { + "type": "path-item", + "pointer": "/paths/~1foo", + "parent": "https://example.com/openapi.json#/paths", + "original": "https://example.com/other.json#/components/pathItems/Foo", + "destination": "https://example.com/other.json#/components/pathItems/Foo" + }, + "https://example.com/other.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/other.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/other.json" + }, + "https://example.com/other.json#/components/pathItems/Foo": { + "type": "path-item", + "pointer": "/components/pathItems/Foo", + "parent": "https://example.com/other.json#/components" + }, + "https://example.com/other.json#/components/pathItems/Foo/get": { + "type": "operation", + "pointer": "/components/pathItems/Foo/get", + "parent": "https://example.com/other.json#/components/pathItems/Foo" + }, + "https://example.com/other.json#/components/pathItems/Foo/get/responses": { + "type": "responses", + "pointer": "/components/pathItems/Foo/get/responses", + "parent": "https://example.com/other.json#/components/pathItems/Foo/get" + }, + "https://example.com/other.json#/components/pathItems/Foo/get/responses/200": { + "type": "response", + "pointer": "/components/pathItems/Foo/get/responses/200", + "parent": "https://example.com/other.json#/components/pathItems/Foo/get/responses" + }, + "https://example.com/other.json#/components/pathItems/Foo/get/security/0": { + "type": "security-requirement", + "pointer": "/components/pathItems/Foo/get/security/0", + "parent": "https://example.com/other.json#/components/pathItems/Foo/get" + }, + "https://example.com/other.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/other.json" + } + }, + "operations": [ + { + "type": "path", + "path": "/foo", + "method": "get", + "origin": "https://example.com/other.json#/components/pathItems/Foo/get", + "endpoint": "https://example.com/openapi.json#/paths/~1foo", + "tags": [], + "servers": [], + "security": [ + "https://example.com/other.json#/components/pathItems/Foo/get/security/0" + ], + "parameters": [] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/security_requirements.json b/test/openapi/frame/3.1/pass/security_requirements.json new file mode 100644 index 000000000..c33e2f98e --- /dev/null +++ b/test/openapi/frame/3.1/pass/security_requirements.json @@ -0,0 +1,153 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": {}, + "security": [ + {}, + { + "ApiKey": [] + }, + { + "OAuth": [ + "read:pets", + "write:pets" + ] + }, + { + "ApiKey": [], + "OAuth": [] + } + ], + "components": { + "securitySchemes": { + "ApiKey": { + "type": "apiKey", + "name": "X-Key", + "in": "header" + }, + "OAuth": { + "type": "oauth2", + "flows": { + "implicit": { + "authorizationUrl": "/auth", + "scopes": { + "read:pets": "read" + } + }, + "password": { + "tokenUrl": "/token", + "scopes": {} + }, + "clientCredentials": { + "tokenUrl": "/token", + "scopes": {} + }, + "authorizationCode": { + "authorizationUrl": "/auth", + "tokenUrl": "/token", + "refreshUrl": "/refresh", + "scopes": {} + } + } + } + } + } + }, + "frame": { + "version": "3.1", + "base": "", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/components": { + "type": "components", + "pointer": "/components", + "parent": "" + }, + "#/components/securitySchemes/ApiKey": { + "type": "security-scheme", + "pointer": "/components/securitySchemes/ApiKey", + "parent": "#/components" + }, + "#/components/securitySchemes/OAuth": { + "type": "security-scheme", + "pointer": "/components/securitySchemes/OAuth", + "parent": "#/components" + }, + "#/components/securitySchemes/OAuth/flows": { + "type": "oauth-flows", + "pointer": "/components/securitySchemes/OAuth/flows", + "parent": "#/components/securitySchemes/OAuth" + }, + "#/components/securitySchemes/OAuth/flows/authorizationCode": { + "type": "oauth-flow", + "pointer": "/components/securitySchemes/OAuth/flows/authorizationCode", + "parent": "#/components/securitySchemes/OAuth/flows" + }, + "#/components/securitySchemes/OAuth/flows/clientCredentials": { + "type": "oauth-flow", + "pointer": "/components/securitySchemes/OAuth/flows/clientCredentials", + "parent": "#/components/securitySchemes/OAuth/flows" + }, + "#/components/securitySchemes/OAuth/flows/implicit": { + "type": "oauth-flow", + "pointer": "/components/securitySchemes/OAuth/flows/implicit", + "parent": "#/components/securitySchemes/OAuth/flows" + }, + "#/components/securitySchemes/OAuth/flows/password": { + "type": "oauth-flow", + "pointer": "/components/securitySchemes/OAuth/flows/password", + "parent": "#/components/securitySchemes/OAuth/flows" + }, + "#/info": { + "type": "info", + "pointer": "/info", + "parent": "" + }, + "#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "" + }, + "#/security/0": { + "type": "security-requirement", + "pointer": "/security/0", + "parent": "" + }, + "#/security/1": { + "type": "security-requirement", + "pointer": "/security/1", + "parent": "" + }, + "#/security/2": { + "type": "security-requirement", + "pointer": "/security/2", + "parent": "" + }, + "#/security/3": { + "type": "security-requirement", + "pointer": "/security/3", + "parent": "" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/security_scheme_http_bearer_format_capitalised.json b/test/openapi/frame/3.1/pass/security_scheme_http_bearer_format_capitalised.json new file mode 100644 index 000000000..354642407 --- /dev/null +++ b/test/openapi/frame/3.1/pass/security_scheme_http_bearer_format_capitalised.json @@ -0,0 +1,57 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "securitySchemes": { + "S": { + "type": "http", + "scheme": "Bearer", + "bearerFormat": "JWT" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/securitySchemes/S": { + "type": "security-scheme", + "pointer": "/components/securitySchemes/S", + "parent": "https://example.com/openapi.json#/components" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/security_schemes_every_type.json b/test/openapi/frame/3.1/pass/security_schemes_every_type.json new file mode 100644 index 000000000..bcc727c58 --- /dev/null +++ b/test/openapi/frame/3.1/pass/security_schemes_every_type.json @@ -0,0 +1,94 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "securitySchemes": { + "Key": { + "type": "apiKey", + "name": "X-Key", + "in": "query", + "description": "A key" + }, + "Basic": { + "type": "http", + "scheme": "basic" + }, + "Bearer": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT" + }, + "Mutual": { + "type": "mutualTLS", + "description": "Client certificate" + }, + "Connect": { + "type": "openIdConnect", + "openIdConnectUrl": "https://example.com/.well-known/openid-configuration" + } + } + } + }, + "frame": { + "version": "3.1", + "base": "", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/components": { + "type": "components", + "pointer": "/components", + "parent": "" + }, + "#/components/securitySchemes/Basic": { + "type": "security-scheme", + "pointer": "/components/securitySchemes/Basic", + "parent": "#/components" + }, + "#/components/securitySchemes/Bearer": { + "type": "security-scheme", + "pointer": "/components/securitySchemes/Bearer", + "parent": "#/components" + }, + "#/components/securitySchemes/Connect": { + "type": "security-scheme", + "pointer": "/components/securitySchemes/Connect", + "parent": "#/components" + }, + "#/components/securitySchemes/Key": { + "type": "security-scheme", + "pointer": "/components/securitySchemes/Key", + "parent": "#/components" + }, + "#/components/securitySchemes/Mutual": { + "type": "security-scheme", + "pointer": "/components/securitySchemes/Mutual", + "parent": "#/components" + }, + "#/info": { + "type": "info", + "pointer": "/info", + "parent": "" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/server_url_with_percent_encoded_delimiters.json b/test/openapi/frame/3.1/pass/server_url_with_percent_encoded_delimiters.json new file mode 100644 index 000000000..3b845eb23 --- /dev/null +++ b/test/openapi/frame/3.1/pass/server_url_with_percent_encoded_delimiters.json @@ -0,0 +1,54 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "servers": [ + { + "url": "https://api.example.com/a%3Fb%23c" + } + ], + "paths": {} + }, + "defaultBase": "https://example.com/openapi.json", + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/servers/0": { + "type": "server", + "pointer": "/servers/0", + "parent": "https://example.com/openapi.json" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/servers_empty_array.json b/test/openapi/frame/3.1/pass/servers_empty_array.json new file mode 100644 index 000000000..3a15e0d75 --- /dev/null +++ b/test/openapi/frame/3.1/pass/servers_empty_array.json @@ -0,0 +1,44 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "servers": [], + "paths": {} + }, + "frame": { + "version": "3.1", + "base": "", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/info": { + "type": "info", + "pointer": "/info", + "parent": "" + }, + "#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/servers_multiple.json b/test/openapi/frame/3.1/pass/servers_multiple.json new file mode 100644 index 000000000..58f6f4e79 --- /dev/null +++ b/test/openapi/frame/3.1/pass/servers_multiple.json @@ -0,0 +1,72 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "servers": [ + { + "url": "https://development.gigantic-server.com/v1", + "description": "Development server" + }, + { + "url": "https://staging.gigantic-server.com/v1", + "description": "Staging server" + }, + { + "url": "https://api.gigantic-server.com/v1", + "description": "Production server" + } + ], + "paths": {} + }, + "frame": { + "version": "3.1", + "base": "", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/info": { + "type": "info", + "pointer": "/info", + "parent": "" + }, + "#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "" + }, + "#/servers/0": { + "type": "server", + "pointer": "/servers/0", + "parent": "" + }, + "#/servers/1": { + "type": "server", + "pointer": "/servers/1", + "parent": "" + }, + "#/servers/2": { + "type": "server", + "pointer": "/servers/2", + "parent": "" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/servers_relative_url.json b/test/openapi/frame/3.1/pass/servers_relative_url.json new file mode 100644 index 000000000..8a45a7822 --- /dev/null +++ b/test/openapi/frame/3.1/pass/servers_relative_url.json @@ -0,0 +1,53 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "servers": [ + { + "url": "/v1" + } + ], + "paths": {} + }, + "frame": { + "version": "3.1", + "base": "", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/info": { + "type": "info", + "pointer": "/info", + "parent": "" + }, + "#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "" + }, + "#/servers/0": { + "type": "server", + "pointer": "/servers/0", + "parent": "" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/servers_with_extensions.json b/test/openapi/frame/3.1/pass/servers_with_extensions.json new file mode 100644 index 000000000..d7f7d2ba6 --- /dev/null +++ b/test/openapi/frame/3.1/pass/servers_with_extensions.json @@ -0,0 +1,65 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "servers": [ + { + "url": "/{version}", + "x-internal": true, + "variables": { + "version": { + "default": "v1", + "x-locked": true + } + } + } + ], + "paths": {} + }, + "frame": { + "version": "3.1", + "base": "", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/info": { + "type": "info", + "pointer": "/info", + "parent": "" + }, + "#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "" + }, + "#/servers/0": { + "type": "server", + "pointer": "/servers/0", + "parent": "" + }, + "#/servers/0/variables/version": { + "type": "server-variable", + "pointer": "/servers/0/variables/version", + "parent": "#/servers/0" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/servers_with_variables.json b/test/openapi/frame/3.1/pass/servers_with_variables.json new file mode 100644 index 000000000..5e3c79f54 --- /dev/null +++ b/test/openapi/frame/3.1/pass/servers_with_variables.json @@ -0,0 +1,85 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "servers": [ + { + "url": "https://{username}.gigantic-server.com:{port}/{basePath}", + "description": "The production API server", + "variables": { + "username": { + "default": "demo", + "description": "A user-specific subdomain." + }, + "port": { + "enum": [ + "8443", + "443" + ], + "default": "8443" + }, + "basePath": { + "default": "v2" + } + } + } + ], + "paths": {} + }, + "frame": { + "version": "3.1", + "base": "", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/info": { + "type": "info", + "pointer": "/info", + "parent": "" + }, + "#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "" + }, + "#/servers/0": { + "type": "server", + "pointer": "/servers/0", + "parent": "" + }, + "#/servers/0/variables/basePath": { + "type": "server-variable", + "pointer": "/servers/0/variables/basePath", + "parent": "#/servers/0" + }, + "#/servers/0/variables/port": { + "type": "server-variable", + "pointer": "/servers/0/variables/port", + "parent": "#/servers/0" + }, + "#/servers/0/variables/username": { + "type": "server-variable", + "pointer": "/servers/0/variables/username", + "parent": "#/servers/0" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/tags_empty_array.json b/test/openapi/frame/3.1/pass/tags_empty_array.json new file mode 100644 index 000000000..ae07ecde3 --- /dev/null +++ b/test/openapi/frame/3.1/pass/tags_empty_array.json @@ -0,0 +1,44 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "tags": [], + "paths": {} + }, + "frame": { + "version": "3.1", + "base": "", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/info": { + "type": "info", + "pointer": "/info", + "parent": "" + }, + "#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/tags_example.json b/test/openapi/frame/3.1/pass/tags_example.json new file mode 100644 index 000000000..28cfa9231 --- /dev/null +++ b/test/openapi/frame/3.1/pass/tags_example.json @@ -0,0 +1,54 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "tags": [ + { + "name": "pet", + "description": "Pets operations" + } + ], + "paths": {} + }, + "frame": { + "version": "3.1", + "base": "", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/info": { + "type": "info", + "pointer": "/info", + "parent": "" + }, + "#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "" + }, + "#/tags/0": { + "type": "tag", + "pointer": "/tags/0", + "parent": "" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/tags_names_differ_by_case.json b/test/openapi/frame/3.1/pass/tags_names_differ_by_case.json new file mode 100644 index 000000000..f329827ab --- /dev/null +++ b/test/openapi/frame/3.1/pass/tags_names_differ_by_case.json @@ -0,0 +1,61 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "tags": [ + { + "name": "pet" + }, + { + "name": "Pet" + } + ], + "paths": {} + }, + "frame": { + "version": "3.1", + "base": "", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/info": { + "type": "info", + "pointer": "/info", + "parent": "" + }, + "#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "" + }, + "#/tags/0": { + "type": "tag", + "pointer": "/tags/0", + "parent": "" + }, + "#/tags/1": { + "type": "tag", + "pointer": "/tags/1", + "parent": "" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/tags_with_external_docs.json b/test/openapi/frame/3.1/pass/tags_with_external_docs.json new file mode 100644 index 000000000..2b74ed8a5 --- /dev/null +++ b/test/openapi/frame/3.1/pass/tags_with_external_docs.json @@ -0,0 +1,72 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "tags": [ + { + "name": "pet", + "description": "Pets operations", + "externalDocs": { + "description": "Find more info here", + "url": "https://example.com/pets" + }, + "x-display-order": 1 + }, + { + "name": "store" + } + ], + "paths": {} + }, + "frame": { + "version": "3.1", + "base": "", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/info": { + "type": "info", + "pointer": "/info", + "parent": "" + }, + "#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "" + }, + "#/tags/0": { + "type": "tag", + "pointer": "/tags/0", + "parent": "" + }, + "#/tags/0/externalDocs": { + "type": "external-documentation", + "pointer": "/tags/0/externalDocs", + "parent": "#/tags/0" + }, + "#/tags/1": { + "type": "tag", + "pointer": "/tags/1", + "parent": "" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/webhooks_map.json b/test/openapi/frame/3.1/pass/webhooks_map.json new file mode 100644 index 000000000..73c211cca --- /dev/null +++ b/test/openapi/frame/3.1/pass/webhooks_map.json @@ -0,0 +1,143 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "webhooks": { + "newPet": { + "post": { + "requestBody": { + "content": { + "application/json": { + "schema": {} + } + } + }, + "responses": { + "200": { + "description": "ok" + } + } + } + }, + "x-legacy": { + "get": { + "responses": { + "200": { + "description": "ok" + } + } + } + } + } + }, + "frame": { + "version": "3.1", + "base": "", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/info": { + "type": "info", + "pointer": "/info", + "parent": "" + }, + "#/webhooks/newPet": { + "type": "path-item", + "pointer": "/webhooks/newPet", + "parent": "" + }, + "#/webhooks/newPet/post": { + "type": "operation", + "pointer": "/webhooks/newPet/post", + "parent": "#/webhooks/newPet" + }, + "#/webhooks/newPet/post/requestBody": { + "type": "request-body", + "pointer": "/webhooks/newPet/post/requestBody", + "parent": "#/webhooks/newPet/post" + }, + "#/webhooks/newPet/post/requestBody/content/application~1json": { + "type": "media-type", + "pointer": "/webhooks/newPet/post/requestBody/content/application~1json", + "parent": "#/webhooks/newPet/post/requestBody" + }, + "#/webhooks/newPet/post/requestBody/content/application~1json/schema": { + "type": "schema", + "pointer": "/webhooks/newPet/post/requestBody/content/application~1json/schema", + "parent": "#/webhooks/newPet/post/requestBody/content/application~1json", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "#/webhooks/newPet/post/responses": { + "type": "responses", + "pointer": "/webhooks/newPet/post/responses", + "parent": "#/webhooks/newPet/post" + }, + "#/webhooks/newPet/post/responses/200": { + "type": "response", + "pointer": "/webhooks/newPet/post/responses/200", + "parent": "#/webhooks/newPet/post/responses" + }, + "#/webhooks/x-legacy": { + "type": "path-item", + "pointer": "/webhooks/x-legacy", + "parent": "" + }, + "#/webhooks/x-legacy/get": { + "type": "operation", + "pointer": "/webhooks/x-legacy/get", + "parent": "#/webhooks/x-legacy" + }, + "#/webhooks/x-legacy/get/responses": { + "type": "responses", + "pointer": "/webhooks/x-legacy/get/responses", + "parent": "#/webhooks/x-legacy/get" + }, + "#/webhooks/x-legacy/get/responses/200": { + "type": "response", + "pointer": "/webhooks/x-legacy/get/responses/200", + "parent": "#/webhooks/x-legacy/get/responses" + } + }, + "operations": [ + { + "type": "webhook", + "path": "newPet", + "method": "post", + "origin": "#/webhooks/newPet/post", + "endpoint": "#/webhooks/newPet", + "tags": [], + "servers": [], + "security": [], + "parameters": [] + }, + { + "type": "webhook", + "path": "x-legacy", + "method": "get", + "origin": "#/webhooks/x-legacy/get", + "endpoint": "#/webhooks/x-legacy", + "tags": [], + "servers": [], + "security": [], + "parameters": [] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/whole_file_reference_from_an_operation.json b/test/openapi/frame/3.1/pass/whole_file_reference_from_an_operation.json new file mode 100644 index 000000000..88cfa4ce6 --- /dev/null +++ b/test/openapi/frame/3.1/pass/whole_file_reference_from_an_operation.json @@ -0,0 +1,118 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets": { + "get": { + "parameters": [ + { + "$ref": "https://example.com/target.json" + } + ], + "responses": { + "200": { + "description": "ok" + } + } + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/target.json": { + "name": "limit", + "in": "query", + "schema": {} + } + }, + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1pets": { + "type": "path-item", + "pointer": "/paths/~1pets", + "parent": "https://example.com/openapi.json#/paths" + }, + "https://example.com/openapi.json#/paths/~1pets/get": { + "type": "operation", + "pointer": "/paths/~1pets/get", + "parent": "https://example.com/openapi.json#/paths/~1pets" + }, + "https://example.com/openapi.json#/paths/~1pets/get/parameters/0": { + "type": "reference", + "pointer": "/paths/~1pets/get/parameters/0", + "parent": "https://example.com/openapi.json#/paths/~1pets/get", + "original": "https://example.com/target.json", + "destination": "https://example.com/target.json" + }, + "https://example.com/openapi.json#/paths/~1pets/get/responses": { + "type": "responses", + "pointer": "/paths/~1pets/get/responses", + "parent": "https://example.com/openapi.json#/paths/~1pets/get" + }, + "https://example.com/openapi.json#/paths/~1pets/get/responses/200": { + "type": "response", + "pointer": "/paths/~1pets/get/responses/200", + "parent": "https://example.com/openapi.json#/paths/~1pets/get/responses" + }, + "https://example.com/target.json": { + "type": "parameter", + "pointer": "", + "parent": null + }, + "https://example.com/target.json#/schema": { + "type": "schema", + "pointer": "/schema", + "parent": "https://example.com/target.json", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + } + }, + "operations": [ + { + "type": "path", + "path": "/pets", + "method": "get", + "origin": "https://example.com/openapi.json#/paths/~1pets/get", + "endpoint": "https://example.com/openapi.json#/paths/~1pets", + "tags": [], + "servers": [], + "security": [], + "parameters": [ + "https://example.com/openapi.json#/paths/~1pets/get/parameters/0" + ] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/whole_file_reference_relative.json b/test/openapi/frame/3.1/pass/whole_file_reference_relative.json new file mode 100644 index 000000000..8a14574d1 --- /dev/null +++ b/test/openapi/frame/3.1/pass/whole_file_reference_relative.json @@ -0,0 +1,67 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "R": { + "$ref": "target.json" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/target.json": { + "description": "Not found" + } + }, + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/responses/R": { + "type": "reference", + "pointer": "/components/responses/R", + "parent": "https://example.com/openapi.json#/components", + "original": "target.json", + "destination": "https://example.com/target.json" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/target.json": { + "type": "response", + "pointer": "", + "parent": null + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/whole_file_reference_to_a_callbacks.json b/test/openapi/frame/3.1/pass/whole_file_reference_to_a_callbacks.json new file mode 100644 index 000000000..a7f82b065 --- /dev/null +++ b/test/openapi/frame/3.1/pass/whole_file_reference_to_a_callbacks.json @@ -0,0 +1,95 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "callbacks": { + "C": { + "$ref": "https://example.com/target.json" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/target.json": { + "{$request.body#/url}": { + "post": { + "responses": { + "200": { + "description": "ok" + } + } + } + } + } + }, + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/callbacks/C": { + "type": "reference", + "pointer": "/components/callbacks/C", + "parent": "https://example.com/openapi.json#/components", + "original": "https://example.com/target.json", + "destination": "https://example.com/target.json" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/target.json": { + "type": "callback", + "pointer": "", + "parent": null + }, + "https://example.com/target.json#/%7B$request.body%23~1url%7D": { + "type": "path-item", + "pointer": "/{$request.body#~1url}", + "parent": "https://example.com/target.json" + }, + "https://example.com/target.json#/%7B$request.body%23~1url%7D/post": { + "type": "operation", + "pointer": "/{$request.body#~1url}/post", + "parent": "https://example.com/target.json#/%7B$request.body%23~1url%7D" + }, + "https://example.com/target.json#/%7B$request.body%23~1url%7D/post/responses": { + "type": "responses", + "pointer": "/{$request.body#~1url}/post/responses", + "parent": "https://example.com/target.json#/%7B$request.body%23~1url%7D/post" + }, + "https://example.com/target.json#/%7B$request.body%23~1url%7D/post/responses/200": { + "type": "response", + "pointer": "/{$request.body#~1url}/post/responses/200", + "parent": "https://example.com/target.json#/%7B$request.body%23~1url%7D/post/responses" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/whole_file_reference_to_a_example.json b/test/openapi/frame/3.1/pass/whole_file_reference_to_a_example.json new file mode 100644 index 000000000..15619aa84 --- /dev/null +++ b/test/openapi/frame/3.1/pass/whole_file_reference_to_a_example.json @@ -0,0 +1,68 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "examples": { + "E": { + "$ref": "https://example.com/target.json" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/target.json": { + "summary": "One", + "value": 1 + } + }, + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/examples/E": { + "type": "reference", + "pointer": "/components/examples/E", + "parent": "https://example.com/openapi.json#/components", + "original": "https://example.com/target.json", + "destination": "https://example.com/target.json" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/target.json": { + "type": "example", + "pointer": "", + "parent": null + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/whole_file_reference_to_a_header.json b/test/openapi/frame/3.1/pass/whole_file_reference_to_a_header.json new file mode 100644 index 000000000..88a132ea6 --- /dev/null +++ b/test/openapi/frame/3.1/pass/whole_file_reference_to_a_header.json @@ -0,0 +1,74 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "headers": { + "H": { + "$ref": "https://example.com/target.json" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/target.json": { + "description": "Rate", + "schema": {} + } + }, + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/headers/H": { + "type": "reference", + "pointer": "/components/headers/H", + "parent": "https://example.com/openapi.json#/components", + "original": "https://example.com/target.json", + "destination": "https://example.com/target.json" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/target.json": { + "type": "header", + "pointer": "", + "parent": null + }, + "https://example.com/target.json#/schema": { + "type": "schema", + "pointer": "/schema", + "parent": "https://example.com/target.json", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/whole_file_reference_to_a_link.json b/test/openapi/frame/3.1/pass/whole_file_reference_to_a_link.json new file mode 100644 index 000000000..eeafa28b0 --- /dev/null +++ b/test/openapi/frame/3.1/pass/whole_file_reference_to_a_link.json @@ -0,0 +1,116 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "links": { + "L": { + "$ref": "https://example.com/target.json" + } + } + }, + "paths": { + "/pets": { + "get": { + "operationId": "listPets", + "responses": { + "200": { + "description": "ok" + } + } + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/target.json": { + "operationId": "listPets" + } + }, + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/links/L": { + "type": "reference", + "pointer": "/components/links/L", + "parent": "https://example.com/openapi.json#/components", + "original": "https://example.com/target.json", + "destination": "https://example.com/target.json" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1pets": { + "type": "path-item", + "pointer": "/paths/~1pets", + "parent": "https://example.com/openapi.json#/paths" + }, + "https://example.com/openapi.json#/paths/~1pets/get": { + "type": "operation", + "pointer": "/paths/~1pets/get", + "parent": "https://example.com/openapi.json#/paths/~1pets" + }, + "https://example.com/openapi.json#/paths/~1pets/get/responses": { + "type": "responses", + "pointer": "/paths/~1pets/get/responses", + "parent": "https://example.com/openapi.json#/paths/~1pets/get" + }, + "https://example.com/openapi.json#/paths/~1pets/get/responses/200": { + "type": "response", + "pointer": "/paths/~1pets/get/responses/200", + "parent": "https://example.com/openapi.json#/paths/~1pets/get/responses" + }, + "https://example.com/target.json": { + "type": "link", + "pointer": "", + "parent": null + } + }, + "operations": [ + { + "type": "path", + "path": "/pets", + "method": "get", + "origin": "https://example.com/openapi.json#/paths/~1pets/get", + "endpoint": "https://example.com/openapi.json#/paths/~1pets", + "tags": [], + "servers": [], + "security": [], + "parameters": [] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/whole_file_reference_to_a_parameter.json b/test/openapi/frame/3.1/pass/whole_file_reference_to_a_parameter.json new file mode 100644 index 000000000..3dd3151bf --- /dev/null +++ b/test/openapi/frame/3.1/pass/whole_file_reference_to_a_parameter.json @@ -0,0 +1,75 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "parameters": { + "P": { + "$ref": "https://example.com/target.json" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/target.json": { + "name": "limit", + "in": "query", + "schema": {} + } + }, + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/parameters/P": { + "type": "reference", + "pointer": "/components/parameters/P", + "parent": "https://example.com/openapi.json#/components", + "original": "https://example.com/target.json", + "destination": "https://example.com/target.json" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/target.json": { + "type": "parameter", + "pointer": "", + "parent": null + }, + "https://example.com/target.json#/schema": { + "type": "schema", + "pointer": "/schema", + "parent": "https://example.com/target.json", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/whole_file_reference_to_a_path_item.json b/test/openapi/frame/3.1/pass/whole_file_reference_to_a_path_item.json new file mode 100644 index 000000000..998aa4e83 --- /dev/null +++ b/test/openapi/frame/3.1/pass/whole_file_reference_to_a_path_item.json @@ -0,0 +1,98 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "paths": { + "/pets": { + "$ref": "https://example.com/target.json" + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/target.json": { + "get": { + "responses": { + "200": { + "description": "ok" + } + } + } + } + }, + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths": { + "type": "paths", + "pointer": "/paths", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/paths/~1pets": { + "type": "path-item", + "pointer": "/paths/~1pets", + "parent": "https://example.com/openapi.json#/paths", + "original": "https://example.com/target.json", + "destination": "https://example.com/target.json" + }, + "https://example.com/target.json": { + "type": "path-item", + "pointer": "", + "parent": null + }, + "https://example.com/target.json#/get": { + "type": "operation", + "pointer": "/get", + "parent": "https://example.com/target.json" + }, + "https://example.com/target.json#/get/responses": { + "type": "responses", + "pointer": "/get/responses", + "parent": "https://example.com/target.json#/get" + }, + "https://example.com/target.json#/get/responses/200": { + "type": "response", + "pointer": "/get/responses/200", + "parent": "https://example.com/target.json#/get/responses" + } + }, + "operations": [ + { + "type": "path", + "path": "/pets", + "method": "get", + "origin": "https://example.com/target.json#/get", + "endpoint": "https://example.com/openapi.json#/paths/~1pets", + "tags": [], + "servers": [], + "security": [], + "parameters": [] + } + ] + } +} diff --git a/test/openapi/frame/3.1/pass/whole_file_reference_to_a_request_body.json b/test/openapi/frame/3.1/pass/whole_file_reference_to_a_request_body.json new file mode 100644 index 000000000..3fbfe8590 --- /dev/null +++ b/test/openapi/frame/3.1/pass/whole_file_reference_to_a_request_body.json @@ -0,0 +1,82 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "requestBodies": { + "B": { + "$ref": "https://example.com/target.json" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/target.json": { + "content": { + "application/json": { + "schema": {} + } + } + } + }, + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/requestBodies/B": { + "type": "reference", + "pointer": "/components/requestBodies/B", + "parent": "https://example.com/openapi.json#/components", + "original": "https://example.com/target.json", + "destination": "https://example.com/target.json" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/target.json": { + "type": "request-body", + "pointer": "", + "parent": null + }, + "https://example.com/target.json#/content/application~1json": { + "type": "media-type", + "pointer": "/content/application~1json", + "parent": "https://example.com/target.json" + }, + "https://example.com/target.json#/content/application~1json/schema": { + "type": "schema", + "pointer": "/content/application~1json/schema", + "parent": "https://example.com/target.json#/content/application~1json", + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/whole_file_reference_to_a_response.json b/test/openapi/frame/3.1/pass/whole_file_reference_to_a_response.json new file mode 100644 index 000000000..b5325dfac --- /dev/null +++ b/test/openapi/frame/3.1/pass/whole_file_reference_to_a_response.json @@ -0,0 +1,67 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "R": { + "$ref": "https://example.com/target.json" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/target.json": { + "description": "Not found" + } + }, + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/responses/R": { + "type": "reference", + "pointer": "/components/responses/R", + "parent": "https://example.com/openapi.json#/components", + "original": "https://example.com/target.json", + "destination": "https://example.com/target.json" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/target.json": { + "type": "response", + "pointer": "", + "parent": null + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/whole_file_reference_to_a_security_scheme.json b/test/openapi/frame/3.1/pass/whole_file_reference_to_a_security_scheme.json new file mode 100644 index 000000000..8eec22a0a --- /dev/null +++ b/test/openapi/frame/3.1/pass/whole_file_reference_to_a_security_scheme.json @@ -0,0 +1,69 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "securitySchemes": { + "S": { + "$ref": "https://example.com/target.json" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/target.json": { + "type": "apiKey", + "name": "X-Key", + "in": "header" + } + }, + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/securitySchemes/S": { + "type": "reference", + "pointer": "/components/securitySchemes/S", + "parent": "https://example.com/openapi.json#/components", + "original": "https://example.com/target.json", + "destination": "https://example.com/target.json" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/target.json": { + "type": "security-scheme", + "pointer": "", + "parent": null + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/whole_file_referenced_as_two_kinds.json b/test/openapi/frame/3.1/pass/whole_file_referenced_as_two_kinds.json new file mode 100644 index 000000000..97a66c946 --- /dev/null +++ b/test/openapi/frame/3.1/pass/whole_file_referenced_as_two_kinds.json @@ -0,0 +1,77 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "examples": { + "E": { + "$ref": "https://example.com/target.json" + } + }, + "callbacks": { + "C": { + "$ref": "https://example.com/target.json" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/target.json": {} + }, + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/callbacks/C": { + "type": "reference", + "pointer": "/components/callbacks/C", + "parent": "https://example.com/openapi.json#/components", + "original": "https://example.com/target.json", + "destination": "https://example.com/target.json" + }, + "https://example.com/openapi.json#/components/examples/E": { + "type": "reference", + "pointer": "/components/examples/E", + "parent": "https://example.com/openapi.json#/components", + "original": "https://example.com/target.json", + "destination": "https://example.com/target.json" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/target.json": { + "type": "callback", + "pointer": "", + "parent": null + } + }, + "operations": [] + } +} diff --git a/test/openapi/frame/3.1/pass/whole_file_referenced_twice_as_one_kind.json b/test/openapi/frame/3.1/pass/whole_file_referenced_twice_as_one_kind.json new file mode 100644 index 000000000..70adf5367 --- /dev/null +++ b/test/openapi/frame/3.1/pass/whole_file_referenced_twice_as_one_kind.json @@ -0,0 +1,77 @@ +{ + "document": { + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0" + }, + "components": { + "responses": { + "A": { + "$ref": "https://example.com/target.json" + }, + "B": { + "$ref": "https://example.com/target.json" + } + } + } + }, + "defaultBase": "https://example.com/openapi.json", + "resolver": { + "https://example.com/target.json": { + "description": "Not found" + } + }, + "frame": { + "version": "3.1", + "base": "https://example.com/openapi.json", + "standalone": true, + "info": { + "title": "Example", + "version": "1.0.0", + "summary": null, + "description": null, + "termsOfService": null, + "contact": null, + "license": null + }, + "locations": { + "https://example.com/openapi.json": { + "type": "openapi", + "pointer": "", + "parent": null, + "dialect": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "https://example.com/openapi.json#/components": { + "type": "components", + "pointer": "/components", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/openapi.json#/components/responses/A": { + "type": "reference", + "pointer": "/components/responses/A", + "parent": "https://example.com/openapi.json#/components", + "original": "https://example.com/target.json", + "destination": "https://example.com/target.json" + }, + "https://example.com/openapi.json#/components/responses/B": { + "type": "reference", + "pointer": "/components/responses/B", + "parent": "https://example.com/openapi.json#/components", + "original": "https://example.com/target.json", + "destination": "https://example.com/target.json" + }, + "https://example.com/openapi.json#/info": { + "type": "info", + "pointer": "/info", + "parent": "https://example.com/openapi.json" + }, + "https://example.com/target.json": { + "type": "response", + "pointer": "", + "parent": null + } + }, + "operations": [] + } +} diff --git a/test/openapi/framesuite.cc b/test/openapi/framesuite.cc new file mode 100644 index 000000000..c28a60925 --- /dev/null +++ b/test/openapi/framesuite.cc @@ -0,0 +1,419 @@ +#include +#include +#include +#include +#include + +#include // std::ranges::find +#include // std::size_t +#include // std::filesystem +#include // std::cerr +#include // std::nullopt +#include // std::ostringstream +#include // std::string +#include // std::string_view +#include // std::vector + +namespace { + +// Every key a fixture may declare. Anything else is a mistake that would +// otherwise go unnoticed, as the runner would simply not read it +// NOLINTBEGIN(cert-err58-cpp,bugprone-throwing-static-initialization) +const std::vector KNOWN_KEYS{ + "document", "defaultBase", "resolver", "frame", "error", "dangling"}; +const std::vector KNOWN_ERROR_KEYS{"message", "location", "base"}; +const std::vector KNOWN_METHODS{ + "get", "put", "post", "delete", "options", "head", "patch", "trace"}; +// The serialised names of the Objects a location may hold, and of the routes +// an operation may be reached through. With the enums private these names are +// the public contract, so a rename or an omission has to fail rather than pass +// every fixture that happens not to cover it +const std::vector KNOWN_TYPES{"openapi", + "path-item", + "parameter", + "request-body", + "response", + "example", + "header", + "link", + "callback", + "security-scheme", + "info", + "contact", + "license", + "server", + "server-variable", + "components", + "paths", + "operation", + "external-documentation", + "media-type", + "encoding", + "responses", + "tag", + "reference", + "schema", + "oauth-flows", + "oauth-flow", + "security-requirement"}; +const std::vector KNOWN_OPERATION_TYPES{"path", "webhook", + "callback"}; +// NOLINTEND(cert-err58-cpp,bugprone-throwing-static-initialization) + +auto make_resolver(const sourcemeta::core::JSON &test) + -> sourcemeta::core::OpenAPIResolver { + if (!test.defines("resolver")) { + return nullptr; + } + + const auto ®istry{test.at("resolver")}; + return [registry](const std::string_view identifier) + -> sourcemeta::core::OpenAPIResolverResult { + const auto *match{ + registry.try_at(sourcemeta::core::JSON::String{identifier})}; + if (match != nullptr) { + return *match; + } + + return std::nullopt; + }; +} + +// A frame keeps a view into the base it was given, so the base has to outlive +// it. The caller owns this, as anything built inside the analysis would dangle +// on return and only misbehave later, when the frame is read back +auto make_default_base(const sourcemeta::core::JSON &test) + -> sourcemeta::core::JSON::String { + const auto *base{test.try_at("defaultBase")}; + return base == nullptr ? sourcemeta::core::JSON::String{} : base->to_string(); +} + +// A fixture cannot name the location it will be read from, so it writes +// `[PATH]` where the directory holding it belongs and the runner fills that in +// with the URI of that directory. That makes the runner stand in for whatever +// retrieved the document, which is what OpenAPI 3.1 leaves to the caller. +// +// It is a URI rather than a path because a path is not one. A Windows path +// carries a drive letter and backslashes, neither of which a URI reference +// admits, so a fixture spelling the scheme itself and having the runner paste a +// path after it produces something that parses nowhere but on POSIX +auto expand(const sourcemeta::core::JSON::String &value, + const std::filesystem::path &directory) + -> sourcemeta::core::JSON::String { + constexpr auto PLACEHOLDER{"[PATH]"}; + const auto replacement{ + sourcemeta::core::URI::from_path(directory).recompose()}; + sourcemeta::core::JSON::String result{value}; + auto position{result.find(PLACEHOLDER)}; + while (position != sourcemeta::core::JSON::String::npos) { + result.replace(position, std::string_view{PLACEHOLDER}.size(), replacement); + position = result.find(PLACEHOLDER, position); + } + + return result; +} + +// The placeholder stands anywhere a path may be written, including the keys a +// frame is reported under, so every string in the fixture goes through it +auto expand_all(const sourcemeta::core::JSON &value, + const std::filesystem::path &directory) + -> sourcemeta::core::JSON { + if (value.is_string()) { + return sourcemeta::core::JSON{expand(value.to_string(), directory)}; + } + + if (value.is_array()) { + auto result{sourcemeta::core::JSON::make_array()}; + for (const auto &entry : value.as_array()) { + result.push_back(expand_all(entry, directory)); + } + + return result; + } + + if (value.is_object()) { + auto result{sourcemeta::core::JSON::make_object()}; + for (const auto &entry : value.as_object()) { + result.assign(expand(entry.first, directory), + expand_all(entry.second, directory)); + } + + return result; + } + + return value; +} + +auto check_known_keys(const sourcemeta::core::JSON &test) -> void { + for (const auto &entry : test.as_object()) { + EXPECT_TRUE(std::ranges::find(KNOWN_KEYS, entry.first) != + KNOWN_KEYS.cend()); + } + + EXPECT_TRUE(test.defines("document")); +} + +// A frame is a graph written down as text, and every edge in it is a key into +// the same map. These hold whatever the description was, so the suite asserts +// them on every fixture rather than on the handful that thought to look +auto check_frame_invariants(const sourcemeta::core::JSON &frame, + const sourcemeta::core::JSON &test) -> void { + const auto &locations{frame.at("locations")}; + const auto *dangling{test.try_at("dangling")}; + std::vector unresolved; + + // The entry document is an Object like any other, so the base names it, and + // what it names is the OpenAPI Object at its root + const auto &base{frame.at("base").to_string()}; + EXPECT_TRUE(locations.defines(base)); + if (locations.defines(base)) { + EXPECT_EQ(locations.at(base).at("type").to_string(), "openapi"); + } + + // A frame stands alone when everything it references is inside it, which is + // exactly the set of destinations a fixture had to declare + EXPECT_EQ(frame.at("standalone").to_boolean(), + dangling == nullptr || dangling->empty()); + + for (const auto &entry : locations.as_object()) { + // A location carries its pointer, and its key is that pointer hung off the + // document it sits in as a properly escaped fragment. So the key has to + // parse as a URI and its fragment has to decode back to that same pointer, + // which is what a path template or a callback expression would otherwise + // break + const auto &pointer{entry.second.at("pointer").to_string()}; + if (pointer.empty()) { + EXPECT_EQ(entry.first.find('#'), sourcemeta::core::JSON::String::npos); + } else { + const sourcemeta::core::URI uri{entry.first}; + const auto recovered{sourcemeta::core::fragment_to_pointer(uri)}; + EXPECT_TRUE(recovered.has_value()); + if (recovered.has_value()) { + EXPECT_EQ(sourcemeta::core::to_string(recovered.value()), pointer); + } + } + + // Only the root of a document has no parent, and every other parent is a + // key into this same map + const auto &parent{entry.second.at("parent")}; + EXPECT_EQ(parent.is_null(), pointer.empty()); + if (parent.is_string()) { + EXPECT_TRUE(locations.defines(parent.to_string())); + } + + const auto type{entry.second.at("type").to_string()}; + EXPECT_TRUE(std::ranges::find(KNOWN_TYPES, type) != KNOWN_TYPES.cend()); + + // The dialect in force is recorded where a JSON Schema implementation + // needs it, which is the root of a document and each Schema Object + EXPECT_EQ(entry.second.defines("dialect"), + type == "openapi" || type == "schema"); + + // A reference is written down on the Object that makes it, which is a + // Reference Object, a Path Item Object declaring a `$ref`, or a Link + // Object declaring an `operationRef` + EXPECT_EQ(entry.second.defines("original"), + entry.second.defines("destination")); + if (!entry.second.defines("destination")) { + continue; + } + + EXPECT_TRUE(type == "reference" || type == "path-item" || type == "link"); + + // Where a reference lands is a key into this same map, unless the fixture + // says the walk was never in a position to read it + const auto &destination{entry.second.at("destination")}; + if (locations.defines(destination.to_string())) { + continue; + } + + unresolved.push_back(destination); + EXPECT_TRUE(dangling != nullptr && + std::ranges::find(dangling->as_array(), destination) != + dangling->as_array().cend()); + } + + // An exemption for a destination that now resolves, or that nothing points + // at any more, quietly stops meaning anything, so every one a fixture + // declares has to still be earned + if (dangling != nullptr) { + for (const auto &entry : dangling->as_array()) { + EXPECT_TRUE(std::ranges::find(unresolved, entry) != unresolved.cend()); + } + } + + for (const auto &operation : frame.at("operations").as_array()) { + // Every operation the description exposes is an Operation Object that the + // walk read, and what is in force where it sits was read too + const auto &origin{operation.at("origin").to_string()}; + EXPECT_TRUE(locations.defines(origin)); + EXPECT_EQ(locations.at(origin).at("type").to_string(), "operation"); + EXPECT_TRUE( + std::ranges::find(KNOWN_METHODS, operation.at("method").to_string()) != + KNOWN_METHODS.cend()); + EXPECT_TRUE(std::ranges::find(KNOWN_OPERATION_TYPES, + operation.at("type").to_string()) != + KNOWN_OPERATION_TYPES.cend()); + + // An Operation Object only ever sits directly inside a Path Item Object + const auto &container{locations.at(origin).at("parent")}; + EXPECT_TRUE(container.is_string() && + locations.defines(container.to_string())); + if (container.is_string() && locations.defines(container.to_string())) { + EXPECT_EQ(locations.at(container.to_string()).at("type").to_string(), + "path-item"); + } + + // The position that exposes an operation is a Path Item too, and it is the + // one that gives it a URL rather than the one that defines it + const auto &endpoint{operation.at("endpoint").to_string()}; + EXPECT_TRUE(locations.defines(endpoint)); + EXPECT_EQ(locations.at(endpoint).at("type").to_string(), "path-item"); + + for (const auto &server : operation.at("servers").as_array()) { + EXPECT_TRUE(locations.defines(server.to_string())); + EXPECT_EQ(locations.at(server.to_string()).at("type").to_string(), + "server"); + } + + for (const auto &requirement : operation.at("security").as_array()) { + EXPECT_TRUE(locations.defines(requirement.to_string())); + EXPECT_EQ(locations.at(requirement.to_string()).at("type").to_string(), + "security-requirement"); + } + + // A tag names a Tag Object the entry document declares, or nothing at all, + // which the specification permits + for (const auto &tag : operation.at("tags").as_array()) { + if (tag.is_null()) { + continue; + } + + EXPECT_TRUE(locations.defines(tag.to_string())); + EXPECT_EQ(locations.at(tag.to_string()).at("type").to_string(), "tag"); + } + + // A parameter position holds a Parameter Object, or a Reference Object + // standing in for one + for (const auto ¶meter : operation.at("parameters").as_array()) { + EXPECT_TRUE(locations.defines(parameter.to_string())); + const auto type{ + locations.at(parameter.to_string()).at("type").to_string()}; + EXPECT_TRUE(type == "parameter" || type == "reference"); + } + } +} + +auto run_pass_test(const sourcemeta::core::JSON &test) -> void { + check_known_keys(test); + EXPECT_TRUE(test.defines("frame")); + + const auto resolver{make_resolver(test)}; + const auto default_base{make_default_base(test)}; + + const sourcemeta::core::OpenAPIFrame frame{test.at("document"), resolver, + default_base}; + // The invariants come first because a failed expectation aborts the test. A + // frame that contradicts itself is a deeper failure than one that merely + // differs from what a fixture recorded, so it is the one worth reporting + const auto result{frame.to_json()}; + check_frame_invariants(result, test); + EXPECT_EQ(result, test.at("frame")); +} + +auto run_fail_test(const sourcemeta::core::JSON &test) -> void { + check_known_keys(test); + EXPECT_TRUE(test.defines("error")); + EXPECT_TRUE(test.at("error").defines("message")); + for (const auto &entry : test.at("error").as_object()) { + EXPECT_TRUE(std::ranges::find(KNOWN_ERROR_KEYS, entry.first) != + KNOWN_ERROR_KEYS.cend()); + } + + const auto resolver{make_resolver(test)}; + const auto default_base{make_default_base(test)}; + + try { + [[maybe_unused]] const sourcemeta::core::OpenAPIFrame frame{ + test.at("document"), resolver, default_base}; + FAIL(); + } catch (const sourcemeta::core::OpenAPIError &error) { + EXPECT_EQ(error.what(), test.at("error").at("message").to_string()); + + // A document with more than one problem reports whichever check runs + // first, so a fixture that cannot pin that down states no location + const auto *location{test.at("error").try_at("location")}; + if (location != nullptr) { + EXPECT_EQ(sourcemeta::core::to_string(error.location()), + location->to_string()); + } + + // A description may span documents, so an error says which one it is in. + // A fixture that names none expects the entry document + const auto *base{test.at("error").try_at("base")}; + EXPECT_EQ(error.base(), + base == nullptr ? make_default_base(test) : base->to_string()); + } +} + +auto register_tests(const std::filesystem::path &directory, + const bool expect_success) -> std::size_t { + std::size_t count{0}; + for (const std::filesystem::directory_entry &entry : + std::filesystem::recursive_directory_iterator{directory}) { + if (!entry.is_regular_file() || entry.path().extension() != ".json") { + continue; + } + + const auto version{ + entry.path().parent_path().parent_path().filename().string()}; + const auto outcome{entry.path().parent_path().filename().string()}; + std::ostringstream name; + for (const auto character : entry.path().stem().string()) { + name << (character == '-' ? '_' : character); + } + + std::string suite{"OpenAPIFrame_"}; + suite.append(version).append("_").append(outcome); + + const auto test{expand_all(sourcemeta::core::read_json(entry.path()), + entry.path().parent_path())}; + sourcemeta::core::test_register(suite, name.str(), __FILE__, __LINE__, + [test, expect_success]() -> void { + if (expect_success) { + run_pass_test(test); + } else { + run_fail_test(test); + } + }); + count += 1; + } + + return count; +} + +} // namespace + +auto main(int argc, char **argv) -> int { + const std::filesystem::path base{FRAME_SUITE_PATH}; + std::size_t passing{0}; + std::size_t failing{0}; + for (const std::filesystem::directory_entry &version : + std::filesystem::directory_iterator{base}) { + if (!version.is_directory()) { + continue; + } + + passing += register_tests(version.path() / "pass", true); + failing += register_tests(version.path() / "fail", false); + } + + // A fixture in the wrong place, or with the wrong extension, would otherwise + // never run and nobody would notice + if (passing == 0 || failing == 0) { + std::cerr << "No frame fixtures found at " << FRAME_SUITE_PATH << "\n"; + return 1; + } + + return sourcemeta::core::test_run(argc, argv); +} diff --git a/test/openapi/oaisuite.cc b/test/openapi/oaisuite.cc new file mode 100644 index 000000000..e65ca4ac3 --- /dev/null +++ b/test/openapi/oaisuite.cc @@ -0,0 +1,110 @@ +#include +#include +#include +#include + +#include // std::size_t +#include // std::filesystem +#include // std::cerr +#include // std::set +#include // std::ostringstream +#include // std::string + +namespace { + +// These fixtures test the meta-schema, not the specification text. Where the +// two differ the text is normative and stricter, and so are we +// NOLINTBEGIN(cert-err58-cpp,bugprone-throwing-static-initialization) +const std::set KNOWN_DIVERGENCES{ + // A `path` parameter with a `content` and no `required`. Section 4.8.12 + // requires that field of every `path` parameter, while the meta-schema + // only asks for it alongside a `schema` + "style_defaults", + // `/pets/{id}` under a parameter named `petId`. Section 4.3 has each + // template expression "correspond to a path parameter", and Section + // 4.8.12 has such a parameter correspond to a template expression back + // the other way, so this fails both ways round. Neither requirement can + // be written as a keyword, as it means comparing a property name against + // a value nested under it, which is why the meta-schema takes it. It is + // one of the specification's own published examples + "operation_object_example", + // `/user/{username}` under a parameter named `usernames`, which fails the + // same two requirements the same two ways, and is likewise published as + // an example + "parameter_object_examples", + // A Link Object naming `getThing`, which no operation in the document + // declares. Section 4.8.20 has such a name "MUST be resolved within the + // scope of the OpenAPI Description", and no keyword can compare a value + // against the operation identifiers scattered through a document, so the + // meta-schema takes it + "path_item_servers_parameters"}; +// NOLINTEND(cert-err58-cpp,bugprone-throwing-static-initialization) + +auto run_pass_case(const sourcemeta::core::JSON &document) -> void { + const sourcemeta::core::OpenAPIFrame frame{document, nullptr}; + EXPECT_EQ(frame.version(), sourcemeta::core::OpenAPIVersion::OPENAPI_3_1); +} + +auto run_fail_case(const sourcemeta::core::JSON &document) -> void { + try { + [[maybe_unused]] const sourcemeta::core::OpenAPIFrame frame{document, + nullptr}; + FAIL(); + } catch (const sourcemeta::core::OpenAPIError &error) { + // Which rule turns the document down is the business of the unit tests. + // What matters here is only that it is turned down at all + EXPECT_TRUE(error.what() != nullptr); + } +} + +auto register_tests(const std::filesystem::path &directory, + const bool expect_success) -> std::size_t { + std::size_t count{0}; + for (const std::filesystem::directory_entry &entry : + std::filesystem::directory_iterator{directory}) { + if (!entry.is_regular_file() || entry.path().extension() != ".yaml") { + continue; + } + + const auto suite{entry.path().parent_path().filename().string()}; + std::ostringstream name; + for (const auto character : entry.path().stem().string()) { + name << (character == '-' ? '_' : character); + } + + if (KNOWN_DIVERGENCES.contains(name.str())) { + continue; + } + + const auto document{sourcemeta::core::read_yaml(entry.path())}; + sourcemeta::core::test_register("OpenAPISuite_" + suite, name.str(), + __FILE__, __LINE__, + [document, expect_success]() -> void { + if (expect_success) { + run_pass_case(document); + } else { + run_fail_case(document); + } + }); + count += 1; + } + + return count; +} + +} // namespace + +auto main(int argc, char **argv) -> int { + const std::filesystem::path base{OPENAPI_SUITE_PATH}; + const auto passing{register_tests(base / "pass", true)}; + const auto failing{register_tests(base / "fail", false)}; + // A fixture in the wrong place, or with the wrong extension, would otherwise + // never run and nobody would notice + if (passing == 0 || failing == 0) { + std::cerr << "No OpenAPI suite fixtures found at " << OPENAPI_SUITE_PATH + << "\n"; + return 1; + } + + return sourcemeta::core::test_run(argc, argv); +} diff --git a/test/openapi/openapi_frame_test.cc b/test/openapi/openapi_frame_test.cc new file mode 100644 index 000000000..bc005c29b --- /dev/null +++ b/test/openapi/openapi_frame_test.cc @@ -0,0 +1,475 @@ +#include +#include + +#include + +#include // std::string_view + +TEST(version_patch_zero) { + const auto document{sourcemeta::core::parse_json(R"JSON({ + "openapi": "3.1.0", + "info": { "title": "Example", "version": "1.0.0" }, + "paths": {} + })JSON")}; + + const sourcemeta::core::OpenAPIFrame frame{document, nullptr}; + EXPECT_EQ(frame.version(), sourcemeta::core::OpenAPIVersion::OPENAPI_3_1); +} + +TEST(version_patch_one) { + const auto document{sourcemeta::core::parse_json(R"JSON({ + "openapi": "3.1.1", + "info": { "title": "Example", "version": "1.0.0" }, + "paths": {} + })JSON")}; + + const sourcemeta::core::OpenAPIFrame frame{document, nullptr}; + EXPECT_EQ(frame.version(), sourcemeta::core::OpenAPIVersion::OPENAPI_3_1); +} + +TEST(version_patch_two) { + const auto document{sourcemeta::core::parse_json(R"JSON({ + "openapi": "3.1.2", + "info": { "title": "Example", "version": "1.0.0" }, + "paths": {} + })JSON")}; + + const sourcemeta::core::OpenAPIFrame frame{document, nullptr}; + EXPECT_EQ(frame.version(), sourcemeta::core::OpenAPIVersion::OPENAPI_3_1); +} + +TEST(version_pre_release) { + const auto document{sourcemeta::core::parse_json(R"JSON({ + "openapi": "3.1.0-rc0", + "info": { "title": "Example", "version": "1.0.0" }, + "paths": {} + })JSON")}; + + const sourcemeta::core::OpenAPIFrame frame{document, nullptr}; + EXPECT_EQ(frame.version(), sourcemeta::core::OpenAPIVersion::OPENAPI_3_1); +} + +TEST(version_with_components_and_no_paths) { + const auto document{sourcemeta::core::parse_json(R"JSON({ + "openapi": "3.1.1", + "info": { "title": "Example", "version": "1.0.0" }, + "components": {} + })JSON")}; + + const sourcemeta::core::OpenAPIFrame frame{document, nullptr}; + EXPECT_EQ(frame.version(), sourcemeta::core::OpenAPIVersion::OPENAPI_3_1); +} + +TEST(version_with_webhooks_and_no_paths) { + const auto document{sourcemeta::core::parse_json(R"JSON({ + "openapi": "3.1.1", + "info": { "title": "Example", "version": "1.0.0" }, + "webhooks": {} + })JSON")}; + + const sourcemeta::core::OpenAPIFrame frame{document, nullptr}; + EXPECT_EQ(frame.version(), sourcemeta::core::OpenAPIVersion::OPENAPI_3_1); +} + +TEST(version_agrees_with_json_export) { + const auto document{sourcemeta::core::parse_json(R"JSON({ + "openapi": "3.1.1", + "info": { "title": "Example", "version": "1.0.0" }, + "paths": {} + })JSON")}; + + const sourcemeta::core::OpenAPIFrame frame{document, nullptr}; + EXPECT_EQ(frame.version(), sourcemeta::core::OpenAPIVersion::OPENAPI_3_1); + EXPECT_EQ(frame.to_json().at("version"), sourcemeta::core::JSON{"3.1"}); +} + +TEST(info_required_fields_only) { + const auto document{sourcemeta::core::parse_json(R"JSON({ + "openapi": "3.1.1", + "info": { "title": "Example", "version": "1.0.0" }, + "paths": {} + })JSON")}; + + const sourcemeta::core::OpenAPIFrame frame{document, nullptr}; + EXPECT_EQ(frame.info().title, "Example"); + EXPECT_EQ(frame.info().version, "1.0.0"); + EXPECT_FALSE(frame.info().summary.has_value()); + EXPECT_FALSE(frame.info().description.has_value()); + EXPECT_FALSE(frame.info().terms_of_service.has_value()); + EXPECT_FALSE(frame.info().contact.has_value()); + EXPECT_FALSE(frame.info().license.has_value()); +} + +TEST(info_every_field) { + const auto document{sourcemeta::core::parse_json(R"JSON({ + "openapi": "3.1.1", + "info": { + "title": "Example Pet Store App", + "summary": "A pet store manager.", + "description": "This is an example server for a pet store.", + "termsOfService": "https://example.com/terms/", + "contact": { + "name": "API Support", + "url": "https://www.example.com/support", + "email": "support@example.com" + }, + "license": { + "name": "Apache 2.0", + "url": "https://www.apache.org/licenses/LICENSE-2.0.html" + }, + "version": "1.0.1" + }, + "paths": {} + })JSON")}; + + const sourcemeta::core::OpenAPIFrame frame{document, nullptr}; + EXPECT_EQ(frame.info().title, "Example Pet Store App"); + EXPECT_EQ(frame.info().version, "1.0.1"); + EXPECT_EQ(frame.info().summary.value(), "A pet store manager."); + EXPECT_EQ(frame.info().description.value(), + "This is an example server for a pet store."); + EXPECT_EQ(frame.info().terms_of_service.value(), + "https://example.com/terms/"); + EXPECT_EQ(frame.info().contact.value().name.value(), "API Support"); + EXPECT_EQ(frame.info().contact.value().url.value(), + "https://www.example.com/support"); + EXPECT_EQ(frame.info().contact.value().email.value(), "support@example.com"); + EXPECT_EQ(frame.info().license.value().name, "Apache 2.0"); + EXPECT_FALSE(frame.info().license.value().identifier.has_value()); + EXPECT_EQ(frame.info().license.value().url.value(), + "https://www.apache.org/licenses/LICENSE-2.0.html"); +} + +TEST(info_empty_string_values_are_kept) { + const auto document{sourcemeta::core::parse_json(R"JSON({ + "openapi": "3.1.1", + "info": { "title": "", "version": "", "summary": "" }, + "paths": {} + })JSON")}; + + const sourcemeta::core::OpenAPIFrame frame{document, nullptr}; + EXPECT_EQ(frame.info().title, ""); + EXPECT_EQ(frame.info().version, ""); + EXPECT_TRUE(frame.info().summary.has_value()); + EXPECT_EQ(frame.info().summary.value(), ""); +} + +TEST(info_relative_terms_of_service) { + const auto document{sourcemeta::core::parse_json(R"JSON({ + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0", + "termsOfService": "/terms" + }, + "paths": {} + })JSON")}; + + const sourcemeta::core::OpenAPIFrame frame{document, nullptr}; + EXPECT_EQ(frame.info().terms_of_service.value(), "/terms"); +} + +TEST(info_empty_contact) { + const auto document{sourcemeta::core::parse_json(R"JSON({ + "openapi": "3.1.1", + "info": { "title": "Example", "version": "1.0.0", "contact": {} }, + "paths": {} + })JSON")}; + + const sourcemeta::core::OpenAPIFrame frame{document, nullptr}; + EXPECT_TRUE(frame.info().contact.has_value()); + EXPECT_FALSE(frame.info().contact.value().name.has_value()); + EXPECT_FALSE(frame.info().contact.value().url.has_value()); + EXPECT_FALSE(frame.info().contact.value().email.has_value()); +} + +TEST(info_license_with_spdx_identifier) { + const auto document{sourcemeta::core::parse_json(R"JSON({ + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0", + "license": { "name": "Apache 2.0", "identifier": "Apache-2.0" } + }, + "paths": {} + })JSON")}; + + const sourcemeta::core::OpenAPIFrame frame{document, nullptr}; + EXPECT_EQ(frame.info().license.value().name, "Apache 2.0"); + EXPECT_EQ(frame.info().license.value().identifier.value(), "Apache-2.0"); + EXPECT_FALSE(frame.info().license.value().url.has_value()); +} + +TEST(info_license_identifier_is_not_validated) { + const auto document{sourcemeta::core::parse_json(R"JSON({ + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0", + "license": { "name": "Nonsense", "identifier": "not an SPDX expression" } + }, + "paths": {} + })JSON")}; + + const sourcemeta::core::OpenAPIFrame frame{document, nullptr}; + EXPECT_EQ(frame.info().license.value().identifier.value(), + "not an SPDX expression"); +} + +TEST(info_extensions_are_accepted) { + const auto document{sourcemeta::core::parse_json(R"JSON({ + "openapi": "3.1.1", + "info": { + "title": "Example", + "version": "1.0.0", + "x-internal-id": 42, + "x-oai-reserved": null, + "x-oas-reserved": [ 1, 2 ], + "x-": "the prefix alone is a legal field name", + "contact": { "name": "Support", "x-slack": "#support" }, + "license": { "name": "MIT", "x-approved": true } + }, + "paths": {} + })JSON")}; + + const sourcemeta::core::OpenAPIFrame frame{document, nullptr}; + EXPECT_EQ(frame.info().title, "Example"); + EXPECT_EQ(frame.info().contact.value().name.value(), "Support"); + EXPECT_EQ(frame.info().license.value().name, "MIT"); +} + +TEST(base_is_absent_by_default) { + const auto document{sourcemeta::core::parse_json(R"JSON({ + "openapi": "3.1.1", + "info": { "title": "Example", "version": "1.0.0" }, + "paths": {} + })JSON")}; + + const sourcemeta::core::OpenAPIFrame frame{document, nullptr}; + EXPECT_TRUE(frame.base().empty()); +} + +TEST(base_is_what_the_caller_established) { + const auto document{sourcemeta::core::parse_json(R"JSON({ + "openapi": "3.1.1", + "info": { "title": "Example", "version": "1.0.0" }, + "paths": {} + })JSON")}; + + const sourcemeta::core::OpenAPIFrame frame{ + document, nullptr, "https://example.com/openapi.json"}; + EXPECT_EQ(frame.base(), "https://example.com/openapi.json"); +} + +TEST(base_is_canonicalised) { + const auto document{sourcemeta::core::parse_json(R"JSON({ + "openapi": "3.1.1", + "info": { "title": "Example", "version": "1.0.0" }, + "paths": {} + })JSON")}; + + const sourcemeta::core::OpenAPIFrame frame{ + document, nullptr, "HTTPS://Example.COM:443/a/../openapi.json"}; + EXPECT_EQ(frame.base(), "https://example.com/openapi.json"); +} + +TEST(base_does_not_borrow_from_the_caller) { + const auto document{sourcemeta::core::parse_json(R"JSON({ + "openapi": "3.1.1", + "info": { "title": "Example", "version": "1.0.0" }, + "paths": {} + })JSON")}; + + // Canonicalising means the frame owns what it reports, so a base that goes + // away afterwards cannot dangle + const sourcemeta::core::OpenAPIFrame frame{ + document, nullptr, + sourcemeta::core::JSON::String{"https://example.com/openapi.json"}}; + EXPECT_EQ(frame.base(), "https://example.com/openapi.json"); +} + +TEST(base_agrees_with_json_export) { + const auto document{sourcemeta::core::parse_json(R"JSON({ + "openapi": "3.1.1", + "info": { "title": "Example", "version": "1.0.0" }, + "paths": {} + })JSON")}; + + const sourcemeta::core::OpenAPIFrame frame{ + document, nullptr, "https://example.com/openapi.json"}; + EXPECT_EQ(frame.to_json().at("base"), + sourcemeta::core::JSON{"https://example.com/openapi.json"}); +} + +TEST(base_absent_is_the_empty_uri_reference_in_json_export) { + const auto document{sourcemeta::core::parse_json(R"JSON({ + "openapi": "3.1.1", + "info": { "title": "Example", "version": "1.0.0" }, + "paths": {} + })JSON")}; + + const sourcemeta::core::OpenAPIFrame frame{document, nullptr}; + EXPECT_EQ(frame.to_json().at("base"), sourcemeta::core::JSON{""}); +} + +// A resolver may hand back a document it owns rather than one the caller keeps +// alive, in which case that document is gone once it has been read. Nothing +// the frame carries on may point into it +TEST(owned_resolver_result_repeating_an_operation_id) { + const auto document{sourcemeta::core::parse_json(R"JSON({ + "openapi": "3.1.1", + "info": { "title": "Example", "version": "1.0.0" }, + "components": { + "responses": { + "R": { "$ref": "https://example.com/common.json#/components/responses/Ok" } + } + }, + "paths": { + "/pets": { "get": { "operationId": "list", "responses": { "200": { "description": "ok" } } } } + } + })JSON")}; + + const sourcemeta::core::OpenAPIResolver resolver{ + [](const std::string_view) -> sourcemeta::core::OpenAPIResolverResult { + return sourcemeta::core::parse_json(R"JSON({ + "openapi": "3.1.1", + "info": { "title": "Common", "version": "1.0.0" }, + "paths": { + "/other": { "get": { "operationId": "list", "responses": { "200": { "description": "ok" } } } } + }, + "components": { "responses": { "Ok": { "description": "ok" } } } + })JSON"); + }}; + + try { + [[maybe_unused]] const sourcemeta::core::OpenAPIFrame frame{ + document, resolver, "https://example.com/openapi.json"}; + FAIL(); + } catch (const sourcemeta::core::OpenAPIError &error) { + EXPECT_STREQ(error.what(), + "The Operation Object identifiers must be unique"); + } +} + +TEST(owned_resolver_result_supplying_a_parameter) { + const auto document{sourcemeta::core::parse_json(R"JSON({ + "openapi": "3.1.1", + "info": { "title": "Example", "version": "1.0.0" }, + "paths": { + "/pets": { + "parameters": [ + { "$ref": "https://example.com/common.json#/components/parameters/Limit" } + ], + "get": { + "parameters": [ { "name": "limit", "in": "query", "schema": {} } ], + "responses": { "200": { "description": "ok" } } + } + } + } + })JSON")}; + + const sourcemeta::core::OpenAPIResolver resolver{ + [](const std::string_view) -> sourcemeta::core::OpenAPIResolverResult { + return sourcemeta::core::parse_json(R"JSON({ + "openapi": "3.1.1", + "info": { "title": "Common", "version": "1.0.0" }, + "components": { + "parameters": { "Limit": { "name": "limit", "in": "query", "schema": {} } } + } + })JSON"); + }}; + + const sourcemeta::core::OpenAPIFrame frame{ + document, resolver, "https://example.com/openapi.json"}; + const auto result{frame.to_json()}; + const auto ¶meters{result.at("operations").at(0).at("parameters")}; + EXPECT_EQ(parameters.size(), 1); + EXPECT_EQ( + parameters.at(0), + sourcemeta::core::JSON{ + "https://example.com/openapi.json#/paths/~1pets/get/parameters/0"}); +} + +TEST(standalone_without_references) { + const auto document{sourcemeta::core::parse_json(R"JSON({ + "openapi": "3.1.1", + "info": { "title": "Example", "version": "1.0.0" }, + "paths": {} + })JSON")}; + + const sourcemeta::core::OpenAPIFrame frame{document, nullptr}; + EXPECT_TRUE(frame.standalone()); +} + +TEST(standalone_with_an_internal_reference) { + const auto document{sourcemeta::core::parse_json(R"JSON({ + "openapi": "3.1.1", + "info": { "title": "Example", "version": "1.0.0" }, + "components": { + "responses": { + "Ok": { "description": "ok" }, + "R": { "$ref": "#/components/responses/Ok" } + } + } + })JSON")}; + + const sourcemeta::core::OpenAPIFrame frame{document, nullptr}; + EXPECT_TRUE(frame.standalone()); +} + +TEST(standalone_with_an_external_reference_and_no_resolver) { + const auto document{sourcemeta::core::parse_json(R"JSON({ + "openapi": "3.1.1", + "info": { "title": "Example", "version": "1.0.0" }, + "components": { + "responses": { + "R": { "$ref": "https://example.com/common.json#/components/responses/Ok" } + } + } + })JSON")}; + + const sourcemeta::core::OpenAPIFrame frame{ + document, nullptr, "https://example.com/openapi.json"}; + EXPECT_FALSE(frame.standalone()); +} + +TEST(standalone_with_an_external_reference_and_a_resolver) { + const auto document{sourcemeta::core::parse_json(R"JSON({ + "openapi": "3.1.1", + "info": { "title": "Example", "version": "1.0.0" }, + "components": { + "responses": { + "R": { "$ref": "https://example.com/common.json#/components/responses/Ok" } + } + } + })JSON")}; + + const sourcemeta::core::OpenAPIResolver resolver{ + [](const std::string_view) -> sourcemeta::core::OpenAPIResolverResult { + return sourcemeta::core::parse_json(R"JSON({ + "openapi": "3.1.1", + "info": { "title": "Common", "version": "1.0.0" }, + "components": { "responses": { "Ok": { "description": "ok" } } } + })JSON"); + }}; + + const sourcemeta::core::OpenAPIFrame frame{ + document, resolver, "https://example.com/openapi.json"}; + EXPECT_TRUE(frame.standalone()); +} + +TEST(standalone_agrees_with_json_export) { + const auto document{sourcemeta::core::parse_json(R"JSON({ + "openapi": "3.1.1", + "info": { "title": "Example", "version": "1.0.0" }, + "components": { + "responses": { + "R": { "$ref": "https://example.com/common.json#/components/responses/Ok" } + } + } + })JSON")}; + + const sourcemeta::core::OpenAPIFrame frame{ + document, nullptr, "https://example.com/openapi.json"}; + EXPECT_EQ(frame.to_json().at("standalone"), sourcemeta::core::JSON{false}); +} diff --git a/test/openapi/openapi_version_test.cc b/test/openapi/openapi_version_test.cc new file mode 100644 index 000000000..5ae54012d --- /dev/null +++ b/test/openapi/openapi_version_test.cc @@ -0,0 +1,154 @@ +#include +#include + +#include + +#include // std::optional + +namespace { + +auto version_of(const sourcemeta::core::JSON::String &version) + -> std::optional { + auto document{sourcemeta::core::JSON::make_object()}; + document.assign("openapi", sourcemeta::core::JSON{version}); + return sourcemeta::core::openapi_version(document); +} + +} // namespace + +TEST(patch_zero) { + EXPECT_EQ(version_of("3.1.0").value(), + sourcemeta::core::OpenAPIVersion::OPENAPI_3_1); +} + +TEST(patch_one) { + EXPECT_EQ(version_of("3.1.1").value(), + sourcemeta::core::OpenAPIVersion::OPENAPI_3_1); +} + +TEST(patch_two) { + EXPECT_EQ(version_of("3.1.2").value(), + sourcemeta::core::OpenAPIVersion::OPENAPI_3_1); +} + +TEST(patch_multiple_digits) { + EXPECT_EQ(version_of("3.1.10").value(), + sourcemeta::core::OpenAPIVersion::OPENAPI_3_1); +} + +TEST(patch_leading_zero) { + EXPECT_EQ(version_of("3.1.01").value(), + sourcemeta::core::OpenAPIVersion::OPENAPI_3_1); +} + +TEST(pre_release_suffix) { + EXPECT_EQ(version_of("3.1.0-rc0").value(), + sourcemeta::core::OpenAPIVersion::OPENAPI_3_1); +} + +TEST(pre_release_suffix_with_dots) { + EXPECT_EQ(version_of("3.1.0-beta.1").value(), + sourcemeta::core::OpenAPIVersion::OPENAPI_3_1); +} + +TEST(no_patch_component) { EXPECT_FALSE(version_of("3.1").has_value()); } + +TEST(empty_patch_component) { EXPECT_FALSE(version_of("3.1.").has_value()); } + +TEST(empty_pre_release_suffix) { + EXPECT_FALSE(version_of("3.1.0-").has_value()); +} + +TEST(pre_release_suffix_with_line_feed) { + EXPECT_FALSE(version_of("3.1.0-rc\x0A" + "0") + .has_value()); +} + +TEST(pre_release_suffix_ending_in_line_feed) { + EXPECT_FALSE(version_of("3.1.0-\x0A").has_value()); +} + +TEST(pre_release_suffix_with_carriage_return) { + EXPECT_FALSE(version_of("3.1.0-rc\x0D" + "0") + .has_value()); +} + +TEST(pre_release_suffix_with_line_separator) { + EXPECT_FALSE(version_of("3.1.0-rc\xE2\x80\xA8" + "0") + .has_value()); +} + +TEST(pre_release_suffix_with_paragraph_separator) { + EXPECT_FALSE(version_of("3.1.0-rc\xE2\x80\xA9" + "0") + .has_value()); +} + +TEST(pre_release_suffix_with_horizontal_tab) { + EXPECT_EQ(version_of("3.1.0-rc\x09" + "0") + .value(), + sourcemeta::core::OpenAPIVersion::OPENAPI_3_1); +} + +TEST(pre_release_suffix_with_form_feed) { + EXPECT_EQ(version_of("3.1.0-rc\x0C" + "0") + .value(), + sourcemeta::core::OpenAPIVersion::OPENAPI_3_1); +} + +TEST(pre_release_suffix_with_next_line) { + EXPECT_EQ(version_of("3.1.0-rc\xC2\x85" + "0") + .value(), + sourcemeta::core::OpenAPIVersion::OPENAPI_3_1); +} + +TEST(non_numeric_patch_component) { + EXPECT_FALSE(version_of("3.1.x").has_value()); +} + +TEST(fourth_version_component) { + EXPECT_FALSE(version_of("3.1.0.0").has_value()); +} + +TEST(leading_whitespace) { EXPECT_FALSE(version_of(" 3.1.0").has_value()); } + +TEST(trailing_whitespace) { EXPECT_FALSE(version_of("3.1.0 ").has_value()); } + +TEST(unrelated_major_minor_prefix) { + EXPECT_FALSE(version_of("13.1.0").has_value()); +} + +TEST(earlier_minor) { EXPECT_FALSE(version_of("3.0.0").has_value()); } + +TEST(later_minor) { EXPECT_FALSE(version_of("3.2.0").has_value()); } + +TEST(later_major) { EXPECT_FALSE(version_of("4.0.0").has_value()); } + +TEST(swagger) { EXPECT_FALSE(version_of("2.0").has_value()); } + +TEST(empty_string) { EXPECT_FALSE(version_of("").has_value()); } + +TEST(missing_field) { + const auto document{sourcemeta::core::parse_json(R"JSON({ + "info": { "title": "Example", "version": "1.0.0" } + })JSON")}; + EXPECT_FALSE(sourcemeta::core::openapi_version(document).has_value()); +} + +TEST(field_is_not_a_string) { + const auto document{sourcemeta::core::parse_json(R"JSON({ + "openapi": 3.1 + })JSON")}; + EXPECT_FALSE(sourcemeta::core::openapi_version(document).has_value()); +} + +TEST(document_is_not_an_object) { + const auto document{sourcemeta::core::parse_json("\"3.1.1\"")}; + EXPECT_FALSE(sourcemeta::core::openapi_version(document).has_value()); +} diff --git a/test/packaging/find_package/CMakeLists.txt b/test/packaging/find_package/CMakeLists.txt index 550356c6b..bff9fe453 100644 --- a/test/packaging/find_package/CMakeLists.txt +++ b/test/packaging/find_package/CMakeLists.txt @@ -39,6 +39,7 @@ target_link_libraries(core_hello PRIVATE sourcemeta::core::preprocessor) target_link_libraries(core_hello PRIVATE sourcemeta::core::jsonrpc) target_link_libraries(core_hello PRIVATE sourcemeta::core::mcp) target_link_libraries(core_hello PRIVATE sourcemeta::core::http) +target_link_libraries(core_hello PRIVATE sourcemeta::core::openapi) target_link_libraries(core_hello PRIVATE sourcemeta::core::jose) target_link_libraries(core_hello PRIVATE sourcemeta::core::oauth) target_link_libraries(core_hello PRIVATE sourcemeta::core::oidc) diff --git a/test/packaging/find_package/hello.cc b/test/packaging/find_package/hello.cc index 56fbf23a7..0b3c01503 100644 --- a/test/packaging/find_package/hello.cc +++ b/test/packaging/find_package/hello.cc @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include diff --git a/vendor/openapi-test-suite-3-1.mask b/vendor/openapi-test-suite-3-1.mask new file mode 100644 index 000000000..fd659aeaf --- /dev/null +++ b/vendor/openapi-test-suite-3-1.mask @@ -0,0 +1,28 @@ +.linkspector.yml +.markdownlint.yaml +.nvmrc +.yarnrc.yml +AI.md +CONTRIBUTING.md +EDITORS.md +GOVERNANCE.md +IMPLEMENTATIONS.md +MAINTAINERS.md +README.md +SECURITY_CONSIDERATIONS.md +SPECIAL_INTEREST_GROUPS.md +TOB.md +_archive_ +package.json +proposals +scripts +spec.config.json +spec.markdownlint.yaml +src +style-guide.md +tests/md2html +tests/schema/oas-schema.mjs +tests/schema/schema.test.mjs +versions +vitest.config.mjs +yarn.lock diff --git a/vendor/openapi-test-suite-3-1/LICENSE b/vendor/openapi-test-suite-3-1/LICENSE new file mode 100644 index 000000000..23b34fdff --- /dev/null +++ b/vendor/openapi-test-suite-3-1/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright The Linux Foundation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/openapi-test-suite-3-1/tests/schema/fail/example-examples.yaml b/vendor/openapi-test-suite-3-1/tests/schema/fail/example-examples.yaml new file mode 100644 index 000000000..6ed2f6b33 --- /dev/null +++ b/vendor/openapi-test-suite-3-1/tests/schema/fail/example-examples.yaml @@ -0,0 +1,17 @@ +openapi: 3.1.1 + +# this example should fail, as example cannot be used together with examples. + +info: + title: API + version: 1.0.0 +components: + parameters: + animal: + name: animal + in: header + schema: {} + example: bear + examples: + a mammalian example: + value: bear diff --git a/vendor/openapi-test-suite-3-1/tests/schema/fail/header-object-allowReserved.yaml b/vendor/openapi-test-suite-3-1/tests/schema/fail/header-object-allowReserved.yaml new file mode 100644 index 000000000..0babaf22d --- /dev/null +++ b/vendor/openapi-test-suite-3-1/tests/schema/fail/header-object-allowReserved.yaml @@ -0,0 +1,12 @@ +openapi: 3.1.0 +info: + title: "allowReserved only permitted with in: query" + version: 1.0.0 +components: + headers: + Style: + schema: + type: array + style: simple + explode: true + allowReserved: true diff --git a/vendor/openapi-test-suite-3-1/tests/schema/fail/invalid_schema_types.yaml b/vendor/openapi-test-suite-3-1/tests/schema/fail/invalid_schema_types.yaml new file mode 100644 index 000000000..55e3c900e --- /dev/null +++ b/vendor/openapi-test-suite-3-1/tests/schema/fail/invalid_schema_types.yaml @@ -0,0 +1,12 @@ +openapi: 3.1.1 + +# this example shows invalid types for the schemaObject + +info: + title: API + version: 1.0.0 +components: + schemas: + invalid_null: null + invalid_number: 0 + invalid_array: [] diff --git a/vendor/openapi-test-suite-3-1/tests/schema/fail/link-object-no-body.yaml b/vendor/openapi-test-suite-3-1/tests/schema/fail/link-object-no-body.yaml new file mode 100644 index 000000000..2c327694f --- /dev/null +++ b/vendor/openapi-test-suite-3-1/tests/schema/fail/link-object-no-body.yaml @@ -0,0 +1,11 @@ +openapi: 3.1.0 +info: + title: API + version: 1.0.0 +components: + links: + Link-Object-with-body-property: + operationId: getThing + description: The "server" property was misspelled as "body" in a previous schema iteration, now fixed + body: + url: https://things.example.com diff --git a/vendor/openapi-test-suite-3-1/tests/schema/fail/no_containers.yaml b/vendor/openapi-test-suite-3-1/tests/schema/fail/no_containers.yaml new file mode 100644 index 000000000..c158bcb2b --- /dev/null +++ b/vendor/openapi-test-suite-3-1/tests/schema/fail/no_containers.yaml @@ -0,0 +1,7 @@ +openapi: 3.1.0 + +# this example should fail as there are no paths, components or webhooks containers (at least one of which must be present) + +info: + title: API + version: 1.0.0 diff --git a/vendor/openapi-test-suite-3-1/tests/schema/fail/parameter-object-cookie-form-allowReserved.yaml b/vendor/openapi-test-suite-3-1/tests/schema/fail/parameter-object-cookie-form-allowReserved.yaml new file mode 100644 index 000000000..ee88d3ac7 --- /dev/null +++ b/vendor/openapi-test-suite-3-1/tests/schema/fail/parameter-object-cookie-form-allowReserved.yaml @@ -0,0 +1,18 @@ +openapi: 3.1.0 +info: + title: allowReserved only permitted with in and style values that percent-encode + version: 1.0.0 +components: + parameters: + style_form: + name: my_form_cookie + in: cookie + # default style is form, therefore allowReserved is allowed + allowReserved: true + schema: {} + style_cookie: + name: my_cookie_cookie + in: cookie + style: cookie + # no percent decoding for style=cookie, therefore allowReserved is not allowed + schema: {} diff --git a/vendor/openapi-test-suite-3-1/tests/schema/fail/parameter-object-header-allowReserved.yaml b/vendor/openapi-test-suite-3-1/tests/schema/fail/parameter-object-header-allowReserved.yaml new file mode 100644 index 000000000..4b956a080 --- /dev/null +++ b/vendor/openapi-test-suite-3-1/tests/schema/fail/parameter-object-header-allowReserved.yaml @@ -0,0 +1,11 @@ +openapi: 3.1.0 +info: + title: allowReserved only permitted with in and style values that percent-encode + version: 1.0.0 +components: + parameters: + header: + name: my-header + in: header + allowReserved: false + schema: {} diff --git a/vendor/openapi-test-suite-3-1/tests/schema/fail/parameter-object-path-allowReserved.yaml b/vendor/openapi-test-suite-3-1/tests/schema/fail/parameter-object-path-allowReserved.yaml new file mode 100644 index 000000000..09a5f94a8 --- /dev/null +++ b/vendor/openapi-test-suite-3-1/tests/schema/fail/parameter-object-path-allowReserved.yaml @@ -0,0 +1,11 @@ +openapi: 3.1.0 +info: + title: "allowReserved only permitted with in: query" + version: 1.0.0 +components: + parameters: + path: + name: my-path + in: path + allowReserved: false + schema: {} diff --git a/vendor/openapi-test-suite-3-1/tests/schema/fail/server_enum_empty.yaml b/vendor/openapi-test-suite-3-1/tests/schema/fail/server_enum_empty.yaml new file mode 100644 index 000000000..eacb0f94c --- /dev/null +++ b/vendor/openapi-test-suite-3-1/tests/schema/fail/server_enum_empty.yaml @@ -0,0 +1,15 @@ +openapi: 3.1.0 + +# this example should fail as the server variable enum is empty +# (it also does not contain the default value, but this is not checked by the schema) + +info: + title: API + version: 1.0.0 +servers: + - url: https://example.com/{var} + variables: + var: + enum: [] + default: a +components: {} diff --git a/vendor/openapi-test-suite-3-1/tests/schema/fail/servers.yaml b/vendor/openapi-test-suite-3-1/tests/schema/fail/servers.yaml new file mode 100644 index 000000000..1470fe1ec --- /dev/null +++ b/vendor/openapi-test-suite-3-1/tests/schema/fail/servers.yaml @@ -0,0 +1,11 @@ +openapi: 3.1.0 + +# this example should fail, as servers must be an array, not an object + +info: + title: API + version: 1.0.0 +paths: {} +servers: + url: /v1 + description: Run locally. diff --git a/vendor/openapi-test-suite-3-1/tests/schema/fail/unknown_container.yaml b/vendor/openapi-test-suite-3-1/tests/schema/fail/unknown_container.yaml new file mode 100644 index 000000000..7f31e8605 --- /dev/null +++ b/vendor/openapi-test-suite-3-1/tests/schema/fail/unknown_container.yaml @@ -0,0 +1,8 @@ +openapi: 3.1.0 + +# this example should fail as overlays is not a valid top-level object/keyword + +info: + title: API + version: 1.0.0 +overlays: {} diff --git a/vendor/openapi-test-suite-3-1/tests/schema/pass/callback-object-examples.yaml b/vendor/openapi-test-suite-3-1/tests/schema/pass/callback-object-examples.yaml new file mode 100644 index 000000000..795627eb8 --- /dev/null +++ b/vendor/openapi-test-suite-3-1/tests/schema/pass/callback-object-examples.yaml @@ -0,0 +1,32 @@ +openapi: 3.1.0 +info: + title: API + version: 1.0.0 +components: + callbacks: + myCallback: + '{$request.query.queryUrl}': + post: + requestBody: + description: Callback payload + content: + application/json: + schema: + $ref: '#/components/schemas/SomePayload' + responses: + '200': + description: callback successfully processed + transactionCallback: + 'http://notificationServer.com?transactionId={$request.body#/id}&email={$request.body#/email}': + post: + requestBody: + description: Callback payload + content: + application/json: + schema: + $ref: '#/components/schemas/SomePayload' + responses: + '200': + description: callback successfully processed + schemas: + SomePayload: {} diff --git a/vendor/openapi-test-suite-3-1/tests/schema/pass/comp_pathitems.yaml b/vendor/openapi-test-suite-3-1/tests/schema/pass/comp_pathitems.yaml new file mode 100644 index 000000000..502ca1fca --- /dev/null +++ b/vendor/openapi-test-suite-3-1/tests/schema/pass/comp_pathitems.yaml @@ -0,0 +1,6 @@ +openapi: 3.1.0 +info: + title: API + version: 1.0.0 +components: + pathItems: {} diff --git a/vendor/openapi-test-suite-3-1/tests/schema/pass/components-object-example.yaml b/vendor/openapi-test-suite-3-1/tests/schema/pass/components-object-example.yaml new file mode 100644 index 000000000..9ef0c1766 --- /dev/null +++ b/vendor/openapi-test-suite-3-1/tests/schema/pass/components-object-example.yaml @@ -0,0 +1,71 @@ +openapi: 3.1.0 +info: + title: API + version: 1.0.0 +components: + schemas: + GeneralError: + type: object + properties: + code: + type: integer + format: int32 + message: + type: string + Category: + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + Tag: + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + parameters: + skipParam: + name: skip + in: query + description: number of items to skip + required: true + schema: + type: integer + format: int32 + limitParam: + name: limit + in: query + description: max records to return + required: true + schema: + type: integer + format: int32 + responses: + NotFound: + description: Entity not found. + IllegalInput: + description: Illegal input for operation. + GeneralError: + description: General Error + content: + application/json: + schema: + $ref: '#/components/schemas/GeneralError' + securitySchemes: + api_key: + type: apiKey + name: api-key + in: header + petstore_auth: + type: oauth2 + flows: + implicit: + authorizationUrl: https://example.org/api/oauth/dialog + scopes: + write:pets: modify pets in your account + read:pets: read your pets \ No newline at end of file diff --git a/vendor/openapi-test-suite-3-1/tests/schema/pass/example-object-examples.yaml b/vendor/openapi-test-suite-3-1/tests/schema/pass/example-object-examples.yaml new file mode 100644 index 000000000..16022e2b4 --- /dev/null +++ b/vendor/openapi-test-suite-3-1/tests/schema/pass/example-object-examples.yaml @@ -0,0 +1,69 @@ +openapi: 3.1.0 +info: + title: API + version: 1.0.0 +components: + examples: + zip-example: {} + confirmation-success: {} + schemas: + SuccessResponse: {} + Address: {} + requestBodies: + with-example: + content: + 'application/json': + schema: + $ref: '#/components/schemas/Address' + examples: + foo: + summary: A foo example + value: + foo: bar + bar: + summary: A bar example + value: + bar: baz + application/xml: + examples: + xmlExample: + summary: This is an example in XML + externalValue: https://example.org/examples/address-example.xml + text/plain: + examples: + textExample: + summary: This is a text example + externalValue: https://foo.bar/examples/address-example.txt + parameters: + with-example: + name: zipCode + in: query + schema: + type: string + format: zip-code + examples: + zip-example: + $ref: '#/components/examples/zip-example' + responses: + '200': + description: your car appointment has been booked + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessResponse' + examples: + confirmation-success: + $ref: '#/components/examples/confirmation-success' + application/x-www-form-urlencoded: + schema: + type: object + properties: + jsonValue: + type: string + encoding: + jsonValue: + contentType: application/json + examples: + jsonFormValue: + description: 'The JSON string "json" as a form value' + value: jsonValue=%22json%22 diff --git a/vendor/openapi-test-suite-3-1/tests/schema/pass/header-object-examples.yaml b/vendor/openapi-test-suite-3-1/tests/schema/pass/header-object-examples.yaml new file mode 100644 index 000000000..389eefc4f --- /dev/null +++ b/vendor/openapi-test-suite-3-1/tests/schema/pass/header-object-examples.yaml @@ -0,0 +1,25 @@ +openapi: 3.1.0 +info: + title: API + version: 1.0.0 +components: + headers: + X-Rate-Limit-Limit: + description: The number of allowed requests in the current period + deprecated: false + schema: + type: integer + ETag: + required: true + content: + text/plain: + schema: + type: string + pattern: ^" + Reference: + $ref: '#/components/headers/ETag' + Style: + schema: + type: array + style: simple + explode: true diff --git a/vendor/openapi-test-suite-3-1/tests/schema/pass/info-object-example.yaml b/vendor/openapi-test-suite-3-1/tests/schema/pass/info-object-example.yaml new file mode 100644 index 000000000..2c32cd9c1 --- /dev/null +++ b/vendor/openapi-test-suite-3-1/tests/schema/pass/info-object-example.yaml @@ -0,0 +1,19 @@ +# including External Documentation Object Example +openapi: 3.1.0 +info: + title: Example Pet Store App + summary: A pet store manager. + description: This is an example server for a pet store. + termsOfService: https://example.com/terms/ + contact: + name: API Support + url: https://www.example.com/support + email: support@example.com + license: + name: Apache 2.0 + url: https://www.apache.org/licenses/LICENSE-2.0.html + version: 1.0.1 +externalDocs: + description: Find more info here + url: https://example.com +components: {} diff --git a/vendor/openapi-test-suite-3-1/tests/schema/pass/info_summary.yaml b/vendor/openapi-test-suite-3-1/tests/schema/pass/info_summary.yaml new file mode 100644 index 000000000..30d224afc --- /dev/null +++ b/vendor/openapi-test-suite-3-1/tests/schema/pass/info_summary.yaml @@ -0,0 +1,6 @@ +openapi: 3.1.0 +info: + title: API + summary: My lovely API + version: 1.0.0 +components: {} diff --git a/vendor/openapi-test-suite-3-1/tests/schema/pass/json_schema_dialect.yaml b/vendor/openapi-test-suite-3-1/tests/schema/pass/json_schema_dialect.yaml new file mode 100644 index 000000000..ae0ed863b --- /dev/null +++ b/vendor/openapi-test-suite-3-1/tests/schema/pass/json_schema_dialect.yaml @@ -0,0 +1,15 @@ +openapi: 3.1.0 +info: + summary: Testing jsonSchemaDialect + title: My API + version: 1.0.0 + license: + name: Apache 2.0 + identifier: Apache-2.0 +jsonSchemaDialect: https://spec.openapis.org/oas/3.1/dialect/WORK-IN-PROGRESS +components: + schemas: + WithDollarSchema: + $id: "locked-metaschema" + $schema: https://spec.openapis.org/oas/3.1/dialect/WORK-IN-PROGRESS +paths: {} diff --git a/vendor/openapi-test-suite-3-1/tests/schema/pass/license_identifier.yaml b/vendor/openapi-test-suite-3-1/tests/schema/pass/license_identifier.yaml new file mode 100644 index 000000000..fbdba5efb --- /dev/null +++ b/vendor/openapi-test-suite-3-1/tests/schema/pass/license_identifier.yaml @@ -0,0 +1,9 @@ +openapi: 3.1.0 +info: + title: API + summary: My lovely API + version: 1.0.0 + license: + name: Apache + identifier: Apache-2.0 +components: {} diff --git a/vendor/openapi-test-suite-3-1/tests/schema/pass/link-object-examples.yaml b/vendor/openapi-test-suite-3-1/tests/schema/pass/link-object-examples.yaml new file mode 100644 index 000000000..b7d8e737a --- /dev/null +++ b/vendor/openapi-test-suite-3-1/tests/schema/pass/link-object-examples.yaml @@ -0,0 +1,66 @@ +openapi: 3.1.0 +info: + title: API + version: 1.0.0 +paths: + /users/{id}: + parameters: + - name: id + in: path + required: true + description: the user identifier, as userId + schema: + type: string + get: + responses: + '200': + description: the user being returned + content: + application/json: + schema: + type: object + properties: + uuid: # the unique user id + type: string + format: uuid + links: + address: + # the target link operationId + operationId: getUserAddress + parameters: + # get the `id` field from the request path parameter named `id` + userid: $request.path.id + address2: + operationId: getUserAddressByUUID + parameters: + # get the `uuid` field from the `uuid` field in the response body + userUuid: $response.body#/uuid + UserRepositories: + # returns array of '#/components/schemas/repository' + operationRef: '#/paths/~12.0~1repositories~1%7Busername%7D/get' + parameters: + username: $response.body#/username + UserRepositories2: + # returns array of '#/components/schemas/repository' + operationRef: https://na2.gigantic-server.com/#/paths/~12.0~1repositories~1%7Busername%7D/get + parameters: + username: $response.body#/username + withBody: + operationId: queryUserWithBody + requestBody: + userId: $request.path.id + # the path item of the linked operation + /users/{userid}/address: + parameters: + - name: userid + in: path + required: true + description: the user identifier, as userId + schema: + type: string + # linked operation + get: + operationId: getUserAddress + responses: + '200': + description: the user's address \ No newline at end of file diff --git a/vendor/openapi-test-suite-3-1/tests/schema/pass/media-type-examples.yaml b/vendor/openapi-test-suite-3-1/tests/schema/pass/media-type-examples.yaml new file mode 100644 index 000000000..924555aff --- /dev/null +++ b/vendor/openapi-test-suite-3-1/tests/schema/pass/media-type-examples.yaml @@ -0,0 +1,123 @@ +# including Encoding Object examples +openapi: 3.1.0 +info: + title: API + version: 1.0.0 +components: + examples: + frog-example: {} + schemas: + Address: {} + Pet: {} +paths: + /something: + put: + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + examples: + cat: + summary: An example of a cat + value: + name: Fluffy + petType: Cat + color: White + gender: male + breed: Persian + dog: + summary: An example of a dog with a cat's name + value: + name: Puma + petType: Dog + color: Black + gender: Female + breed: Mixed + frog: + $ref: '#/components/examples/frog-example' + application/xml: + schema: + type: object + properties: + foo: + type: string + xml: + namespace: https://example.com + prefix: example + name: Foo + bar: + type: array + items: + type: number + xml: + wrapped: true + attr: + type: string + xml: + attribute: true + application/x-www-form-urlencoded: + schema: + type: object + properties: + id: + type: string + format: uuid + address: + # complex types are stringified to support RFC 1866 + type: object + properties: {} + icon: + # The default with "contentEncoding" is application/octet-stream, + # so we need to set image media type(s) in the Encoding Object. + type: string + contentEncoding: base64url + encoding: + icon: + contentType: image/png, image/jpeg + multipart/form-data: + schema: + type: object + properties: + id: + # default is `text/plain` + type: string + format: uuid + addresses: + # default based on the `items` subschema would be + # `application/json`, but we want these address objects + # serialized as `application/xml` instead + description: addresses in XML format + type: array + items: + $ref: '#/components/schemas/Address' + profileImage: + # default is application/octet-stream, but we can declare + # a more specific image type or types + type: string + format: binary + forCoverage: + type: string + forCoverage2: + type: string + encoding: + addresses: + # require XML Content-Type in utf-8 encoding + # This is applied to each address part corresponding + # to each address in he array + contentType: application/xml; charset=utf-8 + profileImage: + # only accept png or jpeg + contentType: image/png, image/jpeg + headers: + X-Rate-Limit-Limit: + description: The number of allowed requests in the current period + schema: + type: integer + forCoverage: + style: form + explode: false + allowReserved: true + forCoverage2: + style: spaceDelimited + explode: true diff --git a/vendor/openapi-test-suite-3-1/tests/schema/pass/mega.yaml b/vendor/openapi-test-suite-3-1/tests/schema/pass/mega.yaml new file mode 100644 index 000000000..dafae3991 --- /dev/null +++ b/vendor/openapi-test-suite-3-1/tests/schema/pass/mega.yaml @@ -0,0 +1,61 @@ +openapi: 3.1.0 +info: + summary: My API's summary + title: My API + version: 1.0.0 + license: + name: Apache 2.0 + identifier: Apache-2.0 +paths: + /: + get: + parameters: [] + /{pathTest}: {} +webhooks: + myWebhook: + $ref: '#/components/pathItems/myPathItem' + description: Overriding description +components: + securitySchemes: + mtls: + type: mutualTLS + schemas: + Foo: + type: object + properties: + type: + const: foo + pathItems: + myPathItem: + post: + requestBody: + required: true + content: + 'application/json': + schema: + externalDocs: + description: More docs! + url: https://example.com/elsewhere.html + type: object + properties: + type: + type: string + int: + type: integer + exclusiveMaximum: 100 + exclusiveMinimum: 0 + none: + type: 'null' + arr: + type: array + $comment: Array without items keyword + either: + type: ['string','null'] + discriminator: + propertyName: type + mapping: + foo: Foo + x-extension: true + anyOf: + - $ref: "#/components/schemas/Foo" + myArbitraryKeyword: true diff --git a/vendor/openapi-test-suite-3-1/tests/schema/pass/minimal_comp.yaml b/vendor/openapi-test-suite-3-1/tests/schema/pass/minimal_comp.yaml new file mode 100644 index 000000000..4553689ab --- /dev/null +++ b/vendor/openapi-test-suite-3-1/tests/schema/pass/minimal_comp.yaml @@ -0,0 +1,5 @@ +openapi: 3.1.0 +info: + title: API + version: 1.0.0 +components: {} diff --git a/vendor/openapi-test-suite-3-1/tests/schema/pass/minimal_hooks.yaml b/vendor/openapi-test-suite-3-1/tests/schema/pass/minimal_hooks.yaml new file mode 100644 index 000000000..e67b2889d --- /dev/null +++ b/vendor/openapi-test-suite-3-1/tests/schema/pass/minimal_hooks.yaml @@ -0,0 +1,5 @@ +openapi: 3.1.0 +info: + title: API + version: 1.0.0 +webhooks: {} diff --git a/vendor/openapi-test-suite-3-1/tests/schema/pass/minimal_paths.yaml b/vendor/openapi-test-suite-3-1/tests/schema/pass/minimal_paths.yaml new file mode 100644 index 000000000..016e86796 --- /dev/null +++ b/vendor/openapi-test-suite-3-1/tests/schema/pass/minimal_paths.yaml @@ -0,0 +1,5 @@ +openapi: 3.1.0 +info: + title: API + version: 1.0.0 +paths: {} diff --git a/vendor/openapi-test-suite-3-1/tests/schema/pass/non-oauth-scopes.yaml b/vendor/openapi-test-suite-3-1/tests/schema/pass/non-oauth-scopes.yaml new file mode 100644 index 000000000..e757452f3 --- /dev/null +++ b/vendor/openapi-test-suite-3-1/tests/schema/pass/non-oauth-scopes.yaml @@ -0,0 +1,19 @@ +openapi: 3.1.0 +info: + title: Non-oAuth Scopes example + version: 1.0.0 +paths: + /users: + get: + security: + - bearerAuth: + - 'read:users' + - 'public' +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: jwt + description: 'note: non-oauth scopes are not defined at the securityScheme level' + diff --git a/vendor/openapi-test-suite-3-1/tests/schema/pass/operation-object-example.yaml b/vendor/openapi-test-suite-3-1/tests/schema/pass/operation-object-example.yaml new file mode 100644 index 000000000..9a5c76d0a --- /dev/null +++ b/vendor/openapi-test-suite-3-1/tests/schema/pass/operation-object-example.yaml @@ -0,0 +1,47 @@ +openapi: 3.1.0 +info: + title: API + version: 1.0.0 +paths: + /pets/{id}: + put: + tags: + - pet + summary: Updates a pet in the store with form data + operationId: updatePetWithForm + parameters: + - name: petId + in: path + description: ID of pet that needs to be updated + required: true + schema: + type: string + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + name: + description: Updated name of the pet + type: string + status: + description: Updated status of the pet + type: string + required: + - status + responses: + '200': + description: Pet updated. + content: + application/json: {} + application/xml: {} + '405': + description: Method Not Allowed + content: + application/json: {} + application/xml: {} + security: + - petstore_auth: + - write:pets + - read:pets \ No newline at end of file diff --git a/vendor/openapi-test-suite-3-1/tests/schema/pass/parameter-object-examples.yaml b/vendor/openapi-test-suite-3-1/tests/schema/pass/parameter-object-examples.yaml new file mode 100644 index 000000000..30c7b2905 --- /dev/null +++ b/vendor/openapi-test-suite-3-1/tests/schema/pass/parameter-object-examples.yaml @@ -0,0 +1,61 @@ +openapi: 3.1.0 +info: + title: API + version: 1.0.0 +paths: + /user/{username}: + parameters: + - name: token + in: header + description: token to be passed as a header + required: true + explode: false + schema: + type: array + items: + type: integer + format: int64 + style: simple + - name: usernames + in: path + description: usernames to fetch + required: true + explode: false + schema: + type: array + - name: id + in: query + description: IDs of the object to fetch + required: false + schema: + type: array + items: + type: string + style: form + explode: true + - in: query + name: freeForm + schema: + type: object + additionalProperties: + type: integer + style: form + - in: query + name: coordinates + content: + application/json: + schema: + type: object + required: + - lat + - long + properties: + lat: + type: number + long: + type: number + - in: cookie + name: my_cookie1 + style: form + explode: false + schema: {} diff --git a/vendor/openapi-test-suite-3-1/tests/schema/pass/parameter-object-query-allowReserved.yaml b/vendor/openapi-test-suite-3-1/tests/schema/pass/parameter-object-query-allowReserved.yaml new file mode 100644 index 000000000..26f89ab78 --- /dev/null +++ b/vendor/openapi-test-suite-3-1/tests/schema/pass/parameter-object-query-allowReserved.yaml @@ -0,0 +1,11 @@ +openapi: 3.1.0 +info: + title: "allowReserved only permitted with in: query" + version: 1.0.0 +components: + parameters: + my_query: + name: my_query + in: query + allowReserved: true + schema: {} diff --git a/vendor/openapi-test-suite-3-1/tests/schema/pass/path-item-object-example.yaml b/vendor/openapi-test-suite-3-1/tests/schema/pass/path-item-object-example.yaml new file mode 100644 index 000000000..777f4e5d0 --- /dev/null +++ b/vendor/openapi-test-suite-3-1/tests/schema/pass/path-item-object-example.yaml @@ -0,0 +1,39 @@ +openapi: 3.1.0 +info: + title: API + version: 1.0.0 +components: + schemas: + Pet: {} + ErrorModel: {} +paths: + /pets/{id}: + get: + description: Returns pets based on ID + summary: Find pets by ID + operationId: getPetsById + responses: + '200': + description: pet response + content: + '*/*': + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + default: + description: error payload + content: + text/html: + schema: + $ref: '#/components/schemas/ErrorModel' + parameters: + - name: id + in: path + description: ID of pet to use + required: true + schema: + type: array + items: + type: string + style: simple diff --git a/vendor/openapi-test-suite-3-1/tests/schema/pass/path_item_servers_parameters.yaml b/vendor/openapi-test-suite-3-1/tests/schema/pass/path_item_servers_parameters.yaml new file mode 100644 index 000000000..25b63395f --- /dev/null +++ b/vendor/openapi-test-suite-3-1/tests/schema/pass/path_item_servers_parameters.yaml @@ -0,0 +1,114 @@ +openapi: 3.1.0 +info: + title: API + version: 1.0.0 +paths: + /things: + summary: Lots of things + servers: + - url: https://things.example.com + get: + summary: Get a list of things + externalDocs: + description: Find more info here + url: https://example.com + parameters: + - $ref: '#/components/parameters/biscuit' + summary: The maximum number of things to return + description: The maximum number of things to return + responses: + default: + description: A list of things + servers: + - url: https://things.example.com + post: + deprecated: false + requestBody: + $ref: '#/components/requestBodies/ThingRequestBody' + responses: + '201': + $ref: '#/components/responses/ThingResponse' + callbacks: + myCallback: + '{$request.query.queryUrl}': + post: + requestBody: + description: Callback payload + content: + application/json: + schema: + $ref: '#/components/schemas/SomePayload' + responses: + '200': + description: callback successfully processed + transactionCallback: + $ref: '#/components/callbacks/transactionCallback' + patch: {} + delete: {} + head: {} + options: {} + trace: {} +components: + callbacks: + transactionCallback: + 'http://notificationServer.com?transactionId={$request.body#/id}&email={$request.body#/email}': + post: + requestBody: + description: Callback payload + content: + application/json: + schema: + $ref: '#/components/schemas/SomePayload' + responses: + '200': + description: callback successfully processed + examples: + ThingExample: + summary: A thing + description: A thing + value: + id: 1 + name: Thing + links: + ThingLink: + description: A link to a thing + operationId: getThing + parameters: + thingId: '$response.body#/id' + server: + url: https://things.example.com + ThingyLink: + $ref: '#/components/links/ThingLink' + parameters: + limit: + name: limit + in: query + required: false + allowEmptyValue: false + allowReserved: false + deprecated: true + description: The maximum number of list items to return + schema: + type: integer + minimum: 0 + biscuit: + name: biscuit + in: cookie + style: form + schema: + type: string + requestBodies: + ThingRequestBody: + content: + application/json: + schema: + type: object + responses: + ThingResponse: + description: A thing + content: + application/json: + schema: + type: object + schemas: + SomePayload: {} diff --git a/vendor/openapi-test-suite-3-1/tests/schema/pass/path_no_response.yaml b/vendor/openapi-test-suite-3-1/tests/schema/pass/path_no_response.yaml new file mode 100644 index 000000000..334608f11 --- /dev/null +++ b/vendor/openapi-test-suite-3-1/tests/schema/pass/path_no_response.yaml @@ -0,0 +1,7 @@ +openapi: 3.1.0 +info: + title: API + version: 1.0.0 +paths: + /: + get: {} diff --git a/vendor/openapi-test-suite-3-1/tests/schema/pass/path_var_empty_pathitem.yaml b/vendor/openapi-test-suite-3-1/tests/schema/pass/path_var_empty_pathitem.yaml new file mode 100644 index 000000000..ba92742f1 --- /dev/null +++ b/vendor/openapi-test-suite-3-1/tests/schema/pass/path_var_empty_pathitem.yaml @@ -0,0 +1,6 @@ +openapi: 3.1.0 +info: + title: API + version: 1.0.0 +paths: + /{var}: {} diff --git a/vendor/openapi-test-suite-3-1/tests/schema/pass/paths-object-example.yaml b/vendor/openapi-test-suite-3-1/tests/schema/pass/paths-object-example.yaml new file mode 100644 index 000000000..82fb2e4f5 --- /dev/null +++ b/vendor/openapi-test-suite-3-1/tests/schema/pass/paths-object-example.yaml @@ -0,0 +1,20 @@ +openapi: 3.1.0 +info: + title: API + version: 1.0.0 +components: + schemas: + pet: {} +paths: + /pets: + get: + description: Returns all pets from the system that the user has access to + responses: + '200': + description: A list of pets. + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/pet' diff --git a/vendor/openapi-test-suite-3-1/tests/schema/pass/request-body-examples.yaml b/vendor/openapi-test-suite-3-1/tests/schema/pass/request-body-examples.yaml new file mode 100644 index 000000000..4a11c440d --- /dev/null +++ b/vendor/openapi-test-suite-3-1/tests/schema/pass/request-body-examples.yaml @@ -0,0 +1,37 @@ +openapi: 3.1.0 +info: + title: API + version: 1.0.0 +components: + schemas: + User: {} +paths: + /something: + put: + requestBody: + description: user to add to the system + content: + application/json: + schema: + $ref: '#/components/schemas/User' + examples: + user: + summary: User example + externalValue: https://foo.bar/examples/user-example.json + application/xml: + schema: + $ref: '#/components/schemas/User' + examples: + user: + summary: User example in XML + externalValue: https://foo.bar/examples/user-example.xml + text/plain: + examples: + user: + summary: User example in plain text + externalValue: https://foo.bar/examples/user-example.txt + '*/*': + examples: + user: + summary: User example in other format + externalValue: https://foo.bar/examples/user-example.whatever diff --git a/vendor/openapi-test-suite-3-1/tests/schema/pass/response-object-examples.yaml b/vendor/openapi-test-suite-3-1/tests/schema/pass/response-object-examples.yaml new file mode 100644 index 000000000..f2348d7cc --- /dev/null +++ b/vendor/openapi-test-suite-3-1/tests/schema/pass/response-object-examples.yaml @@ -0,0 +1,44 @@ +openapi: 3.1.0 +info: + title: API + version: 1.0.0 +components: + responses: + complex-object-array: + description: A complex object array response + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/VeryComplexType' + simple-string: + description: A simple string response + content: + text/plain: + schema: + type: string + plain-text-with-headers: + description: A simple string response + content: + text/plain: + schema: + type: string + example: 'whoa!' + headers: + X-Rate-Limit-Limit: + description: The number of allowed requests in the current period + schema: + type: integer + X-Rate-Limit-Remaining: + description: The number of remaining requests in the current period + schema: + type: integer + X-Rate-Limit-Reset: + description: The number of seconds left in the current period + schema: + type: integer + no-return-value: + description: object created + schemas: + VeryComplexType: {} diff --git a/vendor/openapi-test-suite-3-1/tests/schema/pass/schema-object-deprecated-example-keyword.yaml b/vendor/openapi-test-suite-3-1/tests/schema/pass/schema-object-deprecated-example-keyword.yaml new file mode 100644 index 000000000..92fcbb41a --- /dev/null +++ b/vendor/openapi-test-suite-3-1/tests/schema/pass/schema-object-deprecated-example-keyword.yaml @@ -0,0 +1,17 @@ +openapi: 3.1.0 +info: + title: API + version: 1.0.0 +paths: + /user: + parameters: + - in: query + name: example + schema: + # Allow an arbitrary JSON object to keep + # the example simple + type: object + # DEPRECATED: don't use example keyword inside Schema Object + example: + numbers: [1, 2] + flag: null diff --git a/vendor/openapi-test-suite-3-1/tests/schema/pass/schema.yaml b/vendor/openapi-test-suite-3-1/tests/schema/pass/schema.yaml new file mode 100644 index 000000000..e192529a6 --- /dev/null +++ b/vendor/openapi-test-suite-3-1/tests/schema/pass/schema.yaml @@ -0,0 +1,55 @@ +openapi: 3.1.0 +info: + title: API + version: 1.0.0 +paths: {} +components: + schemas: + model: + type: object + properties: + one: + description: type array + type: + - integer + - string + two: + description: type 'null' + type: "null" + three: + description: type array including 'null' + type: + - string + - "null" + four: + description: array with no items + type: array + five: + description: singular example + type: string + examples: + - exampleValue + six: + description: exclusiveMinimum true + exclusiveMinimum: 10 + seven: + description: exclusiveMinimum false + minimum: 10 + eight: + description: exclusiveMaximum true + exclusiveMaximum: 20 + nine: + description: exclusiveMaximum false + maximum: 20 + ten: + description: nullable string + type: + - string + - "null" + eleven: + description: x-nullable string + type: + - string + - "null" + twelve: + description: file/binary diff --git a/vendor/openapi-test-suite-3-1/tests/schema/pass/security-scheme-object-examples.yaml b/vendor/openapi-test-suite-3-1/tests/schema/pass/security-scheme-object-examples.yaml new file mode 100644 index 000000000..0b0e9900a --- /dev/null +++ b/vendor/openapi-test-suite-3-1/tests/schema/pass/security-scheme-object-examples.yaml @@ -0,0 +1,59 @@ +openapi: 3.1.0 +info: + title: API + version: 1.0.0 +security: + - basic: [] + - apiKey: [] + - JWT-bearer: [] + - mutualTLS: [] + - OAuth2: + - write:pets + - read:pets +components: + securitySchemes: + basic: + type: http + scheme: basic + apiKey: + type: apiKey + name: api-key + in: header + JWT-bearer: + type: http + scheme: bearer + bearerFormat: JWT + mutualTLS: + type: mutualTLS + description: Cert must be signed by example.com CA + OAuth2: + type: oauth2 + flows: + implicit: + authorizationUrl: https://example.com/api/oauth/dialog + scopes: + write:pets: modify pets in your account + read:pets: read your pets + refreshUrl: https://example.com/api/oauth/refresh + authorizationCode: + authorizationUrl: https://example.com/api/oauth/dialog + refreshUrl: https://example.com/api/oauth/refresh + tokenUrl: https://example.com/api/oauth/token + scopes: + write:pets: modify pets in your account + read:pets: read your pets + password: + tokenUrl: https://example.com/api/oauth/token + scopes: + read:pets: read your pets + refreshUrl: https://example.com/api/oauth/refresh + clientCredentials: + tokenUrl: https://example.com/api/oauth/token + scopes: + read:pets: read your pets + refreshUrl: https://example.com/api/oauth/refresh + OpenIdConnect: + type: openIdConnect + openIdConnectUrl: https://example.com/api/oauth/openid + external: + $ref: 'https://example.com/api/openapi.json#/components/externalDocs/ThingExternalDocs' \ No newline at end of file diff --git a/vendor/openapi-test-suite-3-1/tests/schema/pass/servers.yaml b/vendor/openapi-test-suite-3-1/tests/schema/pass/servers.yaml new file mode 100644 index 000000000..ca68a88b9 --- /dev/null +++ b/vendor/openapi-test-suite-3-1/tests/schema/pass/servers.yaml @@ -0,0 +1,25 @@ +openapi: 3.1.0 +info: + title: API + version: 1.0.0 +paths: {} +servers: + - url: /v1 + description: Run locally. + - url: https://production.com/v1 + description: Run on production server. + - url: https://{username}.gigantic-server.com:{port}/{basePath} + description: The production API server + variables: + username: + # note! no enum here means it is an open value + default: demo + description: A user-specific subdomain. Use `demo` for a free sandbox environment. + port: + enum: + - '8443' + - '443' + default: '8443' + basePath: + # open meaning there is the opportunity to use special base paths as assigned by the provider, default is `v2` + default: v2 \ No newline at end of file diff --git a/vendor/openapi-test-suite-3-1/tests/schema/pass/specification-extensions.yaml b/vendor/openapi-test-suite-3-1/tests/schema/pass/specification-extensions.yaml new file mode 100644 index 000000000..3d63a5f14 --- /dev/null +++ b/vendor/openapi-test-suite-3-1/tests/schema/pass/specification-extensions.yaml @@ -0,0 +1,6 @@ +openapi: 3.1.0 +info: + title: API + version: 1.0.0 +paths: {} +x-tensions: specification extensions are prefixed with `x-` diff --git a/vendor/openapi-test-suite-3-1/tests/schema/pass/style-defaults.yaml b/vendor/openapi-test-suite-3-1/tests/schema/pass/style-defaults.yaml new file mode 100644 index 000000000..c3942bdc5 --- /dev/null +++ b/vendor/openapi-test-suite-3-1/tests/schema/pass/style-defaults.yaml @@ -0,0 +1,102 @@ +openapi: 3.1.0 +info: + title: various permutations of parameter objects, with non-required values left to their defaults + version: 1.0.0 +components: + parameters: + encoding_object_defaults: + name: encoding_object_defaults + in: path + content: + encoding_object_defaults: # media type name + encoding: + no_styles: # property name + x-comment: "style, explode and allowReserved are not present, so contentType is used; no defaults expected as default contentType cannot be determined by the schema" + style_form: + x-comment: "expecting defaults: explode=true, allowReserved=false" + style: form + style_spaceDelimited: + x-comment: "expecting defaults: explode=false, allowReserved=false" + style: spaceDelimited + explode: + x-comment: "expecting defaults: style=form, allowReserved=false" + explode: false + allowReserved: + x-comment: "expecting default: style=form, explode=true" + allowReserved: true + path_media_type: + x-comment: "expecting defaults: deprecated=false" + name: path_media-type + in: path + required: true + content: + text/*: + schema: {} + path_simple: + x-comment: "expecting defaults: deprecated=false, style=simple, explode=false, allowReserved=false" + name: path_simple + in: path + required: true + schema: {} + path_matrix: + x-comment: "expecting defaults: deprecated=false, explode=false, allowReserved=false" + name: path_matrix + in: path + required: true + style: matrix + schema: {} + path_label: + x-comment: "expecting defaults: deprecated=false, explode=false, allowReserved=false" + name: path_label + in: path + required: true + style: label + schema: {} + query_media_type: + x-comment: "expecting defaults: required=false, deprecated=false, allowEmptyValue=false" + name: query_media_type + in: query + content: + text/*: + schema: {} + query_form: + x-comment: "expecting defaults: required=false, deprecated=false, allowEmptyValue=false, style=form, explode=true, allowReserved=false" + name: query_form + in: query + schema: {} + query_spaceDelimited: + x-comment: "expecting defaults: required=false, deprecated=false, allowEmptyValue=false, explode=false, allowReserved=false" + name: query_spaceDelimited + in: query + style: spaceDelimited + schema: {} + query_pipeDelimited: + x-comment: "expecting defaults: required=false, deprecated=false, allowEmptyValue=false, explode=false, allowReserved=false" + name: query_pipeDelimited + in: query + style: pipeDelimited + schema: {} + query_deepObject: + x-comment: "expecting defaults: required=false, deprecated=false, allowEmptyValue=false, allowReserved=false" + name: query_deepObject + in: query + style: deepObject + schema: {} + header: + x-comment: "expecting defaults: deprecated=false, style=simple, explode=false, allowReserved=false" + name: header + in: path + required: true + schema: {} + cookie_media_type: + x-comment: "expecting defaults: required=false, deprecated=false" + name: cookie_media_type + in: cookie + content: + text/*: + schema: {} + cookie_form: + x-comment: "expecting defaults: required=false, deprecated=false, style=form, explode=true, allowReserved=false" + name: cookie_form + in: cookie + schema: {} diff --git a/vendor/openapi-test-suite-3-1/tests/schema/pass/tag-object-example.yaml b/vendor/openapi-test-suite-3-1/tests/schema/pass/tag-object-example.yaml new file mode 100644 index 000000000..aba0c7d7d --- /dev/null +++ b/vendor/openapi-test-suite-3-1/tests/schema/pass/tag-object-example.yaml @@ -0,0 +1,15 @@ +openapi: 3.1.0 +info: + title: API + version: 1.0.0 +paths: {} +tags: + + - name: pet + description: Pets operations + + - name: external + description: Operations available to external consumers + externalDocs: + description: Find more info here + url: https://example.com diff --git a/vendor/openapi-test-suite-3-1/tests/schema/pass/valid_schema_types.yaml b/vendor/openapi-test-suite-3-1/tests/schema/pass/valid_schema_types.yaml new file mode 100644 index 000000000..c2459ed37 --- /dev/null +++ b/vendor/openapi-test-suite-3-1/tests/schema/pass/valid_schema_types.yaml @@ -0,0 +1,13 @@ +openapi: 3.1.1 + +# this example shows that top-level schemaObjects MAY be booleans + +info: + title: API + version: 1.0.0 +components: + schemas: + anything_boolean: true + nothing_boolean: false + anything_object: {} + nothing_object: { not: {} } diff --git a/vendor/openapi-test-suite-3-1/tests/schema/pass/webhook-example.yaml b/vendor/openapi-test-suite-3-1/tests/schema/pass/webhook-example.yaml new file mode 100644 index 000000000..44fc73aaa --- /dev/null +++ b/vendor/openapi-test-suite-3-1/tests/schema/pass/webhook-example.yaml @@ -0,0 +1,34 @@ +openapi: 3.1.0 +info: + title: Webhook Example + version: 1.0.0 +# Since OAS 3.1.0 the paths element isn't necessary. Now a valid OpenAPI Document can describe only paths, webhooks, or even only reusable components +webhooks: + # Each webhook needs a name + newPet: + # This is a Path Item Object, the only difference is that the request is initiated by the API provider + post: + requestBody: + description: Information about a new pet in the system + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + responses: + "200": + description: Return a 200 status to indicate that the data was received successfully + +components: + schemas: + Pet: + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string