committee_firetail 5.0.0

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.
Files changed (80) hide show
  1. checksums.yaml +7 -0
  2. data/bin/committee-stub +23 -0
  3. data/lib/committee/bin/committee_stub.rb +67 -0
  4. data/lib/committee/drivers/driver.rb +47 -0
  5. data/lib/committee/drivers/hyper_schema/driver.rb +105 -0
  6. data/lib/committee/drivers/hyper_schema/link.rb +68 -0
  7. data/lib/committee/drivers/hyper_schema/schema.rb +22 -0
  8. data/lib/committee/drivers/hyper_schema.rb +12 -0
  9. data/lib/committee/drivers/open_api_2/driver.rb +252 -0
  10. data/lib/committee/drivers/open_api_2/header_schema_builder.rb +33 -0
  11. data/lib/committee/drivers/open_api_2/link.rb +36 -0
  12. data/lib/committee/drivers/open_api_2/parameter_schema_builder.rb +83 -0
  13. data/lib/committee/drivers/open_api_2/schema.rb +26 -0
  14. data/lib/committee/drivers/open_api_2/schema_builder.rb +33 -0
  15. data/lib/committee/drivers/open_api_2.rb +13 -0
  16. data/lib/committee/drivers/open_api_3/driver.rb +51 -0
  17. data/lib/committee/drivers/open_api_3/schema.rb +41 -0
  18. data/lib/committee/drivers/open_api_3.rb +11 -0
  19. data/lib/committee/drivers/schema.rb +23 -0
  20. data/lib/committee/drivers.rb +84 -0
  21. data/lib/committee/errors.rb +36 -0
  22. data/lib/committee/middleware/base.rb +57 -0
  23. data/lib/committee/middleware/request_validation.rb +41 -0
  24. data/lib/committee/middleware/response_validation.rb +58 -0
  25. data/lib/committee/middleware/stub.rb +75 -0
  26. data/lib/committee/middleware.rb +11 -0
  27. data/lib/committee/request_unpacker.rb +91 -0
  28. data/lib/committee/schema_validator/hyper_schema/parameter_coercer.rb +79 -0
  29. data/lib/committee/schema_validator/hyper_schema/request_validator.rb +55 -0
  30. data/lib/committee/schema_validator/hyper_schema/response_generator.rb +102 -0
  31. data/lib/committee/schema_validator/hyper_schema/response_validator.rb +89 -0
  32. data/lib/committee/schema_validator/hyper_schema/router.rb +46 -0
  33. data/lib/committee/schema_validator/hyper_schema/string_params_coercer.rb +105 -0
  34. data/lib/committee/schema_validator/hyper_schema.rb +119 -0
  35. data/lib/committee/schema_validator/open_api_3/operation_wrapper.rb +139 -0
  36. data/lib/committee/schema_validator/open_api_3/request_validator.rb +52 -0
  37. data/lib/committee/schema_validator/open_api_3/response_validator.rb +29 -0
  38. data/lib/committee/schema_validator/open_api_3/router.rb +45 -0
  39. data/lib/committee/schema_validator/open_api_3.rb +120 -0
  40. data/lib/committee/schema_validator/option.rb +60 -0
  41. data/lib/committee/schema_validator.rb +23 -0
  42. data/lib/committee/test/methods.rb +84 -0
  43. data/lib/committee/test/schema_coverage.rb +101 -0
  44. data/lib/committee/utils.rb +28 -0
  45. data/lib/committee/validation_error.rb +26 -0
  46. data/lib/committee/version.rb +5 -0
  47. data/lib/committee.rb +40 -0
  48. data/test/bin/committee_stub_test.rb +57 -0
  49. data/test/bin_test.rb +25 -0
  50. data/test/committee_test.rb +77 -0
  51. data/test/drivers/hyper_schema/driver_test.rb +49 -0
  52. data/test/drivers/hyper_schema/link_test.rb +56 -0
  53. data/test/drivers/open_api_2/driver_test.rb +156 -0
  54. data/test/drivers/open_api_2/header_schema_builder_test.rb +26 -0
  55. data/test/drivers/open_api_2/link_test.rb +52 -0
  56. data/test/drivers/open_api_2/parameter_schema_builder_test.rb +195 -0
  57. data/test/drivers/open_api_3/driver_test.rb +84 -0
  58. data/test/drivers_test.rb +154 -0
  59. data/test/middleware/base_test.rb +130 -0
  60. data/test/middleware/request_validation_open_api_3_test.rb +626 -0
  61. data/test/middleware/request_validation_test.rb +516 -0
  62. data/test/middleware/response_validation_open_api_3_test.rb +291 -0
  63. data/test/middleware/response_validation_test.rb +189 -0
  64. data/test/middleware/stub_test.rb +145 -0
  65. data/test/request_unpacker_test.rb +200 -0
  66. data/test/schema_validator/hyper_schema/parameter_coercer_test.rb +111 -0
  67. data/test/schema_validator/hyper_schema/request_validator_test.rb +151 -0
  68. data/test/schema_validator/hyper_schema/response_generator_test.rb +142 -0
  69. data/test/schema_validator/hyper_schema/response_validator_test.rb +118 -0
  70. data/test/schema_validator/hyper_schema/router_test.rb +88 -0
  71. data/test/schema_validator/hyper_schema/string_params_coercer_test.rb +137 -0
  72. data/test/schema_validator/open_api_3/operation_wrapper_test.rb +218 -0
  73. data/test/schema_validator/open_api_3/request_validator_test.rb +110 -0
  74. data/test/schema_validator/open_api_3/response_validator_test.rb +92 -0
  75. data/test/test/methods_new_version_test.rb +97 -0
  76. data/test/test/methods_test.rb +363 -0
  77. data/test/test/schema_coverage_test.rb +216 -0
  78. data/test/test_helper.rb +120 -0
  79. data/test/validation_error_test.rb +25 -0
  80. metadata +328 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 373c8ba94c48a49c2766b80dbd3fa35816580a9f7a0060e4bb3ac83bd5c4086e
4
+ data.tar.gz: 2779e70f608ca3b8339c4e9da8aff0083632923fbf6cf64c33150ddd38832bfc
5
+ SHA512:
6
+ metadata.gz: c57b794c6e289d9ffdef839fe999fcd440011892e00e27bb2a6ad2cb9c2f211f41955d058d203895aa59ec8494ca78e191da43b00ca4e90df6f6b0647be477b4
7
+ data.tar.gz: 790ea01efeb3d9e4e5cc906cd85c328771bf75b9a480f8f39beabeb4a1c7dc3e2d74ac031bc759f8adad00c1144ba043e8e3374165ad5fea58f64fe10bba6ffa
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'optparse'
5
+ require 'yaml'
6
+
7
+ require_relative "../lib/committee"
8
+
9
+ args = ARGV.dup
10
+ bin = Committee::Bin::CommitteeStub.new
11
+ options, parser = bin.get_options_parser
12
+
13
+ parser.parse!(args)
14
+
15
+ if options[:help] || !options[:driver] || args.length < 1
16
+ # shows help information
17
+ puts parser.to_s
18
+ else
19
+ driver = Committee::Drivers.driver_from_name(options[:driver])
20
+ schema = driver.parse(::YAML.load(File.read(args[0])))
21
+
22
+ Rack::Server.start(app: bin.get_app(schema, options), Port: options[:port])
23
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Don't Support OpenAPI3
4
+
5
+ module Committee
6
+ module Bin
7
+ # CommitteeStub internalizes the functionality of bin/committee-stub so
8
+ # that we can test code that would otherwise be difficult to get at in an
9
+ # executable.
10
+ class CommitteeStub
11
+ # Gets a Rack app suitable for use as a stub.
12
+ def get_app(schema, options)
13
+ cache = {}
14
+
15
+ raise Committee::OpenAPI3Unsupported.new("Stubs are not yet supported for OpenAPI 3") unless schema.supports_stub?
16
+
17
+ Rack::Builder.new {
18
+ unless options[:tolerant]
19
+ use Committee::Middleware::RequestValidation, schema: schema
20
+ use Committee::Middleware::ResponseValidation, schema: schema
21
+ end
22
+
23
+ use Committee::Middleware::Stub, cache: cache, schema: schema
24
+
25
+ run lambda { |_|
26
+ [404, {}, ["Not found"]]
27
+ }
28
+ }
29
+ end
30
+
31
+ # Gets an option parser for command line arguments.
32
+ def get_options_parser
33
+ options = {
34
+ driver: nil,
35
+ help: false,
36
+ port: 9292,
37
+ tolerant: false,
38
+ }
39
+
40
+ parser = OptionParser.new do |opts|
41
+ opts.banner = "Usage: rackup [options] [JSON Schema file]"
42
+
43
+ opts.separator ""
44
+ opts.separator "Options:"
45
+
46
+ # required
47
+ opts.on("-d", "--driver NAME", "name of driver [open_api_2|hyper_schema]") { |name|
48
+ options[:driver] = name.to_sym
49
+ }
50
+
51
+ opts.on_tail("-h", "-?", "--help", "Show this message") {
52
+ options[:help] = true
53
+ }
54
+
55
+ opts.on("-t", "--tolerant", "don't perform request/response validations") {
56
+ options[:tolerant] = true
57
+ }
58
+
59
+ opts.on("-p", "--port PORT", "use PORT (default: 9292)") { |port|
60
+ options[:port] = port
61
+ }
62
+ end
63
+ [options, parser]
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Committee
4
+ module Drivers
5
+ # Driver is a base class for driver implementations.
6
+ class Driver
7
+ # Whether parameters that were form-encoded will be coerced by default.
8
+ def default_coerce_form_params
9
+ raise "needs implementation"
10
+ end
11
+
12
+ # Use GET request body to request parameter (request body merge to parameter)
13
+ def default_allow_get_body
14
+ raise "needs implementation"
15
+ end
16
+
17
+ # Whether parameters in a request's path will be considered and coerced
18
+ # by default.
19
+ def default_path_params
20
+ raise "needs implementation"
21
+ end
22
+
23
+ # Whether parameters in a request's query string will be considered and
24
+ # coerced by default.
25
+ def default_query_params
26
+ raise "needs implementation"
27
+ end
28
+
29
+ def name
30
+ raise "needs implementation"
31
+ end
32
+
33
+ # Parses an API schema and builds a set of route definitions for use with
34
+ # Committee.
35
+ #
36
+ # The expected input format is a data hash with keys as strings (as
37
+ # opposed to symbols) like the kind produced by JSON.parse or YAML.load.
38
+ def parse(data)
39
+ raise "needs implementation"
40
+ end
41
+
42
+ def schema_class
43
+ raise "needs implementation"
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Committee
4
+ module Drivers
5
+ module HyperSchema
6
+ class Driver < ::Committee::Drivers::Driver
7
+ def default_coerce_date_times
8
+ false
9
+ end
10
+
11
+ # Whether parameters that were form-encoded will be coerced by default.
12
+ def default_coerce_form_params
13
+ false
14
+ end
15
+
16
+ def default_allow_get_body
17
+ true
18
+ end
19
+
20
+ # Whether parameters in a request's path will be considered and coerced by
21
+ # default.
22
+ def default_path_params
23
+ false
24
+ end
25
+
26
+ # Whether parameters in a request's query string will be considered and
27
+ # coerced by default.
28
+ def default_query_params
29
+ false
30
+ end
31
+
32
+ def default_validate_success_only
33
+ true
34
+ end
35
+
36
+ def name
37
+ :hyper_schema
38
+ end
39
+
40
+ # Parses an API schema and builds a set of route definitions for use with
41
+ # Committee.
42
+ #
43
+ # The expected input format is a data hash with keys as strings (as opposed
44
+ # to symbols) like the kind produced by JSON.parse or YAML.load.
45
+ def parse(schema)
46
+ # Really we'd like to only have data hashes passed into drivers these
47
+ # days, but here we handle a JsonSchema::Schema for now to maintain
48
+ # backward compatibility (this library used to be hyper-schema only).
49
+ if schema.is_a?(JsonSchema::Schema)
50
+ hyper_schema = schema
51
+ else
52
+ hyper_schema = JsonSchema.parse!(schema)
53
+ hyper_schema.expand_references!
54
+ end
55
+
56
+ schema = Schema.new
57
+ schema.driver = self
58
+ schema.routes = build_routes(hyper_schema)
59
+ schema
60
+ end
61
+
62
+ def schema_class
63
+ Committee::Drivers::HyperSchema::Schema
64
+ end
65
+
66
+ private
67
+
68
+ def build_routes(hyper_schema)
69
+ routes = {}
70
+
71
+ hyper_schema.links.each do |link|
72
+ method, href = parse_link(link)
73
+ next unless method
74
+
75
+ rx = %r{^#{href}$}
76
+ Committee.log_debug "Created route: #{method} #{href} (regex #{rx})"
77
+
78
+ routes[method] ||= []
79
+ routes[method] << [rx, Link.new(link)]
80
+ end
81
+
82
+ # recursively iterate through all `properties` subschemas to build a
83
+ # complete routing table
84
+ hyper_schema.properties.each do |_, subschema|
85
+ routes.merge!(build_routes(subschema)) { |_, r1, r2| r1 + r2 }
86
+ end
87
+
88
+ routes
89
+ end
90
+
91
+ def href_to_regex(href)
92
+ href.gsub(/\{(.*?)\}/, "[^/]+")
93
+ end
94
+
95
+ def parse_link(link)
96
+ return nil, nil if !link.method || !link.href
97
+ method = link.method.to_s.upcase
98
+ # /apps/{id} --> /apps/([^/]+)
99
+ href = href_to_regex(link.href)
100
+ [method, href]
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Committee
4
+ module Drivers
5
+ module HyperSchema
6
+ # Link abstracts an API link specifically for JSON hyper-schema.
7
+ #
8
+ # For most operations, it's a simple pass through to a
9
+ # JsonSchema::Schema::Link, but implements some exotic behavior in a few
10
+ # places.
11
+ class Link
12
+ def initialize(hyper_schema_link)
13
+ @hyper_schema_link = hyper_schema_link
14
+ end
15
+
16
+ # The link's input media type. i.e. How requests should be encoded.
17
+ def enc_type
18
+ hyper_schema_link.enc_type
19
+ end
20
+
21
+ def href
22
+ hyper_schema_link.href
23
+ end
24
+
25
+ # The link's output media type. i.e. How responses should be encoded.
26
+ def media_type
27
+ hyper_schema_link.media_type
28
+ end
29
+
30
+ def method
31
+ hyper_schema_link.method
32
+ end
33
+
34
+ # Passes through a link's parent resource. Note that this is *not* part
35
+ # of the Link interface and is here to support a legacy Heroku-ism
36
+ # behavior that allowed a link tagged with rel=instances to imply that a
37
+ # list will be returned.
38
+ def parent
39
+ hyper_schema_link.parent
40
+ end
41
+
42
+ def rel
43
+ hyper_schema_link.rel
44
+ end
45
+
46
+ # The link's input schema. i.e. How we validate an endpoint's incoming
47
+ # parameters.
48
+ def schema
49
+ hyper_schema_link.schema
50
+ end
51
+
52
+ def status_success
53
+ hyper_schema_link.rel == "create" ? 201 : 200
54
+ end
55
+
56
+ # The link's output schema. i.e. How we validate an endpoint's response
57
+ # data.
58
+ def target_schema
59
+ hyper_schema_link.target_schema
60
+ end
61
+
62
+ private
63
+
64
+ attr_accessor :hyper_schema_link
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Committee
4
+ module Drivers
5
+ module HyperSchema
6
+ class Schema < ::Committee::Drivers::Schema
7
+ # A link back to the derivative instance of Committee::Drivers::Driver
8
+ # that create this schema.
9
+ attr_accessor :driver
10
+
11
+ attr_accessor :routes
12
+
13
+ attr_reader :validator_option
14
+
15
+ def build_router(options)
16
+ @validator_option = Committee::SchemaValidator::Option.new(options, self, :hyper_schema)
17
+ Committee::SchemaValidator::HyperSchema::Router.new(self, @validator_option)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Committee
4
+ module Drivers
5
+ module HyperSchema
6
+ end
7
+ end
8
+ end
9
+
10
+ require_relative 'hyper_schema/driver'
11
+ require_relative 'hyper_schema/link'
12
+ require_relative 'hyper_schema/schema'
@@ -0,0 +1,252 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Committee
4
+ module Drivers
5
+ module OpenAPI2
6
+ class Driver < ::Committee::Drivers::Driver
7
+ def default_coerce_date_times
8
+ false
9
+ end
10
+
11
+ # Whether parameters that were form-encoded will be coerced by default.
12
+ def default_coerce_form_params
13
+ true
14
+ end
15
+
16
+ def default_allow_get_body
17
+ true
18
+ end
19
+
20
+ # Whether parameters in a request's path will be considered and coerced by
21
+ # default.
22
+ def default_path_params
23
+ true
24
+ end
25
+
26
+ # Whether parameters in a request's query string will be considered and
27
+ # coerced by default.
28
+ def default_query_params
29
+ true
30
+ end
31
+
32
+ def default_validate_success_only
33
+ true
34
+ end
35
+
36
+ def name
37
+ :open_api_2
38
+ end
39
+
40
+ # Parses an API schema and builds a set of route definitions for use with
41
+ # Committee.
42
+ #
43
+ # The expected input format is a data hash with keys as strings (as opposed
44
+ # to symbols) like the kind produced by JSON.parse or YAML.load.
45
+ def parse(data)
46
+ REQUIRED_FIELDS.each do |field|
47
+ if !data[field]
48
+ raise ArgumentError, "Committee: no #{field} section in spec data."
49
+ end
50
+ end
51
+
52
+ if data['swagger'] != '2.0'
53
+ raise ArgumentError, "Committee: driver requires OpenAPI 2.0."
54
+ end
55
+
56
+ schema = Schema.new
57
+ schema.driver = self
58
+
59
+ schema.base_path = data['basePath'] || ''
60
+
61
+ # Arbitrarily choose the first media type found in these arrays. This
62
+ # approach could probably stand to be improved, but at least users will
63
+ # for now have the option of turning media type validation off if they so
64
+ # choose.
65
+ schema.consumes = data['consumes'].first
66
+ schema.produces = data['produces'].first
67
+
68
+ schema.definitions, store = parse_definitions!(data)
69
+ schema.routes = parse_routes!(data, schema, store)
70
+
71
+ schema
72
+ end
73
+
74
+ def schema_class
75
+ Committee::Drivers::OpenAPI2::Schema
76
+ end
77
+
78
+ private
79
+
80
+ DEFINITIONS_PSEUDO_URI = "http://json-schema.org/committee-definitions"
81
+
82
+ # These are fields that the OpenAPI 2 spec considers mandatory to be
83
+ # included in the document's top level.
84
+ REQUIRED_FIELDS = [
85
+ :consumes,
86
+ :definitions,
87
+ :paths,
88
+ :produces,
89
+ :swagger,
90
+ ].map(&:to_s).freeze
91
+
92
+ def find_best_fit_response(link_data)
93
+ if response_data = link_data["responses"]["200"] || response_data = link_data["responses"][200]
94
+ [200, response_data]
95
+ elsif response_data = link_data["responses"]["201"] || response_data = link_data["responses"][201]
96
+ [201, response_data]
97
+ else
98
+ # Sort responses so that we can try to prefer any 3-digit status code.
99
+ # If there are none, we'll just take anything from the list.
100
+ ordered_responses = link_data["responses"].
101
+ select { |k, v| k.to_s =~ /[0-9]{3}/ }
102
+ if first = ordered_responses.first
103
+ [first[0].to_i, first[1]]
104
+ else
105
+ [nil, nil]
106
+ end
107
+ end
108
+ end
109
+
110
+ def href_to_regex(href)
111
+ href.gsub(/\{(.*?)\}/, '(?<\1>[^/]+)')
112
+ end
113
+
114
+ def parse_definitions!(data)
115
+ # The "definitions" section of an OpenAPI 2 spec is a valid JSON schema.
116
+ # We extract it from the spec and parse it as a schema in isolation so
117
+ # that all references to it will still have correct paths (i.e. we can
118
+ # still find a resource at '#/definitions/resource' instead of
119
+ # '#/resource').
120
+ schema = JsonSchema.parse!({
121
+ "definitions" => data['definitions'],
122
+ })
123
+ schema.expand_references!
124
+ schema.uri = DEFINITIONS_PSEUDO_URI
125
+
126
+ # So this is a little weird: an OpenAPI specification is _not_ a valid
127
+ # JSON schema and yet it self-references like it is a valid JSON schema.
128
+ # To work around this what we do is parse its "definitions" section as a
129
+ # JSON schema and then build a document store here containing that. When
130
+ # trying to resolve a reference from elsewhere in the spec, we build a
131
+ # synthetic schema with a JSON reference to the document created from
132
+ # "definitions" and then expand references against this store.
133
+ store = JsonSchema::DocumentStore.new
134
+ store.add_schema(schema)
135
+
136
+ [schema, store]
137
+ end
138
+
139
+ def parse_routes!(data, schema, store)
140
+ routes = {}
141
+
142
+ # This is a performance optimization: instead of going through each link
143
+ # and parsing out its JSON schema separately, instead we just aggregate
144
+ # all schemas into one big hash and then parse it all at the end. After
145
+ # we parse it, go through each link and assign a proper schema object. In
146
+ # practice this comes out to somewhere on the order of 50x faster.
147
+ schemas_data = { "properties" => {} }
148
+
149
+ # Exactly the same idea, but for response schemas.
150
+ target_schemas_data = { "properties" => {} }
151
+
152
+ data['paths'].each do |path, methods|
153
+ href = schema.base_path + path
154
+ schemas_data["properties"][href] = { "properties" => {} }
155
+ target_schemas_data["properties"][href] = { "properties" => {} }
156
+
157
+ methods.each do |method, link_data|
158
+ method = method.upcase
159
+ link = Link.new
160
+ link.enc_type = schema.consumes
161
+ link.href = href
162
+ link.media_type = schema.produces
163
+ link.method = method
164
+
165
+ # Convert the spec's parameter pseudo-schemas into JSON schemas that
166
+ # we can use for some basic request validation.
167
+ link.schema, schema_data = ParameterSchemaBuilder.new(link_data).call
168
+ link.header_schema = HeaderSchemaBuilder.new(link_data).call
169
+
170
+ # If data came back instead of a schema (this occurs when a route has
171
+ # a single `body` parameter instead of a collection of URL/query/form
172
+ # parameters), store it for later parsing.
173
+ if schema_data
174
+ schemas_data["properties"][href]["properties"][method] = schema_data
175
+ end
176
+
177
+ # Arbitrarily pick one response for the time being. Prefers in order:
178
+ # a 200, 201, any 3-digit numerical response, then anything at all.
179
+ status, response_data = find_best_fit_response(link_data)
180
+ if status
181
+ link.status_success = status
182
+
183
+ # A link need not necessarily specify a target schema.
184
+ if response_data["schema"]
185
+ target_schemas_data["properties"][href]["properties"][method] =
186
+ response_data["schema"]
187
+ end
188
+ end
189
+
190
+ rx = %r{^#{href_to_regex(link.href)}$}
191
+ Committee.log_debug "Created route: #{link.method} #{link.href} (regex #{rx})"
192
+
193
+ routes[method] ||= []
194
+ routes[method] << [rx, link]
195
+ end
196
+ end
197
+
198
+ # See the note on our DocumentStore's initialization in
199
+ # #parse_definitions!, but what we're doing here is prefixing references
200
+ # with a specialized internal URI so that they can reference definitions
201
+ # from another document in the store.
202
+ schemas =
203
+ rewrite_references_and_parse(schemas_data, store)
204
+ target_schemas =
205
+ rewrite_references_and_parse(target_schemas_data, store)
206
+
207
+ # As noted above, now that we've parsed our aggregate response schema, go
208
+ # back through each link and them their response schema.
209
+ routes.each do |method, method_routes|
210
+ method_routes.each do |(_, link)|
211
+ # request
212
+ #
213
+ # Differs slightly from responses in that the schema may already have
214
+ # been set for endpoints with non-body parameters, so check for nil
215
+ # before we set it.
216
+ if schema = schemas.properties[link.href].properties[method]
217
+ link.schema = schema
218
+ end
219
+
220
+ # response
221
+ link.target_schema =
222
+ target_schemas.properties[link.href].properties[method]
223
+ end
224
+ end
225
+
226
+ routes
227
+ end
228
+
229
+ def rewrite_references_and_parse(schemas_data, store)
230
+ schemas = rewrite_references(schemas_data)
231
+ schemas = JsonSchema.parse!(schemas_data)
232
+ schemas.expand_references!(store: store)
233
+ schemas
234
+ end
235
+
236
+ def rewrite_references(schema)
237
+ if schema.is_a?(Hash)
238
+ ref = schema["$ref"]
239
+ if ref && ref.is_a?(String) && ref[0] == "#"
240
+ schema["$ref"] = DEFINITIONS_PSEUDO_URI + ref
241
+ else
242
+ schema.each do |_, v|
243
+ rewrite_references(v)
244
+ end
245
+ end
246
+ end
247
+ schema
248
+ end
249
+ end
250
+ end
251
+ end
252
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Committee
4
+ module Drivers
5
+ module OpenAPI2
6
+ class HeaderSchemaBuilder < SchemaBuilder
7
+ def call
8
+ if link_data["parameters"]
9
+ link_schema = JsonSchema::Schema.new
10
+ link_schema.properties = {}
11
+ link_schema.required = []
12
+
13
+ header_parameters = link_data["parameters"].select { |param_data| param_data["in"] == "header" }
14
+ header_parameters.each do |param_data|
15
+ check_required_fields!(param_data)
16
+
17
+ param_schema = JsonSchema::Schema.new
18
+
19
+ param_schema.type = [param_data["type"]]
20
+
21
+ link_schema.properties[param_data["name"]] = param_schema
22
+ if param_data["required"] == true
23
+ link_schema.required << param_data["name"]
24
+ end
25
+ end
26
+
27
+ link_schema
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Committee
4
+ module Drivers
5
+ module OpenAPI2
6
+ # Link abstracts an API link specifically for OpenAPI 2.
7
+ class Link
8
+ # The link's input media type. i.e. How requests should be encoded.
9
+ attr_accessor :enc_type
10
+
11
+ attr_accessor :href
12
+
13
+ # The link's output media type. i.e. How responses should be encoded.
14
+ attr_accessor :media_type
15
+
16
+ attr_accessor :method
17
+
18
+ # The link's input schema. i.e. How we validate an endpoint's incoming
19
+ # parameters.
20
+ attr_accessor :schema
21
+
22
+ attr_accessor :status_success
23
+
24
+ # The link's output schema. i.e. How we validate an endpoint's response
25
+ # data.
26
+ attr_accessor :target_schema
27
+
28
+ attr_accessor :header_schema
29
+
30
+ def rel
31
+ raise "Committee: rel not implemented for OpenAPI"
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end