parametric 0.0.5 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +1 -2
- data/README.md +596 -163
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/parametric/block_validator.rb +64 -0
- data/lib/parametric/context.rb +44 -0
- data/lib/parametric/default_types.rb +95 -0
- data/lib/parametric/dsl.rb +47 -0
- data/lib/parametric/field.rb +111 -0
- data/lib/parametric/field_dsl.rb +20 -0
- data/lib/parametric/policies.rb +94 -55
- data/lib/parametric/registry.rb +21 -0
- data/lib/parametric/results.rb +13 -0
- data/lib/parametric/schema.rb +151 -0
- data/lib/parametric/version.rb +1 -1
- data/lib/parametric.rb +16 -6
- data/parametric.gemspec +2 -1
- data/spec/dsl_spec.rb +135 -0
- data/spec/field_spec.rb +404 -0
- data/spec/policies_spec.rb +72 -0
- data/spec/schema_spec.rb +253 -0
- data/spec/schema_walk_spec.rb +42 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/validators_spec.rb +97 -0
- metadata +54 -24
- data/lib/parametric/hash.rb +0 -38
- data/lib/parametric/params.rb +0 -86
- data/lib/parametric/typed_params.rb +0 -23
- data/lib/parametric/utils.rb +0 -24
- data/lib/support/class_attribute.rb +0 -68
- data/spec/nested_params_spec.rb +0 -90
- data/spec/parametric_spec.rb +0 -261
@@ -0,0 +1,151 @@
|
|
1
|
+
require "parametric/context"
|
2
|
+
require "parametric/results"
|
3
|
+
require "parametric/field"
|
4
|
+
|
5
|
+
module Parametric
|
6
|
+
class Schema
|
7
|
+
def initialize(options = {}, &block)
|
8
|
+
@options = options
|
9
|
+
@fields = {}
|
10
|
+
@definitions = []
|
11
|
+
@definitions << block if block_given?
|
12
|
+
@default_field_policies = []
|
13
|
+
@ignored_field_keys = []
|
14
|
+
end
|
15
|
+
|
16
|
+
def fields
|
17
|
+
apply!
|
18
|
+
@fields
|
19
|
+
end
|
20
|
+
|
21
|
+
def policy(*names, &block)
|
22
|
+
@default_field_policies = names
|
23
|
+
definitions << block if block_given?
|
24
|
+
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
def ignore(*field_keys, &block)
|
29
|
+
@ignored_field_keys += field_keys
|
30
|
+
@ignored_field_keys.uniq!
|
31
|
+
|
32
|
+
definitions << block if block_given?
|
33
|
+
|
34
|
+
self
|
35
|
+
end
|
36
|
+
|
37
|
+
def clone
|
38
|
+
instance = self.class.new(options)
|
39
|
+
copy_into instance
|
40
|
+
end
|
41
|
+
|
42
|
+
def merge(other_schema)
|
43
|
+
instance = self.class.new(options.merge(other_schema.options))
|
44
|
+
|
45
|
+
copy_into(instance)
|
46
|
+
other_schema.copy_into(instance)
|
47
|
+
end
|
48
|
+
|
49
|
+
def copy_into(instance)
|
50
|
+
instance.policy(*default_field_policies) if default_field_policies.any?
|
51
|
+
|
52
|
+
definitions.each do |d|
|
53
|
+
instance.definitions << d
|
54
|
+
end
|
55
|
+
|
56
|
+
instance.ignore *ignored_field_keys
|
57
|
+
instance
|
58
|
+
end
|
59
|
+
|
60
|
+
def structure
|
61
|
+
fields.each_with_object({}) do |(_, field), obj|
|
62
|
+
meta = field.meta_data.dup
|
63
|
+
sc = meta.delete(:schema)
|
64
|
+
meta[:structure] = sc.structure if sc
|
65
|
+
obj[field.key] = meta
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def field(field_or_key)
|
70
|
+
f, key = if field_or_key.kind_of?(Field)
|
71
|
+
[field_or_key, field_or_key.key]
|
72
|
+
else
|
73
|
+
[Field.new(field_or_key), field_or_key.to_sym]
|
74
|
+
end
|
75
|
+
|
76
|
+
if ignored_field_keys.include?(f.key)
|
77
|
+
f
|
78
|
+
else
|
79
|
+
@fields[key] = apply_default_field_policies_to(f)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def resolve(payload)
|
84
|
+
context = Context.new
|
85
|
+
output = coerce(payload, nil, context)
|
86
|
+
Results.new(output, context.errors)
|
87
|
+
end
|
88
|
+
|
89
|
+
def walk(meta_key = nil, &visitor)
|
90
|
+
r = visit(meta_key, &visitor)
|
91
|
+
Results.new(r, {})
|
92
|
+
end
|
93
|
+
|
94
|
+
def eligible?(value, key, payload)
|
95
|
+
payload.key? key
|
96
|
+
end
|
97
|
+
|
98
|
+
def valid?(*_)
|
99
|
+
true
|
100
|
+
end
|
101
|
+
|
102
|
+
def meta_data
|
103
|
+
{}
|
104
|
+
end
|
105
|
+
|
106
|
+
def visit(meta_key = nil, &visitor)
|
107
|
+
fields.each_with_object({}) do |(_, field), m|
|
108
|
+
m[field.key] = field.visit(meta_key, &visitor)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def coerce(val, _, context)
|
113
|
+
if val.is_a?(Array)
|
114
|
+
val.map.with_index{|v, idx|
|
115
|
+
coerce_one(v, context.sub(idx))
|
116
|
+
}
|
117
|
+
else
|
118
|
+
coerce_one val, context
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
protected
|
123
|
+
|
124
|
+
attr_reader :definitions, :options
|
125
|
+
|
126
|
+
private
|
127
|
+
|
128
|
+
attr_reader :default_field_policies, :ignored_field_keys
|
129
|
+
|
130
|
+
def coerce_one(val, context)
|
131
|
+
fields.each_with_object({}) do |(_, field), m|
|
132
|
+
r = field.resolve(val, context.sub(field.key))
|
133
|
+
if r.eligible?
|
134
|
+
m[field.key] = r.value
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def apply_default_field_policies_to(field)
|
140
|
+
default_field_policies.reduce(field) {|f, policy_name| f.policy(policy_name) }
|
141
|
+
end
|
142
|
+
|
143
|
+
def apply!
|
144
|
+
return if @applied
|
145
|
+
definitions.each do |d|
|
146
|
+
self.instance_exec(options, &d)
|
147
|
+
end
|
148
|
+
@applied = true
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
data/lib/parametric/version.rb
CHANGED
data/lib/parametric.rb
CHANGED
@@ -1,10 +1,20 @@
|
|
1
|
+
require "parametric/version"
|
2
|
+
require "parametric/registry"
|
3
|
+
require "parametric/field"
|
4
|
+
require "parametric/results"
|
5
|
+
require "parametric/schema"
|
6
|
+
require "parametric/context"
|
7
|
+
|
1
8
|
module Parametric
|
2
9
|
|
10
|
+
def self.registry
|
11
|
+
@registry ||= Registry.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.policy(name, plcy = nil, &block)
|
15
|
+
registry.policy name, plcy, &block
|
16
|
+
end
|
3
17
|
end
|
4
18
|
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require "parametric/policies"
|
8
|
-
require "parametric/params"
|
9
|
-
require "parametric/typed_params"
|
10
|
-
require "parametric/hash"
|
19
|
+
require 'parametric/default_types'
|
20
|
+
require 'parametric/policies'
|
data/parametric.gemspec
CHANGED
@@ -20,5 +20,6 @@ Gem::Specification.new do |spec|
|
|
20
20
|
|
21
21
|
spec.add_development_dependency "bundler", "~> 1.5"
|
22
22
|
spec.add_development_dependency "rake"
|
23
|
-
spec.add_development_dependency "rspec", '
|
23
|
+
spec.add_development_dependency "rspec", '3.4.0'
|
24
|
+
spec.add_development_dependency "byebug"
|
24
25
|
end
|
data/spec/dsl_spec.rb
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require "parametric/dsl"
|
3
|
+
|
4
|
+
describe "classes including DSL module" do
|
5
|
+
class Parent
|
6
|
+
include Parametric::DSL
|
7
|
+
|
8
|
+
schema(age_type: :integer) do |opts|
|
9
|
+
field(:title).policy(:string)
|
10
|
+
field(:age).policy(opts[:age_type])
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class Child < Parent
|
15
|
+
schema(age_type: :string) do
|
16
|
+
field(:description).policy(:string)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class GrandChild < Child
|
21
|
+
schema(age_type: :integer)
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "#schema" do
|
25
|
+
let(:input) {
|
26
|
+
{
|
27
|
+
title: "A title",
|
28
|
+
age: 38,
|
29
|
+
description: "A description"
|
30
|
+
}
|
31
|
+
}
|
32
|
+
|
33
|
+
it "merges parent's schema into child's" do
|
34
|
+
parent_output = Parent.schema.resolve(input).output
|
35
|
+
child_output = Child.schema.resolve(input).output
|
36
|
+
|
37
|
+
expect(parent_output.keys).to match_array([:title, :age])
|
38
|
+
expect(parent_output[:title]).to eq "A title"
|
39
|
+
expect(parent_output[:age]).to eq 38
|
40
|
+
|
41
|
+
expect(child_output.keys).to match_array([:title, :age, :description])
|
42
|
+
expect(child_output[:title]).to eq "A title"
|
43
|
+
expect(child_output[:age]).to eq "38"
|
44
|
+
expect(child_output[:description]).to eq "A description"
|
45
|
+
end
|
46
|
+
|
47
|
+
it "inherits options" do
|
48
|
+
grand_child_output = GrandChild.schema.resolve(input).output
|
49
|
+
|
50
|
+
expect(grand_child_output.keys).to match_array([:title, :age, :description])
|
51
|
+
expect(grand_child_output[:title]).to eq "A title"
|
52
|
+
expect(grand_child_output[:age]).to eq 38
|
53
|
+
expect(grand_child_output[:description]).to eq "A description"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "inheriting schema policy" do
|
58
|
+
let!(:a) {
|
59
|
+
Class.new do
|
60
|
+
include Parametric::DSL
|
61
|
+
|
62
|
+
schema.policy(:present) do
|
63
|
+
field(:title).policy(:string)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
}
|
67
|
+
|
68
|
+
let!(:b) {
|
69
|
+
Class.new(a)
|
70
|
+
}
|
71
|
+
|
72
|
+
it "inherits policy" do
|
73
|
+
results = a.schema.resolve({})
|
74
|
+
expect(results.errors["$.title"]).not_to be_empty
|
75
|
+
|
76
|
+
results = b.schema.resolve({})
|
77
|
+
expect(results.errors["$.title"]).not_to be_empty
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe "overriding schema policy" do
|
82
|
+
let!(:a) {
|
83
|
+
Class.new do
|
84
|
+
include Parametric::DSL
|
85
|
+
|
86
|
+
schema.policy(:present) do
|
87
|
+
field(:title).policy(:string)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
}
|
91
|
+
|
92
|
+
let!(:b) {
|
93
|
+
Class.new(a) do
|
94
|
+
schema.policy(:declared)
|
95
|
+
end
|
96
|
+
}
|
97
|
+
|
98
|
+
it "does not mutate parent schema" do
|
99
|
+
results = a.schema.resolve({})
|
100
|
+
expect(results.errors).not_to be_empty
|
101
|
+
|
102
|
+
results = b.schema.resolve({})
|
103
|
+
expect(results.errors).to be_empty
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
describe "removes fields defined in the parent class" do
|
108
|
+
let!(:a) {
|
109
|
+
Class.new do
|
110
|
+
include Parametric::DSL
|
111
|
+
|
112
|
+
schema do
|
113
|
+
field(:title).policy(:string)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
}
|
117
|
+
|
118
|
+
let!(:b) {
|
119
|
+
Class.new(a) do
|
120
|
+
schema.ignore(:title) do
|
121
|
+
field(:age)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
}
|
125
|
+
|
126
|
+
it "removes inherited field from child class" do
|
127
|
+
results = a.schema.resolve({title: "Mr.", age: 20})
|
128
|
+
expect(results.output).to eq({title: "Mr."})
|
129
|
+
|
130
|
+
results = b.schema.resolve({title: "Mr.", age: 20})
|
131
|
+
expect(results.output).to eq({age: 20})
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|