Make it actually work with trustfall and dynamic data
Signed-off-by: Marcel Müller <neikos@neikos.email>
This commit is contained in:
parent
6501b42328
commit
8a453a44f9
5 changed files with 203 additions and 46 deletions
|
|
@ -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::<Vec<_>>();
|
||||
|
||||
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:#?}");
|
||||
}
|
||||
ArgMode::Dump => {
|
||||
print_records(&records);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn to_schema(_definitions: &BTreeMap<String, Vec<Definition>>) -> 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<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 {{
|
||||
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<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 {
|
||||
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<V: trustfall::provider::AsVertex<Self::Vertex> + 'a>(
|
||||
&self,
|
||||
_contexts: trustfall::provider::ContextIterator<'a, V>,
|
||||
contexts: trustfall::provider::ContextIterator<'a, V>,
|
||||
_type_name: &Arc<str>,
|
||||
_edge_name: &Arc<str>,
|
||||
edge_name: &Arc<str>,
|
||||
_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<V: trustfall::provider::AsVertex<Self::Vertex> + 'a>(
|
||||
&self,
|
||||
_contexts: trustfall::provider::ContextIterator<'a, V>,
|
||||
_type_name: &Arc<str>,
|
||||
_coerce_to_type: &Arc<str>,
|
||||
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> {
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<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))
|
||||
})
|
||||
.collect::<miette::Result<_>>()?;
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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" }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
13
query
Normal file
13
query
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue