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
11 changes: 6 additions & 5 deletions crates/instrumentation-build/example/model.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,26 +23,27 @@ entities:
Server:
doc: A server that accepts connections.
events:
booted: once
booted: {}

Connection:
doc: A client connection.
events:
opened:
doc: The connection was accepted.
once:
attributes:
peer: Endpoint
session: uuid
host: { scope-ref: Server }
data:
multi:
multi: true
attributes:
bytes: u64
meta: { option: Meta }
routed:
doc: Forwarded to an upstream server.
once:
attributes:
upstream: { ref: Server, data: Route }
closed: once
closed: {}

fsms:
Query:
Expand Down
37 changes: 4 additions & 33 deletions crates/yaml/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,42 +99,22 @@ pub(crate) struct Entity {
pub(crate) events: IndexMap<String, Event>,
}

/// An event: either the short form giving just a cardinality (`started: once`),
/// or a mapping that adds a payload and annotations.
#[derive(Debug, Deserialize)]
#[serde(untagged)]
pub(crate) enum Event {
OneLiner(Cardinality),
Body(Box<EventBody>),
}

/// Whether an event fires once per entity instance or repeatedly.
#[derive(Debug, Clone, Copy, Deserialize)]
#[serde(rename_all = "lowercase")]
pub(crate) enum Cardinality {
Once,
Multi,
}

/// The mapping form of an event: annotations plus a once or multi payload.
/// An entity event.
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct EventBody {
pub(crate) struct Event {
#[serde(default)]
pub(crate) doc: Option<String>,
#[serde(default)]
pub(crate) constraints: AnnotationMap,
#[serde(default)]
pub(crate) metadata: AnnotationMap,
#[serde(default)]
pub(crate) once: Option<Payload>,
pub(crate) multi: bool,
#[serde(default)]
pub(crate) multi: Option<Payload>,
pub(crate) attributes: IndexMap<String, Field>,
}

/// The fields an event carries, or nothing when it has no payload.
pub(crate) type Payload = Option<IndexMap<String, Field>>;

/// A field: either just its type, or a mapping that adds annotations to it.
#[derive(Debug, Deserialize)]
#[serde(untagged)]
Expand Down Expand Up @@ -232,12 +212,3 @@ pub(crate) struct ScopeForm {
#[serde(default)]
pub(crate) data: Option<Box<TypeExpr>>,
}

impl From<Cardinality> for quent_schema::Cardinality {
fn from(c: Cardinality) -> Self {
match c {
Cardinality::Once => quent_schema::Cardinality::Once,
Cardinality::Multi => quent_schema::Cardinality::Multi,
}
}
}
53 changes: 15 additions & 38 deletions crates/yaml/src/lower.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use quent_ref_tree::RefTreeConstraint;
use quent_schema::builder::{
AnnotationsBuilder, BuilderError, EntityBuilder, EventBuilder, RecordBuilder, SchemaBuilder,
};
use quent_schema::{Annotations, DataType, Entity, Field, Identifier, Record, Schema};
use quent_schema::{Annotations, Cardinality, DataType, Entity, Field, Identifier, Record, Schema};
use serde::Deserialize;

use crate::ast::{self, AnnotationMap, Model, TypeExpr};
Expand Down Expand Up @@ -250,43 +250,20 @@ fn event_of(
let events_path = format!("{entity_path}.events");
let path = format!("{events_path}.{name}");
let id = ident(name, &events_path, sink);
match event {
ast::Event::OneLiner(card) => Some(EventBuilder::new(id?, (*card).into()).build()),
ast::Event::Body(body) => {
let (card, payload_key, payload) = match (&body.once, &body.multi) {
(Some(_), Some(_)) => {
sink.error(
&path,
"event declares both `once` and `multi`",
Some("keep exactly one".to_string()),
);
(ast::Cardinality::Once, "once", &body.once)
}
(Some(_), None) => (ast::Cardinality::Once, "once", &body.once),
(None, Some(_)) => (ast::Cardinality::Multi, "multi", &body.multi),
(None, None) => {
sink.error(
&path,
"event must declare a cardinality",
Some("add `once:` or `multi:`, or write `name: once`".to_string()),
);
(ast::Cardinality::Once, "once", &body.once)
}
};
let fields = match payload.as_ref().and_then(|p| p.as_ref()) {
Some(map) => fields_of(map, &format!("{path}.{payload_key}"), sink),
None => Vec::new(),
};
let anns = annotations(&body.doc, &body.constraints, &body.metadata, &path, sink);
Some(
EventBuilder::new(id?, card.into())
.try_with_fields(fields)
.expect("field names are unique")
.with_annotations(anns)
.build(),
)
}
}
let fields = fields_of(&event.attributes, &format!("{path}.attributes"), sink);
let anns = annotations(&event.doc, &event.constraints, &event.metadata, &path, sink);
let cardinality = if event.multi {
Cardinality::Multi
} else {
Cardinality::Once
};
Some(
EventBuilder::new(id?, cardinality)
.try_with_fields(fields)
.expect("field names are unique")
.with_annotations(anns)
.build(),
)
}

fn fields_of(
Expand Down
17 changes: 3 additions & 14 deletions crates/yaml/tests/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,27 +49,16 @@ model: m
}

#[test]
fn event_cardinality_required() {
fn event_multi_must_be_boolean() {
expect_error(
"\
entities:
E:
events:
started:
doc: x
multi: sometimes
",
&["event must declare a cardinality"],
);
expect_error(
"\
entities:
E:
events:
started:
once: {}
multi: {}
",
&["both `once` and `multi`"],
&["invalid boolean"],
);
}

Expand Down
2 changes: 1 addition & 1 deletion crates/yaml/tests/fsm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ model: m
entities:
E:
events:
a: once
a: {}
fsms:
E:
states:
Expand Down
29 changes: 22 additions & 7 deletions crates/yaml/tests/references.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
//! validated against the schema.

use quent_schema::test_utils::ident;
use quent_schema::{Annotations, DataType, Schema};
use quent_schema::{Annotations, Cardinality, DataType, Schema};
use quent_yaml::parse_from_str;

const REF_TARGET: &str = "quent.ref-target.v0.1.0";
Expand All @@ -15,6 +15,15 @@ fn schema_of(src: &str) -> Schema {
parse_from_str(src, None).expect("parses").schema
}

fn cardinality(schema: &Schema, entity: &str, event: &str) -> Cardinality {
schema
.entity(&ident(entity))
.unwrap()
.event(&ident(event))
.unwrap()
.cardinality()
}

/// The annotations on `entity.event.field`, which must be an entity reference.
fn ref_annotations<'s>(
schema: &'s Schema,
Expand Down Expand Up @@ -44,14 +53,15 @@ model: m
entities:
Cluster:
events:
up: once
up: {}
Engine:
events:
started:
once:
attributes:
cluster: { ref: Cluster }
",
);
assert_eq!(cardinality(&schema, "Engine", "started"), Cardinality::Once);
let anns = ref_annotations(&schema, "Engine", "started", "cluster");
assert_eq!(anns.constraint(REF_TARGET).unwrap().data(), Some("Cluster"));
assert!(!anns.has_constraint(REF_TREE));
Expand All @@ -66,16 +76,21 @@ model: m
entities:
Cluster:
events:
up: once
up: {}
Engine:
events:
started:
once:
multi: true
attributes:
cluster:
ref: Cluster
data: u64
",
);
assert_eq!(
cardinality(&schema, "Engine", "started"),
Cardinality::Multi
);
let field = schema
.entity(&ident("Engine"))
.unwrap()
Expand Down Expand Up @@ -103,11 +118,11 @@ model: m
entities:
Cluster:
events:
up: once
up: {}
Engine:
events:
started:
once:
attributes:
parent:
scope-ref: Cluster
data: u64
Expand Down