Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ petgraph = "0.8.3"
postcard = { version = "1", default-features = false, features = ["alloc"] }
prost = "0.14.1"
pyo3 = "0.29"
quent-dynamic-attributes = { path = "crates/dynamic-attributes" }
quent-resource = { path = "crates/resource" }
rmp-serde = "1"
rustc-hash = "2"
Expand Down
5 changes: 3 additions & 2 deletions crates/events/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ publish.workspace = true

[features]
default = []
serde = ["dep:serde"]
serde = ["dep:serde", "quent-dynamic-attributes/serde", "uuid/serde"]

[dependencies]
quent-dynamic-attributes.workspace = true
quent-time = { path = "../time" }
uuid = { workspace = true, features = ["serde"] }
uuid.workspace = true
serde = { workspace = true, optional = true }
13 changes: 12 additions & 1 deletion crates/events/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,28 @@

//! Type definitions of entity events.

mod entity_ref;

use quent_time::{TimeUnixNanoSec, Timestamp, timestamp};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use uuid::Uuid;

pub use entity_ref::{AnyEntity, EntityRef};
pub use quent_dynamic_attributes::DynamicAttributes;
pub use uuid::Uuid;

/// Trait for the event type of an entity.
pub trait EntityEvent {
/// The name of the entity.
const NAME: &'static str;
}

/// Associates an entity marker with the events emitted for that entity.
pub trait Entity: Sized {
/// Events emitted for this entity.
type Event: EntityEvent;
}

#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
#[derive(Debug)]
pub struct Event<T> {
Expand Down
1 change: 1 addition & 0 deletions crates/instrumentation-build/example/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 0 additions & 2 deletions crates/instrumentation-build/example/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {

// Schema -> generated Rust instrumentation source.
let opts = Options {
event_derives: &["Debug"],
record_derives: &["Debug"],
// To just print the events in this example, we'll be using the callback
// exporter. This exporter takes a type-erased event, so in order to
// simplify downcasting back to a statically-typed event, this features
Expand Down
15 changes: 6 additions & 9 deletions crates/instrumentation-build/src/any_event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ use crate::common::{
use crate::namespace::Namespace;
use crate::{GenerateError, Options};

/// Generate `AnyEvent` and its `from_any` decoder, carrying the event enums'
/// derives ([`Options::event_derives`]).
/// Generate `AnyEvent` and its `from_any` decoder.
///
/// # Errors
///
Expand Down Expand Up @@ -51,16 +50,17 @@ pub(crate) fn generate_any_event(
return Ok(quote! {});
}

let derives = derive_attr(opts.event_derives)?;
let derives = derive_attr(opts.event_derives, opts.debug, opts.serde, false)?;
let runtime = opts.event_runtime();
let decls = variants.iter().map(|(variant, event)| {
quote! { #variant(&'a ::quent_instrumentation::Event<#event>) }
quote! { #variant(&'a #runtime::Event<#event>) }
});
let child_decls = children.iter().map(|(variant, module)| {
quote! { #variant(#module::AnyEvent<'a>) }
});
let direct_arms = variants.iter().map(|(variant, event)| {
quote! {
if let Some(event) = any.downcast_ref::<::quent_instrumentation::Event<#event>>() {
if let Some(event) = any.downcast_ref::<#runtime::Event<#event>>() {
return Some(Self::#variant(event));
}
}
Expand Down Expand Up @@ -103,10 +103,7 @@ mod tests {
.with_entity(entity("Server", [event("booted", [])]))
.build()
.unwrap();
let opts = Options {
event_derives: &["Debug"],
..Options::default()
};
let opts = Options::default();
let namespaces = Namespace::root(&schema);
let expected = quote! {
#[derive(Debug)]
Expand Down
83 changes: 69 additions & 14 deletions crates/instrumentation-build/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,84 @@
use convert_case::{Boundary, Case, Casing};
use proc_macro2::{Span, TokenStream};
use quent_schema::{Identifier, Path};
use quote::quote;
use quote::{ToTokens, quote};
use std::collections::HashSet;
use syn::Ident;

use crate::GenerateError;

/// Build a `#[derive(..)]` attribute from `derives`.
pub(crate) fn derive_attr(derives: &[&str]) -> Result<TokenStream, GenerateError> {
if derives.is_empty() {
return Ok(quote! {});
/// Build a deduplicated `#[derive(..)]` attribute.
pub(crate) fn derive_attr(
derives: &[&str],
debug: bool,
serialize: bool,
deserialize: bool,
) -> Result<TokenStream, GenerateError> {
let mut paths = Vec::new();
if debug {
paths.push(syn::parse_quote!(Debug));
}
let paths = derives
.iter()
.copied()
.map(|d| {
syn::parse_str::<syn::Path>(d).map_err(|source| GenerateError::InvalidDerive {
derive: d.to_owned(),
source,
if serialize {
paths.push(syn::parse_quote!(::serde::Serialize));
}
if deserialize {
paths.push(syn::parse_quote!(::serde::Deserialize));
}
paths.extend(
derives
.iter()
.copied()
.map(|derive| {
syn::parse_str::<syn::Path>(derive).map_err(|source| GenerateError::InvalidDerive {
derive: derive.to_owned(),
source,
})
})
})
.collect::<Result<Vec<_>, _>>()?;
.collect::<Result<Vec<_>, _>>()?,
);
for path in &mut paths {
canonicalize_known_derive_path(path);
}
let mut seen = HashSet::new();
paths.retain(|path| seen.insert(path.to_token_stream().to_string()));
Comment thread
coderabbitai[bot] marked this conversation as resolved.
if paths.is_empty() {
return Ok(quote! {});
}
Ok(quote! { #[derive(#(#paths),*)] })
}

fn canonicalize_known_derive_path(path: &mut syn::Path) {
canonicalize_external_derive_path(path, "serde", &["Serialize", "Deserialize"]);
if path_has_segments(path, &["std", "fmt", "Debug"])
|| path_has_segments(path, &["core", "fmt", "Debug"])
{
*path = syn::parse_quote!(Debug);
}
}

fn path_has_segments(path: &syn::Path, names: &[&str]) -> bool {
path.segments.len() == names.len()
&& path.segments.iter().zip(names).all(|(segment, name)| {
segment.ident == *name && matches!(segment.arguments, syn::PathArguments::None)
})
}

fn canonicalize_external_derive_path(
path: &mut syn::Path,
crate_name: &str,
derive_names: &[&str],
) {
if path.leading_colon.is_none()
&& path.segments.len() == 2
&& path.segments[0].ident == crate_name
&& derive_names
.iter()
.any(|derive| path.segments[1].ident == derive)
{
path.leading_colon = Some(Default::default());
}
}

/// Build a `#[doc = ..]` attribute from `docs`.
pub(crate) fn doc_attr(docs: Option<&str>) -> TokenStream {
match docs {
Expand Down
60 changes: 35 additions & 25 deletions crates/instrumentation-build/src/data_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,31 +9,31 @@ use quent_schema::{Annotations, DataType};
use quote::quote;

use crate::common::{relative_root_type, relative_type_path};
use crate::{GenerateError, Options};

/// Maximum nesting depth of `Option`/`List`/`EntityRef` wrappers a single field
/// type may have, far above any realistic schema. Self-referential records are
/// already ruled out by base validation, but even if somehow schemas are
/// produced with great nesting depth, this will produce a friendlier panic
/// instead of a stack overflow.
/// already ruled out by base validation.
pub(crate) const MAX_TYPE_DEPTH: usize = 64;

/// Map a [`DataType`] to its Rust type tokens.
///
/// # Panics
///
/// Panics if `ty` nests deeper than [`MAX_TYPE_DEPTH`].
pub(crate) fn map_data_type(
ty: &DataType,
depth: usize,
source_namespace: &[quent_schema::Identifier],
) -> TokenStream {
assert!(
depth <= MAX_TYPE_DEPTH,
"field type nesting exceeds the maximum depth of {MAX_TYPE_DEPTH}"
);
match ty {
opts: &Options,
) -> Result<TokenStream, GenerateError> {
if depth > MAX_TYPE_DEPTH {
return Err(GenerateError::TypeNestingTooDeep {
max: MAX_TYPE_DEPTH,
});
}
Ok(match ty {
DataType::Bool => quote! { bool },
DataType::Uuid => quote! { ::quent_instrumentation::Uuid },
DataType::Uuid => {
let runtime = opts.event_runtime();
quote! { #runtime::Uuid }
}
DataType::String => quote! { String },
DataType::U8 => quote! { u8 },
DataType::U16 => quote! { u16 },
Expand All @@ -46,26 +46,30 @@ pub(crate) fn map_data_type(
DataType::F32 => quote! { f32 },
DataType::F64 => quote! { f64 },
DataType::Option(inner) => {
let inner = map_data_type(inner, depth + 1, source_namespace);
let inner = map_data_type(inner, depth + 1, source_namespace, opts)?;
quote! { Option<#inner> }
}
DataType::List(inner) => {
let inner = map_data_type(inner, depth + 1, source_namespace);
let inner = map_data_type(inner, depth + 1, source_namespace, opts)?;
quote! { Vec<#inner> }
}
DataType::Record(path) => relative_type_path(path, source_namespace, ""),
DataType::DynamicRecord => quote! { ::quent_instrumentation::DynamicAttributes },
DataType::DynamicRecord => {
let runtime = opts.event_runtime();
quote! { #runtime::DynamicAttributes }
}
DataType::EntityRef { data, annotations } => {
let target = ref_target_marker(annotations, source_namespace);
let runtime = opts.event_runtime();
match data {
Some(inner) => {
let inner = map_data_type(inner, depth + 1, source_namespace);
quote! { ::quent_instrumentation::EntityRef<#target, #inner> }
let inner = map_data_type(inner, depth + 1, source_namespace, opts)?;
quote! { #runtime::EntityRef<#target, #inner> }
}
None => quote! { ::quent_instrumentation::EntityRef<#target> },
None => quote! { #runtime::EntityRef<#target> },
}
}
}
})
}

/// The target-entity marker type for an entity reference, taken from its
Expand All @@ -89,13 +93,17 @@ mod tests {
use quent_schema::DataType;

#[test]
#[should_panic(expected = "maximum depth")]
fn excessive_type_nesting_panics() {
fn excessive_type_nesting_returns_error() {
let mut ty = DataType::U8;
for _ in 0..(MAX_TYPE_DEPTH + 5) {
ty = DataType::Option(Box::new(ty));
}
let _ = map_data_type(&ty, 0, &[]);
assert!(matches!(
map_data_type(&ty, 0, &[], &Options::default()),
Err(GenerateError::TypeNestingTooDeep {
max: MAX_TYPE_DEPTH
})
));
}

#[test]
Expand All @@ -108,7 +116,9 @@ mod tests {
data: Some(Box::new(DataType::U64)),
annotations: annotations.build().unwrap(),
};
let tokens = map_data_type(&ty, 0, &[]).to_string();
let tokens = map_data_type(&ty, 0, &[], &Options::default())
.unwrap()
.to_string();
assert!(tokens.contains("EntityRef < Cluster , u64 >"), "{tokens}");
}
}
Loading
Loading