Use traits to keep event methods dry
This commit is contained in:
@@ -525,6 +525,86 @@ Deserialization does not verify the signature. Parsing is cheap; `verify()`
|
||||
is not, and there are many places where you want to read an event off disk
|
||||
without paying for a signature check.
|
||||
|
||||
## A shared surface across event stages
|
||||
|
||||
There is a lot of overlap between various event structs. A few minimal accessor
|
||||
traits capture read access to various fields so that we can define extensions
|
||||
to events without duplication.
|
||||
|
||||
```rust {file=coracle-lib/src/events.rs}
|
||||
pub trait HasTags {
|
||||
fn tags(&self) -> &Tags;
|
||||
}
|
||||
|
||||
pub trait HasId {
|
||||
fn id(&self) -> &[u8; 32];
|
||||
}
|
||||
|
||||
impl HasTags for HashedEvent {
|
||||
fn tags(&self) -> &Tags {
|
||||
&self.tags
|
||||
}
|
||||
}
|
||||
|
||||
impl HasTags for Event {
|
||||
fn tags(&self) -> &Tags {
|
||||
&self.tags
|
||||
}
|
||||
}
|
||||
|
||||
impl HasId for HashedEvent {
|
||||
fn id(&self) -> &[u8; 32] {
|
||||
&self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl HasId for Event {
|
||||
fn id(&self) -> &[u8; 32] {
|
||||
&self.id
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Subsequent chapters will add *extension traits* bounded on these accessors,
|
||||
supply the method bodies as defaults, and close with a blanket impl over every
|
||||
type that satisfies the bound. The logic is written once and reaches both event
|
||||
types — and any event type added later — without another line per struct.
|
||||
The expiration chapter's version reads like this:
|
||||
|
||||
```rust
|
||||
pub trait EventExtensionExpiration: HasTags {
|
||||
fn is_expired(&self) -> bool {
|
||||
is_expired(self.tags())
|
||||
}
|
||||
// ...the rest of the expiration queries
|
||||
}
|
||||
|
||||
impl<T: HasTags> EventExtensionExpiration for T {}
|
||||
```
|
||||
|
||||
A trait's methods are only callable where the trait is in scope, so the
|
||||
library gathers these extension traits in a `prelude`. One glob import brings
|
||||
the whole behavior-query surface along:
|
||||
|
||||
```rust
|
||||
use coracle_lib::prelude::*;
|
||||
```
|
||||
|
||||
```rust {file=coracle-lib/src/lib.rs}
|
||||
pub mod prelude;
|
||||
```
|
||||
|
||||
```rust {file=coracle-lib/src/prelude.rs}
|
||||
//! One-stop import for the extension traits that add behavior-tag queries to
|
||||
//! events. With `coracle_lib::prelude::*` in scope, methods like
|
||||
//! `is_expired`, `is_protected`, and `get_pow` become callable on
|
||||
//! `HashedEvent` and `Event`.
|
||||
|
||||
pub use crate::events::{HasId, HasTags};
|
||||
```
|
||||
|
||||
Each later chapter that defines an extension trait adds it to this prelude.
|
||||
|
||||
## What's next
|
||||
|
||||
The next chapter introduces kinds — the integer that decides how content
|
||||
|
||||
+17
-17
@@ -44,7 +44,7 @@ pub mod pow;
|
||||
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
use crate::events::{Event, HashedEvent, OwnedEvent};
|
||||
use crate::events::{HasId, HasTags, HashedEvent, OwnedEvent};
|
||||
use crate::tags::{Tag, Tags};
|
||||
```
|
||||
|
||||
@@ -119,31 +119,31 @@ commitment was made.
|
||||
|
||||
### Methods on event types
|
||||
|
||||
Both `HashedEvent` and `Event` carry an id and tags, so both get a
|
||||
`get_pow` method that delegates to the free function. This lets callers
|
||||
write `event.get_pow() >= 20` regardless of whether the event has been
|
||||
signed yet.
|
||||
Proof of work is the first query that needs the event id as well as its
|
||||
tags, so its extension trait is bounded on both `HasId` and `HasTags` from
|
||||
the events chapter. The method body delegates to the free function through
|
||||
those accessors, and the blanket impl puts `get_pow` on every type that
|
||||
satisfies the bound — both `HashedEvent` and `Event` do. Callers write
|
||||
`event.get_pow() >= 20` regardless of whether the event has been signed yet,
|
||||
as long as the trait is in scope (via `coracle_lib::prelude::*`).
|
||||
|
||||
```rust {file=coracle-lib/src/pow.rs}
|
||||
impl HashedEvent {
|
||||
/// Proof-of-work queries on any event that has an id and tags.
|
||||
pub trait EventExtensionProofOfWork: HasId + HasTags {
|
||||
/// Return the validated proof-of-work difficulty of this event.
|
||||
///
|
||||
/// The result is the minimum of the actual leading zero bits in the
|
||||
/// event id and the difficulty claimed in the nonce tag.
|
||||
pub fn get_pow(&self) -> u8 {
|
||||
get_pow(&self.id, &self.tags)
|
||||
fn get_pow(&self) -> u8 {
|
||||
get_pow(self.id(), self.tags())
|
||||
}
|
||||
}
|
||||
|
||||
impl Event {
|
||||
/// Return the validated proof-of-work difficulty of this event.
|
||||
///
|
||||
/// The result is the minimum of the actual leading zero bits in the
|
||||
/// event id and the difficulty claimed in the nonce tag.
|
||||
pub fn get_pow(&self) -> u8 {
|
||||
get_pow(&self.id, &self.tags)
|
||||
}
|
||||
}
|
||||
impl<T: HasId + HasTags> EventExtensionProofOfWork for T {}
|
||||
```
|
||||
|
||||
```rust {file=coracle-lib/src/prelude.rs}
|
||||
pub use crate::pow::EventExtensionProofOfWork;
|
||||
```
|
||||
|
||||
## Mining
|
||||
|
||||
+24
-32
@@ -33,13 +33,13 @@ pub mod expiration;
|
||||
//! NIP-40 expiring events: reading the `expiration` tag and checking
|
||||
//! whether an event has expired.
|
||||
//!
|
||||
//! The free functions in this module operate on [`Tags`] so they can
|
||||
//! be used wherever a tag slice is in hand. Method versions on
|
||||
//! [`HashedEvent`] and [`Event`] delegate to them for a readable call
|
||||
//! site. The tag itself is built with [`Tag::expiration`] from the
|
||||
//! tags module.
|
||||
//! The free functions in this module operate on [`Tags`] so they can be
|
||||
//! used wherever a tag slice is in hand. The [`EventExtensionExpiration`]
|
||||
//! trait layers method-call sugar on top for any event type, reachable
|
||||
//! through the crate prelude. The tag itself is built with
|
||||
//! [`Tag::expiration`] from the tags module.
|
||||
|
||||
use crate::events::{Event, HashedEvent};
|
||||
use crate::events::HasTags;
|
||||
use crate::tags::Tags;
|
||||
use crate::util::now;
|
||||
```
|
||||
@@ -128,45 +128,37 @@ impl Tag {
|
||||
|
||||
## Methods on event types
|
||||
|
||||
Both `HashedEvent` and `Event` carry a `Tags` field, and callers who
|
||||
have an event in hand almost always want to ask questions of its tags
|
||||
without a separate import. The method impls delegate to the free
|
||||
functions verbatim.
|
||||
Rather than repeat these queries on each event struct, expiration exposes
|
||||
them through an extension trait bounded on [`HasTags`]. The method bodies
|
||||
live once, as defaults, and a blanket impl makes them available on every
|
||||
type that can hand back its tags — `HashedEvent`, `Event`, and anything
|
||||
added later. With `coracle_lib::prelude::*` in scope, `event.is_expired()`
|
||||
reads the same whether the event is hashed or signed.
|
||||
|
||||
```rust {file=coracle-lib/src/expiration.rs}
|
||||
impl HashedEvent {
|
||||
/// Expiration queries on any event that carries tags.
|
||||
pub trait EventExtensionExpiration: HasTags {
|
||||
/// Return the expiration timestamp of this event, if any.
|
||||
pub fn get_expiration(&self) -> Option<u64> {
|
||||
get_expiration(&self.tags)
|
||||
fn get_expiration(&self) -> Option<u64> {
|
||||
get_expiration(self.tags())
|
||||
}
|
||||
|
||||
/// Whether this event has expired relative to the given timestamp.
|
||||
pub fn is_expired_at(&self, now: u64) -> bool {
|
||||
is_expired_at(&self.tags, now)
|
||||
fn is_expired_at(&self, now: u64) -> bool {
|
||||
is_expired_at(self.tags(), now)
|
||||
}
|
||||
|
||||
/// Whether this event has expired relative to the system clock.
|
||||
pub fn is_expired(&self) -> bool {
|
||||
is_expired(&self.tags)
|
||||
fn is_expired(&self) -> bool {
|
||||
is_expired(self.tags())
|
||||
}
|
||||
}
|
||||
|
||||
impl Event {
|
||||
/// Return the expiration timestamp of this event, if any.
|
||||
pub fn get_expiration(&self) -> Option<u64> {
|
||||
get_expiration(&self.tags)
|
||||
}
|
||||
impl<T: HasTags> EventExtensionExpiration for T {}
|
||||
```
|
||||
|
||||
/// Whether this event has expired relative to the given timestamp.
|
||||
pub fn is_expired_at(&self, now: u64) -> bool {
|
||||
is_expired_at(&self.tags, now)
|
||||
}
|
||||
|
||||
/// Whether this event has expired relative to the system clock.
|
||||
pub fn is_expired(&self) -> bool {
|
||||
is_expired(&self.tags)
|
||||
}
|
||||
}
|
||||
```rust {file=coracle-lib/src/prelude.rs}
|
||||
pub use crate::expiration::EventExtensionExpiration;
|
||||
```
|
||||
|
||||
## Usage patterns
|
||||
|
||||
+14
-12
@@ -48,7 +48,7 @@ pub mod protected;
|
||||
//! challenge followed by a pubkey comparison) needs a live, authenticated
|
||||
//! connection and is handled in the relay layer.
|
||||
|
||||
use crate::events::{Event, HashedEvent};
|
||||
use crate::events::HasTags;
|
||||
use crate::tags::Tags;
|
||||
```
|
||||
|
||||
@@ -91,23 +91,25 @@ impl Tag {
|
||||
## Methods on event types
|
||||
|
||||
A caller holding an event wants to ask whether it is protected without
|
||||
reaching for its tags directly. Both `HashedEvent` and `Event` carry a
|
||||
`Tags` field, so the method facades delegate to the free function.
|
||||
reaching for its tags directly. Following the pattern from expiration, the
|
||||
query is an extension trait bounded on [`HasTags`], so one default method
|
||||
serves every event type. With `coracle_lib::prelude::*` in scope,
|
||||
`event.is_protected()` works on both `HashedEvent` and `Event`.
|
||||
|
||||
```rust {file=coracle-lib/src/protected.rs}
|
||||
impl HashedEvent {
|
||||
/// Protected-event queries on any event that carries tags.
|
||||
pub trait EventExtensionProtected: HasTags {
|
||||
/// Whether this event carries the NIP-70 protected marker.
|
||||
pub fn is_protected(&self) -> bool {
|
||||
is_protected(&self.tags)
|
||||
fn is_protected(&self) -> bool {
|
||||
is_protected(self.tags())
|
||||
}
|
||||
}
|
||||
|
||||
impl Event {
|
||||
/// Whether this event carries the NIP-70 protected marker.
|
||||
pub fn is_protected(&self) -> bool {
|
||||
is_protected(&self.tags)
|
||||
}
|
||||
}
|
||||
impl<T: HasTags> EventExtensionProtected for T {}
|
||||
```
|
||||
|
||||
```rust {file=coracle-lib/src/prelude.rs}
|
||||
pub use crate::protected::EventExtensionProtected;
|
||||
```
|
||||
|
||||
## Usage patterns
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use coracle_lib::events::{EventContent, HashedEvent};
|
||||
use coracle_lib::expiration::{get_expiration, is_expired, is_expired_at};
|
||||
use coracle_lib::prelude::*;
|
||||
use coracle_lib::keys::SecretKey;
|
||||
use coracle_lib::tags::{Tag, Tags};
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use coracle_lib::events::{EventContent, OwnedEvent};
|
||||
use coracle_lib::keys::SecretKey;
|
||||
use coracle_lib::prelude::*;
|
||||
use coracle_lib::pow::{get_leading_zero_bits, get_pow, mine_pow, mine_pow_batch};
|
||||
use coracle_lib::tags::{Tag, Tags};
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use coracle_lib::events::{EventContent, HashedEvent};
|
||||
use coracle_lib::keys::SecretKey;
|
||||
use coracle_lib::prelude::*;
|
||||
use coracle_lib::protected::is_protected;
|
||||
use coracle_lib::tags::{Tag, Tags};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user