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.
- checksums.yaml +7 -0
- data/lib/bluepine.rb +35 -0
- data/lib/bluepine/assertions.rb +80 -0
- data/lib/bluepine/attributes.rb +92 -0
- data/lib/bluepine/attributes/array_attribute.rb +11 -0
- data/lib/bluepine/attributes/attribute.rb +130 -0
- data/lib/bluepine/attributes/boolean_attribute.rb +22 -0
- data/lib/bluepine/attributes/currency_attribute.rb +19 -0
- data/lib/bluepine/attributes/date_attribute.rb +11 -0
- data/lib/bluepine/attributes/float_attribute.rb +13 -0
- data/lib/bluepine/attributes/integer_attribute.rb +14 -0
- data/lib/bluepine/attributes/ip_address_attribute.rb +15 -0
- data/lib/bluepine/attributes/number_attribute.rb +24 -0
- data/lib/bluepine/attributes/object_attribute.rb +71 -0
- data/lib/bluepine/attributes/schema_attribute.rb +23 -0
- data/lib/bluepine/attributes/string_attribute.rb +36 -0
- data/lib/bluepine/attributes/time_attribute.rb +19 -0
- data/lib/bluepine/attributes/uri_attribute.rb +19 -0
- data/lib/bluepine/attributes/visitor.rb +136 -0
- data/lib/bluepine/endpoint.rb +102 -0
- data/lib/bluepine/endpoints/method.rb +90 -0
- data/lib/bluepine/endpoints/params.rb +115 -0
- data/lib/bluepine/error.rb +17 -0
- data/lib/bluepine/functions.rb +49 -0
- data/lib/bluepine/generators.rb +3 -0
- data/lib/bluepine/generators/generator.rb +16 -0
- data/lib/bluepine/generators/grpc/generator.rb +10 -0
- data/lib/bluepine/generators/open_api/generator.rb +205 -0
- data/lib/bluepine/generators/open_api/property_generator.rb +111 -0
- data/lib/bluepine/registry.rb +75 -0
- data/lib/bluepine/resolvable.rb +11 -0
- data/lib/bluepine/resolver.rb +99 -0
- data/lib/bluepine/serializer.rb +125 -0
- data/lib/bluepine/serializers/serializable.rb +25 -0
- data/lib/bluepine/validator.rb +205 -0
- data/lib/bluepine/validators/normalizable.rb +25 -0
- data/lib/bluepine/validators/proxy.rb +77 -0
- data/lib/bluepine/validators/validatable.rb +48 -0
- data/lib/bluepine/version.rb +3 -0
- 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,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
|