Skip to main content

structured_proxy/transcode/
request.rs

1//! Request message construction for REST→gRPC transcoding.
2//!
3//! Builds the gRPC request message from three `google.api.http` sources, in
4//! precedence order: path parameters (highest), the request body, then query
5//! parameters (lowest, fill only). A JSON body is deserialized straight into
6//! the message; path and query values arrive as strings and are converted to
7//! each field's proto type as they are set.
8
9mod presence;
10
11use std::borrow::Cow;
12use std::collections::HashMap;
13
14use prost_reflect::{
15    DynamicMessage, FieldDescriptor, Kind, MessageDescriptor, ReflectMessage, Value,
16};
17use serde::de::value::StrDeserializer;
18use serde::Deserializer;
19use serde_json::Value as JsonValue;
20
21use presence::{has_special_json, OneEntry, Presence, Recording};
22
23/// How the HTTP request body maps onto the gRPC request message.
24#[derive(Debug, Clone, PartialEq, Eq)]
25#[non_exhaustive]
26pub enum BodyMapping {
27    /// No body is read; fields come from path + query (typical for GET/DELETE).
28    None,
29    /// The entire body maps to the message root (`body: "*"`).
30    Root,
31    /// The body maps to a single named field of the message (`body: "field"`).
32    Field(String),
33}
34
35impl BodyMapping {
36    /// Parse the `body` value of a `google.api.http` rule.
37    ///
38    /// `""` (absent) → [`BodyMapping::None`], `"*"` → [`BodyMapping::Root`],
39    /// any other string → [`BodyMapping::Field`].
40    pub fn parse(raw: &str) -> Self {
41        match raw {
42            "" => BodyMapping::None,
43            "*" => BodyMapping::Root,
44            field => BodyMapping::Field(field.to_string()),
45        }
46    }
47}
48
49/// The HTTP request body the [`BodyMapping`] reads.
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub enum Body<'a> {
52    /// No body is read. Under [`BodyMapping::Field`] the field still belongs
53    /// to the body (a raw `google.api.HttpBody` filled afterwards), so query
54    /// parameters leave it alone.
55    Absent,
56    /// A JSON body (ProtoJSON of the mapped message or field). Empty means
57    /// `{}`.
58    Json(&'a [u8]),
59    /// An `application/x-www-form-urlencoded` body, bound field by field like
60    /// query parameters.
61    Form(&'a [u8]),
62}
63
64impl<'a> Body<'a> {
65    /// The body as its media type (no parameters) says: a form for
66    /// `application/x-www-form-urlencoded`, JSON otherwise.
67    ///
68    /// # Examples
69    ///
70    /// ```
71    /// use structured_proxy::transcode::request::Body;
72    ///
73    /// let form = Body::new(Some("application/x-www-form-urlencoded"), b"a=1");
74    /// assert_eq!(form, Body::Form(b"a=1"));
75    /// assert_eq!(Body::new(None, b"{}"), Body::Json(b"{}"));
76    /// ```
77    pub fn new(content_type: Option<&str>, bytes: &'a [u8]) -> Self {
78        match content_type {
79            Some(ct) if ct.starts_with("application/x-www-form-urlencoded") => Body::Form(bytes),
80            _ => Body::Json(bytes),
81        }
82    }
83}
84
85/// Build the request message from the body mapping, path params, and query.
86///
87/// Path-bound fields win over the body, and the body wins over query parameters
88/// (query only fills fields the body did not set, even to their default).
89/// Unknown query, form and path keys are dropped rather than rejected,
90/// matching common transcoder behavior; an unknown key in a JSON body is an
91/// error, as ProtoJSON has it.
92///
93/// # Errors
94/// A body that is not the ProtoJSON of the mapped message or field, a form
95/// body mapped onto a field that is not a message, or a path, query or form
96/// value that is not a valid value of its field. The message is meant for the
97/// client, as the text of an `INVALID_ARGUMENT` answer.
98///
99/// # Examples
100///
101/// ```
102/// use std::collections::HashMap;
103/// use prost_reflect::{MessageDescriptor, Value};
104/// use structured_proxy::transcode::request::{build_request_message, Body, BodyMapping};
105///
106/// // message Page { int32 size = 1; string cursor = 2; }
107/// # fn page() -> MessageDescriptor {
108/// #     use prost_reflect::prost::Message;
109/// #     use prost_reflect::prost_types::{
110/// #         field_descriptor_proto::{Label, Type},
111/// #         DescriptorProto, FieldDescriptorProto, FileDescriptorProto, FileDescriptorSet,
112/// #     };
113/// #     let field = |name: &str, number, ty: Type| FieldDescriptorProto {
114/// #         name: Some(name.into()),
115/// #         number: Some(number),
116/// #         label: Some(Label::Optional as i32),
117/// #         r#type: Some(ty as i32),
118/// #         ..Default::default()
119/// #     };
120/// #     let file = FileDescriptorProto {
121/// #         name: Some("page.proto".into()),
122/// #         package: Some("example".into()),
123/// #         message_type: vec![DescriptorProto {
124/// #             name: Some("Page".into()),
125/// #             field: vec![field("size", 1, Type::Int32), field("cursor", 2, Type::String)],
126/// #             ..Default::default()
127/// #         }],
128/// #         syntax: Some("proto3".into()),
129/// #         ..Default::default()
130/// #     };
131/// #     let set = FileDescriptorSet { file: vec![file] };
132/// #     prost_reflect::DescriptorPool::decode(set.encode_to_vec().as_slice())
133/// #         .unwrap()
134/// #         .get_message_by_name("example.Page")
135/// #         .unwrap()
136/// # }
137/// let message = build_request_message(
138///     &page(),
139///     &BodyMapping::Root,
140///     Body::Json(br#"{"size": 0}"#),
141///     &HashMap::new(),
142///     Some("size=50&cursor=abc"),
143/// )
144/// .unwrap();
145/// // The body set `size`, to its default: the query cannot replace it.
146/// assert_eq!(message.get_field_by_name("size").unwrap().as_i32(), Some(0));
147/// // It left `cursor` out: the query fills it.
148/// assert_eq!(
149///     *message.get_field_by_name("cursor").unwrap(),
150///     Value::String("abc".into())
151/// );
152/// ```
153pub fn build_request_message(
154    input: &MessageDescriptor,
155    mapping: &BodyMapping,
156    body: Body<'_>,
157    path_params: &HashMap<String, String>,
158    raw_query: Option<&str>,
159) -> Result<DynamicMessage, String> {
160    // Parsed without allocating wherever nothing needs unescaping.
161    let query: Vec<(Cow<'_, str>, Cow<'_, str>)> = match raw_query {
162        Some(q) => url::form_urlencoded::parse(q.as_bytes()).collect(),
163        None => Vec::new(),
164    };
165    build(input, mapping, body, path_params, &query)
166}
167
168/// Which source a string value comes from, and so whether it overwrites.
169#[derive(Clone, Copy, PartialEq, Eq)]
170enum Binding {
171    /// Path parameters and form fields set their field.
172    Set,
173    /// Query parameters fill only what nothing else set.
174    Fill,
175}
176
177fn build(
178    input: &MessageDescriptor,
179    mapping: &BodyMapping,
180    body: Body<'_>,
181    path_params: &HashMap<String, String>,
182    query: &[(Cow<'_, str>, Cow<'_, str>)],
183) -> Result<DynamicMessage, String> {
184    // What the body and the path set matters only when a query can fill.
185    let body_len = match body {
186        Body::Json(bytes) | Body::Form(bytes) => bytes.len(),
187        Body::Absent => 0,
188    };
189    let mut presence = (!query.is_empty()).then(|| Presence::for_body(body_len));
190    // The fields of the path being bound, reused across keys.
191    let mut path = Vec::new();
192
193    // Whether a field inside a well-known type was set from a string.
194    let mut inside = false;
195
196    let mut message = match (mapping, body) {
197        (BodyMapping::None, _) => DynamicMessage::new(input.clone()),
198        (BodyMapping::Field(name), Body::Absent) => {
199            if let (Some(presence), Some(field)) = (&mut presence, input.get_field_by_name(name)) {
200                presence.record_path(&[field], false);
201            }
202            DynamicMessage::new(input.clone())
203        }
204        (BodyMapping::Root, Body::Absent) => DynamicMessage::new(input.clone()),
205        // An empty body is `{}`, whatever its media type.
206        (_, Body::Json(bytes) | Body::Form(bytes)) if bytes.is_empty() => {
207            json_body(input, mapping, b"{}", path_params, presence.as_mut())?
208        }
209        (_, Body::Json(bytes)) => json_body(input, mapping, bytes, path_params, presence.as_mut())?,
210        (_, Body::Form(bytes)) => {
211            let pairs: Vec<(Cow<'_, str>, Cow<'_, str>)> =
212                url::form_urlencoded::parse(bytes).collect();
213            let form = Form {
214                pairs: &pairs,
215                path_params,
216            };
217            let (message, form_inside) =
218                form_body(input, mapping, form, presence.as_mut(), &mut path)?;
219            inside = form_inside;
220            message
221        }
222    };
223
224    let root_inside = has_special_json(input);
225    // Path params win over everything (the router already matched them).
226    for (key, raw) in path_params {
227        let target = Target {
228            root: input,
229            inside: root_inside,
230            prefix: None,
231            key,
232            values: &[raw.as_str()],
233        };
234        inside |= bind(
235            &mut message,
236            target,
237            Binding::Set,
238            presence.as_mut(),
239            &mut path,
240        )?;
241    }
242
243    if let Some(presence) = &mut presence {
244        let start = Start {
245            root: input,
246            inside: root_inside,
247            prefix: None,
248        };
249        inside |= bind_pairs(
250            &mut message,
251            query,
252            start,
253            Binding::Fill,
254            Some(presence),
255            None,
256            &mut path,
257        )?;
258    }
259    if inside {
260        check_well_known(&message)?;
261    }
262    Ok(message)
263}
264
265/// Refuse a well-known type set field by field whose value its JSON form
266/// could not hold: a Timestamp's `nanos` past 999999999, a Duration whose
267/// `seconds` and `nanos` differ in sign, an `Any` of an unknown type.
268/// Checked once all keys are bound, as they arrive in any order.
269fn check_well_known(message: &DynamicMessage) -> Result<(), String> {
270    let invalid = |e: &dyn std::fmt::Display| format!("invalid value for a well-known type: {e}");
271    check_time_ranges(message).map_err(|e| invalid(&e))?;
272    // Writing the ProtoJSON checks the rest (an `Any` must name a known type
273    // and decode as it, a `FieldMask` path must round-trip through camelCase,
274    // a `Value` number must be finite); the text is not kept.
275    serde_json::to_writer(std::io::sink(), message).map_err(|e| invalid(&e))
276}
277
278/// Check every `Timestamp` and `Duration` in `message` against the ranges
279/// their definitions set. prost-reflect's writer checks only part of them and
280/// carries excess nanos into seconds, which would change the value.
281fn check_time_ranges(message: &DynamicMessage) -> Result<(), &'static str> {
282    let field = |name| message.get_field_by_name(name);
283    match message.descriptor().full_name() {
284        // google/protobuf/timestamp.proto: seconds from 0001-01-01T00:00:00Z
285        // to 9999-12-31T23:59:59Z, nanos in [0, 999999999].
286        "google.protobuf.Timestamp" => {
287            let seconds = field("seconds").and_then(|v| v.as_i64()).unwrap_or(0);
288            let nanos = field("nanos").and_then(|v| v.as_i32()).unwrap_or(0);
289            if !(-62_135_596_800..=253_402_300_799).contains(&seconds)
290                || !(0..=999_999_999).contains(&nanos)
291            {
292                return Err("timestamp out of range");
293            }
294        }
295        // google/protobuf/duration.proto: seconds within +-315576000000,
296        // nanos within +-999999999, and nanos of the same sign as a non-zero
297        // seconds.
298        "google.protobuf.Duration" => {
299            let seconds = field("seconds").and_then(|v| v.as_i64()).unwrap_or(0);
300            let nanos = field("nanos").and_then(|v| v.as_i32()).unwrap_or(0);
301            if seconds.unsigned_abs() > 315_576_000_000
302                || nanos.unsigned_abs() > 999_999_999
303                || (seconds > 0 && nanos < 0)
304                || (seconds < 0 && nanos > 0)
305            {
306                return Err("duration out of range");
307            }
308        }
309        _ => {}
310    }
311    for (_, value) in message.fields() {
312        check_value_time_ranges(value)?;
313    }
314    Ok(())
315}
316
317fn check_value_time_ranges(value: &Value) -> Result<(), &'static str> {
318    match value {
319        Value::Message(inner) => check_time_ranges(inner),
320        Value::List(items) => items.iter().try_for_each(check_value_time_ranges),
321        Value::Map(entries) => entries.values().try_for_each(check_value_time_ranges),
322        _ => Ok(()),
323    }
324}
325
326/// Where string values go: the field at the dotted path `key`, below the
327/// message field `prefix` of `root` when there is one, below `root` otherwise.
328#[derive(Clone, Copy)]
329struct Target<'a> {
330    root: &'a MessageDescriptor,
331    /// Whether the message the key starts in is a well-known type.
332    inside: bool,
333    prefix: Option<&'a FieldDescriptor>,
334    key: &'a str,
335    /// Every value of the key, in request order.
336    values: &'a [&'a str],
337}
338
339/// Deserialize a JSON body in one pass.
340///
341/// The path wins over the body, so a body value for a field the path binds
342/// never decides the request, even when it is not a valid value of that field.
343/// Such a body fails the one pass; it is then read again without those keys.
344/// Only a failing body pays for the second read.
345fn json_body(
346    input: &MessageDescriptor,
347    mapping: &BodyMapping,
348    bytes: &[u8],
349    path_params: &HashMap<String, String>,
350    mut presence: Option<&mut Presence>,
351) -> Result<DynamicMessage, String> {
352    // A `null` body sets nothing, like `{}`.
353    if matches!(mapping, BodyMapping::Root) && trim_json_whitespace(bytes) == b"null" {
354        return Ok(DynamicMessage::new(input.clone()));
355    }
356    let mut de = serde_json::Deserializer::from_slice(bytes);
357    let first = deserialize(input, mapping, &mut de, presence.as_deref_mut())
358        .and_then(|message| de.end().map(|()| message));
359    let error = match first {
360        Ok(message) => return Ok(message),
361        Err(error) => error,
362    };
363    let fail = |e: serde_json::Error| format!("failed to decode request body: {e}");
364    if path_params.is_empty() {
365        return Err(fail(error));
366    }
367    // Not JSON at all: the path cannot change that.
368    let Ok(mut value) = serde_json::from_slice::<JsonValue>(bytes) else {
369        return Err(fail(error));
370    };
371    strip_path_fields(&mut value, input, mapping, path_params);
372    if let Some(presence) = presence.as_deref_mut() {
373        presence.clear();
374    }
375    deserialize(input, mapping, value, presence).map_err(fail)
376}
377
378/// Remove from `body` every key the path binds, under its JSON or proto name:
379/// the path sets those fields whatever the body holds.
380fn strip_path_fields(
381    body: &mut JsonValue,
382    input: &MessageDescriptor,
383    mapping: &BodyMapping,
384    path_params: &HashMap<String, String>,
385) {
386    for key in path_params.keys() {
387        let mut segments = key.split('.');
388        let desc = match mapping {
389            BodyMapping::Root => input.clone(),
390            // Under `body: "field"` the body is that field's value, so only
391            // the path keys below the field reach into it.
392            BodyMapping::Field(name) => {
393                if segments.next() != Some(name.as_str()) {
394                    continue;
395                }
396                match input.get_field_by_name(name).map(|f| f.kind()) {
397                    Some(Kind::Message(inner)) => inner,
398                    _ => continue,
399                }
400            }
401            BodyMapping::None => return,
402        };
403        remove_path(body, desc, segments);
404    }
405}
406
407/// Remove the field at `segments` below `desc` from the JSON object `value`.
408fn remove_path<'k>(
409    mut value: &mut JsonValue,
410    mut desc: MessageDescriptor,
411    segments: impl Iterator<Item = &'k str>,
412) {
413    let mut segments = segments.peekable();
414    while let Some(segment) = segments.next() {
415        let Some(field) = field_named(&desc, segment, &mut false) else {
416            return;
417        };
418        let Some(object) = value.as_object_mut() else {
419            return;
420        };
421        if segments.peek().is_none() {
422            object.remove(field.json_name());
423            object.remove(field.name());
424            return;
425        }
426        let Kind::Message(inner) = field.kind() else {
427            return;
428        };
429        let key = if object.contains_key(field.json_name()) {
430            field.json_name()
431        } else {
432            field.name()
433        };
434        let Some(next) = object.get_mut(key) else {
435            return;
436        };
437        value = next;
438        desc = inner;
439    }
440}
441
442fn trim_json_whitespace(bytes: &[u8]) -> &[u8] {
443    let ws = |b: &u8| matches!(b, b' ' | b'\t' | b'\n' | b'\r');
444    let start = bytes.iter().position(|b| !ws(b)).unwrap_or(bytes.len());
445    let end = bytes.iter().rposition(|b| !ws(b)).map_or(start, |i| i + 1);
446    &bytes[start..end]
447}
448
449/// Deserialize `body` as the mapping says: the whole input message, or the
450/// value of one of its fields.
451fn deserialize<'de, D: Deserializer<'de>>(
452    input: &MessageDescriptor,
453    mapping: &BodyMapping,
454    body: D,
455    presence: Option<&mut Presence>,
456) -> Result<DynamicMessage, D::Error> {
457    match mapping {
458        BodyMapping::Field(name) => {
459            // Keyed by the JSON name, which prost-reflect looks up first, so
460            // no other field's JSON name can shadow it.
461            let field = input.get_field_by_name(name);
462            let key = field.as_ref().map_or(name.as_str(), |f| f.json_name());
463            deserialize_input(input, OneEntry { key, value: body }, presence)
464        }
465        _ => deserialize_input(input, body, presence),
466    }
467}
468
469fn deserialize_input<'de, D: Deserializer<'de>>(
470    input: &MessageDescriptor,
471    de: D,
472    presence: Option<&mut Presence>,
473) -> Result<DynamicMessage, D::Error> {
474    let Some(presence) = presence else {
475        return DynamicMessage::deserialize(input.clone(), de);
476    };
477    if has_special_json(input) {
478        presence.record_whole();
479        return DynamicMessage::deserialize(input.clone(), de);
480    }
481    DynamicMessage::deserialize(input.clone(), Recording::root(de, presence))
482}
483
484/// Bind a form body field by field, into the input message or the message
485/// field the mapping names.
486fn form_body(
487    input: &MessageDescriptor,
488    mapping: &BodyMapping,
489    form: Form<'_, '_>,
490    mut presence: Option<&mut Presence>,
491    path: &mut Vec<FieldDescriptor>,
492) -> Result<(DynamicMessage, bool), String> {
493    let Form { pairs, path_params } = form;
494    let mut message = DynamicMessage::new(input.clone());
495    let (prefix, start_inside) = match mapping {
496        BodyMapping::Field(name) => {
497            let (field, inner) = input
498                .get_field_by_name(name)
499                .filter(|f| !f.is_list() && !f.is_map())
500                .and_then(|f| match f.kind() {
501                    Kind::Message(inner) => Some((f, inner)),
502                    _ => None,
503                })
504                .ok_or_else(|| format!("a form body cannot fill field `{name}`"))?;
505            // The field is the body's even when the form is empty of it.
506            message.get_field_mut(&field);
507            if let Some(presence) = presence.as_deref_mut() {
508                presence.record_path(std::slice::from_ref(&field), true);
509            }
510            (Some(field), has_special_json(&inner))
511        }
512        _ => (None, has_special_json(input)),
513    };
514    let start = Start {
515        root: input,
516        inside: start_inside,
517        prefix: prefix.as_ref(),
518    };
519    let inside = bind_pairs(
520        &mut message,
521        pairs,
522        start,
523        Binding::Set,
524        presence,
525        Some(path_params),
526        path,
527    )?;
528    Ok((message, inside))
529}
530
531/// A form body and the path parameters that override it.
532#[derive(Clone, Copy)]
533struct Form<'p, 'q> {
534    pairs: &'p [(Cow<'q, str>, Cow<'q, str>)],
535    path_params: &'p HashMap<String, String>,
536}
537
538/// Whether the dotted path key `bound` names the field at the end of `path`
539/// or one above it. Path keys start at the input message, as `path` does.
540fn covers(bound: &str, path: &[FieldDescriptor]) -> bool {
541    let mut fields = path.iter();
542    bound.split('.').all(|segment| {
543        fields
544            .next()
545            .is_some_and(|f| segment == f.name() || segment == f.json_name())
546    })
547}
548
549/// Where the keys of a query or form start: `root`, or its message field
550/// `prefix`; `inside` when that message is a well-known type.
551#[derive(Clone, Copy)]
552struct Start<'a> {
553    root: &'a MessageDescriptor,
554    inside: bool,
555    prefix: Option<&'a FieldDescriptor>,
556}
557
558/// Bind every key of `pairs` to its field, with all the values of that field
559/// in request order, and tell whether a field inside a well-known type was
560/// set. A key the path binds (`path_params`, for a form) is left to the path.
561///
562/// Keys are grouped by the field they name, not by spelling: `tag_ids` and
563/// `tagIds` are one field, and binding them apart would let one replace the
564/// other's values.
565fn bind_pairs(
566    message: &mut DynamicMessage,
567    pairs: &[(Cow<'_, str>, Cow<'_, str>)],
568    start: Start<'_>,
569    binding: Binding,
570    mut presence: Option<&mut Presence>,
571    path_params: Option<&HashMap<String, String>>,
572    path: &mut Vec<FieldDescriptor>,
573) -> Result<bool, String> {
574    // A stable sort keeps the values of each spelling in request order.
575    let mut order: Vec<usize> = (0..pairs.len()).collect();
576    order.sort_by(|&a, &b| pairs[a].0.cmp(&pairs[b].0));
577    let mut values = Vec::new();
578    let mut inside = false;
579    // Spellings that used a JSON name, by the proto-name key they stand for.
580    // Only a request that mixes the two spellings of a field pays for this.
581    let mut aliased: Vec<(String, &[usize])> = Vec::new();
582    // Spellings whose own values failed to bind, with the error.
583    let mut failed: Vec<(&str, String)> = Vec::new();
584    for run in order.chunk_by(|&a, &b| pairs[a].0 == pairs[b].0) {
585        values.clear();
586        values.extend(run.iter().map(|&i| pairs[i].1.as_ref()));
587        let target = Target {
588            root: start.root,
589            inside: start.inside,
590            prefix: start.prefix,
591            key: &pairs[run[0]].0,
592            values: &values,
593        };
594        let Some(resolved) = resolve(target, path) else {
595            continue;
596        };
597        if resolved.aliased {
598            let own = &path[usize::from(start.prefix.is_some())..];
599            let canonical = own.iter().map(|f| f.name()).collect::<Vec<_>>().join(".");
600            aliased.push((canonical, run));
601            continue;
602        }
603        let bound = bind_resolved(
604            message,
605            path,
606            &values,
607            binding,
608            presence.as_deref_mut(),
609            path_params,
610        );
611        match bound {
612            Ok(set) => inside |= set && resolved.inside,
613            // A failed assignment leaves the message as it was (parsing and
614            // the oneof check come before any write). The error is held: when
615            // the field also came under its JSON name, the values of both
616            // spellings decide the outcome below, not this spelling alone.
617            Err(e) => failed.push((pairs[run[0]].0.as_ref(), e)),
618        }
619    }
620    aliased.sort_by(|a, b| a.0.cmp(&b.0));
621    // Failures come in key order; the first one no merge below redoes wins.
622    let covered = |key: &str| {
623        aliased
624            .binary_search_by(|(c, _)| c.as_str().cmp(key))
625            .is_ok()
626    };
627    if let Some((_, e)) = failed.into_iter().find(|(key, _)| !covered(key)) {
628        return Err(e);
629    }
630    if aliased.is_empty() {
631        return Ok(inside);
632    }
633    let mut indices = Vec::new();
634    for group in aliased.chunk_by(|a, b| a.0 == b.0) {
635        let canonical = group[0].0.as_str();
636        indices.clear();
637        for (_, run) in group {
638            indices.extend_from_slice(run);
639        }
640        // The proto-name spelling, bound above with its own values only.
641        let from = order.partition_point(|&i| pairs[i].0.as_ref() < canonical);
642        let to = order.partition_point(|&i| pairs[i].0.as_ref() <= canonical);
643        indices.extend_from_slice(&order[from..to]);
644        indices.sort_unstable();
645        values.clear();
646        values.extend(indices.iter().map(|&i| pairs[i].1.as_ref()));
647        let target = Target {
648            root: start.root,
649            inside: start.inside,
650            prefix: start.prefix,
651            key: canonical,
652            values: &values,
653        };
654        let Some(resolved) = resolve(target, path) else {
655            continue;
656        };
657        inside |= bind_resolved(
658            message,
659            path,
660            &values,
661            binding,
662            presence.as_deref_mut(),
663            path_params,
664        )? && resolved.inside;
665    }
666    Ok(inside)
667}
668
669/// Assign `values` to the resolved `path` unless a path parameter binds it.
670/// True when it was assigned.
671fn bind_resolved(
672    message: &mut DynamicMessage,
673    path: &[FieldDescriptor],
674    values: &[&str],
675    binding: Binding,
676    presence: Option<&mut Presence>,
677    path_params: Option<&HashMap<String, String>>,
678) -> Result<bool, String> {
679    // The path sets these fields whatever the form holds, so their form
680    // values are never decoded, as a JSON body's are not.
681    if path_params.is_some_and(|bound| bound.keys().any(|key| covers(key, path))) {
682        return Ok(false);
683    }
684    assign(message, path, values, binding, presence)?;
685    Ok(true)
686}
687
688/// Set the field `target` names to its values (see [`resolve`] and
689/// [`assign`]). `path` is a buffer for the fields on the way. True when the
690/// field lies inside a well-known type.
691fn bind(
692    message: &mut DynamicMessage,
693    target: Target<'_>,
694    binding: Binding,
695    presence: Option<&mut Presence>,
696    path: &mut Vec<FieldDescriptor>,
697) -> Result<bool, String> {
698    let Some(resolved) = resolve(target, path) else {
699        return Ok(false);
700    };
701    assign(message, path, target.values, binding, presence)?;
702    Ok(resolved.inside)
703}
704
705/// What [`resolve`] found besides the fields on the way.
706#[derive(Clone, Copy)]
707struct Resolved {
708    /// The field lies inside a well-known type.
709    inside: bool,
710    /// A segment named its field by a JSON name that is not its proto name.
711    aliased: bool,
712}
713
714/// Resolve the dotted key of `target` into the fields on the way, in `path`,
715/// and tell whether the field lies inside a well-known type.
716///
717/// Each segment names a field by its proto name or its JSON name, as
718/// ProtoJSON reads a key. `None` when the key is dropped: it names no field,
719/// or passes through a field that is not a singular message. A field inside
720/// a well-known type (`at.nanos` of a Timestamp) is bound like any other, and
721/// the value it leaves is checked once the request is built (see
722/// [`check_well_known`]).
723fn resolve(target: Target<'_>, path: &mut Vec<FieldDescriptor>) -> Option<Resolved> {
724    path.clear();
725    let mut inside = target.inside;
726    let mut aliased = false;
727    let mut owned;
728    let mut desc = match target.prefix {
729        Some(field) => {
730            let Kind::Message(inner) = field.kind() else {
731                return None;
732            };
733            path.push(field.clone());
734            owned = inner;
735            &owned
736        }
737        None => target.root,
738    };
739    let mut segments = target.key.split('.');
740    let mut leaf = segments.next().unwrap_or_default();
741    for next in segments {
742        let field = field_named(desc, leaf, &mut aliased)?;
743        let Kind::Message(inner) = field.kind() else {
744            return None;
745        };
746        if field.is_list() || field.is_map() {
747            return None;
748        }
749        inside |= has_special_json(&inner);
750        path.push(field);
751        owned = inner;
752        desc = &owned;
753        leaf = next;
754    }
755    path.push(field_named(desc, leaf, &mut aliased)?);
756    Some(Resolved { inside, aliased })
757}
758
759/// The field of `desc` named `name`, by its proto name or else its JSON name;
760/// `aliased` is set when only the JSON name matched.
761fn field_named(
762    desc: &MessageDescriptor,
763    name: &str,
764    aliased: &mut bool,
765) -> Option<FieldDescriptor> {
766    if let Some(field) = desc.get_field_by_name(name) {
767        return Some(field);
768    }
769    let field = desc.get_field_by_json_name(name)?;
770    *aliased = true;
771    Some(field)
772}
773
774/// Set the field at the end of `path` to `values`: all of them for a repeated
775/// field, the last one otherwise. A [`Binding::Fill`] leaves a field the body
776/// or the path set, or one below a field they set to a non-object, alone.
777fn assign(
778    message: &mut DynamicMessage,
779    path: &[FieldDescriptor],
780    values: &[&str],
781    binding: Binding,
782    mut presence: Option<&mut Presence>,
783) -> Result<(), String> {
784    if binding == Binding::Fill && presence.as_deref_mut().is_some_and(|p| p.blocks(path)) {
785        return Ok(());
786    }
787
788    let (field, parents) = path
789        .split_last()
790        .expect("a resolved path ends in its field");
791    let value = field_value(field, values)?;
792    let mut holder = message;
793    for parent in parents {
794        check_oneof(holder, parent)?;
795        holder = match holder.get_field_mut(parent) {
796            Value::Message(inner) => inner,
797            _ => unreachable!("a singular message field holds a message"),
798        };
799    }
800    check_oneof(holder, field)?;
801    holder.set_field(field, value);
802    if binding == Binding::Set {
803        if let Some(presence) = presence {
804            presence.record_path(path, false);
805        }
806    }
807    Ok(())
808}
809
810/// Refuse to set `field` over another member of its oneof, as ProtoJSON
811/// refuses a body naming two of them.
812fn check_oneof(message: &DynamicMessage, field: &FieldDescriptor) -> Result<(), String> {
813    let Some(oneof) = field.containing_oneof() else {
814        return Ok(());
815    };
816    if oneof
817        .fields()
818        .any(|other| other.number() != field.number() && message.has_field(&other))
819    {
820        return Err(format!(
821            "multiple fields provided for oneof '{}'",
822            oneof.name()
823        ));
824    }
825    Ok(())
826}
827
828/// The value of `field` from its string forms.
829fn field_value(field: &FieldDescriptor, raw: &[&str]) -> Result<Value, String> {
830    let invalid = |e: String| format!("invalid value for field `{}`: {e}", field.name());
831    if field.is_map() {
832        return Err(invalid("a map field cannot be set from a string".into()));
833    }
834    let kind = field.kind();
835    if field.is_list() {
836        return raw
837            .iter()
838            .map(|raw| scalar(&kind, raw))
839            .collect::<Result<Vec<_>, _>>()
840            .map(Value::List)
841            .map_err(invalid);
842    }
843    let raw = raw.last().expect("a bound key has a value");
844    scalar(&kind, raw).map_err(invalid)
845}
846
847/// A string as a value of `kind`, read as the ProtoJSON of that value would
848/// be: numbers in decimal (floats also `NaN`, `Infinity`, `-Infinity`), bools
849/// as `true`/`false`, enums by name, bytes as base64, and a message type from
850/// its JSON string form (a `Timestamp`, `Duration`, `FieldMask`, wrapper).
851fn scalar(kind: &Kind, raw: &str) -> Result<Value, String> {
852    Ok(match kind {
853        Kind::Double => Value::F64(parse_f64(raw)?),
854        Kind::Float => Value::F32(parse_f32(raw)?),
855        Kind::Int32 | Kind::Sint32 | Kind::Sfixed32 => Value::I32(parse(raw)?),
856        Kind::Int64 | Kind::Sint64 | Kind::Sfixed64 => Value::I64(parse(raw)?),
857        Kind::Uint32 | Kind::Fixed32 => Value::U32(parse(raw)?),
858        Kind::Uint64 | Kind::Fixed64 => Value::U64(parse(raw)?),
859        Kind::Bool => Value::Bool(parse(raw)?),
860        Kind::String => Value::String(raw.to_owned()),
861        Kind::Bytes => Value::Bytes(decode_base64(raw)?.into()),
862        Kind::Enum(desc) => Value::EnumNumber(
863            desc.get_value_by_name(raw)
864                .ok_or_else(|| format!("unrecognized enum value '{raw}'"))?
865                .number(),
866        ),
867        Kind::Message(desc) => Value::Message(
868            DynamicMessage::deserialize(
869                desc.clone(),
870                StrDeserializer::<serde::de::value::Error>::new(raw),
871            )
872            .map_err(|e| e.to_string())?,
873        ),
874    })
875}
876
877fn parse<T: std::str::FromStr>(raw: &str) -> Result<T, String>
878where
879    T::Err: std::fmt::Display,
880{
881    raw.parse().map_err(|e: T::Err| e.to_string())
882}
883
884/// Special float names of ProtoJSON.
885fn special_float(raw: &str) -> Option<f64> {
886    match raw {
887        "Infinity" => Some(f64::INFINITY),
888        "-Infinity" => Some(f64::NEG_INFINITY),
889        "NaN" => Some(f64::NAN),
890        _ => None,
891    }
892}
893
894/// A ProtoJSON double: one of the special names, or a decimal with a finite
895/// value. Rust's parser also reads `inf`, `nan` and `infinity` in any case,
896/// and an overflowing decimal as infinity; ProtoJSON has none of those
897/// (protobuf JSON mapping, "float, double").
898fn parse_f64(raw: &str) -> Result<f64, String> {
899    if let Some(special) = special_float(raw) {
900        return Ok(special);
901    }
902    match raw.parse::<f64>() {
903        Ok(value) if value.is_finite() => Ok(value),
904        Ok(_) => Err("value out of range, or not a decimal number".to_string()),
905        Err(e) => Err(e.to_string()),
906    }
907}
908
909/// A ProtoJSON float: a special name, or a finite decimal within the `f32`
910/// range.
911fn parse_f32(raw: &str) -> Result<f32, String> {
912    let wide = parse_f64(raw)?;
913    if wide.is_finite() && (wide < f64::from(f32::MIN) || wide > f64::from(f32::MAX)) {
914        return Err("float value out of range".to_string());
915    }
916    // A special value casts to itself; a finite one is in range, so the cast
917    // rounds instead of saturating.
918    Ok(wide as f32)
919}
920
921/// Base64 as ProtoJSON reads it: the standard or the URL-safe alphabet,
922/// padded or not.
923fn decode_base64(raw: &str) -> Result<Vec<u8>, String> {
924    use base64::alphabet;
925    use base64::engine::{DecodePaddingMode, GeneralPurpose, GeneralPurposeConfig};
926    use base64::{DecodeError, Engine};
927
928    const CONFIG: GeneralPurposeConfig = GeneralPurposeConfig::new()
929        .with_decode_allow_trailing_bits(true)
930        .with_decode_padding_mode(DecodePaddingMode::Indifferent);
931    const STANDARD: GeneralPurpose = GeneralPurpose::new(&alphabet::STANDARD, CONFIG);
932    const URL_SAFE: GeneralPurpose = GeneralPurpose::new(&alphabet::URL_SAFE, CONFIG);
933
934    let mut buf = Vec::new();
935    match STANDARD.decode_vec(raw, &mut buf) {
936        Ok(()) => Ok(buf),
937        Err(DecodeError::InvalidByte(_, b'-' | b'_')) => {
938            buf.clear();
939            URL_SAFE
940                .decode_vec(raw, &mut buf)
941                .map(|()| buf)
942                .map_err(|e| format!("invalid base64: {e}"))
943        }
944        Err(e) => Err(format!("invalid base64: {e}")),
945    }
946}
947
948#[cfg(test)]
949mod tests;