representors 0.0.5

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