bluepine 0.1.1

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 (40) hide show
  1. checksums.yaml +7 -0
  2. data/lib/bluepine.rb +35 -0
  3. data/lib/bluepine/assertions.rb +80 -0
  4. data/lib/bluepine/attributes.rb +92 -0
  5. data/lib/bluepine/attributes/array_attribute.rb +11 -0
  6. data/lib/bluepine/attributes/attribute.rb +130 -0
  7. data/lib/bluepine/attributes/boolean_attribute.rb +22 -0
  8. data/lib/bluepine/attributes/currency_attribute.rb +19 -0
  9. data/lib/bluepine/attributes/date_attribute.rb +11 -0
  10. data/lib/bluepine/attributes/float_attribute.rb +13 -0
  11. data/lib/bluepine/attributes/integer_attribute.rb +14 -0
  12. data/lib/bluepine/attributes/ip_address_attribute.rb +15 -0
  13. data/lib/bluepine/attributes/number_attribute.rb +24 -0
  14. data/lib/bluepine/attributes/object_attribute.rb +71 -0
  15. data/lib/bluepine/attributes/schema_attribute.rb +23 -0
  16. data/lib/bluepine/attributes/string_attribute.rb +36 -0
  17. data/lib/bluepine/attributes/time_attribute.rb +19 -0
  18. data/lib/bluepine/attributes/uri_attribute.rb +19 -0
  19. data/lib/bluepine/attributes/visitor.rb +136 -0
  20. data/lib/bluepine/endpoint.rb +102 -0
  21. data/lib/bluepine/endpoints/method.rb +90 -0
  22. data/lib/bluepine/endpoints/params.rb +115 -0
  23. data/lib/bluepine/error.rb +17 -0
  24. data/lib/bluepine/functions.rb +49 -0
  25. data/lib/bluepine/generators.rb +3 -0
  26. data/lib/bluepine/generators/generator.rb +16 -0
  27. data/lib/bluepine/generators/grpc/generator.rb +10 -0
  28. data/lib/bluepine/generators/open_api/generator.rb +205 -0
  29. data/lib/bluepine/generators/open_api/property_generator.rb +111 -0
  30. data/lib/bluepine/registry.rb +75 -0
  31. data/lib/bluepine/resolvable.rb +11 -0
  32. data/lib/bluepine/resolver.rb +99 -0
  33. data/lib/bluepine/serializer.rb +125 -0
  34. data/lib/bluepine/serializers/serializable.rb +25 -0
  35. data/lib/bluepine/validator.rb +205 -0
  36. data/lib/bluepine/validators/normalizable.rb +25 -0
  37. data/lib/bluepine/validators/proxy.rb +77 -0
  38. data/lib/bluepine/validators/validatable.rb +48 -0
  39. data/lib/bluepine/version.rb +3 -0
  40. metadata +208 -0
@@ -0,0 +1,205 @@
1
+ require "bluepine/validators/proxy"
2
+
3
+ module Bluepine
4
+ # A validator (can validate any kind of {Attribute}).
5
+ #
6
+ # @example Validate simple {Attribute}
7
+ # attribute = Attributes.create(:string, :username, required: true)
8
+ # payload = { username: nil }
9
+ #
10
+ # validator = Validator.new(resolver)
11
+ # validator.validate(attribute, payload) # => { username: ["can't be blank"] }
12
+ #
13
+ # @example Validate compound type (e.g. Object, Array).
14
+ # attribute = Attributes.create(:object, :user) do
15
+ # string :username, min: 5
16
+ # array :pets, of: :string
17
+ # end
18
+ # payload = { username: "john", pets: ["jay", 1] }
19
+ #
20
+ # validator.validate(attribute, payload) # =>
21
+ # # {
22
+ # # username: ["is too short (minimum is 5 characters)"]
23
+ # # pets: { 1: ["is not string"] }
24
+ # # }
25
+ class Validator < Bluepine::Attributes::Visitor
26
+ include Bluepine::Resolvable
27
+ include Functions
28
+
29
+ def initialize(resolver = nil)
30
+ @resolver = resolver
31
+ end
32
+
33
+ # Overrides to make it accepts 3 arguments
34
+ def normalize_attribute(attribute, value, options = {})
35
+ super(attribute, options)
36
+ end
37
+
38
+ # Overrides to make it normalizes value before validating attribute
39
+ #
40
+ # @return [Result]
41
+ def visit(attribute, value, options = {})
42
+ method, attribute = find_method!(attribute, value, options)
43
+ value = attribute.normalize(attribute.value(value))
44
+
45
+ send(method, attribute, value, options)
46
+ end
47
+
48
+ alias :validate :visit
49
+
50
+ # catch-all
51
+ def visit_attribute(attribute, value, options = {})
52
+ run(attribute, options, value)
53
+ end
54
+
55
+ def visit_string(attribute, value, options = {})
56
+ compose(
57
+ curry(:is_valid?, :string),
58
+ curry(:run, attribute, options)
59
+ ).(value)
60
+ end
61
+
62
+ def visit_boolean(attribute, value, options = {})
63
+ compose(
64
+ curry(:is_valid?, :boolean),
65
+ curry(:run, attribute, options),
66
+ ).(value)
67
+ end
68
+
69
+ def visit_number(attribute, value, options = {})
70
+ compose(
71
+ curry(:is_valid?, :number),
72
+ curry(:run, attribute, options)
73
+ ).(value)
74
+ end
75
+
76
+ def visit_integer(attribute, value, options = {})
77
+ compose(
78
+ curry(:is_valid?, :integer),
79
+ curry(:run, attribute, options)
80
+ ).(value)
81
+ end
82
+
83
+ def visit_float(attribute, value, options = {})
84
+ compose(
85
+ curry(:is_valid?, :float),
86
+ curry(:run, attribute, options)
87
+ ).(value)
88
+ end
89
+
90
+ def visit_array(attribute, value, options = {})
91
+ compose(
92
+ curry(:is_valid?, :array),
93
+ curry(:iterate, value || [], lambda { |(item, v), i|
94
+
95
+ # when of: is not specified, item can be any type
96
+ next result(item) unless attribute.of
97
+
98
+ # validate each item
99
+ visit(attribute.of, item, options)
100
+ })
101
+ ).(value)
102
+ end
103
+
104
+ def visit_object(attribute, value, options = {})
105
+ compose(
106
+ curry(:is_valid?, :object),
107
+ curry(:iterate, attribute.attributes, lambda { |(key, attr), i|
108
+
109
+ # validate each attribute
110
+ data = get(value, attr.method)
111
+ options[:context] = value
112
+
113
+ visit(attr, data, options)
114
+ })
115
+ ).(value)
116
+ end
117
+
118
+ def visit_schema(attribute, value, options = {})
119
+ attr = resolver.schema(attribute.of)
120
+
121
+ visit_object(attr, value, options)
122
+ end
123
+
124
+ private
125
+
126
+ def create_proxy(attr, value, options = {})
127
+ Bluepine::Validators::Proxy.new(attr, value, options)
128
+ end
129
+
130
+ def get(object, name)
131
+ return unless object
132
+
133
+ object.respond_to?(name) ? object.send(name) : object[name]
134
+ end
135
+
136
+ def run(attribute, options, value)
137
+ proxy = create_proxy(attribute, value, options)
138
+ return result(value, proxy.messages) unless proxy.valid?
139
+
140
+ result(value)
141
+ end
142
+
143
+ # Iterates through Hash/Array
144
+ #
145
+ # @param target Hash|Array
146
+ # @return [Result]
147
+ def iterate(target, proc, value)
148
+ is_hash = target.is_a?(Hash)
149
+ errors = {}
150
+ values = is_hash ? {} : []
151
+
152
+ # When target is a Hash. k = name, v = Attribute.
153
+ # When it's an Array. k = value, v = nil.
154
+ target.each.with_index do |(k, v), i|
155
+ name = is_hash ? v&.name : i
156
+ result = proc.([k, v], i)
157
+
158
+ if result.errors
159
+ errors[name] = result.errors
160
+ else
161
+ values[name] = result.value
162
+ end
163
+ end
164
+
165
+ return result(nil, errors) if errors.any?
166
+
167
+ result(values)
168
+ end
169
+
170
+ def is_valid?(type, value)
171
+ return result(value) if value.nil?
172
+ return result(value, ["is not #{type}"]) unless send("is_#{type}?", value)
173
+
174
+ result(value)
175
+ end
176
+
177
+ def is_boolean?(v)
178
+ [true, false].include?(v)
179
+ end
180
+
181
+ def is_string?(v)
182
+ v.is_a?(String)
183
+ end
184
+
185
+ def is_number?(v)
186
+ is_integer?(v) || is_float?(v)
187
+ end
188
+
189
+ def is_integer?(v)
190
+ v.is_a?(Integer)
191
+ end
192
+
193
+ def is_float?(v)
194
+ v.is_a?(Float)
195
+ end
196
+
197
+ def is_array?(v)
198
+ v.is_a?(Array)
199
+ end
200
+
201
+ def is_object?(v)
202
+ v.is_a?(Hash) || v.is_a?(ActionController::Parameters)
203
+ end
204
+ end
205
+ end
@@ -0,0 +1,25 @@
1
+ module Bluepine
2
+ module Validators
3
+ module Normalizable
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ # Normalizes value before passing it to validator
8
+ class_attribute :normalizer
9
+
10
+ # Default normalizer
11
+ self.normalizer = ->(v) { v }
12
+ end
13
+
14
+ def normalize(value)
15
+ self.class.normalize(value)
16
+ end
17
+
18
+ module ClassMethods
19
+ def normalize(value)
20
+ normalizer.(value)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,77 @@
1
+ module Bluepine
2
+ module Validators
3
+ # Proxy will act as a wrapper for a pair of attribute and value.
4
+ # It internally creates anonymous model with single attribute.
5
+ # This will simplify validation process for nested attributes
6
+ # (let's Visitor handles traversal instead).
7
+ #
8
+ # @example
9
+ # attribute = StringAttribute.new(:username)
10
+ # value = "john"
11
+ #
12
+ # proxy = Proxy.new(attribute, value)
13
+ # proxy.username # => "john"
14
+ # proxy.valid? # => true|false
15
+ # proxy.errors
16
+ class Proxy
17
+ include Bluepine::Assertions
18
+ include ActiveModel::Validations
19
+
20
+ attr_reader :value, :validators
21
+
22
+ # rails requires this for anonymous model
23
+ def self.model_name
24
+ ActiveModel::Name.new(self, nil, name)
25
+ end
26
+
27
+ def initialize(attribute, value = nil, options = {})
28
+ @attribute = attribute
29
+ @value = attribute.value(value)
30
+ @params = { attribute.name.to_sym => @value }
31
+ @context = options[:context] || {}
32
+ end
33
+
34
+ def valid?
35
+ # clear validators
36
+ self.class.clear_validators!
37
+
38
+ register(@attribute.validators.dup)
39
+
40
+ super
41
+ end
42
+
43
+ # Register validators to model
44
+ #
45
+ # register(presense: true, ..., validators: [Validator1, Validator2, ...])
46
+ def register(validators)
47
+ customs = validators.delete(:validators) || []
48
+
49
+ # register custom validators (requires :attributes)
50
+ self.class.validates_with(*customs, attributes: [@attribute.name]) if customs.any?
51
+
52
+ # register ActiveModel's validations e.g. presence: true
53
+ self.class.validates(@attribute.name, validators) if validators.any?
54
+ end
55
+
56
+ def messages
57
+ errors.messages.values.flatten
58
+ end
59
+
60
+ private
61
+
62
+ # Delegates method call to hash accessor e.g. `a.name` will become `a[:name]`
63
+ # and return `nil` for all undefined attributes e.g. `a.non_exists` => `nil`
64
+ def method_missing(m, *args, &block)
65
+ normalize_missing_value m
66
+ end
67
+
68
+ def respond_to_missing?(method, *)
69
+ @params.key?(method) || super
70
+ end
71
+
72
+ def normalize_missing_value(method)
73
+ @params.key?(method) ? @value : @context[method]
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,48 @@
1
+ module Bluepine
2
+ module Validators
3
+ module Validatable
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ RULES = {}.freeze
8
+ end
9
+
10
+ # Returns validation rules (rails compatible)
11
+ def validators
12
+ rules(self.class::RULES, @options).tap do |rules|
13
+ rules[:if] = self.if if self.if
14
+ rules[:unless] = self.unless if self.unless
15
+ rules[:allow_nil] = true if null
16
+ rules[:presence] = true if required
17
+ rules[:inclusion] = { in: self.in, allow_blank: true } if self.in
18
+ rules[:validators] = @options[:validators] if @options[:validators]
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ # Build validation rules
25
+ #
26
+ # @example
27
+ # rules = {
28
+ # min: { name: :minimum, group: :length }
29
+ # }
30
+ #
31
+ # # will be converted to
32
+ # {
33
+ # length: { minimum: {value} }
34
+ # }
35
+ def rules(rules, options)
36
+ rules.keys.each_with_object({}) do |name, hash|
37
+ next if options[name].nil?
38
+
39
+ rule = OpenStruct.new rules[name]
40
+ (hash[rule.group] ||= {}).tap do |r|
41
+ r[:allow_blank] = true
42
+ r[rule.name] = options[name]
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,3 @@
1
+ module Bluepine
2
+ VERSION = "0.1.1"
3
+ end
metadata ADDED
@@ -0,0 +1,208 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bluepine
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Marut K
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-07-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '5.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activemodel
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '5.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '5.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.17'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.17'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: minitest
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '5.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '5.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: mocha
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.8'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.8'
97
+ - !ruby/object:Gem::Dependency
98
+ name: simplecov
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0.16'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0.16'
111
+ - !ruby/object:Gem::Dependency
112
+ name: actionpack
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '5.0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '5.0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rubocop
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: 0.71.0
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: 0.71.0
139
+ description: A DSL for defining API schemas/endpoints, validating, serializing and
140
+ generating Open API v3
141
+ email:
142
+ - marut@omise.co
143
+ executables: []
144
+ extensions: []
145
+ extra_rdoc_files: []
146
+ files:
147
+ - lib/bluepine.rb
148
+ - lib/bluepine/assertions.rb
149
+ - lib/bluepine/attributes.rb
150
+ - lib/bluepine/attributes/array_attribute.rb
151
+ - lib/bluepine/attributes/attribute.rb
152
+ - lib/bluepine/attributes/boolean_attribute.rb
153
+ - lib/bluepine/attributes/currency_attribute.rb
154
+ - lib/bluepine/attributes/date_attribute.rb
155
+ - lib/bluepine/attributes/float_attribute.rb
156
+ - lib/bluepine/attributes/integer_attribute.rb
157
+ - lib/bluepine/attributes/ip_address_attribute.rb
158
+ - lib/bluepine/attributes/number_attribute.rb
159
+ - lib/bluepine/attributes/object_attribute.rb
160
+ - lib/bluepine/attributes/schema_attribute.rb
161
+ - lib/bluepine/attributes/string_attribute.rb
162
+ - lib/bluepine/attributes/time_attribute.rb
163
+ - lib/bluepine/attributes/uri_attribute.rb
164
+ - lib/bluepine/attributes/visitor.rb
165
+ - lib/bluepine/endpoint.rb
166
+ - lib/bluepine/endpoints/method.rb
167
+ - lib/bluepine/endpoints/params.rb
168
+ - lib/bluepine/error.rb
169
+ - lib/bluepine/functions.rb
170
+ - lib/bluepine/generators.rb
171
+ - lib/bluepine/generators/generator.rb
172
+ - lib/bluepine/generators/grpc/generator.rb
173
+ - lib/bluepine/generators/open_api/generator.rb
174
+ - lib/bluepine/generators/open_api/property_generator.rb
175
+ - lib/bluepine/registry.rb
176
+ - lib/bluepine/resolvable.rb
177
+ - lib/bluepine/resolver.rb
178
+ - lib/bluepine/serializer.rb
179
+ - lib/bluepine/serializers/serializable.rb
180
+ - lib/bluepine/validator.rb
181
+ - lib/bluepine/validators/normalizable.rb
182
+ - lib/bluepine/validators/proxy.rb
183
+ - lib/bluepine/validators/validatable.rb
184
+ - lib/bluepine/version.rb
185
+ homepage: https://github.com/omise/bluepine
186
+ licenses:
187
+ - MIT
188
+ metadata: {}
189
+ post_install_message:
190
+ rdoc_options: []
191
+ require_paths:
192
+ - lib
193
+ required_ruby_version: !ruby/object:Gem::Requirement
194
+ requirements:
195
+ - - ">="
196
+ - !ruby/object:Gem::Version
197
+ version: 2.0.0
198
+ required_rubygems_version: !ruby/object:Gem::Requirement
199
+ requirements:
200
+ - - ">="
201
+ - !ruby/object:Gem::Version
202
+ version: '0'
203
+ requirements: []
204
+ rubygems_version: 3.0.4
205
+ signing_key:
206
+ specification_version: 4
207
+ summary: A DSL for defining API schemas/endpoints
208
+ test_files: []