openapi_parser 2.5.0 → 2.6.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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yaml +1 -3
  3. data/CHANGELOG.md +34 -0
  4. data/README.md +44 -1
  5. data/lib/openapi_parser/concerns/media_type_selectable.rb +18 -0
  6. data/lib/openapi_parser/concerns/schema_loader/creator.rb +7 -2
  7. data/lib/openapi_parser/concerns/schema_loader/hash_body_loader.rb +1 -1
  8. data/lib/openapi_parser/config.rb +5 -0
  9. data/lib/openapi_parser/errors.rb +24 -0
  10. data/lib/openapi_parser/schema_validator/any_of_validator.rb +1 -1
  11. data/lib/openapi_parser/schema_validator/base.rb +35 -9
  12. data/lib/openapi_parser/schema_validator/float_validator.rb +4 -0
  13. data/lib/openapi_parser/schema_validator/integer_validator.rb +17 -1
  14. data/lib/openapi_parser/schema_validator/multiple_of.rb +26 -0
  15. data/lib/openapi_parser/schema_validator/nil_validator.rb +4 -0
  16. data/lib/openapi_parser/schema_validator/one_of_validator.rb +1 -1
  17. data/lib/openapi_parser/schema_validator.rb +32 -4
  18. data/lib/openapi_parser/schemas/components.rb +4 -0
  19. data/lib/openapi_parser/schemas/discriminator.rb +4 -0
  20. data/lib/openapi_parser/schemas/media_type.rb +4 -0
  21. data/lib/openapi_parser/schemas/openapi.rb +36 -3
  22. data/lib/openapi_parser/schemas/path_item.rb +32 -6
  23. data/lib/openapi_parser/schemas/request_body.rb +1 -1
  24. data/lib/openapi_parser/schemas/response.rb +5 -1
  25. data/lib/openapi_parser/schemas/schema.rb +8 -1
  26. data/lib/openapi_parser/spec_validator/rule.rb +24 -3
  27. data/lib/openapi_parser/spec_validator/rules/additional_operations_before_32.rb +26 -0
  28. data/lib/openapi_parser/spec_validator/rules/allow_empty_value_deprecation.rb +26 -0
  29. data/lib/openapi_parser/spec_validator/rules/cookie_style_before_32.rb +26 -0
  30. data/lib/openapi_parser/spec_validator/rules/default_mapping_before_32.rb +29 -0
  31. data/lib/openapi_parser/spec_validator/rules/defs_in_30.rb +25 -0
  32. data/lib/openapi_parser/spec_validator/rules/example_value_fields_before_32.rb +48 -0
  33. data/lib/openapi_parser/spec_validator/rules/media_types_before_32.rb +24 -0
  34. data/lib/openapi_parser/spec_validator/rules/query_method_before_32.rb +26 -0
  35. data/lib/openapi_parser/spec_validator/rules/querystring_before_32.rb +26 -0
  36. data/lib/openapi_parser/spec_validator/rules/response_summary_before_32.rb +25 -0
  37. data/lib/openapi_parser/spec_validator/rules/security_scheme_fields_before_32.rb +41 -0
  38. data/lib/openapi_parser/spec_validator/rules/server_name_before_32.rb +35 -0
  39. data/lib/openapi_parser/spec_validator/rules/streaming_fields_before_32.rb +56 -0
  40. data/lib/openapi_parser/spec_validator/rules/tag_fields_before_32.rb +34 -0
  41. data/lib/openapi_parser/spec_validator/rules/xml_attribute_deprecation.rb +29 -0
  42. data/lib/openapi_parser/spec_validator/rules/xml_node_type_before_32.rb +29 -0
  43. data/lib/openapi_parser/spec_validator/rules/xml_wrapped_deprecation.rb +29 -0
  44. data/lib/openapi_parser/spec_validator.rb +34 -0
  45. data/lib/openapi_parser/version.rb +1 -1
  46. data/lib/openapi_parser.rb +5 -4
  47. data/sig/openapi_parser/config.rbs +1 -0
  48. data/sig/openapi_parser/schema_validator.rbs +1 -0
  49. data/sig/openapi_parser/schema_validators/base.rbs +4 -2
  50. data/sig/openapi_parser/spec_validator.rbs +77 -0
  51. data/sig/openapi_parser.rbs +5 -3
  52. data/sig/wip_types.rbs +6 -0
  53. metadata +19 -1
@@ -3,20 +3,46 @@
3
3
 
4
4
  module OpenAPIParser::Schemas
5
5
  class PathItem < Base
6
+ # `query` is an OpenAPI 3.2 addition
7
+ STANDARD_METHODS = %w[get put post delete options head patch trace query].freeze
8
+ PRE_3_2_METHODS = (STANDARD_METHODS - %w[query]).freeze
9
+
6
10
  openapi_attr_values :summary, :description
7
11
 
8
- openapi_attr_objects :get, :put, :post, :delete, :options, :head, :patch, :trace, Operation
12
+ openapi_attr_objects :get, :put, :post, :delete, :options, :head, :patch, :trace, :query, Operation
9
13
  openapi_attr_list_object :parameters, Parameter, reference: true
10
14
 
11
- # @return [Operation]
15
+ # @!attribute [r] additional_operations
16
+ # @return [Hash{String => Operation}, nil] operations for non-standard HTTP methods (OpenAPI 3.2+)
17
+ openapi_attr_hash_object :additional_operations, Operation, reference: false, schema_key: :additionalOperations
18
+
19
+ # @return [Operation, nil]
12
20
  def operation(method)
13
- public_send(method)
14
- rescue NoMethodError
15
- nil
21
+ # before 3.2: exact-case standard methods only, as it always was
22
+ unless root.use_3_2_features?
23
+ return PRE_3_2_METHODS.include?(method.to_s) ? public_send(method.to_s) : nil
24
+ end
25
+
26
+ method_name = method.to_s.downcase
27
+ return public_send(method_name) if STANDARD_METHODS.include?(method_name)
28
+
29
+ additional_operation(method.to_s)
16
30
  end
17
31
 
18
32
  def set_path_item_to_operation
19
- [:get, :put, :post, :delete, :options, :head, :patch, :trace].each{ |method| operation(method)&.set_parent_path_item(self)}
33
+ STANDARD_METHODS.each { |method| operation(method)&.set_parent_path_item(self) }
34
+ additional_operations&.each_value { |op| op.set_parent_path_item(self) }
20
35
  end
36
+
37
+ private
38
+
39
+ # additionalOperations keys are method names as sent on the wire
40
+ # (conventionally uppercase); prefer an exact match, else ignore case
41
+ def additional_operation(name)
42
+ operations = additional_operations
43
+ return nil unless operations
44
+
45
+ operations.fetch(name) { operations.find { |key, _| key.casecmp?(name) }&.last }
46
+ end
21
47
  end
22
48
  end
@@ -12,7 +12,7 @@ module OpenAPIParser::Schemas
12
12
 
13
13
  # @!attribute [r] content
14
14
  # @return [Hash{String => MediaType}, nil] content type to MediaType object
15
- openapi_attr_hash_object :content, MediaType, reference: false
15
+ openapi_attr_hash_object :content, MediaType, reference: OpenAPIParser::MediaTypeSelectable::CONTENT_REFERENCE
16
16
 
17
17
  # @param [String] content_type
18
18
  # @param [Hash] params
@@ -6,9 +6,13 @@ module OpenAPIParser::Schemas
6
6
 
7
7
  openapi_attr_values :description
8
8
 
9
+ # @!attribute [r] summary
10
+ # @return [String, nil] short summary of the response (OpenAPI 3.2+)
11
+ openapi_attr_value :summary
12
+
9
13
  # @!attribute [r] content
10
14
  # @return [Hash{String => MediaType}, nil] content_type to MediaType hash
11
- openapi_attr_hash_object :content, MediaType, reference: false
15
+ openapi_attr_hash_object :content, MediaType, reference: OpenAPIParser::MediaTypeSelectable::CONTENT_REFERENCE
12
16
 
13
17
  # @!attribute [r] headers
14
18
  # @return [Hash{String => Header}, nil] header string to Header
@@ -1,4 +1,3 @@
1
- # TODO: support 'not' because I need check reference...
2
1
  # TODO: support 'xml', 'externalDocs'
3
2
  # TODO: support extended property
4
3
 
@@ -96,6 +95,10 @@ module OpenAPIParser::Schemas
96
95
  # @return [Array<Schema, Reference>, nil]
97
96
  openapi_attr_list_object :any_of, Schema, reference: true, schema_key: :anyOf
98
97
 
98
+ # @!attribute [r] not
99
+ # @return [Schema, nil]
100
+ openapi_attr_object :not, Schema, reference: true
101
+
99
102
  # @!attribute [r] items
100
103
  # @return [Schema, nil]
101
104
  openapi_attr_object :items, Schema, reference: true
@@ -112,6 +115,10 @@ module OpenAPIParser::Schemas
112
115
  # @return [Hash{String => Schema}, nil]
113
116
  openapi_attr_hash_object :properties, Schema, reference: true
114
117
 
118
+ # @!attribute [r] defs
119
+ # @return [Hash{String => Schema}, nil] reusable schemas for `$ref` (OpenAPI 3.1+)
120
+ openapi_attr_hash_object :defs, Schema, reference: true, schema_key: :$defs
121
+
115
122
  # @!attribute [r] discriminator
116
123
  # @return [Discriminator, nil]
117
124
  openapi_attr_object :discriminator, Discriminator
@@ -46,12 +46,33 @@ module OpenAPIParser
46
46
  def each_schema(root, &block)
47
47
  return enum_for(:each_schema, root) unless block
48
48
 
49
- visited = {}
50
- walk(root, visited) do |node|
51
- yield node if node.is_a?(OpenAPIParser::Schemas::Schema)
49
+ each_node(root, OpenAPIParser::Schemas::Schema, &block)
50
+ end
51
+
52
+ # yields every parsed object reachable from root that is one of klasses
53
+ def each_node(root, *klasses)
54
+ walk(root, {}) do |node|
55
+ yield node if klasses.any? { |klass| node.is_a?(klass) }
56
+ end
57
+ end
58
+
59
+ # security schemes are not modeled by the parse layer, so rules
60
+ # about them read the raw `components.securitySchemes` map
61
+ def each_security_scheme(root)
62
+ components = root.raw_schema.is_a?(Hash) ? root.raw_schema['components'] : nil
63
+ schemes = components.is_a?(Hash) ? components['securitySchemes'] : nil
64
+ return unless schemes.is_a?(Hash)
65
+
66
+ schemes.each do |name, scheme|
67
+ yield(name, scheme) if scheme.is_a?(Hash)
52
68
  end
53
69
  end
54
70
 
71
+ # escapes a map key for use in a violation path, matching object_reference
72
+ def escape_reference(key)
73
+ key.to_s.gsub('~', '~0').gsub('/', '~1')
74
+ end
75
+
55
76
  def walk(node, visited, &block)
56
77
  return unless node.respond_to?(:_openapi_all_child_objects)
57
78
  return if visited[node.object_id]
@@ -0,0 +1,26 @@
1
+ module OpenAPIParser
2
+ class SpecValidator
3
+ module Rules
4
+ # `additionalOperations` is a 3.2 Path Item addition for non-standard
5
+ # HTTP methods. The parse layer accepts it permissively; this rule
6
+ # reports the version mismatch.
7
+ class AdditionalOperationsBefore32 < Rule
8
+ def check(root)
9
+ return [] unless version_before?('3.2')
10
+
11
+ violations = []
12
+ each_node(root, OpenAPIParser::Schemas::PathItem) do |node|
13
+ raw = node.raw_schema
14
+ next unless raw.is_a?(Hash) && raw.key?('additionalOperations')
15
+
16
+ violations << violation(
17
+ path: "#{node.object_reference}/additionalOperations",
18
+ message: '`additionalOperations` is a 3.2 Path Item addition; earlier documents have no such field',
19
+ )
20
+ end
21
+ violations
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,26 @@
1
+ module OpenAPIParser
2
+ class SpecValidator
3
+ module Rules
4
+ # 3.2 deprecates `allowEmptyValue` on the Parameter Object (3.1 only
5
+ # called it NOT RECOMMENDED). The field is still allowed but
6
+ # discouraged, so we report it as a violation on 3.2 documents.
7
+ class AllowEmptyValueDeprecation < Rule
8
+ def check(root)
9
+ return [] unless version_at_least?('3.2')
10
+
11
+ violations = []
12
+ each_node(root, OpenAPIParser::Schemas::Parameter) do |node|
13
+ raw = node.raw_schema
14
+ next unless raw.is_a?(Hash) && raw.key?('allowEmptyValue')
15
+
16
+ violations << violation(
17
+ path: "#{node.object_reference}/allowEmptyValue",
18
+ message: '`allowEmptyValue` on a Parameter Object is deprecated in 3.2',
19
+ )
20
+ end
21
+ violations
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,26 @@
1
+ module OpenAPIParser
2
+ class SpecValidator
3
+ module Rules
4
+ # `style: cookie` is a 3.2 Parameter style for cookie serialization.
5
+ # Runtime validation ignores `style`; this rule reports the version
6
+ # mismatch.
7
+ class CookieStyleBefore32 < Rule
8
+ def check(root)
9
+ return [] unless version_before?('3.2')
10
+
11
+ violations = []
12
+ each_node(root, OpenAPIParser::Schemas::Parameter) do |node|
13
+ raw = node.raw_schema
14
+ next unless raw.is_a?(Hash) && raw['style'] == 'cookie'
15
+
16
+ violations << violation(
17
+ path: "#{node.object_reference}/style",
18
+ message: '`style: cookie` is a 3.2 Parameter style; earlier documents have no such style',
19
+ )
20
+ end
21
+ violations
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,29 @@
1
+ module OpenAPIParser
2
+ class SpecValidator
3
+ module Rules
4
+ # `defaultMapping` on the Discriminator Object is a 3.2 addition. The
5
+ # parse layer accepts it permissively; this rule reports the version
6
+ # mismatch.
7
+ class DefaultMappingBefore32 < Rule
8
+ def check(root)
9
+ return [] unless version_before?('3.2')
10
+
11
+ violations = []
12
+ each_schema(root) do |schema|
13
+ raw = schema.raw_schema
14
+ next unless raw.is_a?(Hash)
15
+
16
+ discriminator = raw['discriminator']
17
+ next unless discriminator.is_a?(Hash) && discriminator.key?('defaultMapping')
18
+
19
+ violations << violation(
20
+ path: "#{schema.object_reference}/discriminator/defaultMapping",
21
+ message: '`defaultMapping` on a Discriminator Object is a 3.2 addition; earlier documents have no such field',
22
+ )
23
+ end
24
+ violations
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,25 @@
1
+ module OpenAPIParser
2
+ class SpecValidator
3
+ module Rules
4
+ # `$defs` is a JSON Schema 2020-12 keyword that 3.1 adopted. 3.0
5
+ # Schema Objects do not define it. The parse layer resolves `$ref`s
6
+ # into it regardless of version; this rule reports the version mismatch.
7
+ class DefsIn30 < Rule
8
+ def check(root)
9
+ return [] unless version_before?('3.1')
10
+
11
+ violations = []
12
+ each_schema(root) do |schema|
13
+ next unless schema.raw_schema.is_a?(Hash) && schema.raw_schema.key?('$defs')
14
+
15
+ violations << violation(
16
+ path: schema.object_reference,
17
+ message: '`$defs` is a 3.1 addition (from JSON Schema 2020-12); 3.0 Schema Objects do not define it',
18
+ )
19
+ end
20
+ violations
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,48 @@
1
+ module OpenAPIParser
2
+ class SpecValidator
3
+ module Rules
4
+ # 3.2 adds `dataValue` and `serializedValue` to the Example Object.
5
+ # Example Objects are not modeled by the parse layer, so this rule
6
+ # inspects the raw `examples` maps on Parameter, Media Type, and
7
+ # Header objects, plus `components.examples`. Media Types under a
8
+ # Parameter or Header `content` and anything under callbacks are not
9
+ # modeled, so examples there are not reached.
10
+ class ExampleValueFieldsBefore32 < Rule
11
+ NEW_FIELDS = %w[dataValue serializedValue].freeze
12
+
13
+ def check(root)
14
+ return [] unless version_before?('3.2')
15
+
16
+ violations = []
17
+ node_classes = [
18
+ OpenAPIParser::Schemas::Parameter,
19
+ OpenAPIParser::Schemas::MediaType,
20
+ OpenAPIParser::Schemas::Header,
21
+ OpenAPIParser::Schemas::Components,
22
+ ]
23
+ each_node(root, *node_classes) do |node|
24
+ raw = node.raw_schema
25
+ next unless raw.is_a?(Hash)
26
+
27
+ examples = raw['examples']
28
+ next unless examples.is_a?(Hash)
29
+
30
+ examples.each do |name, example|
31
+ next unless example.is_a?(Hash)
32
+
33
+ NEW_FIELDS.each do |field|
34
+ next unless example.key?(field)
35
+
36
+ violations << violation(
37
+ path: "#{node.object_reference}/examples/#{escape_reference(name)}/#{field}",
38
+ message: "`#{field}` on an Example Object is a 3.2 addition; earlier documents have no such field",
39
+ )
40
+ end
41
+ end
42
+ end
43
+ violations
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,24 @@
1
+ module OpenAPIParser
2
+ class SpecValidator
3
+ module Rules
4
+ # `components.mediaTypes` is a 3.2 addition. The parse layer accepts
5
+ # it permissively; this rule reports the version mismatch.
6
+ class MediaTypesBefore32 < Rule
7
+ def check(root)
8
+ return [] unless version_before?('3.2')
9
+
10
+ components = root.components
11
+ return [] unless components
12
+
13
+ raw = components.raw_schema
14
+ return [] unless raw.is_a?(Hash) && raw.key?('mediaTypes')
15
+
16
+ [violation(
17
+ path: "#{components.object_reference}/mediaTypes",
18
+ message: '`components.mediaTypes` is a 3.2 addition; earlier documents should not declare it',
19
+ )]
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,26 @@
1
+ module OpenAPIParser
2
+ class SpecValidator
3
+ module Rules
4
+ # `query` is a 3.2 Path Item addition for the HTTP QUERY method. The
5
+ # parse layer accepts it permissively; this rule reports the version
6
+ # mismatch.
7
+ class QueryMethodBefore32 < Rule
8
+ def check(root)
9
+ return [] unless version_before?('3.2')
10
+
11
+ violations = []
12
+ each_node(root, OpenAPIParser::Schemas::PathItem) do |node|
13
+ raw = node.raw_schema
14
+ next unless raw.is_a?(Hash) && raw.key?('query')
15
+
16
+ violations << violation(
17
+ path: "#{node.object_reference}/query",
18
+ message: 'the `query` operation is a 3.2 Path Item addition; earlier documents have no such field',
19
+ )
20
+ end
21
+ violations
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,26 @@
1
+ module OpenAPIParser
2
+ class SpecValidator
3
+ module Rules
4
+ # `in: querystring` is a 3.2 Parameter location that treats the whole
5
+ # query string as one value described by `content`. Runtime validation
6
+ # does not support it yet; this rule reports the version mismatch.
7
+ class QuerystringBefore32 < Rule
8
+ def check(root)
9
+ return [] unless version_before?('3.2')
10
+
11
+ violations = []
12
+ each_node(root, OpenAPIParser::Schemas::Parameter) do |node|
13
+ raw = node.raw_schema
14
+ next unless raw.is_a?(Hash) && raw['in'] == 'querystring'
15
+
16
+ violations << violation(
17
+ path: "#{node.object_reference}/in",
18
+ message: '`in: querystring` is a 3.2 Parameter location; earlier documents have no such location',
19
+ )
20
+ end
21
+ violations
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,25 @@
1
+ module OpenAPIParser
2
+ class SpecValidator
3
+ module Rules
4
+ # 3.2 adds `summary` to the Response Object. The parse layer accepts it
5
+ # permissively; this rule reports the version mismatch.
6
+ class ResponseSummaryBefore32 < Rule
7
+ def check(root)
8
+ return [] unless version_before?('3.2')
9
+
10
+ violations = []
11
+ each_node(root, OpenAPIParser::Schemas::Response) do |node|
12
+ raw = node.raw_schema
13
+ next unless raw.is_a?(Hash) && raw.key?('summary')
14
+
15
+ violations << violation(
16
+ path: "#{node.object_reference}/summary",
17
+ message: '`summary` on a Response Object is a 3.2 addition; earlier documents have no such field',
18
+ )
19
+ end
20
+ violations
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,41 @@
1
+ module OpenAPIParser
2
+ class SpecValidator
3
+ module Rules
4
+ # 3.2 adds `deprecated` and `oauth2MetadataUrl` to the Security Scheme
5
+ # Object and the `deviceAuthorization` OAuth flow. Security schemes are
6
+ # not modeled by the parse layer, so this rule inspects the raw
7
+ # `components.securitySchemes` map; schemes given as a `$ref` are not
8
+ # followed.
9
+ class SecuritySchemeFieldsBefore32 < Rule
10
+ NEW_FIELDS = %w[deprecated oauth2MetadataUrl].freeze
11
+
12
+ def check(root)
13
+ return [] unless version_before?('3.2')
14
+
15
+ violations = []
16
+ each_security_scheme(root) do |name, scheme|
17
+ base = "#/components/securitySchemes/#{escape_reference(name)}"
18
+
19
+ NEW_FIELDS.each do |field|
20
+ next unless scheme.key?(field)
21
+
22
+ violations << violation(
23
+ path: "#{base}/#{field}",
24
+ message: "`#{field}` on a Security Scheme Object is a 3.2 addition; earlier documents have no such field",
25
+ )
26
+ end
27
+
28
+ flows = scheme['flows']
29
+ next unless flows.is_a?(Hash) && flows.key?('deviceAuthorization')
30
+
31
+ violations << violation(
32
+ path: "#{base}/flows/deviceAuthorization",
33
+ message: 'the `deviceAuthorization` OAuth flow is a 3.2 addition; earlier documents have no such flow',
34
+ )
35
+ end
36
+ violations
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,35 @@
1
+ module OpenAPIParser
2
+ class SpecValidator
3
+ module Rules
4
+ # 3.2 adds `name` to the Server Object. Servers are not modeled by
5
+ # the parse layer, so this rule inspects the raw `servers` arrays on
6
+ # the root, Path Item, and Operation objects. Servers under callbacks
7
+ # and Link Objects are not reached.
8
+ class ServerNameBefore32 < Rule
9
+ def check(root)
10
+ return [] unless version_before?('3.2')
11
+
12
+ violations = []
13
+ node_classes = [OpenAPIParser::Schemas::OpenAPI, OpenAPIParser::Schemas::PathItem, OpenAPIParser::Schemas::Operation]
14
+ each_node(root, *node_classes) do |node|
15
+ raw = node.raw_schema
16
+ next unless raw.is_a?(Hash)
17
+
18
+ servers = raw['servers']
19
+ next unless servers.is_a?(Array)
20
+
21
+ servers.each_with_index do |server, index|
22
+ next unless server.is_a?(Hash) && server.key?('name')
23
+
24
+ violations << violation(
25
+ path: "#{node.object_reference}/servers/#{index}/name",
26
+ message: '`name` on a Server Object is a 3.2 addition; earlier documents have no such field',
27
+ )
28
+ end
29
+ end
30
+ violations
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,56 @@
1
+ module OpenAPIParser
2
+ class SpecValidator
3
+ module Rules
4
+ # 3.2 adds `itemSchema`, `itemEncoding`, and `prefixEncoding` to the
5
+ # Media Type Object for sequential media types (SSE, JSON Lines,
6
+ # JSON Sequences), and lets an Encoding Object nest `encoding`,
7
+ # `itemEncoding`, and `prefixEncoding`. The parse layer accepts
8
+ # `itemSchema` permissively; this rule reports the version mismatch.
9
+ # Media Types under a Parameter or Header `content` are not modeled,
10
+ # so fields there are not reached.
11
+ class StreamingFieldsBefore32 < Rule
12
+ NEW_FIELDS = %w[itemSchema itemEncoding prefixEncoding].freeze
13
+ NEW_ENCODING_FIELDS = %w[encoding itemEncoding prefixEncoding].freeze
14
+
15
+ def check(root)
16
+ return [] unless version_before?('3.2')
17
+
18
+ violations = []
19
+ each_node(root, OpenAPIParser::Schemas::MediaType) do |node|
20
+ raw = node.raw_schema
21
+ next unless raw.is_a?(Hash)
22
+
23
+ NEW_FIELDS.each do |field|
24
+ next unless raw.key?(field)
25
+
26
+ violations << violation(
27
+ path: "#{node.object_reference}/#{field}",
28
+ message: "`#{field}` on a Media Type Object is a 3.2 addition; earlier documents have no such field",
29
+ )
30
+ end
31
+
32
+ violations.concat(encoding_violations(node, raw['encoding']))
33
+ end
34
+ violations
35
+ end
36
+
37
+ private
38
+
39
+ def encoding_violations(node, encodings)
40
+ return [] unless encodings.is_a?(Hash)
41
+
42
+ encodings.flat_map do |property, encoding|
43
+ next [] unless encoding.is_a?(Hash)
44
+
45
+ NEW_ENCODING_FIELDS.select { |field| encoding.key?(field) }.map do |field|
46
+ violation(
47
+ path: "#{node.object_reference}/encoding/#{escape_reference(property)}/#{field}",
48
+ message: "`#{field}` on an Encoding Object is a 3.2 addition; earlier documents have no such field",
49
+ )
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,34 @@
1
+ module OpenAPIParser
2
+ class SpecValidator
3
+ module Rules
4
+ # 3.2 adds `summary`, `parent`, and `kind` to the Tag Object.
5
+ # Tags are not modeled by the parse layer, so this rule inspects the
6
+ # raw root-level `tags` array.
7
+ class TagFieldsBefore32 < Rule
8
+ NEW_FIELDS = %w[summary parent kind].freeze
9
+
10
+ def check(root)
11
+ return [] unless version_before?('3.2')
12
+
13
+ tags = root.raw_schema.is_a?(Hash) ? root.raw_schema['tags'] : nil
14
+ return [] unless tags.is_a?(Array)
15
+
16
+ violations = []
17
+ tags.each_with_index do |tag, index|
18
+ next unless tag.is_a?(Hash)
19
+
20
+ NEW_FIELDS.each do |field|
21
+ next unless tag.key?(field)
22
+
23
+ violations << violation(
24
+ path: "#/tags/#{index}/#{field}",
25
+ message: "`#{field}` on a Tag Object is a 3.2 addition; earlier documents have no such field",
26
+ )
27
+ end
28
+ end
29
+ violations
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,29 @@
1
+ module OpenAPIParser
2
+ class SpecValidator
3
+ module Rules
4
+ # 3.2 deprecates `attribute: true` on the XML Object in favor of
5
+ # `nodeType: attribute`. The field is still allowed but discouraged,
6
+ # so we report it as a violation on 3.2 documents.
7
+ class XmlAttributeDeprecation < Rule
8
+ def check(root)
9
+ return [] unless version_at_least?('3.2')
10
+
11
+ violations = []
12
+ each_schema(root) do |schema|
13
+ raw = schema.raw_schema
14
+ next unless raw.is_a?(Hash)
15
+
16
+ xml = raw['xml']
17
+ next unless xml.is_a?(Hash) && xml['attribute'] == true
18
+
19
+ violations << violation(
20
+ path: schema.object_reference,
21
+ message: '`attribute` on an XML Object is deprecated in 3.2; use `nodeType: attribute`',
22
+ )
23
+ end
24
+ violations
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,29 @@
1
+ module OpenAPIParser
2
+ class SpecValidator
3
+ module Rules
4
+ # 3.2 adds `nodeType` to the XML Object (replacing `attribute` and
5
+ # `wrapped`). XML Objects are not modeled by the parse layer, so this
6
+ # rule inspects the raw `xml` field on schemas.
7
+ class XmlNodeTypeBefore32 < Rule
8
+ def check(root)
9
+ return [] unless version_before?('3.2')
10
+
11
+ violations = []
12
+ each_schema(root) do |schema|
13
+ raw = schema.raw_schema
14
+ next unless raw.is_a?(Hash)
15
+
16
+ xml = raw['xml']
17
+ next unless xml.is_a?(Hash) && xml.key?('nodeType')
18
+
19
+ violations << violation(
20
+ path: schema.object_reference,
21
+ message: '`nodeType` on an XML Object is a 3.2 addition; earlier documents have no such field',
22
+ )
23
+ end
24
+ violations
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end