diff --git a/crates/dagger-sdk/Cargo.toml b/crates/dagger-sdk/Cargo.toml index e089ce4..791e7bf 100644 --- a/crates/dagger-sdk/Cargo.toml +++ b/crates/dagger-sdk/Cargo.toml @@ -10,5 +10,9 @@ description = "A dagger sdk for rust, written in rust" [dependencies] dagger-core = { path = "../dagger-core" } +eyre = "0.6.8" genco = "0.17.3" +pretty_assertions = "1.3.0" +serde = { version = "1.0.152", features = ["derive"] } +serde_json = "1.0.92" diff --git a/crates/dagger-sdk/src/lib.rs b/crates/dagger-sdk/src/lib.rs index daf205e..8b79c72 100644 --- a/crates/dagger-sdk/src/lib.rs +++ b/crates/dagger-sdk/src/lib.rs @@ -1,4 +1,5 @@ pub mod gen; +mod querybuilder; pub fn add(left: usize, right: usize) -> usize { left + right diff --git a/crates/dagger-sdk/src/querybuilder.rs b/crates/dagger-sdk/src/querybuilder.rs new file mode 100644 index 0000000..852e6df --- /dev/null +++ b/crates/dagger-sdk/src/querybuilder.rs @@ -0,0 +1,211 @@ +use std::{ + any::Any, + collections::HashMap, + ops::Add, + rc::Rc, + sync::{Arc, Once}, +}; + +use serde::{Deserialize, Serialize}; + +pub fn query() -> Selection { + Selection { + name: None, + alias: None, + args: None, + prev: None, + } +} + +#[derive(Debug, Clone)] +pub struct Selection { + name: Option, + alias: Option, + args: Option>, + + prev: Option>, +} + +impl Selection { + pub fn select_with_alias(&self, alias: &str, name: &str) -> Selection { + Self { + name: Some(name.to_string()), + alias: Some(alias.to_string()), + args: None, + prev: Some(Arc::new(self.clone())), + } + } + + pub fn select(&self, name: &str) -> Selection { + Self { + name: Some(name.to_string()), + alias: None, + args: None, + prev: Some(Arc::new(self.clone())), + } + } + + pub fn arg(&self, name: &str, value: S) -> eyre::Result + where + S: Serialize, + { + let mut s = self.clone(); + + let val = serde_json::to_string(&value)?; + + match s.args.as_mut() { + Some(args) => { + let _ = args.insert(name.to_string(), val); + } + None => { + let mut hm = HashMap::new(); + let _ = hm.insert(name.to_string(), val); + s.args = Some(hm); + } + } + + Ok(s) + } + + pub fn build(&self) -> eyre::Result { + let mut fields = vec!["query".to_string()]; + + for sel in self.path() { + if let Some(mut query) = sel.name.map(|q| q.clone()) { + if let Some(args) = sel.args { + let actualargs = args + .iter() + .map(|(name, arg)| format!("{name}:{arg}")) + .collect::>(); + + query = query.add(&format!("({})", actualargs.join(", "))); + } + + if let Some(alias) = sel.alias { + query = format!("{}:{}", alias, query); + } + + fields.push(query); + } + } + + Ok(fields.join("{") + &"}".repeat(fields.len() - 1)) + } + + fn path(&self) -> Vec { + let mut selections: Vec = vec![]; + let mut cur = self; + + while cur.prev.is_some() { + selections.push(cur.clone()); + + if let Some(prev) = cur.prev.as_ref() { + cur = prev; + } + } + + selections.reverse(); + selections + } +} + +#[cfg(test)] +mod tests { + use pretty_assertions::assert_eq; + + use super::query; + + #[test] + fn test_query() { + let root = query() + .select("core") + .select("image") + .arg("ref", "alpine") + .unwrap() + .select("file") + .arg("path", "/etc/alpine-release") + .unwrap(); + + let query = root.build().unwrap(); + + assert_eq!( + query, + r#"query{core{image(ref:"alpine"){file(path:"/etc/alpine-release")}}}"#.to_string() + ) + } + + #[test] + fn test_query_alias() { + let root = query() + .select("core") + .select("image") + .arg("ref", "alpine") + .unwrap() + .select_with_alias("foo", "file") + .arg("path", "/etc/alpine-release") + .unwrap(); + + let query = root.build().unwrap(); + + assert_eq!( + query, + r#"query{core{image(ref:"alpine"){foo:file(path:"/etc/alpine-release")}}}"#.to_string() + ) + } + + #[test] + fn test_arg_collision() { + let root = query() + .select("a") + .arg("arg", "one") + .unwrap() + .select("b") + .arg("arg", "two") + .unwrap(); + + let query = root.build().unwrap(); + + assert_eq!(query, r#"query{a(arg:"one"){b(arg:"two")}}"#.to_string()) + } + + #[test] + fn test_vec_arg() { + let input = vec!["some-string"]; + + let root = query().select("a").arg("arg", input).unwrap(); + let query = root.build().unwrap(); + + assert_eq!(query, r#"query{a(arg:["some-string"])}"#.to_string()) + } + + #[test] + fn test_ref_slice_arg() { + let input = &["some-string"]; + + let root = query().select("a").arg("arg", input).unwrap(); + let query = root.build().unwrap(); + + assert_eq!(query, r#"query{a(arg:["some-string"])}"#.to_string()) + } + + #[test] + fn test_stringb_arg() { + let input = "some-string".to_string(); + + let root = query().select("a").arg("arg", input).unwrap(); + let query = root.build().unwrap(); + + assert_eq!(query, r#"query{a(arg:"some-string")}"#.to_string()) + } + + #[test] + fn test_field_immutability() { + let root = query().select("test"); + + let a = root.select("a").build().unwrap(); + assert_eq!(a, r#"query{test{a}}"#.to_string()); + + let b = root.select("b").build().unwrap(); + assert_eq!(b, r#"query{test{b}}"#.to_string()); + } +}