From 8bba8577d2b1b76dfa3a823537dab9abe8bddd8a Mon Sep 17 00:00:00 2001 From: Eguzki Astiz Lezaun Date: Tue, 18 Dec 2018 12:51:32 +0100 Subject: [PATCH] openapi: load oas resource before parsing with swagger parser --- .../commands/import_command/openapi.rb | 4 +- .../import_command/openapi/resource_reader.rb | 32 ++++---- .../openapi/resource_reader_spec.rb | 81 +++++++++++++++---- .../commands/import_command/openapi_spec.rb | 10 +-- 4 files changed, 85 insertions(+), 42 deletions(-) diff --git a/lib/3scale_toolbox/commands/import_command/openapi.rb b/lib/3scale_toolbox/commands/import_command/openapi.rb index debd83da..d958bd9f 100644 --- a/lib/3scale_toolbox/commands/import_command/openapi.rb +++ b/lib/3scale_toolbox/commands/import_command/openapi.rb @@ -57,11 +57,11 @@ def create_context end def load_openapi - Swagger.build(*openapi_resource(arguments[:openapi_resource])) + Swagger.build(load_resource(arguments[:openapi_resource])) # Disable validation step because https://petstore.swagger.io/v2/swagger.json # does not pass validation. Maybe library's schema is outdated? # openapi.tap(&:validate) - rescue Swagger::InvalidDefinition, Hashie::CoercionError, JSON::ParserError, Psych::SyntaxError => e + rescue Swagger::InvalidDefinition, Hashie::CoercionError, Psych::SyntaxError => e raise ThreeScaleToolbox::Error, "OpenAPI schema validation failed: #{e.message}" end end diff --git a/lib/3scale_toolbox/commands/import_command/openapi/resource_reader.rb b/lib/3scale_toolbox/commands/import_command/openapi/resource_reader.rb index 70dd72ec..8d755b5e 100644 --- a/lib/3scale_toolbox/commands/import_command/openapi/resource_reader.rb +++ b/lib/3scale_toolbox/commands/import_command/openapi/resource_reader.rb @@ -6,15 +6,20 @@ module Commands module ImportCommand module OpenAPI module ResourceReader + ## + # Load resource from different types of sources. + # Supported types are: file, URL, stdin + # Loaded content is returned + def load_resource(resource) + # Json format is parsed as well + YAML.safe_load(read_content(resource)) + end + ## # Reads resources from different types of sources. # Supported types are: file, URL, stdin - # Return type is - # [content, format] where - # content: raw content - # format: Hash with single key: `format`. Value can be `:json` or `:yaml` - # format example: { format: :json } - def openapi_resource(resource) + # Resource raw content is returned + def read_content(resource) case resource when '-' method(:read_stdin) @@ -27,24 +32,15 @@ def openapi_resource(resource) # Detect format from file extension def read_file(resource) - [File.read(resource), { format: File.extname(resource) }] + File.read(resource) end def read_stdin(_resource) - content = STDIN.read - # will try parse json, otherwise yaml - format = :json - begin - JSON.parse(content) - rescue JSON::ParserError - format = :yaml - end - [content, { format: format }] + STDIN.read end def read_url(resource) - uri = URI.parse(resource) - [Net::HTTP.get(uri), { format: File.extname(uri.path) }] + Net::HTTP.get(URI.parse(resource)) end end end diff --git a/spec/unit/commands/import_command/openapi/resource_reader_spec.rb b/spec/unit/commands/import_command/openapi/resource_reader_spec.rb index 1fa39aba..377baf81 100644 --- a/spec/unit/commands/import_command/openapi/resource_reader_spec.rb +++ b/spec/unit/commands/import_command/openapi/resource_reader_spec.rb @@ -1,30 +1,80 @@ require '3scale_toolbox' -RSpec.shared_examples 'parsed content' do - let(:result) { subject.openapi_resource(resource) } +RSpec.shared_examples 'content is read' do + let(:result) { subject.read_content(resource) } it 'does not return nil' do expect(result).not_to be_nil - expect(result.size).to eq(2) end it 'is read' do - expect(result[0]).to eq(content) - end - - it 'has correct format' do - expect(result[1]).to include(expected_format) + expect(result).to eq(content) end end RSpec.describe 'OpenAPI ResourceReader' do include_context :temp_dir - context '#openapi_resource' do - subject do - Class.new { include ThreeScaleToolbox::Commands::ImportCommand::OpenAPI::ResourceReader }.new + subject do + Class.new { include ThreeScaleToolbox::Commands::ImportCommand::OpenAPI::ResourceReader }.new + end + + context '#load_resource' do + let(:resource) { tmp_dir.join('petstore.file').tap { |conf| conf.write(content) } } + let(:result) { subject.load_resource(resource) } + + context 'valid json' do + let(:content) { '{ "some_key": "some value" }' } + + it 'does not return nil' do + expect(result).not_to be_nil + end + + it 'is loaded' do + expect(result).to eq('some_key' => 'some value') + end + end + + context 'valid yaml' do + let(:content) do + <<~YAML + --- + some_key: "some value" + YAML + end + + it 'does not return nil' do + expect(result).not_to be_nil + end + + it 'is loaded' do + expect(result).to eq('some_key' => 'some value') + end end + context 'invalid yaml' do + let(:content) do + <<~YAML + --- + ` + YAML + end + + it 'raises error' do + expect { result }.to raise_error(Psych::SyntaxError) + end + end + + context 'invalid json' do + let(:content) { '{ `some }' } + + it 'raises error' do + expect { result }.to raise_error(Psych::SyntaxError) + end + end + end + + context '#read_content' do let(:content) do <<~YAML --- @@ -34,31 +84,28 @@ context 'from file' do let(:resource) { tmp_dir.join('petstore.yaml').tap { |conf| conf.write(content) } } - let(:expected_format) { { format: '.yaml' } } - it_behaves_like 'parsed content' + it_behaves_like 'content is read' end context 'from URL' do let(:resource) { 'https://example.com/petstore.yaml' } - let(:expected_format) { { format: '.yaml' } } before :each do net_class_stub = class_double(Net::HTTP).as_stubbed_const expect(net_class_stub).to receive(:get).and_return(content) end - it_behaves_like 'parsed content' + it_behaves_like 'content is read' end context 'from stdin' do let(:resource) { '-' } - let(:expected_format) { { format: :yaml } } before :each do expect(STDIN).to receive(:read).and_return(content) end - it_behaves_like 'parsed content' + it_behaves_like 'content is read' end end end diff --git a/spec/unit/commands/import_command/openapi_spec.rb b/spec/unit/commands/import_command/openapi_spec.rb index 0a89aa4a..7bdf23ed 100644 --- a/spec/unit/commands/import_command/openapi_spec.rb +++ b/spec/unit/commands/import_command/openapi_spec.rb @@ -1,21 +1,21 @@ require '3scale_toolbox' RSpec.describe ThreeScaleToolbox::Commands::ImportCommand::OpenAPI::OpenAPISubcommand do + include_context :temp_dir include_context :resources - let(:arguments) { { 'openapi_resource': 'some_resource' } } + let(:arguments) { { 'openapi_resource': oas_resource } } let(:options) { { 'destination': 'https://destination_key@destination.example.com' } } subject { described_class.new(options, arguments, nil) } - let(:oas_resource) { [oas_content, { format: :yaml }] } context 'valid openapi content' do + let(:oas_resource) { File.join(resources_path, 'valid_swagger.yaml') } + context '#run' do - let(:oas_content) { File.read(File.join(resources_path, 'valid_swagger.yaml')) } let(:api_spec) { double('api_spec') } let(:remote) { double('remote') } before :each do - expect(subject).to receive(:openapi_resource).and_return(oas_resource) threescale_api_spec_stub = class_double(ThreeScaleToolbox::Commands::ImportCommand::OpenAPI::ThreeScaleApiSpec).as_stubbed_const expect(threescale_api_spec_stub).to receive(:new).and_return(api_spec) expect_any_instance_of(ThreeScaleToolbox::Remotes).to receive(:remote).and_return(remote) @@ -50,10 +50,10 @@ desSSSSScription: "Invalid description tag" YAML end + let(:oas_resource) { tmp_dir.join('invalid.yaml').tap { |conf| conf.write(oas_content) } } context '#run' do it 'raises error' do - expect(subject).to receive(:openapi_resource).and_return(oas_resource) expect { subject.run }.to raise_error(ThreeScaleToolbox::Error, /OpenAPI schema validation failed/) end