lib

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

region_scripted_paths.rs (27478B)


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