rester 0.1.0 → 0.2.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
  SHA1:
3
- metadata.gz: f6fa57da0afc2d1f97912d9b52df6016e56fa1db
4
- data.tar.gz: 25570c5f460cb04b857003e6956e4b28d7f00968
3
+ metadata.gz: e0666455190175ce3fb1d58396d5504c036a3bd0
4
+ data.tar.gz: e06f3d2e16a01ef7c2c5161749136d68a95b9fda
5
5
  SHA512:
6
- metadata.gz: 3a7ac9757007d8e2bae0d4e31420dd9bc2c418c8bddd000d9e0b258d0161d029282e29963fdf7bcb27eba1340e97231c31af8a752336aaaa3b7d72a4db50c280
7
- data.tar.gz: 207559692ae6d064e1bcd3ec44938e7853197d88a894365ae4220dbdfcfde03b9ad254dca8bdfa2cbb2f58038baa133eb9208edf4cce44efa91df0857ccb631e
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
- VALID_ARG_TYPES = {
66
- String => true,
67
- Symbol => true,
68
- Fixnum => true,
69
- Integer => true,
70
- Float => true
71
- }.freeze
72
-
73
- VALID_PARAM_KEY_TYPES = {
74
- String => true,
75
- Symbol => true
76
- }.freeze
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
- VALID_PARAM_VALUE_TYPES = {
79
- String => true,
80
- Symbol => true,
81
- Fixnum => true,
82
- Integer => true,
83
- Float => true,
84
- DateTime => true
85
- }.freeze
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.each { |key, value|
101
- VALID_PARAM_KEY_TYPES[key.class] or
102
- raise ArgumentError, "Invalid param key type: #{key.inspect}"
103
-
104
- VALID_PARAM_VALUE_TYPES[value.class] or
105
- raise ArgumentError, "Invalid param value type: #{value.inspect}"
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, 'rester/client/adapters/adapter')
5
- autoload(:HttpAdapter, 'rester/client/adapters/http_adapter')
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, _parse_json(body)[:message]
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
@@ -14,6 +14,8 @@ module Rester
14
14
  class MethodError < Error; end
15
15
  class MethodDefinitionError < Error; end
16
16
 
17
+ class ValidationError < Error; end
18
+
17
19
  #############
18
20
  # Http Errors
19
21
  class HttpError < Error; end
@@ -21,11 +21,19 @@ module Rester
21
21
  private
22
22
 
23
23
  def _error_to_response(error)
24
- Rack::Response.new(
25
- [JSON.dump(message: error.message)],
26
- _error_to_http_code(error),
27
- { "Content-Type" => "application/json"}
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
@@ -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
 
@@ -1,3 +1,3 @@
1
1
  module Rester
2
- VERSION = '0.1.0'
2
+ VERSION = '0.2.0'
3
3
  end
data/lib/rester.rb CHANGED
@@ -17,7 +17,11 @@ module Rester
17
17
  end
18
18
 
19
19
  def connect(*args)
20
- Client.new(*args)
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.1.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-04 00:00:00.000000000 Z
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.8
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.