use proc_macro2::{Ident, TokenStream};
use quote::quote;
use prost_protovalidate_types::rules_meta::timestamp as meta;
use prost_protovalidate_types::{TimestampRules, timestamp_rules};
use super::number::generate_range_check;
#[allow(clippy::too_many_lines, clippy::similar_names)]
pub(crate) fn generate(
rules: &TimestampRules,
field_ident: &Ident,
proto_name: &str,
backend: crate::Backend,
) -> Vec<TokenStream> {
let mut checks = Vec::new();
let as_tuple = |t: &prost_types::Timestamp| (t.seconds, t.nanos);
let field = quote! { self.#field_ident };
let bind = quote::format_ident!("_ts");
if let Some(ref c) = rules.r#const {
let (secs, nanos) = as_tuple(c);
let rule_id = meta::CONST_ID;
let msg = meta::CONST_MESSAGE;
let body = quote! {
if (_ts.seconds, _ts.nanos) != (#secs, #nanos) {
violations.push(::prost_protovalidate::Violation::new(
#proto_name, #rule_id, #msg,
));
}
};
checks.push(backend.if_msg_field_set(&field, &bind, &body));
}
let (gt, gte) = match rules.greater_than.as_ref() {
Some(timestamp_rules::GreaterThan::Gt(t)) => (Some(as_tuple(t)), None),
Some(timestamp_rules::GreaterThan::Gte(t)) => (None, Some(as_tuple(t))),
Some(timestamp_rules::GreaterThan::GtNow(_)) | None => (None, None),
};
let (lt, lte) = match rules.less_than.as_ref() {
Some(timestamp_rules::LessThan::Lt(t)) => (Some(as_tuple(t)), None),
Some(timestamp_rules::LessThan::Lte(t)) => (None, Some(as_tuple(t))),
Some(timestamp_rules::LessThan::LtNow(_)) | None => (None, None),
};
if let Some(rule) = meta::range_rule(gt, gte, lt, lte) {
let tuple_tokens = |b: (i64, i32)| {
let (s, n) = b;
quote! { (#s, #n) }
};
let gt_tokens = gt.or(gte).map(tuple_tokens);
let lt_tokens = lt.or(lte).map(tuple_tokens);
let value_access = quote! { (_ts.seconds, _ts.nanos) };
let range_checks = generate_range_check(
&rule,
gt_tokens.as_ref(),
lt_tokens.as_ref(),
&value_access,
proto_name,
);
let body = quote! { #(#range_checks)* };
checks.push(backend.if_msg_field_set(&field, &bind, &body));
}
let has_lt_now = rules
.less_than
.as_ref()
.is_some_and(|lt| matches!(lt, timestamp_rules::LessThan::LtNow(true)));
let has_gt_now = rules
.greater_than
.as_ref()
.is_some_and(|gt| matches!(gt, timestamp_rules::GreaterThan::GtNow(true)));
let within = rules.within;
if has_lt_now || has_gt_now || within.is_some() {
let within_check: Option<TokenStream> = within.map(|w| {
let w_secs = w.seconds;
let w_nanos = w.nanos;
let rule_id = meta::WITHIN_ID;
let msg = meta::WITHIN_MESSAGE;
quote! {
let _diff_nanos: i128 =
(i128::from(_ts.seconds) * 1_000_000_000 + i128::from(_ts.nanos))
- (i128::from(_now.seconds) * 1_000_000_000 + i128::from(_now.nanos));
let _abs_nanos: u128 = _diff_nanos.unsigned_abs();
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
let _diff_secs: i64 = (_abs_nanos / 1_000_000_000) as i64;
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
let _diff_subsec_nanos: i32 = (_abs_nanos % 1_000_000_000) as i32;
if (_diff_secs, _diff_subsec_nanos) > (#w_secs, #w_nanos) {
violations.push(::prost_protovalidate::Violation::new(
#proto_name, #rule_id, #msg,
));
}
}
});
let lt_now_check: Option<TokenStream> = has_lt_now.then(|| {
let rule_id = meta::LT_NOW_ID;
let msg = meta::LT_NOW_MESSAGE;
quote! {
if (_ts.seconds, _ts.nanos) >= (_now.seconds, _now.nanos) {
violations.push(::prost_protovalidate::Violation::new(
#proto_name, #rule_id, #msg,
));
}
}
});
let gt_now_check: Option<TokenStream> = has_gt_now.then(|| {
let rule_id = meta::GT_NOW_ID;
let msg = meta::GT_NOW_MESSAGE;
quote! {
if (_ts.seconds, _ts.nanos) <= (_now.seconds, _now.nanos) {
violations.push(::prost_protovalidate::Violation::new(
#proto_name, #rule_id, #msg,
));
}
}
});
let body = quote! {
let _now = ::prost_protovalidate::time::now_systemtime();
#lt_now_check
#gt_now_check
#within_check
};
checks.push(backend.if_msg_field_set(&field, &bind, &body));
}
checks
}