halchemy 1.0.3 → 1.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.
- checksums.yaml +4 -4
- data/__tests__/common.rb +3 -3
- data/__tests__/configurable/base_url.rb +2 -2
- data/__tests__/make_http_requests/follow_link_relations.rb +8 -8
- data/__tests__/make_http_requests/http_response_details.rb +26 -2
- data/__tests__/make_http_requests/optimistic_concurrency.rb +2 -2
- data/__tests__/make_http_requests/with_headers.rb +4 -4
- data/__tests__/make_http_requests/with_parameters.rb +1 -1
- data/__tests__/use_resources/iterate_collections.rb +8 -6
- data/__tests__/use_resources/use_embedded.rb +98 -0
- data/lib/halchemy/api.rb +7 -4
- data/lib/halchemy/requester.rb +2 -2
- data/lib/halchemy/resource.rb +71 -1
- data/lib/halchemy/version.rb +1 -1
- data/sig/halchemy/api.rbs +1 -1
- data/sig/halchemy/hal_resource.rbs +11 -0
- data/sig/halchemy/multiplicity.rbs +6 -0
- metadata +4 -3
- data/~halchemy +0 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1dd3f79bb5a2f73f4b79dd138835a211febcfb627d1a20608e1a2ed81d6a4f30
|
4
|
+
data.tar.gz: e228d136ae6ec38a4edabf90f37ea70bf6382f11ef47d9ee51371329bccc9339
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 673f337aef6da5b24374367ff6aded12ef2d31f4736814f2b112e6122f49480f21737f303fba64ef4bdc1d6b8003d41465daec15adb6459e1ab98efd06382498
|
7
|
+
data.tar.gz: 2639ee8b448e0fc4b9709a7fdce799bbd2a2e6db1412005961a27d8ecc84d73e36df68f293c681359f87fff5ed056cd4306eebbf6e5f54606865c1a74c4dc3ed
|
data/__tests__/common.rb
CHANGED
@@ -20,7 +20,7 @@ After do
|
|
20
20
|
end
|
21
21
|
|
22
22
|
|
23
|
-
|
23
|
+
HOME_JSON = {
|
24
24
|
_links: {
|
25
25
|
self: { href: "/" },
|
26
26
|
resource1: { href: "/path/to/resource1" },
|
@@ -41,7 +41,7 @@ RESOURCE_JSON = {
|
|
41
41
|
Given(/^a HAL resource$/) do
|
42
42
|
stub_for_hal_resource_scenarios
|
43
43
|
@api = Halchemy::Api.new BASE_URL
|
44
|
-
@
|
44
|
+
@home_resource = @api.home.get
|
45
45
|
end
|
46
46
|
|
47
47
|
# @return [void]
|
@@ -51,7 +51,7 @@ def stub_for_hal_resource_scenarios
|
|
51
51
|
"Etag" => "from header"
|
52
52
|
}
|
53
53
|
|
54
|
-
stub_request(:get, BASE_URL).to_return(body:
|
54
|
+
stub_request(:get, BASE_URL).to_return(body: HOME_JSON, headers: headers)
|
55
55
|
stub_request(:get, %r{\A#{BASE_URL}/path(/.*)?\z}).to_return(status: 200, body: RESOURCE_JSON, headers: headers)
|
56
56
|
end
|
57
57
|
|
@@ -14,9 +14,9 @@ And(/^is later changed to a (.*)$/) do |new_base_url|
|
|
14
14
|
@api.base_url = new_base_url
|
15
15
|
end
|
16
16
|
|
17
|
-
When(/^I GET the
|
17
|
+
When(/^I GET the home resource$/) do
|
18
18
|
@methods_used = [:get]
|
19
|
-
@api.
|
19
|
+
@api.home.get
|
20
20
|
end
|
21
21
|
|
22
22
|
When(/^a request is given a (.*)$/) do |relative_url|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
When(/^I make a request using its link relations$/) do
|
4
|
-
@requests = make_requests(ALL_METHODS, @api.follow(@
|
4
|
+
@requests = make_requests(ALL_METHODS, @api.follow(@home_resource).to("resource1"))
|
5
5
|
end
|
6
6
|
|
7
7
|
Then(/^the href of the link is used for the request$/) do
|
@@ -14,7 +14,7 @@ When(/^I make a request to a link relation the resource does not have$/) do
|
|
14
14
|
@error = {}
|
15
15
|
ALL_METHODS.each do |method|
|
16
16
|
@error[method] = nil
|
17
|
-
@api.follow(@
|
17
|
+
@api.follow(@home_resource).to("non-existent").public_send(method)
|
18
18
|
rescue KeyError => e
|
19
19
|
@error[method] = e
|
20
20
|
end
|
@@ -28,7 +28,7 @@ Then(/^the request fails, informing me of the issue$/) do
|
|
28
28
|
end
|
29
29
|
|
30
30
|
When(/^I ask for the links it has$/) do
|
31
|
-
@links = @
|
31
|
+
@links = @home_resource.links
|
32
32
|
end
|
33
33
|
|
34
34
|
Then(/^I get a list of its relations$/) do
|
@@ -38,8 +38,8 @@ Then(/^I get a list of its relations$/) do
|
|
38
38
|
end
|
39
39
|
|
40
40
|
When(/^I ask if it has a link relation$/) do
|
41
|
-
@true_if_exists = @
|
42
|
-
@false_if_not_exists = @
|
41
|
+
@true_if_exists = @home_resource.rel?("self")
|
42
|
+
@false_if_not_exists = @home_resource.rel?("not-a-rel")
|
43
43
|
end
|
44
44
|
|
45
45
|
Then(/^it tells me whether it does or not$/) do
|
@@ -58,7 +58,7 @@ When(/^I use an object my language uses to represent JSON as the payload of a re
|
|
58
58
|
}
|
59
59
|
|
60
60
|
@payload = data.to_json
|
61
|
-
@requests = make_requests(PAYLOAD_METHODS, @api.follow(@
|
61
|
+
@requests = make_requests(PAYLOAD_METHODS, @api.follow(@home_resource).to("resource1"), @payload)
|
62
62
|
end
|
63
63
|
|
64
64
|
Then(/^the request body is properly formatted JSON$/) do
|
@@ -70,12 +70,12 @@ end
|
|
70
70
|
|
71
71
|
When(/^I use data type that is not an object but is valid as JSON, e\.g\. (.*)$/) do |data|
|
72
72
|
@payload = data
|
73
|
-
@requests = make_requests(PAYLOAD_METHODS, @api.follow(@
|
73
|
+
@requests = make_requests(PAYLOAD_METHODS, @api.follow(@home_resource).to("resource1"), @payload)
|
74
74
|
end
|
75
75
|
|
76
76
|
When(/^the payload of a request is has (.*) of a different (.*)$/) do |data, content_type|
|
77
77
|
@payload = data
|
78
|
-
@requests = make_requests(PAYLOAD_METHODS, @api.follow(@
|
78
|
+
@requests = make_requests(PAYLOAD_METHODS, @api.follow(@home_resource).to("resource1"), data, content_type)
|
79
79
|
end
|
80
80
|
|
81
81
|
Then(/^the request is made with the correct (.*) and (.*) header$/) do |data, content_type|
|
@@ -3,7 +3,7 @@
|
|
3
3
|
When(/^I make a request$/) do
|
4
4
|
@resource = {}
|
5
5
|
ALL_METHODS.each do |method|
|
6
|
-
@resource[method] = @api.follow(@
|
6
|
+
@resource[method] = @api.follow(@home_resource).to("resource1").public_send(method)
|
7
7
|
end
|
8
8
|
end
|
9
9
|
|
@@ -41,7 +41,7 @@ When(/^the request I made fails: (.*)$/) do |failure|
|
|
41
41
|
|
42
42
|
@resource = {}
|
43
43
|
ALL_METHODS.each do |method|
|
44
|
-
@resource[method] = @api.follow(@
|
44
|
+
@resource[method] = @api.follow(@home_resource).to("resource1").public_send(method)
|
45
45
|
end
|
46
46
|
end
|
47
47
|
|
@@ -58,3 +58,27 @@ Then(/^I can access the error details$/) do
|
|
58
58
|
end
|
59
59
|
end
|
60
60
|
end
|
61
|
+
|
62
|
+
When(/^I make a request that returns JSON in the body$/) do
|
63
|
+
response_body = {
|
64
|
+
"_status" => "ERR",
|
65
|
+
"_error" => {
|
66
|
+
"code" => 404,
|
67
|
+
"message" => "The requested URL was not found on the server. /
|
68
|
+
If you entered the URL manually please check your spelling and try again."
|
69
|
+
}
|
70
|
+
}.to_json
|
71
|
+
stub_request(:any, /.*/).to_return(body: response_body, status: 404)
|
72
|
+
|
73
|
+
@resource = {}
|
74
|
+
ALL_METHODS.each do |method|
|
75
|
+
@resource[method] = @api.follow(@home_resource).to("resource1").public_send(method)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
Then(/^I can use the body in the way my language supports JSON$/) do
|
80
|
+
ALL_METHODS.each do |method|
|
81
|
+
response = @resource[method]._halchemy.response
|
82
|
+
expect( response.body.respond_to?("to_json") ).to be true
|
83
|
+
end
|
84
|
+
end
|
@@ -3,8 +3,8 @@
|
|
3
3
|
Given(/^a modifiable HAL resource$/) do
|
4
4
|
stub_for_hal_resource_scenarios
|
5
5
|
@api = Halchemy::Api.new BASE_URL
|
6
|
-
|
7
|
-
@resource = @api.follow(
|
6
|
+
home_resource = @api.home.get
|
7
|
+
@resource = @api.follow(home_resource).to("resource1").get
|
8
8
|
end
|
9
9
|
|
10
10
|
When(/^I request a change to the resource$/) do
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
When(/^I specify additional headers for a request$/) do
|
4
|
-
requester = @api.follow(@
|
4
|
+
requester = @api.follow(@home_resource).to("resource1").with_headers({ "X-CustomHeader" => "custom value" })
|
5
5
|
@requests = make_requests(ALL_METHODS, requester)
|
6
6
|
end
|
7
7
|
|
@@ -16,12 +16,12 @@ end
|
|
16
16
|
Given(/^I have made a request with additional headers$/) do
|
17
17
|
stub_for_hal_resource_scenarios
|
18
18
|
@api = Halchemy::Api.new BASE_URL
|
19
|
-
@
|
20
|
-
@api.follow(@
|
19
|
+
@home_resource = @api.home.get
|
20
|
+
@api.follow(@home_resource).to("resource1").with_headers({ "X-CustomHeader" => "custom value" }).get
|
21
21
|
end
|
22
22
|
|
23
23
|
When(/^I make a new request without headers$/) do
|
24
|
-
@requests = make_requests(ALL_METHODS, @api.follow(@
|
24
|
+
@requests = make_requests(ALL_METHODS, @api.follow(@home_resource).to("resource1"))
|
25
25
|
end
|
26
26
|
|
27
27
|
Then(/^the previous request's headers are not included$/) do
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
When(/^I supply (.*)$/) do |parameters|
|
4
|
-
requester = @api.follow(@
|
4
|
+
requester = @api.follow(@home_resource).to("resource1").with_parameters(JSON.parse(parameters))
|
5
5
|
@requests = make_requests(ALL_METHODS, requester)
|
6
6
|
end
|
7
7
|
|
@@ -1,7 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
Given(/^a HAL resource that has a field which is a collection of objects in HAL format$/) do
|
4
|
-
@resource = Halchemy::HalResource.
|
4
|
+
@resource = Halchemy::HalResource.from_hash(JSON.parse({
|
5
|
+
_links: {
|
6
|
+
self: { href: "/" }
|
7
|
+
},
|
5
8
|
_items: [
|
6
9
|
{
|
7
10
|
field: "alpha",
|
@@ -15,8 +18,7 @@ Given(/^a HAL resource that has a field which is a collection of objects in HAL
|
|
15
18
|
field: "delta",
|
16
19
|
_links: { self: { href: "/resource/delta" } }
|
17
20
|
}
|
18
|
-
]
|
19
|
-
_links: { self: { href: "/resource" } }
|
21
|
+
]
|
20
22
|
}.to_json))
|
21
23
|
end
|
22
24
|
|
@@ -32,7 +34,7 @@ Then(/^each item is a HAL resource$/) do
|
|
32
34
|
end
|
33
35
|
|
34
36
|
When(/^I try to iterate as a collection a field in the resource that does not exist$/) do
|
35
|
-
@
|
37
|
+
@home_resource.collection("_items").reduce(false) { |all_hal, item| all_hal || item.is_a?(Halchemy::HalResource) }
|
36
38
|
rescue KeyError => e
|
37
39
|
@error = e
|
38
40
|
end
|
@@ -43,7 +45,7 @@ Then(/^it throws an exception telling me that the field does not exist$/) do
|
|
43
45
|
end
|
44
46
|
|
45
47
|
Given(/^a HAL resource with a non-collection field$/) do
|
46
|
-
@resource = Halchemy::HalResource.
|
48
|
+
@resource = Halchemy::HalResource.from_hash(JSON.parse({
|
47
49
|
_id: "3a834-34f9f03-39b843",
|
48
50
|
_links: { self: { href: "/resource" } }
|
49
51
|
}.to_json))
|
@@ -61,7 +63,7 @@ Then(/^it throws an exception telling me that the field is not a collection$/) d
|
|
61
63
|
end
|
62
64
|
|
63
65
|
Given(/^a HAL resource that has a field which is a collection, but not of HAL formatted objects$/) do
|
64
|
-
@resource = Halchemy::HalResource.
|
66
|
+
@resource = Halchemy::HalResource.from_hash(JSON.parse({
|
65
67
|
_items: [
|
66
68
|
{
|
67
69
|
make: "Ford Mustang"
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Given(/^a HalResource with an embedded property containing both a single resource and a resource collection$/) do
|
4
|
+
@resource = Halchemy::HalResource.from_hash( JSON.parse({
|
5
|
+
_id: "39402",
|
6
|
+
memberName: "Pat Doe",
|
7
|
+
address: "123 Main St.",
|
8
|
+
_links: {
|
9
|
+
self: { href: "/members/39402" },
|
10
|
+
library: { href: "/libraries/2389" },
|
11
|
+
books: { href: "/members/39402/books" }
|
12
|
+
},
|
13
|
+
_embedded: {
|
14
|
+
library: {
|
15
|
+
name: "Essex County Library",
|
16
|
+
_links: {
|
17
|
+
self: { href: "/libraries/2389" }
|
18
|
+
}
|
19
|
+
},
|
20
|
+
book: [
|
21
|
+
{
|
22
|
+
title: "Stephen's Kingdom",
|
23
|
+
edition: "1st",
|
24
|
+
_links: {
|
25
|
+
self: { href: "/books/3845" }
|
26
|
+
}
|
27
|
+
},
|
28
|
+
{
|
29
|
+
title: "Nature Valley",
|
30
|
+
edition: "paperback",
|
31
|
+
_links: {
|
32
|
+
self: { href: "/books/8842" }
|
33
|
+
}
|
34
|
+
}
|
35
|
+
]
|
36
|
+
}
|
37
|
+
}.to_json))
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
When(/^I retrieve the embedded resource with the (.*) rel$/) do |rel|
|
42
|
+
@fetched = @resource.embedded(rel)
|
43
|
+
rescue KeyError => e
|
44
|
+
@exception = e
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
Then(/^I should receive a single HalResource instance$/) do
|
49
|
+
expect(@fetched).to be_a Halchemy::HalResource
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
And(/^the resource should contain the expected data for the library$/) do
|
54
|
+
expect(@fetched["name"]).to eq("Essex County Library")
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
Then(/^I should receive a collection of HalResource instances$/) do
|
59
|
+
expect(@fetched).to be_a Array
|
60
|
+
expect(@fetched).to all(be_a(Halchemy::HalResource))
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
And(/^the resource should contain the expected book data$/) do
|
65
|
+
titles = ["Stephen's Kingdom", "Nature Valley"]
|
66
|
+
titles.each_with_index do |title, index|
|
67
|
+
expect(title).to eq @fetched[index]["title"]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
When(/^I retrieve the embedded resource with rel (.*) and expect (.*)$/) do |rel, multiplicity|
|
73
|
+
@fetched = @resource.embedded(
|
74
|
+
rel,
|
75
|
+
expect: multiplicity == "many" ? Halchemy::Multiplicity::MANY : Halchemy::Multiplicity::ONE
|
76
|
+
)
|
77
|
+
rescue TypeError => e
|
78
|
+
@exception = e
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
When(/^I call embedded_one with rel library$/) do
|
83
|
+
@fetched = @resource.embedded_one("library")
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
When(/^I call embedded_many with rel book$/) do
|
88
|
+
@fetched = @resource.embedded_many("book")
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
Then(/^an error should be raised indicating a type mismatch$/) do
|
93
|
+
expect(@exception).to be_a(TypeError)
|
94
|
+
end
|
95
|
+
|
96
|
+
Then(/^an error should be raised indicating the resource is not found$/) do
|
97
|
+
expect(@exception).to be_a(KeyError)
|
98
|
+
end
|
data/lib/halchemy/api.rb
CHANGED
@@ -28,10 +28,13 @@ module Halchemy
|
|
28
28
|
@headers.merge!(headers)
|
29
29
|
end
|
30
30
|
|
31
|
-
def
|
31
|
+
def home = using_endpoint("/", is_home: true)
|
32
32
|
|
33
|
-
|
34
|
-
|
33
|
+
# root is deprecated and will be removed in an upcoming release, please use home instead
|
34
|
+
def root = using_endpoint("/", is_home: true)
|
35
|
+
|
36
|
+
def using_endpoint(target, is_home: false)
|
37
|
+
if is_home
|
35
38
|
ReadOnlyRequester.new(self, target)
|
36
39
|
else
|
37
40
|
Requester.new(self, target)
|
@@ -112,7 +115,7 @@ module Halchemy
|
|
112
115
|
resource = if json.nil?
|
113
116
|
Resource.new
|
114
117
|
elsif HalResource.hal?(json)
|
115
|
-
HalResource.
|
118
|
+
HalResource.from_hash json
|
116
119
|
else
|
117
120
|
Resource.new.merge! json
|
118
121
|
end
|
data/lib/halchemy/requester.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
require "uri_template"
|
4
4
|
|
5
5
|
module Halchemy
|
6
|
-
# The results of a Follower#to is a Requester. In the case of a GET for the
|
6
|
+
# The results of a Follower#to is a Requester. In the case of a GET for the home resource, the Requester is Read Only
|
7
7
|
# Otherwise it is a full Requester. Both requester types share much in common. This is defined in BaseRequester
|
8
8
|
class BaseRequester
|
9
9
|
LIST_STYLE_HANDLERS = {
|
@@ -126,7 +126,7 @@ module Halchemy
|
|
126
126
|
end
|
127
127
|
end
|
128
128
|
|
129
|
-
# The result of GET on the
|
129
|
+
# The result of GET on the home URL is a ReadOnlyRequester, i.e. only
|
130
130
|
# GET, HEAD, and OPTIONS are permitted
|
131
131
|
class ReadOnlyRequester < BaseRequester
|
132
132
|
def get
|
data/lib/halchemy/resource.rb
CHANGED
@@ -1,6 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Halchemy
|
4
|
+
module Multiplicity
|
5
|
+
ONE = :one
|
6
|
+
MANY = :many
|
7
|
+
end
|
8
|
+
|
4
9
|
# The Resource class extends a Hash to include a metadata object containing details
|
5
10
|
# about the HTTP request and response. This lets the metadata stay "out of the way" allowing
|
6
11
|
# the client code to use the result of a request directly as a resource without losing access
|
@@ -36,6 +41,23 @@ module Halchemy
|
|
36
41
|
true
|
37
42
|
end
|
38
43
|
|
44
|
+
def initialize(*args)
|
45
|
+
super
|
46
|
+
raise ArgumentError, "Not a WELL-formed HAL resource" unless HalResource.hal?(self)
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.from_hash(json)
|
50
|
+
obj = allocate
|
51
|
+
obj.merge!(json) if json.is_a?(Hash)
|
52
|
+
raise ArgumentError, "Not a well-FORMED HAL resource" unless HalResource.hal?(obj)
|
53
|
+
|
54
|
+
obj
|
55
|
+
end
|
56
|
+
|
57
|
+
def to_s
|
58
|
+
"<HalResource href='#{dig("_links", "self", "href")}' >"
|
59
|
+
end
|
60
|
+
|
39
61
|
def rel?(rel_name)
|
40
62
|
self["_links"].key?(rel_name)
|
41
63
|
end
|
@@ -44,6 +66,14 @@ module Halchemy
|
|
44
66
|
self["_links"].keys ||= []
|
45
67
|
end
|
46
68
|
|
69
|
+
def embedded_rel?(rel_name)
|
70
|
+
self["_embedded"].key?(rel_name)
|
71
|
+
end
|
72
|
+
|
73
|
+
def embedded_rels
|
74
|
+
self["_embedded"]&.keys || []
|
75
|
+
end
|
76
|
+
|
47
77
|
def raise_for_syntax_error(field)
|
48
78
|
unless keys.include?(field)
|
49
79
|
raise KeyError, "Field '#{field}' does not exist, so cannot be iterated as a collection"
|
@@ -60,9 +90,49 @@ module Halchemy
|
|
60
90
|
raise TypeError, "The '#{field}' collection contains non-HAL formatted objects"
|
61
91
|
end
|
62
92
|
|
63
|
-
y.yield Halchemy::HalResource.
|
93
|
+
y.yield Halchemy::HalResource.from_hash(item)
|
64
94
|
end
|
65
95
|
end
|
66
96
|
end
|
97
|
+
|
98
|
+
# @param [string] rel_name
|
99
|
+
# @param [Symbol, nil] expect
|
100
|
+
# @return [Halchemy::HalResource, Array<Halchemy::HalResource>]
|
101
|
+
def embedded(rel_name, expect: nil)
|
102
|
+
val = dig("_embedded", rel_name)
|
103
|
+
raise KeyError, "No embedded resource found for rel '#{rel_name}'" if val.nil?
|
104
|
+
|
105
|
+
if expect == Multiplicity::MANY
|
106
|
+
raise TypeError, "Expected array for rel '#{rel_name}', got #{val.class}" unless val.is_a?(Array)
|
107
|
+
|
108
|
+
return val.map { |item| HalResource.from_hash(item) }
|
109
|
+
elsif expect == Multiplicity::ONE
|
110
|
+
raise TypeError, "Expected object for rel '#{rel_name}', got #{val.class}" unless val.is_a?(Hash)
|
111
|
+
|
112
|
+
return HalResource.from_hash(val)
|
113
|
+
end
|
114
|
+
|
115
|
+
if val.is_a?(Array)
|
116
|
+
val.map { |item| HalResource.from_hash(item) }
|
117
|
+
elsif val.is_a?(Hash)
|
118
|
+
HalResource.from_hash(val)
|
119
|
+
else
|
120
|
+
raise TypeError, "Invalid embedded value for rel '#{rel_name}': expected object or array, got #{val.class}"
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def embedded_many(rel_name)
|
125
|
+
result = embedded(rel_name, expect: Multiplicity::MANY)
|
126
|
+
raise TypeError, "Expected array for embedded '#{rel_name}', got #{result.class}" unless result.is_a?(Array)
|
127
|
+
|
128
|
+
result
|
129
|
+
end
|
130
|
+
|
131
|
+
def embedded_one(rel_name)
|
132
|
+
result = embedded(rel_name, expect: Multiplicity::ONE)
|
133
|
+
raise TypeError, "Expected object for rel '#{rel_name}', got #{result.class}" unless result.is_a?(Hash)
|
134
|
+
|
135
|
+
result
|
136
|
+
end
|
67
137
|
end
|
68
138
|
end
|
data/lib/halchemy/version.rb
CHANGED
data/sig/halchemy/api.rbs
CHANGED
@@ -1,9 +1,20 @@
|
|
1
1
|
module Halchemy
|
2
2
|
class HalResource
|
3
|
+
def self.from_hash: -> HalResource
|
4
|
+
|
3
5
|
def self.hal?: -> bool
|
4
6
|
|
5
7
|
def collection: -> Enumerator[HalResource]
|
6
8
|
|
9
|
+
def embedded: (Symbol | nil) -> (HalResource | Array[HalResource])
|
10
|
+
|
11
|
+
def embedded_many: -> Array[HalResource]
|
12
|
+
def embedded_one: -> HalResource
|
13
|
+
|
14
|
+
def embedded_rel?: -> bool
|
15
|
+
|
16
|
+
def embedded_rels: -> Array[String]
|
17
|
+
|
7
18
|
def links: -> Array[String]
|
8
19
|
|
9
20
|
def raise_for_syntax_error: -> void
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: halchemy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael Ottoson
|
8
8
|
bindir: exe
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-
|
10
|
+
date: 2025-03-31 00:00:00.000000000 Z
|
11
11
|
dependencies: []
|
12
12
|
description: Do you have an API that serves data following the HAL specification? The
|
13
13
|
**halchemy** library makes it easy for your client to make the most of that API.
|
@@ -39,6 +39,7 @@ files:
|
|
39
39
|
- __tests__/make_http_requests/with_parameters.rb
|
40
40
|
- __tests__/make_http_requests/with_templates.rb
|
41
41
|
- __tests__/use_resources/iterate_collections.rb
|
42
|
+
- __tests__/use_resources/use_embedded.rb
|
42
43
|
- bdd
|
43
44
|
- lib/halchemy.rb
|
44
45
|
- lib/halchemy/api.rb
|
@@ -64,13 +65,13 @@ files:
|
|
64
65
|
- sig/halchemy/http_model/request.rbs
|
65
66
|
- sig/halchemy/http_model/response.rbs
|
66
67
|
- sig/halchemy/metadata.rbs
|
68
|
+
- sig/halchemy/multiplicity.rbs
|
67
69
|
- sig/halchemy/read_only_requester.rbs
|
68
70
|
- sig/halchemy/requester.rbs
|
69
71
|
- sig/halchemy/resource.rbs
|
70
72
|
- sig/matchers.rbs
|
71
73
|
- sig/patterns.rbs
|
72
74
|
- test.sh
|
73
|
-
- "~halchemy"
|
74
75
|
homepage: https://github.com/pointw-dev/halchemy
|
75
76
|
licenses:
|
76
77
|
- MIT
|
data/~halchemy
DELETED