halibut 0.1.0 → 0.2.0

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.
@@ -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