Skip to main content

switchback_protocols/
grpc.rs

1//! gRPC protocol implementation.
2
3use switchback_codec_pb::canardleteer::switchback::protocol::grpc::v1alpha1::{
4    GrpcContractMeta, GrpcErrorMeta, GrpcMetadataMeta, GrpcOperationMeta, GrpcPayload,
5    GrpcStatusMeta,
6};
7use switchback_codec_pb::canardleteer::switchback::protocol::grpc::v1alpha1::__buffa::oneof::grpc_payload::Kind;
8use switchback_traits::{ProtocolAttachment, ResponseSeverity, Result};
9
10use crate::severity::{grpc_status_name_severity, grpc_status_severity};
11use crate::traits::{
12    ErrorProtocol, FieldCarrierProtocol, OperationProtocol, Protocol, ProtocolWire,
13    ResponseProtocol,
14};
15use crate::wire::{decode_message, encode_message};
16
17/// Built-in gRPC protocol (`"grpc"`).
18#[derive(Clone, Copy, Debug, Default)]
19pub struct GrpcProtocol;
20
21impl Protocol for GrpcProtocol {
22    fn id(&self) -> &'static str {
23        "grpc"
24    }
25}
26
27impl OperationProtocol for GrpcProtocol {
28    type OperationMeta = GrpcOperationMeta;
29
30    fn format_signature(&self, meta: &Self::OperationMeta) -> String {
31        let streaming = match (meta.client_streaming, meta.server_streaming) {
32            (true, true) => "stream ",
33            (true, false) => "stream ",
34            (false, true) => "",
35            (false, false) => "",
36        };
37        let out_stream = if meta.server_streaming { "stream " } else { "" };
38        format!(
39            "{} ( {streaming}… ) returns ( {out_stream}… )",
40            meta.rpc_name
41        )
42    }
43}
44
45impl ResponseProtocol for GrpcProtocol {
46    fn response_severity(&self, status_key: &str) -> ResponseSeverity {
47        grpc_status_name_severity(status_key)
48    }
49}
50
51impl ErrorProtocol for GrpcProtocol {
52    type ErrorMeta = GrpcErrorMeta;
53
54    fn error_severity(&self, error_key: &str) -> ResponseSeverity {
55        grpc_status_name_severity(error_key)
56    }
57
58    fn format_error_label(&self, error_key: &str) -> String {
59        format!("gRPC {error_key}")
60    }
61}
62
63impl FieldCarrierProtocol for GrpcProtocol {
64    fn field_carrier_kinds(&self) -> &'static [&'static str] {
65        &["metadata"]
66    }
67
68    fn valid_parameter_locations(&self) -> &'static [&'static str] {
69        &["metadata"]
70    }
71}
72
73impl GrpcProtocol {
74    /// Attach contract-level gRPC metadata.
75    pub fn attach_contract(&self, meta: &GrpcContractMeta) -> ProtocolAttachment {
76        attachment_from_payload(GrpcPayload {
77            kind: Some(Kind::Contract(Box::new(meta.clone()))),
78            ..Default::default()
79        })
80    }
81
82    /// Attach operation-level gRPC metadata.
83    pub fn attach_operation(&self, meta: &GrpcOperationMeta) -> ProtocolAttachment {
84        attachment_from_payload(GrpcPayload {
85            kind: Some(Kind::Operation(Box::new(meta.clone()))),
86            ..Default::default()
87        })
88    }
89
90    /// Attach success status gRPC metadata.
91    pub fn attach_status(&self, meta: &GrpcStatusMeta) -> ProtocolAttachment {
92        attachment_from_payload(GrpcPayload {
93            kind: Some(Kind::Status(Box::new(meta.clone()))),
94            ..Default::default()
95        })
96    }
97
98    /// Attach error gRPC metadata.
99    pub fn attach_error(&self, meta: &GrpcErrorMeta) -> ProtocolAttachment {
100        attachment_from_payload(GrpcPayload {
101            kind: Some(Kind::Error(Box::new(meta.clone()))),
102            ..Default::default()
103        })
104    }
105
106    /// Attach metadata key gRPC metadata.
107    pub fn attach_metadata(&self, meta: &GrpcMetadataMeta) -> ProtocolAttachment {
108        attachment_from_payload(GrpcPayload {
109            kind: Some(Kind::Metadata(Box::new(meta.clone()))),
110            ..Default::default()
111        })
112    }
113
114    /// Build [`GrpcOperationMeta`] from RPC descriptor fields.
115    pub fn operation_meta(
116        rpc_name: &str,
117        client_streaming: bool,
118        server_streaming: bool,
119    ) -> GrpcOperationMeta {
120        GrpcOperationMeta {
121            rpc_name: rpc_name.to_string(),
122            client_streaming,
123            server_streaming,
124            ..Default::default()
125        }
126    }
127
128    /// Build [`GrpcStatusMeta`] for OK responses.
129    pub fn status_meta_ok() -> GrpcStatusMeta {
130        GrpcStatusMeta {
131            code: 0,
132            message: "OK".to_string(),
133            ..Default::default()
134        }
135    }
136
137    /// Build [`GrpcErrorMeta`] from a status name.
138    pub fn error_meta(code_name: &str, message: &str) -> GrpcErrorMeta {
139        let code = grpc_code_from_name(code_name);
140        GrpcErrorMeta {
141            code,
142            message: message.to_string(),
143            ..Default::default()
144        }
145    }
146
147    /// Build [`GrpcMetadataMeta`].
148    pub fn metadata_meta(key: &str, required: bool) -> GrpcMetadataMeta {
149        GrpcMetadataMeta {
150            key: key.to_string(),
151            required,
152            ..Default::default()
153        }
154    }
155
156    /// Build [`GrpcContractMeta`].
157    pub fn contract_meta(package_name: &str) -> GrpcContractMeta {
158        GrpcContractMeta {
159            package_name: package_name.to_string(),
160            ..Default::default()
161        }
162    }
163
164    /// Classify gRPC status code for populate.
165    pub fn severity_for_code(code: i32) -> ResponseSeverity {
166        grpc_status_severity(code)
167    }
168}
169
170impl ProtocolWire for GrpcOperationMeta {
171    const PROTOCOL_ID: &'static str = "grpc";
172
173    fn encode_to_vec(&self) -> Vec<u8> {
174        encode_message(&GrpcPayload {
175            kind: Some(Kind::Operation(Box::new(self.clone()))),
176            ..Default::default()
177        })
178    }
179
180    fn decode_from_bytes(bytes: &[u8]) -> Result<Self> {
181        let payload: GrpcPayload = decode_message(bytes)?;
182        match payload.kind {
183            Some(Kind::Operation(meta)) => Ok(*meta),
184            _ => Err(switchback_traits::SwitchbackError::codec(
185                "expected GrpcOperationMeta payload",
186            )),
187        }
188    }
189}
190
191impl ProtocolWire for GrpcErrorMeta {
192    const PROTOCOL_ID: &'static str = "grpc";
193
194    fn encode_to_vec(&self) -> Vec<u8> {
195        encode_message(&GrpcPayload {
196            kind: Some(Kind::Error(Box::new(self.clone()))),
197            ..Default::default()
198        })
199    }
200
201    fn decode_from_bytes(bytes: &[u8]) -> Result<Self> {
202        let payload: GrpcPayload = decode_message(bytes)?;
203        match payload.kind {
204            Some(Kind::Error(meta)) => Ok(*meta),
205            _ => Err(switchback_traits::SwitchbackError::codec(
206                "expected GrpcErrorMeta payload",
207            )),
208        }
209    }
210}
211
212fn attachment_from_payload(payload: GrpcPayload) -> ProtocolAttachment {
213    let protocol = GrpcProtocol;
214    ProtocolAttachment {
215        protocol_id: protocol.id().to_string(),
216        payload: encode_message(&payload),
217    }
218}
219
220fn grpc_code_from_name(name: &str) -> i32 {
221    match name.trim().to_ascii_uppercase().as_str() {
222        "OK" => 0,
223        "CANCELLED" => 1,
224        "UNKNOWN" => 2,
225        "INVALID_ARGUMENT" => 3,
226        "DEADLINE_EXCEEDED" => 4,
227        "NOT_FOUND" => 5,
228        "ALREADY_EXISTS" => 6,
229        "PERMISSION_DENIED" => 7,
230        "RESOURCE_EXHAUSTED" => 8,
231        "FAILED_PRECONDITION" => 9,
232        "ABORTED" => 10,
233        "OUT_OF_RANGE" => 11,
234        "UNIMPLEMENTED" => 12,
235        "INTERNAL" => 13,
236        "UNAVAILABLE" => 14,
237        "DATA_LOSS" => 15,
238        "UNAUTHENTICATED" => 16,
239        _ => 2,
240    }
241}