mirror of
https://github.com/kjuulh/dagger-rs.git
synced 2024-11-29 18:22:28 +01:00
288 lines
7.3 KiB
Rust
288 lines
7.3 KiB
Rust
use std::{collections::HashMap, ops::Add, sync::Arc};
|
|
|
|
use eyre::Context;
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
pub fn query() -> Selection {
|
|
Selection::default()
|
|
}
|
|
|
|
impl Default for Selection {
|
|
fn default() -> Self {
|
|
Self {
|
|
name: Default::default(),
|
|
alias: Default::default(),
|
|
args: Default::default(),
|
|
prev: Default::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct Selection {
|
|
name: Option<String>,
|
|
alias: Option<String>,
|
|
args: Option<HashMap<String, String>>,
|
|
|
|
prev: Option<Arc<Selection>>,
|
|
}
|
|
|
|
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<S>(&self, name: &str, value: S) -> eyre::Result<Selection>
|
|
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<String> {
|
|
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::<Vec<_>>();
|
|
|
|
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))
|
|
}
|
|
|
|
pub fn execute<D>(&self, gql_client: &gql_client::Client) -> eyre::Result<Option<D>>
|
|
where
|
|
D: for<'de> Deserialize<'de>,
|
|
{
|
|
let query = self.build()?;
|
|
|
|
let basic = tokio::runtime::Builder::new_current_thread()
|
|
.enable_all()
|
|
.build()
|
|
.unwrap();
|
|
|
|
let resp: Option<serde_json::Value> = match basic.block_on(gql_client.query(&query)) {
|
|
Ok(r) => r,
|
|
Err(e) => eyre::bail!(e),
|
|
};
|
|
|
|
let resp: Option<D> = self.unpack_resp(resp)?;
|
|
|
|
Ok(resp)
|
|
}
|
|
|
|
fn path(&self) -> Vec<Selection> {
|
|
let mut selections: Vec<Selection> = 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
|
|
}
|
|
|
|
pub(crate) fn unpack_resp<D>(&self, resp: Option<serde_json::Value>) -> eyre::Result<Option<D>>
|
|
where
|
|
D: for<'de> Deserialize<'de>,
|
|
{
|
|
match resp {
|
|
Some(r) => self.unpack_resp_value::<D>(r).map(|v| Some(v)),
|
|
None => Ok(None),
|
|
}
|
|
}
|
|
|
|
fn unpack_resp_value<D>(&self, r: serde_json::Value) -> eyre::Result<D>
|
|
where
|
|
D: for<'de> Deserialize<'de>,
|
|
{
|
|
if let Some(o) = r.as_object() {
|
|
let keys = o.keys();
|
|
if keys.len() != 1 {
|
|
eyre::bail!("too many nested objects inside graphql response")
|
|
}
|
|
|
|
let first = keys.into_iter().next().unwrap();
|
|
return self.unpack_resp_value(o.get(first).unwrap().clone());
|
|
}
|
|
|
|
serde_json::from_value::<D>(r).context("could not deserialize response")
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use pretty_assertions::assert_eq;
|
|
use serde::Serialize;
|
|
|
|
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());
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
struct CustomType {
|
|
pub name: String,
|
|
pub s: Option<Box<CustomType>>,
|
|
}
|
|
|
|
#[test]
|
|
fn test_arg_custom_type() {
|
|
let input = CustomType {
|
|
name: "some-name".to_string(),
|
|
s: Some(Box::new(CustomType {
|
|
name: "some-other-name".to_string(),
|
|
s: None,
|
|
})),
|
|
};
|
|
|
|
let root = query().select("a").arg("arg", input).unwrap();
|
|
let query = root.build().unwrap();
|
|
|
|
assert_eq!(
|
|
query,
|
|
r#"query{a(arg:{"name":"some-name","s":{"name":"some-other-name","s":null}})}"#
|
|
.to_string()
|
|
)
|
|
}
|
|
}
|