restify 1.15.1 → 2.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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +102 -15
  3. data/README.md +23 -31
  4. data/lib/restify/adapter/base.rb +4 -0
  5. data/lib/restify/adapter/telemetry.rb +54 -0
  6. data/lib/restify/adapter/typhoeus.rb +24 -14
  7. data/lib/restify/context.rb +7 -11
  8. data/lib/restify/error.rb +2 -2
  9. data/lib/restify/global.rb +1 -0
  10. data/lib/restify/link.rb +4 -4
  11. data/lib/restify/logging.rb +1 -1
  12. data/lib/restify/processors/base/parsing.rb +5 -24
  13. data/lib/restify/processors/base.rb +1 -1
  14. data/lib/restify/promise.rb +2 -2
  15. data/lib/restify/registry.rb +1 -1
  16. data/lib/restify/relation.rb +45 -17
  17. data/lib/restify/request.rb +6 -6
  18. data/lib/restify/resource.rb +1 -1
  19. data/lib/restify/response.rb +0 -2
  20. data/lib/restify/timeout.rb +2 -2
  21. data/lib/restify/version.rb +4 -4
  22. data/lib/restify.rb +0 -1
  23. data/spec/restify/cache_spec.rb +16 -12
  24. data/spec/restify/context_spec.rb +15 -7
  25. data/spec/restify/error_spec.rb +23 -16
  26. data/spec/restify/features/head_requests_spec.rb +7 -5
  27. data/spec/restify/features/request_bodies_spec.rb +14 -13
  28. data/spec/restify/features/request_errors_spec.rb +2 -2
  29. data/spec/restify/features/request_headers_spec.rb +11 -14
  30. data/spec/restify/features/response_errors_spec.rb +2 -2
  31. data/spec/restify/global_spec.rb +13 -13
  32. data/spec/restify/link_spec.rb +9 -9
  33. data/spec/restify/processors/base_spec.rb +7 -7
  34. data/spec/restify/processors/json_spec.rb +22 -62
  35. data/spec/restify/processors/msgpack_spec.rb +40 -76
  36. data/spec/restify/promise_spec.rb +38 -34
  37. data/spec/restify/registry_spec.rb +6 -8
  38. data/spec/restify/relation_spec.rb +196 -17
  39. data/spec/restify/resource_spec.rb +55 -60
  40. data/spec/restify/timeout_spec.rb +7 -7
  41. data/spec/restify_spec.rb +13 -74
  42. data/spec/spec_helper.rb +13 -17
  43. data/spec/support/stub_server.rb +3 -3
  44. metadata +35 -65
  45. data/lib/restify/adapter/em.rb +0 -139
  46. data/lib/restify/adapter/pooled_em.rb +0 -270
@@ -9,11 +9,6 @@ module Restify
9
9
  # Parses generic data structures into resources
10
10
  #
11
11
  module Parsing
12
- def self.included(base)
13
- base.extend ClassMethods
14
- base.indifferent_access = true
15
- end
16
-
17
12
  def load
18
13
  parse deserialized_body, root: true
19
14
  end
@@ -21,18 +16,16 @@ module Restify
21
16
  def parse(object, root: false)
22
17
  case object
23
18
  when Hash
24
- data = object.each_with_object({}, &method(:parse_data))
25
- relations = object.each_with_object({}, &method(:parse_rels))
26
-
27
- data = with_indifferent_access(data) if self.class.indifferent_access?
19
+ data = object.each_with_object({}) {|each, obj| parse_data(each, obj) }
20
+ relations = object.each_with_object({}) {|each, obj| parse_rels(each, obj) }
28
21
 
29
22
  Resource.new context,
30
- data: data,
23
+ data:,
31
24
  response: root ? response : nil,
32
- relations: relations
25
+ relations:
33
26
 
34
27
  when Array
35
- object.map(&method(:parse))
28
+ object.map {|each| parse(each) }
36
29
  else
37
30
  object
38
31
  end
@@ -58,18 +51,6 @@ module Restify
58
51
 
59
52
  relations[name] = pair[1].to_s
60
53
  end
61
-
62
- def with_indifferent_access(data)
63
- Hashie::Mash.new data
64
- end
65
-
66
- module ClassMethods
67
- def indifferent_access?
68
- @indifferent_access
69
- end
70
-
71
- attr_writer :indifferent_access
72
- end
73
54
  end
74
55
  end
75
56
  end
@@ -16,7 +16,7 @@ module Restify
16
16
  @resource ||= begin
17
17
  resource = load
18
18
 
19
- resource = Resource.new context, response: response, data: resource unless resource.is_a? Restify::Resource
19
+ resource = Resource.new context, response:, data: resource unless resource.is_a? Restify::Resource
20
20
 
21
21
  resource._restify_response = response
22
22
  merge_relations! resource._restify_relations
@@ -25,8 +25,8 @@ module Restify
25
25
  self
26
26
  end
27
27
 
28
- def then(&block)
29
- Promise.new([self], &block)
28
+ def then(&)
29
+ Promise.new([self], &)
30
30
  end
31
31
 
32
32
  def execute(timeout = nil)
@@ -7,7 +7,7 @@ module Restify
7
7
  end
8
8
 
9
9
  def store(name, uri, **opts)
10
- @registry[name] = Context.new uri, **opts
10
+ @registry[name] = Context.new(uri, **opts)
11
11
  end
12
12
 
13
13
  def fetch(name)
@@ -19,32 +19,35 @@ module Restify
19
19
  @template = Addressable::Template.new template
20
20
  end
21
21
 
22
- def request(method, data, params, opts = {})
23
- context.request method, expand(params), **opts, data: data
22
+ def request(method:, params: {}, **opts)
23
+ context.request(method, expand(params), **opts)
24
24
  end
25
25
 
26
- def get(params = {}, opts = {})
27
- request :get, nil, params, opts
26
+ def get(data = {}, params: {}, **opts)
27
+ request(**opts, method: :get, params: data.merge(params))
28
28
  end
29
29
 
30
- def head(params = {}, opts = {})
31
- request :head, nil, params, opts
30
+ def head(data = {}, params: {}, **opts)
31
+ request(**opts, method: :head, params: data.merge(params))
32
32
  end
33
33
 
34
- def delete(params = {}, opts = {})
35
- request :delete, nil, params, opts
34
+ def delete(data = {}, params: {}, **opts)
35
+ request(**opts, method: :delete, params: data.merge(params))
36
36
  end
37
37
 
38
- def post(data = {}, params = {}, opts = {})
39
- request :post, data, params, opts
38
+ def post(data = nil, **opts)
39
+ opts[:data] = data unless opts.key?(:data)
40
+ request(**opts, method: :post)
40
41
  end
41
42
 
42
- def put(data = {}, params = {}, opts = {})
43
- request :put, data, params, opts
43
+ def put(data = nil, **opts)
44
+ opts[:data] = data unless opts.key?(:data)
45
+ request(**opts, method: :put)
44
46
  end
45
47
 
46
- def patch(data = {}, params = {}, opts = {})
47
- request :patch, data, params, opts
48
+ def patch(data = nil, **opts)
49
+ opts[:data] = data unless opts.key?(:data)
50
+ request(**opts, method: :patch)
48
51
  end
49
52
 
50
53
  def ==(other)
@@ -72,14 +75,39 @@ module Restify
72
75
  private
73
76
 
74
77
  def convert(params)
75
- params.each_pair.each_with_object({}) do |param, hash|
78
+ params.each_pair.with_object({}) do |param, hash|
76
79
  hash[param[0]] = convert_param param[1]
77
80
  end
78
81
  end
79
82
 
80
- def convert_param(value)
81
- return value.to_param.to_s if value.respond_to?(:to_param)
83
+ def convert_param(value, nesting: true)
84
+ # Convert parameters into values acceptable in a
85
+ # Addressable::Template, with some support for #to_param, but not
86
+ # for basic types.
87
+ if value == nil || # rubocop:disable Style/NilComparison
88
+ value.is_a?(Numeric) ||
89
+ value.is_a?(Symbol) ||
90
+ value.is_a?(Hash) ||
91
+ value == true ||
92
+ value == false ||
93
+ value.respond_to?(:to_str)
94
+ return value
95
+ end
96
+
97
+ # Handle array-link things first to *not* call #to_params on them,
98
+ # as that will concatenation any Array to "a/b/c". Instead, we
99
+ # want to check one level of basic types only.
100
+ if value.respond_to?(:to_ary)
101
+ return nesting ? value.to_ary.map {|val| convert_param(val, nesting: false) } : value
102
+ end
103
+
104
+ # Handle Rails' #to_param for non-basic types
105
+ if value.respond_to?(:to_param)
106
+ return value.to_param
107
+ end
82
108
 
109
+ # Otherwise, pass raw value to Addressable::Template and let it
110
+ # explode.
83
111
  value
84
112
  end
85
113
 
@@ -29,12 +29,12 @@ module Restify
29
29
  #
30
30
  attr_reader :timeout
31
31
 
32
- def initialize(opts = {})
33
- @method = opts.fetch(:method, :get).downcase
34
- @uri = opts.fetch(:uri) { raise ArgumentError.new ':uri required.' }
35
- @data = opts.fetch(:data, nil)
36
- @timeout = opts.fetch(:timeout, 300)
37
- @headers = opts.fetch(:headers, {})
32
+ def initialize(uri:, method: :get, data: nil, timeout: 300, headers: {})
33
+ @uri = uri
34
+ @method = method.to_s.downcase
35
+ @data = data
36
+ @timeout = timeout
37
+ @headers = headers
38
38
 
39
39
  @headers['Content-Type'] ||= 'application/json' if json?
40
40
  end
@@ -96,7 +96,7 @@ module Restify
96
96
  def inspect
97
97
  text = {
98
98
  '@data' => data,
99
- '@relations' => @relations
99
+ '@relations' => @relations,
100
100
  }.map {|k, v| "#{k}=#{v.inspect}" }.join(' ')
101
101
 
102
102
  "#<#{self.class} #{text}>"
@@ -89,7 +89,6 @@ module Restify
89
89
  #
90
90
  # @return [Array<Link>] Links.
91
91
  #
92
- # rubocop:disable Metrics/MethodLength
93
92
  def links
94
93
  @links ||= begin
95
94
  if headers['LINK']
@@ -104,7 +103,6 @@ module Restify
104
103
  end
105
104
  end
106
105
  end
107
- # rubocop:enable all
108
106
 
109
107
  # Return content type header from response headers.
110
108
  #
@@ -71,9 +71,9 @@ module Restify
71
71
  @target = target
72
72
 
73
73
  if @target
74
- super "Operation on #{@target} timed out"
74
+ super("Operation on #{@target} timed out")
75
75
  else
76
- super 'Operation timed out'
76
+ super('Operation timed out')
77
77
  end
78
78
  end
79
79
  end
@@ -2,11 +2,11 @@
2
2
 
3
3
  module Restify
4
4
  module VERSION
5
- MAJOR = 1
6
- MINOR = 15
7
- PATCH = 1
5
+ MAJOR = 2
6
+ MINOR = 0
7
+ PATCH = 0
8
8
  STAGE = nil
9
- STRING = [MAJOR, MINOR, PATCH, STAGE].reject(&:nil?).join('.').freeze
9
+ STRING = [MAJOR, MINOR, PATCH, STAGE].compact.join('.').freeze
10
10
 
11
11
  def self.to_s
12
12
  STRING
data/lib/restify.rb CHANGED
@@ -4,7 +4,6 @@ require 'forwardable'
4
4
 
5
5
  require 'restify/version'
6
6
 
7
- require 'hashie'
8
7
  require 'concurrent'
9
8
  require 'addressable/uri'
10
9
  require 'addressable/template'
@@ -1,36 +1,40 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'spec_helper'
4
+ require 'active_support'
5
+ require 'active_support/cache'
4
6
 
5
7
  describe Restify::Cache do
6
- let(:store) { double 'store' }
7
- let(:cache) { described_class.new store }
8
+ subject(:cache) { described_class.new(store) }
8
9
 
9
- subject { cache }
10
+ let(:store) { instance_double(ActiveSupport::Cache::Store) }
10
11
 
11
12
  describe '#call' do
12
- let(:request) { double 'request' }
13
- let(:promise0) { double 'promise0' }
14
- let(:promise1) { double 'promise1' }
15
- let(:response) { double 'response' }
13
+ let(:request) { instance_double(Restify::Request) }
14
+ let(:promise0) { instance_double(Restify::Promise, 'promise0') } # rubocop:disable RSpec/IndexedLet
15
+ let(:promise1) { instance_double(Restify::Promise, 'promise1') } # rubocop:disable RSpec/IndexedLet
16
+ let(:response) { instance_double(Restify::Response) }
16
17
 
17
18
  it 'yields with promises' do
18
- expect(promise0).to receive(:then).and_yield(response).and_return(promise1)
19
+ allow(promise0).to receive(:then).and_yield(response).and_return(promise1)
19
20
 
20
- expect(subject.call(request) { promise0 }).to eq promise1
21
+ expect(cache.call(request) { promise0 }).to eq promise1
21
22
  end
22
23
 
23
24
  it 'caches new responses' do
24
- expect(promise0).to receive(:then).and_yield(response)
25
+ allow(promise0).to receive(:then).and_yield(response)
26
+
27
+ # TODO: Do not stub inside tested object
25
28
  expect(cache).to receive(:cache).with(response)
26
29
 
27
- subject.call(request) { promise0 }
30
+ cache.call(request) { promise0 }
28
31
  end
29
32
 
30
33
  it 'returns with match' do
34
+ # TODO: Do not stub inside tested object
31
35
  expect(cache).to receive(:match).with(request).and_return(response)
32
36
 
33
- expect(subject.call(request)).to eq response
37
+ expect(cache.call(request)).to eq response
34
38
  end
35
39
  end
36
40
  end
@@ -18,27 +18,30 @@ describe Restify::Context do
18
18
  end
19
19
 
20
20
  describe '#adapter' do
21
- let(:kwargs) { {adapter: double('adapter')} }
22
21
  subject { super().options[:adapter] }
23
22
 
23
+ let(:kwargs) { {adapter: instance_double(Restify::Adapter::Base)} }
24
+
24
25
  it 'adapter is not serialized' do
25
26
  expect(subject).to equal nil
26
27
  end
27
28
  end
28
29
 
29
30
  describe '#cache' do
30
- let(:kwargs) { {adapter: double('cache')} }
31
31
  subject { super().options[:cache] }
32
32
 
33
+ let(:kwargs) { {cache: Object.new} }
34
+
33
35
  it 'cache is not serialized' do
34
36
  expect(subject).to equal nil
35
37
  end
36
38
  end
37
39
 
38
40
  describe '#headers' do
39
- let(:kwargs) { {headers: {'Accept' => 'application/json'}} }
40
41
  subject { super().options[:headers] }
41
42
 
43
+ let(:kwargs) { {headers: {'Accept' => 'application/json'}} }
44
+
42
45
  it 'all headers are serialized' do
43
46
  expect(subject).to eq('Accept' => 'application/json')
44
47
  end
@@ -46,20 +49,25 @@ describe Restify::Context do
46
49
  end
47
50
 
48
51
  context 'YAML' do
52
+ subject { load }
53
+
49
54
  let(:dump) { YAML.dump(context) }
50
- let(:load) { YAML.load(dump) } # rubocop:disable Security/YAMLLoad
51
55
 
52
- subject { load }
56
+ if RUBY_VERSION >= '3.1'
57
+ let(:load) { YAML.safe_load(dump, permitted_classes: [Restify::Context, Symbol]) }
58
+ else
59
+ let(:load) { YAML.load(dump) }
60
+ end
53
61
 
54
62
  include_examples 'serialization'
55
63
  end
56
64
 
57
65
  context 'Marshall' do
66
+ subject { load }
67
+
58
68
  let(:dump) { Marshal.dump(context) }
59
69
  let(:load) { Marshal.load(dump) } # rubocop:disable Security/MarshalLoad
60
70
 
61
- subject { load }
62
-
63
71
  include_examples 'serialization'
64
72
  end
65
73
  end
@@ -2,16 +2,13 @@
2
2
 
3
3
  require 'spec_helper'
4
4
 
5
- describe Restify::ResponseError do
6
- let(:response) { double 'response' }
5
+ describe Restify::ResponseError do # rubocop:disable RSpec/SpecFilePathFormat
6
+ let(:response) { instance_double(Restify::Response) }
7
7
  let(:message) { 'Error' }
8
8
  let(:uri) { 'http://localhost' }
9
9
 
10
10
  before do
11
- allow(response).to receive(:uri).and_return(uri)
12
- allow(response).to receive(:code).and_return(code)
13
- allow(response).to receive(:message).and_return(message)
14
- allow(response).to receive(:decoded_body).and_return({})
11
+ allow(response).to receive_messages(uri: uri, code: code, message: message, decoded_body: {})
15
12
  end
16
13
 
17
14
  describe '.from_code' do
@@ -19,52 +16,62 @@ describe Restify::ResponseError do
19
16
 
20
17
  context 'with 400 Bad Request' do
21
18
  let(:code) { 400 }
22
- it { is_expected.to be_a ::Restify::BadRequest }
19
+
20
+ it { is_expected.to be_a Restify::BadRequest }
23
21
  end
24
22
 
25
23
  context 'with 401 Unauthorized' do
26
24
  let(:code) { 401 }
27
- it { is_expected.to be_a ::Restify::Unauthorized }
25
+
26
+ it { is_expected.to be_a Restify::Unauthorized }
28
27
  end
29
28
 
30
29
  context 'with 404 Unauthorized' do
31
30
  let(:code) { 404 }
32
- it { is_expected.to be_a ::Restify::NotFound }
31
+
32
+ it { is_expected.to be_a Restify::NotFound }
33
33
  end
34
34
 
35
35
  context 'with 406 Not Acceptable' do
36
36
  let(:code) { 406 }
37
- it { is_expected.to be_a ::Restify::NotAcceptable }
37
+
38
+ it { is_expected.to be_a Restify::NotAcceptable }
38
39
  end
39
40
 
40
41
  context 'with 410 Gone' do
41
42
  let(:code) { 410 }
42
- it { is_expected.to be_a ::Restify::Gone }
43
+
44
+ it { is_expected.to be_a Restify::Gone }
43
45
  end
44
46
 
45
47
  context 'with 422 Unprocessable Entity' do
46
48
  let(:code) { 422 }
47
- it { is_expected.to be_a ::Restify::UnprocessableEntity }
49
+
50
+ it { is_expected.to be_a Restify::UnprocessableEntity }
48
51
  end
49
52
 
50
53
  context 'with 500 Internal Server Error' do
51
54
  let(:code) { 500 }
52
- it { is_expected.to be_a ::Restify::InternalServerError }
55
+
56
+ it { is_expected.to be_a Restify::InternalServerError }
53
57
  end
54
58
 
55
59
  context 'with 502 Bad Gateway' do
56
60
  let(:code) { 502 }
57
- it { is_expected.to be_a ::Restify::BadGateway }
61
+
62
+ it { is_expected.to be_a Restify::BadGateway }
58
63
  end
59
64
 
60
65
  context 'with 503 Service Unavailable' do
61
66
  let(:code) { 503 }
62
- it { is_expected.to be_a ::Restify::ServiceUnavailable }
67
+
68
+ it { is_expected.to be_a Restify::ServiceUnavailable }
63
69
  end
64
70
 
65
71
  context 'with 504 Gateway Timeout' do
66
72
  let(:code) { 504 }
67
- it { is_expected.to be_a ::Restify::GatewayTimeout }
73
+
74
+ it { is_expected.to be_a Restify::GatewayTimeout }
68
75
  end
69
76
  end
70
77
  end
@@ -16,24 +16,26 @@ describe Restify do
16
16
  end
17
17
 
18
18
  describe 'HEAD requests' do
19
- subject { Restify.new('http://localhost:9292/base').head(params).value! }
19
+ subject(:value) { Restify.new('http://localhost:9292/base').head(params:).value! }
20
+
20
21
  let(:params) { {} }
21
22
 
22
23
  it 'returns a resource with access to headers' do
23
- expect(subject.response.headers).to include('CONTENT_LENGTH' => '333')
24
+ expect(value.response.headers).to include('CONTENT_LENGTH' => '333')
24
25
  end
25
26
 
26
27
  it 'parses Link headers into relations' do
27
- expect(subject).to have_relation :neat
28
+ expect(value).to have_relation :neat
28
29
  end
29
30
 
30
31
  context 'with params' do
31
32
  let(:params) { {foo: 'bar'} }
32
33
 
33
34
  it 'adds them to the query string' do
34
- subject
35
+ value
36
+
35
37
  expect(
36
- request_stub.with(query: {foo: 'bar'})
38
+ request_stub.with(query: {foo: 'bar'}),
37
39
  ).to have_been_requested
38
40
  end
39
41
  end
@@ -13,25 +13,26 @@ describe Restify do
13
13
  end
14
14
 
15
15
  describe 'Request body' do
16
- subject { Restify.new('http://localhost:9292/base').post(body, {}, {headers: headers}).value! }
16
+ subject(:value) { Restify.new('http://localhost:9292/base').post(body, headers:).value! }
17
+
17
18
  let(:headers) { {} }
18
19
 
19
20
  context 'with JSON-like data structures' do
20
21
  let(:body) { {a: 'b', c: 'd'} }
21
22
 
22
23
  it 'is serialized as JSON' do
23
- subject
24
+ value
24
25
 
25
26
  expect(
26
- request_stub.with(body: '{"a":"b","c":"d"}')
27
+ request_stub.with(body: '{"a":"b","c":"d"}'),
27
28
  ).to have_been_requested
28
29
  end
29
30
 
30
31
  it 'gets a JSON media type for free' do
31
- subject
32
+ value
32
33
 
33
34
  expect(
34
- request_stub.with(headers: {'Content-Type' => 'application/json'})
35
+ request_stub.with(headers: {'Content-Type' => 'application/json'}),
35
36
  ).to have_been_requested
36
37
  end
37
38
 
@@ -39,10 +40,10 @@ describe Restify do
39
40
  let(:headers) { {'Content-Type' => 'application/vnd.api+json'} }
40
41
 
41
42
  it 'respects the override' do
42
- subject
43
+ value
43
44
 
44
45
  expect(
45
- request_stub.with(headers: {'Content-Type' => 'application/vnd.api+json'})
46
+ request_stub.with(headers: {'Content-Type' => 'application/vnd.api+json'}),
46
47
  ).to have_been_requested
47
48
  end
48
49
  end
@@ -52,18 +53,18 @@ describe Restify do
52
53
  let(:body) { 'a=b&c=d' }
53
54
 
54
55
  it 'is sent as provided' do
55
- subject
56
+ value
56
57
 
57
58
  expect(
58
- request_stub.with(body: 'a=b&c=d')
59
+ request_stub.with(body: 'a=b&c=d'),
59
60
  ).to have_been_requested
60
61
  end
61
62
 
62
63
  it 'does not get a JSON media type' do
63
- subject
64
+ value
64
65
 
65
66
  expect(
66
- request_stub.with {|req| req.headers['Content-Type'] !~ /json/ }
67
+ request_stub.with {|req| !req.headers['Content-Type'].include?('json') },
67
68
  ).to have_been_requested
68
69
  end
69
70
 
@@ -71,10 +72,10 @@ describe Restify do
71
72
  let(:headers) { {'Content-Type' => 'application/text'} }
72
73
 
73
74
  it 'respects the override' do
74
- subject
75
+ value
75
76
 
76
77
  expect(
77
- request_stub.with(headers: {'Content-Type' => 'application/text'})
78
+ request_stub.with(headers: {'Content-Type' => 'application/text'}),
78
79
  ).to have_been_requested
79
80
  end
80
81
  end
@@ -2,13 +2,13 @@
2
2
 
3
3
  require 'spec_helper'
4
4
 
5
- describe Restify, adapter: ::Restify::Adapter::Typhoeus do
5
+ describe Restify, adapter: 'Restify::Adapter::Typhoeus' do
6
6
  before do
7
7
  stub_request(:get, 'http://stubserver/base').to_timeout
8
8
  end
9
9
 
10
10
  describe 'Timeout' do
11
- subject(:request) { Restify.new('http://localhost:9292/base').get({}, timeout: 0.1).value! }
11
+ subject(:request) { Restify.new('http://localhost:9292/base').get(timeout: 0.1).value! }
12
12
 
13
13
  it 'throws a network error' do
14
14
  expect { request }.to raise_error Restify::NetworkError do |error|