shaf_client 0.6.1 → 1.0.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: 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)