render 0.0.8 → 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. data/.ruby-version +1 -1
  2. data/lib/json/draft-04/hyper-schema.json +168 -0
  3. data/lib/json/draft-04/schema.json +150 -0
  4. data/lib/render.rb +2 -0
  5. data/lib/render/attributes/array_attribute.rb +20 -14
  6. data/lib/render/attributes/attribute.rb +23 -7
  7. data/lib/render/attributes/hash_attribute.rb +6 -2
  8. data/lib/render/definition.rb +13 -7
  9. data/lib/render/errors.rb +33 -6
  10. data/lib/render/generator.rb +67 -8
  11. data/lib/render/graph.rb +39 -64
  12. data/lib/render/json_schema.rb +12 -0
  13. data/lib/render/schema.rb +92 -31
  14. data/lib/render/type.rb +51 -9
  15. data/lib/render/version.rb +1 -1
  16. data/readme.md +66 -9
  17. data/render.gemspec +4 -3
  18. data/spec/functional/render/attribute_spec.rb +66 -8
  19. data/spec/functional/render/nested_schemas_spec.rb +18 -26
  20. data/spec/functional/render/schema_spec.rb +28 -0
  21. data/spec/integration/render/graph_spec.rb +3 -3
  22. data/spec/integration/render/nested_graph_spec.rb +12 -14
  23. data/spec/integration/render/schema_spec.rb +4 -4
  24. data/spec/support/schemas/film.json +3 -3
  25. data/spec/support/schemas/films.json +3 -3
  26. data/spec/unit/render/attributes/array_attribute_spec.rb +34 -9
  27. data/spec/unit/render/attributes/attribute_spec.rb +13 -0
  28. data/spec/unit/render/attributes/hash_attribute_spec.rb +17 -7
  29. data/spec/unit/render/definition_spec.rb +7 -25
  30. data/spec/unit/render/generator_spec.rb +102 -2
  31. data/spec/unit/render/graph_spec.rb +18 -19
  32. data/spec/unit/render/schema_spec.rb +185 -54
  33. data/spec/unit/render/type_spec.rb +88 -13
  34. metadata +66 -29
  35. checksums.yaml +0 -15
@@ -25,7 +25,7 @@ module Render
25
25
 
26
26
  it "stubs data for testing" do
27
27
  name = "am I transforming this right?"
28
- rendered_stub = Render::Schema.new(:films_show).serialize!({ name: name })
28
+ rendered_stub = Render::Schema.new("films_show").serialize!({ name: name })
29
29
  stub_request(:get, "http://films.local").to_return({ body: rendered_stub.to_json })
30
30
 
31
31
  TransformerExample.process_name[:transformed_to].should == name
@@ -33,14 +33,14 @@ module Render
33
33
 
34
34
  it "enforces schema's definition" do
35
35
  name = "am I transforming this right?"
36
- rendered_stub = Render::Schema.new(:films_show).serialize!({ wrong_key: name })
36
+ rendered_stub = Render::Schema.new("films_show").serialize!({ wrong_key: name })
37
37
  stub_request(:get, "http://films.local").to_return({ body: rendered_stub.to_json })
38
38
 
39
39
  TransformerExample.process_name[:transformed_to].should_not == name
40
40
  end
41
41
 
42
42
  it "prevents errors related to code anticipating actual data" do
43
- rendered_stub = Render::Schema.new(:films_show).serialize!
43
+ rendered_stub = Render::Schema.new("films_show").serialize!
44
44
  stub_request(:get, "http://films.local").to_return({ body: rendered_stub.to_json })
45
45
 
46
46
  expect {
@@ -67,7 +67,7 @@ module Render
67
67
  },
68
68
  tags: {
69
69
  type: Array,
70
- required: true,
70
+ minItems: 1,
71
71
  items: {
72
72
  type: Object,
73
73
  properties: {
@@ -1,8 +1,8 @@
1
1
  {
2
- "universal_title": "films_show",
3
- "title": "film",
2
+ "$schema": "http://json-schema.org/draft-04/schema#",
3
+ "id": "films_show",
4
4
  "type": "object",
5
- "endpoint": "http://:host/films/:id",
5
+ "endpoint": "http://{host}/films/{id}",
6
6
  "properties": {
7
7
  "name": { "type": "string" },
8
8
  "year": { "type": "integer" }
@@ -1,8 +1,8 @@
1
1
  {
2
- "universal_title": "films_index",
3
- "title": "films",
2
+ "$schema": "http://json-schema.org/draft-04/schema#",
3
+ "id": "films_index",
4
4
  "type": "array",
5
- "endpoint": "http://:host",
5
+ "endpoint": "http://{host}",
6
6
  "items": {
7
7
  "title": "film",
8
8
  "type" : "object",
@@ -18,18 +18,45 @@ module Render
18
18
  end
19
19
  end
20
20
 
21
- describe "archetype" do
22
- it "returns only a value" do
23
- id = UUID.generate
24
- attribute = ArrayAttribute.new({ items: { type: UUID } })
25
- attribute.serialize([id]).should == [id]
21
+ describe "#serialize" do
22
+ describe "simple" do
23
+ it "only returns a value" do
24
+ id = UUID.generate
25
+ attribute = ArrayAttribute.new({ items: { type: UUID } })
26
+ attribute.serialize([id]).should == [id]
27
+ end
28
+
29
+ it "returns value as defined type" do
30
+ attribute = ArrayAttribute.new({ items: { type: Float } })
31
+ attribute.serialize(["2.0"]).should == [2.0]
32
+ end
33
+
34
+ it "returns faux value as defined type" do
35
+ Render.stub({ live: false })
36
+
37
+ attribute = ArrayAttribute.new({ items: { type: Float }, maxItems: 1, minItems: 1 })
38
+ attribute.stub({ default_value: "2" })
39
+
40
+ attribute.serialize.should == [2.0]
41
+ end
42
+
43
+ it "enforces uniqueness" do
44
+ attribute = ArrayAttribute.new({ items: { type: Integer }, uniqueItems: true })
45
+ attribute.serialize(["2.0", 2, "2"]).should == [2]
46
+ end
47
+
48
+ it "does not enforce uniqueness" do
49
+ attribute = ArrayAttribute.new({ items: { type: Integer } })
50
+ attribute.serialize(["2.0", 2, "2"]).should == [2, 2, 2]
51
+ end
26
52
  end
27
53
  end
28
54
 
29
55
  describe "#faux_data" do
30
56
  before(:each) do
31
57
  Render.stub({ live: false })
32
- @attribute = ArrayAttribute.new({ items: { type: Float, required: true } })
58
+ @lower_limit = 6
59
+ @attribute = ArrayAttribute.new({ items: { type: Float }, minItems: @lower_limit })
33
60
  end
34
61
 
35
62
  it "uses explicit value for faux data" do
@@ -38,13 +65,11 @@ module Render
38
65
  end
39
66
 
40
67
  it "generates fake number of elements" do
41
- lower_limit = 6
42
68
  upper_limit = 9
43
- @attribute.stub({ lower_limit: lower_limit })
44
69
  stub_const("Render::ArrayAttribute::FAUX_DATA_UPPER_LIMIT", upper_limit)
45
70
 
46
71
  faux_data = @attribute.serialize
47
- faux_data.size.should >= lower_limit
72
+ faux_data.size.should >= @lower_limit
48
73
  faux_data.size.should <= upper_limit
49
74
  faux_data.sample.class.should == Float
50
75
  end
@@ -12,6 +12,19 @@ module Render
12
12
  faux_value.should include(name)
13
13
  end
14
14
  end
15
+
16
+ describe "#bias_type" do
17
+ it "biases the format" do
18
+ attribute = HashAttribute.new({ name: { type: String, format: UUID } })
19
+ attribute.bias_type.should eq(UUID)
20
+ end
21
+
22
+ it "biases the first type" do
23
+ attribute = HashAttribute.new({ name: { type: [String, Integer] } })
24
+ attribute.bias_type.should eq(String)
25
+ end
26
+ end
27
+
15
28
  end
16
29
  end
17
30
  end
@@ -12,17 +12,17 @@ module Render
12
12
  it "is set from options" do
13
13
  type = Integer
14
14
  attribute = HashAttribute.new({ key_name: { type: type } })
15
- attribute.type.should == type
15
+ attribute.types.should == [type]
16
16
  end
17
17
 
18
18
  it "is set from name hash" do
19
19
  type = String
20
20
  attribute = HashAttribute.new({ id: { type: UUID } })
21
- attribute.type.should == UUID
21
+ attribute.types.should == [UUID]
22
22
  end
23
23
 
24
24
  it "determines type from string" do
25
- HashAttribute.new({ key_name: { type: "string" } }).type.should == String
25
+ HashAttribute.new({ key_name: { type: "string" } }).types.should == [String]
26
26
  end
27
27
  end
28
28
 
@@ -58,7 +58,7 @@ module Render
58
58
  hash_attributes.size.should == 1
59
59
  attribute = hash_attributes.first
60
60
  attribute.name.should == :year
61
- attribute.type.should == Integer
61
+ attribute.types.should == [Integer]
62
62
  end
63
63
  end
64
64
 
@@ -86,6 +86,18 @@ module Render
86
86
  end
87
87
  end
88
88
 
89
+ it "returns value as defined type" do
90
+ attribute = HashAttribute.new({ year: { type: Integer } })
91
+ attribute.serialize("2").should == { year: 2 }
92
+ end
93
+
94
+ it "returns faux value as defined type" do
95
+ attribute = HashAttribute.new({ year: { type: Integer } })
96
+ attribute.stub({ default_value: "2" })
97
+
98
+ attribute.serialize(nil).should == { year: 2 }
99
+ end
100
+
89
101
  context "offline" do
90
102
  before(:each) do
91
103
  Render.stub({ live: false })
@@ -97,11 +109,9 @@ module Render
97
109
  data[:title].should be_a(type)
98
110
  end
99
111
 
100
- it "maintains falsy-ie values when instructed" do
112
+ it "maintains nil values when instructed" do
101
113
  data = HashAttribute.new({ title: { type: Integer } }).serialize(nil, true)
102
114
  data[:title].should == nil
103
- data = HashAttribute.new({ title: { type: Integer } }).serialize(false, true)
104
- data[:title].should == false
105
115
  end
106
116
  end
107
117
  end
@@ -10,31 +10,13 @@ module Render
10
10
  Definition.instances = @original_defs
11
11
  end
12
12
 
13
- describe ".load!" do
14
- it "preferences #universal_title over title" do
15
- universal_title = "a_service_films_show"
16
- definition = {
17
- universal_title: universal_title,
18
- title: "film",
19
- type: Object,
20
- properties: {
21
- name: { type: String },
22
- year: { type: Integer }
23
- }
24
- }
25
-
26
- Definition.load!(definition)
27
- Definition.instances.keys.should include(universal_title.to_sym)
28
- end
29
- end
30
-
31
13
  describe ".load_schemas!" do
32
14
  before(:each) do
33
15
  Definition.instances.clear
34
- @schema_title = "film"
16
+ @schema_id = "films.show"
35
17
  @json_schema = <<-JSON
36
18
  {
37
- "title": "#{@schema_title}",
19
+ "id": "#{@schema_id}",
38
20
  "type": "object",
39
21
  "properties": {
40
22
  "name": { "type": "string" },
@@ -62,24 +44,24 @@ module Render
62
44
  it "accesses parsed schemas with symbols" do
63
45
  Definition.load_from_directory!(@directory)
64
46
  parsed_json = Render::Extensions::DottableHash.new(JSON.parse(@json_schema)).recursively_symbolize_keys!
65
- Definition.instances[@schema_title.to_sym].should == parsed_json
47
+ Definition.instances[@schema_id].should == parsed_json
66
48
  end
67
49
  end
68
50
  end
69
51
 
70
52
  describe ".definition" do
71
53
  it "returns definition by its title" do
72
- def_title = :the_name
73
- definition = { title: def_title, properties: {} }
54
+ def_id = :the_name
55
+ definition = { id: def_id, properties: {} }
74
56
  Definition.load!(definition)
75
57
 
76
- Definition.find(def_title).should == definition
58
+ Definition.find(def_id).should == definition
77
59
  end
78
60
 
79
61
  it "raises meaningful error if definition is not found" do
80
62
  expect {
81
63
  Definition.find(:definition_with_this_title_has_not_been_loaded)
82
- }.to raise_error(Render::Errors::DefinitionNotFound)
64
+ }.to raise_error(Render::Errors::Definition::NotFound)
83
65
  end
84
66
  end
85
67
  end
@@ -28,9 +28,23 @@ module Render
28
28
  end
29
29
 
30
30
  describe ".trigger" do
31
+ context "no generator" do
32
+ it "returns nil" do
33
+ attribute = double(:attribute, { name: "foo" })
34
+ Generator.trigger(:foo, "_to_match", attribute).should == nil
35
+ end
36
+
37
+ it "warns" do
38
+ Render.logger.should_receive(:warn).with(/find.*generator.*foo.*_to_match/i)
39
+
40
+ attribute = double(:attribute, { name: "foo" })
41
+ Generator.trigger(:foo, "_to_match", attribute).should == nil
42
+ end
43
+ end
44
+
31
45
  it "triggers matching generator for Render types" do
32
- enum_attribute = HashAttribute.new({ attribute_name: { type: String, enum: ["foo"] } })
33
- Generator.trigger(:enum, "anything", enum_attribute).should == "foo"
46
+ enum_attribute = HashAttribute.new({ name: { type: String, enum: ["foo"] } })
47
+ Generator.trigger(enum_attribute.bias_type, "anything", enum_attribute).should == "foo"
34
48
  end
35
49
  end
36
50
 
@@ -65,5 +79,91 @@ module Render
65
79
  Generator.new(UUID, //, algorithm).trigger(x).should == algorithm.call(x)
66
80
  end
67
81
  end
82
+
83
+ describe "default set" do
84
+ describe String do
85
+ it "adheres to minLength" do
86
+ min_length = 100
87
+ attribute = HashAttribute.new({ name: { type: String, minLength: min_length } })
88
+ value = Generator.trigger(String, "_to_match", attribute)
89
+ value.length.should >= min_length
90
+ end
91
+
92
+ it "adheres to maxLength" do
93
+ max_length = 2
94
+ attribute = HashAttribute.new({ name: { type: String, maxLength: max_length } })
95
+ value = Generator.trigger(String, "_to_match", attribute)
96
+ value.length.should <= max_length
97
+ end
98
+ end
99
+
100
+ describe Integer do
101
+ it "adheres to multipleOf" do
102
+ attribute = HashAttribute.new({ name: { type: "integer", multipleOf: 13 } })
103
+ value = Generator.trigger(attribute.types.first, "_to_match", attribute)
104
+ (value % 13).should eq(0)
105
+ end
106
+
107
+ it "adheres to minimum" do
108
+ attribute = HashAttribute.new({ name: { type: "integer", minimum: 100 } })
109
+ value = Generator.trigger(attribute.types.first, "_to_match", attribute)
110
+ value.should > 100
111
+ end
112
+
113
+ it "adheres to maximum" do
114
+ attribute = HashAttribute.new({ name: { type: "integer", maximum: 100 } })
115
+ value = Generator.trigger(attribute.types.first, "_to_match", attribute)
116
+ value.should <= 100
117
+ end
118
+
119
+ it "adherse to exclusiveMinimum" do
120
+ attribute = HashAttribute.new({ name: { type: "integer", minimum: 4, maximum: 5, exclusiveMinimum: true } })
121
+ Generator.trigger(attribute.types.first, "_to_match", attribute).should eq(5)
122
+ end
123
+
124
+ it "adherse to exclusiveMaximum" do
125
+ attribute = HashAttribute.new({ name: { type: "integer", minimum: 4, maximum: 5, exclusiveMaximum: true } })
126
+ Generator.trigger(attribute.types.first, "_to_match", attribute).should eq(4)
127
+ end
128
+
129
+ it "adheres to minimum and multipleOf" do
130
+ attribute = HashAttribute.new({ name: { type: "integer", minimum: 60, maximum: 70, multipleOf: 13 } })
131
+ value = Generator.trigger(attribute.types.first, "_to_match", attribute)
132
+ (value % 13).should eq(0)
133
+ value.should eq(65)
134
+ end
135
+ end
136
+
137
+ describe "Numbers (converted to Float)" do
138
+ it "adheres to multipleOf" do
139
+ attribute = HashAttribute.new({ name: { type: "number", multipleOf: 1.3 } })
140
+ value = Generator.trigger(attribute.types.first, "_to_match", attribute)
141
+ (value % 1.3).should eq(0)
142
+ end
143
+
144
+ it "adheres to minimum" do
145
+ attribute = HashAttribute.new({ name: { type: "number", minimum: 100.0 } })
146
+ value = Generator.trigger(attribute.types.first, "_to_match", attribute)
147
+ value.should > 100.0
148
+ end
149
+
150
+ it "adheres to maximum" do
151
+ attribute = HashAttribute.new({ name: { type: "number", maximum: 100 } })
152
+ value = Generator.trigger(attribute.types.first, "_to_match", attribute)
153
+ value.should <= 100
154
+ end
155
+
156
+ it "adherse to exclusiveMinimum" do
157
+ attribute = HashAttribute.new({ name: { type: "number", minimum: 99.49, maximum: 99.50, exclusiveMinimum: true } })
158
+ Generator.trigger(attribute.types.first, "_to_match", attribute).should eq(99.50)
159
+ end
160
+
161
+ it "adherse to exclusiveMaximum" do
162
+ attribute = HashAttribute.new({ name: { type: "number", minimum: 99.49, maximum: 99.50, exclusiveMaximum: true } })
163
+ Generator.trigger(attribute.types.first, "_to_match", attribute).should eq(99.49)
164
+ end
165
+ end
166
+
167
+ end
68
168
  end
69
169
  end
@@ -6,8 +6,8 @@ module Render
6
6
  describe Graph do
7
7
  before(:each) do
8
8
  Render.stub({ live: false })
9
- @definition = double(:definition, { :[] => nil })
10
- @schema = double(:schema, { type: Hash, definition: @definition })
9
+ @definition = { type: Object, properties: { foo: { type: String } } }
10
+ @schema = Schema.new(@definition)
11
11
  Schema.stub(:new).with(@definition).and_return(@schema)
12
12
  end
13
13
 
@@ -49,34 +49,34 @@ module Render
49
49
  end
50
50
  end
51
51
 
52
- describe ".send(:endpoint)" do
52
+ describe "#endpoint" do
53
53
  it "returns #raw_endpoint" do
54
54
  simple_endpoint = "http://endpoint.local"
55
55
  graph = Graph.new(@definition, { endpoint: simple_endpoint })
56
56
  graph.send(:endpoint).should == simple_endpoint
57
57
  end
58
58
 
59
- it "interpolates inherited parameters" do
59
+ it "expands inherited variables" do
60
60
  director_id = UUID.generate
61
- endpoint = "http://endpoint.local/directors/:id"
61
+ endpoint = "http://endpoint.local/directors/{id}"
62
62
  relationships = { director_id: :id }
63
63
 
64
64
  graph = Graph.new(@definition, { endpoint: endpoint, relationships: relationships })
65
- graph.inherited_data = { director_id: director_id }
65
+ graph.relationship_info = { id: director_id }
66
66
 
67
67
  graph.send(:endpoint).should == "http://endpoint.local/directors/#{director_id}"
68
68
  end
69
69
 
70
70
  it "interpolates config options" do
71
71
  client_id = UUID.generate
72
- endpoint = "http://endpoint.local/?:client_id"
72
+ endpoint = "http://endpoint.local/{?client_id}"
73
73
 
74
74
  graph = Graph.new(@definition, { endpoint: endpoint, client_id: client_id })
75
75
  graph.send(:endpoint).should == "http://endpoint.local/?client_id=#{client_id}"
76
76
  end
77
77
 
78
78
  it "raises an error if no value can be found" do
79
- endpoint = "http://endpoint.com/?:undefined_key"
79
+ endpoint = "http://endpoint.com/{?undefined_key}"
80
80
  graph = Graph.new(@definition, { endpoint: endpoint })
81
81
 
82
82
  expect {
@@ -88,11 +88,10 @@ module Render
88
88
  describe "#render" do
89
89
  it "returns its schema's data" do
90
90
  serialized_data = { id: UUID.generate }
91
- pull = { film: serialized_data }
92
- @schema.stub({ render!: pull, serialized_data: serialized_data })
91
+ @schema.stub({ render!: serialized_data })
93
92
 
94
93
  graph = Graph.new(@definition)
95
- graph.render!.should == pull
94
+ graph.render!.should == { untitled: serialized_data }
96
95
  end
97
96
 
98
97
  it "returns a dottable hash" do
@@ -104,11 +103,11 @@ module Render
104
103
  end
105
104
 
106
105
  it "sends interpolated endpoint to its schema" do
107
- endpoint = "http://endpoint.local/?:client_id"
106
+ endpoint = "http://endpoint.local/{?client_id}"
108
107
  client_id = UUID.generate
109
108
  graph = Graph.new(@definition, { endpoint: endpoint, client_id: client_id })
110
109
 
111
- @schema.should_receive(:render!).with(anything, graph.send(:endpoint)).and_return({})
110
+ @schema.should_receive(:render!).with(anything, "http://endpoint.local/?client_id=#{client_id}")
112
111
  graph.render!
113
112
  end
114
113
 
@@ -140,11 +139,11 @@ module Render
140
139
  it "uses parent data to calculate endpoint" do
141
140
  film = Graph.new(@film_schema)
142
141
  relationships = { director_id: :id }
143
- endpoint = "http://endpoint.local/directors/:id"
142
+ endpoint = "http://endpoint.local/directors/{id}"
144
143
  film.graphs << Graph.new(@director_schema, { endpoint: endpoint, relationships: relationships })
145
144
 
146
145
  film_data = { director_id: @director_id }
147
- @film_schema.should_receive(:render!).and_yield(film_data).and_return(film_data)
146
+ @film_schema.should_receive(:render!).and_return(film_data)
148
147
 
149
148
  endpoint = "http://endpoint.local/directors/#{@director_id}"
150
149
  @director_schema.should_receive(:render!).with(anything, endpoint).and_return({})
@@ -169,7 +168,7 @@ module Render
169
168
  first_film_id = UUID.generate
170
169
  second_film_id = UUID.generate
171
170
  films_response = [{ id: first_film_id }, { id: second_film_id }]
172
- films_schema.should_receive(:render!).and_yield(films_response).and_return({ films: films_response })
171
+ films_schema.should_receive(:render!).and_return(films_response)
173
172
 
174
173
  response = films.render!
175
174
  response.film.should be_a(Array)
@@ -179,12 +178,12 @@ module Render
179
178
  it "uses parent data for childrens' properties when explicitly used" do
180
179
  director = Graph.new(@director_schema, { relationships: { director_id: :id } })
181
180
  film = Graph.new(@film_schema, { graphs: [director] })
182
- @film_schema.should_receive(:render!).and_yield({ director_id: @director_id }).and_return({})
181
+ @film_schema.should_receive(:render!).and_return({ director_id: @director_id })
183
182
 
184
183
  film.render!.director.id.should == @director_id
185
184
  end
186
185
 
187
- it "uses archetype parental data" do
186
+ it "uses simple parental data" do
188
187
  films_schema = Schema.new({
189
188
  title: "films",
190
189
  type: Array,
@@ -198,7 +197,7 @@ module Render
198
197
 
199
198
  film_id = UUID.generate
200
199
  films_response = [film_id]
201
- films_schema.should_receive(:render!).and_yield(films_response).and_return({ films: films_response })
200
+ films.schema.stub({ render!: films_response })
202
201
 
203
202
  response = films.render!
204
203
  response.film.should be_a(Array)