diff --git a/Cargo.lock b/Cargo.lock index 48f9df5..7835fc0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -82,6 +82,36 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "anyhow" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" + +[[package]] +name = "async-graphql-parser" +version = "7.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8531ee6d292c26df31c18c565ff22371e7bdfffe7f5e62b69537db0b8fd554dc" +dependencies = [ + "async-graphql-value", + "pest", + "serde", + "serde_json", +] + +[[package]] +name = "async-graphql-value" +version = "7.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "741110dda927420a28fbc1c310543d3416f789a6ba96859c2c265843a0a96887" +dependencies = [ + "bytes", + "indexmap", + "serde", + "serde_json", +] + [[package]] name = "autocfg" version = "1.4.0" @@ -144,6 +174,9 @@ name = "bytes" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +dependencies = [ + "serde", +] [[package]] name = "camino" @@ -233,6 +266,12 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + [[package]] name = "equivalent" version = "1.0.1" @@ -407,6 +446,7 @@ checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" dependencies = [ "equivalent", "hashbrown", + "serde", ] [[package]] @@ -421,6 +461,15 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.14" @@ -464,7 +513,7 @@ checksum = "412e2cf22cb560469db5b211c594ff9dcd490c6964e284ea64eddffe41c2249c" dependencies = [ "miette", "num", - "thiserror", + "thiserror 1.0.69", "winnow", ] @@ -508,6 +557,12 @@ version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + [[package]] name = "matchers" version = "0.1.0" @@ -540,7 +595,7 @@ dependencies = [ "syntect", "terminal_size", "textwrap", - "thiserror", + "thiserror 1.0.69", "unicode-width", ] @@ -756,6 +811,17 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "pest" +version = "2.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" +dependencies = [ + "memchr", + "thiserror 2.0.11", + "ucd-trie", +] + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -790,6 +856,7 @@ dependencies = [ "tokio-stream", "tracing", "tracing-subscriber", + "trustfall", ] [[package]] @@ -1025,6 +1092,9 @@ name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +dependencies = [ + "serde", +] [[package]] name = "socket2" @@ -1091,7 +1161,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "thiserror", + "thiserror 1.0.69", "walkdir", "yaml-rust", ] @@ -1122,7 +1192,16 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +dependencies = [ + "thiserror-impl 2.0.11", ] [[package]] @@ -1136,6 +1215,17 @@ dependencies = [ "syn", ] +[[package]] +name = "thiserror-impl" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "thread_local" version = "1.1.8" @@ -1325,6 +1415,51 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "trustfall" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2c4d6f50f158998ff48a905a4f12e918b9dfd1274c0711c0f4485d8f7ff015e" +dependencies = [ + "anyhow", + "trustfall_core", + "trustfall_derive", +] + +[[package]] +name = "trustfall_core" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "363322af9d4d04fb0e298d8e9f97d9ef316a796f6f4e5f241060ad50a07d0334" +dependencies = [ + "async-graphql-parser", + "async-graphql-value", + "itertools", + "maplit", + "regex", + "serde", + "serde_json", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "trustfall_derive" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb14eb4f23b3b669d232a5c7d2b3d6c89ad9e15be1cbdd2c1e14d87d62569ec" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + [[package]] name = "unicode-ident" version = "1.0.16" diff --git a/crates/plaixt/Cargo.toml b/crates/plaixt/Cargo.toml index dd9158f..e2aead9 100644 --- a/crates/plaixt/Cargo.toml +++ b/crates/plaixt/Cargo.toml @@ -18,6 +18,7 @@ tokio = { version = "1.43.0", features = ["full"] } tokio-stream = { version = "0.1.17", features = ["full"] } tracing = "0.1.41" tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } +trustfall = "0.8.1" [lints] workspace = true diff --git a/crates/plaixt/src/main.rs b/crates/plaixt/src/main.rs index 39c8e87..cb37400 100644 --- a/crates/plaixt/src/main.rs +++ b/crates/plaixt/src/main.rs @@ -1,11 +1,22 @@ #![allow(dead_code)] +use std::collections::BTreeMap; +use std::sync::Arc; + use camino::Utf8PathBuf; use clap::Parser; use clap::Subcommand; use clap::ValueHint; use human_panic::Metadata; +use parsing::Definition; +use parsing::Record; use tracing::info; +use trustfall::execute_query; +use trustfall::provider::field_property; +use trustfall::provider::resolve_property_with; +use trustfall::provider::Adapter; +use trustfall::FieldValue; +use trustfall::Schema; mod config; mod parsing; @@ -46,18 +57,127 @@ async fn main() -> miette::Result<()> { let config = config::parse_config(&args.config).await?; let root_folder = args.root_folder.as_ref().unwrap_or(&config.root_folder); - let load_records = async { - let definitions = parsing::load_definitions(&root_folder.join("definitions")).await?; - parsing::load_records(root_folder, &definitions).await - }; + let definitions = parsing::load_definitions(&root_folder.join("definitions")).await?; + + let records = parsing::load_records(root_folder, &definitions).await?; + + 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 => { - let records = load_records.await?; - - info!("Got records: {records:#?}"); + info!("Got records: {result:#?}"); } } Ok(()) } + +fn to_schema(_definitions: &BTreeMap>) -> Schema { + Schema::parse(format!( + r#"schema {{ + query: RootSchemaQuery +}} +{} + + +type RootSchemaQuery {{ + RecordsAll: [Record!]! +}} +interface Record {{ + at: String!, + kind: String!, +}} +"#, + Schema::ALL_DIRECTIVE_DEFINITIONS + )) + .unwrap() +} + +struct PlaixtAdapter { + records: Vec, +} + +impl<'a> Adapter<'a> for PlaixtAdapter { + type Vertex = Record; + + fn resolve_starting_vertices( + &self, + edge_name: &Arc, + _parameters: &trustfall::provider::EdgeParameters, + _resolve_info: &trustfall::provider::ResolveInfo, + ) -> trustfall::provider::VertexIterator<'a, Self::Vertex> { + match edge_name.as_ref() { + "RecordsAll" => Box::new(self.records.clone().into_iter()), + _ => unreachable!(), + } + } + + fn resolve_property + 'a>( + &self, + contexts: trustfall::provider::ContextIterator<'a, V>, + type_name: &Arc, + property_name: &Arc, + _resolve_info: &trustfall::provider::ResolveInfo, + ) -> trustfall::provider::ContextOutcomeIterator<'a, V, trustfall::FieldValue> { + 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(), + None => FieldValue::Null, + }; + + (ctx, value) + })), + ("Record", "at") => { + resolve_property_with(contexts, field_property!(at, { at.to_string().into() })) + } + ("Record", "kind") => resolve_property_with(contexts, field_property!(kind)), + _ => unreachable!(), + } + } + + fn resolve_neighbors + 'a>( + &self, + _contexts: trustfall::provider::ContextIterator<'a, V>, + _type_name: &Arc, + _edge_name: &Arc, + _parameters: &trustfall::provider::EdgeParameters, + _resolve_info: &trustfall::provider::ResolveEdgeInfo, + ) -> trustfall::provider::ContextOutcomeIterator< + 'a, + V, + trustfall::provider::VertexIterator<'a, Self::Vertex>, + > { + unreachable!() + } + + fn resolve_coercion + 'a>( + &self, + _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!() + } +} diff --git a/crates/plaixt/src/parsing.rs b/crates/plaixt/src/parsing.rs index 04f6d5a..d492686 100644 --- a/crates/plaixt/src/parsing.rs +++ b/crates/plaixt/src/parsing.rs @@ -15,7 +15,7 @@ use miette::NamedSource; use owo_colors::OwoColorize; use tokio_stream::wrappers::ReadDirStream; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Record { pub(crate) kind: String, pub(crate) at: Timestamp, @@ -82,8 +82,7 @@ pub(crate) fn parse_record( .map(|field| { let Some(get) = field.get(0) else { return Err(miette::diagnostic!( - labels = - vec![LabeledSpan::new_primary_with_span(None, at_entry.span())], + labels = vec![LabeledSpan::new_primary_with_span(None, at_entry.span())], "This datetime should be a string formatted as RFC3339." ))?; }; @@ -143,9 +142,8 @@ pub(crate) async fn load_records( }) .flat_map(|val| futures::stream::iter(val.transpose())) .and_then(|(name, bytes)| async move { - parse_record(&bytes, definitions).map_err(|e| { - e.with_source_code(NamedSource::new(name, bytes).with_language("kdl")) - }) + parse_record(&bytes, definitions) + .map_err(|e| e.with_source_code(NamedSource::new(name, bytes).with_language("kdl"))) }) .map(|val| val.map(|recs| futures::stream::iter(recs).map(Ok::<_, miette::Report>))) .try_flatten() @@ -344,4 +342,3 @@ pub(crate) async fn load_definitions( Ok(defs) } - diff --git a/examples/changelog.plrecs b/examples/changelog.plrecs index 5380563..1410479 100644 --- a/examples/changelog.plrecs +++ b/examples/changelog.plrecs @@ -15,3 +15,9 @@ changelog "2025-02-01" { version "0.1.0" kind "Feature" } + +changelog "2025-02-07" { + title "Added trustfall as a query frontend" + version "0.1.0" + kind "Feature" +}