prost-reflect-validate 0.2.9

protoc-gen-validate's validation using prost-reflect
Documentation
use crate::any::make_validate_any;
use crate::bool::make_validate_bool;
use crate::bytes::make_validate_bytes;
use crate::duration::make_validate_duration;
use crate::number::{
    make_validate_double, make_validate_float, make_validate_i32, make_validate_i64,
    make_validate_u32, make_validate_u64,
};
use crate::registry::{Args, NestedValidationFn, ValidationFn, REGISTRY};
use crate::string::make_validate_string;
use crate::timestamp::make_validate_timestamp;
use prost_reflect::{DynamicMessage, FieldDescriptor, Kind};
use prost_validate::errors::message;
use prost_validate::{format_err, Error};
use prost_validate_types::FieldRules;
use std::collections::HashMap;
use std::sync::Arc;

macro_rules! append {
    ($fns:ident, $other:expr) => {{
        $fns.extend($other);
        $fns
    }};
}

#[allow(clippy::unwrap_used)]
pub(crate) fn make_validate_message(
    m: &mut HashMap<String, ValidationFn>,
    field: &FieldDescriptor,
    field_rules: &FieldRules,
) -> Vec<NestedValidationFn<Box<DynamicMessage>>> {
    let mut fns = Vec::new();
    let desc = match field.kind() {
        Kind::Message(desc) => desc,
        _ => return fns,
    };
    let rules = field_rules.message.unwrap_or_default();
    // there is no way currently to check for "synthetic" oneof
    let optional = field
        .containing_oneof()
        .map(|d| d.fields().len() == 1 && d.fields().any(|f| &f == field))
        .unwrap_or(false);
    if rules.required() && !optional {
        let name = field.full_name().to_string();
        fns.push(Arc::new(move |val, _, _| {
            if val.is_none() {
                return Err(Error::new(name.to_string(), message::Error::Required));
            }
            Ok(true)
        }))
    } else {
        fns.push(Arc::new(move |val, rules, _| {
            Ok(val.is_some() || rules.r#type.is_some())
        }))
    }
    if rules.skip() {
        fns.push(Arc::new(move |_, _, _| Ok(false)));
        return fns;
    }

    macro_rules! make_validate_wrapper {
        ($make:ident,$conv:ident) => {{
            let wkt_field = desc.get_field(1).unwrap();
            let wkt_fns = $make(field, field_rules);
            let f: NestedValidationFn<Box<DynamicMessage>> = Arc::new(move |val, rules, _| {
                let val: Box<DynamicMessage> = match val {
                    Some(v) => v,
                    None => return Ok(true),
                };
                let val = val.get_field(&wkt_field).$conv();
                for f in &wkt_fns {
                    let val = val.clone();
                    if !f(val, rules)? {
                        return Ok(false);
                    }
                }
                Ok(true)
            });
            append!(fns, vec![f])
        }};
    }
    match desc.full_name() {
        "google.protobuf.StringValue" => {
            let wkt_field = desc.get_field(1).unwrap();
            let wkt_fns = make_validate_string(field, field_rules);
            let f: NestedValidationFn<Box<DynamicMessage>> = Arc::new(move |val, rules, _| {
                let val: Box<DynamicMessage> = match val {
                    Some(v) => v,
                    None => return Ok(true),
                };
                let val = val.get_field(&wkt_field).as_str().map(|v| v.to_string());
                for f in &wkt_fns {
                    let val = val.clone();
                    if !f(val, rules)? {
                        return Ok(false);
                    }
                }
                Ok(true)
            });
            return append!(fns, vec![f]);
        }
        "google.protobuf.BytesValue" => {
            let wkt_field = desc.get_field(1).unwrap();
            let wkt_fns = make_validate_bytes(field, field_rules);
            let f: NestedValidationFn<Box<DynamicMessage>> = Arc::new(move |val, rules, _| {
                let val: Box<DynamicMessage> = match val {
                    Some(v) => v,
                    None => return Ok(true),
                };
                let val = val
                    .get_field(&wkt_field)
                    .as_bytes()
                    .map(|v| Arc::new(v.clone()));
                for f in &wkt_fns {
                    let val = val.clone();
                    if !f(val, rules)? {
                        return Ok(false);
                    }
                }
                Ok(true)
            });
            return append!(fns, vec![f]);
        }
        "google.protobuf.BoolValue" => return make_validate_wrapper!(make_validate_bool, as_bool),
        "google.protobuf.UInt64Value" => return make_validate_wrapper!(make_validate_u64, as_u64),
        "google.protobuf.UInt32Value" => return make_validate_wrapper!(make_validate_u32, as_u32),
        "google.protobuf.Int64Value" => return make_validate_wrapper!(make_validate_i64, as_i64),
        "google.protobuf.Int32Value" => return make_validate_wrapper!(make_validate_i32, as_i32),
        "google.protobuf.DoubleValue" => {
            return make_validate_wrapper!(make_validate_double, as_f64)
        }
        "google.protobuf.FloatValue" => return make_validate_wrapper!(make_validate_float, as_f32),
        "google.protobuf.Timestamp" => {
            return append!(fns, make_validate_timestamp(field, field_rules))
        }
        "google.protobuf.Duration" => {
            return append!(fns, make_validate_duration(field, field_rules))
        }
        "google.protobuf.Any" => return append!(fns, make_validate_any(field, field_rules)),
        _ => {}
    }
    if REGISTRY.register(m, &desc).is_err() {
        return fns;
    }
    let name = Arc::new(field.full_name().to_string());
    fns.push(Arc::new(move |val, _, m| {
        let validate = m
            .get(&desc.full_name().to_string())
            .ok_or(format_err!(desc.full_name(), "no validator"))?;
        match val.map(|v| validate(&Args { msg: &v, m })) {
            Some(Err(err)) => Err(Error::new(
                name.clone(),
                message::Error::Message(Box::new(err)),
            )),
            Some(Ok(())) => Ok(true),
            None => Ok(true),
        }
    }));
    fns
}