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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7fbc814d168650a7fd1464610ae0c6e87cf7f68b8cf5e3e1e139e2bb71e6891f
4
- data.tar.gz: 3de3b4941071c1afeebbd27327f5642074dfc1e418beb9625e1efcf9e98db58e
3
+ metadata.gz: 11ccc3070c77ca9f04b3a54599437752022c2de7b6bf51f5d3cf7d6bf25bb147
4
+ data.tar.gz: 73658cc5c27287a677e82a935ced861e8775355ee84df02bc10631e33a13a2e8
5
5
  SHA512:
6
- metadata.gz: 3b69fbe36290bda8d3323b6dab93d3931e796667ee85ce92edb383e80e7ea849b2296baefbe2726bab6effe148dfdcfd7a5e896dcf68c71b50a25f730ac90caa
7
- data.tar.gz: c9be99a9577a0d09039bc4208989074a77ad6eb836802b27a362945c6d55a3f6d82c7257502b97cd5f591b86cbe0e6c9191474085c91e87029640ce2aac48d6d
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
- 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
 
@@ -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
- args = [@adapter]
104
- args << stubs if @adapter == :test
105
- connection.adapter(*args)
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
- 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
@@ -29,32 +35,32 @@ class ShafClient
29
35
  JSON.pretty_generate(to_h)
30
36
  end
31
37
 
32
- def attribute(key)
33
- raise Error, "No attribute for key: #{key}" unless attributes.key? key
34
- attributes.fetch(key.to_sym)
38
+ def inspect
39
+ to_s
35
40
  end
36
41
 
37
- def link(rel)
38
- rewritten_rel = best_match(links.keys, rel)
39
- raise Error, "No link with rel: #{rel}" unless links.key? rewritten_rel
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 curie(rel)
44
- raise Error, "No curie with rel: #{rel}" unless curies.key? rel.to_sym
45
- curies[rel.to_sym]
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 embedded(rel)
49
- rewritten_rel = best_match(embedded_resources.keys, rel)
50
- unless embedded_resources.key? rewritten_rel
51
- raise Error, "No embedded resources with rel: #{rel}"
52
- end
53
- embedded_resources[rewritten_rel]
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| BaseResource.new(d) }
157
+ value.map { |d| build_embedded_resource(d) }
118
158
  else
119
- BaseResource.new(value)
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 transform_values_to_s(hash)
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)