Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

## 4.0.1

- Fix: Reduced memory usage with large API descriptions. Schemas no longer each deep-copy (stringify keys) the whole OAD to build a root schema. This is a json_schemer specific optimization.

## 4.0.0

This release has no stricter or less strict request validation. It changes mostly internal stuff and adds a Sinatra integration. It's a major version, but it should be safe to upgrade.
Expand Down
124 changes: 62 additions & 62 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
openapi_first (4.0.0)
openapi_first (4.0.1)
drb (~> 2.0)
hana (~> 1.3)
json_schemer (>= 2.1, < 3.0)
Expand All @@ -12,66 +12,66 @@ GEM
specs:
action_text-trix (2.1.19)
railties
actioncable (8.1.3.1)
actionpack (= 8.1.3.1)
activesupport (= 8.1.3.1)
actioncable (8.1.4)
actionpack (= 8.1.4)
activesupport (= 8.1.4)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
zeitwerk (~> 2.6)
actionmailbox (8.1.3.1)
actionpack (= 8.1.3.1)
activejob (= 8.1.3.1)
activerecord (= 8.1.3.1)
activestorage (= 8.1.3.1)
activesupport (= 8.1.3.1)
actionmailbox (8.1.4)
actionpack (= 8.1.4)
activejob (= 8.1.4)
activerecord (= 8.1.4)
activestorage (= 8.1.4)
activesupport (= 8.1.4)
mail (>= 2.8.0)
actionmailer (8.1.3.1)
actionpack (= 8.1.3.1)
actionview (= 8.1.3.1)
activejob (= 8.1.3.1)
activesupport (= 8.1.3.1)
actionmailer (8.1.4)
actionpack (= 8.1.4)
actionview (= 8.1.4)
activejob (= 8.1.4)
activesupport (= 8.1.4)
mail (>= 2.8.0)
rails-dom-testing (~> 2.2)
actionpack (8.1.3.1)
actionview (= 8.1.3.1)
activesupport (= 8.1.3.1)
actionpack (8.1.4)
actionview (= 8.1.4)
activesupport (= 8.1.4)
nokogiri (>= 1.8.5)
rack (>= 2.2.4)
rack-session (>= 1.0.1)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
useragent (~> 0.16)
actiontext (8.1.3.1)
actiontext (8.1.4)
action_text-trix (~> 2.1.15)
actionpack (= 8.1.3.1)
activerecord (= 8.1.3.1)
activestorage (= 8.1.3.1)
activesupport (= 8.1.3.1)
actionpack (= 8.1.4)
activerecord (= 8.1.4)
activestorage (= 8.1.4)
activesupport (= 8.1.4)
globalid (>= 0.6.0)
nokogiri (>= 1.8.5)
actionview (8.1.3.1)
activesupport (= 8.1.3.1)
actionview (8.1.4)
activesupport (= 8.1.4)
builder (~> 3.1)
erubi (~> 1.11)
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
activejob (8.1.3.1)
activesupport (= 8.1.3.1)
activejob (8.1.4)
activesupport (= 8.1.4)
globalid (>= 0.3.6)
activemodel (8.1.3.1)
activesupport (= 8.1.3.1)
activerecord (8.1.3.1)
activemodel (= 8.1.3.1)
activesupport (= 8.1.3.1)
activemodel (8.1.4)
activesupport (= 8.1.4)
activerecord (8.1.4)
activemodel (= 8.1.4)
activesupport (= 8.1.4)
timeout (>= 0.4.0)
activestorage (8.1.3.1)
actionpack (= 8.1.3.1)
activejob (= 8.1.3.1)
activerecord (= 8.1.3.1)
activesupport (= 8.1.3.1)
activestorage (8.1.4)
actionpack (= 8.1.4)
activejob (= 8.1.4)
activerecord (= 8.1.4)
activesupport (= 8.1.4)
marcel (~> 1.0)
activesupport (8.1.3.1)
activesupport (8.1.4)
base64
bigdecimal
concurrent-ruby (~> 1.0, >= 1.3.1)
Expand All @@ -87,7 +87,7 @@ GEM
ast (2.4.3)
base64 (0.3.0)
benchmark (0.5.0)
bigdecimal (4.1.2)
bigdecimal (4.1.3)
builder (3.3.0)
concurrent-ruby (1.3.8)
connection_pool (3.0.2)
Expand All @@ -102,7 +102,7 @@ GEM
hana (1.3.7)
i18n (1.15.2)
concurrent-ruby (~> 1.0)
io-console (0.9.2)
io-console (0.9.4)
irb (1.18.0)
pp (>= 0.6.0)
prism (>= 1.3.0)
Expand Down Expand Up @@ -137,7 +137,7 @@ GEM
net-protocol
net-pop (0.1.2)
net-protocol
net-protocol (0.3.0)
net-protocol (0.4.0)
timeout
net-smtp (0.5.1)
net-protocol
Expand Down Expand Up @@ -167,30 +167,30 @@ GEM
rack (>= 1.3)
rackup (2.3.1)
rack (>= 3)
rails (8.1.3.1)
actioncable (= 8.1.3.1)
actionmailbox (= 8.1.3.1)
actionmailer (= 8.1.3.1)
actionpack (= 8.1.3.1)
actiontext (= 8.1.3.1)
actionview (= 8.1.3.1)
activejob (= 8.1.3.1)
activemodel (= 8.1.3.1)
activerecord (= 8.1.3.1)
activestorage (= 8.1.3.1)
activesupport (= 8.1.3.1)
rails (8.1.4)
actioncable (= 8.1.4)
actionmailbox (= 8.1.4)
actionmailer (= 8.1.4)
actionpack (= 8.1.4)
actiontext (= 8.1.4)
actionview (= 8.1.4)
activejob (= 8.1.4)
activemodel (= 8.1.4)
activerecord (= 8.1.4)
activestorage (= 8.1.4)
activesupport (= 8.1.4)
bundler (>= 1.15.0)
railties (= 8.1.3.1)
railties (= 8.1.4)
rails-dom-testing (2.3.0)
activesupport (>= 5.0.0)
minitest
nokogiri (>= 1.6)
rails-html-sanitizer (1.7.1)
loofah (~> 2.25, >= 2.25.2)
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
railties (8.1.3.1)
actionpack (= 8.1.3.1)
activesupport (= 8.1.3.1)
railties (8.1.4)
actionpack (= 8.1.4)
activesupport (= 8.1.4)
irb (~> 1.13)
rackup (>= 1.0.0)
rake (>= 12.2)
Expand All @@ -208,7 +208,7 @@ GEM
prism (>= 1.6.0)
rbs (>= 4.0.0)
tsort
regexp_parser (2.12.0)
regexp_parser (2.13.0)
reline (0.7.0)
io-console (~> 0.5)
rspec (3.13.2)
Expand Down Expand Up @@ -246,7 +246,7 @@ GEM
rubydex (0.4.1-arm64-darwin)
rubydex (0.4.1-x86_64-linux)
securerandom (0.4.1)
simplecov (1.2.0)
simplecov (1.3.1)
simpleidn (0.3.0)
sinatra (4.2.1)
logger (>= 1.6.0)
Expand All @@ -261,9 +261,9 @@ GEM
tsort (0.2.0)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
unicode-display_width (3.2.0)
unicode-emoji (~> 4.1)
unicode-emoji (4.2.0)
unicode-display_width (3.3.0)
unicode-emoji (~> 4.3)
unicode-emoji (4.3.0)
uri (1.1.1)
useragent (0.16.11)
websocket-driver (0.8.2)
Expand Down
8 changes: 4 additions & 4 deletions benchmarks/Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: ..
specs:
openapi_first (4.0.0)
openapi_first (4.0.1)
drb (~> 2.0)
hana (~> 1.3)
json_schemer (>= 2.1, < 3.0)
Expand All @@ -15,8 +15,8 @@ GEM
benchmark-ips (2.15.1)
benchmark-memory (0.2.0)
memory_profiler (~> 1)
bigdecimal (4.1.2)
committee (5.6.3)
bigdecimal (4.1.3)
committee (5.6.4)
json_schema (~> 0.14, >= 0.14.3)
openapi_parser (~> 2.0)
rack (>= 1.5)
Expand Down Expand Up @@ -48,7 +48,7 @@ GEM
rack-session (2.1.2)
base64 (>= 0.1.0)
rack (>= 3.0.0)
regexp_parser (2.12.0)
regexp_parser (2.13.0)
simpleidn (0.3.0)
sinatra (4.2.1)
logger (>= 1.6.0)
Expand Down
3 changes: 2 additions & 1 deletion lib/openapi_first.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
require_relative 'openapi_first/file_loader'
require_relative 'openapi_first/errors'
require_relative 'openapi_first/registry'
require_relative 'openapi_first/utils'
require_relative 'openapi_first/configuration'
require_relative 'openapi_first/child_configuration'
require_relative 'openapi_first/definition'
Expand Down Expand Up @@ -82,7 +83,7 @@ def self.load(filepath_or_definition, only: nil, path_prefix: nil, &)
# @return [Definition]
# TODO: This needs to work with unresolved contents as well
def self.parse(contents, only: nil, filepath: nil, path_prefix: nil, &)
contents = ::JSON.parse(::JSON.generate(contents)) # Deeply stringify keys, because of YAML. See https://github.com/ahx/openapi_first/issues/367
contents = Utils.deep_stringify_keys(contents) # Deeply stringify keys, because of YAML. See https://github.com/ahx/openapi_first/issues/367
contents['paths'].filter!(&->(key, _) { only.call(key) }) if only
Definition.new(contents, filepath, path_prefix, &)
end
Expand Down
10 changes: 9 additions & 1 deletion lib/openapi_first/file_loader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,20 @@ def load(file_path)
if extname == '.json'
::JSON.parse(body)
elsif ['.yaml', '.yml'].include?(extname)
YAML.unsafe_load(body)
load_yaml(body, file_path)
else
body
end
end
end
end

private

def load_yaml(body, file_path)
Utils.deep_stringify_keys(YAML.unsafe_load(body))
rescue ::JSON::GeneratorError => e
raise Error, "Could not load #{file_path.inspect}: #{e.message}"
end
end
end
13 changes: 12 additions & 1 deletion lib/openapi_first/ref_resolver.rb
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,17 @@ class Schema
vocabulary: { 'https://json-schema.org/draft/2020-12/vocab/core' => true }
)

# This class is here to monkey-patch JSONSchemer::Schema when we are passing
# an instance to sub-schemas. It skips stringifying all keys of the passed object to
# allocate fewer objects. We don't have to stringify the keys, because they are already stringified upstream.
# @visibility private
class DocumentRootSchema < JSONSchemer::Schema
private

def deep_stringify_keys(value) = value
end
private_constant :DocumentRootSchema

def initialize(value:, context:, base_uri:, options:)
@value = value
@context = context
Expand All @@ -197,7 +208,7 @@ def initialize(value:, context:, base_uri:, options:)

def schema
@schema ||= begin
root_schema = JSONSchemer::Schema.new(context, base_uri:, **options, meta_schema: DOCUMENT_META_SCHEMA)
root_schema = DocumentRootSchema.new(context, base_uri:, **options, meta_schema: DOCUMENT_META_SCHEMA)
apply_dialect(root_schema)
JSONSchemer::Schema.new(value, nil, root_schema, base_uri:, **options)
end
Expand Down
10 changes: 10 additions & 0 deletions lib/openapi_first/utils.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# frozen_string_literal: true

module OpenapiFirst
# @visibility private
module Utils
module_function

def deep_stringify_keys(contents) = ::JSON.parse(::JSON.generate(contents))
end
end
2 changes: 1 addition & 1 deletion lib/openapi_first/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# frozen_string_literal: true

module OpenapiFirst
VERSION = '4.0.0'
VERSION = '4.0.1'
end
19 changes: 19 additions & 0 deletions spec/file_loader_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,25 @@
expect(contents['openapi']).to eq('3.0.0')
end

it 'loads YAML keys as strings' do
Tempfile.create(['codes', '.yaml']) do |file|
file.write("200:\n description: ok\n")
file.flush

expect(described_class.load(file.path)).to eq({ '200' => { 'description' => 'ok' } })
end
end

it 'names the file when YAML contains a value that cannot be represented' do
Tempfile.create(['limits', '.yaml']) do |file|
file.write("maximum: .inf\n")
file.flush

expect { described_class.load(file.path) }
.to raise_error(OpenapiFirst::Error, /\ACould not load "#{Regexp.escape(file.path)}": Infinity/)
end
end

it 'loads .json' do
contents = described_class.load('./spec/data/petstore.json')
expect(contents['openapi']).to eq('3.0.0')
Expand Down
Loading
Loading