Swap tags and events chapters
This commit is contained in:
@@ -1,13 +1,11 @@
|
||||
# Tags
|
||||
|
||||
Every nostr event has two halves. The `content` string is for humans:
|
||||
the text of a note, the prose of an article, the caption of an image.
|
||||
Everything else that matters to a machine — who this event replies to,
|
||||
which topics it belongs under, which pubkeys it mentions, which relay
|
||||
the author recommends, which article it updates — lives in `tags`.
|
||||
Nostr needs a way to attach structured data to things: who wrote
|
||||
this, what it replies to, which topics it belongs under, which relay
|
||||
to find something on, which article it updates. That's what tags are.
|
||||
|
||||
A tag is a list of strings. The first string names the tag; the rest
|
||||
are its values. An event's `tags` field is a list of these. That's the
|
||||
are its values. A collection of tags is a list of these. That's the
|
||||
whole definition:
|
||||
|
||||
```text
|
||||
@@ -21,12 +19,12 @@ It looks plainer than it is. Three choices in that shape matter.
|
||||
|
||||
**Lists of lists, not maps.** If tags were a dictionary, a key could
|
||||
only appear once and the order would be lost. Neither property is one
|
||||
nostr can give up. An event commonly references several pubkeys
|
||||
(`["p", ...]` repeated), several events (`["e", ...]` repeated), and
|
||||
several topics (`["t", ...]` repeated), and the order these appear in
|
||||
sometimes carries meaning — NIP-10 reply threads, for instance, use
|
||||
position as a fallback when explicit markers are missing. Lists of
|
||||
lists preserve both.
|
||||
nostr can give up. A piece of data commonly references several pubkeys
|
||||
(`["p", ...]` repeated), several other pieces of data (`["e", ...]`
|
||||
repeated), and several topics (`["t", ...]` repeated), and the order
|
||||
these appear in sometimes carries meaning — NIP-10 reply threads, for
|
||||
instance, use position as a fallback when explicit markers are missing.
|
||||
Lists of lists preserve both.
|
||||
|
||||
**Single-letter names are indexed.** Relays index tags whose name is a
|
||||
single letter (`a` through `z`, `A` through `Z`) and let clients query
|
||||
@@ -35,24 +33,20 @@ them with `#e`, `#p`, `#t` filters. Multi-character names — `alt`,
|
||||
distinction matters at design time: if you want something to be
|
||||
queryable, give it a single-letter name.
|
||||
|
||||
**Meaning is kind-dependent.** The `e` tag appears in eight different
|
||||
NIPs and means eight different things: a reply, a fork, a merge, a
|
||||
transaction reference, a report target, a list member, an approval,
|
||||
a mention. There is no way to interpret `["e", ...]` without first
|
||||
knowing the event's kind. The [building-nostr] philosophy puts this
|
||||
bluntly: "when resolving the meaning of a tag, always first look at
|
||||
the specifications for the event's kind." A library type that tries
|
||||
to parse tags into a taxonomy is betting on the wrong end of that
|
||||
rule.
|
||||
**Meaning is context-dependent.** The `e` tag appears in eight
|
||||
different NIPs and means eight different things: a reply, a fork, a
|
||||
merge, a transaction reference, a report target, a list member, an
|
||||
approval, a mention. There is no way to interpret `["e", ...]`
|
||||
without knowing the context it appears in. The [building-nostr]
|
||||
philosophy puts this bluntly: "when resolving the meaning of a tag,
|
||||
always first look at the specifications for the event's kind." A
|
||||
library type that tries to parse tags into a taxonomy is betting on
|
||||
the wrong end of that rule.
|
||||
|
||||
This chapter therefore introduces a type that is almost nothing. `Tag`
|
||||
wraps `Vec<String>` and adds five or six convenience methods. A
|
||||
handful of free functions query a slice of tags by name. That is
|
||||
enough for every caller we'll meet in the rest of the book. The one
|
||||
exception is the address tag, `a`, whose payload is three fields
|
||||
glued with colons — that gets a small `Address` struct, because those
|
||||
three fields always travel together and parsing them at the edge of
|
||||
your program is genuinely useful.
|
||||
wraps `Vec<String>` and adds five or six convenience methods. `Tags`
|
||||
wraps `Vec<Tag>` and adds query methods. That is enough for every
|
||||
caller we'll meet in the rest of the book.
|
||||
|
||||
[building-nostr]: https://building-nostr.coracle.social
|
||||
|
||||
@@ -175,20 +169,19 @@ impl From<Tag> for Vec<String> {
|
||||
|
||||
## A collection of tags
|
||||
|
||||
When you hold an `Event`, you usually want to ask questions of its
|
||||
`tags` field: does it have a `p` tag? What's its `d` value? Which
|
||||
topics is it tagged with? These come up often enough that writing the
|
||||
filter by hand every time is noise. They belong on a type.
|
||||
Wherever tags appear, you want to query them by name: does the
|
||||
collection have a `p` tag? What's its `d` value? Which topics are
|
||||
present? These come up often enough that writing the filter by hand
|
||||
every time is noise. They belong on a type.
|
||||
|
||||
We introduce `Tags` as a newtype around `Vec<Tag>`. Like `Tag` itself,
|
||||
the serde impl is transparent, so the wire format and canonical hash
|
||||
bytes are unchanged. A `Deref<Target = [Tag]>` impl gives iteration,
|
||||
indexing, and `len` for free, so the wrapper costs almost nothing at
|
||||
call sites.
|
||||
`Tags` is a newtype around `Vec<Tag>`. Like `Tag` itself, the serde
|
||||
impl is transparent, so the wire format is unchanged. A
|
||||
`Deref<Target = [Tag]>` impl gives iteration, indexing, and `len` for
|
||||
free, so the wrapper costs almost nothing at call sites.
|
||||
|
||||
```rust {file=coracle-lib/src/tags.rs}
|
||||
/// A collection of tags, usually taken from an [`Event`]'s `tags`
|
||||
/// field. Serializes transparently as its inner `Vec<Tag>`.
|
||||
/// A collection of tags. Serializes transparently as its inner
|
||||
/// `Vec<Tag>`.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct Tags(pub Vec<Tag>);
|
||||
@@ -254,14 +247,13 @@ impl FromIterator<Tag> for Tags {
|
||||
```
|
||||
|
||||
That's the entire query API. No `TagKind`, no marker enum, no parsed
|
||||
variants. Readers who want to know whether an event mentions a
|
||||
particular pubkey write `event.tags.values("p").any(|p| p == hex)` and
|
||||
the code reads the way the question sounds.
|
||||
variants. Checking whether a collection mentions a particular pubkey
|
||||
is `tags.values("p").any(|p| p == hex)` — the code reads the way the
|
||||
question sounds.
|
||||
|
||||
## What's next
|
||||
|
||||
We now have both halves of an event in their proper types: the
|
||||
cryptographic core from the last chapter and the structured data half
|
||||
from this one. The next chapter introduces kinds — the integer that
|
||||
decides how content and tags on a given event should be read — and
|
||||
with it the beginning of a taxonomy we have so far resisted building.
|
||||
Tags are the structured-data building block that every other type in
|
||||
the library will carry. The next chapter introduces the `Event` type —
|
||||
the signed, content-addressable JSON object at the heart of the nostr
|
||||
protocol — which uses `Tags` as one of its seven fields.
|
||||
@@ -41,13 +41,6 @@ use crate::keys::PublicKey;
|
||||
use crate::tags::Tags;
|
||||
```
|
||||
|
||||
The `Tag` and `Tags` types are introduced in the next chapter. For
|
||||
this chapter, treat `Tag` as a transparent wrapper around
|
||||
`Vec<String>` and `Tags` as a transparent wrapper around `Vec<Tag>` —
|
||||
serde sees them as bare arrays, the canonical form hashes identically,
|
||||
and you can build them with `Tag::new("t", ["nostr"])` and
|
||||
`Tags::from(vec![...])`.
|
||||
|
||||
## Errors
|
||||
|
||||
```rust {file=coracle-lib/src/events.rs}
|
||||
@@ -489,6 +482,6 @@ without paying for a signature check.
|
||||
|
||||
## What's next
|
||||
|
||||
The next chapter gives `tags` a proper type. Tags are the structured half of
|
||||
every event — references to other events, pubkeys, topics, relay hints — and
|
||||
treating them as bare `Vec<Vec<String>>` gets tiresome fast.
|
||||
The next chapter introduces kinds — the integer that decides how content
|
||||
and tags on a given event should be read — and with it the beginning of
|
||||
a taxonomy we have so far resisted building.
|
||||
+2
-2
@@ -6,8 +6,8 @@
|
||||
|
||||
- [Keys](02-keys.md)
|
||||
- [Encryption](03-encryption.md)
|
||||
- [Events](04-events.md)
|
||||
- [Tags](05-tags.md)
|
||||
- [Tags](04-tags.md)
|
||||
- [Events](05-events.md)
|
||||
- [Kinds](06-kinds.md)
|
||||
- [Kind Ranges](07-kind-ranges.md)
|
||||
- [Addresses](08-addresses.md)
|
||||
|
||||
Reference in New Issue
Block a user