respondo-grpc 1.0.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.
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RespondoGrpc
4
+ # Maps the HTTP-style codes used by every Respondo helper (404, 422, ...) onto
5
+ # gRPC status codes. The numbers are the canonical ones from the gRPC spec
6
+ # (GRPC::Core::StatusCodes), kept here so this file needs no gRPC dependency.
7
+ #
8
+ # The table follows the usual HTTP <-> gRPC correspondence (see the gRPC
9
+ # status-code docs and Google's API design guide). Codes with no natural gRPC
10
+ # twin are mapped to the closest meaning. Override any of them:
11
+ #
12
+ # RespondoGrpc.configure { |c| c.status_map = { 409 => :aborted } }
13
+ module StatusMap
14
+ CODES = {
15
+ ok: 0,
16
+ cancelled: 1,
17
+ unknown: 2,
18
+ invalid_argument: 3,
19
+ deadline_exceeded: 4,
20
+ not_found: 5,
21
+ already_exists: 6,
22
+ permission_denied: 7,
23
+ resource_exhausted: 8,
24
+ failed_precondition: 9,
25
+ aborted: 10,
26
+ out_of_range: 11,
27
+ unimplemented: 12,
28
+ internal: 13,
29
+ unavailable: 14,
30
+ data_loss: 15,
31
+ unauthenticated: 16
32
+ }.freeze
33
+
34
+ HTTP_TO_GRPC = {
35
+ 400 => :invalid_argument,
36
+ 401 => :unauthenticated,
37
+ 402 => :failed_precondition,
38
+ 403 => :permission_denied,
39
+ 404 => :not_found,
40
+ 405 => :unimplemented,
41
+ 406 => :invalid_argument,
42
+ 407 => :unauthenticated,
43
+ 408 => :deadline_exceeded,
44
+ 409 => :already_exists,
45
+ 410 => :not_found,
46
+ 411 => :invalid_argument,
47
+ 412 => :failed_precondition,
48
+ 413 => :resource_exhausted,
49
+ 414 => :invalid_argument,
50
+ 415 => :invalid_argument,
51
+ 416 => :out_of_range,
52
+ 417 => :failed_precondition,
53
+ 418 => :unknown,
54
+ 421 => :failed_precondition,
55
+ 422 => :invalid_argument,
56
+ 423 => :failed_precondition,
57
+ 424 => :failed_precondition,
58
+ 425 => :failed_precondition,
59
+ 426 => :failed_precondition,
60
+ 428 => :failed_precondition,
61
+ 429 => :resource_exhausted,
62
+ 431 => :resource_exhausted,
63
+ 451 => :permission_denied,
64
+ 500 => :internal,
65
+ 501 => :unimplemented,
66
+ 502 => :unavailable,
67
+ 503 => :unavailable,
68
+ 504 => :deadline_exceeded,
69
+ 505 => :unimplemented,
70
+ 506 => :internal,
71
+ 507 => :resource_exhausted,
72
+ 508 => :internal,
73
+ 510 => :unimplemented,
74
+ 511 => :unauthenticated
75
+ }.freeze
76
+
77
+ module_function
78
+
79
+ # @param http_code [Integer] e.g. 404
80
+ # @return [Integer] gRPC status code, e.g. 5
81
+ def grpc_code_for(http_code)
82
+ name = RespondoGrpc.config.status_map[http_code] || HTTP_TO_GRPC[http_code] || fallback_for(http_code)
83
+ resolve(name)
84
+ end
85
+
86
+ # @param value [Integer, Symbol, String] 5, :not_found or "NOT_FOUND"
87
+ # @return [Integer]
88
+ def resolve(value)
89
+ return value if value.is_a?(Integer)
90
+
91
+ CODES.fetch(value.to_s.downcase.to_sym) do
92
+ raise ArgumentError, "Unknown gRPC status #{value.inspect}. Use one of: #{CODES.keys.join(', ')}"
93
+ end
94
+ end
95
+
96
+ # Name for a gRPC status code, e.g. 5 -> :not_found
97
+ def name_for(grpc_code)
98
+ CODES.key(grpc_code)
99
+ end
100
+
101
+ def fallback_for(http_code)
102
+ case http_code
103
+ when 200..299 then :ok
104
+ when 400..499 then :invalid_argument
105
+ when 500..599 then :internal
106
+ else :unknown
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RespondoGrpc
4
+ VERSION = "1.0.0"
5
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "google/protobuf"
4
+ require "google/protobuf/well_known_types"
5
+
6
+ require_relative "respondo_grpc/version"
7
+ require_relative "respondo_grpc/configuration"
8
+ require_relative "respondo_grpc/status_map"
9
+ require_relative "respondo_grpc/serializer"
10
+ require_relative "respondo_grpc/response_builder"
11
+ # The respondo.v1.Response message (compiled from proto/respondo/v1/response.proto).
12
+ require_relative "respondo/v1/response_pb"
13
+ require_relative "respondo_grpc/proto_mapper"
14
+ require_relative "respondo_grpc/service_helpers"
15
+ require_relative "respondo_grpc/short_helpers"
16
+ require_relative "respondo_grpc/client_helpers"
17
+
18
+ # RespondoGrpc — consistent response envelopes for gRPC services.
19
+ #
20
+ # @example Configure once in an initializer
21
+ # RespondoGrpc.configure do |config|
22
+ # config.include_request_id = true
23
+ # config.default_meta = { api_version: "v1" }
24
+ # config.always_ok = false # failures finish with a real gRPC status
25
+ # end
26
+ #
27
+ # @example Use in a gRPC service
28
+ # class UserService < Example::Users::V1::UserService::Service
29
+ # include RespondoGrpc::ServiceHelpers
30
+ #
31
+ # def get_user(request, call)
32
+ # user = User.find_by(id: request.id)
33
+ # return render_grpc_not_found(message: "User not found", call: call) unless user
34
+ #
35
+ # render_grpc_ok(data: user, message: "User fetched", call: call)
36
+ # end
37
+ # end
38
+ #
39
+ # @example Short names (opt-in)
40
+ # include RespondoGrpc::ShortHelpers # render_ok, render_not_found, ...
41
+ module RespondoGrpc
42
+ # Trailing-metadata key that carries the serialized respondo.v1.Response on a
43
+ # failed call. Keys ending in "-bin" are binary-safe in gRPC.
44
+ METADATA_KEY = "respondo-response-bin"
45
+
46
+ class << self
47
+ # @return [RespondoGrpc::Configuration]
48
+ def config
49
+ @config ||= Configuration.new
50
+ end
51
+
52
+ # @yield [RespondoGrpc::Configuration]
53
+ def configure
54
+ yield config
55
+ end
56
+
57
+ # Reset config (useful in tests)
58
+ def reset!
59
+ @config = Configuration.new
60
+ end
61
+
62
+ # Build the exception that finishes a call with `grpc_code`. Returns the
63
+ # matching subclass (GRPC::NotFound, GRPC::InvalidArgument, ...).
64
+ #
65
+ # @param grpc_code [Integer]
66
+ # @param details [String]
67
+ # @param metadata [Hash]
68
+ # @return [GRPC::BadStatus]
69
+ def bad_status(grpc_code, details, metadata = {})
70
+ require "grpc"
71
+ ::GRPC::BadStatus.new_status_exception(grpc_code, details, metadata)
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,78 @@
1
+ syntax = "proto3";
2
+
3
+ package respondo.v1;
4
+
5
+ import "google/protobuf/any.proto";
6
+ import "google/protobuf/struct.proto";
7
+
8
+ // The response envelope shared by the Respondo gems, as a protobuf message.
9
+ //
10
+ // respondo (JSON) { success, message, data, errors, meta }
11
+ // respondo-graphql { data, errors, extensions }
12
+ // respondo-grpc this message
13
+ //
14
+ // Use it as the return type of any rpc:
15
+ //
16
+ // import "respondo/v1/response.proto";
17
+ //
18
+ // service UserService {
19
+ // rpc GetUser(GetUserRequest) returns (respondo.v1.Response);
20
+ // }
21
+ message Response {
22
+ bool success = 1;
23
+ string message = 2;
24
+
25
+ // JSON-like payload: a hash, array, scalar or null. Built from ActiveRecord
26
+ // records, hashes, arrays, ...
27
+ google.protobuf.Value data = 3;
28
+
29
+ // Typed payload. Set instead of `data` when the server passes a protobuf
30
+ // message, so the client can unpack it into a real class.
31
+ google.protobuf.Any payload = 4;
32
+
33
+ // Field-level errors, e.g. { field: "email", messages: ["is invalid"] }.
34
+ repeated FieldError errors = 5;
35
+
36
+ Meta meta = 6;
37
+ }
38
+
39
+ message FieldError {
40
+ string field = 1;
41
+ repeated string messages = 2;
42
+ }
43
+
44
+ message Meta {
45
+ // Copied from request metadata when config.include_request_id is on.
46
+ string request_id = 1;
47
+
48
+ // ISO 8601, UTC.
49
+ string timestamp = 2;
50
+
51
+ // HTTP-style code the helper stands for (200, 404, 422, ...). Same numbers
52
+ // as the respondo / respondo-graphql gems, so envelopes line up across all three.
53
+ int32 code = 3;
54
+
55
+ // Matching status name, e.g. "not_found".
56
+ string status = 4;
57
+
58
+ // gRPC status code (0 = OK, 5 = NOT_FOUND, ...). Always set on failures; this
59
+ // is the code to read when config.always_ok sends failures as a normal reply.
60
+ int32 grpc_code = 5;
61
+
62
+ // Custom machine-readable code, e.g. "AUTH_EXPIRED". Only set when supplied.
63
+ string error_code = 6;
64
+
65
+ Pagination pagination = 7;
66
+
67
+ // config.default_meta plus any extra keys passed through meta:.
68
+ google.protobuf.Struct extra = 8;
69
+ }
70
+
71
+ message Pagination {
72
+ int32 current_page = 1;
73
+ int32 per_page = 2;
74
+ int32 total_pages = 3;
75
+ int32 total_count = 4;
76
+ optional int32 next_page = 5;
77
+ optional int32 prev_page = 6;
78
+ }
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/respondo_grpc/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "respondo-grpc"
7
+ spec.version = RespondoGrpc::VERSION
8
+ spec.authors = ["shailendra Kumar"]
9
+ spec.email = ["shailendrapatidar00@gmail.com"]
10
+
11
+ spec.summary = "Consistent response envelopes for gRPC services — data, errors, meta, real status codes."
12
+ spec.description = <<~DESC
13
+ RespondoGrpc standardizes the responses your gRPC services send. Every rpc can
14
+ return one protobuf envelope (respondo.v1.Response: success, message, data,
15
+ errors, meta), built from the same expressive helpers as its siblings
16
+ (render_grpc_ok, render_grpc_not_found, render_grpc_unprocessable, ...).
17
+ Failures finish the call with the matching gRPC status code (NOT_FOUND,
18
+ INVALID_ARGUMENT, ...) and carry the envelope in the trailing metadata, so
19
+ clients get both a proper status and structured field errors. Protobuf messages
20
+ travel as typed payloads, hashes and ActiveRecord records as JSON-like data.
21
+ Standalone gem — it does not depend on respondo (JSON) or respondo-graphql.
22
+ DESC
23
+ spec.homepage = "https://github.com/spatelpatidar/respondo-grpc"
24
+ spec.license = "Apache-2.0"
25
+ spec.required_ruby_version = ">= 2.7.0"
26
+
27
+ spec.metadata = {
28
+ "homepage_uri" => spec.homepage,
29
+ "source_code_uri" => spec.homepage,
30
+ "changelog_uri" => "#{spec.homepage}/blob/main/CHANGELOG.md",
31
+ "bug_tracker_uri" => "#{spec.homepage}/issues",
32
+ "rubygems_mfa_required" => "true"
33
+ }
34
+
35
+ spec.files = Dir["lib/**/*.rb", "proto/**/*.proto", "README.md", "LICENSE", "CHANGELOG.md", "respondo-grpc.gemspec"]
36
+ spec.require_paths = ["lib"]
37
+
38
+ spec.add_dependency "google-protobuf", ">= 3.25"
39
+ spec.add_dependency "grpc", ">= 1.60"
40
+
41
+ spec.add_development_dependency "grpc-tools", ">= 1.60"
42
+ spec.add_development_dependency "rspec", "~> 3.12"
43
+ spec.add_development_dependency "rake", "~> 13.0"
44
+ spec.add_development_dependency "simplecov", "~> 0.22"
45
+ end
metadata ADDED
@@ -0,0 +1,159 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: respondo-grpc
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - shailendra Kumar
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-11-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: google-protobuf
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '3.25'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '3.25'
27
+ - !ruby/object:Gem::Dependency
28
+ name: grpc
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '1.60'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '1.60'
41
+ - !ruby/object:Gem::Dependency
42
+ name: grpc-tools
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '1.60'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '1.60'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.12'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.12'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '13.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '13.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: simplecov
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.22'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.22'
97
+ description: |
98
+ RespondoGrpc standardizes the responses your gRPC services send. Every rpc can
99
+ return one protobuf envelope (respondo.v1.Response: success, message, data,
100
+ errors, meta), built from the same expressive helpers as its siblings
101
+ (render_grpc_ok, render_grpc_not_found, render_grpc_unprocessable, ...).
102
+ Failures finish the call with the matching gRPC status code (NOT_FOUND,
103
+ INVALID_ARGUMENT, ...) and carry the envelope in the trailing metadata, so
104
+ clients get both a proper status and structured field errors. Protobuf messages
105
+ travel as typed payloads, hashes and ActiveRecord records as JSON-like data.
106
+ Standalone gem — it does not depend on respondo (JSON) or respondo-graphql.
107
+ email:
108
+ - shailendrapatidar00@gmail.com
109
+ executables: []
110
+ extensions: []
111
+ extra_rdoc_files: []
112
+ files:
113
+ - CHANGELOG.md
114
+ - LICENSE
115
+ - README.md
116
+ - lib/respondo-grpc.rb
117
+ - lib/respondo/v1/response_pb.rb
118
+ - lib/respondo_grpc.rb
119
+ - lib/respondo_grpc/client_helpers.rb
120
+ - lib/respondo_grpc/configuration.rb
121
+ - lib/respondo_grpc/proto_mapper.rb
122
+ - lib/respondo_grpc/response_builder.rb
123
+ - lib/respondo_grpc/serializer.rb
124
+ - lib/respondo_grpc/service_helpers.rb
125
+ - lib/respondo_grpc/short_helpers.rb
126
+ - lib/respondo_grpc/status_map.rb
127
+ - lib/respondo_grpc/version.rb
128
+ - proto/respondo/v1/response.proto
129
+ - respondo-grpc.gemspec
130
+ homepage: https://github.com/spatelpatidar/respondo-grpc
131
+ licenses:
132
+ - Apache-2.0
133
+ metadata:
134
+ homepage_uri: https://github.com/spatelpatidar/respondo-grpc
135
+ source_code_uri: https://github.com/spatelpatidar/respondo-grpc
136
+ changelog_uri: https://github.com/spatelpatidar/respondo-grpc/blob/main/CHANGELOG.md
137
+ bug_tracker_uri: https://github.com/spatelpatidar/respondo-grpc/issues
138
+ rubygems_mfa_required: 'true'
139
+ post_install_message:
140
+ rdoc_options: []
141
+ require_paths:
142
+ - lib
143
+ required_ruby_version: !ruby/object:Gem::Requirement
144
+ requirements:
145
+ - - ">="
146
+ - !ruby/object:Gem::Version
147
+ version: 2.7.0
148
+ required_rubygems_version: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ requirements: []
154
+ rubygems_version: 3.4.1
155
+ signing_key:
156
+ specification_version: 4
157
+ summary: Consistent response envelopes for gRPC services — data, errors, meta, real
158
+ status codes.
159
+ test_files: []