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