#[cfg(not(feature = "std"))]
use core as std;
use std::{fmt, str};
use crate::{id::ParseError, Scru64Id, NODE_CTR_SIZE};
#[cfg(doc)]
use super::Scru64Generator;
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub struct NodeSpec {
node_prev: Scru64Id,
node_id_size: u8,
}
impl NodeSpec {
pub const fn node_id_size(&self) -> u8 {
self.node_id_size
}
pub const fn with_node_prev(
node_prev: Scru64Id,
node_id_size: u8,
) -> Result<Self, NodeSpecError> {
if 0 < node_id_size && node_id_size < NODE_CTR_SIZE {
Ok(Self {
node_prev,
node_id_size,
})
} else {
Err(NodeSpecError {
kind: NodeSpecErrorKind::NodeIdSize { node_id_size },
})
}
}
pub const fn node_prev(&self) -> Option<Scru64Id> {
if self.node_prev.timestamp() > 0 {
Some(self.node_prev)
} else {
None
}
}
pub(super) const fn node_prev_raw(&self) -> Scru64Id {
self.node_prev
}
pub const fn with_node_id(node_id: u32, node_id_size: u8) -> Result<Self, NodeSpecError> {
if 0 < node_id_size && node_id_size < NODE_CTR_SIZE {
if node_id < (1 << node_id_size) {
let counter_size = NODE_CTR_SIZE - node_id_size;
match Scru64Id::from_parts(0, node_id << counter_size) {
Ok(node_prev) => Ok(Self {
node_prev,
node_id_size,
}),
Err(_) => unreachable!(),
}
} else {
Err(NodeSpecError {
kind: NodeSpecErrorKind::NodeIdRange {
node_id,
node_id_size,
},
})
}
} else {
Err(NodeSpecError {
kind: NodeSpecErrorKind::NodeIdSize { node_id_size },
})
}
}
pub const fn node_id(&self) -> u32 {
let counter_size = NODE_CTR_SIZE - self.node_id_size;
self.node_prev.node_ctr() >> counter_size
}
}
impl str::FromStr for NodeSpec {
type Err = NodeSpecParseError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
use NodeSpecParseErrorKind::*;
let (lft, rgt) = {
let mut iter = value.split('/');
let lft = iter.next().unwrap();
let rgt = iter.next().ok_or(Syntax)?;
if iter.next().is_some() {
return Err(Syntax.into());
}
(lft, rgt)
};
let node_id_size = {
if rgt.len() > 3 || rgt.starts_with('+') {
return Err(Right.into());
}
rgt.parse().map_err(|_| Right)?
};
if lft.len() == 12 {
let node_prev = lft.parse().map_err(|source| LeftPrev { source })?;
Self::with_node_prev(node_prev, node_id_size)
.map_err(|source| NodeSpec { source }.into())
} else {
if lft.len() > 8 || lft.starts_with('+') {
return Err(LeftNodeId.into());
}
let node_id = if lft.starts_with("0X") || lft.starts_with("0x") {
u32::from_str_radix(&lft[2..], 16).map_err(|_| LeftNodeId)?
} else {
#[allow(clippy::from_str_radix_10)]
u32::from_str_radix(lft, 10).map_err(|_| LeftNodeId)?
};
Self::with_node_id(node_id, node_id_size).map_err(|source| NodeSpec { source }.into())
}
}
}
impl fmt::Display for NodeSpec {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.node_prev() {
Some(node_prev) => write!(f, "{}/{}", node_prev, self.node_id_size()),
None => write!(f, "{}/{}", self.node_id(), self.node_id_size()),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct NodeSpecError {
kind: NodeSpecErrorKind,
}
#[derive(Clone, Debug, Eq, PartialEq)]
enum NodeSpecErrorKind {
NodeIdSize { node_id_size: u8 },
NodeIdRange { node_id: u32, node_id_size: u8 },
}
impl fmt::Display for NodeSpecError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.kind {
NodeSpecErrorKind::NodeIdSize { node_id_size } => write!(
f,
"`node_id_size` ({}) must range from 1 to 23",
node_id_size
),
NodeSpecErrorKind::NodeIdRange {
node_id,
node_id_size,
} => write!(
f,
"`node_id` ({}) must fit in `node_id_size` ({}) bits",
node_id, node_id_size
),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct NodeSpecParseError {
kind: NodeSpecParseErrorKind,
}
#[derive(Clone, Debug, Eq, PartialEq)]
enum NodeSpecParseErrorKind {
Syntax,
Right,
LeftPrev { source: ParseError },
LeftNodeId,
NodeSpec { source: NodeSpecError },
}
impl From<NodeSpecParseErrorKind> for NodeSpecParseError {
fn from(kind: NodeSpecParseErrorKind) -> Self {
Self { kind }
}
}
impl fmt::Display for NodeSpecParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use NodeSpecParseErrorKind::*;
write!(f, "could not parse string as node spec: ")?;
match &self.kind {
Syntax => write!(
f,
r#"syntax error (expected: e.g., "42/8", "0xb00/12", "0u2r85hm2pt3/16")"#
),
Right => write!(f, "could not parse string as `node_id_size`"),
LeftPrev { source } => write!(f, "{}", source),
LeftNodeId => write!(f, "could not parse string as `node_id`"),
NodeSpec { source } => write!(f, "{}", source),
}
}
}
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
mod with_std {
impl std::error::Error for super::NodeSpecError {}
impl std::error::Error for super::NodeSpecParseError {}
}
#[cfg(test)]
mod tests {
use super::{NodeSpec, Scru64Id};
use crate::test_cases::EXAMPLE_NODE_SPECS;
#[test]
fn constructor() {
for e in EXAMPLE_NODE_SPECS {
let node_prev = Scru64Id::const_from_u64(e.node_prev);
let with_node_prev = NodeSpec::with_node_prev(node_prev, e.node_id_size).unwrap();
assert_eq!(with_node_prev.node_id(), e.node_id);
assert_eq!(with_node_prev.node_id_size(), e.node_id_size);
if let Some(p) = with_node_prev.node_prev() {
assert_eq!(p, node_prev);
}
assert_eq!(with_node_prev.node_prev_raw(), node_prev);
let with_node_id = NodeSpec::with_node_id(e.node_id, e.node_id_size).unwrap();
assert_eq!(with_node_id.node_id(), e.node_id);
assert_eq!(with_node_id.node_id_size(), e.node_id_size);
assert!(with_node_id.node_prev().is_none());
if e.spec_type.ends_with("_node_id") {
assert_eq!(with_node_id.node_prev_raw(), node_prev);
}
let parsed = e.node_spec.parse::<NodeSpec>().unwrap();
assert_eq!(parsed.node_id(), e.node_id);
assert_eq!(parsed.node_id_size(), e.node_id_size);
if let Some(p) = parsed.node_prev() {
assert_eq!(p, node_prev);
}
assert_eq!(parsed.node_prev_raw(), node_prev);
#[cfg(feature = "std")]
{
assert_eq!(with_node_prev.to_string(), e.canonical);
if e.spec_type.ends_with("_node_id") {
assert_eq!(with_node_id.to_string(), e.canonical);
}
assert_eq!(parsed.to_string(), e.canonical);
}
}
}
#[test]
fn constructor_error() {
let cases = [
"",
"42",
"/8",
"42/",
" 42/8",
"42/8 ",
" 42/8 ",
"42 / 8",
"+42/8",
"42/+8",
"-42/8",
"42/-8",
"ab/8",
"1/2/3",
"0/0",
"0/24",
"8/1",
"1024/8",
"0000000000001/8",
"1/0016",
"42/800",
];
for e in cases {
assert!(e.parse::<NodeSpec>().is_err());
}
}
}
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
mod with_serde {
use super::{fmt, str, NodeSpec};
use serde::{de, Deserializer, Serializer};
impl serde::Serialize for NodeSpec {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
use fmt::Write as _;
let mut buffer = fstr::FStr::<16>::repeat(b'\0');
write!(buffer.writer(), "{}", self).unwrap();
serializer.serialize_str(buffer.slice_to_terminator('\0'))
}
}
impl<'de> serde::Deserialize<'de> for NodeSpec {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
deserializer.deserialize_str(VisitorImpl)
}
}
struct VisitorImpl;
impl<'de> de::Visitor<'de> for VisitorImpl {
type Value = NodeSpec;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "a SCRU64 node spec string")
}
fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
value.parse().map_err(de::Error::custom)
}
fn visit_bytes<E: de::Error>(self, value: &[u8]) -> Result<Self::Value, E> {
match str::from_utf8(value) {
Ok(str_value) => self.visit_str(str_value),
_ => Err(de::Error::invalid_value(
de::Unexpected::Bytes(value),
&self,
)),
}
}
}
#[cfg(test)]
#[test]
fn test() {
use serde_test::Token;
for e in crate::test_cases::EXAMPLE_NODE_SPECS {
let x = e.node_spec.parse::<NodeSpec>().unwrap();
serde_test::assert_tokens(&x, &[Token::Str(e.canonical)]);
serde_test::assert_de_tokens(&x, &[Token::Bytes(e.canonical.as_bytes())]);
}
}
}