From d60b2f3c86fdd6dbb7dae6f6672d4c6373199192 Mon Sep 17 00:00:00 2001 From: Ethan Date: Tue, 2 Jun 2026 14:37:57 -0700 Subject: [PATCH 01/66] OpenAPI::Document::V3Methods #server don't return nil --- lib/scorpio/openapi/document.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/scorpio/openapi/document.rb b/lib/scorpio/openapi/document.rb index 4ecb7b06..cddbd90c 100644 --- a/lib/scorpio/openapi/document.rb +++ b/lib/scorpio/openapi/document.rb @@ -135,7 +135,7 @@ def server if servers.respond_to?(:to_ary) && servers.size == 1 servers.first else - nil + raise(ConfigError, "configuration required: server (see https://rubydoc.info/gems/scorpio/Scorpio/Request/Configurables#server-instance_method )") end end attr_writer :server_variables @@ -146,9 +146,7 @@ def server_variables attr_writer :base_url def base_url(scheme: nil, server: self.server, server_variables: self.server_variables) return @base_url if instance_variable_defined?(:@base_url) - if server - server.expanded_url(server_variables) - end + server.expanded_url(server_variables) end attr_accessor(:request_media_type) From e66b6a10c13079fc9dd88294b23c842c4545e899 Mon Sep 17 00:00:00 2001 From: Ethan Date: Wed, 3 Jun 2026 01:08:33 -0700 Subject: [PATCH 02/66] OpenAPI Document / Operation / Request #base_url don't return nil; raise when configuration required --- lib/scorpio/openapi/document.rb | 2 ++ lib/scorpio/request.rb | 3 --- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/scorpio/openapi/document.rb b/lib/scorpio/openapi/document.rb index cddbd90c..7e08fe26 100644 --- a/lib/scorpio/openapi/document.rb +++ b/lib/scorpio/openapi/document.rb @@ -189,6 +189,8 @@ def base_url(scheme: self.scheme, server: nil, server_variables: nil) host: host, path: basePath, ).freeze + else + raise(ConfigError, "configuration required: base_url (see https://rubydoc.info/gems/scorpio/Scorpio/Request/Configurables#base_url-instance_method )") end end diff --git a/lib/scorpio/request.rb b/lib/scorpio/request.rb index 4963e7b4..66f04fd2 100644 --- a/lib/scorpio/request.rb +++ b/lib/scorpio/request.rb @@ -213,9 +213,6 @@ def path # @return [Addressable::URI] def url return @url if instance_variable_defined?(:@url) - unless base_url - raise(ArgumentError, "no base_url has been specified for request") - end # we do not use Addressable::URI#join as the paths should just be concatenated, not resolved. # we use File.join just to deal with consecutive slashes. Addressable::URI.parse(File.join(base_url, path)).freeze From afe5d3c5d904eb12d8a602089c2f07817e7efa91 Mon Sep 17 00:00:00 2001 From: Ethan Date: Mon, 13 Apr 2026 13:00:19 -0700 Subject: [PATCH 03/66] OpenAPI::Document::Configurables #base_url setter and stub getter common between V2/V3 --- lib/scorpio/openapi/document.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/scorpio/openapi/document.rb b/lib/scorpio/openapi/document.rb index 7e08fe26..bc45d683 100644 --- a/lib/scorpio/openapi/document.rb +++ b/lib/scorpio/openapi/document.rb @@ -49,6 +49,11 @@ def openapi_document end module Configurables + attr_writer(:base_url) + def base_url(scheme: self.scheme, server: self.server, server_variables: self.server_variables) + fail(NotImplementedError) # overridden + end + attr_writer :request_headers def request_headers return @request_headers if instance_variable_defined?(:@request_headers) @@ -143,7 +148,7 @@ def server_variables return @server_variables if instance_variable_defined?(:@server_variables) {}.freeze end - attr_writer :base_url + def base_url(scheme: nil, server: self.server, server_variables: self.server_variables) return @base_url if instance_variable_defined?(:@base_url) server.expanded_url(server_variables) @@ -177,7 +182,6 @@ def server_variables nil end - attr_writer :base_url # the base url to which paths are appended. # by default this looks at the openapi document's schemes, picking https or http first. # it looks at the openapi_document's host and basePath. From ea38dacb54d040cd1f147f95190ece3445d8899a Mon Sep 17 00:00:00 2001 From: Ethan Date: Mon, 13 Apr 2026 12:44:09 -0700 Subject: [PATCH 04/66] doc Request::Configurables #base_url --- lib/scorpio/openapi/document.rb | 3 --- lib/scorpio/request.rb | 6 ++++++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/scorpio/openapi/document.rb b/lib/scorpio/openapi/document.rb index bc45d683..bed7bcdb 100644 --- a/lib/scorpio/openapi/document.rb +++ b/lib/scorpio/openapi/document.rb @@ -182,9 +182,6 @@ def server_variables nil end - # the base url to which paths are appended. - # by default this looks at the openapi document's schemes, picking https or http first. - # it looks at the openapi_document's host and basePath. def base_url(scheme: self.scheme, server: nil, server_variables: nil) return @base_url if instance_variable_defined?(:@base_url) if host && scheme diff --git a/lib/scorpio/request.rb b/lib/scorpio/request.rb index 66f04fd2..b7e02a5f 100644 --- a/lib/scorpio/request.rb +++ b/lib/scorpio/request.rb @@ -57,6 +57,12 @@ def server_variables end attr_writer :base_url + # The base URL to which API operation paths are appended. + # + # For OpenAPI v3, constructed from {Request::Configurables#server} and {Request::Configurables#server_variables}. + # + # For OpenAPI v2, constructed from the document's `host`, `basePath`, and `schemes` or configurable {Request::Configurables#scheme}. + # @return [Addressable::URI] def base_url return @base_url if instance_variable_defined?(:@base_url) operation.base_url(scheme: scheme, server: server, server_variables: server_variables) From 5d94623a857ceb228522b5bf14817518ee10b569 Mon Sep 17 00:00:00 2001 From: Ethan Date: Fri, 5 Jun 2026 14:16:12 -0700 Subject: [PATCH 05/66] Request mv #url to Configurables --- lib/scorpio/request.rb | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/scorpio/request.rb b/lib/scorpio/request.rb index b7e02a5f..595258ae 100644 --- a/lib/scorpio/request.rb +++ b/lib/scorpio/request.rb @@ -72,6 +72,14 @@ def base_url def url=(url) @url = JSI::Util.uri(url) end + # the full URL for this request + # @return [Addressable::URI] + def url + return @url if instance_variable_defined?(:@url) + # we do not use Addressable::URI#join as the paths should just be concatenated, not resolved. + # we use File.join just to deal with consecutive slashes. + Addressable::URI.parse(File.join(base_url, path)).freeze + end attr_writer :body def body @@ -215,15 +223,6 @@ def path path.freeze end - # the full URL for this request - # @return [Addressable::URI] - def url - return @url if instance_variable_defined?(:@url) - # we do not use Addressable::URI#join as the paths should just be concatenated, not resolved. - # we use File.join just to deal with consecutive slashes. - Addressable::URI.parse(File.join(base_url, path)).freeze - end - # the value of the request Content-Type header # @return [::Ur::ContentType] def content_type_header From 988b3b496b7cf8d05f1bdb9592b6b530bf826f91 Mon Sep 17 00:00:00 2001 From: Ethan Date: Fri, 5 Jun 2026 14:14:39 -0700 Subject: [PATCH 06/66] Request::Configurables attr_writer url, for doc consistency and because transforming given url is not needed --- lib/scorpio/request.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/scorpio/request.rb b/lib/scorpio/request.rb index 595258ae..a5c8cb80 100644 --- a/lib/scorpio/request.rb +++ b/lib/scorpio/request.rb @@ -69,9 +69,7 @@ def base_url end # overriding url will cause all of path_params, query_params, querystring, scheme, server, server_variables, and base_url to be ignored - def url=(url) - @url = JSI::Util.uri(url) - end + attr_writer(:url) # the full URL for this request # @return [Addressable::URI] def url From 14e4d49ebbbbfed904502aec815561b4e1b4fca6 Mon Sep 17 00:00:00 2001 From: Ethan Date: Fri, 5 Jun 2026 14:16:26 -0700 Subject: [PATCH 07/66] doc Request::Configurables #url --- lib/scorpio/request.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/scorpio/request.rb b/lib/scorpio/request.rb index a5c8cb80..5c1c7475 100644 --- a/lib/scorpio/request.rb +++ b/lib/scorpio/request.rb @@ -68,9 +68,11 @@ def base_url operation.base_url(scheme: scheme, server: server, server_variables: server_variables) end - # overriding url will cause all of path_params, query_params, querystring, scheme, server, server_variables, and base_url to be ignored attr_writer(:url) - # the full URL for this request + # The full request URL. + # + # This is constructed using configured {#path_params}, {#query_params}, {#querystring}, {#scheme}, {#server}, {#server_variables}, and {#base_url}. + # Overriding `url` will cause those to be ignored. # @return [Addressable::URI] def url return @url if instance_variable_defined?(:@url) From 5663bb1f61d4a708c6a320453826112eb3926ee3 Mon Sep 17 00:00:00 2001 From: Ethan Date: Fri, 5 Jun 2026 13:54:44 -0700 Subject: [PATCH 08/66] Request::Configurables attr_writer body_object, for doc consistency --- lib/scorpio/request.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/scorpio/request.rb b/lib/scorpio/request.rb index 5c1c7475..e52b6eee 100644 --- a/lib/scorpio/request.rb +++ b/lib/scorpio/request.rb @@ -104,7 +104,11 @@ def body end end - attr_accessor :body_object + attr_writer(:body_object) + def body_object + return @body_object if instance_variable_defined?(:@body_object) + nil + end attr_writer :headers def headers From 38e957a596cf143cb7429dccf3c79d3fdf145b50 Mon Sep 17 00:00:00 2001 From: Ethan Date: Mon, 1 Jun 2026 13:14:52 -0700 Subject: [PATCH 09/66] rm OpenAPI::Document::V3Methods::Configurables#scheme, OpenAPI::Document::V2Methods::Configurables#server, OpenAPI::Document::V2Methods::Configurables#server_variables --- lib/scorpio/openapi/document.rb | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/lib/scorpio/openapi/document.rb b/lib/scorpio/openapi/document.rb index bed7bcdb..5b764b60 100644 --- a/lib/scorpio/openapi/document.rb +++ b/lib/scorpio/openapi/document.rb @@ -131,9 +131,6 @@ def title module Document module V3Methods module Configurables - def scheme - nil - end attr_writer :server def server return @server if instance_variable_defined?(:@server) @@ -175,13 +172,6 @@ def scheme end end - def server - nil - end - def server_variables - nil - end - def base_url(scheme: self.scheme, server: nil, server_variables: nil) return @base_url if instance_variable_defined?(:@base_url) if host && scheme From a8a3468db3815eed985b4f5e8b994796ef147bd9 Mon Sep 17 00:00:00 2001 From: Ethan Date: Tue, 2 Jun 2026 14:26:24 -0700 Subject: [PATCH 10/66] rm OpenAPI::Document::V*::Configurables, OpenAPI::Operation::V*::Configurables. consolidate writers into Document::Configurables, Operation::Configurables; readers/getters defined on Configurables and overridden on OpenAPI::Document::V*Methods / OpenAPI::Operation::V*Methods --- lib/scorpio/openapi/document.rb | 35 ++++++++++++------ lib/scorpio/openapi/operation.rb | 62 +++++++++++++------------------- 2 files changed, 49 insertions(+), 48 deletions(-) diff --git a/lib/scorpio/openapi/document.rb b/lib/scorpio/openapi/document.rb index 5b764b60..0d49e2e6 100644 --- a/lib/scorpio/openapi/document.rb +++ b/lib/scorpio/openapi/document.rb @@ -49,11 +49,31 @@ def openapi_document end module Configurables + attr_writer(:scheme) + def scheme + nil # overridden for v2 + end + + attr_writer(:server) + def server + nil # overridden for v3 + end + + attr_writer(:server_variables) + def server_variables + nil # overridden for v3 + end + attr_writer(:base_url) def base_url(scheme: self.scheme, server: self.server, server_variables: self.server_variables) fail(NotImplementedError) # overridden end + attr_writer(:request_media_type) + def request_media_type + fail(NotImplementedError) # overridden + end + attr_writer :request_headers def request_headers return @request_headers if instance_variable_defined?(:@request_headers) @@ -130,8 +150,6 @@ def title module Document module V3Methods - module Configurables - attr_writer :server def server return @server if instance_variable_defined?(:@server) if servers.respond_to?(:to_ary) && servers.size == 1 @@ -140,7 +158,7 @@ def server raise(ConfigError, "configuration required: server (see https://rubydoc.info/gems/scorpio/Scorpio/Request/Configurables#server-instance_method )") end end - attr_writer :server_variables + def server_variables return @server_variables if instance_variable_defined?(:@server_variables) {}.freeze @@ -151,17 +169,14 @@ def base_url(scheme: nil, server: self.server, server_variables: self.server_var server.expanded_url(server_variables) end - attr_accessor(:request_media_type) - end - include Configurables + attr_reader(:request_media_type) + include(OpenAPI::Document) end end module Document module V2Methods - module Configurables - attr_writer :scheme def scheme return @scheme if instance_variable_defined?(:@scheme) if schemes.nil? @@ -185,7 +200,6 @@ def base_url(scheme: self.scheme, server: nil, server_variables: nil) end end - attr_writer :request_media_type def request_media_type return @request_media_type if instance_variable_defined?(:@request_media_type) if consumes.respond_to?(:to_ary) @@ -194,8 +208,7 @@ def request_media_type nil end end - end - include Configurables + include(OpenAPI::Document) end end diff --git a/lib/scorpio/openapi/operation.rb b/lib/scorpio/openapi/operation.rb index c36bdaa7..1197b255 100644 --- a/lib/scorpio/openapi/operation.rb +++ b/lib/scorpio/openapi/operation.rb @@ -7,12 +7,35 @@ module OpenAPI # Scorpio::OpenAPI::Operation is a module common to V2 and V3 operations. module Operation module Configurables + attr_writer(:scheme) + def scheme + return @scheme if instance_variable_defined?(:@scheme) + openapi_document.scheme + end + + attr_writer(:server) + def server + return @server if instance_variable_defined?(:@server) + openapi_document.server + end + + attr_writer(:server_variables) + def server_variables + return @server_variables if instance_variable_defined?(:@server_variables) + openapi_document.server_variables + end + attr_writer :base_url def base_url(scheme: self.scheme, server: self.server, server_variables: self.server_variables) return @base_url if instance_variable_defined?(:@base_url) openapi_document.base_url(scheme: scheme, server: server, server_variables: server_variables) end + attr_writer(:request_media_type) + def request_media_type + fail(NotImplementedError) # overridden + end + attr_writer :request_headers def request_headers return @request_headers if instance_variable_defined?(:@request_headers) @@ -251,25 +274,6 @@ def jsi_object_group_text module Operation module V3Methods - module Configurables - def scheme - # not applicable; for OpenAPI v3, scheme is specified by servers. - nil - end - - attr_writer :server - def server - return @server if instance_variable_defined?(:@server) - openapi_document.server - end - - attr_writer :server_variables - def server_variables - return @server_variables if instance_variable_defined?(:@server_variables) - openapi_document.server_variables - end - - attr_writer :request_media_type def request_media_type return @request_media_type if instance_variable_defined?(:@request_media_type) if requestBody && requestBody['content'] @@ -278,8 +282,7 @@ def request_media_type openapi_document.request_media_type end end - end - include Configurables + include(OpenAPI::Operation) # @return [JSI::Schema] @@ -338,20 +341,6 @@ def response_schemas module Operation module V2Methods - module Configurables - attr_writer :scheme - def scheme - return @scheme if instance_variable_defined?(:@scheme) - openapi_document.scheme - end - def server - nil - end - def server_variables - nil - end - - attr_writer :request_media_type def request_media_type return @request_media_type if instance_variable_defined?(:@request_media_type) if key?('consumes') @@ -360,8 +349,7 @@ def request_media_type openapi_document.request_media_type end end - end - include Configurables + include(OpenAPI::Operation) # the body parameter From 3ba9f22a1dcef57e8b5419fa6116bee7f4a8df77 Mon Sep 17 00:00:00 2001 From: Ethan Date: Fri, 5 Jun 2026 14:04:21 -0700 Subject: [PATCH 11/66] doc OpenAPI::Document::Configurables, OpenAPI::Operation::Configurables refs to Request::Configurables --- lib/scorpio/openapi/document.rb | 12 ++++++++++++ lib/scorpio/openapi/operation.rb | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/lib/scorpio/openapi/document.rb b/lib/scorpio/openapi/document.rb index 0d49e2e6..742fad30 100644 --- a/lib/scorpio/openapi/document.rb +++ b/lib/scorpio/openapi/document.rb @@ -50,67 +50,79 @@ def openapi_document module Configurables attr_writer(:scheme) + # see {Request::Configurables#scheme} def scheme nil # overridden for v2 end attr_writer(:server) + # see {Request::Configurables#server} def server nil # overridden for v3 end attr_writer(:server_variables) + # see {Request::Configurables#server_variables} def server_variables nil # overridden for v3 end attr_writer(:base_url) + # see {Request::Configurables#base_url} def base_url(scheme: self.scheme, server: self.server, server_variables: self.server_variables) fail(NotImplementedError) # overridden end attr_writer(:request_media_type) + # see {Request::Configurables#media_type} def request_media_type fail(NotImplementedError) # overridden end attr_writer :request_headers + # see {Request::Configurables#headers} def request_headers return @request_headers if instance_variable_defined?(:@request_headers) {}.freeze end attr_writer :user_agent + # see {Request::Configurables#user_agent} def user_agent return @user_agent if instance_variable_defined?(:@user_agent) Request::DEFAULT_USER_AGENT end attr_writer(:accept) + # see {Request::Configurables#accept} def accept return @accept if instance_variable_defined?(:@accept) nil end attr_writer(:authorization) + # see {Request::Configurables#authorization} def authorization return @authorization if instance_variable_defined?(:@authorization) nil end attr_writer :faraday_builder + # see {Request::Configurables#faraday_builder} def faraday_builder return @faraday_builder if instance_variable_defined?(:@faraday_builder) nil end attr_writer :faraday_adapter + # see {Request::Configurables#faraday_adapter} def faraday_adapter return @faraday_adapter if instance_variable_defined?(:@faraday_adapter) [Faraday.default_adapter].freeze end attr_writer :logger + # see {Request::Configurables#logger} def logger return @logger if instance_variable_defined?(:@logger) (Object.const_defined?(:Rails) && ::Rails.respond_to?(:logger) ? ::Rails.logger : nil) diff --git a/lib/scorpio/openapi/operation.rb b/lib/scorpio/openapi/operation.rb index 1197b255..a317e2fa 100644 --- a/lib/scorpio/openapi/operation.rb +++ b/lib/scorpio/openapi/operation.rb @@ -8,71 +8,83 @@ module OpenAPI module Operation module Configurables attr_writer(:scheme) + # see {Request::Configurables#scheme} def scheme return @scheme if instance_variable_defined?(:@scheme) openapi_document.scheme end attr_writer(:server) + # see {Request::Configurables#server} def server return @server if instance_variable_defined?(:@server) openapi_document.server end attr_writer(:server_variables) + # see {Request::Configurables#server_variables} def server_variables return @server_variables if instance_variable_defined?(:@server_variables) openapi_document.server_variables end attr_writer :base_url + # see {Request::Configurables#base_url} def base_url(scheme: self.scheme, server: self.server, server_variables: self.server_variables) return @base_url if instance_variable_defined?(:@base_url) openapi_document.base_url(scheme: scheme, server: server, server_variables: server_variables) end attr_writer(:request_media_type) + # see {Request::Configurables#media_type} def request_media_type fail(NotImplementedError) # overridden end attr_writer :request_headers + # see {Request::Configurables#headers} def request_headers return @request_headers if instance_variable_defined?(:@request_headers) openapi_document.request_headers end attr_writer :user_agent + # see {Request::Configurables#user_agent} def user_agent return @user_agent if instance_variable_defined?(:@user_agent) openapi_document.user_agent end attr_writer(:accept) + # see {Request::Configurables#accept} def accept return @accept if instance_variable_defined?(:@accept) openapi_document.accept end attr_writer(:authorization) + # see {Request::Configurables#authorization} def authorization return @authorization if instance_variable_defined?(:@authorization) openapi_document.authorization end attr_writer :faraday_builder + # see {Request::Configurables#faraday_builder} def faraday_builder return @faraday_builder if instance_variable_defined?(:@faraday_builder) openapi_document.faraday_builder end attr_writer :faraday_adapter + # see {Request::Configurables#faraday_adapter} def faraday_adapter return @faraday_adapter if instance_variable_defined?(:@faraday_adapter) openapi_document.faraday_adapter end attr_writer :logger + # see {Request::Configurables#logger} def logger return @logger if instance_variable_defined?(:@logger) openapi_document.logger From bb535a37895910d6622c3b1ee58d8bbe20091288 Mon Sep 17 00:00:00 2001 From: Ethan Date: Fri, 5 Jun 2026 14:09:39 -0700 Subject: [PATCH 12/66] doc OpenAPI::Document, OpenAPI::Operation @private methods overriding Configurables methods: #server #server_variables #scheme #base_url #request_media_type --- lib/scorpio/openapi/document.rb | 7 +++++++ lib/scorpio/openapi/operation.rb | 2 ++ 2 files changed, 9 insertions(+) diff --git a/lib/scorpio/openapi/document.rb b/lib/scorpio/openapi/document.rb index 742fad30..c694bb79 100644 --- a/lib/scorpio/openapi/document.rb +++ b/lib/scorpio/openapi/document.rb @@ -162,6 +162,7 @@ def title module Document module V3Methods + # @private (doc on Configurables) def server return @server if instance_variable_defined?(:@server) if servers.respond_to?(:to_ary) && servers.size == 1 @@ -171,16 +172,19 @@ def server end end + # @private (doc on Configurables) def server_variables return @server_variables if instance_variable_defined?(:@server_variables) {}.freeze end + # @private (doc on Configurables) def base_url(scheme: nil, server: self.server, server_variables: self.server_variables) return @base_url if instance_variable_defined?(:@base_url) server.expanded_url(server_variables) end + # @private (doc on Configurables) attr_reader(:request_media_type) include(OpenAPI::Document) @@ -189,6 +193,7 @@ def base_url(scheme: nil, server: self.server, server_variables: self.server_var module Document module V2Methods + # @private (doc on Configurables) def scheme return @scheme if instance_variable_defined?(:@scheme) if schemes.nil? @@ -199,6 +204,7 @@ def scheme end end + # @private (doc on Configurables) def base_url(scheme: self.scheme, server: nil, server_variables: nil) return @base_url if instance_variable_defined?(:@base_url) if host && scheme @@ -212,6 +218,7 @@ def base_url(scheme: self.scheme, server: nil, server_variables: nil) end end + # @private (doc on Configurables) def request_media_type return @request_media_type if instance_variable_defined?(:@request_media_type) if consumes.respond_to?(:to_ary) diff --git a/lib/scorpio/openapi/operation.rb b/lib/scorpio/openapi/operation.rb index a317e2fa..0fbe9971 100644 --- a/lib/scorpio/openapi/operation.rb +++ b/lib/scorpio/openapi/operation.rb @@ -286,6 +286,7 @@ def jsi_object_group_text module Operation module V3Methods + # @private (doc on Configurables) def request_media_type return @request_media_type if instance_variable_defined?(:@request_media_type) if requestBody && requestBody['content'] @@ -353,6 +354,7 @@ def response_schemas module Operation module V2Methods + # @private (doc on Configurables) def request_media_type return @request_media_type if instance_variable_defined?(:@request_media_type) if key?('consumes') From f658d58ffb8a0c75706a42759c35a2b5699462e5 Mon Sep 17 00:00:00 2001 From: Ethan Date: Mon, 13 Apr 2026 13:00:19 -0700 Subject: [PATCH 13/66] doc Request::Configurables #scheme #server #server_variables types --- lib/scorpio/request.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/scorpio/request.rb b/lib/scorpio/request.rb index e52b6eee..c039351f 100644 --- a/lib/scorpio/request.rb +++ b/lib/scorpio/request.rb @@ -39,18 +39,21 @@ def query_params end attr_writer :scheme + # @return [#to_str, nil] def scheme return @scheme if instance_variable_defined?(:@scheme) operation.scheme end attr_writer :server + # @return [OpenAPI::Server, nil] def server return @server if instance_variable_defined?(:@server) operation.server end attr_writer :server_variables + # @return [#to_hash, nil] def server_variables return @server_variables if instance_variable_defined?(:@server_variables) operation.server_variables From e45b67f3e0c7eb176e970f4f18af8510a36c16a9 Mon Sep 17 00:00:00 2001 From: Ethan Date: Tue, 2 Jun 2026 14:26:24 -0700 Subject: [PATCH 14/66] doc Request::Configurables #scheme #server #server_variables description --- lib/scorpio/request.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/scorpio/request.rb b/lib/scorpio/request.rb index c039351f..9e7b39a8 100644 --- a/lib/scorpio/request.rb +++ b/lib/scorpio/request.rb @@ -39,6 +39,7 @@ def query_params end attr_writer :scheme + # HTTP scheme (OpenAPI v2 only) # @return [#to_str, nil] def scheme return @scheme if instance_variable_defined?(:@scheme) @@ -46,6 +47,9 @@ def scheme end attr_writer :server + # API server (OpenAPI v3 only) + # + # If the OpenAPI document's `servers` define _one_ server, defaults to that server. # @return [OpenAPI::Server, nil] def server return @server if instance_variable_defined?(:@server) @@ -53,6 +57,7 @@ def server end attr_writer :server_variables + # API server variables, interpolated into the `url` template of the {#server} object (OpenAPI v3 only) # @return [#to_hash, nil] def server_variables return @server_variables if instance_variable_defined?(:@server_variables) From fb3d7dd1267c7cb2a40babf2a3106d98fa799e31 Mon Sep 17 00:00:00 2001 From: Ethan Date: Fri, 5 Jun 2026 14:11:16 -0700 Subject: [PATCH 15/66] doc Request::Configurables #path_params #query_params #body #body_object #headers #media_type #user_agent #accept #authorization #faraday_builder #faraday_adapter #logger --- lib/scorpio/request.rb | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/lib/scorpio/request.rb b/lib/scorpio/request.rb index 9e7b39a8..2505f53d 100644 --- a/lib/scorpio/request.rb +++ b/lib/scorpio/request.rb @@ -27,12 +27,16 @@ def self.best_media_type(media_types) module Configurables attr_writer :path_params + # parameters interpolated into the {Request#path_template} + # @return [#to_hash] def path_params return @path_params if instance_variable_defined?(:@path_params) {}.freeze end attr_writer :query_params + # parameters that compose the query of the request URI + # @return [#to_hash, nil] def query_params return @query_params if instance_variable_defined?(:@query_params) nil @@ -90,6 +94,9 @@ def url end attr_writer :body + # The request body. This may be set directly as a string, or may be generated from the + # request {#body_object}. + # @return [#to_str, nil] def body return @body if instance_variable_defined?(:@body) if instance_variable_defined?(:@body_object) @@ -113,54 +120,83 @@ def body end attr_writer(:body_object) + # An object from which the request {#body} is generated, according to the configured + # request {#media_type}. def body_object return @body_object if instance_variable_defined?(:@body_object) nil end attr_writer :headers + # Request headers + # @return [#to_hash<#to_str, #to_str>] def headers return @headers if instance_variable_defined?(:@headers) operation.request_headers end attr_writer :media_type + # Request media type informs the Content-Type request header and + # the generation of request {#body} from {#body_object}. + # @return [#to_str, nil] def media_type return @media_type if instance_variable_defined?(:@media_type) content_type_header ? content_type_header.media_type : operation.request_media_type end attr_writer :user_agent + # `User-Agent` request header + # + # Defaults to {Request::DEFAULT_USER_AGENT}. + # @return [#to_str, nil] def user_agent return @user_agent if instance_variable_defined?(:@user_agent) operation.user_agent end attr_writer(:accept) + # `Accept` request header + # @return [#to_str, nil] def accept return @accept if instance_variable_defined?(:@accept) operation.accept end attr_writer(:authorization) + # `Authorization` request header + # @return [#to_str, nil] def authorization return @authorization if instance_variable_defined?(:@authorization) operation.authorization end attr_writer :faraday_builder + # A proc/callable to set up the Faraday connection the request will use, + # in particular to configure middleware. This is called with the builder + # object Faraday passes to `Faraday.new` + # + # This should not set the adapter; instead set {#faraday_adapter}. + # @return [#call, nil] def faraday_builder return @faraday_builder if instance_variable_defined?(:@faraday_builder) operation.faraday_builder end attr_writer :faraday_adapter + # Faraday connection adapter. The adapter is specified as a Symbol (e.g. `:net_http`) or + # a class (e.g. `Faraday::Adapter::NetHttp`). `faraday_adapter`'s value is splatted as + # arguments to `Faraday::RackBuilder#adapter` so may be an array with additional arguments. + # + # By default uses `Faraday.default_adapter` which defaults to `:net_http`. def faraday_adapter return @faraday_adapter if instance_variable_defined?(:@faraday_adapter) operation.faraday_adapter end attr_writer :logger + # A logger, only used to set metadata of current logger tags if applicable (Scorpio does not do any logging itself). + # + # Defaults to `Rails.logger`, if that is defined. def logger return @logger if instance_variable_defined?(:@logger) operation.logger From 9e3c4e8fee67caab460e3b0bebdfb8a329aa447c Mon Sep 17 00:00:00 2001 From: Ethan Date: Fri, 5 Jun 2026 13:58:46 -0700 Subject: [PATCH 16/66] doc modules OpenAPI::Document::Configurables, OpenAPI::Operation::Configurables, Request::Configurables --- lib/scorpio/openapi/document.rb | 3 +++ lib/scorpio/openapi/operation.rb | 3 +++ lib/scorpio/request.rb | 5 +++++ 3 files changed, 11 insertions(+) diff --git a/lib/scorpio/openapi/document.rb b/lib/scorpio/openapi/document.rb index c694bb79..58ef7be9 100644 --- a/lib/scorpio/openapi/document.rb +++ b/lib/scorpio/openapi/document.rb @@ -48,6 +48,9 @@ def openapi_document end end + # Configurable attributes set on a document are inherited as configurable attributes + # of each operation of the document (via {OpenAPI::Operation::Configurables}) + # and each request from an operation of the document (via {Request::Configurables}). module Configurables attr_writer(:scheme) # see {Request::Configurables#scheme} diff --git a/lib/scorpio/openapi/operation.rb b/lib/scorpio/openapi/operation.rb index 0fbe9971..25b714cf 100644 --- a/lib/scorpio/openapi/operation.rb +++ b/lib/scorpio/openapi/operation.rb @@ -6,6 +6,9 @@ module OpenAPI # # Scorpio::OpenAPI::Operation is a module common to V2 and V3 operations. module Operation + # Configurable attributes set on an operation override configurable attributes inherited from + # its OpenAPI document (via {OpenAPI::Document::Configurables}) and are inherited as + # configurable attributes of each request from the operation (via {Request::Configurables}). module Configurables attr_writer(:scheme) # see {Request::Configurables#scheme} diff --git a/lib/scorpio/request.rb b/lib/scorpio/request.rb index 2505f53d..480f419d 100644 --- a/lib/scorpio/request.rb +++ b/lib/scorpio/request.rb @@ -25,6 +25,11 @@ def self.best_media_type(media_types) end end + # Configurable attributes set per request. + # + # Many of these inherit from configurable attributes of the request's + # {#operation} (via {OpenAPI::Operation::Configurables}) and from the operation's + # OpenAPI document (via {OpenAPI::Document::Configurables}), unless overridden. module Configurables attr_writer :path_params # parameters interpolated into the {Request#path_template} From 6f2994b780295baa9b166c2fbeb980e94b851088 Mon Sep 17 00:00:00 2001 From: Ethan Date: Sat, 6 Jun 2026 01:58:16 -0700 Subject: [PATCH 17/66] pages/Request_Configuration Configurables modules --- pages/Request_Configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/Request_Configuration.md b/pages/Request_Configuration.md index 397ad602..fa08ebe0 100644 --- a/pages/Request_Configuration.md +++ b/pages/Request_Configuration.md @@ -2,7 +2,7 @@ Scorpio aims to offer flexibility in how applications can configure requests. Requests are initiated from an Operation object (e.g. {Scorpio::OpenAPI::Operation#run}), utilizing the Operation and the OpenAPI document that contains it for configuration. Many configurable attributes can be set with varying granularity, on the document (applying to all requests from all operations, unless overridden), on the operation (applying to all requests from that operation), or on the request itself. -Configurable attributes are defined on several modules: for a request, {Scorpio::Request::Configurables}; for an operation, {Scorpio::OpenAPI::Operation::Configurables}; and for a document {Scorpio::OpenAPI::Document::Configurables} and {Scorpio::OpenAPI::Operation::V3Methods::Configurables} or {Scorpio::OpenAPI::Operation::V2Methods::Configurables}. +Configurable attributes are defined and documented on the module {Scorpio::Request::Configurables}. Modules {Scorpio::OpenAPI::Document::Configurables} and {Scorpio::OpenAPI::Operation::Configurables} define configurable attributes of a document and an operation, respectively, and document their relationship to Request configurables. {Scorpio::Request#initialize} and methods that instantiate a request (such as {Scorpio::OpenAPI::Operation#run}) take a keyword hash of configuration, which may include attributes defined on the Configurables module, or parameters defined by the operation (except where parameter names conflict with configurable attributes, or are ambiguous). From 2adb495c8589f32c709f007491dd94c1af7eb679 Mon Sep 17 00:00:00 2001 From: Ethan Date: Sun, 15 Mar 2026 19:16:38 -0700 Subject: [PATCH 18/66] add OpenAPI V3.2 document schema files from source from https://github.com/OAI/OpenAPI-Specification/ as of commit 9f64ea7d8016cd10c794c08f022450b57892abba --- .../spec.openapis.org/oas/3.2/dialect.yaml | 21 + documents/spec.openapis.org/oas/3.2/meta.yaml | 88 ++ .../oas/3.2/schema-base.yaml | 20 + .../spec.openapis.org/oas/3.2/schema.yaml | 1151 +++++++++++++++++ 4 files changed, 1280 insertions(+) create mode 100644 documents/spec.openapis.org/oas/3.2/dialect.yaml create mode 100644 documents/spec.openapis.org/oas/3.2/meta.yaml create mode 100644 documents/spec.openapis.org/oas/3.2/schema-base.yaml create mode 100644 documents/spec.openapis.org/oas/3.2/schema.yaml diff --git a/documents/spec.openapis.org/oas/3.2/dialect.yaml b/documents/spec.openapis.org/oas/3.2/dialect.yaml new file mode 100644 index 00000000..1986c9e8 --- /dev/null +++ b/documents/spec.openapis.org/oas/3.2/dialect.yaml @@ -0,0 +1,21 @@ +$id: https://spec.openapis.org/oas/3.2/dialect/WORK-IN-PROGRESS +$schema: https://json-schema.org/draft/2020-12/schema + +title: OpenAPI 3.2 Schema Object Dialect +description: A JSON Schema dialect describing schemas found in OpenAPI v3.2.x Descriptions + +$dynamicAnchor: meta + +$vocabulary: + https://json-schema.org/draft/2020-12/vocab/applicator: true + https://json-schema.org/draft/2020-12/vocab/content: true + https://json-schema.org/draft/2020-12/vocab/core: true + https://json-schema.org/draft/2020-12/vocab/format-annotation: true + https://json-schema.org/draft/2020-12/vocab/meta-data: true + https://json-schema.org/draft/2020-12/vocab/unevaluated: true + https://json-schema.org/draft/2020-12/vocab/validation: true + https://spec.openapis.org/oas/3.2/vocab/base: false + +allOf: + - $ref: https://json-schema.org/draft/2020-12/schema + - $ref: https://spec.openapis.org/oas/3.2/meta/WORK-IN-PROGRESS diff --git a/documents/spec.openapis.org/oas/3.2/meta.yaml b/documents/spec.openapis.org/oas/3.2/meta.yaml new file mode 100644 index 00000000..bbd40a18 --- /dev/null +++ b/documents/spec.openapis.org/oas/3.2/meta.yaml @@ -0,0 +1,88 @@ +$id: https://spec.openapis.org/oas/3.2/meta/WORK-IN-PROGRESS +$schema: https://json-schema.org/draft/2020-12/schema + +title: OAS Base Vocabulary +description: A JSON Schema Vocabulary used in the OpenAPI JSON Schema Dialect + +$dynamicAnchor: meta + +$vocabulary: + https://spec.openapis.org/oas/3.2/vocab/base: true + +type: + - object + - boolean +properties: + discriminator: + $ref: '#/$defs/discriminator' + example: + deprecated: true + externalDocs: + $ref: '#/$defs/external-docs' + xml: + $ref: '#/$defs/xml' + +$defs: + discriminator: + $ref: '#/$defs/extensible' + properties: + mapping: + additionalProperties: + type: string + type: object + defaultMapping: + type: string + propertyName: + type: string + required: + - propertyName + type: object + unevaluatedProperties: false + + extensible: + patternProperties: + ^x-: true + external-docs: + $ref: '#/$defs/extensible' + properties: + description: + type: string + url: + format: uri-reference + type: string + required: + - url + type: object + unevaluatedProperties: false + + xml: + $ref: '#/$defs/extensible' + properties: + nodeType: + type: string + enum: + - element + - attribute + - text + - cdata + - none + name: + type: string + namespace: + format: iri + type: string + prefix: + type: string + attribute: + type: boolean + deprecated: true + wrapped: + type: boolean + deprecated: true + type: object + dependentSchemas: + nodeType: + properties: + attribute: false + wrapped: false + unevaluatedProperties: false diff --git a/documents/spec.openapis.org/oas/3.2/schema-base.yaml b/documents/spec.openapis.org/oas/3.2/schema-base.yaml new file mode 100644 index 00000000..195ae5ed --- /dev/null +++ b/documents/spec.openapis.org/oas/3.2/schema-base.yaml @@ -0,0 +1,20 @@ +$id: 'https://spec.openapis.org/oas/3.2/schema-base/WORK-IN-PROGRESS' +$schema: 'https://json-schema.org/draft/2020-12/schema' + +description: The description of OpenAPI v3.2.x Documents using the OpenAPI JSON Schema dialect + +$ref: 'https://spec.openapis.org/oas/3.2/schema/WORK-IN-PROGRESS' +properties: + jsonSchemaDialect: + $ref: '#/$defs/dialect' + +$defs: + dialect: + const: 'https://spec.openapis.org/oas/3.2/dialect/WORK-IN-PROGRESS' + + schema: + $dynamicAnchor: meta + $ref: 'https://spec.openapis.org/oas/3.2/dialect/WORK-IN-PROGRESS' + properties: + $schema: + $ref: '#/$defs/dialect' diff --git a/documents/spec.openapis.org/oas/3.2/schema.yaml b/documents/spec.openapis.org/oas/3.2/schema.yaml new file mode 100644 index 00000000..2f40e4b3 --- /dev/null +++ b/documents/spec.openapis.org/oas/3.2/schema.yaml @@ -0,0 +1,1151 @@ +$id: 'https://spec.openapis.org/oas/3.2/schema/WORK-IN-PROGRESS' +$schema: 'https://json-schema.org/draft/2020-12/schema' + +description: The description of OpenAPI v3.2.x Documents without Schema Object validation + +type: object +properties: + openapi: + type: string + pattern: '^3\.2\.\d+(-.+)?$' + $self: + type: string + format: uri-reference + $comment: MUST NOT contain a fragment + pattern: '^[^#]*$' + info: + $ref: '#/$defs/info' + jsonSchemaDialect: + type: string + format: uri-reference + default: 'https://spec.openapis.org/oas/3.2/dialect/WORK-IN-PROGRESS' + servers: + type: array + items: + $ref: '#/$defs/server' + default: + - url: / + paths: + $ref: '#/$defs/paths' + webhooks: + type: object + additionalProperties: + $ref: '#/$defs/path-item' + components: + $ref: '#/$defs/components' + security: + type: array + items: + $ref: '#/$defs/security-requirement' + tags: + type: array + items: + $ref: '#/$defs/tag' + externalDocs: + $ref: '#/$defs/external-documentation' +required: + - openapi + - info +anyOf: + - required: + - paths + - required: + - components + - required: + - webhooks +$ref: '#/$defs/specification-extensions' +unevaluatedProperties: false + +$defs: + info: + $comment: https://spec.openapis.org/oas/v3.2#info-object + type: object + properties: + title: + type: string + summary: + type: string + description: + type: string + termsOfService: + type: string + format: uri-reference + contact: + $ref: '#/$defs/contact' + license: + $ref: '#/$defs/license' + version: + type: string + required: + - title + - version + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + contact: + $comment: https://spec.openapis.org/oas/v3.2#contact-object + type: object + properties: + name: + type: string + url: + type: string + format: uri-reference + email: + type: string + format: email + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + license: + $comment: https://spec.openapis.org/oas/v3.2#license-object + type: object + properties: + name: + type: string + identifier: + type: string + url: + type: string + format: uri-reference + required: + - name + dependentSchemas: + identifier: + not: + required: + - url + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + server: + $comment: https://spec.openapis.org/oas/v3.2#server-object + type: object + properties: + url: + type: string + description: + type: string + name: + type: string + variables: + type: object + additionalProperties: + $ref: '#/$defs/server-variable' + required: + - url + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + server-variable: + $comment: https://spec.openapis.org/oas/v3.2#server-variable-object + type: object + properties: + enum: + type: array + items: + type: string + minItems: 1 + default: + type: string + description: + type: string + required: + - default + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + components: + $comment: https://spec.openapis.org/oas/v3.2#components-object + type: object + properties: + schemas: + type: object + additionalProperties: + $dynamicRef: '#meta' + responses: + type: object + additionalProperties: + $ref: '#/$defs/response-or-reference' + parameters: + type: object + additionalProperties: + $ref: '#/$defs/parameter-or-reference' + examples: + type: object + additionalProperties: + $ref: '#/$defs/example-or-reference' + requestBodies: + type: object + additionalProperties: + $ref: '#/$defs/request-body-or-reference' + headers: + type: object + additionalProperties: + $ref: '#/$defs/header-or-reference' + securitySchemes: + type: object + additionalProperties: + $ref: '#/$defs/security-scheme-or-reference' + links: + type: object + additionalProperties: + $ref: '#/$defs/link-or-reference' + callbacks: + type: object + additionalProperties: + $ref: '#/$defs/callbacks-or-reference' + pathItems: + type: object + additionalProperties: + $ref: '#/$defs/path-item' + mediaTypes: + type: object + additionalProperties: + $ref: '#/$defs/media-type-or-reference' + patternProperties: + '^(?:schemas|responses|parameters|examples|requestBodies|headers|securitySchemes|links|callbacks|pathItems|mediaTypes)$': + $comment: Enumerating all of the property names in the regex above is necessary for unevaluatedProperties to work as expected + propertyNames: + pattern: '^[a-zA-Z0-9._-]+$' + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + paths: + $comment: https://spec.openapis.org/oas/v3.2#paths-object + type: object + patternProperties: + '^/': + $ref: '#/$defs/path-item' + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + path-item: + $comment: https://spec.openapis.org/oas/v3.2#path-item-object + type: object + properties: + $ref: + type: string + format: uri-reference + summary: + type: string + description: + type: string + servers: + type: array + items: + $ref: '#/$defs/server' + parameters: + $ref: '#/$defs/parameters' + additionalOperations: + type: object + additionalProperties: + $ref: '#/$defs/operation' + propertyNames: + $comment: RFC9110 restricts methods to "1*tchar" in ABNF + pattern: "^[a-zA-Z0-9!#$%&'*+.^_`|~-]+$" + not: + enum: + - GET + - PUT + - POST + - DELETE + - OPTIONS + - HEAD + - PATCH + - TRACE + - QUERY + get: + $ref: '#/$defs/operation' + put: + $ref: '#/$defs/operation' + post: + $ref: '#/$defs/operation' + delete: + $ref: '#/$defs/operation' + options: + $ref: '#/$defs/operation' + head: + $ref: '#/$defs/operation' + patch: + $ref: '#/$defs/operation' + trace: + $ref: '#/$defs/operation' + query: + $ref: '#/$defs/operation' + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + operation: + $comment: https://spec.openapis.org/oas/v3.2#operation-object + type: object + properties: + tags: + type: array + items: + type: string + summary: + type: string + description: + type: string + externalDocs: + $ref: '#/$defs/external-documentation' + operationId: + type: string + parameters: + $ref: '#/$defs/parameters' + requestBody: + $ref: '#/$defs/request-body-or-reference' + responses: + $ref: '#/$defs/responses' + callbacks: + type: object + additionalProperties: + $ref: '#/$defs/callbacks-or-reference' + deprecated: + default: false + type: boolean + security: + type: array + items: + $ref: '#/$defs/security-requirement' + servers: + type: array + items: + $ref: '#/$defs/server' + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + external-documentation: + $comment: https://spec.openapis.org/oas/v3.2#external-documentation-object + type: object + properties: + description: + type: string + url: + type: string + format: uri-reference + required: + - url + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + parameters: + type: array + items: + $ref: '#/$defs/parameter-or-reference' + not: + allOf: + - contains: + type: object + properties: + in: + const: query + required: + - in + - contains: + type: object + properties: + in: + const: querystring + required: + - in + contains: + type: object + properties: + in: + const: querystring + required: + - in + minContains: 0 + maxContains: 1 + + parameter: + $comment: https://spec.openapis.org/oas/v3.2#parameter-object + type: object + properties: + name: + type: string + in: + enum: + - query + - querystring + - header + - path + - cookie + description: + type: string + required: + default: false + type: boolean + deprecated: + default: false + type: boolean + schema: + $dynamicRef: '#meta' + content: + $ref: '#/$defs/content' + minProperties: 1 + maxProperties: 1 + required: + - name + - in + oneOf: + - required: + - schema + - required: + - content + allOf: + - $ref: '#/$defs/examples' + - $ref: '#/$defs/specification-extensions' + - if: + properties: + in: + const: query + then: + properties: + allowEmptyValue: + default: false + type: boolean + - if: + properties: + in: + const: querystring + then: + required: + - content + dependentSchemas: + schema: + properties: + style: + type: string + explode: + type: boolean + allOf: + - $ref: '#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-path' + - $ref: '#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-header' + - $ref: '#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-query' + - $ref: '#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-cookie' + + $defs: + styles-for-path: + if: + properties: + in: + const: path + then: + properties: + name: + pattern: '^[^{}]+$' + style: + default: simple + enum: + - matrix + - label + - simple + required: + const: true + explode: + default: false + allowReserved: + type: boolean + default: false + required: + - required + + styles-for-header: + if: + properties: + in: + const: header + then: + properties: + style: + default: simple + const: simple + explode: + default: false + + styles-for-query: + if: + properties: + in: + const: query + then: + properties: + style: + default: form + enum: + - form + - spaceDelimited + - pipeDelimited + - deepObject + allowReserved: + type: boolean + default: false + $ref: '#/$defs/explode-for-form' + + styles-for-cookie: + if: + properties: + in: + const: cookie + then: + properties: + style: + default: form + enum: + - form + - cookie + explode: + default: true + if: + properties: + style: + const: form + then: + properties: + allowReserved: + type: boolean + default: false + + unevaluatedProperties: false + + parameter-or-reference: + if: + type: object + required: + - $ref + then: + $ref: '#/$defs/reference' + else: + $ref: '#/$defs/parameter' + + request-body: + $comment: https://spec.openapis.org/oas/v3.2#request-body-object + type: object + properties: + description: + type: string + content: + $ref: '#/$defs/content' + required: + default: false + type: boolean + required: + - content + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + request-body-or-reference: + if: + type: object + required: + - $ref + then: + $ref: '#/$defs/reference' + else: + $ref: '#/$defs/request-body' + + content: + $comment: https://spec.openapis.org/oas/v3.2#fixed-fields-10 + type: object + additionalProperties: + $ref: '#/$defs/media-type-or-reference' + propertyNames: + format: media-range + + media-type: + $comment: https://spec.openapis.org/oas/v3.2#media-type-object + type: object + properties: + description: + type: string + schema: + $dynamicRef: '#meta' + itemSchema: + $dynamicRef: '#meta' + encoding: + type: object + additionalProperties: + $ref: '#/$defs/encoding' + prefixEncoding: + type: array + items: + $ref: '#/$defs/encoding' + itemEncoding: + $ref: '#/$defs/encoding' + dependentSchemas: + encoding: + properties: + prefixEncoding: false + itemEncoding: false + allOf: + - $ref: '#/$defs/examples' + - $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + media-type-or-reference: + if: + type: object + required: + - $ref + then: + $ref: '#/$defs/reference' + else: + $ref: '#/$defs/media-type' + + encoding: + $comment: https://spec.openapis.org/oas/v3.2#encoding-object + type: object + properties: + contentType: + type: string + format: media-range + headers: + type: object + additionalProperties: + $ref: '#/$defs/header-or-reference' + style: + enum: + - form + - spaceDelimited + - pipeDelimited + - deepObject + explode: + type: boolean + allowReserved: + type: boolean + encoding: + type: object + additionalProperties: + $ref: '#/$defs/encoding' + prefixEncoding: + type: array + items: + $ref: '#/$defs/encoding' + itemEncoding: + $ref: '#/$defs/encoding' + dependentSchemas: + encoding: + properties: + prefixEncoding: false + itemEncoding: false + style: + properties: + allowReserved: + default: false + $ref: '#/$defs/explode-for-form' + explode: + properties: + style: + default: form + allowReserved: + default: false + allowReserved: + properties: + style: + default: form + $ref: '#/$defs/explode-for-form' + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + responses: + $comment: https://spec.openapis.org/oas/v3.2#responses-object + type: object + properties: + default: + $ref: '#/$defs/response-or-reference' + patternProperties: + '^[1-5](?:[0-9]{2}|XX)$': + $ref: '#/$defs/response-or-reference' + minProperties: 1 + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + if: + $comment: either default, or at least one response code property must exist + patternProperties: + '^[1-5](?:[0-9]{2}|XX)$': false + then: + required: [default] + + response: + $comment: https://spec.openapis.org/oas/v3.2#response-object + type: object + properties: + summary: + type: string + description: + type: string + headers: + type: object + additionalProperties: + $ref: '#/$defs/header-or-reference' + content: + $ref: '#/$defs/content' + links: + type: object + additionalProperties: + $ref: '#/$defs/link-or-reference' + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + response-or-reference: + if: + type: object + required: + - $ref + then: + $ref: '#/$defs/reference' + else: + $ref: '#/$defs/response' + + callbacks: + $comment: https://spec.openapis.org/oas/v3.2#callback-object + type: object + $ref: '#/$defs/specification-extensions' + additionalProperties: + $ref: '#/$defs/path-item' + + callbacks-or-reference: + if: + type: object + required: + - $ref + then: + $ref: '#/$defs/reference' + else: + $ref: '#/$defs/callbacks' + + example: + $comment: https://spec.openapis.org/oas/v3.2#example-object + type: object + properties: + summary: + type: string + description: + type: string + dataValue: true + serializedValue: + type: string + value: true + externalValue: + type: string + format: uri-reference + allOf: + - not: + required: + - value + - externalValue + - not: + required: + - value + - dataValue + - not: + required: + - value + - serializedValue + - not: + required: + - serializedValue + - externalValue + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + example-or-reference: + if: + type: object + required: + - $ref + then: + $ref: '#/$defs/reference' + else: + $ref: '#/$defs/example' + + link: + $comment: https://spec.openapis.org/oas/v3.2#link-object + type: object + properties: + operationRef: + type: string + format: uri-reference + operationId: + type: string + parameters: + $ref: '#/$defs/map-of-strings' + requestBody: true + description: + type: string + server: + $ref: '#/$defs/server' + oneOf: + - required: + - operationRef + - required: + - operationId + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + link-or-reference: + if: + type: object + required: + - $ref + then: + $ref: '#/$defs/reference' + else: + $ref: '#/$defs/link' + + header: + $comment: https://spec.openapis.org/oas/v3.2#header-object + type: object + properties: + description: + type: string + required: + default: false + type: boolean + deprecated: + default: false + type: boolean + schema: + $dynamicRef: '#meta' + content: + $ref: '#/$defs/content' + minProperties: 1 + maxProperties: 1 + oneOf: + - required: + - schema + - required: + - content + dependentSchemas: + schema: + properties: + style: + default: simple + const: simple + explode: + default: false + type: boolean + allOf: + - $ref: '#/$defs/examples' + - $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + header-or-reference: + if: + type: object + required: + - $ref + then: + $ref: '#/$defs/reference' + else: + $ref: '#/$defs/header' + + tag: + $comment: https://spec.openapis.org/oas/v3.2#tag-object + type: object + properties: + name: + type: string + summary: + type: string + description: + type: string + externalDocs: + $ref: '#/$defs/external-documentation' + parent: + type: string + kind: + type: string + required: + - name + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + reference: + $comment: https://spec.openapis.org/oas/v3.2#reference-object + type: object + properties: + $ref: + type: string + format: uri-reference + summary: + type: string + description: + type: string + + schema: + $comment: https://spec.openapis.org/oas/v3.2#schema-object + $dynamicAnchor: meta + type: + - object + - boolean + + security-scheme: + $comment: https://spec.openapis.org/oas/v3.2#security-scheme-object + type: object + properties: + type: + enum: + - apiKey + - http + - mutualTLS + - oauth2 + - openIdConnect + description: + type: string + deprecated: + default: false + type: boolean + required: + - type + allOf: + - $ref: '#/$defs/specification-extensions' + - $ref: '#/$defs/security-scheme/$defs/type-apikey' + - $ref: '#/$defs/security-scheme/$defs/type-http' + - $ref: '#/$defs/security-scheme/$defs/type-http-bearer' + - $ref: '#/$defs/security-scheme/$defs/type-oauth2' + - $ref: '#/$defs/security-scheme/$defs/type-oidc' + unevaluatedProperties: false + + $defs: + type-apikey: + if: + properties: + type: + const: apiKey + then: + properties: + name: + type: string + in: + enum: + - query + - header + - cookie + required: + - name + - in + + type-http: + if: + properties: + type: + const: http + then: + properties: + scheme: + type: string + required: + - scheme + + type-http-bearer: + if: + properties: + type: + const: http + scheme: + type: string + pattern: ^[Bb][Ee][Aa][Rr][Ee][Rr]$ + required: + - type + - scheme + then: + properties: + bearerFormat: + type: string + + type-oauth2: + if: + properties: + type: + const: oauth2 + then: + properties: + flows: + $ref: '#/$defs/oauth-flows' + oauth2MetadataUrl: + type: string + format: uri-reference + required: + - flows + + type-oidc: + if: + properties: + type: + const: openIdConnect + then: + properties: + openIdConnectUrl: + type: string + format: uri-reference + required: + - openIdConnectUrl + + security-scheme-or-reference: + if: + type: object + required: + - $ref + then: + $ref: '#/$defs/reference' + else: + $ref: '#/$defs/security-scheme' + + oauth-flows: + type: object + properties: + implicit: + $ref: '#/$defs/oauth-flows/$defs/implicit' + password: + $ref: '#/$defs/oauth-flows/$defs/password' + clientCredentials: + $ref: '#/$defs/oauth-flows/$defs/client-credentials' + authorizationCode: + $ref: '#/$defs/oauth-flows/$defs/authorization-code' + deviceAuthorization: + $ref: '#/$defs/oauth-flows/$defs/device-authorization' + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + $defs: + implicit: + type: object + properties: + authorizationUrl: + type: string + format: uri-reference + refreshUrl: + type: string + format: uri-reference + scopes: + $ref: '#/$defs/map-of-strings' + required: + - authorizationUrl + - scopes + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + password: + type: object + properties: + tokenUrl: + type: string + format: uri-reference + refreshUrl: + type: string + format: uri-reference + scopes: + $ref: '#/$defs/map-of-strings' + required: + - tokenUrl + - scopes + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + client-credentials: + type: object + properties: + tokenUrl: + type: string + format: uri-reference + refreshUrl: + type: string + format: uri-reference + scopes: + $ref: '#/$defs/map-of-strings' + required: + - tokenUrl + - scopes + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + authorization-code: + type: object + properties: + authorizationUrl: + type: string + format: uri-reference + tokenUrl: + type: string + format: uri-reference + refreshUrl: + type: string + format: uri-reference + scopes: + $ref: '#/$defs/map-of-strings' + required: + - authorizationUrl + - tokenUrl + - scopes + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + device-authorization: + type: object + properties: + deviceAuthorizationUrl: + type: string + format: uri-reference + tokenUrl: + type: string + format: uri-reference + refreshUrl: + type: string + format: uri-reference + scopes: + $ref: '#/$defs/map-of-strings' + required: + - deviceAuthorizationUrl + - tokenUrl + - scopes + $ref: '#/$defs/specification-extensions' + unevaluatedProperties: false + + security-requirement: + $comment: https://spec.openapis.org/oas/v3.2#security-requirement-object + type: object + additionalProperties: + type: array + items: + type: string + + specification-extensions: + $comment: https://spec.openapis.org/oas/v3.2#specification-extensions + patternProperties: + '^x-': true + + examples: + properties: + example: true + examples: + type: object + additionalProperties: + $ref: '#/$defs/example-or-reference' + not: + required: + - example + - examples + + map-of-strings: + type: object + additionalProperties: + type: string + + explode-for-form: + $comment: for encoding objects, and query and cookie parameters, style=form is the default + if: + properties: + style: + const: form + then: + properties: + explode: + default: true + else: + properties: + explode: + default: false From 8ba54362206f97ab43095018cda3b2841ca7f6c8 Mon Sep 17 00:00:00 2001 From: Ethan Date: Sun, 15 Mar 2026 19:45:13 -0700 Subject: [PATCH 19/66] update OpenAPI V3.2 document schema files from https://spec.openapis.org/oas/ latest published versions with date ids this is not exactly the same as the schemas published there: the previously-copied schema from the git repo includes 9f64ea7d8016cd10c794c08f022450b57892abba, readding `required: [propertyName]` to meta.yaml, which does not seem to be in any schema published on the site. --- documents/spec.openapis.org/oas/3.2/dialect.yaml | 4 ++-- documents/spec.openapis.org/oas/3.2/meta.yaml | 2 +- documents/spec.openapis.org/oas/3.2/schema-base.yaml | 8 ++++---- documents/spec.openapis.org/oas/3.2/schema.yaml | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/documents/spec.openapis.org/oas/3.2/dialect.yaml b/documents/spec.openapis.org/oas/3.2/dialect.yaml index 1986c9e8..e7015c54 100644 --- a/documents/spec.openapis.org/oas/3.2/dialect.yaml +++ b/documents/spec.openapis.org/oas/3.2/dialect.yaml @@ -1,4 +1,4 @@ -$id: https://spec.openapis.org/oas/3.2/dialect/WORK-IN-PROGRESS +$id: https://spec.openapis.org/oas/3.2/dialect/2025-09-17 $schema: https://json-schema.org/draft/2020-12/schema title: OpenAPI 3.2 Schema Object Dialect @@ -18,4 +18,4 @@ $vocabulary: allOf: - $ref: https://json-schema.org/draft/2020-12/schema - - $ref: https://spec.openapis.org/oas/3.2/meta/WORK-IN-PROGRESS + - $ref: https://spec.openapis.org/oas/3.2/meta/2025-09-17 diff --git a/documents/spec.openapis.org/oas/3.2/meta.yaml b/documents/spec.openapis.org/oas/3.2/meta.yaml index bbd40a18..816668ce 100644 --- a/documents/spec.openapis.org/oas/3.2/meta.yaml +++ b/documents/spec.openapis.org/oas/3.2/meta.yaml @@ -1,4 +1,4 @@ -$id: https://spec.openapis.org/oas/3.2/meta/WORK-IN-PROGRESS +$id: https://spec.openapis.org/oas/3.2/meta/2025-09-17 $schema: https://json-schema.org/draft/2020-12/schema title: OAS Base Vocabulary diff --git a/documents/spec.openapis.org/oas/3.2/schema-base.yaml b/documents/spec.openapis.org/oas/3.2/schema-base.yaml index 195ae5ed..2c182b6f 100644 --- a/documents/spec.openapis.org/oas/3.2/schema-base.yaml +++ b/documents/spec.openapis.org/oas/3.2/schema-base.yaml @@ -1,20 +1,20 @@ -$id: 'https://spec.openapis.org/oas/3.2/schema-base/WORK-IN-PROGRESS' +$id: 'https://spec.openapis.org/oas/3.2/schema-base/2025-11-23' $schema: 'https://json-schema.org/draft/2020-12/schema' description: The description of OpenAPI v3.2.x Documents using the OpenAPI JSON Schema dialect -$ref: 'https://spec.openapis.org/oas/3.2/schema/WORK-IN-PROGRESS' +$ref: 'https://spec.openapis.org/oas/3.2/schema/2025-11-23' properties: jsonSchemaDialect: $ref: '#/$defs/dialect' $defs: dialect: - const: 'https://spec.openapis.org/oas/3.2/dialect/WORK-IN-PROGRESS' + const: 'https://spec.openapis.org/oas/3.2/dialect/2025-09-17' schema: $dynamicAnchor: meta - $ref: 'https://spec.openapis.org/oas/3.2/dialect/WORK-IN-PROGRESS' + $ref: 'https://spec.openapis.org/oas/3.2/dialect/2025-09-17' properties: $schema: $ref: '#/$defs/dialect' diff --git a/documents/spec.openapis.org/oas/3.2/schema.yaml b/documents/spec.openapis.org/oas/3.2/schema.yaml index 2f40e4b3..7ffc6fae 100644 --- a/documents/spec.openapis.org/oas/3.2/schema.yaml +++ b/documents/spec.openapis.org/oas/3.2/schema.yaml @@ -1,4 +1,4 @@ -$id: 'https://spec.openapis.org/oas/3.2/schema/WORK-IN-PROGRESS' +$id: 'https://spec.openapis.org/oas/3.2/schema/2025-11-23' $schema: 'https://json-schema.org/draft/2020-12/schema' description: The description of OpenAPI v3.2.x Documents without Schema Object validation @@ -18,7 +18,7 @@ properties: jsonSchemaDialect: type: string format: uri-reference - default: 'https://spec.openapis.org/oas/3.2/dialect/WORK-IN-PROGRESS' + default: 'https://spec.openapis.org/oas/3.2/dialect/2025-09-17' servers: type: array items: From 22de64d0edec69507162b4f89b154cdbedebb82f Mon Sep 17 00:00:00 2001 From: Ethan Date: Sun, 15 Mar 2026 19:52:14 -0700 Subject: [PATCH 20/66] init OpenAPI::V3_2 --- lib/scorpio/openapi.rb | 1 + lib/scorpio/openapi/v3_2.rb | 8 ++++++++ 2 files changed, 9 insertions(+) create mode 100644 lib/scorpio/openapi/v3_2.rb diff --git a/lib/scorpio/openapi.rb b/lib/scorpio/openapi.rb index e6c3963e..34c2db80 100644 --- a/lib/scorpio/openapi.rb +++ b/lib/scorpio/openapi.rb @@ -50,6 +50,7 @@ def propertyName autoload(:V3, 'scorpio/openapi/v3_0') autoload(:V3_0, 'scorpio/openapi/v3_0') autoload(:V3_1, 'scorpio/openapi/v3_1') + autoload(:V3_2, 'scorpio/openapi/v3_2') autoload(:SchemaElements, 'scorpio/openapi/schema_elements') end diff --git a/lib/scorpio/openapi/v3_2.rb b/lib/scorpio/openapi/v3_2.rb new file mode 100644 index 00000000..ea0f123a --- /dev/null +++ b/lib/scorpio/openapi/v3_2.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +module Scorpio + module OpenAPI + module V3_2 + end + end +end From 24f04939e90f97adebb29cf16d511e4d1e9a8b82 Mon Sep 17 00:00:00 2001 From: Ethan Date: Sun, 15 Mar 2026 19:52:38 -0700 Subject: [PATCH 21/66] OpenAPI::V3_2::Document --- lib/scorpio/openapi/v3_2.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/scorpio/openapi/v3_2.rb b/lib/scorpio/openapi/v3_2.rb index ea0f123a..d6da2b11 100644 --- a/lib/scorpio/openapi/v3_2.rb +++ b/lib/scorpio/openapi/v3_2.rb @@ -3,6 +3,9 @@ module Scorpio module OpenAPI module V3_2 + module Document + include(OpenAPI::Document::V3Methods) + end end end end From 88776929351e9a7c8f463f27ed3bb508bbcd6758 Mon Sep 17 00:00:00 2001 From: Ethan Date: Sun, 15 Mar 2026 20:15:51 -0700 Subject: [PATCH 22/66] OpenAPI::V3_2::Unscoped::Document --- lib/scorpio/openapi/v3_2.rb | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/lib/scorpio/openapi/v3_2.rb b/lib/scorpio/openapi/v3_2.rb index d6da2b11..96f1a580 100644 --- a/lib/scorpio/openapi/v3_2.rb +++ b/lib/scorpio/openapi/v3_2.rb @@ -6,6 +6,24 @@ module V3_2 module Document include(OpenAPI::Document::V3Methods) end + + + # namespace + module Unscoped + end + + Unscoped::Document = JSI.new_schema_module( + YAML.safe_load(Scorpio.root.join('documents/spec.openapis.org/oas/3.2/schema.yaml').read), + ) + # Schema module: describes an OpenAPI document, but not normally instantiated. + # + # This document schema has no dynamic scope pointing `$dynamicAnchor: "meta"` to a real + # meta-schema. Schemas in the document described by this are just `type: [object, boolean]`, + # have no dialect, and are not usable schemas. + # + # - $id: `https://spec.openapis.org/oas/3.2/schema/2025-11-23` + module Unscoped::Document + end end end end From e094a580de3075a979db588017b98a8249c04724 Mon Sep 17 00:00:00 2001 From: Ethan Date: Sun, 15 Mar 2026 20:17:27 -0700 Subject: [PATCH 23/66] OpenAPI::V3_2.set_up_document_schema_module + .document_name_subschemas --- lib/scorpio/openapi/v3_2.rb | 59 +++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/lib/scorpio/openapi/v3_2.rb b/lib/scorpio/openapi/v3_2.rb index 96f1a580..938f94c0 100644 --- a/lib/scorpio/openapi/v3_2.rb +++ b/lib/scorpio/openapi/v3_2.rb @@ -3,6 +3,65 @@ module Scorpio module OpenAPI module V3_2 + def self.document_name_subschemas(document_schema_module, namespace) + namespace.const_set(:Info, document_schema_module.defs['info']) + namespace.const_set(:Contact, document_schema_module.defs['contact']) + namespace.const_set(:License, document_schema_module.defs['license']) + namespace.const_set(:Server, document_schema_module.defs['server']) + namespace.const_set(:ServerVariable, document_schema_module.defs['server-variable']) + namespace.const_set(:Components, document_schema_module.defs['components']) + namespace.const_set(:Paths, document_schema_module.defs['paths']) + namespace.const_set(:PathItem, document_schema_module.defs['path-item']) + namespace.const_set(:Operation, document_schema_module.defs['operation']) + namespace.const_set(:ExternalDocumentation, document_schema_module.defs['external-documentation']) + namespace.const_set(:Parameters, document_schema_module.defs['parameters']) + namespace.const_set(:Parameter, document_schema_module.defs['parameter']) + namespace.const_set(:ParameterOrReference, document_schema_module.defs['parameter-or-reference']) + namespace.const_set(:RequestBody, document_schema_module.defs['request-body']) + namespace.const_set(:RequestBodyOrReference, document_schema_module.defs['request-body-or-reference']) + namespace.const_set(:Content, document_schema_module.defs['content']) + namespace.const_set(:MediaType, document_schema_module.defs['media-type']) + namespace.const_set(:MediaTypeOrReference, document_schema_module.defs['media-type-or-reference']) + namespace.const_set(:Encoding, document_schema_module.defs['encoding']) + namespace.const_set(:Responses, document_schema_module.defs['responses']) + namespace.const_set(:Response, document_schema_module.defs['response']) + namespace.const_set(:ResponseOrReference, document_schema_module.defs['response-or-reference']) + namespace.const_set(:Callbacks, document_schema_module.defs['callbacks']) + namespace.const_set(:CallbacksOrReference, document_schema_module.defs['callbacks-or-reference']) + namespace.const_set(:Example, document_schema_module.defs['example']) + namespace.const_set(:ExampleOrReference, document_schema_module.defs['example-or-reference']) + namespace.const_set(:Link, document_schema_module.defs['link']) + namespace.const_set(:LinkOrReference, document_schema_module.defs['link-or-reference']) + namespace.const_set(:Header, document_schema_module.defs['header']) + namespace.const_set(:HeaderOrReference, document_schema_module.defs['header-or-reference']) + namespace.const_set(:Tag, document_schema_module.defs['tag']) + namespace.const_set(:Reference, document_schema_module.defs['reference']) + namespace.const_set(:Schema, document_schema_module.defs['schema']) + namespace.const_set(:SecurityScheme, document_schema_module.defs['security-scheme']) + namespace.const_set(:SecuritySchemeOrReference, document_schema_module.defs['security-scheme-or-reference']) + namespace.const_set(:OAuthFlows, document_schema_module.defs['oauth-flows']) + namespace.const_set(:SecurityRequirement, document_schema_module.defs['security-requirement']) + namespace.const_set(:SpecificationExtensions, document_schema_module.defs['specification-extensions']) + namespace.const_set(:Examples, document_schema_module.defs['examples']) + namespace.const_set(:MapOfStrings, document_schema_module.defs['map-of-strings']) + namespace.const_set(:ExplodeForForm, document_schema_module.defs['explode-for-form']) + end + + def self.set_up_document_schema_module(document_schema_module) + document_schema_module.include(OpenAPI::V3_2::Document) + document_schema_module.defs['operation'].include(OpenAPI::Operation::V3Methods) + document_schema_module.defs['reference'].include(OpenAPI::Reference) + document_schema_module.defs['tag'].include(OpenAPI::Tag) + document_schema_module.defs['server'].include(OpenAPI::Server) + document_schema_module.defs['paths'].include(OpenAPI::Paths) + document_schema_module.defs['path-item'].include(OpenAPI::PathItem) + document_schema_module.defs['path-item'].include(OpenAPI::Reference) + document_schema_module.defs['security-scheme'].include(OpenAPI::SecurityScheme) + + document_schema_module + end + + module Document include(OpenAPI::Document::V3Methods) end From 3f806319e7e9204e902d4c3a3dee6bc84c503bbc Mon Sep 17 00:00:00 2001 From: Ethan Date: Sun, 15 Mar 2026 20:18:03 -0700 Subject: [PATCH 24/66] OpenAPI::V3_2.set_up_document_schema_module name SpecificationExtension --- lib/scorpio/openapi/v3_2.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/scorpio/openapi/v3_2.rb b/lib/scorpio/openapi/v3_2.rb index 938f94c0..1c68001f 100644 --- a/lib/scorpio/openapi/v3_2.rb +++ b/lib/scorpio/openapi/v3_2.rb @@ -45,6 +45,7 @@ def self.document_name_subschemas(document_schema_module, namespace) namespace.const_set(:Examples, document_schema_module.defs['examples']) namespace.const_set(:MapOfStrings, document_schema_module.defs['map-of-strings']) namespace.const_set(:ExplodeForForm, document_schema_module.defs['explode-for-form']) + namespace.const_set(:SpecificationExtension, document_schema_module.defs['specification-extensions'].patternProperties["^x-"]) end def self.set_up_document_schema_module(document_schema_module) From e956ddb0bbf0394d8cfa1c4e9fb6f259c7e801ae Mon Sep 17 00:00:00 2001 From: Ethan Date: Sun, 15 Mar 2026 20:18:46 -0700 Subject: [PATCH 25/66] OpenAPI::V3_2.set_up_document_schema_module rm names of *OrReference and SpecificationExtensions schemas - noise --- lib/scorpio/openapi/v3_2.rb | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/lib/scorpio/openapi/v3_2.rb b/lib/scorpio/openapi/v3_2.rb index 1c68001f..0441934d 100644 --- a/lib/scorpio/openapi/v3_2.rb +++ b/lib/scorpio/openapi/v3_2.rb @@ -16,32 +16,22 @@ def self.document_name_subschemas(document_schema_module, namespace) namespace.const_set(:ExternalDocumentation, document_schema_module.defs['external-documentation']) namespace.const_set(:Parameters, document_schema_module.defs['parameters']) namespace.const_set(:Parameter, document_schema_module.defs['parameter']) - namespace.const_set(:ParameterOrReference, document_schema_module.defs['parameter-or-reference']) namespace.const_set(:RequestBody, document_schema_module.defs['request-body']) - namespace.const_set(:RequestBodyOrReference, document_schema_module.defs['request-body-or-reference']) namespace.const_set(:Content, document_schema_module.defs['content']) namespace.const_set(:MediaType, document_schema_module.defs['media-type']) - namespace.const_set(:MediaTypeOrReference, document_schema_module.defs['media-type-or-reference']) namespace.const_set(:Encoding, document_schema_module.defs['encoding']) namespace.const_set(:Responses, document_schema_module.defs['responses']) namespace.const_set(:Response, document_schema_module.defs['response']) - namespace.const_set(:ResponseOrReference, document_schema_module.defs['response-or-reference']) namespace.const_set(:Callbacks, document_schema_module.defs['callbacks']) - namespace.const_set(:CallbacksOrReference, document_schema_module.defs['callbacks-or-reference']) namespace.const_set(:Example, document_schema_module.defs['example']) - namespace.const_set(:ExampleOrReference, document_schema_module.defs['example-or-reference']) namespace.const_set(:Link, document_schema_module.defs['link']) - namespace.const_set(:LinkOrReference, document_schema_module.defs['link-or-reference']) namespace.const_set(:Header, document_schema_module.defs['header']) - namespace.const_set(:HeaderOrReference, document_schema_module.defs['header-or-reference']) namespace.const_set(:Tag, document_schema_module.defs['tag']) namespace.const_set(:Reference, document_schema_module.defs['reference']) namespace.const_set(:Schema, document_schema_module.defs['schema']) namespace.const_set(:SecurityScheme, document_schema_module.defs['security-scheme']) - namespace.const_set(:SecuritySchemeOrReference, document_schema_module.defs['security-scheme-or-reference']) namespace.const_set(:OAuthFlows, document_schema_module.defs['oauth-flows']) namespace.const_set(:SecurityRequirement, document_schema_module.defs['security-requirement']) - namespace.const_set(:SpecificationExtensions, document_schema_module.defs['specification-extensions']) namespace.const_set(:Examples, document_schema_module.defs['examples']) namespace.const_set(:MapOfStrings, document_schema_module.defs['map-of-strings']) namespace.const_set(:ExplodeForForm, document_schema_module.defs['explode-for-form']) From a1dea6011026064aefcae358cfd510ae4b99b227 Mon Sep 17 00:00:00 2001 From: Ethan Date: Sun, 15 Mar 2026 20:20:56 -0700 Subject: [PATCH 26/66] OpenAPI::V3_2 document_schema_modules_by_dialect_id --- lib/scorpio/openapi/v3_2.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/scorpio/openapi/v3_2.rb b/lib/scorpio/openapi/v3_2.rb index 0441934d..7251bdbe 100644 --- a/lib/scorpio/openapi/v3_2.rb +++ b/lib/scorpio/openapi/v3_2.rb @@ -3,6 +3,12 @@ module Scorpio module OpenAPI module V3_2 + class << self + attr_accessor(:document_schema_modules_by_dialect_id) + end + + self.document_schema_modules_by_dialect_id = {} + def self.document_name_subschemas(document_schema_module, namespace) namespace.const_set(:Info, document_schema_module.defs['info']) namespace.const_set(:Contact, document_schema_module.defs['contact']) From c48051fb5622c7c91e4283da0bbd21e300dc9895 Mon Sep 17 00:00:00 2001 From: Ethan Date: Sun, 15 Mar 2026 20:25:54 -0700 Subject: [PATCH 27/66] OpenAPI::V3_2.document_schema_module_by_dialect_id --- lib/scorpio/openapi/v3_2.rb | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/lib/scorpio/openapi/v3_2.rb b/lib/scorpio/openapi/v3_2.rb index 7251bdbe..582def2a 100644 --- a/lib/scorpio/openapi/v3_2.rb +++ b/lib/scorpio/openapi/v3_2.rb @@ -58,6 +58,33 @@ def self.set_up_document_schema_module(document_schema_module) document_schema_module end + # This is pretty much: `Unscoped::Document.with_dynamic_scope_from(JSI.registry.find(dialect_id))` + # plus {.set_up_document_schema_module}. + # + # However, this also supports a dialect whose meta-schema isn't aware of dynamic scope and doesn't + # have a `$dynamicAnchor: "meta"`, e.g. `jsonSchemaDialect: "http://json-schema.org/draft-07/schema"`. + # + # A schema like {Ext::ExtDocument} exists to `$ref` to {Unscoped::Document} with anchor `meta` + # in dynamic scope, with the `$dynamicAnchor: "meta"` schema `$ref`ing to {Ext::MetaSchema}. + # This method obviates the need for such a schema, directly applying dynamic scope. + def self.document_schema_module_by_dialect_id(dialect_id) + dialect_uri = JSI::Util.uri(dialect_id) + document_schema_modules_by_dialect_id[dialect_uri] ||= begin + metaschema = JSI.registry.find(dialect_uri) + dynamic_anchor_map = metaschema.jsi_next_schema_dynamic_anchor_map + unless dynamic_anchor_map.key?('meta') + # hax: pretend that the identified meta-schema has `$dynamicAnchor: "meta"` + # this enables e.g. `jsonSchemaDialect: "http://json-schema.org/draft-07/schema"` to work + # this is non-API JSI internals. + dynamic_anchor_map = dynamic_anchor_map.merge({ + 'meta' => [metaschema, [].freeze].freeze, + }).freeze + end + document_schema = Unscoped::Document.schema.jsi_with_schema_dynamic_anchor_map(dynamic_anchor_map) + set_up_document_schema_module(document_schema.jsi_schema_module) + end + end + module Document include(OpenAPI::Document::V3Methods) From b0e070aa5cafda4cc1b5266d960ef5c4128268c7 Mon Sep 17 00:00:00 2001 From: Ethan Date: Sun, 15 Mar 2026 21:32:19 -0700 Subject: [PATCH 28/66] OpenAPI::V3_2::Ext (OpenAPI extension schema dialect) with schema modules Document, ExtDocument, VocabSchema, MetaSchema --- lib/scorpio/openapi/v3_2.rb | 93 +++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/lib/scorpio/openapi/v3_2.rb b/lib/scorpio/openapi/v3_2.rb index 582def2a..08ee9e11 100644 --- a/lib/scorpio/openapi/v3_2.rb +++ b/lib/scorpio/openapi/v3_2.rb @@ -107,6 +107,99 @@ module Unscoped # - $id: `https://spec.openapis.org/oas/3.2/schema/2025-11-23` module Unscoped::Document end + + + # "Ext" is abbreviation for the "OpenAPI extension schema dialect" that extends JSON Schema draft 2020-12 + # and defines keywords: `discriminator`, `example`, `externalDocs`, `xml`. + # This module is a namespace for that. + module Ext + end + + # vocabulary for implementation of keywords: `discriminator`, `example`, `externalDocs`, `xml` + Ext::VOCAB = JSI::Schema::Vocabulary.new( + id: "https://spec.openapis.org/oas/3.2/vocab/base", + elements: [ + # TODO: + # - discriminator + # - example + # - externalDocs + # - xml + ], + ) + + + Ext::ExtDocument = JSI.new_schema_module( + YAML.safe_load(Scorpio.root.join('documents/spec.openapis.org/oas/3.2/schema-base.yaml').read), + ) + # Schema module: Describes an OAD with schemas of the OpenAPI extension schema dialect. + # This exists to dynamically scope the `meta` anchor + # for {Unscoped::Document} `` + # to {Ext::MetaSchema} `` + # via `<#/$defs/schema>` {Ext::ExtDocument::Schema}. + # + # - $id: `https://spec.openapis.org/oas/3.2/schema-base/2025-11-23` + # - $ref: {Ext::Document} `` + # - $dynamicAnchor: `meta` in `/$defs/schema` ({Ext::ExtDocument::Schema}) + # - properties: jsonSchemaDialect const {Ext::MetaSchema} `` + module Ext::ExtDocument + end + + Ext::ExtDocument::Schema = Ext::ExtDocument["$defs"]["schema"] + # Schema module: Describes schemas in an Ext::Document + # + # - $dynamicAnchor: `meta` + # - $ref: {Ext::MetaSchema} `` + # - properties: $schema const {Ext::MetaSchema} `` + module Ext::ExtDocument::Schema + end + + + # Some Ext schemas are used with dynamic scope from {Ext::ExtDocument}; Ext::Unscoped namespace + # contains those schemas without that dynamic scope. These are not normally instantiated. + module Ext::Unscoped + end + + Ext::Unscoped::VocabSchema = JSI.new_schema_module( + YAML.safe_load(Scorpio.root.join('documents/spec.openapis.org/oas/3.2/meta.yaml').read), + ) + module Ext::Unscoped::VocabSchema + end + + Ext::VocabSchema = Ext::Unscoped::VocabSchema.with_dynamic_scope_from(Ext::ExtDocument) + # Schema module: vocabulary schema for {Ext::VOCAB} + # + # - $id: `https://spec.openapis.org/oas/3.2/meta/2025-09-17` + # - $dynamicAnchor: `meta` (unused) + # - properties (schema keywords) discriminator, example, externalDocs, xml + module Ext::VocabSchema + end + + Ext::Unscoped::MetaSchema = JSI.new_schema_module( + YAML.safe_load(Scorpio.root.join('documents/spec.openapis.org/oas/3.2/dialect.yaml').read), + ) + module Ext::Unscoped::MetaSchema + end + + Ext::MetaSchema = Ext::Unscoped::MetaSchema.with_dynamic_scope_from(Ext::ExtDocument) + Ext::MetaSchema.describes_schema! + # Schema module: Meta-schema describing schemas within an OpenAPI document with the OpenAPI extension schema dialect + # + # - $id: `https://spec.openapis.org/oas/3.2/dialect/2025-09-17` + # - $dynamicAnchor: `meta` (overridden by dynamic scope with `meta` → {Ext::ExtDocument::Schema}) + # - $vocabulary: + # - The draft/2020-12 vocabularies - core, applicator, validation, etc (required: true) + # - {Ext::VOCAB} `` (required: false) + # - allOf: + # - $ref: JSI::JSONSchemaDraft202012 `` (with dynamic scope meta → {Ext::ExtDocument::Schema}) + # - $ref: {Ext::VocabSchema} `` + module Ext::MetaSchema + end + + Ext::Document = Unscoped::Document.with_dynamic_scope_from(Ext::ExtDocument) + # Schema module: Describes an OpenAPI document containing schemas of the Ext dialect. + # This is {Unscoped::Document}, with dynamic scope pointing `$dynamicAnchor: "meta"` to {Ext::ExtDocument::Schema}. + module Ext::Document + end end end end From c96c4534c33fba80c4db41a9f56717b7953f02af Mon Sep 17 00:00:00 2001 From: Ethan Date: Sun, 15 Mar 2026 21:34:17 -0700 Subject: [PATCH 29/66] OpenAPI::V3_2::Ext::VOCAB register --- lib/scorpio/openapi/v3_2.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/scorpio/openapi/v3_2.rb b/lib/scorpio/openapi/v3_2.rb index 08ee9e11..934d669d 100644 --- a/lib/scorpio/openapi/v3_2.rb +++ b/lib/scorpio/openapi/v3_2.rb @@ -126,6 +126,7 @@ module Ext # - xml ], ) + JSI.registry.register_vocabulary(Ext::VOCAB) Ext::ExtDocument = JSI.new_schema_module( From b5c684c3873eb3e24ba19b9391ab13a1ecabc895 Mon Sep 17 00:00:00 2001 From: Ethan Date: Sun, 15 Mar 2026 21:39:21 -0700 Subject: [PATCH 30/66] OAS3.2 schema documents: comment note corresponding schema module names --- documents/spec.openapis.org/oas/3.2/dialect.yaml | 1 + documents/spec.openapis.org/oas/3.2/meta.yaml | 1 + documents/spec.openapis.org/oas/3.2/schema-base.yaml | 1 + documents/spec.openapis.org/oas/3.2/schema.yaml | 1 + 4 files changed, 4 insertions(+) diff --git a/documents/spec.openapis.org/oas/3.2/dialect.yaml b/documents/spec.openapis.org/oas/3.2/dialect.yaml index e7015c54..935a5d37 100644 --- a/documents/spec.openapis.org/oas/3.2/dialect.yaml +++ b/documents/spec.openapis.org/oas/3.2/dialect.yaml @@ -1,3 +1,4 @@ +# Scorpio::OpenAPI::V3_2::Ext::MetaSchema $id: https://spec.openapis.org/oas/3.2/dialect/2025-09-17 $schema: https://json-schema.org/draft/2020-12/schema diff --git a/documents/spec.openapis.org/oas/3.2/meta.yaml b/documents/spec.openapis.org/oas/3.2/meta.yaml index 816668ce..06944d6c 100644 --- a/documents/spec.openapis.org/oas/3.2/meta.yaml +++ b/documents/spec.openapis.org/oas/3.2/meta.yaml @@ -1,3 +1,4 @@ +# Scorpio::OpenAPI::V3_2::Ext::VocabSchema $id: https://spec.openapis.org/oas/3.2/meta/2025-09-17 $schema: https://json-schema.org/draft/2020-12/schema diff --git a/documents/spec.openapis.org/oas/3.2/schema-base.yaml b/documents/spec.openapis.org/oas/3.2/schema-base.yaml index 2c182b6f..1a214f1c 100644 --- a/documents/spec.openapis.org/oas/3.2/schema-base.yaml +++ b/documents/spec.openapis.org/oas/3.2/schema-base.yaml @@ -1,3 +1,4 @@ +# Scorpio::OpenAPI::V3_2::Ext::ExtDocument $id: 'https://spec.openapis.org/oas/3.2/schema-base/2025-11-23' $schema: 'https://json-schema.org/draft/2020-12/schema' diff --git a/documents/spec.openapis.org/oas/3.2/schema.yaml b/documents/spec.openapis.org/oas/3.2/schema.yaml index 7ffc6fae..ab04bf72 100644 --- a/documents/spec.openapis.org/oas/3.2/schema.yaml +++ b/documents/spec.openapis.org/oas/3.2/schema.yaml @@ -1,3 +1,4 @@ +# Scorpio::OpenAPI::V3_2::Unscoped::Document $id: 'https://spec.openapis.org/oas/3.2/schema/2025-11-23' $schema: 'https://json-schema.org/draft/2020-12/schema' From ceb0e606d11b920149db56ec6f61532075108549 Mon Sep 17 00:00:00 2001 From: Ethan Date: Sun, 15 Mar 2026 22:19:15 -0700 Subject: [PATCH 31/66] OpenAPI::V3_2 document_schema_modules_by_dialect_id + Ext::ExtDocument --- lib/scorpio/openapi/v3_2.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/scorpio/openapi/v3_2.rb b/lib/scorpio/openapi/v3_2.rb index 934d669d..01f166f6 100644 --- a/lib/scorpio/openapi/v3_2.rb +++ b/lib/scorpio/openapi/v3_2.rb @@ -201,6 +201,13 @@ module Ext::MetaSchema # This is {Unscoped::Document}, with dynamic scope pointing `$dynamicAnchor: "meta"` to {Ext::ExtDocument::Schema}. module Ext::Document end + + # note: without this mapping set, document_schema_module_by_dialect_id(Ext::MetaSchema.schema_uri) + # would be Unscoped::Document.with_dynamic_scope_from(Ext::Unscoped::MetaSchema) + # instead of Unscoped::Document.with_dynamic_scope_from(Ext::ExtDocument) + # schemas in OADs with this jsonSchemaDialect would have the right dialect, but + # Ext::ExtDocument does also validate OAD jsonSchemaDialect and schema $schema properties. + document_schema_modules_by_dialect_id[Ext::MetaSchema.schema_uri] = Ext::ExtDocument end end end From 2e767a3e2ea51623dc5bbdbfcd34d488423d3322 Mon Sep 17 00:00:00 2001 From: Ethan Date: Sun, 15 Mar 2026 22:19:58 -0700 Subject: [PATCH 32/66] OpenAPI::V3_2::Ext::Document + .set_up_document_schema_module + .document_name_subschemas --- lib/scorpio/openapi/v3_2.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/scorpio/openapi/v3_2.rb b/lib/scorpio/openapi/v3_2.rb index 01f166f6..4033006d 100644 --- a/lib/scorpio/openapi/v3_2.rb +++ b/lib/scorpio/openapi/v3_2.rb @@ -202,6 +202,8 @@ module Ext::MetaSchema module Ext::Document end + set_up_document_schema_module(Ext::Document) + document_name_subschemas(Ext::Document, Ext) # note: without this mapping set, document_schema_module_by_dialect_id(Ext::MetaSchema.schema_uri) # would be Unscoped::Document.with_dynamic_scope_from(Ext::Unscoped::MetaSchema) # instead of Unscoped::Document.with_dynamic_scope_from(Ext::ExtDocument) From 33f9fe32cd180f582222c6b1390a39aac1c9058c Mon Sep 17 00:00:00 2001 From: Ethan Date: Sun, 15 Mar 2026 22:21:52 -0700 Subject: [PATCH 33/66] OpenAPI::V3_2.new_document to instantiate according to jsonSchemaDialect --- lib/scorpio/openapi/v3_2.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/scorpio/openapi/v3_2.rb b/lib/scorpio/openapi/v3_2.rb index 4033006d..4a622dc9 100644 --- a/lib/scorpio/openapi/v3_2.rb +++ b/lib/scorpio/openapi/v3_2.rb @@ -85,6 +85,17 @@ def self.document_schema_module_by_dialect_id(dialect_id) end end + # Instantiates `instance` v3.2 OAD with schemas of the dialect indicated by `jsonSchemaDialect` + # @param instance [#to_hash] + # @return [JSI::Base + Scorpio::OpenAPI::V3_2::Document] + def self.new_document(instance, **new_param) + #jsonSchemaDialect = Scorpio::OpenAPI::V3_2::Unscoped::Document.new_jsi(instance, **new_param).jsonSchemaDialect(use_default: true) + jsonSchemaDialect = instance.fetch('jsonSchemaDialect') { Unscoped::Document.properties['jsonSchemaDialect'].default } + document_schema_module = document_schema_module_by_dialect_id(jsonSchemaDialect) + + document_schema_module.new_jsi(instance, **new_param) + end + module Document include(OpenAPI::Document::V3Methods) From fc19e446eefe3f2036e8f40b8f2a1333ab5a11be Mon Sep 17 00:00:00 2001 From: Ethan Date: Sun, 15 Mar 2026 22:23:22 -0700 Subject: [PATCH 34/66] OpenAPI::Document.from_instance + v3.2 with Scorpio::OpenAPI::V3_2.new_document --- lib/scorpio/openapi/document.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/scorpio/openapi/document.rb b/lib/scorpio/openapi/document.rb index 4ecb7b06..a93583ed 100644 --- a/lib/scorpio/openapi/document.rb +++ b/lib/scorpio/openapi/document.rb @@ -25,6 +25,8 @@ def new_document(instance, **new_param) Scorpio::OpenAPI::V3_0::Document.new_jsi(instance, **new_param) elsif (instance['openapi'].is_a?(String) && instance['openapi'] =~ /\A3\.1(\.|\z)/) || instance['openapi'] == 3.1 Scorpio::OpenAPI::V3_1.new_document(instance, **new_param) + elsif (instance['openapi'].is_a?(String) && instance['openapi'] =~ /\A3\.2(\.|\z)/) || instance['openapi'] == 3.2 + Scorpio::OpenAPI::V3_2.new_document(instance, **new_param) elsif instance['kind'] == 'discovery#restDescription' Scorpio::Google::RestDescription.new_jsi(instance, register: true, **new_param) else From c5e477a9b954638df935e3317ff3215e885b1721 Mon Sep 17 00:00:00 2001 From: Ethan Date: Sun, 15 Mar 2026 22:24:12 -0700 Subject: [PATCH 35/66] test/blog.openapi3_2.yml (same as 3.1) --- test/blog.openapi3_2.yml | 137 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 test/blog.openapi3_2.yml diff --git a/test/blog.openapi3_2.yml b/test/blog.openapi3_2.yml new file mode 100644 index 00000000..bee9058c --- /dev/null +++ b/test/blog.openapi3_2.yml @@ -0,0 +1,137 @@ +openapi: 3.2.0 +servers: +- url: "{scheme}://{host}:{port}/{basePath}" + variables: + scheme: + default: https + host: + default: blog.scorpio + port: + default: '443' + basePath: + enum: + - v1 + default: v1 +info: + title: Scorpio Blog + description: REST service for the Scorpio Blog + version: '' + contact: {} +tags: +- name: articles + description: articles +paths: + /articles: + get: + tags: + - articles + operationId: articles.index + responses: + default: + description: default response + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/article' + post: + tags: + - articles + operationId: articles.post + responses: + default: + description: default response + content: + application/json: + schema: + $ref: '#/components/schemas/article' + requestBody: + $ref: '#/components/requestBodies/article' + /articles_with_root: + get: + tags: + - articles + operationId: articles.index_with_root + parameters: + - name: note + in: header + responses: + default: + description: default response + content: + application/json: + schema: + type: object + properties: + articles: + type: array + items: + $ref: '#/components/schemas/article' + best_article: + $ref: '#/components/schemas/article' + version: + type: string + '/articles/{id}': + get: + tags: + - articles + operationId: articles.read + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + default: + description: default response + content: + application/json: + schema: + $ref: '#/components/schemas/article' + patch: + tags: + - articles + operationId: articles.patch + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + default: + description: default response + content: + application/json: + schema: + $ref: '#/components/schemas/article' + requestBody: + $ref: '#/components/requestBodies/article' + /clean: + post: + tags: + - clean + operationId: clean + responses: + default: + description: default response +components: + requestBodies: + article: + content: + application/json: + schema: + $ref: '#/components/schemas/article' + required: true + schemas: + article: + type: object + properties: + id: + type: integer + title: + type: string + author_id: + type: integer From 7f0ba4fa33f6bb33a61f7772c52606cef46ccfe8 Mon Sep 17 00:00:00 2001 From: Ethan Date: Wed, 18 Mar 2026 10:23:06 -0700 Subject: [PATCH 36/66] test blog with error response --- test/blog.openapi3_2.yml | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/test/blog.openapi3_2.yml b/test/blog.openapi3_2.yml index bee9058c..8cdb4d36 100644 --- a/test/blog.openapi3_2.yml +++ b/test/blog.openapi3_2.yml @@ -40,12 +40,18 @@ paths: - articles operationId: articles.post responses: - default: - description: default response + 2XX: + description: ok content: application/json: schema: $ref: '#/components/schemas/article' + 4XX: + description: error + content: + application/json: + schema: + $ref: '#/components/schemas/error' requestBody: $ref: '#/components/requestBodies/article' /articles_with_root: @@ -101,12 +107,18 @@ paths: schema: type: string responses: - default: - description: default response + 2XX: + description: ok content: application/json: schema: $ref: '#/components/schemas/article' + 4XX: + description: error + content: + application/json: + schema: + $ref: '#/components/schemas/error' requestBody: $ref: '#/components/requestBodies/article' /clean: @@ -135,3 +147,5 @@ components: type: string author_id: type: integer + error: + title: error From f7c97a1f5696950b25bddd2d9c48f199e2b3f134 Mon Sep 17 00:00:00 2001 From: Ethan Date: Thu, 19 Mar 2026 23:54:57 -0700 Subject: [PATCH 37/66] test blog models with wildcard response media type + v3.2 --- test/blog.openapi3_2.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/blog.openapi3_2.yml b/test/blog.openapi3_2.yml index 8cdb4d36..6966ab76 100644 --- a/test/blog.openapi3_2.yml +++ b/test/blog.openapi3_2.yml @@ -30,7 +30,7 @@ paths: default: description: default response content: - application/json: + '*/*': schema: type: array items: @@ -43,7 +43,7 @@ paths: 2XX: description: ok content: - application/json: + 'application/*': schema: $ref: '#/components/schemas/article' 4XX: From ec9a26d53b1871aecb3f9103071863943bbce4b0 Mon Sep 17 00:00:00 2001 From: Ethan Date: Sun, 15 Mar 2026 22:25:00 -0700 Subject: [PATCH 38/66] test with openapi 3.2 --- Rakefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Rakefile b/Rakefile index ae49b853..8f3e4fc2 100644 --- a/Rakefile +++ b/Rakefile @@ -12,6 +12,7 @@ task 'test:each_format' do openapi2 openapi3_0 openapi3_1 + openapi3_2 ) require 'term/ansicolor' From 605f8c9679ca35a07603008ffd16101da5865a5e Mon Sep 17 00:00:00 2001 From: Ethan Date: Sun, 15 Mar 2026 23:12:35 -0700 Subject: [PATCH 39/66] OpenAPI::V3_2::JSONSchemaDraft202012::Document + .document_name_subschemas for jsonSchemaDialect: https://json-schema.org/draft/2020-12/schema --- lib/scorpio/openapi/v3_2.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/scorpio/openapi/v3_2.rb b/lib/scorpio/openapi/v3_2.rb index 4a622dc9..28bd419c 100644 --- a/lib/scorpio/openapi/v3_2.rb +++ b/lib/scorpio/openapi/v3_2.rb @@ -221,6 +221,17 @@ module Ext::Document # schemas in OADs with this jsonSchemaDialect would have the right dialect, but # Ext::ExtDocument does also validate OAD jsonSchemaDialect and schema $schema properties. document_schema_modules_by_dialect_id[Ext::MetaSchema.schema_uri] = Ext::ExtDocument + + + module JSONSchemaDraft202012 + end + + JSONSchemaDraft202012::Document = Unscoped::Document.with_dynamic_scope_from(JSI::JSONSchemaDraft202012) + # Describes an OAD with `jsonSchemaDialect: "https://json-schema.org/draft/2020-12/schema"` + module JSONSchemaDraft202012::Document + end + + document_name_subschemas(JSONSchemaDraft202012::Document, JSONSchemaDraft202012) end end end From 518b57fd9bf09066bc2195363972d56bcb82e9e5 Mon Sep 17 00:00:00 2001 From: Ethan Date: Sun, 15 Mar 2026 23:14:37 -0700 Subject: [PATCH 40/66] OpenAPI::V3_2::Ext::JSONSchemaDraft202012 --- lib/scorpio/openapi/v3_2.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/scorpio/openapi/v3_2.rb b/lib/scorpio/openapi/v3_2.rb index 28bd419c..737832c0 100644 --- a/lib/scorpio/openapi/v3_2.rb +++ b/lib/scorpio/openapi/v3_2.rb @@ -202,7 +202,7 @@ module Ext::Unscoped::MetaSchema # - The draft/2020-12 vocabularies - core, applicator, validation, etc (required: true) # - {Ext::VOCAB} `` (required: false) # - allOf: - # - $ref: JSI::JSONSchemaDraft202012 `` (with dynamic scope meta → {Ext::ExtDocument::Schema}) + # - $ref: {Ext::JSONSchemaDraft202012} `` # - $ref: {Ext::VocabSchema} `` module Ext::MetaSchema end @@ -222,6 +222,12 @@ module Ext::Document # Ext::ExtDocument does also validate OAD jsonSchemaDialect and schema $schema properties. document_schema_modules_by_dialect_id[Ext::MetaSchema.schema_uri] = Ext::ExtDocument + Ext::JSONSchemaDraft202012 = JSI::JSONSchemaDraft202012.with_dynamic_scope_from(Ext::ExtDocument) + JSI::JSONSchemaDraft202012.name_vocab_schemas(Ext::JSONSchemaDraft202012) + # JSI::JSONSchemaDraft202012, with dynamic scope pointing `$dynamicAnchor: "meta"` to {Ext::ExtDocument::Schema}. + module Ext::JSONSchemaDraft202012 + end + module JSONSchemaDraft202012 end From 6d6b56e815b4b085641b2648433fc2848c01787c Mon Sep 17 00:00:00 2001 From: Ethan Date: Wed, 18 Mar 2026 10:35:00 -0700 Subject: [PATCH 41/66] OpenAPI::Response + v3.2 --- lib/scorpio/openapi/v3_2.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/scorpio/openapi/v3_2.rb b/lib/scorpio/openapi/v3_2.rb index 737832c0..f746bb25 100644 --- a/lib/scorpio/openapi/v3_2.rb +++ b/lib/scorpio/openapi/v3_2.rb @@ -46,6 +46,7 @@ def self.document_name_subschemas(document_schema_module, namespace) def self.set_up_document_schema_module(document_schema_module) document_schema_module.include(OpenAPI::V3_2::Document) + document_schema_module.defs['response'].include(OpenAPI::Response) document_schema_module.defs['operation'].include(OpenAPI::Operation::V3Methods) document_schema_module.defs['reference'].include(OpenAPI::Reference) document_schema_module.defs['tag'].include(OpenAPI::Tag) From 31159f64657f01758d6d9d10f05d4d42fcfa3371 Mon Sep 17 00:00:00 2001 From: Ethan Date: Mon, 30 Mar 2026 09:51:58 -0700 Subject: [PATCH 42/66] README OpenAPI v3.2 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index bb4ff783..98fc260d 100644 --- a/README.md +++ b/README.md @@ -11,11 +11,11 @@ Note: The canonical location of this README is on [RubyDoc](https://rubydoc.info ### OpenAPI specification and OpenAPI documents -To start with, you need an OpenAPI document (an OAD) describing a service you will be consuming. OpenAPI Specification v3.1, v3.0, and v2 (formerly known as Swagger) are supported. An OAD can be written by hand or sometimes generated from other existing sources. The creation of an OpenAPI document describing a given service is outside the scope of Scorpio. Here are several resources on OpenAPI: +To start with, you need an OpenAPI document (an OAD) describing a service you will be consuming. OpenAPI Specification v3.2, v3.1, v3.0, and v2 (formerly known as Swagger) are supported. An OAD can be written by hand or sometimes generated from other existing sources. The creation of an OpenAPI document describing a given service is outside the scope of Scorpio. Here are several resources on OpenAPI: - [Learn about OpenAPI](https://learn.openapis.org/) - [OpenAPI Specification at Wikipedia](https://en.wikipedia.org/wiki/OpenAPI_Specification) -- OpenAPI Specifications [v3.1](https://spec.openapis.org/oas/v3.1.html), [v3.0](https://spec.openapis.org/oas/v3.0.html), [v2.0](https://spec.openapis.org/oas/v2.0.html) +- OpenAPI Specifications [v3.2](https://spec.openapis.org/oas/v3.2.html), [v3.1](https://spec.openapis.org/oas/v3.1.html), [v3.0](https://spec.openapis.org/oas/v3.0.html), [v2.0](https://spec.openapis.org/oas/v2.0.html) - [OpenAPI Specification development on GitHub](https://github.com/OAI/OpenAPI-Specification) ### JSON Schema, JSI From 45aa20e1e2e6b5d9d66e25ff36be4add89f95820 Mon Sep 17 00:00:00 2001 From: Ethan Date: Mon, 16 Mar 2026 16:16:27 -0700 Subject: [PATCH 43/66] tests reset JSI.registry for each case --- test/test_helper.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/test_helper.rb b/test/test_helper.rb index 1abd031e..b4c31bfa 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -55,6 +55,10 @@ class ScorpioSpec < Minitest::Spec # :nocov: end + before do + JSI.registry = JSI::DEFAULT_REGISTRY.dup + end + after do BlogClean.clean end From 7eeea8880a8c8e99a4856e7ba4e8a724eb6b703d Mon Sep 17 00:00:00 2001 From: Ethan Date: Mon, 16 Mar 2026 14:40:35 -0700 Subject: [PATCH 44/66] init test/openapi_document_test --- test/openapi_document_test.rb | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 test/openapi_document_test.rb diff --git a/test/openapi_document_test.rb b/test/openapi_document_test.rb new file mode 100644 index 00000000..6c252bda --- /dev/null +++ b/test/openapi_document_test.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true +require_relative 'test_helper' + +describe("OpenAPI::Document") do +end From b201e8d19d549ed0c3fc39ec601968567898082b Mon Sep 17 00:00:00 2001 From: Ethan Date: Mon, 16 Mar 2026 14:40:52 -0700 Subject: [PATCH 45/66] OpenAPI v3.2 $self is a resource URI --- lib/scorpio/openapi/document.rb | 12 ++++++++++ lib/scorpio/openapi/v3_2.rb | 1 + test/openapi_document_test.rb | 40 +++++++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+) diff --git a/lib/scorpio/openapi/document.rb b/lib/scorpio/openapi/document.rb index a93583ed..728ae768 100644 --- a/lib/scorpio/openapi/document.rb +++ b/lib/scorpio/openapi/document.rb @@ -125,6 +125,18 @@ def title end end + # an OAD with a `$self` property that indicates its resource URI + module Document::SelfURI + # overrides JSI::Base#jsi_each_resource_uri_compute. + # this is more into JSI internals than I prefer but currently this is the way to accomplish this. + private def jsi_each_resource_uri_compute + if respond_to?(:to_hash) && key?('$self') + yield jsi_base_uri ? jsi_base_uri.join(jsi_node_content['$self']) : JSI::URI[jsi_node_content['$self']] + end + super + end + end + module Document module V3Methods module Configurables diff --git a/lib/scorpio/openapi/v3_2.rb b/lib/scorpio/openapi/v3_2.rb index f746bb25..7f2fdcc8 100644 --- a/lib/scorpio/openapi/v3_2.rb +++ b/lib/scorpio/openapi/v3_2.rb @@ -100,6 +100,7 @@ def self.new_document(instance, **new_param) module Document include(OpenAPI::Document::V3Methods) + include(OpenAPI::Document::SelfURI) end diff --git a/test/openapi_document_test.rb b/test/openapi_document_test.rb index 6c252bda..46c77f29 100644 --- a/test/openapi_document_test.rb +++ b/test/openapi_document_test.rb @@ -2,4 +2,44 @@ require_relative 'test_helper' describe("OpenAPI::Document") do + describe("v3.2 base URI, $self") do + it("identifies") do + oad_self_abs = Scorpio::OpenAPI::Document.from_instance(YAML.safe_load(<<~YAML + openapi: 3.2.0 + $self: "tag:6a/self" + components: + schemas: + z: + $id: z + YAML + ), register: true) + assert_equal(oad_self_abs, JSI.registry.find('tag:6a/self')) + assert_equal(oad_self_abs.components.schemas['z'], JSI.registry.find('tag:6a/z')) + + oad_self_rel_base = Scorpio::OpenAPI::Document.from_instance(YAML.safe_load(<<~YAML + openapi: 3.2.0 + $self: "base/self" + components: + schemas: + z: + $id: z + YAML + ), register: true, base_uri: 'tag:6b/base') + assert_equal(oad_self_rel_base, JSI.registry.find('tag:6b/base/self')) + assert_equal(oad_self_rel_base.components.schemas['z'], JSI.registry.find('tag:6b/base/z')) + + oad_self_rel_root = Scorpio::OpenAPI::Document.from_instance(YAML.safe_load(<<~YAML + openapi: 3.2.0 + $self: "root/self" + components: + schemas: + z: + $id: z + YAML + ), register: true, root_uri: 'tag:6c/root') + assert_equal(oad_self_rel_root, JSI.registry.find('tag:6c/root')) + assert_equal(oad_self_rel_root, JSI.registry.find('tag:6c/root/self')) + assert_equal(oad_self_rel_root.components.schemas['z'], JSI.registry.find('tag:6c/root/z')) + end + end end From 2aacde1407f9942f510926ef4c5852030cd47cdd Mon Sep 17 00:00:00 2001 From: Ethan Date: Mon, 16 Mar 2026 16:15:15 -0700 Subject: [PATCH 46/66] test OpenAPI::Document v3.2 base URI, $self with Appendix F examples --- test/openapi_document_test.rb | 122 ++++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) diff --git a/test/openapi_document_test.rb b/test/openapi_document_test.rb index 46c77f29..61e12c5c 100644 --- a/test/openapi_document_test.rb +++ b/test/openapi_document_test.rb @@ -41,5 +41,127 @@ assert_equal(oad_self_rel_root, JSI.registry.find('tag:6c/root/self')) assert_equal(oad_self_rel_root.components.schemas['z'], JSI.registry.find('tag:6c/root/z')) end + + it("agrees with OAS v3.2 Appendix F.1 Base URI Within Content") do + # https://spec.openapis.org/oas/v3.2.0.html#base-uri-within-content + oad_f1 = Scorpio::OpenAPI::Document.from_instance(YAML.safe_load(<<~YAML + openapi: 3.2.0 + $self: https://example.com/api/openapi + info: + title: Example API + version: 1.0 + paths: + /foo: + get: + requestBody: + $ref: "shared/foo#/components/requestBodies/Foo" + YAML + ), register: true, root_uri: 'file://home/someone/src/api/openapi.yaml') + oad_f1foo = Scorpio::OpenAPI::Document.from_instance(YAML.safe_load(<<~YAML + openapi: 3.2.0 + $self: https://example.com/api/shared/foo + info: + title: Shared components for all APIs + version: 1.0 + components: + requestBodies: + Foo: + content: + application/json: + schema: + $ref: ../schemas/foo + schemas: + Foo: + $id: https://example.com/api/schemas/foo + properties: + bar: + $ref: bar + Bar: + $id: https://example.com/api/schemas/bar + type: string + YAML + ), register: true, root_uri: 'https://git.example.com/shared/blob/main/shared/foo.yaml') + assert_equal(oad_f1foo.components.requestBodies['Foo'], oad_f1.paths['/foo'].get.requestBody.resolve) + assert_equal(oad_f1foo.components.schemas['Foo'], oad_f1foo.components.requestBodies['Foo'].content['application/json'].schema.schema_ref.resolve) + assert_equal(oad_f1foo.components.schemas['Bar'], oad_f1foo.components.schemas['Foo'].properties['bar'].schema_ref.resolve) + end + + it("agrees with OAS v3.2 Appendix F.2 Base URI From Encapsulating Entity") do + # https://spec.openapis.org/oas/v3.2.0.html#base-uri-from-encapsulating-entity + oad_f2 = Scorpio::OpenAPI::Document.from_instance(YAML.safe_load(<<~YAML + openapi: 3.2.0 + info: + title: Example API + version: 1.0 + externalDocs: + url: docs.html + components: + requestBodies: + Foo: + content: + application/json: + schema: + $ref: "#/components/schemas/Foo" + schemas: + Foo: + properties: + bar: + $ref: schemas/bar + YAML + ), register: true, root_uri: 'https://example.com/api/openapi.yaml') + schema_f2bar = JSI::JSONSchemaDraft202012.new_schema({"type": "string"}, root_uri: 'https://example.com/api/schemas/bar') + assert_equal(oad_f2.components.schemas['Foo'], oad_f2.components.requestBodies['Foo'].content['application/json'].schema.schema_ref.resolve) + assert_equal(schema_f2bar, oad_f2.components.schemas['Foo'].properties['bar'].schema_ref.resolve) + end + + # F.3 seems redundant with F.2. F.4 is not relevant. + + it("agrees with OAS v3.2 Appendix F.5 Resolving Relative $self and $id") do + # https://spec.openapis.org/oas/v3.2.0.html#resolving-relative-self-and-id + oad_f5 = Scorpio::OpenAPI::Document.from_instance(YAML.safe_load(<<~YAML + openapi: 3.2.0 + $self: /api/openapi + info: + title: Example API + version: 1.0 + paths: + /foo: + get: + requestBody: + $ref: "shared/foo#/components/requestBodies/Foo" + YAML + ), register: true, root_uri: 'https://staging.example.com/api/openapi') + oad_f5foo = Scorpio::OpenAPI::Document.from_instance(YAML.safe_load(<<~YAML + openapi: 3.2.0 + $self: /api/shared/foo + info: + title: Shared components for all APIs + version: 1.0 + components: + requestBodies: + Foo: + content: + application/json: + schema: + $ref: ../schemas/foo + schemas: + Foo: + $id: /api/schemas/foo + properties: + bar: + $ref: bar + Bar: + $id: /api/schemas/bar + type: string + YAML + ), register: true, root_uri: 'https://staging.example.com/api/shared/foo') + assert_equal(oad_f5, JSI.registry.find('https://staging.example.com/api/openapi')) + assert_equal(oad_f5foo, JSI.registry.find('https://staging.example.com/api/shared/foo')) + assert_equal(oad_f5foo.components.schemas['Foo'], JSI.registry.find('https://staging.example.com/api/schemas/foo')) + assert_equal(oad_f5foo.components.schemas['Bar'], JSI.registry.find('https://staging.example.com/api/schemas/bar')) + assert_equal(oad_f5foo.components.requestBodies['Foo'], oad_f5.paths['/foo'].get.requestBody.resolve) + assert_equal(oad_f5foo.components.schemas['Foo'], oad_f5foo.components.requestBodies['Foo'].content['application/json'].schema.schema_ref.resolve) + assert_equal(oad_f5foo.components.schemas['Bar'], oad_f5foo.components.schemas['Foo'].properties['bar'].schema_ref.resolve) + end end end From 94296a509367ff2a696b214db25a6f09604750b3 Mon Sep 17 00:00:00 2001 From: Ethan Date: Sat, 19 Apr 2025 10:23:12 -0700 Subject: [PATCH 47/66] OpenAPI::Server#expanded_url resolve relative server url from OAD base uri --- lib/scorpio/openapi/server.rb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/scorpio/openapi/server.rb b/lib/scorpio/openapi/server.rb index b16a809c..f234b846 100644 --- a/lib/scorpio/openapi/server.rb +++ b/lib/scorpio/openapi/server.rb @@ -27,7 +27,15 @@ def expanded_url(given_server_variables) server_variables = given_server_variables end template = Addressable::Template.new(url) - template.expand(server_variables).freeze + expanded_url = template.expand(server_variables).freeze + if expanded_url.relative? + raise(Error, -"server URL is relative with no base URL. server: #{inspect}") if !openapi_document.jsi_base_uri + # note: this uses the OAD jsi_base_uri, not this server object's, because OAS 3.2 + # excludes $self uri as the base for API urls including server url + # https://spec.openapis.org/oas/v3.2.0.html#relative-references-in-api-urls + expanded_url = openapi_document.jsi_base_uri.join(expanded_url) + end + expanded_url end end end From df78a9fd482f13989ba80247c9174f136f55c456 Mon Sep 17 00:00:00 2001 From: Ethan Date: Sun, 20 Apr 2025 13:54:51 -0700 Subject: [PATCH 48/66] test OpenAPI::Server#expanded_url with document base_uri --- test/openapi_servers_test.rb | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 test/openapi_servers_test.rb diff --git a/test/openapi_servers_test.rb b/test/openapi_servers_test.rb new file mode 100644 index 00000000..b2e5718f --- /dev/null +++ b/test/openapi_servers_test.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require_relative('test_helper') + +describe("OpenAPI::Server") do + describe("#expanded_url") do + let(:oad_content) do + YAML.load(<<~YAML + openapi: 3.0.0 + servers: + - url: "{basePath}" + variables: + basePath: + enum: + - v1 + default: v1 + YAML + ) + end + + it("expands url from document base uri") do + oad = Scorpio::OpenAPI::Document.from_instance(oad_content, base_uri: 'http://47z') + assert_equal('http://47z/v1', oad.base_url.to_s) + # relative server url with no base + assert_raises(Scorpio::OpenAPI::Error) { Scorpio::OpenAPI::Document.from_instance(oad_content).base_url } + end + end +end From 104f0d870605540872a5500212b90f628c3e7256 Mon Sep 17 00:00:00 2001 From: Ethan Date: Tue, 6 Jan 2026 15:47:44 -0800 Subject: [PATCH 49/66] Request configurable querystring --- lib/scorpio/request.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/scorpio/request.rb b/lib/scorpio/request.rb index 4963e7b4..29a456d8 100644 --- a/lib/scorpio/request.rb +++ b/lib/scorpio/request.rb @@ -38,6 +38,12 @@ def query_params nil end + attr_writer(:querystring) + def querystring + return @querystring if instance_variable_defined?(:@querystring) + nil + end + attr_writer :scheme def scheme return @scheme if instance_variable_defined?(:@scheme) @@ -203,9 +209,13 @@ def path end path = path_template.expand(path_params) + raise(AmbiguousParameter, "query_params + querystring both specified") if query_params && querystring if query_params path.query_values = query_params end + if querystring + path.query = querystring + end path.freeze end From c59c82d7701e955cdc444922c64fdef147cdd84a Mon Sep 17 00:00:00 2001 From: Ethan Date: Tue, 17 Mar 2026 10:44:36 -0700 Subject: [PATCH 50/66] Request param in querystring --- lib/scorpio/request.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/scorpio/request.rb b/lib/scorpio/request.rb index 29a456d8..3381a652 100644 --- a/lib/scorpio/request.rb +++ b/lib/scorpio/request.rb @@ -330,6 +330,8 @@ def set_param_from(param_in, name, value) self.path_params = self.path_params.merge(name => value) elsif param_in == 'query' self.query_params = (self.query_params || {}).merge(name => value) + elsif param_in == 'querystring' + self.querystring = value elsif param_in == 'header' self.headers = self.headers.merge(name => value.to_str) elsif param_in == 'cookie' @@ -352,6 +354,8 @@ def get_param_from(param_in, name) path_params[name] elsif param_in == 'query' query_params ? query_params[name] : nil + elsif param_in == 'querystring' + querystring elsif param_in == 'header' _, value = headers.detect { |headername, _| headername.casecmp?(name) } value From d2a05741011c8d83b6054c45beac96e663c98fda Mon Sep 17 00:00:00 2001 From: Ethan Date: Tue, 17 Mar 2026 13:03:59 -0700 Subject: [PATCH 51/66] test request with querystring, query param --- test/request_test.rb | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/test/request_test.rb b/test/request_test.rb index cc333262..12f795ed 100644 --- a/test/request_test.rb +++ b/test/request_test.rb @@ -43,4 +43,48 @@ assert_raises(Scorpio::AmbiguousParameter) { oad.operations.first.build_request(a: 'A') } end end + + describe("querystring") do + it("sets") do + oad = Scorpio::OpenAPI::Document.from_instance(YAML.safe_load(<<~YAML + openapi: 3.2.0 + paths: + '/': + get: + parameters: + - name: param1 + in: querystring + YAML + )) + + request = oad.operations.first.build_request(param1: 'x') + assert_equal('x', request.get_param('param1')) + assert_equal('x', request.querystring) + assert_equal('/?x', request.path.to_s) + end + + it("with in: query param") do + oad = Scorpio::OpenAPI::Document.from_instance(YAML.safe_load(<<~YAML + openapi: 3.2.0 + paths: + '/': + get: + parameters: + - name: param1 + in: querystring + - name: param2 + in: query + YAML + )) + + # it builds the request and sets querystring and query_params, but errors when constructing Request#path. + # in future might change Request#query_params= and Request#querystring= to raise instead. + request = oad.operations.first.build_request(param1: 'x', param2: 'y') + assert_equal('x', request.get_param('param1')) + assert_equal('x', request.querystring) + assert_equal('y', request.get_param('param2')) + assert_equal({'param2' => 'y'}, request.query_params) + assert_raises(Scorpio::AmbiguousParameter) { request.path } + end + end end From 358fa9061b7b00c6c095931a8bc1916511538e59 Mon Sep 17 00:00:00 2001 From: Ethan Date: Thu, 19 Mar 2026 00:04:48 -0700 Subject: [PATCH 52/66] OpenAPI::Tag#parent_tag, #child_tags --- lib/scorpio/openapi/tag.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/scorpio/openapi/tag.rb b/lib/scorpio/openapi/tag.rb index febf68f8..012a01cb 100644 --- a/lib/scorpio/openapi/tag.rb +++ b/lib/scorpio/openapi/tag.rb @@ -18,6 +18,16 @@ def each_operation(&block) yield(op) if op.tags.respond_to?(:to_ary) && op.tags.include?(name) end end + + # @return [OpenAPI::Tag, nil] + def parent_tag + self['parent'] ? openapi_document.tags.named(self['parent']) : nil + end + + # @return [Enumerable] + def child_tags + openapi_document.tags.select { |t| t['parent'] == name } + end end module Tags From 7630c6188cb03a4f2bdedf70ab94485987110dd5 Mon Sep 17 00:00:00 2001 From: Ethan Date: Thu, 19 Mar 2026 00:19:50 -0700 Subject: [PATCH 53/66] OpenAPI::Tag#each_descendent_tag_operation --- lib/scorpio/openapi/tag.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/scorpio/openapi/tag.rb b/lib/scorpio/openapi/tag.rb index 012a01cb..7e9d955d 100644 --- a/lib/scorpio/openapi/tag.rb +++ b/lib/scorpio/openapi/tag.rb @@ -19,6 +19,15 @@ def each_operation(&block) end end + # each operation tagged with this tag, a child tag of this, or any further descendent tag. + # @yield [OpenAPI::Operation] + def each_descendent_tag_operation(&block) + return(to_enum(__method__)) unless block + each_operation(&block) + child_tags.each { |tag| tag.each_descendent_operation(&block) } + nil + end + # @return [OpenAPI::Tag, nil] def parent_tag self['parent'] ? openapi_document.tags.named(self['parent']) : nil From 8fa8e0b1ec069c3cbfd73c35df49f1e6dadd2615 Mon Sep 17 00:00:00 2001 From: Ethan Date: Thu, 19 Mar 2026 00:20:28 -0700 Subject: [PATCH 54/66] OpenAPI::Tags#with_kind --- lib/scorpio/openapi/tag.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/scorpio/openapi/tag.rb b/lib/scorpio/openapi/tag.rb index 7e9d955d..515b51e3 100644 --- a/lib/scorpio/openapi/tag.rb +++ b/lib/scorpio/openapi/tag.rb @@ -45,6 +45,11 @@ module Tags def named(name) detect { |tag| tag.name == name } end + + # @return [Enumerable] + def with_kind(kind) + select { |tag| tag['kind'] == kind } + end end end end From 6bb324e1de649b4e4372f900a93afb5cd7526154 Mon Sep 17 00:00:00 2001 From: Ethan Date: Thu, 19 Mar 2026 14:04:56 -0700 Subject: [PATCH 55/66] OpenAPI::Document#operations include additionalOperations; OpenAPI::Operation#http_method support --- lib/scorpio/openapi/document.rb | 1 + lib/scorpio/openapi/operation.rb | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/scorpio/openapi/document.rb b/lib/scorpio/openapi/document.rb index 728ae768..c84743c2 100644 --- a/lib/scorpio/openapi/document.rb +++ b/lib/scorpio/openapi/document.rb @@ -117,6 +117,7 @@ def each_operation(&block) yield(operation) end end + (path_item['additionalOperations'] || {}).each_value(&block) # only OAS v3.2+ end end diff --git a/lib/scorpio/openapi/operation.rb b/lib/scorpio/openapi/operation.rb index c36bdaa7..e2100af3 100644 --- a/lib/scorpio/openapi/operation.rb +++ b/lib/scorpio/openapi/operation.rb @@ -102,7 +102,6 @@ def uri_template(base_url: self.base_url) # @return [String] def http_method return @http_method if instance_variable_defined?(:@http_method) - return(@http_method = nil) unless jsi_parent_node.is_a?(Scorpio::OpenAPI::PathItem) @http_method = jsi_ptr.tokens.last end From 2cd858c5ac7773b232fd5480a52e8e4f7ce1c700 Mon Sep 17 00:00:00 2001 From: Ethan Date: Sat, 28 Mar 2026 22:11:54 -0700 Subject: [PATCH 56/66] OpenAPI::Document.document_schema_module_with_meta deduplicate V3_1, V3_2 set_up_document_schema_module --- lib/scorpio/openapi/document.rb | 28 ++++++++++++++++++++++++++++ lib/scorpio/openapi/v3_1.rb | 26 +++----------------------- lib/scorpio/openapi/v3_2.rb | 26 +++----------------------- 3 files changed, 34 insertions(+), 46 deletions(-) diff --git a/lib/scorpio/openapi/document.rb b/lib/scorpio/openapi/document.rb index c84743c2..7a977cb0 100644 --- a/lib/scorpio/openapi/document.rb +++ b/lib/scorpio/openapi/document.rb @@ -41,6 +41,34 @@ def new_document(instance, **new_param) def from_instance(instance, **kw) Scorpio.new_document(instance, **kw) end + + # This is pretty much: `document_schema_module.with_dynamic_scope_from(JSI.registry.find(dialect_id))` + # + # However, this also supports a dialect whose meta-schema isn't aware of dynamic scope and doesn't + # have a `$dynamicAnchor: "meta"`, e.g. `jsonSchemaDialect: "http://json-schema.org/draft-07/schema"`. + # + # A schema like {OpenAPI::V3_1::Ext::ExtDocument} exists to `$ref` to + # {OpenAPI::V3_1::Unscoped::Document} with anchor `meta` in dynamic scope, with the + # `$dynamicAnchor: "meta"` schema `$ref`ing to {OpenAPI::V3_1::Ext::MetaSchema}. + # This method obviates the need for such a schema, directly applying dynamic scope. + # + # @api private + # @param document_schema_module [JSI::SchemaModule] + # @param dialect_id [#to_str] + # @return [JSI::SchemaModule] + def document_schema_module_with_meta(document_schema_module, dialect_id) + metaschema = JSI.registry.find(dialect_id) + dynamic_anchor_map = metaschema.jsi_next_schema_dynamic_anchor_map + unless dynamic_anchor_map.key?('meta') + # hax: pretend that the identified meta-schema has `$dynamicAnchor: "meta"`. + # this enables e.g. `jsonSchemaDialect: "http://json-schema.org/draft-07/schema"` to work. + # this is non-API JSI internals. + dynamic_anchor_map = dynamic_anchor_map.merge({ + 'meta' => [metaschema, [].freeze].freeze, + }).freeze + end + document_schema_module.schema.jsi_with_schema_dynamic_anchor_map(dynamic_anchor_map).jsi_schema_module + end end module Descendent diff --git a/lib/scorpio/openapi/v3_1.rb b/lib/scorpio/openapi/v3_1.rb index 2c847d61..c7498db4 100644 --- a/lib/scorpio/openapi/v3_1.rb +++ b/lib/scorpio/openapi/v3_1.rb @@ -58,30 +58,10 @@ def self.set_up_document_schema_module(document_schema_module) document_schema_module end - # This is pretty much: `Unscoped::Document.with_dynamic_scope_from(JSI.registry.find(dialect_id))` - # plus {.set_up_document_schema_module}. - # - # However, this also supports a dialect whose meta-schema isn't aware of dynamic scope and doesn't - # have a `$dynamicAnchor: "meta"`, e.g. `jsonSchemaDialect: "http://json-schema.org/draft-07/schema"`. - # - # A schema like {Ext::ExtDocument} exists to `$ref` to {Unscoped::Document} with anchor `meta` - # in dynamic scope, with the `$dynamicAnchor: "meta"` schema `$ref`ing to {Ext::MetaSchema}. - # This method obviates the need for such a schema, directly applying dynamic scope. def self.document_schema_module_by_dialect_id(dialect_id) - dialect_uri = JSI::Util.uri(dialect_id) - document_schema_modules_by_dialect_id[dialect_uri] ||= begin - metaschema = JSI.registry.find(dialect_uri) - dynamic_anchor_map = metaschema.jsi_next_schema_dynamic_anchor_map - unless dynamic_anchor_map.key?('meta') - # hax: pretend that the identified meta-schema has `$dynamicAnchor: "meta"` - # this enables e.g. `jsonSchemaDialect: "http://json-schema.org/draft-07/schema"` to work - # this is non-API JSI internals. - dynamic_anchor_map = dynamic_anchor_map.merge({ - 'meta' => [metaschema, [].freeze].freeze, - }).freeze - end - document_schema = Unscoped::Document.schema.jsi_with_schema_dynamic_anchor_map(dynamic_anchor_map) - set_up_document_schema_module(document_schema.jsi_schema_module) + document_schema_modules_by_dialect_id[JSI::Util.uri(dialect_id)] ||= begin + document_schema_module = OpenAPI::Document.document_schema_module_with_meta(Unscoped::Document, dialect_id) + set_up_document_schema_module(document_schema_module) end end diff --git a/lib/scorpio/openapi/v3_2.rb b/lib/scorpio/openapi/v3_2.rb index 7f2fdcc8..b1cb24ea 100644 --- a/lib/scorpio/openapi/v3_2.rb +++ b/lib/scorpio/openapi/v3_2.rb @@ -59,30 +59,10 @@ def self.set_up_document_schema_module(document_schema_module) document_schema_module end - # This is pretty much: `Unscoped::Document.with_dynamic_scope_from(JSI.registry.find(dialect_id))` - # plus {.set_up_document_schema_module}. - # - # However, this also supports a dialect whose meta-schema isn't aware of dynamic scope and doesn't - # have a `$dynamicAnchor: "meta"`, e.g. `jsonSchemaDialect: "http://json-schema.org/draft-07/schema"`. - # - # A schema like {Ext::ExtDocument} exists to `$ref` to {Unscoped::Document} with anchor `meta` - # in dynamic scope, with the `$dynamicAnchor: "meta"` schema `$ref`ing to {Ext::MetaSchema}. - # This method obviates the need for such a schema, directly applying dynamic scope. def self.document_schema_module_by_dialect_id(dialect_id) - dialect_uri = JSI::Util.uri(dialect_id) - document_schema_modules_by_dialect_id[dialect_uri] ||= begin - metaschema = JSI.registry.find(dialect_uri) - dynamic_anchor_map = metaschema.jsi_next_schema_dynamic_anchor_map - unless dynamic_anchor_map.key?('meta') - # hax: pretend that the identified meta-schema has `$dynamicAnchor: "meta"` - # this enables e.g. `jsonSchemaDialect: "http://json-schema.org/draft-07/schema"` to work - # this is non-API JSI internals. - dynamic_anchor_map = dynamic_anchor_map.merge({ - 'meta' => [metaschema, [].freeze].freeze, - }).freeze - end - document_schema = Unscoped::Document.schema.jsi_with_schema_dynamic_anchor_map(dynamic_anchor_map) - set_up_document_schema_module(document_schema.jsi_schema_module) + document_schema_modules_by_dialect_id[JSI::Util.uri(dialect_id)] ||= begin + document_schema_module = OpenAPI::Document.document_schema_module_with_meta(Unscoped::Document, dialect_id) + set_up_document_schema_module(document_schema_module) end end From 2812e368382486139ff16e35ead9a2eda667fdf9 Mon Sep 17 00:00:00 2001 From: Ethan Date: Sat, 28 Mar 2026 22:33:30 -0700 Subject: [PATCH 57/66] OpenAPI::V3_1, V3_2 refactor document_schema_modules_by_dialect_id, uses Hash default_proc --- lib/scorpio/openapi/v3_1.rb | 12 ++++++------ lib/scorpio/openapi/v3_2.rb | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/scorpio/openapi/v3_1.rb b/lib/scorpio/openapi/v3_1.rb index c7498db4..9f9f3d5f 100644 --- a/lib/scorpio/openapi/v3_1.rb +++ b/lib/scorpio/openapi/v3_1.rb @@ -4,10 +4,13 @@ module Scorpio module OpenAPI module V3_1 class << self - attr_accessor(:document_schema_modules_by_dialect_id) + attr_reader(:document_schema_modules_by_dialect_id) end - self.document_schema_modules_by_dialect_id = {} + @document_schema_modules_by_dialect_id = Hash.new do |h, dialect_id| + document_schema_module = OpenAPI::Document.document_schema_module_with_meta(Unscoped::Document, dialect_id) + h[dialect_id] = set_up_document_schema_module(document_schema_module) + end def self.document_name_subschemas(document_schema_module, namespace) namespace.const_set(:Info, document_schema_module.defs['info']) @@ -59,10 +62,7 @@ def self.set_up_document_schema_module(document_schema_module) end def self.document_schema_module_by_dialect_id(dialect_id) - document_schema_modules_by_dialect_id[JSI::Util.uri(dialect_id)] ||= begin - document_schema_module = OpenAPI::Document.document_schema_module_with_meta(Unscoped::Document, dialect_id) - set_up_document_schema_module(document_schema_module) - end + document_schema_modules_by_dialect_id[JSI::Util.uri(dialect_id)] end # Instantiates `instance` v3.1 OAD with schemas of the dialect indicated by `jsonSchemaDialect` diff --git a/lib/scorpio/openapi/v3_2.rb b/lib/scorpio/openapi/v3_2.rb index b1cb24ea..8ac8cf6a 100644 --- a/lib/scorpio/openapi/v3_2.rb +++ b/lib/scorpio/openapi/v3_2.rb @@ -4,10 +4,13 @@ module Scorpio module OpenAPI module V3_2 class << self - attr_accessor(:document_schema_modules_by_dialect_id) + attr_reader(:document_schema_modules_by_dialect_id) end - self.document_schema_modules_by_dialect_id = {} + @document_schema_modules_by_dialect_id = Hash.new do |h, dialect_id| + document_schema_module = OpenAPI::Document.document_schema_module_with_meta(Unscoped::Document, dialect_id) + h[dialect_id] = set_up_document_schema_module(document_schema_module) + end def self.document_name_subschemas(document_schema_module, namespace) namespace.const_set(:Info, document_schema_module.defs['info']) @@ -60,10 +63,7 @@ def self.set_up_document_schema_module(document_schema_module) end def self.document_schema_module_by_dialect_id(dialect_id) - document_schema_modules_by_dialect_id[JSI::Util.uri(dialect_id)] ||= begin - document_schema_module = OpenAPI::Document.document_schema_module_with_meta(Unscoped::Document, dialect_id) - set_up_document_schema_module(document_schema_module) - end + document_schema_modules_by_dialect_id[JSI::Util.uri(dialect_id)] end # Instantiates `instance` v3.2 OAD with schemas of the dialect indicated by `jsonSchemaDialect` From 9dd16391fd499977f078066e81b07ef089284d83 Mon Sep 17 00:00:00 2001 From: Ethan Date: Sat, 28 Mar 2026 23:31:23 -0700 Subject: [PATCH 58/66] rm OpenAPI::V3_1, V3_2 document_schema_module_by_dialect_id; just use document_schema_modules_by_dialect_id --- lib/scorpio/openapi/v3_1.rb | 8 ++------ lib/scorpio/openapi/v3_2.rb | 8 ++------ 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/lib/scorpio/openapi/v3_1.rb b/lib/scorpio/openapi/v3_1.rb index 9f9f3d5f..ae4d920b 100644 --- a/lib/scorpio/openapi/v3_1.rb +++ b/lib/scorpio/openapi/v3_1.rb @@ -61,17 +61,13 @@ def self.set_up_document_schema_module(document_schema_module) document_schema_module end - def self.document_schema_module_by_dialect_id(dialect_id) - document_schema_modules_by_dialect_id[JSI::Util.uri(dialect_id)] - end - # Instantiates `instance` v3.1 OAD with schemas of the dialect indicated by `jsonSchemaDialect` # @param instance [#to_hash] # @return [JSI::Base + Scorpio::OpenAPI::V3_1::Document] def self.new_document(instance, **new_param) #jsonSchemaDialect = Scorpio::OpenAPI::V3_1::Unscoped::Document.new_jsi(instance, **new_param).jsonSchemaDialect(use_default: true) jsonSchemaDialect = instance.fetch('jsonSchemaDialect') { Unscoped::Document.properties['jsonSchemaDialect'].default } - document_schema_module = document_schema_module_by_dialect_id(jsonSchemaDialect) + document_schema_module = document_schema_modules_by_dialect_id[jsonSchemaDialect] document_schema_module.new_jsi(instance, **new_param) end @@ -195,7 +191,7 @@ module Ext::Document set_up_document_schema_module(Ext::Document) document_name_subschemas(Ext::Document, Ext) - # note: without this mapping set, document_schema_module_by_dialect_id(Ext::MetaSchema.schema_uri) + # note: without this mapping set, document_schema_modules_by_dialect_id[Ext::MetaSchema.schema_uri] # would be Unscoped::Document.with_dynamic_scope_from(Ext::Unscoped::MetaSchema) # instead of Unscoped::Document.with_dynamic_scope_from(Ext::ExtDocument) # schemas in OADs with this jsonSchemaDialect would have the right dialect, but diff --git a/lib/scorpio/openapi/v3_2.rb b/lib/scorpio/openapi/v3_2.rb index 8ac8cf6a..50950e7b 100644 --- a/lib/scorpio/openapi/v3_2.rb +++ b/lib/scorpio/openapi/v3_2.rb @@ -62,17 +62,13 @@ def self.set_up_document_schema_module(document_schema_module) document_schema_module end - def self.document_schema_module_by_dialect_id(dialect_id) - document_schema_modules_by_dialect_id[JSI::Util.uri(dialect_id)] - end - # Instantiates `instance` v3.2 OAD with schemas of the dialect indicated by `jsonSchemaDialect` # @param instance [#to_hash] # @return [JSI::Base + Scorpio::OpenAPI::V3_2::Document] def self.new_document(instance, **new_param) #jsonSchemaDialect = Scorpio::OpenAPI::V3_2::Unscoped::Document.new_jsi(instance, **new_param).jsonSchemaDialect(use_default: true) jsonSchemaDialect = instance.fetch('jsonSchemaDialect') { Unscoped::Document.properties['jsonSchemaDialect'].default } - document_schema_module = document_schema_module_by_dialect_id(jsonSchemaDialect) + document_schema_module = document_schema_modules_by_dialect_id[jsonSchemaDialect] document_schema_module.new_jsi(instance, **new_param) end @@ -197,7 +193,7 @@ module Ext::Document set_up_document_schema_module(Ext::Document) document_name_subschemas(Ext::Document, Ext) - # note: without this mapping set, document_schema_module_by_dialect_id(Ext::MetaSchema.schema_uri) + # note: without this mapping set, document_schema_modules_by_dialect_id[Ext::MetaSchema.schema_uri] # would be Unscoped::Document.with_dynamic_scope_from(Ext::Unscoped::MetaSchema) # instead of Unscoped::Document.with_dynamic_scope_from(Ext::ExtDocument) # schemas in OADs with this jsonSchemaDialect would have the right dialect, but From aac881cb21c671628c63ea6f67333fdecbf239ef Mon Sep 17 00:00:00 2001 From: Ethan Date: Mon, 18 May 2026 21:46:45 -0700 Subject: [PATCH 59/66] OpenAPI::Operation#path_template_str raises, doesn't return nil, if unknown partially reverts c2090b75b267ff99a84f78ea753edc93c9b1ab05 --- lib/scorpio/openapi/operation.rb | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/scorpio/openapi/operation.rb b/lib/scorpio/openapi/operation.rb index c36bdaa7..4d3fc078 100644 --- a/lib/scorpio/openapi/operation.rb +++ b/lib/scorpio/openapi/operation.rb @@ -73,15 +73,19 @@ def v2? # @return [String] def path_template_str return @path_template_str if instance_variable_defined?(:@path_template_str) + @path_template_str = path_template_str_find || raise(NotImplementedError, -"could not determine path template for operation: #{self}") + end + + # @return [String, nil] + private def path_template_str_find path_item = jsi_ancestor_nodes.detect { |n| n.is_a?(Scorpio::OpenAPI::PathItem) } - @path_template_str = path_item && path_item.jsi_ptr.tokens.last + path_item && path_item.jsi_ptr.tokens.last end # the path as an Addressable::Template # @return [Addressable::Template] def path_template return @path_template if instance_variable_defined?(:@path_template) - return(@path_template = nil) if !path_template_str @path_template = Addressable::Template.new(path_template_str) end @@ -147,7 +151,7 @@ def tagged?(tag_name) # a short identifier for this operation appropriate for an error message # @return [String] def human_id - operationId || -"path: #{path_template_str}, method: #{http_method}" + operationId || -"path: #{path_template_str_find}, method: #{http_method}" end # @param status [String, Integer] @@ -245,7 +249,7 @@ def each_link_page(**configuration, &block) private def jsi_object_group_text - [*super, http_method, path_template_str].compact.freeze + [*super, http_method, path_template_str_find].compact.freeze end end From 90cbbef0786248b1206db13fa7fe244b081db0b0 Mon Sep 17 00:00:00 2001 From: Ethan Date: Thu, 26 Mar 2026 22:00:17 -0700 Subject: [PATCH 60/66] OpenAPI::Operation#response_schema m refactor --- lib/scorpio/openapi/operation.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/scorpio/openapi/operation.rb b/lib/scorpio/openapi/operation.rb index 4d3fc078..e5fc9621 100644 --- a/lib/scorpio/openapi/operation.rb +++ b/lib/scorpio/openapi/operation.rb @@ -314,9 +314,9 @@ def request_schemas def response_schema(status: , media_type: ) oa_response = self.oa_response(status: status) || return oa_media_types = oa_response['content'] || return # Scorpio::OpenAPI::V3_*::MediaTypes - oa_media_type = oa_media_types[media_type] # Scorpio::OpenAPI::V3_*::MediaType - oa_media_type ||= oa_media_types[-"#{::Ur::ContentType.new(media_type).type}/*"] - oa_media_type ||= oa_media_types['*/*'] || return + oa_media_type = oa_media_types[media_type] || + oa_media_types[-"#{::Ur::ContentType.new(media_type).type}/*"] || + oa_media_types['*/*'] || return # Scorpio::OpenAPI::V3_*::MediaType oa_schema = oa_media_type['schema'] || return # JSI::Schema, Scorpio::OpenAPI::V3_*::Schema JSI::Schema.ensure_schema(oa_schema) end From f705d4b7714a38bf3895fe981c28b3c5aa6af981 Mon Sep 17 00:00:00 2001 From: Ethan Date: Tue, 25 Apr 2023 15:58:33 -0700 Subject: [PATCH 61/66] ResourceBase::Container .inspect .to_s --- lib/scorpio/resource_base.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/scorpio/resource_base.rb b/lib/scorpio/resource_base.rb index 1b236f00..d16e2066 100644 --- a/lib/scorpio/resource_base.rb +++ b/lib/scorpio/resource_base.rb @@ -536,6 +536,16 @@ def new_container(object, openapi_document_class, options = {}) container_class.new(object, openapi_document_class, options) end + + # @return [String] + def inspect + -"(#{ResourceBase::Container.name} class)" + end + + # @return [String] + def to_s + inspect + end end end From 8f0e0f95e5e86f9ebd46775abf7098fd90264ae6 Mon Sep 17 00:00:00 2001 From: Ethan Date: Sun, 22 Feb 2026 04:48:49 -0800 Subject: [PATCH 62/66] ResourceBase::Container .inspect show schemas --- lib/scorpio/resource_base.rb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/scorpio/resource_base.rb b/lib/scorpio/resource_base.rb index d16e2066..93539eb0 100644 --- a/lib/scorpio/resource_base.rb +++ b/lib/scorpio/resource_base.rb @@ -513,6 +513,8 @@ class Container include(mod) end + define_singleton_method(:container_schemas) { schemas } + schemas.each do |schema| include(JSI::SchemaClasses.schema_property_reader_module(schema, conflicting_modules: modules + [Container])) include(JSI::SchemaClasses.schema_property_writer_module(schema, conflicting_modules: modules + [Container])) @@ -539,7 +541,13 @@ def new_container(object, openapi_document_class, options = {}) # @return [String] def inspect - -"(#{ResourceBase::Container.name} class)" + return super unless respond_to?(:container_schemas) + schema_names = container_schemas.map do |schema| + mod_name = schema.jsi_schema_module_name_from_ancestor + next -"#{mod_name} <#{schema.jsi_resource_uri}>" if mod_name && schema.jsi_resource_uri + mod_name || -"<#{schema.schema_uri || schema.jsi_ptr.uri}>" + end + -"(#{[superclass, *schema_names].join(' + ')})" end # @return [String] From 28ec05e7541883ff942a596703eeda8605a10d5a Mon Sep 17 00:00:00 2001 From: Ethan Date: Sat, 6 Jun 2026 02:22:59 -0700 Subject: [PATCH 63/66] test ResourceBase::Container to_s/inspect --- test/resource_base_test.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/resource_base_test.rb b/test/resource_base_test.rb index ae07aef6..6207bdf4 100644 --- a/test/resource_base_test.rb +++ b/test/resource_base_test.rb @@ -191,3 +191,13 @@ def resource(represented_schemas: nil, tag_name: nil) end end end + +describe("ResourceBase::Container class") do + describe(".to_s") do + it("shows schemas") do + Article.post('title' => "!") + articles = Article.index_with_root + assert_match(%r(\A\(Scorpio::ResourceBase::Container \+ <[^>]*>\)\z), articles.articles.class.to_s) + end + end +end From 6a51cf7a26ef14efa5813c976f6473d7d30f78fd Mon Sep 17 00:00:00 2001 From: Ethan Date: Sat, 23 May 2026 12:00:17 -0700 Subject: [PATCH 64/66] pages/Security link to OAS --- pages/Security.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/Security.md b/pages/Security.md index 3b1854b0..372e8586 100644 --- a/pages/Security.md +++ b/pages/Security.md @@ -1,6 +1,6 @@ # API Security -Scorpio does not currently implement an interface for any particular API security mechanism, which an OpenAPI description might specify using an operation's Security Requirement and corresponding Security Scheme. Scorpio offers flexibility in how applications may authenticate using common mechanisms - for more general guidance see {file:Request_Configuration Request Configuration}. +Scorpio does not currently implement an interface for any particular API security mechanism, which an OpenAPI description might specify using an operation's [Security Requirement](https://spec.openapis.org/oas/v3.2.0.html#security-requirement-object) and corresponding [Security Scheme](https://spec.openapis.org/oas/v3.2.0.html#security-scheme-object). Scorpio offers flexibility in how applications may authenticate using common mechanisms - for more general guidance see {file:Request_Configuration Request Configuration}. ### Authorization header From 273404e026ced4ed642ddd88cac903cc3b7d4726 Mon Sep 17 00:00:00 2001 From: Ethan Date: Tue, 2 Jun 2026 14:39:31 -0700 Subject: [PATCH 65/66] Request#initialize don't stringify_symbol_keys (configurables and parameters both work with symbols) --- lib/scorpio/request.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/scorpio/request.rb b/lib/scorpio/request.rb index 4963e7b4..f511897e 100644 --- a/lib/scorpio/request.rb +++ b/lib/scorpio/request.rb @@ -148,7 +148,6 @@ def logger # operation. def initialize(operation, **configuration, &b) @operation = operation - configuration = JSI::Util.stringify_symbol_keys(configuration) configuration.each do |name, value| if Configurables.public_method_defined?(:"#{name}=") public_send(:"#{name}=", value) From f174a3893a4c37154045620fe89eda3f686a0490 Mon Sep 17 00:00:00 2001 From: Ethan Date: Fri, 5 Jun 2026 14:08:19 -0700 Subject: [PATCH 66/66] Document::Configurables #faraday_adapter rm unneeded array --- lib/scorpio/openapi/document.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/scorpio/openapi/document.rb b/lib/scorpio/openapi/document.rb index 4ecb7b06..ede9c549 100644 --- a/lib/scorpio/openapi/document.rb +++ b/lib/scorpio/openapi/document.rb @@ -82,7 +82,7 @@ def faraday_builder attr_writer :faraday_adapter def faraday_adapter return @faraday_adapter if instance_variable_defined?(:@faraday_adapter) - [Faraday.default_adapter].freeze + Faraday.default_adapter end attr_writer :logger