protocol-grpc 0.5.1 → 0.7.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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/context/getting-started.md +26 -11
- data/design.md +135 -135
- data/lib/protocol/grpc/body/readable.rb +123 -0
- data/lib/protocol/grpc/body/readable_body.rb +10 -111
- data/lib/protocol/grpc/body/writable.rb +101 -0
- data/lib/protocol/grpc/body/writable_body.rb +10 -89
- data/lib/protocol/grpc/header/message.rb +64 -0
- data/lib/protocol/grpc/header/metadata.rb +22 -0
- data/lib/protocol/grpc/header/status.rb +85 -0
- data/lib/protocol/grpc/header.rb +3 -107
- data/lib/protocol/grpc/interface.rb +60 -4
- data/lib/protocol/grpc/methods.rb +1 -1
- data/lib/protocol/grpc/version.rb +1 -1
- data/lib/protocol/grpc.rb +2 -2
- data.tar.gz.sig +0 -0
- metadata +7 -2
- metadata.gz.sig +0 -0
data/lib/protocol/grpc/header.rb
CHANGED
|
@@ -4,120 +4,16 @@
|
|
|
4
4
|
# Copyright, 2025, by Samuel Williams.
|
|
5
5
|
|
|
6
6
|
require "protocol/http"
|
|
7
|
-
require "uri"
|
|
8
7
|
|
|
9
8
|
require_relative "status"
|
|
9
|
+
require_relative "header/status"
|
|
10
|
+
require_relative "header/message"
|
|
11
|
+
require_relative "header/metadata"
|
|
10
12
|
|
|
11
13
|
module Protocol
|
|
12
14
|
module GRPC
|
|
13
15
|
# @namespace
|
|
14
16
|
module Header
|
|
15
|
-
# The `grpc-status` header represents the gRPC status code.
|
|
16
|
-
#
|
|
17
|
-
# The `grpc-status` header contains a numeric status code (0-16) indicating the result of the RPC call.
|
|
18
|
-
# Status code 0 indicates success (OK), while other codes indicate various error conditions.
|
|
19
|
-
# This header can appear both as an initial header (for trailers-only responses) and as a trailer.
|
|
20
|
-
class Status
|
|
21
|
-
# Initialize the status header with the given value.
|
|
22
|
-
#
|
|
23
|
-
# @parameter value [String, Integer, Array] The status code as a string, integer, or array (takes first element).
|
|
24
|
-
def initialize(value)
|
|
25
|
-
@value = normalize_value(value)
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
# Get the status code as an integer.
|
|
29
|
-
#
|
|
30
|
-
# @returns [Integer] The status code.
|
|
31
|
-
def to_i
|
|
32
|
-
@value
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
# Serialize the status code to a string.
|
|
36
|
-
#
|
|
37
|
-
# @returns [String] The status code as a string.
|
|
38
|
-
def to_s
|
|
39
|
-
@value.to_s
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
# Merge another status value (takes the new value, as status should only appear once)
|
|
43
|
-
# @parameter value [String, Integer, Array] The new status code
|
|
44
|
-
def <<(value)
|
|
45
|
-
@value = normalize_value(value)
|
|
46
|
-
self
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
private
|
|
50
|
-
|
|
51
|
-
# Normalize a value to an integer status code.
|
|
52
|
-
# Handles arrays (from external clients), strings, and integers.
|
|
53
|
-
# @parameter value [String, Integer, Array] The raw value
|
|
54
|
-
# @returns [Integer] The normalized status code
|
|
55
|
-
def normalize_value(value)
|
|
56
|
-
# Handle Array case (may occur with external clients)
|
|
57
|
-
actual_value = value.is_a?(Array) ? value.flatten.compact.first : value
|
|
58
|
-
actual_value.to_i
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
# Whether this header is acceptable in HTTP trailers.
|
|
62
|
-
# The `grpc-status` header can appear in trailers as per the gRPC specification.
|
|
63
|
-
# @returns [Boolean] `true`, as grpc-status can appear in trailers.
|
|
64
|
-
def self.trailer?
|
|
65
|
-
true
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
# The `grpc-message` header represents the gRPC status message.
|
|
70
|
-
#
|
|
71
|
-
# The `grpc-message` header contains a human-readable error message, URL-encoded according to RFC 3986.
|
|
72
|
-
# This header is optional and typically only present when there's an error (non-zero status code).
|
|
73
|
-
# This header can appear both as an initial header (for trailers-only responses) and as a trailer.
|
|
74
|
-
class Message < String
|
|
75
|
-
# Initialize the message header with the given value.
|
|
76
|
-
#
|
|
77
|
-
# @parameter value [String] The message value (will be URL-encoded if not already encoded).
|
|
78
|
-
def initialize(value)
|
|
79
|
-
super(value.to_s)
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
# Decode the URL-encoded message.
|
|
83
|
-
#
|
|
84
|
-
# @returns [String] The decoded message.
|
|
85
|
-
def decode
|
|
86
|
-
URI.decode_www_form_component(self)
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
# Encode the message for use in headers.
|
|
90
|
-
#
|
|
91
|
-
# @parameter message [String] The message to encode.
|
|
92
|
-
# @returns [String] The URL-encoded message.
|
|
93
|
-
def self.encode(message)
|
|
94
|
-
URI.encode_www_form_component(message).gsub("+", "%20")
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
# Merge another message value (takes the new value, as message should only appear once)
|
|
98
|
-
# @parameter value [String] The new message value
|
|
99
|
-
def <<(value)
|
|
100
|
-
replace(value.to_s)
|
|
101
|
-
self
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
# Whether this header is acceptable in HTTP trailers.
|
|
105
|
-
# The `grpc-message` header can appear in trailers as per the gRPC specification.
|
|
106
|
-
# @returns [Boolean] `true`, as grpc-message can appear in trailers.
|
|
107
|
-
def self.trailer?
|
|
108
|
-
true
|
|
109
|
-
end
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
# Base class for custom gRPC metadata (allowed in trailers).
|
|
113
|
-
class Metadata < Protocol::HTTP::Header::Split
|
|
114
|
-
# Whether this header is acceptable in HTTP trailers.
|
|
115
|
-
# The `grpc-metadata` header can appear in trailers as per the gRPC specification.
|
|
116
|
-
# @returns [Boolean] `true`, as grpc-metadata can appear in trailers.
|
|
117
|
-
def self.trailer?
|
|
118
|
-
true
|
|
119
|
-
end
|
|
120
|
-
end
|
|
121
17
|
end
|
|
122
18
|
|
|
123
19
|
# Custom header policy for gRPC.
|
|
@@ -7,6 +7,19 @@ require_relative "methods"
|
|
|
7
7
|
|
|
8
8
|
module Protocol
|
|
9
9
|
module GRPC
|
|
10
|
+
# Wrapper class to mark a message type as streamed.
|
|
11
|
+
# Used with the stream() helper method in RPC definitions.
|
|
12
|
+
class Streaming
|
|
13
|
+
# Initialize a new Streaming wrapper.
|
|
14
|
+
# @parameter message_class [Class] The message class being wrapped
|
|
15
|
+
def initialize(message_class)
|
|
16
|
+
@message_class = message_class
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# @attribute [Class] The wrapped message class
|
|
20
|
+
attr :message_class
|
|
21
|
+
end
|
|
22
|
+
|
|
10
23
|
# Represents an interface definition for gRPC methods.
|
|
11
24
|
# Can be used by both client stubs and server implementations.
|
|
12
25
|
class Interface
|
|
@@ -24,6 +37,21 @@ module Protocol
|
|
|
24
37
|
end
|
|
25
38
|
end
|
|
26
39
|
|
|
40
|
+
# Helper method to mark a message type as streamed in RPC definitions.
|
|
41
|
+
# Can be called directly within Interface subclasses without the Protocol::GRPC prefix.
|
|
42
|
+
# @parameter message_class [Class] The message class to mark as streamed
|
|
43
|
+
# @returns [Streaming] A wrapper indicating this type is streamed
|
|
44
|
+
#
|
|
45
|
+
# @example Define streaming RPCs
|
|
46
|
+
# class MyService < Protocol::GRPC::Interface
|
|
47
|
+
# rpc :sum, stream(Num), Num # client streaming
|
|
48
|
+
# rpc :fib, FibArgs, stream(Num) # server streaming
|
|
49
|
+
# rpc :chat, stream(Msg), stream(Msg) # bidirectional streaming
|
|
50
|
+
# end
|
|
51
|
+
def self.stream(message_class)
|
|
52
|
+
Streaming.new(message_class)
|
|
53
|
+
end
|
|
54
|
+
|
|
27
55
|
# Hook called when a subclass is created.
|
|
28
56
|
# Initializes the RPC hash for the subclass.
|
|
29
57
|
# @parameter subclass [Class] The subclass being created
|
|
@@ -35,13 +63,41 @@ module Protocol
|
|
|
35
63
|
|
|
36
64
|
# Define an RPC method.
|
|
37
65
|
# @parameter name [Symbol] Method name in PascalCase (e.g., :SayHello, matching .proto file)
|
|
38
|
-
# @parameter request_class [Class] Request message class
|
|
39
|
-
# @parameter response_class [Class] Response message class
|
|
66
|
+
# @parameter request_class [Class | Streaming] Request message class, optionally wrapped with stream()
|
|
67
|
+
# @parameter response_class [Class | Streaming] Response message class, optionally wrapped with stream()
|
|
40
68
|
# @parameter streaming [Symbol] Streaming type (:unary, :server_streaming, :client_streaming, :bidirectional)
|
|
69
|
+
# This is automatically inferred from stream() decorators if not explicitly provided
|
|
41
70
|
# @parameter method [Symbol | Nil] Optional explicit Ruby method name (snake_case). If not provided, automatically converts PascalCase to snake_case.
|
|
42
|
-
|
|
71
|
+
#
|
|
72
|
+
# @example Using stream() decorator syntax
|
|
73
|
+
# rpc :div, DivArgs, DivReply # unary
|
|
74
|
+
# rpc :sum, stream(Num), Num # client streaming
|
|
75
|
+
# rpc :fib, FibArgs, stream(Num) # server streaming
|
|
76
|
+
# rpc :chat, stream(DivArgs), stream(DivReply) # bidirectional streaming
|
|
77
|
+
def self.rpc(name, request_class = nil, response_class = nil, **options)
|
|
43
78
|
options[:name] = name
|
|
44
79
|
|
|
80
|
+
# Check if request or response are wrapped with stream()
|
|
81
|
+
request_streaming = request_class.is_a?(Streaming)
|
|
82
|
+
response_streaming = response_class.is_a?(Streaming)
|
|
83
|
+
|
|
84
|
+
# Unwrap StreamWrapper if present
|
|
85
|
+
options[:request_class] ||= request_streaming ? request_class.message_class : request_class
|
|
86
|
+
options[:response_class] ||= response_streaming ? response_class.message_class : response_class
|
|
87
|
+
|
|
88
|
+
# Auto-detect streaming type from stream() decorators if not explicitly set
|
|
89
|
+
if !options.key?(:streaming)
|
|
90
|
+
if request_streaming && response_streaming
|
|
91
|
+
options[:streaming] = :bidirectional
|
|
92
|
+
elsif request_streaming
|
|
93
|
+
options[:streaming] = :client_streaming
|
|
94
|
+
elsif response_streaming
|
|
95
|
+
options[:streaming] = :server_streaming
|
|
96
|
+
else
|
|
97
|
+
options[:streaming] = :unary
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
45
101
|
# Ensure snake_case method name is always available
|
|
46
102
|
options[:method] ||= pascal_case_to_snake_case(name.to_s).to_sym
|
|
47
103
|
|
|
@@ -94,7 +150,7 @@ module Protocol
|
|
|
94
150
|
attr :name
|
|
95
151
|
|
|
96
152
|
# Build gRPC path for a method.
|
|
97
|
-
# @parameter method_name [String
|
|
153
|
+
# @parameter method_name [String | Symbol] Method name in PascalCase (e.g., :SayHello)
|
|
98
154
|
# @returns [String] gRPC path with PascalCase method name
|
|
99
155
|
def path(method_name)
|
|
100
156
|
Methods.build_path(@name, method_name.to_s)
|
|
@@ -20,7 +20,7 @@ module Protocol
|
|
|
20
20
|
|
|
21
21
|
# Parse service and method from gRPC path.
|
|
22
22
|
# @parameter path [String] e.g., "/my_service.Greeter/SayHello"
|
|
23
|
-
# @returns [Array(String
|
|
23
|
+
# @returns [Array(String | String)] [service, method]
|
|
24
24
|
def self.parse_path(path)
|
|
25
25
|
parts = path.split("/")
|
|
26
26
|
[parts[1], parts[2]]
|
data/lib/protocol/grpc.rb
CHANGED
|
@@ -11,8 +11,8 @@ require_relative "grpc/methods"
|
|
|
11
11
|
require_relative "grpc/header"
|
|
12
12
|
require_relative "grpc/metadata"
|
|
13
13
|
require_relative "grpc/call"
|
|
14
|
-
require_relative "grpc/body/
|
|
15
|
-
require_relative "grpc/body/
|
|
14
|
+
require_relative "grpc/body/readable"
|
|
15
|
+
require_relative "grpc/body/writable"
|
|
16
16
|
require_relative "grpc/interface"
|
|
17
17
|
require_relative "grpc/middleware"
|
|
18
18
|
require_relative "grpc/health_check"
|
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: protocol-grpc
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Samuel Williams
|
|
@@ -102,11 +102,16 @@ files:
|
|
|
102
102
|
- context/index.yaml
|
|
103
103
|
- design.md
|
|
104
104
|
- lib/protocol/grpc.rb
|
|
105
|
+
- lib/protocol/grpc/body/readable.rb
|
|
105
106
|
- lib/protocol/grpc/body/readable_body.rb
|
|
107
|
+
- lib/protocol/grpc/body/writable.rb
|
|
106
108
|
- lib/protocol/grpc/body/writable_body.rb
|
|
107
109
|
- lib/protocol/grpc/call.rb
|
|
108
110
|
- lib/protocol/grpc/error.rb
|
|
109
111
|
- lib/protocol/grpc/header.rb
|
|
112
|
+
- lib/protocol/grpc/header/message.rb
|
|
113
|
+
- lib/protocol/grpc/header/metadata.rb
|
|
114
|
+
- lib/protocol/grpc/header/status.rb
|
|
110
115
|
- lib/protocol/grpc/health_check.rb
|
|
111
116
|
- lib/protocol/grpc/interface.rb
|
|
112
117
|
- lib/protocol/grpc/metadata.rb
|
|
@@ -137,7 +142,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
137
142
|
- !ruby/object:Gem::Version
|
|
138
143
|
version: '0'
|
|
139
144
|
requirements: []
|
|
140
|
-
rubygems_version:
|
|
145
|
+
rubygems_version: 4.0.3
|
|
141
146
|
specification_version: 4
|
|
142
147
|
summary: Protocol abstractions for gRPC, built on top of protocol-http.
|
|
143
148
|
test_files: []
|
metadata.gz.sig
CHANGED
|
Binary file
|