use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HttpMethod {
Get,
Post,
Put,
Patch,
Delete,
Options,
Head,
}
impl HttpMethod {
pub fn try_parse(s: &str) -> Option<Self> {
match s {
"GET" | "get" => Some(Self::Get),
"POST" | "post" => Some(Self::Post),
"PUT" | "put" => Some(Self::Put),
"PATCH" | "patch" => Some(Self::Patch),
"DELETE" | "delete" => Some(Self::Delete),
"OPTIONS" | "options" => Some(Self::Options),
"HEAD" | "head" => Some(Self::Head),
_ => None,
}
}
#[allow(clippy::should_implement_trait)]
pub fn from_str(s: &str) -> Self {
Self::try_parse(s).unwrap_or(Self::Get)
}
pub fn as_str(&self) -> &'static str {
match self {
Self::Get => "GET",
Self::Post => "POST",
Self::Put => "PUT",
Self::Patch => "PATCH",
Self::Delete => "DELETE",
Self::Options => "OPTIONS",
Self::Head => "HEAD",
}
}
pub fn is_bodyless(&self) -> bool {
matches!(self, Self::Get | Self::Head | Self::Options | Self::Delete)
}
}
impl fmt::Display for HttpMethod {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone)]
pub struct DataError {
pub code: String,
pub message: String,
}
impl fmt::Display for DataError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[{}] {}", self.code, self.message)
}
}
impl std::error::Error for DataError {}
pub trait DataStore: Send + Sync {
fn manifest(&self) -> &pylon_kernel::AppManifest;
fn insert(&self, entity: &str, data: &serde_json::Value) -> Result<String, DataError>;
fn get_by_id(&self, entity: &str, id: &str) -> Result<Option<serde_json::Value>, DataError>;
fn list(&self, entity: &str) -> Result<Vec<serde_json::Value>, DataError>;
fn list_after(
&self,
entity: &str,
after: Option<&str>,
limit: usize,
) -> Result<Vec<serde_json::Value>, DataError>;
fn update(&self, entity: &str, id: &str, data: &serde_json::Value) -> Result<bool, DataError>;
fn delete(&self, entity: &str, id: &str) -> Result<bool, DataError>;
fn lookup(
&self,
entity: &str,
field: &str,
value: &str,
) -> Result<Option<serde_json::Value>, DataError>;
fn link(
&self,
entity: &str,
id: &str,
relation: &str,
target_id: &str,
) -> Result<bool, DataError>;
fn unlink(&self, entity: &str, id: &str, relation: &str) -> Result<bool, DataError>;
fn query_filtered(
&self,
entity: &str,
filter: &serde_json::Value,
) -> Result<Vec<serde_json::Value>, DataError>;
fn query_graph(&self, query: &serde_json::Value) -> Result<serde_json::Value, DataError>;
fn aggregate(
&self,
_entity: &str,
_spec: &serde_json::Value,
) -> Result<serde_json::Value, DataError> {
Err(DataError {
code: "NOT_SUPPORTED".into(),
message: "aggregate() is not implemented by this backend".into(),
})
}
fn transact(
&self,
ops: &[serde_json::Value],
) -> Result<(bool, Vec<serde_json::Value>), DataError>;
fn search(
&self,
_entity: &str,
_query: &serde_json::Value,
) -> Result<serde_json::Value, DataError> {
Err(DataError {
code: "NOT_SUPPORTED".into(),
message: "search() is not implemented by this backend".into(),
})
}
fn crdt_snapshot(&self, _entity: &str, _row_id: &str) -> Result<Option<Vec<u8>>, DataError> {
Ok(None)
}
fn crdt_apply_update(
&self,
_entity: &str,
_row_id: &str,
_update: &[u8],
) -> Result<Vec<u8>, DataError> {
Err(DataError {
code: "NOT_SUPPORTED".into(),
message: "crdt_apply_update() is not implemented by this backend".into(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn http_method_roundtrip() {
assert_eq!(HttpMethod::from_str("GET"), HttpMethod::Get);
assert_eq!(HttpMethod::from_str("post"), HttpMethod::Post);
assert_eq!(HttpMethod::from_str("DELETE"), HttpMethod::Delete);
assert_eq!(HttpMethod::Get.as_str(), "GET");
}
#[test]
fn data_error_display() {
let e = DataError {
code: "TEST".into(),
message: "fail".into(),
};
assert_eq!(format!("{e}"), "[TEST] fail");
}
}