lib

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

region_scripted_paths.rs (26492B)


      1 use std::collections::VecDeque;
      2 use std::sync::Mutex;
      3 
      4 use radroots_replica_db::{ExecOutcome, ReplicaSql, SqlError, SqlExecutor};
      5 use radroots_replica_db_schema::farm::{
      6     IFarmCreate, IFarmDelete, IFarmFindMany, IFarmFindOne, IFarmUpdate,
      7 };
      8 use radroots_replica_db_schema::farm_gcs_location::{
      9     IFarmGcsLocationCreate, IFarmGcsLocationDelete, IFarmGcsLocationFindMany,
     10     IFarmGcsLocationFindOne, IFarmGcsLocationUpdate,
     11 };
     12 use radroots_replica_db_schema::farm_member::{
     13     IFarmMemberCreate, IFarmMemberDelete, IFarmMemberFindMany, IFarmMemberFindOne,
     14     IFarmMemberUpdate,
     15 };
     16 use radroots_replica_db_schema::farm_member_claim::{
     17     IFarmMemberClaimCreate, IFarmMemberClaimDelete, IFarmMemberClaimFindMany,
     18     IFarmMemberClaimFindOne, IFarmMemberClaimUpdate,
     19 };
     20 use radroots_replica_db_schema::farm_tag::{
     21     IFarmTagCreate, IFarmTagDelete, IFarmTagFindMany, IFarmTagFindOne, IFarmTagUpdate,
     22 };
     23 use radroots_replica_db_schema::gcs_location::{
     24     GcsLocationFarmArgs, GcsLocationFindManyRel, GcsLocationPlotArgs, GcsLocationTradeProductArgs,
     25     IGcsLocationCreate, IGcsLocationDelete, IGcsLocationFindMany, IGcsLocationFindOne,
     26     IGcsLocationFindOneRelArgs, IGcsLocationUpdate,
     27 };
     28 use radroots_replica_db_schema::log_error::{
     29     ILogErrorCreate, ILogErrorDelete, ILogErrorFindMany, ILogErrorFindOne, ILogErrorUpdate,
     30 };
     31 use radroots_replica_db_schema::media_image::{
     32     IMediaImageCreate, IMediaImageDelete, IMediaImageFindMany, IMediaImageFindOne,
     33     IMediaImageFindOneRelArgs, IMediaImageUpdate, MediaImageFindManyRel,
     34     MediaImageTradeProductArgs,
     35 };
     36 use radroots_replica_db_schema::nostr_event_head::{
     37     INostrEventHeadCreate, INostrEventHeadDelete, INostrEventHeadFindMany, INostrEventHeadFindOne,
     38     INostrEventHeadUpdate,
     39 };
     40 use radroots_replica_db_schema::nostr_profile::{
     41     INostrProfileCreate, INostrProfileDelete, INostrProfileFindMany, INostrProfileFindOne,
     42     INostrProfileFindOneRelArgs, INostrProfileUpdate, NostrProfileFindManyRel,
     43     NostrProfileRelayArgs,
     44 };
     45 use radroots_replica_db_schema::nostr_relay::{
     46     INostrRelayCreate, INostrRelayDelete, INostrRelayFindMany, INostrRelayFindOne,
     47     INostrRelayFindOneRelArgs, INostrRelayUpdate, NostrRelayFindManyRel, NostrRelayProfileArgs,
     48 };
     49 use radroots_replica_db_schema::plot::{
     50     IPlotCreate, IPlotDelete, IPlotFindMany, IPlotFindOne, IPlotUpdate,
     51 };
     52 use radroots_replica_db_schema::plot_gcs_location::{
     53     IPlotGcsLocationCreate, IPlotGcsLocationDelete, IPlotGcsLocationFindMany,
     54     IPlotGcsLocationFindOne, IPlotGcsLocationUpdate,
     55 };
     56 use radroots_replica_db_schema::plot_tag::{
     57     IPlotTagCreate, IPlotTagDelete, IPlotTagFindMany, IPlotTagFindOne, IPlotTagUpdate,
     58 };
     59 use radroots_replica_db_schema::trade_product::{
     60     ITradeProductCreate, ITradeProductFindMany, ITradeProductFindOne, ITradeProductUpdate,
     61 };
     62 use radroots_types::types::IError;
     63 use serde::de::DeserializeOwned;
     64 use serde_json::json;
     65 
     66 struct ScriptedExecutor {
     67     exec_results: Mutex<VecDeque<Result<ExecOutcome, SqlError>>>,
     68     query_results: Mutex<VecDeque<Result<String, SqlError>>>,
     69     begin_results: Mutex<VecDeque<Result<(), SqlError>>>,
     70     commit_results: Mutex<VecDeque<Result<(), SqlError>>>,
     71     rollback_results: Mutex<VecDeque<Result<(), SqlError>>>,
     72 }
     73 
     74 impl ScriptedExecutor {
     75     fn new(
     76         exec_results: Vec<Result<ExecOutcome, SqlError>>,
     77         query_results: Vec<Result<String, SqlError>>,
     78     ) -> Self {
     79         Self {
     80             exec_results: Mutex::new(VecDeque::from(exec_results)),
     81             query_results: Mutex::new(VecDeque::from(query_results)),
     82             begin_results: Mutex::new(VecDeque::new()),
     83             commit_results: Mutex::new(VecDeque::new()),
     84             rollback_results: Mutex::new(VecDeque::new()),
     85         }
     86     }
     87 }
     88 
     89 impl SqlExecutor for ScriptedExecutor {
     90     fn exec(&self, _sql: &str, _params_json: &str) -> Result<ExecOutcome, SqlError> {
     91         if let Some(result) = self
     92             .exec_results
     93             .lock()
     94             .expect("lock exec queue")
     95             .pop_front()
     96         {
     97             result
     98         } else {
     99             Ok(ExecOutcome {
    100                 changes: 1,
    101                 last_insert_id: 1,
    102             })
    103         }
    104     }
    105 
    106     fn query_raw(&self, _sql: &str, _params_json: &str) -> Result<String, SqlError> {
    107         if let Some(result) = self
    108             .query_results
    109             .lock()
    110             .expect("lock query queue")
    111             .pop_front()
    112         {
    113             result
    114         } else {
    115             Ok(String::from("[]"))
    116         }
    117     }
    118 
    119     fn begin(&self) -> Result<(), SqlError> {
    120         if let Some(result) = self
    121             .begin_results
    122             .lock()
    123             .expect("lock begin queue")
    124             .pop_front()
    125         {
    126             result
    127         } else {
    128             Ok(())
    129         }
    130     }
    131 
    132     fn commit(&self) -> Result<(), SqlError> {
    133         if let Some(result) = self
    134             .commit_results
    135             .lock()
    136             .expect("lock commit queue")
    137             .pop_front()
    138         {
    139             result
    140         } else {
    141             Ok(())
    142         }
    143     }
    144 
    145     fn rollback(&self) -> Result<(), SqlError> {
    146         if let Some(result) = self
    147             .rollback_results
    148             .lock()
    149             .expect("lock rollback queue")
    150             .pop_front()
    151         {
    152             result
    153         } else {
    154             Ok(())
    155         }
    156     }
    157 }
    158 
    159 fn parse_json<T: DeserializeOwned>(value: serde_json::Value) -> T {
    160     serde_json::from_value(value).expect("valid test payload")
    161 }
    162 
    163 fn hex64(ch: char) -> String {
    164     std::iter::repeat_n(ch, 64).collect()
    165 }
    166 
    167 fn err_query() -> Result<String, SqlError> {
    168     Err(SqlError::InvalidQuery(String::from("forced query error")))
    169 }
    170 
    171 fn bad_json() -> Result<String, SqlError> {
    172     Ok(String::from("{"))
    173 }
    174 
    175 fn ok_rows() -> Result<String, SqlError> {
    176     Ok(String::from("[]"))
    177 }
    178 
    179 fn ok_exec() -> Result<ExecOutcome, SqlError> {
    180     Ok(ExecOutcome {
    181         changes: 1,
    182         last_insert_id: 1,
    183     })
    184 }
    185 
    186 fn db_with_scripts(
    187     exec_results: Vec<Result<ExecOutcome, SqlError>>,
    188     query_results: Vec<Result<String, SqlError>>,
    189 ) -> ReplicaSql<ScriptedExecutor> {
    190     ReplicaSql::new(ScriptedExecutor::new(exec_results, query_results))
    191 }
    192 
    193 fn assert_ierror_code<T>(result: Result<T, IError<SqlError>>, code: &str) {
    194     let err = match result {
    195         Ok(_) => panic!("expected ierror"),
    196         Err(err) => err,
    197     };
    198     assert_eq!(err.err.code(), code);
    199 }
    200 
    201 macro_rules! assert_secondary_model_paths {
    202     (
    203         $test_name:ident,
    204         $create_ty:ty, $create_json:expr, $create_call:ident,
    205         $find_many_ty:ty, $find_many_json:expr, $find_many_call:ident,
    206         $find_one_ty:ty, $find_one_json:expr, $find_one_call:ident,
    207         $update_ty:ty, $update_id_json:expr, $update_lookup_json:expr, $update_call:ident,
    208         $delete_ty:ty, $delete_lookup_json:expr, $delete_call:ident
    209     ) => {
    210         #[test]
    211         fn $test_name() {
    212             let create_opts: $create_ty = parse_json($create_json);
    213 
    214             let db = db_with_scripts(vec![ok_exec()], vec![err_query()]);
    215             assert_ierror_code(db.$create_call(&create_opts), "ERR_INVALID_QUERY");
    216 
    217             let db = db_with_scripts(vec![ok_exec()], vec![ok_rows()]);
    218             assert_ierror_code(db.$create_call(&create_opts), "ERR_NOT_FOUND");
    219 
    220             let find_many_opts: $find_many_ty = parse_json($find_many_json);
    221             let db = db_with_scripts(vec![], vec![bad_json()]);
    222             assert_ierror_code(db.$find_many_call(&find_many_opts), "ERR_SERIALIZATION");
    223 
    224             let find_one_opts: $find_one_ty = parse_json($find_one_json);
    225             let db = db_with_scripts(vec![], vec![bad_json()]);
    226             assert_ierror_code(db.$find_one_call(&find_one_opts), "ERR_SERIALIZATION");
    227 
    228             let update_id_opts: $update_ty = parse_json($update_id_json);
    229             let db = db_with_scripts(vec![ok_exec()], vec![err_query()]);
    230             assert_ierror_code(db.$update_call(&update_id_opts), "ERR_INVALID_QUERY");
    231 
    232             let db = db_with_scripts(vec![ok_exec()], vec![bad_json()]);
    233             assert_ierror_code(db.$update_call(&update_id_opts), "ERR_SERIALIZATION");
    234 
    235             let update_lookup_opts: $update_ty = parse_json($update_lookup_json);
    236             let db = db_with_scripts(vec![], vec![err_query()]);
    237             assert_ierror_code(db.$update_call(&update_lookup_opts), "ERR_INVALID_QUERY");
    238 
    239             let delete_lookup_opts: $delete_ty = parse_json($delete_lookup_json);
    240             let db = db_with_scripts(vec![], vec![err_query()]);
    241             assert_ierror_code(db.$delete_call(&delete_lookup_opts), "ERR_INVALID_QUERY");
    242         }
    243     };
    244 }
    245 
    246 macro_rules! assert_rel_model_paths {
    247     (
    248         $test_name:ident,
    249         $create_ty:ty, $create_json:expr, $create_call:ident,
    250         $find_many_ty:ty, $find_many_filter_json:expr, $find_many_rel_expr:expr, $find_many_call:ident,
    251         $find_one_ty:ty, $find_one_on_json:expr, $find_one_rel_expr:expr, $find_one_call:ident,
    252         $update_ty:ty, $update_id_json:expr, $update_lookup_json:expr, $update_call:ident,
    253         $delete_ty:ty, $delete_lookup_json:expr, $delete_rel_expr:expr, $delete_call:ident
    254     ) => {
    255         #[test]
    256         fn $test_name() {
    257             let create_opts: $create_ty = parse_json($create_json);
    258 
    259             let db = db_with_scripts(vec![ok_exec()], vec![err_query()]);
    260             assert_ierror_code(db.$create_call(&create_opts), "ERR_INVALID_QUERY");
    261 
    262             let db = db_with_scripts(vec![ok_exec()], vec![ok_rows()]);
    263             assert_ierror_code(db.$create_call(&create_opts), "ERR_NOT_FOUND");
    264 
    265             let find_many_filter_opts: $find_many_ty = parse_json($find_many_filter_json);
    266             let db = db_with_scripts(vec![], vec![bad_json()]);
    267             assert_ierror_code(
    268                 db.$find_many_call(&find_many_filter_opts),
    269                 "ERR_SERIALIZATION",
    270             );
    271 
    272             let find_many_rel_opts: $find_many_ty = $find_many_rel_expr;
    273             let db = db_with_scripts(vec![], vec![bad_json()]);
    274             assert_ierror_code(db.$find_many_call(&find_many_rel_opts), "ERR_SERIALIZATION");
    275 
    276             let find_many_rel_opts: $find_many_ty = $find_many_rel_expr;
    277             let db = db_with_scripts(vec![], vec![err_query()]);
    278             assert_ierror_code(db.$find_many_call(&find_many_rel_opts), "ERR_INVALID_QUERY");
    279 
    280             let find_one_on_opts: $find_one_ty = parse_json($find_one_on_json);
    281             let db = db_with_scripts(vec![], vec![bad_json()]);
    282             assert_ierror_code(db.$find_one_call(&find_one_on_opts), "ERR_SERIALIZATION");
    283 
    284             let find_one_rel_opts: $find_one_ty = $find_one_rel_expr;
    285             let db = db_with_scripts(vec![], vec![bad_json()]);
    286             assert_ierror_code(db.$find_one_call(&find_one_rel_opts), "ERR_SERIALIZATION");
    287 
    288             let update_id_opts: $update_ty = parse_json($update_id_json);
    289             let db = db_with_scripts(vec![ok_exec()], vec![err_query()]);
    290             assert_ierror_code(db.$update_call(&update_id_opts), "ERR_INVALID_QUERY");
    291 
    292             let db = db_with_scripts(vec![ok_exec()], vec![bad_json()]);
    293             assert_ierror_code(db.$update_call(&update_id_opts), "ERR_SERIALIZATION");
    294 
    295             let update_lookup_opts: $update_ty = parse_json($update_lookup_json);
    296             let db = db_with_scripts(vec![], vec![err_query()]);
    297             assert_ierror_code(db.$update_call(&update_lookup_opts), "ERR_INVALID_QUERY");
    298 
    299             let delete_lookup_opts: $delete_ty = parse_json($delete_lookup_json);
    300             let db = db_with_scripts(vec![], vec![err_query()]);
    301             assert_ierror_code(db.$delete_call(&delete_lookup_opts), "ERR_INVALID_QUERY");
    302 
    303             let delete_rel_opts: $delete_ty = $delete_rel_expr;
    304             let db = db_with_scripts(vec![], vec![err_query()]);
    305             assert_ierror_code(db.$delete_call(&delete_rel_opts), "ERR_INVALID_QUERY");
    306         }
    307     };
    308 }
    309 
    310 macro_rules! assert_trade_product_paths {
    311     (
    312         $test_name:ident,
    313         $create_ty:ty, $create_json:expr, $create_call:ident,
    314         $find_many_ty:ty, $find_many_json:expr, $find_many_call:ident,
    315         $find_one_ty:ty, $find_one_json:expr, $find_one_call:ident,
    316         $update_ty:ty, $update_json:expr, $update_call:ident
    317     ) => {
    318         #[test]
    319         fn $test_name() {
    320             let create_opts: $create_ty = parse_json($create_json);
    321 
    322             let db = db_with_scripts(vec![ok_exec()], vec![err_query()]);
    323             assert_ierror_code(db.$create_call(&create_opts), "ERR_INVALID_QUERY");
    324 
    325             let db = db_with_scripts(vec![ok_exec()], vec![ok_rows()]);
    326             assert_ierror_code(db.$create_call(&create_opts), "ERR_NOT_FOUND");
    327 
    328             let find_many_opts: $find_many_ty = parse_json($find_many_json);
    329             let db = db_with_scripts(vec![], vec![bad_json()]);
    330             assert_ierror_code(db.$find_many_call(&find_many_opts), "ERR_SERIALIZATION");
    331 
    332             let find_one_opts: $find_one_ty = parse_json($find_one_json);
    333             let db = db_with_scripts(vec![], vec![bad_json()]);
    334             assert_ierror_code(db.$find_one_call(&find_one_opts), "ERR_SERIALIZATION");
    335 
    336             let update_opts: $update_ty = parse_json($update_json);
    337             let db = db_with_scripts(vec![ok_exec()], vec![err_query()]);
    338             assert_ierror_code(db.$update_call(&update_opts), "ERR_INVALID_QUERY");
    339 
    340             let db = db_with_scripts(vec![ok_exec()], vec![bad_json()]);
    341             assert_ierror_code(db.$update_call(&update_opts), "ERR_SERIALIZATION");
    342         }
    343     };
    344 }
    345 
    346 assert_secondary_model_paths!(
    347     farm_scripted_region_paths,
    348     IFarmCreate,
    349     json!({ "d_tag": "farm-a", "pubkey": hex64('a'), "name": "farm a" }),
    350     farm_create,
    351     IFarmFindMany,
    352     json!({ "filter": { "id": "id-1" } }),
    353     farm_find_many,
    354     IFarmFindOne,
    355     json!({ "on": { "id": "id-1" } }),
    356     farm_find_one,
    357     IFarmUpdate,
    358     json!({ "on": { "id": "id-1" }, "fields": { "name": "farm z" } }),
    359     json!({ "on": { "d_tag": "farm-a" }, "fields": { "name": "farm y" } }),
    360     farm_update,
    361     IFarmDelete,
    362     json!({ "on": { "d_tag": "farm-a" } }),
    363     farm_delete
    364 );
    365 
    366 assert_secondary_model_paths!(
    367     plot_scripted_region_paths,
    368     IPlotCreate,
    369     json!({ "d_tag": "plot-a", "farm_id": "farm-1", "name": "plot a" }),
    370     plot_create,
    371     IPlotFindMany,
    372     json!({ "filter": { "id": "id-1" } }),
    373     plot_find_many,
    374     IPlotFindOne,
    375     json!({ "on": { "id": "id-1" } }),
    376     plot_find_one,
    377     IPlotUpdate,
    378     json!({ "on": { "id": "id-1" }, "fields": { "name": "plot z" } }),
    379     json!({ "on": { "d_tag": "plot-a" }, "fields": { "name": "plot y" } }),
    380     plot_update,
    381     IPlotDelete,
    382     json!({ "on": { "d_tag": "plot-a" } }),
    383     plot_delete
    384 );
    385 
    386 assert_secondary_model_paths!(
    387     farm_gcs_location_scripted_region_paths,
    388     IFarmGcsLocationCreate,
    389     json!({ "farm_id": "farm-1", "gcs_location_id": "gcs-1", "role": "primary" }),
    390     farm_gcs_location_create,
    391     IFarmGcsLocationFindMany,
    392     json!({ "filter": { "id": "id-1" } }),
    393     farm_gcs_location_find_many,
    394     IFarmGcsLocationFindOne,
    395     json!({ "on": { "id": "id-1" } }),
    396     farm_gcs_location_find_one,
    397     IFarmGcsLocationUpdate,
    398     json!({ "on": { "id": "id-1" }, "fields": { "role": "z" } }),
    399     json!({ "on": { "farm_id": "farm-1" }, "fields": { "role": "y" } }),
    400     farm_gcs_location_update,
    401     IFarmGcsLocationDelete,
    402     json!({ "on": { "farm_id": "farm-1" } }),
    403     farm_gcs_location_delete
    404 );
    405 
    406 assert_secondary_model_paths!(
    407     plot_gcs_location_scripted_region_paths,
    408     IPlotGcsLocationCreate,
    409     json!({ "plot_id": "plot-1", "gcs_location_id": "gcs-1", "role": "primary" }),
    410     plot_gcs_location_create,
    411     IPlotGcsLocationFindMany,
    412     json!({ "filter": { "id": "id-1" } }),
    413     plot_gcs_location_find_many,
    414     IPlotGcsLocationFindOne,
    415     json!({ "on": { "id": "id-1" } }),
    416     plot_gcs_location_find_one,
    417     IPlotGcsLocationUpdate,
    418     json!({ "on": { "id": "id-1" }, "fields": { "role": "z" } }),
    419     json!({ "on": { "plot_id": "plot-1" }, "fields": { "role": "y" } }),
    420     plot_gcs_location_update,
    421     IPlotGcsLocationDelete,
    422     json!({ "on": { "plot_id": "plot-1" } }),
    423     plot_gcs_location_delete
    424 );
    425 
    426 assert_secondary_model_paths!(
    427     farm_tag_scripted_region_paths,
    428     IFarmTagCreate,
    429     json!({ "farm_id": "farm-1", "tag": "organic" }),
    430     farm_tag_create,
    431     IFarmTagFindMany,
    432     json!({ "filter": { "id": "id-1" } }),
    433     farm_tag_find_many,
    434     IFarmTagFindOne,
    435     json!({ "on": { "id": "id-1" } }),
    436     farm_tag_find_one,
    437     IFarmTagUpdate,
    438     json!({ "on": { "id": "id-1" }, "fields": { "tag": "z" } }),
    439     json!({ "on": { "farm_id": "farm-1" }, "fields": { "tag": "y" } }),
    440     farm_tag_update,
    441     IFarmTagDelete,
    442     json!({ "on": { "farm_id": "farm-1" } }),
    443     farm_tag_delete
    444 );
    445 
    446 assert_secondary_model_paths!(
    447     plot_tag_scripted_region_paths,
    448     IPlotTagCreate,
    449     json!({ "plot_id": "plot-1", "tag": "north" }),
    450     plot_tag_create,
    451     IPlotTagFindMany,
    452     json!({ "filter": { "id": "id-1" } }),
    453     plot_tag_find_many,
    454     IPlotTagFindOne,
    455     json!({ "on": { "id": "id-1" } }),
    456     plot_tag_find_one,
    457     IPlotTagUpdate,
    458     json!({ "on": { "id": "id-1" }, "fields": { "tag": "z" } }),
    459     json!({ "on": { "plot_id": "plot-1" }, "fields": { "tag": "y" } }),
    460     plot_tag_update,
    461     IPlotTagDelete,
    462     json!({ "on": { "plot_id": "plot-1" } }),
    463     plot_tag_delete
    464 );
    465 
    466 assert_secondary_model_paths!(
    467     farm_member_scripted_region_paths,
    468     IFarmMemberCreate,
    469     json!({ "farm_id": "farm-1", "member_pubkey": hex64('b'), "role": "owner" }),
    470     farm_member_create,
    471     IFarmMemberFindMany,
    472     json!({ "filter": { "id": "id-1" } }),
    473     farm_member_find_many,
    474     IFarmMemberFindOne,
    475     json!({ "on": { "id": "id-1" } }),
    476     farm_member_find_one,
    477     IFarmMemberUpdate,
    478     json!({ "on": { "id": "id-1" }, "fields": { "role": "z" } }),
    479     json!({ "on": { "member_pubkey": hex64('b') }, "fields": { "role": "y" } }),
    480     farm_member_update,
    481     IFarmMemberDelete,
    482     json!({ "on": { "member_pubkey": hex64('b') } }),
    483     farm_member_delete
    484 );
    485 
    486 assert_secondary_model_paths!(
    487     farm_member_claim_scripted_region_paths,
    488     IFarmMemberClaimCreate,
    489     json!({ "member_pubkey": hex64('b'), "farm_pubkey": hex64('a') }),
    490     farm_member_claim_create,
    491     IFarmMemberClaimFindMany,
    492     json!({ "filter": { "id": "id-1" } }),
    493     farm_member_claim_find_many,
    494     IFarmMemberClaimFindOne,
    495     json!({ "on": { "id": "id-1" } }),
    496     farm_member_claim_find_one,
    497     IFarmMemberClaimUpdate,
    498     json!({ "on": { "id": "id-1" }, "fields": { "farm_pubkey": hex64('c') } }),
    499     json!({ "on": { "member_pubkey": hex64('b') }, "fields": { "farm_pubkey": hex64('d') } }),
    500     farm_member_claim_update,
    501     IFarmMemberClaimDelete,
    502     json!({ "on": { "member_pubkey": hex64('b') } }),
    503     farm_member_claim_delete
    504 );
    505 
    506 assert_secondary_model_paths!(
    507     log_error_scripted_region_paths,
    508     ILogErrorCreate,
    509     json!({
    510         "error": "panic",
    511         "message": "boom",
    512         "app_system": "studio",
    513         "app_version": "1.0.0",
    514         "nostr_pubkey": hex64('c')
    515     }),
    516     log_error_create,
    517     ILogErrorFindMany,
    518     json!({ "filter": { "id": "id-1" } }),
    519     log_error_find_many,
    520     ILogErrorFindOne,
    521     json!({ "on": { "id": "id-1" } }),
    522     log_error_find_one,
    523     ILogErrorUpdate,
    524     json!({ "on": { "id": "id-1" }, "fields": { "message": "z" } }),
    525     json!({ "on": { "nostr_pubkey": hex64('c') }, "fields": { "message": "y" } }),
    526     log_error_update,
    527     ILogErrorDelete,
    528     json!({ "on": { "nostr_pubkey": hex64('c') } }),
    529     log_error_delete
    530 );
    531 
    532 assert_secondary_model_paths!(
    533     nostr_event_head_scripted_region_paths,
    534     INostrEventHeadCreate,
    535     json!({
    536         "key": "state-a",
    537         "kind": 30023,
    538         "pubkey": hex64('d'),
    539         "d_tag": "listing-a",
    540         "last_event_id": hex64('e'),
    541         "last_created_at": 1,
    542         "content_hash": "hash-a"
    543     }),
    544     nostr_event_head_create,
    545     INostrEventHeadFindMany,
    546     json!({ "filter": { "id": "id-1" } }),
    547     nostr_event_head_find_many,
    548     INostrEventHeadFindOne,
    549     json!({ "on": { "id": "id-1" } }),
    550     nostr_event_head_find_one,
    551     INostrEventHeadUpdate,
    552     json!({ "on": { "id": "id-1" }, "fields": { "content_hash": "hash-z" } }),
    553     json!({ "on": { "key": "state-a" }, "fields": { "content_hash": "hash-y" } }),
    554     nostr_event_head_update,
    555     INostrEventHeadDelete,
    556     json!({ "on": { "key": "state-a" } }),
    557     nostr_event_head_delete
    558 );
    559 
    560 assert_rel_model_paths!(
    561     gcs_location_scripted_region_paths,
    562     IGcsLocationCreate,
    563     json!({
    564         "d_tag": "gcs-a",
    565         "lat": 59.33,
    566         "lng": 18.06,
    567         "geohash": "u6sce4f",
    568         "point": "POINT(18.06 59.33)",
    569         "polygon": "POLYGON((18.06 59.33,18.07 59.33,18.07 59.34,18.06 59.34,18.06 59.33))"
    570     }),
    571     gcs_location_create,
    572     IGcsLocationFindMany,
    573     json!({ "filter": { "id": "id-1" } }),
    574     IGcsLocationFindMany::Rel {
    575         rel: GcsLocationFindManyRel::OnFarm(GcsLocationFarmArgs {
    576             id: String::from("farm-1")
    577         })
    578     },
    579     gcs_location_find_many,
    580     IGcsLocationFindOne,
    581     json!({ "on": { "id": "id-1" } }),
    582     IGcsLocationFindOne::Rel(IGcsLocationFindOneRelArgs {
    583         rel: GcsLocationFindManyRel::OffTradeProduct(GcsLocationTradeProductArgs {
    584             id: String::from("tp-1")
    585         })
    586     }),
    587     gcs_location_find_one,
    588     IGcsLocationUpdate,
    589     json!({ "on": { "id": "id-1" }, "fields": { "label": "z" } }),
    590     json!({ "on": { "d_tag": "gcs-a" }, "fields": { "label": "y" } }),
    591     gcs_location_update,
    592     IGcsLocationDelete,
    593     json!({ "on": { "d_tag": "gcs-a" } }),
    594     IGcsLocationDelete::Rel(IGcsLocationFindOneRelArgs {
    595         rel: GcsLocationFindManyRel::OnPlot(GcsLocationPlotArgs {
    596             id: String::from("plot-1")
    597         })
    598     }),
    599     gcs_location_delete
    600 );
    601 
    602 assert_rel_model_paths!(
    603     media_image_scripted_region_paths,
    604     IMediaImageCreate,
    605     json!({
    606         "file_path": "/img/a.jpg",
    607         "mime_type": "image/jpeg",
    608         "res_base": "https://cdn.example.com",
    609         "res_path": "img/a.jpg"
    610     }),
    611     media_image_create,
    612     IMediaImageFindMany,
    613     json!({ "filter": { "id": "id-1" } }),
    614     IMediaImageFindMany::Rel {
    615         rel: MediaImageFindManyRel::OnTradeProduct(MediaImageTradeProductArgs {
    616             id: String::from("tp-1")
    617         })
    618     },
    619     media_image_find_many,
    620     IMediaImageFindOne,
    621     json!({ "on": { "id": "id-1" } }),
    622     IMediaImageFindOne::Rel(IMediaImageFindOneRelArgs {
    623         rel: MediaImageFindManyRel::OffTradeProduct(MediaImageTradeProductArgs {
    624             id: String::from("tp-1")
    625         })
    626     }),
    627     media_image_find_one,
    628     IMediaImageUpdate,
    629     json!({ "on": { "id": "id-1" }, "fields": { "label": "z" } }),
    630     json!({ "on": { "file_path": "/img/a.jpg" }, "fields": { "label": "y" } }),
    631     media_image_update,
    632     IMediaImageDelete,
    633     json!({ "on": { "file_path": "/img/a.jpg" } }),
    634     IMediaImageDelete::Rel(IMediaImageFindOneRelArgs {
    635         rel: MediaImageFindManyRel::OnTradeProduct(MediaImageTradeProductArgs {
    636             id: String::from("tp-1")
    637         })
    638     }),
    639     media_image_delete
    640 );
    641 
    642 assert_rel_model_paths!(
    643     nostr_profile_scripted_region_paths,
    644     INostrProfileCreate,
    645     json!({ "public_key": hex64('d'), "profile_type": "farm", "name": "profile a" }),
    646     nostr_profile_create,
    647     INostrProfileFindMany,
    648     json!({ "filter": { "id": "id-1" } }),
    649     INostrProfileFindMany::Rel {
    650         rel: NostrProfileFindManyRel::OnRelay(NostrProfileRelayArgs {
    651             id: String::from("relay-1")
    652         })
    653     },
    654     nostr_profile_find_many,
    655     INostrProfileFindOne,
    656     json!({ "on": { "id": "id-1" } }),
    657     INostrProfileFindOne::Rel(INostrProfileFindOneRelArgs {
    658         rel: NostrProfileFindManyRel::OffRelay(NostrProfileRelayArgs {
    659             id: String::from("relay-1")
    660         })
    661     }),
    662     nostr_profile_find_one,
    663     INostrProfileUpdate,
    664     json!({ "on": { "id": "id-1" }, "fields": { "name": "z" } }),
    665     json!({ "on": { "public_key": hex64('d') }, "fields": { "name": "y" } }),
    666     nostr_profile_update,
    667     INostrProfileDelete,
    668     json!({ "on": { "public_key": hex64('d') } }),
    669     INostrProfileDelete::Rel(INostrProfileFindOneRelArgs {
    670         rel: NostrProfileFindManyRel::OnRelay(NostrProfileRelayArgs {
    671             id: String::from("relay-1")
    672         })
    673     }),
    674     nostr_profile_delete
    675 );
    676 
    677 assert_rel_model_paths!(
    678     nostr_relay_scripted_region_paths,
    679     INostrRelayCreate,
    680     json!({ "url": "wss://relay.example.com" }),
    681     nostr_relay_create,
    682     INostrRelayFindMany,
    683     json!({ "filter": { "id": "id-1" } }),
    684     INostrRelayFindMany::Rel {
    685         rel: NostrRelayFindManyRel::OnProfile(NostrRelayProfileArgs {
    686             public_key: hex64('d')
    687         })
    688     },
    689     nostr_relay_find_many,
    690     INostrRelayFindOne,
    691     json!({ "on": { "id": "id-1" } }),
    692     INostrRelayFindOne::Rel(INostrRelayFindOneRelArgs {
    693         rel: NostrRelayFindManyRel::OffProfile(NostrRelayProfileArgs {
    694             public_key: hex64('d')
    695         })
    696     }),
    697     nostr_relay_find_one,
    698     INostrRelayUpdate,
    699     json!({ "on": { "id": "id-1" }, "fields": { "name": "z" } }),
    700     json!({ "on": { "url": "wss://relay.example.com" }, "fields": { "name": "y" } }),
    701     nostr_relay_update,
    702     INostrRelayDelete,
    703     json!({ "on": { "url": "wss://relay.example.com" } }),
    704     INostrRelayDelete::Rel(INostrRelayFindOneRelArgs {
    705         rel: NostrRelayFindManyRel::OnProfile(NostrRelayProfileArgs {
    706             public_key: hex64('d')
    707         })
    708     }),
    709     nostr_relay_delete
    710 );
    711 
    712 assert_trade_product_paths!(
    713     trade_product_scripted_region_paths,
    714     ITradeProductCreate,
    715     json!({
    716         "key": "product-a",
    717         "category": "coffee",
    718         "title": "coffee a",
    719         "summary": "summary",
    720         "process": "washed",
    721         "lot": "lot-a",
    722         "profile": "floral",
    723         "year": 2024,
    724         "qty_amt": 100,
    725         "qty_amt_exact": "100",
    726         "qty_unit": "kg",
    727         "price_amt": 7.5,
    728         "price_amt_exact": "7.5",
    729         "price_currency": "USD",
    730         "price_qty_amt": 1,
    731         "price_qty_amt_exact": "1",
    732         "price_qty_unit": "kg"
    733     }),
    734     trade_product_create,
    735     ITradeProductFindMany,
    736     json!({ "filter": { "id": "id-1" } }),
    737     trade_product_find_many,
    738     ITradeProductFindOne,
    739     json!({ "on": { "id": "id-1" } }),
    740     trade_product_find_one,
    741     ITradeProductUpdate,
    742     json!({ "on": { "id": "id-1" }, "fields": { "title": "z" } }),
    743     trade_product_update
    744 );