shaf_client 0.6.2 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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)