registry_v7.rs (36856B)
1 #[cfg(not(feature = "std"))] 2 use alloc::{ 3 format, 4 string::{String, ToString}, 5 vec::Vec, 6 }; 7 8 use radroots_event::{ 9 contract::{ 10 AuthorRole, ContentSchema, EventAuthoringPolicy, EventClass, EventContract, 11 EventDiscriminator, EventPrivacy, EventStability, KindContract, NostrStandard, 12 RADROOTS_EVENT_CONTRACT_REGISTRY_VERSION, Reducer, TagCardinality, TagContract, 13 TagSemantic, TagValueType, all_event_contracts_registry_v7, all_kind_contracts_registry_v7, 14 }, 15 listing::classified::ClassifiedListingPartition, 16 }; 17 use serde::{Deserialize, Serialize}; 18 19 use super::{canonical_manifest_json, manifest_sha256, parse_manifest_json}; 20 21 pub const RADROOTS_EVENT_CONTRACT_REGISTRY_V7_INVENTORY_SCHEMA_VERSION: u32 = 1; 22 pub const RADROOTS_EVENT_CONTRACT_REGISTRY_V7_VERSION: u32 = 23 RADROOTS_EVENT_CONTRACT_REGISTRY_VERSION; 24 pub const RADROOTS_EVENT_CONTRACT_REGISTRY_V7_KIND_COUNT: usize = 25 all_kind_contracts_registry_v7().len(); 26 pub const RADROOTS_EVENT_CONTRACT_REGISTRY_V7_EVENT_COUNT: usize = 27 all_event_contracts_registry_v7().len(); 28 29 #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] 30 #[serde(deny_unknown_fields)] 31 pub struct RadrootsEventContractRegistryV7Inventory { 32 pub schema_version: u32, 33 pub event_contract_registry_version: u32, 34 pub kind_contracts: Vec<RadrootsKindContractRegistryV7InventoryEntry>, 35 pub event_contracts: Vec<RadrootsEventContractRegistryV7InventoryEntry>, 36 } 37 38 #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] 39 #[serde(deny_unknown_fields)] 40 pub struct RadrootsKindContractRegistryV7InventoryEntry { 41 pub ordinal: usize, 42 pub kind: u32, 43 pub canonical_constant: String, 44 pub name: String, 45 pub class: RadrootsEventContractRegistryV7Class, 46 pub standard: RadrootsEventContractRegistryV7Standard, 47 pub accepted_event_contracts: Vec<String>, 48 } 49 50 #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] 51 #[serde(deny_unknown_fields)] 52 pub struct RadrootsEventContractRegistryV7InventoryEntry { 53 pub ordinal: usize, 54 pub contract_id: String, 55 pub kind: u32, 56 pub name: String, 57 pub payload_type: String, 58 pub class: RadrootsEventContractRegistryV7Class, 59 pub stability: RadrootsEventContractRegistryV7Stability, 60 pub privacy: RadrootsEventContractRegistryV7Privacy, 61 pub author_role: RadrootsEventContractRegistryV7ActorRole, 62 pub content_schema: RadrootsEventContractRegistryV7ContentSchema, 63 pub authoring_policy: RadrootsEventContractRegistryV7AuthoringPolicy, 64 pub discriminator: RadrootsEventContractRegistryV7Discriminator, 65 pub tags: Vec<RadrootsEventContractRegistryV7Tag>, 66 pub reducers: Vec<RadrootsEventContractRegistryV7Reducer>, 67 } 68 69 #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] 70 #[serde(rename_all = "snake_case")] 71 pub enum RadrootsEventContractRegistryV7Class { 72 Regular, 73 Replaceable, 74 Addressable, 75 Ephemeral, 76 } 77 78 #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] 79 #[serde(rename_all = "snake_case")] 80 pub enum RadrootsEventContractRegistryV7Standard { 81 Nip01, 82 Nip09, 83 Nip17, 84 Nip18, 85 Nip22, 86 Nip23, 87 Nip25, 88 Nip28, 89 Nip29, 90 Nip42, 91 Nip51, 92 Nip52, 93 Nip53, 94 Nip54, 95 Nip56, 96 Nip57, 97 Nip78, 98 Nip90, 99 Nip94, 100 Nip98, 101 Nip99, 102 Radroots, 103 } 104 105 #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] 106 #[serde(rename_all = "snake_case")] 107 pub enum RadrootsEventContractRegistryV7Stability { 108 Stable, 109 Experimental, 110 } 111 112 #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] 113 #[serde(rename_all = "snake_case")] 114 pub enum RadrootsEventContractRegistryV7Privacy { 115 Public, 116 Encrypted, 117 LocalOnly, 118 Secret, 119 } 120 121 #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] 122 #[serde(rename_all = "snake_case")] 123 pub enum RadrootsEventContractRegistryV7ActorRole { 124 Any, 125 Application, 126 Buyer, 127 Farmer, 128 Member, 129 Moderator, 130 Relay, 131 Seller, 132 Service, 133 } 134 135 #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] 136 #[serde(rename_all = "snake_case")] 137 pub enum RadrootsEventContractRegistryV7ContentSchema { 138 Empty, 139 JsonObject, 140 PlainText, 141 Markdown, 142 Djot, 143 Encrypted, 144 BinaryReference, 145 } 146 147 #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] 148 #[serde(rename_all = "snake_case")] 149 pub enum RadrootsEventContractRegistryV7AuthoringPolicy { 150 GenericDraft, 151 TypedOnly, 152 ReadOnly, 153 } 154 155 #[derive(Clone, Debug, PartialEq, Eq, Serialize)] 156 #[serde(tag = "type", rename_all = "snake_case")] 157 pub enum RadrootsEventContractRegistryV7Discriminator { 158 KindOnly, 159 AdmissionOnly, 160 ClassifiedListingPartition { 161 value: RadrootsEventContractRegistryV7ClassifiedListingPartition, 162 }, 163 DTagExact { 164 value: String, 165 }, 166 DTagPrefix { 167 prefix: String, 168 }, 169 DTagSuffix { 170 suffix: String, 171 }, 172 TagEquals { 173 name: String, 174 value: String, 175 }, 176 ContentJsonFieldEquals { 177 field: String, 178 value: String, 179 }, 180 EnvelopeType { 181 value: String, 182 }, 183 Composite { 184 parts: Vec<RadrootsEventContractRegistryV7Discriminator>, 185 }, 186 } 187 188 impl<'de> Deserialize<'de> for RadrootsEventContractRegistryV7Discriminator { 189 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 190 where 191 D: serde::Deserializer<'de>, 192 { 193 let value = serde_json::Value::deserialize(deserializer)?; 194 discriminator_from_json_value(&value).map_err(serde::de::Error::custom) 195 } 196 } 197 198 #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] 199 #[serde(rename_all = "snake_case")] 200 pub enum RadrootsEventContractRegistryV7ClassifiedListingPartition { 201 FocusedFoodAvailability, 202 OperationalListing, 203 GenericNip99, 204 Ambiguous, 205 } 206 207 #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] 208 #[serde(deny_unknown_fields)] 209 pub struct RadrootsEventContractRegistryV7Tag { 210 pub name: String, 211 pub cardinality: RadrootsEventContractRegistryV7TagCardinality, 212 pub semantic: RadrootsEventContractRegistryV7TagSemantic, 213 pub value_type: RadrootsEventContractRegistryV7TagValueType, 214 pub relay_indexed: bool, 215 } 216 217 #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] 218 #[serde(rename_all = "snake_case")] 219 pub enum RadrootsEventContractRegistryV7TagCardinality { 220 RequiredOne, 221 OptionalOne, 222 OptionalMany, 223 RequiredMany, 224 } 225 226 #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] 227 #[serde(rename_all = "snake_case")] 228 pub enum RadrootsEventContractRegistryV7TagSemantic { 229 AddressableCoordinate, 230 CalendarEventAuthor, 231 CalendarEventReference, 232 CalendarEventRevision, 233 CalendarInclusionRequest, 234 CalendarEnd, 235 CalendarStart, 236 Category, 237 Citation, 238 Contract, 239 Counterparty, 240 Evidence, 241 EventPointer, 242 FreeBusy, 243 Geohash, 244 GroupId, 245 Identifier, 246 Image, 247 Kind, 248 ClassifiedListingAddress, 249 OperationalListingSnapshot, 250 ListDescription, 251 Location, 252 Nip01Coordinate, 253 Participant, 254 PreviousEvent, 255 Price, 256 PublishedAt, 257 Relay, 258 Reference, 259 ReviewTarget, 260 RootEvent, 261 ServiceInput, 262 ServiceOutput, 263 Source, 264 Status, 265 Summary, 266 Title, 267 Topic, 268 TimeZone, 269 TradeMutation, 270 Url, 271 UtcDayCoverage, 272 } 273 274 #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] 275 #[serde(rename_all = "snake_case")] 276 pub enum RadrootsEventContractRegistryV7TagValueType { 277 AddressableCoordinate, 278 CalendarDate, 279 CalendarEventCoordinate, 280 CalendarFreeBusy, 281 CalendarRsvpStatus, 282 CalendarUid, 283 ContractId, 284 DTag, 285 EventId, 286 EventPointer, 287 Geohash, 288 IanaTimeZoneId, 289 Kind, 290 MutationId, 291 Nip01Coordinate, 292 PublicKey, 293 RelayUrl, 294 Sha256, 295 Text, 296 UnixTimestamp, 297 Uri, 298 Url, 299 UtcDayIndex, 300 Uuid, 301 } 302 303 #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] 304 #[serde(rename_all = "snake_case")] 305 pub enum RadrootsEventContractRegistryV7Reducer { 306 CalendarProjection, 307 FarmOpsProjection, 308 GroupProjection, 309 KnowledgeProjection, 310 OperationalListingInventoryAccounting, 311 OperationalListingProjection, 312 MarketProjection, 313 OrderProjection, 314 ProfileProjection, 315 NostrRelayPolicyProjection, 316 SocialProjection, 317 TradeProjection, 318 TradeValidation, 319 } 320 321 pub fn event_contract_registry_v7_inventory() -> RadrootsEventContractRegistryV7Inventory { 322 RadrootsEventContractRegistryV7Inventory { 323 schema_version: RADROOTS_EVENT_CONTRACT_REGISTRY_V7_INVENTORY_SCHEMA_VERSION, 324 event_contract_registry_version: RADROOTS_EVENT_CONTRACT_REGISTRY_V7_VERSION, 325 kind_contracts: all_kind_contracts_registry_v7() 326 .iter() 327 .enumerate() 328 .map(|(ordinal, contract)| kind_inventory_entry(ordinal, contract)) 329 .collect(), 330 event_contracts: all_event_contracts_registry_v7() 331 .iter() 332 .enumerate() 333 .map(|(ordinal, contract)| event_inventory_entry(ordinal, contract)) 334 .collect(), 335 } 336 } 337 338 pub fn event_contract_registry_v7_inventory_json() -> Result<String, serde_json::Error> { 339 canonical_manifest_json(&event_contract_registry_v7_inventory()) 340 } 341 342 pub fn event_contract_registry_v7_inventory_sha256() -> Result<String, serde_json::Error> { 343 let json = event_contract_registry_v7_inventory_json()?; 344 Ok(manifest_sha256(&json)) 345 } 346 347 pub fn parse_event_contract_registry_v7_inventory_json( 348 json: &str, 349 ) -> Result<RadrootsEventContractRegistryV7Inventory, serde_json::Error> { 350 parse_manifest_json(json) 351 } 352 353 fn kind_inventory_entry( 354 ordinal: usize, 355 contract: &KindContract, 356 ) -> RadrootsKindContractRegistryV7InventoryEntry { 357 RadrootsKindContractRegistryV7InventoryEntry { 358 ordinal, 359 kind: contract.kind, 360 canonical_constant: contract.canonical_constant.to_string(), 361 name: contract.name.to_string(), 362 class: class_inventory(contract.class), 363 standard: standard_inventory(contract.standard), 364 accepted_event_contracts: contract 365 .accepted_event_contracts 366 .iter() 367 .map(|contract_id| (*contract_id).to_string()) 368 .collect(), 369 } 370 } 371 372 fn event_inventory_entry( 373 ordinal: usize, 374 contract: &EventContract, 375 ) -> RadrootsEventContractRegistryV7InventoryEntry { 376 RadrootsEventContractRegistryV7InventoryEntry { 377 ordinal, 378 contract_id: contract.id.to_string(), 379 kind: contract.kind, 380 name: contract.name.to_string(), 381 payload_type: contract.payload_type.to_string(), 382 class: class_inventory(contract.class), 383 stability: stability_inventory(contract.stability), 384 privacy: privacy_inventory(contract.privacy), 385 author_role: author_role_inventory(contract.required_author_role()), 386 content_schema: content_schema_inventory(contract.content_schema), 387 authoring_policy: authoring_policy_inventory(contract.authoring_policy()), 388 discriminator: discriminator_inventory(&contract.discriminator), 389 tags: contract.tags.iter().map(tag_inventory).collect(), 390 reducers: contract 391 .reducers 392 .iter() 393 .copied() 394 .map(reducer_inventory) 395 .collect(), 396 } 397 } 398 399 fn class_inventory(value: EventClass) -> RadrootsEventContractRegistryV7Class { 400 match value { 401 EventClass::Regular => RadrootsEventContractRegistryV7Class::Regular, 402 EventClass::Replaceable => RadrootsEventContractRegistryV7Class::Replaceable, 403 EventClass::Addressable => RadrootsEventContractRegistryV7Class::Addressable, 404 EventClass::Ephemeral => RadrootsEventContractRegistryV7Class::Ephemeral, 405 } 406 } 407 408 fn standard_inventory(value: NostrStandard) -> RadrootsEventContractRegistryV7Standard { 409 match value { 410 NostrStandard::Nip01 => RadrootsEventContractRegistryV7Standard::Nip01, 411 NostrStandard::Nip09 => RadrootsEventContractRegistryV7Standard::Nip09, 412 NostrStandard::Nip17 => RadrootsEventContractRegistryV7Standard::Nip17, 413 NostrStandard::Nip18 => RadrootsEventContractRegistryV7Standard::Nip18, 414 NostrStandard::Nip22 => RadrootsEventContractRegistryV7Standard::Nip22, 415 NostrStandard::Nip23 => RadrootsEventContractRegistryV7Standard::Nip23, 416 NostrStandard::Nip25 => RadrootsEventContractRegistryV7Standard::Nip25, 417 NostrStandard::Nip28 => RadrootsEventContractRegistryV7Standard::Nip28, 418 NostrStandard::Nip29 => RadrootsEventContractRegistryV7Standard::Nip29, 419 NostrStandard::Nip42 => RadrootsEventContractRegistryV7Standard::Nip42, 420 NostrStandard::Nip51 => RadrootsEventContractRegistryV7Standard::Nip51, 421 NostrStandard::Nip52 => RadrootsEventContractRegistryV7Standard::Nip52, 422 NostrStandard::Nip53 => RadrootsEventContractRegistryV7Standard::Nip53, 423 NostrStandard::Nip54 => RadrootsEventContractRegistryV7Standard::Nip54, 424 NostrStandard::Nip56 => RadrootsEventContractRegistryV7Standard::Nip56, 425 NostrStandard::Nip57 => RadrootsEventContractRegistryV7Standard::Nip57, 426 NostrStandard::Nip78 => RadrootsEventContractRegistryV7Standard::Nip78, 427 NostrStandard::Nip90 => RadrootsEventContractRegistryV7Standard::Nip90, 428 NostrStandard::Nip94 => RadrootsEventContractRegistryV7Standard::Nip94, 429 NostrStandard::Nip98 => RadrootsEventContractRegistryV7Standard::Nip98, 430 NostrStandard::Nip99 => RadrootsEventContractRegistryV7Standard::Nip99, 431 NostrStandard::Radroots => RadrootsEventContractRegistryV7Standard::Radroots, 432 } 433 } 434 435 fn stability_inventory(value: EventStability) -> RadrootsEventContractRegistryV7Stability { 436 match value { 437 EventStability::Stable => RadrootsEventContractRegistryV7Stability::Stable, 438 EventStability::Experimental => RadrootsEventContractRegistryV7Stability::Experimental, 439 } 440 } 441 442 fn privacy_inventory(value: EventPrivacy) -> RadrootsEventContractRegistryV7Privacy { 443 match value { 444 EventPrivacy::Public => RadrootsEventContractRegistryV7Privacy::Public, 445 EventPrivacy::Encrypted => RadrootsEventContractRegistryV7Privacy::Encrypted, 446 EventPrivacy::LocalOnly => RadrootsEventContractRegistryV7Privacy::LocalOnly, 447 EventPrivacy::Secret => RadrootsEventContractRegistryV7Privacy::Secret, 448 } 449 } 450 451 fn author_role_inventory(value: AuthorRole) -> RadrootsEventContractRegistryV7ActorRole { 452 match value { 453 AuthorRole::Any => RadrootsEventContractRegistryV7ActorRole::Any, 454 AuthorRole::Application => RadrootsEventContractRegistryV7ActorRole::Application, 455 AuthorRole::Buyer => RadrootsEventContractRegistryV7ActorRole::Buyer, 456 AuthorRole::Farmer => RadrootsEventContractRegistryV7ActorRole::Farmer, 457 AuthorRole::Member => RadrootsEventContractRegistryV7ActorRole::Member, 458 AuthorRole::Moderator => RadrootsEventContractRegistryV7ActorRole::Moderator, 459 AuthorRole::Relay => RadrootsEventContractRegistryV7ActorRole::Relay, 460 AuthorRole::Seller => RadrootsEventContractRegistryV7ActorRole::Seller, 461 AuthorRole::Service => RadrootsEventContractRegistryV7ActorRole::Service, 462 } 463 } 464 465 fn content_schema_inventory(value: ContentSchema) -> RadrootsEventContractRegistryV7ContentSchema { 466 match value { 467 ContentSchema::Empty => RadrootsEventContractRegistryV7ContentSchema::Empty, 468 ContentSchema::JsonObject => RadrootsEventContractRegistryV7ContentSchema::JsonObject, 469 ContentSchema::PlainText => RadrootsEventContractRegistryV7ContentSchema::PlainText, 470 ContentSchema::Markdown => RadrootsEventContractRegistryV7ContentSchema::Markdown, 471 ContentSchema::Djot => RadrootsEventContractRegistryV7ContentSchema::Djot, 472 ContentSchema::Encrypted => RadrootsEventContractRegistryV7ContentSchema::Encrypted, 473 ContentSchema::BinaryReference => { 474 RadrootsEventContractRegistryV7ContentSchema::BinaryReference 475 } 476 } 477 } 478 479 fn authoring_policy_inventory( 480 value: EventAuthoringPolicy, 481 ) -> RadrootsEventContractRegistryV7AuthoringPolicy { 482 match value { 483 EventAuthoringPolicy::GenericDraft => { 484 RadrootsEventContractRegistryV7AuthoringPolicy::GenericDraft 485 } 486 EventAuthoringPolicy::TypedOnly => { 487 RadrootsEventContractRegistryV7AuthoringPolicy::TypedOnly 488 } 489 EventAuthoringPolicy::ReadOnly => RadrootsEventContractRegistryV7AuthoringPolicy::ReadOnly, 490 } 491 } 492 493 fn discriminator_inventory( 494 value: &EventDiscriminator, 495 ) -> RadrootsEventContractRegistryV7Discriminator { 496 match value { 497 EventDiscriminator::KindOnly => RadrootsEventContractRegistryV7Discriminator::KindOnly, 498 EventDiscriminator::AdmissionOnly => { 499 RadrootsEventContractRegistryV7Discriminator::AdmissionOnly 500 } 501 EventDiscriminator::ClassifiedListingPartition(value) => { 502 RadrootsEventContractRegistryV7Discriminator::ClassifiedListingPartition { 503 value: classified_listing_partition_inventory(*value), 504 } 505 } 506 EventDiscriminator::DTagExact(value) => { 507 RadrootsEventContractRegistryV7Discriminator::DTagExact { 508 value: (*value).to_string(), 509 } 510 } 511 EventDiscriminator::DTagPrefix(prefix) => { 512 RadrootsEventContractRegistryV7Discriminator::DTagPrefix { 513 prefix: (*prefix).to_string(), 514 } 515 } 516 EventDiscriminator::DTagSuffix(suffix) => { 517 RadrootsEventContractRegistryV7Discriminator::DTagSuffix { 518 suffix: (*suffix).to_string(), 519 } 520 } 521 EventDiscriminator::TagEquals { name, value } => { 522 RadrootsEventContractRegistryV7Discriminator::TagEquals { 523 name: (*name).to_string(), 524 value: (*value).to_string(), 525 } 526 } 527 EventDiscriminator::ContentJsonFieldEquals { field, value } => { 528 RadrootsEventContractRegistryV7Discriminator::ContentJsonFieldEquals { 529 field: (*field).to_string(), 530 value: (*value).to_string(), 531 } 532 } 533 EventDiscriminator::EnvelopeType(value) => { 534 RadrootsEventContractRegistryV7Discriminator::EnvelopeType { 535 value: (*value).to_string(), 536 } 537 } 538 EventDiscriminator::Composite(parts) => { 539 RadrootsEventContractRegistryV7Discriminator::Composite { 540 parts: parts.iter().map(discriminator_inventory).collect(), 541 } 542 } 543 } 544 } 545 546 fn discriminator_from_json_value( 547 value: &serde_json::Value, 548 ) -> Result<RadrootsEventContractRegistryV7Discriminator, String> { 549 let object = value 550 .as_object() 551 .ok_or_else(|| "registry-v7 discriminator must be an object".to_string())?; 552 let discriminator_type = object 553 .get("type") 554 .and_then(serde_json::Value::as_str) 555 .ok_or_else(|| "registry-v7 discriminator type must be a string".to_string())?; 556 557 match discriminator_type { 558 "kind_only" => { 559 require_exact_discriminator_fields(object, &["type"])?; 560 Ok(RadrootsEventContractRegistryV7Discriminator::KindOnly) 561 } 562 "admission_only" => { 563 require_exact_discriminator_fields(object, &["type"])?; 564 Ok(RadrootsEventContractRegistryV7Discriminator::AdmissionOnly) 565 } 566 "classified_listing_partition" => { 567 require_exact_discriminator_fields(object, &["type", "value"])?; 568 let value = serde_json::from_value( 569 object 570 .get("value") 571 .cloned() 572 .ok_or_else(|| "registry-v7 discriminator is missing value".to_string())?, 573 ) 574 .map_err(|error| { 575 format!("invalid registry-v7 classified-listing discriminator value: {error}") 576 })?; 577 Ok(RadrootsEventContractRegistryV7Discriminator::ClassifiedListingPartition { value }) 578 } 579 "d_tag_exact" => { 580 require_exact_discriminator_fields(object, &["type", "value"])?; 581 Ok(RadrootsEventContractRegistryV7Discriminator::DTagExact { 582 value: discriminator_string_field(object, "value")?, 583 }) 584 } 585 "d_tag_prefix" => { 586 require_exact_discriminator_fields(object, &["type", "prefix"])?; 587 Ok(RadrootsEventContractRegistryV7Discriminator::DTagPrefix { 588 prefix: discriminator_string_field(object, "prefix")?, 589 }) 590 } 591 "d_tag_suffix" => { 592 require_exact_discriminator_fields(object, &["type", "suffix"])?; 593 Ok(RadrootsEventContractRegistryV7Discriminator::DTagSuffix { 594 suffix: discriminator_string_field(object, "suffix")?, 595 }) 596 } 597 "tag_equals" => { 598 require_exact_discriminator_fields(object, &["type", "name", "value"])?; 599 Ok(RadrootsEventContractRegistryV7Discriminator::TagEquals { 600 name: discriminator_string_field(object, "name")?, 601 value: discriminator_string_field(object, "value")?, 602 }) 603 } 604 "content_json_field_equals" => { 605 require_exact_discriminator_fields(object, &["type", "field", "value"])?; 606 Ok( 607 RadrootsEventContractRegistryV7Discriminator::ContentJsonFieldEquals { 608 field: discriminator_string_field(object, "field")?, 609 value: discriminator_string_field(object, "value")?, 610 }, 611 ) 612 } 613 "envelope_type" => { 614 require_exact_discriminator_fields(object, &["type", "value"])?; 615 Ok(RadrootsEventContractRegistryV7Discriminator::EnvelopeType { 616 value: discriminator_string_field(object, "value")?, 617 }) 618 } 619 "composite" => { 620 require_exact_discriminator_fields(object, &["type", "parts"])?; 621 let parts = serde_json::from_value( 622 object 623 .get("parts") 624 .cloned() 625 .ok_or_else(|| "registry-v7 discriminator is missing parts".to_string())?, 626 ) 627 .map_err(|error| format!("invalid registry-v7 composite discriminator: {error}"))?; 628 Ok(RadrootsEventContractRegistryV7Discriminator::Composite { parts }) 629 } 630 unknown => Err(format!( 631 "unknown registry-v7 discriminator type {unknown:?}" 632 )), 633 } 634 } 635 636 fn require_exact_discriminator_fields( 637 object: &serde_json::Map<String, serde_json::Value>, 638 expected: &[&str], 639 ) -> Result<(), String> { 640 if object.len() != expected.len() 641 || object 642 .keys() 643 .any(|field| !expected.contains(&field.as_str())) 644 { 645 return Err("registry-v7 discriminator contains unknown or missing fields".to_string()); 646 } 647 Ok(()) 648 } 649 650 fn discriminator_string_field( 651 object: &serde_json::Map<String, serde_json::Value>, 652 field: &str, 653 ) -> Result<String, String> { 654 object 655 .get(field) 656 .and_then(serde_json::Value::as_str) 657 .map(ToString::to_string) 658 .ok_or_else(|| format!("registry-v7 discriminator {field} must be a string")) 659 } 660 661 fn classified_listing_partition_inventory( 662 value: ClassifiedListingPartition, 663 ) -> RadrootsEventContractRegistryV7ClassifiedListingPartition { 664 match value { 665 ClassifiedListingPartition::FocusedFoodAvailability => { 666 RadrootsEventContractRegistryV7ClassifiedListingPartition::FocusedFoodAvailability 667 } 668 ClassifiedListingPartition::OperationalListing => { 669 RadrootsEventContractRegistryV7ClassifiedListingPartition::OperationalListing 670 } 671 ClassifiedListingPartition::GenericNip99 => { 672 RadrootsEventContractRegistryV7ClassifiedListingPartition::GenericNip99 673 } 674 ClassifiedListingPartition::Ambiguous => { 675 RadrootsEventContractRegistryV7ClassifiedListingPartition::Ambiguous 676 } 677 } 678 } 679 680 fn tag_inventory(contract: &TagContract) -> RadrootsEventContractRegistryV7Tag { 681 RadrootsEventContractRegistryV7Tag { 682 name: contract.name.to_string(), 683 cardinality: tag_cardinality_inventory(contract.cardinality), 684 semantic: tag_semantic_inventory(contract.semantic), 685 value_type: tag_value_type_inventory(contract.value_type), 686 relay_indexed: contract.relay_indexed, 687 } 688 } 689 690 fn tag_cardinality_inventory( 691 value: TagCardinality, 692 ) -> RadrootsEventContractRegistryV7TagCardinality { 693 match value { 694 TagCardinality::RequiredOne => RadrootsEventContractRegistryV7TagCardinality::RequiredOne, 695 TagCardinality::OptionalOne => RadrootsEventContractRegistryV7TagCardinality::OptionalOne, 696 TagCardinality::OptionalMany => RadrootsEventContractRegistryV7TagCardinality::OptionalMany, 697 TagCardinality::RequiredMany => RadrootsEventContractRegistryV7TagCardinality::RequiredMany, 698 } 699 } 700 701 fn tag_semantic_inventory(value: TagSemantic) -> RadrootsEventContractRegistryV7TagSemantic { 702 match value { 703 TagSemantic::AddressableCoordinate => { 704 RadrootsEventContractRegistryV7TagSemantic::AddressableCoordinate 705 } 706 TagSemantic::CalendarEventAuthor => { 707 RadrootsEventContractRegistryV7TagSemantic::CalendarEventAuthor 708 } 709 TagSemantic::CalendarEventReference => { 710 RadrootsEventContractRegistryV7TagSemantic::CalendarEventReference 711 } 712 TagSemantic::CalendarEventRevision => { 713 RadrootsEventContractRegistryV7TagSemantic::CalendarEventRevision 714 } 715 TagSemantic::CalendarInclusionRequest => { 716 RadrootsEventContractRegistryV7TagSemantic::CalendarInclusionRequest 717 } 718 TagSemantic::CalendarEnd => RadrootsEventContractRegistryV7TagSemantic::CalendarEnd, 719 TagSemantic::CalendarStart => RadrootsEventContractRegistryV7TagSemantic::CalendarStart, 720 TagSemantic::Category => RadrootsEventContractRegistryV7TagSemantic::Category, 721 TagSemantic::Citation => RadrootsEventContractRegistryV7TagSemantic::Citation, 722 TagSemantic::Contract => RadrootsEventContractRegistryV7TagSemantic::Contract, 723 TagSemantic::Counterparty => RadrootsEventContractRegistryV7TagSemantic::Counterparty, 724 TagSemantic::Evidence => RadrootsEventContractRegistryV7TagSemantic::Evidence, 725 TagSemantic::EventPointer => RadrootsEventContractRegistryV7TagSemantic::EventPointer, 726 TagSemantic::FreeBusy => RadrootsEventContractRegistryV7TagSemantic::FreeBusy, 727 TagSemantic::Geohash => RadrootsEventContractRegistryV7TagSemantic::Geohash, 728 TagSemantic::GroupId => RadrootsEventContractRegistryV7TagSemantic::GroupId, 729 TagSemantic::Identifier => RadrootsEventContractRegistryV7TagSemantic::Identifier, 730 TagSemantic::Image => RadrootsEventContractRegistryV7TagSemantic::Image, 731 TagSemantic::Kind => RadrootsEventContractRegistryV7TagSemantic::Kind, 732 TagSemantic::ClassifiedListingAddress => { 733 RadrootsEventContractRegistryV7TagSemantic::ClassifiedListingAddress 734 } 735 TagSemantic::OperationalListingSnapshot => { 736 RadrootsEventContractRegistryV7TagSemantic::OperationalListingSnapshot 737 } 738 TagSemantic::ListDescription => RadrootsEventContractRegistryV7TagSemantic::ListDescription, 739 TagSemantic::Location => RadrootsEventContractRegistryV7TagSemantic::Location, 740 TagSemantic::Nip01Coordinate => RadrootsEventContractRegistryV7TagSemantic::Nip01Coordinate, 741 TagSemantic::Participant => RadrootsEventContractRegistryV7TagSemantic::Participant, 742 TagSemantic::PreviousEvent => RadrootsEventContractRegistryV7TagSemantic::PreviousEvent, 743 TagSemantic::Price => RadrootsEventContractRegistryV7TagSemantic::Price, 744 TagSemantic::PublishedAt => RadrootsEventContractRegistryV7TagSemantic::PublishedAt, 745 TagSemantic::Relay => RadrootsEventContractRegistryV7TagSemantic::Relay, 746 TagSemantic::Reference => RadrootsEventContractRegistryV7TagSemantic::Reference, 747 TagSemantic::ReviewTarget => RadrootsEventContractRegistryV7TagSemantic::ReviewTarget, 748 TagSemantic::RootEvent => RadrootsEventContractRegistryV7TagSemantic::RootEvent, 749 TagSemantic::ServiceInput => RadrootsEventContractRegistryV7TagSemantic::ServiceInput, 750 TagSemantic::ServiceOutput => RadrootsEventContractRegistryV7TagSemantic::ServiceOutput, 751 TagSemantic::Source => RadrootsEventContractRegistryV7TagSemantic::Source, 752 TagSemantic::Status => RadrootsEventContractRegistryV7TagSemantic::Status, 753 TagSemantic::Summary => RadrootsEventContractRegistryV7TagSemantic::Summary, 754 TagSemantic::Title => RadrootsEventContractRegistryV7TagSemantic::Title, 755 TagSemantic::Topic => RadrootsEventContractRegistryV7TagSemantic::Topic, 756 TagSemantic::TimeZone => RadrootsEventContractRegistryV7TagSemantic::TimeZone, 757 TagSemantic::TradeMutation => RadrootsEventContractRegistryV7TagSemantic::TradeMutation, 758 TagSemantic::Url => RadrootsEventContractRegistryV7TagSemantic::Url, 759 TagSemantic::UtcDayCoverage => RadrootsEventContractRegistryV7TagSemantic::UtcDayCoverage, 760 } 761 } 762 763 fn tag_value_type_inventory(value: TagValueType) -> RadrootsEventContractRegistryV7TagValueType { 764 match value { 765 TagValueType::AddressableCoordinate => { 766 RadrootsEventContractRegistryV7TagValueType::AddressableCoordinate 767 } 768 TagValueType::CalendarDate => RadrootsEventContractRegistryV7TagValueType::CalendarDate, 769 TagValueType::CalendarEventCoordinate => { 770 RadrootsEventContractRegistryV7TagValueType::CalendarEventCoordinate 771 } 772 TagValueType::CalendarFreeBusy => { 773 RadrootsEventContractRegistryV7TagValueType::CalendarFreeBusy 774 } 775 TagValueType::CalendarRsvpStatus => { 776 RadrootsEventContractRegistryV7TagValueType::CalendarRsvpStatus 777 } 778 TagValueType::CalendarUid => RadrootsEventContractRegistryV7TagValueType::CalendarUid, 779 TagValueType::ContractId => RadrootsEventContractRegistryV7TagValueType::ContractId, 780 TagValueType::DTag => RadrootsEventContractRegistryV7TagValueType::DTag, 781 TagValueType::EventId => RadrootsEventContractRegistryV7TagValueType::EventId, 782 TagValueType::EventPointer => RadrootsEventContractRegistryV7TagValueType::EventPointer, 783 TagValueType::Geohash => RadrootsEventContractRegistryV7TagValueType::Geohash, 784 TagValueType::IanaTimeZoneId => RadrootsEventContractRegistryV7TagValueType::IanaTimeZoneId, 785 TagValueType::Kind => RadrootsEventContractRegistryV7TagValueType::Kind, 786 TagValueType::MutationId => RadrootsEventContractRegistryV7TagValueType::MutationId, 787 TagValueType::Nip01Coordinate => { 788 RadrootsEventContractRegistryV7TagValueType::Nip01Coordinate 789 } 790 TagValueType::PublicKey => RadrootsEventContractRegistryV7TagValueType::PublicKey, 791 TagValueType::RelayUrl => RadrootsEventContractRegistryV7TagValueType::RelayUrl, 792 TagValueType::Sha256 => RadrootsEventContractRegistryV7TagValueType::Sha256, 793 TagValueType::Text => RadrootsEventContractRegistryV7TagValueType::Text, 794 TagValueType::UnixTimestamp => RadrootsEventContractRegistryV7TagValueType::UnixTimestamp, 795 TagValueType::Uri => RadrootsEventContractRegistryV7TagValueType::Uri, 796 TagValueType::Url => RadrootsEventContractRegistryV7TagValueType::Url, 797 TagValueType::UtcDayIndex => RadrootsEventContractRegistryV7TagValueType::UtcDayIndex, 798 TagValueType::Uuid => RadrootsEventContractRegistryV7TagValueType::Uuid, 799 } 800 } 801 802 fn reducer_inventory(value: Reducer) -> RadrootsEventContractRegistryV7Reducer { 803 match value { 804 Reducer::CalendarProjection => RadrootsEventContractRegistryV7Reducer::CalendarProjection, 805 Reducer::FarmOpsProjection => RadrootsEventContractRegistryV7Reducer::FarmOpsProjection, 806 Reducer::GroupProjection => RadrootsEventContractRegistryV7Reducer::GroupProjection, 807 Reducer::KnowledgeProjection => RadrootsEventContractRegistryV7Reducer::KnowledgeProjection, 808 Reducer::OperationalListingInventoryAccounting => { 809 RadrootsEventContractRegistryV7Reducer::OperationalListingInventoryAccounting 810 } 811 Reducer::OperationalListingProjection => { 812 RadrootsEventContractRegistryV7Reducer::OperationalListingProjection 813 } 814 Reducer::MarketProjection => RadrootsEventContractRegistryV7Reducer::MarketProjection, 815 Reducer::OrderProjection => RadrootsEventContractRegistryV7Reducer::OrderProjection, 816 Reducer::ProfileProjection => RadrootsEventContractRegistryV7Reducer::ProfileProjection, 817 Reducer::NostrRelayPolicyProjection => { 818 RadrootsEventContractRegistryV7Reducer::NostrRelayPolicyProjection 819 } 820 Reducer::SocialProjection => RadrootsEventContractRegistryV7Reducer::SocialProjection, 821 Reducer::TradeProjection => RadrootsEventContractRegistryV7Reducer::TradeProjection, 822 Reducer::TradeValidation => RadrootsEventContractRegistryV7Reducer::TradeValidation, 823 } 824 } 825 826 #[cfg(test)] 827 mod tests { 828 use super::*; 829 830 #[test] 831 fn inventory_pins_runtime_order_and_counts() { 832 let inventory = event_contract_registry_v7_inventory(); 833 834 assert_eq!( 835 inventory.schema_version, 836 RADROOTS_EVENT_CONTRACT_REGISTRY_V7_INVENTORY_SCHEMA_VERSION 837 ); 838 assert_eq!( 839 inventory.event_contract_registry_version, 840 RADROOTS_EVENT_CONTRACT_REGISTRY_V7_VERSION 841 ); 842 assert_eq!( 843 inventory.kind_contracts.len(), 844 RADROOTS_EVENT_CONTRACT_REGISTRY_V7_KIND_COUNT 845 ); 846 assert_eq!( 847 inventory.event_contracts.len(), 848 RADROOTS_EVENT_CONTRACT_REGISTRY_V7_EVENT_COUNT 849 ); 850 assert!( 851 inventory 852 .kind_contracts 853 .iter() 854 .enumerate() 855 .all(|(ordinal, contract)| contract.ordinal == ordinal) 856 ); 857 assert!( 858 inventory 859 .event_contracts 860 .iter() 861 .enumerate() 862 .all(|(ordinal, contract)| contract.ordinal == ordinal) 863 ); 864 } 865 866 #[test] 867 fn inventory_json_is_canonical_and_round_trips_strictly() { 868 let json = event_contract_registry_v7_inventory_json().expect("serialize inventory"); 869 870 assert!(json.ends_with('\n')); 871 assert!(!json.ends_with("\n\n")); 872 assert!(!json.contains("\r\n")); 873 assert!(json.contains("\n \"schema_version\": 1,")); 874 assert_eq!( 875 parse_event_contract_registry_v7_inventory_json(&json).expect("parse inventory"), 876 event_contract_registry_v7_inventory() 877 ); 878 } 879 880 #[test] 881 fn inventory_parser_rejects_unknown_fields_and_enum_values() { 882 let mut top_level = 883 serde_json::to_value(event_contract_registry_v7_inventory()).expect("inventory value"); 884 top_level 885 .as_object_mut() 886 .expect("inventory object") 887 .insert("unknown".to_string(), serde_json::Value::Bool(true)); 888 assert!( 889 serde_json::from_value::<RadrootsEventContractRegistryV7Inventory>(top_level).is_err() 890 ); 891 892 let mut nested = 893 serde_json::to_value(event_contract_registry_v7_inventory()).expect("inventory value"); 894 nested["event_contracts"][0]["discriminator"]["unknown"] = serde_json::Value::Bool(true); 895 assert!( 896 serde_json::from_value::<RadrootsEventContractRegistryV7Inventory>(nested).is_err() 897 ); 898 899 let mut enum_value = 900 serde_json::to_value(event_contract_registry_v7_inventory()).expect("inventory value"); 901 enum_value["kind_contracts"][0]["class"] = 902 serde_json::Value::String("future_class".to_string()); 903 assert!( 904 serde_json::from_value::<RadrootsEventContractRegistryV7Inventory>(enum_value).is_err() 905 ); 906 } 907 908 #[test] 909 fn inventory_digest_is_deterministic_lowercase_hex() { 910 let first = event_contract_registry_v7_inventory_sha256().expect("hash inventory"); 911 let second = event_contract_registry_v7_inventory_sha256().expect("hash inventory"); 912 913 assert_eq!(first, second); 914 assert_eq!(first.len(), 64); 915 assert!( 916 first 917 .bytes() 918 .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte)) 919 ); 920 } 921 }