aboutsummaryrefslogtreecommitdiffstats
path: root/warden/src/server/routes/config/schema/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'warden/src/server/routes/config/schema/mod.rs')
-rw-r--r--warden/src/server/routes/config/schema/mod.rs258
1 files changed, 258 insertions, 0 deletions
diff --git a/warden/src/server/routes/config/schema/mod.rs b/warden/src/server/routes/config/schema/mod.rs
index 17db5ce..901d116 100644
--- a/warden/src/server/routes/config/schema/mod.rs
+++ b/warden/src/server/routes/config/schema/mod.rs
@@ -25,3 +25,261 @@ pub fn router(store: Arc<AppState>) -> OpenApiRouter {
.routes(utoipa_axum::routes!(update::update_schema))
.with_state(store)
}
+
+#[cfg(test)]
+pub mod tests {
+ use std::collections::HashMap;
+ use std::sync::Arc;
+
+ use api_config::schema::TransactionSchema;
+ use api_config::{ConfigurationError, schema::SchemaDriver};
+ use async_trait::async_trait;
+ use time::OffsetDateTime;
+ use tokio::sync::RwLock;
+
+ use serde_json::Value;
+ use warden_core::pagination::{Connection, Edge, PageInfo, PaginationArgs};
+
+ #[derive(Default)]
+ pub struct MockSchemaDriver {
+ store: Arc<RwLock<HashMap<(String, String), TransactionSchema>>>,
+ }
+
+ impl MockSchemaDriver {
+ pub fn new() -> Self {
+ Self::default()
+ }
+ }
+
+ use sqlx::error::DatabaseError;
+ use std::fmt;
+
+ #[derive(Debug)]
+ struct UniqueViolationError {
+ msg: String,
+ }
+
+ impl fmt::Display for UniqueViolationError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{}", self.msg)
+ }
+ }
+
+ impl std::error::Error for UniqueViolationError {}
+
+ impl DatabaseError for UniqueViolationError {
+ fn message(&self) -> &str {
+ &self.msg
+ }
+
+ fn as_error(&self) -> &(dyn std::error::Error + Send + Sync + 'static) {
+ unimplemented!()
+ }
+
+ fn as_error_mut(&mut self) -> &mut (dyn std::error::Error + Send + Sync + 'static) {
+ unimplemented!()
+ }
+
+ fn into_error(self: Box<Self>) -> Box<dyn std::error::Error + Send + Sync + 'static> {
+ unimplemented!()
+ }
+
+ fn kind(&self) -> sqlx::error::ErrorKind {
+ sqlx::error::ErrorKind::UniqueViolation
+ }
+ }
+
+ #[derive(Debug)]
+ struct OtherDbError {
+ msg: String,
+ }
+
+ impl fmt::Display for OtherDbError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{}", self.msg)
+ }
+ }
+
+ impl std::error::Error for OtherDbError {}
+
+ impl DatabaseError for OtherDbError {
+ fn message(&self) -> &str {
+ &self.msg
+ }
+
+ fn as_error(&self) -> &(dyn std::error::Error + Send + Sync + 'static) {
+ unimplemented!()
+ }
+
+ fn as_error_mut(&mut self) -> &mut (dyn std::error::Error + Send + Sync + 'static) {
+ unimplemented!()
+ }
+
+ fn into_error(self: Box<Self>) -> Box<dyn std::error::Error + Send + Sync + 'static> {
+ unimplemented!()
+ }
+
+ fn kind(&self) -> sqlx::error::ErrorKind {
+ sqlx::error::ErrorKind::Other
+ }
+ }
+
+ #[async_trait]
+ impl SchemaDriver for MockSchemaDriver {
+ async fn create_schema(
+ &self,
+ kind: &str,
+ version: &str,
+ schema: &Value,
+ ) -> Result<TransactionSchema, ConfigurationError> {
+ let mut store = self.store.write().await;
+ let key = (kind.to_string(), version.to_string());
+
+ if store.contains_key(&key) {
+ let err = UniqueViolationError {
+ msg: "key".to_string(),
+ };
+ dbg!(err.code());
+ return Err(ConfigurationError::Database(sqlx::Error::Database(
+ Box::new(err),
+ )));
+ };
+
+ if matches!(kind, "error") {
+ let err = OtherDbError {
+ msg: "key".to_string(),
+ };
+ dbg!(err.code());
+ return Err(ConfigurationError::Database(sqlx::Error::Database(
+ Box::new(err),
+ )));
+ }
+
+ let schema_obj = TransactionSchema {
+ id: 1,
+ schema_type: kind.to_string(),
+ schema_version: version.to_string(),
+ schema: schema.clone(),
+ created_at: OffsetDateTime::now_utc(),
+ updated_at: OffsetDateTime::now_utc(),
+ };
+
+ store.insert(key, schema_obj.clone());
+ Ok(schema_obj)
+ }
+
+ async fn delete_schema(&self, kind: &str, version: &str) -> Result<(), ConfigurationError> {
+ let mut store = self.store.write().await;
+ let key = (kind.to_string(), version.to_string());
+
+ store.remove(&key);
+ Ok(())
+ }
+
+ async fn get_schema(
+ &self,
+ kind: &str,
+ version: &str,
+ ) -> Result<Option<TransactionSchema>, ConfigurationError> {
+ let store = self.store.read().await;
+ let key = (kind.to_string(), version.to_string());
+
+ Ok(store.get(&key).cloned())
+ }
+
+ async fn update_schema(
+ &self,
+ kind: &str,
+ version: &str,
+ schema: &Value,
+ ) -> Result<Option<TransactionSchema>, ConfigurationError> {
+ let mut store = self.store.write().await;
+ let key = (kind.to_string(), version.to_string());
+
+ if let Some(existing) = store.get_mut(&key) {
+ existing.schema = schema.clone();
+ return Ok(Some(existing.clone()));
+ }
+
+ Ok(None)
+ }
+
+ async fn list_schemas(
+ &self,
+ input: &PaginationArgs,
+ limit: i64,
+ ) -> Result<Connection<TransactionSchema>, ConfigurationError> {
+ let store = self.store.read().await;
+
+ // 1. Collect + sort for stable pagination
+ let mut items: Vec<_> = store.values().cloned().collect();
+ items.sort_by(|a, b| {
+ (a.schema_type.clone(), a.schema_version.clone())
+ .cmp(&(b.schema_type.clone(), b.schema_version.clone()))
+ });
+
+ // 2. Convert to edges
+ let mut edges: Vec<Edge<TransactionSchema>> = items
+ .into_iter()
+ .map(|schema| Edge {
+ cursor: format!("{}:{}", schema.schema_type, schema.schema_version),
+ node: schema,
+ })
+ .collect();
+
+ // 3. Apply cursors (after / before)
+ if let Some(after) = &input.after
+ && let Some(pos) = edges.iter().position(|e| &e.cursor == after)
+ {
+ edges = edges.into_iter().skip(pos + 1).collect();
+ }
+
+ if let Some(before) = &input.before
+ && let Some(pos) = edges.iter().position(|e| &e.cursor == before)
+ {
+ edges = edges.into_iter().take(pos).collect();
+ }
+
+ let total = edges.len();
+
+ // 4. Apply first / last
+ let mut sliced = edges;
+
+ if let Some(first) = input.first {
+ let take = first.min(limit).max(0) as usize;
+ sliced = sliced.into_iter().take(take).collect();
+ } else if let Some(last) = input.last {
+ let take = last.min(limit).max(0) as usize;
+ let len = sliced.len();
+ sliced = sliced.into_iter().skip(len.saturating_sub(take)).collect();
+ } else {
+ // default limit
+ sliced = sliced.into_iter().take(limit as usize).collect();
+ }
+
+ // 5. PageInfo
+ let start_cursor = sliced.first().map(|e| e.cursor.clone());
+ let end_cursor = sliced.last().map(|e| e.cursor.clone());
+
+ let has_next_page = match input.first {
+ Some(first) => total > first as usize,
+ None => false,
+ };
+
+ let has_previous_page = match input.last {
+ Some(last) => total > last as usize,
+ None => false,
+ };
+
+ Ok(Connection {
+ edges: sliced,
+ page_info: PageInfo {
+ has_next_page,
+ has_previous_page,
+ start_cursor,
+ end_cursor,
+ },
+ })
+ }
+ }
+}