Skip to main content

pylon_query/
lib.rs

1use pylon_kernel::{AppManifest, ManifestField, ManifestQuery};
2
3// ---------------------------------------------------------------------------
4// Query descriptor — runtime-facing query metadata
5// ---------------------------------------------------------------------------
6
7/// A query descriptor holds the contract for a single named query.
8/// It describes what inputs the query accepts, derived from the manifest.
9/// It does not implement execution.
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub struct QueryDescriptor {
12    pub name: String,
13    pub input: Vec<InputField>,
14}
15
16/// An input field descriptor for a query or action.
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct InputField {
19    pub name: String,
20    pub field_type: String,
21    pub optional: bool,
22}
23
24impl InputField {
25    pub fn from_manifest_field(f: &ManifestField) -> Self {
26        Self {
27            name: f.name.clone(),
28            field_type: f.field_type.clone(),
29            optional: f.optional,
30        }
31    }
32}
33
34impl QueryDescriptor {
35    pub fn from_manifest(mq: &ManifestQuery) -> Self {
36        Self {
37            name: mq.name.clone(),
38            input: mq
39                .input
40                .iter()
41                .map(InputField::from_manifest_field)
42                .collect(),
43        }
44    }
45}
46
47// ---------------------------------------------------------------------------
48// Query registry — all queries from a manifest
49// ---------------------------------------------------------------------------
50
51/// A registry of query descriptors, built from a manifest.
52#[derive(Debug, Clone, PartialEq, Eq)]
53pub struct QueryRegistry {
54    pub queries: Vec<QueryDescriptor>,
55}
56
57impl QueryRegistry {
58    pub fn from_manifest(manifest: &AppManifest) -> Self {
59        Self {
60            queries: manifest
61                .queries
62                .iter()
63                .map(QueryDescriptor::from_manifest)
64                .collect(),
65        }
66    }
67
68    pub fn get(&self, name: &str) -> Option<&QueryDescriptor> {
69        self.queries.iter().find(|q| q.name == name)
70    }
71
72    pub fn names(&self) -> Vec<&str> {
73        self.queries.iter().map(|q| q.name.as_str()).collect()
74    }
75}
76
77// ---------------------------------------------------------------------------
78// Tests
79// ---------------------------------------------------------------------------
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84    use pylon_kernel::ManifestField;
85
86    fn test_manifest() -> AppManifest {
87        serde_json::from_str(include_str!(
88            "../../../examples/todo-app/pylon.manifest.json"
89        ))
90        .unwrap()
91    }
92
93    #[test]
94    fn registry_from_manifest() {
95        let reg = QueryRegistry::from_manifest(&test_manifest());
96        assert_eq!(reg.queries.len(), 3);
97        assert_eq!(reg.names(), vec!["todosByAuthor", "allTodos", "todoById"]);
98    }
99
100    #[test]
101    fn get_query_by_name() {
102        let reg = QueryRegistry::from_manifest(&test_manifest());
103        let q = reg.get("todosByAuthor").unwrap();
104        assert_eq!(q.name, "todosByAuthor");
105        assert_eq!(q.input.len(), 1);
106        assert_eq!(q.input[0].name, "authorId");
107        assert_eq!(q.input[0].field_type, "id(User)");
108        assert!(!q.input[0].optional);
109    }
110
111    #[test]
112    fn get_query_with_optional_input() {
113        let reg = QueryRegistry::from_manifest(&test_manifest());
114        let q = reg.get("allTodos").unwrap();
115        assert_eq!(q.input.len(), 1);
116        assert_eq!(q.input[0].name, "done");
117        assert!(q.input[0].optional);
118    }
119
120    #[test]
121    fn get_missing_query_returns_none() {
122        let reg = QueryRegistry::from_manifest(&test_manifest());
123        assert!(reg.get("nonexistent").is_none());
124    }
125
126    #[test]
127    fn descriptor_from_manifest_query() {
128        let mq = ManifestQuery {
129            name: "test".into(),
130            input: vec![ManifestField {
131                name: "id".into(),
132                field_type: "string".into(),
133                optional: false,
134                unique: false,
135                crdt: None,
136            }],
137        };
138        let desc = QueryDescriptor::from_manifest(&mq);
139        assert_eq!(desc.name, "test");
140        assert_eq!(desc.input.len(), 1);
141        assert_eq!(desc.input[0].name, "id");
142    }
143}