openapi_first 0.16.0 → 0.19.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
  SHA256:
3
- metadata.gz: ac3d5709b36ac797521a16ec11af5fe1106faf2aa2a48daf502516cc875fad47
4
- data.tar.gz: 311e4b513cb5e655397e38cadfec1eff07ae70d1136c75197f87dba8b058073c
3
+ metadata.gz: d09bae8b5126245d86fceeb81b2d0d46fb1a9e1cb10370527327edb3e370fe59
4
+ data.tar.gz: 2bd5e71bd8022506be07ab7c60b2740d490fc0f2a0995ded0f6a9c6dc4d165e3
5
5
  SHA512:
6
- metadata.gz: 85d40c11308a34a3f1a7c9886cf9826d5a36b6e9d8dc15f0a6bf9ae0d435b88f9e16576b339b0dc3b4e2a3c13bc00710fdbe6dd6bdabf1d8aa644f7f34f6cfd7
7
- data.tar.gz: ce475547d199cfabd050e4bdcbf995cecd9a72480b9bfe32e8b84e3287b4be5a95970063542522437362dd65154719c40bb3bc8449b5e4ec82a30c9bfec3f337
6
+ metadata.gz: 4d7d449f19fe40c49ea701eb28aca2a140ec41a5efb5be7433a5a60d85376a139a18c772ff34f211fbfb02e557ddddfdfad8ca547a695cb849bd62b69015775d
7
+ data.tar.gz: 6693234aab12e85893fedfa831ff21c831f00aa4040475a8a6e9fdbc5a2500e094f552ff1efef1c459af4df2eff21ad9f7ac4b6f93b2628f76d5f5169c4ad228
data/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.19.0
4
+
5
+ - Add `RackResponder`
6
+
7
+ - BREAKING CHANGE: Handler classes are now instantiated only once without any arguments and the same instance is called on each following call/request.
8
+
9
+ ## 0.18.0
10
+
11
+ Yanked. No useful changes.
12
+
13
+ ## 0.17.0
14
+
15
+ - BREAKING CHANGE: Use a Hash instead of named arguments for middleware options for better compatibility
16
+ Using named arguments is actually not supported in Rack.
17
+
18
+ ## 0.16.1
19
+
20
+ - Pin hanami-router version, because alpha6 is broken.
21
+
3
22
  ## 0.16.0
4
23
 
5
24
  - Support status code wildcards like "2XX", "4XX"
data/Gemfile.lock CHANGED
@@ -1,11 +1,11 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- openapi_first (0.16.0)
4
+ openapi_first (0.19.0)
5
5
  deep_merge (>= 1.2.1)
6
- hanami-router (~> 2.0.alpha5)
7
- hanami-utils (~> 2.0.alpha3)
8
- json_refs (>= 0.1.7)
6
+ hanami-router (= 2.0.alpha5)
7
+ hanami-utils (= 2.0.alpha3)
8
+ json_refs (~> 0.1, >= 0.1.7)
9
9
  json_schemer (~> 0.2.16)
10
10
  multi_json (~> 1.14)
11
11
  rack (~> 2.2)
@@ -29,33 +29,34 @@ GEM
29
29
  hanami-utils (2.0.0.alpha3)
30
30
  concurrent-ruby (~> 1.0)
31
31
  dry-transformer (~> 0.1)
32
- hansi (0.2.0)
32
+ hansi (0.2.1)
33
+ json (2.6.2)
33
34
  json_refs (0.1.7)
34
35
  hana
35
- json_schemer (0.2.19)
36
+ json_schemer (0.2.21)
36
37
  ecma-re-validator (~> 0.3)
37
38
  hana (~> 1.3)
38
39
  regexp_parser (~> 2.0)
39
40
  uri_template (~> 0.7)
40
41
  method_source (1.0.0)
41
42
  multi_json (1.15.0)
42
- mustermann (1.1.1)
43
+ mustermann (1.1.2)
43
44
  ruby2_keywords (~> 0.0.1)
44
- mustermann-contrib (1.1.1)
45
+ mustermann-contrib (1.1.2)
45
46
  hansi (~> 0.2.0)
46
- mustermann (= 1.1.1)
47
- parallel (1.21.0)
48
- parser (3.1.0.0)
47
+ mustermann (= 1.1.2)
48
+ parallel (1.22.1)
49
+ parser (3.1.2.0)
49
50
  ast (~> 2.4.1)
50
51
  pry (0.14.1)
51
52
  coderay (~> 1.1)
52
53
  method_source (~> 1.0)
53
- rack (2.2.3)
54
+ rack (2.2.4)
54
55
  rack-test (1.1.0)
55
56
  rack (>= 1.0, < 3)
56
57
  rainbow (3.1.1)
57
58
  rake (13.0.6)
58
- regexp_parser (2.2.1)
59
+ regexp_parser (2.5.0)
59
60
  rexml (3.2.5)
60
61
  rspec (3.11.0)
61
62
  rspec-core (~> 3.11.0)
@@ -66,29 +67,31 @@ GEM
66
67
  rspec-expectations (3.11.0)
67
68
  diff-lcs (>= 1.2.0, < 2.0)
68
69
  rspec-support (~> 3.11.0)
69
- rspec-mocks (3.11.0)
70
+ rspec-mocks (3.11.1)
70
71
  diff-lcs (>= 1.2.0, < 2.0)
71
72
  rspec-support (~> 3.11.0)
72
73
  rspec-support (3.11.0)
73
- rubocop (1.25.1)
74
+ rubocop (1.32.0)
75
+ json (~> 2.3)
74
76
  parallel (~> 1.10)
75
77
  parser (>= 3.1.0.0)
76
78
  rainbow (>= 2.2.2, < 4.0)
77
79
  regexp_parser (>= 1.8, < 3.0)
78
- rexml
79
- rubocop-ast (>= 1.15.1, < 2.0)
80
+ rexml (>= 3.2.5, < 4.0)
81
+ rubocop-ast (>= 1.19.1, < 2.0)
80
82
  ruby-progressbar (~> 1.7)
81
83
  unicode-display_width (>= 1.4.0, < 3.0)
82
- rubocop-ast (1.15.2)
83
- parser (>= 3.0.1.1)
84
+ rubocop-ast (1.19.1)
85
+ parser (>= 3.1.1.0)
84
86
  ruby-progressbar (1.11.0)
85
87
  ruby2_keywords (0.0.5)
86
- unicode-display_width (2.1.0)
88
+ unicode-display_width (2.2.0)
87
89
  uri_template (0.7.0)
88
90
 
89
91
  PLATFORMS
90
92
  arm64-darwin-21
91
93
  x86_64-darwin-20
94
+ x86_64-linux
92
95
 
93
96
  DEPENDENCIES
94
97
  bundler (~> 2)
data/README.md CHANGED
@@ -20,7 +20,8 @@ OpenapiFirst consists of these Rack middlewares:
20
20
 
21
21
  - [`OpenapiFirst::Router`](#OpenapiFirst::Router) – Finds the OpenAPI operation for the current request or returns 404 if no operation was found. This can be customized.
22
22
  - [`OpenapiFirst::RequestValidation`](#OpenapiFirst::RequestValidation) – Validates the request against the API description and returns 400 if the request is invalid.
23
- - [`OpenapiFirst::Responder`](#OpenapiFirst::Responder) calls the [handler](#handlers) found for the operation, sets the correct content-type and serialized the response body to json if needed.
23
+ - [`OpenapiFirst::Responder`](#OpenapiFirst::Responder) calls the [handler](#handlers) found for the operation, sets the correct content-type and serializes the response body to json if needed.
24
+ - [`OpenapiFirst::RackResponder`](#OpenapiFirst::RackResponder) calls the [handler](#handlers) found for the operation as a normal Rack application (`call(env)`) and returns the result as is.
24
25
  - [`OpenapiFirst::ResponseValidation`](#OpenapiFirst::ResponseValidation) Validates the response and raises an exception if the response body is invalid.
25
26
 
26
27
  ## OpenapiFirst::Router
@@ -121,6 +122,24 @@ run OpenapiFirst::Responder
121
122
  | `namespace:` | Optional. A class or module where to find the handler method. |
122
123
  | `resolver:` | Optional. An object that responds to `#call(operation)` and returns a [handler](#handlers). By default this is an instance of [DefaultOperationResolver](#OpenapiFirst::DefaultOperationResolver) |
123
124
 
125
+
126
+ ## OpenapiFirst::RackResponder
127
+
128
+ This Rack endpoint maps the HTTP request to a method call based on the [operationId](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#operation-object) in your API description and calls it as a normal Rack application.
129
+ It does not not serialize objects as JSON or adds a content-type.
130
+
131
+ ```ruby
132
+ run OpenapiFirst::RackResponder
133
+ ```
134
+
135
+ ### Options
136
+
137
+ | Name | Description |
138
+ | :----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
139
+ | `namespace:` | Optional. A class or module where to find the handler method. |
140
+ | `resolver:` | Optional. An object that responds to `#call(operation)` and returns a [handler](#handlers). By default this is an instance of [DefaultOperationResolver](#OpenapiFirst::DefaultOperationResolver) |
141
+
142
+
124
143
  ### OpenapiFirst::DefaultOperationResolver
125
144
 
126
145
  This is the default way to look up a handler method for an operation. Handlers are always looked up in a namespace module that needs to be specified.
@@ -129,7 +148,7 @@ It works like this:
129
148
 
130
149
  - An operationId "create_pet" or "createPet" or "create pet" calls `MyApi.create_pet(params, response)`
131
150
  - "some_things.create" calls: `MyApi::SomeThings.create(params, response)`
132
- - "pets#create" calls: `MyApi::Pets::Create.new.call(params, response)` If `MyApi::Pets::Create.new` accepts an argument, it will pass the rack `env`.
151
+ - "pets#create" instantiates the class once (`MyApi::Pets::Create.new) and calls it on every request(`instance.call(params, response)`).
133
152
 
134
153
  ### Handlers
135
154
 
data/benchmarks/Gemfile CHANGED
@@ -7,8 +7,9 @@ gem 'benchmark-memory'
7
7
  gem 'committee'
8
8
  gem 'grape'
9
9
  gem 'hanami-api'
10
- gem 'hanami-router', '~> 2.0.0.alpha5'
10
+ gem 'hanami-router'
11
11
  gem 'multi_json'
12
12
  gem 'openapi_first', path: '../'
13
+ gem 'roda'
13
14
  gem 'sinatra'
14
15
  gem 'syro'
@@ -1,11 +1,11 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- openapi_first (0.15.0)
4
+ openapi_first (0.19.0)
5
5
  deep_merge (>= 1.2.1)
6
- hanami-router (~> 2.0.alpha5)
7
- hanami-utils (~> 2.0.alpha3)
8
- json_refs (>= 0.1.7)
6
+ hanami-router (= 2.0.alpha5)
7
+ hanami-utils (= 2.0.alpha3)
8
+ json_refs (~> 0.1, >= 0.1.7)
9
9
  json_schemer (~> 0.2.16)
10
10
  multi_json (~> 1.14)
11
11
  rack (~> 2.2)
@@ -13,12 +13,12 @@ PATH
13
13
  GEM
14
14
  remote: https://rubygems.org/
15
15
  specs:
16
- activesupport (7.0.2.2)
16
+ activesupport (7.0.2.3)
17
17
  concurrent-ruby (~> 1.0, >= 1.0.2)
18
18
  i18n (>= 1.6, < 2)
19
19
  minitest (>= 5.1)
20
20
  tzinfo (~> 2.0)
21
- benchmark-ips (2.9.3)
21
+ benchmark-ips (2.10.0)
22
22
  benchmark-memory (0.2.0)
23
23
  memory_profiler (~> 1)
24
24
  builder (3.2.4)
@@ -26,7 +26,7 @@ GEM
26
26
  json_schema (~> 0.14, >= 0.14.3)
27
27
  openapi_parser (>= 0.11.1, < 1.0)
28
28
  rack (>= 1.5)
29
- concurrent-ruby (1.1.9)
29
+ concurrent-ruby (1.1.10)
30
30
  deep_merge (1.2.2)
31
31
  dry-configurable (0.14.0)
32
32
  concurrent-ruby (~> 1.0)
@@ -63,16 +63,16 @@ GEM
63
63
  mustermann (~> 1.0)
64
64
  mustermann-contrib (~> 1.0)
65
65
  rack (~> 2.0)
66
- hanami-utils (2.0.0.alpha6)
66
+ hanami-utils (2.0.0.alpha3)
67
67
  concurrent-ruby (~> 1.0)
68
68
  dry-transformer (~> 0.1)
69
69
  hansi (0.2.0)
70
- i18n (1.9.1)
70
+ i18n (1.10.0)
71
71
  concurrent-ruby (~> 1.0)
72
72
  json_refs (0.1.7)
73
73
  hana
74
74
  json_schema (0.21.0)
75
- json_schemer (0.2.18)
75
+ json_schemer (0.2.21)
76
76
  ecma-re-validator (~> 0.3)
77
77
  hana (~> 1.3)
78
78
  regexp_parser (~> 2.0)
@@ -88,18 +88,20 @@ GEM
88
88
  mustermann-grape (1.0.1)
89
89
  mustermann (>= 1.0.0)
90
90
  openapi_parser (0.15.0)
91
- rack (2.2.3)
91
+ rack (2.2.3.1)
92
92
  rack-accept (0.4.5)
93
93
  rack (>= 0.4)
94
- rack-protection (2.1.0)
94
+ rack-protection (2.2.0)
95
+ rack
96
+ regexp_parser (2.5.0)
97
+ roda (3.54.0)
95
98
  rack
96
- regexp_parser (2.2.1)
97
99
  ruby2_keywords (0.0.5)
98
100
  seg (1.2.0)
99
- sinatra (2.1.0)
101
+ sinatra (2.2.0)
100
102
  mustermann (~> 1.0)
101
103
  rack (~> 2.2)
102
- rack-protection (= 2.1.0)
104
+ rack-protection (= 2.2.0)
103
105
  tilt (~> 2.0)
104
106
  syro (3.2.1)
105
107
  rack (>= 1.6.0)
@@ -110,7 +112,8 @@ GEM
110
112
  uri_template (0.7.0)
111
113
 
112
114
  PLATFORMS
113
- x86_64-darwin-20
115
+ arm64-darwin-21
116
+ x86_64-linux
114
117
 
115
118
  DEPENDENCIES
116
119
  benchmark-ips
@@ -118,11 +121,12 @@ DEPENDENCIES
118
121
  committee
119
122
  grape
120
123
  hanami-api
121
- hanami-router (~> 2.0.0.alpha5)
124
+ hanami-router
122
125
  multi_json
123
126
  openapi_first!
127
+ roda
124
128
  sinatra
125
129
  syro
126
130
 
127
131
  BUNDLED WITH
128
- 2.2.28
132
+ 2.3.10
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'roda'
4
+ require 'multi_json'
5
+
6
+ class App < Roda
7
+ route do |r|
8
+ r.on 'hello' do
9
+ r.on :id do
10
+ r.get do
11
+ MultiJson.dump({ hello: 'world', id: r.params[:id] })
12
+ end
13
+ end
14
+
15
+ r.get do
16
+ MultiJson.dump([{ hello: 'world' }])
17
+ end
18
+
19
+ r.post do
20
+ response.status = 201
21
+ MultiJson.dump({ hello: 'world' })
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ run App.freeze.app
@@ -37,9 +37,7 @@ module OpenapiFirst
37
37
  module_name, klass_name = name.split('#')
38
38
  const = find_const(@namespace, module_name)
39
39
  klass = find_const(const, klass_name)
40
- return ->(params, res) { klass.new.call(params, res) } if klass.instance_method(:initialize).arity.zero?
41
-
42
- ->(params, res) { klass.new(params.env).call(params, res) }
40
+ klass.new
43
41
  end
44
42
 
45
43
  def find_const(parent, name)
@@ -47,9 +47,8 @@ module OpenapiFirst
47
47
  end
48
48
  end
49
49
 
50
- def content_type_for(status)
51
- content = response_for(status)['content']
52
- content.keys[0] if content
50
+ def content_types_for(status)
51
+ response_for(status)['content']&.keys
53
52
  end
54
53
 
55
54
  def response_schema_for(status, content_type)
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rack'
4
+ require 'multi_json'
5
+ require_relative 'inbox'
6
+ require_relative 'default_operation_resolver'
7
+
8
+ module OpenapiFirst
9
+ class RackResponder
10
+ def initialize(namespace: nil, resolver: nil)
11
+ @resolver = resolver || DefaultOperationResolver.new(namespace)
12
+ @namespace = namespace
13
+ end
14
+
15
+ def call(env)
16
+ operation = env[OpenapiFirst::OPERATION]
17
+ find_handler(operation)&.call(env)
18
+ end
19
+
20
+ private
21
+
22
+ def find_handler(operation)
23
+ handler = @resolver.call(operation)
24
+ raise NotImplementedError, "Could not find handler for #{operation.name}" unless handler
25
+
26
+ handler
27
+ end
28
+
29
+ def serialize(result)
30
+ return result if result.is_a?(String)
31
+
32
+ MultiJson.dump(result)
33
+ end
34
+ end
35
+ end
@@ -10,9 +10,9 @@ module OpenapiFirst
10
10
  class RequestValidation # rubocop:disable Metrics/ClassLength
11
11
  prepend RouterRequired
12
12
 
13
- def initialize(app, raise_error: false)
13
+ def initialize(app, options = {})
14
14
  @app = app
15
- @raise = raise_error
15
+ @raise = options.fetch(:raise_error, false)
16
16
  end
17
17
 
18
18
  def call(env) # rubocop:disable Metrics/AbcSize
@@ -18,7 +18,7 @@ module OpenapiFirst
18
18
  handler = find_handler(operation)
19
19
  result = handler.call(env[INBOX], res)
20
20
  res.write serialize(result) if result && res.body.empty?
21
- res[Rack::CONTENT_TYPE] ||= operation.content_type_for(res.status)
21
+ res[Rack::CONTENT_TYPE] ||= operation.content_types_for(res.status)&.first
22
22
  res.finish
23
23
  end
24
24
 
@@ -7,15 +7,13 @@ module OpenapiFirst
7
7
  class Router
8
8
  def initialize(
9
9
  app,
10
- spec:,
11
- raise_error: false,
12
- not_found: :halt,
13
- parent_app: nil
10
+ options
14
11
  )
15
12
  @app = app
16
- @parent_app = parent_app
17
- @raise = raise_error
18
- @not_found = not_found
13
+ @parent_app = options.fetch(:parent_app, nil)
14
+ @raise = options.fetch(:raise_error, false)
15
+ @not_found = options.fetch(:not_found, :halt)
16
+ spec = options.fetch(:spec)
19
17
  @filepath = spec.filepath
20
18
  @router = build_router(spec.operations)
21
19
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OpenapiFirst
4
- VERSION = '0.16.0'
4
+ VERSION = '0.19.0'
5
5
  end
data/lib/openapi_first.rb CHANGED
@@ -20,7 +20,7 @@ module OpenapiFirst
20
20
  HANDLER = 'openapi_first.handler'
21
21
 
22
22
  def self.env
23
- ENV['RACK_ENV'] || ENV['HANAMI_ENV'] || ENV['RAILS_ENV']
23
+ ENV['RACK_ENV'] || ENV['HANAMI_ENV'] || ENV.fetch('RAILS_ENV', nil)
24
24
  end
25
25
 
26
26
  def self.load(spec_path, only: nil)
@@ -35,9 +35,9 @@ Gem::Specification.new do |spec|
35
35
  spec.required_ruby_version = '>= 2.6.0'
36
36
 
37
37
  spec.add_runtime_dependency 'deep_merge', '>= 1.2.1'
38
- spec.add_runtime_dependency 'hanami-router', '~> 2.0.alpha5'
39
- spec.add_runtime_dependency 'hanami-utils', '~> 2.0.alpha3'
40
- spec.add_runtime_dependency 'json_refs', '>= 0.1.7'
38
+ spec.add_runtime_dependency 'hanami-router', '2.0.alpha5'
39
+ spec.add_runtime_dependency 'hanami-utils', '2.0.alpha3'
40
+ spec.add_runtime_dependency 'json_refs', '~> 0.1', '>= 0.1.7'
41
41
  spec.add_runtime_dependency 'json_schemer', '~> 0.2.16'
42
42
  spec.add_runtime_dependency 'multi_json', '~> 1.14'
43
43
  spec.add_runtime_dependency 'rack', '~> 2.2'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: openapi_first
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.16.0
4
+ version: 0.19.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andreas Haller
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-04-05 00:00:00.000000000 Z
11
+ date: 2022-08-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: deep_merge
@@ -28,34 +28,37 @@ dependencies:
28
28
  name: hanami-router
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - '='
32
32
  - !ruby/object:Gem::Version
33
33
  version: 2.0.alpha5
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - '='
39
39
  - !ruby/object:Gem::Version
40
40
  version: 2.0.alpha5
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: hanami-utils
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - "~>"
45
+ - - '='
46
46
  - !ruby/object:Gem::Version
47
47
  version: 2.0.alpha3
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - "~>"
52
+ - - '='
53
53
  - !ruby/object:Gem::Version
54
54
  version: 2.0.alpha3
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: json_refs
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.1'
59
62
  - - ">="
60
63
  - !ruby/object:Gem::Version
61
64
  version: 0.1.7
@@ -63,6 +66,9 @@ dependencies:
63
66
  prerelease: false
64
67
  version_requirements: !ruby/object:Gem::Requirement
65
68
  requirements:
69
+ - - "~>"
70
+ - !ruby/object:Gem::Version
71
+ version: '0.1'
66
72
  - - ">="
67
73
  - !ruby/object:Gem::Version
68
74
  version: 0.1.7
@@ -194,6 +200,7 @@ files:
194
200
  - benchmarks/apps/openapi_first.ru
195
201
  - benchmarks/apps/openapi_first_with_hanami_api.ru
196
202
  - benchmarks/apps/openapi_first_with_response_validation.ru
203
+ - benchmarks/apps/roda.ru
197
204
  - benchmarks/apps/sinatra.ru
198
205
  - benchmarks/apps/syro.ru
199
206
  - benchmarks/benchmarks.rb
@@ -210,6 +217,7 @@ files:
210
217
  - lib/openapi_first/definition.rb
211
218
  - lib/openapi_first/inbox.rb
212
219
  - lib/openapi_first/operation.rb
220
+ - lib/openapi_first/rack_responder.rb
213
221
  - lib/openapi_first/request_validation.rb
214
222
  - lib/openapi_first/responder.rb
215
223
  - lib/openapi_first/response_object.rb