shaperail_core/
relation.rs1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
5#[serde(rename_all = "snake_case")]
6pub enum RelationType {
7 BelongsTo,
9 HasMany,
11 HasOne,
13}
14
15impl std::fmt::Display for RelationType {
16 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
17 let s = match self {
18 Self::BelongsTo => "belongs_to",
19 Self::HasMany => "has_many",
20 Self::HasOne => "has_one",
21 };
22 write!(f, "{s}")
23 }
24}
25
26#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
32#[serde(deny_unknown_fields)]
33pub struct RelationSpec {
34 pub resource: String,
36
37 #[serde(rename = "type")]
39 pub relation_type: RelationType,
40
41 #[serde(default, skip_serializing_if = "Option::is_none")]
43 pub key: Option<String>,
44
45 #[serde(default, skip_serializing_if = "Option::is_none")]
47 pub foreign_key: Option<String>,
48}
49
50#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
58#[serde(deny_unknown_fields)]
59pub struct IndexSpec {
60 pub fields: Vec<String>,
62
63 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
65 pub unique: bool,
66
67 #[serde(default, skip_serializing_if = "Option::is_none")]
69 pub order: Option<String>,
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75
76 #[test]
77 fn relation_type_display() {
78 assert_eq!(RelationType::BelongsTo.to_string(), "belongs_to");
79 assert_eq!(RelationType::HasMany.to_string(), "has_many");
80 assert_eq!(RelationType::HasOne.to_string(), "has_one");
81 }
82
83 #[test]
84 fn relation_type_serde() {
85 let rt: RelationType = serde_json::from_str("\"belongs_to\"").unwrap();
86 assert_eq!(rt, RelationType::BelongsTo);
87 let rt: RelationType = serde_json::from_str("\"has_many\"").unwrap();
88 assert_eq!(rt, RelationType::HasMany);
89 let rt: RelationType = serde_json::from_str("\"has_one\"").unwrap();
90 assert_eq!(rt, RelationType::HasOne);
91 }
92
93 #[test]
94 fn relation_spec_belongs_to() {
95 let json = r#"{"resource": "organizations", "type": "belongs_to", "key": "org_id"}"#;
96 let rs: RelationSpec = serde_json::from_str(json).unwrap();
97 assert_eq!(rs.resource, "organizations");
98 assert_eq!(rs.relation_type, RelationType::BelongsTo);
99 assert_eq!(rs.key.as_deref(), Some("org_id"));
100 assert!(rs.foreign_key.is_none());
101 }
102
103 #[test]
104 fn relation_spec_has_many() {
105 let json = r#"{"resource": "orders", "type": "has_many", "foreign_key": "user_id"}"#;
106 let rs: RelationSpec = serde_json::from_str(json).unwrap();
107 assert_eq!(rs.relation_type, RelationType::HasMany);
108 assert_eq!(rs.foreign_key.as_deref(), Some("user_id"));
109 assert!(rs.key.is_none());
110 }
111
112 #[test]
113 fn relation_spec_has_one() {
114 let json = r#"{"resource": "profiles", "type": "has_one", "foreign_key": "user_id"}"#;
115 let rs: RelationSpec = serde_json::from_str(json).unwrap();
116 assert_eq!(rs.relation_type, RelationType::HasOne);
117 }
118
119 #[test]
120 fn index_spec_composite() {
121 let json = r#"{"fields": ["org_id", "role"]}"#;
122 let idx: IndexSpec = serde_json::from_str(json).unwrap();
123 assert_eq!(idx.fields, vec!["org_id", "role"]);
124 assert!(!idx.unique);
125 assert!(idx.order.is_none());
126 }
127
128 #[test]
129 fn index_spec_with_order() {
130 let json = r#"{"fields": ["created_at"], "order": "desc"}"#;
131 let idx: IndexSpec = serde_json::from_str(json).unwrap();
132 assert_eq!(idx.order.as_deref(), Some("desc"));
133 }
134
135 #[test]
136 fn index_spec_unique() {
137 let json = r#"{"fields": ["email"], "unique": true}"#;
138 let idx: IndexSpec = serde_json::from_str(json).unwrap();
139 assert!(idx.unique);
140 }
141}