From 4a4c03f3c2ee7f6268c65976715e70767b4ea78d Mon Sep 17 00:00:00 2001 From: Kasper Juul Hermansen Date: Fri, 17 Feb 2023 12:33:16 +0100 Subject: [PATCH] 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 * run full alpine * add roadmap item --- README.md | 2 +- crates/dagger-codegen/src/codegen.rs | 155 -- crates/dagger-codegen/src/functions.rs | 422 ++++ crates/dagger-codegen/src/generator.rs | 21 + .../src/handlers/enumeration.rs | 33 - crates/dagger-codegen/src/handlers/fields.rs | 122 - crates/dagger-codegen/src/handlers/input.rs | 110 - .../src/handlers/input_field.rs | 22 - crates/dagger-codegen/src/handlers/mod.rs | 93 - crates/dagger-codegen/src/handlers/object.rs | 167 -- crates/dagger-codegen/src/handlers/scalar.rs | 42 - .../dagger-codegen/src/handlers/type_ref.rs | 97 - crates/dagger-codegen/src/handlers/utility.rs | 56 - crates/dagger-codegen/src/lib.rs | 29 +- crates/dagger-codegen/src/models.rs | 11 - crates/dagger-codegen/src/predicates.rs | 101 - crates/dagger-codegen/src/rust/format.rs | 64 + crates/dagger-codegen/src/rust/functions.rs | 206 ++ crates/dagger-codegen/src/rust/mod.rs | 114 + .../src/rust/templates/enum_tmpl.rs | 33 + .../src/rust/templates/input_tmpl.rs | 38 + .../dagger-codegen/src/rust/templates/mod.rs | 4 + .../src/rust/templates/object_tmpl.rs | 112 + .../src/rust/templates/scalar_tmpl.rs | 16 + crates/dagger-codegen/src/utility.rs | 18 + crates/dagger-codegen/src/visitor.rs | 106 + crates/dagger-core/src/introspection.rs | 3 + crates/dagger-sdk/src/gen.rs | 1977 +++++++++++------ crates/dagger-sdk/tests/mod.rs | 19 +- src/cli_generate.rs | 9 +- 30 files changed, 2478 insertions(+), 1724 deletions(-) delete mode 100644 crates/dagger-codegen/src/codegen.rs create mode 100644 crates/dagger-codegen/src/functions.rs create mode 100644 crates/dagger-codegen/src/generator.rs delete mode 100644 crates/dagger-codegen/src/handlers/enumeration.rs delete mode 100644 crates/dagger-codegen/src/handlers/fields.rs delete mode 100644 crates/dagger-codegen/src/handlers/input.rs delete mode 100644 crates/dagger-codegen/src/handlers/input_field.rs delete mode 100644 crates/dagger-codegen/src/handlers/mod.rs delete mode 100644 crates/dagger-codegen/src/handlers/object.rs delete mode 100644 crates/dagger-codegen/src/handlers/scalar.rs delete mode 100644 crates/dagger-codegen/src/handlers/type_ref.rs delete mode 100644 crates/dagger-codegen/src/handlers/utility.rs delete mode 100644 crates/dagger-codegen/src/models.rs delete mode 100644 crates/dagger-codegen/src/predicates.rs create mode 100644 crates/dagger-codegen/src/rust/format.rs create mode 100644 crates/dagger-codegen/src/rust/functions.rs create mode 100644 crates/dagger-codegen/src/rust/mod.rs create mode 100644 crates/dagger-codegen/src/rust/templates/enum_tmpl.rs create mode 100644 crates/dagger-codegen/src/rust/templates/input_tmpl.rs create mode 100644 crates/dagger-codegen/src/rust/templates/mod.rs create mode 100644 crates/dagger-codegen/src/rust/templates/object_tmpl.rs create mode 100644 crates/dagger-codegen/src/rust/templates/scalar_tmpl.rs create mode 100644 crates/dagger-codegen/src/utility.rs create mode 100644 crates/dagger-codegen/src/visitor.rs diff --git a/README.md b/README.md index 30a6ab5..291fb8a 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/crates/dagger-codegen/src/codegen.rs b/crates/dagger-codegen/src/codegen.rs deleted file mode 100644 index 94d167e..0000000 --- a/crates/dagger-codegen/src/codegen.rs +++ /dev/null @@ -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 { - 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 { - 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> = 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::().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)> { - let mut group = vec![]; - - for handler in self.handlers.iter() { - let mut group_types: Vec = 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 { - 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 { - 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> { - 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) -} diff --git a/crates/dagger-codegen/src/functions.rs b/crates/dagger-codegen/src/functions.rs new file mode 100644 index 0000000..b89304b --- /dev/null +++ b/crates/dagger-codegen/src/functions.rs @@ -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; + +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::>() + }) + .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::>() + .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::>().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::>().as_slice()); + + assert_eq!(output, false); + } +} diff --git a/crates/dagger-codegen/src/generator.rs b/crates/dagger-codegen/src/generator.rs new file mode 100644 index 0000000..5ef6e8a --- /dev/null +++ b/crates/dagger-codegen/src/generator.rs @@ -0,0 +1,21 @@ +use std::sync::Arc; + +use dagger_core::introspection::Schema; + +pub trait Generator { + fn generate(&self, schema: Schema) -> eyre::Result; +} + +pub type DynGenerator = Arc; + +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; +} diff --git a/crates/dagger-codegen/src/handlers/enumeration.rs b/crates/dagger-codegen/src/handlers/enumeration.rs deleted file mode 100644 index ee2a7b8..0000000 --- a/crates/dagger-codegen/src/handlers/enumeration.rs +++ /dev/null @@ -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 { - 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) - } -} diff --git a/crates/dagger-codegen/src/handlers/fields.rs b/crates/dagger-codegen/src/handlers/fields.rs deleted file mode 100644 index 93fd614..0000000 --- a/crates/dagger-codegen/src/handlers/fields.rs +++ /dev/null @@ -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) -> eyre::Result> { - let mut collected_fields: Vec = 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, - type_: rust::Tokens, -} - -struct CollectedArgs { - description: Option, - args: rust::Tokens, -} - -fn render_args(args: &[Option]) -> Option { - let mut collected_args: Vec = 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 { - let inner = &field.type_.as_ref().unwrap(); - type_ref::render_type_ref(&inner.type_ref) -} diff --git a/crates/dagger-codegen/src/handlers/input.rs b/crates/dagger-codegen/src/handlers/input.rs deleted file mode 100644 index 9d16578..0000000 --- a/crates/dagger-codegen/src/handlers/input.rs +++ /dev/null @@ -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 { - 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); - } -} diff --git a/crates/dagger-codegen/src/handlers/input_field.rs b/crates/dagger-codegen/src/handlers/input_field.rs deleted file mode 100644 index 2bd7ccd..0000000 --- a/crates/dagger-codegen/src/handlers/input_field.rs +++ /dev/null @@ -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, -) -> eyre::Result> { - 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 { - let inner = &field.input_value.type_; - type_ref::render_type_ref(inner) -} diff --git a/crates/dagger-codegen/src/handlers/mod.rs b/crates/dagger-codegen/src/handlers/mod.rs deleted file mode 100644 index a584e87..0000000 --- a/crates/dagger-codegen/src/handlers/mod.rs +++ /dev/null @@ -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 { - 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 { - 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 { - let name = t.name.as_ref().ok_or(eyre::anyhow!("name not found"))?; - - Ok(quote! { - impl $name {} { - // TODO: Add fields - } - }) - } -} - -pub type DynHandler = Arc; -pub type Handlers = Vec; - -#[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() - ); - } -} diff --git a/crates/dagger-codegen/src/handlers/object.rs b/crates/dagger-codegen/src/handlers/object.rs deleted file mode 100644 index 114fdec..0000000 --- a/crates/dagger-codegen/src/handlers/object.rs +++ /dev/null @@ -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 { - 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); - } -} diff --git a/crates/dagger-codegen/src/handlers/scalar.rs b/crates/dagger-codegen/src/handlers/scalar.rs deleted file mode 100644 index e8d7b2f..0000000 --- a/crates/dagger-codegen/src/handlers/scalar.rs +++ /dev/null @@ -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 { - 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 { - 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 { - todo!() - } -} diff --git a/crates/dagger-codegen/src/handlers/type_ref.rs b/crates/dagger-codegen/src/handlers/type_ref.rs deleted file mode 100644 index 1096313..0000000 --- a/crates/dagger-codegen/src/handlers/type_ref.rs +++ /dev/null @@ -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 { - let extract_of_type = |t: &TypeRef| -> Option { - 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> - }); - } - 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") -} diff --git a/crates/dagger-codegen/src/handlers/utility.rs b/crates/dagger-codegen/src/handlers/utility.rs deleted file mode 100644 index 3fc9f6d..0000000 --- a/crates/dagger-codegen/src/handlers/utility.rs +++ /dev/null @@ -1,56 +0,0 @@ -use dagger_core::introspection::{FullType, FullTypeFields, InputValue}; -use genco::{prelude::*, quote}; - -pub fn render_description(t: &FullType) -> Option { - 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 { - 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 { - if let Some(description) = t.description.as_ref() { - if description == "" { - return None; - } - let lines = description.split('\n').collect::>(); - 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 -} diff --git a/crates/dagger-codegen/src/lib.rs b/crates/dagger-codegen/src/lib.rs index af8fd65..4ca63cd 100644 --- a/crates/dagger-codegen/src/lib.rs +++ b/crates/dagger-codegen/src/lib.rs @@ -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 { + let schema = set_schema_parents(schema); + generator.generate(schema) +} diff --git a/crates/dagger-codegen/src/models.rs b/crates/dagger-codegen/src/models.rs deleted file mode 100644 index 7ed5a54..0000000 --- a/crates/dagger-codegen/src/models.rs +++ /dev/null @@ -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), -} diff --git a/crates/dagger-codegen/src/predicates.rs b/crates/dagger-codegen/src/predicates.rs deleted file mode 100644 index d75f0ce..0000000 --- a/crates/dagger-codegen/src/predicates.rs +++ /dev/null @@ -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 -} diff --git a/crates/dagger-codegen/src/rust/format.rs b/crates/dagger-codegen/src/rust/format.rs new file mode 100644 index 0000000..1f32f30 --- /dev/null +++ b/crates/dagger-codegen/src/rust/format.rs @@ -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 + } +} diff --git a/crates/dagger-codegen/src/rust/functions.rs b/crates/dagger-codegen/src/rust/functions.rs new file mode 100644 index 0000000..e91d921 --- /dev/null +++ b/crates/dagger-codegen/src/rust/functions.rs @@ -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 { + 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 { + 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 { + 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::>(); + 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 { + 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::>(); + + 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 { + 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::>(); + 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 + } +} diff --git a/crates/dagger-codegen/src/rust/mod.rs b/crates/dagger-codegen/src/rust/mod.rs new file mode 100644 index 0000000..46ef19f --- /dev/null +++ b/crates/dagger-codegen/src/rust/mod.rs @@ -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 { + 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") + } +} diff --git a/crates/dagger-codegen/src/rust/templates/enum_tmpl.rs b/crates/dagger-codegen/src/rust/templates/enum_tmpl.rs new file mode 100644 index 0000000..860584d --- /dev/null +++ b/crates/dagger-codegen/src/rust/templates/enum_tmpl.rs @@ -0,0 +1,33 @@ +use dagger_core::introspection::FullType; +use genco::prelude::rust; +use genco::quote; + +fn render_enum_values(values: &FullType) -> Option { + let values = values + .enum_values + .as_ref() + .into_iter() + .map(|values| { + values + .into_iter() + .map(|val| quote! { $(val.name.as_ref()) }) + }) + .flatten() + .collect::>(); + + 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 { + Ok(quote! { + pub enum $(t.name.as_ref()) { + $(render_enum_values(t)) + } + }) +} diff --git a/crates/dagger-codegen/src/rust/templates/input_tmpl.rs b/crates/dagger-codegen/src/rust/templates/input_tmpl.rs new file mode 100644 index 0000000..6632981 --- /dev/null +++ b/crates/dagger-codegen/src/rust/templates/input_tmpl.rs @@ -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 { + 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 { + 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_)), + } +} diff --git a/crates/dagger-codegen/src/rust/templates/mod.rs b/crates/dagger-codegen/src/rust/templates/mod.rs new file mode 100644 index 0000000..b37c44f --- /dev/null +++ b/crates/dagger-codegen/src/rust/templates/mod.rs @@ -0,0 +1,4 @@ +pub mod enum_tmpl; +pub mod input_tmpl; +pub mod object_tmpl; +pub mod scalar_tmpl; diff --git a/crates/dagger-codegen/src/rust/templates/object_tmpl.rs b/crates/dagger-codegen/src/rust/templates/object_tmpl.rs new file mode 100644 index 0000000..96f7f63 --- /dev/null +++ b/crates/dagger-codegen/src/rust/templates/object_tmpl.rs @@ -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 { + 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, +) -> Option { + let rendered_fields = fields + .iter() + .map(|f| render_optional_arg(funcs, f)) + .flatten() + .collect::>(); + + 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 { + let output_type = field_options_struct_name(field); + let fields = field + .args + .pipe(|t| t.into_iter().flatten().collect::>()) + .map(|t| { + t.into_iter() + .filter(|t| type_ref_is_optional(&t.input_value.type_)) + .collect::>() + }) + .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 { + 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) -> Option { + let rendered_functions = fields + .iter() + .map(|f| render_function(funcs, f)) + .collect::>(); + + 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 { + Some(quote! { + $(format_function(funcs, field)) + }) +} diff --git a/crates/dagger-codegen/src/rust/templates/scalar_tmpl.rs b/crates/dagger-codegen/src/rust/templates/scalar_tmpl.rs new file mode 100644 index 0000000..a83c664 --- /dev/null +++ b/crates/dagger-codegen/src/rust/templates/scalar_tmpl.rs @@ -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 { + 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); + }) +} diff --git a/crates/dagger-codegen/src/utility.rs b/crates/dagger-codegen/src/utility.rs new file mode 100644 index 0000000..11594e5 --- /dev/null +++ b/crates/dagger-codegen/src/utility.rs @@ -0,0 +1,18 @@ +pub trait OptionExt<'t, T: 't> { + fn pipe(&'t self, f: F) -> Option + where + F: FnOnce(&'t T) -> U; +} + +impl<'t, T: 't> OptionExt<'t, T> for Option { + #[inline] + fn pipe(&'t self, f: F) -> Option + where + F: FnOnce(&'t T) -> U, + { + match *self { + Some(ref x) => Some(f(x)), + None => None, + } + } +} diff --git a/crates/dagger-codegen/src/visitor.rs b/crates/dagger-codegen/src/visitor.rs new file mode 100644 index 0000000..4bd6924 --- /dev/null +++ b/crates/dagger-codegen/src/visitor.rs @@ -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 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>, +} + +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::>>()?; + + Ok(()) + } +} diff --git a/crates/dagger-core/src/introspection.rs b/crates/dagger-core/src/introspection.rs index 3ae0d03..87a93ad 100644 --- a/crates/dagger-core/src/introspection.rs +++ b/crates/dagger-core/src/introspection.rs @@ -161,6 +161,9 @@ pub struct FullTypeFields { pub type_: Option, pub is_deprecated: Option, pub deprecation_reason: Option, + + #[serde(skip)] + pub parent_type: Option, } #[derive(Clone, Debug, Deserialize)] diff --git a/crates/dagger-sdk/src/gen.rs b/crates/dagger-sdk/src/gen.rs index 14caa4b..5d77a12 100644 --- a/crates/dagger-sdk/src/gen.rs +++ b/crates/dagger-sdk/src/gen.rs @@ -1,852 +1,1431 @@ +use crate::client::graphql_client; +use crate::querybuilder::Selection; +use dagger_core::connect_params::ConnectParams; +use serde::{Deserialize, Serialize}; use std::process::Child; use std::sync::Arc; -use dagger_core::connect_params::ConnectParams; -use dagger_core::{Boolean, Input, Int, Scalar}; - -use crate::client::graphql_client; -use crate::querybuilder::Selection; - -// code generated by dagger. DO NOT EDIT. - -/// A global cache volume identifier. -pub struct CacheID(Scalar); - -/// A unique container identifier. Null designates an empty container (scratch). -pub struct ContainerID(Scalar); - -/// A content-addressed directory identifier. -pub struct DirectoryID(Scalar); - -/// A file identifier. -pub struct FileID(Scalar); - -/// The platform config OS and architecture in a Container. -/// The format is [os]/[platform]/[version] (e.g. darwin/arm64/v7, windows/amd64, linux/arm64). -pub struct Platform(Scalar); - -/// A unique identifier for a secret. -pub struct SecretID(Scalar); - -/// A content-addressed socket identifier. -pub struct SocketID(Scalar); - -/// +#[derive(Serialize, Deserialize)] +pub struct CacheId(String); +#[derive(Serialize, Deserialize)] +pub struct ContainerId(String); +#[derive(Serialize, Deserialize)] +pub struct DirectoryId(String); +#[derive(Serialize, Deserialize)] +pub struct FileId(String); +#[derive(Serialize, Deserialize)] +pub struct Platform(String); +#[derive(Serialize, Deserialize)] +pub struct SecretId(String); +#[derive(Serialize, Deserialize)] +pub struct SocketId(String); +#[derive(Serialize, Deserialize)] pub struct BuildArg { - pub name: String, - pub value: String, + pub name: String, } - -impl Input for BuildArg {} - -/// A directory whose contents persist across runs. -pub struct CacheVolume {} - -impl CacheVolume { - /// - pub fn id(&self) -> CacheID { - todo!() - } -} - -impl Input for CacheVolume {} - -/// An OCI-compatible container, also known as a docker container. -pub struct Container { - pub conn: ConnectParams, +pub struct CacheVolume { pub proc: Arc, pub selection: Selection, + pub conn: ConnectParams, +} + +impl CacheVolume { + pub fn id(&self) -> CacheId { + let mut query = self.selection.select("id"); + + query.execute(&graphql_client(&self.conn)).unwrap().unwrap() + } +} +pub struct Container { + pub proc: Arc, + pub selection: Selection, + pub conn: ConnectParams, +} + +pub struct ContainerBuildOpts { + pub dockerfile: Option, + pub build_args: Option>, + pub target: Option, +} +pub struct ContainerExecOpts { + pub args: Option>, + pub stdin: Option, + pub redirect_stdout: Option, + pub redirect_stderr: Option, + pub experimental_privileged_nesting: Option, +} +pub struct ContainerExportOpts { + pub platform_variants: Option>, +} +pub struct ContainerPipelineOpts { + pub description: Option, +} +pub struct ContainerPublishOpts { + pub platform_variants: Option>, +} +pub struct ContainerWithDefaultArgsOpts { + pub args: Option>, +} +pub struct ContainerWithDirectoryOpts { + pub exclude: Option>, + pub include: Option>, +} +pub struct ContainerWithExecOpts { + pub stdin: Option, + pub redirect_stdout: Option, + pub redirect_stderr: Option, + pub experimental_privileged_nesting: Option, +} +pub struct ContainerWithFileOpts { + pub permissions: Option, +} +pub struct ContainerWithMountedCacheOpts { + pub source: Option, +} +pub struct ContainerWithNewFileOpts { + pub contents: Option, + pub permissions: Option, } impl Container { - /// Initializes this container from a Dockerfile build, using the context, a dockerfile file path and some additional buildArgs. - /// # Arguments - /// - /// * `context` - Directory context used by the Dockerfile. - /// * `dockerfile` - Path to the Dockerfile to use. - /// Defaults to './Dockerfile'. - /// * `buildArgs` - Additional build arguments. - /// * `target` - Target build stage to build. - pub fn build( - &self, - _context: DirectoryID, - _dockerfile: Option, - _build_args: Option>, - _target: Option, - ) -> Container { - todo!() - } + pub fn build(&self, context: DirectoryId, opts: Option) -> Container { + let mut query = self.selection.select("build"); - /// Retrieves default arguments for future commands. - pub fn default_args(&self) -> Option> { - todo!() - } + query = query.arg("context", context).unwrap(); + if let Some(opts) = opts { + if let Some(dockerfile) = opts.dockerfile { + query = query.arg("dockerfile", dockerfile).unwrap(); + } + if let Some(build_args) = opts.build_args { + query = query.arg("buildArgs", build_args).unwrap(); + } + if let Some(target) = opts.target { + query = query.arg("target", target).unwrap(); + } + } - /// Retrieves a directory at the given path. Mounts are included. - pub fn directory(&self, _path: String) -> Directory { - todo!() + return Container { + proc: self.proc.clone(), + selection: query, + conn: self.conn.clone(), + }; } + pub fn default_args(&self) -> Vec { + let mut query = self.selection.select("defaultArgs"); - /// Retrieves entrypoint to be prepended to the arguments of all commands. - pub fn entrypoint(&self) -> Option> { - todo!() + query.execute(&graphql_client(&self.conn)).unwrap().unwrap() } + pub fn directory(&self, path: String) -> Directory { + let mut query = self.selection.select("directory"); - /// Retrieves the value of the specified environment variable. - pub fn env_variable(&self, _name: String) -> Option { - todo!() + query = query.arg("path", path).unwrap(); + + return Directory { + proc: self.proc.clone(), + selection: query, + conn: self.conn.clone(), + }; } + pub fn entrypoint(&self) -> Vec { + let mut query = self.selection.select("entrypoint"); - /// Retrieves the list of environment variables passed to commands. + query.execute(&graphql_client(&self.conn)).unwrap().unwrap() + } + pub fn env_variable(&self, name: String) -> String { + let mut query = self.selection.select("envVariable"); + + query = query.arg("name", name).unwrap(); + + query.execute(&graphql_client(&self.conn)).unwrap().unwrap() + } pub fn env_variables(&self) -> Vec { - todo!() - } + let mut query = self.selection.select("envVariables"); - /// Retrieves this container after executing the specified command inside it. - /// # Arguments - /// - /// * `args` - Command to run instead of the container's default command. - /// * `stdin` - Content to write to the command's standard input before closing. - /// * `redirectStdout` - Redirect the command's standard output to a file in the container. - /// * `redirectStderr` - Redirect the command's standard error to a file in the container. - /// * `experimentalPrivilegedNesting` - Provide dagger access to the executed command. - /// Do not use this option unless you trust the command being executed. - /// The command being executed WILL BE GRANTED FULL ACCESS TO YOUR HOST FILESYSTEM. - pub fn exec( - &self, - args: Option>, - _stdin: Option, - _redirect_stdout: Option, - _redirect_stderr: Option, - _experimental_privileged_nesting: Option, - ) -> Container { - let query = self.selection.select("exec"); - let query = query.arg("args", args).unwrap(); - - Container { - conn: self.conn.clone(), + return vec![EnvVariable { proc: self.proc.clone(), selection: query, + conn: self.conn.clone(), + }]; + } + pub fn exec(&self, opts: Option) -> Container { + let mut query = self.selection.select("exec"); + + if let Some(opts) = opts { + if let Some(args) = opts.args { + query = query.arg("args", args).unwrap(); + } + if let Some(stdin) = opts.stdin { + query = query.arg("stdin", stdin).unwrap(); + } + if let Some(redirect_stdout) = opts.redirect_stdout { + query = query.arg("redirectStdout", redirect_stdout).unwrap(); + } + if let Some(redirect_stderr) = opts.redirect_stderr { + query = query.arg("redirectStderr", redirect_stderr).unwrap(); + } + if let Some(experimental_privileged_nesting) = opts.experimental_privileged_nesting { + query = query + .arg( + "experimentalPrivilegedNesting", + experimental_privileged_nesting, + ) + .unwrap(); + } } - } - /// Exit code of the last executed command. Zero means success. - /// Null if no command has been executed. - pub fn exit_code(&self) -> Option { - todo!() + return Container { + proc: self.proc.clone(), + selection: query, + conn: self.conn.clone(), + }; } + pub fn exit_code(&self) -> isize { + let mut query = self.selection.select("exitCode"); - /// Writes the container as an OCI tarball to the destination file path on the host for the specified platformVariants. - /// Return true on success. - /// # Arguments - /// - /// * `path` - Host's destination path. - /// Path can be relative to the engine's workdir or absolute. - /// * `platformVariants` - Identifiers for other platform specific containers. - /// Used for multi-platform image. - pub fn export(&self, _path: String, _platform_variants: Option>) -> Boolean { - todo!() + query.execute(&graphql_client(&self.conn)).unwrap().unwrap() } + pub fn export(&self, path: String, opts: Option) -> bool { + let mut query = self.selection.select("export"); - /// Retrieves a file at the given path. Mounts are included. - pub fn file(&self, _path: String) -> File { - todo!() + query = query.arg("path", path).unwrap(); + if let Some(opts) = opts { + if let Some(platform_variants) = opts.platform_variants { + query = query.arg("platformVariants", platform_variants).unwrap(); + } + } + + query.execute(&graphql_client(&self.conn)).unwrap().unwrap() } + pub fn file(&self, path: String) -> File { + let mut query = self.selection.select("file"); - /// Initializes this container from the base image published at the given address. - /// # Arguments - /// - /// * `address` - Image's address from its registry. - /// Formatted as [host]/[user]/[repo]:[tag] (e.g. docker.io/dagger/dagger:main). + query = query.arg("path", path).unwrap(); + + return File { + proc: self.proc.clone(), + selection: query, + conn: self.conn.clone(), + }; + } pub fn from(&self, address: String) -> Container { - let query = self.selection.select("from"); - let query = query.arg("address", address).unwrap(); + let mut query = self.selection.select("from"); - Container { - conn: self.conn.clone(), + query = query.arg("address", address).unwrap(); + + return Container { proc: self.proc.clone(), selection: query, - } + conn: self.conn.clone(), + }; } - - /// Retrieves this container's root filesystem. Mounts are not included. pub fn fs(&self) -> Directory { - todo!() - } + let mut query = self.selection.select("fs"); - /// A unique identifier for this container. - pub fn id(&self) -> ContainerID { - todo!() + return Directory { + proc: self.proc.clone(), + selection: query, + conn: self.conn.clone(), + }; } + pub fn id(&self) -> ContainerId { + let mut query = self.selection.select("id"); - /// Retrieves the value of the specified label. - pub fn label(&self, _name: String) -> Option { - todo!() + query.execute(&graphql_client(&self.conn)).unwrap().unwrap() } + pub fn label(&self, name: String) -> String { + let mut query = self.selection.select("label"); - /// Retrieves the list of labels passed to container. + query = query.arg("name", name).unwrap(); + + query.execute(&graphql_client(&self.conn)).unwrap().unwrap() + } pub fn labels(&self) -> Vec