Skip to main content

prost_protovalidate_build/
backend.rs

1//! Backend selection — which protobuf runtime the generated `impl Validate`
2//! blocks target.
3//!
4//! The rule evaluation logic is backend-independent; what differs is the
5//! *storage shape* of generated message structs. Every shape-dependent
6//! emission goes through the helpers on [`Backend`]:
7//!
8//! | shape | `Prost` | `Buffa` |
9//! |---|---|---|
10//! | message field | `Option<T>` / `Option<Box<T>>` | `MessageField<T>` |
11//! | enum field | bare `i32` | `EnumValue<E>` (open) / `E` (closed) |
12//! | `optional` scalar | `Option<T>` | `Option<T>` (identical) |
13//! | repeated / map | `Vec<T>` / `HashMap<K, V>` | identical (hasher differs — irrelevant to emission) |
14//! | real oneof | `Option<OneofEnum>` | identical |
15//! | type idents | renamed to `UpperCamelCase` | verbatim proto names |
16
17use proc_macro2::{Ident, TokenStream};
18use quote::quote;
19
20/// The protobuf runtime whose generated message types the emitted
21/// `impl Validate` blocks will access.
22///
23/// Select via [`Builder::backend`](crate::Builder::backend). Defaults to
24/// [`Backend::Prost`].
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
26#[non_exhaustive]
27pub enum Backend {
28    /// `prost` / `prost-build` generated types (the default).
29    #[default]
30    Prost,
31    /// [`buffa`](https://crates.io/crates/buffa) generated types
32    /// (`MessageField<T>` presence, `EnumValue<E>` enums, verbatim type
33    /// names).
34    Buffa,
35}
36
37impl Backend {
38    /// Presence test for a message-typed field: `true` when **unset**.
39    pub(crate) fn msg_field_is_unset(self, field: &TokenStream) -> TokenStream {
40        match self {
41            Self::Prost => quote! { #field.is_none() },
42            Self::Buffa => quote! { #field.is_unset() },
43        }
44    }
45
46    /// Presence test for a message-typed field: `true` when **set**.
47    pub(crate) fn msg_field_is_set(self, field: &TokenStream) -> TokenStream {
48        match self {
49            Self::Prost => quote! { #field.is_some() },
50            Self::Buffa => quote! { #field.is_set() },
51        }
52    }
53
54    /// Unwrap a set message-typed field, binding `bind` to `&T` inside `body`.
55    /// Emits nothing (skips `body`) when the field is unset.
56    pub(crate) fn if_msg_field_set(
57        self,
58        field: &TokenStream,
59        bind: &Ident,
60        body: &TokenStream,
61    ) -> TokenStream {
62        match self {
63            Self::Prost => quote! {
64                if let ::core::option::Option::Some(ref #bind) = #field {
65                    #body
66                }
67            },
68            Self::Buffa => quote! {
69                if let ::core::option::Option::Some(#bind) = #field.as_option() {
70                    #body
71                }
72            },
73        }
74    }
75
76    /// Normalize an enum-typed access expression to its `i32` wire value.
77    ///
78    /// prost stores enum fields as bare `i32` — the access passes through.
79    /// buffa stores `EnumValue<E>` for open enums and `E` for closed enums;
80    /// both provide a `to_i32()` method, so one emission covers both.
81    pub(crate) fn enum_to_i32(self, access: &TokenStream) -> TokenStream {
82        match self {
83            Self::Prost => access.clone(),
84            Self::Buffa => quote! { #access.to_i32() },
85        }
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use quote::{format_ident, quote};
92
93    use super::Backend;
94
95    #[test]
96    fn presence_shapes_per_backend() {
97        let field = quote! { self.inner };
98        assert_eq!(
99            Backend::Prost.msg_field_is_unset(&field).to_string(),
100            "self . inner . is_none ()"
101        );
102        assert_eq!(
103            Backend::Buffa.msg_field_is_unset(&field).to_string(),
104            "self . inner . is_unset ()"
105        );
106        assert_eq!(
107            Backend::Prost.msg_field_is_set(&field).to_string(),
108            "self . inner . is_some ()"
109        );
110        assert_eq!(
111            Backend::Buffa.msg_field_is_set(&field).to_string(),
112            "self . inner . is_set ()"
113        );
114    }
115
116    #[test]
117    fn unwrap_binds_by_ref_in_prost_and_via_as_option_in_buffa() {
118        let field = quote! { self.inner };
119        let bind = format_ident!("_nested");
120        let body = quote! { touch(_nested); };
121
122        let prost = Backend::Prost
123            .if_msg_field_set(&field, &bind, &body)
124            .to_string();
125        assert!(
126            prost.contains("Some (ref _nested) = self . inner"),
127            "{prost}"
128        );
129
130        let buffa = Backend::Buffa
131            .if_msg_field_set(&field, &bind, &body)
132            .to_string();
133        assert!(
134            buffa.contains("Some (_nested) = self . inner . as_option ()"),
135            "{buffa}"
136        );
137    }
138
139    #[test]
140    fn enum_access_gets_to_i32_only_in_buffa() {
141        let access = quote! { self.status };
142        assert_eq!(
143            Backend::Prost.enum_to_i32(&access).to_string(),
144            "self . status"
145        );
146        assert_eq!(
147            Backend::Buffa.enum_to_i32(&access).to_string(),
148            "self . status . to_i32 ()"
149        );
150    }
151}