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,111 @@
1
+ module Bluepine
2
+ module Generators
3
+ module OpenAPI
4
+ # Generate property based on Open API Spec (shared for both Omise/Open API specs)
5
+ class PropertyGenerator < Bluepine::Attributes::Visitor
6
+ include Bluepine::Assertions
7
+
8
+ class << self
9
+ def visit(attr, options = {})
10
+ new.visit(attr, options)
11
+ end
12
+
13
+ alias_method :generate, :visit
14
+ end
15
+
16
+ def visit(attr, options = {})
17
+ attr = normalize_attribute(attr, options)
18
+
19
+ # handle case when attr is a Symbol (reference)
20
+ return attr unless attr.respond_to?(:native_type)
21
+
22
+ super
23
+ end
24
+
25
+ # catch-all
26
+ def visit_attribute(attr, options = {})
27
+ build(attr, options)
28
+ end
29
+
30
+ def visit_array(attr, options = {})
31
+ build(attr, options).tap do |property|
32
+ property[:items] = attr.of ? visit(attr.of, options) : {}
33
+ end
34
+ end
35
+
36
+ def visit_object(attr, options = {})
37
+ build(attr, options).tap do |property|
38
+ required = []
39
+ attr.attributes.values.each_with_object(property) do |attribute, object|
40
+
41
+ # Adds to required list
42
+ required << attribute.name if attribute.required
43
+
44
+ object[:properties] ||= {}
45
+ object[:properties][attribute.name] = visit(attribute, options) if attribute.serializable?
46
+ end
47
+
48
+ # additional options
49
+ property[:required] = required unless required.empty?
50
+ end
51
+ end
52
+
53
+ # Handle SchemaAttribute
54
+ def visit_schema(attr, options)
55
+ return build_ref(attr.of) unless attr.expandable
56
+
57
+ # SchemaAttribute#of may contains array of references
58
+ # e.g. of = [:user, :customer]
59
+ refs = Array(attr.of).map { |of| build_ref(of) }
60
+ refs << visit("string")
61
+
62
+ {
63
+ "oneOf": refs,
64
+ }
65
+ end
66
+
67
+ def normalize_attribute(object, options = {})
68
+ return build_ref(object, options) if object.kind_of?(Symbol)
69
+ return object if object.respond_to?(:native_type)
70
+
71
+ # object is string (native types e.g. "integer", "boolean" etc)
72
+ Bluepine::Attributes.create(object.to_sym, object)
73
+ end
74
+
75
+ private
76
+
77
+ def build(attr, options = {})
78
+ assert_kind_of Bluepine::Attributes::Attribute, attr
79
+
80
+ # build base property
81
+ {
82
+ type: attr.native_type,
83
+ }.tap do |property|
84
+ property[:description] = attr.description if attr.description.present?
85
+ property[:default] = attr.default if attr.default
86
+ property[:enum] = attr.in if attr.in
87
+ property[:nullable] = attr.null if attr.null
88
+ property[:format] = attr.format if attr.format
89
+ property[:pattern] = build_pattern(attr.match) if attr.match
90
+ property["x-omise-schema"] = options[:schema] if options[:schema].present?
91
+ end
92
+ end
93
+
94
+ # create $ref
95
+ def build_ref(attr, options = {})
96
+ ref = options[:as] || attr
97
+
98
+ {
99
+ "$ref": "#/components/schemas/#{ref}",
100
+ }
101
+ end
102
+
103
+ def build_pattern(value)
104
+ return value.source if value.respond_to?(:source)
105
+
106
+ value
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,75 @@
1
+ module Bluepine
2
+ # A generic registry
3
+ #
4
+ # @example Create key/value pairs registry (Hash)
5
+ # registry = Registry.new do |key, value|
6
+ # { name: key, age: value }
7
+ # end
8
+ #
9
+ # # Register object
10
+ # registry.register(:john, name: :john, age: 10)
11
+ #
12
+ # # Retrieve object
13
+ # registry.get(:john) # => { name: :john, age: 10 }
14
+ #
15
+ # # Create new object
16
+ # registry.create(:joe, 10) # => { name: joe, age: 10 }
17
+ class Registry
18
+ include Bluepine::Assertions
19
+
20
+ KeyError = Bluepine::Error.create("Object %s already exists")
21
+
22
+ # @param [Object] A collection of objects which has #name property
23
+ # @param &block A {Proc} that'll create new object
24
+ def initialize(objects = [], error: KeyError, &block)
25
+ assert_kind_of Proc, block
26
+
27
+ @objects = normalize objects
28
+ @factory = block
29
+ @error = error
30
+ end
31
+
32
+ # Registers new object by id
33
+ #
34
+ # @param id [String] Unique name
35
+ # @param object [Object] Object to register
36
+ # @param override [Boolean] Overrides existing key if exists
37
+ def register(id, object, override: false)
38
+ if key?(id) && !override
39
+ raise @error, id
40
+ end
41
+
42
+ @objects[id.to_sym] = object
43
+ end
44
+
45
+ # Creates new object by using a {Proc} from #new
46
+ #
47
+ # @return [Object]
48
+ # @example
49
+ # registry.create(:user, "john")
50
+ def create(id, *args, &block)
51
+ instance_exec(id, *args, block, &@factory)
52
+ end
53
+
54
+ # Retrieves registered Object by key
55
+ def get(id)
56
+ raise @error, id unless key?(id)
57
+
58
+ @objects[id.to_sym]
59
+ end
60
+
61
+ def key?(id)
62
+ @objects.key? id
63
+ end
64
+
65
+ def keys
66
+ @objects.keys
67
+ end
68
+
69
+ private
70
+
71
+ def normalize(objects = [])
72
+ (objects || []).each_with_object({}) { |object, target| target[object.name] = object }
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,11 @@
1
+ module Bluepine
2
+ module Resolvable
3
+ ResolverRequired = Bluepine::Error.create("Resolver is required")
4
+
5
+ def resolver
6
+ raise ResolverRequired unless @resolver
7
+
8
+ @resolver
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,99 @@
1
+ module Bluepine
2
+ # Responsible for registering and looking up the schemas and endpoints
3
+ #
4
+ # @example Register via +:schemas+ and +:endpoints+ options
5
+ # resolver = Resolver.new(schemas: [], endpoints: [])
6
+ #
7
+ # @example Register via +block+
8
+ # resolver = Resolver.new do
9
+ # schema :user do
10
+ # string :username
11
+ # end
12
+ #
13
+ # schema :team do
14
+ # string :name
15
+ # end
16
+ #
17
+ # endpoint "/users" do
18
+ # string :username
19
+ # end
20
+ # end
21
+ #
22
+ # @example Manually register new schema/endpoint
23
+ # resolver.schema(:user) do
24
+ # string :username
25
+ # end
26
+ #
27
+ # resolver.endpoint("/teams") do
28
+ # post :create
29
+ # end
30
+ #
31
+ # @example Register an existing schema/endpoint
32
+ # resolver.schemas.register(:user, user_schema)
33
+ #
34
+ class Resolver
35
+ Endpoint = Bluepine::Endpoint
36
+ Attributes = Bluepine::Attributes
37
+
38
+ SchemaNotFound = Bluepine::Error.create("Endpoint %s cannot be found")
39
+ EndpointNotFound = Bluepine::Error.create("Schema %s cannot be found")
40
+
41
+ def initialize(schemas: [], endpoints: [], schema_registry: nil, endpoint_registry: nil, &block)
42
+ @registries = {
43
+ schemas: create_schema_registry(schemas, schema_registry),
44
+ endpoints: create_endpoint_registry(endpoints, endpoint_registry)
45
+ }
46
+
47
+ instance_exec(&block) if block_given?
48
+ end
49
+
50
+ def resolve(type, name)
51
+ @registries[type].get(name)
52
+ end
53
+
54
+ def register(type, name, *args, &block)
55
+ @registries[type].create(name, *args, &block)
56
+ end
57
+
58
+ # Exposes schema registry
59
+ def schemas
60
+ @registries[:schemas]
61
+ end
62
+
63
+ # Exposes endpoint registry
64
+ def endpoints
65
+ @registries[:endpoints]
66
+ end
67
+
68
+ def schema(name, options = {}, &block)
69
+ return resolve(:schemas, name) unless block_given?
70
+
71
+ register(:schemas, name, options, &block)
72
+ end
73
+
74
+ def endpoint(path, options = {}, &block)
75
+ return resolve(:endpoints, Endpoint.normalize_name(path)) unless block_given?
76
+
77
+ register(:endpoints, path, options, &block)
78
+ end
79
+
80
+ private
81
+
82
+ def create_schema_registry(schemas, registry = nil)
83
+ return registry.(schemas) if registry
84
+
85
+ Registry.new(schemas, error: SchemaNotFound) do |name, options = {}, block|
86
+ @objects[name] = Attributes.create(:object, name, options, &block)
87
+ end
88
+ end
89
+
90
+ def create_endpoint_registry(endpoints, registry = nil)
91
+ return registry.(endpoints) if registry
92
+
93
+ Registry.new(endpoints, error: EndpointNotFound) do |path, options = {}, block|
94
+ endpoint = Endpoint.new(path, options, &block)
95
+ @objects[endpoint.name] = endpoint
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,125 @@
1
+ module Bluepine
2
+ # user_schema = Attributes.create(:object) do
3
+ # string :username
4
+ # string :email
5
+ # end
6
+ #
7
+ # user = <any object>
8
+ #
9
+ # resolver = Resolver.new(schemas: [user_schema, ...])
10
+ # serializer.serialize(user_schema, user)
11
+ class Serializer < Bluepine::Attributes::Visitor
12
+ include Bluepine::Resolvable
13
+
14
+ InvalidPredicate = Bluepine::Error.create("Invalid predicate value (must be either Symbol or Proc)")
15
+
16
+ def initialize(resolver = nil)
17
+ @resolver = resolver
18
+ end
19
+
20
+ # Override to make it accepts 3 arguments
21
+ def normalize_attribute(attribute, object, options = {})
22
+ super(attribute, options)
23
+ end
24
+
25
+ # catch all
26
+ def visit_attribute(attribute, object, options = {})
27
+ attribute.serialize(attribute.value(object))
28
+ end
29
+
30
+ # Primitive attribute
31
+ # -------------------
32
+ # serialize(string, "user")
33
+ # serialize(object, { name: "john" })
34
+ #
35
+ # Schema Attribute
36
+ # ----------------
37
+ # schema = Attributes.create(:object, :user) do
38
+ # string :username
39
+ # end
40
+ #
41
+ # class User
42
+ # def initialize(data)
43
+ # @data = data
44
+ # end
45
+ #
46
+ # def username
47
+ # @data[:name]
48
+ # end
49
+ # end
50
+ # user = User.new(name: "john")
51
+ #
52
+ # serialize(schema, user)
53
+ alias :serialize :visit
54
+
55
+ # Defines visitors for primitive types e.g. `visit_string` etc
56
+ Bluepine::Attributes::SCALAR_TYPES.each do |type|
57
+ alias_method "visit_#{type}", :visit_attribute
58
+ end
59
+
60
+ def visit_object(attribute, object, options = {})
61
+ visit_object_handler(attribute, object) do |attr, value, attrs|
62
+ attrs[attr.name] = visit(attr, value, options)
63
+ end
64
+ end
65
+
66
+ def visit_array(attribute, object, options = {})
67
+ as = attribute.of
68
+
69
+ Array(object).map do |item|
70
+ # item#serialize_as will be used when of: option is not specified.
71
+ # e.g. ListSerializer.schema has `array :data`
72
+ # as = attribute.of || (item.serialize_as if item.respond_to?(:serialize_as))
73
+ unless as.kind_of?(Symbol)
74
+ item
75
+ else
76
+ visit(as, item, options)
77
+ end
78
+ end
79
+ end
80
+
81
+ def visit_schema(attribute, object, options = {})
82
+ attribute = resolver.schema(attribute.of)
83
+
84
+ visit_object(attribute, object, options)
85
+ end
86
+
87
+ private
88
+
89
+ def get(object, name)
90
+ object.respond_to?(name) ? object.send(name) : object&.fetch(name, nil)
91
+ end
92
+
93
+ def visit_object_handler(attribute, object)
94
+ attribute.attributes.values.each_with_object({}) do |attr, attrs|
95
+ next unless serializable?(attr, object)
96
+
97
+ # get value for each field
98
+ value = get(object, attr.method)
99
+
100
+ yield(attr, value, attrs)
101
+ end
102
+ end
103
+
104
+ def serializable?(attr, object)
105
+ return unless attr.serializable?
106
+
107
+ # check predicate :if and :unless
108
+ return execute_predicate(attr.if, object) if attr.if
109
+ return !execute_predicate(attr.unless, object) if attr.unless
110
+
111
+ true
112
+ end
113
+
114
+ def execute_predicate(predicate, object)
115
+ case predicate
116
+ when Symbol
117
+ get(object, predicate)
118
+ when Proc
119
+ predicate.call(object)
120
+ else
121
+ raise InvalidPredicate
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,25 @@
1
+ module Bluepine
2
+ module Serializers
3
+ module Serializable
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ # Serializer
8
+ class_attribute :serializer
9
+
10
+ # Default serializer
11
+ self.serializer = ->(v) { v }
12
+ end
13
+
14
+ def serialize(value)
15
+ self.class.serialize(value)
16
+ end
17
+
18
+ module ClassMethods
19
+ def serialize(value)
20
+ serializer.(value)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end