rester 0.1.0 → 0.2.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 +4 -4
- data/lib/rester/client/adapters/adapter.rb +41 -35
- data/lib/rester/client/adapters/local_adapter.rb +50 -0
- data/lib/rester/client/adapters.rb +3 -2
- data/lib/rester/client.rb +1 -1
- data/lib/rester/errors.rb +2 -0
- data/lib/rester/middleware/error_handling.rb +13 -5
- data/lib/rester/service/object/validator.rb +151 -0
- data/lib/rester/service/object.rb +14 -2
- data/lib/rester/service.rb +1 -1
- data/lib/rester/version.rb +1 -1
- data/lib/rester.rb +5 -1
- metadata +19 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e0666455190175ce3fb1d58396d5504c036a3bd0
|
4
|
+
data.tar.gz: e06f3d2e16a01ef7c2c5161749136d68a95b9fda
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 77529a26c51aa18efbe08814151905556863f41cc79bd45e3804ce1951a436df2786dc053bf445a7c5ad92968cd1fb3a3e170c875052e4dde141c9d864da1dad
|
7
|
+
data.tar.gz: 7f66c1970e7f6c98c462f682df91a79eb5c1687c746d52321b0b1188cb05879064bf5e526ab2a775ffb440fcfc1fcf8456500b04de418a7567843c570a7900fa
|
@@ -28,7 +28,7 @@ module Rester
|
|
28
28
|
def request(verb, path, params={}, &block)
|
29
29
|
params ||= {}
|
30
30
|
_validate_verb(verb)
|
31
|
-
_validate_params(params)
|
31
|
+
params = _validate_params(params)
|
32
32
|
public_send("#{verb}!", path.to_s, params)
|
33
33
|
end
|
34
34
|
|
@@ -62,48 +62,54 @@ module Rester
|
|
62
62
|
delete: true
|
63
63
|
}.freeze
|
64
64
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
65
|
+
##
|
66
|
+
# PARAM_KEY_TRANSFORMERS
|
67
|
+
#
|
68
|
+
# Defines how to transform a key value before being sent to the server.
|
69
|
+
# At the moment, this is a simple to_s conversion.
|
70
|
+
PARAM_KEY_TRANSFORMERS = Hash.new { |_, key|
|
71
|
+
proc { |value|
|
72
|
+
fail ArgumentError, "Invalid param key type: #{key.inspect}"
|
73
|
+
}
|
74
|
+
}.merge(
|
75
|
+
String => :to_s.to_proc,
|
76
|
+
Symbol => :to_s.to_proc
|
77
|
+
).freeze
|
77
78
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
79
|
+
##
|
80
|
+
# PARAM_VALUE_TRANSFORMERS
|
81
|
+
#
|
82
|
+
# Defines how values should be transformed before being sent to the
|
83
|
+
# server. Mostly, this is just a simple conversion to a string, but in
|
84
|
+
# the case of `nil` we want to convert it to 'null'.
|
85
|
+
PARAM_VALUE_TRANSFORMERS = Hash.new { |_, key|
|
86
|
+
proc { |value|
|
87
|
+
fail ArgumentError, "Invalid param value type: #{key.inspect}"
|
88
|
+
}
|
89
|
+
}.merge(
|
90
|
+
String => :to_s.to_proc,
|
91
|
+
Symbol => :to_s.to_proc,
|
92
|
+
Fixnum => :to_s.to_proc,
|
93
|
+
Integer => :to_s.to_proc,
|
94
|
+
Float => :to_s.to_proc,
|
95
|
+
DateTime => :to_s.to_proc,
|
96
|
+
TrueClass => :to_s.to_proc,
|
97
|
+
FalseClass => :to_s.to_proc,
|
98
|
+
NilClass => proc { 'null' }
|
99
|
+
).freeze
|
86
100
|
|
87
101
|
def _validate_verb(verb)
|
88
102
|
VALID_VERBS[verb] or
|
89
103
|
raise ArgumentError, "Invalid verb: #{verb.inspect}"
|
90
104
|
end
|
91
105
|
|
92
|
-
def _validate_args(args)
|
93
|
-
args.each { |arg|
|
94
|
-
VALID_ARG_TYPES[arg.class] or
|
95
|
-
raise ArgumentError, "Invalid argument type: #{arg.inspect}"
|
96
|
-
}
|
97
|
-
end
|
98
|
-
|
99
106
|
def _validate_params(params)
|
100
|
-
params.
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
}
|
107
|
+
params.map { |key, value|
|
108
|
+
[
|
109
|
+
PARAM_KEY_TRANSFORMERS[key.class].call(key),
|
110
|
+
PARAM_VALUE_TRANSFORMERS[value.class].call(value)
|
111
|
+
]
|
112
|
+
}.to_h
|
107
113
|
end
|
108
114
|
end # Adapter
|
109
115
|
end # Client::Adapters
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
|
3
|
+
module Rester
|
4
|
+
module Client::Adapters
|
5
|
+
##
|
6
|
+
# An adapter for "connecting" to a service internally, without needing to
|
7
|
+
# interface over a HTTP connection.
|
8
|
+
class LocalAdapter < Adapter
|
9
|
+
attr_reader :service
|
10
|
+
|
11
|
+
def connect(service)
|
12
|
+
nil.tap { @service = service }
|
13
|
+
end
|
14
|
+
|
15
|
+
def connected?
|
16
|
+
!!service
|
17
|
+
end
|
18
|
+
|
19
|
+
def get!(path, params={})
|
20
|
+
_request(:get, path, headers: headers, query: params)
|
21
|
+
end
|
22
|
+
|
23
|
+
def delete!(path, params={})
|
24
|
+
_request(:delete, path, headers: headers, query: params)
|
25
|
+
end
|
26
|
+
|
27
|
+
def put!(path, params={})
|
28
|
+
_request(:put, path, headers: headers, data: params)
|
29
|
+
end
|
30
|
+
|
31
|
+
def post!(path, params={})
|
32
|
+
_request(:post, path, headers: headers, data: params)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def _request(verb, path, opts={})
|
38
|
+
body = URI.encode_www_form(opts[:data] || {})
|
39
|
+
query = URI.encode_www_form(opts[:query] || {})
|
40
|
+
|
41
|
+
service.call(
|
42
|
+
'REQUEST_METHOD' => verb.to_s.upcase,
|
43
|
+
'PATH_INFO' => path,
|
44
|
+
'QUERY_STRING' => query,
|
45
|
+
'rack.input' => StringIO.new(body)
|
46
|
+
).tap { |r| r.delete_at(1) }
|
47
|
+
end
|
48
|
+
end # LocalAdapter
|
49
|
+
end # Client::Adapters
|
50
|
+
end # Rester
|
@@ -1,8 +1,9 @@
|
|
1
1
|
module Rester
|
2
2
|
class Client
|
3
3
|
module Adapters
|
4
|
-
autoload(:Adapter,
|
5
|
-
autoload(:HttpAdapter,
|
4
|
+
autoload(:Adapter, 'rester/client/adapters/adapter')
|
5
|
+
autoload(:HttpAdapter, 'rester/client/adapters/http_adapter')
|
6
|
+
autoload(:LocalAdapter, 'rester/client/adapters/local_adapter')
|
6
7
|
end
|
7
8
|
end
|
8
9
|
end
|
data/lib/rester/client.rb
CHANGED
@@ -51,7 +51,7 @@ module Rester
|
|
51
51
|
elsif status == 400
|
52
52
|
raise Errors::RequestError, _parse_json(body)[:message]
|
53
53
|
elsif status == 404
|
54
|
-
raise Errors::NotFoundError,
|
54
|
+
raise Errors::NotFoundError, "/#{path}"
|
55
55
|
else
|
56
56
|
raise Errors::ServerError, _parse_json(body)[:message]
|
57
57
|
end
|
data/lib/rester/errors.rb
CHANGED
@@ -21,11 +21,19 @@ module Rester
|
|
21
21
|
private
|
22
22
|
|
23
23
|
def _error_to_response(error)
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
{
|
28
|
-
|
24
|
+
code = _error_to_http_code(error)
|
25
|
+
|
26
|
+
unless code == 404
|
27
|
+
body_h = { message: error.message }
|
28
|
+
|
29
|
+
if code == 500
|
30
|
+
body_h[:error] = error.class.name
|
31
|
+
body_h[:backtrace] = error.backtrace
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
body = body_h ? [JSON.dump(body_h)] : []
|
36
|
+
Rack::Response.new(body, code, { "Content-Type" => "application/json"})
|
29
37
|
end
|
30
38
|
|
31
39
|
def _error_to_http_code(error)
|
@@ -0,0 +1,151 @@
|
|
1
|
+
module Rester
|
2
|
+
class Service::Object
|
3
|
+
class Validator
|
4
|
+
BASIC_TYPES = [String, Symbol, Float, Integer].freeze
|
5
|
+
|
6
|
+
attr_reader :options
|
7
|
+
|
8
|
+
def initialize(opts={})
|
9
|
+
@options = opts.dup.freeze
|
10
|
+
@_required_fields = []
|
11
|
+
@_all_fields = []
|
12
|
+
|
13
|
+
# Default "validator" is to just treat the param as a string.
|
14
|
+
@_validators = Hash.new([String, {}])
|
15
|
+
end
|
16
|
+
|
17
|
+
##
|
18
|
+
# Whether or not validation will be done strictly (i.e., only specified
|
19
|
+
# params will be allowed).
|
20
|
+
def strict?
|
21
|
+
!!options[:strict]
|
22
|
+
end
|
23
|
+
|
24
|
+
def freeze
|
25
|
+
@_validators.freeze
|
26
|
+
@_required_fields.freeze
|
27
|
+
@_all_fields.freeze
|
28
|
+
end
|
29
|
+
|
30
|
+
def required_params
|
31
|
+
@_required_fields.dup
|
32
|
+
end
|
33
|
+
|
34
|
+
def validate(params)
|
35
|
+
param_keys = params.keys.map(&:to_sym)
|
36
|
+
|
37
|
+
unless (missing = @_required_fields - param_keys).empty?
|
38
|
+
_error!("missing params: #{missing.join(', ')}")
|
39
|
+
end
|
40
|
+
|
41
|
+
if strict? && !(unexpected = param_keys - @_all_fields).empty?
|
42
|
+
_error!("unexpected params: #{unexpected.join(', ')}")
|
43
|
+
end
|
44
|
+
|
45
|
+
params.map do |key, value|
|
46
|
+
[key.to_sym, validate!(key.to_sym, value)]
|
47
|
+
end.to_h
|
48
|
+
end
|
49
|
+
|
50
|
+
def validate!(key, value)
|
51
|
+
klass, opts = @_validators[key]
|
52
|
+
|
53
|
+
_parse_with_class(klass, value).tap do |obj|
|
54
|
+
if obj.nil? && @_required_fields.include?(key)
|
55
|
+
_error!("#{key} cannot be null")
|
56
|
+
end
|
57
|
+
|
58
|
+
opts.each do |opt, value|
|
59
|
+
case opt
|
60
|
+
when :within
|
61
|
+
_validate_within(key, obj, value)
|
62
|
+
else
|
63
|
+
_validate_method(key, obj, opt, value) unless obj.nil?
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
##
|
70
|
+
# The basic data types all have helper methods named after them in Kernel.
|
71
|
+
# This allows you to do things like String(1234) to get '1234'. It's the
|
72
|
+
# same as doing 1234.to_s.
|
73
|
+
#
|
74
|
+
# Since methods already exist globally for these types, we need to override
|
75
|
+
# them so we can capture their calls. If this weren't the case, then we'd
|
76
|
+
# be catch them in `method_missing`.
|
77
|
+
BASIC_TYPES.each do |type|
|
78
|
+
define_method(type.to_s) { |name, opts={}|
|
79
|
+
_add_validator(name, type, opts)
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
##
|
84
|
+
# Need to have special handling for Boolean since Ruby doesn't have a
|
85
|
+
# Boolean type, instead it has TrueClass and FalseClass...
|
86
|
+
def Boolean(name, opts={})
|
87
|
+
_add_validator(name, :boolean, opts)
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
def method_missing(meth, *args)
|
93
|
+
meth_str = meth.to_s
|
94
|
+
|
95
|
+
if meth.to_s.match(/\A[A-Z][A-Za-z]+\z/)
|
96
|
+
name = args.shift
|
97
|
+
opts = args.shift || {}
|
98
|
+
_add_validator(name, self.class.const_get(meth), opts)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def _add_validator(name, klass, opts)
|
103
|
+
fail 'must specify param name' unless name
|
104
|
+
fail 'validation options must be a Hash' unless opts.is_a?(Hash)
|
105
|
+
opts = opts.dup
|
106
|
+
@_required_fields << name.to_sym if opts.delete(:required)
|
107
|
+
@_all_fields << name.to_sym
|
108
|
+
@_validators[name.to_sym] = [klass, opts]
|
109
|
+
nil
|
110
|
+
end
|
111
|
+
|
112
|
+
def _parse_with_class(klass, value)
|
113
|
+
return nil if value == 'null'
|
114
|
+
|
115
|
+
if klass == String
|
116
|
+
value
|
117
|
+
elsif klass == Integer
|
118
|
+
value.to_i
|
119
|
+
elsif klass == Float
|
120
|
+
value.to_f
|
121
|
+
elsif klass == Symbol
|
122
|
+
value.to_sym
|
123
|
+
elsif klass == :boolean
|
124
|
+
value.downcase == 'true' ? true : false
|
125
|
+
else
|
126
|
+
klass.parse(value)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def _validate_within(key, obj, value)
|
131
|
+
unless value.include?(obj)
|
132
|
+
_error!("#{key} not within #{value.inspect}")
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def _validate_method(key, obj, opt, value)
|
137
|
+
unless (meth = obj.respond_to?(opt) && obj.method(opt))
|
138
|
+
_error!("#{key} does not respond to #{opt.inspect}")
|
139
|
+
end
|
140
|
+
|
141
|
+
unless meth.call(*value)
|
142
|
+
_error!("#{key} failed #{opt}(#{[value].flatten.join(',')}) validation")
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def _error!(message)
|
147
|
+
Errors.throw_error!(Errors::ValidationError, message)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -4,6 +4,8 @@ require 'active_support/inflector'
|
|
4
4
|
module Rester
|
5
5
|
class Service
|
6
6
|
class Object
|
7
|
+
autoload(:Validator, 'rester/service/object/validator')
|
8
|
+
|
7
9
|
REQUEST_METHOD_TO_INSTANCE_METHOD = {
|
8
10
|
'GET' => :get,
|
9
11
|
'PUT' => :update,
|
@@ -33,7 +35,12 @@ module Rester
|
|
33
35
|
def mount(klass)
|
34
36
|
raise "Only other Service Objects can be mounted." unless klass < Object
|
35
37
|
start = self.name.split('::')[0..-2].join('::').length + 2
|
36
|
-
mounts[klass.name[start..-1].underscore] = klass
|
38
|
+
mounts[klass.name[start..-1].pluralize.underscore] = klass
|
39
|
+
end
|
40
|
+
|
41
|
+
def params(opts={}, &block)
|
42
|
+
(@_validator = Validator.new(opts)).instance_eval(&block)
|
43
|
+
@_validator.freeze
|
37
44
|
end
|
38
45
|
end # DSL
|
39
46
|
|
@@ -53,12 +60,17 @@ module Rester
|
|
53
60
|
(@__mounts ||= {})
|
54
61
|
end
|
55
62
|
|
63
|
+
def validator
|
64
|
+
@_validator ||= Validator.new
|
65
|
+
end
|
66
|
+
|
56
67
|
##
|
57
68
|
# Helper method called at the class and instance level that calls the
|
58
69
|
# specified method on the passed object with the params. Allows for
|
59
70
|
# the arity of the method to be 0, 1 or -1.
|
60
71
|
def process!(obj, meth, params)
|
61
|
-
if obj.respond_to?(meth)
|
72
|
+
if meth && obj.respond_to?(meth)
|
73
|
+
params = validator.validate(params)
|
62
74
|
meth = obj.method(meth)
|
63
75
|
|
64
76
|
case meth.arity.abs
|
data/lib/rester/service.rb
CHANGED
@@ -133,7 +133,7 @@ module Rester
|
|
133
133
|
# If the version is not found, it throws a NotFoundError (HTTP 404).
|
134
134
|
def _validate_version(request)
|
135
135
|
unless self.class.versions.include?(request.version)
|
136
|
-
_error!(Errors::NotFoundError)
|
136
|
+
_error!(Errors::NotFoundError, request.version)
|
137
137
|
end
|
138
138
|
end
|
139
139
|
|
data/lib/rester/version.rb
CHANGED
data/lib/rester.rb
CHANGED
@@ -17,7 +17,11 @@ module Rester
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def connect(*args)
|
20
|
-
|
20
|
+
if (service = args.first).is_a?(Class) && service < Service
|
21
|
+
Client.new(Client::Adapters::LocalAdapter.new(service))
|
22
|
+
else
|
23
|
+
Client.new(*args)
|
24
|
+
end
|
21
25
|
end
|
22
26
|
end # Class Methods
|
23
27
|
end # Rester
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rester
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robert Honer
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2015-10-
|
12
|
+
date: 2015-10-09 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rack
|
@@ -101,6 +101,20 @@ dependencies:
|
|
101
101
|
- - "~>"
|
102
102
|
- !ruby/object:Gem::Version
|
103
103
|
version: 4.0.0
|
104
|
+
- !ruby/object:Gem::Dependency
|
105
|
+
name: bundler-audit
|
106
|
+
requirement: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
type: :development
|
112
|
+
prerelease: false
|
113
|
+
version_requirements: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
104
118
|
description: A framework for creating simple RESTful interfaces between services.
|
105
119
|
email:
|
106
120
|
- robert@payout.com
|
@@ -115,6 +129,7 @@ files:
|
|
115
129
|
- lib/rester/client/adapters/adapter.rb
|
116
130
|
- lib/rester/client/adapters/http_adapter.rb
|
117
131
|
- lib/rester/client/adapters/http_adapter/connection.rb
|
132
|
+
- lib/rester/client/adapters/local_adapter.rb
|
118
133
|
- lib/rester/client/resource.rb
|
119
134
|
- lib/rester/errors.rb
|
120
135
|
- lib/rester/middleware.rb
|
@@ -124,6 +139,7 @@ files:
|
|
124
139
|
- lib/rester/railtie.rb
|
125
140
|
- lib/rester/service.rb
|
126
141
|
- lib/rester/service/object.rb
|
142
|
+
- lib/rester/service/object/validator.rb
|
127
143
|
- lib/rester/service/request.rb
|
128
144
|
- lib/rester/utils.rb
|
129
145
|
- lib/rester/version.rb
|
@@ -147,7 +163,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
147
163
|
version: '0'
|
148
164
|
requirements: []
|
149
165
|
rubyforge_project:
|
150
|
-
rubygems_version: 2.4.
|
166
|
+
rubygems_version: 2.4.6
|
151
167
|
signing_key:
|
152
168
|
specification_version: 4
|
153
169
|
summary: A framework for creating simple RESTful interfaces between services.
|