Skip to main content

prax_schema/ast/
attribute.rs

1//! Attribute definitions for the Prax schema AST.
2
3use serde::{Deserialize, Serialize};
4use smol_str::SmolStr;
5
6use super::{Ident, Span};
7
8/// An attribute argument value.
9#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
10pub enum AttributeValue {
11    /// A string literal.
12    String(String),
13    /// An integer literal.
14    Int(i64),
15    /// A float literal.
16    Float(f64),
17    /// A boolean literal.
18    Boolean(bool),
19    /// An identifier/constant reference (e.g., enum value).
20    Ident(SmolStr),
21    /// A function call (e.g., `now()`, `uuid()`).
22    Function(SmolStr, Vec<AttributeValue>),
23    /// An array of values.
24    Array(Vec<AttributeValue>),
25    /// A field reference (e.g., `[field_name]`).
26    FieldRef(SmolStr),
27    /// A list of field references (e.g., `[field1, field2]`).
28    FieldRefList(Vec<SmolStr>),
29}
30
31impl AttributeValue {
32    /// Try to get the value as a string.
33    pub fn as_string(&self) -> Option<&str> {
34        match self {
35            Self::String(s) => Some(s),
36            _ => None,
37        }
38    }
39
40    /// Try to get the value as an integer.
41    pub fn as_int(&self) -> Option<i64> {
42        match self {
43            Self::Int(i) => Some(*i),
44            _ => None,
45        }
46    }
47
48    /// Try to get the value as a float.
49    pub fn as_float(&self) -> Option<f64> {
50        match self {
51            Self::Float(f) => Some(*f),
52            Self::Int(i) => Some(*i as f64),
53            _ => None,
54        }
55    }
56
57    /// Try to get the value as a boolean.
58    pub fn as_bool(&self) -> Option<bool> {
59        match self {
60            Self::Boolean(b) => Some(*b),
61            _ => None,
62        }
63    }
64
65    /// Try to get the value as an identifier.
66    pub fn as_ident(&self) -> Option<&str> {
67        match self {
68            Self::Ident(s) => Some(s),
69            _ => None,
70        }
71    }
72}
73
74/// An attribute argument (named or positional).
75#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
76pub struct AttributeArg {
77    /// Argument name (None for positional arguments).
78    pub name: Option<Ident>,
79    /// Argument value.
80    pub value: AttributeValue,
81    /// Source location.
82    pub span: Span,
83}
84
85impl AttributeArg {
86    /// Create a positional argument.
87    pub fn positional(value: AttributeValue, span: Span) -> Self {
88        Self {
89            name: None,
90            value,
91            span,
92        }
93    }
94
95    /// Create a named argument.
96    pub fn named(name: Ident, value: AttributeValue, span: Span) -> Self {
97        Self {
98            name: Some(name),
99            value,
100            span,
101        }
102    }
103
104    /// Check if this is a positional argument.
105    pub fn is_positional(&self) -> bool {
106        self.name.is_none()
107    }
108}
109
110/// An attribute applied to a field, model, or enum.
111#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
112pub struct Attribute {
113    /// Attribute name (without `@` prefix).
114    pub name: Ident,
115    /// Attribute arguments.
116    pub args: Vec<AttributeArg>,
117    /// Source location (including `@`).
118    pub span: Span,
119}
120
121impl Attribute {
122    /// Create a new attribute.
123    pub fn new(name: Ident, args: Vec<AttributeArg>, span: Span) -> Self {
124        Self { name, args, span }
125    }
126
127    /// Create an attribute with no arguments.
128    pub fn simple(name: Ident, span: Span) -> Self {
129        Self {
130            name,
131            args: vec![],
132            span,
133        }
134    }
135
136    /// Get the attribute name as a string.
137    pub fn name(&self) -> &str {
138        self.name.as_str()
139    }
140
141    /// Check if this attribute has the given name.
142    pub fn is(&self, name: &str) -> bool {
143        self.name.as_str() == name
144    }
145
146    /// Get the first positional argument.
147    pub fn first_arg(&self) -> Option<&AttributeValue> {
148        self.args.first().map(|a| &a.value)
149    }
150
151    /// Get a named argument by name.
152    pub fn get_arg(&self, name: &str) -> Option<&AttributeValue> {
153        self.args
154            .iter()
155            .find(|a| a.name.as_ref().map(|n| n.as_str()) == Some(name))
156            .map(|a| &a.value)
157    }
158
159    /// Check if this is a field-level attribute.
160    pub fn is_field_attribute(&self) -> bool {
161        matches!(
162            self.name(),
163            "id" | "auto"
164                | "unique"
165                | "index"
166                | "default"
167                | "updated_at"
168                | "omit"
169                | "map"
170                | "db"
171                | "relation"
172        )
173    }
174
175    /// Check if this is a model-level attribute (prefixed with `@@`).
176    pub fn is_model_attribute(&self) -> bool {
177        matches!(
178            self.name(),
179            "map" | "index" | "unique" | "id" | "search" | "sql"
180        )
181    }
182}
183
184/// Common field attributes.
185#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
186pub struct FieldAttributes {
187    /// This field is the primary key.
188    pub is_id: bool,
189    /// This field auto-increments (for integer IDs).
190    pub is_auto: bool,
191    /// This field has a unique constraint.
192    pub is_unique: bool,
193    /// This field is indexed.
194    pub is_indexed: bool,
195    /// This field is updated automatically on record update.
196    pub is_updated_at: bool,
197    /// This field should be omitted from default selections.
198    pub is_omit: bool,
199    /// Default value expression.
200    pub default: Option<AttributeValue>,
201    /// Database column name mapping.
202    pub map: Option<String>,
203    /// Native database type (e.g., `@db.VarChar(255)`).
204    pub native_type: Option<NativeType>,
205    /// Relation attributes.
206    pub relation: Option<RelationAttribute>,
207}
208
209/// Native database type specification.
210#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
211pub struct NativeType {
212    /// Type name (e.g., "VarChar", "Text", "Decimal").
213    pub name: SmolStr,
214    /// Type arguments (e.g., length, precision, scale).
215    pub args: Vec<AttributeValue>,
216}
217
218impl NativeType {
219    /// Create a new native type.
220    pub fn new(name: impl Into<SmolStr>, args: Vec<AttributeValue>) -> Self {
221        Self {
222            name: name.into(),
223            args,
224        }
225    }
226}
227
228/// Relation attribute details.
229#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
230pub struct RelationAttribute {
231    /// Relation name (for disambiguation).
232    pub name: Option<String>,
233    /// Fields on this model that reference the other model.
234    pub fields: Vec<SmolStr>,
235    /// Fields on the other model being referenced.
236    pub references: Vec<SmolStr>,
237    /// On delete action.
238    pub on_delete: Option<ReferentialAction>,
239    /// On update action.
240    pub on_update: Option<ReferentialAction>,
241    /// Custom foreign key constraint name in the database.
242    pub map: Option<String>,
243}
244
245/// Referential actions for relations.
246#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
247pub enum ReferentialAction {
248    /// Cascade the operation.
249    Cascade,
250    /// Restrict the operation (error if references exist).
251    Restrict,
252    /// No action (deferred check).
253    NoAction,
254    /// Set to null.
255    SetNull,
256    /// Set to default value.
257    SetDefault,
258}
259
260impl ReferentialAction {
261    /// Parse from string.
262    #[allow(clippy::should_implement_trait)]
263    pub fn from_str(s: &str) -> Option<Self> {
264        match s {
265            "Cascade" => Some(Self::Cascade),
266            "Restrict" => Some(Self::Restrict),
267            "NoAction" => Some(Self::NoAction),
268            "SetNull" => Some(Self::SetNull),
269            "SetDefault" => Some(Self::SetDefault),
270            _ => None,
271        }
272    }
273
274    /// Get the action name.
275    pub fn as_str(&self) -> &'static str {
276        match self {
277            Self::Cascade => "CASCADE",
278            Self::Restrict => "RESTRICT",
279            Self::NoAction => "NO ACTION",
280            Self::SetNull => "SET NULL",
281            Self::SetDefault => "SET DEFAULT",
282        }
283    }
284}
285
286#[cfg(test)]
287mod tests {
288    use super::*;
289
290    // ==================== AttributeValue Tests ====================
291
292    #[test]
293    fn test_attribute_value_string() {
294        let val = AttributeValue::String("hello".into());
295        assert_eq!(val.as_string(), Some("hello"));
296        assert_eq!(val.as_int(), None);
297        assert_eq!(val.as_bool(), None);
298        assert_eq!(val.as_ident(), None);
299    }
300
301    #[test]
302    fn test_attribute_value_int() {
303        let val = AttributeValue::Int(42);
304        assert_eq!(val.as_int(), Some(42));
305        assert_eq!(val.as_string(), None);
306        assert_eq!(val.as_bool(), None);
307    }
308
309    #[test]
310    fn test_attribute_value_int_negative() {
311        let val = AttributeValue::Int(-100);
312        assert_eq!(val.as_int(), Some(-100));
313    }
314
315    #[test]
316    #[allow(clippy::approx_constant)]
317    fn test_attribute_value_float() {
318        let val = AttributeValue::Float(3.14);
319        assert_eq!(val.as_int(), None);
320        assert_eq!(val.as_string(), None);
321    }
322
323    #[test]
324    fn test_attribute_value_boolean_true() {
325        let val = AttributeValue::Boolean(true);
326        assert_eq!(val.as_bool(), Some(true));
327        assert_eq!(val.as_int(), None);
328    }
329
330    #[test]
331    fn test_attribute_value_boolean_false() {
332        let val = AttributeValue::Boolean(false);
333        assert_eq!(val.as_bool(), Some(false));
334    }
335
336    #[test]
337    fn test_attribute_value_ident() {
338        let val = AttributeValue::Ident("MyEnum".into());
339        assert_eq!(val.as_ident(), Some("MyEnum"));
340        assert_eq!(val.as_string(), None);
341    }
342
343    #[test]
344    fn test_attribute_value_function() {
345        let val = AttributeValue::Function("now".into(), vec![]);
346        assert_eq!(val.as_string(), None);
347        assert_eq!(val.as_int(), None);
348
349        // Check it's the right variant
350        if let AttributeValue::Function(name, args) = val {
351            assert_eq!(name.as_str(), "now");
352            assert!(args.is_empty());
353        } else {
354            panic!("Expected Function variant");
355        }
356    }
357
358    #[test]
359    fn test_attribute_value_function_with_args() {
360        let val = AttributeValue::Function("uuid".into(), vec![AttributeValue::Int(4)]);
361
362        if let AttributeValue::Function(name, args) = val {
363            assert_eq!(name.as_str(), "uuid");
364            assert_eq!(args.len(), 1);
365        } else {
366            panic!("Expected Function variant");
367        }
368    }
369
370    #[test]
371    fn test_attribute_value_array() {
372        let val = AttributeValue::Array(vec![
373            AttributeValue::String("a".into()),
374            AttributeValue::String("b".into()),
375        ]);
376
377        if let AttributeValue::Array(items) = val {
378            assert_eq!(items.len(), 2);
379        } else {
380            panic!("Expected Array variant");
381        }
382    }
383
384    #[test]
385    fn test_attribute_value_field_ref() {
386        let val = AttributeValue::FieldRef("user_id".into());
387        assert_eq!(val.as_ident(), None);
388
389        if let AttributeValue::FieldRef(name) = val {
390            assert_eq!(name.as_str(), "user_id");
391        } else {
392            panic!("Expected FieldRef variant");
393        }
394    }
395
396    #[test]
397    fn test_attribute_value_field_ref_list() {
398        let val = AttributeValue::FieldRefList(vec!["id".into(), "name".into()]);
399
400        if let AttributeValue::FieldRefList(fields) = val {
401            assert_eq!(fields.len(), 2);
402            assert_eq!(fields[0].as_str(), "id");
403            assert_eq!(fields[1].as_str(), "name");
404        } else {
405            panic!("Expected FieldRefList variant");
406        }
407    }
408
409    #[test]
410    fn test_attribute_value_equality() {
411        let val1 = AttributeValue::Int(42);
412        let val2 = AttributeValue::Int(42);
413        let val3 = AttributeValue::Int(43);
414
415        assert_eq!(val1, val2);
416        assert_ne!(val1, val3);
417    }
418
419    // ==================== AttributeArg Tests ====================
420
421    #[test]
422    fn test_attribute_arg_positional() {
423        let arg = AttributeArg::positional(AttributeValue::Int(42), Span::new(0, 2));
424
425        assert!(arg.is_positional());
426        assert!(arg.name.is_none());
427        assert_eq!(arg.value.as_int(), Some(42));
428    }
429
430    #[test]
431    fn test_attribute_arg_named() {
432        let arg = AttributeArg::named(
433            Ident::new("length", Span::new(0, 6)),
434            AttributeValue::Int(255),
435            Span::new(0, 10),
436        );
437
438        assert!(!arg.is_positional());
439        assert!(arg.name.is_some());
440        assert_eq!(arg.name.as_ref().unwrap().as_str(), "length");
441        assert_eq!(arg.value.as_int(), Some(255));
442    }
443
444    #[test]
445    fn test_attribute_arg_equality() {
446        let arg1 = AttributeArg::positional(AttributeValue::Int(42), Span::new(0, 2));
447        let arg2 = AttributeArg::positional(AttributeValue::Int(42), Span::new(0, 2));
448        let arg3 = AttributeArg::positional(AttributeValue::Int(43), Span::new(0, 2));
449
450        assert_eq!(arg1, arg2);
451        assert_ne!(arg1, arg3);
452    }
453
454    // ==================== Attribute Tests ====================
455
456    #[test]
457    fn test_attribute_new() {
458        let attr = Attribute::new(
459            Ident::new("default", Span::new(0, 7)),
460            vec![AttributeArg::positional(
461                AttributeValue::Int(0),
462                Span::new(8, 9),
463            )],
464            Span::new(0, 10),
465        );
466
467        assert_eq!(attr.name(), "default");
468        assert_eq!(attr.args.len(), 1);
469    }
470
471    #[test]
472    fn test_attribute_simple() {
473        let attr = Attribute::simple(Ident::new("id", Span::new(0, 2)), Span::new(0, 3));
474
475        assert_eq!(attr.name(), "id");
476        assert!(attr.args.is_empty());
477    }
478
479    #[test]
480    fn test_attribute_is() {
481        let attr = Attribute::simple(Ident::new("unique", Span::new(0, 6)), Span::new(0, 7));
482
483        assert!(attr.is("unique"));
484        assert!(!attr.is("id"));
485        assert!(!attr.is("UNIQUE")); // case sensitive
486    }
487
488    #[test]
489    fn test_attribute_first_arg() {
490        let attr = Attribute::new(
491            Ident::new("default", Span::new(0, 7)),
492            vec![
493                AttributeArg::positional(AttributeValue::Int(42), Span::new(8, 10)),
494                AttributeArg::positional(AttributeValue::String("extra".into()), Span::new(12, 19)),
495            ],
496            Span::new(0, 20),
497        );
498
499        assert_eq!(attr.first_arg().unwrap().as_int(), Some(42));
500    }
501
502    #[test]
503    fn test_attribute_first_arg_none() {
504        let attr = Attribute::simple(Ident::new("id", Span::new(0, 2)), Span::new(0, 3));
505        assert!(attr.first_arg().is_none());
506    }
507
508    #[test]
509    fn test_attribute_get_arg() {
510        let attr = Attribute::new(
511            Ident::new("relation", Span::new(0, 8)),
512            vec![
513                AttributeArg::named(
514                    Ident::new("fields", Span::new(9, 15)),
515                    AttributeValue::FieldRefList(vec!["user_id".into()]),
516                    Span::new(9, 30),
517                ),
518                AttributeArg::named(
519                    Ident::new("references", Span::new(32, 42)),
520                    AttributeValue::FieldRefList(vec!["id".into()]),
521                    Span::new(32, 50),
522                ),
523            ],
524            Span::new(0, 51),
525        );
526
527        let fields = attr.get_arg("fields").unwrap();
528        if let AttributeValue::FieldRefList(f) = fields {
529            assert_eq!(f[0].as_str(), "user_id");
530        } else {
531            panic!("Expected FieldRefList");
532        }
533
534        assert!(attr.get_arg("onDelete").is_none());
535    }
536
537    #[test]
538    fn test_attribute_is_field_attribute() {
539        let id_attr = Attribute::simple(Ident::new("id", Span::new(0, 2)), Span::new(0, 3));
540        let auto_attr = Attribute::simple(Ident::new("auto", Span::new(0, 4)), Span::new(0, 5));
541        let unique_attr = Attribute::simple(Ident::new("unique", Span::new(0, 6)), Span::new(0, 7));
542        let index_attr = Attribute::simple(Ident::new("index", Span::new(0, 5)), Span::new(0, 6));
543        let default_attr =
544            Attribute::simple(Ident::new("default", Span::new(0, 7)), Span::new(0, 8));
545        let updated_at_attr =
546            Attribute::simple(Ident::new("updated_at", Span::new(0, 10)), Span::new(0, 11));
547        let omit_attr = Attribute::simple(Ident::new("omit", Span::new(0, 4)), Span::new(0, 5));
548        let map_attr = Attribute::simple(Ident::new("map", Span::new(0, 3)), Span::new(0, 4));
549        let db_attr = Attribute::simple(Ident::new("db", Span::new(0, 2)), Span::new(0, 3));
550        let relation_attr =
551            Attribute::simple(Ident::new("relation", Span::new(0, 8)), Span::new(0, 9));
552        let unknown_attr =
553            Attribute::simple(Ident::new("unknown", Span::new(0, 7)), Span::new(0, 8));
554
555        assert!(id_attr.is_field_attribute());
556        assert!(auto_attr.is_field_attribute());
557        assert!(unique_attr.is_field_attribute());
558        assert!(index_attr.is_field_attribute());
559        assert!(default_attr.is_field_attribute());
560        assert!(updated_at_attr.is_field_attribute());
561        assert!(omit_attr.is_field_attribute());
562        assert!(map_attr.is_field_attribute());
563        assert!(db_attr.is_field_attribute());
564        assert!(relation_attr.is_field_attribute());
565        assert!(!unknown_attr.is_field_attribute());
566    }
567
568    #[test]
569    fn test_attribute_is_model_attribute() {
570        let map_attr = Attribute::simple(Ident::new("map", Span::new(0, 3)), Span::new(0, 4));
571        let index_attr = Attribute::simple(Ident::new("index", Span::new(0, 5)), Span::new(0, 6));
572        let unique_attr = Attribute::simple(Ident::new("unique", Span::new(0, 6)), Span::new(0, 7));
573        let id_attr = Attribute::simple(Ident::new("id", Span::new(0, 2)), Span::new(0, 3));
574        let search_attr = Attribute::simple(Ident::new("search", Span::new(0, 6)), Span::new(0, 7));
575        let sql_attr = Attribute::simple(Ident::new("sql", Span::new(0, 3)), Span::new(0, 4));
576        let unknown_attr =
577            Attribute::simple(Ident::new("unknown", Span::new(0, 7)), Span::new(0, 8));
578
579        assert!(map_attr.is_model_attribute());
580        assert!(index_attr.is_model_attribute());
581        assert!(unique_attr.is_model_attribute());
582        assert!(id_attr.is_model_attribute());
583        assert!(search_attr.is_model_attribute());
584        assert!(sql_attr.is_model_attribute());
585        assert!(!unknown_attr.is_model_attribute());
586    }
587
588    // ==================== FieldAttributes Tests ====================
589
590    #[test]
591    fn test_field_attributes_default() {
592        let attrs = FieldAttributes::default();
593
594        assert!(!attrs.is_id);
595        assert!(!attrs.is_auto);
596        assert!(!attrs.is_unique);
597        assert!(!attrs.is_indexed);
598        assert!(!attrs.is_updated_at);
599        assert!(!attrs.is_omit);
600        assert!(attrs.default.is_none());
601        assert!(attrs.map.is_none());
602        assert!(attrs.native_type.is_none());
603        assert!(attrs.relation.is_none());
604    }
605
606    #[test]
607    fn test_field_attributes_with_values() {
608        let attrs = FieldAttributes {
609            is_id: true,
610            is_auto: true,
611            is_unique: false,
612            is_indexed: false,
613            is_updated_at: false,
614            is_omit: false,
615            default: Some(AttributeValue::Function("auto".into(), vec![])),
616            map: Some("user_id".to_string()),
617            native_type: None,
618            relation: None,
619        };
620
621        assert!(attrs.is_id);
622        assert!(attrs.is_auto);
623        assert!(attrs.default.is_some());
624        assert_eq!(attrs.map, Some("user_id".to_string()));
625    }
626
627    // ==================== NativeType Tests ====================
628
629    #[test]
630    fn test_native_type_new() {
631        let nt = NativeType::new("VarChar", vec![AttributeValue::Int(255)]);
632
633        assert_eq!(nt.name.as_str(), "VarChar");
634        assert_eq!(nt.args.len(), 1);
635        assert_eq!(nt.args[0].as_int(), Some(255));
636    }
637
638    #[test]
639    fn test_native_type_no_args() {
640        let nt = NativeType::new("Text", vec![]);
641
642        assert_eq!(nt.name.as_str(), "Text");
643        assert!(nt.args.is_empty());
644    }
645
646    #[test]
647    fn test_native_type_multiple_args() {
648        let nt = NativeType::new(
649            "Decimal",
650            vec![AttributeValue::Int(10), AttributeValue::Int(2)],
651        );
652
653        assert_eq!(nt.name.as_str(), "Decimal");
654        assert_eq!(nt.args.len(), 2);
655    }
656
657    #[test]
658    fn test_native_type_equality() {
659        let nt1 = NativeType::new("VarChar", vec![AttributeValue::Int(255)]);
660        let nt2 = NativeType::new("VarChar", vec![AttributeValue::Int(255)]);
661        let nt3 = NativeType::new("VarChar", vec![AttributeValue::Int(100)]);
662
663        assert_eq!(nt1, nt2);
664        assert_ne!(nt1, nt3);
665    }
666
667    // ==================== RelationAttribute Tests ====================
668
669    #[test]
670    fn test_relation_attribute() {
671        let rel = RelationAttribute {
672            name: Some("UserPosts".to_string()),
673            fields: vec!["author_id".into()],
674            references: vec!["id".into()],
675            on_delete: Some(ReferentialAction::Cascade),
676            on_update: Some(ReferentialAction::Cascade),
677            map: Some("fk_post_author".to_string()),
678        };
679
680        assert_eq!(rel.name, Some("UserPosts".to_string()));
681        assert_eq!(rel.fields[0].as_str(), "author_id");
682        assert_eq!(rel.references[0].as_str(), "id");
683        assert_eq!(rel.on_delete, Some(ReferentialAction::Cascade));
684        assert_eq!(rel.map, Some("fk_post_author".to_string()));
685    }
686
687    #[test]
688    fn test_relation_attribute_minimal() {
689        let rel = RelationAttribute {
690            name: None,
691            fields: vec![],
692            references: vec![],
693            on_delete: None,
694            on_update: None,
695            map: None,
696        };
697
698        assert!(rel.name.is_none());
699        assert!(rel.fields.is_empty());
700        assert!(rel.map.is_none());
701    }
702
703    // ==================== ReferentialAction Tests ====================
704
705    #[test]
706    fn test_referential_action_from_str_cascade() {
707        assert_eq!(
708            ReferentialAction::from_str("Cascade"),
709            Some(ReferentialAction::Cascade)
710        );
711    }
712
713    #[test]
714    fn test_referential_action_from_str_restrict() {
715        assert_eq!(
716            ReferentialAction::from_str("Restrict"),
717            Some(ReferentialAction::Restrict)
718        );
719    }
720
721    #[test]
722    fn test_referential_action_from_str_no_action() {
723        assert_eq!(
724            ReferentialAction::from_str("NoAction"),
725            Some(ReferentialAction::NoAction)
726        );
727    }
728
729    #[test]
730    fn test_referential_action_from_str_set_null() {
731        assert_eq!(
732            ReferentialAction::from_str("SetNull"),
733            Some(ReferentialAction::SetNull)
734        );
735    }
736
737    #[test]
738    fn test_referential_action_from_str_set_default() {
739        assert_eq!(
740            ReferentialAction::from_str("SetDefault"),
741            Some(ReferentialAction::SetDefault)
742        );
743    }
744
745    #[test]
746    fn test_referential_action_from_str_unknown() {
747        assert_eq!(ReferentialAction::from_str("Unknown"), None);
748        assert_eq!(ReferentialAction::from_str("cascade"), None); // case sensitive
749        assert_eq!(ReferentialAction::from_str(""), None);
750    }
751
752    #[test]
753    fn test_referential_action_as_str() {
754        assert_eq!(ReferentialAction::Cascade.as_str(), "CASCADE");
755        assert_eq!(ReferentialAction::Restrict.as_str(), "RESTRICT");
756        assert_eq!(ReferentialAction::NoAction.as_str(), "NO ACTION");
757        assert_eq!(ReferentialAction::SetNull.as_str(), "SET NULL");
758        assert_eq!(ReferentialAction::SetDefault.as_str(), "SET DEFAULT");
759    }
760
761    #[test]
762    fn test_referential_action_equality() {
763        assert_eq!(ReferentialAction::Cascade, ReferentialAction::Cascade);
764        assert_ne!(ReferentialAction::Cascade, ReferentialAction::Restrict);
765    }
766
767    #[test]
768    fn test_referential_action_copy() {
769        let action = ReferentialAction::Cascade;
770        let copy = action;
771        assert_eq!(action, copy);
772    }
773}