From 2c336f0339747aa77a8fe6613b83200c8d4902a5 Mon Sep 17 00:00:00 2001 From: rtkay123 Date: Tue, 31 Mar 2026 21:35:49 +0200 Subject: test(config): schema --- lib/api-config/src/schema/create.rs | 43 ------- lib/api-config/src/schema/create_schema.rs | 70 ++++++++++ lib/api-config/src/schema/delete_schema.rs | 19 +++ lib/api-config/src/schema/get_schema.rs | 26 ++++ lib/api-config/src/schema/implementation.rs | 84 ++++++++++++ lib/api-config/src/schema/mod.rs | 192 +++++++++------------------- lib/api-config/src/schema/update_schema.rs | 34 +++++ 7 files changed, 296 insertions(+), 172 deletions(-) delete mode 100644 lib/api-config/src/schema/create.rs create mode 100644 lib/api-config/src/schema/create_schema.rs create mode 100644 lib/api-config/src/schema/delete_schema.rs create mode 100644 lib/api-config/src/schema/get_schema.rs create mode 100644 lib/api-config/src/schema/implementation.rs create mode 100644 lib/api-config/src/schema/update_schema.rs (limited to 'lib/api-config/src/schema') diff --git a/lib/api-config/src/schema/create.rs b/lib/api-config/src/schema/create.rs deleted file mode 100644 index e6511d5..0000000 --- a/lib/api-config/src/schema/create.rs +++ /dev/null @@ -1,43 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Deserialize, Serialize)] -#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] -#[cfg_attr(feature = "utoipa", schema(example = json!({ - "schemaType": "custom.schema", - "schemaVersion": "1.0.0", - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "FinancialTransaction", - "type": "object", - "required": ["transactionId", "amount", "currency", "timestamp"], - "properties": { - "transactionId": { - "type": "string", - "format": "uuid" - }, - "amount": { - "type": "number", - "exclusiveMinimum": 0 - }, - "currency": { - "type": "string", - "pattern": "^[A-Z]{3}$", - "description": "ISO 4217 Alpha-3 code (e.g., USD, EUR)" - }, - "timestamp": { - "type": "string", - "format": "date-time" - }, - } - } -})))] -#[serde(rename_all = "camelCase")] -/// The json schema to validate for each transaction of this type and version -pub struct CreateSchema { - /// Transaction schema type - pub schema_type: String, - /// The schema's version - pub schema_version: String, - /// The json schema - pub schema: serde_json::Value, -} diff --git a/lib/api-config/src/schema/create_schema.rs b/lib/api-config/src/schema/create_schema.rs new file mode 100644 index 0000000..493fb09 --- /dev/null +++ b/lib/api-config/src/schema/create_schema.rs @@ -0,0 +1,70 @@ +use serde::{Deserialize, Serialize}; +use tracing::debug; + +use crate::{ + ConfigurationError, + schema::{SchemaService, TransactionSchema}, +}; + +#[derive(Deserialize, Serialize)] +#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] +#[cfg_attr(feature = "utoipa", schema(example = json!({ + "schemaType": "custom.schema", + "schemaVersion": "1.0.0", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "FinancialTransaction", + "type": "object", + "required": ["transactionId", "amount", "currency", "timestamp"], + "properties": { + "transactionId": { + "type": "string", + "format": "uuid" + }, + "amount": { + "type": "number", + "exclusiveMinimum": 0 + }, + "currency": { + "type": "string", + "pattern": "^[A-Z]{3}$", + "description": "ISO 4217 Alpha-3 code (e.g., USD, EUR)" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + } + } +})))] +#[serde(rename_all = "camelCase")] +/// The json schema to validate for each transaction of this type and version +pub struct CreateSchema { + /// Transaction schema type + pub schema_type: String, + /// The schema's version + pub schema_version: String, + /// The json schema + pub schema: serde_json::Value, +} + +pub(super) async fn create_schema( + state: &SchemaService, + kind: &str, + version: &str, + schema: &serde_json::Value, +) -> Result { + debug!("creating transaction schema"); + sqlx::query_as!( + TransactionSchema, + "insert into transaction_schema (schema_type, schema_version, schema) values ($1, $2, $3) + returning * + ", + kind, + version, + sqlx::types::Json(&schema) as _ + ) + .fetch_one(&state.database) + .await + .map_err(|e| e.into()) +} diff --git a/lib/api-config/src/schema/delete_schema.rs b/lib/api-config/src/schema/delete_schema.rs new file mode 100644 index 0000000..86e71d5 --- /dev/null +++ b/lib/api-config/src/schema/delete_schema.rs @@ -0,0 +1,19 @@ +use tracing::debug; + +use crate::{ConfigurationError, schema::SchemaService}; + +pub(super) async fn delete_schema( + state: &SchemaService, + kind: &str, + version: &str, +) -> Result<(), ConfigurationError> { + debug!("deleting transaction schema"); + sqlx::query!( + "delete from transaction_schema where schema_type = $1 and schema_version = $2", + kind, + version, + ) + .execute(&state.database) + .await?; + Ok(()) +} diff --git a/lib/api-config/src/schema/get_schema.rs b/lib/api-config/src/schema/get_schema.rs new file mode 100644 index 0000000..427a02f --- /dev/null +++ b/lib/api-config/src/schema/get_schema.rs @@ -0,0 +1,26 @@ +use tracing::debug; + +use crate::{ + ConfigurationError, + schema::{SchemaService, TransactionSchema}, +}; + +pub(super) async fn get_schema( + state: &SchemaService, + kind: &str, + version: &str, +) -> Result, ConfigurationError> { + debug!("getting transaction schema"); + let result = sqlx::query_as!( + TransactionSchema, + "select + * + from transaction_schema where schema_type = $1 and schema_version = $2", + kind, + version, + ) + .fetch_optional(&state.database) + .await?; + + Ok(result) +} diff --git a/lib/api-config/src/schema/implementation.rs b/lib/api-config/src/schema/implementation.rs new file mode 100644 index 0000000..c414879 --- /dev/null +++ b/lib/api-config/src/schema/implementation.rs @@ -0,0 +1,84 @@ +use async_trait::async_trait; +use tracing::debug; + +use crate::schema::{self, SchemaDriver, SchemaService, TransactionSchema}; + +#[async_trait] +impl SchemaDriver for SchemaService { + #[tracing::instrument(skip(self, schema))] + async fn create_schema( + &self, + kind: &str, + version: &str, + schema: &serde_json::Value, + ) -> Result { + schema::create_schema::create_schema(self, kind, version, schema).await + } + + #[tracing::instrument(skip(self))] + async fn delete_schema( + &self, + kind: &str, + version: &str, + ) -> Result<(), crate::ConfigurationError> { + schema::delete_schema::delete_schema(self, kind, version).await + } + + #[tracing::instrument(skip(self))] + async fn get_schema( + &self, + kind: &str, + version: &str, + ) -> Result, crate::ConfigurationError> { + schema::get_schema::get_schema(self, kind, version).await + } + + #[tracing::instrument(skip(self, schema))] + async fn update_schema( + &self, + kind: &str, + version: &str, + schema: &serde_json::Value, + ) -> Result, crate::ConfigurationError> { + schema::update_schema::update_schema(self, kind, version, schema).await + } + + #[tracing::instrument(skip(self))] + async fn get_schemas( + &self, + limit: i64, + first: Option, + after: Option<&str>, + ) -> Result, crate::ConfigurationError> { + debug!("getting transaction schemas"); + let limit = first.unwrap_or(limit); + let mut last_type = String::default(); + let mut last_version = String::default(); + + if let Some(s) = after { + let parts: Vec<&str> = s.split(',').collect(); + if parts.len() == 2 { + last_type = parts[0].to_string(); + last_version = parts[1].to_string(); + } + } + + let rows = sqlx::query_as!( + TransactionSchema, + " + select * + from transaction_schema + where ($1 = '' or (schema_type, schema_version) > ($1, $2)) + order by schema_type asc, schema_version asc + limit $3 + ", + &last_type, + &last_version, + limit + 1 + ) + .fetch_all(&self.database) + .await?; + + Ok(rows) + } +} diff --git a/lib/api-config/src/schema/mod.rs b/lib/api-config/src/schema/mod.rs index f626e87..d16ee9f 100644 --- a/lib/api-config/src/schema/mod.rs +++ b/lib/api-config/src/schema/mod.rs @@ -1,7 +1,10 @@ -mod create; -pub use create::CreateSchema; +mod create_schema; +mod delete_schema; +mod get_schema; +mod implementation; +mod update_schema; +pub use create_schema::CreateSchema; use sqlx::PgPool; -use tracing::debug; use async_trait::async_trait; use serde::{Deserialize, Serialize}; @@ -96,133 +99,64 @@ pub trait SchemaDriver: Send + Sync { ) -> Result, ConfigurationError>; } -#[async_trait] -impl SchemaDriver for SchemaService { - #[tracing::instrument(skip(self, schema))] - async fn create_schema( - &self, - kind: &str, - version: &str, - schema: &serde_json::Value, - ) -> Result { - debug!("creating transaction schema"); - sqlx::query_as!( - TransactionSchema, - "insert into transaction_schema (schema_type, schema_version, schema) values ($1, $2, $3) - returning * - ", - kind, - version, - sqlx::types::Json(&schema) as _ - ) - .fetch_one(&self.database) - .await - .map_err(|e| e.into()) - } +#[cfg(test)] +mod tests { + use sqlx::PgPool; + + use crate::schema::{SchemaDriver, SchemaService}; + + #[sqlx::test( + migrator = "crate::MIGRATOR", + fixtures(path = "../../tests/fixtures", scripts("schema")) + )] + async fn schema(pool: PgPool) -> anyhow::Result<()> { + let driver = SchemaService { database: pool }; + + // 2. Define Fixtures + let kind = "fin_tx_v1"; + let version = "1.0.0"; + let min_schema = serde_json::json!({ + "type": "object", + "required": ["amount", "currency"], + "properties": { + "amount": { "type": "integer" }, + "ccy": { "type": "string" } + } + }); + + // CREATE + let created = driver + .create_schema(kind, version, &min_schema) + .await + .expect("Create failed"); + assert_eq!(created.schema_type, kind); + + // GET + let found = driver + .get_schema("payment", "1.0.0") + .await + .expect("Get failed") + .expect("Schema missing"); + assert_eq!(found.schema["required"], min_schema["required"]); + + // UPDATE + let updated_json = serde_json::json!({"type": "object", "note": "updated"}); + let updated = driver + .update_schema(kind, version, &updated_json) + .await + .expect("Update failed") + .expect("No row to update"); + assert_eq!(updated.schema["note"], "updated"); + + // DELETE + driver + .delete_schema(kind, version) + .await + .expect("Delete failed"); + + let final_check = driver.get_schema(kind, version).await.unwrap(); + assert!(final_check.is_none()); - #[tracing::instrument(skip(self))] - async fn delete_schema( - &self, - kind: &str, - version: &str, - ) -> Result<(), crate::ConfigurationError> { - debug!("deleting transaction schema"); - sqlx::query!( - "delete from transaction_schema where schema_type = $1 and schema_version = $2", - kind, - version, - ) - .execute(&self.database) - .await?; Ok(()) } - - #[tracing::instrument(skip(self))] - async fn get_schema( - &self, - kind: &str, - version: &str, - ) -> Result, crate::ConfigurationError> { - debug!("getting transaction schema"); - let result = sqlx::query_as!( - TransactionSchema, - "select - * - from transaction_schema where schema_type = $1 and schema_version = $2", - kind, - version, - ) - .fetch_optional(&self.database) - .await?; - - Ok(result) - } - - #[tracing::instrument(skip(self, schema))] - async fn update_schema( - &self, - kind: &str, - version: &str, - schema: &serde_json::Value, - ) -> Result, crate::ConfigurationError> { - debug!("updating transaction schema"); - sqlx::query_as!( - TransactionSchema, - " - update - transaction_schema - set - schema = $3 - where - schema_type = $1 - and schema_version = $2 - returning * - ", - kind, - version, - sqlx::types::Json(&schema) as _ - ) - .fetch_optional(&self.database) - .await - .map_err(|e| e.into()) - } - - #[tracing::instrument(skip(self))] - async fn get_schemas( - &self, - limit: i64, - first: Option, - after: Option<&str>, - ) -> Result, ConfigurationError> { - debug!("getting transaction schemas"); - let limit = first.unwrap_or(limit); - let mut last_type = String::default(); - let mut last_version = String::default(); - - if let Some(s) = after { - let parts: Vec<&str> = s.split(',').collect(); - if parts.len() == 2 { - last_type = parts[0].to_string(); - last_version = parts[1].to_string(); - } - } - - let rows = sqlx::query_as!( - TransactionSchema, - " - select * - from transaction_schema - where ($1 = '' or (schema_type, schema_version) > ($1, $2)) - order by schema_type asc, schema_version asc - limit $3 - ", - &last_type, - &last_version, - limit + 1 - ) - .fetch_all(&self.database) - .await?; - - Ok(rows) - } } diff --git a/lib/api-config/src/schema/update_schema.rs b/lib/api-config/src/schema/update_schema.rs new file mode 100644 index 0000000..4aa0862 --- /dev/null +++ b/lib/api-config/src/schema/update_schema.rs @@ -0,0 +1,34 @@ +use tracing::debug; + +use crate::{ + ConfigurationError, + schema::{SchemaService, TransactionSchema}, +}; + +pub(super) async fn update_schema( + state: &SchemaService, + kind: &str, + version: &str, + schema: &serde_json::Value, +) -> Result, ConfigurationError> { + debug!("updating transaction schema"); + sqlx::query_as!( + TransactionSchema, + " + update + transaction_schema + set + schema = $3 + where + schema_type = $1 + and schema_version = $2 + returning * + ", + kind, + version, + sqlx::types::Json(&schema) as _ + ) + .fetch_optional(&state.database) + .await + .map_err(|e| e.into()) +} -- cgit v1.2.3