vcap_common 1.0.10 → 2.0.8

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.
data/lib/json_message.rb CHANGED
@@ -1,46 +1,67 @@
1
- # Copyright (c) 2009-2011 VMware, Inc.
1
+ # Copyright (c) 2009-2011 VMware, Inc
2
2
  require 'rubygems'
3
-
4
3
  require 'yajl'
5
-
6
- require 'json_schema'
4
+ require 'membrane'
7
5
 
8
6
  class JsonMessage
9
- # Base error class that all other JsonMessage related errors should inherit from
7
+ # Base error class that all other JsonMessage related errors should
8
+ # inherit from
10
9
  class Error < StandardError
11
10
  end
12
11
 
12
+ # Fields not defined properly.
13
+ class DefinitionError < Error
14
+ end
15
+
13
16
  # Failed to parse json during +decode+
14
17
  class ParseError < Error
15
18
  end
16
19
 
17
20
  # One or more field's values didn't match their schema
18
21
  class ValidationError < Error
19
- def initialize(field_errs)
20
- @field_errs = field_errs
22
+ attr_reader :errors
23
+
24
+ def initialize(errors)
25
+ @errors = errors
21
26
  end
22
27
 
23
28
  def to_s
24
- err_strs = @field_errs.map{|f, e| "Field: #{f}, Error: #{e}"}
29
+ err_strs = @errors.map { |f, e| "Field: #{f}, Error: #{e}" }
25
30
  err_strs.join(', ')
26
31
  end
27
32
  end
28
33
 
29
34
  class Field
30
- attr_reader :name, :schema, :required
35
+ attr_reader :name, :schema, :required, :default
36
+
37
+ def initialize(name, options = {}, &blk)
38
+ blk ||= lambda { |*_| options[:schema] || any }
31
39
 
32
- def initialize(name, schema, required=true)
33
40
  @name = name
34
- @schema = schema.is_a?(JsonSchema) ? schema : JsonSchema.new(schema)
35
- @required = required
41
+ @schema = Membrane::SchemaParser.parse(&blk)
42
+ @required = options[:required] || false
43
+ @default = options[:default]
44
+
45
+ if @required && @default
46
+ raise DefinitionError, \
47
+ "Cannot define a default value for required field #{name}"
48
+ end
49
+
50
+ validate(@default) if @default
51
+ end
52
+
53
+ def validate(value)
54
+ begin
55
+ @schema.validate(value)
56
+ rescue Membrane::SchemaValidationError => e
57
+ raise ValidationError.new( { name => e.message } )
58
+ end
36
59
  end
37
60
  end
38
61
 
39
62
  class << self
40
- attr_reader :fields
41
-
42
- def schema(&blk)
43
- instance_eval &blk
63
+ def fields
64
+ @fields ||= {}
44
65
  end
45
66
 
46
67
  def decode(json)
@@ -58,19 +79,23 @@ class JsonMessage
58
79
 
59
80
  errs = {}
60
81
 
61
- # Treat null values as if the keys aren't present. This isn't as strict as one would like,
62
- # but conforms to typical use cases.
82
+ # Treat null values as if the keys aren't present. This isn't as strict
83
+ # as one would like, but conforms to typical use cases.
63
84
  dec_json.delete_if {|k, v| v == nil}
64
85
 
65
86
  # Collect errors by field
66
- @fields.each do |name, field|
87
+ fields.each do |name, field|
67
88
  err = nil
68
- name_s = name.to_s
69
- if dec_json.has_key?(name_s)
70
- err = field.schema.validate(dec_json[name_s])
89
+ if dec_json.has_key?(name.to_s)
90
+ begin
91
+ field.validate(dec_json[name.to_s])
92
+ rescue ValidationError => e
93
+ err = e.errors[name]
94
+ end
71
95
  elsif field.required
72
96
  err = "Missing field #{name}"
73
97
  end
98
+
74
99
  errs[name] = err if err
75
100
  end
76
101
 
@@ -79,27 +104,27 @@ class JsonMessage
79
104
  new(dec_json)
80
105
  end
81
106
 
82
- def required(field_name, schema=JsonSchema::WILDCARD)
83
- define_field(field_name, schema, true)
107
+ def required(name, schema = nil, &blk)
108
+ define_field(name, :schema => schema, :required => true, &blk)
84
109
  end
85
110
 
86
- def optional(field_name, schema=JsonSchema::WILDCARD)
87
- define_field(field_name, schema, false)
111
+ def optional(name, schema = nil, default = nil, &blk)
112
+ define_field(name, :schema => schema, :default => default, &blk)
88
113
  end
89
114
 
90
115
  protected
91
116
 
92
- def define_field(name, schema, required)
117
+ def define_field(name, options = {}, &blk)
93
118
  name = name.to_sym
94
119
 
95
- @fields ||= {}
96
- @fields[name] = Field.new(name, schema, required)
120
+ fields[name] = Field.new(name, options, &blk)
97
121
 
98
- define_method name.to_sym do
122
+ define_method(name) do
123
+ set_default(name)
99
124
  @msg[name]
100
125
  end
101
126
 
102
- define_method "#{name}=".to_sym do |value|
127
+ define_method("#{name}=") do |value|
103
128
  set_field(name, value)
104
129
  end
105
130
  end
@@ -107,33 +132,61 @@ class JsonMessage
107
132
 
108
133
  def initialize(fields={})
109
134
  @msg = {}
110
- fields.each {|k, v| set_field(k, v)}
135
+ fields.each { |name, value| set_field(name, value) }
136
+ set_defaults
111
137
  end
112
138
 
113
139
  def encode
114
- if self.class.fields
115
- missing_fields = {}
116
- self.class.fields.each do |name, field|
117
- missing_fields[name] = "Missing field #{name}" unless (!field.required || @msg.has_key?(name))
140
+ set_defaults
141
+
142
+ missing_fields = {}
143
+
144
+ self.class.fields.each do |name, field|
145
+ if field.required && !@msg.has_key?(name)
146
+ missing_fields[name] = "Missing field #{name}"
118
147
  end
119
- raise ValidationError.new(missing_fields) unless missing_fields.empty?
120
148
  end
121
149
 
150
+ raise ValidationError.new(missing_fields) unless missing_fields.empty?
151
+
122
152
  Yajl::Encoder.encode(@msg)
123
153
  end
124
154
 
125
- def extract
126
- @msg.dup.freeze
155
+ def extract(opts = {})
156
+ hash = @msg.dup
157
+ if opts[:stringify_keys]
158
+ hash = hash.inject({}) { |memo,(k,v)| memo[k.to_s] = v; memo }.freeze
159
+ end
160
+
161
+ hash.freeze
127
162
  end
128
163
 
129
164
  protected
130
165
 
131
- def set_field(field, value)
132
- field = field.to_sym
133
- raise ValidationError.new({field => "Unknown field #{field}"}) unless self.class.fields.has_key?(field)
166
+ def set_field(name, value)
167
+ name = name.to_sym
168
+ field = self.class.fields[name]
169
+
170
+ unless field
171
+ raise ValidationError.new( { name => "Unknown field: #{name}" } )
172
+ end
134
173
 
135
- errs = self.class.fields[field].schema.validate(value)
136
- raise ValidationError.new({field => errs}) if errs
137
- @msg[field] = value
174
+ field.validate(value)
175
+ @msg[name] = value
176
+ end
177
+
178
+ def set_defaults
179
+ self.class.fields.each do |name, _|
180
+ set_default(name)
181
+ end
182
+ end
183
+
184
+ def set_default(name)
185
+ unless @msg.has_key?(name)
186
+ field = self.class.fields[name]
187
+ if field
188
+ @msg[name] = field.default if field.default
189
+ end
190
+ end
138
191
  end
139
192
  end
data/lib/services/api.rb CHANGED
@@ -3,4 +3,5 @@ require 'services/api/async_requests'
3
3
  require 'services/api/const'
4
4
  require 'services/api/messages'
5
5
  require 'services/api/clients/service_gateway_client'
6
+ require 'services/api/clients/sds_client'
6
7
  require 'services/api/util'
@@ -2,6 +2,7 @@
2
2
  require 'eventmachine'
3
3
  require 'em-http-request'
4
4
  require 'fiber'
5
+ require 'httpclient'
5
6
 
6
7
  require 'services/api/const'
7
8
 
@@ -31,11 +32,54 @@ module VCAP::Services::Api
31
32
  end
32
33
  end
33
34
 
34
- def fibered(url, token, verb, timeout, msg=VCAP::Services::Api::EMPTY_REQUEST)
35
+ def request(url, token, verb, timeout, msg=VCAP::Services::Api::EMPTY_REQUEST)
35
36
  req = new(url, token, verb, timeout, msg)
36
37
  f = Fiber.current
37
38
  req.callback { f.resume(req) }
38
39
  req.errback { f.resume(req) }
40
+ http = Fiber.yield
41
+ raise UnexpectedResponse, "Error sending request #{msg.extract.to_json} to gateway #{@url}: #{http.error}" unless http.error.empty?
42
+ code = http.response_header.status.to_i
43
+ body = http.response
44
+ [code, body]
45
+ end
46
+ end
47
+ end
48
+
49
+ module SynchronousHttpRequest
50
+ def self.request(url, token, verb, timeout, msg=VCAP::Services::Api::EMPTY_REQUEST)
51
+ header = {
52
+ VCAP::Services::Api::GATEWAY_TOKEN_HEADER => token,
53
+ 'Content-Type' => 'application/json',
54
+ }
55
+ body = msg.encode
56
+ client = HTTPClient.new
57
+ msg = client.request(verb.to_sym, url, :body => body, :header => header)
58
+ [msg.code, msg.body]
59
+ end
60
+ end
61
+
62
+ class AsyncHttpMultiPartUpload
63
+ class << self
64
+ def new(url, timeout, multipart, head={})
65
+ req = {
66
+ :head => head,
67
+ :body => "",
68
+ :multipart => multipart
69
+ }
70
+
71
+ if timeout
72
+ EM::HttpRequest.new(url, :inactivity_timeout => timeout).post req
73
+ else
74
+ EM::HttpRequest.new(url).post req
75
+ end
76
+ end
77
+
78
+ def fibered(url, timeout, multipart, head={})
79
+ req = new(url, timeout, multipart, head)
80
+ f = Fiber.current
81
+ req.callback { f.resume(req) }
82
+ req.errback {f.resume(req)}
39
83
  Fiber.yield
40
84
  end
41
85
  end
@@ -0,0 +1,84 @@
1
+ # Copyright (c) 2009-2012 VMware, Inc.
2
+ require 'net/http'
3
+ require 'net/http/post/multipart'
4
+ require 'mime/types'
5
+ require 'uri'
6
+
7
+ require 'services/api/const'
8
+ require 'services/api/messages'
9
+ require 'services/api/multipart'
10
+
11
+ module VCAP
12
+ module Services
13
+ module Api
14
+ end
15
+ end
16
+ end
17
+
18
+ module VCAP::Services::Api
19
+ class SDSClient
20
+
21
+ class SDSErrorResponse < StandardError; end
22
+ class UnexpectedResponse < StandardError; end
23
+
24
+ def initialize(url, upload_token, timeout=60, opts={})
25
+ @url = url
26
+ @timeout = timeout
27
+ @hdrs = {
28
+ 'Content-Type' => 'application/json',
29
+ }
30
+ @upload_hdrs = {
31
+ 'Content-Type' => 'multipart/form-data',
32
+ SDS_UPLOAD_TOKEN_HEADER => upload_token
33
+ }
34
+ end
35
+
36
+ def import_from_data(args)
37
+ resp = perform_multipart_upload("/serialized/#{args[:service]}/#{args[:service_id]}/serialized/data", args[:msg])
38
+ SerializedURL.decode(resp)
39
+ end
40
+
41
+ protected
42
+
43
+ def perform_multipart_upload(path, file_path)
44
+ # upload file using multipart/form data
45
+ result = nil
46
+ uri = URI.parse(@url)
47
+
48
+ mime_types = MIME::Types.type_for(file_path) || []
49
+ mime_types << "application/octet-stream" if mime_types.empty?
50
+
51
+ if EM.reactor_running?
52
+ payload = {:_method => 'put', :data_file => EM::StreamUploadIO.new(file_path, mime_types[0])}
53
+ multipart = EM::Multipart.new(payload, @upload_hdrs)
54
+ url = URI.parse(uri.to_s + path)
55
+ http = AsyncHttpMultiPartUpload.fibered(url, @timeout, multipart)
56
+ raise UnexpectedResponse, "Error uploading #{file_path} to serialized_data_server #{@url}: #{http.error}" unless http.error.empty?
57
+ code = http.response_header.status.to_i
58
+ body = http.response
59
+ else
60
+ payload = {:_method => 'put', :data_file => UploadIO.new(file_path, mime_types[0])}
61
+ req = Net::HTTP::Post::Multipart.new(path, payload, @upload_hdrs)
62
+ resp = Net::HTTP.new(uri.host, uri.port).start do |http|
63
+ http.request(req)
64
+ end
65
+ code = resp.code.to_i
66
+ body = resp.body
67
+ end
68
+ case code
69
+ when 200
70
+ body
71
+ when 400
72
+ raise SDSErrorResponse, "Fail to upload the file to serialization_data_server."
73
+ when 403
74
+ raise SDSErrorResponse, "You are forbidden to access serialization_data_server."
75
+ when 404
76
+ raise SDSErrorResponse, "Not found in serialization_data_server."
77
+ when 501
78
+ raise SDSErrorResponse, "Serialized data file is recognized, but file not found in serialization_data_server."
79
+ else
80
+ raise UnexpectedResponse, "Unexpected exception in serialization_data_server: #{(uri.to_s + path)} #{code} #{body}"
81
+ end
82
+ end
83
+ end
84
+ end
@@ -1,8 +1,10 @@
1
1
  # Copyright (c) 2009-2011 VMware, Inc.
2
2
  require 'net/http'
3
+ require 'uri'
3
4
 
4
5
  require 'services/api/const'
5
6
  require 'services/api/messages'
7
+ require 'services/api/async_requests'
6
8
 
7
9
  module VCAP
8
10
  module Services
@@ -11,97 +13,172 @@ module VCAP
11
13
  end
12
14
  end
13
15
 
14
- class VCAP::Services::Api::ServiceGatewayClient
16
+ module VCAP::Services::Api
17
+ class ServiceGatewayClient
18
+ METHODS_MAP = {
19
+ :get => Net::HTTP::Get,
20
+ :post=> Net::HTTP::Post,
21
+ :put => Net::HTTP::Put,
22
+ :delete => Net::HTTP::Delete,
23
+ }
15
24
 
16
- class UnexpectedResponse < StandardError
17
- attr_reader :response
25
+ # Public: Indicate gateway client encounter an unexpected error,
26
+ # such as can't connect to gateway or can't decode response.
27
+ #
28
+ class UnexpectedResponse < StandardError; end
29
+
30
+ # Public: Indicate an error response from gateway
31
+ #
32
+ class ErrorResponse < StandardError
33
+ attr_reader :status, :error
34
+
35
+ # status - the http status
36
+ # error - a ServiceErrorResponse object
37
+ #
38
+ def initialize(status, error)
39
+ @status = status
40
+ @error = error
41
+ end
42
+
43
+ def to_s
44
+ "Reponse status:#{status},error:[#{error.extract}]"
45
+ end
46
+ end
18
47
 
19
- def initialize(resp)
20
- @response = resp
48
+ class NotFoundResponse < ErrorResponse
49
+ def initialize(error)
50
+ super(404, error)
51
+ end
21
52
  end
22
- end
23
53
 
24
- attr_reader :host, :port, :token
54
+ class GatewayInternalResponse < ErrorResponse
55
+ def initialize(error)
56
+ super(503, error)
57
+ end
58
+ end
25
59
 
26
- def initialize(host, token, port=80)
27
- @host = host
28
- @port = port
29
- @token = token
30
- @hdrs = {
31
- 'Content-Type' => 'application/json',
32
- VCAP::Services::Api::GATEWAY_TOKEN_HEADER => @token
33
- }
34
- end
60
+ attr_reader :host, :port, :token
61
+ attr_reader :requester
62
+ def initialize(url, token, timeout, opts={})
63
+ @url = url
64
+ @timeout = timeout
65
+ @token = token
66
+ @hdrs = {
67
+ 'Content-Type' => 'application/json',
68
+ GATEWAY_TOKEN_HEADER => @token
69
+ }
70
+ # contract: @requester.responds_to? :request(url, token, timeout, [msg])
71
+ # contract @requester.request(url, token, timeout, [msg]) => [code, body]
72
+ @requester = opts[:requester] || AsyncHttpRequest
73
+ end
35
74
 
36
- def provision(args)
37
- msg = VCAP::Services::Api::GatewayProvisionRequest.new(args)
38
- resp = perform_request(Net::HTTP::Post, '/gateway/v1/configurations', msg)
39
- VCAP::Services::Api::GatewayProvisionResponse.decode(resp.body)
40
- end
75
+ def provision(args)
76
+ msg = GatewayProvisionRequest.new(args)
77
+ resp = perform_request(:post, '/gateway/v1/configurations', msg)
78
+ GatewayHandleResponse.decode(resp)
79
+ end
41
80
 
42
- def unprovision(args)
43
- perform_request(Net::HTTP::Delete, "/gateway/v1/configurations/#{args[:service_id]}")
44
- end
81
+ def unprovision(args)
82
+ resp = perform_request(:delete, "/gateway/v1/configurations/#{args[:service_id]}")
83
+ EMPTY_REQUEST
84
+ end
45
85
 
46
- def create_snapshot(args)
47
- resp = perform_request(Net::HTTP::Post, "/gateway/v1/configurations/#{args[:service_id]}/snapshots")
48
- VCAP::Services::Api::Job.decode(resp.body)
49
- end
86
+ def create_snapshot(args)
87
+ resp = perform_request(:post, "/gateway/v1/configurations/#{args[:service_id]}/snapshots")
88
+ Job.decode(resp)
89
+ end
50
90
 
51
- def enum_snapshots(args)
52
- resp = perform_request(Net::HTTP::Get, "/gateway/v1/configurations/#{args[:service_id]}/snapshots")
53
- VCAP::Services::Api::SnapshotList.decode(resp.body)
54
- end
91
+ def enum_snapshots(args)
92
+ resp = perform_request(:get, "/gateway/v1/configurations/#{args[:service_id]}/snapshots")
93
+ SnapshotList.decode(resp)
94
+ end
55
95
 
56
- def snapshot_details(args)
57
- resp = perform_request(Net::HTTP::Get, "/gateway/v1/configurations/#{args[:service_id]}/snapshots/#{args[:snapshot_id]}")
58
- VCAP::Services::Api::Snapshot.decode(resp.body)
59
- end
96
+ def snapshot_details(args)
97
+ resp = perform_request(:get, "/gateway/v1/configurations/#{args[:service_id]}/snapshots/#{args[:snapshot_id]}")
98
+ Snapshot.decode(resp)
99
+ end
60
100
 
61
- def rollback_snapshot(args)
62
- resp = perform_request(Net::HTTP::Put, "/gateway/v1/configurations/#{args[:service_id]}/snapshots/#{args[:snapshot_id]}")
63
- VCAP::Services::Api::Job.decode(resp.body)
64
- end
101
+ def update_snapshot_name(args)
102
+ perform_request(:post, "/gateway/v1/configurations/#{args[:service_id]}/snapshots/#{args[:snapshot_id]}/name", args[:msg])
103
+ EMPTY_REQUEST
104
+ end
65
105
 
66
- def serialized_url(args)
67
- resp = perform_request(Net::HTTP::Get, "/gateway/v1/configurations/#{args[:service_id]}/serialized/url")
68
- VCAP::Services::Api::Job.decode(resp.body)
69
- end
106
+ def rollback_snapshot(args)
107
+ resp = perform_request(:put, "/gateway/v1/configurations/#{args[:service_id]}/snapshots/#{args[:snapshot_id]}")
108
+ Job.decode(resp)
109
+ end
70
110
 
71
- def import_from_url(args)
72
- resp = perform_request(Net::HTTP::Put, "/gateway/v1/configurations/#{args[:service_id]}/serialized/url", args[:msg])
73
- VCAP::Services::Api::Job.decode(resp.body)
74
- end
111
+ def delete_snapshot(args)
112
+ resp = perform_request(:delete, "/gateway/v1/configurations/#{args[:service_id]}/snapshots/#{args[:snapshot_id]}")
113
+ Job.decode(resp)
114
+ end
75
115
 
76
- def import_from_data(args)
77
- resp = perform_request(Net::HTTP::Put, "/gateway/v1/configurations/#{args[:service_id]}/serialized/data", args[:msg])
78
- VCAP::Services::Api::Job.decode(resp.body)
79
- end
116
+ def create_serialized_url(args)
117
+ resp = perform_request(:post, "/gateway/v1/configurations/#{args[:service_id]}/serialized/url/snapshots/#{args[:snapshot_id]}")
118
+ Job.decode(resp)
119
+ end
80
120
 
81
- def job_info(args)
82
- resp = perform_request(Net::HTTP::Get, "/gateway/v1/configurations/#{args[:service_id]}/jobs/#{args[:job_id]}")
83
- VCAP::Services::Api::Job.decode(resp.body)
84
- end
121
+ def serialized_url(args)
122
+ resp = perform_request(:get, "/gateway/v1/configurations/#{args[:service_id]}/serialized/url/snapshots/#{args[:snapshot_id]}")
123
+ SerializedURL.decode(resp)
124
+ end
85
125
 
86
- def bind(args)
87
- msg = VCAP::Services::Api::GatewayBindRequest.new(args)
88
- resp = perform_request(Net::HTTP::Post, "/gateway/v1/configurations/#{msg.service_id}/handles", msg)
89
- VCAP::Services::Api::GatewayBindResponse.decode(resp.body)
90
- end
126
+ def import_from_url(args)
127
+ resp = perform_request(:put, "/gateway/v1/configurations/#{args[:service_id]}/serialized/url", args[:msg])
128
+ Job.decode(resp)
129
+ end
91
130
 
92
- def unbind(args)
93
- msg = VCAP::Services::Api::GatewayUnbindRequest.new(args)
94
- perform_request(Net::HTTP::Delete, "/gateway/v1/configurations/#{msg.service_id}/handles/#{msg.handle_id}", msg)
95
- end
131
+ def job_info(args)
132
+ resp = perform_request(:get, "/gateway/v1/configurations/#{args[:service_id]}/jobs/#{args[:job_id]}")
133
+ Job.decode(resp)
134
+ end
96
135
 
97
- protected
136
+ def bind(args)
137
+ msg = GatewayBindRequest.new(args)
138
+ resp = perform_request(:post, "/gateway/v1/configurations/#{msg.service_id}/handles", msg)
139
+ GatewayHandleResponse.decode(resp)
140
+ end
98
141
 
99
- def perform_request(klass, path, msg=VCAP::Services::Api::EMPTY_REQUEST)
100
- req = klass.new(path, initheader=@hdrs)
101
- req.body = msg.encode
102
- resp = Net::HTTP.new(@host, @port).start {|http| http.request(req)}
103
- raise UnexpectedResponse, resp unless resp.is_a? Net::HTTPOK
104
- resp
105
- end
142
+ def unbind(args)
143
+ msg = GatewayUnbindRequest.new(args)
144
+ perform_request(:delete, "/gateway/v1/configurations/#{msg.service_id}/handles/#{msg.handle_id}", msg)
145
+ EMPTY_REQUEST
146
+ end
106
147
 
148
+ protected
149
+
150
+ def perform_request(http_method, path, msg=VCAP::Services::Api::EMPTY_REQUEST)
151
+ result = nil
152
+ uri = URI.parse(@url)
153
+ if EM.reactor_running?
154
+ url = URI.parse(uri.to_s + path)
155
+ code, body = requester.request(url, @token, http_method, @timeout, msg)
156
+ else
157
+ klass = METHODS_MAP[http_method]
158
+ req = klass.new(path, initheader=@hdrs)
159
+ req.body = msg.encode
160
+ resp = Net::HTTP.new(uri.host, uri.port).start {|http| http.request(req)}
161
+ code = resp.code.to_i
162
+ body = resp.body
163
+ end
164
+ case code
165
+ when 200
166
+ body
167
+ when 404
168
+ err = ServiceErrorResponse.decode(body)
169
+ raise NotFoundResponse.new(err)
170
+ when 503
171
+ err = ServiceErrorResponse.decode(body)
172
+ raise GatewayInternalResponse.new(err)
173
+ else
174
+ begin
175
+ # try to decode the response
176
+ err = ServiceErrorResponse.decode(body)
177
+ rescue => e
178
+ raise UnexpectedResponse, "Can't decode gateway response. status code:#{code}, response body:#{body}"
179
+ end
180
+ raise ErrorResponse.new(code, err)
181
+ end
182
+ end
183
+ end
107
184
  end