mod presence;
use std::borrow::Cow;
use std::collections::HashMap;
use prost_reflect::{
DynamicMessage, FieldDescriptor, Kind, MessageDescriptor, ReflectMessage, Value,
};
use serde::de::value::StrDeserializer;
use serde::Deserializer;
use serde_json::Value as JsonValue;
use presence::{has_special_json, OneEntry, Presence, Recording};
pub(crate) trait PathFields {
fn fields(&self) -> impl Iterator<Item = (&str, &str)>;
fn is_empty(&self) -> bool {
self.fields().next().is_none()
}
}
impl PathFields for HashMap<String, String> {
fn fields(&self) -> impl Iterator<Item = (&str, &str)> {
self.iter()
.map(|(key, value)| (key.as_str(), value.as_str()))
}
}
impl PathFields for [(&str, Cow<'_, str>)] {
fn fields(&self) -> impl Iterator<Item = (&str, &str)> {
self.iter().map(|(key, value)| (*key, value.as_ref()))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum BodyMapping {
None,
Root,
Field(String),
}
impl BodyMapping {
pub fn parse(raw: &str) -> Self {
match raw {
"" => BodyMapping::None,
"*" => BodyMapping::Root,
field => BodyMapping::Field(field.to_string()),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Body<'a> {
Absent,
Json(&'a [u8]),
Form(&'a [u8]),
}
impl<'a> Body<'a> {
pub fn new(content_type: Option<&str>, bytes: &'a [u8]) -> Self {
match content_type {
Some(ct) if ct.starts_with("application/x-www-form-urlencoded") => Body::Form(bytes),
_ => Body::Json(bytes),
}
}
}
pub fn build_request_message(
input: &MessageDescriptor,
mapping: &BodyMapping,
body: Body<'_>,
path_params: &HashMap<String, String>,
raw_query: Option<&str>,
) -> Result<DynamicMessage, String> {
build_message(input, mapping, body, path_params, raw_query)
}
pub(crate) fn build_message<P: PathFields + ?Sized>(
input: &MessageDescriptor,
mapping: &BodyMapping,
body: Body<'_>,
path_params: &P,
raw_query: Option<&str>,
) -> Result<DynamicMessage, String> {
let query: Vec<(Cow<'_, str>, Cow<'_, str>)> = match raw_query {
Some(q) => url::form_urlencoded::parse(q.as_bytes()).collect(),
None => Vec::new(),
};
build(input, mapping, body, path_params, &query)
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Binding {
Set,
Fill,
}
fn build<P: PathFields + ?Sized>(
input: &MessageDescriptor,
mapping: &BodyMapping,
body: Body<'_>,
path_params: &P,
query: &[(Cow<'_, str>, Cow<'_, str>)],
) -> Result<DynamicMessage, String> {
let body_len = match body {
Body::Json(bytes) | Body::Form(bytes) => bytes.len(),
Body::Absent => 0,
};
let mut presence = (!query.is_empty()).then(|| Presence::for_body(body_len));
let mut path = Vec::new();
let mut inside = false;
let mut message = match (mapping, body) {
(BodyMapping::None, _) => DynamicMessage::new(input.clone()),
(BodyMapping::Field(name), Body::Absent) => {
if let (Some(presence), Some(field)) = (&mut presence, input.get_field_by_name(name)) {
presence.record_path(&[field], false);
}
DynamicMessage::new(input.clone())
}
(BodyMapping::Root, Body::Absent) => DynamicMessage::new(input.clone()),
(_, Body::Json(bytes) | Body::Form(bytes)) if bytes.is_empty() => {
json_body(input, mapping, b"{}", path_params, presence.as_mut())?
}
(_, Body::Json(bytes)) => json_body(input, mapping, bytes, path_params, presence.as_mut())?,
(_, Body::Form(bytes)) => {
let pairs: Vec<(Cow<'_, str>, Cow<'_, str>)> =
url::form_urlencoded::parse(bytes).collect();
let form = Form {
pairs: &pairs,
path_params,
};
let (message, form_inside) =
form_body(input, mapping, form, presence.as_mut(), &mut path)?;
inside = form_inside;
message
}
};
let root_inside = has_special_json(input);
for (key, raw) in path_params.fields() {
let target = Target {
root: input,
inside: root_inside,
prefix: None,
key,
values: &[raw],
};
inside |= bind(
&mut message,
target,
Binding::Set,
presence.as_mut(),
&mut path,
)?;
}
if let Some(presence) = &mut presence {
let start = Start {
root: input,
inside: root_inside,
prefix: None,
};
inside |= bind_pairs(
&mut message,
query,
start,
Binding::Fill,
Some(presence),
None::<&P>,
&mut path,
)?;
}
if inside {
check_well_known(&message)?;
}
Ok(message)
}
fn check_well_known(message: &DynamicMessage) -> Result<(), String> {
let invalid = |e: &dyn std::fmt::Display| format!("invalid value for a well-known type: {e}");
check_time_ranges(message).map_err(|e| invalid(&e))?;
serde_json::to_writer(std::io::sink(), message).map_err(|e| invalid(&e))
}
fn check_time_ranges(message: &DynamicMessage) -> Result<(), &'static str> {
let field = |name| message.get_field_by_name(name);
match message.descriptor().full_name() {
"google.protobuf.Timestamp" => {
let seconds = field("seconds").and_then(|v| v.as_i64()).unwrap_or(0);
let nanos = field("nanos").and_then(|v| v.as_i32()).unwrap_or(0);
if !(-62_135_596_800..=253_402_300_799).contains(&seconds)
|| !(0..=999_999_999).contains(&nanos)
{
return Err("timestamp out of range");
}
}
"google.protobuf.Duration" => {
let seconds = field("seconds").and_then(|v| v.as_i64()).unwrap_or(0);
let nanos = field("nanos").and_then(|v| v.as_i32()).unwrap_or(0);
if seconds.unsigned_abs() > 315_576_000_000
|| nanos.unsigned_abs() > 999_999_999
|| (seconds > 0 && nanos < 0)
|| (seconds < 0 && nanos > 0)
{
return Err("duration out of range");
}
}
_ => {}
}
for (_, value) in message.fields() {
check_value_time_ranges(value)?;
}
Ok(())
}
fn check_value_time_ranges(value: &Value) -> Result<(), &'static str> {
match value {
Value::Message(inner) => check_time_ranges(inner),
Value::List(items) => items.iter().try_for_each(check_value_time_ranges),
Value::Map(entries) => entries.values().try_for_each(check_value_time_ranges),
_ => Ok(()),
}
}
#[derive(Clone, Copy)]
struct Target<'a> {
root: &'a MessageDescriptor,
inside: bool,
prefix: Option<&'a FieldDescriptor>,
key: &'a str,
values: &'a [&'a str],
}
fn json_body<P: PathFields + ?Sized>(
input: &MessageDescriptor,
mapping: &BodyMapping,
bytes: &[u8],
path_params: &P,
mut presence: Option<&mut Presence>,
) -> Result<DynamicMessage, String> {
if matches!(mapping, BodyMapping::Root) && trim_json_whitespace(bytes) == b"null" {
return Ok(DynamicMessage::new(input.clone()));
}
let mut de = serde_json::Deserializer::from_slice(bytes);
let first = deserialize(input, mapping, &mut de, presence.as_deref_mut())
.and_then(|message| de.end().map(|()| message));
let error = match first {
Ok(message) => return Ok(message),
Err(error) => error,
};
let fail = |e: serde_json::Error| format!("failed to decode request body: {e}");
if path_params.is_empty() {
return Err(fail(error));
}
let Ok(mut value) = serde_json::from_slice::<JsonValue>(bytes) else {
return Err(fail(error));
};
strip_path_fields(&mut value, input, mapping, path_params);
if let Some(presence) = presence.as_deref_mut() {
presence.clear();
}
deserialize(input, mapping, value, presence).map_err(fail)
}
fn strip_path_fields<P: PathFields + ?Sized>(
body: &mut JsonValue,
input: &MessageDescriptor,
mapping: &BodyMapping,
path_params: &P,
) {
for (key, _) in path_params.fields() {
let mut segments = key.split('.');
let desc = match mapping {
BodyMapping::Root => input.clone(),
BodyMapping::Field(name) => {
if segments.next() != Some(name.as_str()) {
continue;
}
match input.get_field_by_name(name).map(|f| f.kind()) {
Some(Kind::Message(inner)) => inner,
_ => continue,
}
}
BodyMapping::None => return,
};
remove_path(body, desc, segments);
}
}
fn remove_path<'k>(
mut value: &mut JsonValue,
mut desc: MessageDescriptor,
segments: impl Iterator<Item = &'k str>,
) {
let mut segments = segments.peekable();
while let Some(segment) = segments.next() {
let Some(field) = field_named(&desc, segment, &mut false) else {
return;
};
let Some(object) = value.as_object_mut() else {
return;
};
if segments.peek().is_none() {
object.remove(field.json_name());
object.remove(field.name());
return;
}
let Kind::Message(inner) = field.kind() else {
return;
};
let key = if object.contains_key(field.json_name()) {
field.json_name()
} else {
field.name()
};
let Some(next) = object.get_mut(key) else {
return;
};
value = next;
desc = inner;
}
}
fn trim_json_whitespace(bytes: &[u8]) -> &[u8] {
let ws = |b: &u8| matches!(b, b' ' | b'\t' | b'\n' | b'\r');
let start = bytes.iter().position(|b| !ws(b)).unwrap_or(bytes.len());
let end = bytes.iter().rposition(|b| !ws(b)).map_or(start, |i| i + 1);
&bytes[start..end]
}
fn deserialize<'de, D: Deserializer<'de>>(
input: &MessageDescriptor,
mapping: &BodyMapping,
body: D,
presence: Option<&mut Presence>,
) -> Result<DynamicMessage, D::Error> {
match mapping {
BodyMapping::Field(name) => {
let field = input.get_field_by_name(name);
let key = field.as_ref().map_or(name.as_str(), |f| f.json_name());
deserialize_input(input, OneEntry { key, value: body }, presence)
}
_ => deserialize_input(input, body, presence),
}
}
fn deserialize_input<'de, D: Deserializer<'de>>(
input: &MessageDescriptor,
de: D,
presence: Option<&mut Presence>,
) -> Result<DynamicMessage, D::Error> {
let Some(presence) = presence else {
return DynamicMessage::deserialize(input.clone(), de);
};
if has_special_json(input) {
presence.record_whole();
return DynamicMessage::deserialize(input.clone(), de);
}
DynamicMessage::deserialize(input.clone(), Recording::root(de, presence))
}
fn form_body<P: PathFields + ?Sized>(
input: &MessageDescriptor,
mapping: &BodyMapping,
form: Form<'_, '_, P>,
mut presence: Option<&mut Presence>,
path: &mut Vec<FieldDescriptor>,
) -> Result<(DynamicMessage, bool), String> {
let Form { pairs, path_params } = form;
let mut message = DynamicMessage::new(input.clone());
let (prefix, start_inside) = match mapping {
BodyMapping::Field(name) => {
let (field, inner) = input
.get_field_by_name(name)
.filter(|f| !f.is_list() && !f.is_map())
.and_then(|f| match f.kind() {
Kind::Message(inner) => Some((f, inner)),
_ => None,
})
.ok_or_else(|| format!("a form body cannot fill field `{name}`"))?;
message.get_field_mut(&field);
if let Some(presence) = presence.as_deref_mut() {
presence.record_path(std::slice::from_ref(&field), true);
}
(Some(field), has_special_json(&inner))
}
_ => (None, has_special_json(input)),
};
let start = Start {
root: input,
inside: start_inside,
prefix: prefix.as_ref(),
};
let inside = bind_pairs(
&mut message,
pairs,
start,
Binding::Set,
presence,
Some(path_params),
path,
)?;
Ok((message, inside))
}
struct Form<'p, 'q, P: ?Sized> {
pairs: &'p [(Cow<'q, str>, Cow<'q, str>)],
path_params: &'p P,
}
fn covers(bound: &str, path: &[FieldDescriptor]) -> bool {
let mut fields = path.iter();
bound.split('.').all(|segment| {
fields
.next()
.is_some_and(|f| segment == f.name() || segment == f.json_name())
})
}
#[derive(Clone, Copy)]
struct Start<'a> {
root: &'a MessageDescriptor,
inside: bool,
prefix: Option<&'a FieldDescriptor>,
}
fn bind_pairs<P: PathFields + ?Sized>(
message: &mut DynamicMessage,
pairs: &[(Cow<'_, str>, Cow<'_, str>)],
start: Start<'_>,
binding: Binding,
mut presence: Option<&mut Presence>,
path_params: Option<&P>,
path: &mut Vec<FieldDescriptor>,
) -> Result<bool, String> {
let mut order: Vec<usize> = (0..pairs.len()).collect();
order.sort_by(|&a, &b| pairs[a].0.cmp(&pairs[b].0));
let mut values = Vec::new();
let mut inside = false;
let mut aliased: Vec<(String, &[usize])> = Vec::new();
let mut failed: Vec<(&str, String)> = Vec::new();
for run in order.chunk_by(|&a, &b| pairs[a].0 == pairs[b].0) {
values.clear();
values.extend(run.iter().map(|&i| pairs[i].1.as_ref()));
let target = Target {
root: start.root,
inside: start.inside,
prefix: start.prefix,
key: &pairs[run[0]].0,
values: &values,
};
let Some(resolved) = resolve(target, path) else {
continue;
};
if resolved.aliased {
let own = &path[usize::from(start.prefix.is_some())..];
let canonical = own.iter().map(|f| f.name()).collect::<Vec<_>>().join(".");
aliased.push((canonical, run));
continue;
}
let bound = bind_resolved(
message,
path,
&values,
binding,
presence.as_deref_mut(),
path_params,
);
match bound {
Ok(set) => inside |= set && resolved.inside,
Err(e) => failed.push((pairs[run[0]].0.as_ref(), e)),
}
}
aliased.sort_by(|a, b| a.0.cmp(&b.0));
let covered = |key: &str| {
aliased
.binary_search_by(|(c, _)| c.as_str().cmp(key))
.is_ok()
};
if let Some((_, e)) = failed.into_iter().find(|(key, _)| !covered(key)) {
return Err(e);
}
if aliased.is_empty() {
return Ok(inside);
}
let mut indices = Vec::new();
for group in aliased.chunk_by(|a, b| a.0 == b.0) {
let canonical = group[0].0.as_str();
indices.clear();
for (_, run) in group {
indices.extend_from_slice(run);
}
let from = order.partition_point(|&i| pairs[i].0.as_ref() < canonical);
let to = order.partition_point(|&i| pairs[i].0.as_ref() <= canonical);
indices.extend_from_slice(&order[from..to]);
indices.sort_unstable();
values.clear();
values.extend(indices.iter().map(|&i| pairs[i].1.as_ref()));
let target = Target {
root: start.root,
inside: start.inside,
prefix: start.prefix,
key: canonical,
values: &values,
};
let Some(resolved) = resolve(target, path) else {
continue;
};
inside |= bind_resolved(
message,
path,
&values,
binding,
presence.as_deref_mut(),
path_params,
)? && resolved.inside;
}
Ok(inside)
}
fn bind_resolved<P: PathFields + ?Sized>(
message: &mut DynamicMessage,
path: &[FieldDescriptor],
values: &[&str],
binding: Binding,
presence: Option<&mut Presence>,
path_params: Option<&P>,
) -> Result<bool, String> {
if path_params.is_some_and(|bound| bound.fields().any(|(key, _)| covers(key, path))) {
return Ok(false);
}
assign(message, path, values, binding, presence)?;
Ok(true)
}
fn bind(
message: &mut DynamicMessage,
target: Target<'_>,
binding: Binding,
presence: Option<&mut Presence>,
path: &mut Vec<FieldDescriptor>,
) -> Result<bool, String> {
let Some(resolved) = resolve(target, path) else {
return Ok(false);
};
assign(message, path, target.values, binding, presence)?;
Ok(resolved.inside)
}
#[derive(Clone, Copy)]
struct Resolved {
inside: bool,
aliased: bool,
}
fn resolve(target: Target<'_>, path: &mut Vec<FieldDescriptor>) -> Option<Resolved> {
path.clear();
let mut inside = target.inside;
let mut aliased = false;
let mut owned;
let mut desc = match target.prefix {
Some(field) => {
let Kind::Message(inner) = field.kind() else {
return None;
};
path.push(field.clone());
owned = inner;
&owned
}
None => target.root,
};
let mut segments = target.key.split('.');
let mut leaf = segments.next().unwrap_or_default();
for next in segments {
let field = field_named(desc, leaf, &mut aliased)?;
let Kind::Message(inner) = field.kind() else {
return None;
};
if field.is_list() || field.is_map() {
return None;
}
inside |= has_special_json(&inner);
path.push(field);
owned = inner;
desc = &owned;
leaf = next;
}
path.push(field_named(desc, leaf, &mut aliased)?);
Some(Resolved { inside, aliased })
}
fn field_named(
desc: &MessageDescriptor,
name: &str,
aliased: &mut bool,
) -> Option<FieldDescriptor> {
if let Some(field) = desc.get_field_by_name(name) {
return Some(field);
}
let field = desc.get_field_by_json_name(name)?;
*aliased = true;
Some(field)
}
fn assign(
message: &mut DynamicMessage,
path: &[FieldDescriptor],
values: &[&str],
binding: Binding,
mut presence: Option<&mut Presence>,
) -> Result<(), String> {
if binding == Binding::Fill && presence.as_deref_mut().is_some_and(|p| p.blocks(path)) {
return Ok(());
}
let (field, parents) = path
.split_last()
.expect("a resolved path ends in its field");
let value = field_value(field, values)?;
let mut holder = message;
for parent in parents {
check_oneof(holder, parent)?;
holder = match holder.get_field_mut(parent) {
Value::Message(inner) => inner,
_ => unreachable!("a singular message field holds a message"),
};
}
check_oneof(holder, field)?;
holder.set_field(field, value);
if binding == Binding::Set {
if let Some(presence) = presence {
presence.record_path(path, false);
}
}
Ok(())
}
fn check_oneof(message: &DynamicMessage, field: &FieldDescriptor) -> Result<(), String> {
let Some(oneof) = field.containing_oneof() else {
return Ok(());
};
if oneof
.fields()
.any(|other| other.number() != field.number() && message.has_field(&other))
{
return Err(format!(
"multiple fields provided for oneof '{}'",
oneof.name()
));
}
Ok(())
}
fn field_value(field: &FieldDescriptor, raw: &[&str]) -> Result<Value, String> {
let invalid = |e: String| format!("invalid value for field `{}`: {e}", field.name());
if field.is_map() {
return Err(invalid("a map field cannot be set from a string".into()));
}
let kind = field.kind();
if field.is_list() {
return raw
.iter()
.map(|raw| scalar(&kind, raw))
.collect::<Result<Vec<_>, _>>()
.map(Value::List)
.map_err(invalid);
}
let raw = raw.last().expect("a bound key has a value");
scalar(&kind, raw).map_err(invalid)
}
fn scalar(kind: &Kind, raw: &str) -> Result<Value, String> {
Ok(match kind {
Kind::Double => Value::F64(parse_f64(raw)?),
Kind::Float => Value::F32(parse_f32(raw)?),
Kind::Int32 | Kind::Sint32 | Kind::Sfixed32 => Value::I32(parse(raw)?),
Kind::Int64 | Kind::Sint64 | Kind::Sfixed64 => Value::I64(parse(raw)?),
Kind::Uint32 | Kind::Fixed32 => Value::U32(parse(raw)?),
Kind::Uint64 | Kind::Fixed64 => Value::U64(parse(raw)?),
Kind::Bool => Value::Bool(parse(raw)?),
Kind::String => Value::String(raw.to_owned()),
Kind::Bytes => Value::Bytes(decode_base64(raw)?.into()),
Kind::Enum(desc) => Value::EnumNumber(
desc.get_value_by_name(raw)
.ok_or_else(|| format!("unrecognized enum value '{raw}'"))?
.number(),
),
Kind::Message(desc) => Value::Message(
DynamicMessage::deserialize(
desc.clone(),
StrDeserializer::<serde::de::value::Error>::new(raw),
)
.map_err(|e| e.to_string())?,
),
})
}
fn parse<T: std::str::FromStr>(raw: &str) -> Result<T, String>
where
T::Err: std::fmt::Display,
{
raw.parse().map_err(|e: T::Err| e.to_string())
}
fn special_float(raw: &str) -> Option<f64> {
match raw {
"Infinity" => Some(f64::INFINITY),
"-Infinity" => Some(f64::NEG_INFINITY),
"NaN" => Some(f64::NAN),
_ => None,
}
}
fn parse_f64(raw: &str) -> Result<f64, String> {
if let Some(special) = special_float(raw) {
return Ok(special);
}
match raw.parse::<f64>() {
Ok(value) if value.is_finite() => Ok(value),
Ok(_) => Err("value out of range, or not a decimal number".to_string()),
Err(e) => Err(e.to_string()),
}
}
fn parse_f32(raw: &str) -> Result<f32, String> {
let wide = parse_f64(raw)?;
if wide.is_finite() && (wide < f64::from(f32::MIN) || wide > f64::from(f32::MAX)) {
return Err("float value out of range".to_string());
}
Ok(wide as f32)
}
fn decode_base64(raw: &str) -> Result<Vec<u8>, String> {
use base64::alphabet;
use base64::engine::{DecodePaddingMode, GeneralPurpose, GeneralPurposeConfig};
use base64::{DecodeError, Engine};
const CONFIG: GeneralPurposeConfig = GeneralPurposeConfig::new()
.with_decode_allow_trailing_bits(true)
.with_decode_padding_mode(DecodePaddingMode::Indifferent);
const STANDARD: GeneralPurpose = GeneralPurpose::new(&alphabet::STANDARD, CONFIG);
const URL_SAFE: GeneralPurpose = GeneralPurpose::new(&alphabet::URL_SAFE, CONFIG);
let mut buf = Vec::new();
match STANDARD.decode_vec(raw, &mut buf) {
Ok(()) => Ok(buf),
Err(DecodeError::InvalidByte(_, b'-' | b'_')) => {
buf.clear();
URL_SAFE
.decode_vec(raw, &mut buf)
.map(|()| buf)
.map_err(|e| format!("invalid base64: {e}"))
}
Err(e) => Err(format!("invalid base64: {e}")),
}
}
#[cfg(test)]
mod tests;