Add Op ID string formatting

To help users, create an Op ID string format of the form `<actor-id>/<lamport>`.

Signed-off-by: Slack Coder <slackcoder@server.ky>
This commit is contained in:
Slack Coder 2023-01-27 09:12:37 -05:00 committed by Alexis Sellier
parent 98f7fe1db8
commit 1cb8986830
No known key found for this signature in database
4 changed files with 103 additions and 0 deletions

1
Cargo.lock generated
View File

@ -1886,6 +1886,7 @@ dependencies = [
"radicle-crypto",
"serde",
"tempfile",
"thiserror",
]
[[package]]

View File

@ -11,6 +11,7 @@ fastrand = { version = "1.8.0", optional = true }
num-traits = { version = "0.2.15", default-features = false, features = ["std"] }
qcheck = { version = "1", optional = true }
serde = { version = "1" }
thiserror = { version = "1" }
[dependencies.radicle-crypto]
path = "../radicle-crypto"

View File

@ -1,8 +1,11 @@
use std::fmt;
use std::str::FromStr;
use std::time::SystemTime;
use std::time::UNIX_EPOCH;
use num_traits::Bounded;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::ord::Max;
use crate::Semilattice as _;
@ -45,6 +48,12 @@ impl Lamport {
}
}
impl fmt::Display for Lamport {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.get())
}
}
impl From<u64> for Lamport {
fn from(counter: u64) -> Self {
Self {
@ -53,6 +62,36 @@ impl From<u64> for Lamport {
}
}
impl From<Lamport> for u64 {
fn from(arg: Lamport) -> Self {
arg.get()
}
}
/// Error decoding an operation from an entry.
#[derive(Error, Debug)]
pub enum LamportError {
#[error("invalid lamport clock value")]
Invalid,
}
impl FromStr for Lamport {
type Err = LamportError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::try_from(s)
}
}
impl TryFrom<&str> for Lamport {
type Error = LamportError;
fn try_from(s: &str) -> Result<Self, Self::Error> {
let v = s.parse::<u64>().map_err(|_| LamportError::Invalid)?;
Ok(v.into())
}
}
impl Bounded for Lamport {
fn min_value() -> Self {
Self::from(u64::min_value())

View File

@ -1,4 +1,7 @@
use std::collections::BTreeMap;
use std::fmt;
use std::str;
use std::str::FromStr;
use nonempty::NonEmpty;
use serde::{Deserialize, Serialize};
@ -34,6 +37,47 @@ impl OpId {
}
}
impl fmt::Display for OpId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}/{}", self.1, self.0)
}
}
/// Error decoding an operation from an entry.
#[derive(Error, Debug)]
pub enum OpIdError {
#[error("cannot parse op id from empty string")]
Empty,
#[error("badly formatted op id")]
BadFormat,
}
impl FromStr for OpId {
type Err = OpIdError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::try_from(s)
}
}
impl TryFrom<&str> for OpId {
type Error = OpIdError;
fn try_from(s: &str) -> Result<Self, Self::Error> {
if s.is_empty() {
return Err(OpIdError::Empty);
}
let Some((actor_id, clock)) = s.split_once('/') else {
return Err(OpIdError::BadFormat);
};
Ok(Self(
Lamport::from_str(clock).map_err(|_| OpIdError::BadFormat)?,
ActorId::from_str(actor_id).map_err(|_| OpIdError::BadFormat)?,
))
}
}
/// The author of an [`Op`].
pub type ActorId = PublicKey;
@ -182,3 +226,21 @@ impl<G: Signer, A: Clone> Actor<G, A> {
op
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_opid_try_from_str() {
let str = "z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT/12";
let id = OpId::try_from(str).expect("Op ID parses string");
assert_eq!(str, id.to_string(), "string conversion is consistent");
let str = "";
assert!(OpId::try_from(str).is_err(), "empty strings are invalid");
let str = "jlkjfksgi";
assert!(OpId::try_from(str).is_err(), "badly formatted string");
}
}