Skip to main content

dagger_sdk/
client.rs

1use std::sync::Arc;
2
3use crate::core::config::Config;
4use crate::core::engine::Engine as DaggerEngine;
5use crate::core::graphql_client::DefaultGraphQLClient;
6
7use crate::errors::{ConnectError, DaggerError};
8use crate::gen::{Id, Query};
9use crate::id::IntoID;
10use crate::loadable::Loadable;
11use crate::logging::StdLogger;
12use crate::querybuilder::query;
13
14pub type DaggerConn = Query;
15
16pub async fn connect<F, Fut>(dagger: F) -> Result<(), ConnectError>
17where
18    F: FnOnce(DaggerConn) -> Fut + 'static,
19    Fut: futures::Future<Output = eyre::Result<()>> + 'static,
20{
21    let cfg = Config::builder()
22        .logger(Arc::new(StdLogger::default()))
23        .build();
24
25    connect_opts(cfg, dagger).await
26}
27
28pub async fn connect_opts<F, Fut>(cfg: Config, dagger: F) -> Result<(), ConnectError>
29where
30    F: FnOnce(DaggerConn) -> Fut + 'static,
31    Fut: futures::Future<Output = eyre::Result<()>> + 'static,
32{
33    let (conn, proc) = DaggerEngine::new()
34        .start(&cfg)
35        .await
36        .map_err(ConnectError::FailedToConnect)?;
37
38    let proc = proc.map(Arc::new);
39
40    let client = Query {
41        proc: proc.clone(),
42        selection: query(),
43        graphql_client: Arc::new(DefaultGraphQLClient::new(&conn, &cfg)),
44    };
45
46    dagger(client).await.map_err(ConnectError::DaggerContext)?;
47
48    if let Some(proc) = &proc {
49        proc.shutdown()
50            .await
51            .map_err(ConnectError::FailedToShutdown)?;
52    }
53
54    Ok(())
55}
56
57// Conn will automatically close on drop of proc
58
59impl Query {
60    /// Return a lazy reference to a node by its ID without making a
61    /// network call. The returned value can be used to chain further
62    /// queries.
63    ///
64    /// ```ignore
65    /// let ctr: Container = client.r#ref(id);
66    /// let out = ctr.with_exec(vec!["echo", "hi"]).stdout().await?;
67    /// ```
68    pub fn r#ref<T: Loadable>(&self, id: impl IntoID<Id>) -> T {
69        let selection = self
70            .selection
71            .select("node")
72            .arg_lazy(
73                "id",
74                Box::new(move || {
75                    let id = id.clone();
76                    Box::pin(async move {
77                        let resolved = id.into_id().await.unwrap();
78                        format!("\"{}\"", resolved.0)
79                    })
80                }),
81            )
82            .inline_fragment(T::graphql_type());
83
84        T::from_query(self.proc.clone(), selection, self.graphql_client.clone())
85    }
86
87    /// Load a node by its ID with type safety. Verifies the node
88    /// exists and matches the expected type before returning.
89    ///
90    /// ```ignore
91    /// let ctr: Container = client.load(id).await?;
92    /// ```
93    pub async fn load<T: Loadable>(&self, id: impl IntoID<Id>) -> Result<T, DaggerError> {
94        let type_name = T::graphql_type();
95
96        // Verify the node exists by querying __typename through the
97        // inline fragment. If the concrete type doesn't match the
98        // fragment, the response will be empty.
99        let check_selection = self
100            .selection
101            .select("node")
102            .arg_lazy("id", {
103                let id = id.clone();
104                Box::new(move || {
105                    let id = id.clone();
106                    Box::pin(async move {
107                        let resolved = id.into_id().await.unwrap();
108                        format!("\"{}\"", resolved.0)
109                    })
110                })
111            })
112            .inline_fragment(type_name)
113            .select("id");
114
115        let _: Id = check_selection.execute(self.graphql_client.clone()).await?;
116
117        Ok(self.r#ref(id))
118    }
119}
120
121#[cfg(test)]
122mod test {
123    use super::connect;
124
125    #[tokio::test]
126    async fn test_connect() -> eyre::Result<()> {
127        tracing_subscriber::fmt::init();
128
129        connect(|client| async move {
130            client
131                .container()
132                .from("alpine:latest")
133                .with_exec(vec!["echo", "1"])
134                .sync()
135                .await?;
136
137            Ok(())
138        })
139        .await?;
140
141        Ok(())
142    }
143}