Skip to content
This repository has been archived by the owner on Feb 25, 2021. It is now read-only.

Support $ref without a "file:" prefix #11

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions lib/open_api_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
require "open_api_parser/document"
require "open_api_parser/file_cache"
require "open_api_parser/pointer"
require "open_api_parser/reference"
require "open_api_parser/specification"
require "open_api_parser/specification/endpoint"
require "open_api_parser/specification/root"
Expand Down
42 changes: 13 additions & 29 deletions lib/open_api_parser/document.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,49 +19,33 @@ def resolve

private

def deeply_expand_refs(fragment, cur_path)
fragment, cur_path = expand_refs(fragment, cur_path)
def deeply_expand_refs(fragment, current_pointer)
fragment, current_pointer = expand_refs(fragment, current_pointer)

if fragment.is_a?(Hash)
fragment.reduce({}) do |hash, (k, v)|
hash.merge(k => deeply_expand_refs(v, "#{cur_path}/#{k}"))
hash.merge(k => deeply_expand_refs(v, "#{current_pointer}/#{k}"))
end
elsif fragment.is_a?(Array)
fragment.map { |e| deeply_expand_refs(e, cur_path) }
fragment.map { |e| deeply_expand_refs(e, current_pointer) }
else
fragment
end
end

def expand_refs(fragment, cur_path)
def expand_refs(fragment, current_pointer)
if fragment.is_a?(Hash) && fragment.key?("$ref")
ref = fragment["$ref"]

if ref.start_with?("file:")
expand_file(ref)
raw_uri = fragment["$ref"]
ref = OpenApiParser::Reference.new(raw_uri)
fully_resolved, referrent_document, referrent_pointer =
ref.resolve(@path, current_pointer, @content, @file_cache)
unless fully_resolved
expand_refs(referrent_document, referrent_pointer)
else
expand_pointer(ref, cur_path)
[referrent_document, referrent_pointer]
end
else
[fragment, cur_path]
end
end

def expand_file(ref)
relative_path = ref.split(":").last
absolute_path = File.expand_path(File.join("..", relative_path), @path)

Document.resolve(absolute_path, @file_cache)
end

def expand_pointer(ref, cur_path)
pointer = OpenApiParser::Pointer.new(ref)

if pointer.exists_in_path?(cur_path)
{ "$ref" => ref }
else
fragment = pointer.resolve(@content)
expand_refs(fragment, cur_path + pointer.escaped_pointer)
[fragment, current_pointer]
end
end
end
Expand Down
19 changes: 12 additions & 7 deletions lib/open_api_parser/pointer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,21 @@ def resolve(document)
end
end

def exists_in_path?(path)
path.include?(escaped_pointer)
def equal_or_ancestor_of?(other_pointer)
other_tokens = OpenApiParser::Pointer.new(other_pointer).escaped_pointer.split("/")
self_tokens = escaped_pointer.split("/")
perhaps_common_prefix = other_tokens[0...self_tokens.length]
perhaps_common_prefix == self_tokens
end

def escaped_pointer
if @raw_pointer.start_with?("#")
Addressable::URI.unencode(@raw_pointer[1..-1])
else
@raw_pointer
end
fragment =
if @raw_pointer.start_with?("#")
@raw_pointer[1..-1]
else
@raw_pointer
end
Addressable::URI.unencode(fragment)
end

private
Expand Down
54 changes: 54 additions & 0 deletions lib/open_api_parser/reference.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
module OpenApiParser
class Reference
def initialize(raw_uri)
@raw_uri = raw_uri
end

def resolve(base_path, base_pointer, current_document, file_cache)
ref_uri = normalize_file_uri(Addressable::URI.parse(@raw_uri))
base_uri = normalize_file_uri(Addressable::URI.parse(base_path)).omit(:fragment).normalize
resolved_uri = base_uri.join(ref_uri).omit(:fragment).normalize

fully_expanded, referenced_document, base_pointer =
case resolved_uri.scheme
when nil, 'file'
if base_uri == resolved_uri
[false, current_document, base_pointer]
else
[true, OpenApiParser::Document.resolve(resolved_uri.path, file_cache), '']
end
else
fail "$ref with scheme #{ref_uri.scheme} is not supported"
end

if !ref_uri.fragment.nil? && ref_uri.fragment != ''
resolve_pointer(ref_uri.fragment, base_pointer, referenced_document, fully_expanded)
else
[fully_expanded, referenced_document, '']
end
end

private

def normalize_file_uri(uri)
if uri.scheme == 'file' && uri.host.nil?
uri.merge(scheme: nil)
else
uri
end
end

def resolve_pointer(raw_pointer, base_pointer, within_document, fully_expanded)
pointer = OpenApiParser::Pointer.new(raw_pointer)

if pointer.equal_or_ancestor_of?(base_pointer)
referrent_document = { "$ref" => '#' + raw_pointer }
[true, referrent_document, base_pointer]
else
referrent_document = pointer.resolve(within_document)
referrent_pointer = pointer.escaped_pointer
[fully_expanded, referrent_document, referrent_pointer]
end
end
end
end
6 changes: 6 additions & 0 deletions spec/open_api_parser/document_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
expect(json["person"]).to eq({
"name" => "Drew"
})

expect(json["person_without_scheme"]).to eq({
"name" => "Drew"
})
end

it "resolves a mix of pointers and file references" do
Expand All @@ -32,6 +36,8 @@
expect(json["person"]["stats"]).to eq({
"age" => 34
})

expect(json["person_greeting"]).to eq("Drew")
end
end

Expand Down
26 changes: 14 additions & 12 deletions spec/open_api_parser/pointer_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,23 @@
describe "resolve" do
it "works with RFC examples" do
resolutions = {
"" => DOCUMENT,
"/foo" => ["bar", "baz"],
"/foo/0" => "bar",
"/" => 0,
"/a~1b" => 1,
"/c%d" => 2,
"/e^f" => 3,
"/g|h" => 4,
"/i\\j" => 5,
"/k\"l" => 6,
"/ " => 7,
"/m~0n" => 8,
"#" => DOCUMENT,
"#/foo" => ["bar", "baz"],
"#/foo/0" => "bar",
"#/" => 0,
"#/a~1b" => 1,
"#/c%d" => 2,
"#/e^f" => 3,
"#/g|h" => 4,
"#/i\\j" => 5,
"#/k\"l" => 6,
"#/ " => 7,
"#/m~0n" => 8,
}

resolutions.each do |pointer, expected|
expect(OpenApiParser::Pointer.new(pointer).resolve(DOCUMENT)).to eq(expected)
expect(OpenApiParser::Pointer.new(pointer[1..-1]).resolve(DOCUMENT)).to eq(expected)
end
end

Expand All @@ -54,6 +55,7 @@

resolutions.each do |pointer, expected|
expect(OpenApiParser::Pointer.new(pointer).resolve(DOCUMENT)).to eq(expected)
expect(OpenApiParser::Pointer.new(pointer[1..-1]).resolve(DOCUMENT)).to eq(expected)
end
end
end
Expand Down
Loading