lib

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

domain_encode_non_serde.rs (44910B)


      1 #[path = "../src/test_fixtures.rs"]
      2 mod test_fixtures;
      3 
      4 use std::str::FromStr;
      5 
      6 use radroots_core::{Currency, Decimal, Money, Quantity, QuantityPrice, Unit};
      7 use radroots_event::{
      8     farm::change_set::{GcsLocation, GeoJsonPoint, GeoJsonPolygon},
      9     farm::coop::{Coop, CoopLocation, CoopRef},
     10     farm::plot::{Plot, PlotLocation, PlotRef},
     11     farm::resource_area::{ResourceArea, ResourceAreaLocation, ResourceAreaRef},
     12     farm::resource_cap::{ResourceHarvestCap, ResourceHarvestProduct},
     13     farm::{Farm, FarmPublicLocation, FarmRef},
     14     id::{DTag, InventoryBinId},
     15     listing::operational::{
     16         OperationalListing, OperationalListingAvailability, OperationalListingBin,
     17         OperationalListingDeliveryMethod, OperationalListingProduct,
     18         OperationalListingPublicLocation,
     19     },
     20     post::document::{Document, DocumentSubject},
     21 };
     22 use radroots_event_codec::encode::EventEncodeError;
     23 use radroots_event_codec::encode::coop::{coop_build_tags, coop_ref_tags};
     24 use radroots_event_codec::encode::coop::{coop_members_farms_list_set, coop_members_list_set};
     25 use radroots_event_codec::encode::document::document_build_tags;
     26 use radroots_event_codec::encode::farm::{farm_build_tags, farm_ref_tags};
     27 use radroots_event_codec::encode::farm::{
     28     farm_members_list_set, farm_operational_listings_list_set,
     29 };
     30 use radroots_event_codec::encode::operational_listing::operational_listing_build_tags;
     31 use radroots_event_codec::encode::operational_listing::{
     32     OperationalListingTagOptions, operational_listing_tags_full,
     33     operational_listing_tags_with_options,
     34 };
     35 use radroots_event_codec::encode::plot::{plot_address, plot_build_tags};
     36 use radroots_event_codec::encode::resource_area::{
     37     resource_area_build_tags, resource_area_ref_tags,
     38 };
     39 use radroots_event_codec::encode::resource_area::{
     40     resource_area_members_farms_list_set, resource_area_members_plots_list_set,
     41     resource_area_stewards_list_set,
     42 };
     43 use radroots_event_codec::encode::resource_cap::resource_harvest_cap_build_tags;
     44 use test_fixtures::FIXTURE_ALICE_PUBLIC_KEY_HEX;
     45 
     46 const VALID_PUBKEY: &str = FIXTURE_ALICE_PUBLIC_KEY_HEX;
     47 const VALID_FARM_D_TAG: &str = "AAAAAAAAAAAAAAAAAAAAAA";
     48 const VALID_PLOT_D_TAG: &str = "AAAAAAAAAAAAAAAAAAAAAQ";
     49 const VALID_COOP_D_TAG: &str = "BAAAAAAAAAAAAAAAAAAAAA";
     50 const VALID_AREA_D_TAG: &str = "AAAAAAAAAAAAAAAAAAAAAw";
     51 const VALID_CAP_D_TAG: &str = "AAAAAAAAAAAAAAAAAAAABA";
     52 const VALID_DOC_D_TAG: &str = "AAAAAAAAAAAAAAAAAAAAAg";
     53 
     54 fn decimal(value: &str) -> Decimal {
     55     Decimal::from_str(value).expect("valid decimal")
     56 }
     57 
     58 fn money(amount: Decimal, currency: Currency) -> Money {
     59     Money::try_new(amount, currency).unwrap()
     60 }
     61 
     62 fn quantity(amount: Decimal, unit: Unit) -> Quantity {
     63     Quantity::try_new(amount, unit).unwrap()
     64 }
     65 
     66 fn quantity_price(amount: Money, quantity: Quantity) -> QuantityPrice {
     67     QuantityPrice::try_new(amount, quantity).unwrap()
     68 }
     69 
     70 fn listing_d_tag(raw: &str) -> DTag {
     71     raw.parse().unwrap()
     72 }
     73 
     74 fn bin_id(raw: &str) -> InventoryBinId {
     75     raw.parse().unwrap()
     76 }
     77 
     78 fn sample_gcs(geohash: &str) -> GcsLocation {
     79     GcsLocation {
     80         lat: 37.0,
     81         lng: -122.0,
     82         geohash: geohash.to_string(),
     83         point: GeoJsonPoint {
     84             r#type: "Point".to_string(),
     85             coordinates: [-122.0, 37.0],
     86         },
     87         polygon: GeoJsonPolygon {
     88             r#type: "Polygon".to_string(),
     89             coordinates: vec![vec![
     90                 [-122.0, 37.0],
     91                 [-122.0, 37.0001],
     92                 [-122.0001, 37.0001],
     93                 [-122.0, 37.0],
     94             ]],
     95         },
     96         accuracy: None,
     97         altitude: None,
     98         tag_0: None,
     99         label: None,
    100         area: None,
    101         elevation: None,
    102         soil: None,
    103         climate: None,
    104         gc_id: None,
    105         gc_name: None,
    106         gc_admin1_id: None,
    107         gc_admin1_name: None,
    108         gc_country_id: None,
    109         gc_country_name: None,
    110     }
    111 }
    112 
    113 fn sample_coop() -> Coop {
    114     Coop {
    115         d_tag: VALID_COOP_D_TAG.to_string(),
    116         name: "Test Coop".to_string(),
    117         about: None,
    118         website: None,
    119         picture: None,
    120         banner: None,
    121         location: Some(CoopLocation {
    122             primary: None,
    123             city: None,
    124             region: None,
    125             country: None,
    126             gcs: sample_gcs("9q8yy"),
    127         }),
    128         tags: Some(vec!["regional".to_string()]),
    129     }
    130 }
    131 
    132 fn sample_farm() -> Farm {
    133     Farm {
    134         d_tag: VALID_FARM_D_TAG.to_string(),
    135         name: "Test Farm".to_string(),
    136         about: None,
    137         website: None,
    138         picture: None,
    139         banner: None,
    140         location: Some(FarmPublicLocation {
    141             primary: "Test Farm".to_string(),
    142             city: Some("Santa Cruz".to_string()),
    143             region: Some("California".to_string()),
    144             country: Some("US".to_string()),
    145             geohash: "9q8yy".to_string(),
    146         }),
    147         tags: Some(vec!["orchard".to_string()]),
    148     }
    149 }
    150 
    151 fn sample_plot() -> Plot {
    152     Plot {
    153         d_tag: VALID_PLOT_D_TAG.to_string(),
    154         farm: FarmRef {
    155             pubkey: VALID_PUBKEY.to_string(),
    156             d_tag: VALID_FARM_D_TAG.to_string(),
    157         },
    158         name: "Plot 1".to_string(),
    159         about: None,
    160         location: Some(PlotLocation {
    161             primary: None,
    162             city: None,
    163             region: None,
    164             country: None,
    165             gcs: sample_gcs("9q8yy"),
    166         }),
    167         tags: Some(vec!["orchard".to_string()]),
    168     }
    169 }
    170 
    171 fn sample_listing() -> OperationalListing {
    172     let quantity = quantity(Decimal::from(1u32), Unit::Each);
    173     let price_per_canonical_unit =
    174         quantity_price(money(Decimal::from(10u32), Currency::USD), quantity.clone());
    175 
    176     OperationalListing {
    177         d_tag: listing_d_tag(VALID_DOC_D_TAG),
    178         published_at: None,
    179         farm: FarmRef {
    180             pubkey: VALID_PUBKEY.to_string(),
    181             d_tag: VALID_FARM_D_TAG.to_string(),
    182         },
    183         product: OperationalListingProduct {
    184             key: "nutmeg".to_string(),
    185             title: "Nutmeg".to_string(),
    186             category: "spice".to_string(),
    187             summary: None,
    188             process: None,
    189             lot: None,
    190             location: None,
    191             profile: None,
    192             year: None,
    193         },
    194         primary_bin_id: bin_id("bin-1"),
    195         bins: vec![OperationalListingBin {
    196             bin_id: bin_id("bin-1"),
    197             quantity,
    198             price_per_canonical_unit,
    199             display_amount: None,
    200             display_unit: None,
    201             display_label: None,
    202             display_price: None,
    203             display_price_unit: None,
    204         }],
    205         resource_area: None,
    206         plot: None,
    207         discounts: None,
    208         inventory_available: Some(decimal("12")),
    209         availability: Some(OperationalListingAvailability::Window {
    210             start: Some(1),
    211             end: Some(2),
    212         }),
    213         delivery_method: Some(OperationalListingDeliveryMethod::Shipping),
    214         location: None,
    215         images: None,
    216     }
    217 }
    218 
    219 fn sample_resource_area() -> ResourceArea {
    220     ResourceArea {
    221         d_tag: VALID_AREA_D_TAG.to_string(),
    222         name: "Banda Grove".to_string(),
    223         about: None,
    224         location: ResourceAreaLocation {
    225             primary: None,
    226             city: None,
    227             region: None,
    228             country: None,
    229             gcs: sample_gcs("pmb5v"),
    230         },
    231         tags: Some(vec!["nutmeg".to_string()]),
    232     }
    233 }
    234 
    235 fn sample_resource_cap() -> ResourceHarvestCap {
    236     ResourceHarvestCap {
    237         d_tag: VALID_CAP_D_TAG.to_string(),
    238         resource_area: ResourceAreaRef {
    239             pubkey: VALID_PUBKEY.to_string(),
    240             d_tag: VALID_AREA_D_TAG.to_string(),
    241         },
    242         product: ResourceHarvestProduct {
    243             key: "nutmeg".to_string(),
    244             category: Some("spice".to_string()),
    245         },
    246         start: 1,
    247         end: 2,
    248         cap_quantity: quantity(decimal("1000"), Unit::MassG),
    249         display_amount: None,
    250         display_unit: None,
    251         display_label: None,
    252         tags: None,
    253     }
    254 }
    255 
    256 fn sample_document() -> Document {
    257     Document {
    258         d_tag: VALID_DOC_D_TAG.to_string(),
    259         doc_type: "charter".to_string(),
    260         title: "Charter".to_string(),
    261         version: "1.0.0".to_string(),
    262         summary: None,
    263         effective_at: None,
    264         body_markdown: None,
    265         subject: DocumentSubject {
    266             pubkey: VALID_PUBKEY.to_string(),
    267             address: Some(format!("30340:{VALID_PUBKEY}:{VALID_FARM_D_TAG}")),
    268         },
    269         tags: Some(vec!["policy".to_string()]),
    270     }
    271 }
    272 
    273 #[test]
    274 fn coop_encode_and_list_set_paths() {
    275     let tags = coop_build_tags(&sample_coop()).expect("coop tags");
    276     assert!(
    277         tags.iter()
    278             .any(|tag| tag.first().map(|v| v.as_str()) == Some("d"))
    279     );
    280     assert!(
    281         tags.iter()
    282             .any(|tag| tag.first().map(|v| v.as_str()) == Some("t"))
    283     );
    284     assert!(
    285         tags.iter()
    286             .any(|tag| tag.first().map(|v| v.as_str()) == Some("g"))
    287     );
    288 
    289     let mut coop = sample_coop();
    290     coop.tags = None;
    291     coop.location = None;
    292     let tags = coop_build_tags(&coop).expect("coop tags without optional fields");
    293     assert!(
    294         !tags
    295             .iter()
    296             .any(|tag| tag.first().map(|v| v.as_str()) == Some("t"))
    297     );
    298     assert!(
    299         !tags
    300             .iter()
    301             .any(|tag| tag.first().map(|v| v.as_str()) == Some("g"))
    302     );
    303 
    304     let mut coop = sample_coop();
    305     coop.d_tag = " ".to_string();
    306     let err = coop_build_tags(&coop).expect_err("empty d_tag");
    307     assert!(matches!(err, EventEncodeError::EmptyRequiredField("d_tag")));
    308 
    309     let mut coop = sample_coop();
    310     coop.name = " ".to_string();
    311     let err = coop_build_tags(&coop).expect_err("empty name");
    312     assert!(matches!(err, EventEncodeError::EmptyRequiredField("name")));
    313 
    314     let mut coop = sample_coop();
    315     coop.location.as_mut().expect("location").gcs.geohash = " ".to_string();
    316     let err = coop_build_tags(&coop).expect_err("empty geohash");
    317     assert!(matches!(
    318         err,
    319         EventEncodeError::EmptyRequiredField("location.gcs.geohash")
    320     ));
    321 
    322     let mut coop = sample_coop();
    323     coop.d_tag = "invalid".to_string();
    324     let err = coop_build_tags(&coop).expect_err("invalid d_tag");
    325     assert!(matches!(err, EventEncodeError::InvalidField("d_tag")));
    326 
    327     let tags = coop_ref_tags(&CoopRef {
    328         pubkey: VALID_PUBKEY.to_string(),
    329         d_tag: VALID_COOP_D_TAG.to_string(),
    330     })
    331     .expect("coop ref tags");
    332     assert_eq!(tags.len(), 2);
    333 
    334     let err = coop_ref_tags(&CoopRef {
    335         pubkey: " ".to_string(),
    336         d_tag: VALID_COOP_D_TAG.to_string(),
    337     })
    338     .expect_err("empty coop pubkey");
    339     assert!(matches!(
    340         err,
    341         EventEncodeError::EmptyRequiredField("coop.pubkey")
    342     ));
    343 
    344     let err = coop_ref_tags(&CoopRef {
    345         pubkey: VALID_PUBKEY.to_string(),
    346         d_tag: " ".to_string(),
    347     })
    348     .expect_err("empty coop d_tag");
    349     assert!(matches!(
    350         err,
    351         EventEncodeError::EmptyRequiredField("coop.d_tag")
    352     ));
    353 
    354     let err = coop_ref_tags(&CoopRef {
    355         pubkey: VALID_PUBKEY.to_string(),
    356         d_tag: "invalid".to_string(),
    357     })
    358     .expect_err("invalid coop d_tag");
    359     assert!(matches!(err, EventEncodeError::InvalidField("coop.d_tag")));
    360 
    361     let err = coop_members_list_set("invalid", ["member"]).expect_err("invalid coop id");
    362     assert!(matches!(err, EventEncodeError::InvalidField("coop_id")));
    363 
    364     let err = coop_members_list_set(" ", ["member"]).expect_err("empty coop id");
    365     assert!(matches!(
    366         err,
    367         EventEncodeError::EmptyRequiredField("coop_id")
    368     ));
    369 
    370     let members = coop_members_list_set(VALID_COOP_D_TAG, ["member"]).expect("members list set");
    371     assert_eq!(members.entries.len(), 1);
    372     assert_eq!(members.entries[0].tag, "p");
    373 
    374     let err = coop_members_list_set(VALID_COOP_D_TAG, [" "]).expect_err("empty member entry");
    375     assert!(matches!(
    376         err,
    377         EventEncodeError::EmptyRequiredField("entry.values")
    378     ));
    379 
    380     let member_farms = coop_members_farms_list_set(
    381         VALID_COOP_D_TAG,
    382         vec![FarmRef {
    383             pubkey: VALID_PUBKEY.to_string(),
    384             d_tag: VALID_FARM_D_TAG.to_string(),
    385         }],
    386     )
    387     .expect("member farms list set");
    388     assert_eq!(member_farms.entries.len(), 2);
    389     assert_eq!(member_farms.entries[0].tag, "a");
    390     assert_eq!(member_farms.entries[1].tag, "p");
    391 
    392     let member_farms_from_array = coop_members_farms_list_set(
    393         VALID_COOP_D_TAG,
    394         [FarmRef {
    395             pubkey: VALID_PUBKEY.to_string(),
    396             d_tag: VALID_FARM_D_TAG.to_string(),
    397         }],
    398     )
    399     .expect("member farms list set array");
    400     assert_eq!(member_farms_from_array.entries.len(), 2);
    401     assert_eq!(member_farms_from_array.entries[0].tag, "a");
    402     assert_eq!(member_farms_from_array.entries[1].tag, "p");
    403 
    404     let err = coop_members_farms_list_set(
    405         VALID_COOP_D_TAG,
    406         vec![FarmRef {
    407             pubkey: " ".to_string(),
    408             d_tag: VALID_FARM_D_TAG.to_string(),
    409         }],
    410     )
    411     .expect_err("empty farm pubkey");
    412     assert!(matches!(
    413         err,
    414         EventEncodeError::EmptyRequiredField("farm.pubkey")
    415     ));
    416 
    417     let err = coop_members_farms_list_set(
    418         VALID_COOP_D_TAG,
    419         vec![FarmRef {
    420             pubkey: VALID_PUBKEY.to_string(),
    421             d_tag: " ".to_string(),
    422         }],
    423     )
    424     .expect_err("empty farm d_tag");
    425     assert!(matches!(
    426         err,
    427         EventEncodeError::EmptyRequiredField("farm.d_tag")
    428     ));
    429 
    430     let err = coop_members_farms_list_set(
    431         VALID_COOP_D_TAG,
    432         vec![FarmRef {
    433             pubkey: VALID_PUBKEY.to_string(),
    434             d_tag: "invalid".to_string(),
    435         }],
    436     )
    437     .expect_err("invalid farm d_tag");
    438     assert!(matches!(err, EventEncodeError::InvalidField("farm.d_tag")));
    439 }
    440 
    441 #[test]
    442 fn farm_encode_and_list_set_paths() {
    443     let tags = farm_build_tags(&sample_farm()).expect("farm tags");
    444     assert!(
    445         tags.iter()
    446             .any(|tag| tag.first().map(|v| v.as_str()) == Some("d"))
    447     );
    448     assert!(
    449         tags.iter()
    450             .any(|tag| tag.first().map(|v| v.as_str()) == Some("t"))
    451     );
    452     assert!(
    453         tags.iter()
    454             .any(|tag| tag.first().map(|v| v.as_str()) == Some("g"))
    455     );
    456 
    457     let mut farm = sample_farm();
    458     farm.tags = None;
    459     farm.location = None;
    460     let tags = farm_build_tags(&farm).expect("farm tags without optional fields");
    461     assert!(
    462         !tags
    463             .iter()
    464             .any(|tag| tag.first().map(|v| v.as_str()) == Some("t"))
    465     );
    466     assert!(
    467         !tags
    468             .iter()
    469             .any(|tag| tag.first().map(|v| v.as_str()) == Some("g"))
    470     );
    471 
    472     let mut farm = sample_farm();
    473     farm.d_tag = " ".to_string();
    474     let err = farm_build_tags(&farm).expect_err("empty d_tag");
    475     assert!(matches!(err, EventEncodeError::EmptyRequiredField("d_tag")));
    476 
    477     let mut farm = sample_farm();
    478     farm.name = " ".to_string();
    479     let err = farm_build_tags(&farm).expect_err("empty name");
    480     assert!(matches!(err, EventEncodeError::EmptyRequiredField("name")));
    481 
    482     let mut farm = sample_farm();
    483     farm.location.as_mut().expect("location").geohash = " ".to_string();
    484     let err = farm_build_tags(&farm).expect_err("empty geohash");
    485     assert!(matches!(
    486         err,
    487         EventEncodeError::EmptyRequiredField("location.geohash")
    488     ));
    489 
    490     let mut farm = sample_farm();
    491     farm.location.as_mut().expect("location").geohash = "9q8yy6".to_string();
    492     let err = farm_build_tags(&farm).expect_err("invalid public geohash");
    493     assert!(matches!(
    494         err,
    495         EventEncodeError::InvalidField("location.geohash")
    496     ));
    497 
    498     let tags = farm_ref_tags(&FarmRef {
    499         pubkey: VALID_PUBKEY.to_string(),
    500         d_tag: VALID_FARM_D_TAG.to_string(),
    501     })
    502     .expect("farm ref tags");
    503     assert_eq!(tags.len(), 2);
    504 
    505     let err = farm_ref_tags(&FarmRef {
    506         pubkey: " ".to_string(),
    507         d_tag: VALID_FARM_D_TAG.to_string(),
    508     })
    509     .expect_err("empty farm pubkey");
    510     assert!(matches!(
    511         err,
    512         EventEncodeError::EmptyRequiredField("farm.pubkey")
    513     ));
    514 
    515     let err = farm_ref_tags(&FarmRef {
    516         pubkey: VALID_PUBKEY.to_string(),
    517         d_tag: " ".to_string(),
    518     })
    519     .expect_err("empty farm d_tag");
    520     assert!(matches!(
    521         err,
    522         EventEncodeError::EmptyRequiredField("farm.d_tag")
    523     ));
    524 
    525     let err = farm_ref_tags(&FarmRef {
    526         pubkey: VALID_PUBKEY.to_string(),
    527         d_tag: "invalid".to_string(),
    528     })
    529     .expect_err("invalid farm d_tag");
    530     assert!(matches!(err, EventEncodeError::InvalidField("farm.d_tag")));
    531 
    532     let err = farm_members_list_set("invalid", ["member"]).expect_err("invalid farm id");
    533     assert!(matches!(err, EventEncodeError::InvalidField("farm_id")));
    534 
    535     let err = farm_members_list_set(VALID_FARM_D_TAG, [" "]).expect_err("empty member entry");
    536     assert!(matches!(
    537         err,
    538         EventEncodeError::EmptyRequiredField("entry.values")
    539     ));
    540 
    541     let err = farm_operational_listings_list_set(VALID_FARM_D_TAG, VALID_PUBKEY, [" "])
    542         .expect_err("empty listing id");
    543     assert!(matches!(
    544         err,
    545         EventEncodeError::EmptyRequiredField("listing_id")
    546     ));
    547 
    548     let err = farm_operational_listings_list_set(VALID_FARM_D_TAG, VALID_PUBKEY, ["invalid"])
    549         .expect_err("invalid listing id");
    550     assert!(matches!(err, EventEncodeError::InvalidField("listing_id")));
    551 }
    552 
    553 #[test]
    554 fn plot_encode_paths() {
    555     let tags = plot_build_tags(&sample_plot()).expect("plot tags");
    556     assert!(
    557         tags.iter()
    558             .any(|tag| tag.first().map(|v| v.as_str()) == Some("a"))
    559     );
    560     assert!(
    561         tags.iter()
    562             .any(|tag| tag.first().map(|v| v.as_str()) == Some("p"))
    563     );
    564     assert!(
    565         tags.iter()
    566             .any(|tag| tag.first().map(|v| v.as_str()) == Some("t"))
    567     );
    568     assert!(
    569         tags.iter()
    570             .any(|tag| tag.first().map(|v| v.as_str()) == Some("g"))
    571     );
    572 
    573     let mut plot = sample_plot();
    574     plot.tags = None;
    575     plot.location = None;
    576     let tags = plot_build_tags(&plot).expect("plot tags without optional fields");
    577     assert!(
    578         !tags
    579             .iter()
    580             .any(|tag| tag.first().map(|v| v.as_str()) == Some("t"))
    581     );
    582     assert!(
    583         !tags
    584             .iter()
    585             .any(|tag| tag.first().map(|v| v.as_str()) == Some("g"))
    586     );
    587 
    588     let err = plot_address(" ", VALID_PLOT_D_TAG).expect_err("empty author pubkey");
    589     assert!(matches!(
    590         err,
    591         EventEncodeError::EmptyRequiredField("plot.author_pubkey")
    592     ));
    593 
    594     let err = plot_address(VALID_PUBKEY, " ").expect_err("empty plot d_tag");
    595     assert!(matches!(
    596         err,
    597         EventEncodeError::EmptyRequiredField("plot.d_tag")
    598     ));
    599 
    600     let err = plot_address(VALID_PUBKEY, "invalid").expect_err("invalid plot d_tag");
    601     assert!(matches!(err, EventEncodeError::InvalidField("plot.d_tag")));
    602 
    603     let mut plot = sample_plot();
    604     plot.d_tag = " ".to_string();
    605     let err = plot_build_tags(&plot).expect_err("empty plot d_tag");
    606     assert!(matches!(err, EventEncodeError::EmptyRequiredField("d_tag")));
    607 
    608     let mut plot = sample_plot();
    609     plot.name = " ".to_string();
    610     let err = plot_build_tags(&plot).expect_err("empty plot name");
    611     assert!(matches!(err, EventEncodeError::EmptyRequiredField("name")));
    612 
    613     let mut plot = sample_plot();
    614     plot.farm.pubkey = " ".to_string();
    615     let err = plot_build_tags(&plot).expect_err("empty farm pubkey");
    616     assert!(matches!(
    617         err,
    618         EventEncodeError::EmptyRequiredField("farm.pubkey")
    619     ));
    620 
    621     let mut plot = sample_plot();
    622     plot.farm.d_tag = " ".to_string();
    623     let err = plot_build_tags(&plot).expect_err("empty farm d_tag");
    624     assert!(matches!(
    625         err,
    626         EventEncodeError::EmptyRequiredField("farm.d_tag")
    627     ));
    628 
    629     let mut plot = sample_plot();
    630     plot.farm.d_tag = "invalid".to_string();
    631     let err = plot_build_tags(&plot).expect_err("invalid farm d_tag");
    632     assert!(matches!(err, EventEncodeError::InvalidField("farm.d_tag")));
    633 
    634     let mut plot = sample_plot();
    635     plot.location.as_mut().expect("location").gcs.geohash = " ".to_string();
    636     let err = plot_build_tags(&plot).expect_err("empty geohash");
    637     assert!(matches!(
    638         err,
    639         EventEncodeError::EmptyRequiredField("location.gcs.geohash")
    640     ));
    641 }
    642 
    643 #[test]
    644 fn listing_encode_paths() {
    645     let listing = sample_listing();
    646     let tags = operational_listing_build_tags(&listing).expect("listing tags");
    647     assert!(
    648         tags.iter()
    649             .any(|tag| tag.first().map(|v| v.as_str()) == Some("d"))
    650     );
    651 
    652     let full_tags = operational_listing_tags_full(&listing).expect("listing full tags");
    653     assert!(full_tags.iter().any(|tag| {
    654         tag.first().map(|v| v.as_str()) == Some("inventory")
    655             && tag.get(1).map(|v| v.as_str()) == Some("12")
    656     }));
    657     assert!(full_tags.iter().any(|tag| {
    658         tag.first().map(|v| v.as_str()) == Some("radroots:availability_start")
    659             && tag.get(1).map(|v| v.as_str()) == Some("1")
    660     }));
    661     assert!(full_tags.iter().any(|tag| {
    662         tag.first().map(|v| v.as_str()) == Some("expires_at")
    663             && tag.get(1).map(|v| v.as_str()) == Some("2")
    664     }));
    665     assert!(full_tags.iter().any(|tag| {
    666         tag.first().map(|v| v.as_str()) == Some("delivery")
    667             && tag.get(1).map(|v| v.as_str()) == Some("shipping")
    668     }));
    669 
    670     let with_trade_fields: fn() -> OperationalListingTagOptions =
    671         OperationalListingTagOptions::with_trade_fields;
    672     let option_tags = operational_listing_tags_with_options(&listing, with_trade_fields())
    673         .expect("listing option tags");
    674     assert!(option_tags.iter().any(|tag| {
    675         tag.first().map(|v| v.as_str()) == Some("inventory")
    676             && tag.get(1).map(|v| v.as_str()) == Some("12")
    677     }));
    678 
    679     let mut listing_with_display_fallback = sample_listing();
    680     listing_with_display_fallback.bins[0].quantity = listing_with_display_fallback.bins[0]
    681         .quantity
    682         .clone()
    683         .with_label("fallback-label");
    684     listing_with_display_fallback.bins[0].display_amount = Some(decimal("1"));
    685     listing_with_display_fallback.bins[0].display_unit = Some(Unit::Each);
    686     listing_with_display_fallback.bins[0].display_label = None;
    687     let display_tags = operational_listing_tags_with_options(
    688         &listing_with_display_fallback,
    689         OperationalListingTagOptions::default(),
    690     )
    691     .expect("listing tags with display fallback");
    692     assert!(display_tags.iter().any(|tag| {
    693         tag.first().map(|v| v.as_str()) == Some("radroots:bin")
    694             && tag.last().map(|v| v.as_str()) == Some("fallback-label")
    695     }));
    696 
    697     let mut listing_with_geohash = sample_listing();
    698     listing_with_geohash.location = Some(OperationalListingPublicLocation {
    699         primary: "Origin".to_string(),
    700         city: Some("Town".to_string()),
    701         region: Some("Region".to_string()),
    702         country: Some("PE".to_string()),
    703         geohash: "6gkzw".to_string(),
    704     });
    705     let decoded_tags = operational_listing_tags_with_options(
    706         &listing_with_geohash,
    707         OperationalListingTagOptions {
    708             include_geohash: false,
    709             ..OperationalListingTagOptions::default()
    710         },
    711     )
    712     .expect("listing tags without geohash tag");
    713     assert!(
    714         !decoded_tags
    715             .iter()
    716             .any(|tag| tag.first().map(|v| v.as_str()) == Some("g"))
    717     );
    718 
    719     let mut listing_with_shared_geohash = sample_listing();
    720     listing_with_shared_geohash.location = Some(OperationalListingPublicLocation {
    721         primary: "Origin".to_string(),
    722         city: Some("Town".to_string()),
    723         region: Some("Region".to_string()),
    724         country: Some("PE".to_string()),
    725         geohash: "6gkzw".to_string(),
    726     });
    727     let shared_geohash_tags = operational_listing_tags_with_options(
    728         &listing_with_shared_geohash,
    729         OperationalListingTagOptions::default(),
    730     )
    731     .expect("listing tags with shared geohash");
    732     assert!(shared_geohash_tags.iter().any(|tag| {
    733         tag.first().map(|v| v.as_str()) == Some("g")
    734             && tag.get(1).map(|v| v.as_str()) == Some("6gkzw")
    735     }));
    736 
    737     let mut listing_without_coordinates = sample_listing();
    738     listing_without_coordinates.location = Some(OperationalListingPublicLocation {
    739         primary: "Origin".to_string(),
    740         city: Some("Town".to_string()),
    741         region: Some("Region".to_string()),
    742         country: Some("PE".to_string()),
    743         geohash: "6gkzw".to_string(),
    744     });
    745     let no_coordinates_tags = operational_listing_tags_with_options(
    746         &listing_without_coordinates,
    747         OperationalListingTagOptions::default(),
    748     )
    749     .expect("listing tags without coordinates");
    750     assert!(
    751         !no_coordinates_tags
    752             .iter()
    753             .any(|tag| tag.first().map(|v| v.as_str()) == Some("L"))
    754     );
    755     assert!(no_coordinates_tags.iter().any(|tag| {
    756         tag.first().map(|v| v.as_str()) == Some("g")
    757             && tag.get(1).map(|v| v.as_str()) == Some("6gkzw")
    758     }));
    759 
    760     let mut listing_with_blank_optionals = sample_listing();
    761     listing_with_blank_optionals.product.summary = Some(" ".to_string());
    762     listing_with_blank_optionals.product.process = Some("null".to_string());
    763     listing_with_blank_optionals.product.location = Some(" ".to_string());
    764     listing_with_blank_optionals.product.profile = Some("null".to_string());
    765     listing_with_blank_optionals.product.year = Some(" ".to_string());
    766     listing_with_blank_optionals.location = Some(OperationalListingPublicLocation {
    767         primary: " ".to_string(),
    768         city: Some(" ".to_string()),
    769         region: Some("null".to_string()),
    770         country: Some(" ".to_string()),
    771         geohash: "6gkzw".to_string(),
    772     });
    773     let blank_optional_tags = operational_listing_tags_with_options(
    774         &listing_with_blank_optionals,
    775         OperationalListingTagOptions::default(),
    776     )
    777     .expect_err("blank public location is invalid");
    778     assert!(matches!(
    779         blank_optional_tags,
    780         EventEncodeError::EmptyRequiredField("location.primary")
    781     ));
    782     let mut listing_with_blank_product_optionals = sample_listing();
    783     listing_with_blank_product_optionals.product.summary = Some(" ".to_string());
    784     listing_with_blank_product_optionals.product.process = Some("null".to_string());
    785     listing_with_blank_product_optionals.product.location = Some(" ".to_string());
    786     listing_with_blank_product_optionals.product.profile = Some("null".to_string());
    787     listing_with_blank_product_optionals.product.year = Some(" ".to_string());
    788     let blank_optional_tags = operational_listing_tags_with_options(
    789         &listing_with_blank_product_optionals,
    790         OperationalListingTagOptions::default(),
    791     )
    792     .expect("listing tags with blank product optional values");
    793     assert!(
    794         !blank_optional_tags
    795             .iter()
    796             .any(|tag| tag.first().map(|v| v.as_str()) == Some("summary"))
    797     );
    798     assert!(
    799         !blank_optional_tags
    800             .iter()
    801             .any(|tag| tag.first().map(|v| v.as_str()) == Some("location"))
    802     );
    803 
    804     let mut listing_without_geohash = sample_listing();
    805     listing_without_geohash.location = Some(OperationalListingPublicLocation {
    806         primary: "Origin".to_string(),
    807         city: Some("Town".to_string()),
    808         region: Some("Region".to_string()),
    809         country: Some("PE".to_string()),
    810         geohash: "6gkzw".to_string(),
    811     });
    812     let without_geohash_tags = operational_listing_tags_with_options(
    813         &listing_without_geohash,
    814         OperationalListingTagOptions {
    815             include_geohash: false,
    816             ..OperationalListingTagOptions::default()
    817         },
    818     )
    819     .expect("listing tags without geohash");
    820     assert!(
    821         !without_geohash_tags
    822             .iter()
    823             .any(|tag| tag.first().map(|v| v.as_str()) == Some("L"))
    824     );
    825 
    826     let mut listing_without_availability = sample_listing();
    827     listing_without_availability.availability = None;
    828     let no_availability_tags =
    829         operational_listing_tags_with_options(&listing_without_availability, with_trade_fields())
    830             .expect("listing tags without availability");
    831     assert!(
    832         !no_availability_tags
    833             .iter()
    834             .any(|tag| { tag.first().map(|v| v.as_str()) == Some("radroots:availability_start") })
    835     );
    836 
    837     let mut listing_pickup = sample_listing();
    838     listing_pickup.delivery_method = Some(OperationalListingDeliveryMethod::Pickup);
    839     let pickup_tags = operational_listing_tags_with_options(&listing_pickup, with_trade_fields())
    840         .expect("listing tags with pickup delivery");
    841     assert!(pickup_tags.iter().any(|tag| {
    842         tag.first().map(|v| v.as_str()) == Some("delivery")
    843             && tag.get(1).map(|v| v.as_str()) == Some("pickup")
    844     }));
    845 
    846     let mut listing_local = sample_listing();
    847     listing_local.delivery_method = Some(OperationalListingDeliveryMethod::LocalDelivery);
    848     let local_tags = operational_listing_tags_with_options(&listing_local, with_trade_fields())
    849         .expect("listing tags with local delivery");
    850     assert!(local_tags.iter().any(|tag| {
    851         tag.first().map(|v| v.as_str()) == Some("delivery")
    852             && tag.get(1).map(|v| v.as_str()) == Some("local_delivery")
    853     }));
    854 
    855     let mut listing_other_delivery = sample_listing();
    856     listing_other_delivery.delivery_method = Some(OperationalListingDeliveryMethod::Other {
    857         method: "courier".to_string(),
    858     });
    859     let other_delivery_tags =
    860         operational_listing_tags_with_options(&listing_other_delivery, with_trade_fields())
    861             .expect("listing tags with other delivery");
    862     assert!(other_delivery_tags.iter().any(|tag| {
    863         tag.first().map(|v| v.as_str()) == Some("delivery")
    864             && tag.get(1).map(|v| v.as_str()) == Some("other")
    865             && tag.get(2).map(|v| v.as_str()) == Some("courier")
    866     }));
    867 
    868     let mut invalid = sample_listing();
    869     invalid.bins[0].display_price = None;
    870     invalid.bins[0].display_price_unit = Some(Unit::Each);
    871     let err =
    872         operational_listing_tags_with_options(&invalid, OperationalListingTagOptions::default())
    873             .expect_err("missing display price");
    874     assert!(matches!(
    875         err,
    876         EventEncodeError::EmptyRequiredField("bin.display_price")
    877     ));
    878 
    879     let mut invalid = sample_listing();
    880     invalid.bins[0].display_price = Some(money(decimal("10"), Currency::USD));
    881     invalid.bins[0].display_price_unit = None;
    882     let err =
    883         operational_listing_tags_with_options(&invalid, OperationalListingTagOptions::default())
    884             .expect_err("missing display price unit");
    885     assert!(matches!(
    886         err,
    887         EventEncodeError::EmptyRequiredField("bin.display_price_unit")
    888     ));
    889 
    890     let mut listing_with_display_price = sample_listing();
    891     listing_with_display_price.bins[0].display_price = Some(money(decimal("10"), Currency::USD));
    892     listing_with_display_price.bins[0].display_price_unit = Some(Unit::Each);
    893     let display_price_tags = operational_listing_tags_with_options(
    894         &listing_with_display_price,
    895         OperationalListingTagOptions::default(),
    896     )
    897     .expect("listing tags with display price");
    898     assert!(display_price_tags.iter().any(|tag| {
    899         tag.first().map(|v| v.as_str()) == Some("radroots:price")
    900             && tag.get(6).map(|v| v.as_str()) == Some("10")
    901             && tag.get(7).map(|v| v.as_str()) == Some("each")
    902     }));
    903 
    904     let mut invalid = listing_with_display_price.clone();
    905     invalid.bins[0].display_price = Some(money(decimal("10"), Currency::EUR));
    906     let err =
    907         operational_listing_tags_with_options(&invalid, OperationalListingTagOptions::default())
    908             .expect_err("display price currency mismatch");
    909     assert!(matches!(
    910         err,
    911         EventEncodeError::EmptyRequiredField("bin.display_price")
    912     ));
    913 
    914     let mut invalid = sample_listing();
    915     invalid.bins[0].display_amount = None;
    916     invalid.bins[0].display_unit = Some(Unit::Each);
    917     let err =
    918         operational_listing_tags_with_options(&invalid, OperationalListingTagOptions::default())
    919             .expect_err("missing display amount");
    920     assert!(matches!(
    921         err,
    922         EventEncodeError::EmptyRequiredField("bin.display_amount")
    923     ));
    924 
    925     let mut invalid = sample_listing();
    926     invalid.bins[0].quantity = quantity(decimal("1"), Unit::MassKg);
    927     invalid.bins[0].price_per_canonical_unit = quantity_price(
    928         money(decimal("10"), Currency::USD),
    929         quantity(decimal("1"), Unit::MassG),
    930     );
    931     let err =
    932         operational_listing_tags_with_options(&invalid, OperationalListingTagOptions::default())
    933             .expect_err("non-canonical bin quantity");
    934     assert!(matches!(
    935         err,
    936         EventEncodeError::EmptyRequiredField("bin.quantity")
    937     ));
    938 
    939     let mut invalid = sample_listing();
    940     invalid.bins[0].price_per_canonical_unit = quantity_price(
    941         money(decimal("10"), Currency::USD),
    942         quantity(decimal("2"), Unit::Each),
    943     );
    944     let err =
    945         operational_listing_tags_with_options(&invalid, OperationalListingTagOptions::default())
    946             .expect_err("price must be per canonical unit");
    947     assert!(matches!(
    948         err,
    949         EventEncodeError::EmptyRequiredField("bin.price_per_canonical_unit")
    950     ));
    951 
    952     let mut invalid = sample_listing();
    953     invalid.bins[0].price_per_canonical_unit = quantity_price(
    954         money(decimal("10"), Currency::USD),
    955         quantity(decimal("1"), Unit::MassG),
    956     );
    957     let err =
    958         operational_listing_tags_with_options(&invalid, OperationalListingTagOptions::default())
    959             .expect_err("non-convertible bin total price");
    960     assert!(matches!(
    961         err,
    962         EventEncodeError::EmptyRequiredField("bin.price_per_canonical_unit")
    963     ));
    964 
    965     let mut invalid = sample_listing();
    966     invalid.farm.d_tag = " ".to_string();
    967     let err = operational_listing_build_tags(&invalid).expect_err("empty listing farm d_tag");
    968     assert!(matches!(
    969         err,
    970         EventEncodeError::EmptyRequiredField("farm.d_tag")
    971     ));
    972 
    973     let mut invalid = sample_listing();
    974     invalid.bins.clear();
    975     let err =
    976         operational_listing_tags_with_options(&invalid, OperationalListingTagOptions::default())
    977             .expect_err("empty bins");
    978     assert!(matches!(err, EventEncodeError::EmptyRequiredField("bins")));
    979 }
    980 
    981 #[test]
    982 fn resource_area_encode_and_list_set_paths() {
    983     let tags = resource_area_build_tags(&sample_resource_area()).expect("resource area tags");
    984     assert!(
    985         tags.iter()
    986             .any(|tag| tag.first().map(|v| v.as_str()) == Some("d"))
    987     );
    988     assert!(
    989         tags.iter()
    990             .any(|tag| tag.first().map(|v| v.as_str()) == Some("g"))
    991     );
    992     assert!(
    993         tags.iter()
    994             .any(|tag| tag.first().map(|v| v.as_str()) == Some("t"))
    995     );
    996 
    997     let mut area = sample_resource_area();
    998     area.tags = None;
    999     let tags = resource_area_build_tags(&area).expect("resource area tags without optional tags");
   1000     assert!(
   1001         !tags
   1002             .iter()
   1003             .any(|tag| tag.first().map(|v| v.as_str()) == Some("t"))
   1004     );
   1005 
   1006     let mut area = sample_resource_area();
   1007     area.d_tag = " ".to_string();
   1008     let err = resource_area_build_tags(&area).expect_err("empty d_tag");
   1009     assert!(matches!(err, EventEncodeError::EmptyRequiredField("d_tag")));
   1010 
   1011     let mut area = sample_resource_area();
   1012     area.name = " ".to_string();
   1013     let err = resource_area_build_tags(&area).expect_err("empty name");
   1014     assert!(matches!(err, EventEncodeError::EmptyRequiredField("name")));
   1015 
   1016     let mut area = sample_resource_area();
   1017     area.location.gcs.geohash = " ".to_string();
   1018     let err = resource_area_build_tags(&area).expect_err("empty geohash");
   1019     assert!(matches!(
   1020         err,
   1021         EventEncodeError::EmptyRequiredField("location.gcs.geohash")
   1022     ));
   1023 
   1024     let tags = resource_area_ref_tags(&ResourceAreaRef {
   1025         pubkey: VALID_PUBKEY.to_string(),
   1026         d_tag: VALID_AREA_D_TAG.to_string(),
   1027     })
   1028     .expect("resource area ref tags");
   1029     assert_eq!(tags.len(), 2);
   1030 
   1031     let err = resource_area_ref_tags(&ResourceAreaRef {
   1032         pubkey: " ".to_string(),
   1033         d_tag: VALID_AREA_D_TAG.to_string(),
   1034     })
   1035     .expect_err("empty resource area pubkey");
   1036     assert!(matches!(
   1037         err,
   1038         EventEncodeError::EmptyRequiredField("resource_area.pubkey")
   1039     ));
   1040 
   1041     let err = resource_area_ref_tags(&ResourceAreaRef {
   1042         pubkey: VALID_PUBKEY.to_string(),
   1043         d_tag: " ".to_string(),
   1044     })
   1045     .expect_err("empty resource area d_tag");
   1046     assert!(matches!(
   1047         err,
   1048         EventEncodeError::EmptyRequiredField("resource_area.d_tag")
   1049     ));
   1050 
   1051     let err = resource_area_ref_tags(&ResourceAreaRef {
   1052         pubkey: VALID_PUBKEY.to_string(),
   1053         d_tag: "invalid".to_string(),
   1054     })
   1055     .expect_err("invalid resource area d_tag");
   1056     assert!(matches!(
   1057         err,
   1058         EventEncodeError::InvalidField("resource_area.d_tag")
   1059     ));
   1060 
   1061     let err = resource_area_members_farms_list_set(
   1062         "invalid",
   1063         vec![FarmRef {
   1064             pubkey: VALID_PUBKEY.to_string(),
   1065             d_tag: VALID_FARM_D_TAG.to_string(),
   1066         }],
   1067     )
   1068     .expect_err("invalid area id");
   1069     assert!(matches!(err, EventEncodeError::InvalidField("area_id")));
   1070 
   1071     let err =
   1072         resource_area_stewards_list_set(VALID_AREA_D_TAG, [" "]).expect_err("empty steward entry");
   1073     assert!(matches!(
   1074         err,
   1075         EventEncodeError::EmptyRequiredField("entry.values")
   1076     ));
   1077 
   1078     let stewards =
   1079         resource_area_stewards_list_set(VALID_AREA_D_TAG, ["steward"]).expect("stewards list set");
   1080     assert_eq!(stewards.entries.len(), 1);
   1081     assert_eq!(stewards.entries[0].tag, "p");
   1082 
   1083     let err = resource_area_members_farms_list_set(
   1084         VALID_AREA_D_TAG,
   1085         vec![FarmRef {
   1086             pubkey: " ".to_string(),
   1087             d_tag: VALID_FARM_D_TAG.to_string(),
   1088         }],
   1089     )
   1090     .expect_err("empty farm pubkey");
   1091     assert!(matches!(
   1092         err,
   1093         EventEncodeError::EmptyRequiredField("farm.pubkey")
   1094     ));
   1095 
   1096     let err = resource_area_members_farms_list_set(
   1097         VALID_AREA_D_TAG,
   1098         vec![FarmRef {
   1099             pubkey: VALID_PUBKEY.to_string(),
   1100             d_tag: " ".to_string(),
   1101         }],
   1102     )
   1103     .expect_err("empty farm d_tag");
   1104     assert!(matches!(
   1105         err,
   1106         EventEncodeError::EmptyRequiredField("farm.d_tag")
   1107     ));
   1108 
   1109     let err = resource_area_members_farms_list_set(
   1110         VALID_AREA_D_TAG,
   1111         vec![FarmRef {
   1112             pubkey: VALID_PUBKEY.to_string(),
   1113             d_tag: "invalid".to_string(),
   1114         }],
   1115     )
   1116     .expect_err("invalid farm d_tag");
   1117     assert!(matches!(err, EventEncodeError::InvalidField("farm.d_tag")));
   1118 
   1119     let err = resource_area_members_plots_list_set(
   1120         VALID_AREA_D_TAG,
   1121         vec![PlotRef {
   1122             pubkey: " ".to_string(),
   1123             d_tag: VALID_PLOT_D_TAG.to_string(),
   1124         }],
   1125     )
   1126     .expect_err("empty plot pubkey");
   1127     assert!(matches!(
   1128         err,
   1129         EventEncodeError::EmptyRequiredField("plot.pubkey")
   1130     ));
   1131 
   1132     let err = resource_area_members_plots_list_set(
   1133         VALID_AREA_D_TAG,
   1134         vec![PlotRef {
   1135             pubkey: VALID_PUBKEY.to_string(),
   1136             d_tag: " ".to_string(),
   1137         }],
   1138     )
   1139     .expect_err("empty plot d_tag");
   1140     assert!(matches!(
   1141         err,
   1142         EventEncodeError::EmptyRequiredField("plot.d_tag")
   1143     ));
   1144 
   1145     let err = resource_area_members_plots_list_set(
   1146         VALID_AREA_D_TAG,
   1147         vec![PlotRef {
   1148             pubkey: VALID_PUBKEY.to_string(),
   1149             d_tag: "invalid".to_string(),
   1150         }],
   1151     )
   1152     .expect_err("invalid plot d_tag");
   1153     assert!(matches!(err, EventEncodeError::InvalidField("plot.d_tag")));
   1154 }
   1155 
   1156 #[test]
   1157 fn resource_harvest_cap_encode_paths() {
   1158     let tags = resource_harvest_cap_build_tags(&sample_resource_cap()).expect("resource cap tags");
   1159     assert!(
   1160         tags.iter()
   1161             .any(|tag| tag.first().map(|v| v.as_str()) == Some("category"))
   1162     );
   1163 
   1164     let mut cap = sample_resource_cap();
   1165     cap.product.category = None;
   1166     let tags = resource_harvest_cap_build_tags(&cap).expect("resource cap tags without category");
   1167     assert!(
   1168         !tags
   1169             .iter()
   1170             .any(|tag| tag.first().map(|v| v.as_str()) == Some("category"))
   1171     );
   1172 
   1173     let mut cap = sample_resource_cap();
   1174     cap.d_tag = " ".to_string();
   1175     let err = resource_harvest_cap_build_tags(&cap).expect_err("empty cap d_tag");
   1176     assert!(matches!(err, EventEncodeError::EmptyRequiredField("d_tag")));
   1177 
   1178     let mut cap = sample_resource_cap();
   1179     cap.d_tag = "invalid".to_string();
   1180     let err = resource_harvest_cap_build_tags(&cap).expect_err("invalid cap d_tag");
   1181     assert!(matches!(err, EventEncodeError::InvalidField("d_tag")));
   1182 
   1183     let mut cap = sample_resource_cap();
   1184     cap.product.key = " ".to_string();
   1185     let err = resource_harvest_cap_build_tags(&cap).expect_err("empty product key");
   1186     assert!(matches!(
   1187         err,
   1188         EventEncodeError::EmptyRequiredField("product.key")
   1189     ));
   1190 
   1191     let mut cap = sample_resource_cap();
   1192     cap.resource_area.pubkey = " ".to_string();
   1193     let err = resource_harvest_cap_build_tags(&cap).expect_err("empty resource_area pubkey");
   1194     assert!(matches!(
   1195         err,
   1196         EventEncodeError::EmptyRequiredField("resource_area.pubkey")
   1197     ));
   1198 
   1199     let mut cap = sample_resource_cap();
   1200     cap.resource_area.d_tag = " ".to_string();
   1201     let err = resource_harvest_cap_build_tags(&cap).expect_err("empty resource_area d_tag");
   1202     assert!(matches!(
   1203         err,
   1204         EventEncodeError::EmptyRequiredField("resource_area.d_tag")
   1205     ));
   1206 
   1207     let mut cap = sample_resource_cap();
   1208     cap.resource_area.d_tag = "invalid".to_string();
   1209     let err = resource_harvest_cap_build_tags(&cap).expect_err("invalid resource_area d_tag");
   1210     assert!(matches!(
   1211         err,
   1212         EventEncodeError::InvalidField("resource_area.d_tag")
   1213     ));
   1214 }
   1215 
   1216 #[test]
   1217 fn document_encode_paths() {
   1218     let tags = document_build_tags(&sample_document()).expect("document tags");
   1219     assert!(
   1220         tags.iter()
   1221             .any(|tag| tag.first().map(|v| v.as_str()) == Some("d"))
   1222     );
   1223     assert!(
   1224         tags.iter()
   1225             .any(|tag| tag.first().map(|v| v.as_str()) == Some("p"))
   1226     );
   1227     assert!(
   1228         tags.iter()
   1229             .any(|tag| tag.first().map(|v| v.as_str()) == Some("a"))
   1230     );
   1231     assert!(
   1232         tags.iter()
   1233             .any(|tag| tag.first().map(|v| v.as_str()) == Some("t"))
   1234     );
   1235 
   1236     let mut document = sample_document();
   1237     document.subject.address = None;
   1238     document.tags = None;
   1239     let tags = document_build_tags(&document).expect("document without optional tags");
   1240     assert!(
   1241         !tags
   1242             .iter()
   1243             .any(|tag| tag.first().map(|v| v.as_str()) == Some("a"))
   1244     );
   1245     assert!(
   1246         !tags
   1247             .iter()
   1248             .any(|tag| tag.first().map(|v| v.as_str()) == Some("t"))
   1249     );
   1250 
   1251     let mut document = sample_document();
   1252     document.d_tag = " ".to_string();
   1253     let err = document_build_tags(&document).expect_err("empty d_tag");
   1254     assert!(matches!(err, EventEncodeError::EmptyRequiredField("d_tag")));
   1255 
   1256     let mut document = sample_document();
   1257     document.doc_type = " ".to_string();
   1258     let err = document_build_tags(&document).expect_err("empty doc_type");
   1259     assert!(matches!(
   1260         err,
   1261         EventEncodeError::EmptyRequiredField("doc_type")
   1262     ));
   1263 
   1264     let mut document = sample_document();
   1265     document.title = " ".to_string();
   1266     let err = document_build_tags(&document).expect_err("empty title");
   1267     assert!(matches!(err, EventEncodeError::EmptyRequiredField("title")));
   1268 
   1269     let mut document = sample_document();
   1270     document.version = " ".to_string();
   1271     let err = document_build_tags(&document).expect_err("empty version");
   1272     assert!(matches!(
   1273         err,
   1274         EventEncodeError::EmptyRequiredField("version")
   1275     ));
   1276 
   1277     let mut document = sample_document();
   1278     document.subject.pubkey = " ".to_string();
   1279     let err = document_build_tags(&document).expect_err("empty subject pubkey");
   1280     assert!(matches!(
   1281         err,
   1282         EventEncodeError::EmptyRequiredField("subject.pubkey")
   1283     ));
   1284 
   1285     let mut document = sample_document();
   1286     document.subject.address = Some(" ".to_string());
   1287     let err = document_build_tags(&document).expect_err("empty subject address");
   1288     assert!(matches!(
   1289         err,
   1290         EventEncodeError::EmptyRequiredField("subject.address")
   1291     ));
   1292 
   1293     let mut document = sample_document();
   1294     document.d_tag = "invalid".to_string();
   1295     let err = document_build_tags(&document).expect_err("invalid d_tag");
   1296     assert!(matches!(err, EventEncodeError::InvalidField("d_tag")));
   1297 }
   1298 
   1299 #[test]
   1300 fn resource_harvest_cap_money_type_sanity() {
   1301     let money = money(decimal("1"), Currency::USD);
   1302     assert_eq!(money.amount().to_string(), "1");
   1303 }