restify 1.15.1 → 2.0.0

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