use std::any::TypeId;
use smallvec::SmallVec;
use crate::Unit;
#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub struct Style {
pub index: u8,
pub name: &'static str,
}
#[non_exhaustive]
pub struct Styles;
impl Styles {
pub const PRESERVE: Style = Style {
index: 0,
name: "preserve",
};
pub const PASCAL: Style = Style {
index: 1,
name: "PascalCase",
};
pub const SNAKE: Style = Style {
index: 2,
name: "snake_case",
};
pub const KEBAB: Style = Style {
index: 3,
name: "kebab-case",
};
pub const SCREAMING_SNAKE: Style = Style {
index: 4,
name: "SCREAMING_SNAKE_CASE",
};
pub const ALL: &'static [Style] = &[
Self::PRESERVE,
Self::PASCAL,
Self::SNAKE,
Self::KEBAB,
Self::SCREAMING_SNAKE,
];
pub const COUNT: usize = Self::ALL.len();
}
#[derive(Debug)]
pub struct EntryDescriptor {
name: &'static str,
fields: &'static [FieldDescriptor],
timestamp: Option<TimestampDescriptor>,
}
impl EntryDescriptor {
pub const fn builder(
name: &'static str,
fields: &'static [FieldDescriptor],
) -> EntryDescriptorBuilder {
EntryDescriptorBuilder {
name,
fields,
timestamp: None,
}
}
}
#[derive(Debug)]
pub struct FieldDescriptor {
names: [&'static str; Styles::COUNT],
flags: &'static [FieldFlag],
skipped_flags: &'static [FieldFlag],
shape: FieldShape<'static>,
unit: Option<Unit>,
}
impl FieldDescriptor {
pub const fn builder(name: &'static str) -> FieldDescriptorBuilder {
FieldDescriptorBuilder {
names: [name; Styles::COUNT],
flags: &[],
skipped_flags: &[],
shape: FieldShape::Opaque,
unit: None,
}
}
}
#[derive(Debug)]
pub struct TimestampDescriptor {
name: &'static str,
}
impl TimestampDescriptor {
pub const fn new(name: &'static str) -> Self {
Self { name }
}
pub fn name(&self) -> &str {
self.name
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum Descriptors<'a> {
Available(AvailableDescriptors<'a>),
Unavailable,
}
#[derive(Debug, Clone)]
pub struct AvailableDescriptors<'a>(SmallVec<[DescriptorRef<'a>; 2]>);
impl<'a> AvailableDescriptors<'a> {
pub fn iter(&self) -> impl Iterator<Item = &DescriptorRef<'a>> {
self.0.iter()
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
impl<'a> std::ops::Index<usize> for AvailableDescriptors<'a> {
type Output = DescriptorRef<'a>;
fn index(&self, index: usize) -> &Self::Output {
&self.0[index]
}
}
impl<'a> IntoIterator for AvailableDescriptors<'a> {
type Item = DescriptorRef<'a>;
type IntoIter = DescriptorIter<'a>;
fn into_iter(self) -> Self::IntoIter {
DescriptorIter(self.0.into_iter())
}
}
#[derive(Debug)]
pub struct DescriptorIter<'a>(smallvec::IntoIter<[DescriptorRef<'a>; 2]>);
impl<'a> Iterator for DescriptorIter<'a> {
type Item = DescriptorRef<'a>;
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.0.size_hint()
}
}
impl<'a> ExactSizeIterator for DescriptorIter<'a> {}
impl<'a> Descriptors<'a> {
pub fn available(iter: impl IntoIterator<Item = DescriptorRef<'a>>) -> Self {
Descriptors::Available(AvailableDescriptors(iter.into_iter().collect()))
}
pub fn is_available(&self) -> bool {
matches!(self, Descriptors::Available(_))
}
pub fn unwrap(self) -> AvailableDescriptors<'a> {
match self {
Descriptors::Available(v) => v,
Descriptors::Unavailable => panic!("called unwrap() on Descriptors::Unavailable"),
}
}
pub fn into_available(self) -> Option<AvailableDescriptors<'a>> {
match self {
Descriptors::Available(v) => Some(v),
Descriptors::Unavailable => None,
}
}
pub fn map_available(self, f: impl FnMut(DescriptorRef<'a>) -> DescriptorRef<'a>) -> Self {
match self {
Descriptors::Available(a) => {
let mapped: SmallVec<[DescriptorRef<'a>; 2]> = a.0.into_iter().map(f).collect();
Descriptors::Available(AvailableDescriptors(mapped))
}
Descriptors::Unavailable => Descriptors::Unavailable,
}
}
pub fn chain(self, other: Descriptors<'a>) -> Self {
match (self, other) {
(Descriptors::Available(mut a), Descriptors::Available(b)) => {
a.0.extend(b.0);
Descriptors::Available(a)
}
_ => Descriptors::Unavailable,
}
}
}
#[derive(Clone, Debug)]
pub struct DescriptorRef<'a> {
descriptor: &'a EntryDescriptor,
id: DescriptorId,
prefixes: SmallVec<[&'static str; 1]>,
style_index: u8,
extra_flags: SmallVec<[&'static [FieldFlag]; 1]>,
}
impl<'a> DescriptorRef<'a> {
#[doc(hidden)]
pub fn from_static(
descriptor: &'static EntryDescriptor,
style_index: u8,
) -> DescriptorRef<'static> {
let id = DescriptorId::compute(descriptor, &[], &[]);
DescriptorRef {
descriptor,
id,
prefixes: SmallVec::new(),
style_index,
extra_flags: SmallVec::new(),
}
}
#[doc(hidden)]
pub fn with_prefix(mut self, prefix: &'static str) -> Self {
self.prefixes.insert(0, prefix);
self.id = DescriptorId::compute(self.descriptor, &self.prefixes, &self.extra_flags);
self
}
#[doc(hidden)]
pub fn with_extra_flags(mut self, flags: &'static [FieldFlag]) -> Self {
self.extra_flags.push(flags);
self.id = DescriptorId::compute(self.descriptor, &self.prefixes, &self.extra_flags);
self
}
pub fn id(&self) -> DescriptorId {
self.id
}
pub fn name(&self) -> &str {
self.descriptor.name
}
pub fn fields_len(&self) -> usize {
self.descriptor.fields.len()
}
pub fn timestamp(&self) -> Option<&TimestampDescriptor> {
self.descriptor.timestamp.as_ref()
}
pub fn fields(&self) -> impl Iterator<Item = FieldView<'_>> {
(0..self.descriptor.fields.len()).map(move |i| FieldView { desc: self, idx: i })
}
}
#[derive(Clone, Debug)]
pub struct FieldView<'a> {
desc: &'a DescriptorRef<'a>,
idx: usize,
}
impl<'a> FieldView<'a> {
pub fn name_parts(&self) -> impl Iterator<Item = &str> {
self.desc.prefixes.iter().copied().chain(std::iter::once(
self.desc.descriptor.fields[self.idx].names[self.desc.style_index as usize],
))
}
pub fn base_name(&self) -> &'static str {
self.desc.descriptor.fields[self.idx].names[self.desc.style_index as usize]
}
pub fn flags(&self) -> impl Iterator<Item = &'a FieldFlag> {
let field = &self.desc.descriptor.fields[self.idx];
let skipped = field.skipped_flags;
field.flags.iter().chain(
self.desc
.extra_flags
.iter()
.flat_map(|slice| slice.iter())
.filter(move |ef| !skipped.iter().any(|s| s.type_id() == ef.type_id())),
)
}
pub fn shape(&self) -> FieldShape<'a> {
self.desc.descriptor.fields[self.idx].shape
}
pub fn unit(&self) -> Option<Unit> {
self.desc.descriptor.fields[self.idx].unit
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct DescriptorId(u64);
impl DescriptorId {
fn compute(
descriptor: &EntryDescriptor,
prefixes: &[&'static str],
extra_flags: &[&'static [FieldFlag]],
) -> Self {
let mut id = descriptor as *const EntryDescriptor as u64;
for p in prefixes {
id = id.wrapping_mul(31).wrapping_add(p.as_ptr() as u64);
}
for f in extra_flags {
id = id.wrapping_mul(31).wrapping_add(f.as_ptr() as u64);
}
DescriptorId(id)
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FieldShape<'a> {
Known(KnownShape),
Optional(ShapeRef<'a>),
Flex {
key: StringShape,
value: ShapeRef<'a>,
},
List(ShapeRef<'a>),
Opaque,
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum KnownShape {
Bool,
U8,
U16,
U32,
U64,
I8,
I16,
I32,
I64,
F32,
F64,
String,
Bytes,
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum StringShape {
String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ShapeRef<'a> {
inner: &'a FieldShape<'a>,
}
impl<'a> ShapeRef<'a> {
pub fn get(&self) -> &FieldShape<'a> {
self.inner
}
pub const fn new(inner: &'a FieldShape<'a>) -> Self {
Self { inner }
}
}
#[derive(Debug)]
pub struct FieldFlag {
type_id: TypeId,
construct: fn() -> crate::value::MetricFlags<'static>,
}
impl FieldFlag {
pub const fn new<T: crate::value::FlagConstructor + 'static>() -> Self {
Self {
type_id: TypeId::of::<T>(),
construct: T::construct,
}
}
pub fn type_id(&self) -> TypeId {
self.type_id
}
pub fn is<T: 'static>(&self) -> bool {
self.type_id == TypeId::of::<T>()
}
pub fn construct(&self) -> crate::value::MetricFlags<'static> {
(self.construct)()
}
}
pub struct EntryDescriptorBuilder {
name: &'static str,
fields: &'static [FieldDescriptor],
timestamp: Option<TimestampDescriptor>,
}
impl EntryDescriptorBuilder {
pub const fn timestamp(mut self, ts: TimestampDescriptor) -> Self {
self.timestamp = Some(ts);
self
}
pub const fn maybe_timestamp(mut self, ts: Option<TimestampDescriptor>) -> Self {
self.timestamp = ts;
self
}
pub const fn build(self) -> EntryDescriptor {
EntryDescriptor {
name: self.name,
fields: self.fields,
timestamp: self.timestamp,
}
}
}
pub struct FieldDescriptorBuilder {
names: [&'static str; Styles::COUNT],
flags: &'static [FieldFlag],
skipped_flags: &'static [FieldFlag],
shape: FieldShape<'static>,
unit: Option<Unit>,
}
impl FieldDescriptorBuilder {
pub const fn pascal(mut self, name: &'static str) -> Self {
self.names[Styles::PASCAL.index as usize] = name;
self
}
pub const fn snake(mut self, name: &'static str) -> Self {
self.names[Styles::SNAKE.index as usize] = name;
self
}
pub const fn kebab(mut self, name: &'static str) -> Self {
self.names[Styles::KEBAB.index as usize] = name;
self
}
pub const fn screaming_snake(mut self, name: &'static str) -> Self {
self.names[Styles::SCREAMING_SNAKE.index as usize] = name;
self
}
pub const fn flags(mut self, flags: &'static [FieldFlag]) -> Self {
self.flags = flags;
self
}
pub const fn skipped_flags(mut self, flags: &'static [FieldFlag]) -> Self {
self.skipped_flags = flags;
self
}
pub const fn shape(mut self, shape: FieldShape<'static>) -> Self {
self.shape = shape;
self
}
pub const fn unit(mut self, unit: Unit) -> Self {
self.unit = Some(unit);
self
}
pub const fn maybe_unit(mut self, unit: Option<Unit>) -> Self {
self.unit = unit;
self
}
pub const fn build(self) -> FieldDescriptor {
FieldDescriptor {
names: self.names,
flags: self.flags,
skipped_flags: self.skipped_flags,
shape: self.shape,
unit: self.unit,
}
}
}
const _: () = assert!(
Styles::COUNT == 5,
"Styles::COUNT changed; update FieldDescriptorBuilder with a new style method"
);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn descriptor_ref_stable_id() {
static DESC: EntryDescriptor = EntryDescriptor::builder("Test", &[]).build();
let r1 = DescriptorRef::from_static(&DESC, 0);
let r2 = DescriptorRef::from_static(&DESC, 0);
assert_eq!(r1.id(), r2.id());
assert_eq!(r1.name(), "Test");
}
#[test]
fn different_descriptors_different_ids() {
static A: EntryDescriptor = EntryDescriptor::builder("A", &[]).build();
static B: EntryDescriptor = EntryDescriptor::builder("B", &[]).build();
assert_ne!(
DescriptorRef::from_static(&A, 0).id(),
DescriptorRef::from_static(&B, 0).id()
);
}
#[test]
fn prefix_changes_id() {
static DESC: EntryDescriptor = EntryDescriptor::builder("T", &[]).build();
let plain = DescriptorRef::from_static(&DESC, 0);
let prefixed = DescriptorRef::from_static(&DESC, 0).with_prefix("Api");
assert_ne!(plain.id(), prefixed.id());
}
#[test]
fn extra_flags_accumulate_and_change_id() {
use crate::value::{FlagConstructor, MetricFlags, MetricOptions};
#[derive(Debug)]
struct AOpt;
impl MetricOptions for AOpt {}
struct A;
impl FlagConstructor for A {
fn construct() -> MetricFlags<'static> {
MetricFlags::upcast(&AOpt)
}
}
#[derive(Debug)]
struct BOpt;
impl MetricOptions for BOpt {}
struct B;
impl FlagConstructor for B {
fn construct() -> MetricFlags<'static> {
MetricFlags::upcast(&BOpt)
}
}
static A_FLAGS: [FieldFlag; 1] = [FieldFlag::new::<A>()];
static B_FLAGS: [FieldFlag; 1] = [FieldFlag::new::<B>()];
static FIELDS: [FieldDescriptor; 1] = [FieldDescriptor::builder("F").build()];
static DESC: EntryDescriptor = EntryDescriptor::builder("T", &FIELDS).build();
let plain = DescriptorRef::from_static(&DESC, 0);
let stacked = DescriptorRef::from_static(&DESC, 0)
.with_extra_flags(&B_FLAGS)
.with_extra_flags(&A_FLAGS);
let flags: Vec<_> = stacked.fields().next().unwrap().flags().collect();
assert!(flags.iter().any(|f| f.is::<A>()));
assert!(flags.iter().any(|f| f.is::<B>()));
assert_ne!(plain.id(), stacked.id());
assert_ne!(
DescriptorRef::from_static(&DESC, 0)
.with_extra_flags(&B_FLAGS)
.id(),
stacked.id()
);
}
#[test]
fn field_name_no_prefix() {
static FIELDS: [FieldDescriptor; 1] = [FieldDescriptor::builder("MyField").build()];
static DESC: EntryDescriptor = EntryDescriptor::builder("T", &FIELDS).build();
let d = DescriptorRef::from_static(&DESC, 0);
assert_eq!(d.fields().next().unwrap().base_name(), "MyField");
}
#[test]
fn field_name_with_prefix() {
static FIELDS: [FieldDescriptor; 1] = [FieldDescriptor::builder("Latency").build()];
static DESC: EntryDescriptor = EntryDescriptor::builder("T", &FIELDS).build();
let d = DescriptorRef::from_static(&DESC, 0).with_prefix("Api");
let fields: Vec<_> = d.fields().collect();
let parts: Vec<&str> = fields[0].name_parts().collect();
assert_eq!(parts, vec!["Api", "Latency"]);
}
#[test]
fn field_name_with_nested_prefixes() {
static FIELDS: [FieldDescriptor; 1] = [FieldDescriptor::builder("Latency").build()];
static DESC: EntryDescriptor = EntryDescriptor::builder("T", &FIELDS).build();
let d = DescriptorRef::from_static(&DESC, 0)
.with_prefix("Api")
.with_prefix("Http");
let fields: Vec<_> = d.fields().collect();
let parts: Vec<&str> = fields[0].name_parts().collect();
assert_eq!(parts, vec!["Http", "Api", "Latency"]);
}
#[test]
fn field_view_iteration() {
static FIELDS: [FieldDescriptor; 2] = [
FieldDescriptor::builder("Alpha").build(),
FieldDescriptor::builder("Beta").unit(Unit::Count).build(),
];
static DESC: EntryDescriptor = EntryDescriptor::builder("T", &FIELDS).build();
let d = DescriptorRef::from_static(&DESC, 0);
let fields: Vec<_> = d.fields().collect();
assert_eq!(fields.len(), 2);
assert_eq!(fields[0].base_name(), "Alpha");
assert_eq!(fields[1].base_name(), "Beta");
assert_eq!(fields[1].unit(), Some(Unit::Count));
}
#[test]
fn timestamp() {
static DESC: EntryDescriptor = EntryDescriptor::builder("E", &[])
.timestamp(TimestampDescriptor::new("ts"))
.build();
let d = DescriptorRef::from_static(&DESC, 0);
assert_eq!(d.timestamp().unwrap().name(), "ts");
}
#[test]
fn hand_written_entry_empty() {
use crate::{Entry, EntryWriter};
struct HandWritten;
impl Entry for HandWritten {
fn write<'a>(&'a self, _w: &mut impl EntryWriter<'a>) {}
}
assert_eq!(HandWritten.descriptors().is_available(), false);
}
#[test]
fn boxentry_forwards() {
use crate::{BoxEntry, Entry, EntryWriter};
static DESC: EntryDescriptor = EntryDescriptor::builder("X", &[]).build();
struct WithDesc;
impl Entry for WithDesc {
fn write<'a>(&'a self, _w: &mut impl EntryWriter<'a>) {}
fn descriptors(&self) -> Descriptors<'_> {
Descriptors::available(std::iter::once(DescriptorRef::from_static(&DESC, 0)))
}
}
let boxed = BoxEntry::new(WithDesc);
let descs = boxed.descriptors().unwrap();
assert_eq!(descs.len(), 1);
assert_eq!(descs[0].name(), "X");
}
}