Skip to main content

type_bridge_server/crud/
types.rs

1//! Request and response types for CRUD endpoints.
2
3use std::collections::HashMap;
4
5use serde::{Deserialize, Serialize};
6
7/// Request body for inserting a new entity.
8///
9/// # Example
10///
11/// ```json
12/// {
13///     "database": "my_db",
14///     "attributes": {
15///         "name": { "value": "Alice", "value_type": "string" },
16///         "age": { "value": 30, "value_type": "long" }
17///     }
18/// }
19/// ```
20#[derive(Debug, Deserialize)]
21pub struct EntityInsertRequest {
22    /// Optional database override (uses pipeline default if not specified).
23    pub database: Option<String>,
24    /// Attribute name-to-value map.
25    pub attributes: HashMap<String, AttributeValueSpec>,
26}
27
28/// Request body for fetching entities with optional filters.
29///
30/// Used as query parameters on GET requests.
31#[derive(Debug, Deserialize)]
32pub struct EntityFetchRequest {
33    /// Optional database override.
34    pub database: Option<String>,
35    /// Optional filter specifications.
36    #[serde(default)]
37    pub filters: Vec<FilterSpec>,
38    /// Optional sort specifications.
39    #[serde(default)]
40    pub sort: Vec<SortSpec>,
41    /// Maximum number of results.
42    pub limit: Option<u64>,
43    /// Number of results to skip.
44    pub offset: Option<u64>,
45}
46
47/// Request body for updating an entity's attributes.
48#[derive(Debug, Deserialize)]
49pub struct EntityUpdateRequest {
50    /// Optional database override.
51    pub database: Option<String>,
52    /// New attribute values to set.
53    pub attributes: HashMap<String, AttributeValueSpec>,
54}
55
56/// Request body for inserting a new relation.
57///
58/// # Example
59///
60/// ```json
61/// {
62///     "database": "my_db",
63///     "role_players": [
64///         { "role": "employee", "entity_type": "person", "key_attr": "name", "key_value": { "value": "Alice", "value_type": "string" } },
65///         { "role": "employer", "entity_type": "company", "key_attr": "name", "key_value": { "value": "Acme", "value_type": "string" } }
66///     ],
67///     "attributes": {
68///         "start-date": { "value": "2024-01-01", "value_type": "date" }
69///     }
70/// }
71/// ```
72#[derive(Debug, Deserialize)]
73pub struct RelationInsertRequest {
74    /// Optional database override.
75    pub database: Option<String>,
76    /// Role player specifications for the relation.
77    pub role_players: Vec<RolePlayerSpec>,
78    /// Optional attributes on the relation itself.
79    #[serde(default)]
80    pub attributes: HashMap<String, AttributeValueSpec>,
81}
82
83/// A typed attribute value.
84#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct AttributeValueSpec {
86    /// The raw JSON value (string, number, boolean, etc.).
87    pub value: serde_json::Value,
88    /// The TypeDB value type (e.g. "string", "long", "double", "boolean", "datetime").
89    pub value_type: String,
90}
91
92/// A filter specification for query endpoints.
93#[derive(Debug, Deserialize)]
94pub struct FilterSpec {
95    /// The attribute name to filter on.
96    pub attr: String,
97    /// The comparison operator (e.g. "==", "!=", ">", "<", ">=", "<=", "contains", "like").
98    pub op: String,
99    /// The value to compare against.
100    pub value: AttributeValueSpec,
101}
102
103/// A sort specification for query endpoints.
104#[derive(Debug, Deserialize)]
105pub struct SortSpec {
106    /// The attribute name to sort by.
107    pub attr: String,
108    /// Sort direction: "asc" or "desc".
109    #[serde(default = "default_sort_dir")]
110    pub dir: String,
111}
112
113fn default_sort_dir() -> String {
114    "asc".to_string()
115}
116
117/// Specification for a role player in a relation insert.
118#[derive(Debug, Deserialize)]
119pub struct RolePlayerSpec {
120    /// The role name (e.g. "employee", "employer").
121    pub role: String,
122    /// The entity type of the role player.
123    pub entity_type: String,
124    /// Optional IID to identify the role player directly.
125    pub iid: Option<String>,
126    /// Optional key attribute name to find the role player by.
127    pub key_attr: Option<String>,
128    /// Optional key attribute value to find the role player by.
129    pub key_value: Option<AttributeValueSpec>,
130}
131
132/// Unified CRUD response.
133#[derive(Debug, Serialize)]
134pub struct CrudResponse {
135    /// Status indicator ("ok" on success).
136    pub status: String,
137    /// The results of the operation.
138    pub results: serde_json::Value,
139    /// Response metadata (request ID, timing, etc.).
140    pub metadata: CrudMetadata,
141}
142
143/// Metadata for CRUD responses.
144#[derive(Debug, Serialize)]
145pub struct CrudMetadata {
146    /// Unique request identifier for tracking.
147    pub request_id: String,
148    /// Query execution time in milliseconds.
149    pub execution_time_ms: u64,
150    /// The TypeQL query that was executed.
151    pub typeql: String,
152}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157
158    #[test]
159    fn deserialize_entity_insert_request() {
160        let json = serde_json::json!({
161            "attributes": {
162                "name": { "value": "Alice", "value_type": "string" },
163                "age": { "value": 30, "value_type": "long" }
164            }
165        });
166        let req: EntityInsertRequest = serde_json::from_value(json).unwrap();
167        assert!(req.database.is_none());
168        assert_eq!(req.attributes.len(), 2);
169        assert_eq!(req.attributes["name"].value_type, "string");
170    }
171
172    #[test]
173    fn deserialize_entity_fetch_request_defaults() {
174        let json = serde_json::json!({});
175        let req: EntityFetchRequest = serde_json::from_value(json).unwrap();
176        assert!(req.filters.is_empty());
177        assert!(req.sort.is_empty());
178        assert!(req.limit.is_none());
179    }
180
181    #[test]
182    fn deserialize_relation_insert_request() {
183        let json = serde_json::json!({
184            "role_players": [
185                {
186                    "role": "employee",
187                    "entity_type": "person",
188                    "key_attr": "name",
189                    "key_value": { "value": "Alice", "value_type": "string" }
190                }
191            ],
192            "attributes": {}
193        });
194        let req: RelationInsertRequest = serde_json::from_value(json).unwrap();
195        assert_eq!(req.role_players.len(), 1);
196        assert_eq!(req.role_players[0].role, "employee");
197    }
198
199    #[test]
200    fn deserialize_filter_spec() {
201        let json = serde_json::json!({
202            "attr": "age",
203            "op": ">=",
204            "value": { "value": 18, "value_type": "long" }
205        });
206        let filter: FilterSpec = serde_json::from_value(json).unwrap();
207        assert_eq!(filter.attr, "age");
208        assert_eq!(filter.op, ">=");
209    }
210
211    #[test]
212    fn sort_spec_default_direction() {
213        let json = serde_json::json!({ "attr": "name" });
214        let sort: SortSpec = serde_json::from_value(json).unwrap();
215        assert_eq!(sort.dir, "asc");
216    }
217
218    #[test]
219    fn serialize_crud_response() {
220        let resp = CrudResponse {
221            status: "ok".to_string(),
222            results: serde_json::json!({"inserted": true}),
223            metadata: CrudMetadata {
224                request_id: "abc-123".to_string(),
225                execution_time_ms: 42,
226                typeql: "insert $e isa person;".to_string(),
227            },
228        };
229        let json = serde_json::to_value(&resp).unwrap();
230        assert_eq!(json["status"], "ok");
231        assert_eq!(json["metadata"]["execution_time_ms"], 42);
232        assert_eq!(json["metadata"]["typeql"], "insert $e isa person;");
233    }
234
235    #[test]
236    fn role_player_spec_with_iid() {
237        let json = serde_json::json!({
238            "role": "friend",
239            "entity_type": "person",
240            "iid": "0xabc123"
241        });
242        let rp: RolePlayerSpec = serde_json::from_value(json).unwrap();
243        assert_eq!(rp.iid.as_deref(), Some("0xabc123"));
244        assert!(rp.key_attr.is_none());
245    }
246
247    #[test]
248    fn attribute_value_spec_roundtrip() {
249        let spec = AttributeValueSpec {
250            value: serde_json::json!("hello"),
251            value_type: "string".to_string(),
252        };
253        let json = serde_json::to_value(&spec).unwrap();
254        let spec2: AttributeValueSpec = serde_json::from_value(json).unwrap();
255        assert_eq!(spec2.value, serde_json::json!("hello"));
256        assert_eq!(spec2.value_type, "string");
257    }
258}