Adapt to new trustfall model
Signed-off-by: Marcel Müller <neikos@neikos.email>
This commit is contained in:
parent
e600807376
commit
11c3a8de94
14 changed files with 1632 additions and 685 deletions
941
Cargo.lock
generated
941
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -15,6 +15,7 @@ jiff = "0.1.28"
|
||||||
kdl.workspace = true
|
kdl.workspace = true
|
||||||
miette = { version = "7.4.0", features = ["fancy", "syntect-highlighter"] }
|
miette = { version = "7.4.0", features = ["fancy", "syntect-highlighter"] }
|
||||||
owo-colors = "4.1.0"
|
owo-colors = "4.1.0"
|
||||||
|
paperless-rs = "0.1.5"
|
||||||
tokio = { version = "1.43.0", features = ["full"] }
|
tokio = { version = "1.43.0", features = ["full"] }
|
||||||
tokio-stream = { version = "0.1.17", features = ["full"] }
|
tokio-stream = { version = "0.1.17", features = ["full"] }
|
||||||
tracing = "0.1.41"
|
tracing = "0.1.41"
|
||||||
|
|
|
||||||
185
crates/plaixt/src/adapter/adapter_impl.rs
Normal file
185
crates/plaixt/src/adapter/adapter_impl.rs
Normal file
|
|
@ -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<Schema> = OnceLock::new();
|
||||||
|
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct Adapter {
|
||||||
|
schema: Arc<Schema>,
|
||||||
|
records: Vec<Record>,
|
||||||
|
definitions: Arc<BTreeMap<String, BTreeMap<String, DefinitionKind>>>,
|
||||||
|
paperless_client: Option<PaperlessClient>,
|
||||||
|
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<Record>,
|
||||||
|
definitions: BTreeMap<String, BTreeMap<String, DefinitionKind>>,
|
||||||
|
paperless_client: Option<PaperlessClient>,
|
||||||
|
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<str>,
|
||||||
|
_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<V: AsVertex<Self::Vertex> + 'a>(
|
||||||
|
&self,
|
||||||
|
contexts: ContextIterator<'a, V>,
|
||||||
|
type_name: &Arc<str>,
|
||||||
|
property_name: &Arc<str>,
|
||||||
|
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<V: AsVertex<Self::Vertex> + 'a>(
|
||||||
|
&self,
|
||||||
|
contexts: ContextIterator<'a, V>,
|
||||||
|
type_name: &Arc<str>,
|
||||||
|
edge_name: &Arc<str>,
|
||||||
|
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<V: AsVertex<Self::Vertex> + 'a>(
|
||||||
|
&self,
|
||||||
|
contexts: ContextIterator<'a, V>,
|
||||||
|
_type_name: &Arc<str>,
|
||||||
|
coerce_to_type: &Arc<str>,
|
||||||
|
_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::<Vertex>() {
|
||||||
|
None => (ctx, false),
|
||||||
|
Some(vertex) => {
|
||||||
|
let typename = vertex.typename();
|
||||||
|
let can_coerce = subtypes.contains(typename);
|
||||||
|
(ctx, can_coerce)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
85
crates/plaixt/src/adapter/edges.rs
Normal file
85
crates/plaixt/src/adapter/edges.rs
Normal file
|
|
@ -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<Vertex> + '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<Vertex> + '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<impl Iterator<Item = Vertex>> {
|
||||||
|
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<dyn Iterator<Item = Vertex>> = Box::new(i);
|
||||||
|
it
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| Box::new(std::iter::empty()))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn resolve_record_edge<'a, V: AsVertex<Vertex> + 'a>(
|
||||||
|
contexts: ContextIterator<'a, V>,
|
||||||
|
edge_name: &Arc<str>,
|
||||||
|
_parameters: &EdgeParameters,
|
||||||
|
_resolve_info: &ResolveEdgeInfo,
|
||||||
|
definitions: &Arc<BTreeMap<String, BTreeMap<String, DefinitionKind>>>,
|
||||||
|
) -> 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"),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
16
crates/plaixt/src/adapter/entrypoints.rs
Normal file
16
crates/plaixt/src/adapter/entrypoints.rs
Normal file
|
|
@ -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))
|
||||||
|
}
|
||||||
55
crates/plaixt/src/adapter/mod.rs
Normal file
55
crates/plaixt/src/adapter/mod.rs
Normal file
|
|
@ -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<CustomVertex> {
|
||||||
|
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::<Vec<_>>();
|
||||||
|
|
||||||
|
let definition = format!("type {name} implements Record {{ {} }}", fields.join(","));
|
||||||
|
|
||||||
|
[CustomVertex { name, definition }].into_iter().collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn to_schema(
|
||||||
|
definitions: &std::collections::BTreeMap<String, Vec<crate::parsing::Definition>>,
|
||||||
|
) -> 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::<Vec<_>>()
|
||||||
|
.join("\n");
|
||||||
|
|
||||||
|
let input = format!("{base_text}{generated}");
|
||||||
|
trace!(%input, "Using schema");
|
||||||
|
Schema::parse(input).unwrap()
|
||||||
|
}
|
||||||
159
crates/plaixt/src/adapter/properties.rs
Normal file
159
crates/plaixt/src/adapter/properties.rs
Normal file
|
|
@ -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<Vertex> + '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<Vertex> + '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<Vertex> + '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<Vertex> + '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<Vertex> + 'a>(
|
||||||
|
contexts: ContextIterator<'a, V>,
|
||||||
|
property_name: &Arc<str>,
|
||||||
|
_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()),
|
||||||
|
}
|
||||||
|
}
|
||||||
88
crates/plaixt/src/adapter/schema.graphql
Normal file
88
crates/plaixt/src/adapter/schema.graphql
Normal file
|
|
@ -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!
|
||||||
|
}
|
||||||
16
crates/plaixt/src/adapter/tests.rs
Normal file
16
crates/plaixt/src/adapter/tests.rs
Normal file
|
|
@ -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);
|
||||||
|
}
|
||||||
15
crates/plaixt/src/adapter/vertex.rs
Normal file
15
crates/plaixt/src/adapter/vertex.rs
Normal file
|
|
@ -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),
|
||||||
|
}
|
||||||
|
|
@ -8,7 +8,6 @@ use camino::Utf8PathBuf;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use clap::Subcommand;
|
use clap::Subcommand;
|
||||||
use clap::ValueHint;
|
use clap::ValueHint;
|
||||||
use filesystem_trustfall_adapter::FileSystemAdapter;
|
|
||||||
use human_panic::Metadata;
|
use human_panic::Metadata;
|
||||||
use miette::IntoDiagnostic;
|
use miette::IntoDiagnostic;
|
||||||
use parsing::Definition;
|
use parsing::Definition;
|
||||||
|
|
@ -18,9 +17,9 @@ use tracing_subscriber::EnvFilter;
|
||||||
use trustfall::execute_query;
|
use trustfall::execute_query;
|
||||||
use trustfall::FieldValue;
|
use trustfall::FieldValue;
|
||||||
|
|
||||||
|
mod adapter;
|
||||||
mod config;
|
mod config;
|
||||||
mod parsing;
|
mod parsing;
|
||||||
mod trustfall_plaixt;
|
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
struct Args {
|
struct Args {
|
||||||
|
|
@ -97,14 +96,19 @@ async fn main() -> miette::Result<()> {
|
||||||
fn get_schema_and_adapter(
|
fn get_schema_and_adapter(
|
||||||
definitions: &BTreeMap<String, Vec<Definition>>,
|
definitions: &BTreeMap<String, Vec<Definition>>,
|
||||||
records: Vec<Record>,
|
records: Vec<Record>,
|
||||||
) -> (trustfall::Schema, trustfall_plaixt::TrustfallMultiAdapter) {
|
) -> (trustfall::Schema, adapter::Adapter) {
|
||||||
let schema = trustfall_plaixt::to_schema(definitions);
|
let schema = adapter::to_schema(definitions);
|
||||||
let adapter = trustfall_plaixt::TrustfallMultiAdapter {
|
let definitions = definitions
|
||||||
plaixt: trustfall_plaixt::PlaixtAdapter {
|
.iter()
|
||||||
records: records.clone(),
|
.map(|(name, def)| (name.clone(), def.last().cloned().unwrap().fields))
|
||||||
},
|
.collect();
|
||||||
filesystem: FileSystemAdapter::new(),
|
let adapter = adapter::Adapter::new(
|
||||||
};
|
schema.clone(),
|
||||||
|
records,
|
||||||
|
definitions,
|
||||||
|
None,
|
||||||
|
tokio::runtime::Handle::current(),
|
||||||
|
);
|
||||||
(schema, adapter)
|
(schema, adapter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use camino::Utf8Path;
|
use camino::Utf8Path;
|
||||||
use camino::Utf8PathBuf;
|
use camino::Utf8PathBuf;
|
||||||
|
|
@ -15,8 +14,6 @@ use miette::NamedSource;
|
||||||
use owo_colors::OwoColorize;
|
use owo_colors::OwoColorize;
|
||||||
use tokio_stream::wrappers::ReadDirStream;
|
use tokio_stream::wrappers::ReadDirStream;
|
||||||
|
|
||||||
use crate::trustfall_plaixt::ADAPTER_SEP;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Record {
|
pub struct Record {
|
||||||
pub(crate) kind: String,
|
pub(crate) kind: String,
|
||||||
|
|
@ -155,7 +152,7 @@ pub(crate) async fn load_records(
|
||||||
Ok(defs)
|
Ok(defs)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum DefinitionKind {
|
pub enum DefinitionKind {
|
||||||
String,
|
String,
|
||||||
Path,
|
Path,
|
||||||
|
|
@ -163,11 +160,11 @@ pub enum DefinitionKind {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DefinitionKind {
|
impl DefinitionKind {
|
||||||
pub(crate) fn trustfall_kind(&self) -> String {
|
pub(crate) fn trustfall_kind(&self, _namespace: &str) -> String {
|
||||||
match self {
|
match self {
|
||||||
DefinitionKind::String => String::from("String"),
|
DefinitionKind::String => String::from("String!"),
|
||||||
DefinitionKind::Path => format!("fs{ADAPTER_SEP}Path"),
|
DefinitionKind::Path => String::from("Path!"),
|
||||||
DefinitionKind::OneOf(_vecs) => String::from("String"),
|
DefinitionKind::OneOf(_vecs) => String::from("String!"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -188,6 +185,23 @@ impl DefinitionKind {
|
||||||
.ok_or_else(|| format!("Expected one of: {}", options.join(", "))),
|
.ok_or_else(|| format!("Expected one of: {}", options.join(", "))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn extra_trustfall_kinds(
|
||||||
|
&self,
|
||||||
|
namespace: &str,
|
||||||
|
) -> Vec<crate::adapter::CustomVertex> {
|
||||||
|
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 {
|
impl TryFrom<&str> for DefinitionKind {
|
||||||
|
|
@ -201,11 +215,11 @@ impl TryFrom<&str> for DefinitionKind {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Definition {
|
pub struct Definition {
|
||||||
pub(crate) name: String,
|
pub(crate) name: String,
|
||||||
pub(crate) since: Timestamp,
|
pub(crate) since: Timestamp,
|
||||||
pub(crate) fields: HashMap<String, DefinitionKind>,
|
pub(crate) fields: BTreeMap<String, DefinitionKind>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn parse_definition(
|
pub(crate) fn parse_definition(
|
||||||
|
|
|
||||||
|
|
@ -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<String, String>,
|
|
||||||
implements: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VertexType {
|
|
||||||
pub fn new(
|
|
||||||
adapter_name: String,
|
|
||||||
vertex_name: String,
|
|
||||||
vertex_fields: HashMap<String, String>,
|
|
||||||
implements: Vec<String>,
|
|
||||||
) -> 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<StartingVertex>,
|
|
||||||
types: Vec<VertexType>,
|
|
||||||
}
|
|
||||||
|
|
||||||
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<String, Vec<Definition>>) -> 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<filesystem_trustfall_adapter::vertex::Vertex> for TrustfallMultiVertex {
|
|
||||||
fn as_vertex(&self) -> Option<&filesystem_trustfall_adapter::vertex::Vertex> {
|
|
||||||
self.as_filesystem()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn into_vertex(self) -> Option<filesystem_trustfall_adapter::vertex::Vertex> {
|
|
||||||
self.as_filesystem().cloned()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsVertex<PlaixtVertex> for TrustfallMultiVertex {
|
|
||||||
fn as_vertex(&self) -> Option<&PlaixtVertex> {
|
|
||||||
self.as_plaixt()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn into_vertex(self) -> Option<PlaixtVertex> {
|
|
||||||
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<
|
|
||||||
&<filesystem_trustfall_adapter::FileSystemAdapter as trustfall::provider::Adapter<
|
|
||||||
'static,
|
|
||||||
>>::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<str>,
|
|
||||||
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<V>(
|
|
||||||
&self,
|
|
||||||
contexts: trustfall::provider::ContextIterator<'v, V>,
|
|
||||||
type_name: &Arc<str>,
|
|
||||||
property_name: &Arc<str>,
|
|
||||||
resolve_info: &trustfall::provider::ResolveInfo,
|
|
||||||
) -> trustfall::provider::ContextOutcomeIterator<'v, V, FieldValue>
|
|
||||||
where
|
|
||||||
V: trustfall::provider::AsVertex<Self::Vertex> + '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::<Vec<_>>();
|
|
||||||
|
|
||||||
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::<Vec<_>>();
|
|
||||||
|
|
||||||
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: trustfall::provider::AsVertex<Self::Vertex> + 'v>(
|
|
||||||
&self,
|
|
||||||
contexts: trustfall::provider::ContextIterator<'v, V>,
|
|
||||||
type_name: &Arc<str>,
|
|
||||||
edge_name: &Arc<str>,
|
|
||||||
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::<Vec<_>>();
|
|
||||||
|
|
||||||
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::<Vec<_>>();
|
|
||||||
|
|
||||||
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: trustfall::provider::AsVertex<Self::Vertex> + 'v>(
|
|
||||||
&self,
|
|
||||||
contexts: trustfall::provider::ContextIterator<'v, V>,
|
|
||||||
type_name: &Arc<str>,
|
|
||||||
coerce_to_type: &Arc<str>,
|
|
||||||
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::<Vec<_>>();
|
|
||||||
|
|
||||||
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::<Vec<_>>();
|
|
||||||
|
|
||||||
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<Record>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub(crate) enum PlaixtVertex {
|
|
||||||
Record(Record),
|
|
||||||
Fields {
|
|
||||||
name: String,
|
|
||||||
values: BTreeMap<String, KdlValue>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PlaixtVertex {
|
|
||||||
pub(crate) fn as_fields(&self) -> Option<&BTreeMap<String, KdlValue>> {
|
|
||||||
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<str>,
|
|
||||||
_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<V: trustfall::provider::AsVertex<Self::Vertex> + 'a>(
|
|
||||||
&self,
|
|
||||||
contexts: trustfall::provider::ContextIterator<'a, V>,
|
|
||||||
type_name: &Arc<str>,
|
|
||||||
property_name: &Arc<str>,
|
|
||||||
_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<V: trustfall::provider::AsVertex<Self::Vertex> + 'a>(
|
|
||||||
&self,
|
|
||||||
contexts: trustfall::provider::ContextIterator<'a, V>,
|
|
||||||
type_name: &Arc<str>,
|
|
||||||
edge_name: &Arc<str>,
|
|
||||||
_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<V: trustfall::provider::AsVertex<Self::Vertex> + 'a>(
|
|
||||||
&self,
|
|
||||||
contexts: trustfall::provider::ContextIterator<'a, V>,
|
|
||||||
type_name: &Arc<str>,
|
|
||||||
coerce_to_type: &Arc<str>,
|
|
||||||
_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)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
59
flake.nix
59
flake.nix
|
|
@ -16,8 +16,17 @@
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = { self, nixpkgs, crane, flake-utils, rust-overlay, ... }:
|
outputs =
|
||||||
flake-utils.lib.eachDefaultSystem (system:
|
{
|
||||||
|
self,
|
||||||
|
nixpkgs,
|
||||||
|
crane,
|
||||||
|
flake-utils,
|
||||||
|
rust-overlay,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
flake-utils.lib.eachDefaultSystem (
|
||||||
|
system:
|
||||||
let
|
let
|
||||||
pkgs = import nixpkgs {
|
pkgs = import nixpkgs {
|
||||||
inherit system;
|
inherit system;
|
||||||
|
|
@ -25,29 +34,51 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
rustTarget = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
|
rustTarget = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
|
||||||
unstableRustTarget = pkgs.rust-bin.selectLatestNightlyWith (toolchain: toolchain.default.override {
|
unstableRustTarget = pkgs.rust-bin.selectLatestNightlyWith (
|
||||||
extensions = [ "rust-src" "miri" "rustfmt" ];
|
toolchain:
|
||||||
});
|
toolchain.default.override {
|
||||||
|
extensions = [
|
||||||
|
"rust-src"
|
||||||
|
"miri"
|
||||||
|
"rustfmt"
|
||||||
|
];
|
||||||
|
}
|
||||||
|
);
|
||||||
craneLib = (crane.mkLib pkgs).overrideToolchain rustTarget;
|
craneLib = (crane.mkLib pkgs).overrideToolchain rustTarget;
|
||||||
unstableCraneLib = (crane.mkLib pkgs).overrideToolchain unstableRustTarget;
|
unstableCraneLib = (crane.mkLib pkgs).overrideToolchain unstableRustTarget;
|
||||||
|
|
||||||
tomlInfo = craneLib.crateNameFromCargoToml { cargoToml = ./Cargo.toml; };
|
tomlInfo = craneLib.crateNameFromCargoToml { cargoToml = ./Cargo.toml; };
|
||||||
inherit (tomlInfo) pname version;
|
inherit (tomlInfo) version;
|
||||||
|
|
||||||
src = ./.;
|
src = ./.;
|
||||||
|
|
||||||
rustfmt' = pkgs.writeShellScriptBin "rustfmt" ''
|
rustfmt' = pkgs.writeShellScriptBin "rustfmt" ''
|
||||||
exec "${unstableRustTarget}/bin/rustfmt" "$@"
|
exec "${unstableRustTarget}/bin/rustfmt" "$@"
|
||||||
'';
|
'';
|
||||||
|
|
||||||
cargoArtifacts = craneLib.buildDepsOnly {
|
common = {
|
||||||
inherit src;
|
src = ./.;
|
||||||
cargoExtraArgs = "--all-features --all";
|
|
||||||
|
buildInputs = [
|
||||||
|
pkgs.openssl
|
||||||
|
pkgs.pkg-config
|
||||||
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
plaixt = craneLib.buildPackage {
|
cargoArtifacts = craneLib.buildDepsOnly (
|
||||||
inherit cargoArtifacts src version;
|
common
|
||||||
cargoExtraArgs = "--all-features --all";
|
// {
|
||||||
};
|
cargoExtraArgs = "--all-features --all";
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
plaixt = craneLib.buildPackage (
|
||||||
|
common
|
||||||
|
// {
|
||||||
|
inherit cargoArtifacts version;
|
||||||
|
cargoExtraArgs = "--all-features --all";
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
in
|
in
|
||||||
rec {
|
rec {
|
||||||
|
|
@ -78,6 +109,8 @@
|
||||||
devShells.plaixt = pkgs.mkShell {
|
devShells.plaixt = pkgs.mkShell {
|
||||||
buildInputs = [ ];
|
buildInputs = [ ];
|
||||||
|
|
||||||
|
inputsFrom = [ plaixt ];
|
||||||
|
|
||||||
nativeBuildInputs = [
|
nativeBuildInputs = [
|
||||||
rustfmt'
|
rustfmt'
|
||||||
rustTarget
|
rustTarget
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue