From 082e16232019bd0a2c6e4dbe2e4499eb47b148ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Sat, 1 Feb 2025 11:46:05 +0100 Subject: [PATCH 01/10] Add changelog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcel Müller --- examples/changelog.plrecs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/examples/changelog.plrecs b/examples/changelog.plrecs index c2514ec..5380563 100644 --- a/examples/changelog.plrecs +++ b/examples/changelog.plrecs @@ -9,3 +9,9 @@ changelog "2025-01-30 09:10:59+01:00" { version "0.1.0" kind "Feature" } + +changelog "2025-02-01" { + title "Added CLI options" + version "0.1.0" + kind "Feature" +} From 68fbc3ce25312eca289db1e34e6e4c1cc12cee8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Sat, 1 Feb 2025 11:47:40 +0100 Subject: [PATCH 02/10] Move config to own module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcel Müller --- Cargo.lock | 2 +- Cargo.toml | 2 +- crates/plaixt/src/config.rs | 39 +++++++++++++++++++++++++++++++++++ crates/plaixt/src/main.rs | 41 ++----------------------------------- 4 files changed, 43 insertions(+), 41 deletions(-) create mode 100644 crates/plaixt/src/config.rs diff --git a/Cargo.lock b/Cargo.lock index 94e3c71..48f9df5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -776,7 +776,7 @@ checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "plaixt" -version = "0.0.0" +version = "0.1.0" dependencies = [ "camino", "clap", diff --git a/Cargo.toml b/Cargo.toml index 6c2d52a..5f55f38 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ resolver = "2" unsafe_code = "forbid" [workspace.package] -version = "0.0.0" +version = "0.1.0" edition = "2021" description = "PLAIn teXT tools for your data" license = "EUPL-1.2" diff --git a/crates/plaixt/src/config.rs b/crates/plaixt/src/config.rs new file mode 100644 index 0000000..0c09e69 --- /dev/null +++ b/crates/plaixt/src/config.rs @@ -0,0 +1,39 @@ +use camino::Utf8Path; +use camino::Utf8PathBuf; +use kdl::KdlDocument; +use miette::Context; +use miette::LabeledSpan; + +#[derive(Debug)] +pub struct Config { + pub(crate) root_folder: Utf8PathBuf, +} + +pub(crate) async fn parse_config(path: &Utf8Path) -> miette::Result { + let data = tokio::fs::read_to_string(path) + .await + .map_err(|e| miette::miette!(e)) + .wrap_err_with(|| miette::miette!("Could not read configuration at \"{path}\""))?; + + let doc: KdlDocument = data + .parse() + .map_err(|e| miette::Error::from(e).with_source_code(data.clone()))?; + + Ok(Config { + root_folder: doc + .get("root_folder") + .ok_or_else(|| miette::miette!("\"root_folder\" configuration value not found")) + .and_then(|val| { + val.get(0) + .and_then(|v| v.as_string().map(Into::into)) + .ok_or_else(|| { + miette::diagnostic!( + labels = vec![LabeledSpan::new_primary_with_span(None, val.span())], + "root_folder is expected to be a path" + ) + .into() + }) + .map_err(|e: miette::Report| e.with_source_code(data)) + })?, + }) +} diff --git a/crates/plaixt/src/main.rs b/crates/plaixt/src/main.rs index 9b28ba4..39c8e87 100644 --- a/crates/plaixt/src/main.rs +++ b/crates/plaixt/src/main.rs @@ -1,16 +1,13 @@ #![allow(dead_code)] -use camino::Utf8Path; use camino::Utf8PathBuf; use clap::Parser; use clap::Subcommand; use clap::ValueHint; use human_panic::Metadata; -use kdl::KdlDocument; -use miette::LabeledSpan; -use miette::WrapErr; use tracing::info; +mod config; mod parsing; #[derive(Debug, Parser)] @@ -35,11 +32,6 @@ enum ArgMode { Dump, } -#[derive(Debug)] -pub struct Config { - root_folder: Utf8PathBuf, -} - #[tokio::main] async fn main() -> miette::Result<()> { human_panic::setup_panic!( @@ -51,7 +43,7 @@ async fn main() -> miette::Result<()> { let args = Args::parse(); - let config = parse_config(&args.config).await?; + 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 { @@ -69,32 +61,3 @@ async fn main() -> miette::Result<()> { Ok(()) } - -async fn parse_config(path: &Utf8Path) -> miette::Result { - let data = tokio::fs::read_to_string(path) - .await - .map_err(|e| miette::miette!(e)) - .wrap_err_with(|| miette::miette!("Could not read configuration at \"{path}\""))?; - - let doc: KdlDocument = data - .parse() - .map_err(|e| miette::Error::from(e).with_source_code(data.clone()))?; - - Ok(Config { - root_folder: doc - .get("root_folder") - .ok_or_else(|| miette::miette!("\"root_folder\" configuration value not found")) - .and_then(|val| { - val.get(0) - .and_then(|v| v.as_string().map(Into::into)) - .ok_or_else(|| { - miette::diagnostic!( - labels = vec![LabeledSpan::new_primary_with_span(None, val.span())], - "root_folder is expected to be a path" - ) - .into() - }) - .map_err(|e: miette::Report| e.with_source_code(data)) - })?, - }) -} From 6501b42328f46921d7e9af6db446f253d7575a91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Fri, 7 Feb 2025 21:57:28 +0100 Subject: [PATCH 03/10] Start using trustfall to query the repo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcel Müller --- Cargo.lock | 143 ++++++++++++++++++++++++++++++++++- crates/plaixt/Cargo.toml | 1 + crates/plaixt/src/main.rs | 134 ++++++++++++++++++++++++++++++-- crates/plaixt/src/parsing.rs | 11 +-- examples/changelog.plrecs | 6 ++ 5 files changed, 277 insertions(+), 18 deletions(-) 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" +} 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 04/10] 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 + } + } + } +} From 3112e78bb4b28f03d548d84be8043976df28bf91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Sat, 8 Feb 2025 10:08:58 +0100 Subject: [PATCH 05/10] Move trustfall related parts to its own module 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 | 209 +------------------------ crates/plaixt/src/trustfall_plaixt.rs | 210 ++++++++++++++++++++++++++ 2 files changed, 213 insertions(+), 206 deletions(-) create mode 100644 crates/plaixt/src/trustfall_plaixt.rs diff --git a/crates/plaixt/src/main.rs b/crates/plaixt/src/main.rs index 8e5e5bb..45e85d3 100644 --- a/crates/plaixt/src/main.rs +++ b/crates/plaixt/src/main.rs @@ -9,25 +9,16 @@ 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; -use trustfall::Schema; mod config; mod parsing; +mod trustfall_plaixt; #[derive(Debug, Parser)] struct Args { @@ -73,7 +64,7 @@ async fn main() -> miette::Result<()> { let records = parsing::load_records(root_folder, &definitions).await?; - let schema = to_schema(&definitions); + let schema = trustfall_plaixt::to_schema(&definitions); match args.mode { ArgMode::Query => { @@ -84,7 +75,7 @@ async fn main() -> miette::Result<()> { let result = execute_query( &schema, - Arc::new(PlaixtAdapter { + Arc::new(trustfall_plaixt::PlaixtAdapter { records: records.clone(), }), &query, @@ -112,197 +103,3 @@ fn print_records(records: &[Record]) { 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 -}} -{} - - -type RootSchemaQuery {{ - RecordsAll: [Record!]! -}} -interface Record {{ - at: String!, - kind: String!, -}} - -{} -"#, - 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 = PlaixtVertex; - - 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().map(PlaixtVertex::Record)), - _ => 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.typename().into(), - None => FieldValue::Null, - }; - - (ctx, value) - })), - (_, "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()), - } - }) - } - } - } - - 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>, - > { - 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, - _resolve_info: &trustfall::provider::ResolveInfo, - ) -> trustfall::provider::ContextOutcomeIterator<'a, V, bool> { - 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/trustfall_plaixt.rs b/crates/plaixt/src/trustfall_plaixt.rs new file mode 100644 index 0000000..c3c43b7 --- /dev/null +++ b/crates/plaixt/src/trustfall_plaixt.rs @@ -0,0 +1,210 @@ +use std::collections::BTreeMap; +use std::sync::Arc; + +use kdl::KdlValue; +use tracing::debug; +use tracing::trace; +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; +use trustfall::Schema; + +use crate::parsing::Definition; +use crate::parsing::Record; + +pub(crate) 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 +}} +{} + + +type RootSchemaQuery {{ + RecordsAll: [Record!]! +}} +interface Record {{ + at: String!, + kind: String!, +}} + +{} +"#, + Schema::ALL_DIRECTIVE_DEFINITIONS, + custom_schemas + ); + trace!(%schema, "Using schema"); + Schema::parse(schema).unwrap() +} + +pub(crate) struct PlaixtAdapter { + pub(crate) records: Vec, +} + +#[derive(Clone, Debug)] +pub(crate) enum PlaixtVertex { + Record(Record), + Fields { + name: String, + values: BTreeMap, + }, +} + +impl PlaixtVertex { + pub(crate) fn as_fields(&self) -> Option<&BTreeMap> { + if let Self::Fields { values, .. } = self { + Some(values) + } else { + None + } + } + + pub(crate) fn as_record(&self) -> Option<&Record> { + if let Self::Record(v) = self { + Some(v) + } else { + None + } + } + + pub(crate) fn typename(&self) -> String { + match self { + PlaixtVertex::Record { .. } => "Record".to_string(), + PlaixtVertex::Fields { name, .. } => name.clone(), + } + } +} + +impl<'a> Adapter<'a> for PlaixtAdapter { + type Vertex = PlaixtVertex; + + 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().map(PlaixtVertex::Record)), + _ => 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.typename().into(), + None => FieldValue::Null, + }; + + (ctx, value) + })), + (_, "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()), + } + }) + } + } + } + + 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>, + > { + 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, + _resolve_info: &trustfall::provider::ResolveInfo, + ) -> trustfall::provider::ContextOutcomeIterator<'a, V, bool> { + 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) + }) + } +} From 73391b5b7bf3c68d645e4310dfe423cb48cd1b32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Sat, 8 Feb 2025 11:58:14 +0100 Subject: [PATCH 06/10] Add a first attempt at multiplexing adapters 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 | 6 +- crates/plaixt/src/parsing.rs | 17 +- crates/plaixt/src/trustfall_plaixt.rs | 374 +++++++++++++++++++++++--- query | 4 +- 4 files changed, 349 insertions(+), 52 deletions(-) diff --git a/crates/plaixt/src/main.rs b/crates/plaixt/src/main.rs index 45e85d3..9611dee 100644 --- a/crates/plaixt/src/main.rs +++ b/crates/plaixt/src/main.rs @@ -75,8 +75,10 @@ async fn main() -> miette::Result<()> { let result = execute_query( &schema, - Arc::new(trustfall_plaixt::PlaixtAdapter { - records: records.clone(), + Arc::new(trustfall_plaixt::TrustfallMultiAdapter { + plaixt: trustfall_plaixt::PlaixtAdapter { + records: records.clone(), + }, }), &query, BTreeMap::, FieldValue>::from([("search".into(), "trust".into())]), diff --git a/crates/plaixt/src/parsing.rs b/crates/plaixt/src/parsing.rs index b591b42..df05113 100644 --- a/crates/plaixt/src/parsing.rs +++ b/crates/plaixt/src/parsing.rs @@ -194,11 +194,15 @@ impl TryFrom<&str> for DefinitionKind { #[derive(Debug)] pub struct Definition { + pub(crate) name: String, pub(crate) since: Timestamp, pub(crate) fields: HashMap, } -pub(crate) fn parse_definition(bytes: &str) -> miette::Result> { +pub(crate) fn parse_definition( + bytes: &str, + definition_name: String, +) -> miette::Result> { let doc: KdlDocument = bytes.parse()?; let mut defs = vec![]; @@ -309,7 +313,11 @@ pub(crate) fn parse_definition(bytes: &str) -> miette::Result> { }) .collect::>()?; - defs.push(Definition { since, fields }); + defs.push(Definition { + since, + fields, + name: definition_name.clone(), + }); } unknown => { return Err(miette::diagnostic!( @@ -349,9 +357,10 @@ pub(crate) async fn load_definitions( }) .flat_map(|val| futures::stream::iter(val.transpose())) .and_then(|(name, bytes)| async move { + let definition_name = name.file_stem().unwrap().to_string(); Ok(( - name.file_stem().unwrap().to_string(), - parse_definition(&bytes).map_err(|e| { + definition_name.clone(), + parse_definition(&bytes, definition_name).map_err(|e| { e.with_source_code(NamedSource::new(name, bytes).with_language("kdl")) })?, )) diff --git a/crates/plaixt/src/trustfall_plaixt.rs b/crates/plaixt/src/trustfall_plaixt.rs index c3c43b7..4c27f8d 100644 --- a/crates/plaixt/src/trustfall_plaixt.rs +++ b/crates/plaixt/src/trustfall_plaixt.rs @@ -1,4 +1,7 @@ use std::collections::BTreeMap; +use std::collections::HashMap; +use std::fmt::Write; +use std::ops::Not; use std::sync::Arc; use kdl::KdlValue; @@ -9,69 +12,351 @@ use trustfall::provider::resolve_coercion_with; use trustfall::provider::resolve_neighbors_with; use trustfall::provider::resolve_property_with; use trustfall::provider::Adapter; +use trustfall::provider::AsVertex; use trustfall::FieldValue; use trustfall::Schema; use crate::parsing::Definition; use crate::parsing::Record; +const ADAPTER_SEP: &str = "__"; + +#[derive(Debug, Default)] +pub struct StartingVertex { + adapter_name: String, + start_vertex_name: String, + vertex_type: String, +} + +impl StartingVertex { + pub fn new(adapter_name: String, start_vertex_name: String, start_vertex_type: String) -> Self { + Self { + adapter_name, + start_vertex_name, + vertex_type: start_vertex_type, + } + } + + pub fn schema_name(&self) -> String { + format!( + "{}{ADAPTER_SEP}{}", + self.adapter_name, self.start_vertex_name + ) + } + + pub fn vertex_type(&self) -> &str { + &self.vertex_type + } +} + +#[derive(Debug, Default)] +pub struct VertexType { + adapter_name: String, + vertex_name: String, + vertex_fields: HashMap, + implements: Vec, +} + +impl VertexType { + pub fn new( + adapter_name: String, + vertex_name: String, + vertex_fields: HashMap, + implements: Vec, + ) -> Self { + Self { + adapter_name, + vertex_name, + vertex_fields, + implements, + } + } + + pub fn schema_name(&self) -> String { + format!("{}{ADAPTER_SEP}{}", self.adapter_name, self.vertex_name) + } + + pub fn schema_type(&self) -> String { + format!( + r#"type {name} {impls} {{ {fields} }}"#, + name = self.schema_name(), + impls = self + .implements + .is_empty() + .not() + .then(|| format!("implements {}", self.implements.join(" & "))) + .unwrap_or_else(String::new), + fields = self.vertex_fields.iter().fold(String::new(), |mut out, f| { + write!(out, "{}: {}, ", f.0, f.1).unwrap(); + out + }), + ) + } +} + +#[derive(Debug, Default)] +pub struct DynamicSchema { + roots: Vec, + types: Vec, +} + +impl DynamicSchema { + pub fn new() -> Self { + Self::default() + } + + pub fn add_root(&mut self, root: StartingVertex) { + self.roots.push(root); + } + + pub fn add_type(&mut self, kind: VertexType) { + self.types.push(kind); + } +} + pub(crate) fn to_schema(definitions: &BTreeMap>) -> Schema { - let custom_schemas = definitions - .iter() - .map(|(name, def)| { - let fields = def - .last() - .unwrap() + let mut schema = DynamicSchema::new(); + + schema.add_root(StartingVertex::new( + "Plaixt".to_string(), + "RecordsAll".to_string(), + "[Record!]!".to_string(), + )); + + for definition in definitions.values().flat_map(|d| d.first()) { + let fields = VertexType::new( + "Plaixt".to_string(), + format!("{}Fields", definition.name), + definition .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(""); + .map(|(name, val)| (name.clone(), format!("{}!", val.trustfall_kind()))) + .collect(), + vec![], + ); + schema.add_type(VertexType::new( + "Plaixt".to_string(), + definition.name.clone(), + [ + (String::from("at"), String::from("String!")), + (String::from("kind"), String::from("String!")), + (String::from("fields"), format!("{}!", fields.schema_name())), + ] + .into(), + vec![String::from("Record")], + )); + schema.add_type(fields); + } let schema = format!( r#"schema {{ - query: RootSchemaQuery -}} -{} + query: RootSchemaQuery + }} + {} + type RootSchemaQuery {{ + {roots} + }} + interface Record {{ + at: String!, + kind: String!, + }} - -type RootSchemaQuery {{ - RecordsAll: [Record!]! -}} -interface Record {{ - at: String!, - kind: String!, -}} - -{} -"#, + {types} + "#, Schema::ALL_DIRECTIVE_DEFINITIONS, - custom_schemas + roots = schema.roots.iter().fold(String::new(), |mut out, r| { + write!(out, "{}: {}, ", r.schema_name(), r.vertex_type()).unwrap(); + out + }), + types = schema.types.iter().fold(String::new(), |mut out, t| { + writeln!(out, "{}", t.schema_type()).unwrap(); + out + }), ); trace!(%schema, "Using schema"); Schema::parse(schema).unwrap() } +pub struct TrustfallMultiAdapter { + pub plaixt: PlaixtAdapter, +} + +#[derive(Debug, Clone)] +pub enum TrustfallMultiVertex { + Plaixt(PlaixtVertex), +} + +impl AsVertex for TrustfallMultiVertex { + fn as_vertex(&self) -> Option<&PlaixtVertex> { + self.as_plaixt() + } + + fn into_vertex(self) -> Option { + self.as_plaixt().cloned() + } +} + +impl TrustfallMultiVertex { + pub fn as_plaixt(&self) -> Option<&PlaixtVertex> { + if let Self::Plaixt(v) = self { + Some(v) + } else { + None + } + } +} + +impl<'v> Adapter<'v> for TrustfallMultiAdapter { + type Vertex = TrustfallMultiVertex; + + fn resolve_starting_vertices( + &self, + edge_name: &Arc, + parameters: &trustfall::provider::EdgeParameters, + resolve_info: &trustfall::provider::ResolveInfo, + ) -> trustfall::provider::VertexIterator<'v, Self::Vertex> { + let (adapter_name, edge_name) = edge_name.split_once(ADAPTER_SEP).unwrap(); + + trace!(?adapter_name, ?edge_name, "Got start vertex"); + + match adapter_name { + "Plaixt" => { + let iter = self.plaixt.resolve_starting_vertices( + &Arc::from(edge_name), + parameters, + resolve_info, + ); + + Box::new(iter.map(TrustfallMultiVertex::Plaixt)) + } + _ => unreachable!(), + } + } + + fn resolve_property( + &self, + contexts: trustfall::provider::ContextIterator<'v, V>, + type_name: &Arc, + property_name: &Arc, + resolve_info: &trustfall::provider::ResolveInfo, + ) -> trustfall::provider::ContextOutcomeIterator<'v, V, FieldValue> + where + V: trustfall::provider::AsVertex + 'v, + { + let (adapter_name, type_name) = type_name.split_once(ADAPTER_SEP).unwrap(); + + match adapter_name { + "Plaixt" => { + let contexts = contexts.collect::>(); + + let properties = self.plaixt.resolve_property( + Box::new( + contexts + .clone() + .into_iter() + .map(|v| v.map(&mut |v: V| v.into_vertex().unwrap())), + ), + &Arc::from(type_name), + property_name, + resolve_info, + ); + + Box::new( + properties + .into_iter() + .zip(contexts) + .map(|((_ctx, name), og_ctx)| (og_ctx, name)), + ) + } + _ => unreachable!(), + } + } + + fn resolve_neighbors + 'v>( + &self, + contexts: trustfall::provider::ContextIterator<'v, V>, + type_name: &Arc, + edge_name: &Arc, + parameters: &trustfall::provider::EdgeParameters, + resolve_info: &trustfall::provider::ResolveEdgeInfo, + ) -> trustfall::provider::ContextOutcomeIterator< + 'v, + V, + trustfall::provider::VertexIterator<'v, Self::Vertex>, + > { + let (adapter_name, type_name) = type_name.split_once(ADAPTER_SEP).unwrap(); + + match adapter_name { + "Plaixt" => { + let contexts = contexts.collect::>(); + + let properties = self.plaixt.resolve_neighbors( + Box::new( + contexts + .clone() + .into_iter() + .map(|v| v.map(&mut |v: V| v.into_vertex().unwrap())), + ), + &Arc::from(type_name), + edge_name, + parameters, + resolve_info, + ); + + Box::new( + properties + .into_iter() + .zip(contexts) + .map(|((_ctx, vals), og_ctx)| { + ( + og_ctx, + Box::new(vals.map(TrustfallMultiVertex::Plaixt)) as Box<_>, + ) + }), + ) + } + _ => unreachable!(), + } + } + + fn resolve_coercion + 'v>( + &self, + contexts: trustfall::provider::ContextIterator<'v, V>, + type_name: &Arc, + coerce_to_type: &Arc, + resolve_info: &trustfall::provider::ResolveInfo, + ) -> trustfall::provider::ContextOutcomeIterator<'v, V, bool> { + trace!(?type_name, ?coerce_to_type, "Trying to coerce"); + let (adapter_name, coerce_to_type) = coerce_to_type.split_once(ADAPTER_SEP).unwrap(); + + match adapter_name { + "Plaixt" => { + let contexts = contexts.collect::>(); + + let properties = self.plaixt.resolve_coercion( + Box::new( + contexts + .clone() + .into_iter() + .map(|v| v.map(&mut |v: V| v.into_vertex().unwrap())), + ), + type_name, + &Arc::from(coerce_to_type), + resolve_info, + ); + + Box::new( + properties + .into_iter() + .zip(contexts) + .map(|((_ctx, val), og_ctx)| (og_ctx, val)), + ) + } + _ => unreachable!(), + } + } +} + pub(crate) struct PlaixtAdapter { pub(crate) records: Vec, } @@ -119,6 +404,7 @@ impl<'a> Adapter<'a> for PlaixtAdapter { _parameters: &trustfall::provider::EdgeParameters, _resolve_info: &trustfall::provider::ResolveInfo, ) -> trustfall::provider::VertexIterator<'a, Self::Vertex> { + trace!(?edge_name, "Resolving start vertex"); match edge_name.as_ref() { "RecordsAll" => Box::new(self.records.clone().into_iter().map(PlaixtVertex::Record)), _ => unreachable!(), diff --git a/query b/query index f6bb732..80fc919 100644 --- a/query +++ b/query @@ -1,6 +1,6 @@ { - RecordsAll { - ... on changelog { + Plaixt__RecordsAll { + ... on Plaixt__changelog { at @output kind @output fields { From 6b1e799d6b02d36c4f73ece7f44f2fc4f4163e30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Sat, 8 Feb 2025 12:01:30 +0100 Subject: [PATCH 07/10] Remove a source of unwrap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcel Müller --- crates/plaixt/src/trustfall_plaixt.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/plaixt/src/trustfall_plaixt.rs b/crates/plaixt/src/trustfall_plaixt.rs index 4c27f8d..422962b 100644 --- a/crates/plaixt/src/trustfall_plaixt.rs +++ b/crates/plaixt/src/trustfall_plaixt.rs @@ -254,7 +254,7 @@ impl<'v> Adapter<'v> for TrustfallMultiAdapter { contexts .clone() .into_iter() - .map(|v| v.map(&mut |v: V| v.into_vertex().unwrap())), + .map(|v| v.flat_map(&mut |v: V| v.into_vertex())), ), &Arc::from(type_name), property_name, @@ -295,7 +295,7 @@ impl<'v> Adapter<'v> for TrustfallMultiAdapter { contexts .clone() .into_iter() - .map(|v| v.map(&mut |v: V| v.into_vertex().unwrap())), + .map(|v| v.flat_map(&mut |v: V| v.into_vertex())), ), &Arc::from(type_name), edge_name, @@ -338,7 +338,7 @@ impl<'v> Adapter<'v> for TrustfallMultiAdapter { contexts .clone() .into_iter() - .map(|v| v.map(&mut |v: V| v.into_vertex().unwrap())), + .map(|v| v.flat_map(&mut |v: V| v.into_vertex())), ), type_name, &Arc::from(coerce_to_type), From e600807376744c8099ee4139a8cdc9a80a403448 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Sat, 8 Feb 2025 15:45:21 +0100 Subject: [PATCH 08/10] Fix some things MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcel Müller --- Cargo.lock | 236 +++++++++++++++++++------- crates/plaixt/Cargo.toml | 1 + crates/plaixt/src/main.rs | 60 ++++++- crates/plaixt/src/parsing.rs | 9 + crates/plaixt/src/trustfall_plaixt.rs | 155 ++++++++++++++++- examples/changelog.plrecs | 4 + examples/definitions/file_test.pldef | 7 + flake.lock | 6 +- query | 22 ++- rust-toolchain.toml | 2 +- 10 files changed, 421 insertions(+), 81 deletions(-) create mode 100644 examples/definitions/file_test.pldef diff --git a/Cargo.lock b/Cargo.lock index 7835fc0..c1ac24f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,19 +4,13 @@ version = 4 [[package]] name = "addr2line" -version = "0.21.0" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - [[package]] name = "adler2" version = "2.0.0" @@ -112,6 +106,17 @@ dependencies = [ "serde_json", ] +[[package]] +name = "async-trait" +version = "0.1.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "autocfg" version = "1.4.0" @@ -120,17 +125,17 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backtrace" -version = "0.3.71" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", - "cc", "cfg-if", "libc", - "miniz_oxide 0.7.4", + "miniz_oxide", "object", "rustc-demangle", + "windows-targets", ] [[package]] @@ -170,10 +175,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" [[package]] -name = "bytes" -version = "1.9.0" +name = "block-buffer" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bytes" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" dependencies = [ "serde", ] @@ -189,9 +203,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.10" +version = "1.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229" +checksum = "c7777341816418c02e033934a09f20dc0ccaf65a5201ef8a450ae0105a573fda" dependencies = [ "shlex", ] @@ -204,9 +218,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.5.27" +version = "4.5.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "769b0145982b4b48713e01ec42d61614425f27b7058bda7180a3a41f30104796" +checksum = "3e77c3243bd94243c03672cb5154667347c457ca271254724f9f393aee1c05ff" dependencies = [ "clap_builder", "clap_derive", @@ -226,9 +240,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.24" +version = "4.5.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c" +checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed" dependencies = [ "heck", "proc-macro2", @@ -248,6 +262,15 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" version = "1.4.2" @@ -257,6 +280,16 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "deranged" version = "0.3.11" @@ -266,6 +299,16 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + [[package]] name = "either" version = "1.13.0" @@ -288,6 +331,16 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "filesystem-trustfall-adapter" +version = "0.1.1" +dependencies = [ + "serde", + "serde_json", + "sha256", + "trustfall", +] + [[package]] name = "flate2" version = "1.0.35" @@ -295,7 +348,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" dependencies = [ "crc32fast", - "miniz_oxide 0.8.3", + "miniz_oxide", ] [[package]] @@ -394,21 +447,32 @@ dependencies = [ ] [[package]] -name = "getrandom" -version = "0.2.15" +name = "generic-array" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.13.3+wasi-0.2.2", + "windows-targets", ] [[package]] name = "gimli" -version = "0.28.1" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "hashbrown" @@ -422,6 +486,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "human-panic" version = "2.0.2" @@ -478,9 +548,9 @@ checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "jiff" -version = "0.1.28" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c607c728e28764fecde611a2764a3a5db19ae21dcec46f292244f5cc5c085a81" +checksum = "c04ef77ae73f3cf50510712722f0c4e8b46f5aaa1bf5ffad2ae213e6495e78e5" dependencies = [ "jiff-tzdb-platform", "log", @@ -580,9 +650,9 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "miette" -version = "7.4.0" +version = "7.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317f146e2eb7021892722af37cf1b971f0a70c8406f487e24952667616192c64" +checksum = "1a955165f87b37fd1862df2a59547ac542c77ef6d17c666f619d1ad22dd89484" dependencies = [ "backtrace", "backtrace-ext", @@ -601,24 +671,15 @@ dependencies = [ [[package]] name = "miette-derive" -version = "7.4.0" +version = "7.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23c9b935fbe1d6cbd1dac857b54a688145e2d93f48db36010514d0f612d0ad67" +checksum = "bf45bf44ab49be92fd1227a3be6fc6f617f1a337c06af54981048574d8783147" dependencies = [ "proc-macro2", "quote", "syn", ] -[[package]] -name = "miniz_oxide" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" -dependencies = [ - "adler", -] - [[package]] name = "miniz_oxide" version = "0.8.3" @@ -635,7 +696,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.52.0", ] @@ -730,18 +791,18 @@ dependencies = [ [[package]] name = "object" -version = "0.32.2" +version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.20.2" +version = "1.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" [[package]] name = "onig" @@ -846,6 +907,7 @@ version = "0.1.0" dependencies = [ "camino", "clap", + "filesystem-trustfall-adapter", "futures", "human-panic", "jiff", @@ -994,9 +1056,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" [[package]] name = "same-file" @@ -1035,9 +1097,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.137" +version = "1.0.138" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "930cfb6e6abf99298aaad7d29abbef7a9999a9a8806a40088f55f0dcec03146b" +checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" dependencies = [ "itoa", "memchr", @@ -1054,6 +1116,30 @@ dependencies = [ "serde", ] +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha256" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18278f6a914fa3070aa316493f7d2ddfb9ac86ebc06fa3b83bffda487e9065b0" +dependencies = [ + "async-trait", + "bytes", + "hex", + "sha2", + "tokio", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -1135,9 +1221,9 @@ checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2" [[package]] name = "syn" -version = "2.0.96" +version = "2.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" +checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" dependencies = [ "proc-macro2", "quote", @@ -1323,9 +1409,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" dependencies = [ "serde", "serde_spanned", @@ -1454,6 +1540,12 @@ dependencies = [ "syn", ] +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + [[package]] name = "ucd-trie" version = "0.1.7" @@ -1486,9 +1578,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.12.1" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3758f5e68192bb96cc8f9b7e2c2cfdabb435499a28499a42f8f984092adad4b" +checksum = "ced87ca4be083373936a67f8de945faa23b6b42384bd5b64434850802c6dccd0" dependencies = [ "getrandom", ] @@ -1499,6 +1591,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "walkdir" version = "2.5.0" @@ -1515,6 +1613,15 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi" +version = "0.13.3+wasi-0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "winapi" version = "0.3.9" @@ -1630,13 +1737,22 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.25" +version = "0.6.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad699df48212c6cc6eb4435f35500ac6fd3b9913324f938aea302022ce19d310" +checksum = "1e90edd2ac1aa278a5c4599b1d89cf03074b610800f866d4026dc199d7929a28" dependencies = [ "memchr", ] +[[package]] +name = "wit-bindgen-rt" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +dependencies = [ + "bitflags 2.8.0", +] + [[package]] name = "yaml-rust" version = "0.4.5" diff --git a/crates/plaixt/Cargo.toml b/crates/plaixt/Cargo.toml index e2aead9..610df66 100644 --- a/crates/plaixt/Cargo.toml +++ b/crates/plaixt/Cargo.toml @@ -8,6 +8,7 @@ license.workspace = true [dependencies] camino = { version = "1.1.9", features = ["serde", "serde1"] } clap = { version = "4.5.27", features = ["derive"] } +filesystem-trustfall-adapter = { version = "0.1.1", path = "../../../trustfall-adapters-zimbopro/filesystem-trustfall-adapter" } futures = "0.3.31" human-panic = "2.0.2" jiff = "0.1.28" diff --git a/crates/plaixt/src/main.rs b/crates/plaixt/src/main.rs index 9611dee..c64aa27 100644 --- a/crates/plaixt/src/main.rs +++ b/crates/plaixt/src/main.rs @@ -8,8 +8,10 @@ use camino::Utf8PathBuf; use clap::Parser; use clap::Subcommand; use clap::ValueHint; +use filesystem_trustfall_adapter::FileSystemAdapter; use human_panic::Metadata; use miette::IntoDiagnostic; +use parsing::Definition; use parsing::Record; use tracing::info; use tracing_subscriber::EnvFilter; @@ -64,7 +66,7 @@ async fn main() -> miette::Result<()> { let records = parsing::load_records(root_folder, &definitions).await?; - let schema = trustfall_plaixt::to_schema(&definitions); + let (schema, adapter) = get_schema_and_adapter(&definitions, records.clone()); match args.mode { ArgMode::Query => { @@ -75,13 +77,9 @@ async fn main() -> miette::Result<()> { let result = execute_query( &schema, - Arc::new(trustfall_plaixt::TrustfallMultiAdapter { - plaixt: trustfall_plaixt::PlaixtAdapter { - records: records.clone(), - }, - }), + Arc::new(adapter), &query, - BTreeMap::, FieldValue>::from([("search".into(), "trust".into())]), + BTreeMap::, FieldValue>::from([]), ) .unwrap() .collect::>(); @@ -96,6 +94,20 @@ async fn main() -> miette::Result<()> { Ok(()) } +fn get_schema_and_adapter( + definitions: &BTreeMap>, + records: Vec, +) -> (trustfall::Schema, trustfall_plaixt::TrustfallMultiAdapter) { + let schema = trustfall_plaixt::to_schema(definitions); + let adapter = trustfall_plaixt::TrustfallMultiAdapter { + plaixt: trustfall_plaixt::PlaixtAdapter { + records: records.clone(), + }, + filesystem: FileSystemAdapter::new(), + }; + (schema, adapter) +} + fn print_records(records: &[Record]) { for record in records { println!("{kind} @ {at} {{", kind = record.kind, at = record.at); @@ -105,3 +117,37 @@ fn print_records(records: &[Record]) { println!("}}") } } + +#[cfg(test)] +mod tests { + use camino::Utf8PathBuf; + use tracing_subscriber::EnvFilter; + use trustfall::provider::check_adapter_invariants; + + use crate::get_schema_and_adapter; + use crate::parsing; + + #[tokio::test] + async fn ensure_adapter_satisfies_invariants() { + tracing_subscriber::fmt() + .with_env_filter(EnvFilter::from_default_env()) + .pretty() + .with_test_writer() + .init(); + + let root_folder = Utf8PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../examples"); + + println!("{root_folder}"); + let definitions = parsing::load_definitions(&root_folder.join("definitions")) + .await + .unwrap(); + + let records = parsing::load_records(&root_folder, &definitions) + .await + .unwrap(); + + let (schema, adapter) = get_schema_and_adapter(&definitions, records.clone()); + + check_adapter_invariants(&schema, adapter); + } +} diff --git a/crates/plaixt/src/parsing.rs b/crates/plaixt/src/parsing.rs index df05113..243dd61 100644 --- a/crates/plaixt/src/parsing.rs +++ b/crates/plaixt/src/parsing.rs @@ -15,6 +15,8 @@ use miette::NamedSource; use owo_colors::OwoColorize; use tokio_stream::wrappers::ReadDirStream; +use crate::trustfall_plaixt::ADAPTER_SEP; + #[derive(Debug, Clone)] pub struct Record { pub(crate) kind: String, @@ -156,6 +158,7 @@ pub(crate) async fn load_records( #[derive(Debug)] pub enum DefinitionKind { String, + Path, OneOf(Vec), } @@ -163,6 +166,7 @@ impl DefinitionKind { pub(crate) fn trustfall_kind(&self) -> String { match self { DefinitionKind::String => String::from("String"), + DefinitionKind::Path => format!("fs{ADAPTER_SEP}Path"), DefinitionKind::OneOf(_vecs) => String::from("String"), } } @@ -173,6 +177,10 @@ impl DefinitionKind { .is_string() .then_some(()) .ok_or("Expected a string here".to_string()), + DefinitionKind::Path => val + .is_string() + .then_some(()) + .ok_or("Expected a path encoded as a string here".to_string()), DefinitionKind::OneOf(options) => val .as_string() .is_some_and(|val| options.iter().any(|o| o == val)) @@ -187,6 +195,7 @@ impl TryFrom<&str> for DefinitionKind { fn try_from(value: &str) -> Result { match value.to_ascii_lowercase().as_str() { "string" => Ok(DefinitionKind::String), + "path" => Ok(DefinitionKind::Path), other => miette::bail!("Did not recognize valid field kind: \"{other}\""), } } diff --git a/crates/plaixt/src/trustfall_plaixt.rs b/crates/plaixt/src/trustfall_plaixt.rs index 422962b..7e95369 100644 --- a/crates/plaixt/src/trustfall_plaixt.rs +++ b/crates/plaixt/src/trustfall_plaixt.rs @@ -19,7 +19,7 @@ use trustfall::Schema; use crate::parsing::Definition; use crate::parsing::Record; -const ADAPTER_SEP: &str = "__"; +pub(crate) const ADAPTER_SEP: &str = "__"; #[derive(Debug, Default)] pub struct StartingVertex { @@ -155,12 +155,29 @@ pub(crate) fn to_schema(definitions: &BTreeMap>) -> Sche {} type RootSchemaQuery {{ {roots} + fs{ADAPTER_SEP}Path(path: String!): fs{ADAPTER_SEP}Path!, }} interface Record {{ at: String!, kind: String!, }} + interface fs{ADAPTER_SEP}Path {{ + path: String! + }} + + type fs{ADAPTER_SEP}Folder implements fs{ADAPTER_SEP}Path {{ + path: String! + children: [fs{ADAPTER_SEP}Path!] + }} + + type fs{ADAPTER_SEP}File implements fs{ADAPTER_SEP}Path {{ + path: String! + size: Int! + extension: String! + Hash: String! + }} + {types} "#, Schema::ALL_DIRECTIVE_DEFINITIONS, @@ -179,11 +196,23 @@ pub(crate) fn to_schema(definitions: &BTreeMap>) -> Sche pub struct TrustfallMultiAdapter { pub plaixt: PlaixtAdapter, + pub filesystem: filesystem_trustfall_adapter::FileSystemAdapter, } #[derive(Debug, Clone)] pub enum TrustfallMultiVertex { Plaixt(PlaixtVertex), + Filesystem(filesystem_trustfall_adapter::vertex::Vertex), +} + +impl AsVertex for TrustfallMultiVertex { + fn as_vertex(&self) -> Option<&filesystem_trustfall_adapter::vertex::Vertex> { + self.as_filesystem() + } + + fn into_vertex(self) -> Option { + self.as_filesystem().cloned() + } } impl AsVertex for TrustfallMultiVertex { @@ -204,6 +233,20 @@ impl TrustfallMultiVertex { None } } + + pub fn as_filesystem( + &self, + ) -> Option< + &>::Vertex, + > { + if let Self::Filesystem(v) = self { + Some(v) + } else { + None + } + } } impl<'v> Adapter<'v> for TrustfallMultiAdapter { @@ -217,7 +260,11 @@ impl<'v> Adapter<'v> for TrustfallMultiAdapter { ) -> trustfall::provider::VertexIterator<'v, Self::Vertex> { let (adapter_name, edge_name) = edge_name.split_once(ADAPTER_SEP).unwrap(); - trace!(?adapter_name, ?edge_name, "Got start vertex"); + trace!( + ?adapter_name, + ?edge_name, + "resolving top-level starting vertex" + ); match adapter_name { "Plaixt" => { @@ -229,6 +276,18 @@ impl<'v> Adapter<'v> for TrustfallMultiAdapter { Box::new(iter.map(TrustfallMultiVertex::Plaixt)) } + "fs" => { + let iter = self.filesystem.resolve_starting_vertices( + &Arc::from(edge_name), + parameters, + resolve_info, + ); + + Box::new( + iter.inspect(|v| trace!(?v, "Got vertex")) + .map(TrustfallMultiVertex::Filesystem), + ) + } _ => unreachable!(), } } @@ -243,7 +302,10 @@ impl<'v> Adapter<'v> for TrustfallMultiAdapter { where V: trustfall::provider::AsVertex + 'v, { - let (adapter_name, type_name) = type_name.split_once(ADAPTER_SEP).unwrap(); + trace!(?type_name, ?property_name, "resolving top-level property"); + let (adapter_name, type_name) = type_name + .split_once(ADAPTER_SEP) + .unwrap_or(("Plaixt", type_name)); match adapter_name { "Plaixt" => { @@ -268,6 +330,29 @@ impl<'v> Adapter<'v> for TrustfallMultiAdapter { .map(|((_ctx, name), og_ctx)| (og_ctx, name)), ) } + "fs" => { + let contexts = contexts.collect::>(); + + let properties = self.filesystem.resolve_property( + Box::new( + contexts + .clone() + .into_iter() + .map(|v| v.flat_map(&mut |v: V| v.into_vertex())) + .inspect(|v| trace!(?v, "Got vertex")), + ), + &Arc::from(type_name), + property_name, + resolve_info, + ); + + Box::new( + properties + .into_iter() + .zip(contexts) + .map(|((_ctx, name), og_ctx)| (og_ctx, name)), + ) + } _ => unreachable!(), } } @@ -284,6 +369,7 @@ impl<'v> Adapter<'v> for TrustfallMultiAdapter { V, trustfall::provider::VertexIterator<'v, Self::Vertex>, > { + trace!(?type_name, ?edge_name, "Resolving top-level neighbor"); let (adapter_name, type_name) = type_name.split_once(ADAPTER_SEP).unwrap(); match adapter_name { @@ -315,6 +401,37 @@ impl<'v> Adapter<'v> for TrustfallMultiAdapter { }), ) } + "fs" => { + let contexts = contexts.collect::>(); + + let properties = self.filesystem.resolve_neighbors( + Box::new( + contexts + .clone() + .into_iter() + .map(|v| v.flat_map(&mut |v: V| v.into_vertex())), + ), + &Arc::from(type_name), + edge_name, + parameters, + resolve_info, + ); + + Box::new( + properties + .into_iter() + .zip(contexts) + .map(|((_ctx, vals), og_ctx)| { + ( + og_ctx, + Box::new( + vals.inspect(|v| trace!(?v, "Got vertex")) + .map(TrustfallMultiVertex::Filesystem), + ) as Box<_>, + ) + }), + ) + } _ => unreachable!(), } } @@ -326,7 +443,7 @@ impl<'v> Adapter<'v> for TrustfallMultiAdapter { coerce_to_type: &Arc, resolve_info: &trustfall::provider::ResolveInfo, ) -> trustfall::provider::ContextOutcomeIterator<'v, V, bool> { - trace!(?type_name, ?coerce_to_type, "Trying to coerce"); + trace!(?type_name, ?coerce_to_type, "Top-level coerce"); let (adapter_name, coerce_to_type) = coerce_to_type.split_once(ADAPTER_SEP).unwrap(); match adapter_name { @@ -352,6 +469,30 @@ impl<'v> Adapter<'v> for TrustfallMultiAdapter { .map(|((_ctx, val), og_ctx)| (og_ctx, val)), ) } + "fs" => { + let contexts = contexts.collect::>(); + + let properties = self.filesystem.resolve_coercion( + Box::new( + contexts + .clone() + .into_iter() + .map(|v| v.flat_map(&mut |v: V| v.into_vertex())), + ), + type_name, + &Arc::from(coerce_to_type), + resolve_info, + ); + + let type_name = type_name.clone(); + let coerce_to_type = coerce_to_type.to_string(); + Box::new(properties.into_iter().zip(contexts).map( + move |((_ctx, allowed), og_ctx)| { + trace!(?allowed, ?type_name, ?coerce_to_type, "Allowed coercion?"); + (og_ctx, allowed) + }, + )) + } _ => unreachable!(), } } @@ -454,7 +595,7 @@ impl<'a> Adapter<'a> for PlaixtAdapter { fn resolve_neighbors + 'a>( &self, contexts: trustfall::provider::ContextIterator<'a, V>, - _type_name: &Arc, + type_name: &Arc, edge_name: &Arc, _parameters: &trustfall::provider::EdgeParameters, _resolve_info: &trustfall::provider::ResolveEdgeInfo, @@ -463,6 +604,7 @@ impl<'a> Adapter<'a> for PlaixtAdapter { V, trustfall::provider::VertexIterator<'a, Self::Vertex>, > { + trace!(?type_name, ?edge_name, "Resolving neighbors"); match edge_name.as_ref() { "fields" => resolve_neighbors_with(contexts, |c| { Box::new( @@ -474,7 +616,8 @@ impl<'a> Adapter<'a> for PlaixtAdapter { .into_iter(), ) }), - _ => unreachable!(), + _ => resolve_neighbors_with(contexts, |c| todo!()), + _ => unreachable!("Could not resolve {edge_name}"), } } diff --git a/examples/changelog.plrecs b/examples/changelog.plrecs index 03e02c2..af02d2a 100644 --- a/examples/changelog.plrecs +++ b/examples/changelog.plrecs @@ -21,3 +21,7 @@ changelog "2025-02-07" { version "0.1.0" type "Feature" } + +file_test "2025-02-08" { + path "Cargo.toml" +} diff --git a/examples/definitions/file_test.pldef b/examples/definitions/file_test.pldef new file mode 100644 index 0000000..40b12c0 --- /dev/null +++ b/examples/definitions/file_test.pldef @@ -0,0 +1,7 @@ +// This is the default changelog entry for the plaixt project + +define since="2025-02-08 00:00:00+01:00" { + fields { + path is="Path" + } +} diff --git a/flake.lock b/flake.lock index 5db1f8b..8eff4fc 100644 --- a/flake.lock +++ b/flake.lock @@ -63,11 +63,11 @@ ] }, "locked": { - "lastModified": 1738117527, - "narHash": "sha256-GFviGfaezjGLFUlxdv3zyC7rSZvTXqwcG/YsF6MDkOw=", + "lastModified": 1738981474, + "narHash": "sha256-YIELTXxfATG0g1wXjyaOWA4qrlubds3MG4FvMPCxSGg=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "6a3dc6ce4132bd57359214d986db376f2333c14d", + "rev": "5c571e5ff246d8fc5f76ba6e38dc8edb6e4002fe", "type": "github" }, "original": { diff --git a/query b/query index 80fc919..1824356 100644 --- a/query +++ b/query @@ -1,13 +1,27 @@ { Plaixt__RecordsAll { - ... on Plaixt__changelog { + ... on Plaixt__file_test { at @output kind @output fields { - title @output @filter(op: "has_substring", value: ["$search"]) - version @output - type @output + path { + ... on fs__File { + size @output + } + } } } } + + # fs__Path(path: "./crates/plaixt/") { + # ... on fs__Folder { + # directory: path @output + # children @fold { + # ... on fs__File { + # file: path @output + # size @output + # } + # } + # } + # } } diff --git a/rust-toolchain.toml b/rust-toolchain.toml index efd9dc3..fcb78ec 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "1.84.0" +channel = "1.84.1" From 11c3a8de942a5586b7187c85a6c39271bbbb3e27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Sun, 9 Feb 2025 19:14:54 +0100 Subject: [PATCH 09/10] Adapt to new trustfall model MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcel Müller --- Cargo.lock | 941 +++++++++++++++++++++- crates/plaixt/Cargo.toml | 1 + crates/plaixt/src/adapter/adapter_impl.rs | 185 +++++ crates/plaixt/src/adapter/edges.rs | 85 ++ crates/plaixt/src/adapter/entrypoints.rs | 16 + crates/plaixt/src/adapter/mod.rs | 55 ++ crates/plaixt/src/adapter/properties.rs | 159 ++++ crates/plaixt/src/adapter/schema.graphql | 88 ++ crates/plaixt/src/adapter/tests.rs | 16 + crates/plaixt/src/adapter/vertex.rs | 15 + crates/plaixt/src/main.rs | 24 +- crates/plaixt/src/parsing.rs | 34 +- crates/plaixt/src/trustfall_plaixt.rs | 639 --------------- flake.nix | 59 +- 14 files changed, 1632 insertions(+), 685 deletions(-) create mode 100644 crates/plaixt/src/adapter/adapter_impl.rs create mode 100644 crates/plaixt/src/adapter/edges.rs create mode 100644 crates/plaixt/src/adapter/entrypoints.rs create mode 100644 crates/plaixt/src/adapter/mod.rs create mode 100644 crates/plaixt/src/adapter/properties.rs create mode 100644 crates/plaixt/src/adapter/schema.graphql create mode 100644 crates/plaixt/src/adapter/tests.rs create mode 100644 crates/plaixt/src/adapter/vertex.rs delete mode 100644 crates/plaixt/src/trustfall_plaixt.rs diff --git a/Cargo.lock b/Cargo.lock index c1ac24f..23b0445 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -135,7 +135,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -147,6 +147,12 @@ dependencies = [ "backtrace", ] +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "base64" version = "0.22.1" @@ -183,6 +189,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bumpalo" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" + [[package]] name = "bytes" version = "1.10.0" @@ -262,6 +274,28 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -299,6 +333,19 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "derive_more" +version = "0.99.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3da29a38df43d6f156149c9b43ded5e018ddff2a855cf2cfd62e8cd7d079c69f" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn", +] + [[package]] name = "digest" version = "0.10.7" @@ -309,12 +356,32 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "either" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -331,6 +398,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + [[package]] name = "filesystem-trustfall-adapter" version = "0.1.1" @@ -357,6 +430,30 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + [[package]] name = "futures" version = "0.3.31" @@ -465,7 +562,7 @@ dependencies = [ "cfg-if", "libc", "wasi 0.13.3+wasi-0.2.2", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -474,6 +571,25 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "h2" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.15.2" @@ -492,6 +608,40 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + [[package]] name = "human-panic" version = "2.0.2" @@ -508,6 +658,182 @@ dependencies = [ "uuid", ] +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + [[package]] name = "indexmap" version = "2.7.1" @@ -519,6 +845,12 @@ dependencies = [ "serde", ] +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + [[package]] name = "is_ci" version = "1.2.0" @@ -575,6 +907,16 @@ dependencies = [ "jiff-tzdb", ] +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + [[package]] name = "kdl" version = "6.3.3" @@ -611,6 +953,12 @@ version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" +[[package]] +name = "litemap" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" + [[package]] name = "lock_api" version = "0.4.12" @@ -680,6 +1028,12 @@ dependencies = [ "syn", ] +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + [[package]] name = "miniz_oxide" version = "0.8.3" @@ -700,6 +1054,23 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "native-tls" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dab59f8e050d5df8e4dd87d9206fb6f65a483e20ac9fda365ade4fab353196c" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -826,6 +1197,50 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "openssl" +version = "0.10.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61cfb4e166a8bb8c9b55c500bc2308550148ece889be90f609377e58140f42c6" +dependencies = [ + "bitflags 2.8.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b22d5b84be05a8d6947c7cb71f7c849aa0f112acd4bf51c2a7c1c988ac0a9dc" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "os_info" version = "3.9.2" @@ -849,6 +1264,21 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb37767f6569cd834a413442455e0f066d0d522de8630436e2a1761d9726ba56" +[[package]] +name = "paperless-rs" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e929358cf0d9963f0ee77487fc1b45d4bbc20542e79b072bec05b4bd66e085f4" +dependencies = [ + "async-trait", + "base64 0.21.7", + "bytes", + "derive_more", + "reqwest", + "serde", + "tokio", +] + [[package]] name = "parking_lot" version = "0.12.3" @@ -869,9 +1299,15 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.52.6", ] +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + [[package]] name = "pest" version = "2.7.15" @@ -914,6 +1350,7 @@ dependencies = [ "kdl", "miette", "owo-colors", + "paperless-rs", "tokio", "tokio-stream", "tracing", @@ -927,7 +1364,7 @@ version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016" dependencies = [ - "base64", + "base64 0.22.1", "indexmap", "quick-xml", "serde", @@ -1035,12 +1472,61 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "reqwest" +version = "0.11.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +dependencies = [ + "base64 0.21.7", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + [[package]] name = "rustc-demangle" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "0.38.44" @@ -1054,6 +1540,21 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + +[[package]] +name = "rustversion" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" + [[package]] name = "ryu" version = "1.0.19" @@ -1069,12 +1570,50 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.8.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" + [[package]] name = "serde" version = "1.0.217" @@ -1116,6 +1655,18 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "sha2" version = "0.10.8" @@ -1192,6 +1743,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "strsim" version = "0.11.1" @@ -1230,6 +1787,23 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "syntect" version = "5.2.0" @@ -1252,6 +1826,41 @@ dependencies = [ "yaml-rust", ] +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91" +dependencies = [ + "cfg-if", + "fastrand", + "getrandom", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + [[package]] name = "terminal_size" version = "0.4.1" @@ -1353,6 +1962,16 @@ dependencies = [ "time-core", ] +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tokio" version = "1.43.0" @@ -1382,6 +2001,16 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.17" @@ -1440,6 +2069,12 @@ dependencies = [ "toml_datetime", ] +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + [[package]] name = "tracing" version = "0.1.41" @@ -1540,6 +2175,12 @@ dependencies = [ "syn", ] +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "typenum" version = "1.17.0" @@ -1570,6 +2211,29 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" version = "0.2.2" @@ -1591,6 +2255,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.5" @@ -1607,6 +2277,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -1622,6 +2301,87 @@ dependencies = [ "wit-bindgen-rt", ] +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "winapi" version = "0.3.9" @@ -1653,13 +2413,22 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -1668,7 +2437,22 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] @@ -1677,28 +2461,46 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -1711,24 +2513,48 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -1744,6 +2570,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "wit-bindgen-rt" version = "0.33.0" @@ -1753,6 +2589,18 @@ dependencies = [ "bitflags 2.8.0", ] +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + [[package]] name = "yaml-rust" version = "0.4.5" @@ -1761,3 +2609,70 @@ checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" dependencies = [ "linked-hash-map", ] + +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/crates/plaixt/Cargo.toml b/crates/plaixt/Cargo.toml index 610df66..9dce34e 100644 --- a/crates/plaixt/Cargo.toml +++ b/crates/plaixt/Cargo.toml @@ -15,6 +15,7 @@ jiff = "0.1.28" kdl.workspace = true miette = { version = "7.4.0", features = ["fancy", "syntect-highlighter"] } owo-colors = "4.1.0" +paperless-rs = "0.1.5" tokio = { version = "1.43.0", features = ["full"] } tokio-stream = { version = "0.1.17", features = ["full"] } tracing = "0.1.41" diff --git a/crates/plaixt/src/adapter/adapter_impl.rs b/crates/plaixt/src/adapter/adapter_impl.rs new file mode 100644 index 0000000..dbfa146 --- /dev/null +++ b/crates/plaixt/src/adapter/adapter_impl.rs @@ -0,0 +1,185 @@ +use std::collections::BTreeMap; +use std::collections::BTreeSet; +use std::sync::Arc; +use std::sync::OnceLock; + +use paperless_rs::PaperlessClient; +use trustfall::provider::resolve_coercion_using_schema; +use trustfall::provider::resolve_property_with; +use trustfall::provider::AsVertex; +use trustfall::provider::ContextIterator; +use trustfall::provider::ContextOutcomeIterator; +use trustfall::provider::EdgeParameters; +use trustfall::provider::ResolveEdgeInfo; +use trustfall::provider::ResolveInfo; +use trustfall::provider::Typename; +use trustfall::provider::VertexIterator; +use trustfall::FieldValue; +use trustfall::Schema; + +use super::vertex::Vertex; +use crate::parsing::DefinitionKind; +use crate::parsing::Record; + +static SCHEMA: OnceLock = OnceLock::new(); + +#[non_exhaustive] +pub struct Adapter { + schema: Arc, + records: Vec, + definitions: Arc>>, + paperless_client: Option, + runtime_handle: tokio::runtime::Handle, +} + +impl std::fmt::Debug for Adapter { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Adapter").finish_non_exhaustive() + } +} + +impl Adapter { + pub fn new( + schema: Schema, + records: Vec, + definitions: BTreeMap>, + paperless_client: Option, + runtime: tokio::runtime::Handle, + ) -> Self { + Self { + schema: Arc::new(schema), + records, + definitions: Arc::new(definitions), + paperless_client, + runtime_handle: runtime, + } + } + + pub const SCHEMA_TEXT: &'static str = include_str!("./schema.graphql"); + + pub fn schema() -> &'static Schema { + SCHEMA.get_or_init(|| Schema::parse(Self::SCHEMA_TEXT).expect("not a valid schema")) + } +} + +impl<'a> trustfall::provider::Adapter<'a> for Adapter { + type Vertex = Vertex; + + fn resolve_starting_vertices( + &self, + edge_name: &Arc, + _parameters: &EdgeParameters, + resolve_info: &ResolveInfo, + ) -> VertexIterator<'a, Self::Vertex> { + match edge_name.as_ref() { + "Records" => super::entrypoints::records(resolve_info, &self.records), + _ => { + unreachable!( + "attempted to resolve starting vertices for unexpected edge name: {edge_name}" + ) + } + } + } + + fn resolve_property + 'a>( + &self, + contexts: ContextIterator<'a, V>, + type_name: &Arc, + property_name: &Arc, + resolve_info: &ResolveInfo, + ) -> ContextOutcomeIterator<'a, V, FieldValue> { + if property_name.as_ref() == "__typename" { + return resolve_property_with(contexts, |vertex| vertex.typename().into()); + } + match type_name.as_ref() { + "PaperlessDocument" => super::properties::resolve_paperless_document_property( + contexts, + property_name.as_ref(), + resolve_info, + ), + "Path" => super::properties::resolve_path_property( + contexts, + property_name.as_ref(), + resolve_info, + ), + "File" => super::properties::resolve_file_property( + contexts, + property_name.as_ref(), + resolve_info, + ), + "Directory" => super::properties::resolve_directory_property( + contexts, + property_name.as_ref(), + resolve_info, + ), + "Record" => { + super::properties::resolve_record_property(contexts, property_name, resolve_info) + } + kind if kind.starts_with("p_") => { + super::properties::resolve_record_property(contexts, property_name, resolve_info) + } + _ => { + unreachable!( + "attempted to read property '{property_name}' on unexpected type: {type_name}" + ) + } + } + } + + fn resolve_neighbors + 'a>( + &self, + contexts: ContextIterator<'a, V>, + type_name: &Arc, + edge_name: &Arc, + parameters: &EdgeParameters, + resolve_info: &ResolveEdgeInfo, + ) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Self::Vertex>> { + match type_name.as_ref() { + "Directory" => super::edges::resolve_directory_edge( + contexts, + edge_name.as_ref(), + parameters, + resolve_info, + ), + kind if kind.starts_with("p_") => super::edges::resolve_record_edge( + contexts, + edge_name, + parameters, + resolve_info, + &self.definitions, + ), + _ => { + unreachable!( + "attempted to resolve edge '{edge_name}' on unexpected type: {type_name}" + ) + } + } + } + + fn resolve_coercion + 'a>( + &self, + contexts: ContextIterator<'a, V>, + _type_name: &Arc, + coerce_to_type: &Arc, + _resolve_info: &ResolveInfo, + ) -> ContextOutcomeIterator<'a, V, bool> { + let schema = self.schema.clone(); + let coerce_to_type = coerce_to_type.clone(); + + Box::new(contexts.map(move |ctx| { + let subtypes: BTreeSet<_> = schema + .subtypes(coerce_to_type.as_ref()) + .unwrap_or_else(|| panic!("type {coerce_to_type} is not part of this schema")) + .collect(); + + match ctx.active_vertex::() { + None => (ctx, false), + Some(vertex) => { + let typename = vertex.typename(); + let can_coerce = subtypes.contains(typename); + (ctx, can_coerce) + } + } + })) + } +} diff --git a/crates/plaixt/src/adapter/edges.rs b/crates/plaixt/src/adapter/edges.rs new file mode 100644 index 0000000..54b841b --- /dev/null +++ b/crates/plaixt/src/adapter/edges.rs @@ -0,0 +1,85 @@ +use std::collections::BTreeMap; +use std::sync::Arc; + +use trustfall::provider::resolve_neighbors_with; +use trustfall::provider::AsVertex; +use trustfall::provider::ContextIterator; +use trustfall::provider::ContextOutcomeIterator; +use trustfall::provider::EdgeParameters; +use trustfall::provider::ResolveEdgeInfo; +use trustfall::provider::VertexIterator; + +use super::Vertex; +use crate::parsing::DefinitionKind; + +pub(super) fn resolve_directory_edge<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, + edge_name: &str, + _parameters: &EdgeParameters, + resolve_info: &ResolveEdgeInfo, +) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Vertex>> { + match edge_name { + "Children" => directory::children(contexts, resolve_info), + _ => unreachable!("attempted to resolve unexpected edge '{edge_name}' on type 'Directory'"), + } +} + +mod directory { + use camino::Utf8Path; + use trustfall::provider::resolve_neighbors_with; + use trustfall::provider::AsVertex; + use trustfall::provider::ContextIterator; + use trustfall::provider::ContextOutcomeIterator; + use trustfall::provider::ResolveEdgeInfo; + use trustfall::provider::VertexIterator; + + use crate::adapter::Vertex; + + pub(super) fn children<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, + _resolve_info: &ResolveEdgeInfo, + ) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Vertex>> { + resolve_neighbors_with(contexts, move |vertex| { + let vertex = vertex + .as_directory() + .expect("conversion failed, vertex was not a Directory"); + + fn read_children(path: &Utf8Path) -> Option> { + Some( + path.read_dir_utf8() + .ok()? + .flat_map(|item| Some(Vertex::Path(item.ok()?.path().to_path_buf()))), + ) + } + + read_children(vertex) + .map(|i| { + let it: Box> = Box::new(i); + it + }) + .unwrap_or_else(|| Box::new(std::iter::empty())) + }) + } +} + +pub(super) fn resolve_record_edge<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, + edge_name: &Arc, + _parameters: &EdgeParameters, + _resolve_info: &ResolveEdgeInfo, + definitions: &Arc>>, +) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Vertex>> { + let edge_name = edge_name.clone(); + let definitions = definitions.clone(); + resolve_neighbors_with(contexts, move |v| { + let rec = v.as_record().expect("Expected a record"); + let def = &definitions[&rec.kind][edge_name.as_ref()]; + + match def { + DefinitionKind::Path => Box::new(std::iter::once(Vertex::Path( + rec.fields[edge_name.as_ref()].as_string().unwrap().into(), + ))), + _ => unreachable!("Only `Path` can appear as edge for now"), + } + }) +} diff --git a/crates/plaixt/src/adapter/entrypoints.rs b/crates/plaixt/src/adapter/entrypoints.rs new file mode 100644 index 0000000..5ee05a5 --- /dev/null +++ b/crates/plaixt/src/adapter/entrypoints.rs @@ -0,0 +1,16 @@ +use trustfall::provider::ResolveInfo; +use trustfall::provider::VertexIterator; + +use super::vertex::Vertex; +use crate::parsing::Record; + +pub(super) fn records<'a>( + _resolve_info: &ResolveInfo, + records: &'_ [Record], +) -> VertexIterator<'a, Vertex> { + #[expect( + clippy::unnecessary_to_owned, + reason = "We have to go through a vec to satisfy the lifetimes" + )] + Box::new(records.to_vec().into_iter().map(Vertex::Record)) +} diff --git a/crates/plaixt/src/adapter/mod.rs b/crates/plaixt/src/adapter/mod.rs new file mode 100644 index 0000000..c2219c0 --- /dev/null +++ b/crates/plaixt/src/adapter/mod.rs @@ -0,0 +1,55 @@ +mod adapter_impl; +mod edges; +mod entrypoints; +mod properties; +mod vertex; + +#[cfg(test)] +mod tests; + +pub use adapter_impl::Adapter; +use tracing::trace; +use trustfall::Schema; +pub use vertex::Vertex; + +pub struct CustomVertex { + pub name: String, + pub definition: String, +} + +impl crate::parsing::Definition { + fn to_custom_vertices(&self) -> Vec { + let name = format!("p_{}", self.name); + + let fields = self + .fields + .iter() + .map(|(fname, ftype)| { + let kind = ftype.trustfall_kind(&format!("{name}{fname}")); + format!("{fname}: {kind}") + }) + .chain([String::from("_at: String!"), String::from("_kind: String!")]) + .collect::>(); + + let definition = format!("type {name} implements Record {{ {} }}", fields.join(",")); + + [CustomVertex { name, definition }].into_iter().collect() + } +} + +pub(crate) fn to_schema( + definitions: &std::collections::BTreeMap>, +) -> trustfall::Schema { + let base_text = Adapter::SCHEMA_TEXT; + + let generated = definitions + .values() + .flat_map(|defs| defs.last().unwrap().to_custom_vertices()) + .map(|v| v.definition) + .collect::>() + .join("\n"); + + let input = format!("{base_text}{generated}"); + trace!(%input, "Using schema"); + Schema::parse(input).unwrap() +} diff --git a/crates/plaixt/src/adapter/properties.rs b/crates/plaixt/src/adapter/properties.rs new file mode 100644 index 0000000..4f8d955 --- /dev/null +++ b/crates/plaixt/src/adapter/properties.rs @@ -0,0 +1,159 @@ +use std::collections::BTreeMap; +use std::sync::Arc; + +use kdl::KdlValue; +use paperless_rs::PaperlessClient; +use trustfall::provider::field_property; +use trustfall::provider::resolve_property_with; +use trustfall::provider::AsVertex; +use trustfall::provider::ContextIterator; +use trustfall::provider::ContextOutcomeIterator; +use trustfall::provider::ResolveInfo; +use trustfall::FieldValue; + +use super::vertex::Vertex; + +pub(super) fn resolve_path_property<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, + property_name: &str, + _resolve_info: &ResolveInfo, +) -> ContextOutcomeIterator<'a, V, FieldValue> { + match property_name { + "exists" => resolve_property_with(contexts, move |v: &Vertex| { + let path = v.as_path().expect("vertex was not a Path"); + + path.exists().into() + }), + "basename" => resolve_property_with(contexts, move |v: &Vertex| { + let path = v.as_path().expect("vertex was not a Path"); + + path.file_name().into() + }), + "path" => resolve_property_with(contexts, move |v: &Vertex| { + let path = v.as_path().expect("vertex was not a Path"); + + path.to_string().into() + }), + _ => { + unreachable!("attempted to read unexpected property '{property_name}' on type 'Path'") + } + } +} + +pub(super) fn resolve_directory_property<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, + property_name: &str, + _resolve_info: &ResolveInfo, +) -> ContextOutcomeIterator<'a, V, FieldValue> { + match property_name { + "exists" => resolve_property_with(contexts, move |v: &Vertex| { + let directory = v.as_directory().expect("vertex was not a Directory"); + + directory.exists().into() + }), + "basename" => resolve_property_with(contexts, move |v: &Vertex| { + let directory = v.as_directory().expect("vertex was not a Directory"); + + directory.file_name().into() + }), + "path" => resolve_property_with(contexts, move |v: &Vertex| { + let directory = v.as_directory().expect("vertex was not a Directory"); + + directory.to_string().into() + }), + _ => { + unreachable!("attempted to read unexpected property '{property_name}' on type 'File'") + } + } +} + +pub(super) fn resolve_file_property<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, + property_name: &str, + _resolve_info: &ResolveInfo, +) -> ContextOutcomeIterator<'a, V, FieldValue> { + match property_name { + "exists" => resolve_property_with(contexts, move |v: &Vertex| { + let file = v.as_file().expect("vertex was not a File"); + + file.exists().into() + }), + "basename" => resolve_property_with(contexts, move |v: &Vertex| { + let file = v.as_file().expect("vertex was not a File"); + + file.file_name().into() + }), + "path" => resolve_property_with(contexts, move |v: &Vertex| { + let file = v.as_file().expect("vertex was not a File"); + + file.to_string().into() + }), + "extension" => resolve_property_with(contexts, move |v: &Vertex| { + let file = v.as_file().expect("vertex was not a File"); + + file.extension().into() + }), + _ => { + unreachable!("attempted to read unexpected property '{property_name}' on type 'File'") + } + } +} + +pub(super) fn resolve_paperless_document_property<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, + property_name: &str, + _resolve_info: &ResolveInfo, +) -> ContextOutcomeIterator<'a, V, FieldValue> { + match property_name { + "added" => resolve_property_with(contexts, field_property!(as_paperless_document, added)), + "archive_serial_number" => resolve_property_with( + contexts, + field_property!(as_paperless_document, archive_serial_number), + ), + "content" => { + resolve_property_with(contexts, field_property!(as_paperless_document, content)) + } + "created" => { + resolve_property_with(contexts, field_property!(as_paperless_document, created)) + } + "id" => resolve_property_with(contexts, field_property!(as_paperless_document, id)), + "title" => resolve_property_with(contexts, field_property!(as_paperless_document, title)), + _ => { + unreachable!( + "attempted to read unexpected property '{property_name}' on type 'PaperlessDocument'" + ) + } + } +} + +pub(super) fn resolve_record_property<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, + property_name: &Arc, + _resolve_info: &ResolveInfo, +) -> ContextOutcomeIterator<'a, V, FieldValue> { + let property_name = property_name.clone(); + match property_name.as_ref() { + "_at" => resolve_property_with( + contexts, + field_property!(as_record, at, { at.to_string().into() }), + ), + "_kind" => resolve_property_with(contexts, field_property!(as_record, kind)), + _ => resolve_property_with(contexts, move |v: &Vertex| { + let rec = v + .as_record() + .expect("Called record property without it being a record"); + + kdl_to_trustfall_value(rec.fields[property_name.as_ref()].clone()) + }), + } +} + +fn kdl_to_trustfall_value(val: KdlValue) -> FieldValue { + match val { + 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()), + } +} diff --git a/crates/plaixt/src/adapter/schema.graphql b/crates/plaixt/src/adapter/schema.graphql new file mode 100644 index 0000000..b56faad --- /dev/null +++ b/crates/plaixt/src/adapter/schema.graphql @@ -0,0 +1,88 @@ +schema { + query: RootSchemaQuery +} + +directive @filter( + """ + Name of the filter operation to perform. + """ + op: String! + """ + List of string operands for the operator. + """ + value: [String!] +) repeatable on FIELD | INLINE_FRAGMENT +directive @tag( + """ + Name to apply to the given property field. + """ + name: String +) on FIELD +directive @output( + """ + What to designate the output field generated from this property field. + """ + name: String +) on FIELD +directive @optional on FIELD +directive @recurse( + """ + Recurse up to this many times on this edge. A depth of 1 produces the current + vertex and its immediate neighbors along the given edge. + """ + depth: Int! +) on FIELD +directive @fold on FIELD +directive @transform( + """ + Name of the transformation operation to perform. + """ + op: String! +) on FIELD + + +""" +All the possible data types to begin querying +""" +type RootSchemaQuery { + """ + All records in your plaixt instance + """ + Records: [Record!]! +} + +interface Record { + _kind: String! + _at: String! +} + +interface Path { + path: String! + exists: Boolean! + basename: String! +} + +interface File implements Path { + path: String! + exists: Boolean! + basename: String! + + extension: String! +} + +type Directory implements Path { + path: String! + exists: Boolean! + basename: String! + + Children: [Path!]! +} + +type PaperlessDocument { + id: Int! + title: String! + content: String! + archive_serial_number: Int + created: String! + added: String! +} diff --git a/crates/plaixt/src/adapter/tests.rs b/crates/plaixt/src/adapter/tests.rs new file mode 100644 index 0000000..8acec27 --- /dev/null +++ b/crates/plaixt/src/adapter/tests.rs @@ -0,0 +1,16 @@ +use trustfall::provider::check_adapter_invariants; + +use super::Adapter; + +#[tokio::test] +async fn adapter_satisfies_trustfall_invariants() { + let schema = Adapter::schema(); + let adapter = Adapter::new( + schema.clone(), + vec![], + [].into(), + None, + tokio::runtime::Handle::current(), + ); + check_adapter_invariants(schema, adapter); +} diff --git a/crates/plaixt/src/adapter/vertex.rs b/crates/plaixt/src/adapter/vertex.rs new file mode 100644 index 0000000..3829e2e --- /dev/null +++ b/crates/plaixt/src/adapter/vertex.rs @@ -0,0 +1,15 @@ +use camino::Utf8PathBuf; +use paperless_rs::endpoint::documents::Document as PaperlessDocument; + +use crate::parsing::Record; + +#[non_exhaustive] +#[derive(Debug, Clone, trustfall::provider::TrustfallEnumVertex)] +pub enum Vertex { + Path(Utf8PathBuf), + File(Utf8PathBuf), + Directory(Utf8PathBuf), + + PaperlessDocument(PaperlessDocument), + Record(Record), +} diff --git a/crates/plaixt/src/main.rs b/crates/plaixt/src/main.rs index c64aa27..4361ea5 100644 --- a/crates/plaixt/src/main.rs +++ b/crates/plaixt/src/main.rs @@ -8,7 +8,6 @@ use camino::Utf8PathBuf; use clap::Parser; use clap::Subcommand; use clap::ValueHint; -use filesystem_trustfall_adapter::FileSystemAdapter; use human_panic::Metadata; use miette::IntoDiagnostic; use parsing::Definition; @@ -18,9 +17,9 @@ use tracing_subscriber::EnvFilter; use trustfall::execute_query; use trustfall::FieldValue; +mod adapter; mod config; mod parsing; -mod trustfall_plaixt; #[derive(Debug, Parser)] struct Args { @@ -97,14 +96,19 @@ async fn main() -> miette::Result<()> { fn get_schema_and_adapter( definitions: &BTreeMap>, records: Vec, -) -> (trustfall::Schema, trustfall_plaixt::TrustfallMultiAdapter) { - let schema = trustfall_plaixt::to_schema(definitions); - let adapter = trustfall_plaixt::TrustfallMultiAdapter { - plaixt: trustfall_plaixt::PlaixtAdapter { - records: records.clone(), - }, - filesystem: FileSystemAdapter::new(), - }; +) -> (trustfall::Schema, adapter::Adapter) { + let schema = adapter::to_schema(definitions); + let definitions = definitions + .iter() + .map(|(name, def)| (name.clone(), def.last().cloned().unwrap().fields)) + .collect(); + let adapter = adapter::Adapter::new( + schema.clone(), + records, + definitions, + None, + tokio::runtime::Handle::current(), + ); (schema, adapter) } diff --git a/crates/plaixt/src/parsing.rs b/crates/plaixt/src/parsing.rs index 243dd61..f3ca840 100644 --- a/crates/plaixt/src/parsing.rs +++ b/crates/plaixt/src/parsing.rs @@ -1,5 +1,4 @@ use std::collections::BTreeMap; -use std::collections::HashMap; use camino::Utf8Path; use camino::Utf8PathBuf; @@ -15,8 +14,6 @@ use miette::NamedSource; use owo_colors::OwoColorize; use tokio_stream::wrappers::ReadDirStream; -use crate::trustfall_plaixt::ADAPTER_SEP; - #[derive(Debug, Clone)] pub struct Record { pub(crate) kind: String, @@ -155,7 +152,7 @@ pub(crate) async fn load_records( Ok(defs) } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum DefinitionKind { String, Path, @@ -163,11 +160,11 @@ pub enum DefinitionKind { } impl DefinitionKind { - pub(crate) fn trustfall_kind(&self) -> String { + pub(crate) fn trustfall_kind(&self, _namespace: &str) -> String { match self { - DefinitionKind::String => String::from("String"), - DefinitionKind::Path => format!("fs{ADAPTER_SEP}Path"), - DefinitionKind::OneOf(_vecs) => String::from("String"), + DefinitionKind::String => String::from("String!"), + DefinitionKind::Path => String::from("Path!"), + DefinitionKind::OneOf(_vecs) => String::from("String!"), } } @@ -188,6 +185,23 @@ impl DefinitionKind { .ok_or_else(|| format!("Expected one of: {}", options.join(", "))), } } + + pub(crate) fn extra_trustfall_kinds( + &self, + namespace: &str, + ) -> Vec { + match self { + DefinitionKind::OneOf(defs) => { + let name = format!("{namespace}Def"); + let vec = vec![crate::adapter::CustomVertex { + definition: format!("enum {name} {{ {} }}", defs.join(",")), + name, + }]; + vec + } + _ => vec![], + } + } } impl TryFrom<&str> for DefinitionKind { @@ -201,11 +215,11 @@ impl TryFrom<&str> for DefinitionKind { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Definition { pub(crate) name: String, pub(crate) since: Timestamp, - pub(crate) fields: HashMap, + pub(crate) fields: BTreeMap, } pub(crate) fn parse_definition( diff --git a/crates/plaixt/src/trustfall_plaixt.rs b/crates/plaixt/src/trustfall_plaixt.rs deleted file mode 100644 index 7e95369..0000000 --- a/crates/plaixt/src/trustfall_plaixt.rs +++ /dev/null @@ -1,639 +0,0 @@ -use std::collections::BTreeMap; -use std::collections::HashMap; -use std::fmt::Write; -use std::ops::Not; -use std::sync::Arc; - -use kdl::KdlValue; -use tracing::debug; -use tracing::trace; -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::provider::AsVertex; -use trustfall::FieldValue; -use trustfall::Schema; - -use crate::parsing::Definition; -use crate::parsing::Record; - -pub(crate) const ADAPTER_SEP: &str = "__"; - -#[derive(Debug, Default)] -pub struct StartingVertex { - adapter_name: String, - start_vertex_name: String, - vertex_type: String, -} - -impl StartingVertex { - pub fn new(adapter_name: String, start_vertex_name: String, start_vertex_type: String) -> Self { - Self { - adapter_name, - start_vertex_name, - vertex_type: start_vertex_type, - } - } - - pub fn schema_name(&self) -> String { - format!( - "{}{ADAPTER_SEP}{}", - self.adapter_name, self.start_vertex_name - ) - } - - pub fn vertex_type(&self) -> &str { - &self.vertex_type - } -} - -#[derive(Debug, Default)] -pub struct VertexType { - adapter_name: String, - vertex_name: String, - vertex_fields: HashMap, - implements: Vec, -} - -impl VertexType { - pub fn new( - adapter_name: String, - vertex_name: String, - vertex_fields: HashMap, - implements: Vec, - ) -> Self { - Self { - adapter_name, - vertex_name, - vertex_fields, - implements, - } - } - - pub fn schema_name(&self) -> String { - format!("{}{ADAPTER_SEP}{}", self.adapter_name, self.vertex_name) - } - - pub fn schema_type(&self) -> String { - format!( - r#"type {name} {impls} {{ {fields} }}"#, - name = self.schema_name(), - impls = self - .implements - .is_empty() - .not() - .then(|| format!("implements {}", self.implements.join(" & "))) - .unwrap_or_else(String::new), - fields = self.vertex_fields.iter().fold(String::new(), |mut out, f| { - write!(out, "{}: {}, ", f.0, f.1).unwrap(); - out - }), - ) - } -} - -#[derive(Debug, Default)] -pub struct DynamicSchema { - roots: Vec, - types: Vec, -} - -impl DynamicSchema { - pub fn new() -> Self { - Self::default() - } - - pub fn add_root(&mut self, root: StartingVertex) { - self.roots.push(root); - } - - pub fn add_type(&mut self, kind: VertexType) { - self.types.push(kind); - } -} - -pub(crate) fn to_schema(definitions: &BTreeMap>) -> Schema { - let mut schema = DynamicSchema::new(); - - schema.add_root(StartingVertex::new( - "Plaixt".to_string(), - "RecordsAll".to_string(), - "[Record!]!".to_string(), - )); - - for definition in definitions.values().flat_map(|d| d.first()) { - let fields = VertexType::new( - "Plaixt".to_string(), - format!("{}Fields", definition.name), - definition - .fields - .iter() - .map(|(name, val)| (name.clone(), format!("{}!", val.trustfall_kind()))) - .collect(), - vec![], - ); - schema.add_type(VertexType::new( - "Plaixt".to_string(), - definition.name.clone(), - [ - (String::from("at"), String::from("String!")), - (String::from("kind"), String::from("String!")), - (String::from("fields"), format!("{}!", fields.schema_name())), - ] - .into(), - vec![String::from("Record")], - )); - schema.add_type(fields); - } - - let schema = format!( - r#"schema {{ - query: RootSchemaQuery - }} - {} - type RootSchemaQuery {{ - {roots} - fs{ADAPTER_SEP}Path(path: String!): fs{ADAPTER_SEP}Path!, - }} - interface Record {{ - at: String!, - kind: String!, - }} - - interface fs{ADAPTER_SEP}Path {{ - path: String! - }} - - type fs{ADAPTER_SEP}Folder implements fs{ADAPTER_SEP}Path {{ - path: String! - children: [fs{ADAPTER_SEP}Path!] - }} - - type fs{ADAPTER_SEP}File implements fs{ADAPTER_SEP}Path {{ - path: String! - size: Int! - extension: String! - Hash: String! - }} - - {types} - "#, - Schema::ALL_DIRECTIVE_DEFINITIONS, - roots = schema.roots.iter().fold(String::new(), |mut out, r| { - write!(out, "{}: {}, ", r.schema_name(), r.vertex_type()).unwrap(); - out - }), - types = schema.types.iter().fold(String::new(), |mut out, t| { - writeln!(out, "{}", t.schema_type()).unwrap(); - out - }), - ); - trace!(%schema, "Using schema"); - Schema::parse(schema).unwrap() -} - -pub struct TrustfallMultiAdapter { - pub plaixt: PlaixtAdapter, - pub filesystem: filesystem_trustfall_adapter::FileSystemAdapter, -} - -#[derive(Debug, Clone)] -pub enum TrustfallMultiVertex { - Plaixt(PlaixtVertex), - Filesystem(filesystem_trustfall_adapter::vertex::Vertex), -} - -impl AsVertex for TrustfallMultiVertex { - fn as_vertex(&self) -> Option<&filesystem_trustfall_adapter::vertex::Vertex> { - self.as_filesystem() - } - - fn into_vertex(self) -> Option { - self.as_filesystem().cloned() - } -} - -impl AsVertex for TrustfallMultiVertex { - fn as_vertex(&self) -> Option<&PlaixtVertex> { - self.as_plaixt() - } - - fn into_vertex(self) -> Option { - self.as_plaixt().cloned() - } -} - -impl TrustfallMultiVertex { - pub fn as_plaixt(&self) -> Option<&PlaixtVertex> { - if let Self::Plaixt(v) = self { - Some(v) - } else { - None - } - } - - pub fn as_filesystem( - &self, - ) -> Option< - &>::Vertex, - > { - if let Self::Filesystem(v) = self { - Some(v) - } else { - None - } - } -} - -impl<'v> Adapter<'v> for TrustfallMultiAdapter { - type Vertex = TrustfallMultiVertex; - - fn resolve_starting_vertices( - &self, - edge_name: &Arc, - parameters: &trustfall::provider::EdgeParameters, - resolve_info: &trustfall::provider::ResolveInfo, - ) -> trustfall::provider::VertexIterator<'v, Self::Vertex> { - let (adapter_name, edge_name) = edge_name.split_once(ADAPTER_SEP).unwrap(); - - trace!( - ?adapter_name, - ?edge_name, - "resolving top-level starting vertex" - ); - - match adapter_name { - "Plaixt" => { - let iter = self.plaixt.resolve_starting_vertices( - &Arc::from(edge_name), - parameters, - resolve_info, - ); - - Box::new(iter.map(TrustfallMultiVertex::Plaixt)) - } - "fs" => { - let iter = self.filesystem.resolve_starting_vertices( - &Arc::from(edge_name), - parameters, - resolve_info, - ); - - Box::new( - iter.inspect(|v| trace!(?v, "Got vertex")) - .map(TrustfallMultiVertex::Filesystem), - ) - } - _ => unreachable!(), - } - } - - fn resolve_property( - &self, - contexts: trustfall::provider::ContextIterator<'v, V>, - type_name: &Arc, - property_name: &Arc, - resolve_info: &trustfall::provider::ResolveInfo, - ) -> trustfall::provider::ContextOutcomeIterator<'v, V, FieldValue> - where - V: trustfall::provider::AsVertex + 'v, - { - trace!(?type_name, ?property_name, "resolving top-level property"); - let (adapter_name, type_name) = type_name - .split_once(ADAPTER_SEP) - .unwrap_or(("Plaixt", type_name)); - - match adapter_name { - "Plaixt" => { - let contexts = contexts.collect::>(); - - let properties = self.plaixt.resolve_property( - Box::new( - contexts - .clone() - .into_iter() - .map(|v| v.flat_map(&mut |v: V| v.into_vertex())), - ), - &Arc::from(type_name), - property_name, - resolve_info, - ); - - Box::new( - properties - .into_iter() - .zip(contexts) - .map(|((_ctx, name), og_ctx)| (og_ctx, name)), - ) - } - "fs" => { - let contexts = contexts.collect::>(); - - let properties = self.filesystem.resolve_property( - Box::new( - contexts - .clone() - .into_iter() - .map(|v| v.flat_map(&mut |v: V| v.into_vertex())) - .inspect(|v| trace!(?v, "Got vertex")), - ), - &Arc::from(type_name), - property_name, - resolve_info, - ); - - Box::new( - properties - .into_iter() - .zip(contexts) - .map(|((_ctx, name), og_ctx)| (og_ctx, name)), - ) - } - _ => unreachable!(), - } - } - - fn resolve_neighbors + 'v>( - &self, - contexts: trustfall::provider::ContextIterator<'v, V>, - type_name: &Arc, - edge_name: &Arc, - parameters: &trustfall::provider::EdgeParameters, - resolve_info: &trustfall::provider::ResolveEdgeInfo, - ) -> trustfall::provider::ContextOutcomeIterator< - 'v, - V, - trustfall::provider::VertexIterator<'v, Self::Vertex>, - > { - trace!(?type_name, ?edge_name, "Resolving top-level neighbor"); - let (adapter_name, type_name) = type_name.split_once(ADAPTER_SEP).unwrap(); - - match adapter_name { - "Plaixt" => { - let contexts = contexts.collect::>(); - - let properties = self.plaixt.resolve_neighbors( - Box::new( - contexts - .clone() - .into_iter() - .map(|v| v.flat_map(&mut |v: V| v.into_vertex())), - ), - &Arc::from(type_name), - edge_name, - parameters, - resolve_info, - ); - - Box::new( - properties - .into_iter() - .zip(contexts) - .map(|((_ctx, vals), og_ctx)| { - ( - og_ctx, - Box::new(vals.map(TrustfallMultiVertex::Plaixt)) as Box<_>, - ) - }), - ) - } - "fs" => { - let contexts = contexts.collect::>(); - - let properties = self.filesystem.resolve_neighbors( - Box::new( - contexts - .clone() - .into_iter() - .map(|v| v.flat_map(&mut |v: V| v.into_vertex())), - ), - &Arc::from(type_name), - edge_name, - parameters, - resolve_info, - ); - - Box::new( - properties - .into_iter() - .zip(contexts) - .map(|((_ctx, vals), og_ctx)| { - ( - og_ctx, - Box::new( - vals.inspect(|v| trace!(?v, "Got vertex")) - .map(TrustfallMultiVertex::Filesystem), - ) as Box<_>, - ) - }), - ) - } - _ => unreachable!(), - } - } - - fn resolve_coercion + 'v>( - &self, - contexts: trustfall::provider::ContextIterator<'v, V>, - type_name: &Arc, - coerce_to_type: &Arc, - resolve_info: &trustfall::provider::ResolveInfo, - ) -> trustfall::provider::ContextOutcomeIterator<'v, V, bool> { - trace!(?type_name, ?coerce_to_type, "Top-level coerce"); - let (adapter_name, coerce_to_type) = coerce_to_type.split_once(ADAPTER_SEP).unwrap(); - - match adapter_name { - "Plaixt" => { - let contexts = contexts.collect::>(); - - let properties = self.plaixt.resolve_coercion( - Box::new( - contexts - .clone() - .into_iter() - .map(|v| v.flat_map(&mut |v: V| v.into_vertex())), - ), - type_name, - &Arc::from(coerce_to_type), - resolve_info, - ); - - Box::new( - properties - .into_iter() - .zip(contexts) - .map(|((_ctx, val), og_ctx)| (og_ctx, val)), - ) - } - "fs" => { - let contexts = contexts.collect::>(); - - let properties = self.filesystem.resolve_coercion( - Box::new( - contexts - .clone() - .into_iter() - .map(|v| v.flat_map(&mut |v: V| v.into_vertex())), - ), - type_name, - &Arc::from(coerce_to_type), - resolve_info, - ); - - let type_name = type_name.clone(); - let coerce_to_type = coerce_to_type.to_string(); - Box::new(properties.into_iter().zip(contexts).map( - move |((_ctx, allowed), og_ctx)| { - trace!(?allowed, ?type_name, ?coerce_to_type, "Allowed coercion?"); - (og_ctx, allowed) - }, - )) - } - _ => unreachable!(), - } - } -} - -pub(crate) struct PlaixtAdapter { - pub(crate) records: Vec, -} - -#[derive(Clone, Debug)] -pub(crate) enum PlaixtVertex { - Record(Record), - Fields { - name: String, - values: BTreeMap, - }, -} - -impl PlaixtVertex { - pub(crate) fn as_fields(&self) -> Option<&BTreeMap> { - if let Self::Fields { values, .. } = self { - Some(values) - } else { - None - } - } - - pub(crate) fn as_record(&self) -> Option<&Record> { - if let Self::Record(v) = self { - Some(v) - } else { - None - } - } - - pub(crate) fn typename(&self) -> String { - match self { - PlaixtVertex::Record { .. } => "Record".to_string(), - PlaixtVertex::Fields { name, .. } => name.clone(), - } - } -} - -impl<'a> Adapter<'a> for PlaixtAdapter { - type Vertex = PlaixtVertex; - - fn resolve_starting_vertices( - &self, - edge_name: &Arc, - _parameters: &trustfall::provider::EdgeParameters, - _resolve_info: &trustfall::provider::ResolveInfo, - ) -> trustfall::provider::VertexIterator<'a, Self::Vertex> { - trace!(?edge_name, "Resolving start vertex"); - match edge_name.as_ref() { - "RecordsAll" => Box::new(self.records.clone().into_iter().map(PlaixtVertex::Record)), - _ => 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.typename().into(), - None => FieldValue::Null, - }; - - (ctx, value) - })), - (_, "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()), - } - }) - } - } - } - - 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>, - > { - trace!(?type_name, ?edge_name, "Resolving neighbors"); - 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(), - ) - }), - _ => resolve_neighbors_with(contexts, |c| todo!()), - _ => unreachable!("Could not resolve {edge_name}"), - } - } - - 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> { - 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/flake.nix b/flake.nix index e5c7893..2d2449a 100644 --- a/flake.nix +++ b/flake.nix @@ -16,8 +16,17 @@ }; }; - outputs = { self, nixpkgs, crane, flake-utils, rust-overlay, ... }: - flake-utils.lib.eachDefaultSystem (system: + outputs = + { + self, + nixpkgs, + crane, + flake-utils, + rust-overlay, + ... + }: + flake-utils.lib.eachDefaultSystem ( + system: let pkgs = import nixpkgs { inherit system; @@ -25,29 +34,51 @@ }; rustTarget = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml; - unstableRustTarget = pkgs.rust-bin.selectLatestNightlyWith (toolchain: toolchain.default.override { - extensions = [ "rust-src" "miri" "rustfmt" ]; - }); + unstableRustTarget = pkgs.rust-bin.selectLatestNightlyWith ( + toolchain: + toolchain.default.override { + extensions = [ + "rust-src" + "miri" + "rustfmt" + ]; + } + ); craneLib = (crane.mkLib pkgs).overrideToolchain rustTarget; unstableCraneLib = (crane.mkLib pkgs).overrideToolchain unstableRustTarget; tomlInfo = craneLib.crateNameFromCargoToml { cargoToml = ./Cargo.toml; }; - inherit (tomlInfo) pname version; + inherit (tomlInfo) version; + src = ./.; rustfmt' = pkgs.writeShellScriptBin "rustfmt" '' exec "${unstableRustTarget}/bin/rustfmt" "$@" ''; - cargoArtifacts = craneLib.buildDepsOnly { - inherit src; - cargoExtraArgs = "--all-features --all"; + common = { + src = ./.; + + buildInputs = [ + pkgs.openssl + pkgs.pkg-config + ]; }; - plaixt = craneLib.buildPackage { - inherit cargoArtifacts src version; - cargoExtraArgs = "--all-features --all"; - }; + cargoArtifacts = craneLib.buildDepsOnly ( + common + // { + cargoExtraArgs = "--all-features --all"; + } + ); + + plaixt = craneLib.buildPackage ( + common + // { + inherit cargoArtifacts version; + cargoExtraArgs = "--all-features --all"; + } + ); in rec { @@ -78,6 +109,8 @@ devShells.plaixt = pkgs.mkShell { buildInputs = [ ]; + inputsFrom = [ plaixt ]; + nativeBuildInputs = [ rustfmt' rustTarget From a6406ebd5af78c4940263f6e1ed9cb3d1b3815e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Sun, 9 Feb 2025 20:16:07 +0100 Subject: [PATCH 10/10] Make filesystem types handle their path impl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcel Müller --- Cargo.lock | 101 ---------------------- crates/plaixt/Cargo.toml | 1 - crates/plaixt/src/adapter/adapter_impl.rs | 31 ++++--- crates/plaixt/src/adapter/edges.rs | 17 +++- crates/plaixt/src/adapter/properties.rs | 49 ++++++++--- examples/changelog.plrecs | 4 + query | 16 ++-- 7 files changed, 78 insertions(+), 141 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 23b0445..39963e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -180,15 +180,6 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - [[package]] name = "bumpalo" version = "3.17.0" @@ -296,15 +287,6 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - [[package]] name = "crc32fast" version = "1.4.2" @@ -314,16 +296,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - [[package]] name = "deranged" version = "0.3.11" @@ -346,16 +318,6 @@ dependencies = [ "syn", ] -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", -] - [[package]] name = "displaydoc" version = "0.2.5" @@ -404,16 +366,6 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" -[[package]] -name = "filesystem-trustfall-adapter" -version = "0.1.1" -dependencies = [ - "serde", - "serde_json", - "sha256", - "trustfall", -] - [[package]] name = "flate2" version = "1.0.35" @@ -543,16 +495,6 @@ dependencies = [ "slab", ] -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - [[package]] name = "getrandom" version = "0.3.1" @@ -602,12 +544,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - [[package]] name = "http" version = "0.2.12" @@ -1343,7 +1279,6 @@ version = "0.1.0" dependencies = [ "camino", "clap", - "filesystem-trustfall-adapter", "futures", "human-panic", "jiff", @@ -1667,30 +1602,6 @@ dependencies = [ "serde", ] -[[package]] -name = "sha2" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "sha256" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18278f6a914fa3070aa316493f7d2ddfb9ac86ebc06fa3b83bffda487e9065b0" -dependencies = [ - "async-trait", - "bytes", - "hex", - "sha2", - "tokio", -] - [[package]] name = "sharded-slab" version = "0.1.7" @@ -2181,12 +2092,6 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" -[[package]] -name = "typenum" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" - [[package]] name = "ucd-trie" version = "0.1.7" @@ -2261,12 +2166,6 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - [[package]] name = "walkdir" version = "2.5.0" diff --git a/crates/plaixt/Cargo.toml b/crates/plaixt/Cargo.toml index 9dce34e..3ff7c07 100644 --- a/crates/plaixt/Cargo.toml +++ b/crates/plaixt/Cargo.toml @@ -8,7 +8,6 @@ license.workspace = true [dependencies] camino = { version = "1.1.9", features = ["serde", "serde1"] } clap = { version = "4.5.27", features = ["derive"] } -filesystem-trustfall-adapter = { version = "0.1.1", path = "../../../trustfall-adapters-zimbopro/filesystem-trustfall-adapter" } futures = "0.3.31" human-panic = "2.0.2" jiff = "0.1.28" diff --git a/crates/plaixt/src/adapter/adapter_impl.rs b/crates/plaixt/src/adapter/adapter_impl.rs index dbfa146..648f22a 100644 --- a/crates/plaixt/src/adapter/adapter_impl.rs +++ b/crates/plaixt/src/adapter/adapter_impl.rs @@ -4,6 +4,7 @@ use std::sync::Arc; use std::sync::OnceLock; use paperless_rs::PaperlessClient; +use tracing::debug; use trustfall::provider::resolve_coercion_using_schema; use trustfall::provider::resolve_property_with; use trustfall::provider::AsVertex; @@ -91,24 +92,18 @@ impl<'a> trustfall::provider::Adapter<'a> for Adapter { if property_name.as_ref() == "__typename" { return resolve_property_with(contexts, |vertex| vertex.typename().into()); } + + debug!(?type_name, ?property_name, "Resolving property"); + match type_name.as_ref() { "PaperlessDocument" => super::properties::resolve_paperless_document_property( contexts, property_name.as_ref(), resolve_info, ), - "Path" => super::properties::resolve_path_property( - contexts, - property_name.as_ref(), - resolve_info, - ), - "File" => super::properties::resolve_file_property( - contexts, - property_name.as_ref(), - resolve_info, - ), - "Directory" => super::properties::resolve_directory_property( + "Path" | "File" | "Directory" => super::properties::resolve_fs_property( contexts, + type_name.as_ref(), property_name.as_ref(), resolve_info, ), @@ -159,12 +154,13 @@ impl<'a> trustfall::provider::Adapter<'a> for Adapter { fn resolve_coercion + 'a>( &self, contexts: ContextIterator<'a, V>, - _type_name: &Arc, + type_name: &Arc, coerce_to_type: &Arc, _resolve_info: &ResolveInfo, ) -> ContextOutcomeIterator<'a, V, bool> { let schema = self.schema.clone(); let coerce_to_type = coerce_to_type.clone(); + debug!(?coerce_to_type, ?type_name, "Trying to coerce"); Box::new(contexts.map(move |ctx| { let subtypes: BTreeSet<_> = schema @@ -176,8 +172,15 @@ impl<'a> trustfall::provider::Adapter<'a> for Adapter { None => (ctx, false), Some(vertex) => { let typename = vertex.typename(); - let can_coerce = subtypes.contains(typename); - (ctx, can_coerce) + debug!(?coerce_to_type, ?vertex, "Trying to coerce"); + if let Some(rec) = vertex.as_record() { + let is_rec = coerce_to_type.starts_with("p_"); + let is_kind = rec.kind == coerce_to_type.as_ref()[2..]; + (ctx, is_rec && is_kind) + } else { + let can_coerce = subtypes.contains(typename); + (ctx, can_coerce) + } } } })) diff --git a/crates/plaixt/src/adapter/edges.rs b/crates/plaixt/src/adapter/edges.rs index 54b841b..bda40f6 100644 --- a/crates/plaixt/src/adapter/edges.rs +++ b/crates/plaixt/src/adapter/edges.rs @@ -1,6 +1,7 @@ use std::collections::BTreeMap; use std::sync::Arc; +use camino::Utf8PathBuf; use trustfall::provider::resolve_neighbors_with; use trustfall::provider::AsVertex; use trustfall::provider::ContextIterator; @@ -11,6 +12,7 @@ use trustfall::provider::VertexIterator; use super::Vertex; use crate::parsing::DefinitionKind; +use crate::parsing::Record; pub(super) fn resolve_directory_edge<'a, V: AsVertex + 'a>( contexts: ContextIterator<'a, V>, @@ -76,10 +78,19 @@ pub(super) fn resolve_record_edge<'a, V: AsVertex + 'a>( let def = &definitions[&rec.kind][edge_name.as_ref()]; match def { - DefinitionKind::Path => Box::new(std::iter::once(Vertex::Path( - rec.fields[edge_name.as_ref()].as_string().unwrap().into(), - ))), + DefinitionKind::Path => Box::new(std::iter::once(path_from_rec(rec, &edge_name))), _ => unreachable!("Only `Path` can appear as edge for now"), } }) } + +fn path_from_rec(rec: &Record, edge_name: &str) -> Vertex { + let pathb = Utf8PathBuf::from(rec.fields[edge_name].as_string().unwrap()); + if pathb.is_file() { + Vertex::File(pathb) + } else if pathb.is_dir() { + Vertex::Directory(pathb) + } else { + Vertex::Path(pathb) + } +} diff --git a/crates/plaixt/src/adapter/properties.rs b/crates/plaixt/src/adapter/properties.rs index 4f8d955..6a13dd4 100644 --- a/crates/plaixt/src/adapter/properties.rs +++ b/crates/plaixt/src/adapter/properties.rs @@ -1,8 +1,6 @@ -use std::collections::BTreeMap; use std::sync::Arc; use kdl::KdlValue; -use paperless_rs::PaperlessClient; use trustfall::provider::field_property; use trustfall::provider::resolve_property_with; use trustfall::provider::AsVertex; @@ -13,26 +11,49 @@ use trustfall::FieldValue; use super::vertex::Vertex; +pub(super) fn resolve_fs_property<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, + type_name: &str, + property_name: &str, + resolve_info: &ResolveInfo, +) -> ContextOutcomeIterator<'a, V, FieldValue> { + match (type_name, property_name) { + (_, "exists" | "basename" | "path") => { + resolve_path_property(contexts, property_name, resolve_info) + } + ("Directory", _) => resolve_directory_property(contexts, property_name, resolve_info), + ("File", _) => resolve_file_property(contexts, property_name, resolve_info), + _ => { + unreachable!( + "attempted to read unexpected property '{property_name}' on type '{type_name}'" + ) + } + } +} + pub(super) fn resolve_path_property<'a, V: AsVertex + 'a>( contexts: ContextIterator<'a, V>, property_name: &str, _resolve_info: &ResolveInfo, ) -> ContextOutcomeIterator<'a, V, FieldValue> { match property_name { - "exists" => resolve_property_with(contexts, move |v: &Vertex| { - let path = v.as_path().expect("vertex was not a Path"); - - path.exists().into() + "exists" => resolve_property_with(contexts, move |v: &Vertex| match v { + Vertex::Path(p) | Vertex::File(p) | Vertex::Directory(p) => p.exists().into(), + _ => { + panic!("Vertex was not a filesystem type") + } }), - "basename" => resolve_property_with(contexts, move |v: &Vertex| { - let path = v.as_path().expect("vertex was not a Path"); - - path.file_name().into() + "basename" => resolve_property_with(contexts, move |v: &Vertex| match v { + Vertex::Path(p) | Vertex::File(p) | Vertex::Directory(p) => p.file_name().into(), + _ => { + panic!("Vertex was not a filesystem type") + } }), - "path" => resolve_property_with(contexts, move |v: &Vertex| { - let path = v.as_path().expect("vertex was not a Path"); - - path.to_string().into() + "path" => resolve_property_with(contexts, move |v: &Vertex| match v { + Vertex::Path(p) | Vertex::File(p) | Vertex::Directory(p) => p.to_string().into(), + _ => { + panic!("Vertex was not a filesystem type") + } }), _ => { unreachable!("attempted to read unexpected property '{property_name}' on type 'Path'") diff --git a/examples/changelog.plrecs b/examples/changelog.plrecs index af02d2a..7e5be24 100644 --- a/examples/changelog.plrecs +++ b/examples/changelog.plrecs @@ -25,3 +25,7 @@ changelog "2025-02-07" { file_test "2025-02-08" { path "Cargo.toml" } + +file_test "2025-02-08" { + path "/etc" +} diff --git a/query b/query index 1824356..d6ba545 100644 --- a/query +++ b/query @@ -1,12 +1,12 @@ { - Plaixt__RecordsAll { - ... on Plaixt__file_test { - at @output - kind @output - fields { - path { - ... on fs__File { - size @output + Records { + ... on p_file_test { + _at @output + _kind @output + path { + ... on Directory { + Children @recurse(depth: 10) { + path @output } } }