rspec-openapi 0.26.0 → 0.27.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 254e5e91a3965a417ad69c8b61d4bfdde6af42ab7aba14854d5531d73f4a793d
4
- data.tar.gz: 3fa43709cf02b95bb1f4815eb4eebefde3ad5e5e2aabf0439267dce2c06cba36
3
+ metadata.gz: 48caaf42c5ceaca30f18251c5bf29f427e71385d188f5abcc01295de3eab55b4
4
+ data.tar.gz: 171aacce0afccc800c6f5fb1e696dcd2f7d124ad0d09044e7797eb0a353452da
5
5
  SHA512:
6
- metadata.gz: 486180ee4a5b04de2f3539864e6f5be519ea262f0b30afc661377aa2da69c9c6fba883128ff02c006a9b073766848fc8b2ef5ad99cbb2fc55f8201de1a5975de
7
- data.tar.gz: a074396a8847d7d9950fc414591e68da5c1def3121fdae896aa6e626e512c5860dc58b5be83248979bb6612628f224d367d066a8d5939991fbe98f8000626934
6
+ metadata.gz: 9a6f77ff9446f74a56bc49576c6340176a58a6a0097e5d96a6b0a5f4334e06dfae55704a2e7dee3f1e12e160cea2a59933616ce2be8e60466f2c9ff10df460ac
7
+ data.tar.gz: f75d0f697a19d72408033e8e538643334649c2a7dab832c09e96d47f53f5be2660571d7a2b8b05cc5fdcb1d43fe86e9851f9d5d8e33a4fde8375d12120aa1c2f
@@ -4,7 +4,7 @@ on:
4
4
  workflow_dispatch:
5
5
  inputs:
6
6
  version:
7
- description: 'Version to release (e.g. 0.26.1 or 0.27.0)'
7
+ description: 'Version to release (e.g. 0.27.1 or 0.28.0)'
8
8
  required: true
9
9
 
10
10
  jobs:
data/.rubocop.yml CHANGED
@@ -20,6 +20,10 @@ Style/ClassAndModuleChildren:
20
20
  - 'lib/rspec/openapi/version.rb'
21
21
  Layout/FirstArrayElementIndentation:
22
22
  EnforcedStyle: consistent
23
+ Style/SymbolArray:
24
+ EnforcedStyle: brackets
25
+ Style/WordArray:
26
+ EnforcedStyle: brackets
23
27
  Metrics/BlockLength:
24
28
  Exclude:
25
29
  - 'spec/**/*'
data/.rubocop_todo.yml CHANGED
@@ -1,45 +1,45 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2024-04-20 21:25:22 UTC using RuboCop version 1.62.1.
3
+ # on 2026-05-25 01:13:29 UTC using RuboCop version 1.80.1.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
7
7
  # versions of RuboCop, may require this file to be generated again.
8
8
 
9
- # Offense count: 14
9
+ # Offense count: 15
10
10
  # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
11
11
  Metrics/AbcSize:
12
- Max: 46
12
+ Max: 35
13
13
 
14
- # Offense count: 1
14
+ # Offense count: 4
15
15
  # Configuration parameters: CountComments, CountAsOne.
16
16
  Metrics/ClassLength:
17
- Max: 195
17
+ Max: 319
18
18
 
19
- # Offense count: 9
19
+ # Offense count: 10
20
20
  # Configuration parameters: AllowedMethods, AllowedPatterns.
21
21
  Metrics/CyclomaticComplexity:
22
- Max: 13
22
+ Max: 11
23
23
 
24
- # Offense count: 22
24
+ # Offense count: 29
25
25
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
26
26
  Metrics/MethodLength:
27
- Max: 37
27
+ Max: 26
28
28
 
29
- # Offense count: 5
29
+ # Offense count: 3
30
30
  # Configuration parameters: AllowedMethods, AllowedPatterns.
31
31
  Metrics/PerceivedComplexity:
32
- Max: 13
32
+ Max: 11
33
33
 
34
34
  # Offense count: 1
35
35
  # Configuration parameters: EnforcedStyle, CheckMethodNames, CheckSymbols, AllowedIdentifiers, AllowedPatterns.
36
36
  # SupportedStyles: snake_case, normalcase, non_integer
37
- # AllowedIdentifiers: capture3, iso8601, rfc1123_date, rfc822, rfc2822, rfc3339, x86_64
37
+ # AllowedIdentifiers: TLS1_1, TLS1_2, capture3, iso8601, rfc1123_date, rfc822, rfc2822, rfc3339, x86_64
38
38
  Naming/VariableNumber:
39
39
  Exclude:
40
40
  - 'spec/integration_tests/roda_test.rb'
41
41
 
42
- # Offense count: 7
42
+ # Offense count: 8
43
43
  # Configuration parameters: AllowedConstants.
44
44
  Style/Documentation:
45
45
  Exclude:
@@ -48,52 +48,25 @@ end)
48
48
  class << RSpec::OpenAPI::Extractors::Hanami = Object.new
49
49
  # @param [ActionDispatch::Request] request
50
50
  # @param [RSpec::Core::Example] example
51
- # @return Array
51
+ # @return [Hash]
52
52
  def request_attributes(request, example)
53
53
  route = Hanami.app.router.recognize(Rack::MockRequest.env_for(request.path, method: request.method))
54
54
 
55
55
  return RSpec::OpenAPI::Extractors::Rack.request_attributes(request, example) unless route.routable?
56
56
 
57
- summary, tags, formats, operation_id, required_request_params, security, description, deprecated,
58
- request_example_mode, response_example_mode,
59
- example_key, example_name, response_enum, request_enum, response_additional_properties,
60
- request_additional_properties, response_hybrid_additional_properties,
61
- request_hybrid_additional_properties = SharedExtractor.attributes(example)
62
-
57
+ attrs = SharedExtractor.attributes(example)
63
58
  path = request.path
59
+ result = InspectorAnalyzer.call(request.method, replace_path_params(path, route, '/:%<key>s'))
64
60
 
65
61
  raw_path_params = route.params
66
-
67
- result = InspectorAnalyzer.call(request.method, replace_path_params(path, route, '/:%{key}'))
68
-
69
- summary ||= result[:summary]
70
- tags ||= result[:tags]
71
- path = replace_path_params(path, route, '/{%{key}}')
72
-
73
62
  raw_path_params = raw_path_params.slice(*(raw_path_params.keys - RSpec::OpenAPI.ignored_path_params))
74
63
 
75
- [
76
- path,
77
- summary,
78
- tags,
79
- operation_id,
80
- required_request_params,
81
- raw_path_params,
82
- description,
83
- security,
84
- deprecated,
85
- formats,
86
- request_example_mode,
87
- response_example_mode,
88
- example_key,
89
- example_name,
90
- response_enum,
91
- request_enum,
92
- response_additional_properties,
93
- request_additional_properties,
94
- response_hybrid_additional_properties,
95
- request_hybrid_additional_properties,
96
- ]
64
+ attrs.merge(
65
+ path: replace_path_params(path, route, '/{%<key>s}'),
66
+ path_params: raw_path_params,
67
+ summary: attrs[:summary] || result[:summary],
68
+ tags: attrs[:tags] || result[:tags],
69
+ )
97
70
  end
98
71
 
99
72
  # @param [RSpec::ExampleGroups::*] context
@@ -111,7 +84,7 @@ class << RSpec::OpenAPI::Extractors::Hanami = Object.new
111
84
  return path if route.params.empty?
112
85
 
113
86
  route.params.each_pair do |key, value|
114
- path = path.sub("/#{value}", format % { key: key })
87
+ path = path.sub("/#{value}", format(format, key: key))
115
88
  end
116
89
 
117
90
  path
@@ -4,40 +4,15 @@
4
4
  class << RSpec::OpenAPI::Extractors::Rack = Object.new
5
5
  # @param [ActionDispatch::Request] request
6
6
  # @param [RSpec::Core::Example] example
7
- # @return Array
7
+ # @return [Hash]
8
8
  def request_attributes(request, example)
9
- summary, tags, formats, operation_id, required_request_params, security, description, deprecated,
10
- request_example_mode, response_example_mode,
11
- example_key, example_name, response_enum, request_enum, response_additional_properties,
12
- request_additional_properties, response_hybrid_additional_properties,
13
- request_hybrid_additional_properties = SharedExtractor.attributes(example)
14
-
15
- raw_path_params = request.path_parameters
16
9
  path = request.path
17
- summary ||= "#{request.method} #{path}"
18
-
19
- [
20
- path,
21
- summary,
22
- tags,
23
- operation_id,
24
- required_request_params,
25
- raw_path_params,
26
- description,
27
- security,
28
- deprecated,
29
- formats,
30
- request_example_mode,
31
- response_example_mode,
32
- example_key,
33
- example_name,
34
- response_enum,
35
- request_enum,
36
- response_additional_properties,
37
- request_additional_properties,
38
- response_hybrid_additional_properties,
39
- request_hybrid_additional_properties,
40
- ]
10
+ attrs = SharedExtractor.attributes(example)
11
+ attrs.merge(
12
+ path: path,
13
+ path_params: request.path_parameters,
14
+ summary: attrs[:summary] || "#{request.method} #{path}",
15
+ )
41
16
  end
42
17
 
43
18
  # @param [RSpec::ExampleGroups::*] context
@@ -4,7 +4,7 @@
4
4
  class << RSpec::OpenAPI::Extractors::Rails = Object.new
5
5
  # @param [ActionDispatch::Request] request
6
6
  # @param [RSpec::Core::Example] example
7
- # @return Array
7
+ # @return [Hash]
8
8
  def request_attributes(request, example)
9
9
  # Reverse the destructive modification by Rails https://github.com/rails/rails/blob/v6.0.3.4/actionpack/lib/action_dispatch/journey/router.rb#L33-L41
10
10
  fixed_request = request.dup
@@ -16,44 +16,18 @@ class << RSpec::OpenAPI::Extractors::Rails = Object.new
16
16
 
17
17
  raise "No route matched for #{fixed_request.request_method} #{fixed_request.path_info}" if route.nil?
18
18
 
19
- summary, tags, formats, operation_id, required_request_params, security, description, deprecated,
20
- request_example_mode, response_example_mode,
21
- example_key, example_name, response_enum, request_enum, response_additional_properties,
22
- request_additional_properties, response_hybrid_additional_properties,
23
- request_hybrid_additional_properties = SharedExtractor.attributes(example)
24
-
25
- raw_path_params = request.path_parameters
26
-
27
- summary ||= route.requirements[:action]
28
- tags ||= [route.requirements[:controller]&.classify].compact
19
+ attrs = SharedExtractor.attributes(example)
29
20
  # :controller and :action always exist. :format is added when routes is configured as such.
30
21
  # TODO: Use .except(:controller, :action, :format) when we drop support for Ruby 2.x
22
+ raw_path_params = request.path_parameters
31
23
  raw_path_params = raw_path_params.slice(*(raw_path_params.keys - RSpec::OpenAPI.ignored_path_params))
32
24
 
33
- summary ||= "#{request.method} #{path}"
34
-
35
- [
36
- path,
37
- summary,
38
- tags,
39
- operation_id,
40
- required_request_params,
41
- raw_path_params,
42
- description,
43
- security,
44
- deprecated,
45
- formats,
46
- request_example_mode,
47
- response_example_mode,
48
- example_key,
49
- example_name,
50
- response_enum,
51
- request_enum,
52
- response_additional_properties,
53
- request_additional_properties,
54
- response_hybrid_additional_properties,
55
- request_hybrid_additional_properties,
56
- ]
25
+ attrs.merge(
26
+ path: path,
27
+ path_params: raw_path_params,
28
+ summary: attrs[:summary] || route.requirements[:action] || "#{request.method} #{path}",
29
+ tags: attrs[:tags] || [route.requirements[:controller]&.classify].compact,
30
+ )
57
31
  end
58
32
 
59
33
  # @param [RSpec::ExampleGroups::*] context
@@ -2,7 +2,7 @@
2
2
 
3
3
  # Shared extractor for extracting OpenAPI metadata from RSpec examples
4
4
  class SharedExtractor
5
- VALID_EXAMPLE_MODES = %i[none single multiple].freeze
5
+ VALID_EXAMPLE_MODES = [:none, :single, :multiple].freeze
6
6
 
7
7
  EXAMPLE_MODE_MULTIPLE_SHORTHAND_WARNING = <<~MSG.tr("\n", ' ').strip.freeze
8
8
  [rspec-openapi] DEPRECATION: example_mode: :multiple currently means
@@ -14,34 +14,41 @@ class SharedExtractor
14
14
 
15
15
  def self.attributes(example)
16
16
  metadata = merge_openapi_metadata(example.metadata)
17
- summary = metadata[:summary] || RSpec::OpenAPI.summary_builder.call(example)
18
- tags = metadata[:tags] || RSpec::OpenAPI.tags_builder.call(example)
19
- formats = metadata[:formats] || RSpec::OpenAPI.formats_builder.curry.call(example)
20
- operation_id = metadata[:operation_id]
21
- required_request_params = metadata[:required_request_params] || []
22
- security = metadata[:security]
23
- description = metadata[:description] || RSpec::OpenAPI.description_builder.call(example)
24
- deprecated = metadata[:deprecated]
25
17
  request_example_mode, response_example_mode = normalize_example_mode(metadata[:example_mode], example)
26
- example_name = metadata[:example_name] || RSpec::OpenAPI.example_name_builder.call(example)
27
- raw_example_key = metadata[:example_key] || example_name
28
- example_key = RSpec::OpenAPI::ExampleKey.normalize(raw_example_key)
29
- example_key = 'default' if example_key.nil? || example_key.empty?
30
-
31
- # Enum support: response_enum and request_enum can override the general enum
32
- base_enum = normalize_enum(metadata[:enum])
33
- response_enum = normalize_enum(metadata[:response_enum]) || base_enum
34
- request_enum = normalize_enum(metadata[:request_enum]) || base_enum
35
-
36
18
  response_additional_properties, request_additional_properties = resolve_additional_properties(metadata)
37
19
  response_hybrid_additional_properties, request_hybrid_additional_properties =
38
20
  resolve_hybrid_additional_properties(metadata)
21
+ # Enum support: response_enum and request_enum can override the general enum
22
+ base_enum = normalize_enum(metadata[:enum])
23
+
24
+ {
25
+ summary: metadata[:summary] || RSpec::OpenAPI.summary_builder.call(example),
26
+ tags: metadata[:tags] || RSpec::OpenAPI.tags_builder.call(example),
27
+ formats: metadata[:formats] || RSpec::OpenAPI.formats_builder.curry.call(example),
28
+ operation_id: metadata[:operation_id],
29
+ required_request_params: metadata[:required_request_params] || [],
30
+ security: metadata[:security],
31
+ description: metadata[:description] || RSpec::OpenAPI.description_builder.call(example),
32
+ deprecated: metadata[:deprecated],
33
+ request_example_mode: request_example_mode,
34
+ response_example_mode: response_example_mode,
35
+ example_key: resolve_example_key(metadata, example),
36
+ example_name: metadata[:example_name] || RSpec::OpenAPI.example_name_builder.call(example),
37
+ response_enum: normalize_enum(metadata[:response_enum]) || base_enum,
38
+ request_enum: normalize_enum(metadata[:request_enum]) || base_enum,
39
+ response_additional_properties: response_additional_properties,
40
+ request_additional_properties: request_additional_properties,
41
+ response_hybrid_additional_properties: response_hybrid_additional_properties,
42
+ request_hybrid_additional_properties: request_hybrid_additional_properties,
43
+ }
44
+ end
39
45
 
40
- [summary, tags, formats, operation_id, required_request_params, security, description, deprecated,
41
- request_example_mode, response_example_mode,
42
- example_key, example_name, response_enum, request_enum, response_additional_properties,
43
- request_additional_properties, response_hybrid_additional_properties,
44
- request_hybrid_additional_properties,]
46
+ def self.resolve_example_key(metadata, example)
47
+ example_name = metadata[:example_name] || RSpec::OpenAPI.example_name_builder.call(example)
48
+ raw_example_key = metadata[:example_key] || example_name
49
+ example_key = RSpec::OpenAPI::ExampleKey.normalize(raw_example_key)
50
+ example_key = 'default' if example_key.nil? || example_key.empty?
51
+ example_key
45
52
  end
46
53
 
47
54
  def self.resolve_additional_properties(metadata)
@@ -96,7 +103,7 @@ class SharedExtractor
96
103
  # shorthand for { request: :single, response: :multiple } and emits a one-time
97
104
  # deprecation warning) or a Hash with :request / :response keys.
98
105
  def self.normalize_example_mode(value, example = nil)
99
- return %i[single single] if value.nil?
106
+ return [:single, :single] if value.nil?
100
107
 
101
108
  case value
102
109
  when Hash
@@ -108,7 +115,7 @@ class SharedExtractor
108
115
  mode = coerce_example_mode_value(value, example)
109
116
  if mode == :multiple
110
117
  warn_example_mode_multiple_shorthand
111
- %i[single multiple]
118
+ [:single, :multiple]
112
119
  else
113
120
  [mode, mode]
114
121
  end
@@ -11,55 +11,34 @@ class << RSpec::OpenAPI::RecordBuilder = Object.new
11
11
  request, response = extractor.request_response(context)
12
12
  return if request.nil?
13
13
 
14
+ attributes = extractor.request_attributes(request, example)
15
+ return if RSpec::OpenAPI.ignored_paths.any? { |ignored_path| attributes[:path].match?(ignored_path) }
16
+
14
17
  title = RSpec::OpenAPI.title.then { |t| t.is_a?(Proc) ? t.call(example) : t }
15
- path, summary, tags, operation_id, required_request_params, raw_path_params,
16
- description, security, deprecated, formats, request_example_mode, response_example_mode,
17
- example_key, example_name, response_enum, request_enum, response_additional_properties,
18
- request_additional_properties, response_hybrid_additional_properties,
19
- request_hybrid_additional_properties = extractor.request_attributes(request, example)
18
+ build_record(title, request, response, attributes)
19
+ end
20
20
 
21
- return if RSpec::OpenAPI.ignored_paths.any? { |ignored_path| path.match?(ignored_path) }
21
+ private
22
22
 
23
+ def build_record(title, request, response, attributes)
23
24
  request_headers, response_headers = extract_headers(request, response)
24
-
25
25
  RSpec::OpenAPI::Record.new(
26
26
  title: title,
27
27
  http_method: request.method,
28
- path: path,
29
- path_params: raw_path_params,
30
28
  query_params: request.query_parameters,
31
29
  request_params: raw_request_params(request),
32
- required_request_params: required_request_params,
33
30
  request_content_type: request.media_type,
34
31
  request_headers: request_headers,
35
- summary: summary,
36
- tags: tags,
37
- formats: formats,
38
- operation_id: operation_id,
39
- description: description,
40
- security: security,
41
- deprecated: deprecated,
42
32
  status: response.status,
43
33
  response_body: safe_parse_body(response, response.media_type),
44
34
  response_headers: response_headers,
45
35
  response_content_type: response.media_type,
46
36
  response_content_disposition: response.header['Content-Disposition'],
47
37
  example_enabled: RSpec::OpenAPI.enable_example,
48
- request_example_mode: request_example_mode,
49
- response_example_mode: response_example_mode,
50
- example_key: example_key,
51
- example_name: example_name,
52
- response_enum: response_enum,
53
- request_enum: request_enum,
54
- response_additional_properties: response_additional_properties,
55
- request_additional_properties: request_additional_properties,
56
- response_hybrid_additional_properties: response_hybrid_additional_properties,
57
- request_hybrid_additional_properties: request_hybrid_additional_properties,
38
+ **attributes,
58
39
  ).freeze
59
40
  end
60
41
 
61
- private
62
-
63
42
  def safe_parse_body(response, media_type)
64
43
  # Use raw body, because Nokogiri-parsed HTML are modified (new lines injection, meta injection, and so on) :(
65
44
  return response.body if media_type == 'text/html'
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ class << RSpec::OpenAPI::SchemaBuilder
4
+ # Lookup context threaded through schema building. Bundles the metadata
5
+ # needed to resolve formats, enums, and additionalProperties overrides for
6
+ # a value at a given path under a record's request/response side.
7
+ BuildContext = Struct.new(:record, :context, :path, :key, keyword_init: true) do
8
+ def descend(child_key)
9
+ self.class.new(
10
+ record: record, context: context, key: child_key,
11
+ path: path ? "#{path}.#{child_key}" : child_key.to_s,
12
+ )
13
+ end
14
+
15
+ def for_array_items
16
+ self.class.new(record: record, context: context, path: path, key: nil)
17
+ end
18
+ end
19
+ private_constant :BuildContext
20
+ end