openapi_parser 0.1.2 → 0.1.3

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 (36) hide show
  1. checksums.yaml +4 -4
  2. data/lib/openapi_parser.rb +14 -3
  3. data/lib/openapi_parser/concern.rb +1 -0
  4. data/lib/openapi_parser/concerns/expandable.rb +76 -0
  5. data/lib/openapi_parser/concerns/findable.rb +12 -1
  6. data/lib/openapi_parser/concerns/parseable.rb +8 -2
  7. data/lib/openapi_parser/config.rb +24 -0
  8. data/lib/openapi_parser/errors.rb +58 -0
  9. data/lib/openapi_parser/parameter_validator.rb +23 -0
  10. data/lib/openapi_parser/path_item_finder.rb +4 -4
  11. data/lib/openapi_parser/reference_expander.rb +9 -0
  12. data/lib/openapi_parser/request_operation.rb +36 -9
  13. data/lib/openapi_parser/schema_validator.rb +65 -128
  14. data/lib/openapi_parser/schema_validators/any_of_validator.rb +14 -0
  15. data/lib/openapi_parser/schema_validators/array_validator.rb +25 -0
  16. data/lib/openapi_parser/schema_validators/base.rb +18 -0
  17. data/lib/openapi_parser/schema_validators/boolean_validator.rb +22 -0
  18. data/lib/openapi_parser/schema_validators/enumable.rb +10 -0
  19. data/lib/openapi_parser/schema_validators/float_validator.rb +29 -0
  20. data/lib/openapi_parser/schema_validators/integer_validator.rb +26 -0
  21. data/lib/openapi_parser/schema_validators/nil_validator.rb +10 -0
  22. data/lib/openapi_parser/schema_validators/object_validator.rb +28 -0
  23. data/lib/openapi_parser/schema_validators/options.rb +10 -0
  24. data/lib/openapi_parser/schema_validators/string_validator.rb +38 -0
  25. data/lib/openapi_parser/schemas/base.rb +1 -0
  26. data/lib/openapi_parser/schemas/media_type.rb +2 -2
  27. data/lib/openapi_parser/schemas/openapi.rb +4 -3
  28. data/lib/openapi_parser/schemas/operation.rb +31 -2
  29. data/lib/openapi_parser/schemas/parameter.rb +16 -0
  30. data/lib/openapi_parser/schemas/reference.rb +2 -0
  31. data/lib/openapi_parser/schemas/request_body.rb +3 -3
  32. data/lib/openapi_parser/schemas/response.rb +10 -0
  33. data/lib/openapi_parser/schemas/responses.rb +11 -0
  34. data/lib/openapi_parser/version.rb +1 -1
  35. data/openapi_parser.gemspec +1 -0
  36. metadata +32 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1f0397e692b94ed93a1ddea1b86020f4ba38c53c7f62b2c97f67ca30ea15a209
4
- data.tar.gz: fcf39298f2a36666354248f10389852c51ced48daafacb21d90d4960cdf3739d
3
+ metadata.gz: ddf9e2711b22d6b164e2bccd23ec3d06a2bd66918b61213a2f0d5c84eb0f5fa5
4
+ data.tar.gz: 908dc71a0e892164a987968587c0a4899e91207c27bbd8a4e65d27b517398d8e
5
5
  SHA512:
6
- metadata.gz: c877e1f8b4029aa7da1acceeb11ad0f4f4d35a9f95e00f194d21a048991e64bd2b7ad002bc7949c2f9a31a67ed10c51b74d5dd26460ab2c3d2351170e6150a57
7
- data.tar.gz: 0eb513c8deef047edc07c5aa6a3b2c3f622c4c64a75998adfc03a8609f7a91cf077b52c79d923db93e47bed8fb226175584d84217907ea60079f572588abbd4b
6
+ metadata.gz: 1e38fba425316e75580b5dbfd2f2e012890e4d4d9ad9e0abd83f74c8500176d21b1ddee533cf9120accd9355e60b8d5307fb3913d9678d6e3239c9fcbb3727f1
7
+ data.tar.gz: a7e22a6b8248fb0931c396ba1a8b07b8f9cea6a7e0f6832a1a2b1af4eba82bf673357044d9d0f8bb0c9c5bf5929106d47356e97411412bab60ef5fcc5efac703
@@ -1,15 +1,26 @@
1
+ require 'time'
2
+
1
3
  require 'openapi_parser/version'
4
+ require 'openapi_parser/config'
5
+ require 'openapi_parser/errors'
2
6
  require 'openapi_parser/concern'
3
7
  require 'openapi_parser/schemas'
4
8
  require 'openapi_parser/path_item_finder'
5
9
  require 'openapi_parser/request_operation'
6
10
  require 'openapi_parser/schema_validator'
11
+ require 'openapi_parser/parameter_validator'
12
+ require 'openapi_parser/reference_expander'
7
13
 
8
14
  module OpenAPIParser
9
15
  class << self
10
- # @param [OpenAPIParser::Schemas::OpenAPI]
11
- def parse(schema)
12
- Schemas::OpenAPI.new(schema)
16
+ # @return [OpenAPIParser::Schemas::OpenAPI]
17
+ def parse(schema, config = {})
18
+ c = Config.new(config)
19
+ root = Schemas::OpenAPI.new(schema, c)
20
+
21
+ OpenAPIParser::ReferenceExpander.expand(root) if c.expand_reference
22
+
23
+ root
13
24
  end
14
25
  end
15
26
  end
@@ -1,2 +1,3 @@
1
1
  require_relative 'concerns/parseable'
2
2
  require_relative 'concerns/findable'
3
+ require_relative 'concerns/expandable'
@@ -0,0 +1,76 @@
1
+ module OpenAPIParser::Expandable
2
+ def expand_reference(root)
3
+ expand_list_objects(root, self.class._openapi_attr_list_objects&.keys)
4
+ expand_objects(root, self.class._openapi_attr_objects&.keys)
5
+ expand_hash_objects(root, self.class._openapi_attr_hash_objects&.keys)
6
+ expand_hash_objects(root, self.class._openapi_attr_hash_body_objects&.keys)
7
+ end
8
+
9
+ private
10
+
11
+ def expand_hash_objects(root, attribute_names)
12
+ return unless attribute_names
13
+
14
+ attribute_names.each do |name|
15
+ h = send(name)
16
+ next if h.nil?
17
+
18
+ update_values = h.map do |k, v|
19
+ next [k, referenced_object(root, v)] if v.is_a?(OpenAPIParser::Schemas::Reference)
20
+ v.expand_reference(root) if v.is_a?(OpenAPIParser::Expandable)
21
+
22
+ nil
23
+ end
24
+
25
+ update_values.compact.each do |k, v|
26
+ _update_child_object(h[k], v)
27
+ h[k] = v
28
+ end
29
+ end
30
+ end
31
+
32
+ def expand_objects(root, attribute_names)
33
+ return unless attribute_names
34
+
35
+ attribute_names.each do |name|
36
+ v = send(name)
37
+ next if v.nil?
38
+
39
+ if v.is_a?(OpenAPIParser::Schemas::Reference)
40
+ obj = referenced_object(root, v)
41
+ _update_child_object(v, obj)
42
+ self.instance_variable_set("@#{name}", obj)
43
+ elsif v.is_a?(OpenAPIParser::Expandable)
44
+ v.expand_reference(root)
45
+ end
46
+ end
47
+ end
48
+
49
+ def expand_list_objects(root, attribute_names)
50
+ return unless attribute_names
51
+
52
+ attribute_names.each do |name|
53
+ l = send(name)
54
+ next if l.nil?
55
+
56
+ l.each_with_index do |v, idx|
57
+ if v.is_a?(OpenAPIParser::Schemas::Reference)
58
+ obj = referenced_object(root, v)
59
+ _update_child_object(v, obj)
60
+ l[idx] = obj
61
+ elsif v.is_a?(OpenAPIParser::Expandable)
62
+ v.expand_reference(root)
63
+ end
64
+ end
65
+ end
66
+ end
67
+
68
+
69
+ # @param [OpenAPIParser::Schemas::OpenAPI] root
70
+ # @param [OpenAPIParser::Schemas::Reference] reference
71
+ def referenced_object(root, reference)
72
+ obj = root.find_object(reference.ref)
73
+
74
+ obj.is_a?(OpenAPIParser::Schemas::Reference) ? referenced_object(root, obj) : obj
75
+ end
76
+ end
@@ -10,7 +10,12 @@ module OpenAPIParser::Findable
10
10
  return obj
11
11
  end
12
12
 
13
- _openapi_all_child_objects.each do |c|
13
+ if (child = _openapi_all_child_objects[reference])
14
+ @find_object_cache[reference] = child
15
+ return child
16
+ end
17
+
18
+ _openapi_all_child_objects.values.each do |c|
14
19
  if (obj = c.find_object(reference))
15
20
  @find_object_cache[reference] = obj
16
21
  return obj
@@ -19,4 +24,10 @@ module OpenAPIParser::Findable
19
24
 
20
25
  nil
21
26
  end
27
+
28
+ def purge_object_cache
29
+ @find_object_cache = {}
30
+
31
+ _openapi_all_child_objects.values.each(&:purge_object_cache)
32
+ end
22
33
  end
@@ -57,16 +57,22 @@ module OpenAPIParser::Parseable
57
57
  end
58
58
  end
59
59
 
60
+ def child_from_reference(reference)
61
+ _openapi_all_child_objects[reference]
62
+ end
60
63
 
61
64
  def _openapi_all_child_objects
62
- @_openapi_all_child_objects ||= []
65
+ @_openapi_all_child_objects ||= {}
63
66
  end
64
67
 
65
68
  def _add_child_object(object)
66
69
  return unless object.is_a?(OpenAPIParser::Parseable)
67
- _openapi_all_child_objects << object
70
+ _openapi_all_child_objects[object.object_reference] = object
68
71
  end
69
72
 
73
+ def _update_child_object(old, new)
74
+ _openapi_all_child_objects[old.object_reference] = new
75
+ end
70
76
 
71
77
  def load_data
72
78
  create_attr_values(self.class._openapi_attr_values)
@@ -0,0 +1,24 @@
1
+ class OpenAPIParser::Config
2
+ def initialize(config)
3
+ @config = config
4
+ end
5
+
6
+ def datetime_coerce_class
7
+ @config[:datetime_coerce_class]
8
+ end
9
+
10
+ def coerce_value
11
+ @config[:coerce_value]
12
+ end
13
+
14
+ def expand_reference
15
+ @config.fetch(:expand_reference, true)
16
+ end
17
+
18
+ def request_validator_options
19
+ @request_options ||= OpenAPIParser::SchemaValidator::Options.new(coerce_value: coerce_value, datetime_coerce_class: datetime_coerce_class)
20
+ end
21
+
22
+ alias_method :request_body_options, :request_validator_options
23
+ alias_method :path_params_options, :request_validator_options
24
+ end
@@ -0,0 +1,58 @@
1
+ module OpenAPIParser
2
+ class OpenAPIError < StandardError
3
+ def initialize(reference)
4
+ @reference = reference
5
+ end
6
+ end
7
+
8
+ class ValidateError < OpenAPIError
9
+ def initialize(data, type, reference)
10
+ super(reference)
11
+ @data = data
12
+ @type = type
13
+ end
14
+
15
+ def message
16
+ "#{@data} class is #{@data.class} but it's not valid #{@type} in #{@reference}"
17
+ end
18
+ end
19
+
20
+ class NotNullError < OpenAPIError
21
+ def message
22
+ "#{@reference} don't allow null"
23
+ end
24
+ end
25
+
26
+ class NotExistRequiredKey < OpenAPIError
27
+ def initialize(keys, reference)
28
+ super(reference)
29
+ @keys = keys
30
+ end
31
+
32
+ def message
33
+ "required parameters #{@keys.join(",")} not exist in #{@reference}"
34
+ end
35
+ end
36
+
37
+ class NotAnyOf < OpenAPIError
38
+ def initialize(value, reference)
39
+ super(reference)
40
+ @value = value
41
+ end
42
+
43
+ def message
44
+ "#{@value} isn't any of in #{@reference}"
45
+ end
46
+ end
47
+
48
+ class NotEnumInclude < OpenAPIError
49
+ def initialize(value, reference)
50
+ super(reference)
51
+ @value = value
52
+ end
53
+
54
+ def message
55
+ "#{@value} isn't include enum in #{@reference}"
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,23 @@
1
+ class OpenAPIParser::ParameterValidator
2
+ class << self
3
+ # @param [Hash{String => Parameter}] parameters_hash
4
+ # @param [Hash] params
5
+ # @param [String] object_reference
6
+ # @param [OpenAPIParser::SchemaValidator::Options] options
7
+ def validate_parameter(parameters_hash, params, object_reference, options)
8
+ no_exist_required_key = []
9
+ parameters_hash.each do |k, v|
10
+ if (data = params[k])
11
+ coerced = v.validate_params(data, options)
12
+ params[k] = coerced if options.coerce_value
13
+ elsif v.required
14
+ no_exist_required_key << k
15
+ end
16
+ end
17
+
18
+ raise OpenAPIParser::NotExistRequiredKey.new(no_exist_required_key, object_reference) unless no_exist_required_key.empty?
19
+
20
+ params
21
+ end
22
+ end
23
+ end
@@ -9,11 +9,11 @@ class OpenAPIParser::PathItemFinder
9
9
 
10
10
  # @param [String, Symbol] http_method like (get, post .... allow symbol)
11
11
  # @param [String] request_path
12
- # @return [OpenAPIParser::Schemas::Paths]
12
+ # return operation_object, original_path, path_params
13
13
  def operation_object(http_method, request_path)
14
14
  if (path_item_object = @paths.path[request_path])
15
15
  if (op = path_item_object.operation(http_method))
16
- return op, {} # find no path_params path
16
+ return op, request_path, {} # find no path_params path
17
17
  end
18
18
  end
19
19
 
@@ -83,11 +83,11 @@ class OpenAPIParser::PathItemFinder
83
83
 
84
84
  def parse_request_path(http_method, request_path)
85
85
  original_path, path_params = @root.find_full_path(request_path.split("/"))
86
- return nil, path_params unless original_path
86
+ return nil, nil, {} unless original_path # # can't find
87
87
 
88
88
  path_item_object = @paths.path[original_path]
89
89
  obj = path_item_object.operation(http_method.to_s)
90
90
 
91
- [obj, path_params]
91
+ [obj, original_path, path_params]
92
92
  end
93
93
  end
@@ -0,0 +1,9 @@
1
+ class OpenAPIParser::ReferenceExpander
2
+ class << self
3
+ # @param [OpenAPIParser::Schemas::OpenAPI] openapi
4
+ def expand(openapi)
5
+ openapi.expand_reference(openapi)
6
+ openapi.purge_object_cache
7
+ end
8
+ end
9
+ end
@@ -2,29 +2,56 @@
2
2
 
3
3
  class OpenAPIParser::RequestOperation
4
4
  class << self
5
+ # @param [OpenAPIParser::Config] config
5
6
  # @param [OpenAPIParser::PathItemFinder] path_item_finder
6
- def create(http_method, request_path, path_item_finder)
7
- endpoint, path_params = path_item_finder.operation_object(http_method, request_path)
7
+ # @return [OpenAPIParser::RequestOperation, nil]
8
+ def create(http_method, request_path, path_item_finder, config)
9
+ operation, original_path, path_params = path_item_finder.operation_object(http_method, request_path)
8
10
 
9
- return nil unless endpoint
11
+ return nil unless operation
10
12
 
11
- self.new(endpoint, path_params)
13
+ self.new(http_method, original_path, operation, path_params, config)
12
14
  end
13
15
  end
14
16
 
15
17
  # @!attribute [r] operation_object
16
- # @return [OpenAPIParser::Schemas::Operation, nil]
18
+ # @return [OpenAPIParser::Schemas::Operation]
17
19
  # @!attribute [r] path_params
18
20
  # @return [Hash{String => String}]
19
- attr_reader :operation_object, :path_params
21
+ # @!attribute [r] config
22
+ # # @return [OpenAPIParser::Config]
23
+ # @!attribute [r] http_method
24
+ # @return [String]
25
+ # @!attribute [r] original_path
26
+ # @return [String]
27
+ attr_reader :operation_object, :path_params, :config, :http_method, :original_path
20
28
 
21
- def initialize(operation_object, path_params)
29
+ # @param [OpenAPIParser::Config] config
30
+ def initialize(http_method, original_path, operation_object, path_params, config)
31
+ @http_method = http_method.to_s
32
+ @original_path = original_path
22
33
  @operation_object = operation_object
23
34
  @path_params = path_params || {}
35
+ @config = config
36
+ end
37
+
38
+ def validate_path_params(options = nil)
39
+ options = config.path_params_options unless options
40
+ operation_object&.validate_path_params(path_params, options)
24
41
  end
25
42
 
26
43
  # support application/json only :(
27
- def validate_request_body(content_type, params)
28
- operation_object&.validate_request_body(content_type, params)
44
+ def validate_request_body(content_type, params, options = nil)
45
+ options = config.request_body_options unless options
46
+ operation_object&.validate_request_body(content_type, params, options)
47
+ end
48
+
49
+ def validate_response_body(status_code, content_type, data)
50
+ operation_object&.validate_response_body(status_code, content_type, data)
51
+ end
52
+
53
+ def validate_request_parameter(params, options = nil)
54
+ options = config.request_validator_options unless options
55
+ operation_object&.validate_request_parameter(params, options)
29
56
  end
30
57
  end
@@ -1,172 +1,109 @@
1
- class OpenAPIParser::SchemaValidator
2
- class ValidateError < StandardError
3
- def initialize(data, type, reference)
4
- @data = data
5
- @type = type
6
- @reference = reference
7
- end
8
-
9
- def message
10
- "#{@data} class is #{@data.class} but it's not valid #{@type} in #{@reference}"
11
- end
12
- end
13
-
14
- class NotNullError < StandardError
15
- def initialize(reference)
16
- @reference = reference
17
- end
18
-
19
- def message
20
- "#{@reference} don't allow null"
21
- end
22
- end
1
+ require_relative 'schema_validators/options'
2
+ require_relative 'schema_validators/enumable'
3
+ require_relative 'schema_validators/base'
4
+ require_relative 'schema_validators/string_validator'
5
+ require_relative 'schema_validators/integer_validator'
6
+ require_relative 'schema_validators/float_validator'
7
+ require_relative 'schema_validators/boolean_validator'
8
+ require_relative 'schema_validators/object_validator'
9
+ require_relative 'schema_validators/array_validator'
10
+ require_relative 'schema_validators/any_of_validator'
11
+ require_relative 'schema_validators/nil_validator'
23
12
 
24
- class NotExistRequiredKey < StandardError
25
- def initialize(keys, reference)
26
- @keys = keys
27
- @reference = reference
28
- end
29
-
30
- def message
31
- "required parameters #{@keys.join(",")} not exist in #{@reference}"
32
- end
33
- end
34
-
35
- class NotAnyOf < StandardError
36
- def initialize(value, reference)
37
- @value = value
38
- @reference = reference
39
- end
40
-
41
- def message
42
- "#{@value} isn't any of in #{@reference}"
43
- end
44
- end
45
-
46
- class NotEnumInclude < StandardError
47
- def initialize(value, reference)
48
- @value = value
49
- @reference = reference
50
- end
13
+ class OpenAPIParser::SchemaValidator
14
+ class ValidatorOption
51
15
 
52
- def message
53
- "#{@value} isn't include enum in #{@reference}"
54
- end
55
16
  end
56
17
 
57
18
  class << self
58
19
  # @param [Hash] value
59
20
  # @param [OpenAPIParser::Schemas::Schema]
60
- def validate(value, schema)
61
- new(value, schema).validate_data
21
+ # @param [OpenAPIParser::SchemaValidator::Options] options
22
+ # @return [Object] coerced or original params
23
+ def validate(value, schema, options)
24
+ new(value, schema, options).validate_data
62
25
  end
63
26
  end
64
27
 
65
28
  # @param [Hash] value
66
29
  # @param [OpenAPIParser::Schemas::Schema] schema
67
- def initialize(value, schema)
30
+ # @param [OpenAPIParser::SchemaValidator::Options] options
31
+ def initialize(value, schema, options)
68
32
  @value = value
69
33
  @schema = schema
34
+ @coerce_value = options.coerce_value
35
+ @datetime_coerce_class = options.datetime_coerce_class
70
36
  end
71
37
 
38
+ # @return [Object] coerced or original params
72
39
  def validate_data
73
- err = validate_schema(@value, @schema)
40
+ coerced, err = validate_schema(@value, @schema)
74
41
  raise err if err
75
- nil
42
+ coerced
76
43
  end
77
44
 
78
- private
79
-
80
- # @param [Object] value
81
- # @param [OpenAPIParser::Schemas::Schema] schema
82
- def validate_schema(value, schema)
83
- return unless schema
84
- return validate_any_of(value, schema) if schema.any_of
85
-
86
- if value.nil?
87
- return if schema.nullable
88
- return NotNullError.new(schema.object_reference)
89
- end
90
-
91
- case schema.type
92
- when "string"
93
- return validate_string(value, schema) if value.is_a?(String)
94
- when "integer"
95
- return validate_integer(value, schema) if value.is_a?(Integer)
96
- when "boolean"
97
- return if value.is_a?(TrueClass)
98
- return if value.is_a?(FalseClass)
99
- when "number"
100
- return validate_integer(value, schema) if value.is_a?(Integer)
101
- return validate_number(value, schema) if value.is_a?(Numeric)
102
- when "object"
103
- return validate_object(value, schema) if value.is_a?(Hash)
104
- when "array"
105
- return validate_array(value, schema) if value.is_a?(Array)
106
- else
107
- # TODO: unknown type support
108
- end
109
-
110
- ValidateError.new(value, schema.type, schema.object_reference)
111
- end
112
-
113
- # @param [Object] value
114
- # @param [OpenAPIParser::Schemas::Schema] schema
115
- def validate_any_of(value, schema)
116
- # in all schema return error (=true) not any of data
117
- schema.any_of.all? { |s| validate_schema(value, s) } ? NotAnyOf.new(value, schema.object_reference) : nil
45
+ def validate_error(value, schema)
46
+ [nil, OpenAPIParser::ValidateError.new(value, schema.type, schema.object_reference)]
118
47
  end
119
48
 
120
49
  def validate_string(value, schema)
121
- check_enum_include(value, schema)
50
+ (@string_validator ||= OpenAPIParser::SchemaValidator::StringValidator.new(self, @coerce_value, @datetime_coerce_class)).coerce_and_validate(value, schema)
122
51
  end
123
52
 
124
53
  def validate_integer(value, schema)
125
- check_enum_include(value, schema)
54
+ (@integer_validator ||= OpenAPIParser::SchemaValidator::IntegerValidator.new(self, @coerce_value)).coerce_and_validate(value, schema)
126
55
  end
127
56
 
128
- def validate_number(value, schema)
129
- check_enum_include(value, schema)
57
+ def validate_float(value, schema)
58
+ (@float_validator ||= OpenAPIParser::SchemaValidator::FloatValidator.new(self, @coerce_value)).coerce_and_validate(value, schema)
130
59
  end
131
60
 
132
- # @param [Object] value
133
- # @param [OpenAPIParser::Schemas::Schema] schema
134
- def check_enum_include(value, schema)
135
- return unless schema.enum
136
- return if schema.enum.include?(value)
137
-
138
- NotEnumInclude.new(value, schema.object_reference)
61
+ def validate_boolean(value, schema)
62
+ (@boolean_validator ||= OpenAPIParser::SchemaValidator::BooleanValidator.new(self, @coerce_value)).coerce_and_validate(value, schema)
139
63
  end
140
64
 
141
- # @param [Hash] value
142
- # @param [OpenAPIParser::Schemas::Schema] schema
143
65
  def validate_object(value, schema)
144
- return unless schema.properties
145
- required_set = schema.required ? schema.required.to_set : Set.new
66
+ (@object_validator ||= OpenAPIParser::SchemaValidator::ObjectValidator.new(self, @coerce_value)).coerce_and_validate(value, schema)
67
+ end
146
68
 
147
- value.each do |name, v|
148
- s = schema.properties[name]
149
- err = validate_schema(v, s)
150
- return err if err
69
+ def validate_array(value, schema)
70
+ (@array_validator ||= OpenAPIParser::SchemaValidator::ArrayValidator.new(self, @coerce_value)).coerce_and_validate(value, schema)
71
+ end
151
72
 
152
- required_set.delete(name)
153
- end
73
+ def validate_any_of(value, schema)
74
+ (@any_of_validator ||= OpenAPIParser::SchemaValidator::AnyOfValidator.new(self, @coerce_value)).coerce_and_validate(value, schema)
75
+ end
154
76
 
155
- return NotExistRequiredKey.new(required_set.to_a, schema.object_reference) unless required_set.empty?
156
- nil
77
+ def validate_nil(value, schema)
78
+ (@nil_validator ||= OpenAPIParser::SchemaValidator::NilValidator.new(self, @coerce_value)).coerce_and_validate(value, schema)
157
79
  end
158
80
 
159
- # @param [Array] value
81
+ # @param [Object] value
160
82
  # @param [OpenAPIParser::Schemas::Schema] schema
161
- def validate_array(value, schema)
162
- # array type have an schema in items property
163
- items_schema = schema.items
83
+ def validate_schema(value, schema)
84
+ return [value, nil] unless schema # no schema
85
+
86
+ return validate_any_of(value, schema) if schema.any_of
87
+
88
+ return validate_nil(value, schema) if value.nil?
164
89
 
165
- value.each do |v|
166
- err = validate_schema(v, items_schema)
167
- return err if err
90
+ case schema.type
91
+ when "string"
92
+ return validate_string(value, schema)
93
+ when "integer"
94
+ return validate_integer(value, schema)
95
+ when "boolean"
96
+ return validate_boolean(value, schema)
97
+ when "number"
98
+ return validate_float(value, schema)
99
+ when "object"
100
+ return validate_object(value, schema)
101
+ when "array"
102
+ return validate_array(value, schema)
103
+ else
104
+ # TODO: unknown type support
168
105
  end
169
106
 
170
- nil
107
+ validate_error(value, schema)
171
108
  end
172
109
  end
@@ -0,0 +1,14 @@
1
+ class OpenAPIParser::SchemaValidator
2
+ class AnyOfValidator < Base
3
+ # @param [Object] value
4
+ # @param [OpenAPIParser::Schemas::Schema] schema
5
+ def coerce_and_validate(value, schema)
6
+ # in all schema return error (=true) not any of data
7
+ schema.any_of.each do |s|
8
+ coerced, err = validator.validate_schema(value, s)
9
+ return [coerced, nil] if err.nil?
10
+ end
11
+ [nil, OpenAPIParser::NotAnyOf.new(value, schema.object_reference)]
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,25 @@
1
+ class OpenAPIParser::SchemaValidator
2
+ class ArrayValidator < Base
3
+
4
+ # @param [Array] value
5
+ # @param [OpenAPIParser::Schemas::Schema] schema
6
+ def coerce_and_validate(value, schema)
7
+ return validator.validate_error(value, schema) unless value.is_a?(Array)
8
+
9
+ # array type have an schema in items property
10
+ items_schema = schema.items
11
+
12
+
13
+ coerced_values = value.map do |v|
14
+ coerced, err = validator.validate_schema(v, items_schema)
15
+ return [nil, err] if err
16
+
17
+ coerced
18
+ end
19
+
20
+ value.each_index { |idx| value[idx] = coerced_values[idx] } if @coerce_value
21
+
22
+ [value, nil]
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,18 @@
1
+ class OpenAPIParser::SchemaValidator
2
+ class Base
3
+ def initialize(validator, coerce_value)
4
+ @validator = validator
5
+ @coerce_value = coerce_value
6
+ end
7
+
8
+ attr_reader :validator
9
+
10
+ # @!attribute [r] validator
11
+ # @return [OpenAPIParser::SchemaValidator]
12
+
13
+ # return [coerced_value, error]
14
+ def coerce_and_validate(_value, _schema)
15
+ raise 'need implement'
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,22 @@
1
+ class OpenAPIParser::SchemaValidator
2
+ class BooleanValidator < Base
3
+ def coerce_and_validate(value, schema)
4
+ value = coerce(value) if @coerce_value
5
+
6
+ return validator.validate_error(value, schema) unless value.is_a?(TrueClass) || value.is_a?(FalseClass)
7
+ [value, nil]
8
+ end
9
+
10
+ private
11
+
12
+ def coerce(value)
13
+ if value == "true" || value == "1"
14
+ return true
15
+ end
16
+ if value == "false" || value == "0"
17
+ return false
18
+ end
19
+ value
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,10 @@
1
+ class OpenAPIParser::SchemaValidator
2
+ module Enumable
3
+ def check_enum_include(value, schema)
4
+ return [value, nil] unless schema.enum
5
+ return [value, nil] if schema.enum.include?(value)
6
+
7
+ [nil, OpenAPIParser::NotEnumInclude.new(value, schema.object_reference)]
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,29 @@
1
+ class OpenAPIParser::SchemaValidator
2
+ class FloatValidator < Base
3
+ include ::OpenAPIParser::SchemaValidator::Enumable
4
+
5
+ def coerce_and_validate(value, schema)
6
+ value = coerce(value) if @coerce_value
7
+
8
+ return validator.validate_integer(value, schema) if value.is_a?(Integer)
9
+ coercer_and_validate_numeric(value, schema)
10
+ end
11
+
12
+ private
13
+
14
+ def coercer_and_validate_numeric(value, schema)
15
+ return validator.validate_error(value, schema) unless value.is_a?(Numeric)
16
+ check_enum_include(value, schema)
17
+ end
18
+
19
+ private
20
+
21
+ def coerce(value)
22
+ begin
23
+ return Float(value)
24
+ rescue ArgumentError => e
25
+ raise e unless e.message =~ /invalid value for Float/
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,26 @@
1
+ class OpenAPIParser::SchemaValidator
2
+ class IntegerValidator < Base
3
+ include ::OpenAPIParser::SchemaValidator::Enumable
4
+
5
+ def coerce_and_validate(value, schema)
6
+ value = coerce(value) if @coerce_value
7
+
8
+ return validator.validate_error(value, schema) unless value.is_a?(Integer)
9
+ check_enum_include(value, schema)
10
+ end
11
+
12
+ private
13
+
14
+ def coerce(value)
15
+ return value if value.is_a?(Integer)
16
+
17
+ begin
18
+ return Integer(value)
19
+ rescue ArgumentError => e
20
+ raise e unless e.message =~ /invalid value for Integer/
21
+ end
22
+
23
+ value
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,10 @@
1
+ class OpenAPIParser::SchemaValidator
2
+ class NilValidator < Base
3
+ # @param [Object] value
4
+ # @param [OpenAPIParser::Schemas::Schema] schema
5
+ def coerce_and_validate(value, schema)
6
+ return [value, nil] if schema.nullable
7
+ [nil, OpenAPIParser::NotNullError.new(schema.object_reference)]
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,28 @@
1
+ class OpenAPIParser::SchemaValidator
2
+ class ObjectValidator < Base
3
+
4
+ # @param [Hash] value
5
+ # @param [OpenAPIParser::Schemas::Schema] schema
6
+ def coerce_and_validate(value, schema)
7
+ return validator.validate_error(value, schema) unless value.is_a?(Hash)
8
+
9
+ return [value, nil] unless schema.properties
10
+ required_set = schema.required ? schema.required.to_set : Set.new
11
+
12
+ coerced_values = value.map do |name, v|
13
+ s = schema.properties[name]
14
+ coerced, err = validator.validate_schema(v, s)
15
+ return [nil, err] if err
16
+
17
+ required_set.delete(name)
18
+ [name, coerced]
19
+ end
20
+
21
+ return [nil, OpenAPIParser::NotExistRequiredKey.new(required_set.to_a, schema.object_reference)] unless required_set.empty?
22
+
23
+ value = value.merge(coerced_values.to_h) if @coerce_value
24
+
25
+ [value, nil]
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,10 @@
1
+ class OpenAPIParser::SchemaValidator
2
+ class Options
3
+ attr_reader :coerce_value, :datetime_coerce_class
4
+
5
+ def initialize(coerce_value: nil, datetime_coerce_class: nil)
6
+ @coerce_value = coerce_value
7
+ @datetime_coerce_class = datetime_coerce_class
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,38 @@
1
+ class OpenAPIParser::SchemaValidator
2
+ class StringValidator < Base
3
+ include ::OpenAPIParser::SchemaValidator::Enumable
4
+
5
+ def initialize(validator, coerce_value, datetime_coerce_class)
6
+ super(validator, coerce_value)
7
+ @datetime_coerce_class = datetime_coerce_class
8
+ end
9
+
10
+ def coerce_and_validate(value, schema)
11
+ return validator.validate_error(value, schema) unless value.is_a?(String)
12
+
13
+ value, err = check_enum_include(value, schema)
14
+ return [nil, err] if err
15
+
16
+ value = coerce_date_time(value, schema) unless @datetime_coerce_class.nil?
17
+
18
+ [value, nil]
19
+ end
20
+
21
+ private
22
+
23
+ # @param [OpenAPIParser::Schemas::Schema] schema
24
+ def coerce_date_time(value, schema)
25
+ schema.format == "date-time" ? parse_date_time(value) : value
26
+ end
27
+
28
+ def parse_date_time(value)
29
+ begin
30
+ return @datetime_coerce_class.parse(value)
31
+ rescue ArgumentError => e
32
+ raise e unless e.message =~ /invalid date/
33
+ end
34
+
35
+ value
36
+ end
37
+ end
38
+ end
@@ -2,6 +2,7 @@ module OpenAPIParser::Schemas
2
2
  class Base
3
3
  include OpenAPIParser::Parseable
4
4
  include OpenAPIParser::Findable
5
+ include OpenAPIParser::Expandable
5
6
 
6
7
  attr_reader :parent, :raw_schema, :object_reference, :root
7
8
 
@@ -9,8 +9,8 @@ module OpenAPIParser::Schemas
9
9
  openapi_attr_object :schema, Schema, reference: true
10
10
 
11
11
  # @param [Hash] params
12
- def validate_parameter(params)
13
- OpenAPIParser::SchemaValidator.validate(params, schema)
12
+ def validate_parameter(params, options)
13
+ OpenAPIParser::SchemaValidator.validate(params, schema, options)
14
14
  end
15
15
  end
16
16
  end
@@ -5,10 +5,11 @@
5
5
 
6
6
  module OpenAPIParser::Schemas
7
7
  class OpenAPI < Base
8
- def initialize(raw_schema)
8
+ def initialize(raw_schema, config)
9
9
  super('#', nil, self, raw_schema)
10
10
  @find_object_cache = {}
11
11
  @path_item_finder = OpenAPIParser::PathItemFinder.new(paths)
12
+ @config = config
12
13
  end
13
14
 
14
15
  # @!attribute [r] openapi
@@ -23,9 +24,9 @@ module OpenAPIParser::Schemas
23
24
  # @return [Components, nil]
24
25
  openapi_attr_object :components, Components, reference: false
25
26
 
26
- # @return [OpenAPIParser::RequestOperation]
27
+ # @return [OpenAPIParser::RequestOperation, nil]
27
28
  def request_operation(http_method, request_path)
28
- OpenAPIParser::RequestOperation.create(http_method, request_path, @path_item_finder)
29
+ OpenAPIParser::RequestOperation.create(http_method, request_path, @path_item_finder, @config)
29
30
  end
30
31
  end
31
32
  end
@@ -17,8 +17,37 @@ module OpenAPIParser::Schemas
17
17
 
18
18
  openapi_attr_object :responses, Responses, reference: false
19
19
 
20
- def validate_request_body(content_type, params)
21
- request_body&.validate_request_body(content_type, params)
20
+ def validate_request_body(content_type, params, options)
21
+ request_body&.validate_request_body(content_type, params, options)
22
+ end
23
+
24
+ def validate_response_body(status_code, content_type, data)
25
+ responses&.validate_response_body(status_code, content_type, data)
26
+ end
27
+
28
+ # @param [OpenAPIParser::SchemaValidator::Options] options
29
+ def validate_request_parameter(params, options)
30
+ OpenAPIParser::ParameterValidator.validate_parameter(query_parameter_hash, params, object_reference, options)
31
+ end
32
+
33
+ def validate_path_params(path_params, options)
34
+ OpenAPIParser::ParameterValidator.validate_parameter(path_parameter_hash, path_params, object_reference, options)
35
+ end
36
+
37
+ private
38
+
39
+ def path_parameter_hash
40
+ @path_parameter_hash ||= (parameters || []).
41
+ select(&:in_path?).
42
+ map{ |param| [param.name, param] }.
43
+ to_h
44
+ end
45
+
46
+ def query_parameter_hash
47
+ @query_parameter_hash ||= (parameters || []).
48
+ select(&:in_query?).
49
+ map{ |param| [param.name, param] }.
50
+ to_h
22
51
  end
23
52
  end
24
53
  end
@@ -7,6 +7,22 @@ module OpenAPIParser::Schemas
7
7
  openapi_attr_value :allow_empty_value, schema_key: :allowEmptyValue
8
8
  openapi_attr_value :allow_reserved, schema_key: :allowReserved
9
9
 
10
+ # @!attribute [r] schema
11
+ # @return [Schema, Reference, nil]
10
12
  openapi_attr_object :schema, Schema, reference: true
13
+
14
+ def in_query?
15
+ self.in == "query"
16
+ end
17
+
18
+ def in_path?
19
+ self.in == "path"
20
+ end
21
+
22
+ # @return [Object] coerced or original params
23
+ # @param [OpenAPIParser::SchemaValidator::Options] options
24
+ def validate_params(params, options)
25
+ ::OpenAPIParser::SchemaValidator.validate(params, schema, options)
26
+ end
11
27
  end
12
28
  end
@@ -1,5 +1,7 @@
1
1
  module OpenAPIParser::Schemas
2
2
  class Reference < Base
3
+ # @!attribute [r] ref
4
+ # @return [Base]
3
5
  openapi_attr_value :ref, schema_key: '$ref'
4
6
  end
5
7
  end
@@ -8,13 +8,13 @@ module OpenAPIParser::Schemas
8
8
  # @return [Hash{String => MediaType}, nil]
9
9
  openapi_attr_hash_object :content, MediaType, reference: false
10
10
 
11
- def validate_request_body(_content_type, params)
11
+ def validate_request_body(_content_type, params, options)
12
12
  # TODO: now support application/json only :(
13
13
 
14
14
  media_type = content['application/json']
15
- return nil unless media_type
15
+ return params unless media_type
16
16
 
17
- media_type.validate_parameter(params)
17
+ media_type.validate_parameter(params, options)
18
18
  end
19
19
  end
20
20
  end
@@ -8,5 +8,15 @@ module OpenAPIParser::Schemas
8
8
  # @!attribute [r] content
9
9
  # @return [Hash{String => MediaType}, nil]
10
10
  openapi_attr_hash_object :content, MediaType, reference: false
11
+
12
+ def validate_parameter(content_type, params)
13
+ # TODO: support wildcard type like application/* (OpenAPI3 definition)
14
+
15
+ media_type = content[content_type]
16
+ return nil unless media_type
17
+
18
+ options = ::OpenAPIParser::SchemaValidator::Options.new # response validator not support any options
19
+ media_type.validate_parameter(params, options)
20
+ end
11
21
  end
12
22
  end
@@ -9,5 +9,16 @@ module OpenAPIParser::Schemas
9
9
  # @!attribute [r] response
10
10
  # @return [Hash{String => Response, Reference}, nil]
11
11
  openapi_attr_hash_body_objects 'response', Response, reject_keys: [:default], allow_reference: true, allow_data_type: false
12
+
13
+ def validate_response_body(status_code, content_type, params)
14
+ # TODO: support wildcard status code like 2XX
15
+ return nil unless response
16
+
17
+ res = response[status_code.to_s]
18
+ return res.validate_parameter(content_type, params) if res
19
+
20
+
21
+ default&.validate_parameter(content_type, params)
22
+ end
12
23
  end
13
24
  end
@@ -1,3 +1,3 @@
1
1
  module OpenAPIParser
2
- VERSION = '0.1.2'.freeze
2
+ VERSION = '0.1.3'.freeze
3
3
  end
@@ -28,4 +28,5 @@ Gem::Specification.new do |spec|
28
28
  spec.add_development_dependency 'rspec', '~> 3.0'
29
29
  spec.add_development_dependency 'pry'
30
30
  spec.add_development_dependency 'pry-byebug'
31
+ spec.add_development_dependency 'rspec-parameterized'
31
32
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: openapi_parser
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - ota42y
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-12-14 00:00:00.000000000 Z
11
+ date: 2018-12-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -94,6 +94,20 @@ dependencies:
94
94
  - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rspec-parameterized
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
97
111
  description: parser for OpenAPI 3.0 or later
98
112
  email:
99
113
  - ota42y@gmail.com
@@ -114,11 +128,27 @@ files:
114
128
  - bin/setup
115
129
  - lib/openapi_parser.rb
116
130
  - lib/openapi_parser/concern.rb
131
+ - lib/openapi_parser/concerns/expandable.rb
117
132
  - lib/openapi_parser/concerns/findable.rb
118
133
  - lib/openapi_parser/concerns/parseable.rb
134
+ - lib/openapi_parser/config.rb
135
+ - lib/openapi_parser/errors.rb
136
+ - lib/openapi_parser/parameter_validator.rb
119
137
  - lib/openapi_parser/path_item_finder.rb
138
+ - lib/openapi_parser/reference_expander.rb
120
139
  - lib/openapi_parser/request_operation.rb
121
140
  - lib/openapi_parser/schema_validator.rb
141
+ - lib/openapi_parser/schema_validators/any_of_validator.rb
142
+ - lib/openapi_parser/schema_validators/array_validator.rb
143
+ - lib/openapi_parser/schema_validators/base.rb
144
+ - lib/openapi_parser/schema_validators/boolean_validator.rb
145
+ - lib/openapi_parser/schema_validators/enumable.rb
146
+ - lib/openapi_parser/schema_validators/float_validator.rb
147
+ - lib/openapi_parser/schema_validators/integer_validator.rb
148
+ - lib/openapi_parser/schema_validators/nil_validator.rb
149
+ - lib/openapi_parser/schema_validators/object_validator.rb
150
+ - lib/openapi_parser/schema_validators/options.rb
151
+ - lib/openapi_parser/schema_validators/string_validator.rb
122
152
  - lib/openapi_parser/schemas.rb
123
153
  - lib/openapi_parser/schemas/base.rb
124
154
  - lib/openapi_parser/schemas/classes.rb