Make it actually work with trustfall and dynamic data

Signed-off-by: Marcel Müller <neikos@neikos.email>
This commit is contained in:
Marcel Müller 2025-02-08 09:40:52 +01:00
parent 6501b42328
commit 8a453a44f9
5 changed files with 203 additions and 46 deletions

View file

@ -1,6 +1,7 @@
#![allow(dead_code)] #![allow(dead_code)]
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::io::Read;
use std::sync::Arc; use std::sync::Arc;
use camino::Utf8PathBuf; use camino::Utf8PathBuf;
@ -8,11 +9,18 @@ use clap::Parser;
use clap::Subcommand; use clap::Subcommand;
use clap::ValueHint; use clap::ValueHint;
use human_panic::Metadata; use human_panic::Metadata;
use kdl::KdlValue;
use miette::IntoDiagnostic;
use parsing::Definition; use parsing::Definition;
use parsing::Record; use parsing::Record;
use tracing::debug;
use tracing::info; use tracing::info;
use tracing::trace;
use tracing_subscriber::EnvFilter;
use trustfall::execute_query; use trustfall::execute_query;
use trustfall::provider::field_property; 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::resolve_property_with;
use trustfall::provider::Adapter; use trustfall::provider::Adapter;
use trustfall::FieldValue; use trustfall::FieldValue;
@ -41,6 +49,7 @@ struct Args {
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
enum ArgMode { enum ArgMode {
Dump, Dump,
Query,
} }
#[tokio::main] #[tokio::main]
@ -50,7 +59,10 @@ async fn main() -> miette::Result<()> {
.authors(env!("CARGO_PKG_AUTHORS")) .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(); let args = Args::parse();
@ -63,37 +75,78 @@ async fn main() -> miette::Result<()> {
let schema = to_schema(&definitions); 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::<Vec<_>>();
match args.mode { 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::<Arc<str>, FieldValue>::from([("search".into(), "trust".into())]),
)
.unwrap()
.collect::<Vec<_>>();
info!("Got records: {result:#?}"); info!("Got records: {result:#?}");
} }
ArgMode::Dump => {
print_records(&records);
}
} }
Ok(()) Ok(())
} }
fn to_schema(_definitions: &BTreeMap<String, Vec<Definition>>) -> Schema { fn print_records(records: &[Record]) {
Schema::parse(format!( 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<String, Vec<Definition>>) -> 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::<Vec<_>>()
.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::<Vec<_>>()
.join("");
let schema = format!(
r#"schema {{ r#"schema {{
query: RootSchemaQuery query: RootSchemaQuery
}} }}
@ -107,18 +160,56 @@ interface Record {{
at: String!, at: String!,
kind: String!, kind: String!,
}} }}
{}
"#, "#,
Schema::ALL_DIRECTIVE_DEFINITIONS Schema::ALL_DIRECTIVE_DEFINITIONS,
)) custom_schemas
.unwrap() );
trace!(%schema, "Using schema");
Schema::parse(schema).unwrap()
} }
struct PlaixtAdapter { struct PlaixtAdapter {
records: Vec<Record>, records: Vec<Record>,
} }
#[derive(Clone, Debug)]
enum PlaixtVertex {
Record(Record),
Fields {
name: String,
values: BTreeMap<String, KdlValue>,
},
}
impl PlaixtVertex {
fn as_fields(&self) -> Option<&BTreeMap<String, KdlValue>> {
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 { impl<'a> Adapter<'a> for PlaixtAdapter {
type Vertex = Record; type Vertex = PlaixtVertex;
fn resolve_starting_vertices( fn resolve_starting_vertices(
&self, &self,
@ -127,7 +218,7 @@ impl<'a> Adapter<'a> for PlaixtAdapter {
_resolve_info: &trustfall::provider::ResolveInfo, _resolve_info: &trustfall::provider::ResolveInfo,
) -> trustfall::provider::VertexIterator<'a, Self::Vertex> { ) -> trustfall::provider::VertexIterator<'a, Self::Vertex> {
match edge_name.as_ref() { 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!(), _ => unreachable!(),
} }
} }
@ -142,25 +233,41 @@ impl<'a> Adapter<'a> for PlaixtAdapter {
match (type_name.as_ref(), property_name.as_ref()) { match (type_name.as_ref(), property_name.as_ref()) {
(_, "__typename") => Box::new(contexts.map(|ctx| { (_, "__typename") => Box::new(contexts.map(|ctx| {
let value = match ctx.active_vertex() { let value = match ctx.active_vertex() {
Some(_record) => "Record".into(), Some(_record) => _record.typename().into(),
None => FieldValue::Null, None => FieldValue::Null,
}; };
(ctx, value) (ctx, value)
})), })),
("Record", "at") => { (_, "at") => resolve_property_with(
resolve_property_with(contexts, field_property!(at, { at.to_string().into() })) 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<V: trustfall::provider::AsVertex<Self::Vertex> + 'a>( fn resolve_neighbors<V: trustfall::provider::AsVertex<Self::Vertex> + 'a>(
&self, &self,
_contexts: trustfall::provider::ContextIterator<'a, V>, contexts: trustfall::provider::ContextIterator<'a, V>,
_type_name: &Arc<str>, _type_name: &Arc<str>,
_edge_name: &Arc<str>, edge_name: &Arc<str>,
_parameters: &trustfall::provider::EdgeParameters, _parameters: &trustfall::provider::EdgeParameters,
_resolve_info: &trustfall::provider::ResolveEdgeInfo, _resolve_info: &trustfall::provider::ResolveEdgeInfo,
) -> trustfall::provider::ContextOutcomeIterator< ) -> trustfall::provider::ContextOutcomeIterator<
@ -168,16 +275,34 @@ impl<'a> Adapter<'a> for PlaixtAdapter {
V, V,
trustfall::provider::VertexIterator<'a, Self::Vertex>, 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<V: trustfall::provider::AsVertex<Self::Vertex> + 'a>( fn resolve_coercion<V: trustfall::provider::AsVertex<Self::Vertex> + 'a>(
&self, &self,
_contexts: trustfall::provider::ContextIterator<'a, V>, contexts: trustfall::provider::ContextIterator<'a, V>,
_type_name: &Arc<str>, type_name: &Arc<str>,
_coerce_to_type: &Arc<str>, coerce_to_type: &Arc<str>,
_resolve_info: &trustfall::provider::ResolveInfo, _resolve_info: &trustfall::provider::ResolveInfo,
) -> trustfall::provider::ContextOutcomeIterator<'a, V, bool> { ) -> 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)
})
} }
} }

View file

@ -160,6 +160,13 @@ pub enum DefinitionKind {
} }
impl 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> { pub(crate) fn validate(&self, val: &KdlValue) -> Result<(), String> {
match self { match self {
DefinitionKind::String => val DefinitionKind::String => val
@ -286,6 +293,18 @@ pub(crate) fn parse_definition(bytes: &str) -> miette::Result<Vec<Definition>> {
} }
}; };
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)) Ok((field.name().to_string(), kind))
}) })
.collect::<miette::Result<_>>()?; .collect::<miette::Result<_>>()?;

View file

@ -1,23 +1,23 @@
changelog "2025-01-29" { changelog "2025-01-29" {
title "Added parsing of plaixt definitions" title "Added parsing of plaixt definitions"
version "0.1.0" version "0.1.0"
kind "Feature" type "Feature"
} }
changelog "2025-01-30 09:10:59+01:00" { changelog "2025-01-30 09:10:59+01:00" {
title "Added parsing of plaixt records" title "Added parsing of plaixt records"
version "0.1.0" version "0.1.0"
kind "Feature" type "Feature"
} }
changelog "2025-02-01" { changelog "2025-02-01" {
title "Added CLI options" title "Added CLI options"
version "0.1.0" version "0.1.0"
kind "Feature" type "Feature"
} }
changelog "2025-02-07" { changelog "2025-02-07" {
title "Added trustfall as a query frontend" title "Added trustfall as a query frontend"
version "0.1.0" version "0.1.0"
kind "Feature" type "Feature"
} }

View file

@ -4,6 +4,6 @@ define since="2025-01-29 20:27:30+01:00" {
fields { fields {
title is=string title is=string
version is=string version is=string
kind { oneOf "Bugfix" "Feature" "Chore" } type { oneOf "Bugfix" "Feature" "Chore" }
} }
} }

13
query Normal file
View file

@ -0,0 +1,13 @@
{
RecordsAll {
... on changelog {
at @output
kind @output
fields {
title @output @filter(op: "has_substring", value: ["$search"])
version @output
type @output
}
}
}
}