Skip to main content

dbt_yaml/
verbatim.rs

1//! This module defines the `Verbatim` type, which is a wrapper type that can be
2//! used to in `#[derive(Deserialize)]` structs to protect fields from the
3//! `field_transfomer` when deserialized by the `Value::into_typed` method.
4
5use std::{
6    fmt::{self, Debug},
7    hash::Hash,
8    hash::Hasher,
9    ops::{Deref, DerefMut},
10};
11
12use serde::{Deserialize, Deserializer, Serialize, Serializer};
13
14////////////////////////////////////////////////////////////////////////
15
16/// A wrapper type that protects the inner value from being transformed by the
17/// `field_transformer` when deserialized by the `Value::into_typed` method.
18///
19/// Generic type parameters `T` is the type of the inner value. Optional type
20/// parameter `Sch` is used to specify a type for JsonSchema generation, and
21/// defaults to the same type as `T`. This is useful for cases where e.g. you
22/// want to capture the value of a field as a `Value` during deserialization,
23/// but still treat the field *as though* it were some primitive type like
24/// `bool` in the Json schema -- in which case you would use `Verbatim<Value,
25/// bool>`.
26pub struct Verbatim<T, Sch = T>(pub T, pub std::marker::PhantomData<Sch>);
27
28impl<T, Sch> Deref for Verbatim<T, Sch> {
29    type Target = T;
30
31    fn deref(&self) -> &Self::Target {
32        &self.0
33    }
34}
35
36impl<T, Sch> DerefMut for Verbatim<T, Sch> {
37    fn deref_mut(&mut self) -> &mut Self::Target {
38        &mut self.0
39    }
40}
41
42impl<T, Sch> AsRef<T> for Verbatim<T, Sch> {
43    fn as_ref(&self) -> &T {
44        &self.0
45    }
46}
47
48impl<T, Sch> AsMut<T> for Verbatim<T, Sch> {
49    fn as_mut(&mut self) -> &mut T {
50        &mut self.0
51    }
52}
53
54impl<T, Sch> Clone for Verbatim<T, Sch>
55where
56    T: Clone,
57{
58    fn clone(&self) -> Self {
59        Verbatim(self.0.clone(), std::marker::PhantomData::<Sch>)
60    }
61}
62
63impl<T, Sch> Copy for Verbatim<T, Sch> where T: Copy {}
64
65impl<T, Sch> Debug for Verbatim<T, Sch>
66where
67    T: Debug,
68{
69    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70        self.0.fmt(f)
71    }
72}
73
74impl<T, Sch> PartialEq for Verbatim<T, Sch>
75where
76    T: PartialEq,
77{
78    fn eq(&self, other: &Self) -> bool {
79        self.0 == other.0
80    }
81}
82
83impl<T, Sch> Eq for Verbatim<T, Sch> where T: Eq {}
84
85impl<T, Sch> PartialOrd for Verbatim<T, Sch>
86where
87    T: PartialOrd,
88{
89    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
90        self.0.partial_cmp(&other.0)
91    }
92}
93
94impl<T, Sch> Ord for Verbatim<T, Sch>
95where
96    T: Ord,
97{
98    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
99        self.0.cmp(&other.0)
100    }
101}
102
103impl<T, Sch> Hash for Verbatim<T, Sch>
104where
105    T: Hash,
106{
107    fn hash<H: Hasher>(&self, state: &mut H) {
108        self.0.hash(state)
109    }
110}
111
112impl<T, Sch> Default for Verbatim<T, Sch>
113where
114    T: Default,
115{
116    fn default() -> Self {
117        Verbatim(T::default(), std::marker::PhantomData::<Sch>)
118    }
119}
120
121impl<T, Sch> From<T> for Verbatim<T, Sch> {
122    fn from(value: T) -> Self {
123        Verbatim(value, std::marker::PhantomData::<Sch>)
124    }
125}
126
127impl<T, Sch> Serialize for Verbatim<T, Sch>
128where
129    T: Serialize,
130{
131    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
132    where
133        S: Serializer,
134    {
135        self.0.serialize(serializer)
136    }
137}
138
139impl<'de, T, Sch> Deserialize<'de> for Verbatim<T, Sch>
140where
141    T: Deserialize<'de>,
142{
143    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
144    where
145        D: Deserializer<'de>,
146    {
147        let _g = with_should_not_transform_any();
148        T::deserialize(deserializer).map(|value| Verbatim(value, std::marker::PhantomData::<Sch>))
149    }
150}
151
152#[cfg(feature = "schemars")]
153impl<T, Sch> schemars::JsonSchema for Verbatim<T, Sch>
154where
155    Sch: schemars::JsonSchema,
156{
157    fn schema_name() -> String {
158        Sch::schema_name()
159    }
160
161    fn json_schema(generator: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
162        Sch::json_schema(generator)
163    }
164
165    fn is_referenceable() -> bool {
166        Sch::is_referenceable()
167    }
168
169    fn schema_id() -> std::borrow::Cow<'static, str> {
170        Sch::schema_id()
171    }
172
173    #[doc(hidden)]
174    fn _schemars_private_non_optional_json_schema(
175        generator: &mut schemars::gen::SchemaGenerator,
176    ) -> schemars::schema::Schema {
177        Sch::_schemars_private_non_optional_json_schema(generator)
178    }
179
180    #[doc(hidden)]
181    fn _schemars_private_is_option() -> bool {
182        Sch::_schemars_private_is_option()
183    }
184}
185
186#[cfg(feature = "schemars")]
187/// Module containing `MaybeTransformable` wrapper type for `DbtSchema` derive.
188pub mod maybe_transformable {
189    use std::collections::BTreeMap;
190
191    const TRANSFORMABLE_STRING_PATTERN: &str = "^\\{.*\\}$";
192
193    /// Transforms the schema of a field to allow it to be either the original
194    /// type or a string matching the `TRANSFORMABLE_STRING_PATTERN`.
195    fn make_stringable_field(schema: schemars::schema::Schema) -> schemars::schema::Schema {
196        let schemars::schema::Schema::Object(mut schema_object) = schema else {
197            return schema;
198        };
199
200        if let Some(obj) = &mut schema_object.object {
201            if let Some(add) = obj.additional_properties.take() {
202                obj.additional_properties = Some(Box::new(make_stringable_field(*add)));
203            }
204        }
205        if let Some(arr) = &mut schema_object.array {
206            if let Some(items) = arr.items.take() {
207                match items {
208                    schemars::schema::SingleOrVec::Single(sch) => {
209                        arr.items = Some(schemars::schema::SingleOrVec::Single(Box::new(
210                            make_stringable_field(*sch),
211                        )));
212                    }
213                    schemars::schema::SingleOrVec::Vec(schs) => {
214                        arr.items = Some(schemars::schema::SingleOrVec::Vec(
215                            schs.into_iter().map(make_stringable_field).collect(),
216                        ));
217                    }
218                }
219            }
220        }
221
222        let is_primitive = match schema_object.instance_type {
223            Some(schemars::schema::SingleOrVec::Single(ref mut ty)) => match **ty {
224                schemars::schema::InstanceType::Boolean
225                | schemars::schema::InstanceType::Integer
226                | schemars::schema::InstanceType::Number => true,
227                _ => false,
228            },
229            Some(schemars::schema::SingleOrVec::Vec(ref mut tys)) => {
230                tys.iter().any(|ty| match *ty {
231                    schemars::schema::InstanceType::Boolean
232                    | schemars::schema::InstanceType::Integer
233                    | schemars::schema::InstanceType::Number => true,
234                    _ => false,
235                })
236            }
237            _ => false,
238        };
239
240        if is_primitive {
241            let subschema = schemars::schema::SubschemaValidation {
242                any_of: Some(vec![
243                    schemars::schema::Schema::Object(schema_object),
244                    schemars::schema::Schema::Object(schemars::schema::SchemaObject {
245                        instance_type: Some(schemars::schema::SingleOrVec::Single(Box::new(
246                            schemars::schema::InstanceType::String,
247                        ))),
248                        string: Some(Box::new(schemars::schema::StringValidation {
249                            pattern: Some(TRANSFORMABLE_STRING_PATTERN.to_string()),
250                            ..Default::default()
251                        })),
252                        ..Default::default()
253                    }),
254                ]),
255                ..Default::default()
256            };
257            schemars::schema::Schema::Object(schemars::schema::SchemaObject {
258                subschemas: Some(Box::new(subschema)),
259                ..Default::default()
260            })
261        } else {
262            schemars::schema::Schema::Object(schema_object)
263        }
264    }
265
266    /// Transforms the given schema to allow transformable fields to be strings.
267    pub fn maybe_transformable(schema: schemars::schema::Schema) -> schemars::schema::Schema {
268        if !SHOULD_GENERATE_PRE_TRANSFORMATION_SCHEMA.with(|flag| flag.get()) {
269            return schema;
270        }
271        let schemars::schema::Schema::Object(mut schema_object) = schema else {
272            return schema;
273        };
274
275        match &schema_object.instance_type {
276            Some(schemars::schema::SingleOrVec::Single(ty)) => match **ty {
277                schemars::schema::InstanceType::Object => {
278                    // This is a struct -- apply the transformation to all
279                    // fields in the struct
280                    if let Some(obj) = schema_object.object.take() {
281                        let properties = obj
282                            .properties
283                            .into_iter()
284                            .map(|(key, value)| (key, make_stringable_field(value)))
285                            .collect::<BTreeMap<_, _>>();
286                        let additional_properties = obj
287                            .additional_properties
288                            .map(|add| Box::new(make_stringable_field(*add)));
289                        schema_object.object = Some(Box::new(schemars::schema::ObjectValidation {
290                            properties,
291                            additional_properties,
292                            ..*obj
293                        }));
294                    }
295                }
296                schemars::schema::InstanceType::Array => {
297                    // This is a Vec -- apply the transformation to the items
298                    if let Some(arr) = &mut schema_object.array {
299                        if let Some(items) = arr.items.take() {
300                            match items {
301                                schemars::schema::SingleOrVec::Single(sch) => {
302                                    arr.items = Some(schemars::schema::SingleOrVec::Single(
303                                        Box::new(make_stringable_field(*sch)),
304                                    ));
305                                }
306                                schemars::schema::SingleOrVec::Vec(schs) => {
307                                    arr.items = Some(schemars::schema::SingleOrVec::Vec(
308                                        schs.into_iter().map(make_stringable_field).collect(),
309                                    ));
310                                }
311                            }
312                        }
313                    }
314                }
315                _ => {}
316            },
317            None => {
318                if let Some(subschemas) = &mut schema_object.subschemas {
319                    // This is an enum
320                    if let Some(any_of) = &mut subschemas.any_of {
321                        // This is an untagged enum -- add a stringable alternative
322                        any_of.push(schemars::schema::Schema::Object(
323                            schemars::schema::SchemaObject {
324                                instance_type: Some(schemars::schema::SingleOrVec::Single(
325                                    Box::new(schemars::schema::InstanceType::String),
326                                )),
327                                string: Some(Box::new(schemars::schema::StringValidation {
328                                    pattern: Some(TRANSFORMABLE_STRING_PATTERN.to_string()),
329                                    ..Default::default()
330                                })),
331                                ..Default::default()
332                            },
333                        ));
334                    }
335                    if let Some(one_of) = &mut subschemas.one_of {
336                        // This is a tagged enum -- apply the transformation to each variant
337                        *one_of = one_of.drain(..).map(make_stringable_field).collect();
338                    }
339                }
340            }
341            _ => {}
342        }
343
344        schemars::schema::Schema::Object(schema_object)
345    }
346
347    /// Whether `schemar_for!` should generate a schema that allows
348    /// transformable fields to be strings. Call this before generating any
349    /// schemas to set the behavior for all subsequent schema generation.
350    pub fn set_generate_pre_transformation_schema(value: bool) {
351        SHOULD_GENERATE_PRE_TRANSFORMATION_SCHEMA.with(|flag| flag.set(value));
352    }
353
354    thread_local! {
355        static SHOULD_GENERATE_PRE_TRANSFORMATION_SCHEMA: std::cell::Cell<bool> = const {
356            std::cell::Cell::new(false)
357        };
358    }
359}
360
361pub(crate) fn should_transform_any() -> bool {
362    SHOULD_TRANSFORM_ANY.with(|flag| flag.get())
363}
364
365pub(crate) struct ShouldTransformAnyGuard(bool);
366
367impl Drop for ShouldTransformAnyGuard {
368    fn drop(&mut self) {
369        SHOULD_TRANSFORM_ANY.with(|flag| flag.set(self.0));
370    }
371}
372
373pub(crate) fn with_should_not_transform_any() -> ShouldTransformAnyGuard {
374    let current = SHOULD_TRANSFORM_ANY.with(|flag| flag.get());
375    SHOULD_TRANSFORM_ANY.with(|flag| flag.set(false));
376    ShouldTransformAnyGuard(current)
377}
378
379thread_local! {
380    static SHOULD_TRANSFORM_ANY: std::cell::Cell<bool>  = const {
381        std::cell::Cell::new(true)
382    };
383}