From 513dc7b35e8395b2f458cb4f073d86aa5a2147bf Mon Sep 17 00:00:00 2001 From: peter scholz Date: Thu, 31 Aug 2023 12:27:04 +0200 Subject: [PATCH] Ignores possible version segments in path. (#37) --- lib/starter/import.rb | 2 +- lib/starter/importer/specification.rb | 25 +++--- spec/lib/import_spec.rb | 14 ++-- spec/lib/importer/specification_spec.rb | 100 +++++++++++++++++++++++- 4 files changed, 119 insertions(+), 22 deletions(-) diff --git a/lib/starter/import.rb b/lib/starter/import.rb index c59bda6..dcba7b4 100644 --- a/lib/starter/import.rb +++ b/lib/starter/import.rb @@ -23,7 +23,7 @@ def load_spec(path) JSON.load_file(path) end - Importer::Specification.new(spec) + Importer::Specification.new(spec:) end def create_files_from(spec) diff --git a/lib/starter/importer/specification.rb b/lib/starter/importer/specification.rb index 04b0dd5..87fd8d5 100644 --- a/lib/starter/importer/specification.rb +++ b/lib/starter/importer/specification.rb @@ -8,28 +8,25 @@ class Error < StandardError; end attr_accessor :openapi, :info, :paths, :components, :webhooks - def initialize(raw) + def initialize(spec:) # mandatory - @openapi = raw.fetch('openapi') - @info = raw.fetch('info') + @openapi = spec.fetch('openapi') + @info = spec.fetch('info') # in contrast to the spec, paths are required - @paths = raw.fetch('paths').except('/').sort.to_h + @paths = spec.fetch('paths').except('/').sort.to_h # optional -> not used atm - @components = raw.fetch('components', false) - @webhooks = raw.fetch('webhooks', false) + @components = spec.fetch('components', false) + @webhooks = spec.fetch('webhooks', false) end def namespaces validate_paths @namespaces ||= paths.keys.each_with_object({}) do |path, memo| - segments = path.split('/').delete_if(&:empty?) - namespace = segments.shift - rest_path = "/#{segments.join('/')}" + namespace, rest_path = segmentize(path) - # TODO: build additional stuff from `paths[path]` memo[namespace] ||= {} memo[namespace][rest_path] = prepare_verbs(paths[path]) end @@ -42,6 +39,14 @@ def validate_paths raise Error, 'only template given' if paths.keys.one? && paths.keys.first.match?(%r{/\{\w*\}}) end + def segmentize(path) + segments = path.split('/').delete_if(&:empty?) + ignore = segments.take_while { |x| x =~ /(v)*(\.\d)+/ || x =~ /(v\d)+/ || x == 'api' } + rest = segments - ignore + + [rest.shift, rest.empty? ? '/' : "/#{rest.join('/')}"] + end + def prepare_verbs(spec) path_params = nil spec.each_with_object({}) do |(verb, content), memo| diff --git a/spec/lib/import_spec.rb b/spec/lib/import_spec.rb index 9102215..7676d2b 100644 --- a/spec/lib/import_spec.rb +++ b/spec/lib/import_spec.rb @@ -1,15 +1,15 @@ # frozen_string_literal: false RSpec.describe Starter::Import do - describe '.do_it!' do - let(:path) { './spec/fixtures/links.json' } + # describe '.do_it!' do + # let(:path) { './spec/fixtures/links.json' } - subject { described_class.do_it!(path) } + # subject { described_class.do_it!(path) } - specify do - subject - end - end + # specify do + # subject + # end + # end describe '.load_spec' do subject { described_class.load_spec(path) } diff --git a/spec/lib/importer/specification_spec.rb b/spec/lib/importer/specification_spec.rb index 9d7f447..cfab91f 100644 --- a/spec/lib/importer/specification_spec.rb +++ b/spec/lib/importer/specification_spec.rb @@ -8,7 +8,7 @@ JSON.load_file('./spec/fixtures/tictactoe.json') end - subject { described_class.new(spec) } + subject { described_class.new(spec:) } describe 'mandatory attributes' do specify do @@ -67,7 +67,7 @@ end describe '#namespaces' do - subject { described_class.new(spec).namespaces } + subject { described_class.new(spec:).namespaces } describe 'paths empty -> valid, but senseless' do let(:spec) do @@ -130,18 +130,110 @@ describe 'unusual endpoints' do let(:spec) do - default_specification[:paths] = { '/' => {}, '/2.0/{c}' => {}, '/a-c' => {}, '/2.0/u' => {} } + default_specification[:paths] = { '/' => {}, '/u/{c}' => {}, '/a-c' => {}, '/u' => {} } default_specification.deep_stringify_keys end specify do expect(subject).to eql( { - '2.0' => { '/u' => {}, '/{c}' => {} }, + 'u' => { '/' => {}, '/{c}' => {} }, 'a-c' => { '/' => {} } } ) end end + + describe 'ignores possible versioning' do + let(:spec) do + default_specification[:paths] = { + '/' => {}, + '/2.0/a' => {}, + '/2.0/a/{c}' => {}, + '/v3/b' => {}, + '/v3/b/{c}' => {}, + '/api/v3/c' => {}, + '/api/v3/c/{c}' => {}, + '/api/v3.1/d' => {}, + '/api/v3.1/d/{c}' => {} + } + default_specification.deep_stringify_keys + end + + specify do + expect(subject).to eql( + { + 'a' => { '/' => {}, '/{c}' => {} }, + 'b' => { '/' => {}, '/{c}' => {} }, + 'c' => { '/' => {}, '/{c}' => {} }, + 'd' => { '/' => {}, '/{c}' => {} } + } + ) + end + end + end + + describe '#segmentizen' do + let(:spec) do + default_specification.deep_stringify_keys + end + + subject { described_class.new(spec:).send(:segmentize, path) } + + describe '/2.0/a' do + let(:path) { '/2.0/a' } + specify do + expect(subject).to match_array ['a', '/'] + end + end + + describe '/2.0/a/{c}' do + let(:path) { '/2.0/a/{c}' } + specify do + expect(subject).to match_array ['a', '/{c}'] + end + end + + describe '/v3/b' do + let(:path) { '/v3/b' } + specify do + expect(subject).to match_array ['b', '/'] + end + end + + describe '/v3/b/{c}' do + let(:path) { '/v3/b/{c}' } + specify do + expect(subject).to match_array ['b', '/{c}'] + end + end + + describe '/api/v3/c' do + let(:path) { '/api/v3/c' } + specify do + expect(subject).to match_array ['c', '/'] + end + end + + describe '/api/v3/c/{c}' do + let(:path) { '/api/v3/c/{c}' } + specify do + expect(subject).to match_array ['c', '/{c}'] + end + end + + describe '/api/v3.1/c' do + let(:path) { '/api/v3/c' } + specify do + expect(subject).to match_array ['c', '/'] + end + end + + describe '/api/v3.1/c/{c}' do + let(:path) { '/api/v3/c/{c}' } + specify do + expect(subject).to match_array ['c', '/{c}'] + end + end end end