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