halchemy 1.0.2
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 +7 -0
- data/.idea/.gitignore +8 -0
- data/.idea/halchemy.iml +69 -0
- data/.idea/icon.png +0 -0
- data/.idea/inspectionProfiles/Project_Default.xml +36 -0
- data/.idea/modules.xml +8 -0
- data/.idea/vcs.xml +6 -0
- data/.rubocop.yml +8 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +43 -0
- data/Rakefile +12 -0
- data/__tests__/common.rb +97 -0
- data/__tests__/configurable/base_url.rb +46 -0
- data/__tests__/configurable/error_handling.rb +75 -0
- data/__tests__/configurable/headers.rb +92 -0
- data/__tests__/make_http_requests/follow_link_relations.rb +86 -0
- data/__tests__/make_http_requests/http_response_details.rb +60 -0
- data/__tests__/make_http_requests/optimistic_concurrency.rb +38 -0
- data/__tests__/make_http_requests/with_headers.rb +32 -0
- data/__tests__/make_http_requests/with_parameters.rb +32 -0
- data/__tests__/make_http_requests/with_templates.rb +45 -0
- data/__tests__/use_resources/iterate_collections.rb +89 -0
- data/bdd +1 -0
- data/lib/halchemy/api.rb +150 -0
- data/lib/halchemy/error_handling.rb +13 -0
- data/lib/halchemy/follower.rb +20 -0
- data/lib/halchemy/http_model.rb +41 -0
- data/lib/halchemy/metadata.rb +20 -0
- data/lib/halchemy/requester.rb +181 -0
- data/lib/halchemy/resource.rb +68 -0
- data/lib/halchemy/status_codes.rb +60 -0
- data/lib/halchemy/version.rb +5 -0
- data/lib/halchemy.rb +8 -0
- data/sig/__tests__/base_url.rbs +1 -0
- data/sig/__tests__/halchemy.rbs +4 -0
- data/sig/__tests__/headers.rbs +1 -0
- data/sig/__tests__/remove_headers.rbs +1 -0
- data/sig/halchemy/api.rbs +54 -0
- data/sig/halchemy/base_requester.rbs +35 -0
- data/sig/halchemy/error_handling.rbs +6 -0
- data/sig/halchemy/follower.rbs +9 -0
- data/sig/halchemy/hal_resource.rbs +13 -0
- data/sig/halchemy/http_model/request.rbs +14 -0
- data/sig/halchemy/http_model/response.rbs +15 -0
- data/sig/halchemy/metadata.rbs +9 -0
- data/sig/halchemy/read_only_requester.rbs +9 -0
- data/sig/halchemy/requester.rbs +18 -0
- data/sig/halchemy/resource.rbs +5 -0
- data/sig/list_style_handlers.rbs +1 -0
- data/sig/matchers.rbs +1 -0
- data/sig/patterns.rbs +1 -0
- metadata +93 -0
@@ -0,0 +1,181 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "uri_template"
|
4
|
+
|
5
|
+
LIST_STYLE_HANDLERS = {
|
6
|
+
"repeat_key" => ->(key, array) { array.map { |item| [key, URI.encode_www_form_component(item.to_s)] } },
|
7
|
+
"bracket" => ->(key, array) { array.map { |item| ["#{key}[]", URI.encode_www_form_component(item.to_s)] } },
|
8
|
+
"index" => lambda { |key, array|
|
9
|
+
array.each_with_index.map do |item, index|
|
10
|
+
["#{key}[#{index}]", URI.encode_www_form_component(item.to_s)]
|
11
|
+
end
|
12
|
+
},
|
13
|
+
"comma" => ->(key, array) { [[key, array.map { |item| URI.encode_www_form_component(item.to_s) }.join(",")]] },
|
14
|
+
"pipe" => ->(key, array) { [[key, array.map { |item| URI.encode_www_form_component(item.to_s) }.join("|")]] }
|
15
|
+
}.freeze
|
16
|
+
|
17
|
+
module Halchemy
|
18
|
+
# The results of a Follower#to is a Requester. In the case of a GET for the root resource, the Requester is Read Only
|
19
|
+
# Otherwise it is a full Requester. Both requester types share much in common. This is defined in BaseRequester
|
20
|
+
class BaseRequester
|
21
|
+
# @param [Halchemy::Api] api
|
22
|
+
# @param [String | Tuple[HalResource, String]] target
|
23
|
+
# @return [void]
|
24
|
+
def initialize(api, target)
|
25
|
+
@api = api
|
26
|
+
@_data = nil
|
27
|
+
@_headers = CICPHash.new
|
28
|
+
@_template_values = {}
|
29
|
+
@_parameters = {}
|
30
|
+
|
31
|
+
process_target(target)
|
32
|
+
end
|
33
|
+
|
34
|
+
def url
|
35
|
+
rtn = @_url
|
36
|
+
|
37
|
+
if @_is_templated
|
38
|
+
tpl = URITemplate.new(rtn)
|
39
|
+
rtn = tpl.expand(@_template_values)
|
40
|
+
end
|
41
|
+
|
42
|
+
rtn = add_parameters_to_url rtn unless @_parameters.empty?
|
43
|
+
rtn
|
44
|
+
end
|
45
|
+
|
46
|
+
# @param [Hash] headers
|
47
|
+
def with_headers(headers)
|
48
|
+
@_headers.merge! headers
|
49
|
+
self
|
50
|
+
end
|
51
|
+
|
52
|
+
# @param [Hash] values
|
53
|
+
def with_template_values(values)
|
54
|
+
@_template_values.merge! values
|
55
|
+
self
|
56
|
+
end
|
57
|
+
|
58
|
+
# @param [Hash] parameters
|
59
|
+
def with_parameters(parameters)
|
60
|
+
@_parameters.merge! parameters
|
61
|
+
self
|
62
|
+
end
|
63
|
+
|
64
|
+
def request(method)
|
65
|
+
data = @_data.is_a?(Hash) ? @_data.to_json : @_data
|
66
|
+
@api.request(method, url, @_headers, data)
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
# @param [String | Tuple[HalResource, String]] target
|
72
|
+
# @return [void]
|
73
|
+
def process_target(target)
|
74
|
+
if target.is_a?(String)
|
75
|
+
@_url = target
|
76
|
+
else
|
77
|
+
resource, rel = target
|
78
|
+
@_url = resource["_links"][rel]["href"]
|
79
|
+
@_is_templated = resource["_links"][rel].fetch("templated", false)
|
80
|
+
@resource = resource
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Handle list-style parameters based on the list style configuration
|
85
|
+
def handle_list(key, array)
|
86
|
+
handler = LIST_STYLE_HANDLERS[@api.parameters_list_style]
|
87
|
+
raise ArgumentError, "Unsupported parameters list style: #{@api.parameters_list_style}" unless handler
|
88
|
+
|
89
|
+
handler.call(key, array)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Recursively flatten parameters into a list of key-value pairs
|
93
|
+
def flatten_parameters(prefix, parameters)
|
94
|
+
flattened = []
|
95
|
+
|
96
|
+
parameters.each do |key, value|
|
97
|
+
full_key = prefix.nil? || prefix.empty? ? key.to_s : "#{prefix}.#{key}"
|
98
|
+
|
99
|
+
case value
|
100
|
+
when nil
|
101
|
+
flattened << [full_key, nil]
|
102
|
+
when Array
|
103
|
+
flattened.concat(handle_list(full_key, value))
|
104
|
+
when Hash
|
105
|
+
flattened.concat(flatten_parameters(full_key, value))
|
106
|
+
when TrueClass, FalseClass
|
107
|
+
flattened << [full_key, value.to_s]
|
108
|
+
else
|
109
|
+
flattened << [full_key, URI.encode_www_form_component(value.to_s)]
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
flattened
|
114
|
+
end
|
115
|
+
|
116
|
+
# Add the flattened parameters to the URL as a query string
|
117
|
+
def add_parameters_to_url(url)
|
118
|
+
query_params = flatten_parameters(nil, @_parameters)
|
119
|
+
query_string = query_params.map { |key, value| value.nil? ? key : "#{key}=#{value}" }.join("&")
|
120
|
+
|
121
|
+
if url.include?("?")
|
122
|
+
"#{url}&#{query_string}"
|
123
|
+
else
|
124
|
+
"#{url}?#{query_string}"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# The result of GET on the root URL is a ReadOnlyRequester, i.e. only
|
130
|
+
# GET, HEAD, and OPTIONS are permitted
|
131
|
+
class ReadOnlyRequester < BaseRequester
|
132
|
+
def get
|
133
|
+
request :get
|
134
|
+
end
|
135
|
+
|
136
|
+
def head
|
137
|
+
request :head
|
138
|
+
end
|
139
|
+
|
140
|
+
def options
|
141
|
+
request :options
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# This provides a full-suite of HTTP methods, with handling of payload conversion and
|
146
|
+
# optimistic concurrency.
|
147
|
+
class Requester < ReadOnlyRequester
|
148
|
+
def post(data = nil, content_type = nil)
|
149
|
+
prepare_payload(content_type, data)
|
150
|
+
request :post
|
151
|
+
end
|
152
|
+
|
153
|
+
def put(data = nil, content_type = nil)
|
154
|
+
prepare_payload(content_type, data)
|
155
|
+
prepare_modify_header
|
156
|
+
request :put
|
157
|
+
end
|
158
|
+
|
159
|
+
def patch(data = nil, content_type = nil)
|
160
|
+
prepare_payload(content_type, data)
|
161
|
+
prepare_modify_header
|
162
|
+
request :patch
|
163
|
+
end
|
164
|
+
|
165
|
+
def delete
|
166
|
+
prepare_modify_header
|
167
|
+
request :delete
|
168
|
+
end
|
169
|
+
|
170
|
+
private
|
171
|
+
|
172
|
+
def prepare_payload(content_type, data)
|
173
|
+
@_data = data
|
174
|
+
@_headers["Content-Type"] = content_type unless content_type.nil?
|
175
|
+
end
|
176
|
+
|
177
|
+
def prepare_modify_header
|
178
|
+
@_headers.merge!(@api.optimistic_concurrency_header(@resource)) if @resource.is_a?(HalResource)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Halchemy
|
4
|
+
# The Resource class extends a Hash to include a metadata object containing details
|
5
|
+
# about the HTTP request and response. This lets the metadata stay "out of the way" allowing
|
6
|
+
# the client code to use the result of a request directly as a resource without losing access
|
7
|
+
# to the request details, response details, and any error details.
|
8
|
+
class Resource < Hash
|
9
|
+
attr_accessor :_halchemy
|
10
|
+
|
11
|
+
def to_s
|
12
|
+
if !keys.empty?
|
13
|
+
to_json
|
14
|
+
else
|
15
|
+
_halchemy&.response&.body.to_s
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# The HalResource, like Resource, is also a Hash that adds functionality to work with the
|
21
|
+
# link relations in a HAL Resource.
|
22
|
+
class HalResource < Resource
|
23
|
+
# @param [Hash] hash
|
24
|
+
# @return [boolean]
|
25
|
+
def self.hal?(hash)
|
26
|
+
return false unless hash.is_a?(Hash)
|
27
|
+
|
28
|
+
links = hash["_links"]
|
29
|
+
embedded = hash["_embedded"]
|
30
|
+
|
31
|
+
return false unless links.is_a?(Hash)
|
32
|
+
return false unless links["self"].is_a?(Hash)
|
33
|
+
return false unless links["self"]["href"].is_a?(String)
|
34
|
+
return false if embedded && !embedded.is_a?(Hash)
|
35
|
+
|
36
|
+
true
|
37
|
+
end
|
38
|
+
|
39
|
+
def rel?(rel_name)
|
40
|
+
self["_links"].key?(rel_name)
|
41
|
+
end
|
42
|
+
|
43
|
+
def links
|
44
|
+
self["_links"].keys ||= []
|
45
|
+
end
|
46
|
+
|
47
|
+
def raise_for_syntax_error(field)
|
48
|
+
unless keys.include?(field)
|
49
|
+
raise KeyError, "Field '#{field}' does not exist, so cannot be iterated as a collection"
|
50
|
+
end
|
51
|
+
raise TypeError, "Field '#{field}' is not a collection" unless self[field].is_a?(Array)
|
52
|
+
end
|
53
|
+
|
54
|
+
def collection(field)
|
55
|
+
raise_for_syntax_error(field)
|
56
|
+
|
57
|
+
Enumerator.new do |y|
|
58
|
+
self[field].each do |item|
|
59
|
+
unless Halchemy::HalResource.hal?(item)
|
60
|
+
raise TypeError, "The '#{field}' collection contains non-HAL formatted objects"
|
61
|
+
end
|
62
|
+
|
63
|
+
y.yield Halchemy::HalResource.new.merge!(item)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
private
|
4
|
+
|
5
|
+
PATTERNS = [
|
6
|
+
[/^(\d+)-(\d+)$/, ->(m) { { type: :range, start: m[1].to_i, end: m[2].to_i } }],
|
7
|
+
[/^>=(\d+)$/, ->(m) { { type: :gte, value: m[1].to_i } }],
|
8
|
+
[/^<=(\d+)$/, ->(m) { { type: :lte, value: m[1].to_i } }],
|
9
|
+
[/^>(\d+)$/, ->(m) { { type: :gt, value: m[1].to_i } }],
|
10
|
+
[/^<(\d+)$/, ->(m) { { type: :lt, value: m[1].to_i } }],
|
11
|
+
[/^(\d+)$/, ->(m) { { type: :eq, value: m[1].to_i } }]
|
12
|
+
].freeze
|
13
|
+
|
14
|
+
MATCHERS = {
|
15
|
+
range: ->(condition, status_code) { (condition[:start]..condition[:end]).include?(status_code) },
|
16
|
+
gt: ->(condition, status_code) { status_code > condition[:value] },
|
17
|
+
lt: ->(condition, status_code) { status_code < condition[:value] },
|
18
|
+
gte: ->(condition, status_code) { status_code >= condition[:value] },
|
19
|
+
lte: ->(condition, status_code) { status_code <= condition[:value] },
|
20
|
+
eq: ->(condition, status_code) { status_code == condition[:value] }
|
21
|
+
}.freeze
|
22
|
+
|
23
|
+
def settings_include_status_code?(settings, status_code)
|
24
|
+
return false if settings.nil? || settings.strip.empty?
|
25
|
+
|
26
|
+
parse_status_code_settings(settings).any? do |condition|
|
27
|
+
match_condition?(condition, status_code)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return [Array[String]]
|
32
|
+
# @param [String] settings
|
33
|
+
def parse_status_code_settings(settings)
|
34
|
+
settings.split(/,|\s+/).each_with_object([]) do |part, conditions|
|
35
|
+
part.strip!
|
36
|
+
conditions << parse_condition(part)
|
37
|
+
end.compact
|
38
|
+
end
|
39
|
+
|
40
|
+
# Parses an individual condition string into a hash.
|
41
|
+
# @param [String] part The individual condition string.
|
42
|
+
# @return [Hash, nil] The parsed condition hash or nil for blank parts.
|
43
|
+
def parse_condition(part)
|
44
|
+
return nil if part.match?(/^\s*$/)
|
45
|
+
|
46
|
+
PATTERNS.each do |pattern, handler|
|
47
|
+
if (match = part.match(pattern))
|
48
|
+
return handler.call(match)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
raise SyntaxError, "Invalid status code settings string: '#{part}'"
|
53
|
+
end
|
54
|
+
|
55
|
+
# @param [Array[Hash]] condition
|
56
|
+
# @param [Integer] status_code
|
57
|
+
# @return [bool]
|
58
|
+
def match_condition?(condition, status_code)
|
59
|
+
MATCHERS.fetch(condition[:type], ->(_, _) { false }).call(condition, status_code)
|
60
|
+
end
|
data/lib/halchemy.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
BASE_URL: String
|
@@ -0,0 +1 @@
|
|
1
|
+
HEADERS: hash[String, String]
|
@@ -0,0 +1 @@
|
|
1
|
+
REMOVE_HEADERS: array[String]
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Halchemy
|
2
|
+
class Api
|
3
|
+
@error_handling: ErrorHandling
|
4
|
+
|
5
|
+
@etag_field: String
|
6
|
+
@parameters_list_style: String
|
7
|
+
|
8
|
+
def self.build_response: -> HttpModel::Response
|
9
|
+
|
10
|
+
attr_accessor base_url: String
|
11
|
+
attr_accessor error_handling: ErrorHandling
|
12
|
+
attr_accessor headers: hash[String, String]
|
13
|
+
|
14
|
+
attr_accessor parameters_list_style: String
|
15
|
+
|
16
|
+
def add_headers: -> void
|
17
|
+
|
18
|
+
def follow: -> Follower
|
19
|
+
|
20
|
+
def optimistic_concurrency_header: -> Hash[String, String]
|
21
|
+
|
22
|
+
def remove_headers: -> void
|
23
|
+
|
24
|
+
def request: -> (Resource | HalResource)
|
25
|
+
|
26
|
+
def root: -> BaseRequester
|
27
|
+
|
28
|
+
def using_endpoint: -> BaseRequester
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def self.build_resource: -> (Resource | HalResource)
|
33
|
+
|
34
|
+
def build_resource: -> (Resource | HalResource)
|
35
|
+
|
36
|
+
def build_response: -> HttpModel::Response
|
37
|
+
|
38
|
+
def build_url: -> String
|
39
|
+
|
40
|
+
def configure: -> void
|
41
|
+
|
42
|
+
def do_settings_include_status_code: -> bool
|
43
|
+
|
44
|
+
def extract_body: -> (String | Hash[String, Object] | nil)
|
45
|
+
|
46
|
+
def match_condition?: -> bool
|
47
|
+
|
48
|
+
def parse_body: -> (Hash[String, Object] | nil)
|
49
|
+
|
50
|
+
def parse_status_code_setting: -> Array[String]
|
51
|
+
|
52
|
+
def raise_for_errors: -> void
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Halchemy
|
2
|
+
class BaseRequester
|
3
|
+
@_data: Object | nil
|
4
|
+
@_headers: CICPHash
|
5
|
+
@_is_templated: bool
|
6
|
+
@_parameters: Hash[String, Object]
|
7
|
+
@_template_values: Hash[String, Object]
|
8
|
+
@_url: String
|
9
|
+
@api: Api
|
10
|
+
@resource: Resource | HalResource
|
11
|
+
|
12
|
+
def initialize: -> void
|
13
|
+
|
14
|
+
def request: -> (Resource | HalResource)
|
15
|
+
|
16
|
+
def url: -> String
|
17
|
+
|
18
|
+
def with_headers: -> (BaseRequester | ReadOnlyRequester | Requester)
|
19
|
+
|
20
|
+
def with_parameters: -> (BaseRequester | ReadOnlyRequester | Requester)
|
21
|
+
|
22
|
+
def with_template_values: -> (BaseRequester | ReadOnlyRequester | Requester)
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def add_parameters_to_url: -> String
|
27
|
+
|
28
|
+
def flatten_parameters: -> Array[String]
|
29
|
+
|
30
|
+
def handle_list: -> Array[Object]
|
31
|
+
|
32
|
+
def process_target: -> void
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Halchemy
|
2
|
+
module HttpModel
|
3
|
+
class Request
|
4
|
+
@headers: CICPHash
|
5
|
+
@method: String
|
6
|
+
@url: String
|
7
|
+
|
8
|
+
attr_accessor body: Object | nil
|
9
|
+
attr_accessor headers: CICPHash
|
10
|
+
attr_accessor method: Symbol
|
11
|
+
attr_accessor url: String
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Halchemy
|
2
|
+
module HttpModel
|
3
|
+
class Response
|
4
|
+
@headers: CICPHash
|
5
|
+
@reason: String
|
6
|
+
@status_code: int
|
7
|
+
|
8
|
+
attr_accessor body: Object | nil
|
9
|
+
attr_accessor error: Object | nil
|
10
|
+
attr_accessor headers: CICPHash
|
11
|
+
attr_accessor reason: String
|
12
|
+
attr_accessor status_code: Integer
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Halchemy
|
2
|
+
class Requester < ReadOnlyRequester
|
3
|
+
|
4
|
+
def delete: -> (Resource | HalResource)
|
5
|
+
|
6
|
+
def patch: -> (Resource | HalResource)
|
7
|
+
|
8
|
+
def post: -> (Resource | HalResource)
|
9
|
+
|
10
|
+
def put: -> (Resource | HalResource)
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def prepare_modify_header: -> void
|
15
|
+
|
16
|
+
def prepare_payload: -> void
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
LIST_STYLE_HANDLERS: Hash[String, Object]
|
data/sig/matchers.rbs
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
MATCHERS: Hash[Symbol, Object]
|
data/sig/patterns.rbs
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
PATTERNS: Array[Tuple[Regexp, Object]]
|
metadata
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: halchemy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Michael Ottoson
|
8
|
+
bindir: exe
|
9
|
+
cert_chain: []
|
10
|
+
date: 2025-01-26 00:00:00.000000000 Z
|
11
|
+
dependencies: []
|
12
|
+
description: Do you have an API that serves data following the HAL specification? The
|
13
|
+
**halchemy** library makes it easy for your client to make the most of that API.
|
14
|
+
email:
|
15
|
+
- michael@pointw.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- ".idea/.gitignore"
|
21
|
+
- ".idea/halchemy.iml"
|
22
|
+
- ".idea/icon.png"
|
23
|
+
- ".idea/inspectionProfiles/Project_Default.xml"
|
24
|
+
- ".idea/modules.xml"
|
25
|
+
- ".idea/vcs.xml"
|
26
|
+
- ".rubocop.yml"
|
27
|
+
- CHANGELOG.md
|
28
|
+
- LICENSE.txt
|
29
|
+
- README.md
|
30
|
+
- Rakefile
|
31
|
+
- __tests__/common.rb
|
32
|
+
- __tests__/configurable/base_url.rb
|
33
|
+
- __tests__/configurable/error_handling.rb
|
34
|
+
- __tests__/configurable/headers.rb
|
35
|
+
- __tests__/make_http_requests/follow_link_relations.rb
|
36
|
+
- __tests__/make_http_requests/http_response_details.rb
|
37
|
+
- __tests__/make_http_requests/optimistic_concurrency.rb
|
38
|
+
- __tests__/make_http_requests/with_headers.rb
|
39
|
+
- __tests__/make_http_requests/with_parameters.rb
|
40
|
+
- __tests__/make_http_requests/with_templates.rb
|
41
|
+
- __tests__/use_resources/iterate_collections.rb
|
42
|
+
- bdd
|
43
|
+
- lib/halchemy.rb
|
44
|
+
- lib/halchemy/api.rb
|
45
|
+
- lib/halchemy/error_handling.rb
|
46
|
+
- lib/halchemy/follower.rb
|
47
|
+
- lib/halchemy/http_model.rb
|
48
|
+
- lib/halchemy/metadata.rb
|
49
|
+
- lib/halchemy/requester.rb
|
50
|
+
- lib/halchemy/resource.rb
|
51
|
+
- lib/halchemy/status_codes.rb
|
52
|
+
- lib/halchemy/version.rb
|
53
|
+
- sig/__tests__/base_url.rbs
|
54
|
+
- sig/__tests__/halchemy.rbs
|
55
|
+
- sig/__tests__/headers.rbs
|
56
|
+
- sig/__tests__/remove_headers.rbs
|
57
|
+
- sig/halchemy/api.rbs
|
58
|
+
- sig/halchemy/base_requester.rbs
|
59
|
+
- sig/halchemy/error_handling.rbs
|
60
|
+
- sig/halchemy/follower.rbs
|
61
|
+
- sig/halchemy/hal_resource.rbs
|
62
|
+
- sig/halchemy/http_model/request.rbs
|
63
|
+
- sig/halchemy/http_model/response.rbs
|
64
|
+
- sig/halchemy/metadata.rbs
|
65
|
+
- sig/halchemy/read_only_requester.rbs
|
66
|
+
- sig/halchemy/requester.rbs
|
67
|
+
- sig/halchemy/resource.rbs
|
68
|
+
- sig/list_style_handlers.rbs
|
69
|
+
- sig/matchers.rbs
|
70
|
+
- sig/patterns.rbs
|
71
|
+
homepage: https://github.com/pointw-dev/halchemy
|
72
|
+
licenses:
|
73
|
+
- MIT
|
74
|
+
metadata:
|
75
|
+
homepage_uri: https://github.com/pointw-dev/halchemy
|
76
|
+
rdoc_options: []
|
77
|
+
require_paths:
|
78
|
+
- lib
|
79
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: 3.1.0
|
84
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
requirements: []
|
90
|
+
rubygems_version: 3.6.2
|
91
|
+
specification_version: 4
|
92
|
+
summary: HAL for humans
|
93
|
+
test_files: []
|