mirror of
https://github.com/kjuulh/dagger-rs.git
synced 2024-11-25 16:32:42 +01:00
feature/add impl (#6)
* format code * with object gen and args * add implementation * add rust generator * reset generated code * add basic output * reset output * add object * add format function * with opts * fix vec * add context to unwrap * fix arguments * with function body * first complete generation: Still missing Vec<Obj> * run full alpine * add roadmap item
This commit is contained in:
parent
2eb027754b
commit
4a4c03f3c2
@ -20,7 +20,7 @@ Work in progress. This is not ready for usage yet
|
||||
- [x] Querier
|
||||
- [x] Context
|
||||
- [x] Deserializer for nested response (bind)
|
||||
- [ ] Add codegen to hook into querier
|
||||
- [x] Add codegen to hook into querier
|
||||
- [ ] fix build / release cycle
|
||||
- [ ] general api stabilisation
|
||||
- [ ] document usage
|
||||
|
@ -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)
|
||||
}
|
422
crates/dagger-codegen/src/functions.rs
Normal file
422
crates/dagger-codegen/src/functions.rs
Normal file
@ -0,0 +1,422 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use dagger_core::introspection::{FullType, FullTypeFields, InputValue, TypeRef, __TypeKind};
|
||||
use eyre::ContextCompat;
|
||||
|
||||
use crate::utility::OptionExt;
|
||||
|
||||
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() {
|
||||
return 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 => {
|
||||
let mut inner_type = rf
|
||||
.of_type
|
||||
.as_ref()
|
||||
.map(|t| t.clone())
|
||||
.map(|t| *t)
|
||||
.map(|t| self.format_type(&t, input))
|
||||
.context("could not get inner type of list")
|
||||
.unwrap();
|
||||
|
||||
representation =
|
||||
self.format_type_funcs.format_kind_list(&mut inner_type);
|
||||
|
||||
return representation;
|
||||
}
|
||||
__TypeKind::NON_NULL => {
|
||||
r = rf.of_type.as_ref().map(|t| t.clone()).map(|t| *t);
|
||||
continue;
|
||||
}
|
||||
__TypeKind::Other(_) => {
|
||||
r = rf.of_type.as_ref().map(|t| t.clone()).map(|t| *t);
|
||||
continue;
|
||||
}
|
||||
__TypeKind::INTERFACE => break,
|
||||
__TypeKind::UNION => break,
|
||||
},
|
||||
None => break,
|
||||
},
|
||||
None => break,
|
||||
};
|
||||
}
|
||||
|
||||
println!("rep: {}", representation);
|
||||
println!("{:?}", t);
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_type_from_name<'t>(types: &'t [FullType], name: &'t str) -> Option<&'t FullType> {
|
||||
types
|
||||
.into_iter()
|
||||
.find(|t| t.name.as_ref().map(|s| s.as_str()) == Some(name))
|
||||
}
|
||||
|
||||
pub fn type_ref_is_optional(type_ref: &TypeRef) -> bool {
|
||||
type_ref
|
||||
.kind
|
||||
.pipe(|k| *k != __TypeKind::NON_NULL)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn type_field_has_optional(field: &FullTypeFields) -> bool {
|
||||
field
|
||||
.args
|
||||
.pipe(|a| {
|
||||
a.iter()
|
||||
.map(|a| a.pipe(|a| &a.input_value))
|
||||
.flatten()
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.pipe(|s| input_values_has_optionals(s.as_slice()))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn type_ref_is_scalar(type_ref: &TypeRef) -> bool {
|
||||
let mut type_ref = type_ref.clone();
|
||||
if type_ref
|
||||
.kind
|
||||
.pipe(|k| *k == __TypeKind::NON_NULL)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
type_ref = *type_ref.of_type.unwrap().clone();
|
||||
}
|
||||
|
||||
type_ref
|
||||
.kind
|
||||
.pipe(|k| *k == __TypeKind::SCALAR)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn type_ref_is_object(type_ref: &TypeRef) -> bool {
|
||||
let mut type_ref = type_ref.clone();
|
||||
if type_ref
|
||||
.kind
|
||||
.pipe(|k| *k == __TypeKind::NON_NULL)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
type_ref = *type_ref.of_type.unwrap().clone();
|
||||
}
|
||||
|
||||
type_ref
|
||||
.kind
|
||||
.pipe(|k| *k == __TypeKind::OBJECT)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn type_ref_is_list(type_ref: &TypeRef) -> bool {
|
||||
let mut type_ref = type_ref.clone();
|
||||
if type_ref
|
||||
.kind
|
||||
.pipe(|k| *k == __TypeKind::NON_NULL)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
type_ref = *type_ref.of_type.unwrap().clone();
|
||||
}
|
||||
|
||||
type_ref
|
||||
.kind
|
||||
.pipe(|k| *k == __TypeKind::LIST)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn type_ref_is_list_of_objects(type_ref: &TypeRef) -> bool {
|
||||
let mut type_ref = type_ref.clone();
|
||||
if type_ref
|
||||
.kind
|
||||
.pipe(|k| *k == __TypeKind::NON_NULL)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
type_ref = *type_ref.of_type.unwrap().clone();
|
||||
}
|
||||
|
||||
if type_ref
|
||||
.kind
|
||||
.pipe(|k| *k == __TypeKind::LIST)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
type_ref = *type_ref.of_type.unwrap().clone();
|
||||
}
|
||||
|
||||
type_ref_is_object(&type_ref)
|
||||
}
|
||||
|
||||
pub fn input_values_has_optionals(input_values: &[&InputValue]) -> bool {
|
||||
input_values
|
||||
.into_iter()
|
||||
.map(|k| type_ref_is_optional(&k.type_))
|
||||
.filter(|k| *k)
|
||||
.collect::<Vec<_>>()
|
||||
.len()
|
||||
> 0
|
||||
}
|
||||
|
||||
pub fn input_values_is_empty(input_values: &[InputValue]) -> bool {
|
||||
input_values.len() > 0
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use dagger_core::introspection::{FullType, InputValue, TypeRef, __TypeKind};
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use crate::functions::{input_values_has_optionals, type_ref_is_optional};
|
||||
|
||||
use super::get_type_from_name;
|
||||
|
||||
#[test]
|
||||
fn get_type_from_name_has_no_item() {
|
||||
let input = vec![];
|
||||
let output = get_type_from_name(&input, "some-name");
|
||||
|
||||
assert_eq!(output.is_none(), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_type_from_name_has_item() {
|
||||
let name = "some-name";
|
||||
let input = vec![FullType {
|
||||
kind: None,
|
||||
name: Some(name.to_string()),
|
||||
description: None,
|
||||
fields: None,
|
||||
input_fields: None,
|
||||
interfaces: None,
|
||||
enum_values: None,
|
||||
possible_types: None,
|
||||
}];
|
||||
let output = get_type_from_name(&input, name);
|
||||
|
||||
assert_eq!(output.is_some(), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_type_from_name_has_item_multiple_entries() {
|
||||
let name = "some-name";
|
||||
let input = vec![
|
||||
FullType {
|
||||
kind: None,
|
||||
name: Some(name.to_string()),
|
||||
description: None,
|
||||
fields: None,
|
||||
input_fields: None,
|
||||
interfaces: None,
|
||||
enum_values: None,
|
||||
possible_types: None,
|
||||
},
|
||||
FullType {
|
||||
kind: None,
|
||||
name: Some(name.to_string()),
|
||||
description: None,
|
||||
fields: None,
|
||||
input_fields: None,
|
||||
interfaces: None,
|
||||
enum_values: None,
|
||||
possible_types: None,
|
||||
},
|
||||
];
|
||||
let output = get_type_from_name(&input, name);
|
||||
|
||||
assert_eq!(output.is_some(), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_ref_is_optional_has_none() {
|
||||
let input = TypeRef {
|
||||
kind: None,
|
||||
name: None,
|
||||
of_type: None,
|
||||
};
|
||||
let output = type_ref_is_optional(&input);
|
||||
|
||||
assert_eq!(output, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_ref_is_optional_is_required() {
|
||||
let input = TypeRef {
|
||||
kind: Some(__TypeKind::NON_NULL),
|
||||
name: None,
|
||||
of_type: None,
|
||||
};
|
||||
let output = type_ref_is_optional(&input);
|
||||
|
||||
assert_eq!(output, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_ref_is_optional_is_optional() {
|
||||
let input = TypeRef {
|
||||
kind: Some(__TypeKind::OBJECT),
|
||||
name: None,
|
||||
of_type: None,
|
||||
};
|
||||
let output = type_ref_is_optional(&input);
|
||||
|
||||
assert_eq!(output, true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn input_values_has_optionals_none() {
|
||||
let input = vec![];
|
||||
|
||||
let output = input_values_has_optionals(&input);
|
||||
|
||||
assert_eq!(output, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn input_values_has_optionals_has_optional() {
|
||||
let input = vec![
|
||||
InputValue {
|
||||
name: "some-name".to_string(),
|
||||
description: None,
|
||||
type_: TypeRef {
|
||||
kind: Some(__TypeKind::NON_NULL),
|
||||
name: None,
|
||||
of_type: None,
|
||||
},
|
||||
default_value: None,
|
||||
},
|
||||
InputValue {
|
||||
name: "some-other-name".to_string(),
|
||||
description: None,
|
||||
type_: TypeRef {
|
||||
kind: Some(__TypeKind::OBJECT),
|
||||
name: None,
|
||||
of_type: None,
|
||||
},
|
||||
default_value: None,
|
||||
},
|
||||
];
|
||||
|
||||
let output = input_values_has_optionals(input.iter().collect::<Vec<_>>().as_slice());
|
||||
|
||||
assert_eq!(output, true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn input_values_has_optionals_is_required() {
|
||||
let input = vec![
|
||||
InputValue {
|
||||
name: "some-name".to_string(),
|
||||
description: None,
|
||||
type_: TypeRef {
|
||||
kind: Some(__TypeKind::NON_NULL),
|
||||
name: None,
|
||||
of_type: None,
|
||||
},
|
||||
default_value: None,
|
||||
},
|
||||
InputValue {
|
||||
name: "some-other-name".to_string(),
|
||||
description: None,
|
||||
type_: TypeRef {
|
||||
kind: Some(__TypeKind::NON_NULL),
|
||||
name: None,
|
||||
of_type: None,
|
||||
},
|
||||
default_value: None,
|
||||
},
|
||||
];
|
||||
|
||||
let output = input_values_has_optionals(input.iter().collect::<Vec<_>>().as_slice());
|
||||
|
||||
assert_eq!(output, false);
|
||||
}
|
||||
}
|
21
crates/dagger-codegen/src/generator.rs
Normal file
21
crates/dagger-codegen/src/generator.rs
Normal 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;
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -1,122 +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>> {
|
||||
let mut collected_fields: 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,
|
||||
};
|
||||
|
||||
let mut tkns = rust::Tokens::new();
|
||||
|
||||
if let Some(args) = args.as_ref() {
|
||||
tkns.append(quote! {
|
||||
$description
|
||||
pub struct $(&name)Args {
|
||||
$(&args.args)
|
||||
}
|
||||
});
|
||||
tkns.push();
|
||||
}
|
||||
|
||||
tkns.append(quote! {
|
||||
pub fn $(&name)(
|
||||
&self,
|
||||
$(if let Some(_) = args.as_ref() => args: $(&name)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,
|
||||
}
|
||||
|
||||
todo!()
|
||||
}
|
||||
});
|
||||
|
||||
collected_fields.push(tkns);
|
||||
}
|
||||
|
||||
Ok(Some(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 {
|
||||
if let Some(desc) = arg.description {
|
||||
if let Some(inner_desc) = collected_arg.description.as_mut() {
|
||||
inner_desc.append(desc);
|
||||
inner_desc.push();
|
||||
}
|
||||
}
|
||||
|
||||
collected_arg.args.append(quote! {
|
||||
$(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)
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
@ -1,167 +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 out = quote! {
|
||||
$(if description.is_some() => $description)
|
||||
pub struct $name {
|
||||
$(if input_fields.is_some() => $input_fields)
|
||||
}
|
||||
|
||||
impl $name {
|
||||
$(if fields.is_some() => $fields)
|
||||
}
|
||||
};
|
||||
|
||||
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#"
|
||||
/// A directory whose contents persists across sessions
|
||||
pub struct CacheVolume {}
|
||||
|
||||
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 t: FullType = FullType {
|
||||
kind: Some(__TypeKind::OBJECT),
|
||||
name: Some("Query".into()),
|
||||
description: None,
|
||||
fields: Some(vec![FullTypeFields {
|
||||
name: Some("container".into()),
|
||||
description: Some("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()),
|
||||
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#"
|
||||
"#;
|
||||
let handler = Object {};
|
||||
let obj = handler.render(&t).unwrap();
|
||||
let actual = obj.to_file_string().unwrap();
|
||||
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
}
|
@ -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!()
|
||||
}
|
||||
}
|
@ -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")
|
||||
}
|
@ -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
|
||||
}
|
@ -1,4 +1,25 @@
|
||||
pub mod codegen;
|
||||
mod handlers;
|
||||
mod models;
|
||||
mod predicates;
|
||||
mod functions;
|
||||
mod generator;
|
||||
pub mod rust;
|
||||
pub mod utility;
|
||||
mod visitor;
|
||||
|
||||
use dagger_core::introspection::Schema;
|
||||
|
||||
use self::generator::DynGenerator;
|
||||
|
||||
fn set_schema_parents(mut schema: Schema) -> Schema {
|
||||
for t in schema.types.as_mut().into_iter().flatten().flatten() {
|
||||
let t_parent = t.full_type.clone();
|
||||
for mut field in t.full_type.fields.as_mut().into_iter().flatten() {
|
||||
field.parent_type = Some(t_parent.clone());
|
||||
}
|
||||
}
|
||||
|
||||
return schema;
|
||||
}
|
||||
|
||||
pub fn generate(schema: Schema, generator: DynGenerator) -> eyre::Result<String> {
|
||||
let schema = set_schema_parents(schema);
|
||||
generator.generate(schema)
|
||||
}
|
||||
|
@ -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),
|
||||
}
|
@ -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
|
||||
}
|
64
crates/dagger-codegen/src/rust/format.rs
Normal file
64
crates/dagger-codegen/src/rust/format.rs
Normal 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(&format_name(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(&format_name(ref_name));
|
||||
rep
|
||||
}
|
||||
}
|
206
crates/dagger-codegen/src/rust/functions.rs
Normal file
206
crates/dagger-codegen/src/rust/functions.rs
Normal file
@ -0,0 +1,206 @@
|
||||
use convert_case::{Case, Casing};
|
||||
use dagger_core::introspection::FullTypeFields;
|
||||
use genco::prelude::rust;
|
||||
use genco::quote;
|
||||
use genco::tokens::quoted;
|
||||
|
||||
use crate::functions::{
|
||||
type_field_has_optional, type_ref_is_list, type_ref_is_list_of_objects, type_ref_is_object,
|
||||
type_ref_is_optional, type_ref_is_scalar, CommonFunctions,
|
||||
};
|
||||
use crate::utility::OptionExt;
|
||||
|
||||
pub fn format_name(s: &str) -> String {
|
||||
s.to_case(Case::Pascal)
|
||||
}
|
||||
|
||||
pub fn format_struct_name(s: &str) -> String {
|
||||
s.to_case(Case::Snake)
|
||||
}
|
||||
|
||||
pub fn field_options_struct_name(field: &FullTypeFields) -> Option<String> {
|
||||
field
|
||||
.parent_type
|
||||
.as_ref()
|
||||
.map(|p| p.name.as_ref().map(|n| format_name(n)))
|
||||
.flatten()
|
||||
.zip(field.name.as_ref().map(|n| format_name(n)))
|
||||
.map(|(parent_name, field_name)| format!("{parent_name}{field_name}Opts"))
|
||||
}
|
||||
|
||||
pub fn format_function(funcs: &CommonFunctions, field: &FullTypeFields) -> Option<rust::Tokens> {
|
||||
let signature = quote! {
|
||||
pub fn $(field.name.pipe(|n | format_struct_name(n)))
|
||||
};
|
||||
let args = format_function_args(funcs, field);
|
||||
|
||||
let output_type = field
|
||||
.type_
|
||||
.pipe(|t| &t.type_ref)
|
||||
.pipe(|t| funcs.format_output_type(t));
|
||||
|
||||
Some(quote! {
|
||||
$(signature)(
|
||||
$(args)
|
||||
) -> $(output_type) {
|
||||
let mut query = self.selection.select($(quoted(field.name.as_ref())));
|
||||
|
||||
$(render_required_args(funcs, field))
|
||||
$(render_optional_args(funcs, field))
|
||||
|
||||
$(render_execution(funcs, field))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn render_required_args(funcs: &CommonFunctions, field: &FullTypeFields) -> Option<rust::Tokens> {
|
||||
if let Some(args) = field.args.as_ref() {
|
||||
let args = args
|
||||
.into_iter()
|
||||
.map(|a| {
|
||||
a.as_ref().and_then(|s| {
|
||||
if type_ref_is_optional(&s.input_value.type_) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let n = format_struct_name(&s.input_value.name);
|
||||
let name = &s.input_value.name;
|
||||
|
||||
Some(quote! {
|
||||
query = query.arg($(quoted(name)), $(n)).unwrap();
|
||||
})
|
||||
})
|
||||
})
|
||||
.flatten()
|
||||
.collect::<Vec<_>>();
|
||||
let required_args = quote! {
|
||||
$(for arg in args join ($['\r']) => $arg)
|
||||
};
|
||||
|
||||
Some(required_args)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn render_optional_args(funcs: &CommonFunctions, field: &FullTypeFields) -> Option<rust::Tokens> {
|
||||
if let Some(args) = field.args.as_ref() {
|
||||
let args = args
|
||||
.into_iter()
|
||||
.map(|a| {
|
||||
a.as_ref().and_then(|s| {
|
||||
if !type_ref_is_optional(&s.input_value.type_) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let n = format_struct_name(&s.input_value.name);
|
||||
let name = &s.input_value.name;
|
||||
|
||||
Some(quote! {
|
||||
if let Some($(&n)) = opts.$(&n) {
|
||||
query = query.arg($(quoted(name)), $(&n)).unwrap();
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
.flatten()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if args.len() == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let required_args = quote! {
|
||||
if let Some(opts) = opts {
|
||||
$(for arg in args join ($['\r']) => $arg)
|
||||
}
|
||||
};
|
||||
|
||||
Some(required_args)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn render_execution(funcs: &CommonFunctions, field: &FullTypeFields) -> rust::Tokens {
|
||||
if let Some(true) = field.type_.pipe(|t| type_ref_is_object(&t.type_ref)) {
|
||||
let output_type = funcs.format_output_type(&field.type_.as_ref().unwrap().type_ref);
|
||||
return quote! {
|
||||
return $(output_type) {
|
||||
proc: self.proc.clone(),
|
||||
selection: query,
|
||||
conn: self.conn.clone(),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(true) = field
|
||||
.type_
|
||||
.pipe(|t| type_ref_is_list_of_objects(&t.type_ref))
|
||||
{
|
||||
let output_type = funcs.format_output_type(
|
||||
&field
|
||||
.type_
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.type_ref
|
||||
.of_type
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.of_type
|
||||
.as_ref()
|
||||
.unwrap(),
|
||||
);
|
||||
return quote! {
|
||||
return vec![$(output_type) {
|
||||
proc: self.proc.clone(),
|
||||
selection: query,
|
||||
conn: self.conn.clone(),
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
let graphql_client = rust::import("crate::client", "graphql_client");
|
||||
|
||||
quote! {
|
||||
query.execute(&$graphql_client(&self.conn)).unwrap().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
fn format_function_args(funcs: &CommonFunctions, field: &FullTypeFields) -> Option<rust::Tokens> {
|
||||
if let Some(args) = field.args.as_ref() {
|
||||
let args = args
|
||||
.into_iter()
|
||||
.map(|a| {
|
||||
a.as_ref().and_then(|s| {
|
||||
if type_ref_is_optional(&s.input_value.type_) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let t = funcs.format_input_type(&s.input_value.type_);
|
||||
let n = format_struct_name(&s.input_value.name);
|
||||
|
||||
Some(quote! {
|
||||
$(n): $(t),
|
||||
})
|
||||
})
|
||||
})
|
||||
.flatten()
|
||||
.collect::<Vec<_>>();
|
||||
let required_args = quote! {
|
||||
&self,
|
||||
$(for arg in args join ($['\r']) => $arg)
|
||||
};
|
||||
|
||||
if type_field_has_optional(field) {
|
||||
Some(quote! {
|
||||
$(required_args)
|
||||
opts: Option<$(field_options_struct_name(field))>
|
||||
})
|
||||
} else {
|
||||
Some(required_args)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
114
crates/dagger-codegen/src/rust/mod.rs
Normal file
114
crates/dagger-codegen/src/rust/mod.rs
Normal file
@ -0,0 +1,114 @@
|
||||
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::functions::CommonFunctions;
|
||||
use crate::generator::Generator;
|
||||
use crate::visitor::{VisitHandlers, Visitor};
|
||||
|
||||
use self::format::FormatTypeFunc;
|
||||
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 common_funcs = Arc::new(CommonFunctions::new(Arc::new(FormatTypeFunc {})));
|
||||
println!("generating dagger for rust");
|
||||
|
||||
let visitor = Visitor {
|
||||
schema,
|
||||
handlers: VisitHandlers {
|
||||
visit_scalar: Arc::new({
|
||||
let render = render.clone();
|
||||
let common_funcs = common_funcs.clone();
|
||||
|
||||
move |t| {
|
||||
println!("generating scalar");
|
||||
let rendered_scalar = render_scalar(t)?;
|
||||
|
||||
let mut render = render.lock().unwrap();
|
||||
|
||||
render.append(rendered_scalar);
|
||||
render.push();
|
||||
|
||||
println!("generated scalar");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}),
|
||||
visit_object: Arc::new({
|
||||
let render = render.clone();
|
||||
let common_funcs = common_funcs.clone();
|
||||
|
||||
move |t| {
|
||||
println!("generating object");
|
||||
let rendered_scalar = render_object(&common_funcs, t)?;
|
||||
|
||||
let mut render = render.lock().unwrap();
|
||||
|
||||
render.append(rendered_scalar);
|
||||
render.push();
|
||||
println!("generated object");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}),
|
||||
visit_input: Arc::new({
|
||||
let render = render.clone();
|
||||
let common_funcs = common_funcs.clone();
|
||||
|
||||
move |t| {
|
||||
println!("generating input");
|
||||
let rendered_scalar = render_input(&common_funcs, t)?;
|
||||
|
||||
let mut render = render.lock().unwrap();
|
||||
|
||||
render.append(rendered_scalar);
|
||||
render.push();
|
||||
println!("generated input");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}),
|
||||
visit_enum: Arc::new({
|
||||
let render = render.clone();
|
||||
let common_funcs = common_funcs.clone();
|
||||
|
||||
move |t| {
|
||||
println!("generating enum");
|
||||
let rendered_scalar = render_enum(t)?;
|
||||
|
||||
let mut render = render.lock().unwrap();
|
||||
|
||||
render.append(rendered_scalar);
|
||||
render.push();
|
||||
println!("generated enum");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
visitor.run()?;
|
||||
|
||||
println!("done generating objects");
|
||||
|
||||
let rendered = render.lock().unwrap();
|
||||
|
||||
rendered
|
||||
.to_file_string()
|
||||
.context("could not render to file string")
|
||||
}
|
||||
}
|
33
crates/dagger-codegen/src/rust/templates/enum_tmpl.rs
Normal file
33
crates/dagger-codegen/src/rust/templates/enum_tmpl.rs
Normal file
@ -0,0 +1,33 @@
|
||||
use dagger_core::introspection::FullType;
|
||||
use genco::prelude::rust;
|
||||
use genco::quote;
|
||||
|
||||
fn render_enum_values(values: &FullType) -> Option<rust::Tokens> {
|
||||
let values = values
|
||||
.enum_values
|
||||
.as_ref()
|
||||
.into_iter()
|
||||
.map(|values| {
|
||||
values
|
||||
.into_iter()
|
||||
.map(|val| quote! { $(val.name.as_ref()) })
|
||||
})
|
||||
.flatten()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut tokens = rust::Tokens::new();
|
||||
for val in values {
|
||||
tokens.append(val);
|
||||
tokens.push();
|
||||
}
|
||||
|
||||
Some(tokens)
|
||||
}
|
||||
|
||||
pub fn render_enum(t: &FullType) -> eyre::Result<rust::Tokens> {
|
||||
Ok(quote! {
|
||||
pub enum $(t.name.as_ref()) {
|
||||
$(render_enum_values(t))
|
||||
}
|
||||
})
|
||||
}
|
38
crates/dagger-codegen/src/rust/templates/input_tmpl.rs
Normal file
38
crates/dagger-codegen/src/rust/templates/input_tmpl.rs
Normal file
@ -0,0 +1,38 @@
|
||||
use dagger_core::introspection::{FullType, FullTypeInputFields};
|
||||
use genco::prelude::rust;
|
||||
use genco::quote;
|
||||
|
||||
use crate::functions::CommonFunctions;
|
||||
use crate::rust::functions::{format_name, format_struct_name};
|
||||
|
||||
pub fn render_input(funcs: &CommonFunctions, t: &FullType) -> eyre::Result<rust::Tokens> {
|
||||
let deserialize = rust::import("serde", "Deserialize");
|
||||
let serialize = rust::import("serde", "Serialize");
|
||||
Ok(quote! {
|
||||
#[derive($serialize, $deserialize)]
|
||||
pub struct $(format_name(t.name.as_ref().unwrap())) {
|
||||
$(render_input_fields(funcs, t.input_fields.as_ref().unwrap_or(&Vec::new()) ))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn render_input_fields(
|
||||
funcs: &CommonFunctions,
|
||||
fields: &[FullTypeInputFields],
|
||||
) -> Option<rust::Tokens> {
|
||||
let rendered_fields = fields.iter().map(|f| render_input_field(funcs, f));
|
||||
|
||||
if rendered_fields.len() == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(quote! {
|
||||
$(for field in rendered_fields join ($['\r']) => $field)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_input_field(funcs: &CommonFunctions, field: &FullTypeInputFields) -> rust::Tokens {
|
||||
quote! {
|
||||
pub $(format_struct_name(&field.input_value.name)): $(funcs.format_input_type(&field.input_value.type_)),
|
||||
}
|
||||
}
|
4
crates/dagger-codegen/src/rust/templates/mod.rs
Normal file
4
crates/dagger-codegen/src/rust/templates/mod.rs
Normal file
@ -0,0 +1,4 @@
|
||||
pub mod enum_tmpl;
|
||||
pub mod input_tmpl;
|
||||
pub mod object_tmpl;
|
||||
pub mod scalar_tmpl;
|
112
crates/dagger-codegen/src/rust/templates/object_tmpl.rs
Normal file
112
crates/dagger-codegen/src/rust/templates/object_tmpl.rs
Normal file
@ -0,0 +1,112 @@
|
||||
use dagger_core::introspection::{FullType, FullTypeFields, FullTypeFieldsArgs};
|
||||
use genco::prelude::rust;
|
||||
use genco::quote;
|
||||
|
||||
use crate::functions::{type_ref_is_optional, CommonFunctions};
|
||||
use crate::rust::functions::{
|
||||
field_options_struct_name, format_function, format_name, format_struct_name,
|
||||
};
|
||||
use crate::utility::OptionExt;
|
||||
|
||||
pub fn render_object(funcs: &CommonFunctions, t: &FullType) -> eyre::Result<rust::Tokens> {
|
||||
let selection = rust::import("crate::querybuilder", "Selection");
|
||||
let child = rust::import("std::process", "Child");
|
||||
let conn = rust::import("dagger_core::connect_params", "ConnectParams");
|
||||
let arc = rust::import("std::sync", "Arc");
|
||||
|
||||
Ok(quote! {
|
||||
pub struct $(t.name.pipe(|s| format_name(s))) {
|
||||
pub proc: $arc<$child>,
|
||||
pub selection: $selection,
|
||||
pub conn: $conn,
|
||||
}
|
||||
|
||||
$(t.fields.pipe(|f| render_optional_args(funcs, f)))
|
||||
|
||||
impl $(t.name.pipe(|s| format_name(s))) {
|
||||
$(t.fields.pipe(|f| render_functions(funcs, f)))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn render_optional_args(
|
||||
funcs: &CommonFunctions,
|
||||
fields: &Vec<FullTypeFields>,
|
||||
) -> Option<rust::Tokens> {
|
||||
let rendered_fields = fields
|
||||
.iter()
|
||||
.map(|f| render_optional_arg(funcs, f))
|
||||
.flatten()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if rendered_fields.len() == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(quote! {
|
||||
$(for field in rendered_fields join ($['\r']) => $field)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn render_optional_arg(funcs: &CommonFunctions, field: &FullTypeFields) -> Option<rust::Tokens> {
|
||||
let output_type = field_options_struct_name(field);
|
||||
let fields = field
|
||||
.args
|
||||
.pipe(|t| t.into_iter().flatten().collect::<Vec<_>>())
|
||||
.map(|t| {
|
||||
t.into_iter()
|
||||
.filter(|t| type_ref_is_optional(&t.input_value.type_))
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.pipe(|t| render_optional_field_args(funcs, t))
|
||||
.flatten();
|
||||
|
||||
if let Some(fields) = fields {
|
||||
Some(quote! {
|
||||
pub struct $output_type {
|
||||
$fields
|
||||
}
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn render_optional_field_args(
|
||||
funcs: &CommonFunctions,
|
||||
args: &Vec<&FullTypeFieldsArgs>,
|
||||
) -> Option<rust::Tokens> {
|
||||
if args.len() == 0 {
|
||||
return None;
|
||||
}
|
||||
let rendered_args = args.into_iter().map(|a| &a.input_value).map(|a| {
|
||||
quote! {
|
||||
pub $(format_struct_name(&a.name)): Option<$(funcs.format_output_type(&a.type_))>,
|
||||
}
|
||||
});
|
||||
|
||||
Some(quote! {
|
||||
$(for arg in rendered_args join ($['\r']) => $arg)
|
||||
})
|
||||
}
|
||||
|
||||
fn render_functions(funcs: &CommonFunctions, fields: &Vec<FullTypeFields>) -> Option<rust::Tokens> {
|
||||
let rendered_functions = fields
|
||||
.iter()
|
||||
.map(|f| render_function(funcs, f))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if rendered_functions.len() > 0 {
|
||||
Some(quote! {
|
||||
$(for func in rendered_functions join ($['\r']) => $func)
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn render_function(funcs: &CommonFunctions, field: &FullTypeFields) -> Option<rust::Tokens> {
|
||||
Some(quote! {
|
||||
$(format_function(funcs, field))
|
||||
})
|
||||
}
|
16
crates/dagger-codegen/src/rust/templates/scalar_tmpl.rs
Normal file
16
crates/dagger-codegen/src/rust/templates/scalar_tmpl.rs
Normal file
@ -0,0 +1,16 @@
|
||||
use dagger_core::introspection::FullType;
|
||||
use genco::prelude::rust;
|
||||
use genco::quote;
|
||||
|
||||
use crate::rust::functions::format_name;
|
||||
use crate::utility::OptionExt;
|
||||
|
||||
pub fn render_scalar(t: &FullType) -> eyre::Result<rust::Tokens> {
|
||||
let deserialize = rust::import("serde", "Deserialize");
|
||||
let serialize = rust::import("serde", "Serialize");
|
||||
|
||||
Ok(quote! {
|
||||
#[derive($serialize, $deserialize)]
|
||||
pub struct $(t.name.pipe(|n|format_name(n)))(String);
|
||||
})
|
||||
}
|
18
crates/dagger-codegen/src/utility.rs
Normal file
18
crates/dagger-codegen/src/utility.rs
Normal file
@ -0,0 +1,18 @@
|
||||
pub trait OptionExt<'t, T: 't> {
|
||||
fn pipe<U, F>(&'t self, f: F) -> Option<U>
|
||||
where
|
||||
F: FnOnce(&'t T) -> U;
|
||||
}
|
||||
|
||||
impl<'t, T: 't> OptionExt<'t, T> for Option<T> {
|
||||
#[inline]
|
||||
fn pipe<U, F>(&'t self, f: F) -> Option<U>
|
||||
where
|
||||
F: FnOnce(&'t T) -> U,
|
||||
{
|
||||
match *self {
|
||||
Some(ref x) => Some(f(x)),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
106
crates/dagger-codegen/src/visitor.rs
Normal file
106
crates/dagger-codegen/src/visitor.rs
Normal file
@ -0,0 +1,106 @@
|
||||
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 name.starts_with("__") {
|
||||
return false;
|
||||
}
|
||||
if ignore.contains(name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
(None, Some(name)) => {
|
||||
if name.starts_with("__") {
|
||||
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(())
|
||||
}
|
||||
}
|
@ -161,6 +161,9 @@ pub struct FullTypeFields {
|
||||
pub type_: Option<FullTypeFieldsType>,
|
||||
pub is_deprecated: Option<bool>,
|
||||
pub deprecation_reason: Option<String>,
|
||||
|
||||
#[serde(skip)]
|
||||
pub parent_type: Option<FullType>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,20 +1,21 @@
|
||||
use dagger_sdk::client::connect;
|
||||
use dagger_sdk::gen::ContainerExecOpts;
|
||||
|
||||
#[test]
|
||||
fn test_example_container() {
|
||||
let client = connect().unwrap();
|
||||
|
||||
let alpine = client.container(None, None).from("alpine:3.16.2".into());
|
||||
let alpine = client.container(None).from("alpine:3.16.2".into());
|
||||
|
||||
let out = alpine
|
||||
.exec(
|
||||
Some(vec!["cat".into(), "/etc/alpine-release".into()]),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.exec(Some(ContainerExecOpts {
|
||||
args: Some(vec!["cat".into(), "/etc/alpine-release".into()]),
|
||||
stdin: None,
|
||||
redirect_stdout: None,
|
||||
redirect_stderr: None,
|
||||
experimental_privileged_nesting: None,
|
||||
}))
|
||||
.stdout();
|
||||
|
||||
assert_eq!(out, Some("3.16.2\n".to_string()))
|
||||
assert_eq!(out, "3.16.2\n".to_string())
|
||||
}
|
||||
|
@ -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)?;
|
||||
|
Loading…
Reference in New Issue
Block a user