1use indexmap::IndexMap;
2use serde::{Deserialize, Serialize};
3
4use crate::{EndpointSpec, FieldSchema, IndexSpec, RelationSpec};
5
6#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
18#[serde(deny_unknown_fields)]
19pub struct ResourceDefinition {
20 pub resource: String,
22
23 pub version: u32,
25
26 #[serde(default, skip_serializing_if = "Option::is_none")]
28 pub db: Option<String>,
29
30 #[serde(default, skip_serializing_if = "Option::is_none")]
34 pub tenant_key: Option<String>,
35
36 pub schema: IndexMap<String, FieldSchema>,
38
39 #[serde(default, skip_serializing_if = "Option::is_none")]
41 pub endpoints: Option<IndexMap<String, EndpointSpec>>,
42
43 #[serde(default, skip_serializing_if = "Option::is_none")]
45 pub relations: Option<IndexMap<String, RelationSpec>>,
46
47 #[serde(default, skip_serializing_if = "Option::is_none")]
49 pub indexes: Option<Vec<IndexSpec>>,
50}
51
52#[cfg(test)]
53mod tests {
54 use super::*;
55 use crate::{
56 AuthRule, CacheSpec, EndpointSpec, FieldType, HttpMethod, PaginationStyle, RelationType,
57 };
58
59 fn sample_resource() -> ResourceDefinition {
60 let mut schema = IndexMap::new();
61 schema.insert(
62 "id".to_string(),
63 FieldSchema {
64 field_type: FieldType::Uuid,
65 primary: true,
66 generated: true,
67 required: false,
68 unique: false,
69 nullable: false,
70 reference: None,
71 min: None,
72 max: None,
73 format: None,
74 values: None,
75 default: None,
76 sensitive: false,
77 search: false,
78 items: None,
79 },
80 );
81 schema.insert(
82 "email".to_string(),
83 FieldSchema {
84 field_type: FieldType::String,
85 primary: false,
86 generated: false,
87 required: true,
88 unique: true,
89 nullable: false,
90 reference: None,
91 min: None,
92 max: None,
93 format: Some("email".to_string()),
94 values: None,
95 default: None,
96 sensitive: false,
97 search: false,
98 items: None,
99 },
100 );
101
102 let mut endpoints = IndexMap::new();
103 endpoints.insert(
104 "list".to_string(),
105 EndpointSpec {
106 method: Some(HttpMethod::Get),
107 path: Some("/users".to_string()),
108 auth: Some(AuthRule::Roles(vec![
109 "member".to_string(),
110 "admin".to_string(),
111 ])),
112 input: None,
113 filters: Some(vec!["role".to_string()]),
114 search: Some(vec!["email".to_string()]),
115 pagination: Some(PaginationStyle::Cursor),
116 sort: None,
117 cache: Some(CacheSpec {
118 ttl: 60,
119 invalidate_on: None,
120 }),
121 controller: None,
122 events: None,
123 jobs: None,
124 upload: None,
125 soft_delete: false,
126 },
127 );
128
129 let mut relations = IndexMap::new();
130 relations.insert(
131 "orders".to_string(),
132 RelationSpec {
133 resource: "orders".to_string(),
134 relation_type: RelationType::HasMany,
135 key: None,
136 foreign_key: Some("user_id".to_string()),
137 },
138 );
139
140 ResourceDefinition {
141 resource: "users".to_string(),
142 version: 1,
143 db: None,
144 tenant_key: None,
145 schema,
146 endpoints: Some(endpoints),
147 relations: Some(relations),
148 indexes: Some(vec![IndexSpec {
149 fields: vec!["created_at".to_string()],
150 unique: false,
151 order: Some("desc".to_string()),
152 }]),
153 }
154 }
155
156 #[test]
157 fn resource_definition_construction() {
158 let rd = sample_resource();
159 assert_eq!(rd.resource, "users");
160 assert_eq!(rd.version, 1);
161 assert_eq!(rd.schema.len(), 2);
162 assert!(rd.schema.contains_key("id"));
163 assert!(rd.schema.contains_key("email"));
164 }
165
166 #[test]
167 fn resource_definition_serde_roundtrip() {
168 let rd = sample_resource();
169 let json = serde_json::to_string_pretty(&rd).unwrap();
170 let back: ResourceDefinition = serde_json::from_str(&json).unwrap();
171 assert_eq!(rd, back);
172 }
173
174 #[test]
175 fn resource_definition_preserves_field_order() {
176 let rd = sample_resource();
177 let keys: Vec<&String> = rd.schema.keys().collect();
178 assert_eq!(keys, vec!["id", "email"]);
179 }
180
181 #[test]
182 fn resource_definition_optional_sections() {
183 let rd = ResourceDefinition {
184 resource: "tags".to_string(),
185 version: 1,
186 db: None,
187 tenant_key: None,
188 schema: IndexMap::new(),
189 endpoints: None,
190 relations: None,
191 indexes: None,
192 };
193 assert!(rd.endpoints.is_none());
194 assert!(rd.relations.is_none());
195 assert!(rd.indexes.is_none());
196
197 let json = serde_json::to_string(&rd).unwrap();
198 assert!(!json.contains("endpoints"));
199 assert!(!json.contains("relations"));
200 assert!(!json.contains("indexes"));
201 }
202}