committee 5.5.3 → 5.5.5

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: 4d63a9269323d485600c6f853e59ea9ac678faa6090838560cea1e54cec88f40
4
- data.tar.gz: d8168ded9e92aabbf7144e6f98c8b52a90f1b5b956c3c4d9ae1bbba61504fbf2
3
+ metadata.gz: d5ca2d98a65682acdd759d0fdbb8de493cec34b96889fec3cfd18bf4928b50cc
4
+ data.tar.gz: f2c50536c4bd4840dae6dc1a3da96c4a85dd2ec25ee69e55c944a90f3a514d00
5
5
  SHA512:
6
- metadata.gz: 3b951a8e7d81870e3d5947145dfdafb777e980367d41f83f4bc66c4ed902face9b29e6e92f7bb2db55e5a3ed56ff0c95544b35305373061501dcba76cb71ee5e
7
- data.tar.gz: ac977e7c4f909abd4707aa0b540cf5efcd885b03212bec6eca6cf69f4199e2bed0ed2711768d7852f93921192d12ea957e7f4f07868cc3259b96374097c89a12
6
+ metadata.gz: b3f35d67aab36148b9085b0a129ee315ea41e27ed6fde96c2038830c2046a3d1d5e212feeae849cd3d4571d69b01349911826711a699d326d6e799ca725a9391
7
+ data.tar.gz: 411bb7a8a21de88b9c978cb848480a2a40ebd9aab2e9819648d066d301db47420e9ccf9820c292e441c90b546d9c42ff42b0363c852e7db78e7eb902415adf6c
@@ -9,25 +9,38 @@ module Committee
9
9
  super
10
10
  @strict = options[:strict]
11
11
  @validate_success_only = @schema.validator_option.validate_success_only
12
+ @streaming_content_parsers = options[:streaming_content_parsers] || {}
12
13
  end
13
14
 
14
15
  def handle(request)
15
16
  status, headers, response = @app.call(request.env)
16
17
 
17
- begin
18
- v = build_schema_validator(request)
19
- v.response_validate(status, headers, response, @strict) if v.link_exist? && self.class.validate?(status, validate_success_only)
18
+ streaming_content_parser = retrieve_streaming_content_parser(headers)
20
19
 
21
- rescue Committee::InvalidResponse
22
- handle_exception($!, request.env)
20
+ if streaming_content_parser
21
+ response = Rack::BodyProxy.new(response) do
22
+ begin
23
+ validate(request, status, headers, response, streaming_content_parser)
24
+ rescue => e
25
+ handle_exception(e, request.env)
23
26
 
24
- raise if @raise
25
- return @error_class.new(500, :invalid_response, $!.message).render unless @ignore_error
26
- rescue JSON::ParserError
27
- handle_exception($!, request.env)
27
+ raise e if @raise
28
+ end
29
+ end
30
+ else
31
+ begin
32
+ validate(request, status, headers, response)
33
+ rescue Committee::InvalidResponse
34
+ handle_exception($!, request.env)
35
+
36
+ raise if @raise
37
+ return @error_class.new(500, :invalid_response, $!.message).render unless @ignore_error
38
+ rescue JSON::ParserError
39
+ handle_exception($!, request.env)
28
40
 
29
- raise Committee::InvalidResponse if @raise
30
- return @error_class.new(500, :invalid_response, "Response wasn't valid JSON.").render unless @ignore_error
41
+ raise Committee::InvalidResponse if @raise
42
+ return @error_class.new(500, :invalid_response, "Response wasn't valid JSON.").render unless @ignore_error
43
+ end
31
44
  end
32
45
 
33
46
  [status, headers, response]
@@ -50,6 +63,19 @@ module Committee
50
63
 
51
64
  private
52
65
 
66
+ def validate(request, status, headers, response, streaming_content_parser = nil)
67
+ v = build_schema_validator(request)
68
+ if v.link_exist? && self.class.validate?(status, validate_success_only)
69
+ v.response_validate(status, headers, response, @strict, streaming_content_parser)
70
+ end
71
+ end
72
+
73
+ def retrieve_streaming_content_parser(headers)
74
+ content_type_key = headers.keys.detect { |k| k.casecmp?('Content-Type') }
75
+ content_type = headers.fetch(content_type_key, nil)
76
+ @streaming_content_parsers[content_type]
77
+ end
78
+
53
79
  def handle_exception(e, env)
54
80
  @error_handler.call(e, env) if @error_handler
55
81
  end
@@ -18,7 +18,7 @@ module Committee
18
18
  parameter_coerce!(request, link, "rack.request.query_hash") if link_exist? && !request.GET.nil? && !link.schema.nil?
19
19
  end
20
20
 
21
- def response_validate(status, headers, response, _test_method = false)
21
+ def response_validate(status, headers, response, _test_method = false, custom_body_parser = nil)
22
22
  return unless link_exist?
23
23
 
24
24
  full_body = +""
@@ -26,8 +26,9 @@ module Committee
26
26
  full_body << chunk
27
27
  end
28
28
 
29
- data = {}
30
- unless full_body.empty?
29
+ data = if custom_body_parser
30
+ custom_body_parser.call(full_body)
31
+ elsif !full_body.empty?
31
32
  parse_to_json = if validator_option.parse_response_by_content_type
32
33
  content_type_key = headers.keys.detect { |k| k.casecmp?('Content-Type') }
33
34
  headers.fetch(content_type_key, nil)&.start_with?('application/json')
@@ -35,7 +36,9 @@ module Committee
35
36
  true
36
37
  end
37
38
 
38
- data = JSON.parse(full_body) if parse_to_json
39
+ JSON.parse(full_body) if parse_to_json
40
+ else
41
+ {}
39
42
  end
40
43
 
41
44
  Committee::SchemaValidator::HyperSchema::ResponseValidator.new(link, validate_success_only: validator_option.validate_success_only, allow_blank_structures: validator_option.allow_blank_structures).call(status, headers, data)
@@ -110,7 +110,8 @@ module Committee
110
110
  end
111
111
 
112
112
  def validate_post_request_params(path_params, query_params, body_params, headers, validator_option)
113
- content_type = headers['Content-Type'].to_s.split(";").first.to_s
113
+ content_type_key = headers.keys.detect { |k| k.casecmp?('Content-Type') }
114
+ content_type = headers[content_type_key].to_s.split(';').first.to_s
114
115
 
115
116
  # bad performance because when we coerce value, same check
116
117
  validate_path_and_query_params(path_params, query_params, headers, validator_option)
@@ -20,7 +20,7 @@ module Committee
20
20
  copy_coerced_data_to_params(request)
21
21
  end
22
22
 
23
- def response_validate(status, headers, response, test_method = false)
23
+ def response_validate(status, headers, response, test_method = false, custom_body_parser = nil)
24
24
  full_body = +""
25
25
  response.each do |chunk|
26
26
  full_body << chunk
@@ -28,16 +28,18 @@ module Committee
28
28
 
29
29
  parse_to_json = if validator_option.parse_response_by_content_type
30
30
  content_type_key = headers.keys.detect { |k| k.casecmp?('Content-Type') }
31
- headers.fetch(content_type_key, nil)&.start_with?('application/json')
32
- else
33
- true
34
- end
35
-
36
- data = if parse_to_json
31
+ headers.fetch(content_type_key, nil)&.start_with?('application/json')
32
+ else
33
+ true
34
+ end
35
+
36
+ data = if custom_body_parser
37
+ custom_body_parser.call(full_body)
38
+ elsif parse_to_json
37
39
  full_body.empty? ? {} : JSON.parse(full_body)
38
- else
39
- full_body
40
- end
40
+ else
41
+ full_body
42
+ end
41
43
 
42
44
  # TODO: refactoring name
43
45
  strict = test_method
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Committee
4
- VERSION = '5.5.3'.freeze
4
+ VERSION = '5.5.5'.freeze
5
5
  end
@@ -196,6 +196,74 @@ describe Committee::Middleware::ResponseValidation do
196
196
  end
197
197
  end
198
198
 
199
+ describe 'streaming response' do
200
+ describe "text/event-stream; e.g. server-sent events" do
201
+ it 'validates the response stream as a string' do
202
+ options = {
203
+ schema: open_api_3_streaming_response_schema,
204
+ streaming_content_parsers: { 'text/event-stream' => ->(body) { body } },
205
+ }
206
+ status = 200
207
+ headers = { 'content-type' => 'text/event-stream' }
208
+ @app = Rack::Builder.new {
209
+ use Committee::Middleware::ResponseValidation, options
210
+ run lambda { |_|
211
+ [status, headers, ["hello"]]
212
+ }
213
+ }
214
+
215
+ get "/events/stream"
216
+ assert_equal 200, last_response.status
217
+ end
218
+ end
219
+
220
+ describe 'application/x-json-stream; customized streaming event' do
221
+ it "successfully validates the response as a special stream using a customized parser" do
222
+ error_handler_called = false
223
+ error_handler = ->(_e, _env) { error_handler_called = true }
224
+ options = {
225
+ schema: open_api_3_streaming_response_schema,
226
+ streaming_content_parsers: { 'application/x-json-stream' => ->(body) { JSON.parse!(body) } },
227
+ error_handler: error_handler,
228
+ }
229
+ status = 200
230
+ headers = { 'content-type' => 'application/x-json-stream' }
231
+ @app = Rack::Builder.new {
232
+ use Committee::Middleware::ResponseValidation, options
233
+ run lambda { |_|
234
+ [status, headers, [JSON.dump({ "id" => 12345, "message" => "hello" })]]
235
+ }
236
+ }
237
+
238
+ get "/events/stream/json"
239
+ assert_equal 200, last_response.status
240
+ assert_equal false, error_handler_called
241
+ end
242
+
243
+ it "fails to validate the response as a special stream using a customized parser due to a schema mismatch" do
244
+ error_handler_called = false
245
+ error_handler = ->(_e, _env) { error_handler_called = true }
246
+ options = {
247
+ schema: open_api_3_streaming_response_schema,
248
+ streaming_content_parsers: { 'application/x-json-stream' => ->(body) { JSON.parse!(body) } },
249
+ error_handler: error_handler,
250
+ }
251
+ status = 200
252
+ headers = { 'content-type' => 'application/x-json-stream' }
253
+ @app = Rack::Builder.new {
254
+ use Committee::Middleware::ResponseValidation, options
255
+ run lambda { |_|
256
+ [status, headers, [JSON.dump({ "message" => "hello" })]] # Missing 'id' field
257
+ }
258
+ }
259
+
260
+ get "/events/stream/json"
261
+ assert_equal 200, last_response.status
262
+ assert_equal true, error_handler_called
263
+ end
264
+ end
265
+ end
266
+
199
267
  private
200
268
 
201
269
  def new_rack_app(response, headers = {}, options = {})
data/test/test_helper.rb CHANGED
@@ -61,6 +61,10 @@ def open_api_3_coverage_schema
61
61
  @open_api_3_coverage_schema ||= Committee::Drivers.load_from_file(open_api_3_coverage_schema_path, parser_options: { strict_reference_validation: true })
62
62
  end
63
63
 
64
+ def open_api_3_streaming_response_schema
65
+ @open_api_3_streaming_response_schema ||= Committee::Drivers.load_from_file(open_api_3_streaming_response_schema_path, parser_options: { strict_reference_validation: true })
66
+ end
67
+
64
68
  # Don't cache this because we'll often manipulate the created hash in tests.
65
69
  def hyper_schema_data
66
70
  JSON.parse(File.read(hyper_schema_schema_path))
@@ -122,3 +126,7 @@ end
122
126
  def open_api_3_invalid_reference_path
123
127
  "./test/data/openapi3/invalid_reference.yaml"
124
128
  end
129
+
130
+ def open_api_3_streaming_response_schema_path
131
+ "./test/data/openapi3/streaming_response.yaml"
132
+ end
metadata CHANGED
@@ -1,16 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: committee
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.5.3
4
+ version: 5.5.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brandur
8
8
  - geemus (Wesley Beary)
9
9
  - ota42y
10
- autorequire:
11
10
  bindir: bin
12
11
  cert_chain: []
13
- date: 2025-04-07 00:00:00.000000000 Z
12
+ date: 1980-01-02 00:00:00.000000000 Z
14
13
  dependencies:
15
14
  - !ruby/object:Gem::Dependency
16
15
  name: json_schema
@@ -39,9 +38,6 @@ dependencies:
39
38
  - - ">="
40
39
  - !ruby/object:Gem::Version
41
40
  version: '1.5'
42
- - - "<"
43
- - !ruby/object:Gem::Version
44
- version: '3.2'
45
41
  type: :runtime
46
42
  prerelease: false
47
43
  version_requirements: !ruby/object:Gem::Requirement
@@ -49,9 +45,6 @@ dependencies:
49
45
  - - ">="
50
46
  - !ruby/object:Gem::Version
51
47
  version: '1.5'
52
- - - "<"
53
- - !ruby/object:Gem::Version
54
- version: '3.2'
55
48
  - !ruby/object:Gem::Dependency
56
49
  name: openapi_parser
57
50
  requirement: !ruby/object:Gem::Requirement
@@ -164,7 +157,6 @@ dependencies:
164
157
  - - ">="
165
158
  - !ruby/object:Gem::Version
166
159
  version: '0'
167
- description:
168
160
  email:
169
161
  - brandur@mutelight.org
170
162
  - geemus+github@gmail.com
@@ -260,7 +252,6 @@ metadata:
260
252
  changelog_uri: https://github.com/interagent/committee/blob/master/CHANGELOG.md
261
253
  rubygems_mfa_required: 'true'
262
254
  source_code_uri: https://github.com/interagent/committee
263
- post_install_message:
264
255
  rdoc_options: []
265
256
  require_paths:
266
257
  - lib
@@ -275,8 +266,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
275
266
  - !ruby/object:Gem::Version
276
267
  version: '0'
277
268
  requirements: []
278
- rubygems_version: 3.5.18
279
- signing_key:
269
+ rubygems_version: 3.6.9
280
270
  specification_version: 4
281
271
  summary: A collection of Rack middleware to support JSON Schema.
282
272
  test_files: []