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