1use std::fmt;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum HttpMethod {
9 Get,
10 Post,
11 Put,
12 Patch,
13 Delete,
14 Options,
15 Head,
16}
17
18impl HttpMethod {
19 pub fn try_parse(s: &str) -> Option<Self> {
21 match s {
22 "GET" | "get" => Some(Self::Get),
23 "POST" | "post" => Some(Self::Post),
24 "PUT" | "put" => Some(Self::Put),
25 "PATCH" | "patch" => Some(Self::Patch),
26 "DELETE" | "delete" => Some(Self::Delete),
27 "OPTIONS" | "options" => Some(Self::Options),
28 "HEAD" | "head" => Some(Self::Head),
29 _ => None,
30 }
31 }
32
33 #[allow(clippy::should_implement_trait)]
36 pub fn from_str(s: &str) -> Self {
37 Self::try_parse(s).unwrap_or(Self::Get)
38 }
39
40 pub fn as_str(&self) -> &'static str {
41 match self {
42 Self::Get => "GET",
43 Self::Post => "POST",
44 Self::Put => "PUT",
45 Self::Patch => "PATCH",
46 Self::Delete => "DELETE",
47 Self::Options => "OPTIONS",
48 Self::Head => "HEAD",
49 }
50 }
51
52 pub fn is_bodyless(&self) -> bool {
54 matches!(self, Self::Get | Self::Head | Self::Options | Self::Delete)
55 }
56}
57
58impl fmt::Display for HttpMethod {
59 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60 f.write_str(self.as_str())
61 }
62}
63
64#[derive(Debug, Clone)]
69pub struct DataError {
70 pub code: String,
71 pub message: String,
72}
73
74impl fmt::Display for DataError {
75 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76 write!(f, "[{}] {}", self.code, self.message)
77 }
78}
79
80impl std::error::Error for DataError {}
81
82pub trait DataStore: Send + Sync {
92 fn manifest(&self) -> &pylon_kernel::AppManifest;
93
94 fn insert(&self, entity: &str, data: &serde_json::Value) -> Result<String, DataError>;
95
96 fn get_by_id(&self, entity: &str, id: &str) -> Result<Option<serde_json::Value>, DataError>;
97
98 fn list(&self, entity: &str) -> Result<Vec<serde_json::Value>, DataError>;
99
100 fn list_after(
101 &self,
102 entity: &str,
103 after: Option<&str>,
104 limit: usize,
105 ) -> Result<Vec<serde_json::Value>, DataError>;
106
107 fn update(&self, entity: &str, id: &str, data: &serde_json::Value) -> Result<bool, DataError>;
108
109 fn delete(&self, entity: &str, id: &str) -> Result<bool, DataError>;
110
111 fn lookup(
112 &self,
113 entity: &str,
114 field: &str,
115 value: &str,
116 ) -> Result<Option<serde_json::Value>, DataError>;
117
118 fn link(
119 &self,
120 entity: &str,
121 id: &str,
122 relation: &str,
123 target_id: &str,
124 ) -> Result<bool, DataError>;
125
126 fn unlink(&self, entity: &str, id: &str, relation: &str) -> Result<bool, DataError>;
127
128 fn query_filtered(
129 &self,
130 entity: &str,
131 filter: &serde_json::Value,
132 ) -> Result<Vec<serde_json::Value>, DataError>;
133
134 fn query_graph(&self, query: &serde_json::Value) -> Result<serde_json::Value, DataError>;
135
136 fn aggregate(
153 &self,
154 _entity: &str,
155 _spec: &serde_json::Value,
156 ) -> Result<serde_json::Value, DataError> {
157 Err(DataError {
158 code: "NOT_SUPPORTED".into(),
159 message: "aggregate() is not implemented by this backend".into(),
160 })
161 }
162
163 fn transact(
169 &self,
170 ops: &[serde_json::Value],
171 ) -> Result<(bool, Vec<serde_json::Value>), DataError>;
172}
173
174#[cfg(test)]
175mod tests {
176 use super::*;
177
178 #[test]
179 fn http_method_roundtrip() {
180 assert_eq!(HttpMethod::from_str("GET"), HttpMethod::Get);
181 assert_eq!(HttpMethod::from_str("post"), HttpMethod::Post);
182 assert_eq!(HttpMethod::from_str("DELETE"), HttpMethod::Delete);
183 assert_eq!(HttpMethod::Get.as_str(), "GET");
184 }
185
186 #[test]
187 fn data_error_display() {
188 let e = DataError {
189 code: "TEST".into(),
190 message: "fail".into(),
191 };
192 assert_eq!(format!("{e}"), "[TEST] fail");
193 }
194}