From 8a453a44f930e96dfbc60cc9f3ab5505fc04e1fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Sat, 8 Feb 2025 09:40:52 +0100 Subject: [PATCH] Make it actually work with trustfall and dynamic data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcel Müller --- crates/plaixt/src/main.rs | 207 +++++++++++++++++++++------ crates/plaixt/src/parsing.rs | 19 +++ examples/changelog.plrecs | 8 +- examples/definitions/changelog.pldef | 2 +- query | 13 ++ 5 files changed, 203 insertions(+), 46 deletions(-) create mode 100644 query diff --git a/crates/plaixt/src/main.rs b/crates/plaixt/src/main.rs index cb37400..8e5e5bb 100644 --- a/crates/plaixt/src/main.rs +++ b/crates/plaixt/src/main.rs @@ -1,6 +1,7 @@ #![allow(dead_code)] use std::collections::BTreeMap; +use std::io::Read; use std::sync::Arc; use camino::Utf8PathBuf; @@ -8,11 +9,18 @@ use clap::Parser; use clap::Subcommand; use clap::ValueHint; use human_panic::Metadata; +use kdl::KdlValue; +use miette::IntoDiagnostic; use parsing::Definition; use parsing::Record; +use tracing::debug; use tracing::info; +use tracing::trace; +use tracing_subscriber::EnvFilter; use trustfall::execute_query; use trustfall::provider::field_property; +use trustfall::provider::resolve_coercion_with; +use trustfall::provider::resolve_neighbors_with; use trustfall::provider::resolve_property_with; use trustfall::provider::Adapter; use trustfall::FieldValue; @@ -41,6 +49,7 @@ struct Args { #[derive(Debug, Subcommand)] enum ArgMode { Dump, + Query, } #[tokio::main] @@ -50,7 +59,10 @@ async fn main() -> miette::Result<()> { .authors(env!("CARGO_PKG_AUTHORS")) ); - tracing_subscriber::fmt().pretty().init(); + tracing_subscriber::fmt() + .with_env_filter(EnvFilter::from_default_env()) + .pretty() + .init(); let args = Args::parse(); @@ -63,37 +75,78 @@ async fn main() -> miette::Result<()> { let schema = to_schema(&definitions); - let result = execute_query( - &schema, - Arc::new(PlaixtAdapter { - records: records.clone(), - }), - "{ - RecordsAll { - at @output - kind @output @filter(op: \"=\", value: [\"$foobar\"]) - } - }", - [( - Arc::from("foobar"), - FieldValue::String(Arc::from("changelog")), - )] - .into(), - ) - .unwrap() - .collect::>(); - match args.mode { - ArgMode::Dump => { + ArgMode::Query => { + let mut query = String::new(); + std::io::stdin() + .read_to_string(&mut query) + .into_diagnostic()?; + + let result = execute_query( + &schema, + Arc::new(PlaixtAdapter { + records: records.clone(), + }), + &query, + BTreeMap::, FieldValue>::from([("search".into(), "trust".into())]), + ) + .unwrap() + .collect::>(); + info!("Got records: {result:#?}"); } + ArgMode::Dump => { + print_records(&records); + } } Ok(()) } -fn to_schema(_definitions: &BTreeMap>) -> Schema { - Schema::parse(format!( +fn print_records(records: &[Record]) { + for record in records { + println!("{kind} @ {at} {{", kind = record.kind, at = record.at); + for field in &record.fields { + println!("\t{name} = {value}", name = field.0, value = field.1); + } + println!("}}") + } +} + +fn to_schema(definitions: &BTreeMap>) -> Schema { + let custom_schemas = definitions + .iter() + .map(|(name, def)| { + let fields = def + .last() + .unwrap() + .fields + .iter() + .map(|(name, def)| format!("{name}: {}!", def.trustfall_kind())) + .collect::>() + .join("\n"); + + let field_type = format!("{name}Fields"); + + format!( + r#" + + type {field_type} {{ + {fields} + }} + + type {name} implements Record {{ + at: String! + kind: String! + fields: {field_type}! + }} + "# + ) + }) + .collect::>() + .join(""); + + let schema = format!( r#"schema {{ query: RootSchemaQuery }} @@ -107,18 +160,56 @@ interface Record {{ at: String!, kind: String!, }} + +{} "#, - Schema::ALL_DIRECTIVE_DEFINITIONS - )) - .unwrap() + Schema::ALL_DIRECTIVE_DEFINITIONS, + custom_schemas + ); + trace!(%schema, "Using schema"); + Schema::parse(schema).unwrap() } struct PlaixtAdapter { records: Vec, } +#[derive(Clone, Debug)] +enum PlaixtVertex { + Record(Record), + Fields { + name: String, + values: BTreeMap, + }, +} + +impl PlaixtVertex { + fn as_fields(&self) -> Option<&BTreeMap> { + if let Self::Fields { values, .. } = self { + Some(values) + } else { + None + } + } + + fn as_record(&self) -> Option<&Record> { + if let Self::Record(v) = self { + Some(v) + } else { + None + } + } + + fn typename(&self) -> String { + match self { + PlaixtVertex::Record { .. } => "Record".to_string(), + PlaixtVertex::Fields { name, .. } => name.clone(), + } + } +} + impl<'a> Adapter<'a> for PlaixtAdapter { - type Vertex = Record; + type Vertex = PlaixtVertex; fn resolve_starting_vertices( &self, @@ -127,7 +218,7 @@ impl<'a> Adapter<'a> for PlaixtAdapter { _resolve_info: &trustfall::provider::ResolveInfo, ) -> trustfall::provider::VertexIterator<'a, Self::Vertex> { match edge_name.as_ref() { - "RecordsAll" => Box::new(self.records.clone().into_iter()), + "RecordsAll" => Box::new(self.records.clone().into_iter().map(PlaixtVertex::Record)), _ => unreachable!(), } } @@ -142,25 +233,41 @@ impl<'a> Adapter<'a> for PlaixtAdapter { match (type_name.as_ref(), property_name.as_ref()) { (_, "__typename") => Box::new(contexts.map(|ctx| { let value = match ctx.active_vertex() { - Some(_record) => "Record".into(), + Some(_record) => _record.typename().into(), None => FieldValue::Null, }; (ctx, value) })), - ("Record", "at") => { - resolve_property_with(contexts, field_property!(at, { at.to_string().into() })) + (_, "at") => resolve_property_with( + contexts, + field_property!(as_record, at, { at.to_string().into() }), + ), + (_, "kind") => resolve_property_with(contexts, field_property!(as_record, kind)), + (name, field) => { + debug!(?name, ?field, "Asking for properties"); + + let field = field.to_string(); + resolve_property_with(contexts, move |vertex| { + trace!(?vertex, ?field, "Getting property"); + let fields = vertex.as_fields().unwrap(); + match fields.get(&field).unwrap().clone() { + KdlValue::Bool(b) => FieldValue::Boolean(b), + KdlValue::Float(f) => FieldValue::Float64(f), + KdlValue::Null => FieldValue::Null, + KdlValue::Integer(i) => FieldValue::Int64(i.try_into().unwrap()), + KdlValue::String(s) => FieldValue::String(s.into()), + } + }) } - ("Record", "kind") => resolve_property_with(contexts, field_property!(kind)), - _ => unreachable!(), } } fn resolve_neighbors + 'a>( &self, - _contexts: trustfall::provider::ContextIterator<'a, V>, + contexts: trustfall::provider::ContextIterator<'a, V>, _type_name: &Arc, - _edge_name: &Arc, + edge_name: &Arc, _parameters: &trustfall::provider::EdgeParameters, _resolve_info: &trustfall::provider::ResolveEdgeInfo, ) -> trustfall::provider::ContextOutcomeIterator< @@ -168,16 +275,34 @@ impl<'a> Adapter<'a> for PlaixtAdapter { V, trustfall::provider::VertexIterator<'a, Self::Vertex>, > { - unreachable!() + match edge_name.as_ref() { + "fields" => resolve_neighbors_with(contexts, |c| { + Box::new( + c.as_record() + .map(|r| PlaixtVertex::Fields { + name: format!("{}Fields", r.kind), + values: r.fields.clone(), + }) + .into_iter(), + ) + }), + _ => unreachable!(), + } } fn resolve_coercion + 'a>( &self, - _contexts: trustfall::provider::ContextIterator<'a, V>, - _type_name: &Arc, - _coerce_to_type: &Arc, + contexts: trustfall::provider::ContextIterator<'a, V>, + type_name: &Arc, + coerce_to_type: &Arc, _resolve_info: &trustfall::provider::ResolveInfo, ) -> trustfall::provider::ContextOutcomeIterator<'a, V, bool> { - unreachable!() + debug!("Asking to coerce {type_name} into {coerce_to_type}"); + let coerce_to_type = coerce_to_type.clone(); + resolve_coercion_with(contexts, move |node| { + node.as_record() + .map(|r| r.kind == *coerce_to_type) + .unwrap_or(false) + }) } } diff --git a/crates/plaixt/src/parsing.rs b/crates/plaixt/src/parsing.rs index d492686..b591b42 100644 --- a/crates/plaixt/src/parsing.rs +++ b/crates/plaixt/src/parsing.rs @@ -160,6 +160,13 @@ pub enum DefinitionKind { } impl DefinitionKind { + pub(crate) fn trustfall_kind(&self) -> String { + match self { + DefinitionKind::String => String::from("String"), + DefinitionKind::OneOf(_vecs) => String::from("String"), + } + } + pub(crate) fn validate(&self, val: &KdlValue) -> Result<(), String> { match self { DefinitionKind::String => val @@ -286,6 +293,18 @@ pub(crate) fn parse_definition(bytes: &str) -> miette::Result> { } }; + match field.name().value() { + "at" | "kind" => return Err(miette::diagnostic!( + labels = vec![LabeledSpan::new_primary_with_span( + Some(String::from("this name")), + field.name().span() + )], + help = "Both `at` and `kind` are reserved field names.", + "Reserved field name." + ))?, + _ => {} + } + Ok((field.name().to_string(), kind)) }) .collect::>()?; diff --git a/examples/changelog.plrecs b/examples/changelog.plrecs index 1410479..03e02c2 100644 --- a/examples/changelog.plrecs +++ b/examples/changelog.plrecs @@ -1,23 +1,23 @@ changelog "2025-01-29" { title "Added parsing of plaixt definitions" version "0.1.0" - kind "Feature" + type "Feature" } changelog "2025-01-30 09:10:59+01:00" { title "Added parsing of plaixt records" version "0.1.0" - kind "Feature" + type "Feature" } changelog "2025-02-01" { title "Added CLI options" version "0.1.0" - kind "Feature" + type "Feature" } changelog "2025-02-07" { title "Added trustfall as a query frontend" version "0.1.0" - kind "Feature" + type "Feature" } diff --git a/examples/definitions/changelog.pldef b/examples/definitions/changelog.pldef index 195ed5c..5709003 100644 --- a/examples/definitions/changelog.pldef +++ b/examples/definitions/changelog.pldef @@ -4,6 +4,6 @@ define since="2025-01-29 20:27:30+01:00" { fields { title is=string version is=string - kind { oneOf "Bugfix" "Feature" "Chore" } + type { oneOf "Bugfix" "Feature" "Chore" } } } diff --git a/query b/query new file mode 100644 index 0000000..f6bb732 --- /dev/null +++ b/query @@ -0,0 +1,13 @@ +{ + RecordsAll { + ... on changelog { + at @output + kind @output + fields { + title @output @filter(op: "has_substring", value: ["$search"]) + version @output + type @output + } + } + } +}