add rust generator

This commit is contained in:
Kasper Juul Hermansen 2023-02-12 16:22:41 +01:00
parent 58de5386e2
commit f41103a9fa
Signed by: kjuulh
GPG Key ID: 57B6E1465221F912
26 changed files with 463 additions and 1128 deletions

View File

@ -1,155 +0,0 @@
use std::{
io::{BufWriter, Write},
sync::Arc,
};
use dagger_core::introspection::{FullType, IntrospectionResponse, Schema};
use genco::{fmt, prelude::rust, prelude::*, quote};
use crate::handlers::{
enumeration::Enumeration, input::Input, object::Object, scalar::Scalar, DynHandler, Handlers,
};
#[allow(dead_code)]
pub struct CodeGeneration {
handlers: Handlers,
}
impl CodeGeneration {
pub fn new() -> Self {
Self {
handlers: vec![
Arc::new(Scalar {}),
Arc::new(Enumeration {}),
Arc::new(Input {}),
Arc::new(Object {}),
],
}
}
pub fn generate(&self, schema: &IntrospectionResponse) -> eyre::Result<String> {
let code = self.generate_from_schema(
schema
.as_schema()
.schema
.as_ref()
.ok_or(eyre::anyhow!("could not get schema to generate code from"))?,
)?;
Ok(code)
}
fn generate_from_schema(&self, schema: &Schema) -> eyre::Result<String> {
let mut output = rust::Tokens::new();
output.push();
output.append(quote! {
$(format!("// code generated by dagger. DO NOT EDIT."))
});
output.push();
output.append(render_base_types());
output.push();
let mut types = get_types(schema)?;
//let remaining: Vec<Option<String>> = types.into_iter().map(type_name).collect();
types.sort_by_key(|a| a.name.as_ref());
for (handler, types) in self.group_by_handlers(&types) {
for t in types {
if let Some(_) = self.type_name(&t) {
let rendered = handler.render(&t)?;
output.push();
output.append(rendered);
}
}
}
let mut buffer = BufWriter::new(Vec::new());
let mut w = fmt::IoWriter::new(buffer.by_ref());
let fmt = fmt::Config::from_lang::<Rust>().with_indentation(fmt::Indentation::Space(4));
let config = rust::Config::default();
// Prettier imports and use.
//.with_default_import(rust::ImportMode::Qualified);
output.format_file(&mut w.as_formatter(&fmt), &config)?;
let out = String::from_utf8(buffer.into_inner()?)?;
Ok(out)
}
pub fn group_by_handlers(&self, types: &Vec<&FullType>) -> Vec<(DynHandler, Vec<FullType>)> {
let mut group = vec![];
for handler in self.handlers.iter() {
let mut group_types: Vec<FullType> = vec![];
for t in types.iter() {
if handler.predicate(*t) {
group_types.push(t.clone().clone());
}
}
group.push((handler.clone(), group_types))
}
group
}
pub fn type_name(&self, t: &FullType) -> Option<String> {
let name = t.name.as_ref();
if let Some(name) = name {
if name.starts_with("_") {
//|| !is_custom_scalar_type(t) {
return None;
}
return Some(name.replace("Query", "Client"));
}
None
}
fn group_key(&self, t: &FullType) -> Option<DynHandler> {
for handler in self.handlers.iter() {
if handler.predicate(&t) {
return Some(handler.clone());
}
}
None
}
fn sort_key(&self, t: &FullType) -> (isize, String) {
for (i, handler) in self.handlers.iter().enumerate() {
if handler.predicate(t) {
return (i as isize, t.name.as_ref().unwrap().clone());
}
}
return (-1, t.name.as_ref().unwrap().clone());
}
}
fn render_base_types() -> rust::Tokens {
let i = rust::import("dagger_core", "Int");
let b = rust::import("dagger_core", "Boolean");
quote! {
$(register(i))
$(register(b))
}
}
fn get_types(schema: &Schema) -> eyre::Result<Vec<&FullType>> {
let types = schema
.types
.as_ref()
.ok_or(eyre::anyhow!("types not found on schema"))?;
let types: Vec<&FullType> = types
.iter()
.map(|t| t.as_ref().map(|t| &t.full_type))
.flatten()
.collect();
Ok(types)
}

View File

@ -0,0 +1,126 @@
use std::sync::Arc;
use dagger_core::introspection::{TypeRef, __TypeKind};
pub trait FormatTypeFuncs {
fn format_kind_list(&self, representation: &str) -> String;
fn format_kind_scalar_string(&self, representation: &str) -> String;
fn format_kind_scalar_int(&self, representation: &str) -> String;
fn format_kind_scalar_float(&self, representation: &str) -> String;
fn format_kind_scalar_boolean(&self, representation: &str) -> String;
fn format_kind_scalar_default(
&self,
representation: &str,
ref_name: &str,
input: bool,
) -> String;
fn format_kind_object(&self, representation: &str, ref_name: &str) -> String;
fn format_kind_input_object(&self, representation: &str, ref_name: &str) -> String;
fn format_kind_enum(&self, representation: &str, ref_name: &str) -> String;
}
pub type DynFormatTypeFuncs = Arc<dyn FormatTypeFuncs + Send + Sync>;
pub struct CommonFunctions {
format_type_funcs: DynFormatTypeFuncs,
}
impl CommonFunctions {
pub fn new(funcs: DynFormatTypeFuncs) -> Self {
Self {
format_type_funcs: funcs,
}
}
pub fn format_input_type(&self, t: &TypeRef) -> String {
self.format_type(t, true)
}
pub fn format_output_type(&self, t: &TypeRef) -> String {
self.format_type(t, false)
}
fn format_type(&self, t: &TypeRef, input: bool) -> String {
let mut representation = String::new();
let mut r = Some(t.clone());
while r.is_some() {
match r.as_ref() {
Some(rf) => match rf.kind.as_ref() {
Some(k) => match k {
__TypeKind::SCALAR => match Scalar::from(rf) {
Scalar::Int => {
self.format_type_funcs
.format_kind_scalar_int(&mut representation);
}
Scalar::Float => {
self.format_type_funcs
.format_kind_scalar_float(&mut representation);
}
Scalar::String => {
self.format_type_funcs
.format_kind_scalar_string(&mut representation);
}
Scalar::Boolean => {
self.format_type_funcs
.format_kind_scalar_boolean(&mut representation);
}
Scalar::Default => {
self.format_type_funcs.format_kind_scalar_default(
&mut representation,
rf.name.as_ref().unwrap(),
input,
);
}
},
__TypeKind::OBJECT => {
self.format_type_funcs
.format_kind_object(&mut representation, rf.name.as_ref().unwrap());
}
__TypeKind::ENUM => {
self.format_type_funcs
.format_kind_enum(&mut representation, rf.name.as_ref().unwrap());
}
__TypeKind::INPUT_OBJECT => {
self.format_type_funcs.format_kind_input_object(
&mut representation,
&rf.name.as_ref().unwrap(),
);
}
__TypeKind::LIST => {
self.format_type_funcs.format_kind_list(&mut representation);
}
__TypeKind::Other(_) => {
r = rf.of_type.as_ref().map(|t| t.clone()).map(|t| *t)
}
_ => {}
},
None => break,
},
None => break,
}
}
representation
}
}
pub enum Scalar {
Int,
Float,
String,
Boolean,
Default,
}
impl From<&TypeRef> for Scalar {
fn from(value: &TypeRef) -> Self {
match value.name.as_ref().map(|n| n.as_str()) {
Some("Int") => Scalar::Int,
Some("Float") => Scalar::Float,
Some("String") => Scalar::String,
Some("Boolean") => Scalar::Boolean,
Some(_) => Scalar::Default,
None => Scalar::Default,
}
}
}

View File

@ -0,0 +1,21 @@
use std::sync::Arc;
use dagger_core::introspection::Schema;
pub trait Generator {
fn generate(&self, schema: Schema) -> eyre::Result<String>;
}
pub type DynGenerator = Arc<dyn Generator + Send + Sync>;
pub trait FormatTypeRefs {
fn format_kind_list(representation: &str) -> String;
fn format_kind_scalar_string(representation: &str) -> String;
fn format_kind_scalar_int(representation: &str) -> String;
fn format_kind_scalar_float(representation: &str) -> String;
fn format_kind_scalar_boolean(representation: &str) -> String;
fn format_kind_scalar_default(representation: &str, ref_name: &str, input: bool) -> String;
fn format_kind_object(representation: &str, ref_name: &str) -> String;
fn format_kind_input_object(representation: &str, ref_name: &str) -> String;
fn format_kind_enum(representation: &str, ref_name: &str) -> String;
}

View File

@ -1,33 +0,0 @@
use dagger_core::introspection::FullType;
use genco::{prelude::rust, quote};
use crate::predicates::is_enum_type;
use super::{utility::render_description, Handler};
pub struct Enumeration;
impl Handler for Enumeration {
fn predicate(&self, t: &FullType) -> bool {
is_enum_type(t)
}
fn render(&self, t: &FullType) -> eyre::Result<rust::Tokens> {
let name = t
.name
.as_ref()
.ok_or(eyre::anyhow!("could not get name from type"))?;
let description =
render_description(t).ok_or(eyre::anyhow!("could not find description"))?;
let out = quote! {
$description
pub enum $name {
// TODO: Add individual items
}
};
Ok(out)
}
}

View File

@ -1,123 +0,0 @@
use convert_case::{Case, Casing};
use dagger_core::introspection::{FullTypeFields, FullTypeFieldsArgs};
use genco::{prelude::rust, quote};
use super::{
type_ref::{self, render_type_ref},
utility::{render_description_from_field, render_description_from_input_value},
};
pub fn render_fields(
fields: &Vec<FullTypeFields>,
) -> eyre::Result<Option<(rust::Tokens, rust::Tokens)>> {
let mut collected_fields: Vec<rust::Tokens> = vec![];
let mut collected_args: Vec<rust::Tokens> = vec![];
for field in fields.iter() {
let name = field.name.as_ref().map(|n| n.to_case(Case::Snake)).unwrap();
let output = render_field_output(field)?;
let description = render_description_from_field(field);
let args = match field.args.as_ref() {
Some(a) => render_args(a),
None => None,
};
if let Some(args) = args.as_ref() {
let mut args_tkns = rust::Tokens::new();
args_tkns.append(quote! {
$description
pub struct $(&field.name.as_ref().map(|n| n.to_case(Case::Pascal)).unwrap())Args {
$(&args.args)
}
});
args_tkns.push();
collected_args.push(args_tkns);
}
let mut tkns = rust::Tokens::new();
tkns.append(quote! {
pub fn $(&name)(
&self,
$(if let Some(_) = args.as_ref() => args: &$(&field.name.as_ref().map(|n| n.to_case(Case::Pascal)).unwrap())Args)
) -> $(&output) {
let query = self.selection.select($(field.name.as_ref().map(|n| format!("\"{}\"", n))));
$(if let Some(_) = args.as_ref() => query.args(args);)
$output {
conn: self.conn.clone(),
proc: self.proc.clone(),
selection: query,
}
}
});
collected_fields.push(tkns);
}
Ok(Some((
quote! {
$(for arg in collected_args => $arg $['\n'] )
},
quote! {
$(for field in collected_fields => $field $['\n'] )
},
)))
}
struct Arg {
name: String,
description: Option<rust::Tokens>,
type_: rust::Tokens,
}
struct CollectedArgs {
description: Option<rust::Tokens>,
args: rust::Tokens,
}
fn render_args(args: &[Option<FullTypeFieldsArgs>]) -> Option<CollectedArgs> {
let mut collected_args: Vec<Arg> = vec![];
for arg in args {
if let Some(arg) = arg.as_ref().map(|a| &a.input_value) {
let name = arg.name.clone();
let description = render_description_from_input_value(&arg, &name);
let t = render_type_ref(&arg.type_).unwrap();
collected_args.push(Arg {
name,
description,
type_: t,
})
}
}
if collected_args.len() > 0 {
let mut collected_arg = CollectedArgs {
description: Some(rust::Tokens::new()),
args: rust::Tokens::new(),
};
for arg in collected_args {
collected_arg.args.append(quote! {
$(arg.description)
pub $(arg.name.to_case(Case::Snake)): $(arg.type_),
});
collected_arg.args.push();
}
if let Some(desc) = collected_arg.description.as_ref() {
if desc.is_empty() {
collected_arg.description = None;
}
}
Some(collected_arg)
} else {
None
}
}
pub fn render_field_output(field: &FullTypeFields) -> eyre::Result<rust::Tokens> {
let inner = &field.type_.as_ref().unwrap();
type_ref::render_type_ref(&inner.type_ref)
}

View File

@ -1,110 +0,0 @@
use dagger_core::introspection::FullType;
use genco::prelude::rust;
use genco::prelude::*;
use crate::predicates::is_input_object_type;
use super::{input_field::render_input_fields, utility::render_description, Handler};
pub struct Input;
impl Handler for Input {
fn predicate(&self, t: &FullType) -> bool {
is_input_object_type(t)
}
fn render(&self, t: &FullType) -> eyre::Result<rust::Tokens> {
let name = t
.name
.as_ref()
.ok_or(eyre::anyhow!("could not find name"))?;
let description = render_description(t);
//let input = rust::import("dagger_core", "Input");
let fields = match t.input_fields.as_ref() {
Some(i) => render_input_fields(i)?,
None => None,
};
let out = quote! {
$(if description.is_some() => $description)
pub struct $name {
$(if fields.is_some() => $fields)
}
};
Ok(out)
}
}
#[cfg(test)]
mod tests {
use dagger_core::introspection::{
FullType, FullTypeInputFields, InputValue, TypeRef, __TypeKind,
};
use pretty_assertions::assert_eq;
use crate::handlers::Handler;
use super::Input;
#[test]
fn can_gen_input() {
let input = Input {};
let t = FullType {
kind: Some(__TypeKind::INPUT_OBJECT),
name: Some("BuildArg".into()),
description: None,
input_fields: Some(vec![
FullTypeInputFields {
input_value: InputValue {
name: "name".into(),
description: None,
type_: TypeRef {
name: None,
kind: Some(__TypeKind::NON_NULL),
of_type: Some(Box::new(TypeRef {
kind: Some(__TypeKind::SCALAR),
name: Some("String".into()),
of_type: None,
})),
},
default_value: None,
},
},
FullTypeInputFields {
input_value: InputValue {
name: "value".into(),
description: None,
type_: TypeRef {
name: None,
kind: Some(__TypeKind::NON_NULL),
of_type: Some(Box::new(TypeRef {
kind: Some(__TypeKind::SCALAR),
name: Some("String".into()),
of_type: None,
})),
},
default_value: None,
},
},
]),
interfaces: None,
enum_values: None,
possible_types: None,
fields: None,
};
let expected = r#"pub struct BuildArg {
pub name: String,
pub value: String,
}
"#;
let output = input.render(&t).unwrap();
assert_eq!(output.to_file_string().unwrap(), expected);
}
}

View File

@ -1,22 +0,0 @@
use dagger_core::introspection::FullTypeInputFields;
use genco::{prelude::rust, quote};
use super::type_ref;
pub fn render_input_fields(
input_fields: &Vec<FullTypeInputFields>,
) -> eyre::Result<Option<rust::Tokens>> {
let mut fields: Vec<(String, rust::Tokens)> = vec![];
for field in input_fields.iter() {
fields.push((field.input_value.name.clone(), render_input_field(field)?));
}
Ok(Some(quote! {
$(for (name, field) in fields => pub $name: $field, $['\n'] )
}))
}
pub fn render_input_field(field: &FullTypeInputFields) -> eyre::Result<rust::Tokens> {
let inner = &field.input_value.type_;
type_ref::render_type_ref(inner)
}

View File

@ -1,93 +0,0 @@
pub mod enumeration;
mod fields;
pub mod input;
mod input_field;
pub mod object;
pub mod scalar;
mod type_ref;
mod utility;
use std::sync::Arc;
use dagger_core::introspection::FullType;
use genco::prelude::rust::Tokens;
use genco::prelude::*;
pub trait Handler {
fn predicate(&self, _t: &FullType) -> bool {
false
}
fn render(&self, t: &FullType) -> eyre::Result<rust::Tokens> {
let tstruct = self.render_struct(t)?;
let timpl = self.render_impl(t)?;
let mut out = rust::Tokens::new();
out.append(tstruct);
out.push();
out.append(timpl);
out.push();
Ok(out)
}
fn render_struct(&self, t: &FullType) -> eyre::Result<Tokens> {
let name = t.name.as_ref().ok_or(eyre::anyhow!("name not found"))?;
Ok(quote! {
pub struct $name {} {
// TODO: Add fields
}
})
}
fn render_impl(&self, t: &FullType) -> eyre::Result<Tokens> {
let name = t.name.as_ref().ok_or(eyre::anyhow!("name not found"))?;
Ok(quote! {
impl $name {} {
// TODO: Add fields
}
})
}
}
pub type DynHandler = Arc<dyn Handler + Send + Sync>;
pub type Handlers = Vec<DynHandler>;
#[cfg(test)]
mod tests {
use dagger_core::introspection::FullType;
use pretty_assertions::assert_eq;
use super::Handler;
struct DefaultHandler;
impl Handler for DefaultHandler {}
#[test]
fn render_returns_expected() {
let handler = DefaultHandler {};
let t = FullType {
kind: None,
name: Some("SomeName".into()),
description: None,
fields: None,
input_fields: None,
interfaces: None,
enum_values: None,
possible_types: None,
};
let res = handler.render(&t).unwrap();
assert_eq!(
res.to_string().unwrap(),
"pub struct SomeName {} {
}
impl SomeName {} {
}"
.to_string()
);
}
}

View File

@ -1,234 +0,0 @@
use dagger_core::introspection::FullType;
use genco::{prelude::rust, quote};
use crate::predicates::is_object_type;
use super::{fields, input_field, utility::render_description, Handler};
pub struct Object;
impl Handler for Object {
fn predicate(&self, t: &FullType) -> bool {
is_object_type(t)
}
fn render(&self, t: &FullType) -> eyre::Result<rust::Tokens> {
let name = t
.name
.as_ref()
.ok_or(eyre::anyhow!("could not find name"))?;
let description = render_description(t);
let fields = match t.fields.as_ref() {
Some(i) => fields::render_fields(i)?,
None => None,
};
let input_fields = match t.input_fields.as_ref() {
Some(i) => input_field::render_input_fields(i)?,
None => None,
};
let child = rust::import("std::process", "Child");
let connect_params = rust::import("dagger_core::connect_params", "ConnectParams");
let selection = rust::import("crate::querybuilder", "Selection");
let arc = rust::import("std::sync", "Arc");
let out = quote! {
$(if fields.as_ref().is_some() => $(fields.as_ref().map(|f| &f.0)))
$(if description.is_some() => $description)
pub struct $name {
$(if input_fields.is_some() => $input_fields)
pub conn: $connect_params,
pub proc: $arc<$child>,
pub selection: $selection,
}
impl $name {
$(if fields.is_some() => $(fields.map(|f| f.1)))
}
};
Ok(out)
}
}
#[cfg(test)]
mod tests {
use dagger_core::introspection::{
FullType, FullTypeFields, FullTypeFieldsArgs, FullTypeFieldsType, InputValue, TypeRef,
__TypeKind,
};
use pretty_assertions::assert_eq;
use crate::handlers::Handler;
use super::Object;
#[test]
fn can_render_object() {
let t: FullType = FullType {
kind: Some(__TypeKind::OBJECT),
name: Some("CacheVolume".into()),
description: Some("A directory whose contents persists across sessions".into()),
fields: Some(vec![FullTypeFields {
name: Some("id".into()),
description: None,
args: None,
type_: Some(FullTypeFieldsType {
type_ref: TypeRef {
kind: Some(__TypeKind::NON_NULL),
name: None,
of_type: Some(Box::new(TypeRef {
kind: Some(__TypeKind::SCALAR),
name: Some("CacheID".into()),
of_type: None,
})),
},
}),
is_deprecated: Some(false),
deprecation_reason: None,
}]),
input_fields: None,
interfaces: None,
enum_values: None,
possible_types: None,
};
let expected = r#"use crate::querybuilder::Selection;
use dagger_core::connect_params::ConnectParams;
use std::process::Child;
use std::sync::Arc;
/// A directory whose contents persists across sessions
pub struct CacheVolume {
pub conn: ConnectParams,
pub proc: Arc<Child>,
pub selection: Selection,
}
impl CacheVolume {
pub fn id(
&self,
) -> CacheID {
let query = self.selection.select("id");
CacheID {
conn: self.conn.clone(),
proc: self.proc.clone(),
selection: query,
}
todo!()
}
}
"#;
let handler = Object {};
let obj = handler.render(&t).unwrap();
let actual = obj.to_file_string().unwrap();
assert_eq!(actual, expected);
}
#[test]
fn can_render_query_container() {
let description = "Loads a container from ID.\nNull ID returns an empty container (scratch).\nOptional platform argument initializes new containers to execute and publish as that platform. Platform defaults to that of the builder's host.".into();
let t: FullType = FullType {
kind: Some(__TypeKind::OBJECT),
name: Some("Query".into()),
description: None,
fields: Some(vec![FullTypeFields {
name: Some("container".into()),
description: Some(description),
args: Some(vec![
Some(FullTypeFieldsArgs {
input_value: InputValue {
name: "id".into(),
description: None,
type_: TypeRef {
kind: Some(__TypeKind::SCALAR),
name: Some("ContainerID".into()),
of_type: None,
},
default_value: None,
},
}),
Some(FullTypeFieldsArgs {
input_value: InputValue {
name: "platform".into(),
description: None,
type_: TypeRef {
kind: Some(__TypeKind::SCALAR),
name: Some("Platform".into()),
of_type: None,
},
default_value: None,
},
}),
]),
type_: Some(FullTypeFieldsType {
type_ref: TypeRef {
kind: Some(__TypeKind::NON_NULL),
name: None,
of_type: Some(Box::new(TypeRef {
kind: Some(__TypeKind::SCALAR),
name: Some("CacheID".into()),
of_type: None,
})),
},
}),
is_deprecated: Some(false),
deprecation_reason: None,
}]),
input_fields: None,
interfaces: None,
enum_values: None,
possible_types: None,
};
let expected = r#"use crate::querybuilder::Selection;
use dagger_core::connect_params::ConnectParams;
use std::process::Child;
use std::sync::Arc;
/// Loads a container from ID.
/// Null ID returns an empty container (scratch).
/// Optional platform argument initializes new containers to execute and publish as that platform. Platform defaults to that of the builder's host.
pub struct ContainerArgs {
pub id: Option<ContainerID>,
pub platform: Option<Platform>,
}
pub struct Query {
pub conn: ConnectParams,
pub proc: Arc<Child>,
pub selection: Selection,
}
impl Query {
pub fn container(
&self,
args: &ContainerArgs
) -> CacheID {
let query = self.selection.select("container");
query.args(args);
CacheID {
conn: self.conn.clone(),
proc: self.proc.clone(),
selection: query,
}
todo!()
}
}
"#;
let handler = Object {};
let obj = handler.render(&t).unwrap();
let actual = obj.to_file_string().unwrap();
assert_eq!(actual, expected);
}
}

View File

@ -1,42 +0,0 @@
use dagger_core::introspection::FullType;
use genco::{prelude::rust, quote};
use crate::predicates::is_custom_scalar_type;
use super::{utility::render_description, Handler};
pub struct Scalar;
impl Handler for Scalar {
fn predicate(&self, t: &FullType) -> bool {
is_custom_scalar_type(t)
}
fn render(&self, t: &FullType) -> eyre::Result<rust::Tokens> {
let mut out = rust::Tokens::new();
let description =
render_description(t).ok_or(eyre::anyhow!("could not find description"))?;
let tstruct = self.render_struct(t)?;
out.append(description);
out.push();
out.append(tstruct);
Ok(out)
}
fn render_struct(&self, t: &FullType) -> eyre::Result<genco::prelude::rust::Tokens> {
let name = t.name.as_ref().ok_or(eyre::anyhow!("name not found"))?;
let scalar = rust::import("dagger_core", "Scalar");
Ok(quote! {
pub struct $name($scalar);
})
}
fn render_impl(&self, _t: &FullType) -> eyre::Result<genco::prelude::rust::Tokens> {
todo!()
}
}

View File

@ -1,97 +0,0 @@
use dagger_core::introspection::TypeRef;
use genco::prelude::rust;
use genco::prelude::*;
use crate::predicates::{
is_custom_scalar_type_ref, is_list_type, is_required_type_ref, is_scalar_type_ref,
};
pub fn render_type_ref(inner: &TypeRef) -> eyre::Result<rust::Tokens> {
let extract_of_type = |t: &TypeRef| -> Option<TypeRef> {
return t.clone().of_type.map(|t| *t);
};
let (optional, inner) = if !is_required_type_ref(inner) {
(true, inner.clone())
} else {
(false, extract_of_type(inner).unwrap())
};
if is_list_type(&inner) {
if let Some(inner_of_type) = extract_of_type(&inner) {
let inner_field = render_type_ref(&inner_of_type)?;
if optional {
return Ok(quote! {
Option<Vec<$inner_field>>
});
}
return Ok(quote! {
Vec<$inner_field>
});
}
}
if is_custom_scalar_type_ref(&inner) {
if let Some(inner_of_type) = extract_of_type(&inner) {
let inner_field = render_type_ref(&inner_of_type)?;
if optional {
return Ok(quote! {
Option<$inner_field>
});
}
return Ok(quote! {
$inner_field
});
}
}
if is_scalar_type_ref(&inner) {
let name = match inner.name.as_ref().map(|s| s.as_str()) {
Some("ID") => "ID",
Some("Int") => "Int",
Some("String") => "String",
Some("Float") => "Float",
Some("Boolean") => "Boolean",
Some("Date") => "Date",
Some("DateTime") => "DateTime",
Some("Time") => "Time",
Some("Decimal") => "Decimal",
Some(n) => n,
_ => eyre::bail!("missing type in the end of chain"),
};
if optional {
return Ok(quote! {
Option<$name>
});
}
return Ok(quote! {
$name
});
}
if let Some(inner_type) = inner.of_type.as_ref() {
let inner_field = render_type_ref(&inner_type)?;
if optional {
return Ok(quote! {
Option<$inner_field>
});
}
return Ok(inner_field);
}
if let Some(name) = inner.name.as_ref() {
if optional {
return Ok(quote! {
Option<$name>
});
}
return Ok(quote! {
$name
});
}
eyre::bail!("could not determine type")
}

View File

@ -1,56 +0,0 @@
use dagger_core::introspection::{FullType, FullTypeFields, InputValue};
use genco::{prelude::*, quote};
pub fn render_description(t: &FullType) -> Option<rust::Tokens> {
if let Some(description) = t.description.as_ref() {
let lines = description.split('\n');
let output: rust::Tokens = quote! {
$(for line in lines => $(format!("\n/// {line}")))
};
return Some(output);
}
None
}
pub fn render_description_from_field(t: &FullTypeFields) -> Option<rust::Tokens> {
if let Some(description) = t.description.as_ref() {
let lines = description.split('\n');
let output: rust::Tokens = quote! {
$(for line in lines => $(format!("\n/// {line}")))
};
return Some(output);
}
None
}
pub fn render_description_from_input_value(t: &InputValue, name: &String) -> Option<rust::Tokens> {
if let Some(description) = t.description.as_ref() {
if description == "" {
return None;
}
let lines = description.split('\n').collect::<Vec<&str>>();
let mut output = rust::Tokens::new();
if let Some(line) = lines.first() {
output.append(quote! {
$(format!("/// * `{name}` - {line}"))
});
output.push();
}
for line in lines.iter().skip(1) {
output.append(quote! {
$(format!("/// {line}"))
});
output.push();
}
return Some(output);
}
None
}

View File

@ -1,4 +1,12 @@
pub mod codegen;
mod handlers;
mod models;
mod predicates;
mod functions;
mod generator;
pub mod rust;
mod visitor;
use dagger_core::introspection::Schema;
use self::generator::DynGenerator;
pub fn generate(schema: Schema, generator: DynGenerator) -> eyre::Result<String> {
generator.generate(schema)
}

View File

@ -1,11 +0,0 @@
pub enum Scalars {
ID(String),
Int(usize),
String(String),
Float(f64),
Boolean(bool),
Date(String),
DateTime(String),
Time(String),
Decimal(f64),
}

View File

@ -1,101 +0,0 @@
use dagger_core::introspection::{FullType, FullTypeInputFields, TypeRef, __TypeKind};
use crate::models::Scalars;
pub fn is_scalar_type(t: &FullType) -> bool {
if let Some(__TypeKind::SCALAR) = t.kind {
return true;
}
false
}
pub fn is_scalar_type_ref(t: &TypeRef) -> bool {
if let Some(__TypeKind::SCALAR) = t.kind {
return true;
}
false
}
pub fn is_enum_type(t: &FullType) -> bool {
if let Some(__TypeKind::ENUM) = t.kind {
return true;
}
false
}
pub fn is_input_object_type(t: &FullType) -> bool {
if let Some(__TypeKind::INPUT_OBJECT) = t.kind {
return true;
}
false
}
pub fn is_required_type(t: &FullTypeInputFields) -> bool {
match t.input_value.type_.kind {
Some(__TypeKind::NON_NULL) => return true,
Some(_) => return false,
_ => return false,
}
}
pub fn is_required_type_ref(t: &TypeRef) -> bool {
match t.kind {
Some(__TypeKind::NON_NULL) => return true,
Some(_) => return false,
_ => return false,
}
}
pub fn is_list_type(t: &TypeRef) -> bool {
if let Some(__TypeKind::LIST) = t.kind {
return true;
}
false
}
pub fn is_object_type(t: &FullType) -> bool {
if let Some(__TypeKind::OBJECT) = t.kind {
return true;
}
false
}
pub fn is_custom_scalar_type(t: &FullType) -> bool {
if is_scalar_type(t) {
// TODO: Insert scalar
let _ = match t.name.as_ref().map(|s| s.as_str()) {
Some("ID") => Scalars::ID("ID".into()),
Some("Int") => Scalars::Int(0),
Some("String") => Scalars::String("ID".into()),
Some("Float") => Scalars::Float(0.0),
Some("Boolean") => Scalars::Boolean(false),
Some("Date") => Scalars::Date("ID".into()),
Some("DateTime") => Scalars::DateTime("ID".into()),
Some("Time") => Scalars::Time("ID".into()),
Some("Decimal") => Scalars::Decimal(0.0),
Some(_) => return true,
None => return false,
};
}
false
}
pub fn is_custom_scalar_type_ref(t: &TypeRef) -> bool {
if is_scalar_type_ref(t) {
// TODO: Insert scalar
let _ = match t.name.as_ref().map(|s| s.as_str()) {
Some("ID") => Scalars::ID("ID".into()),
Some("Int") => Scalars::Int(0),
Some("String") => Scalars::String("ID".into()),
Some("Float") => Scalars::Float(0.0),
Some("Boolean") => Scalars::Boolean(false),
Some("Date") => Scalars::Date("ID".into()),
Some("DateTime") => Scalars::DateTime("ID".into()),
Some("Time") => Scalars::Time("ID".into()),
Some("Decimal") => Scalars::Decimal(0.0),
Some(_) => return true,
None => return false,
};
}
false
}

View File

@ -0,0 +1,64 @@
use crate::functions::FormatTypeFuncs;
use super::functions::format_name;
pub struct FormatTypeFunc;
impl FormatTypeFuncs for FormatTypeFunc {
fn format_kind_list(&self, representation: &str) -> String {
format!("Vec<{}>", representation)
}
fn format_kind_scalar_string(&self, representation: &str) -> String {
let mut rep = representation.to_string();
rep.push_str("String");
rep
}
fn format_kind_scalar_int(&self, representation: &str) -> String {
let mut rep = representation.to_string();
rep.push_str("isize");
rep
}
fn format_kind_scalar_float(&self, representation: &str) -> String {
let mut rep = representation.to_string();
rep.push_str("float");
rep
}
fn format_kind_scalar_boolean(&self, representation: &str) -> String {
let mut rep = representation.to_string();
rep.push_str("bool");
rep
}
fn format_kind_scalar_default(
&self,
representation: &str,
ref_name: &str,
input: bool,
) -> String {
let mut rep = representation.to_string();
rep.push_str(ref_name);
rep
}
fn format_kind_object(&self, representation: &str, ref_name: &str) -> String {
let mut rep = representation.to_string();
rep.push_str(&format_name(ref_name));
rep
}
fn format_kind_input_object(&self, representation: &str, ref_name: &str) -> String {
let mut rep = representation.to_string();
rep.push_str(&format_name(ref_name));
rep
}
fn format_kind_enum(&self, representation: &str, ref_name: &str) -> String {
let mut rep = representation.to_string();
rep.push_str(ref_name);
rep
}
}

View File

@ -0,0 +1,5 @@
use convert_case::{Case, Casing};
pub fn format_name(s: &str) -> String {
s.to_case(Case::Pascal)
}

View File

@ -0,0 +1,94 @@
pub mod format;
mod functions;
pub mod templates;
use std::sync::{Arc, Mutex};
use dagger_core::introspection::Schema;
use eyre::Context;
use genco::prelude::rust;
use crate::generator::Generator;
use crate::visitor::{VisitHandlers, Visitor};
use self::templates::enum_tmpl::render_enum;
use self::templates::input_tmpl::render_input;
use self::templates::object_tmpl::render_object;
use self::templates::scalar_tmpl::render_scalar;
pub struct RustGenerator {}
impl Generator for RustGenerator {
fn generate(&self, schema: Schema) -> eyre::Result<String> {
let render = Arc::new(Mutex::new(rust::Tokens::new()));
let visitor = Visitor {
schema,
handlers: VisitHandlers {
visit_scalar: Arc::new({
let render = render.clone();
move |t| {
let rendered_scalar = render_scalar()?;
let mut render = render.lock().unwrap();
render.append(rendered_scalar);
render.push();
Ok(())
}
}),
visit_object: Arc::new({
let render = render.clone();
move |t| {
let rendered_scalar = render_object()?;
let mut render = render.lock().unwrap();
render.append(rendered_scalar);
render.push();
Ok(())
}
}),
visit_input: Arc::new({
let render = render.clone();
move |t| {
let rendered_scalar = render_input()?;
let mut render = render.lock().unwrap();
render.append(rendered_scalar);
render.push();
Ok(())
}
}),
visit_enum: Arc::new({
let render = render.clone();
move |t| {
let rendered_scalar = render_enum()?;
let mut render = render.lock().unwrap();
render.append(rendered_scalar);
render.push();
Ok(())
}
}),
},
};
visitor.run()?;
let rendered = render.lock().unwrap();
rendered
.to_file_string()
.context("could not render to file string")
}
}

View File

@ -0,0 +1,6 @@
use genco::prelude::rust;
use genco::quote;
pub fn render_enum() -> eyre::Result<rust::Tokens> {
Ok(quote! {})
}

View File

@ -0,0 +1,6 @@
use genco::prelude::rust;
use genco::quote;
pub fn render_input() -> eyre::Result<rust::Tokens> {
Ok(quote! {})
}

View File

@ -0,0 +1,4 @@
pub mod enum_tmpl;
pub mod input_tmpl;
pub mod object_tmpl;
pub mod scalar_tmpl;

View File

@ -0,0 +1,6 @@
use genco::prelude::rust;
use genco::quote;
pub fn render_object() -> eyre::Result<rust::Tokens> {
Ok(quote! {})
}

View File

@ -0,0 +1,6 @@
use genco::prelude::rust;
use genco::quote;
pub fn render_scalar() -> eyre::Result<rust::Tokens> {
Ok(quote! {})
}

View File

@ -0,0 +1,97 @@
use std::sync::Arc;
use dagger_core::introspection::{FullType, Schema, __TypeKind};
use itertools::Itertools;
pub struct Visitor {
pub schema: Schema,
pub handlers: VisitHandlers,
}
pub type VisitFunc = Arc<dyn Fn(&FullType) -> eyre::Result<()>>;
pub struct VisitHandlers {
pub visit_scalar: VisitFunc,
pub visit_object: VisitFunc,
pub visit_input: VisitFunc,
pub visit_enum: VisitFunc,
}
struct SequenceItem {
kind: __TypeKind,
handler: VisitFunc,
ignore: Option<Vec<String>>,
}
impl Visitor {
pub fn run(&self) -> eyre::Result<()> {
let sequence = vec![
SequenceItem {
kind: __TypeKind::SCALAR,
handler: self.handlers.visit_scalar.clone(),
ignore: Some(vec![
"String".into(),
"Float".into(),
"Int".into(),
"Boolean".into(),
"DateTime".into(),
"ID".into(),
]),
},
SequenceItem {
kind: __TypeKind::INPUT_OBJECT,
handler: self.handlers.visit_input.clone(),
ignore: None,
},
SequenceItem {
kind: __TypeKind::OBJECT,
handler: self.handlers.visit_object.clone(),
ignore: None,
},
SequenceItem {
kind: __TypeKind::ENUM,
handler: self.handlers.visit_enum.clone(),
ignore: None,
},
];
for item in sequence {
self.visit(&item)?;
}
Ok(())
}
fn visit(&self, item: &SequenceItem) -> eyre::Result<()> {
self.schema
.types
.as_ref()
.unwrap()
.into_iter()
.map(|t| t.as_ref().unwrap())
.filter(|t| match t.full_type.kind.as_ref().unwrap() == &item.kind {
true => match (item.ignore.as_ref(), t.full_type.name.as_ref()) {
(Some(ignore), Some(name)) => {
if ignore.contains(name) {
return false;
}
return true;
}
_ => false,
},
false => false,
})
.sorted_by(|a, b| {
a.full_type
.name
.as_ref()
.unwrap()
.cmp(&b.full_type.name.as_ref().unwrap())
})
.map(|t| (*item.handler)(&t.full_type))
.collect::<eyre::Result<Vec<_>>>()?;
Ok(())
}
}

View File

@ -382,11 +382,7 @@ impl Container {
let query = self.selection.select("envVariable");
query.args(args);
Option<String> {
conn: self.conn.clone(),
proc: self.proc.clone(),
selection: query,
}
query.execute(&graphql_client(&self.conn)).unwrap()
}
pub fn env_variables(
@ -420,11 +416,7 @@ impl Container {
) -> Option<Int> {
let query = self.selection.select("exitCode");
Option<Int> {
conn: self.conn.clone(),
proc: self.proc.clone(),
selection: query,
}
query.execute(&graphql_client(&self.conn)).unwrap()
}
pub fn export(
@ -500,11 +492,7 @@ impl Container {
let query = self.selection.select("label");
query.args(args);
Option<String> {
conn: self.conn.clone(),
proc: self.proc.clone(),
selection: query,
}
query.execute(&graphql_client(&self.conn)).unwrap()
}
pub fn labels(
@ -588,11 +576,7 @@ impl Container {
) -> Option<String> {
let query = self.selection.select("stderr");
Option<String> {
conn: self.conn.clone(),
proc: self.proc.clone(),
selection: query,
}
query.execute(&graphql_client(&self.conn)).unwrap()
}
pub fn stdout(
@ -600,11 +584,7 @@ impl Container {
) -> Option<String> {
let query = self.selection.select("stdout");
Option<String> {
conn: self.conn.clone(),
proc: self.proc.clone(),
selection: query,
}
query.execute(&graphql_client(&self.conn)).unwrap()
}
pub fn user(
@ -612,11 +592,7 @@ impl Container {
) -> Option<String> {
let query = self.selection.select("user");
Option<String> {
conn: self.conn.clone(),
proc: self.proc.clone(),
selection: query,
}
query.execute(&graphql_client(&self.conn)).unwrap()
}
pub fn with_default_args(
@ -946,11 +922,7 @@ impl Container {
) -> Option<String> {
let query = self.selection.select("workdir");
Option<String> {
conn: self.conn.clone(),
proc: self.proc.clone(),
selection: query,
}
query.execute(&graphql_client(&self.conn)).unwrap()
}
}
@ -1784,11 +1756,7 @@ impl Project {
) -> Option<String> {
let query = self.selection.select("schema");
Option<String> {
conn: self.conn.clone(),
proc: self.proc.clone(),
selection: query,
}
query.execute(&graphql_client(&self.conn)).unwrap()
}
pub fn sdk(
@ -1796,11 +1764,7 @@ impl Project {
) -> Option<String> {
let query = self.selection.select("sdk");
Option<String> {
conn: self.conn.clone(),
proc: self.proc.clone(),
selection: query,
}
query.execute(&graphql_client(&self.conn)).unwrap()
}
}

View File

@ -1,7 +1,9 @@
use std::io::Write;
use std::sync::Arc;
use clap::{Arg, ArgMatches};
use dagger_codegen::codegen::CodeGeneration;
use dagger_codegen::generate;
use dagger_codegen::rust::RustGenerator;
use dagger_core::config::Config;
use dagger_core::engine::Engine;
use dagger_core::session::Session;
@ -21,7 +23,10 @@ impl GenerateCommand {
let session = Session::new();
let req = session.start(&cfg, &conn)?;
let schema = session.schema(req)?;
let code = CodeGeneration::new().generate(&schema)?;
let code = generate(
schema.into_schema().schema.unwrap(),
Arc::new(RustGenerator {}),
)?;
if let Some(output) = arg_matches.get_one::<String>("output") {
let mut file = std::fs::File::create(output)?;