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
3 changes: 3 additions & 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 crates/instrumentation-build/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ convert_case = "0.11"
prettyplease = "0.2"
proc-macro2 = "1"
quent-constraints = { path = "../constraints" }
quent-ref-target = { path = "../ref-target" }
quent-schema = { path = "../schema" }
quote = "1"
syn = { version = "2", features = ["full", "parsing"] }
Expand Down
24 changes: 24 additions & 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.

14 changes: 14 additions & 0 deletions crates/instrumentation-build/example/model.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,17 @@ records:
fields:
tags: { list: string }
extra: dynamic
Route:
doc: Details of one routing edge to an upstream server.
fields:
hops: u8

entities:
Server:
doc: A server that accepts connections.
events:
booted: once

Connection:
doc: A client connection.
events:
Expand All @@ -24,8 +33,13 @@ entities:
once:
peer: Endpoint
session: uuid
host: { scope-ref: Server }
data:
multi:
bytes: u64
meta: { option: Meta }
routed:
doc: Forwarded to an upstream server.
once:
upstream: { ref: Server, data: Route }
closed: once
16 changes: 15 additions & 1 deletion crates/instrumentation-build/example/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ mod demo {

fn main() -> Result<(), Box<dyn std::error::Error>> {
let context: DemoContext = demo::DemoContext::try_new(Some(debug_printing_exporter()))?;

// Boot a server; its instance id is the target the connection references.
let mut server = context.server_observer().handle();
server.booted()?;

let observer: ConnectionObserver = context.connection_observer();

// The handle (may) hold per-instance state that enforces once-cardinality,
Expand All @@ -24,6 +29,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
port: 8080,
},
Uuid::nil(),
server.as_entity_ref(),
)?;
conn.data(1234, None)?;

Expand All @@ -41,6 +47,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
}),
)?;

// `ref` field carrying data: the server reference plus details of the edge.
conn.routed(server.as_entity_ref_with(demo::Route { hops: 3 }))?;

conn.closed()?;

// Emitting a once-event a second time fails.
Expand All @@ -53,7 +62,12 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
/// Return an exporter that debug-prints each emitted event's payload.
fn debug_printing_exporter() -> ExporterOptions {
ExporterOptions::Callback(EventCallback::new(|recorded| {
if let Ok(event) = recorded.event.downcast::<Event<demo::ConnectionEvent>>() {
if let Some(event) = recorded
.event
.downcast_ref::<Event<demo::ConnectionEvent>>()
{
println!("[{} @ {}] {:?}", event.id, event.timestamp, event.data);
} else if let Some(event) = recorded.event.downcast_ref::<Event<demo::ServerEvent>>() {
println!("[{} @ {}] {:?}", event.id, event.timestamp, event.data);
} else {
unreachable!()
Expand Down
51 changes: 43 additions & 8 deletions crates/instrumentation-build/src/data_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@

//! Mapping from schema [`DataType`]s to Rust type tokens.

use convert_case::Case;
use convert_case::{Case, Casing};
use proc_macro2::TokenStream;
use quent_schema::DataType;
use quent_constraints::Constraint;
use quent_ref_target::RefTargetConstraint;
use quent_schema::{Annotations, DataType};
use quote::quote;

use crate::common::{raw_ident, to_case};
Expand Down Expand Up @@ -54,13 +56,32 @@ pub(crate) fn map_data_type(ty: &DataType, depth: usize) -> TokenStream {
quote! { #ident }
}
DataType::DynamicRecord => quote! { ::quent_instrumentation::CustomAttributes },
DataType::EntityRef { data, .. } => match data {
Some(inner) => {
let inner = map_data_type(inner, depth + 1);
quote! { ::quent_instrumentation::EntityRef<#inner> }
DataType::EntityRef { data, annotations } => {
let target = ref_target_marker(annotations);
match data {
Some(inner) => {
let inner = map_data_type(inner, depth + 1);
quote! { ::quent_instrumentation::EntityRef<#target, #inner> }
}
None => quote! { ::quent_instrumentation::EntityRef<#target> },
}
None => quote! { ::quent_instrumentation::EntityRef },
},
}
}
}

/// The target-entity marker type for an entity reference, taken from its
/// ref-target constraint, or the `AnyEntity` marker when it is not restricted
/// to a target entity.
fn ref_target_marker(annotations: &Annotations) -> TokenStream {
match annotations
.constraint(RefTargetConstraint::NAME)
.and_then(|c| c.data())
{
Some(entity) => {
let marker = raw_ident(entity.to_case(Case::Pascal));
quote! { #marker }
}
None => quote! { AnyEntity },
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
}

Expand All @@ -78,4 +99,18 @@ mod tests {
}
let _ = map_data_type(&ty, 0);
}

#[test]
fn entity_ref_uses_its_ref_target_marker() {
use quent_schema::builder::AnnotationsBuilder;

let mut annotations = AnnotationsBuilder::new();
annotations.set_constraint(RefTargetConstraint::NAME, Some("Cluster".to_string()));
let ty = DataType::EntityRef {
data: Some(Box::new(DataType::U64)),
annotations: annotations.build(),
};
let tokens = map_data_type(&ty, 0).to_string();
assert!(tokens.contains("EntityRef < Cluster , u64 >"), "{tokens}");
}
}
6 changes: 3 additions & 3 deletions crates/instrumentation-build/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,8 @@ mod tests {
list: Vec<String>,
rec: SomeRecord,
dynrec: ::quent_instrumentation::CustomAttributes,
eref: ::quent_instrumentation::EntityRef,
eref_payload: ::quent_instrumentation::EntityRef<u64>
eref: ::quent_instrumentation::EntityRef<AnyEntity>,
eref_payload: ::quent_instrumentation::EntityRef<AnyEntity, u64>
}
}
};
Expand Down Expand Up @@ -237,7 +237,7 @@ mod tests {
#[doc = "The `ev` event."]
Ev {
nested: Option<Vec<Option<u8>>>,
eref_list: ::quent_instrumentation::EntityRef<Vec<String>>
eref_list: ::quent_instrumentation::EntityRef<AnyEntity, Vec<String>>
}
}
};
Expand Down
25 changes: 24 additions & 1 deletion crates/instrumentation-build/src/runtime/handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use proc_macro2::{Literal, TokenStream};
use quent_schema::{Cardinality, Entity};
use quote::quote;

use super::{event_ident, handle_ident};
use super::{event_ident, handle_ident, marker_ident};
use crate::GenerateError;
use crate::common::{doc_attr_or, raw_ident, to_case};
use crate::data_type::map_data_type;
Expand All @@ -27,6 +27,7 @@ pub(super) fn entity_handle(entity: &Entity) -> Result<TokenStream, GenerateErro
let entity_pascal = to_case(entity.name(), Case::Pascal);
let event_ty = event_ident(entity);
let handle_ty = handle_ident(entity);
let marker_ty = marker_ident(entity);

let once_count = entity
.events()
Expand Down Expand Up @@ -133,6 +134,28 @@ pub(super) fn entity_handle(entity: &Entity) -> Result<TokenStream, GenerateErro
self.inner.id()
}

/// A typed reference to this instance, carrying no data.
pub fn as_entity_ref(&self) -> ::quent_instrumentation::EntityRef<#marker_ty> {
::quent_instrumentation::EntityRef::new(self.uuid(), ())
}

/// A typed reference to this instance, carrying `data`.
pub fn as_entity_ref_with<T>(&self, data: T) -> ::quent_instrumentation::EntityRef<#marker_ty, T> {
::quent_instrumentation::EntityRef::new(self.uuid(), data)
}

/// A reference to this instance for a field not restricted to a
/// target entity type, carrying no data.
pub fn as_any_entity_ref(&self) -> ::quent_instrumentation::EntityRef<::quent_instrumentation::AnyEntity> {
::quent_instrumentation::EntityRef::new(self.uuid(), ())
}

/// A reference to this instance for a field not restricted to a
/// target entity type, carrying `data`.
pub fn as_any_entity_ref_with<T>(&self, data: T) -> ::quent_instrumentation::EntityRef<::quent_instrumentation::AnyEntity, T> {
::quent_instrumentation::EntityRef::new(self.uuid(), data)
}

#(#methods)*
}
})
Expand Down
24 changes: 23 additions & 1 deletion crates/instrumentation-build/src/runtime/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,12 @@ pub(crate) fn generate_runtime_types(schema: &Schema) -> Result<TokenStream, Gen
let entities: Vec<TokenStream> = schema
.entities()
.map(|entity| {
let marker = entity_marker(entity);
let event_impl = entity_event_impl(entity);
let observer = observer::entity_observer(entity);
let handle = handle::entity_handle(entity)?;
Ok::<_, GenerateError>(quote! {
#marker
#event_impl
#observer
#handle
Expand All @@ -55,11 +57,26 @@ pub(crate) fn generate_runtime_types(schema: &Schema) -> Result<TokenStream, Gen
pub(crate) fn reexports() -> TokenStream {
quote! {
pub use ::quent_instrumentation::{
CustomAttributes, EntityRef, Event, HandleError, Uuid,
AnyEntity, CustomAttributes, EntityRef, Event, HandleError, Uuid,
};
}
}

/// `{Entity}` — the zero-size marker naming the entity, used as the target
/// type of [`EntityRef`](quent_instrumentation::EntityRef) fields that point at it.
fn entity_marker(entity: &Entity) -> TokenStream {
let marker = marker_ident(entity);
let doc = format!(
"Marker type for the `{}` entity.",
to_case(entity.name(), Case::Pascal)
);
quote! {
#[doc = #doc]
#[derive(Debug, Clone, Copy)]
pub struct #marker;
}
}

/// Tie an entity's event enum to its stream name (the entity's snake-case name).
fn entity_event_impl(entity: &Entity) -> TokenStream {
let event_ty = event_ident(entity);
Expand All @@ -76,6 +93,11 @@ fn event_ident(entity: &Entity) -> Ident {
raw_ident(format!("{}Event", to_case(entity.name(), Case::Pascal)))
}

/// `{Entity}` — the entity's ref-target marker type.
fn marker_ident(entity: &Entity) -> Ident {
raw_ident(to_case(entity.name(), Case::Pascal))
}

/// `{Entity}Observer`.
fn observer_ident(entity: &Entity) -> Ident {
raw_ident(format!("{}Observer", to_case(entity.name(), Case::Pascal)))
Expand Down
31 changes: 27 additions & 4 deletions crates/instrumentation/src/entity_ref.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,40 @@

//! Reference from one entity instance to another.

use std::marker::PhantomData;

use uuid::Uuid;

/// Reference from one entity instance to another by id, optionally carrying
/// payload data `T`.
/// Reference to an entity instance of type `E`, optionally carrying payload
/// data `T`.
///
/// Placeholder backing the schema generator's `DataType::EntityRef` fields.
/// In instrumentation libraries generated with `instrumentation-build`, `E` is
/// typically a marker type.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone)]
pub struct EntityRef<T = ()> {
pub struct EntityRef<E, T = ()> {
#[cfg_attr(feature = "serde", serde(skip))]
_entity: PhantomData<E>,
/// Identifier of the referenced entity instance.
pub target: Uuid,
/// Payload carried alongside the reference.
pub data: T,
}

impl<E, T> EntityRef<E, T> {
/// A reference to the entity instance identified by `target`, carrying `data`.
pub fn new(target: Uuid, data: T) -> Self {
Self {
_entity: PhantomData,
target,
data,
}
}
}

/// Entity marker for a reference not restricted to a single entity type.
///
/// Untargeted entity reference fields in instrumentation have the type:
/// `EntityRef<AnyEntity, ...>`.
#[derive(Debug, Clone, Copy)]
pub struct AnyEntity;
2 changes: 1 addition & 1 deletion crates/instrumentation/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ mod observer;
mod sidecar;

pub use context::Context;
pub use entity_ref::EntityRef;
pub use entity_ref::{AnyEntity, EntityRef};
pub use handle::{Handle, HandleError};
pub use observer::{EventSender, Observer};
pub use sidecar::write_sidecar;
Expand Down
2 changes: 2 additions & 0 deletions crates/yaml/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ publish.workspace = true
[dependencies]
indexmap = { workspace = true, features = ["serde"] }
quent-constraints = { path = "../constraints" }
quent-ref-target = { path = "../ref-target" }
quent-ref-tree = { path = "../ref-tree" }
Comment thread
johanpel marked this conversation as resolved.
quent-schema = { path = "../schema" }
serde = { workspace = true }
serde-saphyr = "0.0.29"
Expand Down
Loading
Loading