lib

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

commit 63599d4d1ed8585f9f10a2097310dd9d385cd474
parent 27a35becfbfae9977533c6700ac77869d0197f1d
Author: triesap <tyson@radroots.org>
Date:   Sun, 21 Jun 2026 21:14:02 +0000

events-codec: cover relay list validation

- Cover relay list encode/decode validation for ws relay URLs, non-r tags, missing URLs, and oversized marker shapes.

- Preserve existing NIP-65 roundtrip assertions while covering both ws and wss accepted relay forms.

- Validate the list target, full radroots_events_codec tests, crate check, diff check, and refreshed coverage run.

Diffstat:
Mcrates/events_codec/tests/list.rs | 84+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 82 insertions(+), 2 deletions(-)

diff --git a/crates/events_codec/tests/list.rs b/crates/events_codec/tests/list.rs @@ -202,23 +202,74 @@ fn relay_list_kind_roundtrips_nip65_r_tags() { tag: TAG_R.to_string(), values: vec!["wss://both.example.test".to_string()], }, + RadrootsListEntry { + tag: TAG_R.to_string(), + values: vec!["ws://local-relay.example.test".to_string()], + }, ], }; let parts = to_wire_parts_with_kind(&list, KIND_LIST_READ_WRITE_RELAYS).unwrap(); assert_eq!(parts.kind, KIND_LIST_READ_WRITE_RELAYS); assert!(parts.content.is_empty()); - assert_eq!(parts.tags.len(), 3); + assert_eq!(parts.tags.len(), 4); let decoded = list_from_tags(parts.kind, parts.content, &parts.tags).unwrap(); - assert_eq!(decoded.entries.len(), 3); + assert_eq!(decoded.entries.len(), 4); assert_eq!(decoded.entries[0].values[1], "read"); assert_eq!(decoded.entries[1].values[1], "write"); assert_eq!(decoded.entries[2].values.len(), 1); + assert_eq!( + decoded.entries[3].values[0], + "ws://local-relay.example.test" + ); } #[test] fn relay_list_kind_validates_url_shape_and_markers() { + let invalid_tag = RadrootsList { + content: String::new(), + entries: vec![RadrootsListEntry { + tag: "p".to_string(), + values: vec!["wss://relay.example.test".to_string()], + }], + }; + assert!(matches!( + to_wire_parts_with_kind(&invalid_tag, KIND_LIST_READ_WRITE_RELAYS), + Err(EventEncodeError::InvalidField("relay.tag")) + )); + assert!(matches!( + list_from_tags( + KIND_LIST_READ_WRITE_RELAYS, + String::new(), + &[vec![ + "p".to_string(), + "wss://relay.example.test".to_string() + ]] + ), + Err(EventParseError::InvalidTag(TAG_R)) + )); + + let missing_url = RadrootsList { + content: String::new(), + entries: vec![RadrootsListEntry { + tag: TAG_R.to_string(), + values: Vec::new(), + }], + }; + assert!(matches!( + to_wire_parts_with_kind(&missing_url, KIND_LIST_READ_WRITE_RELAYS), + Err(EventEncodeError::EmptyRequiredField("relay.url")) + )); + assert!(matches!( + list_from_tags( + KIND_LIST_READ_WRITE_RELAYS, + String::new(), + &[vec![TAG_R.to_string()]] + ), + Err(EventParseError::InvalidTag(TAG_R)) + )); + let invalid_url = RadrootsList { content: String::new(), entries: vec![RadrootsListEntry { @@ -266,6 +317,35 @@ fn relay_list_kind_validates_url_shape_and_markers() { Err(EventParseError::InvalidTag(TAG_R)) )); + let extra_marker = RadrootsList { + content: String::new(), + entries: vec![RadrootsListEntry { + tag: TAG_R.to_string(), + values: vec![ + "wss://relay.example.test".to_string(), + "read".to_string(), + "write".to_string(), + ], + }], + }; + assert!(matches!( + to_wire_parts_with_kind(&extra_marker, KIND_LIST_READ_WRITE_RELAYS), + Err(EventEncodeError::InvalidField("relay.marker")) + )); + assert!(matches!( + list_from_tags( + KIND_LIST_READ_WRITE_RELAYS, + String::new(), + &[vec![ + TAG_R.to_string(), + "wss://relay.example.test".to_string(), + "read".to_string(), + "write".to_string() + ]] + ), + Err(EventParseError::InvalidTag(TAG_R)) + )); + let empty = RadrootsList { content: String::new(), entries: Vec::new(),