use serde::Serialize;
pub trait IdBrand {
const BRAND: &'static str;
}
impl IdBrand for () {
const BRAND: &'static str = "";
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
#[serde(tag = "seg", content = "v")]
pub enum PathSeg {
Field(&'static str),
Variant(&'static str),
Index(u32),
Elem,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct FieldRef {
pub path: Vec<PathSeg>,
pub brand: &'static str,
}
impl FieldRef {
pub fn leaf(brand: &'static str) -> Self {
Self {
path: Vec::new(),
brand,
}
}
#[must_use]
pub fn prefixed(mut self, seg: PathSeg) -> Self {
self.path.insert(0, seg);
self
}
}
pub trait GetRefs {
fn refs() -> Vec<FieldRef>
where
Self: Sized,
{
Vec::new()
}
fn visit_ids(&self, f: &mut dyn FnMut(&'static str, u64)) {
let _ = f;
}
}
macro_rules! empty_refs {
($($t:ty),* $(,)?) => {
$(impl GetRefs for $t {})*
};
}
empty_refs!(
bool,
u8,
u16,
u32,
u64,
usize,
i32,
i64,
f32,
f64,
core::num::NonZeroU32,
core::num::NonZeroU64,
String,
time::Duration,
time::OffsetDateTime,
time::PrimitiveDateTime,
time::Date,
time::Time,
std::net::Ipv6Addr,
std::net::IpAddr,
std::net::SocketAddrV6,
uuid::Uuid,
);
impl<const CAP: usize> GetRefs for [u8; CAP] {}
impl<const CAP: usize> GetRefs for arrayvec::ArrayString<CAP> {}
#[cfg(feature = "std")]
impl GetRefs for bytes::Bytes {}
#[cfg(feature = "std")]
impl GetRefs for serde_json::Value {}
#[cfg(feature = "rust_decimal")]
impl GetRefs for rust_decimal::Decimal {}
#[cfg(feature = "compact_str")]
impl GetRefs for compact_str::CompactString {}
#[cfg(feature = "smol_str")]
impl GetRefs for smol_str::SmolStr {}
#[cfg(feature = "solana")]
impl GetRefs for solana_pubkey::Pubkey {}
#[cfg(feature = "solana")]
impl GetRefs for solana_signature::Signature {}
fn elem_refs<T: GetRefs>() -> Vec<FieldRef> {
T::refs()
.into_iter()
.map(|r| r.prefixed(PathSeg::Elem))
.collect()
}
impl<T: GetRefs> GetRefs for Vec<T> {
fn refs() -> Vec<FieldRef> {
elem_refs::<T>()
}
fn visit_ids(&self, f: &mut dyn FnMut(&'static str, u64)) {
for item in self {
item.visit_ids(f);
}
}
}
impl<T: GetRefs, const CAP: usize> GetRefs for arrayvec::ArrayVec<T, CAP> {
fn refs() -> Vec<FieldRef> {
elem_refs::<T>()
}
fn visit_ids(&self, f: &mut dyn FnMut(&'static str, u64)) {
for item in self {
item.visit_ids(f);
}
}
}
#[cfg(feature = "smallvec")]
impl<T: GetRefs, const CAP: usize> GetRefs for smallvec::SmallVec<[T; CAP]> {
fn refs() -> Vec<FieldRef> {
elem_refs::<T>()
}
fn visit_ids(&self, f: &mut dyn FnMut(&'static str, u64)) {
for item in self {
item.visit_ids(f);
}
}
}
impl<T: GetRefs> GetRefs for Option<T> {
fn refs() -> Vec<FieldRef> {
elem_refs::<T>()
}
fn visit_ids(&self, f: &mut dyn FnMut(&'static str, u64)) {
if let Some(v) = self {
v.visit_ids(f);
}
}
}
impl<T: GetRefs> GetRefs for Box<T> {
fn refs() -> Vec<FieldRef> {
T::refs()
}
fn visit_ids(&self, f: &mut dyn FnMut(&'static str, u64)) {
(**self).visit_ids(f);
}
}
macro_rules! tuple_refs {
($(($($t:ident . $i:tt),+)),+ $(,)?) => {
$(
impl<$($t: GetRefs),+> GetRefs for ($($t,)+) {
fn refs() -> Vec<FieldRef> {
let mut out = Vec::new();
$(out.extend($t::refs().into_iter().map(|r| r.prefixed(PathSeg::Index($i))));)+
out
}
fn visit_ids(&self, f: &mut dyn FnMut(&'static str, u64)) {
$(self.$i.visit_ids(f);)+
}
}
)+
};
}
tuple_refs!((T1.0, T2.1), (T1.0, T2.1, T3.2), (T1.0, T2.1, T3.2, T4.3),);
impl<K: GetRefs, V: GetRefs> GetRefs for std::collections::HashMap<K, V> {
fn refs() -> Vec<FieldRef> {
elem_refs::<(K, V)>()
}
fn visit_ids(&self, f: &mut dyn FnMut(&'static str, u64)) {
for (k, v) in self {
k.visit_ids(f);
v.visit_ids(f);
}
}
}
impl<K: GetRefs, V: GetRefs> GetRefs for std::collections::BTreeMap<K, V> {
fn refs() -> Vec<FieldRef> {
elem_refs::<(K, V)>()
}
fn visit_ids(&self, f: &mut dyn FnMut(&'static str, u64)) {
for (k, v) in self {
k.visit_ids(f);
v.visit_ids(f);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
struct TestBrand;
impl IdBrand for TestBrand {
const BRAND: &'static str = "TEST";
}
struct FakeId(u64);
impl GetRefs for FakeId {
fn refs() -> Vec<FieldRef> {
vec![FieldRef::leaf(TestBrand::BRAND)]
}
fn visit_ids(&self, f: &mut dyn FnMut(&'static str, u64)) {
f(TestBrand::BRAND, self.0);
}
}
#[test]
fn refs_leaf_and_prefix() {
let r = FieldRef::leaf("TEST").prefixed(PathSeg::Field("a"));
assert_eq!(r.path, vec![PathSeg::Field("a")]);
assert_eq!(r.brand, "TEST");
}
#[test]
fn refs_default_empty() {
assert!(u64::refs().is_empty());
assert!(String::refs().is_empty());
let mut seen = Vec::new();
42u64.visit_ids(&mut |b, v| seen.push((b, v)));
assert!(seen.is_empty());
}
#[test]
fn refs_containers_forward() {
let refs = <Vec<Option<FakeId>>>::refs();
assert_eq!(refs.len(), 1);
assert_eq!(refs[0].path, vec![PathSeg::Elem, PathSeg::Elem]);
let v: Vec<Option<FakeId>> = vec![Some(FakeId(7)), None, Some(FakeId(9))];
let mut seen = Vec::new();
v.visit_ids(&mut |b, id| seen.push((b, id)));
assert_eq!(seen, vec![("TEST", 7), ("TEST", 9)]);
}
#[test]
fn refs_tuple_paths() {
let refs = <(u64, FakeId)>::refs();
assert_eq!(refs.len(), 1);
assert_eq!(refs[0].path, vec![PathSeg::Index(1)]);
}
}