Add tags chapter

This commit is contained in:
Jon Staab
2026-04-16 16:49:18 -07:00
parent 2553cff300
commit 8a29ff39d6
6 changed files with 901 additions and 13 deletions
+4 -3
View File
@@ -2,6 +2,7 @@ use coracle_lib::events::{
Event, EventContent, EventError, EventTemplate, HashedEvent, OwnedEvent, StampedEvent,
};
use coracle_lib::keys::SecretKey;
use coracle_lib::tags::{Tag, Tags};
fn fixed_secret() -> SecretKey {
let bytes: [u8; 32] = [
@@ -11,7 +12,7 @@ fn fixed_secret() -> SecretKey {
SecretKey::from_hex(&hex::encode(bytes)).unwrap()
}
fn build_hashed(content: &str, tags: Vec<Vec<String>>) -> HashedEvent {
fn build_hashed(content: &str, tags: Vec<Tag>) -> HashedEvent {
EventContent::new(content, tags)
.kind(1)
.stamp(1_700_000_000)
@@ -21,14 +22,14 @@ fn build_hashed(content: &str, tags: Vec<Vec<String>>) -> HashedEvent {
fn sign_fixture() -> Event {
let sk = fixed_secret();
let hashed = build_hashed("hello nostr", vec![vec!["t".into(), "nostr".into()]]);
let hashed = build_hashed("hello nostr", vec![Tag::new("t", ["nostr"])]);
let sig = sk.sign(&hashed.id);
hashed.sign(sig)
}
#[test]
fn pipeline_progresses_through_types() {
let content = EventContent::new("hi", vec![]);
let content = EventContent::new("hi", Tags::new());
let template: EventTemplate = content.kind(1);
let stamped: StampedEvent = template.stamp(1_700_000_000);
let owned: OwnedEvent = stamped.own(fixed_secret().public_key());
+121
View File
@@ -0,0 +1,121 @@
use coracle_lib::tags::{Tag, Tags};
#[test]
fn new_tag_accessors() {
let t = Tag::new("e", ["abc", "wss://relay.example", "reply"]);
assert_eq!(t.name(), "e");
assert_eq!(t.value(), "abc");
assert_eq!(t.get(2), Some("wss://relay.example"));
assert_eq!(t.get(3), Some("reply"));
assert_eq!(t.get(4), None);
assert_eq!(t.len(), 4);
assert_eq!(t.values().len(), 3);
assert!(!t.is_empty());
}
#[test]
fn empty_tag_returns_empty_strings() {
let t = Tag(Vec::new());
assert_eq!(t.name(), "");
assert_eq!(t.value(), "");
assert!(t.values().is_empty());
assert!(t.is_empty());
}
#[test]
fn single_entry_tag_has_empty_value() {
let t = Tag::new("-", std::iter::empty::<String>());
assert_eq!(t.name(), "-");
assert_eq!(t.value(), "");
assert_eq!(t.len(), 1);
}
#[test]
fn from_and_into_vec_string() {
let raw = vec!["t".to_string(), "nostr".to_string()];
let tag: Tag = raw.clone().into();
assert_eq!(tag.name(), "t");
let back: Vec<String> = tag.into();
assert_eq!(back, raw);
}
#[test]
fn tag_serde_is_transparent() {
let tag = Tag::new("t", ["nostr"]);
let json = serde_json::to_string(&tag).unwrap();
assert_eq!(json, r#"["t","nostr"]"#);
let parsed: Tag = serde_json::from_str(r#"["p","abcdef"]"#).unwrap();
assert_eq!(parsed.name(), "p");
assert_eq!(parsed.value(), "abcdef");
}
fn sample_tags() -> Tags {
Tags::from(vec![
Tag::new("t", ["nostr"]),
Tag::new("t", ["rust"]),
Tag::new("p", ["abcd"]),
Tag::new("e", ["ffff", "wss://relay.example", "root"]),
])
}
#[test]
fn tags_serde_is_transparent() {
let tags = sample_tags();
let json = serde_json::to_string(&tags).unwrap();
// outer type is just an array of arrays
assert!(json.starts_with("[["));
let parsed: Tags = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, tags);
}
#[test]
fn find_returns_first_match() {
let ts = sample_tags();
let t = ts.find("t").unwrap();
assert_eq!(t.value(), "nostr");
assert!(ts.find("x").is_none());
}
#[test]
fn find_all_iterates_matches() {
let ts = sample_tags();
let found: Vec<&str> = ts.find_all("t").map(Tag::value).collect();
assert_eq!(found, vec!["nostr", "rust"]);
assert_eq!(ts.find_all("p").count(), 1);
assert_eq!(ts.find_all("x").count(), 0);
}
#[test]
fn value_and_values_helpers() {
let ts = sample_tags();
assert_eq!(ts.value("t"), Some("nostr"));
assert_eq!(ts.value("p"), Some("abcd"));
assert_eq!(ts.value("x"), None);
let all_t: Vec<&str> = ts.values("t").collect();
assert_eq!(all_t, vec!["nostr", "rust"]);
}
#[test]
fn has_checks_presence() {
let ts = sample_tags();
assert!(ts.has("p"));
assert!(ts.has("e"));
assert!(!ts.has("q"));
}
#[test]
fn tags_deref_to_slice() {
let ts = sample_tags();
// Deref lets us use slice methods directly.
assert_eq!(ts.len(), 4);
assert_eq!(ts[0].name(), "t");
let names: Vec<&str> = ts.iter().map(Tag::name).collect();
assert_eq!(names, vec!["t", "t", "p", "e"]);
}
#[test]
fn tags_new_is_empty() {
let ts = Tags::new();
assert!(ts.is_empty());
assert_eq!(ts.len(), 0);
}