diff --git a/lib/reynard/external.rb b/lib/reynard/external.rb index 54266a5..b3bd6ec 100644 --- a/lib/reynard/external.rb +++ b/lib/reynard/external.rb @@ -8,6 +8,7 @@ class External def initialize(path:, ref:) @path = path @relative_path, @anchor = ref.split('#', 2) + @filename = File.expand_path(@relative_path, @path) end def path @@ -22,11 +23,14 @@ def data end end + def filesystem_path + File.dirname(@filename) + end + private def filename - filename = File.expand_path(@relative_path, @path) - return filename if filename.start_with?(@path) + return @filename if @filename.start_with?(@path) raise 'You are only allowed to reference files relative to the specification file.' end diff --git a/lib/reynard/specification.rb b/lib/reynard/specification.rb index 868e0da..28be257 100644 --- a/lib/reynard/specification.rb +++ b/lib/reynard/specification.rb @@ -14,7 +14,7 @@ def initialize(filename:) # Digs a value out of the specification, taking $ref into account. def dig(*path) - dig_into(@data, @data, path.dup) + dig_into(@data, @data, path.dup, File.dirname(@filename)) end def servers @@ -103,7 +103,7 @@ def read # rubocop:disable Metrics/AbcSize # rubocop:disable Metrics/CyclomaticComplexity # rubocop:disable Metrics/MethodLength - def dig_into(data, cursor, path) + def dig_into(data, cursor, path, filesystem_path) while path.length.positive? cursor = cursor[path.first] return unless cursor @@ -118,7 +118,8 @@ def dig_into(data, cursor, path) cursor = data # References another file, with an optional anchor to an element in the data. when %r{\A\./} - external = External.new(path: File.dirname(@filename), ref: cursor['$ref']) + external = External.new(path: filesystem_path, ref: cursor['$ref']) + filesystem_path = external.filesystem_path path = external.path + path cursor = external.data end diff --git a/test/files/openapi/schemas/author.yml b/test/files/openapi/schemas/author.yml index cd68827..88211d3 100644 --- a/test/files/openapi/schemas/author.yml +++ b/test/files/openapi/schemas/author.yml @@ -8,4 +8,6 @@ properties: type: integer name: type: string + bio: + "$ref": "./bio.yml" additionalProperties: false diff --git a/test/files/openapi/schemas/bio.yml b/test/files/openapi/schemas/bio.yml new file mode 100644 index 0000000..a86d040 --- /dev/null +++ b/test/files/openapi/schemas/bio.yml @@ -0,0 +1,9 @@ +type: object +title: Bio +description: >- + Biographical details about an author. +required: [age] +properties: + age: + type: integer +additionalProperties: false diff --git a/test/reynard/object_builder_test.rb b/test/reynard/object_builder_test.rb index 910e329..1cd1f4c 100644 --- a/test/reynard/object_builder_test.rb +++ b/test/reynard/object_builder_test.rb @@ -61,11 +61,14 @@ def setup author = Reynard::ObjectBuilder.new( schema: schema, inflector: @inflector, - parsed_body: { id: 42, name: 'Jerry Writer' } + parsed_body: { + id: 42, name: 'Jerry Writer', bio: { age: 42 } + } ).call assert_model_name('Author', author) assert_equal 42, author.id assert_equal 'Jerry Writer', author.name + assert_equal 42, author.bio.age end end diff --git a/test/reynard/specification_test.rb b/test/reynard/specification_test.rb index 5db3dcf..6822071 100644 --- a/test/reynard/specification_test.rb +++ b/test/reynard/specification_test.rb @@ -234,6 +234,15 @@ def setup assert_equal 'integer', data end + test 'digs into an external file through a reference in an external file' do + data = @specification.dig( + 'paths', '/authors/{id}', 'get', 'responses', '200', + 'content', 'application/json', 'schema', + 'properties', 'bio', 'properties', 'age', 'type' + ) + assert_equal 'integer', data + end + test 'digs into an external file with an anchor' do data = @specification.dig( 'paths', '/authors/{id}', 'get', 'responses', 'default', @@ -242,7 +251,7 @@ def setup assert_equal %w[code message], data['required'] end - test 'digs into an external file through a refenence with with an anchor' do + test 'digs into an external file through a reference with with an anchor' do data = @specification.dig( 'paths', '/authors/{id}', 'get', 'responses', 'default', 'content', 'application/json', 'schema', 'required'