respondo-graphql 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 479397a32c82bd347b77d8ae9a342abb4b3796a3a6e33919661313a4e7a172dd
4
+ data.tar.gz: 84492ab253009751ffc4f7ffd391cc79576a1af64330eebb54179f31612356f3
5
+ SHA512:
6
+ metadata.gz: 6a967a00d8e92435ebb7f69cf82a43aced84acfe9aafc2a53f2e55c7fb4a447d9d0607d1d19cd091417aba22c67d92b391c4b49c05276bc74a308364b9087d85
7
+ data.tar.gz: '01168e9e5bc5fdb2de915ff285a70c428bc12910ac6d63b03dd91b47ce24319b5e94c6c82f42f31b1cb3b8ae6247ade373e2a91b977f95eb1a05c813ef88a20b'
data/CHANGELOG.md ADDED
@@ -0,0 +1,26 @@
1
+ # Changelog
2
+
3
+ ## [0.1.0] — Initial release
4
+
5
+ First release of `respondo-graphql`, the GraphQL sibling of the `respondo` JSON
6
+ gem. It is a standalone gem — no dependency on `respondo`.
7
+
8
+ ### Added
9
+ - GraphQL-spec response envelope: `errors` (failures only), `data`, `extensions`.
10
+ - 50 status-aware controller helpers with the same names and signatures as
11
+ `respondo`: ten 2xx helpers and forty 4xx/5xx helpers (`render_ok`,
12
+ `render_not_found`, `render_unprocessable`, …). 1xx and 3xx are omitted on
13
+ purpose — they have no meaning in a GraphQL response.
14
+ - Field-level errors become one `errors[]` entry per message, tagged
15
+ `extensions.field`; `errors[0]` is always a summary carrying the message.
16
+ - Configuration: `default_success_message`, `default_error_message`,
17
+ `include_request_id`, `camelize_keys`, `serializer`, `default_meta`,
18
+ `data_key`, `always_ok`, `auto_include`.
19
+ - `always_ok` sends every response with HTTP 200 (Apollo-style clients).
20
+ - `data_key` wraps Array / scalar payloads, since GraphQL `data` must be a map.
21
+ - Railtie that includes the helpers into `ActionController::Base` / `::API`.
22
+ It skips auto-include when the `respondo` JSON gem is loaded (both define the
23
+ same helper names); include `RespondoGraphql::ControllerHelpers` in the
24
+ GraphQL controller instead, where it takes precedence over the parent's helpers.
25
+ - `rails generate respondo_graphql:install` — interactive setup that writes
26
+ `config/initializers/respondo_graphql.rb`.
data/README.md ADDED
@@ -0,0 +1,143 @@
1
+ <div align="center">
2
+
3
+ # RespondoGraphql
4
+
5
+ **Consistent GraphQL-spec responses for Rails controllers — in one line.**
6
+
7
+ </div>
8
+
9
+ ---
10
+
11
+ `respondo-graphql` is the GraphQL sibling of [`respondo`](https://github.com/spatelpatidar/respondo) (JSON). It is a **standalone gem**: it has no dependency on `respondo`, its own namespace (`RespondoGraphql`), its own config, and its own generator. Install either one, or both.
12
+
13
+ It gives your controllers one expressive helper per situation (`render_ok`, `render_not_found`, `render_unprocessable`, …) and always answers in the GraphQL response shape: `data`, `errors`, `extensions`.
14
+
15
+ ## Install
16
+
17
+ ```ruby
18
+ # Gemfile
19
+ gem "respondo-graphql"
20
+ ```
21
+
22
+ ```bash
23
+ bundle install
24
+ rails generate respondo_graphql:install
25
+ ```
26
+
27
+ The Railtie includes the helpers into `ActionController::Base` and `ActionController::API` for you. (If the `respondo` JSON gem is installed too, see [Using both gems](#using-both-gems).)
28
+
29
+ ## Usage
30
+
31
+ ```ruby
32
+ class GraphqlController < ApplicationController
33
+ def show
34
+ user = User.find_by(id: params[:id])
35
+ return render_not_found(message: "User not found") unless user
36
+
37
+ render_ok(data: { user: user }, message: "User fetched")
38
+ end
39
+
40
+ def create
41
+ user = User.new(user_params)
42
+ if user.save
43
+ render_created(data: { user: user }, message: "Account created")
44
+ else
45
+ render_unprocessable(message: "Validation failed", errors: user.errors)
46
+ end
47
+ end
48
+ end
49
+ ```
50
+
51
+ **Success** — `render_ok(data: { user: { id: 1 } }, message: "User fetched")`
52
+
53
+ ```json
54
+ {
55
+ "data": { "user": { "id": 1 } },
56
+ "extensions": { "message": "User fetched", "timestamp": "2026-10-04T10:30:00Z", "code": 200, "status": "ok" }
57
+ }
58
+ ```
59
+
60
+ **Failure** — `data` is `null`, and `errors[0]` is always a summary carrying your message. Field-level errors follow, one entry per message:
61
+
62
+ ```json
63
+ {
64
+ "errors": [
65
+ { "message": "Validation failed",
66
+ "extensions": { "code": "UNPROCESSABLE_CONTENT", "http": { "status": 422 } } },
67
+ { "message": "email is invalid",
68
+ "extensions": { "code": "UNPROCESSABLE_CONTENT", "http": { "status": 422 }, "field": "email" } }
69
+ ],
70
+ "data": null,
71
+ "extensions": { "message": "Validation failed", "timestamp": "2026-10-04T10:30:00Z", "code": 422, "status": "unprocessable_content" }
72
+ }
73
+ ```
74
+
75
+ | What you pass | Where it ends up |
76
+ |---|---|
77
+ | `data:` | `data` — always a map or `null` |
78
+ | `message:` | `extensions.message`, and `errors[0].message` on failure |
79
+ | `meta:` / `pagination:` | `extensions` |
80
+ | `errors: { field: [...] }` | one `errors[]` entry per message, tagged `extensions.field` |
81
+ | success vs. failure | presence of the `errors` array |
82
+
83
+ GraphQL requires `data` to be a map, so an Array or scalar payload is wrapped as `{ "result": [...] }` (rename it with `config.data_key`).
84
+
85
+ ## Helpers
86
+
87
+ Same names and signatures as the `respondo` JSON gem.
88
+
89
+ | Group | Helpers | Arguments |
90
+ |---|---|---|
91
+ | 2xx with payload | `render_ok` `render_created` `render_accepted` `render_non_authoritative` `render_partial_content` `render_multi_status` `render_already_reported` `render_im_used` | `data:` `message:` `meta:` `pagination:` |
92
+ | 2xx no payload | `render_no_content` `render_reset_content` | `message:` `meta:` `pagination:` |
93
+ | 4xx / 5xx | `render_bad_request` `render_unauthorized` `render_forbidden` `render_not_found` `render_conflict` `render_unprocessable` `render_too_many_requests` `render_server_error` `render_service_unavailable` … (40 in total) | `message:` `errors:` `meta:` |
94
+
95
+ 1xx (informational) and 3xx (redirect) helpers are deliberately left out: they are transport-level and have no meaning in a GraphQL response. The full list is in `RespondoGraphql::ControllerHelpers::SUCCESS_HELPERS` and `ERROR_HELPERS`.
96
+
97
+ ## Configuration
98
+
99
+ ```ruby
100
+ # config/initializers/respondo_graphql.rb (written by the generator)
101
+ RespondoGraphql.configure do |config|
102
+ config.default_success_message = "Success"
103
+ config.default_error_message = "An error occurred"
104
+ config.include_request_id = false # adds request_id to extensions
105
+ config.camelize_keys = false # created_at -> createdAt
106
+ config.always_ok = false # see below
107
+ config.data_key = :result # wrapper for Array / scalar data
108
+ config.default_meta = { api_version: "v1" }
109
+ config.serializer = ->(obj) { MySerializer.new(obj).as_json }
110
+ config.auto_include = true # see "Using both gems"
111
+ end
112
+ ```
113
+
114
+ **`always_ok`** — many GraphQL clients, Apollo among them, treat non-2xx responses as network failures. Set `config.always_ok = true` to send every response with HTTP 200 and let the `errors` array carry the failure. The default (`false`) keeps each helper's own status (404 for `render_not_found`, …).
115
+
116
+ ## Using both gems
117
+
118
+ `respondo` and `respondo-graphql` define the same helper names (`render_ok`, …), so they must not both be auto-included into every controller — whichever loaded last would silently win. When the Railtie sees that `respondo` is loaded, it **skips auto-include**. Keep `respondo` for your REST controllers and include the GraphQL helpers where you want them:
119
+
120
+ ```ruby
121
+ class GraphqlController < ApplicationController
122
+ include RespondoGraphql::ControllerHelpers # wins over respondo's helpers in this controller
123
+ end
124
+ ```
125
+
126
+ You can also turn auto-include off yourself with `config.auto_include = false`.
127
+
128
+ ## When to use this vs. graphql-ruby
129
+
130
+ Your GraphQL schema already produces valid responses for queries and mutations. RespondoGraphql is for everything your controller answers *outside* the schema — authentication failures, rate limits, malformed requests, REST-style endpoints that speak GraphQL-shaped JSON — so those responses look the same as the rest.
131
+
132
+ ## Development
133
+
134
+ ```bash
135
+ git clone https://github.com/spatelpatidar/respondo-graphql
136
+ cd respondo-graphql
137
+ bundle install
138
+ bundle exec rspec
139
+ ```
140
+
141
+ ## License
142
+
143
+ Released under the [MIT License](LICENSE.txt).
@@ -0,0 +1,224 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+ require "fileutils"
5
+
6
+ module RespondoGraphql
7
+ module Generators
8
+ # rails generate respondo_graphql:install
9
+ #
10
+ # Asks a few questions and writes config/initializers/respondo_graphql.rb.
11
+ # All output goes through $stdout (never Thor's `say`) so it is not echoed twice.
12
+ class InstallGenerator < Rails::Generators::Base
13
+ desc "Interactive setup — creates config/initializers/respondo_graphql.rb with your preferences."
14
+
15
+ def run_interactive_setup
16
+ out banner
17
+ out blank
18
+
19
+ unless confirm(" Ready to configure RespondoGraphql? (y/n) ")
20
+ out blank
21
+ out line(yellow(" Skipped. Run this again any time:"))
22
+ out line(" rails generate respondo_graphql:install")
23
+ out blank
24
+ return
25
+ end
26
+
27
+ @cfg = {}
28
+
29
+ step_api_version
30
+ step_request_id
31
+ step_camelize
32
+ step_always_ok
33
+
34
+ print_summary
35
+ write_initializer
36
+ print_done
37
+ end
38
+
39
+ private
40
+
41
+ # =========================================================================
42
+ # Steps
43
+ # =========================================================================
44
+
45
+ def step_api_version
46
+ out section("API version")
47
+ out line(" Added to the extensions block of every response (e.g. v1, v2).")
48
+ out blank
49
+ @cfg[:api_version] = prompt_default("v1")
50
+ end
51
+
52
+ def step_request_id
53
+ out section("Request ID")
54
+ out line(" Include Rails request.request_id in every response's extensions —")
55
+ out line(" useful for log correlation and debugging.")
56
+ out blank
57
+ @cfg[:include_request_id] = confirm(" Include request_id in every response? (y/n) ")
58
+ end
59
+
60
+ def step_camelize
61
+ out section("Key Format")
62
+ out line(" CamelCase all response keys:")
63
+ out line(yellow(' { "data": { "createdAt": "...", "userId": 1 } }'))
64
+ out blank
65
+ @cfg[:camelize_keys] = confirm(" Camelize all response keys? (y/n) ")
66
+ end
67
+
68
+ def step_always_ok
69
+ out section("HTTP status")
70
+ out line(" Many GraphQL clients (e.g. Apollo) treat non-2xx responses as network")
71
+ out line(" errors. Answer y to always send HTTP 200 and report failures in `errors`.")
72
+ out line(" Answer n to keep each helper's status (404 for render_not_found, ...).")
73
+ out blank
74
+ @cfg[:always_ok] = confirm(" Always send HTTP 200? (y/n) ")
75
+ end
76
+
77
+ # =========================================================================
78
+ # Summary
79
+ # =========================================================================
80
+
81
+ def print_summary
82
+ out blank
83
+ out divider
84
+ out line(cyan(" Configuration Summary"))
85
+ out divider
86
+ out blank
87
+ summary_row "API version", api_version.empty? ? "(none)" : api_version
88
+ summary_row "Include request_id", @cfg[:include_request_id]
89
+ summary_row "Camelize keys", @cfg[:camelize_keys]
90
+ summary_row "Always HTTP 200", @cfg[:always_ok]
91
+ out blank
92
+ out divider
93
+ out blank
94
+ end
95
+
96
+ def summary_row(label, value)
97
+ shown = value == true ? green(value.inspect) : yellow(value.is_a?(String) ? value : value.inspect)
98
+ out " #{"#{label}:".ljust(24)}#{shown}\n"
99
+ end
100
+
101
+ def api_version
102
+ @cfg[:api_version].to_s
103
+ end
104
+
105
+ # =========================================================================
106
+ # Write file
107
+ # =========================================================================
108
+
109
+ def write_initializer
110
+ dir = File.join(destination_root, "config", "initializers")
111
+ path = File.join(dir, "respondo_graphql.rb")
112
+ FileUtils.mkdir_p(dir)
113
+ File.write(path, build_content)
114
+ out line(green(" ✅ Created config/initializers/respondo_graphql.rb"))
115
+ out blank
116
+ end
117
+
118
+ def build_content
119
+ lines = []
120
+ lines << "# frozen_string_literal: true"
121
+ lines << ""
122
+ lines << "# RespondoGraphql initializer"
123
+ lines << "# Generated by: rails generate respondo_graphql:install"
124
+ lines << "# RespondoGraphql version: #{RespondoGraphql::VERSION}"
125
+ lines << ""
126
+ lines << "RespondoGraphql.configure do |config|"
127
+ lines << " # Fallback messages when a helper is called without message:"
128
+ lines << " config.default_success_message = \"Success\""
129
+ lines << " config.default_error_message = \"An error occurred\""
130
+ lines << ""
131
+ lines << " # Include Rails request.request_id in every response's extensions."
132
+ lines << " config.include_request_id = #{@cfg[:include_request_id] ? true : false}"
133
+ lines << ""
134
+ lines << " # CamelCase all response keys."
135
+ lines << " config.camelize_keys = #{@cfg[:camelize_keys] ? true : false}"
136
+ lines << ""
137
+ lines << " # Send every response with HTTP 200; failures are reported in `errors`."
138
+ lines << " config.always_ok = #{@cfg[:always_ok] ? true : false}"
139
+ lines << ""
140
+ lines << " # GraphQL `data` must be a map: Array / scalar payloads are wrapped under this key."
141
+ lines << " config.data_key = :result"
142
+ lines << ""
143
+ lines << " # Static fields merged into the extensions block of every response."
144
+ lines << (api_version.empty? ? " config.default_meta = {}" : " config.default_meta = { api_version: #{api_version.inspect} }")
145
+ lines << ""
146
+ lines << " # Custom serializer — receives the payload, returns a Hash/Array."
147
+ lines << " # config.serializer = ->(obj) { MySerializer.new(obj).as_json }"
148
+ lines << ""
149
+ lines << " # Helpers are included into your controllers automatically. This is skipped"
150
+ lines << " # when the respondo (JSON) gem is also installed — include"
151
+ lines << " # RespondoGraphql::ControllerHelpers in your GraphQL controller instead."
152
+ lines << " # config.auto_include = false"
153
+ lines << "end"
154
+ lines << ""
155
+ lines.join("\n")
156
+ end
157
+
158
+ # =========================================================================
159
+ # Done
160
+ # =========================================================================
161
+
162
+ def print_done
163
+ out divider
164
+ out blank
165
+ out line(cyan(" 🎉 RespondoGraphql is ready!"))
166
+ out blank
167
+ out line(" Next steps:")
168
+ out line(" 1. Review config/initializers/respondo_graphql.rb")
169
+ out line(" 2. Use render_ok / render_not_found / render_unprocessable ... in your controllers")
170
+ out line(" 3. Re-run rails generate respondo_graphql:install to regenerate")
171
+ out blank
172
+ out divider
173
+ out blank
174
+ end
175
+
176
+ # =========================================================================
177
+ # Output primitives — all output goes through $stdout, never through Thor
178
+ # =========================================================================
179
+
180
+ def out(str)
181
+ $stdout.print str
182
+ $stdout.flush
183
+ end
184
+
185
+ def line(str)
186
+ "#{str}\n"
187
+ end
188
+
189
+ def blank
190
+ "\n"
191
+ end
192
+
193
+ def divider
194
+ " #{cyan("─" * 68)}\n"
195
+ end
196
+
197
+ def section(title)
198
+ dashes = "─" * [0, 54 - title.length].max
199
+ "\n #{cyan("┌─ #{title} #{dashes}┐")}\n\n"
200
+ end
201
+
202
+ def banner
203
+ green("\n RespondoGraphql — GraphQL-spec responses for Rails (v#{RespondoGraphql::VERSION})\n")
204
+ end
205
+
206
+ def prompt_default(default)
207
+ $stdout.print " #{cyan("›")} #{yellow("[#{default}]")}: "
208
+ $stdout.flush
209
+ result = $stdin.gets.to_s.strip
210
+ result.empty? ? default.to_s : result
211
+ end
212
+
213
+ def confirm(question)
214
+ $stdout.print question
215
+ $stdout.flush
216
+ $stdin.gets.to_s.strip.downcase.start_with?("y")
217
+ end
218
+
219
+ def cyan(text) = "\e[36m#{text}\e[0m"
220
+ def green(text) = "\e[32m#{text}\e[0m"
221
+ def yellow(text) = "\e[33m#{text}\e[0m"
222
+ end
223
+ end
224
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Lets `gem "respondo-graphql"` be auto-required by Bundler.require
4
+ # (Bundler requires the gem's own name, hyphen included).
5
+ require_relative "respondo_graphql"
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RespondoGraphql
4
+ # Global configuration for RespondoGraphql.
5
+ #
6
+ # @example
7
+ # RespondoGraphql.configure do |config|
8
+ # config.default_success_message = "OK"
9
+ # config.include_request_id = true
10
+ # config.camelize_keys = true
11
+ # config.always_ok = true
12
+ # end
13
+ class Configuration
14
+ # Message used when none is supplied to a success helper
15
+ attr_accessor :default_success_message
16
+
17
+ # Message used when none is supplied to an error helper
18
+ attr_accessor :default_error_message
19
+
20
+ # When true, includes request.request_id in every extensions block (Rails only)
21
+ attr_accessor :include_request_id
22
+
23
+ # When true, all response keys are camelized (e.g. created_at -> createdAt)
24
+ attr_accessor :camelize_keys
25
+
26
+ # Custom serializer callable — receives (object) and returns a Hash/Array.
27
+ # Defaults to nil (built-in serialization strategy is used).
28
+ # @example config.serializer = ->(obj) { MySerializer.new(obj).as_json }
29
+ attr_accessor :serializer
30
+
31
+ # Static key-value pairs merged into every response's `extensions` block.
32
+ # @example config.default_meta = { api_version: "v1" }
33
+ attr_accessor :default_meta
34
+
35
+ # GraphQL requires `data` to be a map, so a payload that is an Array or a
36
+ # scalar is wrapped as { <data_key> => payload }. Hash payloads pass through.
37
+ attr_accessor :data_key
38
+
39
+ # When true every response is sent with HTTP 200, whatever the helper's
40
+ # status, and failures are reported through the `errors` array. Many GraphQL
41
+ # clients (e.g. Apollo) treat non-2xx responses as network failures.
42
+ # Defaults to false so helper status codes keep meaning what they say.
43
+ attr_accessor :always_ok
44
+
45
+ # When true (default) the Railtie includes the helpers into
46
+ # ActionController::Base / ::API, so no `include` is needed. It is skipped
47
+ # automatically when the respondo (JSON) gem is also loaded, because both
48
+ # gems define the same helper names — include RespondoGraphql::ControllerHelpers
49
+ # in your GraphQL controller instead.
50
+ attr_accessor :auto_include
51
+
52
+ def initialize
53
+ @default_success_message = "Success"
54
+ @default_error_message = "An error occurred"
55
+ @include_request_id = false
56
+ @camelize_keys = false
57
+ @serializer = nil
58
+ @default_meta = {}
59
+ @data_key = :result
60
+ @always_ok = false
61
+ @auto_include = true
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,188 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RespondoGraphql
4
+ # Mixed into Rails controllers to render GraphQL-spec responses with
5
+ # expressive, status-aware helpers.
6
+ #
7
+ # Success helpers (2xx) — accept data:, message:, meta:, pagination:
8
+ # render_ok, render_created, render_accepted, render_non_authoritative,
9
+ # render_partial_content, render_multi_status, render_already_reported,
10
+ # render_im_used
11
+ #
12
+ # Success helpers (2xx) without a payload — accept message:, meta:, pagination:
13
+ # render_no_content, render_reset_content
14
+ #
15
+ # Error helpers (4xx / 5xx) — accept message:, errors:, meta:
16
+ # render_bad_request, render_unauthorized, render_payment_required,
17
+ # render_forbidden, render_not_found, render_method_not_allowed,
18
+ # render_not_acceptable, render_proxy_auth_required, render_request_timeout,
19
+ # render_conflict, render_gone, render_length_required,
20
+ # render_precondition_failed, render_payload_too_large, render_uri_too_long,
21
+ # render_unsupported_media_type, render_range_not_satisfiable,
22
+ # render_expectation_failed, render_im_a_teapot, render_misdirected_request,
23
+ # render_unprocessable, render_locked, render_failed_dependency,
24
+ # render_too_early, render_upgrade_required, render_precondition_required,
25
+ # render_too_many_requests, render_request_header_fields_too_large,
26
+ # render_unavailable_for_legal_reasons, render_server_error,
27
+ # render_not_implemented, render_bad_gateway, render_service_unavailable,
28
+ # render_gateway_timeout, render_http_version_not_supported,
29
+ # render_variant_also_negotiates, render_insufficient_storage,
30
+ # render_loop_detected, render_not_extended,
31
+ # render_network_authentication_required
32
+ #
33
+ # 1xx (informational) and 3xx (redirect) helpers are intentionally not
34
+ # provided: they are transport-level and have no meaning in a GraphQL response.
35
+ module ControllerHelpers
36
+ # name => [numeric code, Rails status symbol, default message, takes data?]
37
+ SUCCESS_HELPERS = {
38
+ ok: { code: 200, status: :ok, message: nil, data: true },
39
+ created: { code: 201, status: :created, message: "Created successfully", data: true },
40
+ accepted: { code: 202, status: :accepted, message: "Request accepted and is being processed", data: true },
41
+ non_authoritative: { code: 203, status: :non_authoritative_information, message: "Non-authoritative information", data: true },
42
+ no_content: { code: 204, status: :no_content, message: "Deleted successfully", data: false },
43
+ reset_content: { code: 205, status: :reset_content, message: "Reset content", data: false },
44
+ partial_content: { code: 206, status: :partial_content, message: "Partial content returned", data: true },
45
+ multi_status: { code: 207, status: :multi_status, message: "Multi-status response", data: true },
46
+ already_reported: { code: 208, status: :already_reported, message: "Already reported", data: true },
47
+ im_used: { code: 226, status: :im_used, message: "IM used", data: true }
48
+ }.freeze
49
+
50
+ # name => [numeric code, Rails status symbol, default message]
51
+ ERROR_HELPERS = {
52
+ bad_request: { code: 400, status: :bad_request, message: "Bad request" },
53
+ unauthorized: { code: 401, status: :unauthorized, message: "Unauthorized" },
54
+ payment_required: { code: 402, status: :payment_required, message: "Payment required to access this resource" },
55
+ forbidden: { code: 403, status: :forbidden, message: "You do not have permission to perform this action" },
56
+ not_found: { code: 404, status: :not_found, message: "Resource not found" },
57
+ method_not_allowed: { code: 405, status: :method_not_allowed, message: "HTTP method not allowed" },
58
+ not_acceptable: { code: 406, status: :not_acceptable, message: "Requested format not acceptable" },
59
+ proxy_auth_required: { code: 407, status: :proxy_authentication_required, message: "Proxy authentication required" },
60
+ request_timeout: { code: 408, status: :request_timeout, message: "Request timed out" },
61
+ conflict: { code: 409, status: :conflict, message: "Resource conflict" },
62
+ gone: { code: 410, status: :gone, message: "Resource no longer available" },
63
+ length_required: { code: 411, status: :length_required, message: "Content-Length header required" },
64
+ precondition_failed: { code: 412, status: :precondition_failed, message: "Precondition failed" },
65
+ payload_too_large: { code: 413, status: :payload_too_large, message: "Payload too large" },
66
+ uri_too_long: { code: 414, status: :uri_too_long, message: "URI too long" },
67
+ unsupported_media_type: { code: 415, status: :unsupported_media_type, message: "Unsupported media type" },
68
+ range_not_satisfiable: { code: 416, status: :range_not_satisfiable, message: "Range not satisfiable" },
69
+ expectation_failed: { code: 417, status: :expectation_failed, message: "Expectation failed" },
70
+ im_a_teapot: { code: 418, status: :im_a_teapot, message: "I'm a teapot" },
71
+ misdirected_request: { code: 421, status: :misdirected_request, message: "Misdirected request" },
72
+ unprocessable: { code: 422, status: :unprocessable_content, message: "Validation failed" },
73
+ locked: { code: 423, status: :locked, message: "Resource is locked" },
74
+ failed_dependency: { code: 424, status: :failed_dependency, message: "Failed dependency" },
75
+ too_early: { code: 425, status: :too_early, message: "Too early" },
76
+ upgrade_required: { code: 426, status: :upgrade_required, message: "Upgrade required" },
77
+ precondition_required: { code: 428, status: :precondition_required, message: "Precondition required" },
78
+ too_many_requests: { code: 429, status: :too_many_requests, message: "Too many requests. Please slow down." },
79
+ request_header_fields_too_large: { code: 431, status: :request_header_fields_too_large, message: "Request header fields too large" },
80
+ unavailable_for_legal_reasons: { code: 451, status: :unavailable_for_legal_reasons, message: "Unavailable for legal reasons" },
81
+ server_error: { code: 500, status: :internal_server_error, message: "An unexpected error occurred" },
82
+ not_implemented: { code: 501, status: :not_implemented, message: "This feature is not yet implemented" },
83
+ bad_gateway: { code: 502, status: :bad_gateway, message: "Bad gateway — upstream service error" },
84
+ service_unavailable: { code: 503, status: :service_unavailable, message: "Service temporarily unavailable" },
85
+ gateway_timeout: { code: 504, status: :gateway_timeout, message: "Gateway timeout — upstream service did not respond" },
86
+ http_version_not_supported: { code: 505, status: :http_version_not_supported, message: "HTTP version not supported" },
87
+ variant_also_negotiates: { code: 506, status: :variant_also_negotiates, message: "Variant also negotiates" },
88
+ insufficient_storage: { code: 507, status: :insufficient_storage, message: "Insufficient storage" },
89
+ loop_detected: { code: 508, status: :loop_detected, message: "Loop detected" },
90
+ not_extended: { code: 510, status: :not_extended, message: "Not extended" },
91
+ network_authentication_required: { code: 511, status: :network_authentication_required, message: "Network authentication required" }
92
+ }.freeze
93
+
94
+ SUCCESS_HELPERS.each do |name, spec|
95
+ default_message = spec[:message]
96
+
97
+ if spec[:data]
98
+ # e.g. render_ok(data: @user, message: "User fetched", meta: {}, pagination: nil)
99
+ define_method(:"render_#{name}") do |data: nil, message: default_message, meta: {}, pagination: nil|
100
+ render_success(data: data, message: message, meta: meta, pagination: pagination,
101
+ code: spec[:code], status: spec[:status])
102
+ end
103
+ else
104
+ # e.g. render_no_content(message: "Deleted", meta: {}, pagination: nil)
105
+ define_method(:"render_#{name}") do |message: default_message, meta: {}, pagination: nil|
106
+ render_success(data: nil, message: message, meta: meta, pagination: pagination,
107
+ code: spec[:code], status: spec[:status])
108
+ end
109
+ end
110
+ end
111
+
112
+ ERROR_HELPERS.each do |name, spec|
113
+ default_message = spec[:message]
114
+
115
+ # e.g. render_not_found(message: "User not found", errors: nil, meta: {})
116
+ define_method(:"render_#{name}") do |message: default_message, errors: nil, meta: {}|
117
+ render_error(message: message, errors: errors, meta: meta, code: spec[:code], status: spec[:status])
118
+ end
119
+ end
120
+
121
+ private
122
+
123
+ # =========================================================================
124
+ # Core DSL — every helper delegates to these two
125
+ # =========================================================================
126
+
127
+ # Render a successful GraphQL response.
128
+ #
129
+ # @param data [Object] payload — AR model, collection, Hash, Array, nil
130
+ # @param message [String] human-readable description (goes in extensions)
131
+ # @param meta [Hash] extra fields merged into extensions
132
+ # @param pagination [Hash, nil] pagination hash built by the caller
133
+ # @param code [Integer, nil] numeric HTTP code recorded in extensions
134
+ # @param status [Symbol, Integer] HTTP status (default: :ok / 200)
135
+ def render_success(data: nil, message: nil, meta: {}, code: nil, pagination: nil, status: :ok)
136
+ merged_meta = code ? meta.merge(code: code, status: status) : meta
137
+
138
+ payload = ResponseBuilder.new(
139
+ success: true,
140
+ data: data,
141
+ message: message,
142
+ meta: merged_meta,
143
+ pagination: pagination,
144
+ request: try(:request)
145
+ ).build
146
+
147
+ render json: payload, status: respondo_graphql_status(status)
148
+ end
149
+
150
+ # Render a failed GraphQL response: data is null, `errors` explains why.
151
+ #
152
+ # @param message [String] human-readable error description
153
+ # @param errors [Hash, ActiveModel::Errors] field-level errors
154
+ # @param code [Integer, String, nil] HTTP code (Integer) or machine-readable code (String)
155
+ # @param meta [Hash] extra fields merged into extensions
156
+ # @param status [Symbol, Integer] HTTP status (default: :unprocessable_content / 422)
157
+ def render_error(message: nil, errors: nil, code: nil, meta: {}, status: :unprocessable_content)
158
+ merged_meta = code ? meta.merge(code: code, status: status) : meta
159
+
160
+ payload = ResponseBuilder.new(
161
+ success: false,
162
+ data: nil,
163
+ message: message,
164
+ meta: merged_meta,
165
+ errors: extract_errors(errors),
166
+ request: try(:request)
167
+ ).build
168
+
169
+ render json: payload, status: respondo_graphql_status(status)
170
+ end
171
+
172
+ # With config.always_ok every response is sent as HTTP 200.
173
+ def respondo_graphql_status(status)
174
+ RespondoGraphql.config.always_ok ? :ok : status
175
+ end
176
+
177
+ # Normalize errors into a plain Hash regardless of source type.
178
+ def extract_errors(errors)
179
+ return nil if errors.nil?
180
+ return errors.to_hash if defined?(ActiveModel::Errors) && errors.is_a?(ActiveModel::Errors)
181
+ return errors if errors.is_a?(Hash)
182
+ return { base: errors } if errors.is_a?(Array)
183
+ return { base: [errors] } if errors.is_a?(String)
184
+
185
+ nil
186
+ end
187
+ end
188
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RespondoGraphql
4
+ # Includes RespondoGraphql::ControllerHelpers into ActionController::Base and
5
+ # ActionController::API so no `include` is needed in ApplicationController.
6
+ #
7
+ # Skipped when config.auto_include is false, or when the respondo (JSON) gem
8
+ # is also loaded — both gems define the same helper names, so the last one
9
+ # included would silently win in every controller. In that case include
10
+ # RespondoGraphql::ControllerHelpers in your GraphQL controller yourself; a
11
+ # module included in a subclass takes precedence over its parent's helpers.
12
+ class Railtie < Rails::Railtie
13
+ initializer "respondo_graphql.include_controller_helpers" do
14
+ ActiveSupport.on_load(:action_controller_base) do
15
+ include RespondoGraphql::ControllerHelpers if RespondoGraphql.auto_include?
16
+ end
17
+
18
+ ActiveSupport.on_load(:action_controller_api) do
19
+ include RespondoGraphql::ControllerHelpers if RespondoGraphql.auto_include?
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,167 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "time"
4
+
5
+ module RespondoGraphql
6
+ # Builds a GraphQL-spec response envelope.
7
+ #
8
+ # {
9
+ # errors: [ { message:, extensions: { code:, http: { status: } } } ], # only on failure
10
+ # data: Hash | nil,
11
+ # extensions: { message:, timestamp:, ...meta }
12
+ # }
13
+ #
14
+ # On failure `data` is null and `errors` always starts with a summary entry
15
+ # carrying the helper's message, followed by one entry per field-level error,
16
+ # so `errors[0].message` is always safe to show to a user.
17
+ #
18
+ # The extensions block always contains the message and an ISO8601 timestamp,
19
+ # plus request_id (config.include_request_id), config.default_meta,
20
+ # caller-supplied meta, pagination, and the helper's code/status.
21
+ class ResponseBuilder
22
+ # @param success [Boolean]
23
+ # @param data [Object] anything — serialized automatically
24
+ # @param message [String]
25
+ # @param meta [Hash] caller-supplied extra extensions (merged in)
26
+ # @param errors [Hash] field-level errors, e.g. { email: ["is invalid"] }
27
+ # @param pagination [Hash, nil] plain pagination hash supplied by the caller
28
+ # @param request [Object] ActionDispatch::Request (for request_id)
29
+ def initialize(success:, data: nil, message: nil, meta: {}, errors: nil,
30
+ pagination: nil, request: nil)
31
+ @success = success
32
+ @raw_data = data
33
+ @message = message
34
+ @extra_meta = meta || {}
35
+ @errors = errors
36
+ @pagination = pagination
37
+ @request = request
38
+ end
39
+
40
+ # @return [Hash] the complete GraphQL response payload
41
+ def build
42
+ payload = {}
43
+
44
+ errors = build_errors
45
+ payload[:errors] = errors unless errors.empty?
46
+
47
+ payload[:data] = build_data
48
+ payload[:extensions] = { message: resolve_message }.merge(build_meta)
49
+
50
+ apply_camelize(payload)
51
+ end
52
+
53
+ private
54
+
55
+ def resolve_message
56
+ return @message if @message && !@message.empty?
57
+
58
+ @success ? RespondoGraphql.config.default_success_message : RespondoGraphql.config.default_error_message
59
+ end
60
+
61
+ # GraphQL requires `data` to be a map (or null when execution failed).
62
+ def build_data
63
+ return nil unless @success
64
+
65
+ data = Serializer.call(@raw_data)
66
+ return data if data.nil? || data.is_a?(Hash)
67
+
68
+ { RespondoGraphql.config.data_key.to_sym => data }
69
+ end
70
+
71
+ def build_errors
72
+ return [] if @success
73
+
74
+ [{ message: resolve_message, extensions: error_extensions }] + field_error_entries
75
+ end
76
+
77
+ # One entry per field message, e.g. { email: ["is invalid"] } becomes
78
+ # { message: "email is invalid", extensions: { code: ..., field: "email" } }
79
+ def field_error_entries
80
+ return [] unless @errors.is_a?(Hash)
81
+
82
+ @errors.flat_map do |field, messages|
83
+ Array(messages).map do |text|
84
+ label = field.to_s == "base" ? text.to_s : "#{field} #{text}"
85
+ { message: label, extensions: error_extensions.merge(field: field.to_s) }
86
+ end
87
+ end
88
+ end
89
+
90
+ def error_extensions
91
+ extensions = { code: error_code }
92
+ extensions[:http] = { status: http_status } if http_status
93
+ extensions
94
+ end
95
+
96
+ # A String `code:` passed to render_error is already machine-readable and
97
+ # is used as-is; otherwise the status symbol is used, e.g. :not_found
98
+ # becomes "NOT_FOUND". Falls back to "ERROR" when neither is available.
99
+ def error_code
100
+ custom = @extra_meta[:code]
101
+ return custom if custom.is_a?(String) && !custom.empty?
102
+
103
+ status = @extra_meta[:status]
104
+ status.is_a?(Symbol) || status.is_a?(String) ? status.to_s.upcase : "ERROR"
105
+ end
106
+
107
+ # The numeric HTTP status the helper stands for, when known.
108
+ def http_status
109
+ [@extra_meta[:code], @extra_meta[:status]].find { |value| value.is_a?(Integer) }
110
+ end
111
+
112
+ def build_meta
113
+ config = RespondoGraphql.config
114
+ meta = {}
115
+
116
+ # 1. Request ID first (opt-in)
117
+ meta[:request_id] = @request.request_id if config.include_request_id && @request.respond_to?(:request_id)
118
+
119
+ # 2. Timestamp second
120
+ meta[:timestamp] = current_timestamp
121
+
122
+ # 3. Global defaults (lowest priority), then caller-supplied meta
123
+ meta.merge!(config.default_meta)
124
+ meta.merge!(@extra_meta)
125
+
126
+ # 4. Pagination — plain hash from the caller
127
+ meta[:pagination] = @pagination if @pagination.is_a?(Hash) && !@pagination.empty?
128
+
129
+ # 5. Re-pin code and status to the very end
130
+ code = meta.delete(:code)
131
+ status = meta.delete(:status)
132
+ meta[:code] = code if code
133
+ meta[:status] = status if status
134
+
135
+ meta
136
+ end
137
+
138
+ def current_timestamp
139
+ if Time.respond_to?(:current)
140
+ Time.current.iso8601
141
+ else
142
+ Time.now.utc.iso8601
143
+ end
144
+ end
145
+
146
+ def apply_camelize(hash)
147
+ return hash unless RespondoGraphql.config.camelize_keys
148
+
149
+ camelize_hash(hash)
150
+ end
151
+
152
+ def camelize_hash(obj)
153
+ case obj
154
+ when Hash
155
+ obj.each_with_object({}) { |(key, value), memo| memo[camelize_key(key)] = camelize_hash(value) }
156
+ when Array
157
+ obj.map { |item| camelize_hash(item) }
158
+ else
159
+ obj
160
+ end
161
+ end
162
+
163
+ def camelize_key(key)
164
+ key.to_s.gsub(/_([a-z])/) { Regexp.last_match(1).upcase }.to_sym
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RespondoGraphql
4
+ # Responsible for converting any Ruby object into a JSON-safe Hash or Array.
5
+ #
6
+ # Priority order:
7
+ # 1. Custom serializer from RespondoGraphql.config (if set)
8
+ # 2. ActiveRecord::Base / ActiveRecord::Relation
9
+ # 3. ActiveModel::Errors / objects responding to #errors
10
+ # 4. Objects responding to #as_json or #to_h
11
+ # 5. Arrays — each element is serialized recursively
12
+ # 6. Primitives — returned as-is
13
+ module Serializer
14
+ module_function
15
+
16
+ # @param object [Object] anything — AR model, collection, error, hash, array, primitive
17
+ # @return [Hash, Array, String, Numeric, nil]
18
+ def call(object)
19
+ return nil if object.nil?
20
+
21
+ # 1. Custom serializer wins
22
+ if RespondoGraphql.config.serializer
23
+ return RespondoGraphql.config.serializer.call(object)
24
+ end
25
+
26
+ # 2. ActiveRecord::Relation or any object with #map (lazy collections)
27
+ if ar_relation?(object)
28
+ return object.map { |record| serialize_record(record) }
29
+ end
30
+
31
+ # 3. Array — recurse
32
+ if object.is_a?(Array)
33
+ return object.map { |item| call(item) }
34
+ end
35
+
36
+ # 4. ActiveRecord model instance
37
+ if ar_model?(object)
38
+ return serialize_record(object)
39
+ end
40
+
41
+ # 5. ActiveModel::Errors object
42
+ if active_model_errors?(object)
43
+ return serialize_errors(object)
44
+ end
45
+
46
+ # 6. Exception / StandardError
47
+ if object.is_a?(Exception)
48
+ return { message: object.message }
49
+ end
50
+
51
+ # 7. Hash — recursively serialize values
52
+ if object.is_a?(Hash)
53
+ return object.transform_values { |v| call(v) }
54
+ end
55
+
56
+ # 8. Responds to #as_json (ActiveSupport)
57
+ if object.respond_to?(:as_json)
58
+ return object.as_json
59
+ end
60
+
61
+ # 9. Responds to #to_h
62
+ if object.respond_to?(:to_h)
63
+ return object.to_h
64
+ end
65
+
66
+ # 10. Primitive — return as-is
67
+ object
68
+ end
69
+
70
+ private
71
+
72
+ module_function
73
+
74
+ def ar_relation?(object)
75
+ defined?(ActiveRecord::Relation) &&
76
+ object.is_a?(ActiveRecord::Relation)
77
+ end
78
+
79
+ def ar_model?(object)
80
+ defined?(ActiveRecord::Base) &&
81
+ object.is_a?(ActiveRecord::Base)
82
+ end
83
+
84
+ def active_model_errors?(object)
85
+ defined?(ActiveModel::Errors) &&
86
+ object.is_a?(ActiveModel::Errors)
87
+ end
88
+
89
+ def serialize_record(record)
90
+ if record.respond_to?(:as_json)
91
+ record.as_json
92
+ elsif record.respond_to?(:to_h)
93
+ record.to_h
94
+ else
95
+ record
96
+ end
97
+ end
98
+
99
+ def serialize_errors(errors)
100
+ # ActiveModel::Errors — return { field: ["msg", ...] }
101
+ if errors.respond_to?(:to_hash)
102
+ errors.to_hash
103
+ elsif errors.respond_to?(:messages)
104
+ errors.messages
105
+ else
106
+ {}
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RespondoGraphql
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "respondo_graphql/version"
4
+ require_relative "respondo_graphql/configuration"
5
+ require_relative "respondo_graphql/serializer"
6
+ require_relative "respondo_graphql/response_builder"
7
+ require_relative "respondo_graphql/controller_helpers"
8
+
9
+ # RespondoGraphql — consistent GraphQL-spec responses for Rails controllers.
10
+ #
11
+ # @example Configure once in an initializer
12
+ # # config/initializers/respondo_graphql.rb
13
+ # RespondoGraphql.configure do |config|
14
+ # config.camelize_keys = true
15
+ # config.always_ok = true # HTTP 200 for every response
16
+ # end
17
+ #
18
+ # @example Use in a controller
19
+ # class GraphqlController < ApplicationController
20
+ # def show
21
+ # render_ok(data: { user: User.find(params[:id]) }, message: "User fetched")
22
+ # end
23
+ # end
24
+ #
25
+ # @example Include manually (outside Rails, or next to the respondo JSON gem)
26
+ # class GraphqlController < ApplicationController
27
+ # include RespondoGraphql::ControllerHelpers
28
+ # end
29
+ module RespondoGraphql
30
+ class << self
31
+ # @return [RespondoGraphql::Configuration]
32
+ def config
33
+ @config ||= Configuration.new
34
+ end
35
+
36
+ # @yield [RespondoGraphql::Configuration]
37
+ def configure
38
+ yield config
39
+ end
40
+
41
+ # Reset config (useful in tests)
42
+ def reset!
43
+ @config = Configuration.new
44
+ end
45
+
46
+ # Whether the Railtie should include the helpers into the base controllers.
47
+ # False when the user turned it off, or when the respondo (JSON) gem is
48
+ # loaded too (both define the same helper names).
49
+ def auto_include?
50
+ config.auto_include && !defined?(::Respondo::ControllerHelpers) ? true : false
51
+ end
52
+ end
53
+ end
54
+
55
+ # Auto-integrate with Rails if present
56
+ require_relative "respondo_graphql/railtie" if defined?(Rails::Railtie)
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/respondo_graphql/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "respondo-graphql"
7
+ spec.version = RespondoGraphql::VERSION
8
+ spec.authors = ["shailendra Kumar"]
9
+ spec.email = ["shailendrapatidar00@gmail.com"]
10
+
11
+ spec.summary = "Consistent GraphQL-spec responses for Rails controllers — data, errors, extensions."
12
+ spec.description = <<~DESC
13
+ RespondoGraphql standardizes the GraphQL-style responses your Rails
14
+ controllers send: data, errors and extensions, built from one set of
15
+ expressive helpers (render_ok, render_not_found, render_unprocessable, ...).
16
+ Field errors become spec-compliant errors[] entries, message and meta live in
17
+ extensions, and HTTP statuses can optionally be pinned to 200.
18
+ Standalone gem — it does not depend on the respondo (JSON) gem.
19
+ DESC
20
+ spec.homepage = "https://github.com/spatelpatidar/respondo-graphql"
21
+ spec.license = "MIT"
22
+ spec.required_ruby_version = ">= 3.0.0"
23
+
24
+ spec.metadata = {
25
+ "homepage_uri" => spec.homepage,
26
+ "source_code_uri" => spec.homepage,
27
+ "changelog_uri" => "#{spec.homepage}/blob/main/CHANGELOG.md",
28
+ "bug_tracker_uri" => "#{spec.homepage}/issues",
29
+ "rubygems_mfa_required" => "true"
30
+ }
31
+
32
+ spec.files = Dir["lib/**/*.rb", "README.md", "LICENSE.txt", "CHANGELOG.md", "respondo-graphql.gemspec"]
33
+ spec.require_paths = ["lib"]
34
+
35
+ spec.add_development_dependency "railties", "~> 8.1.3"
36
+ spec.add_development_dependency "rspec", "~> 3.12"
37
+ spec.add_development_dependency "rake", "~> 13.0"
38
+ spec.add_development_dependency "activesupport", "~> 8.1.3"
39
+ spec.add_development_dependency "simplecov", "~> 0.22"
40
+ end
metadata ADDED
@@ -0,0 +1,136 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: respondo-graphql
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - shailendra Kumar
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-10-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: railties
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 8.1.3
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 8.1.3
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.12'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.12'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '13.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '13.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: activesupport
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 8.1.3
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 8.1.3
69
+ - !ruby/object:Gem::Dependency
70
+ name: simplecov
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.22'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.22'
83
+ description: |
84
+ RespondoGraphql standardizes the GraphQL-style responses your Rails
85
+ controllers send: data, errors and extensions, built from one set of
86
+ expressive helpers (render_ok, render_not_found, render_unprocessable, ...).
87
+ Field errors become spec-compliant errors[] entries, message and meta live in
88
+ extensions, and HTTP statuses can optionally be pinned to 200.
89
+ Standalone gem — it does not depend on the respondo (JSON) gem.
90
+ email:
91
+ - shailendrapatidar00@gmail.com
92
+ executables: []
93
+ extensions: []
94
+ extra_rdoc_files: []
95
+ files:
96
+ - CHANGELOG.md
97
+ - README.md
98
+ - lib/generators/respondo_graphql/install/install_generator.rb
99
+ - lib/respondo-graphql.rb
100
+ - lib/respondo_graphql.rb
101
+ - lib/respondo_graphql/configuration.rb
102
+ - lib/respondo_graphql/controller_helpers.rb
103
+ - lib/respondo_graphql/railtie.rb
104
+ - lib/respondo_graphql/response_builder.rb
105
+ - lib/respondo_graphql/serializer.rb
106
+ - lib/respondo_graphql/version.rb
107
+ - respondo-graphql.gemspec
108
+ homepage: https://github.com/spatelpatidar/respondo-graphql
109
+ licenses:
110
+ - MIT
111
+ metadata:
112
+ homepage_uri: https://github.com/spatelpatidar/respondo-graphql
113
+ source_code_uri: https://github.com/spatelpatidar/respondo-graphql
114
+ changelog_uri: https://github.com/spatelpatidar/respondo-graphql/blob/main/CHANGELOG.md
115
+ bug_tracker_uri: https://github.com/spatelpatidar/respondo-graphql/issues
116
+ rubygems_mfa_required: 'true'
117
+ post_install_message:
118
+ rdoc_options: []
119
+ require_paths:
120
+ - lib
121
+ required_ruby_version: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ version: 3.0.0
126
+ required_rubygems_version: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ requirements: []
132
+ rubygems_version: 3.4.10
133
+ signing_key:
134
+ specification_version: 4
135
+ summary: Consistent GraphQL-spec responses for Rails controllers — data, errors, extensions.
136
+ test_files: []