lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

commit 15d845943bc67b8c83c1aeab886559dd5e004954
parent ccb17e40486c5ac568e391fad959ac517a6a4879
Author: triesap <tyson@radroots.org>
Date:   Thu, 13 Nov 2025 03:04:08 +0000

types: add shared error and result wrappers for `radroots-types`

Diffstat:
MCargo.lock | 1+
Msql-core/src/error.rs | 3++-
Mtangle-schema/bindings/ts/src/types.ts | 6+++---
Mtangle-schema/src/tables/log_error.rs | 16++++++++--------
Mtangle-sql-wasm/src/lib.rs | 32+++++++++++++++++++++-----------
Mtangle-sql/Cargo.toml | 1+
Mtangle-sql/src/lib.rs | 42+++++++++++++++++++++---------------------
Mtangle-sql/src/tables/log_error.rs | 139++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
Mtypes/bindings/ts/src/types.ts | 2++
Mtypes/src/types.rs | 13+++++++++++++
10 files changed, 164 insertions(+), 91 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -1868,6 +1868,7 @@ version = "0.1.0" dependencies = [ "radroots-sql-core", "radroots-tangle-schema", + "radroots-types", "serde_json", ] diff --git a/sql-core/src/error.rs b/sql-core/src/error.rs @@ -3,12 +3,13 @@ #[cfg(any(feature = "embedded", target_os = "espidf"))] extern crate alloc; +use serde::Serialize; use thiserror::Error; #[cfg(any(feature = "embedded", target_os = "espidf"))] use alloc::string::String; -#[derive(Error, Debug, Clone)] +#[derive(Error, Debug, Clone, Serialize)] pub enum SqlError { #[error("invalid argument: {0}")] InvalidArgument(String), diff --git a/tangle-schema/bindings/ts/src/types.ts b/tangle-schema/bindings/ts/src/types.ts @@ -4,7 +4,7 @@ import type { IResult, IResultList } from "@radroots/types-bindings"; export type ILogErrorCreate = ILogErrorFields; -export type ILogErrorCreateResolve = IResult<string>; +export type ILogErrorCreateResolve = IResult<LogError>; export type ILogErrorDelete = ILogErrorFindOne; @@ -22,11 +22,11 @@ export type ILogErrorFindManyResolve = IResultList<LogError>; export type ILogErrorFindOne = { on: LogErrorQueryBindValues, }; -export type ILogErrorFindOneResolve = IResult<LogError>; +export type ILogErrorFindOneResolve = IResult<LogError | undefined>; export type ILogErrorUpdate = { on: LogErrorQueryBindValues, fields: ILogErrorFieldsPartial, }; -export type ILogErrorUpdateResolve = IResult<string>; +export type ILogErrorUpdateResolve = IResult<LogError>; export type LogError = { id: string, created_at: string, updated_at: string, error: string, message: string, stack_trace: string | null, cause: string | null, app_system: string, app_version: string, nostr_pubkey: string, data: string | null, }; diff --git a/tangle-schema/src/tables/log_error.rs b/tangle-schema/src/tables/log_error.rs @@ -86,7 +86,7 @@ pub struct ILogErrorFieldsFilter { #[cfg_attr(feature = "ts-rs", derive(TS))] #[cfg_attr(feature = "ts-rs", ts(export, export_to = "types.ts"))] -#[derive(Clone, Serialize, Deserialize)] +#[derive(Clone, Deserialize, Serialize)] #[serde(untagged)] pub enum LogErrorQueryBindValues { Id { id: String }, @@ -114,11 +114,11 @@ pub type ILogErrorCreate = ILogErrorFields; export, export_to = "types.ts", rename = "ILogErrorCreateResolve", - type = "IResult<string>" + type = "IResult<LogError>" ) )] pub struct ILogErrorCreateResolveTs; -pub type ILogErrorCreateResolve = IResult<String>; +pub type ILogErrorCreateResolve = IResult<LogError>; #[cfg_attr(feature = "ts-rs", derive(TS))] #[cfg_attr( @@ -138,11 +138,11 @@ pub type ILogErrorFindOne = ILogErrorFindOneArgs; export, export_to = "types.ts", rename = "ILogErrorFindOneResolve", - type = "IResult<LogError>" + type = "IResult<LogError | undefined>" ) )] pub struct ILogErrorFindOneResolveTs; -pub type ILogErrorFindOneResolve = IResult<LogError>; +pub type ILogErrorFindOneResolve = IResult<Option<LogError>>; #[cfg_attr(feature = "ts-rs", derive(TS))] #[cfg_attr( @@ -180,7 +180,7 @@ pub type ILogErrorFindManyResolve = IResultList<LogError>; )] pub struct ILogErrorDeleteTs; -pub type ILogErrorDelete = ILogErrorFields; +pub type ILogErrorDelete = ILogErrorFindOneArgs; #[cfg_attr(feature = "ts-rs", derive(TS))] #[cfg_attr( @@ -214,8 +214,8 @@ pub type ILogErrorUpdate = ILogErrorUpdateArgs; export, export_to = "types.ts", rename = "ILogErrorUpdateResolve", - type = "IResult<string>" + type = "IResult<LogError>" ) )] pub struct ILogErrorUpdateResolveTs; -pub type ILogErrorUpdateResolve = IResult<String>; +pub type ILogErrorUpdateResolve = IResult<LogError>; diff --git a/tangle-sql-wasm/src/lib.rs b/tangle-sql-wasm/src/lib.rs @@ -4,8 +4,8 @@ use wasm_bindgen::prelude::*; use radroots_sql_core::WasmSqlExecutor; use radroots_tangle_schema::log_error::{ - ILogErrorFields, ILogErrorFieldsFilter, ILogErrorFieldsPartial, LogError, - LogErrorQueryBindValues, + ILogErrorDelete, ILogErrorFields, ILogErrorFieldsFilter, ILogErrorFieldsPartial, + ILogErrorFindMany, ILogErrorFindOne, ILogErrorUpdate, LogErrorQueryBindValues, }; use radroots_tangle_sql::{log_error, migrations}; @@ -29,7 +29,8 @@ pub fn tangle_db_log_error_create(opts_json: &str) -> Result<JsValue, JsValue> { let payload = radroots_sql_wasm_core::parse_json::<ILogErrorFields>(opts_json) .map_err(radroots_sql_wasm_core::err_js)?; let exec = WasmSqlExecutor::new(); - let out = log_error::insert(&exec, payload).map_err(radroots_sql_wasm_core::err_js)?; + let out = log_error::create(&exec, &payload) + .map_err(|err| radroots_sql_wasm_core::err_js(err.error))?; value_to_js(out) } @@ -38,8 +39,9 @@ pub fn tangle_db_log_error_find_many(filter_json: &str) -> Result<JsValue, JsVal let filter = parse_optional_json::<ILogErrorFieldsFilter>(filter_json) .map_err(radroots_sql_wasm_core::err_js)?; let exec = WasmSqlExecutor::new(); - let out = - log_error::find_many(&exec, filter.as_ref()).map_err(radroots_sql_wasm_core::err_js)?; + let opts = ILogErrorFindMany { filter }; + let out = log_error::find_many(&exec, &opts) + .map_err(|err| radroots_sql_wasm_core::err_js(err.error))?; value_to_js(out) } @@ -48,8 +50,9 @@ pub fn tangle_db_log_error_find_one(bind_json: &str) -> Result<JsValue, JsValue> let bind = radroots_sql_wasm_core::parse_json::<LogErrorQueryBindValues>(bind_json) .map_err(radroots_sql_wasm_core::err_js)?; let exec = WasmSqlExecutor::new(); - let out: Option<LogError> = - log_error::find_one(&exec, &bind).map_err(radroots_sql_wasm_core::err_js)?; + let opts = ILogErrorFindOne { on: bind }; + let out = log_error::find_one(&exec, &opts) + .map_err(|err| radroots_sql_wasm_core::err_js(err.error))?; value_to_js(out) } @@ -58,8 +61,13 @@ pub fn tangle_db_log_error_update(id: &str, fields_json: &str) -> Result<JsValue let fields = radroots_sql_wasm_core::parse_json::<ILogErrorFieldsPartial>(fields_json) .map_err(radroots_sql_wasm_core::err_js)?; let exec = WasmSqlExecutor::new(); - let outcome = log_error::update(&exec, id, fields).map_err(radroots_sql_wasm_core::err_js)?; - outcome_to_js(outcome) + let opts = ILogErrorUpdate { + on: LogErrorQueryBindValues::Id { id: id.to_owned() }, + fields, + }; + let out = + log_error::update(&exec, &opts).map_err(|err| radroots_sql_wasm_core::err_js(err.error))?; + value_to_js(out) } #[wasm_bindgen(js_name = tangle_db_log_error_delete)] @@ -67,6 +75,8 @@ pub fn tangle_db_log_error_delete(bind_json: &str) -> Result<JsValue, JsValue> { let bind = radroots_sql_wasm_core::parse_json::<LogErrorQueryBindValues>(bind_json) .map_err(radroots_sql_wasm_core::err_js)?; let exec = WasmSqlExecutor::new(); - let outcome = log_error::delete(&exec, &bind).map_err(radroots_sql_wasm_core::err_js)?; - outcome_to_js(outcome) + let opts = ILogErrorDelete { on: bind }; + let out = + log_error::delete(&exec, &opts).map_err(|err| radroots_sql_wasm_core::err_js(err.error))?; + value_to_js(out) } diff --git a/tangle-sql/Cargo.toml b/tangle-sql/Cargo.toml @@ -18,4 +18,5 @@ embedded = ["radroots-sql-core/embedded"] [dependencies] radroots-sql-core = { workspace = true } radroots-tangle-schema = { workspace = true } +radroots-types = { workspace = true } serde_json = { workspace = true } diff --git a/tangle-sql/src/lib.rs b/tangle-sql/src/lib.rs @@ -3,6 +3,7 @@ pub use radroots_sql_core::{ExecOutcome, SqlExecutor}; pub mod migrations; pub mod tables; +use radroots_types::types::IError; pub use tables::log_error; pub struct TangleSql<E: SqlExecutor> { @@ -26,39 +27,38 @@ impl<E: SqlExecutor> TangleSql<E> { crate::migrations::run_all_down(self.executor()) } - pub fn insert_log_error( + pub fn log_error_create( &self, - fields: radroots_tangle_schema::log_error::ILogErrorFields, - ) -> Result<radroots_tangle_schema::log_error::LogError, SqlError> { - tables::log_error::insert(self.executor(), fields) + opts: &radroots_tangle_schema::log_error::ILogErrorCreate, + ) -> Result<radroots_tangle_schema::log_error::ILogErrorCreateResolve, IError<SqlError>> { + tables::log_error::create(self.executor(), opts) } - pub fn find_log_errors( + pub fn log_error_find_many( &self, - filter: Option<&radroots_tangle_schema::log_error::ILogErrorFieldsFilter>, - ) -> Result<Vec<radroots_tangle_schema::log_error::LogError>, SqlError> { - tables::log_error::find_many(self.executor(), filter) + opts: &radroots_tangle_schema::log_error::ILogErrorFindMany, + ) -> Result<radroots_tangle_schema::log_error::ILogErrorFindManyResolve, IError<SqlError>> { + tables::log_error::find_many(self.executor(), opts) } - pub fn find_log_error( + pub fn log_error_find_one( &self, - bind: &radroots_tangle_schema::log_error::LogErrorQueryBindValues, - ) -> Result<Option<radroots_tangle_schema::log_error::LogError>, SqlError> { - tables::log_error::find_one(self.executor(), bind) + opts: &radroots_tangle_schema::log_error::ILogErrorFindOne, + ) -> Result<radroots_tangle_schema::log_error::ILogErrorFindOneResolve, IError<SqlError>> { + tables::log_error::find_one(self.executor(), opts) } - pub fn update_log_error( + pub fn log_error_update( &self, - id: &str, - fields: radroots_tangle_schema::log_error::ILogErrorFieldsPartial, - ) -> Result<ExecOutcome, SqlError> { - tables::log_error::update(self.executor(), id, fields) + opts: &radroots_tangle_schema::log_error::ILogErrorUpdate, + ) -> Result<radroots_tangle_schema::log_error::ILogErrorUpdateResolve, IError<SqlError>> { + tables::log_error::update(self.executor(), opts) } - pub fn delete_log_error( + pub fn log_error_delete( &self, - bind: &radroots_tangle_schema::log_error::LogErrorQueryBindValues, - ) -> Result<ExecOutcome, SqlError> { - tables::log_error::delete(self.executor(), bind) + opts: &radroots_tangle_schema::log_error::ILogErrorDelete, + ) -> Result<radroots_tangle_schema::log_error::ILogErrorDeleteResolve, IError<SqlError>> { + tables::log_error::delete(self.executor(), opts) } } diff --git a/tangle-sql/src/tables/log_error.rs b/tangle-sql/src/tables/log_error.rs @@ -1,16 +1,20 @@ use radroots_sql_core::error::SqlError; -use radroots_sql_core::utils; -use radroots_sql_core::{ExecOutcome, SqlExecutor}; +use radroots_sql_core::{SqlExecutor, utils}; use radroots_tangle_schema::log_error::{ - ILogErrorFields, ILogErrorFieldsFilter, ILogErrorFieldsPartial, LogError, - LogErrorQueryBindValues, + ILogErrorCreate, ILogErrorCreateResolve, ILogErrorDelete, ILogErrorDeleteResolve, + ILogErrorFindMany, ILogErrorFindManyResolve, ILogErrorFindOne, ILogErrorFindOneResolve, + ILogErrorUpdate, ILogErrorUpdateResolve, LogError, LogErrorQueryBindValues, }; +use radroots_types::types::{IError, IResult, IResultList}; use serde_json::Value; const TABLE_NAME: &str = "log_error"; -pub fn insert<E: SqlExecutor>(exec: &E, fields: ILogErrorFields) -> Result<LogError, SqlError> { - let field_map = utils::to_object_map(&fields)?; +pub fn create<E: SqlExecutor>( + exec: &E, + opts: &ILogErrorCreate, +) -> Result<ILogErrorCreateResolve, IError<SqlError>> { + let field_map = utils::to_object_map(opts)?; let id = utils::uuidv4(); let now = utils::time_created_on(); let meta: [(&str, Value); 3] = [ @@ -21,54 +25,90 @@ pub fn insert<E: SqlExecutor>(exec: &E, fields: ILogErrorFields) -> Result<LogEr let (sql, bind_values) = utils::build_insert_query_with_meta(TABLE_NAME, &meta, &field_map); let params_json = utils::to_params_json(bind_values)?; let _ = exec.exec(&sql, &params_json)?; - let log_error = LogError { + let result = LogError { id, created_at: now.clone(), updated_at: now, - error: fields.error, - message: fields.message, - stack_trace: fields.stack_trace, - cause: fields.cause, - app_system: fields.app_system, - app_version: fields.app_version, - nostr_pubkey: fields.nostr_pubkey, - data: fields.data, + error: opts.error.clone(), + message: opts.message.clone(), + stack_trace: opts.stack_trace.clone(), + cause: opts.cause.clone(), + app_system: opts.app_system.clone(), + app_version: opts.app_version.clone(), + nostr_pubkey: opts.nostr_pubkey.clone(), + data: opts.data.clone(), }; - Ok(log_error) + Ok(IResult { result }) } -pub fn find_many<E: SqlExecutor>( +pub fn find_one<E: SqlExecutor>( exec: &E, - filter: Option<&ILogErrorFieldsFilter>, -) -> Result<Vec<LogError>, SqlError> { - let (sql, bind_values) = utils::build_select_query_with_meta(TABLE_NAME, filter); - let params_json = utils::to_params_json(bind_values)?; + opts: &ILogErrorFindOne, +) -> Result<ILogErrorFindOneResolve, IError<SqlError>> { + let (column, value) = match &opts.on { + LogErrorQueryBindValues::Id { id } => ("id", Value::from(id.clone())), + LogErrorQueryBindValues::NostrPubkey { nostr_pubkey } => { + ("nostr_pubkey", Value::from(nostr_pubkey.clone())) + } + }; + let sql = format!("SELECT * FROM {TABLE_NAME} WHERE {column} = ? LIMIT 1;"); + let params_json = utils::to_params_json(vec![value])?; let json = exec.query_raw(&sql, &params_json)?; - let rows: Vec<LogError> = utils::parse_json(&json)?; - Ok(rows) + let mut rows: Vec<LogError> = utils::parse_json(&json)?; + let result = rows.pop(); + Ok(IResult { result }) } -pub fn find_one<E: SqlExecutor>( +pub fn find_many<E: SqlExecutor>( exec: &E, - bind: &LogErrorQueryBindValues, -) -> Result<Option<LogError>, SqlError> { - let (sql, bind_values) = utils::build_select_query_with_meta(TABLE_NAME, Some(bind)); + opts: &ILogErrorFindMany, +) -> Result<ILogErrorFindManyResolve, IError<SqlError>> { + let (sql, bind_values) = utils::build_select_query_with_meta(TABLE_NAME, opts.filter.as_ref()); let params_json = utils::to_params_json(bind_values)?; let json = exec.query_raw(&sql, &params_json)?; + let results: Vec<LogError> = utils::parse_json(&json)?; + Ok(IResultList { results }) +} + +fn select_by_id<E: SqlExecutor>(exec: &E, id: &str) -> Result<LogError, IError<SqlError>> { + let params_json = utils::to_params_json(vec![Value::from(id.to_owned())])?; + let sql = format!("SELECT * FROM {TABLE_NAME} WHERE id = ?;"); + let json = exec.query_raw(&sql, &params_json)?; let mut rows: Vec<LogError> = utils::parse_json(&json)?; - Ok(rows.pop()) + rows.pop() + .ok_or_else(|| IError::from(SqlError::NotFound(id.to_owned()))) +} + +fn resolve_on_bind<E: SqlExecutor>( + exec: &E, + on: &LogErrorQueryBindValues, +) -> Result<(&'static str, Value, String), IError<SqlError>> { + match on { + LogErrorQueryBindValues::Id { id } => Ok(("id", Value::from(id.clone()), id.clone())), + LogErrorQueryBindValues::NostrPubkey { nostr_pubkey } => { + let args = ILogErrorFindOne { + on: LogErrorQueryBindValues::NostrPubkey { + nostr_pubkey: nostr_pubkey.clone(), + }, + }; + let found = find_one(exec, &args)?; + let model = found + .result + .ok_or_else(|| IError::from(SqlError::NotFound(nostr_pubkey.clone())))?; + Ok(("nostr_pubkey", Value::from(nostr_pubkey.clone()), model.id)) + } + } } pub fn update<E: SqlExecutor>( exec: &E, - id: &str, - fields: ILogErrorFieldsPartial, -) -> Result<ExecOutcome, SqlError> { - let mut updates = utils::to_partial_object_map(fields)?; + opts: &ILogErrorUpdate, +) -> Result<ILogErrorUpdateResolve, IError<SqlError>> { + let mut updates = utils::to_partial_object_map(&opts.fields)?; if updates.is_empty() { - return Err(SqlError::InvalidArgument(String::from( + return Err(IError::from(SqlError::InvalidArgument(String::from( "no fields to update", - ))); + )))); } updates.insert( String::from("updated_at"), @@ -80,26 +120,31 @@ pub fn update<E: SqlExecutor>( set_parts.push(format!("{column} = ?")); bind_values.push(utils::to_db_bind_value(&value)); } - bind_values.push(Value::from(String::from(id))); + let (where_column, where_value, id_for_lookup) = resolve_on_bind(exec, &opts.on)?; + bind_values.push(where_value); let sql = format!( - "UPDATE {TABLE_NAME} SET {} WHERE id = ?;", + "UPDATE {TABLE_NAME} SET {} WHERE {where_column} = ?;", set_parts.join(", ") ); let params_json = utils::to_params_json(bind_values)?; - exec.exec(&sql, &params_json) + let outcome = exec.exec(&sql, &params_json)?; + if outcome.changes == 0 { + return Err(IError::from(SqlError::NotFound(id_for_lookup.clone()))); + } + let updated = select_by_id(exec, &id_for_lookup)?; + Ok(IResult { result: updated }) } pub fn delete<E: SqlExecutor>( exec: &E, - bind: &LogErrorQueryBindValues, -) -> Result<ExecOutcome, SqlError> { - let (where_clause, bind_values) = utils::build_where_clause_eq(bind)?; - if where_clause.is_empty() { - return Err(SqlError::InvalidArgument(String::from( - "delete requires at least one filter field", - ))); + opts: &ILogErrorDelete, +) -> Result<ILogErrorDeleteResolve, IError<SqlError>> { + let (_, _, id) = resolve_on_bind(exec, &opts.on)?; + let params_json = utils::to_params_json(vec![Value::from(id.clone())])?; + let sql = format!("DELETE FROM {TABLE_NAME} WHERE id = ?;"); + let outcome = exec.exec(&sql, &params_json)?; + if outcome.changes == 0 { + return Err(IError::from(SqlError::NotFound(id))); } - let sql = format!("DELETE FROM {TABLE_NAME}{where_clause};"); - let params_json = utils::to_params_json(bind_values)?; - exec.exec(&sql, &params_json) + Ok(IResult { result: id }) } diff --git a/types/bindings/ts/src/types.ts b/types/bindings/ts/src/types.ts @@ -1,5 +1,7 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +export type IError<T> = { error: T, }; + export type IResult<T> = { result: T, }; export type IResultList<T> = { results: Array<T>, }; diff --git a/types/src/types.rs b/types/src/types.rs @@ -5,6 +5,13 @@ use ts_rs::TS; #[cfg_attr(feature = "ts-rs", derive(TS))] #[cfg_attr(feature = "ts-rs", ts(export, export_to = "types.ts"))] #[derive(Serialize)] +pub struct IError<T> { + pub error: T, +} + +#[cfg_attr(feature = "ts-rs", derive(TS))] +#[cfg_attr(feature = "ts-rs", ts(export, export_to = "types.ts"))] +#[derive(Serialize)] pub struct IResult<T> { pub result: T, } @@ -22,3 +29,9 @@ pub struct IResultList<T> { pub struct IResultPass { pub pass: bool, } + +impl<T> From<T> for IError<T> { + fn from(error: T) -> Self { + Self { error } + } +}