representors 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +18 -0
  3. data/Gemfile +3 -0
  4. data/Gemfile.lock +126 -0
  5. data/LICENSE.md +19 -0
  6. data/README.md +28 -0
  7. data/Rakefile +10 -0
  8. data/lib/representor_support/utilities.rb +39 -0
  9. data/lib/representors.rb +5 -0
  10. data/lib/representors/errors.rb +7 -0
  11. data/lib/representors/field.rb +108 -0
  12. data/lib/representors/options.rb +67 -0
  13. data/lib/representors/representor.rb +161 -0
  14. data/lib/representors/representor_builder.rb +64 -0
  15. data/lib/representors/representor_hash.rb +59 -0
  16. data/lib/representors/serialization.rb +4 -0
  17. data/lib/representors/serialization/deserializer_base.rb +29 -0
  18. data/lib/representors/serialization/deserializer_factory.rb +13 -0
  19. data/lib/representors/serialization/hal_deserializer.rb +44 -0
  20. data/lib/representors/serialization/hal_serializer.rb +91 -0
  21. data/lib/representors/serialization/hale_deserializer.rb +162 -0
  22. data/lib/representors/serialization/hale_serializer.rb +110 -0
  23. data/lib/representors/serialization/serialization_base.rb +27 -0
  24. data/lib/representors/serialization/serialization_factory_base.rb +54 -0
  25. data/lib/representors/serialization/serializer_base.rb +20 -0
  26. data/lib/representors/serialization/serializer_factory.rb +17 -0
  27. data/lib/representors/transition.rb +130 -0
  28. data/lib/representors/version.rb +4 -0
  29. data/spec/fixtures/complex_hal.json +92 -0
  30. data/spec/fixtures/complex_hale_document.json +81 -0
  31. data/spec/fixtures/drds_hash.rb +120 -0
  32. data/spec/fixtures/hale_spec_examples/basic.json +77 -0
  33. data/spec/fixtures/hale_spec_examples/complex_reference_objects.json +157 -0
  34. data/spec/fixtures/hale_spec_examples/data.json +17 -0
  35. data/spec/fixtures/hale_spec_examples/data_objects.json +96 -0
  36. data/spec/fixtures/hale_spec_examples/link_objects.json +18 -0
  37. data/spec/fixtures/hale_spec_examples/nested_ref.json +43 -0
  38. data/spec/fixtures/hale_spec_examples/reference_objects.json +89 -0
  39. data/spec/fixtures/hale_tutorial_examples/basic_links.json +85 -0
  40. data/spec/fixtures/hale_tutorial_examples/basic_links_with_orders.json +96 -0
  41. data/spec/fixtures/hale_tutorial_examples/basic_links_with_references.json +108 -0
  42. data/spec/fixtures/hale_tutorial_examples/embedded.json +182 -0
  43. data/spec/fixtures/hale_tutorial_examples/empty.json +1 -0
  44. data/spec/fixtures/hale_tutorial_examples/enctype.json +14 -0
  45. data/spec/fixtures/hale_tutorial_examples/final.json +141 -0
  46. data/spec/fixtures/hale_tutorial_examples/get_link.json +17 -0
  47. data/spec/fixtures/hale_tutorial_examples/get_link_with_data.json +29 -0
  48. data/spec/fixtures/hale_tutorial_examples/links.json +11 -0
  49. data/spec/fixtures/hale_tutorial_examples/links_only.json +3 -0
  50. data/spec/fixtures/hale_tutorial_examples/meta.json +208 -0
  51. data/spec/fixtures/hale_tutorial_examples/self_link.json +7 -0
  52. data/spec/fixtures/single_drd.rb +266 -0
  53. data/spec/lib/representors/complex_representor_spec.rb +288 -0
  54. data/spec/lib/representors/field_spec.rb +141 -0
  55. data/spec/lib/representors/representor_builder_spec.rb +223 -0
  56. data/spec/lib/representors/representor_spec.rb +285 -0
  57. data/spec/lib/representors/serialization/deserializer_factory_spec.rb +118 -0
  58. data/spec/lib/representors/serialization/hal_deserializer_spec.rb +34 -0
  59. data/spec/lib/representors/serialization/hal_serializer_spec.rb +171 -0
  60. data/spec/lib/representors/serialization/hale_deserializer_spec.rb +59 -0
  61. data/spec/lib/representors/serialization/hale_roundtrip_spec.rb +34 -0
  62. data/spec/lib/representors/serialization/hale_serializer_spec.rb +659 -0
  63. data/spec/lib/representors/serialization/serializer_factory_spec.rb +108 -0
  64. data/spec/lib/representors/transition_spec.rb +349 -0
  65. data/spec/spec_helper.rb +32 -0
  66. data/spec/support/basic-hale.json +12 -0
  67. data/spec/support/hal_representor_shared.rb +206 -0
  68. data/spec/support/helpers.rb +8 -0
  69. data/tasks/benchmark.rake +75 -0
  70. data/tasks/complex_hal_document.json +98 -0
  71. data/tasks/test_specs.rake +37 -0
  72. data/tasks/yard.rake +22 -0
  73. metadata +232 -0
@@ -0,0 +1,32 @@
1
+ unless ENV['MUTANT']
2
+ require 'simplecov'
3
+ SimpleCov.start
4
+ end
5
+
6
+ SPEC_DIR = File.expand_path("..", __FILE__)
7
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
8
+ require 'representors'
9
+
10
+ Dir["#{SPEC_DIR}/support/*.rb"].each { |f| require f }
11
+
12
+ def create_serializer(name)
13
+ Class.new(Representors::SerializerBase) do |klass|
14
+ klass.media_symbol name.to_sym
15
+ klass.media_type name
16
+ end
17
+ end
18
+
19
+ RSpec.configure do |config|
20
+ config.expect_with :rspec do |c|
21
+ c.syntax = :expect
22
+ end
23
+
24
+ # Run specs in random order to surface order dependencies. If you find an
25
+ # order dependency and want to debug it, you can fix the order by providing
26
+ # the seed, which is printed after each run.
27
+ # --seed 1234
28
+ config.order = 'random' unless ENV['RANDOMIZE'] == 'false'
29
+
30
+ config.include Support::Helpers
31
+ config.include RepresentorSupport::Utilities
32
+ end
@@ -0,0 +1,12 @@
1
+ {
2
+ "_meta": {
3
+ "any": { "json": "object" }
4
+ },
5
+ "_links": {
6
+ "self": { "href": "/goes_to_myself"},
7
+ "customers": [
8
+ { "href": "/customer/1"},
9
+ { "href": "/customer/2"}
10
+ ]
11
+ }
12
+ }
@@ -0,0 +1,206 @@
1
+ shared_examples_for 'can create a representor from a hal document' do
2
+
3
+ let(:semantics_field) {deserializer.to_representor.properties}
4
+ let(:transitions_field) {deserializer.to_representor.transitions}
5
+ let(:embedded_field) {deserializer.to_representor.embedded }
6
+ context "empty document" do
7
+ let(:document) { {}.to_json }
8
+
9
+ it "returns a hash with no attributes, links or embedded resources" do
10
+ expect(deserializer.to_representor.properties).to be_empty
11
+ expect(deserializer.to_representor.transitions).to be_empty
12
+ expect(deserializer.to_representor.embedded).to be_empty
13
+ end
14
+ end
15
+
16
+ context 'Document with only properties' do
17
+ let(:original_hash) do
18
+ {
19
+ 'title' => 'The Neverending Story',
20
+ 'author' => 'Michael Ende',
21
+ 'pages' => '396'
22
+ }
23
+ end
24
+ let(:document) { original_hash.to_json}
25
+
26
+ it 'return a representor with all the attributes of the document' do
27
+ expect(semantics_field).to eq(original_hash)
28
+ end
29
+
30
+ end
31
+
32
+ context 'Document with properties and links' do
33
+ let(:semantics) { { 'title' => 'The Neverending Story'}}
34
+ let(:transition_rel) { 'author'}
35
+ let(:transition_href) { '/mike'}
36
+ let(:document) do
37
+ {
38
+ 'title' => 'The Neverending Story',
39
+ '_links' => {
40
+ 'author' => {'href' => '/mike'}
41
+ }
42
+ }
43
+ end
44
+
45
+ it 'return a hash with all the attributes of the document' do
46
+ expect(semantics_field).to eq(semantics)
47
+ end
48
+ it 'Create a transition with the link' do
49
+ expect(transitions_field.first.rel).to eq(transition_rel)
50
+ expect(transitions_field.first.uri).to eq(transition_href)
51
+ end
52
+ it 'does not return any embedded resource' do
53
+ expect(embedded_field).to be_empty
54
+ end
55
+
56
+ end
57
+
58
+ context 'Document with properties, links and embedded' do
59
+ let(:semantics) { { 'title' => 'The Neverending Story'}}
60
+ let(:transition_rel) { 'author'}
61
+ let(:transition_href) { '/mike'}
62
+ let(:embedded_book) { {'content' => 'A...'} }
63
+ let(:document) do
64
+ {
65
+ 'title' => 'The Neverending Story',
66
+ '_links' => {
67
+ transition_rel => {'href' => transition_href}
68
+ },
69
+ '_embedded' => {
70
+ 'embedded_book' => embedded_book
71
+ }
72
+ }
73
+ end
74
+ it 'Returns a hash with all the attributes of the document' do
75
+ expect(semantics_field).to eq(semantics)
76
+ end
77
+ it 'Creates a transition with the link' do
78
+ expect(transitions_field.first.rel).to eq(transition_rel)
79
+ expect(transitions_field.first.uri).to eq(transition_href)
80
+ end
81
+
82
+ it 'Creates an embedded resource with its data' do
83
+ expect(embedded_field['embedded_book'].properties).to eq(embedded_book)
84
+ end
85
+ end
86
+
87
+
88
+ context 'Document with an embedded collection' do
89
+ let(:embedded_book1) { {'content' => 'A...'} }
90
+ let(:embedded_book2) { {'content' => 'When...'} }
91
+ let(:embedded_book3) { {'content' => 'Once upon...'} }
92
+ let(:embedded_books) { [ embedded_book1, embedded_book2, embedded_book3 ] }
93
+ let(:document) do
94
+ {
95
+ '_embedded' => {
96
+ 'embedded_books' => [ embedded_book1, embedded_book2, embedded_book3 ]
97
+ }
98
+ }
99
+ end
100
+
101
+ it 'Creates three embedded resources' do
102
+ expect(embedded_field['embedded_books'].size).to eq(embedded_books.size)
103
+ end
104
+
105
+ it 'Creates embedded resources with its data' do
106
+ embedded_books.each_with_index do |item, index|
107
+ expect(embedded_field['embedded_books'][index].properties).to eq(item)
108
+ end
109
+ end
110
+ end
111
+
112
+ context 'Document with only a self link and a title' do
113
+ let(:href) { '/example_resource'}
114
+ let(:title) { 'super!'}
115
+ let(:document) do
116
+ { '_links' => {
117
+ 'self' => { 'href' => href, 'title' => title}
118
+ }
119
+ }
120
+ end
121
+
122
+ it 'the transition has a "self" rel' do
123
+ expect(transitions_field.first.rel).to eq('self')
124
+ end
125
+
126
+ it 'The transition has its href set properly' do
127
+ expect(transitions_field.first.uri).to eq(href)
128
+ end
129
+
130
+ it 'The transition has a title' do
131
+ expect(transitions_field.first['title']).to eq(title)
132
+ end
133
+
134
+ end
135
+
136
+ context 'Document with an array of two links under items' do
137
+ let(:first_href) {'/example_resource'}
138
+ let(:second_href) {'/lotr_resource2'}
139
+ let(:rel) { 'items'}
140
+ let(:document) do
141
+ { '_links' => {
142
+ rel => [{ 'href' => first_href, 'title' => 'resource1'},
143
+ { 'href' => second_href, 'title' => 'resource2'}]
144
+ }
145
+ }
146
+ end
147
+
148
+ it 'The representor has two links' do
149
+ expect(transitions_field.size).to eq(2)
150
+ end
151
+
152
+ it 'The transitions have a rel properly set' do
153
+ expect(transitions_field.all?{|link| link.rel == 'items'}).to eq(true)
154
+ end
155
+
156
+ it 'The transitions have a href properly set ' do
157
+ expect(transitions_field[0].uri).to eq(first_href)
158
+ expect(transitions_field[1].uri).to eq(second_href)
159
+ end
160
+ end
161
+
162
+ context 'Document with a link without a href' do
163
+ let(:document) do
164
+ { '_links' => {
165
+ 'self' => { 'title' => 'things'}
166
+ }
167
+ }
168
+ end
169
+
170
+ it 'raises a DeserializationError' do
171
+ expect{transitions_field}.to raise_error(Representors::DeserializationError, "All links must contain the href attribute")
172
+ end
173
+ end
174
+
175
+ context 'Document where not all links have href' do
176
+ let(:link_properties) { { 'href' => '/example_resource'} }
177
+ let(:document) do
178
+ { '_links' => {
179
+ 'items' => [{ 'title' => 'resource1'},
180
+ { 'href' => '/example_resource2', 'title' => 'resource2'}]
181
+ }
182
+ }
183
+ end
184
+
185
+ it 'raises a DeserializationError' do
186
+ expect{transitions_field}.to raise_error(Representors::DeserializationError, "All links must contain the href attribute")
187
+ end
188
+ end
189
+
190
+ context 'Document with CURIEs' do
191
+ let(:link_properties) { { 'href' => '/example_resource'} }
192
+ let(:document) do
193
+ { '_links' => {
194
+ 'curies' => { 'href' => '/example_resource'}
195
+ }
196
+ }
197
+ end
198
+
199
+ it 'raises a DeserializationError' do
200
+ expect{transitions_field}.to raise_error(Representors::DeserializationError, "CURIE support not implemented for HAL")
201
+ end
202
+ end
203
+
204
+ end
205
+
206
+
@@ -0,0 +1,8 @@
1
+ module Support
2
+ module Helpers
3
+ # Add some helpers
4
+ def fixture_path(*args)
5
+ File.join(SPEC_DIR, 'fixtures', args)
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,75 @@
1
+ require 'representors'
2
+ require 'benchmark'
3
+
4
+ ITERATIONS = 5
5
+ DESERIALIZATIONS = 10_000
6
+ HAL_BENCHMARK_FILE = 'complex_hal_document.json'
7
+ HALE_BENCHMARK_FILE = 'complex_hale_document.json'
8
+
9
+
10
+ def benchmark
11
+ benchmark_result = Benchmark.bm do |benchmarker|
12
+ 1.upto(ITERATIONS) do |iteration|
13
+ benchmarker.report("Iteration #{iteration}") do
14
+ DESERIALIZATIONS.times do
15
+ yield
16
+ end
17
+ end
18
+ end
19
+ end
20
+ benchmark_result
21
+ average_total_times = benchmark_result.map(&:total).inject(&:+) / ITERATIONS
22
+ average_operation_ms = (average_total_times * 1000) / DESERIALIZATIONS
23
+
24
+ puts "Processing #{DESERIALIZATIONS} objects took on average #{'%.4f' % average_total_times} seconds"
25
+ puts "It took #{'%.4f' % average_operation_ms} milliseconds to process each document or representor"
26
+ end
27
+
28
+ def benchmark_deserializer(format, document)
29
+ data = File.read( File.join(File.dirname(__FILE__), document))
30
+ puts "Deserializing #{format}:"
31
+
32
+ benchmark do
33
+ result = Representors::DeserializerFactory.build(format, data).to_representor
34
+ result.properties
35
+ result.transitions
36
+ result.embedded
37
+ end
38
+ puts "------------------"
39
+ puts " "
40
+ end
41
+
42
+
43
+ def benchmark_serializer(format, document)
44
+ data = File.read( File.join(File.dirname(__FILE__), document))
45
+ representor = Representors::DeserializerFactory.build(format, data).to_representor
46
+
47
+ puts "Serializing #{format}:"
48
+
49
+ benchmark do
50
+ Representors::SerializerFactory.build(format, representor).to_media_type
51
+ end
52
+ puts "------------------"
53
+ puts " "
54
+ end
55
+
56
+ namespace :benchmark do
57
+ desc 'Benchmark deserializations'
58
+ task :deserializations do
59
+ benchmark_deserializer('application/hal+json', HAL_BENCHMARK_FILE )
60
+ benchmark_deserializer('application/vnd.hale+json', HALE_BENCHMARK_FILE )
61
+ end
62
+
63
+ desc 'Benchmark serializations'
64
+ task :serializations do
65
+ benchmark_serializer('application/hal+json', HAL_BENCHMARK_FILE )
66
+ benchmark_serializer('application/vnd.hale+json', HALE_BENCHMARK_FILE )
67
+ end
68
+
69
+ desc 'runs all benchmarks'
70
+ task :all do
71
+ Rake::Task['benchmark:deserializations'].invoke
72
+ Rake::Task['benchmark:serializations'].invoke
73
+ end
74
+
75
+ end
@@ -0,0 +1,98 @@
1
+ {
2
+ "_links":{
3
+ "self":{
4
+ "href":"http://example.org/api/user/ed"
5
+ },
6
+ "testrel":{
7
+ "href":"http://example.org/api/user/test",
8
+ "templated":true,
9
+ "type":"some-type",
10
+ "deprecation":"http://very-deprecated.com",
11
+ "name":"some-name",
12
+ "profile":"some-profile",
13
+ "title":"A Great Title",
14
+ "hreflang":"en-US"
15
+ },
16
+ "testrelarray":[
17
+ {
18
+ "href":"http://example.org/api/user/test1",
19
+ "templated":true,
20
+ "type":"some-type",
21
+ "deprecation":"http://very-deprecated.com",
22
+ "name":"some-name",
23
+ "profile":"some-profile",
24
+ "title":"A Great Title",
25
+ "hreflang":"en-US"
26
+ },
27
+ {
28
+ "href":"http://example.org/api/user/test2",
29
+ "templated":true,
30
+ "type":"some-type",
31
+ "deprecation":"http://very-deprecated.com",
32
+ "name":"some-name",
33
+ "profile":"some-profile",
34
+ "title":"A Great Title",
35
+ "hreflang":"en-US"
36
+ }
37
+ ]
38
+ },
39
+ "some-property":123,
40
+ "_embedded":{
41
+ "embedded1":{
42
+ "_links":{
43
+ "self":{
44
+ "href":"http:/nice.com"
45
+ },
46
+ "testrel":{
47
+ "href":"http://example.org/api/user/test",
48
+ "templated":true,
49
+ "type":"some-type",
50
+ "deprecation":"http://very-deprecated.com",
51
+ "name":"some-name",
52
+ "profile":"some-profile",
53
+ "title":"A Great Title",
54
+ "hreflang":"en-US"
55
+ }
56
+ },
57
+ "some-property":1234
58
+ },
59
+ "embeddedarray":[
60
+ {
61
+ "_links":{
62
+ "self":{
63
+ "href":"http:/nice.com"
64
+ },
65
+ "testrel":{
66
+ "href":"http://example.org/api/user/test",
67
+ "templated":true,
68
+ "type":"some-type",
69
+ "deprecation":"http://very-deprecated.com",
70
+ "name":"some-name",
71
+ "profile":"some-profile",
72
+ "title":"A Great Title",
73
+ "hreflang":"en-US"
74
+ }
75
+ },
76
+ "some-property":1234
77
+ },
78
+ {
79
+ "_links":{
80
+ "self":{
81
+ "href":"http:/nice.com"
82
+ },
83
+ "testrel":{
84
+ "href":"http://example.org/api/user/test",
85
+ "templated":true,
86
+ "type":"some-type",
87
+ "deprecation":"http://very-deprecated.com",
88
+ "name":"some-name",
89
+ "profile":"some-profile",
90
+ "title":"A Great Title",
91
+ "hreflang":"en-US"
92
+ }
93
+ },
94
+ "some-property":1234
95
+ }
96
+ ]
97
+ }
98
+ }
@@ -0,0 +1,37 @@
1
+ require 'open-uri'
2
+ require 'json'
3
+
4
+
5
+ def convert_to_representor(index, example)
6
+ representor = Representors::HaleDeserializer.new(example).to_representor
7
+ serialized_representor = Representors::Serialization::HaleSerializer.new(representor).to_media_type
8
+ if JSON.parse(serialized_representor) != JSON.parse(example)
9
+ puts example
10
+ puts "Example number #{index} can not be roundtriped!"
11
+ else
12
+ puts "Example number #{index} was roundtriped, HOORAY!"
13
+ end
14
+
15
+ rescue JSON::ParserError
16
+ puts example
17
+ puts "Example number #{index} has a JSON error!"
18
+ rescue TypeError => e
19
+ puts example
20
+ puts "Example number #{index} breaks our code!"
21
+ puts e
22
+ end
23
+
24
+ #TODO: Test Hal spec also
25
+ desc "Rake tast to test the implementation against the specs"
26
+ task :test_specs do
27
+
28
+ hale_spec = ''
29
+ hale_spec << open('https://raw.githubusercontent.com/mdsol/hale/master/README.md').read
30
+
31
+ examples = hale_spec.scan(/```json(.*?)```/m).flatten
32
+
33
+ examples.each_with_index do |example, index|
34
+ convert_to_representor(index+1, example)
35
+ end
36
+
37
+ end