use std::collections::BTreeSet;
use std::sync::OnceLock;
use quark::inventory;
use crate::classification::FieldClassification;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
pub enum SpectraLevel {
Error,
Warn,
#[default]
Info,
Debug,
Trace,
}
impl SpectraLevel {
pub fn parse(s: &str) -> Option<Self> {
match s.trim().to_ascii_lowercase().as_str() {
"error" => Some(Self::Error),
"warn" | "warning" => Some(Self::Warn),
"info" => Some(Self::Info),
"debug" => Some(Self::Debug),
"trace" => Some(Self::Trace),
_ => None,
}
}
pub fn is_always_on(self) -> bool {
matches!(self, Self::Error | Self::Warn)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LoggingKind {
Event,
Metric,
}
#[derive(Debug, Clone)]
pub struct SchemaFieldMetadata {
pub name: String,
pub rust_type: String,
pub classification: FieldClassification,
}
#[derive(Debug, Clone)]
pub struct SchemaMetadata {
pub table_or_metric: String,
pub store: String,
pub version: String,
pub description: Option<String>,
pub logging_kind: LoggingKind,
pub fields: Vec<SchemaFieldMetadata>,
pub default_level: SpectraLevel,
pub default_sample_rate: f64,
pub gauge_coalesce_ms: Option<u64>,
}
impl Default for SchemaMetadata {
fn default() -> Self {
Self {
table_or_metric: String::new(),
store: "default".to_string(),
version: String::new(),
description: None,
logging_kind: LoggingKind::Metric,
fields: Vec::new(),
default_level: SpectraLevel::Info,
default_sample_rate: 1.0,
gauge_coalesce_ms: None,
}
}
}
impl SchemaMetadata {
pub fn table_name(&self) -> &str {
&self.table_or_metric
}
}
impl quark::Registrable for SchemaMetadata {
fn registry_key(&self) -> &str {
&self.table_or_metric
}
}
pub struct SchemaMetadataInit(pub fn() -> SchemaMetadata);
inventory::collect!(SchemaMetadataInit);
#[derive(Debug)]
pub struct SchemaRegistry {
inner: quark::Registry<SchemaMetadata>,
}
impl SchemaRegistry {
pub fn new() -> Self {
Self {
inner: quark::Registry::new(),
}
}
pub fn auto_discover() -> Self {
let mut registry = Self::new();
for init in inventory::iter::<SchemaMetadataInit> {
let metadata = (init.0)();
registry.register(Box::leak(Box::new(metadata)));
}
registry
}
pub fn set_global(registry: SchemaRegistry) {
#[allow(clippy::expect_used)]
GLOBAL_REGISTRY
.set(registry)
.expect("SchemaRegistry::set_global called more than once");
}
pub fn global() -> &'static Self {
GLOBAL_REGISTRY.get_or_init(Self::auto_discover)
}
pub fn register(&mut self, metadata: &'static SchemaMetadata) {
self.inner.register(metadata);
}
pub fn get_schema(&self, table_or_metric: &str) -> Option<&'static SchemaMetadata> {
self.inner.get(table_or_metric)
}
pub fn list_schemas(&self) -> Vec<&str> {
self.inner.list()
}
pub fn has_schema(&self, table_or_metric: &str) -> bool {
self.inner.get(table_or_metric).is_some()
}
pub fn distinct_store_names(&self) -> Vec<String> {
let mut names: BTreeSet<String> = BTreeSet::new();
names.insert("default".to_string());
for name in self.list_schemas() {
if let Some(meta) = self.get_schema(name) {
if !meta.store.is_empty() {
names.insert(meta.store.clone());
}
}
}
names.into_iter().collect()
}
}
static GLOBAL_REGISTRY: OnceLock<SchemaRegistry> = OnceLock::new();
pub fn collect_distinct_spectra_store_names() -> Vec<String> {
SchemaRegistry::global().distinct_store_names()
}
impl Default for SchemaRegistry {
fn default() -> Self {
Self::new()
}
}
impl std::ops::Deref for SchemaRegistry {
type Target = quark::Registry<SchemaMetadata>;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn registry_empty_when_no_submissions_in_test_crate() {
let reg = SchemaRegistry::new();
assert!(reg.list_schemas().is_empty());
}
}