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:
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, ¶ms_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, ¶ms_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, ¶ms_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, ¶ms_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, ¶ms_json)
+ let outcome = exec.exec(&sql, ¶ms_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, ¶ms_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, ¶ms_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 }
+ }
+}