shaf_client 0.6.2 → 1.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f4ef66759fc8f50747be3c2db4cc9b723ccdb0d0fcce0ed1889b485de2376577
4
- data.tar.gz: ae7f83ed0066939cf04784ce5fbcd128b193db0b708327129d47f53b3028b09f
3
+ metadata.gz: 688d7345f3b3c1962b973d323845060cf57b92aeb7b60de4b6f680e566414edf
4
+ data.tar.gz: 4ccaecadff24f4a78a805f9998af50f93857ac461505c4fc48832e34884123d4
5
5
  SHA512:
6
- metadata.gz: 0bf113e75dc7c84a6801d8e665886938c1987426b251cfbebfb9e35161504a1053d32f6b7d87143f79997057d262590b1f2ebda86889871fc4aa9d8d7af1d6e4
7
- data.tar.gz: dddad11c115f767ef389b4052b5e4ae302bccd19ecbed3db27b5818a61a37922ac4445a3b5ea17f42d94ddfa8c39f20b5c6a167854fa214b37e11755fce439aa
6
+ metadata.gz: 8904fef8dc91ed8baa3fa92d434569b4f83a443e1274df372334a27a1a4fb84d3b5a8bfd5956ca85abf08957ec0c28520c424216ec7eaf9edf5719d72d336cde
7
+ data.tar.gz: 5328f8b5053b81c590b755c25bf8423b9902f7ea0cfe2a03aacf2317f7eccbe49032276e1a6d2abb304823283436eb5e3f6445b2ceb4785d79f8fdc99d51289e
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
- MIME_TYPE_JSON = 'application/json'
17
- MIME_TYPE_HAL = 'application/hal+json'
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
- def get_doc(uri, **options)
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'] = nil if body.empty?
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, MIME_TYPE_HAL)
72
+ 'Accept' => options.fetch(:accept, DEFAULT_ACCEPT_HEADER)
67
73
  }
68
74
  return unless token = options[:auth_token]
69
75
 
@@ -101,9 +107,11 @@ class ShafClient
101
107
  end
102
108
 
103
109
  def connect_adapter(connection)
104
- args = [@adapter]
105
- args << stubs if @adapter == :test
106
- connection.adapter(*args)
110
+ connection.adapter(*adapter_args)
111
+ end
112
+
113
+ def adapter_args
114
+ [@adapter]
107
115
  end
108
116
 
109
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
@@ -4,7 +4,8 @@ require 'shaf_client/link'
4
4
  class ShafClient
5
5
  class ApiError < Resource
6
6
 
7
- profile 'shaf-error'
7
+ profile 'shaf-error' # Legacy profiles
8
+ profile 'urn:shaf:error' # New style. Shaf version >= 2.1.0
8
9
 
9
10
  def title
10
11
  attribute(:title)
@@ -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
- payload
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] = transform_values_to_s(links)
23
- embedded = transform_values_to_s(embedded_resources)
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
@@ -33,32 +39,28 @@ class ShafClient
33
39
  to_s
34
40
  end
35
41
 
36
- def attribute(key)
37
- raise Error, "No attribute for key: #{key}" unless attributes.key? key
38
- attributes.fetch(key.to_sym)
42
+ def attribute(key, &block)
43
+ block ||= proc { raise Error, "No attribute for key: #{key}" }
44
+ _attribute(key, &block)
39
45
  end
40
46
 
41
- def link(rel)
42
- rewritten_rel = best_match(links.keys, rel)
43
- raise Error, "No link with rel: #{rel}" unless links.key? rewritten_rel
44
- links[rewritten_rel]
47
+ def link(rel, &block)
48
+ block ||= proc { raise Error, "No link with rel: #{rel}" }
49
+ _link(rel, &block)
45
50
  end
46
51
 
47
- def curie(rel)
48
- raise Error, "No curie with rel: #{rel}" unless curies.key? rel.to_sym
49
- curies[rel.to_sym]
52
+ def curie(rel, &block)
53
+ block ||= proc { raise Error, "No curie with rel: #{rel}" }
54
+ _curie(rel, &block)
50
55
  end
51
56
 
52
- def embedded(rel)
53
- rewritten_rel = best_match(embedded_resources.keys, rel)
54
- unless embedded_resources.key? rewritten_rel
55
- raise Error, "No embedded resources with rel: #{rel}"
56
- end
57
- embedded_resources[rewritten_rel]
57
+ def embedded(rel, &block)
58
+ block ||= proc { raise Error, "No embedded resources with rel: #{rel}" }
59
+ _embedded(rel, &block)
58
60
  end
59
61
 
60
62
  def rel?(rel)
61
- !link(rel).nil?
63
+ !link(rel).nil? || !embedded(rel).nil?
62
64
  rescue StandardError
63
65
  false
64
66
  end
@@ -77,6 +79,40 @@ class ShafClient
77
79
  @payload ||= {}
78
80
  end
79
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
+
80
116
  def <<(other)
81
117
  @payload = other.payload.dup
82
118
  @attributes = other.attributes.dup
@@ -118,13 +154,17 @@ class ShafClient
118
154
  embedded.each do |key, value|
119
155
  @embedded_resources[key.to_sym] =
120
156
  if value.is_a? Array
121
- value.map { |d| BaseResource.new(d) }
157
+ value.map { |d| build_embedded_resource(d) }
122
158
  else
123
- BaseResource.new(value)
159
+ build_embedded_resource(value)
124
160
  end
125
161
  end
126
162
  end
127
163
 
164
+ def build_embedded_resource(payload)
165
+ BaseResource.new(payload)
166
+ end
167
+
128
168
  def method_missing(method_name, *args, &block)
129
169
  return super unless attributes.key?(method_name)
130
170
  attribute(method_name)
@@ -148,7 +188,7 @@ class ShafClient
148
188
  best_match(rels, rel.to_s.tr('_', '-')) if rel.to_s.include? '_'
149
189
  end
150
190
 
151
- def transform_values_to_s(hash)
191
+ def transform_values_to_h(hash)
152
192
  hash.transform_values do |value|
153
193
  if value.is_a? Array
154
194
  value.map(&:to_h)