1use prost_reflect::prost::Message;
19use prost_reflect::{DescriptorPool, DynamicMessage, Kind, MessageDescriptor, ReflectMessage, Value};
20
21use crate::transcode::TranscodeError;
22
23const HTTP_EXT: &str = "google.api.http";
25
26#[derive(Debug, Clone)]
28enum Segment {
29 Literal(String),
31 Single(Vec<String>),
34 Rest(Vec<String>),
36}
37
38#[derive(Debug, Clone)]
40enum BodyRule {
41 Wildcard,
43 Field(String),
45 None,
47}
48
49#[derive(Debug, Clone)]
52struct Binding {
53 http_method: String, segments: Vec<Segment>,
55 body: BodyRule,
56 grpc_method: String, input: MessageDescriptor,
58}
59
60type PathVar = (Vec<String>, String);
63
64pub struct HttpCall {
66 pub grpc_method: String,
67 pub message: Vec<u8>,
68}
69
70pub struct WsBinding {
73 binding: Binding,
74 vars: Vec<PathVar>,
75 query: Option<String>,
76}
77
78impl WsBinding {
79 pub fn grpc_method(&self) -> &str {
81 &self.binding.grpc_method
82 }
83
84 pub fn has_body(&self) -> bool {
87 !matches!(self.binding.body, BodyRule::None)
88 }
89
90 pub fn build_message(&self, body: &[u8]) -> Result<Vec<u8>, TranscodeError> {
92 build_message(&self.binding, &self.vars, self.query.as_deref(), body)
93 }
94}
95
96#[derive(Clone, Default)]
98pub struct HttpRouter {
99 bindings: Vec<Binding>,
100}
101
102impl HttpRouter {
103 pub fn from_pool(pool: &DescriptorPool) -> Self {
106 let mut bindings = Vec::new();
107 let Some(ext) = pool.get_extension_by_name(HTTP_EXT) else {
108 return Self { bindings };
109 };
110 for service in pool.services() {
111 for method in service.methods() {
112 let opts = method.options();
113 if !opts.has_extension(&ext) {
114 continue;
115 }
116 let grpc_method = format!("/{}/{}", service.full_name(), method.name());
117 let input = method.input();
118 let rule = opts.get_extension(&ext);
119 if let Some(rule) = rule.as_message() {
120 collect_rule(&mut bindings, rule, &grpc_method, &input);
121 }
122 }
123 }
124 Self { bindings }
125 }
126
127 pub fn is_empty(&self) -> bool {
128 self.bindings.is_empty()
129 }
130
131 pub fn match_ws(&self, path: &str, query: Option<&str>) -> Option<WsBinding> {
134 for b in &self.bindings {
135 if let Some(vars) = match_segments(&b.segments, path) {
136 return Some(WsBinding {
137 binding: b.clone(),
138 vars,
139 query: query.map(str::to_string),
140 });
141 }
142 }
143 None
144 }
145
146 pub fn is_annotated(&self, grpc_method: &str) -> bool {
148 self.bindings.iter().any(|b| b.grpc_method == grpc_method)
149 }
150
151 fn match_request(&self, method: &str, path: &str) -> Option<(&Binding, Vec<PathVar>)> {
154 let want = method.to_ascii_uppercase();
155 for b in &self.bindings {
156 if b.http_method != want {
157 continue;
158 }
159 if let Some(vars) = match_segments(&b.segments, path) {
160 return Some((b, vars));
161 }
162 }
163 None
164 }
165
166 pub fn transcode(
168 &self,
169 method: &str,
170 path: &str,
171 query: Option<&str>,
172 body: &[u8],
173 ) -> Result<Option<HttpCall>, TranscodeError> {
174 let Some((binding, vars)) = self.match_request(method, path) else {
175 return Ok(None);
176 };
177 let message = build_message(binding, &vars, query, body)?;
178 Ok(Some(HttpCall { grpc_method: binding.grpc_method.clone(), message }))
179 }
180}
181
182fn collect_rule(bindings: &mut Vec<Binding>, rule: &DynamicMessage, grpc_method: &str, input: &MessageDescriptor) {
184 push_binding(bindings, rule, grpc_method, input);
185 if let Some(list) = rule.get_field_by_name("additional_bindings") {
186 if let Some(items) = list.as_list() {
187 for item in items {
188 if let Some(m) = item.as_message() {
189 push_binding(bindings, m, grpc_method, input);
190 }
191 }
192 }
193 }
194}
195
196fn push_binding(bindings: &mut Vec<Binding>, rule: &DynamicMessage, grpc_method: &str, input: &MessageDescriptor) {
197 let Some((http_method, template)) = verb_and_path(rule) else {
198 return;
199 };
200 bindings.push(Binding {
201 http_method,
202 segments: parse_template(&template),
203 body: body_rule(rule),
204 grpc_method: grpc_method.to_string(),
205 input: input.clone(),
206 });
207}
208
209fn verb_and_path(rule: &DynamicMessage) -> Option<(String, String)> {
211 for (field, verb) in [("get", "GET"), ("put", "PUT"), ("post", "POST"), ("delete", "DELETE"), ("patch", "PATCH")] {
212 if let Some(v) = rule.get_field_by_name(field) {
213 if let Some(s) = v.as_str() {
214 if !s.is_empty() {
215 return Some((verb.to_string(), s.to_string()));
216 }
217 }
218 }
219 }
220 if let Some(v) = rule.get_field_by_name("custom") {
222 if let Some(m) = v.as_message() {
223 let kind = m.get_field_by_name("kind").and_then(|k| k.as_str().map(str::to_string)).unwrap_or_default();
224 let path = m.get_field_by_name("path").and_then(|p| p.as_str().map(str::to_string)).unwrap_or_default();
225 if !kind.is_empty() {
226 return Some((kind.to_ascii_uppercase(), path));
227 }
228 }
229 }
230 None
231}
232
233fn body_rule(rule: &DynamicMessage) -> BodyRule {
234 match rule.get_field_by_name("body").and_then(|b| b.as_str().map(str::to_string)).unwrap_or_default().as_str() {
235 "" => BodyRule::None,
236 "*" => BodyRule::Wildcard,
237 field => BodyRule::Field(field.to_string()),
238 }
239}
240
241fn parse_template(template: &str) -> Vec<Segment> {
243 let path = template.split(':').next().unwrap_or(template);
244 path.trim_matches('/')
245 .split('/')
246 .filter(|s| !s.is_empty())
247 .map(|seg| {
248 if let Some(inner) = seg.strip_prefix('{').and_then(|s| s.strip_suffix('}')) {
249 let (field, pattern) = inner.split_once('=').unwrap_or((inner, "*"));
250 let field_path: Vec<String> = field.split('.').map(str::to_string).collect();
251 if pattern == "**" {
252 Segment::Rest(field_path)
253 } else {
254 Segment::Single(field_path)
255 }
256 } else {
257 Segment::Literal(seg.to_string())
258 }
259 })
260 .collect()
261}
262
263fn match_segments(segments: &[Segment], path: &str) -> Option<Vec<PathVar>> {
265 let parts: Vec<&str> = path.trim_matches('/').split('/').filter(|s| !s.is_empty()).collect();
266 let mut vars = Vec::new();
267 let mut i = 0;
268 for seg in segments {
269 match seg {
270 Segment::Literal(lit) => {
271 if parts.get(i) != Some(&lit.as_str()) {
272 return None;
273 }
274 i += 1;
275 }
276 Segment::Single(field) => {
277 let part = parts.get(i)?;
278 vars.push((field.clone(), percent_decode(part)));
279 i += 1;
280 }
281 Segment::Rest(field) => {
282 let rest: Vec<String> = parts[i..].iter().map(|p| percent_decode(p)).collect();
283 vars.push((field.clone(), rest.join("/")));
284 i = parts.len();
285 }
286 }
287 }
288 if i == parts.len() {
289 Some(vars)
290 } else {
291 None
292 }
293}
294
295fn build_message(
297 binding: &Binding,
298 vars: &[PathVar],
299 query: Option<&str>,
300 body: &[u8],
301) -> Result<Vec<u8>, TranscodeError> {
302 let mut msg = match &binding.body {
303 BodyRule::Wildcard => deserialize_message(binding.input.clone(), body)?,
304 BodyRule::None => DynamicMessage::new(binding.input.clone()),
305 BodyRule::Field(field) => {
306 let mut m = DynamicMessage::new(binding.input.clone());
307 if !body.is_empty() {
308 set_message_field(&mut m, field, body)?;
309 }
310 m
311 }
312 };
313
314 for (field_path, value) in vars {
315 set_by_path(&mut msg, field_path, value)?;
316 }
317
318 if !matches!(binding.body, BodyRule::Wildcard) {
320 if let Some(q) = query {
321 for (key, value) in parse_query(q) {
322 let field_path: Vec<String> = key.split('.').map(str::to_string).collect();
323 if vars.iter().any(|(fp, _)| *fp == field_path) {
324 continue; }
326 set_by_path(&mut msg, &field_path, &value)?;
327 }
328 }
329 }
330
331 Ok(msg.encode_to_vec())
332}
333
334fn deserialize_message(desc: MessageDescriptor, json: &[u8]) -> Result<DynamicMessage, TranscodeError> {
335 if json.is_empty() {
336 return Ok(DynamicMessage::new(desc));
337 }
338 let mut de = serde_json::Deserializer::from_slice(json);
339 let msg = DynamicMessage::deserialize(desc, &mut de)?;
340 de.end()?;
341 Ok(msg)
342}
343
344fn set_message_field(msg: &mut DynamicMessage, name: &str, json: &[u8]) -> Result<(), TranscodeError> {
346 let field = msg
347 .descriptor()
348 .get_field_by_name(name)
349 .ok_or_else(|| TranscodeError::Http(format!("unknown body field: {name}")))?;
350 match field.kind() {
351 Kind::Message(md) => {
352 let sub = deserialize_message(md, json)?;
353 msg.set_field(&field, Value::Message(sub));
354 Ok(())
355 }
356 _ => Err(TranscodeError::Http(format!("body field {name} must be a message"))),
357 }
358}
359
360fn set_by_path(msg: &mut DynamicMessage, path: &[String], raw: &str) -> Result<(), TranscodeError> {
362 let field = msg
363 .descriptor()
364 .get_field_by_name(&path[0])
365 .ok_or_else(|| TranscodeError::Http(format!("unknown field: {}", path[0])))?;
366
367 if path.len() == 1 {
368 let value = coerce(&field, raw)?;
369 if field.is_list() {
370 if let Some(list) = msg.get_field_mut(&field).as_list_mut() {
371 list.push(value);
372 }
373 } else {
374 msg.set_field(&field, value);
375 }
376 Ok(())
377 } else {
378 let sub = msg
379 .get_field_mut(&field)
380 .as_message_mut()
381 .ok_or_else(|| TranscodeError::Http(format!("field {} is not a message", path[0])))?;
382 set_by_path(sub, &path[1..], raw)
383 }
384}
385
386fn coerce(field: &prost_reflect::FieldDescriptor, raw: &str) -> Result<Value, TranscodeError> {
388 let num = |ok: Option<Value>| ok.ok_or_else(|| TranscodeError::Http(format!("invalid value for {}: {raw:?}", field.name())));
389 Ok(match field.kind() {
390 Kind::String => Value::String(raw.to_string()),
391 Kind::Bool => match raw {
392 "true" | "1" => Value::Bool(true),
393 "false" | "0" => Value::Bool(false),
394 _ => return Err(TranscodeError::Http(format!("invalid bool: {raw:?}"))),
395 },
396 Kind::Int32 | Kind::Sint32 | Kind::Sfixed32 => num(raw.parse().ok().map(Value::I32))?,
397 Kind::Int64 | Kind::Sint64 | Kind::Sfixed64 => num(raw.parse().ok().map(Value::I64))?,
398 Kind::Uint32 | Kind::Fixed32 => num(raw.parse().ok().map(Value::U32))?,
399 Kind::Uint64 | Kind::Fixed64 => num(raw.parse().ok().map(Value::U64))?,
400 Kind::Float => num(raw.parse().ok().map(Value::F32))?,
401 Kind::Double => num(raw.parse().ok().map(Value::F64))?,
402 Kind::Enum(e) => {
403 if let Ok(n) = raw.parse::<i32>() {
404 Value::EnumNumber(n)
405 } else {
406 let v = e
407 .values()
408 .find(|v| v.name() == raw)
409 .ok_or_else(|| TranscodeError::Http(format!("unknown enum value: {raw:?}")))?;
410 Value::EnumNumber(v.number())
411 }
412 }
413 Kind::Bytes => return Err(TranscodeError::Http("bytes fields cannot bind from path/query".into())),
414 Kind::Message(_) => return Err(TranscodeError::Http("cannot bind a scalar to a message field".into())),
415 })
416}
417
418fn parse_query(query: &str) -> Vec<(String, String)> {
420 query
421 .split('&')
422 .filter(|p| !p.is_empty())
423 .map(|pair| {
424 let (k, v) = pair.split_once('=').unwrap_or((pair, ""));
425 (decode_query(k), decode_query(v))
426 })
427 .collect()
428}
429
430fn decode_query(s: &str) -> String {
431 percent_decode(&s.replace('+', " "))
432}
433
434fn percent_decode(s: &str) -> String {
436 let bytes = s.as_bytes();
437 let mut out = Vec::with_capacity(bytes.len());
438 let mut i = 0;
439 while i < bytes.len() {
440 if bytes[i] == b'%' && i + 2 < bytes.len() {
441 if let Ok(b) = u8::from_str_radix(&s[i + 1..i + 3], 16) {
442 out.push(b);
443 i += 3;
444 continue;
445 }
446 }
447 out.push(bytes[i]);
448 i += 1;
449 }
450 String::from_utf8_lossy(&out).into_owned()
451}