openapi_parser 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
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