lib

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

commit 9e603005a3f41583cb26def3b3406e2fcc5f3bed
parent e791ed375864725dc5ba915eb43230a1d1e49cc7
Author: triesap <tyson@radroots.org>
Date:   Tue, 14 Jul 2026 07:04:39 +0000

event: add canonical NIP-01 wire

- add RadrootsNip01EventWire parsing, limits, extra preservation, and ID verification
- move event ID preimage generation onto a dedicated canonical serializer
- add event wire conformance vectors for valid, escaped, and mismatched events
- keep no-default event tests green with test-only serde and std harness support

Diffstat:
Acontracts/conformance/vectors/event/nip01_wire.v1.json | 41+++++++++++++++++++++++++++++++++++++++++
Mcrates/event/Cargo.toml | 8++++++++
Mcrates/event/src/account.rs | 5++++-
Mcrates/event/src/app_data.rs | 5++++-
Mcrates/event/src/article.rs | 5++++-
Mcrates/event/src/calendar.rs | 20++++++++++++++++----
Mcrates/event/src/comment.rs | 5++++-
Mcrates/event/src/coop.rs | 15++++++++++++---
Mcrates/event/src/document.rs | 10++++++++--
Mcrates/event/src/draft.rs | 116+++++++++++++++++++++++++++++++++++++------------------------------------------
Mcrates/event/src/farm.rs | 15++++++++++++---
Mcrates/event/src/farm_crdt.rs | 24++++++++++++++++++------
Mcrates/event/src/farm_file.rs | 15++++++++++++---
Mcrates/event/src/farm_workspace.rs | 25++++++++++++++++++++-----
Mcrates/event/src/file_metadata.rs | 5++++-
Mcrates/event/src/follow.rs | 10++++++++--
Mcrates/event/src/gcs.rs | 19++++++++++++++-----
Mcrates/event/src/geochat.rs | 5++++-
Mcrates/event/src/gift_wrap.rs | 10++++++++--
Mcrates/event/src/group.rs | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Mcrates/event/src/http_auth.rs | 5++++-
Mcrates/event/src/ids.rs | 6+++---
Mcrates/event/src/job.rs | 37+++++++++++++++++++++++--------------
Mcrates/event/src/job_feedback.rs | 5++++-
Mcrates/event/src/job_request.rs | 15++++++++++++---
Mcrates/event/src/job_result.rs | 5++++-
Mcrates/event/src/knowledge.rs | 124++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
Mcrates/event/src/lib.rs | 23+++++++++++++++++++----
Mcrates/event/src/list.rs | 10++++++++--
Mcrates/event/src/list_set.rs | 5++++-
Mcrates/event/src/listing.rs | 47+++++++++++++++++++++++++++++++++++++----------
Mcrates/event/src/message.rs | 10++++++++--
Mcrates/event/src/message_file.rs | 10++++++++--
Mcrates/event/src/order.rs | 96+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Mcrates/event/src/order_economics.rs | 69++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Mcrates/event/src/plot.rs | 15++++++++++++---
Mcrates/event/src/post.rs | 5++++-
Mcrates/event/src/profile.rs | 22++++++++++++++--------
Mcrates/event/src/reaction.rs | 5++++-
Mcrates/event/src/relay_auth.rs | 5++++-
Mcrates/event/src/relay_document.rs | 40++++++++++++++++++++++++++++++++--------
Mcrates/event/src/report.rs | 5++++-
Mcrates/event/src/repost.rs | 10++++++++--
Mcrates/event/src/resource_area.rs | 15++++++++++++---
Mcrates/event/src/resource_cap.rs | 10++++++++--
Mcrates/event/src/seal.rs | 5++++-
Mcrates/event/src/social.rs | 76+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
Mcrates/event/src/trade_validation.rs | 15++++++++++++---
Acrates/event/src/wire.rs | 883+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
49 files changed, 1732 insertions(+), 284 deletions(-)

diff --git a/contracts/conformance/vectors/event/nip01_wire.v1.json b/contracts/conformance/vectors/event/nip01_wire.v1.json @@ -0,0 +1,41 @@ +{ + "suite": "event", + "contract_version": "0.1.0", + "vectors": [ + { + "id": "nip01_wire_profile_minimal_001", + "kind": "event.nip01_wire.valid", + "input": { + "raw_json": "{\"id\":\"2a15e33622a155ae231b28bebe390869e67a0e228f77ecfcd652b1ce180a9dde\",\"pubkey\":\"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\",\"created_at\":1700000001,\"kind\":0,\"tags\":[],\"content\":\"{}\",\"sig\":\"dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd\",\"client\":\"radroots-test\"}" + }, + "expected": { + "canonical_id_preimage": "[0,\"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\",1700000001,0,[],\"{}\"]", + "computed_event_id": "2a15e33622a155ae231b28bebe390869e67a0e228f77ecfcd652b1ce180a9dde", + "extra_fields": [ + "client" + ] + } + }, + { + "id": "nip01_wire_escape_002", + "kind": "event.nip01_wire.valid", + "input": { + "raw_json": "{\"id\":\"bb2ca7d7e3718fb17d70263ff0dc4e8bf4e63b9f425b2e32914a20ca9adc7199\",\"pubkey\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"created_at\":10,\"kind\":1,\"tags\":[[\"t\",\"line\\nsoil\"]],\"content\":\"\\\"\\\\\\n\\r\\t\\b\\f\\u0001\",\"sig\":\"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\"}" + }, + "expected": { + "canonical_id_preimage": "[0,\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",10,1,[[\"t\",\"line\\nsoil\"]],\"\\\"\\\\\\n\\r\\t\\b\\f\\u0001\"]", + "computed_event_id": "bb2ca7d7e3718fb17d70263ff0dc4e8bf4e63b9f425b2e32914a20ca9adc7199" + } + }, + { + "id": "nip01_wire_id_mismatch_003", + "kind": "event.nip01_wire.invalid", + "input": { + "raw_json": "{\"id\":\"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\",\"pubkey\":\"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\",\"created_at\":1700000001,\"kind\":0,\"tags\":[],\"content\":\"{}\",\"sig\":\"dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd\"}" + }, + "expected": { + "error": "event_id_mismatch" + } + } + ] +} diff --git a/crates/event/Cargo.toml b/crates/event/Cargo.toml @@ -40,3 +40,11 @@ serde_json = { workspace = true, default-features = false, features = [ sha2 = { workspace = true, default-features = false } [dev-dependencies] +radroots_core = { workspace = true, default-features = false, features = [ + "serde", + "std", +] } +serde = { workspace = true, default-features = false, features = [ + "alloc", + "derive", +] } diff --git a/crates/event/src/account.rs b/crates/event/src/account.rs @@ -7,7 +7,10 @@ pub const KIND_ACCOUNT_CLAIM: u32 = KIND_ACCOUNT_CLAIM_EVENT; #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug)] pub struct RadrootsAccountClaim { pub username: String, diff --git a/crates/event/src/app_data.rs b/crates/event/src/app_data.rs @@ -9,7 +9,10 @@ pub const KIND_APP_DATA: u32 = KIND_APP_DATA_EVENT; #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug)] pub struct RadrootsAppData { pub d_tag: String, diff --git a/crates/event/src/article.rs b/crates/event/src/article.rs @@ -3,7 +3,10 @@ use alloc::{string::String, vec::Vec}; use crate::social::{RadrootsSocialFarmAnchor, RadrootsSocialLocation}; -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug)] pub struct RadrootsArticle { pub d_tag: String, diff --git a/crates/event/src/calendar.rs b/crates/event/src/calendar.rs @@ -6,7 +6,10 @@ use crate::social::{ RadrootsCalendarParticipant, RadrootsSocialLocation, RadrootsSocialTarget, }; -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug)] pub struct RadrootsCalendar { pub d_tag: String, @@ -29,7 +32,10 @@ pub struct RadrootsCalendar { pub image: Option<String>, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug)] pub struct RadrootsCalendarDateEvent { pub d_tag: String, @@ -72,7 +78,10 @@ pub struct RadrootsCalendarDateEvent { pub participants: Option<Vec<RadrootsCalendarParticipant>>, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug)] pub struct RadrootsCalendarTimeEvent { pub d_tag: String, @@ -125,7 +134,10 @@ pub struct RadrootsCalendarTimeEvent { pub participants: Option<Vec<RadrootsCalendarParticipant>>, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug)] pub struct RadrootsCalendarEventRsvp { pub d_tag: String, diff --git a/crates/event/src/comment.rs b/crates/event/src/comment.rs @@ -3,7 +3,10 @@ use crate::social::RadrootsSocialTarget; #[cfg(not(feature = "std"))] use alloc::string::String; -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[derive(Clone, Debug)] diff --git a/crates/event/src/coop.rs b/crates/event/src/coop.rs @@ -7,7 +7,10 @@ use alloc::{string::String, vec::Vec}; #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug)] pub struct RadrootsCoop { pub d_tag: String, @@ -22,7 +25,10 @@ pub struct RadrootsCoop { #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug)] pub struct RadrootsCoopRef { pub pubkey: String, @@ -31,7 +37,10 @@ pub struct RadrootsCoopRef { #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug)] pub struct RadrootsCoopLocation { pub primary: Option<String>, diff --git a/crates/event/src/document.rs b/crates/event/src/document.rs @@ -5,7 +5,10 @@ use alloc::{string::String, vec::Vec}; #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug)] pub struct RadrootsDocumentSubject { pub pubkey: String, @@ -14,7 +17,10 @@ pub struct RadrootsDocumentSubject { #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug)] pub struct RadrootsDocument { pub d_tag: String, diff --git a/crates/event/src/draft.rs b/crates/event/src/draft.rs @@ -1,18 +1,10 @@ #![forbid(unsafe_code)] -#[cfg(not(feature = "std"))] -use alloc::{ - borrow::ToOwned, - string::{String, ToString}, - vec::Vec, -}; +#[cfg(all(not(feature = "std"), not(test)))] +use alloc::{borrow::ToOwned, string::String, vec::Vec}; -#[cfg(feature = "std")] -use std::{ - borrow::ToOwned, - string::{String, ToString}, - vec::Vec, -}; +#[cfg(any(feature = "std", test))] +use std::{borrow::ToOwned, string::String, vec::Vec}; use crate::RadrootsEventEnvelope; use crate::contract::{ @@ -22,8 +14,11 @@ use crate::contract::{ use crate::ids::{ RadrootsEventId, RadrootsEventSignature, RadrootsIdParseError, RadrootsPublicKey, }; +use crate::wire::{ + RadrootsCanonicalEventIdError, canonical_nip01_event_id_preimage, + compute_canonical_nip01_event_id, +}; use core::fmt; -use sha2::{Digest, Sha256}; #[derive(Clone, Debug, PartialEq, Eq)] pub enum RadrootsDraftError { @@ -66,7 +61,7 @@ pub enum RadrootsDraftError { computed_event_id: String, }, IdParse(RadrootsIdParseError), - JsonString(String), + CanonicalEventId(RadrootsCanonicalEventIdError), } impl fmt::Display for RadrootsDraftError { @@ -138,7 +133,7 @@ impl fmt::Display for RadrootsDraftError { "signed event computed id mismatch: expected {expected_event_id}, computed {computed_event_id}" ), Self::IdParse(error) => write!(f, "{error}"), - Self::JsonString(error) => write!(f, "json string serialization failed: {error}"), + Self::CanonicalEventId(error) => write!(f, "{error}"), } } } @@ -152,13 +147,19 @@ impl From<RadrootsIdParseError> for RadrootsDraftError { } } -impl From<serde_json::Error> for RadrootsDraftError { - fn from(value: serde_json::Error) -> Self { - Self::JsonString(value.to_string()) +impl From<RadrootsCanonicalEventIdError> for RadrootsDraftError { + fn from(value: RadrootsCanonicalEventIdError) -> Self { + match value { + RadrootsCanonicalEventIdError::InvalidPubkey(error) => Self::IdParse(error), + error => Self::CanonicalEventId(error), + } } } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug, PartialEq, Eq)] pub struct RadrootsEventDraft { pub contract_id: String, @@ -226,7 +227,10 @@ impl RadrootsEventDraft { } } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug, PartialEq, Eq)] pub struct RadrootsSignedEventParts { pub id: String, @@ -239,7 +243,10 @@ pub struct RadrootsSignedEventParts { pub raw_json: String, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug, PartialEq, Eq)] pub struct RadrootsSignedEvent { pub id: String, @@ -350,11 +357,14 @@ pub fn compute_nip01_event_id( tags: &[Vec<String>], content: &str, ) -> Result<RadrootsEventId, RadrootsDraftError> { - let pubkey = RadrootsPublicKey::parse(pubkey)?; - let preimage = nip01_event_id_preimage(pubkey.as_str(), created_at, kind, tags, content)?; - let digest = Sha256::digest(preimage.as_bytes()); - let event_id = hex::encode(digest); - Ok(RadrootsEventId::parse(event_id)?) + RadrootsPublicKey::parse(pubkey)?; + Ok(compute_canonical_nip01_event_id( + pubkey, + u64::from(created_at), + kind, + tags, + content, + )?) } pub fn nip01_event_id_preimage( @@ -364,36 +374,13 @@ pub fn nip01_event_id_preimage( tags: &[Vec<String>], content: &str, ) -> Result<String, RadrootsDraftError> { - let mut preimage = String::new(); - preimage.push_str("[0,"); - push_json_string(&mut preimage, pubkey)?; - preimage.push(','); - preimage.push_str(created_at.to_string().as_str()); - preimage.push(','); - preimage.push_str(kind.to_string().as_str()); - preimage.push_str(",["); - for (tag_index, tag) in tags.iter().enumerate() { - if tag_index > 0 { - preimage.push(','); - } - preimage.push('['); - for (value_index, value) in tag.iter().enumerate() { - if value_index > 0 { - preimage.push(','); - } - push_json_string(&mut preimage, value)?; - } - preimage.push(']'); - } - preimage.push_str("],"); - push_json_string(&mut preimage, content)?; - preimage.push(']'); - Ok(preimage) -} - -fn push_json_string(target: &mut String, value: &str) -> Result<(), RadrootsDraftError> { - target.push_str(serde_json::to_string(value)?.as_str()); - Ok(()) + Ok(canonical_nip01_event_id_preimage( + pubkey, + u64::from(created_at), + kind, + tags, + content, + )?) } #[cfg(test)] @@ -850,18 +837,25 @@ mod tests { computed_event_id: hex_64('f'), }, RadrootsDraftError::from(RadrootsIdParseError::Empty), + RadrootsDraftError::CanonicalEventId( + RadrootsCanonicalEventIdError::InvalidComputedEventId( + RadrootsIdParseError::InvalidFormat, + ), + ), ]; for error in errors { assert!(!error.to_string().is_empty()); } - let json_error = serde_json::from_str::<String>("{").expect_err("json error"); - let error = RadrootsDraftError::from(json_error); assert!( - error - .to_string() - .contains("json string serialization failed") + RadrootsDraftError::CanonicalEventId( + RadrootsCanonicalEventIdError::InvalidComputedEventId( + RadrootsIdParseError::InvalidFormat, + ), + ) + .to_string() + .contains("canonical event id digest") ); } diff --git a/crates/event/src/farm.rs b/crates/event/src/farm.rs @@ -3,7 +3,10 @@ use alloc::{string::String, vec::Vec}; #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug)] pub struct RadrootsFarm { pub d_tag: String, @@ -18,7 +21,10 @@ pub struct RadrootsFarm { #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug, Default)] pub struct RadrootsFarmRef { pub pubkey: String, @@ -27,7 +33,10 @@ pub struct RadrootsFarmRef { #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug)] pub struct RadrootsFarmPublicLocation { pub primary: String, diff --git a/crates/event/src/farm_crdt.rs b/crates/event/src/farm_crdt.rs @@ -13,7 +13,10 @@ pub const KIND_FARM_CRDT_CHANGE: u32 = KIND_FARM_CRDT_CHANGE_EVENT; pub const RADROOTS_FARM_CRDT_CHANGE_SCHEMA: &str = "radroots.farm.crdt.change.v1"; pub const RADROOTS_FARM_CRDT_TAG: &str = "radroots:farm:crdt"; -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug, PartialEq, Eq)] pub struct RadrootsFarmCrdtChange { pub schema: String, @@ -33,8 +36,11 @@ pub struct RadrootsFarmCrdtChange { pub app_version: Option<String>, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(from = "String", into = "String"))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] +#[cfg_attr(any(feature = "serde", test), serde(from = "String", into = "String"))] #[derive(Clone, Debug, PartialEq, Eq)] pub enum RadrootsFarmCrdtDocumentKind { FarmMembership, @@ -55,7 +61,10 @@ pub enum RadrootsFarmCrdtDocumentKind { Other { value: String }, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum RadrootsCrdtBackend { Automerge, @@ -63,8 +72,11 @@ pub enum RadrootsCrdtBackend { Loro, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(from = "String", into = "String"))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] +#[cfg_attr(any(feature = "serde", test), serde(from = "String", into = "String"))] #[derive(Clone, Debug, PartialEq, Eq)] pub enum RadrootsFarmSemanticKind { FarmTaskCreate, diff --git a/crates/event/src/farm_file.rs b/crates/event/src/farm_file.rs @@ -9,7 +9,10 @@ use alloc::{string::String, vec::Vec}; pub const KIND_FARM_FILE_METADATA: u32 = KIND_FARM_FILE_METADATA_EVENT; -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug, PartialEq, Eq)] pub struct RadrootsFarmFileMetadata { pub d_tag: String, @@ -31,14 +34,20 @@ pub struct RadrootsFarmFileMetadata { pub fallbacks: Vec<String>, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct RadrootsFarmFileDimensions { pub w: u32, pub h: u32, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug, PartialEq, Eq)] pub struct RadrootsFarmFileSource { pub url: String, diff --git a/crates/event/src/farm_workspace.rs b/crates/event/src/farm_workspace.rs @@ -11,7 +11,10 @@ pub const RADROOTS_FARM_WORKSPACE_SCHEMA: &str = "radroots.farm.workspace.v1"; pub const RADROOTS_FARM_WORKSPACE_PROTOCOL_VERSION: &str = "field-nostr-v1"; pub const RADROOTS_FARM_WORKSPACE_TAG: &str = "radroots:farm:workspace"; -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug)] pub struct RadrootsFarmWorkspaceManifest { pub d_tag: String, @@ -28,21 +31,30 @@ pub struct RadrootsFarmWorkspaceManifest { pub updated_at_ms: Option<u64>, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct RadrootsFarmWorkspaceRef { pub pubkey: String, pub d_tag: String, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug, PartialEq, Eq)] pub struct RadrootsFarmWorkspaceRelay { pub url: String, pub mode: RadrootsFarmWorkspaceRelayMode, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum RadrootsFarmWorkspaceRelayMode { Read, @@ -50,7 +62,10 @@ pub enum RadrootsFarmWorkspaceRelayMode { ReadWrite, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug, PartialEq, Eq)] pub struct RadrootsFarmWorkspaceMediaServer { pub url: String, diff --git a/crates/event/src/file_metadata.rs b/crates/event/src/file_metadata.rs @@ -3,7 +3,10 @@ use alloc::{string::String, vec::Vec}; use crate::social::{RadrootsSocialMediaDimensions, RadrootsSocialMediaThumbnail}; -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug)] pub struct RadrootsFileMetadata { pub url: String, diff --git a/crates/event/src/follow.rs b/crates/event/src/follow.rs @@ -3,7 +3,10 @@ use alloc::{string::String, vec::Vec}; #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug)] pub struct RadrootsFollow { pub list: Vec<RadrootsFollowProfile>, @@ -11,7 +14,10 @@ pub struct RadrootsFollow { #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug)] pub struct RadrootsFollowProfile { pub published_at: u32, diff --git a/crates/event/src/gcs.rs b/crates/event/src/gcs.rs @@ -3,27 +3,36 @@ use alloc::{string::String, vec::Vec}; #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug)] pub struct RadrootsGeoJsonPoint { - #[cfg_attr(feature = "serde", serde(rename = "type"))] + #[cfg_attr(any(feature = "serde", test), serde(rename = "type"))] pub r#type: String, pub coordinates: [f64; 2], } #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug)] pub struct RadrootsGeoJsonPolygon { - #[cfg_attr(feature = "serde", serde(rename = "type"))] + #[cfg_attr(any(feature = "serde", test), serde(rename = "type"))] pub r#type: String, pub coordinates: Vec<Vec<[f64; 2]>>, } #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug)] pub struct RadrootsGcsLocation { pub lat: f64, diff --git a/crates/event/src/geochat.rs b/crates/event/src/geochat.rs @@ -5,7 +5,10 @@ use alloc::string::String; #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug)] pub struct RadrootsGeoChat { pub geohash: String, diff --git a/crates/event/src/gift_wrap.rs b/crates/event/src/gift_wrap.rs @@ -5,7 +5,10 @@ use alloc::string::String; #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug)] pub struct RadrootsGiftWrap { pub recipient: RadrootsGiftWrapRecipient, @@ -15,7 +18,10 @@ pub struct RadrootsGiftWrap { #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug)] pub struct RadrootsGiftWrapRecipient { pub public_key: String, diff --git a/crates/event/src/group.rs b/crates/event/src/group.rs @@ -33,7 +33,10 @@ pub const KIND_GROUP_ADMINS: u32 = KIND_GROUP_ADMINS_EVENT; pub const KIND_GROUP_MEMBERS: u32 = KIND_GROUP_MEMBERS_EVENT; pub const KIND_GROUP_ROLES: u32 = KIND_GROUP_ROLES_EVENT; -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug, PartialEq, Eq)] pub struct RadrootsGroupPutUser { pub group_id: String, @@ -42,7 +45,10 @@ pub struct RadrootsGroupPutUser { pub roles: Vec<String>, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug, PartialEq, Eq)] pub struct RadrootsGroupRemoveUser { pub group_id: String, @@ -50,7 +56,10 @@ pub struct RadrootsGroupRemoveUser { pub pubkey: String, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug, PartialEq, Eq)] pub struct RadrootsGroupCreateGroup { pub group_id: String, @@ -58,7 +67,10 @@ pub struct RadrootsGroupCreateGroup { pub metadata: RadrootsGroupEditableMetadata, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug, PartialEq, Eq)] pub struct RadrootsGroupEditMetadata { pub group_id: String, @@ -66,14 +78,20 @@ pub struct RadrootsGroupEditMetadata { pub metadata: RadrootsGroupEditableMetadata, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug, PartialEq, Eq)] pub struct RadrootsGroupDeleteGroup { pub group_id: String, pub message: Option<String>, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug, PartialEq, Eq)] pub struct RadrootsGroupDeleteEvent { pub group_id: String, @@ -81,7 +99,10 @@ pub struct RadrootsGroupDeleteEvent { pub event_id: String, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug, PartialEq, Eq)] pub struct RadrootsGroupCreateInvite { pub group_id: String, @@ -89,7 +110,10 @@ pub struct RadrootsGroupCreateInvite { pub code: String, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug, PartialEq, Eq)] pub struct RadrootsGroupJoinRequest { pub group_id: String, @@ -97,21 +121,30 @@ pub struct RadrootsGroupJoinRequest { pub code: Option<String>, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug, PartialEq, Eq)] pub struct RadrootsGroupLeaveRequest { pub group_id: String, pub message: Option<String>, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug, PartialEq, Eq)] pub struct RadrootsGroupMetadata { pub d_tag: String, pub metadata: RadrootsGroupEditableMetadata, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug, PartialEq, Eq)] pub struct RadrootsGroupAdmins { pub d_tag: String, @@ -119,7 +152,10 @@ pub struct RadrootsGroupAdmins { pub admins: Vec<RadrootsGroupUserRef>, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug, PartialEq, Eq)] pub struct RadrootsGroupMembers { pub d_tag: String, @@ -127,7 +163,10 @@ pub struct RadrootsGroupMembers { pub members: Vec<RadrootsGroupUserRef>, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug, PartialEq, Eq)] pub struct RadrootsGroupRoles { pub d_tag: String, @@ -135,7 +174,10 @@ pub struct RadrootsGroupRoles { pub roles: Vec<RadrootsGroupRole>, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct RadrootsGroupEditableMetadata { pub name: Option<String>, @@ -148,14 +190,20 @@ pub struct RadrootsGroupEditableMetadata { pub supported_kinds: Option<Vec<u32>>, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug, PartialEq, Eq)] pub struct RadrootsGroupUserRef { pub pubkey: String, pub roles: Vec<String>, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug, PartialEq, Eq)] pub struct RadrootsGroupRole { pub name: String, diff --git a/crates/event/src/http_auth.rs b/crates/event/src/http_auth.rs @@ -7,7 +7,10 @@ use alloc::string::String; pub const KIND_HTTP_AUTH: u32 = KIND_HTTP_AUTH_EVENT; -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug, PartialEq, Eq)] pub struct RadrootsHttpAuth { pub url: String, diff --git a/crates/event/src/ids.rs b/crates/event/src/ids.rs @@ -150,7 +150,7 @@ macro_rules! validated_string_id { } } - #[cfg(feature = "serde")] + #[cfg(any(feature = "serde", test))] impl serde::Serialize for $name { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where @@ -160,7 +160,7 @@ macro_rules! validated_string_id { } } - #[cfg(feature = "serde")] + #[cfg(any(feature = "serde", test))] impl<'de> serde::Deserialize<'de> for $name { fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where @@ -378,7 +378,7 @@ mod tests { assert_eq!(converted, value); assert_eq!(id.into_string(), value.to_owned()); - #[cfg(feature = "serde")] + #[cfg(any(feature = "serde", test))] { let id = <$ty>::parse(value).expect("parse"); let encoded = serde_json::to_string(&id).expect("serialize"); diff --git a/crates/event/src/job.rs b/crates/event/src/job.rs @@ -3,41 +3,50 @@ use alloc::string::String; #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] +#[cfg_attr(any(feature = "serde", test), serde(rename_all = "snake_case"))] #[derive(Clone, Debug, PartialEq, Eq, Copy)] pub enum JobInputType { - #[cfg_attr(feature = "serde", serde(rename = "url"))] + #[cfg_attr(any(feature = "serde", test), serde(rename = "url"))] Url, - #[cfg_attr(feature = "serde", serde(rename = "event"))] + #[cfg_attr(any(feature = "serde", test), serde(rename = "event"))] Event, - #[cfg_attr(feature = "serde", serde(rename = "job"))] + #[cfg_attr(any(feature = "serde", test), serde(rename = "job"))] Job, - #[cfg_attr(feature = "serde", serde(rename = "text"))] + #[cfg_attr(any(feature = "serde", test), serde(rename = "text"))] Text, } #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] +#[cfg_attr(any(feature = "serde", test), serde(rename_all = "snake_case"))] #[derive(Clone, Debug, PartialEq, Eq, Copy)] pub enum JobFeedbackStatus { - #[cfg_attr(feature = "serde", serde(rename = "payment_required"))] + #[cfg_attr(any(feature = "serde", test), serde(rename = "payment_required"))] PaymentRequired, - #[cfg_attr(feature = "serde", serde(rename = "processing"))] + #[cfg_attr(any(feature = "serde", test), serde(rename = "processing"))] Processing, - #[cfg_attr(feature = "serde", serde(rename = "error"))] + #[cfg_attr(any(feature = "serde", test), serde(rename = "error"))] Error, - #[cfg_attr(feature = "serde", serde(rename = "success"))] + #[cfg_attr(any(feature = "serde", test), serde(rename = "success"))] Success, - #[cfg_attr(feature = "serde", serde(rename = "partial"))] + #[cfg_attr(any(feature = "serde", test), serde(rename = "partial"))] Partial, } #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug, PartialEq, Eq)] pub struct JobPaymentRequest { pub amount_sat: u32, diff --git a/crates/event/src/job_feedback.rs b/crates/event/src/job_feedback.rs @@ -8,7 +8,10 @@ use alloc::string::String; #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug, PartialEq, Eq)] pub struct RadrootsJobFeedback { pub kind: u16, diff --git a/crates/event/src/job_request.rs b/crates/event/src/job_request.rs @@ -5,7 +5,10 @@ use alloc::{string::String, vec::Vec}; #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug, PartialEq, Eq)] pub struct RadrootsJobInput { pub data: String, @@ -16,7 +19,10 @@ pub struct RadrootsJobInput { #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug, PartialEq, Eq)] pub struct RadrootsJobParam { pub key: String, @@ -25,7 +31,10 @@ pub struct RadrootsJobParam { #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug, PartialEq, Eq)] pub struct RadrootsJobRequest { pub kind: u16, diff --git a/crates/event/src/job_result.rs b/crates/event/src/job_result.rs @@ -5,7 +5,10 @@ use alloc::{string::String, vec::Vec}; #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug, PartialEq, Eq)] pub struct RadrootsJobResult { pub kind: u16, diff --git a/crates/event/src/knowledge.rs b/crates/event/src/knowledge.rs @@ -524,7 +524,10 @@ pub fn validate_contribution_attestation( validate_event_refs(&attestation.evidence_refs, "evidence_refs") } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[derive(Clone, Debug, PartialEq, Eq)] @@ -535,7 +538,10 @@ pub struct RadrootsAddressableRef { pub relays: Vec<String>, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[derive(Clone, Debug, PartialEq, Eq)] @@ -546,7 +552,10 @@ pub struct RadrootsRightsAssertion { pub url: Option<String>, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[derive(Clone, Debug, PartialEq, Eq)] @@ -561,7 +570,10 @@ pub struct RadrootsWikiArticle { pub deferred_to: Option<RadrootsWikiArticleVersionRef>, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[derive(Clone, Debug, PartialEq, Eq)] @@ -570,7 +582,10 @@ pub struct RadrootsWikiArticleVersionRef { pub address_ref: RadrootsAddressableRef, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[derive(Clone, Debug, PartialEq, Eq)] @@ -579,7 +594,10 @@ pub struct RadrootsWikiRedirect { pub target: RadrootsAddressableRef, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[derive(Clone, Debug, PartialEq, Eq)] @@ -591,7 +609,10 @@ pub struct RadrootsWikiMergeRequest { pub explanation: Option<String>, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[derive(Clone, Debug, PartialEq, Eq)] @@ -612,7 +633,10 @@ pub struct RadrootsKnowledgeSource { pub summary: Option<String>, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[derive(Clone, Debug, PartialEq, Eq)] @@ -628,7 +652,10 @@ pub struct RadrootsKnowledgeClaim { pub supersedes: Vec<RadrootsEventRef>, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[derive(Clone, Debug, PartialEq, Eq)] @@ -642,7 +669,10 @@ pub struct RadrootsKnowledgeCitationSpan { pub chunk_id: Option<String>, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[derive(Clone, Debug, PartialEq, Eq)] @@ -654,7 +684,10 @@ pub struct RadrootsKnowledgeNodeRef { pub label: Option<String>, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[derive(Clone, Debug, PartialEq, Eq)] @@ -669,7 +702,10 @@ pub struct RadrootsKnowledgeRelation { pub supersedes: Vec<RadrootsEventRef>, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[derive(Clone, Debug, PartialEq, Eq)] @@ -684,7 +720,10 @@ pub struct RadrootsKnowledgeReview { pub evidence_refs: Vec<RadrootsEventRef>, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[derive(Clone, Debug, PartialEq, Eq)] @@ -697,10 +736,13 @@ pub struct RadrootsKnowledgeReviewTarget { pub review_scope: RadrootsKnowledgeReviewScope, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] +#[cfg_attr(any(feature = "serde", test), serde(rename_all = "snake_case"))] #[derive(Clone, Debug, PartialEq, Eq)] pub enum RadrootsKnowledgeReviewScope { SpecificVersion, @@ -708,7 +750,10 @@ pub enum RadrootsKnowledgeReviewScope { PolicyLatest, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[derive(Clone, Debug, PartialEq, Eq)] @@ -718,7 +763,10 @@ pub struct RadrootsKnowledgeReviewScore { pub note: Option<String>, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[derive(Clone, Debug, PartialEq, Eq)] @@ -735,7 +783,10 @@ pub struct RadrootsKnowledgeFieldReport { pub limitations: Vec<String>, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[derive(Clone, Debug, PartialEq, Eq)] @@ -747,10 +798,13 @@ pub struct RadrootsKnowledgeFieldContext { pub context_tags: Vec<String>, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] +#[cfg_attr(any(feature = "serde", test), serde(rename_all = "snake_case"))] #[derive(Clone, Debug, PartialEq, Eq)] pub enum RadrootsKnowledgeLocationPrecision { None, @@ -761,7 +815,10 @@ pub enum RadrootsKnowledgeLocationPrecision { ExactPrivateReference, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[derive(Clone, Debug, PartialEq, Eq)] @@ -772,7 +829,10 @@ pub struct RadrootsKnowledgeLocation { pub geohash: Option<String>, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[derive(Clone, Debug, PartialEq, Eq)] @@ -783,7 +843,10 @@ pub struct RadrootsKnowledgeObservation { pub values: Vec<RadrootsKnowledgeObservationValue>, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[derive(Clone, Debug, PartialEq, Eq)] @@ -793,7 +856,10 @@ pub struct RadrootsKnowledgeObservationValue { pub unit: Option<String>, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[derive(Clone, Debug, PartialEq, Eq)] @@ -809,7 +875,10 @@ pub struct RadrootsEvidenceBounty { pub closes_at: Option<String>, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[derive(Clone, Debug, PartialEq, Eq)] @@ -824,7 +893,10 @@ pub struct RadrootsKnowledgeChangeProposal { pub supersedes: Vec<RadrootsEventRef>, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[derive(Clone, Debug, PartialEq, Eq)] diff --git a/crates/event/src/lib.rs b/crates/event/src/lib.rs @@ -1,4 +1,4 @@ -#![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(all(not(feature = "std"), not(test)), no_std)] #![forbid(unsafe_code)] #[cfg(not(feature = "std"))] extern crate alloc; @@ -61,8 +61,17 @@ pub mod seal; pub mod social; pub mod tags; pub mod trade_validation; +pub mod wire; -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub use wire::{ + RadrootsCanonicalEventIdError, RadrootsEventWireError, RadrootsEventWireLimits, + RadrootsNip01EventWire, canonical_nip01_event_id_preimage, compute_canonical_nip01_event_id, +}; + +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[derive(Clone, Debug, PartialEq, Eq)] @@ -76,7 +85,10 @@ pub struct RadrootsEventEnvelope { pub sig: String, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[derive(Clone, Debug, PartialEq, Eq)] @@ -88,7 +100,10 @@ pub struct RadrootsEventRef { pub relays: Option<Vec<String>>, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[derive(Clone, Debug, PartialEq, Eq)] diff --git a/crates/event/src/list.rs b/crates/event/src/list.rs @@ -3,7 +3,10 @@ use alloc::{string::String, vec::Vec}; #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug)] pub struct RadrootsList { pub content: String, @@ -12,7 +15,10 @@ pub struct RadrootsList { #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug)] pub struct RadrootsListEntry { pub tag: String, diff --git a/crates/event/src/list_set.rs b/crates/event/src/list_set.rs @@ -5,7 +5,10 @@ use alloc::{string::String, vec::Vec}; #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug)] pub struct RadrootsListSet { pub d_tag: String, diff --git a/crates/event/src/listing.rs b/crates/event/src/listing.rs @@ -11,7 +11,10 @@ use crate::resource_area::RadrootsResourceAreaRef; #[cfg(not(feature = "std"))] use alloc::{string::String, vec::Vec}; -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[cfg_attr( @@ -31,7 +34,10 @@ pub enum RadrootsListingAvailability { }, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[cfg_attr( @@ -45,7 +51,10 @@ pub enum RadrootsListingStatus { Other { value: String }, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[cfg_attr( @@ -60,7 +69,10 @@ pub enum RadrootsListingDeliveryMethod { Other { method: String }, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[derive(Clone, Debug)] @@ -72,7 +84,7 @@ pub struct RadrootsListing { serde(default, skip_serializing_if = "Option::is_none") )] pub published_at: Option<u64>, - #[cfg_attr(feature = "serde", serde(default))] + #[cfg_attr(any(feature = "serde", test), serde(default))] pub farm: RadrootsFarmRef, pub product: RadrootsListingProduct, pub primary_bin_id: RadrootsInventoryBinId, @@ -87,7 +99,10 @@ pub struct RadrootsListing { pub images: Option<Vec<RadrootsListingImage>>, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[derive(Clone, Debug)] @@ -109,7 +124,10 @@ pub const RADROOTS_LISTING_PRODUCT_TAG_KEYS: [&str; 9] = [ pub struct RadrootsListingProductTagKeys; -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[derive(Clone, Debug)] @@ -126,7 +144,10 @@ pub struct RadrootsListingBin { #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug)] pub struct RadrootsListingPublicLocation { pub primary: String, @@ -136,7 +157,10 @@ pub struct RadrootsListingPublicLocation { pub geohash: String, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[derive(Clone, Debug)] @@ -145,7 +169,10 @@ pub struct RadrootsListingImage { pub size: Option<RadrootsListingImageSize>, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[derive(Clone, Debug)] pub struct RadrootsListingImageSize { diff --git a/crates/event/src/message.rs b/crates/event/src/message.rs @@ -7,7 +7,10 @@ use alloc::{string::String, vec::Vec}; #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug)] pub struct RadrootsMessage { pub recipients: Vec<RadrootsMessageRecipient>, @@ -18,7 +21,10 @@ pub struct RadrootsMessage { #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug)] pub struct RadrootsMessageRecipient { pub public_key: String, diff --git a/crates/event/src/message_file.rs b/crates/event/src/message_file.rs @@ -8,7 +8,10 @@ use alloc::{string::String, vec::Vec}; #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug)] pub struct RadrootsMessageFile { pub recipients: Vec<RadrootsMessageRecipient>, @@ -31,7 +34,10 @@ pub struct RadrootsMessageFile { #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct RadrootsMessageFileDimensions { pub w: u32, diff --git a/crates/event/src/order.rs b/crates/event/src/order.rs @@ -21,7 +21,10 @@ use radroots_core::{RadrootsCoreCurrency, RadrootsCoreDecimal, RadrootsCoreMoney pub const RADROOTS_COMMERCIAL_LISTING_DOMAIN: &str = "trade:listing"; pub const RADROOTS_ORDER_ENVELOPE_VERSION: u16 = 1; -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[derive(Clone, Debug, PartialEq, Eq)] @@ -166,7 +169,10 @@ impl RadrootsOrderEconomics { #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[cfg_attr(feature = "dto-bindgen", dto(ts(name = "RadrootsOrderRequest")))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug, PartialEq, Eq)] pub struct RadrootsOrderRequest { pub order_id: RadrootsOrderId, @@ -195,7 +201,10 @@ impl RadrootsOrderRequest { feature = "dto-bindgen", dto(ts(name = "RadrootsOrderRevisionProposal")) )] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug, PartialEq, Eq)] pub struct RadrootsOrderRevisionProposal { pub revision_id: RadrootsOrderRevisionId, @@ -226,10 +235,16 @@ impl RadrootsOrderRevisionProposal { } } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case", tag = "decision"))] +#[cfg_attr( + any(feature = "serde", test), + serde(rename_all = "snake_case", tag = "decision") +)] #[derive(Clone, Debug, PartialEq, Eq)] pub enum RadrootsOrderRevisionOutcome { Accepted, @@ -251,7 +266,10 @@ impl RadrootsOrderRevisionOutcome { feature = "dto-bindgen", dto(ts(name = "RadrootsOrderRevisionDecision")) )] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug, PartialEq, Eq)] pub struct RadrootsOrderRevisionDecision { pub revision_id: RadrootsOrderRevisionId, @@ -283,7 +301,10 @@ impl RadrootsOrderRevisionDecision { feature = "dto-bindgen", dto(ts(name = "RadrootsOrderInventoryCommitment")) )] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug, PartialEq, Eq)] pub struct RadrootsOrderInventoryCommitment { pub bin_id: RadrootsInventoryBinId, @@ -296,15 +317,21 @@ pub struct RadrootsOrderInventoryCommitment { feature = "dto-bindgen", dto(ts(name = "RadrootsOrderDecisionOutcome")) )] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case", tag = "decision"))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] +#[cfg_attr( + any(feature = "serde", test), + serde(rename_all = "snake_case", tag = "decision") +)] #[derive(Clone, Debug, PartialEq, Eq)] pub enum RadrootsOrderDecisionOutcome { - #[cfg_attr(feature = "serde", serde(rename = "accepted"))] + #[cfg_attr(any(feature = "serde", test), serde(rename = "accepted"))] Accepted { inventory_commitments: Vec<RadrootsOrderInventoryCommitment>, }, - #[cfg_attr(feature = "serde", serde(rename = "declined"))] + #[cfg_attr(any(feature = "serde", test), serde(rename = "declined"))] Declined { reason: String }, } @@ -322,7 +349,10 @@ impl RadrootsOrderDecisionOutcome { #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[cfg_attr(feature = "dto-bindgen", dto(ts(name = "RadrootsOrderDecision")))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug, PartialEq, Eq)] pub struct RadrootsOrderDecision { pub order_id: RadrootsOrderId, @@ -345,7 +375,10 @@ impl RadrootsOrderDecision { #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[cfg_attr(feature = "dto-bindgen", dto(ts(name = "RadrootsOrderCancellation")))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug, PartialEq, Eq)] pub struct RadrootsOrderCancellation { pub order_id: RadrootsOrderId, @@ -368,29 +401,41 @@ impl RadrootsOrderCancellation { #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[cfg_attr(feature = "dto-bindgen", dto(ts(name = "RadrootsCommercialDomain")))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] +#[cfg_attr(any(feature = "serde", test), serde(rename_all = "snake_case"))] #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum RadrootsCommercialDomain { - #[cfg_attr(feature = "serde", serde(rename = "trade:listing"))] + #[cfg_attr(any(feature = "serde", test), serde(rename = "trade:listing"))] Listing, } #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[cfg_attr(feature = "dto-bindgen", dto(ts(name = "RadrootsOrderEventType")))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum RadrootsOrderEventType { - #[cfg_attr(feature = "serde", serde(rename = "TradeOrderRequested"))] + #[cfg_attr(any(feature = "serde", test), serde(rename = "TradeOrderRequested"))] OrderRequested, - #[cfg_attr(feature = "serde", serde(rename = "TradeOrderDecision"))] + #[cfg_attr(any(feature = "serde", test), serde(rename = "TradeOrderDecision"))] OrderDecision, - #[cfg_attr(feature = "serde", serde(rename = "TradeOrderRevisionProposed"))] + #[cfg_attr( + any(feature = "serde", test), + serde(rename = "TradeOrderRevisionProposed") + )] OrderRevisionProposed, - #[cfg_attr(feature = "serde", serde(rename = "TradeOrderRevisionDecision"))] + #[cfg_attr( + any(feature = "serde", test), + serde(rename = "TradeOrderRevisionDecision") + )] OrderRevisionDecision, - #[cfg_attr(feature = "serde", serde(rename = "TradeOrderCancelled"))] + #[cfg_attr(any(feature = "serde", test), serde(rename = "TradeOrderCancelled"))] OrderCancelled, } @@ -446,12 +491,15 @@ impl RadrootsOrderEventType { } } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug, PartialEq, Eq)] pub struct RadrootsOrderEnvelope<T> { pub version: u16, pub domain: RadrootsCommercialDomain, - #[cfg_attr(feature = "serde", serde(rename = "type"))] + #[cfg_attr(any(feature = "serde", test), serde(rename = "type"))] pub message_type: RadrootsOrderEventType, pub order_id: String, pub listing_addr: String, diff --git a/crates/event/src/order_economics.rs b/crates/event/src/order_economics.rs @@ -12,7 +12,10 @@ use crate::ids::{RadrootsInventoryBinId, RadrootsOrderQuoteId}; #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[cfg_attr(feature = "dto-bindgen", dto(ts(name = "RadrootsOrderItem")))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug, PartialEq, Eq)] pub struct RadrootsOrderItem { pub bin_id: RadrootsInventoryBinId, @@ -22,11 +25,14 @@ pub struct RadrootsOrderItem { #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[cfg_attr(feature = "dto-bindgen", dto(ts(name = "RadrootsOrderPricingBasis")))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] +#[cfg_attr(any(feature = "serde", test), serde(rename_all = "snake_case"))] #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum RadrootsOrderPricingBasis { - #[cfg_attr(feature = "serde", serde(rename = "listing_event"))] + #[cfg_attr(any(feature = "serde", test), serde(rename = "listing_event"))] ListingEvent, } @@ -36,45 +42,57 @@ pub enum RadrootsOrderPricingBasis { feature = "dto-bindgen", dto(ts(name = "RadrootsOrderEconomicLineKind")) )] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] +#[cfg_attr(any(feature = "serde", test), serde(rename_all = "snake_case"))] #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum RadrootsOrderEconomicLineKind { - #[cfg_attr(feature = "serde", serde(rename = "listing_discount"))] + #[cfg_attr(any(feature = "serde", test), serde(rename = "listing_discount"))] ListingDiscount, - #[cfg_attr(feature = "serde", serde(rename = "basket_adjustment"))] + #[cfg_attr(any(feature = "serde", test), serde(rename = "basket_adjustment"))] BasketAdjustment, - #[cfg_attr(feature = "serde", serde(rename = "revision_adjustment"))] + #[cfg_attr(any(feature = "serde", test), serde(rename = "revision_adjustment"))] RevisionAdjustment, } #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[cfg_attr(feature = "dto-bindgen", dto(ts(name = "RadrootsOrderEconomicActor")))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] +#[cfg_attr(any(feature = "serde", test), serde(rename_all = "snake_case"))] #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum RadrootsOrderEconomicActor { - #[cfg_attr(feature = "serde", serde(rename = "buyer"))] + #[cfg_attr(any(feature = "serde", test), serde(rename = "buyer"))] Buyer, - #[cfg_attr(feature = "serde", serde(rename = "seller"))] + #[cfg_attr(any(feature = "serde", test), serde(rename = "seller"))] Seller, } #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[cfg_attr(feature = "dto-bindgen", dto(ts(name = "RadrootsOrderEconomicEffect")))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] +#[cfg_attr(any(feature = "serde", test), serde(rename_all = "snake_case"))] #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum RadrootsOrderEconomicEffect { - #[cfg_attr(feature = "serde", serde(rename = "increase"))] + #[cfg_attr(any(feature = "serde", test), serde(rename = "increase"))] Increase, - #[cfg_attr(feature = "serde", serde(rename = "decrease"))] + #[cfg_attr(any(feature = "serde", test), serde(rename = "decrease"))] Decrease, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[derive(Clone, Debug, PartialEq, Eq)] @@ -91,7 +109,10 @@ pub struct RadrootsOrderEconomicItem { #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[cfg_attr(feature = "dto-bindgen", dto(ts(name = "RadrootsOrderEconomicLine")))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug, PartialEq, Eq)] pub struct RadrootsOrderEconomicLine { pub id: String, @@ -105,7 +126,10 @@ pub struct RadrootsOrderEconomicLine { #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[cfg_attr(feature = "dto-bindgen", dto(ts(name = "RadrootsOrderEconomicTotals")))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug, PartialEq, Eq)] pub struct RadrootsOrderEconomicTotals { pub subtotal: RadrootsCoreMoney, @@ -114,7 +138,10 @@ pub struct RadrootsOrderEconomicTotals { pub total: RadrootsCoreMoney, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[derive(Clone, Debug, PartialEq, Eq)] diff --git a/crates/event/src/plot.rs b/crates/event/src/plot.rs @@ -5,7 +5,10 @@ use alloc::{string::String, vec::Vec}; #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug)] pub struct RadrootsPlotRef { pub pubkey: String, @@ -14,7 +17,10 @@ pub struct RadrootsPlotRef { #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug)] pub struct RadrootsPlot { pub d_tag: String, @@ -27,7 +33,10 @@ pub struct RadrootsPlot { #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug)] pub struct RadrootsPlotLocation { pub primary: Option<String>, diff --git a/crates/event/src/post.rs b/crates/event/src/post.rs @@ -6,7 +6,10 @@ use crate::social::{ RadrootsSocialTarget, }; -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[derive(Clone, Debug)] diff --git a/crates/event/src/profile.rs b/crates/event/src/profile.rs @@ -10,19 +10,22 @@ pub const RADROOTS_PROFILE_TYPE_TAG_RADROOTSD: &str = "radroots:type:radrootsd"; #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] +#[cfg_attr(any(feature = "serde", test), serde(rename_all = "snake_case"))] #[derive(Clone, Debug, PartialEq, Eq, Copy)] pub enum RadrootsProfileType { - #[cfg_attr(feature = "serde", serde(rename = "individual"))] + #[cfg_attr(any(feature = "serde", test), serde(rename = "individual"))] Individual, - #[cfg_attr(feature = "serde", serde(rename = "farm"))] + #[cfg_attr(any(feature = "serde", test), serde(rename = "farm"))] Farm, - #[cfg_attr(feature = "serde", serde(rename = "coop"))] + #[cfg_attr(any(feature = "serde", test), serde(rename = "coop"))] Coop, - #[cfg_attr(feature = "serde", serde(rename = "any"))] + #[cfg_attr(any(feature = "serde", test), serde(rename = "any"))] Any, - #[cfg_attr(feature = "serde", serde(rename = "radrootsd"))] + #[cfg_attr(any(feature = "serde", test), serde(rename = "radrootsd"))] Radrootsd, } @@ -49,7 +52,10 @@ pub fn radroots_profile_type_from_tag_value(value: &str) -> Option<RadrootsProfi #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug)] pub struct RadrootsProfile { pub name: String, diff --git a/crates/event/src/reaction.rs b/crates/event/src/reaction.rs @@ -3,7 +3,10 @@ use crate::social::RadrootsSocialTarget; #[cfg(not(feature = "std"))] use alloc::string::String; -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[derive(Clone, Debug)] diff --git a/crates/event/src/relay_auth.rs b/crates/event/src/relay_auth.rs @@ -7,7 +7,10 @@ use alloc::string::String; pub const KIND_RELAY_AUTH: u32 = KIND_RELAY_AUTH_EVENT; -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug, PartialEq, Eq)] pub struct RadrootsRelayAuth { pub relay: String, diff --git a/crates/event/src/relay_document.rs b/crates/event/src/relay_document.rs @@ -3,21 +3,45 @@ use alloc::{string::String, vec::Vec}; #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug)] pub struct RadrootsRelayDocument { - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + #[cfg_attr( + any(feature = "serde", test), + serde(skip_serializing_if = "Option::is_none") + )] pub name: Option<String>, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + #[cfg_attr( + any(feature = "serde", test), + serde(skip_serializing_if = "Option::is_none") + )] pub description: Option<String>, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + #[cfg_attr( + any(feature = "serde", test), + serde(skip_serializing_if = "Option::is_none") + )] pub pubkey: Option<String>, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + #[cfg_attr( + any(feature = "serde", test), + serde(skip_serializing_if = "Option::is_none") + )] pub contact: Option<String>, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + #[cfg_attr( + any(feature = "serde", test), + serde(skip_serializing_if = "Option::is_none") + )] pub supported_nips: Option<Vec<u16>>, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + #[cfg_attr( + any(feature = "serde", test), + serde(skip_serializing_if = "Option::is_none") + )] pub software: Option<String>, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + #[cfg_attr( + any(feature = "serde", test), + serde(skip_serializing_if = "Option::is_none") + )] pub version: Option<String>, } diff --git a/crates/event/src/report.rs b/crates/event/src/report.rs @@ -3,7 +3,10 @@ use alloc::string::String; use crate::social::{RadrootsReportFileTarget, RadrootsReportType, RadrootsSocialTarget}; -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug)] pub struct RadrootsReport { pub reported_pubkey: String, diff --git a/crates/event/src/repost.rs b/crates/event/src/repost.rs @@ -3,7 +3,10 @@ use alloc::string::String; use crate::social::RadrootsSocialTarget; -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug)] pub struct RadrootsRepost { pub target: RadrootsSocialTarget, @@ -14,7 +17,10 @@ pub struct RadrootsRepost { pub content: Option<String>, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug)] pub struct RadrootsGenericRepost { pub target: RadrootsSocialTarget, diff --git a/crates/event/src/resource_area.rs b/crates/event/src/resource_area.rs @@ -7,7 +7,10 @@ use alloc::{string::String, vec::Vec}; #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug)] pub struct RadrootsResourceArea { pub d_tag: String, @@ -19,7 +22,10 @@ pub struct RadrootsResourceArea { #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug)] pub struct RadrootsResourceAreaRef { pub pubkey: String, @@ -28,7 +34,10 @@ pub struct RadrootsResourceAreaRef { #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug)] pub struct RadrootsResourceAreaLocation { pub primary: Option<String>, diff --git a/crates/event/src/resource_cap.rs b/crates/event/src/resource_cap.rs @@ -9,14 +9,20 @@ use alloc::{string::String, vec::Vec}; #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug)] pub struct RadrootsResourceHarvestProduct { pub key: String, pub category: Option<String>, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[derive(Clone, Debug)] diff --git a/crates/event/src/seal.rs b/crates/event/src/seal.rs @@ -5,7 +5,10 @@ use alloc::string::String; #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug)] pub struct RadrootsSeal { pub content: String, diff --git a/crates/event/src/social.rs b/crates/event/src/social.rs @@ -3,9 +3,15 @@ use crate::farm::RadrootsFarmRef; #[cfg(not(feature = "std"))] use alloc::{string::String, vec::Vec}; -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case", tag = "kind"))] +#[cfg_attr( + any(feature = "serde", test), + serde(rename_all = "snake_case", tag = "kind") +)] #[derive(Clone, Debug, PartialEq, Eq)] pub enum RadrootsSocialTarget { Event { @@ -27,7 +33,10 @@ pub enum RadrootsSocialTarget { }, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[derive(Clone, Debug, Default)] pub struct RadrootsSocialFarmAnchor { @@ -35,7 +44,10 @@ pub struct RadrootsSocialFarmAnchor { pub relays: Option<Vec<String>>, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct RadrootsSocialLocation { @@ -43,7 +55,10 @@ pub struct RadrootsSocialLocation { pub geohash: Option<String>, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct RadrootsSocialMediaDimensions { @@ -51,7 +66,10 @@ pub struct RadrootsSocialMediaDimensions { pub height: u32, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct RadrootsSocialMediaThumbnail { @@ -59,7 +77,10 @@ pub struct RadrootsSocialMediaThumbnail { pub dimensions: Option<RadrootsSocialMediaDimensions>, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct RadrootsSocialMediaMetadata { @@ -82,7 +103,10 @@ pub struct RadrootsSocialMediaMetadata { pub imeta: Option<Vec<Vec<String>>>, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct RadrootsCalendarParticipant { pub pubkey: String, @@ -90,14 +114,20 @@ pub struct RadrootsCalendarParticipant { pub role: Option<String>, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct RadrootsCalendarDateValue { pub value: String, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] +#[cfg_attr(any(feature = "serde", test), serde(rename_all = "snake_case"))] #[derive(Clone, Debug, PartialEq, Eq)] pub enum RadrootsCalendarEventRsvpStatus { Accepted, @@ -105,16 +135,22 @@ pub enum RadrootsCalendarEventRsvpStatus { Tentative, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] +#[cfg_attr(any(feature = "serde", test), serde(rename_all = "snake_case"))] #[derive(Clone, Debug, PartialEq, Eq)] pub enum RadrootsCalendarEventFreeBusy { Free, Busy, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] +#[cfg_attr(any(feature = "serde", test), serde(rename_all = "snake_case"))] #[derive(Clone, Debug, PartialEq, Eq)] pub enum RadrootsReportType { Nudity, @@ -126,7 +162,10 @@ pub enum RadrootsReportType { Other, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct RadrootsReportFileTarget { pub sha256: Option<String>, @@ -134,7 +173,10 @@ pub struct RadrootsReportFileTarget { pub magnet: Option<String>, } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug, PartialEq, Eq)] pub struct RadrootsReportTarget { pub reported_pubkey: String, diff --git a/crates/event/src/trade_validation.rs b/crates/event/src/trade_validation.rs @@ -5,7 +5,10 @@ use alloc::{string::String, vec::Vec}; use crate::{RadrootsEventPtr, order::RadrootsListingParseError}; -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[cfg_attr(feature = "dto-bindgen", derive(dto_bindgen::Dto))] #[cfg_attr(feature = "dto-bindgen", dto(export))] #[cfg_attr( @@ -84,7 +87,10 @@ impl std::error::Error for RadrootsTradeValidationListingError {} feature = "dto-bindgen", dto(ts(name = "RadrootsTradeListingValidateRequest")) )] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug, PartialEq, Eq)] pub struct RadrootsTradeValidationListingRequest { pub listing_event: Option<RadrootsEventPtr>, @@ -96,7 +102,10 @@ pub struct RadrootsTradeValidationListingRequest { feature = "dto-bindgen", dto(ts(name = "RadrootsTradeListingValidateResult")) )] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(feature = "serde", test), + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug, PartialEq, Eq)] pub struct RadrootsTradeValidationListingResult { pub valid: bool, diff --git a/crates/event/src/wire.rs b/crates/event/src/wire.rs @@ -0,0 +1,883 @@ +#![forbid(unsafe_code)] + +#[cfg(all(not(feature = "std"), not(test)))] +use alloc::{ + collections::BTreeMap, + string::{String, ToString}, + vec::Vec, +}; + +#[cfg(any(feature = "std", test))] +use std::{collections::BTreeMap, string::String, vec::Vec}; + +use crate::ids::{ + RadrootsEventId, RadrootsEventSignature, RadrootsIdParseError, RadrootsPublicKey, +}; +use core::fmt; +use serde_json::{Map, Value}; +use sha2::{Digest, Sha256}; + +pub const DEFAULT_RAW_JSON_MAX_BYTES: usize = 256 * 1024; +pub const DEFAULT_CONTENT_MAX_BYTES: usize = 128 * 1024; +pub const DEFAULT_TAG_MAX_COUNT: usize = 1024; +pub const DEFAULT_TAG_ELEMENT_MAX_BYTES: usize = 4 * 1024; +pub const DEFAULT_TAG_TOTAL_MAX_BYTES: usize = 128 * 1024; +pub const DEFAULT_EXTRA_MAX_FIELDS: usize = 64; +pub const DEFAULT_EXTRA_TOTAL_JSON_MAX_BYTES: usize = 64 * 1024; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct RadrootsEventWireLimits { + pub max_raw_json_bytes: usize, + pub max_content_bytes: usize, + pub max_tag_count: usize, + pub max_tag_element_bytes: usize, + pub max_total_tag_bytes: usize, + pub max_extra_fields: usize, + pub max_total_extra_json_bytes: usize, +} + +impl Default for RadrootsEventWireLimits { + fn default() -> Self { + Self { + max_raw_json_bytes: DEFAULT_RAW_JSON_MAX_BYTES, + max_content_bytes: DEFAULT_CONTENT_MAX_BYTES, + max_tag_count: DEFAULT_TAG_MAX_COUNT, + max_tag_element_bytes: DEFAULT_TAG_ELEMENT_MAX_BYTES, + max_total_tag_bytes: DEFAULT_TAG_TOTAL_MAX_BYTES, + max_extra_fields: DEFAULT_EXTRA_MAX_FIELDS, + max_total_extra_json_bytes: DEFAULT_EXTRA_TOTAL_JSON_MAX_BYTES, + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum RadrootsCanonicalEventIdError { + InvalidPubkey(RadrootsIdParseError), + InvalidComputedEventId(RadrootsIdParseError), +} + +impl fmt::Display for RadrootsCanonicalEventIdError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::InvalidPubkey(error) => { + write!(f, "canonical event id pubkey is invalid: {error}") + } + Self::InvalidComputedEventId(error) => { + write!(f, "canonical event id digest is invalid: {error}") + } + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for RadrootsCanonicalEventIdError {} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum RadrootsEventWireError { + Json(String), + RootNotObject, + MissingField(&'static str), + InvalidField(&'static str), + InvalidIdentifier { + field: &'static str, + error: RadrootsIdParseError, + }, + NonCanonicalIdentifier { + field: &'static str, + }, + RawJsonTooLarge { + max: usize, + actual: usize, + }, + ContentTooLarge { + max: usize, + actual: usize, + }, + TooManyTags { + max: usize, + actual: usize, + }, + EmptyTag { + index: usize, + }, + EmptyTagKey { + index: usize, + }, + ControlCharacterTagKey { + index: usize, + }, + TagElementTooLarge { + tag_index: usize, + element_index: usize, + max: usize, + actual: usize, + }, + TagsTooLarge { + max: usize, + actual: usize, + }, + TooManyExtraFields { + max: usize, + actual: usize, + }, + ExtraJsonTooLarge { + max: usize, + actual: usize, + }, + CanonicalEventId(RadrootsCanonicalEventIdError), + EventIdMismatch { + declared: String, + computed: String, + }, +} + +impl fmt::Display for RadrootsEventWireError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Json(error) => write!(f, "event wire json is invalid: {error}"), + Self::RootNotObject => write!(f, "event wire root must be a JSON object"), + Self::MissingField(field) => write!(f, "event wire missing required field {field}"), + Self::InvalidField(field) => write!(f, "event wire field {field} is invalid"), + Self::InvalidIdentifier { field, error } => { + write!(f, "event wire field {field} is invalid: {error}") + } + Self::NonCanonicalIdentifier { field } => { + write!( + f, + "event wire field {field} must be canonical lowercase hex" + ) + } + Self::RawJsonTooLarge { max, actual } => { + write!(f, "event wire raw JSON size {actual} exceeds {max} bytes") + } + Self::ContentTooLarge { max, actual } => { + write!(f, "event wire content size {actual} exceeds {max} bytes") + } + Self::TooManyTags { max, actual } => { + write!(f, "event wire tag count {actual} exceeds {max}") + } + Self::EmptyTag { index } => write!(f, "event wire tag {index} is empty"), + Self::EmptyTagKey { index } => write!(f, "event wire tag {index} key is empty"), + Self::ControlCharacterTagKey { index } => { + write!(f, "event wire tag {index} key contains a control character") + } + Self::TagElementTooLarge { + tag_index, + element_index, + max, + actual, + } => write!( + f, + "event wire tag {tag_index} element {element_index} size {actual} exceeds {max} bytes" + ), + Self::TagsTooLarge { max, actual } => { + write!(f, "event wire tag bytes {actual} exceed {max}") + } + Self::TooManyExtraFields { max, actual } => { + write!(f, "event wire extra field count {actual} exceeds {max}") + } + Self::ExtraJsonTooLarge { max, actual } => { + write!(f, "event wire extra JSON bytes {actual} exceed {max}") + } + Self::CanonicalEventId(error) => write!(f, "{error}"), + Self::EventIdMismatch { declared, computed } => write!( + f, + "event wire id mismatch: declared {declared}, computed {computed}" + ), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for RadrootsEventWireError {} + +impl From<RadrootsCanonicalEventIdError> for RadrootsEventWireError { + fn from(value: RadrootsCanonicalEventIdError) -> Self { + Self::CanonicalEventId(value) + } +} + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RadrootsNip01EventWire { + pub id: String, + pub pubkey: String, + pub created_at: u64, + pub kind: u32, + pub tags: Vec<Vec<String>>, + pub content: String, + pub sig: String, + #[cfg_attr(feature = "serde", serde(flatten))] + pub extra: BTreeMap<String, Value>, +} + +impl RadrootsNip01EventWire { + pub fn parse_json(raw_json: &str) -> Result<Self, RadrootsEventWireError> { + Self::parse_json_with_limits(raw_json, RadrootsEventWireLimits::default()) + } + + pub fn parse_json_with_limits( + raw_json: &str, + limits: RadrootsEventWireLimits, + ) -> Result<Self, RadrootsEventWireError> { + let raw_len = raw_json.len(); + if raw_len > limits.max_raw_json_bytes { + return Err(RadrootsEventWireError::RawJsonTooLarge { + max: limits.max_raw_json_bytes, + actual: raw_len, + }); + } + let value = serde_json::from_str::<Value>(raw_json) + .map_err(|error| RadrootsEventWireError::Json(error.to_string()))?; + Self::from_json_value(value, limits) + } + + pub fn canonical_id_preimage(&self) -> Result<String, RadrootsCanonicalEventIdError> { + canonical_nip01_event_id_preimage( + self.pubkey.as_str(), + self.created_at, + self.kind, + &self.tags, + self.content.as_str(), + ) + } + + pub fn computed_event_id(&self) -> Result<RadrootsEventId, RadrootsCanonicalEventIdError> { + compute_canonical_nip01_event_id( + self.pubkey.as_str(), + self.created_at, + self.kind, + &self.tags, + self.content.as_str(), + ) + } + + pub fn verify_id(&self) -> Result<(), RadrootsEventWireError> { + let computed = self.computed_event_id()?.into_string(); + if computed.as_str() != self.id.as_str() { + return Err(RadrootsEventWireError::EventIdMismatch { + declared: self.id.clone(), + computed, + }); + } + Ok(()) + } + + fn from_json_value( + value: Value, + limits: RadrootsEventWireLimits, + ) -> Result<Self, RadrootsEventWireError> { + let mut object = match value { + Value::Object(object) => object, + _ => return Err(RadrootsEventWireError::RootNotObject), + }; + let id = take_canonical_event_id(&mut object)?; + let pubkey = take_canonical_pubkey(&mut object)?; + let created_at = take_u64(&mut object, "created_at")?; + let kind = take_u32(&mut object, "kind")?; + let tags = take_tags(&mut object, limits)?; + let content = take_string(&mut object, "content")?; + let content_len = content.len(); + if content_len > limits.max_content_bytes { + return Err(RadrootsEventWireError::ContentTooLarge { + max: limits.max_content_bytes, + actual: content_len, + }); + } + let sig = take_canonical_signature(&mut object)?; + let extra = validate_extra(object, limits)?; + let wire = Self { + id, + pubkey, + created_at, + kind, + tags, + content, + sig, + extra, + }; + wire.verify_id()?; + Ok(wire) + } +} + +pub fn canonical_nip01_event_id_preimage( + pubkey: &str, + created_at: u64, + kind: u32, + tags: &[Vec<String>], + content: &str, +) -> Result<String, RadrootsCanonicalEventIdError> { + let pubkey = + RadrootsPublicKey::parse(pubkey).map_err(RadrootsCanonicalEventIdError::InvalidPubkey)?; + let mut preimage = String::new(); + preimage.push_str("[0,"); + push_canonical_json_string(&mut preimage, pubkey.as_str()); + preimage.push(','); + preimage.push_str(created_at.to_string().as_str()); + preimage.push(','); + preimage.push_str(kind.to_string().as_str()); + preimage.push_str(",["); + for (tag_index, tag) in tags.iter().enumerate() { + if tag_index > 0 { + preimage.push(','); + } + preimage.push('['); + for (value_index, value) in tag.iter().enumerate() { + if value_index > 0 { + preimage.push(','); + } + push_canonical_json_string(&mut preimage, value); + } + preimage.push(']'); + } + preimage.push_str("],"); + push_canonical_json_string(&mut preimage, content); + preimage.push(']'); + Ok(preimage) +} + +pub fn compute_canonical_nip01_event_id( + pubkey: &str, + created_at: u64, + kind: u32, + tags: &[Vec<String>], + content: &str, +) -> Result<RadrootsEventId, RadrootsCanonicalEventIdError> { + let preimage = canonical_nip01_event_id_preimage(pubkey, created_at, kind, tags, content)?; + let digest = Sha256::digest(preimage.as_bytes()); + let event_id = hex::encode(digest); + RadrootsEventId::parse(event_id).map_err(RadrootsCanonicalEventIdError::InvalidComputedEventId) +} + +fn take_string( + object: &mut Map<String, Value>, + field: &'static str, +) -> Result<String, RadrootsEventWireError> { + match object.remove(field) { + Some(Value::String(value)) => Ok(value), + Some(_) => Err(RadrootsEventWireError::InvalidField(field)), + None => Err(RadrootsEventWireError::MissingField(field)), + } +} + +fn take_canonical_event_id( + object: &mut Map<String, Value>, +) -> Result<String, RadrootsEventWireError> { + let raw = take_string(object, "id")?; + let parsed = RadrootsEventId::parse(raw.as_str()) + .map_err(|error| RadrootsEventWireError::InvalidIdentifier { field: "id", error })?; + canonical_identifier_string("id", raw, parsed.into_string()) +} + +fn take_canonical_pubkey( + object: &mut Map<String, Value>, +) -> Result<String, RadrootsEventWireError> { + let raw = take_string(object, "pubkey")?; + let parsed = RadrootsPublicKey::parse(raw.as_str()).map_err(|error| { + RadrootsEventWireError::InvalidIdentifier { + field: "pubkey", + error, + } + })?; + canonical_identifier_string("pubkey", raw, parsed.into_string()) +} + +fn take_canonical_signature( + object: &mut Map<String, Value>, +) -> Result<String, RadrootsEventWireError> { + let raw = take_string(object, "sig")?; + let parsed = RadrootsEventSignature::parse(raw.as_str()).map_err(|error| { + RadrootsEventWireError::InvalidIdentifier { + field: "sig", + error, + } + })?; + canonical_identifier_string("sig", raw, parsed.into_string()) +} + +fn canonical_identifier_string( + field: &'static str, + raw: String, + canonical: String, +) -> Result<String, RadrootsEventWireError> { + if canonical.as_str() != raw.as_str() { + return Err(RadrootsEventWireError::NonCanonicalIdentifier { field }); + } + Ok(canonical) +} + +fn take_u64( + object: &mut Map<String, Value>, + field: &'static str, +) -> Result<u64, RadrootsEventWireError> { + match object.remove(field) { + Some(Value::Number(value)) => value + .as_u64() + .ok_or(RadrootsEventWireError::InvalidField(field)), + Some(_) => Err(RadrootsEventWireError::InvalidField(field)), + None => Err(RadrootsEventWireError::MissingField(field)), + } +} + +fn take_u32( + object: &mut Map<String, Value>, + field: &'static str, +) -> Result<u32, RadrootsEventWireError> { + let value = take_u64(object, field)?; + u32::try_from(value).map_err(|_| RadrootsEventWireError::InvalidField(field)) +} + +fn take_tags( + object: &mut Map<String, Value>, + limits: RadrootsEventWireLimits, +) -> Result<Vec<Vec<String>>, RadrootsEventWireError> { + let raw_tags = match object.remove("tags") { + Some(Value::Array(raw_tags)) => raw_tags, + Some(_) => return Err(RadrootsEventWireError::InvalidField("tags")), + None => return Err(RadrootsEventWireError::MissingField("tags")), + }; + let tag_count = raw_tags.len(); + if tag_count > limits.max_tag_count { + return Err(RadrootsEventWireError::TooManyTags { + max: limits.max_tag_count, + actual: tag_count, + }); + } + let mut total_tag_bytes = 0usize; + let mut tags = Vec::with_capacity(tag_count); + for (tag_index, raw_tag) in raw_tags.into_iter().enumerate() { + let raw_values = match raw_tag { + Value::Array(values) => values, + _ => return Err(RadrootsEventWireError::InvalidField("tags")), + }; + if raw_values.is_empty() { + return Err(RadrootsEventWireError::EmptyTag { index: tag_index }); + } + let mut tag = Vec::with_capacity(raw_values.len()); + for (element_index, raw_value) in raw_values.into_iter().enumerate() { + let value = match raw_value { + Value::String(value) => value, + _ => return Err(RadrootsEventWireError::InvalidField("tags")), + }; + let value_len = value.len(); + if value_len > limits.max_tag_element_bytes { + return Err(RadrootsEventWireError::TagElementTooLarge { + tag_index, + element_index, + max: limits.max_tag_element_bytes, + actual: value_len, + }); + } + if element_index == 0 { + validate_tag_key(tag_index, value.as_str())?; + } + total_tag_bytes = total_tag_bytes.saturating_add(value_len); + if total_tag_bytes > limits.max_total_tag_bytes { + return Err(RadrootsEventWireError::TagsTooLarge { + max: limits.max_total_tag_bytes, + actual: total_tag_bytes, + }); + } + tag.push(value); + } + tags.push(tag); + } + Ok(tags) +} + +fn validate_tag_key(index: usize, value: &str) -> Result<(), RadrootsEventWireError> { + if value.is_empty() { + return Err(RadrootsEventWireError::EmptyTagKey { index }); + } + if value.chars().any(char::is_control) { + return Err(RadrootsEventWireError::ControlCharacterTagKey { index }); + } + Ok(()) +} + +fn validate_extra( + object: Map<String, Value>, + limits: RadrootsEventWireLimits, +) -> Result<BTreeMap<String, Value>, RadrootsEventWireError> { + let extra_count = object.len(); + if extra_count > limits.max_extra_fields { + return Err(RadrootsEventWireError::TooManyExtraFields { + max: limits.max_extra_fields, + actual: extra_count, + }); + } + let mut total_json_bytes = 0usize; + let mut extra = BTreeMap::new(); + for (key, value) in object { + let key_json_len = serde_json::to_vec(&key) + .map_err(|error| RadrootsEventWireError::Json(error.to_string()))? + .len(); + let value_json_len = serde_json::to_vec(&value) + .map_err(|error| RadrootsEventWireError::Json(error.to_string()))? + .len(); + total_json_bytes = total_json_bytes + .saturating_add(key_json_len) + .saturating_add(1) + .saturating_add(value_json_len); + if total_json_bytes > limits.max_total_extra_json_bytes { + return Err(RadrootsEventWireError::ExtraJsonTooLarge { + max: limits.max_total_extra_json_bytes, + actual: total_json_bytes, + }); + } + extra.insert(key, value); + } + Ok(extra) +} + +fn push_canonical_json_string(target: &mut String, value: &str) { + target.push('"'); + for character in value.chars() { + match character { + '"' => target.push_str("\\\""), + '\\' => target.push_str("\\\\"), + '\n' => target.push_str("\\n"), + '\r' => target.push_str("\\r"), + '\t' => target.push_str("\\t"), + '\u{08}' => target.push_str("\\b"), + '\u{0c}' => target.push_str("\\f"), + '\u{00}'..='\u{1f}' => push_unicode_escape(target, character), + _ => target.push(character), + } + } + target.push('"'); +} + +fn push_unicode_escape(target: &mut String, character: char) { + const HEX: &[u8; 16] = b"0123456789abcdef"; + let value = character as u32; + target.push_str("\\u00"); + target.push(HEX[((value >> 4) & 0x0f) as usize] as char); + target.push(HEX[(value & 0x0f) as usize] as char); +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + fn hex_64(character: char) -> String { + core::iter::repeat_n(character, 64).collect() + } + + fn hex_128(character: char) -> String { + core::iter::repeat_n(character, 128).collect() + } + + fn valid_event_value(content: &str, tags: Vec<Vec<String>>) -> Value { + let pubkey = hex_64('a'); + let id = + compute_canonical_nip01_event_id(pubkey.as_str(), 1_700_000_000, 1, &tags, content) + .expect("event id") + .into_string(); + json!({ + "id": id, + "pubkey": pubkey, + "created_at": 1_700_000_000u64, + "kind": 1u32, + "tags": tags, + "content": content, + "sig": hex_128('b') + }) + } + + fn raw_json(value: &Value) -> String { + serde_json::to_string(value).expect("event json") + } + + fn valid_event_json(content: &str, tags: Vec<Vec<String>>) -> String { + raw_json(&valid_event_value(content, tags)) + } + + fn default_tags() -> Vec<Vec<String>> { + vec![vec!["t".to_owned(), "soil".to_owned()]] + } + + #[test] + fn canonical_preimage_escapes_required_json_characters() { + let preimage = canonical_nip01_event_id_preimage( + hex_64('a').as_str(), + 10, + 1, + &[vec!["t".to_owned(), "line\nsoil".to_owned()]], + "\"\\\n\r\t\u{08}\u{0c}\u{01}", + ) + .expect("preimage"); + + assert_eq!( + preimage, + r#"[0,"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",10,1,[["t","line\nsoil"]],"\"\\\n\r\t\b\f\u0001"]"# + ); + } + + #[test] + fn parses_wire_json_preserves_extra_and_verifies_id() { + let mut value = valid_event_value("hello", default_tags()); + value + .as_object_mut() + .expect("object") + .insert("client".to_owned(), json!({"name":"radroots-test"})); + let wire = RadrootsNip01EventWire::parse_json(raw_json(&value).as_str()).expect("wire"); + + assert_eq!(wire.pubkey, hex_64('a')); + assert_eq!(wire.created_at, 1_700_000_000); + assert_eq!(wire.kind, 1); + assert_eq!(wire.tags, default_tags()); + assert_eq!(wire.content, "hello"); + assert_eq!( + wire.extra.get("client").expect("client extra"), + &json!({"name":"radroots-test"}) + ); + wire.verify_id().expect("verified id"); + assert_eq!( + wire.canonical_id_preimage().expect("preimage"), + r#"[0,"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",1700000000,1,[["t","soil"]],"hello"]"# + ); + } + + #[cfg(feature = "serde")] + #[test] + fn serde_flatten_preserves_extra_fields() { + let mut value = valid_event_value("hello", default_tags()); + value + .as_object_mut() + .expect("object") + .insert("client".to_owned(), json!("radroots-test")); + let wire = RadrootsNip01EventWire::parse_json(raw_json(&value).as_str()).expect("wire"); + let encoded = serde_json::to_value(&wire).expect("encoded"); + + assert_eq!(encoded.get("client"), Some(&json!("radroots-test"))); + assert_eq!(encoded.get("id"), Some(&Value::String(wire.id))); + } + + #[test] + fn parse_json_rejects_required_field_errors_and_id_mismatch() { + let mut value = valid_event_value("hello", default_tags()); + value.as_object_mut().expect("object").remove("id"); + assert!(matches!( + RadrootsNip01EventWire::parse_json(raw_json(&value).as_str()), + Err(RadrootsEventWireError::MissingField("id")) + )); + + let mut value = valid_event_value("hello", default_tags()); + value + .as_object_mut() + .expect("object") + .insert("pubkey".to_owned(), json!("not-hex")); + assert!(matches!( + RadrootsNip01EventWire::parse_json(raw_json(&value).as_str()), + Err(RadrootsEventWireError::InvalidIdentifier { + field: "pubkey", + .. + }) + )); + + let mut value = valid_event_value("hello", default_tags()); + value + .as_object_mut() + .expect("object") + .insert("sig".to_owned(), json!(hex_64('b'))); + assert!(matches!( + RadrootsNip01EventWire::parse_json(raw_json(&value).as_str()), + Err(RadrootsEventWireError::InvalidIdentifier { field: "sig", .. }) + )); + + let mut value = valid_event_value("hello", default_tags()); + value + .as_object_mut() + .expect("object") + .insert("id".to_owned(), json!(hex_64('f'))); + assert!(matches!( + RadrootsNip01EventWire::parse_json(raw_json(&value).as_str()), + Err(RadrootsEventWireError::EventIdMismatch { .. }) + )); + + let mut value = valid_event_value("hello", default_tags()); + value + .as_object_mut() + .expect("object") + .insert("id".to_owned(), json!(hex_64('A'))); + assert!(matches!( + RadrootsNip01EventWire::parse_json(raw_json(&value).as_str()), + Err(RadrootsEventWireError::NonCanonicalIdentifier { field: "id" }) + )); + } + + #[test] + fn parse_json_rejects_tag_shape_errors() { + let mut value = valid_event_value("hello", default_tags()); + value + .as_object_mut() + .expect("object") + .insert("tags".to_owned(), json!([[]])); + assert!(matches!( + RadrootsNip01EventWire::parse_json(raw_json(&value).as_str()), + Err(RadrootsEventWireError::EmptyTag { index: 0 }) + )); + + let mut value = valid_event_value("hello", default_tags()); + value + .as_object_mut() + .expect("object") + .insert("tags".to_owned(), json!([["", "soil"]])); + assert!(matches!( + RadrootsNip01EventWire::parse_json(raw_json(&value).as_str()), + Err(RadrootsEventWireError::EmptyTagKey { index: 0 }) + )); + + let mut value = valid_event_value("hello", default_tags()); + value + .as_object_mut() + .expect("object") + .insert("tags".to_owned(), json!([["t\n", "soil"]])); + assert!(matches!( + RadrootsNip01EventWire::parse_json(raw_json(&value).as_str()), + Err(RadrootsEventWireError::ControlCharacterTagKey { index: 0 }) + )); + } + + #[test] + fn parse_json_rejects_resource_budget_violations() { + let raw = valid_event_json("hello", default_tags()); + assert!(matches!( + RadrootsNip01EventWire::parse_json_with_limits( + raw.as_str(), + RadrootsEventWireLimits { + max_raw_json_bytes: 1, + ..RadrootsEventWireLimits::default() + } + ), + Err(RadrootsEventWireError::RawJsonTooLarge { .. }) + )); + + let raw = valid_event_json("hello", default_tags()); + assert!(matches!( + RadrootsNip01EventWire::parse_json_with_limits( + raw.as_str(), + RadrootsEventWireLimits { + max_content_bytes: 1, + ..RadrootsEventWireLimits::default() + } + ), + Err(RadrootsEventWireError::ContentTooLarge { .. }) + )); + + let raw = valid_event_json("hello", default_tags()); + assert!(matches!( + RadrootsNip01EventWire::parse_json_with_limits( + raw.as_str(), + RadrootsEventWireLimits { + max_tag_count: 0, + ..RadrootsEventWireLimits::default() + } + ), + Err(RadrootsEventWireError::TooManyTags { .. }) + )); + + let raw = valid_event_json("hello", vec![vec!["t".to_owned(), "soil".to_owned()]]); + assert!(matches!( + RadrootsNip01EventWire::parse_json_with_limits( + raw.as_str(), + RadrootsEventWireLimits { + max_tag_element_bytes: 1, + ..RadrootsEventWireLimits::default() + } + ), + Err(RadrootsEventWireError::TagElementTooLarge { .. }) + )); + + let raw = valid_event_json("hello", default_tags()); + assert!(matches!( + RadrootsNip01EventWire::parse_json_with_limits( + raw.as_str(), + RadrootsEventWireLimits { + max_total_tag_bytes: 1, + ..RadrootsEventWireLimits::default() + } + ), + Err(RadrootsEventWireError::TagsTooLarge { .. }) + )); + + let mut value = valid_event_value("hello", default_tags()); + value + .as_object_mut() + .expect("object") + .insert("client".to_owned(), json!("radroots-test")); + let raw = raw_json(&value); + assert!(matches!( + RadrootsNip01EventWire::parse_json_with_limits( + raw.as_str(), + RadrootsEventWireLimits { + max_extra_fields: 0, + ..RadrootsEventWireLimits::default() + } + ), + Err(RadrootsEventWireError::TooManyExtraFields { .. }) + )); + + assert!(matches!( + RadrootsNip01EventWire::parse_json_with_limits( + raw.as_str(), + RadrootsEventWireLimits { + max_total_extra_json_bytes: 1, + ..RadrootsEventWireLimits::default() + } + ), + Err(RadrootsEventWireError::ExtraJsonTooLarge { .. }) + )); + } + + #[test] + fn checked_in_conformance_vectors_match_wire_behavior() { + let vectors = + include_str!("../../../contracts/conformance/vectors/event/nip01_wire.v1.json"); + let document: Value = serde_json::from_str(vectors).expect("vectors json"); + let entries = document + .get("vectors") + .and_then(Value::as_array) + .expect("vector entries"); + + for entry in entries { + match entry.get("kind").and_then(Value::as_str).expect("kind") { + "event.nip01_wire.valid" => { + let raw = entry + .get("input") + .and_then(|input| input.get("raw_json")) + .and_then(Value::as_str) + .expect("raw json"); + let expected = entry.get("expected").expect("expected"); + let wire = RadrootsNip01EventWire::parse_json(raw).expect("wire"); + assert_eq!( + wire.canonical_id_preimage().expect("preimage"), + expected + .get("canonical_id_preimage") + .and_then(Value::as_str) + .expect("expected preimage") + ); + assert_eq!( + wire.computed_event_id().expect("event id").as_str(), + expected + .get("computed_event_id") + .and_then(Value::as_str) + .expect("expected event id") + ); + } + "event.nip01_wire.invalid" => { + let raw = entry + .get("input") + .and_then(|input| input.get("raw_json")) + .and_then(Value::as_str) + .expect("raw json"); + assert!(RadrootsNip01EventWire::parse_json(raw).is_err()); + } + other => panic!("unknown event wire vector kind {other}"), + } + } + } +}