1use prost_reflect::prost::Message;
11use prost_reflect::{DescriptorPool, DynamicMessage, MessageDescriptor};
12use serde::Serialize;
13
14use crate::httprule::HttpRouter;
15pub use crate::httprule::{HttpCall, WsBinding};
16
17#[derive(Debug, thiserror::Error)]
18pub enum TranscodeError {
19 #[error("failed to load descriptor set: {0}")]
20 Descriptor(String),
21 #[error("unknown method: {0}")]
22 UnknownMethod(String),
23 #[error("json error: {0}")]
24 Json(#[from] serde_json::Error),
25 #[error("protobuf decode error: {0}")]
26 Decode(String),
27 #[error("http transcoding: {0}")]
28 Http(String),
29}
30
31#[derive(Clone)]
35pub struct Transcoder {
36 pool: DescriptorPool,
37 router: HttpRouter,
38}
39
40impl Transcoder {
41 pub fn from_file_descriptor_set(bytes: &[u8]) -> Result<Self, TranscodeError> {
43 let pool = DescriptorPool::decode(bytes)
44 .map_err(|e| TranscodeError::Descriptor(e.to_string()))?;
45 let router = HttpRouter::from_pool(&pool);
46 Ok(Self { pool, router })
47 }
48
49 pub fn has_http_rules(&self) -> bool {
51 !self.router.is_empty()
52 }
53
54 pub fn transcode_http_request(
57 &self,
58 method: &str,
59 path: &str,
60 query: Option<&str>,
61 body: &[u8],
62 ) -> Result<Option<HttpCall>, TranscodeError> {
63 self.router.transcode(method, path, query, body)
64 }
65
66 pub fn match_ws(&self, path: &str, query: Option<&str>) -> Option<WsBinding> {
69 self.router.match_ws(path, query)
70 }
71
72 pub fn is_annotated_method(&self, grpc_method: &str) -> bool {
75 self.router.is_annotated(grpc_method)
76 }
77
78 fn io_types(&self, path: &str) -> Result<(MessageDescriptor, MessageDescriptor), TranscodeError> {
80 let (service, method) = path
81 .trim_start_matches('/')
82 .split_once('/')
83 .ok_or_else(|| TranscodeError::UnknownMethod(path.to_string()))?;
84 let svc = self
85 .pool
86 .get_service_by_name(service)
87 .ok_or_else(|| TranscodeError::UnknownMethod(path.to_string()))?;
88 let m = svc
89 .methods()
90 .find(|m| m.name() == method)
91 .ok_or_else(|| TranscodeError::UnknownMethod(path.to_string()))?;
92 Ok((m.input(), m.output()))
93 }
94
95 pub fn has_method(&self, path: &str) -> bool {
99 self.io_types(path).is_ok()
100 }
101
102 pub fn request_json_to_proto(&self, path: &str, json: &[u8]) -> Result<Vec<u8>, TranscodeError> {
104 let (input, _) = self.io_types(path)?;
105 Ok(self.json_to_proto(input, json)?.encode_to_vec())
106 }
107
108 pub fn response_proto_to_json(&self, path: &str, proto: &[u8]) -> Result<Vec<u8>, TranscodeError> {
110 self.response_proto_to_json_body(path, proto, "")
111 }
112
113 pub fn response_proto_to_json_body(
118 &self,
119 path: &str,
120 proto: &[u8],
121 response_body: &str,
122 ) -> Result<Vec<u8>, TranscodeError> {
123 let (_, output) = self.io_types(path)?;
124 if response_body.is_empty() {
125 return self.proto_to_json(output, proto);
126 }
127 let field = crate::httprule::field_by_any_name(&output, response_body)
128 .ok_or_else(|| TranscodeError::Http(format!("unknown response_body field: {response_body}")))?;
129
130 let whole = self.proto_to_json(output, proto)?;
137 let obj: serde_json::Value = serde_json::from_slice(&whole)?;
138 let value = obj.get(field.json_name()).cloned().unwrap_or_else(|| json_zero(&field));
139 Ok(serde_json::to_vec(&value)?)
140 }
141
142 fn json_to_proto(
143 &self,
144 desc: MessageDescriptor,
145 json: &[u8],
146 ) -> Result<DynamicMessage, TranscodeError> {
147 if json.is_empty() {
148 return Ok(DynamicMessage::new(desc));
149 }
150 let mut de = serde_json::Deserializer::from_slice(json);
151 let msg = DynamicMessage::deserialize(desc, &mut de)?;
152 de.end()?;
153 Ok(msg)
154 }
155
156 fn proto_to_json(&self, desc: MessageDescriptor, proto: &[u8]) -> Result<Vec<u8>, TranscodeError> {
157 let msg =
158 DynamicMessage::decode(desc, proto).map_err(|e| TranscodeError::Decode(e.to_string()))?;
159 let mut buf = Vec::new();
160 let mut ser = serde_json::Serializer::new(&mut buf);
161 msg.serialize(&mut ser)?;
162 Ok(buf)
163 }
164}
165
166fn json_zero(field: &prost_reflect::FieldDescriptor) -> serde_json::Value {
174 use prost_reflect::Kind;
175 use serde_json::Value;
176
177 if field.is_list() {
178 return Value::Array(Vec::new());
179 }
180 if field.is_map() {
181 return Value::Object(serde_json::Map::new());
182 }
183 match field.kind() {
184 Kind::Message(_) => Value::Null,
186 Kind::String => Value::String(String::new()),
187 Kind::Bytes => Value::String(String::new()),
189 Kind::Bool => Value::Bool(false),
190 Kind::Int64 | Kind::Sint64 | Kind::Sfixed64 | Kind::Uint64 | Kind::Fixed64 => {
192 Value::String("0".to_string())
193 }
194 Kind::Enum(e) => match e.get_value(0) {
197 Some(v) => Value::String(v.name().to_string()),
198 None => Value::from(0),
199 },
200 _ => Value::from(0),
201 }
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207 use prost_reflect::prost_types::{
208 field_descriptor_proto::{Label, Type},
209 DescriptorProto, EnumDescriptorProto, EnumValueDescriptorProto, FieldDescriptorProto,
210 FileDescriptorProto, FileDescriptorSet,
211 };
212
213 fn zero_fixture() -> MessageDescriptor {
216 let field = |name: &str, number: i32, ty: Type| FieldDescriptorProto {
217 name: Some(name.to_string()),
218 number: Some(number),
219 label: Some(Label::Optional as i32),
220 r#type: Some(ty as i32),
221 ..Default::default()
222 };
223 let typed = |name: &str, number: i32, ty: Type, type_name: &str| FieldDescriptorProto {
224 type_name: Some(type_name.to_string()),
225 ..field(name, number, ty)
226 };
227 let mut tags = field("tags", 20, Type::String);
228 tags.label = Some(Label::Repeated as i32);
229
230 let file = FileDescriptorProto {
231 name: Some("zero_fixture.proto".to_string()),
232 package: Some("webnext.zero.test".to_string()),
233 syntax: Some("proto3".to_string()),
234 enum_type: vec![EnumDescriptorProto {
235 name: Some("Color".to_string()),
236 value: vec![
237 EnumValueDescriptorProto {
238 name: Some("COLOR_UNSPECIFIED".to_string()),
239 number: Some(0),
240 ..Default::default()
241 },
242 EnumValueDescriptorProto {
243 name: Some("RED".to_string()),
244 number: Some(1),
245 ..Default::default()
246 },
247 ],
248 ..Default::default()
249 }],
250 message_type: vec![
251 DescriptorProto {
252 name: Some("Nested".to_string()),
253 field: vec![field("id", 1, Type::String)],
254 ..Default::default()
255 },
256 DescriptorProto {
257 name: Some("Req".to_string()),
258 field: vec![
259 field("name", 1, Type::String),
260 field("count", 2, Type::Uint32),
261 field("big", 3, Type::Int64),
262 field("ratio", 4, Type::Double),
263 field("flag", 5, Type::Bool),
264 field("blob", 6, Type::Bytes),
265 typed("color", 7, Type::Enum, ".webnext.zero.test.Color"),
266 typed("nested", 8, Type::Message, ".webnext.zero.test.Nested"),
267 tags,
268 ],
269 ..Default::default()
270 },
271 ],
272 ..Default::default()
273 };
274 let pool = DescriptorPool::from_file_descriptor_set(FileDescriptorSet { file: vec![file] })
275 .expect("build fixture pool");
276 pool.get_message_by_name("webnext.zero.test.Req").expect("Req")
277 }
278
279 #[test]
284 fn json_zero_per_kind() {
285 let desc = zero_fixture();
286 let cases = [
287 ("name", "\"\""), ("count", "0"), ("big", "\"0\""), ("ratio", "0"), ("flag", "false"), ("blob", "\"\""), ("color", "\"COLOR_UNSPECIFIED\""), ("nested", "null"), ("tags", "[]"), ];
297 for (name, want) in cases {
298 let field = desc.get_field_by_name(name).expect(name);
299 let got = serde_json::to_string(&json_zero(&field)).expect("encode");
300 assert_eq!(got, want, "json_zero({name})");
301 }
302 }
303
304 #[test]
307 fn response_body_extraction() {
308 let tc = Transcoder::from_file_descriptor_set(testecho::FILE_DESCRIPTOR_SET).expect("transcoder");
309 const METHOD: &str = "/echo.v1.Echo/Unary";
311 let proto = tc.request_json_to_proto(METHOD, br#"{"message":"hi"}"#).expect("encode");
312
313 let body = tc.response_proto_to_json_body(METHOD, &proto, "message").expect("extract");
315 assert_eq!(String::from_utf8(body).unwrap(), "\"hi\"");
316
317 let whole = tc.response_proto_to_json(METHOD, &proto).expect("whole");
319 assert_eq!(String::from_utf8(whole).unwrap(), r#"{"message":"hi"}"#);
320
321 let empty = tc.request_json_to_proto(METHOD, b"{}").expect("encode empty");
323 let body = tc.response_proto_to_json_body(METHOD, &empty, "message").expect("extract");
324 assert_eq!(String::from_utf8(body).unwrap(), "\"\"");
325
326 assert!(tc.response_proto_to_json_body(METHOD, &proto, "nope").is_err());
328 }
329}