render 0.0.8 → 0.0.9

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 (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)