cratestack_core/schema/
composite_unique.rs1use super::field_list::{FieldListSpec, parse_field_list};
8
9const SPEC: FieldListSpec = FieldListSpec {
10 prefix: "@@unique(",
11 label: "composite unique attribute",
12 example: "@@unique([field1, field2])",
13};
14
15pub fn parse_composite_unique_attribute(raw: &str) -> Result<Vec<String>, String> {
23 let fields = parse_field_list(raw, &SPEC)?;
24 if fields.len() < 2 {
25 return Err(format!(
26 "composite unique attribute `{raw}` must list at least two fields; use a field-level `@unique` instead"
27 ));
28 }
29 Ok(fields)
30}
31
32#[cfg(test)]
33mod tests {
34 use super::parse_composite_unique_attribute;
35
36 #[test]
37 fn parses_two_fields() {
38 let fields = parse_composite_unique_attribute("@@unique([tenantId, name])").unwrap();
39 assert_eq!(fields, vec!["tenantId".to_string(), "name".to_string()]);
40 }
41
42 #[test]
43 fn parses_three_fields_in_declared_order() {
44 let fields =
45 parse_composite_unique_attribute("@@unique([tenantId, name, environment])").unwrap();
46 assert_eq!(
47 fields,
48 vec![
49 "tenantId".to_string(),
50 "name".to_string(),
51 "environment".to_string(),
52 ]
53 );
54 }
55
56 #[test]
57 fn rejects_missing_brackets() {
58 let error = parse_composite_unique_attribute("@@unique(tenantId, name)").unwrap_err();
59 assert!(error.contains("must list fields as"), "error: {error}");
60 assert!(
61 error.contains("composite unique attribute"),
62 "error: {error}"
63 );
64 }
65
66 #[test]
67 fn rejects_single_field() {
68 let error = parse_composite_unique_attribute("@@unique([email])").unwrap_err();
69 assert!(error.contains("at least two fields"), "error: {error}");
70 assert!(error.contains("field-level `@unique`"), "error: {error}");
71 }
72
73 #[test]
74 fn rejects_duplicate_field() {
75 let error = parse_composite_unique_attribute("@@unique([name, name])").unwrap_err();
76 assert!(error.contains("more than once"), "error: {error}");
77 }
78
79 #[test]
80 fn rejects_invalid_identifier() {
81 let error = parse_composite_unique_attribute("@@unique([tenant-id, name])").unwrap_err();
82 assert!(error.contains("invalid field name"), "error: {error}");
83 }
84
85 #[test]
86 fn rejects_other_attributes() {
87 let error = parse_composite_unique_attribute("@@id([a, b])").unwrap_err();
88 assert!(error.contains("unsupported composite unique attribute"));
89 }
90}