halibut 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,81 @@
1
+ module Halibut::HAL
2
+ # This class represents a HAL Link object.
3
+ #
4
+ # spec spec spec.
5
+ class Link
6
+ extend Forwardable
7
+
8
+ attr_reader :href
9
+
10
+ def_delegators :@options, :templated, :templated?, :type,
11
+ :name, :profile, :title, :hreflang
12
+ # Returns an instance of a HAL Link object
13
+ #
14
+ # @param [String] href URI or URI Template
15
+ # @param [Boolean] templated true if URI Template or false otherwise
16
+ # @param [Hash] opts Options: type, name, profile, title, hreflang
17
+ #
18
+ # @return [Halibut::HAL::Link] HAL Link object
19
+ def initialize(href, opts={})
20
+ @href = href
21
+ @options = Options.new opts
22
+ end
23
+
24
+ # Returns the Link as a Hash
25
+ #
26
+ # @return [Hash] hash from Link Object
27
+ def to_hash
28
+ { 'href' => href }.merge @options
29
+ end
30
+
31
+ def ==(other)
32
+ @href == other.href && @options == other.options
33
+ end
34
+
35
+ protected
36
+ attr_reader :options
37
+
38
+ private
39
+ class Options
40
+ attr_reader :templated, :type, :name,
41
+ :profile, :title, :hreflang
42
+
43
+ def initialize opts
44
+ @templated = opts[:templated] || opts['templated']
45
+ @type = opts[:type] || opts['type']
46
+ @name = opts[:name] || opts['name']
47
+ @profile = opts[:profile] || opts['profile']
48
+ @title = opts[:title] || opts['title']
49
+ @hreflang = opts[:hreflang] || opts['hreflang']
50
+ end
51
+
52
+ # Tells us if the href of the associated link is templated.
53
+ #
54
+ # The reason for not returning @templated directly is that all of the
55
+ # options are optional, thus nil could be returned instead of a boolean.
56
+ #
57
+ # @return [true, false] whether the href is a templated uri or not.
58
+ def templated?
59
+ @templated || false
60
+ end
61
+
62
+ def to_hash
63
+ instance_variables.each_with_object({}) do |name, output|
64
+ next if (ivar = instance_variable_get(name)).nil?
65
+
66
+ output[name[1..-1]] = ivar
67
+ end
68
+ end
69
+
70
+ def ==(other)
71
+ templated == other.templated &&
72
+ type == other.type &&
73
+ name == other.name &&
74
+ profile == other.profile &&
75
+ title == other.title &&
76
+ hreflang == other.hreflang
77
+ end
78
+ end
79
+ end
80
+
81
+ end
@@ -0,0 +1,74 @@
1
+ module Halibut::HAL
2
+
3
+ # This class represents a HAL Resource object.
4
+ #
5
+ # spec spec spec
6
+ class Resource
7
+ attr_reader :properties, :links, :embedded
8
+
9
+ # TDK
10
+ #
11
+ # @param [String] href Link that will be added to the self relation.
12
+ def initialize(href=nil)
13
+ @links = Halibut::RelationMap.new
14
+ @embedded = Halibut::RelationMap.new
15
+ @properties = {}
16
+
17
+ add_link('self', href) if href
18
+ end
19
+
20
+ # TDK
21
+ #
22
+ # @param [Object] property the key
23
+ # @param [Object] value the value
24
+ def set_property(property, value)
25
+ @properties[property] = value
26
+ end
27
+
28
+ # Returns the value of a property in the resource
29
+ #
30
+ # @param [String] property property
31
+ def get_property property
32
+ @properties[property]
33
+ end
34
+
35
+ # Adds link to relation
36
+ #
37
+ # @param [String] relation relation
38
+ # @param [String] href href
39
+ # @param [true, false] templated templated
40
+ # @param [Hash] opts options: name, type, hreflang
41
+ def add_link(relation, href, opts={})
42
+ @links.add relation, Link.new(href, opts)
43
+ end
44
+
45
+ # Embeds resource in relation
46
+ #
47
+ # @param [String] relation relation
48
+ # @param [Resource] resource resource to embed
49
+ def embed_resource(relation, resource)
50
+ @embedded.add relation, resource
51
+ end
52
+
53
+ # Hash representation of the resource.
54
+ # Will ommit links and embedded keys if they're empty
55
+ #
56
+ # @return [Hash] hash representation of the resource
57
+ def to_hash
58
+ {}.merge(@properties).tap do |h|
59
+ h['_links'] = {}.merge @links unless @links.empty?
60
+ h['_embedded'] = {}.merge @embedded unless @embedded.empty?
61
+ end
62
+ end
63
+
64
+ # Compares two resources.
65
+ #
66
+ # @param [Halibut::HAL::Resource] other Resource to compare to
67
+ # @return [true, false] Result of the comparison
68
+ def ==(other)
69
+ @properties == other.properties &&
70
+ @links == other.links &&
71
+ @embedded == other.embedded
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,29 @@
1
+ module Halibut
2
+
3
+ # Domain object that represents a Relation
4
+ #
5
+ # spec spec spec.
6
+ class LinkRelation
7
+ attr_accessor :name, :curie
8
+
9
+ def initialize(name)
10
+ splits = name.to_s.split(":")
11
+
12
+ splits.size < 2 ? @name = splits.first : (@curie, @name = splits)
13
+ end
14
+
15
+ def eql?(other)
16
+ hash == other.hash
17
+ end
18
+
19
+ def hash
20
+ instance_variables.hash
21
+ end
22
+
23
+ def to_s
24
+ @curie and "#{@curie}:#{@name}" or @name
25
+ end
26
+
27
+ end
28
+
29
+ end
@@ -1,39 +1,40 @@
1
1
  module Halibut
2
2
 
3
+ # This is an abstract map with behaviour specific to HAL.
4
+ #
5
+ # spec spec spec
3
6
  class RelationMap
4
-
7
+ extend Forwardable
8
+
9
+ def_delegators :@relations, :[], :empty?, :==
10
+
5
11
  def initialize
6
12
  @relations = {}
7
13
  end
8
-
14
+
15
+ # Adds an object to a relation.
16
+ #
17
+ # @param [String] relation relation that the object belongs to
18
+ # @param [Object] item the object to add to the relation
9
19
  def add(relation, item)
10
- @relations[relation] = [] unless @relations.has_key? relation
11
-
12
- @relations[relation] << item
13
- end
14
-
15
- def [](relation)
16
- @relations[relation]
17
- end
18
-
19
- def empty?
20
- @relations.empty?
20
+ @relations[relation] = @relations[relation].to_a << item
21
21
  end
22
-
22
+
23
+ # Returns a hash corresponding to the object.
24
+ #
25
+ # RelationMap doens't just return @relations because it needs to convert
26
+ # correctly when a relation only has a single item.
27
+ #
28
+ # @return [Hash] relation map in hash format
23
29
  def to_hash
24
- a = @relations.each_with_object({}) do |pair, obj|
30
+ @relations.each_with_object({}) do |pair, obj|
25
31
  key, *value = pair.flatten
26
-
32
+
33
+ key = key.to_s
34
+
27
35
  obj[key] = value.map &:to_hash
28
36
  obj[key].length == 1 and obj[key] = obj[key].first
29
37
  end
30
-
31
- end
32
-
33
- def ==(other)
34
- @relations == other.instance_variable_get(:@relations)
35
38
  end
36
-
37
39
  end
38
-
39
40
  end
@@ -1,4 +1,3 @@
1
1
  module Halibut
2
- # Halibut.VERSION denotes the version of this library.
3
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
4
3
  end
@@ -0,0 +1,72 @@
1
+ require_relative '../spec_helper'
2
+
3
+ read_files = ->() {
4
+ Dir.tap {|it| it.chdir('spec/test-resources/src/main/resources') } \
5
+ .glob('*.json') \
6
+ .map {|f| File.read f }
7
+ }
8
+
9
+ describe Halibut::Adapter::JSON do
10
+
11
+ it "serializes to JSON" do
12
+ resource = Halibut::HAL::Resource.new("http://example.com")
13
+ subject = Halibut::Adapter::JSON.dump resource
14
+ json = load_json "simple"
15
+
16
+ MultiJson.load(subject).must_equal MultiJson.load(json)
17
+ end
18
+
19
+ it "deserializes from JSON" do
20
+ subject = Halibut::Adapter::JSON.load(load_json "serialize")
21
+
22
+ order = Halibut::HAL::Resource.new "/orders/123"
23
+ order.set_property "total", 30.00
24
+ order.set_property "currency", "USD"
25
+ order.set_property "status", "shipped"
26
+
27
+ resource = Halibut::HAL::Resource.new "/orders"
28
+ resource.add_link "find", "/orders{?id}", templated: true
29
+ resource.add_link "next", "/orders/1", "name" => 'hotdog'
30
+ resource.add_link "next", "/orders/9"
31
+ resource.set_property "currentlyProcessing", 14
32
+ resource.set_property "shippedToday", 20
33
+ resource.embed_resource "orders", order
34
+
35
+ subject.must_equal resource
36
+ end
37
+
38
+ it "provides to_json helper" do
39
+ json = Halibut::Adapter::JSON.load(load_json "serialize")
40
+ json = Halibut::Adapter::JSON.dump(json)
41
+
42
+ order = Halibut::HAL::Resource.new "/orders/123"
43
+ order.set_property "total", 30.00
44
+ order.set_property "currency", "USD"
45
+ order.set_property "status", "shipped"
46
+
47
+ resource = Halibut::HAL::Resource.new "/orders"
48
+ resource.add_link "find", "/orders{?id}", templated: true
49
+ resource.add_link "next", "/orders/1", "name" => 'hotdog'
50
+ resource.add_link "next", "/orders/9"
51
+ resource.set_property "currentlyProcessing", 14
52
+ resource.set_property "shippedToday", 20
53
+ resource.embed_resource "orders", order
54
+
55
+ resource.to_json.wont_equal json
56
+ resource.extend Halibut::Adapter::JSON
57
+ resource.to_json.must_equal json
58
+ end
59
+
60
+ it "tests against test-resources" do
61
+ files = read_files[]
62
+
63
+ refilled = files.map {|f| MultiJson.load f }
64
+ resources = files.map {|f| Halibut::Adapter::JSON.load f }.map &:to_hash
65
+
66
+ zipped = refilled.zip resources
67
+ zipped.each do |json, hal|
68
+ json.must_equal hal
69
+ end
70
+ end
71
+
72
+ end
@@ -0,0 +1,50 @@
1
+ require_relative '../spec_helper'
2
+
3
+
4
+ read_files = ->() {
5
+ Dir.tap {|it| it.chdir('spec/test-resources/src/main/resources') } \
6
+ .glob('*.xml') \
7
+ .map {|f| File.read f }
8
+ }
9
+
10
+
11
+ describe Halibut::Adapter::XML do
12
+
13
+ it "serializes to XML" do
14
+ skip "To Be Implemented"
15
+ end
16
+
17
+ it "deserializes from XML" do
18
+ # skip "Test is failing god knows why."
19
+ builder = Halibut::Builder.new 'https://example.com/api/customer/123456' do
20
+ property 'age', "33"
21
+ property 'expired', "false"
22
+ property 'id', "123456"
23
+ property 'name', 'Example Resource'
24
+ property 'nullprop', ""
25
+ property 'optional', "true"
26
+
27
+ relation 'curie' do
28
+ link 'https://example.com/apidocs/accounts', name: 'ns'
29
+ link 'https://example.com/apidocs/roles', name: 'role'
30
+ end
31
+ link 'ns:parent', 'https://example.com/api/customer/1234', name: 'bob',
32
+ title: 'The Parent',
33
+ hreflang: 'en'
34
+ link 'ns:users', 'https://example.com/api/customer/123456?users'
35
+ end.resource
36
+
37
+ xml = load_resource('exampleWithNullProperty.xml')
38
+
39
+ deserialized = Halibut::Adapter::XML.load(xml)
40
+ deserialized.must_equal builder, diff(deserialized.to_hash, builder.to_hash)
41
+ end
42
+
43
+ it "provides to_xml helper" do
44
+ skip "To Be Implemented"
45
+
46
+ xml = Halibut::Adapter::XML.load(load_resource 'exampleWithNullProperty.xml')
47
+ xml = Halibut::Adapter::XML.dump(xml)
48
+ end
49
+
50
+ end
@@ -0,0 +1,185 @@
1
+ require_relative 'spec_helper'
2
+
3
+
4
+ require 'hash'
5
+
6
+ describe Halibut::Builder do
7
+ describe "Empty resource" do
8
+ it "builds empty resource with no self link" do
9
+ builder = Halibut::Builder.new
10
+ resource = Halibut::HAL::Resource.new
11
+
12
+ builder.resource.must_equal resource
13
+ end
14
+
15
+ it "builds empty resource with self link" do
16
+ builder = Halibut::Builder.new 'default'
17
+ resource = Halibut::HAL::Resource.new 'default'
18
+
19
+ builder.resource.must_equal resource
20
+ end
21
+ end
22
+
23
+ describe "Properties" do
24
+ it "builds resource with a single property" do
25
+ builder = Halibut::Builder.new do
26
+ property 'foo', 'bar'
27
+ end
28
+
29
+ resource = Halibut::HAL::Resource.new
30
+ resource.set_property 'foo', 'bar'
31
+
32
+ builder.resource.properties['foo'].must_equal 'bar'
33
+ builder.resource.must_equal resource, diff(builder.resource.to_hash, resource.to_hash)
34
+ end
35
+
36
+ it "builds resource with several properties" do
37
+ builder = Halibut::Builder.new do
38
+ property 'foo', 'bar'
39
+ property 'baz', 'quux'
40
+ property 'medals', { gold: 1, silver: 5, bronze: 10 }
41
+ end
42
+
43
+ resource = Halibut::HAL::Resource.new
44
+ resource.set_property 'foo', 'bar'
45
+ resource.set_property 'baz', 'quux'
46
+ resource.set_property 'medals', { gold: 1, silver: 5, bronze: 10 }
47
+
48
+ builder.resource.properties['foo'].must_equal 'bar'
49
+ builder.resource.properties['baz'].must_equal 'quux'
50
+ builder.resource.properties['medals'].must_equal({ gold: 1, silver: 5, bronze: 10 })
51
+
52
+ builder.resource.must_equal resource, diff(builder.resource.to_hash, resource.to_hash)
53
+ end
54
+
55
+ end
56
+
57
+ describe "Links" do
58
+ it "builds resource with a single links per relation" do
59
+ builder = Halibut::Builder.new do
60
+ link 'cs:broms', '/broms/1'
61
+ link 'cs:search', '/search{?broms,noms}', templated: true
62
+ end
63
+
64
+ resource = Halibut::HAL::Resource.new
65
+ resource.add_link 'cs:broms', '/broms/1'
66
+ resource.add_link 'cs:search', '/search{?broms,noms}', templated: true
67
+
68
+ resource.must_equal builder.resource, diff(builder.resource.to_hash, resource.to_hash)
69
+ end
70
+
71
+ it "builds resource with multiple links per relation" do
72
+ builder = Halibut::Builder.new do
73
+ link 'cs:broms', '/broms/1'
74
+ link 'cs:broms', '/broms/2'
75
+ link 'cs:search', '/search{?broms,noms}', templated: true
76
+ end
77
+
78
+ resource = Halibut::HAL::Resource.new
79
+ resource.add_link 'cs:broms', '/broms/1'
80
+ resource.add_link 'cs:broms', '/broms/2'
81
+ resource.add_link 'cs:search', '/search{?broms,noms}', templated: true
82
+
83
+ resource.must_equal builder.resource, diff(builder.resource.to_hash, resource.to_hash)
84
+ end
85
+ end
86
+
87
+ describe "Embedded resources" do
88
+ it "builds resource with a single resource per relation" do
89
+ builder = Halibut::Builder.new do
90
+ resource 'games', '/game/1' do
91
+ property :name, 'Crash Bandicoot'
92
+ property :console, 'PlayStation'
93
+ end
94
+ end
95
+
96
+ game = Halibut::HAL::Resource.new '/game/1'
97
+ game.set_property(:name, 'Crash Bandicoot')
98
+ game.set_property(:console, 'PlayStation')
99
+
100
+ resource = Halibut::HAL::Resource.new
101
+ resource.embed_resource('games', game)
102
+
103
+ builder.resource.must_equal resource, diff(builder.resource.to_hash, resource.to_hash)
104
+ end
105
+
106
+ it "builds resource with multiple resourcer per relation" do
107
+ builder = Halibut::Builder.new do
108
+ resource 'games', '/game/1' do
109
+ property :name, 'Crash Bandicoot'
110
+ property :console, 'PlayStation'
111
+ end
112
+ resource 'games', '/game/2' do
113
+ property :name, 'Super Mario Land'
114
+ property :console, 'Game Boy'
115
+ end
116
+ end
117
+
118
+ game1 = Halibut::HAL::Resource.new '/game/1'
119
+ game1.set_property(:name, 'Crash Bandicoot')
120
+ game1.set_property(:console, 'PlayStation')
121
+
122
+ game2 = Halibut::HAL::Resource.new '/game/2'
123
+ game2.set_property(:name, 'Super Mario Land')
124
+ game2.set_property(:console, 'Game Boy')
125
+
126
+ resource = Halibut::HAL::Resource.new
127
+ resource.embed_resource('games', game1)
128
+ resource.embed_resource('games', game2)
129
+
130
+ builder.resource.must_equal resource, diff(builder.resource.to_hash, resource.to_hash)
131
+ end
132
+ end
133
+
134
+ describe "Relation helper" do
135
+ it "builds resource using relation DSL" do
136
+ builder = Halibut::Builder.new do
137
+ relation 'games' do
138
+ link '/games/1'
139
+ link '/games/2'
140
+ link '/games/3'
141
+ end
142
+ link 'next', '/games/next'
143
+ relation 'users' do
144
+ resource '/users/1' do
145
+ property :name, "foo"
146
+ property :nick, "bar"
147
+ end
148
+ end
149
+ end
150
+
151
+ user = Halibut::HAL::Resource.new '/users/1'
152
+ user.set_property :name, "foo"
153
+ user.set_property :nick, "bar"
154
+
155
+ resource = Halibut::HAL::Resource.new
156
+ resource.add_link 'games', '/games/1'
157
+ resource.add_link 'games', '/games/2'
158
+ resource.add_link 'games', '/games/3'
159
+ resource.add_link 'next', '/games/next'
160
+ resource.embed_resource 'users', user
161
+
162
+ builder.resource.must_equal resource, diff(builder.resource.to_hash, resource.to_hash)
163
+ end
164
+ end
165
+
166
+ describe "Namespace helper" do
167
+ let(:name) { 'cs' }
168
+ let(:href) { 'http://cs-api.herokuapp.com/rels/{rel}' }
169
+
170
+ it "builds resource using curie DSL" do
171
+ builder = Halibut::Builder.new do
172
+ namespace 'cs', 'http://cs-api.herokuapp.com/rels/{rel}'
173
+ end
174
+
175
+ builder = Halibut::Builder.new
176
+ builder.namespace 'cs', 'http://cs-api.herokuapp.com/rels/{rel}'
177
+
178
+ curie = builder.resource.links['curie'].first
179
+
180
+ curie.name.must_equal name
181
+ curie.href.must_equal href
182
+ end
183
+ end
184
+
185
+ end