mod arrays;
mod bytes;
mod persist;
mod tag;
mod tagged;
use std::num::NonZeroU64;
use serde::{Deserialize, Serialize};
use crate::Database;
use crate::error::{Error, Result};
pub use self::arrays::{Altvals, HashEntries, Supvals};
pub use self::bytes::{NetnodeBytes, NetnodeBytesError};
pub use self::persist::Persist;
pub use self::tag::Tag;
pub use self::tagged::{TaggedNetnode, TaggedNetnodeMut};
const BADNODE: u64 = u64::MAX;
fn rejected(db: &Database, address: u64, op: &'static str) -> Error {
let (errno, reason) = db.last_reason();
Error::WriteRejected {
op,
address,
errno,
reason,
}
}
fn checked(db: &Database, id: NodeId, ok: bool, op: &'static str) -> Result<()> {
if ok {
Ok(())
} else {
Err(rejected(db, id.get(), op))
}
}
impl Database {
#[must_use]
#[doc(alias("netnode::netnode"))]
pub fn netnode(&self, name: &str) -> Option<Netnode<'_>> {
NodeId::try_new(self.netnode_open(name)).map(|id| Netnode { db: self, id })
}
#[inline]
#[must_use]
#[doc(alias("netnode::netnode"))]
pub fn netnode_at(&self, id: NodeId) -> Netnode<'_> {
Netnode { db: self, id }
}
#[must_use]
#[doc(alias("netnode::create"))]
pub fn netnode_mut(&mut self, name: &str) -> NetnodeMut<'_> {
let id = NodeId::try_new(self.netnode_create(name))
.expect("netnode creation returned BADNODE for a valid name");
NetnodeMut { db: self, id }
}
#[inline]
#[must_use]
#[doc(alias("netnode::start"))]
pub fn netnodes(&self) -> Netnodes<'_> {
Netnodes::new(self)
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
#[doc(alias("nodeidx_t"))]
pub struct NodeId(NonZeroU64);
impl NodeId {
#[inline]
#[must_use]
#[doc(alias("BADNODE"))]
pub const fn try_new(raw: u64) -> Option<Self> {
match NonZeroU64::new(!raw) {
Some(n) => Some(Self(n)),
None => None,
}
}
#[inline]
#[must_use]
pub const fn get(self) -> u64 {
!self.0.get()
}
}
impl std::fmt::Debug for NodeId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "NodeId({:#x})", self.get())
}
}
impl std::fmt::LowerHex for NodeId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::LowerHex::fmt(&self.get(), f)
}
}
impl Ord for NodeId {
#[inline]
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.get().cmp(&other.get())
}
}
impl PartialOrd for NodeId {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl From<NodeId> for u64 {
#[inline]
fn from(id: NodeId) -> Self {
id.get()
}
}
impl Serialize for NodeId {
#[inline]
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_u64(self.get())
}
}
impl<'de> Deserialize<'de> for NodeId {
#[inline]
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let raw = u64::deserialize(deserializer)?;
Self::try_new(raw)
.ok_or_else(|| serde::de::Error::custom("node id is the BADNODE sentinel"))
}
}
macro_rules! netnode_reads {
() => {
#[inline]
#[must_use]
pub const fn id(&self) -> NodeId {
self.id
}
#[inline]
#[must_use]
#[doc(alias("netnode::get_name"))]
pub fn name(&self) -> Option<String> {
self.db.netnode_get_name(self.id.get())
}
#[inline]
#[must_use]
#[doc(alias("netnode_exist"))]
pub fn exists(&self) -> bool {
self.db.netnode_exists(self.id.get())
}
#[inline]
#[must_use]
#[doc(alias("netnode::valobj"))]
pub fn value(&self) -> Option<Vec<u8>> {
self.db.netnode_value(self.id.get())
}
#[inline]
#[must_use]
#[doc(alias("netnode::valstr"))]
pub fn value_str(&self) -> Option<String> {
self.db.netnode_value_str(self.id.get())
}
#[inline]
#[must_use]
#[doc(alias("netnode::altval"))]
pub fn altval(&self, index: u64) -> u64 {
self.db
.netnode_altval(self.id.get(), index, $crate::netnode::Tag::ALTVAL.raw())
}
#[inline]
#[must_use]
#[doc(alias("netnode::supval"))]
pub fn supval(&self, index: u64) -> Option<Vec<u8>> {
self.db
.netnode_supval(self.id.get(), index, $crate::netnode::Tag::SUPVAL.raw())
}
#[inline]
#[must_use]
#[doc(alias("netnode::hashval"))]
pub fn hash(&self, key: &str) -> Option<Vec<u8>> {
self.db
.netnode_hashval(self.id.get(), key, $crate::netnode::Tag::HASH.raw())
}
#[inline]
#[must_use]
#[doc(alias("netnode::hashval_long"))]
pub fn hash_integer(&self, key: &str) -> u64 {
self.db
.netnode_hashval_long(self.id.get(), key, $crate::netnode::Tag::HASH.raw())
}
#[inline]
#[must_use]
#[doc(alias("netnode::getblob"))]
pub fn blob(&self) -> Option<Vec<u8>> {
self.db
.netnode_getblob(self.id.get(), 0, $crate::netnode::Tag::BLOB.raw())
}
#[inline]
#[must_use]
#[doc(alias("netnode::blobsize"))]
pub fn blob_size(&self) -> usize {
self.db
.netnode_blobsize(self.id.get(), 0, $crate::netnode::Tag::BLOB.raw())
}
#[inline]
#[must_use]
#[doc(alias("netnode::altfirst"))]
pub fn altvals(&self) -> $crate::netnode::Altvals<'_> {
$crate::netnode::Altvals::new(&*self.db, self.id, $crate::netnode::Tag::ALTVAL.raw())
}
#[inline]
#[must_use]
#[doc(alias("netnode::supfirst"))]
pub fn supvals(&self) -> $crate::netnode::Supvals<'_> {
$crate::netnode::Supvals::new(&*self.db, self.id, $crate::netnode::Tag::SUPVAL.raw())
}
#[inline]
#[must_use]
#[doc(alias("netnode::hashfirst"))]
pub fn hash_entries(&self) -> $crate::netnode::HashEntries<'_> {
$crate::netnode::HashEntries::new(&*self.db, self.id, $crate::netnode::Tag::HASH.raw())
}
#[inline]
#[must_use]
pub fn get<T: $crate::netnode::Persist>(&self, key: &str) -> Option<T> {
self.hash(key)
.and_then(|bytes| T::from_netnode_bytes(&bytes))
}
#[inline]
#[must_use]
pub fn contains(&self, key: &str) -> bool {
self.hash(key).is_some()
}
#[cfg(feature = "serde")]
#[inline]
#[must_use]
pub fn get_serde<T: ::serde::de::DeserializeOwned>(&self, key: &str) -> Option<T> {
self.hash(key)
.and_then(|bytes| ::postcard::from_bytes(&bytes).ok())
}
#[cfg(feature = "serde")]
#[inline]
#[must_use]
pub fn get_serde_at<T: ::serde::de::DeserializeOwned>(&self, index: u64) -> Option<T> {
self.db
.netnode_getblob(self.id.get(), index, $crate::netnode::Tag::BLOB.raw())
.and_then(|bytes| ::postcard::from_bytes(&bytes).ok())
}
};
}
macro_rules! write_ops {
($(
$(#[$meta:meta])*
fn $name:ident($this:ident $(, $arg:ident: $aty:ty)* $(,)?) => $call:expr;
)*) => {$(
$(#[$meta])*
pub fn $name(&mut self $(, $arg: $aty)*) -> $crate::error::Result<()> {
let $this = self;
let ok = $call;
$crate::netnode::checked(&*$this.db, $this.id, ok, stringify!($name))
}
)*};
}
pub(crate) use write_ops;
macro_rules! delete_ops {
($(
$(#[$meta:meta])*
fn $name:ident($this:ident $(, $arg:ident: $aty:ty)* $(,)?) => $call:expr;
)*) => {$(
$(#[$meta])*
pub fn $name(&mut self $(, $arg: $aty)*) -> bool {
let $this = self;
$call
}
)*};
}
pub(crate) use delete_ops;
#[derive(Clone, Copy)]
#[doc(alias("netnode"))]
pub struct Netnode<'db> {
db: &'db Database,
id: NodeId,
}
impl<'db> Netnode<'db> {
netnode_reads!();
#[inline]
#[must_use]
pub fn tag(self, tag: Tag) -> TaggedNetnode<'db> {
TaggedNetnode::new(self.db, self.id, tag)
}
}
impl std::fmt::Debug for Netnode<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Netnode")
.field("id", &self.id)
.field("name", &self.name())
.finish()
}
}
key_identity!(Netnode, id, ord);
#[doc(alias("netnode"))]
pub struct NetnodeMut<'db> {
db: &'db mut Database,
id: NodeId,
}
impl std::fmt::Debug for NetnodeMut<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("NetnodeMut")
.field("id", &self.id)
.finish_non_exhaustive()
}
}
impl NetnodeMut<'_> {
netnode_reads!();
#[inline]
pub fn tag(&mut self, tag: Tag) -> TaggedNetnodeMut<'_> {
TaggedNetnodeMut::new(&mut *self.db, self.id, tag)
}
write_ops! {
#[doc(alias("netnode::altset"))]
fn set_altval(this, index: u64, value: u64) => this.db.netnode_altset(this.id.get(), index, value, Tag::ALTVAL.raw());
#[doc(alias("netnode::hashset"))]
fn set_hash_integer(this, key: &str, value: u64) => this.db.netnode_hashset_long(this.id.get(), key, value, Tag::HASH.raw());
#[doc(alias("netnode::setblob"))]
fn set_blob(this, value: &[u8]) => this.db.netnode_setblob(this.id.get(), value, 0, Tag::BLOB.raw());
#[doc(alias("netnode::rename"))]
fn rename(this, name: &str) => this.db.netnode_rename(this.id.get(), name);
}
delete_ops! {
#[doc(alias("netnode::delvalue"))]
fn clear_value(this) => this.db.netnode_del_value(this.id.get());
#[doc(alias("netnode::altdel"))]
fn remove_altval(this, index: u64) => this.db.netnode_altdel(this.id.get(), index, Tag::ALTVAL.raw());
#[doc(alias("netnode::altdel_all"))]
fn clear_altvals(this) => this.db.netnode_altdel_all(this.id.get(), Tag::ALTVAL.raw());
#[doc(alias("netnode::supdel"))]
fn remove_supval(this, index: u64) => this.db.netnode_supdel(this.id.get(), index, Tag::SUPVAL.raw());
#[doc(alias("netnode::supdel_all"))]
fn clear_supvals(this) => this.db.netnode_supdel_all(this.id.get(), Tag::SUPVAL.raw());
#[doc(alias("netnode::hashdel"))]
fn remove_hash(this, key: &str) => this.db.netnode_hashdel(this.id.get(), key, Tag::HASH.raw());
#[doc(alias("netnode::hashdel_all"))]
fn clear_hash(this) => this.db.netnode_hashdel_all(this.id.get(), Tag::HASH.raw());
#[doc(alias("netnode::delblob"))]
fn remove_blob(this) => this.db.netnode_delblob(this.id.get(), 0, Tag::BLOB.raw()) > 0;
}
#[doc(alias("netnode::set"))]
pub fn set_value<'a>(
&mut self,
value: impl TryInto<NetnodeBytes<'a>, Error: Into<NetnodeBytesError>>,
) -> Result<()> {
let bytes: NetnodeBytes<'_> = value.try_into().map_err(Into::into)?;
let ok = self.db.netnode_set_value(self.id.get(), bytes.as_bytes());
checked(&*self.db, self.id, ok, "set_value")
}
#[doc(alias("netnode::supset"))]
pub fn set_supval<'a>(
&mut self,
index: u64,
value: impl TryInto<NetnodeBytes<'a>, Error: Into<NetnodeBytesError>>,
) -> Result<()> {
let bytes: NetnodeBytes<'_> = value.try_into().map_err(Into::into)?;
let ok = self
.db
.netnode_supset(self.id.get(), index, bytes.as_bytes(), Tag::SUPVAL.raw());
checked(&*self.db, self.id, ok, "set_supval")
}
#[doc(alias("netnode::hashset"))]
pub fn set_hash<'a>(
&mut self,
key: &str,
value: impl TryInto<NetnodeBytes<'a>, Error: Into<NetnodeBytesError>>,
) -> Result<()> {
let bytes: NetnodeBytes<'_> = value.try_into().map_err(Into::into)?;
let ok = self
.db
.netnode_hashset(self.id.get(), key, bytes.as_bytes(), Tag::HASH.raw());
checked(&*self.db, self.id, ok, "set_hash")
}
pub fn put<T: Persist>(&mut self, key: &str, value: &T) -> Result<()> {
self.set_hash(key, &value.to_netnode_bytes())
}
pub fn remove(&mut self, key: &str) -> bool {
self.remove_hash(key)
}
#[cfg(feature = "serde")]
pub fn put_serde<T: ::serde::Serialize>(&mut self, key: &str, value: &T) -> Result<()> {
let bytes = ::postcard::to_allocvec(value).map_err(|e| Error::SerializeFailed {
reason: e.to_string(),
})?;
self.set_hash(key, &bytes)
}
#[cfg(feature = "serde")]
#[doc(alias("netnode::setblob"))]
pub fn put_serde_at<T: ::serde::Serialize>(&mut self, index: u64, value: &T) -> Result<()> {
let bytes = ::postcard::to_allocvec(value).map_err(|e| Error::SerializeFailed {
reason: e.to_string(),
})?;
let ok = self
.db
.netnode_setblob(self.id.get(), &bytes, index, Tag::BLOB.raw());
checked(&*self.db, self.id, ok, "put_serde_at")
}
#[doc(alias("netnode::kill"))]
pub fn kill(&mut self) {
self.db.netnode_kill(self.id.get());
}
}
pub struct Netnodes<'db> {
db: &'db Database,
next: Option<NodeId>,
}
impl std::fmt::Debug for Netnodes<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Netnodes")
.field("next", &self.next)
.finish_non_exhaustive()
}
}
impl<'db> Netnodes<'db> {
pub(crate) fn new(db: &'db Database) -> Self {
Self {
db,
next: NodeId::try_new(db.netnode_first()),
}
}
}
impl<'db> Iterator for Netnodes<'db> {
type Item = Netnode<'db>;
fn next(&mut self) -> Option<Self::Item> {
let id = self.next?;
self.next = NodeId::try_new(self.db.netnode_next(id.get()));
Some(Netnode { db: self.db, id })
}
}
#[cfg(test)]
mod tests {
use assert2::assert;
use super::*;
#[test]
fn netnode_mut_debug_renders_the_id() {
let mut db = Database::new();
let id = NodeId::try_new(1).unwrap();
let cursor = NetnodeMut { db: &mut db, id };
assert!(format!("{cursor:?}").starts_with("NetnodeMut"));
}
#[test]
fn netnodes_debug_renders_the_cursor() {
let db = Database::new();
let iter = Netnodes {
db: &db,
next: NodeId::try_new(1),
};
assert!(format!("{iter:?}").starts_with("Netnodes"));
}
#[test]
fn node_id_debug_renders_the_hex_id() {
let id = NodeId::try_new(0x1234).unwrap();
assert!(format!("{id:?}") == "NodeId(0x1234)");
}
#[test]
fn node_id_lower_hex_matches_get() {
let id = NodeId::try_new(0x1234).unwrap();
assert!(format!("{id:x}") == "1234");
}
#[test]
fn node_id_partial_cmp_orders_by_the_real_id() {
let a = NodeId::try_new(1).unwrap();
let b = NodeId::try_new(2).unwrap();
assert!(a.partial_cmp(&b) == Some(std::cmp::Ordering::Less));
}
#[test]
fn node_id_into_u64_is_the_real_id() {
let id = NodeId::try_new(0x1234).unwrap();
assert!(u64::from(id) == 0x1234);
}
#[test]
fn node_id_serde_round_trips_as_the_real_id() {
let id = NodeId::try_new(0x1234).unwrap();
let json = serde_json::to_string(&id).unwrap();
assert!(json == id.get().to_string());
let back: NodeId = serde_json::from_str(&json).unwrap();
assert!(back == id);
}
#[test]
fn node_id_serde_rejects_the_sentinel() {
assert!(serde_json::from_str::<NodeId>(&BADNODE.to_string()).is_err());
}
}