diff --git a/crates/dagger-codegen/src/codegen.rs b/crates/dagger-codegen/src/codegen.rs index e815f04..ad84843 100644 --- a/crates/dagger-codegen/src/codegen.rs +++ b/crates/dagger-codegen/src/codegen.rs @@ -9,7 +9,7 @@ use graphql_introspection_query::introspection_response::{ }; use crate::{ - handlers::{enumeration::Enumeration, scalar::Scalar, DynHandler, Handlers}, + handlers::{enumeration::Enumeration, input::Input, scalar::Scalar, DynHandler, Handlers}, predicates::is_custom_scalar_type, }; @@ -21,7 +21,11 @@ pub struct CodeGeneration { impl CodeGeneration { pub fn new() -> Self { Self { - handlers: vec![Arc::new(Scalar {}), Arc::new(Enumeration {})], + handlers: vec![ + Arc::new(Scalar {}), + Arc::new(Enumeration {}), + Arc::new(Input {}), + ], } } diff --git a/crates/dagger-codegen/src/handlers/input.rs b/crates/dagger-codegen/src/handlers/input.rs new file mode 100644 index 0000000..6254e3f --- /dev/null +++ b/crates/dagger-codegen/src/handlers/input.rs @@ -0,0 +1,243 @@ +use genco::prelude::rust; +use genco::prelude::*; +use graphql_introspection_query::introspection_response::{FullType, FullTypeInputFields, TypeRef}; + +use crate::predicates::{ + is_custom_scalar_type_ref, is_input_object_type, is_list_type, is_required_type_ref, + is_scalar_type_ref, +}; + +use super::{utility::render_description, Handler}; + +pub struct Input; +impl Input { + fn render_input_fields( + &self, + 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(), + self.render_input_field(field)?, + )); + } + + Ok(Some(quote! { + $(for (name, field) in fields => pub $name: $field $['\n'] ) + })) + } + + fn render_input_field(&self, field: &FullTypeInputFields) -> eyre::Result { + let name = &field.input_value.name; + + let mut inner: Option<&TypeRef> = None; + let inner = &field.input_value.type_; + self.render_type_ref(inner) + } + + fn render_type_ref(&self, inner: &TypeRef) -> eyre::Result { + let extract_of_type = |t: &TypeRef| -> Option { + return t.clone().of_type.map(|t| *t); + }; + + if !is_required_type_ref(inner) { + if let Some(inner_of_type) = extract_of_type(inner) { + let inner_field = self.render_type_ref(&inner_of_type)?; + return Ok(quote! { + Option<$inner_field> + }); + } + } + + if is_list_type(&inner) { + if let Some(inner_of_type) = extract_of_type(inner) { + let inner_field = self.render_type_ref(&inner_of_type)?; + 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 = self.render_type_ref(&inner_of_type)?; + 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"), + }; + + return Ok(quote! { + $name + }); + } + + eyre::bail!("could not determine type") + } +} + +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) => self.render_input_fields(i)?, + None => None, + }; + + let out = quote! { + $(if description.is_some() => $description) + pub struct $name { + $(if fields.is_some() => $fields) + } + + impl $input for $name {} + }; + + Ok(out) + } +} + +#[cfg(test)] +mod tests { + use graphql_introspection_query::introspection_response::{ + FullType, FullTypeFields, 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#"use dagger_core::Input; + +pub struct BuildArg { + pub name: Option + + pub value: Option +} + +impl Input for BuildArg {} +"#; + + let output = input.render(&t).unwrap(); + + assert_eq!(output.to_file_string().unwrap(), expected); + } +} + +const something: &str = r#" + { + "description": "", + "enumValues": null, + "fields": null, + "inputFields": [ + { + "defaultValue": null, + "description": "", + "name": "name", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + { + "defaultValue": null, + "description": "", + "name": "value", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + } + ], + "interfaces": null, + "kind": "INPUT_OBJECT", + "name": "BuildArg", + "possibleTypes": null + }, + +"#; diff --git a/crates/dagger-codegen/src/handlers/mod.rs b/crates/dagger-codegen/src/handlers/mod.rs index 1b5977d..d0b5ae7 100644 --- a/crates/dagger-codegen/src/handlers/mod.rs +++ b/crates/dagger-codegen/src/handlers/mod.rs @@ -1,4 +1,5 @@ pub mod enumeration; +pub mod input; pub mod scalar; mod utility; @@ -76,9 +77,13 @@ mod tests { assert_eq!( res.to_string().unwrap(), - "pub struct SomeName {} { } -impl SomeName {} { }" - .to_string() + "pub struct SomeName {} { + +} +impl SomeName {} { + +}" + .to_string() ); } } diff --git a/crates/dagger-codegen/src/lib.rs b/crates/dagger-codegen/src/lib.rs index 9872943..c54d51f 100644 --- a/crates/dagger-codegen/src/lib.rs +++ b/crates/dagger-codegen/src/lib.rs @@ -15,7 +15,33 @@ mod tests { fn can_generate_from_schema() { let schema: IntrospectionResponse = serde_json::from_str(INTROSPECTION_QUERY).unwrap(); let code = CodeGeneration::new().generate(&schema).unwrap(); - assert_eq!("some-code", code); + let expected = "use dagger_core::Scalar; + +// code generated by dagger. DO NOT EDIT. + +/// A global cache volume identifier. +pub struct CacheID(Scalar); + +/// A content-addressed directory identifier. +pub struct DirectoryID(Scalar); + +/// A file identifier. +pub struct FileID(Scalar); + +/// A unique identifier for a secret. +pub struct SecretID(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 container identifier. Null designates an empty container (scratch). +pub struct ContainerID(Scalar); + +/// A content-addressed socket identifier. +pub struct SocketID(Scalar); +"; + assert_eq!(expected, code); } const INTROSPECTION_QUERY: &str = r#"{ diff --git a/crates/dagger-codegen/src/predicates.rs b/crates/dagger-codegen/src/predicates.rs index d4c8800..b6fea88 100644 --- a/crates/dagger-codegen/src/predicates.rs +++ b/crates/dagger-codegen/src/predicates.rs @@ -1,4 +1,6 @@ -use graphql_introspection_query::introspection_response::{self, FullType}; +use graphql_introspection_query::introspection_response::{ + self, FullType, FullTypeInputFields, TypeRef, __TypeKind, +}; use crate::models::Scalars; @@ -9,6 +11,13 @@ pub fn is_scalar_type(t: &FullType) -> bool { false } +pub fn is_scalar_type_ref(t: &TypeRef) -> bool { + if let Some(introspection_response::__TypeKind::SCALAR) = t.kind { + return true; + } + false +} + pub fn is_enum_type(t: &FullType) -> bool { if let Some(introspection_response::__TypeKind::ENUM) = t.kind { return true; @@ -16,6 +25,36 @@ pub fn is_enum_type(t: &FullType) -> bool { false } +pub fn is_input_object_type(t: &FullType) -> bool { + if let Some(introspection_response::__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 false, + Some(_) => return true, + _ => return false, + } +} + +pub fn is_required_type_ref(t: &TypeRef) -> bool { + match t.kind { + Some(__TypeKind::NON_NULL) => return false, + Some(_) => return true, + _ => return false, + } +} + +pub fn is_list_type(t: &TypeRef) -> bool { + if let Some(introspection_response::__TypeKind::LIST) = t.kind { + return true; + } + false +} + pub fn is_custom_scalar_type(t: &FullType) -> bool { if is_scalar_type(t) { // TODO: Insert scalar @@ -35,3 +74,23 @@ pub fn is_custom_scalar_type(t: &FullType) -> bool { } 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 +}