shaf_client 0.6.1 → 1.0.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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/bin/shaf_client +30 -0
- data/lib/faraday_http_cache_patch.rb +17 -0
- data/lib/shaf_client.rb +28 -19
- data/lib/shaf_client/alps/descriptor.rb +60 -0
- data/lib/shaf_client/alps/extension.rb +35 -0
- data/lib/shaf_client/alps_json.rb +44 -0
- data/lib/shaf_client/base_resource.rb +67 -23
- data/lib/shaf_client/content_type_map.rb +53 -0
- data/lib/shaf_client/curie.rb +4 -0
- data/lib/shaf_client/empty_resource.rb +1 -1
- data/lib/shaf_client/hal_form.rb +1 -1
- data/lib/shaf_client/hypertext_cache_strategy.rb +43 -0
- data/lib/shaf_client/link.rb +2 -2
- data/lib/shaf_client/mime_types.rb +8 -0
- data/lib/shaf_client/problem_json.rb +38 -0
- data/lib/shaf_client/resource.rb +82 -10
- data/lib/shaf_client/resource_extension.rb +36 -0
- data/lib/shaf_client/resource_extension/alps_http_method.rb +67 -0
- data/lib/shaf_client/resource_extension/base.rb +13 -0
- data/lib/shaf_client/resource_mapper.rb +86 -10
- data/lib/shaf_client/shaf_form.rb +1 -1
- data/lib/shaf_client/status_codes.rb +70 -0
- data/lib/shaf_client/test/stubbing.rb +16 -0
- data/lib/shaf_client/unknown_resource.rb +3 -3
- data/lib/shaf_client/version.rb +3 -0
- metadata +58 -29
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 11ccc3070c77ca9f04b3a54599437752022c2de7b6bf51f5d3cf7d6bf25bb147
|
4
|
+
data.tar.gz: 73658cc5c27287a677e82a935ced861e8775355ee84df02bc10631e33a13a2e8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 322b0537cb301169f77dc76b078c11d74d60b156718b9bdef1cf019d3983fb2c6e3aab01750bd309b6e7e672e0fa7f987b74d2ddd6d0edac21ff03088ddb724e
|
7
|
+
data.tar.gz: 3d8f6b58ed02af7892425fa39eb1bfb9f52845cba81cff2a8e9204e2dd1780ceafb8bf6b13f2b12f77a7ee6a74ecafcca60a631dacf28dd66e06c7d98a3b4726
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data.tar.gz.sig
CHANGED
Binary file
|
data/bin/shaf_client
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$:.unshift File.join(__dir__, '../lib/')
|
4
|
+
|
5
|
+
require 'irb'
|
6
|
+
require 'optparse'
|
7
|
+
require 'shaf_client'
|
8
|
+
|
9
|
+
url = ARGV.shift || String(ENV['SHAF_CLIENT_URL'])
|
10
|
+
options = {
|
11
|
+
user: ENV['SHAF_CLIENT_USER'],
|
12
|
+
password: ENV['SHAF_CLIENT_PASSWORD']
|
13
|
+
}
|
14
|
+
|
15
|
+
OptionParser.new do |opts|
|
16
|
+
opts.banner = "Usage: shaf_client [url]"
|
17
|
+
|
18
|
+
opts.on("-u", "--user user", "Username used for authentication") do |user|
|
19
|
+
options[:user] = user
|
20
|
+
end
|
21
|
+
|
22
|
+
opts.on("-p", "--password password", "Password used for authentication") do |pass|
|
23
|
+
options[:password] = pass
|
24
|
+
end
|
25
|
+
end.parse! ARGV
|
26
|
+
|
27
|
+
client = ShafClient.new(url, **options) unless url.empty?
|
28
|
+
TOPLEVEL_BINDING.local_variable_set(:client, client)
|
29
|
+
|
30
|
+
IRB.start
|
@@ -0,0 +1,17 @@
|
|
1
|
+
faraday_version = Gem.loaded_specs['faraday']&.version.to_s
|
2
|
+
faraday_http_cache_version = Gem.loaded_specs['faraday-http-cache']&.version.to_s
|
3
|
+
|
4
|
+
if faraday_version.start_with?("0.16") && faraday_http_cache_version <= '2.0.0'
|
5
|
+
module Faraday
|
6
|
+
class HttpCache < Faraday::Middleware
|
7
|
+
def create_response(env)
|
8
|
+
hash = env.to_hash
|
9
|
+
{
|
10
|
+
status: hash[:status],
|
11
|
+
body: hash[:response_body] || hash[:body],
|
12
|
+
response_headers: hash[:response_headers]
|
13
|
+
}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/shaf_client.rb
CHANGED
@@ -2,20 +2,36 @@
|
|
2
2
|
|
3
3
|
require 'faraday'
|
4
4
|
require 'faraday-http-cache'
|
5
|
+
|
6
|
+
# FIXME remove this when faraday-http-cache has released this fix
|
7
|
+
# https://github.com/plataformatec/faraday-http-cache/pull/116
|
8
|
+
require 'faraday_http_cache_patch'
|
9
|
+
|
5
10
|
require 'json'
|
6
11
|
require 'shaf_client/error'
|
12
|
+
require 'shaf_client/mime_types'
|
7
13
|
require 'shaf_client/middleware/redirect'
|
8
14
|
require 'shaf_client/resource'
|
9
15
|
require 'shaf_client/shaf_form'
|
10
16
|
require 'shaf_client/hal_form'
|
11
17
|
require 'shaf_client/api_error'
|
18
|
+
require 'shaf_client/problem_json'
|
19
|
+
require 'shaf_client/alps_json'
|
12
20
|
require 'shaf_client/empty_resource'
|
13
21
|
require 'shaf_client/unknown_resource'
|
22
|
+
require 'shaf_client/hypertext_cache_strategy'
|
14
23
|
|
15
24
|
class ShafClient
|
16
|
-
|
17
|
-
|
25
|
+
extend HypertextCacheStrategy
|
26
|
+
include MimeTypes
|
27
|
+
|
18
28
|
DEFAULT_ADAPTER = :net_http
|
29
|
+
DEFAULT_ACCEPT_HEADER = [
|
30
|
+
MIME_TYPE_HAL,
|
31
|
+
MIME_TYPE_PROBLEM_JSON,
|
32
|
+
MIME_TYPE_ALPS_JSON,
|
33
|
+
'*/*;q=0.8',
|
34
|
+
].join(', ')
|
19
35
|
|
20
36
|
def initialize(root_uri, **options)
|
21
37
|
@root_uri = root_uri.dup
|
@@ -30,12 +46,7 @@ class ShafClient
|
|
30
46
|
get(@root_uri, **options)
|
31
47
|
end
|
32
48
|
|
33
|
-
|
34
|
-
response = request(method: :get, uri: uri, opts: options)
|
35
|
-
response&.body || ''
|
36
|
-
end
|
37
|
-
|
38
|
-
%i[get put post delete patch].each do |method|
|
49
|
+
%i[head get put post delete patch].each do |method|
|
39
50
|
define_method(method) do |uri, payload: nil, **options|
|
40
51
|
response = request(
|
41
52
|
method: method,
|
@@ -45,17 +56,12 @@ class ShafClient
|
|
45
56
|
)
|
46
57
|
|
47
58
|
body = String(response.body)
|
48
|
-
response.headers['content-type']
|
59
|
+
content_type = response.headers['content-type'] unless body.empty?
|
49
60
|
|
50
|
-
Resource.build(self, body, response.status, response.headers)
|
61
|
+
Resource.build(self, body, content_type, response.status, response.headers)
|
51
62
|
end
|
52
63
|
end
|
53
64
|
|
54
|
-
def stubs
|
55
|
-
return unless @adapter == :test
|
56
|
-
@stubs ||= Faraday::Adapter::Test::Stubs.new
|
57
|
-
end
|
58
|
-
|
59
65
|
private
|
60
66
|
|
61
67
|
attr_reader :options, :auth_header
|
@@ -63,7 +69,7 @@ class ShafClient
|
|
63
69
|
def setup_default_headers
|
64
70
|
@default_headers = {
|
65
71
|
'Content-Type' => options.fetch(:content_type, MIME_TYPE_JSON),
|
66
|
-
'Accept' => options.fetch(:accept,
|
72
|
+
'Accept' => options.fetch(:accept, DEFAULT_ACCEPT_HEADER)
|
67
73
|
}
|
68
74
|
return unless token = options[:auth_token]
|
69
75
|
|
@@ -96,13 +102,16 @@ class ShafClient
|
|
96
102
|
options.fetch(:faraday_http_cache, {}).tap do |cache_params|
|
97
103
|
cache_params[:store] ||= options[:http_cache_store] if options[:http_cache_store]
|
98
104
|
cache_params[:shared_cache] ||= false
|
105
|
+
cache_params[:serializer] ||= Marshal
|
99
106
|
end
|
100
107
|
end
|
101
108
|
|
102
109
|
def connect_adapter(connection)
|
103
|
-
|
104
|
-
|
105
|
-
|
110
|
+
connection.adapter(*adapter_args)
|
111
|
+
end
|
112
|
+
|
113
|
+
def adapter_args
|
114
|
+
[@adapter]
|
106
115
|
end
|
107
116
|
|
108
117
|
def request(method:, uri:, payload: nil, opts: {})
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'shaf_client/alps/extension'
|
2
|
+
|
3
|
+
class ShafClient
|
4
|
+
module Alps
|
5
|
+
class Descriptor
|
6
|
+
attr_reader :id, :href, :name, :type, :doc, :ext
|
7
|
+
|
8
|
+
def initialize(id:, **kwargs)
|
9
|
+
@id = id.to_sym
|
10
|
+
@href = kwargs[:href]
|
11
|
+
@name = kwargs[:name]
|
12
|
+
@type = kwargs[:type]
|
13
|
+
@doc = kwargs[:doc]
|
14
|
+
@ext = parse_extentions(kwargs[:ext])
|
15
|
+
end
|
16
|
+
|
17
|
+
alias extensions ext
|
18
|
+
|
19
|
+
def to_h
|
20
|
+
{
|
21
|
+
id: id,
|
22
|
+
href: href,
|
23
|
+
name: name,
|
24
|
+
type: type,
|
25
|
+
doc: doc,
|
26
|
+
ext: extensions.map(&:to_h),
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
def semantic?
|
31
|
+
type == 'semantic'
|
32
|
+
end
|
33
|
+
|
34
|
+
def safe?
|
35
|
+
type == 'safe'
|
36
|
+
end
|
37
|
+
|
38
|
+
def idempotent?
|
39
|
+
type == 'idempotent'
|
40
|
+
end
|
41
|
+
|
42
|
+
def unsafe?
|
43
|
+
type == 'unsafe'
|
44
|
+
end
|
45
|
+
|
46
|
+
def extension(id)
|
47
|
+
extensions.find { |ext| ext.id == id.to_sym }
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def parse_extentions(extensions)
|
53
|
+
extensions ||= []
|
54
|
+
extensions = [extensions] unless extensions.is_a? Array
|
55
|
+
extensions.map { |ext| Extension.new(**ext.transform_keys(&:to_sym)) }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
@@ -0,0 +1,35 @@
|
|
1
|
+
class ShafClient
|
2
|
+
module Alps
|
3
|
+
class Extension
|
4
|
+
attr_reader :id, :href, :value
|
5
|
+
|
6
|
+
def initialize(id:, href: nil, value: nil)
|
7
|
+
@id = id.to_sym
|
8
|
+
@href = href
|
9
|
+
@value = value
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_h
|
13
|
+
{
|
14
|
+
id: id,
|
15
|
+
href: href,
|
16
|
+
value: value,
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def method_missing(method_name, *args, &block)
|
23
|
+
name = method_name.to_s
|
24
|
+
return super unless name.end_with? '?'
|
25
|
+
|
26
|
+
id.to_s == name[0..-2]
|
27
|
+
end
|
28
|
+
|
29
|
+
def respond_to_missing?(method_name, include_private = false)
|
30
|
+
return true if method_name.to_s.end_with? '?'
|
31
|
+
super
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'shaf_client/alps/descriptor'
|
2
|
+
|
3
|
+
class ShafClient
|
4
|
+
class AlpsJson < Resource
|
5
|
+
content_type MIME_TYPE_ALPS_JSON
|
6
|
+
|
7
|
+
attr_reader :descriptors
|
8
|
+
|
9
|
+
def initialize(_client, payload, status = nil, headers = {})
|
10
|
+
super
|
11
|
+
|
12
|
+
@links = {}.freeze
|
13
|
+
@curies = {}.freeze
|
14
|
+
@embedded_resources = {}.freeze
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_h
|
18
|
+
attributes.merge(
|
19
|
+
descriptors: descriptors.map(&:to_h)
|
20
|
+
)
|
21
|
+
end
|
22
|
+
|
23
|
+
def descriptor(id)
|
24
|
+
descriptors.find { |desc| desc.id == id.to_sym }
|
25
|
+
end
|
26
|
+
|
27
|
+
def each_descriptor(&block)
|
28
|
+
descriptors.each(&block)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def parse
|
34
|
+
alps = payload&.dig('alps') || {}
|
35
|
+
@attributes = {
|
36
|
+
version: alps['version'],
|
37
|
+
doc: alps['doc'],
|
38
|
+
}
|
39
|
+
@descriptors = alps.fetch('descriptor', []).map do |desc|
|
40
|
+
Alps::Descriptor.new(**desc.transform_keys(&:to_sym))
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -10,8 +10,13 @@ class ShafClient
|
|
10
10
|
@payload =
|
11
11
|
if payload&.is_a? String
|
12
12
|
JSON.parse(payload)
|
13
|
+
elsif payload.respond_to? :to_h
|
14
|
+
payload.to_h
|
13
15
|
else
|
14
|
-
|
16
|
+
raise Error, <<~ERR
|
17
|
+
Trying to create an instance of #{self.class} with a payload that
|
18
|
+
cannot be coerced into a Hash
|
19
|
+
ERR
|
15
20
|
end
|
16
21
|
|
17
22
|
parse
|
@@ -19,8 +24,9 @@ class ShafClient
|
|
19
24
|
|
20
25
|
def to_h
|
21
26
|
attributes.dup.tap do |hash|
|
22
|
-
hash[:_links] =
|
23
|
-
|
27
|
+
hash[:_links] = transform_values_to_h(links)
|
28
|
+
hash[:_links].merge!(curies: curies.values.map(&:to_h)) unless curies.empty?
|
29
|
+
embedded = transform_values_to_h(embedded_resources)
|
24
30
|
hash[:_embedded] = embedded unless embedded.empty?
|
25
31
|
end
|
26
32
|
end
|
@@ -29,32 +35,32 @@ class ShafClient
|
|
29
35
|
JSON.pretty_generate(to_h)
|
30
36
|
end
|
31
37
|
|
32
|
-
def
|
33
|
-
|
34
|
-
attributes.fetch(key.to_sym)
|
38
|
+
def inspect
|
39
|
+
to_s
|
35
40
|
end
|
36
41
|
|
37
|
-
def
|
38
|
-
|
39
|
-
|
40
|
-
links[rewritten_rel]
|
42
|
+
def attribute(key, &block)
|
43
|
+
block ||= proc { raise Error, "No attribute for key: #{key}" }
|
44
|
+
_attribute(key, &block)
|
41
45
|
end
|
42
46
|
|
43
|
-
def
|
44
|
-
raise Error, "No
|
45
|
-
|
47
|
+
def link(rel, &block)
|
48
|
+
block ||= proc { raise Error, "No link with rel: #{rel}" }
|
49
|
+
_link(rel, &block)
|
46
50
|
end
|
47
51
|
|
48
|
-
def
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
52
|
+
def curie(rel, &block)
|
53
|
+
block ||= proc { raise Error, "No curie with rel: #{rel}" }
|
54
|
+
_curie(rel, &block)
|
55
|
+
end
|
56
|
+
|
57
|
+
def embedded(rel, &block)
|
58
|
+
block ||= proc { raise Error, "No embedded resources with rel: #{rel}" }
|
59
|
+
_embedded(rel, &block)
|
54
60
|
end
|
55
61
|
|
56
62
|
def rel?(rel)
|
57
|
-
!link(rel).nil?
|
63
|
+
!link(rel).nil? || !embedded(rel).nil?
|
58
64
|
rescue StandardError
|
59
65
|
false
|
60
66
|
end
|
@@ -73,6 +79,40 @@ class ShafClient
|
|
73
79
|
@payload ||= {}
|
74
80
|
end
|
75
81
|
|
82
|
+
def _attribute(key, &block)
|
83
|
+
if block
|
84
|
+
attributes.fetch(key.to_sym, &block)
|
85
|
+
else
|
86
|
+
attributes[key.to_sym]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def _link(rel, &block)
|
91
|
+
rewritten_rel = best_match(links.keys, rel)
|
92
|
+
if block
|
93
|
+
links.fetch(rewritten_rel, &block)
|
94
|
+
else
|
95
|
+
links[rewritten_rel]
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def _curie(rel, &block)
|
100
|
+
if block
|
101
|
+
curies.fetch(rel.to_sym, &block)
|
102
|
+
else
|
103
|
+
curies[rel.to_sym]
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def _embedded(rel, &block)
|
108
|
+
rewritten_rel = best_match(embedded_resources.keys, rel)
|
109
|
+
if block
|
110
|
+
embedded_resources.fetch(rewritten_rel, &block)
|
111
|
+
else
|
112
|
+
embedded_resources[rewritten_rel]
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
76
116
|
def <<(other)
|
77
117
|
@payload = other.payload.dup
|
78
118
|
@attributes = other.attributes.dup
|
@@ -114,13 +154,17 @@ class ShafClient
|
|
114
154
|
embedded.each do |key, value|
|
115
155
|
@embedded_resources[key.to_sym] =
|
116
156
|
if value.is_a? Array
|
117
|
-
value.map { |d|
|
157
|
+
value.map { |d| build_embedded_resource(d) }
|
118
158
|
else
|
119
|
-
|
159
|
+
build_embedded_resource(value)
|
120
160
|
end
|
121
161
|
end
|
122
162
|
end
|
123
163
|
|
164
|
+
def build_embedded_resource(payload)
|
165
|
+
BaseResource.new(payload)
|
166
|
+
end
|
167
|
+
|
124
168
|
def method_missing(method_name, *args, &block)
|
125
169
|
return super unless attributes.key?(method_name)
|
126
170
|
attribute(method_name)
|
@@ -144,7 +188,7 @@ class ShafClient
|
|
144
188
|
best_match(rels, rel.to_s.tr('_', '-')) if rel.to_s.include? '_'
|
145
189
|
end
|
146
190
|
|
147
|
-
def
|
191
|
+
def transform_values_to_h(hash)
|
148
192
|
hash.transform_values do |value|
|
149
193
|
if value.is_a? Array
|
150
194
|
value.map(&:to_h)
|