openapi-sourcetools 0.8.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 783a57d4ca224bb9710cb523d3ee2fd2dee0e821cb7382ffc32fa9b8cb36b4f1
4
- data.tar.gz: b7c02ac6cd2e30fc4f9f5d9fb87bfcd84b8f60b76007e82d82a1a1bfb7f37e42
3
+ metadata.gz: 93117ebfa33920b762da40afe48264ead793574ad336628487d3cfb3b6019987
4
+ data.tar.gz: 67cb3400cd879b3625e58b72db11cfc860a87179d069ff341a6202391f8d61f5
5
5
  SHA512:
6
- metadata.gz: eec2859f712324b85f9db32f5c92d4e3947a80021f9bf920f287f9b01a2a56ab7d20e3f2b5d30e039a2e606e57b4ca0f0b5872961e91f1898c44c402eade14ef
7
- data.tar.gz: 72bbafa47688fd1eb3dd78d1ff0782d8e1a3510e541e4ad4f4e49ceec056cedaf9c6ebbbbe4dd9c7c6b446f3c0dd4c22d54a23afb0bacfa8f5dea33fe751e55a
6
+ metadata.gz: af4681c719424375326d4149ede787f0d6a58d33ff28082d7c1f7b00db4b40186f6406bfe99707012aeae0ac03d4504fb14cd49b6e587f55d91c1f2cb353a3b3
7
+ data.tar.gz: f6c84c0a9e6b20040fefa9611c8abf211abc1fb65c6a3c3041ce1f96f3b3482fa1789eb2eb16460c5a1d892d56fd843164ae4d7dfb9c4cdb6e3a13f26977d323
@@ -0,0 +1,108 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Copyright © 2025 Ismo Kärkkäinen
5
+ # Licensed under Universal Permissive License. See LICENSE.txt.
6
+
7
+ require_relative '../lib/openapi/sourcetools/apiobjects'
8
+ require_relative '../lib/openapi/sourcetools/common'
9
+ require 'optparse'
10
+ include OpenAPISourceTools
11
+
12
+
13
+ def add_combinations(combinations, security)
14
+ security.each do |s|
15
+ subset = s.keys.sort!
16
+ next if subset.empty?
17
+ combinations.add(subset)
18
+ end
19
+ end
20
+
21
+ def gather_security(doc)
22
+ combinations = Set.new
23
+ security = doc.fetch('security', [])
24
+ add_combinations(combinations, security)
25
+ operation_objects = []
26
+ doc.fetch('paths', {}).each_value do |item|
27
+ oos = ApiObjects.operation_objects(item).values
28
+ oos.each do |oo|
29
+ add_combinations(combinations, oo['security'] || [])
30
+ operation_objects.push(oo)
31
+ end
32
+ end
33
+ [security, operation_objects, combinations]
34
+ end
35
+
36
+ def check_security(doc, combinations)
37
+ singles = Set.new
38
+ combinations.each do |c|
39
+ c.each do |s|
40
+ singles.add(s)
41
+ end
42
+ end
43
+ available = Set.new((doc.dig('components', 'securitySchemes') || {}).keys)
44
+ missing = false
45
+ singles.each do |s|
46
+ unless available.include?(s)
47
+ $stderr.puts "Security scheme unavailable: #{s}"
48
+ missing = true
49
+ end
50
+ end
51
+ !missing
52
+ end
53
+
54
+ def namelist2security(item)
55
+ item.keys.sort!.join(' ')
56
+ end
57
+
58
+ def security2hash(security)
59
+ out = {}
60
+ security.each { |s| out[namelist2security(s)] = s }
61
+ out
62
+ end
63
+
64
+ def add_security_to_operations(operation_objects, top_level_security)
65
+ operation_objects.each do |oo|
66
+ sec = security2hash(oo['security'] || top_level_security)
67
+ oo['security'] = sec.keys.sort!.map { |k| Marshal.load(Marshal.dump(sec[k])) }
68
+ end
69
+ end
70
+
71
+ def main
72
+ input_name = nil
73
+ output_name = nil
74
+
75
+ parser = OptionParser.new do |opts|
76
+ opts.summary_indent = ' '
77
+ opts.summary_width = 26
78
+ opts.banner = 'Usage: openapi-addsecurityschemes [options]'
79
+ opts.separator ''
80
+ opts.separator 'Options:'
81
+ opts.on('-i', '--input FILE', 'Read API spec from FILE, not stdin.') do |f|
82
+ input_name = f
83
+ end
84
+ opts.on('-o', '--output FILE', 'Output to FILE, not stdout.') do |f|
85
+ output_name = f
86
+ end
87
+ opts.on('-h', '--help', 'Print this help and exit.') do
88
+ $stdout.puts %(#{opts}
89
+
90
+ Loads API document in OpenAPI format and moves security schemes under components and
91
+ replaces the original with reference.
92
+ )
93
+ exit 0
94
+ end
95
+ end
96
+ parser.order!
97
+
98
+ doc = Common.load_source(input_name)
99
+ return 2 if doc.nil?
100
+
101
+ security, operation_objects, combinations = gather_security(doc)
102
+ return 4 unless check_security(doc, combinations)
103
+ add_security_to_operations(operation_objects, security)
104
+
105
+ Common.dump_result(output_name, doc, 3)
106
+ end
107
+
108
+ exit(main) unless defined?($unit_test)
data/bin/openapi-generate CHANGED
@@ -18,7 +18,7 @@ def main
18
18
 
19
19
  parser = OptionParser.new do |opts|
20
20
  opts.summary_indent = ' '
21
- opts.summary_width = 26
21
+ opts.summary_width = 20
22
22
  opts.banner = 'Usage: openapi-generate [options] generator-names...'
23
23
  opts.separator ''
24
24
  opts.separator 'Options:'
@@ -37,7 +37,7 @@ Loads API document in OpenAPI format and generator names. Built-in generator
37
37
  or additional document loaders accept the following:
38
38
  #{OpenAPISourceTools::Loaders.document.strip}
39
39
 
40
- During load each generator can add and modify tasks via Gen module:
40
+ During load each generator can add and modify tasks via Gen singleton:
41
41
  #{Gen.document.strip}
42
42
 
43
43
  After all generators have loaded succesfully, tasks are run.
@@ -48,10 +48,10 @@ After all generators have loaded succesfully, tasks are run.
48
48
  parser.order!
49
49
 
50
50
  return OpenAPISourceTools::Common.aargh('Generator names must be given.', 1) if ARGV.empty?
51
+ return OpenAPISourceTools::Common.aargh("Not a directory: #{output_dir}", 3) unless File.directory?(output_dir)
51
52
 
52
53
  a = OpenAPISourceTools::Common.load_source(input_name)
53
54
  return 2 if a.nil?
54
- return OpenAPISourceTools::Common.aargh("Not a directory: #{output_dir}", 3) unless File.directory?(output_dir)
55
55
  gen = OpenAPISourceTools::Generator.new(a, input_name, output_dir, config_prefix)
56
56
  ec = gen.load(ARGV)
57
57
  return ec unless ec.zero?
@@ -9,97 +9,77 @@ require_relative 'docs'
9
9
  require_relative 'output'
10
10
  require_relative 'config'
11
11
  require 'deep_merge'
12
-
12
+ require 'singleton'
13
13
 
14
14
  # The generation module that contains things visible to tasks.
15
- module Gen
15
+ class Gen
16
+ include Singleton
17
+
18
+ @docsrc = []
16
19
  def self.add_doc(symbol, docstr)
17
- return if docstr.nil?
18
- @docsrc = [] unless instance_variable_defined?('@docsrc')
19
20
  @docsrc.push("- #{symbol} : #{docstr}")
20
21
  end
21
22
  private_class_method :add_doc
22
23
 
23
- def self.read_attr(symbol, default)
24
- return if symbol.nil?
24
+ def self.attrib_reader(symbol, docstr, initial_value = nil)
25
+ add_doc(symbol, docstr)
25
26
  attr_reader(symbol)
26
- module_function(symbol)
27
- instance_variable_set("@#{symbol}", default)
27
+ instance_eval("def #{symbol}; Gen.instance.#{symbol}; end")
28
+ Gen.instance.instance_variable_set("@#{symbol}", initial_value) unless initial_value.nil?
28
29
  end
29
- private_class_method :read_attr
30
+ private_class_method :attrib_reader
30
31
 
31
- def self.mod_attr2_reader(symbol, symbol2, docstr = nil, default = nil)
32
- read_attr(symbol, default)
33
- read_attr(symbol2, default)
32
+ def self.attrib_accessor(symbol, docstr, initial_value = nil)
34
33
  add_doc(symbol, docstr)
35
- end
36
- private_class_method :mod_attr2_reader
37
-
38
- def self.mod_attr_reader(symbol, docstr = nil, default = nil)
39
- mod_attr2_reader(symbol, nil, docstr, default)
40
- end
41
- private_class_method :mod_attr_reader
42
-
43
- def self.rw_attr(symbol, default)
44
34
  attr_accessor(symbol)
45
- module_function(symbol)
46
- s = symbol.to_s
47
- module_function((s + '=').to_sym)
48
- instance_variable_set("@#{s}", default)
35
+ instance_eval("def #{symbol}; Gen.instance.#{symbol}; end")
36
+ instance_eval("def #{symbol}=(v); Gen.instance.#{symbol} = v; end")
37
+ Gen.instance.instance_variable_set("@#{symbol}", initial_value) unless initial_value.nil?
49
38
  end
50
- private_class_method :rw_attr
51
-
52
- def self.mod_attr2_accessor(symbol, symbol2, docstr = nil, default = nil)
53
- rw_attr(symbol, default)
54
- rw_attr(symbol2, default) unless symbol2.nil?
55
- add_doc(symbol, docstr)
56
- end
57
- private_class_method :mod_attr2_accessor
58
-
59
- def self.mod_attr_accessor(symbol, docstr = nil, default = nil)
60
- mod_attr2_accessor(symbol, nil, docstr, default)
61
- end
62
- private_class_method :mod_attr_accessor
63
-
64
- mod_attr_reader :doc, 'OpenAPI document.'
65
- mod_attr_reader :outdir, 'Output directory name.'
66
- mod_attr_reader :d, 'Other documents object.', OpenAPISourceTools::Docs.new
67
- mod_attr_reader :wd, 'Original working directory', Dir.pwd
68
- mod_attr_reader :configuration, 'Generator internal configuration'
69
- mod_attr_accessor :config, 'Configuration file name for next gem or Ruby file.'
70
- mod_attr_accessor :separator, 'Key separator in config file names.', nil
71
- mod_attr_accessor :in_name, 'OpenAPI document name, nil if stdin.'
72
- mod_attr_accessor :in_basename, 'OpenAPI document basename, nil if stdin.'
73
- mod_attr_reader :g, 'Hash for storing values visible to all tasks.', {}
74
- mod_attr_accessor :x, 'Hash for storing values visible to tasks from processor.', {}
75
- mod_attr_accessor :h, 'Instance of class with helper methods.'
76
- mod_attr_accessor :tasks, 'Tasks array.', []
77
- mod_attr2_accessor :task, :t, 'Current task instance.'
78
- mod_attr_accessor :task_index, 'Current task index.'
79
- mod_attr_accessor :loaders, 'Array of processor loader methods.', []
80
- mod_attr_accessor :output, 'Output-formatting helper.', OpenAPISourceTools::Output.new
81
-
82
- def self.load_config(config_prefix)
39
+ private_class_method :attrib_accessor
40
+
41
+ attrib_reader :doc, 'OpenAPI document.'
42
+ attrib_reader :outdir, 'Output directory name.'
43
+ attrib_reader :d, 'Other documents object.', OpenAPISourceTools::Docs.new
44
+ attrib_reader :wd, 'Original working directory', Dir.pwd
45
+ attrib_reader :configuration, 'Generator internal configuration'
46
+ attrib_accessor :config, 'Configuration file name for next gem or Ruby file.'
47
+ attrib_accessor :separator, 'Key separator in config file names.'
48
+ attrib_accessor :in_name, 'OpenAPI document name, nil if stdin.'
49
+ attrib_accessor :in_basename, 'OpenAPI document basename, nil if stdin.'
50
+ attrib_reader :g, 'Hash for storing values visible to all tasks.', {}
51
+ attrib_accessor :x, 'Hash for storing values visible to tasks from processor.', {}
52
+ attrib_accessor :h, 'Instance of class with helper methods.'
53
+ attrib_accessor :tasks, 'Tasks array.', []
54
+ attrib_accessor :t, 'Current task instance.'
55
+ attrib_accessor :task_index, 'Current task index.'
56
+ attrib_accessor :loaders, 'Array of processor loader methods.', []
57
+ attrib_accessor :output, 'Output-formatting helper.', OpenAPISourceTools::Output.new
58
+
59
+ def self.load_config(name_prefix, extensions = [ '.*' ])
83
60
  cfg = {}
84
- cfgs = OpenAPISourceTools::ConfigLoader.find_files(name_prefix: config_prefix)
61
+ cfgs = OpenAPISourceTools::ConfigLoader.find_files(name_prefix:, extensions:)
85
62
  cfgs = OpenAPISourceTools::ConfigLoader.read_contents(cfgs)
86
63
  cfgs.each { |c| cfg.deep_merge!(c) }
87
64
  cfg
88
65
  end
89
- private_class_method :load_config
90
66
 
91
- def self.setup(document_content, input_name, output_directory, config_prefix)
67
+ def setup(document_content, input_name, output_directory, config_prefix)
92
68
  @doc = document_content
93
69
  @outdir = output_directory
94
70
  unless input_name.nil?
95
71
  @in_name = File.basename(input_name)
96
72
  @in_basename = File.basename(input_name, '.*')
97
73
  end
98
- @configuration = load_config(config_prefix)
74
+ @configuration = Gen.load_config(config_prefix)
99
75
  add_task(task: OpenAPISourceTools::HelperTask.new)
100
76
  end
101
77
 
102
- def self.add_task(task:, name: nil, executable: false, x: nil)
78
+ def self.setup(document_content, input_name, output_directory, config_prefix)
79
+ Gen.instance.setup(document_content, input_name, output_directory, config_prefix)
80
+ end
81
+
82
+ def add_task(task:, name: nil, executable: false, x: nil)
103
83
  @tasks.push(task)
104
84
  # Since this method allows the user to pass their own task type instance,
105
85
  # assign optional values with defaults only when clearly given.
@@ -108,15 +88,27 @@ module Gen
108
88
  @tasks.last.x = x unless x.nil?
109
89
  end
110
90
 
111
- def self.add_write_content(name:, content:, executable: false)
91
+ def self.add_task(task:, name: nil, executable: false, x: nil)
92
+ Gen.instance.add_task(task:, name:, executable:, x:)
93
+ end
94
+
95
+ def add_write_content(name:, content:, executable: false)
112
96
  add_task(task: OpenAPISourceTools::WriteTask.new(name, content, executable))
113
97
  end
114
98
 
115
- def self.add(source:, template: nil, template_name: nil, name: nil, executable: false, x: nil)
99
+ def self.add_write_content(name:, content:, executable: false)
100
+ Gen.instance.add_write_content(name:, content:, executable:)
101
+ end
102
+
103
+ def add(source:, template: nil, template_name: nil, name: nil, executable: false, x: nil)
116
104
  add_task(task: OpenAPISourceTools::Task.new(source, template, template_name),
117
105
  name:, executable:, x:)
118
106
  end
119
107
 
108
+ def self.add(source:, template: nil, template_name: nil, name: nil, executable: false, x: nil)
109
+ Gen.instance.add(source:, template:, template_name:, name:, executable:, x:)
110
+ end
111
+
120
112
  def self.document
121
113
  @docsrc.join("\n") + %(
122
114
  - add_task(task:, name: nil, executable: false, x: nil) : Adds task object.
@@ -78,7 +78,6 @@ module OpenAPISourceTools
78
78
  Gen.task_index = 0
79
79
  while Gen.task_index < Gen.tasks.size
80
80
  Gen.t = Gen.tasks[Gen.task_index]
81
- Gen.task = Gen.t
82
81
  out = generate(Gen.t)
83
82
  Gen.task_index += 1
84
83
  return out if out.is_a?(Integer)
@@ -4,6 +4,7 @@
4
4
  # Licensed under Universal Permissive License. See LICENSE.txt.
5
5
 
6
6
  require_relative 'task'
7
+ require_relative 'gen'
7
8
 
8
9
 
9
10
  # Original loader functions. These are accessible via Gen.loaders. New loaders
@@ -33,19 +34,17 @@ module OpenAPISourceTools
33
34
  true
34
35
  end
35
36
 
36
- REREQ_PREFIX = 'rereq:'
37
+ EVAL_PREFIX = 'eval:'
37
38
 
38
- def self.rereq_loader(name)
39
- return false unless name.downcase.start_with?(REREQ_PREFIX)
39
+ def self.eval_loader(name)
40
+ return false unless name.downcase.start_with?(EVAL_PREFIX)
40
41
  begin
41
42
  t = OpenAPISourceTools::RestoreProcessorStorage.new({})
42
43
  Gen.tasks.push(t)
43
- code = name.slice(REREQ_PREFIX.size...name.size)
44
+ code = name.slice(EVAL_PREFIX.size...name.size)
44
45
  eval(code)
45
46
  Gen.config = nil
46
47
  t.x = Gen.x # In case setup code replaced the object.
47
- rescue LoadError => e
48
- raise StandardError, "Failed to require again #{name}\n#{e}"
49
48
  rescue Exception => e
50
49
  raise StandardError, "Problem with #{name}\n#{e}\n#{e.backtrace.join("\n")}"
51
50
  end
@@ -64,11 +63,11 @@ module OpenAPISourceTools
64
63
  Gen.tasks.push(t)
65
64
  base = File.basename(name)
66
65
  Gen.config = base[0..-4] if Gen.config.nil?
67
- require(File.join(Dir.pwd, base))
66
+ load(File.join(Dir.pwd, base))
68
67
  Gen.config = nil
69
68
  t.x = Gen.x # In case setup code replaced the object.
70
69
  rescue LoadError => e
71
- raise StandardError, "Failed to require #{name}\n#{e}"
70
+ raise StandardError, "Failed to load #{name}\n#{e}"
72
71
  rescue Exception => e
73
72
  raise StandardError, "Problem with #{name}\n#{e}\n#{e.backtrace.join("\n")}"
74
73
  end
@@ -139,7 +138,7 @@ module OpenAPISourceTools
139
138
  def self.loaders
140
139
  [
141
140
  method(:req_loader),
142
- method(:rereq_loader),
141
+ method(:eval_loader),
143
142
  method(:ruby_loader),
144
143
  method(:yaml_loader),
145
144
  method(:bin_loader),
@@ -151,7 +150,7 @@ module OpenAPISourceTools
151
150
  def self.document
152
151
  <<EOB
153
152
  - #{Loaders::REQ_PREFIX}req_name : requires the gem.
154
- - #{Loaders::REREQ_PREFIX}code : runs code to add gem tasks again.
153
+ - #{Loaders::EVAL_PREFIX}code : runs code to add gem tasks again.
155
154
  - ruby_file#{Loaders::RUBY_EXT} : changes to Ruby file directory and requires the file.
156
155
  - #{Loaders::YAML_PREFIX}name:filename : Loads YAML file into Gen.d.name.
157
156
  - name:filename.{#{(Loaders::YAML_EXTS.map { |s| s[1...s.size] }).join('|')}} : Loads YAML file into Gen.d.name.
@@ -5,7 +5,7 @@
5
5
 
6
6
  module OpenAPISourceTools
7
7
  NAME = 'openapi-sourcetools'
8
- VERSION = '0.8.0'
8
+ VERSION = '0.8.1'
9
9
 
10
10
  def self.info(separator = ': ')
11
11
  "#{NAME}#{separator}#{VERSION}"
@@ -6,6 +6,7 @@
6
6
  require_relative 'sourcetools/task'
7
7
  require_relative 'sourcetools/config'
8
8
  require_relative 'sourcetools/version'
9
+ require_relative 'sourcetools/apiobjects'
9
10
  # Other modules or classes are exposed via Gen attributes as class instances as needed.
10
11
  # Docs is only needed for run-time storage of whatever loaders can handle.
11
12
  # Loaders array is exposed and can be added to at run-time.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: openapi-sourcetools
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.8.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ismo Kärkkäinen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-01-28 00:00:00.000000000 Z
11
+ date: 2025-01-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: deep_merge
@@ -41,6 +41,7 @@ executables:
41
41
  - openapi-addparameters
42
42
  - openapi-addresponses
43
43
  - openapi-addschemas
44
+ - openapi-addsecurityschemes
44
45
  - openapi-checkschemas
45
46
  - openapi-frequencies
46
47
  - openapi-generate
@@ -55,6 +56,7 @@ files:
55
56
  - bin/openapi-addparameters
56
57
  - bin/openapi-addresponses
57
58
  - bin/openapi-addschemas
59
+ - bin/openapi-addsecurityschemes
58
60
  - bin/openapi-checkschemas
59
61
  - bin/openapi-frequencies
60
62
  - bin/openapi-generate
@@ -71,7 +73,6 @@ files:
71
73
  - lib/openapi/sourcetools/helper.rb
72
74
  - lib/openapi/sourcetools/loaders.rb
73
75
  - lib/openapi/sourcetools/output.rb
74
- - lib/openapi/sourcetools/securityschemes.rb
75
76
  - lib/openapi/sourcetools/task.rb
76
77
  - lib/openapi/sourcetools/version.rb
77
78
  homepage: https://xn--ismo-krkkinen-gfbd.fi/openapi-sourcetools/index.html
@@ -1,268 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Copyright © 2024-2025 Ismo Kärkkäinen
4
- # Licensed under Universal Permissive License. See LICENSE.txt.
5
-
6
- require_relative 'task'
7
- require 'yaml'
8
-
9
-
10
- module OpenAPISourceTools
11
- # Class that contains security scheme objects and what headers
12
- # and parameters are added to the request when the scheme is used.
13
- class SecuritySchemeInfo
14
- include Comparable
15
-
16
- attr_reader :headers, :parameters, :query_parameters, :cookies
17
-
18
- def initialize(security_scheme = {}, scheme_templates = [])
19
- @headers = {}
20
- @parameters = {}
21
- @query_parameters = {}
22
- @cookies = {}
23
- scheme_templates.each do |template|
24
- s = template['scheme']
25
- match = true
26
- s.each do |k, v|
27
- unless v == security_scheme[k]
28
- match = false
29
- break
30
- end
31
- end
32
- next unless match
33
- o = template['output']
34
- fill_in(@headers, o['headers'] || {}, security_scheme)
35
- fill_in(@parameters, o['parameters'] || {}, security_scheme)
36
- fill_in(@query_parameters, o['query_parameters'] || {}, security_scheme)
37
- fill_in(@cookies, o['cookies'] || {}, security_scheme)
38
- break
39
- end
40
- end
41
-
42
- def fill_in(output, source, scheme)
43
- source.each do |k, v|
44
- if k.start_with?('<') && k.end_with?('>')
45
- scheme_key = k[1..-2]
46
- scheme_value = scheme[scheme_key]
47
- raise "Missing security scheme value for #{scheme_key}" if scheme_value.nil?
48
- output[scheme_value] = v
49
- else
50
- output[k] = v
51
- end
52
- end
53
- end
54
-
55
- def merge!(other)
56
- @headers.merge!(other.headers)
57
- @parameters.merge!(other.parameters)
58
- @query_parameters.merge!(other.query_parameters)
59
- @cookies.merge!(other.cookies)
60
- end
61
-
62
- def merge(other)
63
- out = SecuritySchemeInfo.new({}, [])
64
- out.merge!(self)
65
- out.merge!(other)
66
- out
67
- end
68
-
69
- def <=>(other)
70
- # Only really interested in equality.
71
- @headers <=> other.headers || @parameters <=> other.parameters || @query_parameters <=> other.query_parameters || @cookies <=> other.cookies
72
- end
73
- end
74
-
75
- class ScopedSecuritySchemeInfo
76
- include Comparable
77
-
78
- attr_reader :ssi, :scopes
79
-
80
- def initialize(ssi, scopes)
81
- @ssi = ssi
82
- @scopes = scopes
83
- end
84
-
85
- def merge(other)
86
- @ssi = @ssi.merge(other.ssi)
87
- @scopes = @scopes.concat(other.scopes).uniq
88
- end
89
-
90
- def <=>(other)
91
- # Only really interested in equality.
92
- @ssi <=> other.ssi || @scopes <=> other.scopes
93
- end
94
- end
95
-
96
- # Helper class for dealing with securitySchemes.
97
- # Adds :security key to each operation object and to root.
98
- # They contain the used securitySchemes in use and the security schemes
99
- # in effect for that operation.
100
- class SecuritySchemesTask
101
- include TaskInterface
102
-
103
- def default_configuration
104
- # type apiKey, name, in.
105
- # type http, scheme, bearerFormat.
106
- # type mutualTLS, beyond the scope of code templates.
107
- # type oauth2, flows object.
108
- # type openIdConnect, openIdConnectUrl. More for login?
109
- YAML.safe_load(%q(---
110
- security_schemes:
111
- - scheme:
112
- type: apiKey
113
- in: header
114
- output:
115
- headers:
116
- '<name>': string
117
- - scheme:
118
- type: apiKey
119
- in: query
120
- output:
121
- parameters:
122
- '<name>': string
123
- - scheme:
124
- type: apiKey
125
- in: query
126
- output:
127
- query_parameters:
128
- '<name>': string
129
- - scheme:
130
- type: apiKey
131
- in: cookie
132
- output:
133
- cookies:
134
- '<name>': true
135
- - scheme:
136
- type: http
137
- scheme: basic
138
- output:
139
- headers:
140
- Authorization: string
141
- - scheme:
142
- type: http
143
- scheme: bearer
144
- output:
145
- headers:
146
- Authorization: string
147
- - scheme:
148
- type: mutualTLS
149
- output: {}
150
- - scheme:
151
- type: oauth2
152
- output:
153
- headers:
154
- Authorization: string
155
- - scheme:
156
- type: openIdConnect
157
- output:
158
- headers:
159
- Authorization: string
160
- ))
161
- end
162
-
163
- def convert_security_schemes(doc)
164
- ss = doc.dig('components', 'securitySchemes')
165
- return nil if ss.nil?
166
- # Should create unique objects. Different name may lead to same object.
167
- out = {}
168
- ss.each do |name, security_scheme|
169
- out[name] = SecuritySchemeInfo.new(security_scheme)
170
- end
171
- out
172
- end
173
-
174
- def operation_object_security(doc, schemes)
175
- # Find all operation objects and security in effect.
176
- all_ops = []
177
- seen_secs = Set.new
178
- root_sec = doc['security'] || []
179
- not_method = %w[parameters servers summary description]
180
- doc['paths'].each_value do |path_item|
181
- path_sec = path_item['security'] || root_sec
182
- path_item.each do |method, op|
183
- next if not_method.include?(method)
184
- op_sec = op['security'] || path_sec
185
- all_ops.push([ op, op_sec ])
186
- op_sec.each do |security_requirement|
187
- names = security_requirement.keys
188
- seen_secs.merge(names)
189
- if names.empty? && !schemes.key?('')
190
- schemes[''] = SecuritySchemeInfo.new
191
- end
192
- end
193
- end
194
- end
195
- # Check that all seen secs names have a scheme. Report all in one place.
196
- missing = false
197
- seen_secs.to_a.sort!.each do |name|
198
- unless schemes.key?(name)
199
- missing = true
200
- warn("#/components/securitySchemes is missing: #{name}")
201
- end
202
- end
203
- return 1 if missing
204
- # Now we know all individual parts are available.
205
- # Map security arrays of objects to arrays of arrays.
206
- all_scopeds = [] # For having just one instance for unique data combination.
207
- all_ops.each do |pair|
208
- sec = pair.second.map do |sec_req|
209
- keys = sec_req.keys.sort!
210
- values = keys.map { |name| sec_req[name].sort! }
211
- if keys.empty?
212
- keys = [ '' ]
213
- values = [ [] ]
214
- end
215
- s3is = []
216
- keys.size.times do |idx|
217
- name = keys[idx]
218
- scopes = values[idx]
219
- s3i = ScopedSecuritySchemeInfo.new(schemes[name], scopes)
220
- idx = all_scopeds.index(s3i)
221
- if idx.nil?
222
- all_scopeds.push(s3i)
223
- else
224
- s3i = all_scopeds(idx) # Use the same instance everywhere.
225
- end
226
- s3is.push(s3i)
227
- end
228
- s3is
229
- end
230
- pair.first[:security] = sec # Arrays of ScopedSecuritySchemeInfo.
231
- # When individual objects are not needed, provide merged together items.
232
- all_merged = []
233
- pair.first[:security_merged] = sec.map do |s3is| # ScopedSecuritySchemeInfos.
234
- m = s3is.first
235
- s3is[1..].each do |s3i|
236
- m = m.merge(s3i)
237
- end
238
- idx = all_merged.index(m)
239
- if idx.nil?
240
- all_merged.push(m)
241
- else
242
- m = all_merged[idx] # Use the same instance everywhere.
243
- end
244
- m
245
- end
246
- end
247
- all_merged.map(&:ssi).uniq.sort!
248
- end
249
-
250
- def generate(_context_binding)
251
- # Get security_schemes from config, append defaults to it.
252
- scheme_templates = gen.configuration['security_schemes'] || []
253
- scheme_templates.concat(default_configuration['security_schemes'])
254
- simple_schemes = convert_security_schemes(Gen.doc)
255
- # For all operation objects, add :security array with all used schemes.
256
- merged_schemes = operation_object_security(Gen.doc, simple_schemes || {})
257
- return merged_schemes if merged_schemes.is_a?(Integer)
258
- Gen.doc[:securitySchemes] = simple_schemes unless simple_schemes.nil?
259
- Gen.doc[:securitySchemes_merged] = merged_schemes unless merged_schemes.empty?
260
- end
261
-
262
- def discard
263
- true
264
- end
265
-
266
- COMPONENTS = '#/components/'
267
- end
268
- end