|
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. |
|
0 commit comments