Add thiserror instead of exposing eyre anonymous errors

The change here is to make it easier for the consumer to debug the api.
Such that they can `match` on individual errors instead of having to
parse text.

eyre is convenient, but mostly from a consumers perspective
This commit is contained in:
Kasper Juul Hermansen 2023-04-30 12:57:50 +02:00
parent 66ab2f552c
commit 6b82e523bd
Signed by: kjuulh
GPG Key ID: 57B6E1465221F912
12 changed files with 157 additions and 71 deletions

2
Cargo.lock generated
View File

@ -334,6 +334,7 @@ dependencies = [
"sha2", "sha2",
"tar", "tar",
"tempfile", "tempfile",
"thiserror",
"tokio", "tokio",
"tracing", "tracing",
"tracing-subscriber", "tracing-subscriber",
@ -352,6 +353,7 @@ dependencies = [
"rand", "rand",
"serde", "serde",
"serde_json", "serde_json",
"thiserror",
"tokio", "tokio",
"tracing", "tracing",
"tracing-subscriber", "tracing-subscriber",

View File

@ -235,8 +235,10 @@ fn render_output_type(funcs: &CommonFunctions, type_ref: &TypeRef) -> rust::Toke
}; };
} }
let dagger_error = rust::import("crate::errors", "DaggerError");
quote! { quote! {
eyre::Result<$output_type> Result<$output_type, $dagger_error>
} }
} }

View File

@ -16,6 +16,7 @@ serde_json = { workspace = true }
tokio = { workspace = true } tokio = { workspace = true }
tracing = { workspace = true } tracing = { workspace = true }
tracing-subscriber = { workspace = true } tracing-subscriber = { workspace = true }
thiserror.workspace = true
base64 = "0.21.0" base64 = "0.21.0"
dirs = "4.0.0" dirs = "4.0.0"

View File

@ -15,7 +15,7 @@ pub struct GraphQLError {
#[derive(Deserialize, Debug, Clone)] #[derive(Deserialize, Debug, Clone)]
#[allow(dead_code)] #[allow(dead_code)]
pub struct GraphQLErrorMessage { pub struct GraphQLErrorMessage {
message: String, pub message: String,
locations: Option<Vec<GraphQLErrorLocation>>, locations: Option<Vec<GraphQLErrorLocation>>,
extensions: Option<HashMap<String, String>>, extensions: Option<HashMap<String, String>>,
path: Option<Vec<GraphQLErrorPathParam>>, path: Option<Vec<GraphQLErrorPathParam>>,

View File

@ -4,13 +4,14 @@ use std::sync::Arc;
use async_trait::async_trait; use async_trait::async_trait;
use base64::engine::general_purpose; use base64::engine::general_purpose;
use base64::Engine; use base64::Engine;
use thiserror::Error;
use crate::connect_params::ConnectParams; use crate::connect_params::ConnectParams;
use crate::gql_client::{ClientConfig, GQLClient}; use crate::gql_client::{ClientConfig, GQLClient};
#[async_trait] #[async_trait]
pub trait GraphQLClient { pub trait GraphQLClient {
async fn query(&self, query: &str) -> eyre::Result<Option<serde_json::Value>>; async fn query(&self, query: &str) -> Result<Option<serde_json::Value>, GraphQLError>;
} }
pub type DynGraphQLClient = Arc<dyn GraphQLClient + Send + Sync>; pub type DynGraphQLClient = Arc<dyn GraphQLClient + Send + Sync>;
@ -40,13 +41,50 @@ impl DefaultGraphQLClient {
#[async_trait] #[async_trait]
impl GraphQLClient for DefaultGraphQLClient { impl GraphQLClient for DefaultGraphQLClient {
async fn query(&self, query: &str) -> eyre::Result<Option<serde_json::Value>> { async fn query(&self, query: &str) -> Result<Option<serde_json::Value>, GraphQLError> {
let res: Option<serde_json::Value> = self let res: Option<serde_json::Value> =
.client self.client.query(&query).await.map_err(map_graphql_error)?;
.query(&query)
.await
.map_err(|r| eyre::anyhow!(r.to_string()))?;
return Ok(res); return Ok(res);
} }
} }
fn map_graphql_error(gql_error: crate::gql_client::GraphQLError) -> GraphQLError {
let message = gql_error.message().to_string();
let json = gql_error.json();
if let Some(json) = json {
if !json.is_empty() {
return GraphQLError::DomainError {
message,
fields: GraphqlErrorMessages(json.into_iter().map(|e| e.message).collect()),
};
}
}
GraphQLError::HttpError(message)
}
#[derive(Error, Debug)]
pub enum GraphQLError {
#[error("http error: {0}")]
HttpError(String),
#[error("domain error:\n{message}\n{fields}")]
DomainError {
message: String,
fields: GraphqlErrorMessages,
},
}
#[derive(Debug, Clone)]
pub struct GraphqlErrorMessages(Vec<String>);
impl std::fmt::Display for GraphqlErrorMessages {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for error in self.0.iter() {
f.write_fmt(format_args!("{error}\n"))?;
}
Ok(())
}
}

View File

@ -19,6 +19,7 @@ serde = { workspace = true }
serde_json = { workspace = true } serde_json = { workspace = true }
tracing.workspace = true tracing.workspace = true
tracing-subscriber.workspace = true tracing-subscriber.workspace = true
thiserror.workspace = true
futures = "0.3.28" futures = "0.3.28"
derive_builder = "0.12.0" derive_builder = "0.12.0"

View File

@ -5,20 +5,24 @@ use dagger_core::graphql_client::DefaultGraphQLClient;
use dagger_core::config::Config; use dagger_core::config::Config;
use dagger_core::engine::Engine as DaggerEngine; use dagger_core::engine::Engine as DaggerEngine;
use crate::errors::ConnectError;
use crate::gen::Query; use crate::gen::Query;
use crate::logging::StdLogger; use crate::logging::StdLogger;
use crate::querybuilder::query; use crate::querybuilder::query;
pub type DaggerConn = Arc<Query>; pub type DaggerConn = Arc<Query>;
pub async fn connect() -> eyre::Result<DaggerConn> { pub async fn connect() -> Result<DaggerConn, ConnectError> {
let cfg = Config::new(None, None, None, None, Some(Arc::new(StdLogger::default()))); let cfg = Config::new(None, None, None, None, Some(Arc::new(StdLogger::default())));
connect_opts(cfg).await connect_opts(cfg).await
} }
pub async fn connect_opts(cfg: Config) -> eyre::Result<DaggerConn> { pub async fn connect_opts(cfg: Config) -> Result<DaggerConn, ConnectError> {
let (conn, proc) = DaggerEngine::new().start(&cfg).await?; let (conn, proc) = DaggerEngine::new()
.start(&cfg)
.await
.map_err(ConnectError::FailedToConnect)?;
Ok(Arc::new(Query { Ok(Arc::new(Query {
proc: proc.map(|p| Arc::new(p)), proc: proc.map(|p| Arc::new(p)),

View File

@ -0,0 +1,27 @@
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ConnectError {
#[error("failed to connect to dagger engine")]
FailedToConnect(#[source] eyre::Error),
}
#[derive(Error, Debug)]
pub enum DaggerError {
#[error("failed to build dagger internal graph")]
Build(#[source] eyre::Error),
#[error("failed to parse input type")]
Serialize(#[source] eyre::Error),
#[error("failed to query dagger engine: {0}")]
Query(#[source] dagger_core::graphql_client::GraphQLError),
#[error("failed to unpack response")]
Unpack(#[source] DaggerUnpackError),
}
#[derive(Error, Debug)]
pub enum DaggerUnpackError {
#[error("Too many nested objects inside graphql response")]
TooManyNestedObjects,
#[error("failed to deserialize response")]
Deserialize(#[source] serde_json::Error),
}

View File

@ -1,3 +1,4 @@
use crate::errors::DaggerError;
use crate::querybuilder::Selection; use crate::querybuilder::Selection;
use dagger_core::graphql_client::DynGraphQLClient; use dagger_core::graphql_client::DynGraphQLClient;
use derive_builder::Builder; use derive_builder::Builder;
@ -121,7 +122,7 @@ pub struct CacheVolume {
} }
impl CacheVolume { impl CacheVolume {
pub async fn id(&self) -> eyre::Result<CacheId> { pub async fn id(&self) -> Result<CacheId, DaggerError> {
let query = self.selection.select("id"); let query = self.selection.select("id");
query.execute(self.graphql_client.clone()).await query.execute(self.graphql_client.clone()).await
@ -397,7 +398,7 @@ impl Container {
}; };
} }
/// Retrieves default arguments for future commands. /// Retrieves default arguments for future commands.
pub async fn default_args(&self) -> eyre::Result<Vec<String>> { pub async fn default_args(&self) -> Result<Vec<String>, DaggerError> {
let query = self.selection.select("defaultArgs"); let query = self.selection.select("defaultArgs");
query.execute(self.graphql_client.clone()).await query.execute(self.graphql_client.clone()).await
@ -427,7 +428,7 @@ impl Container {
/// # Arguments /// # Arguments
/// ///
/// * `opt` - optional argument, see inner type for documentation, use <func>_opts to use /// * `opt` - optional argument, see inner type for documentation, use <func>_opts to use
pub async fn endpoint(&self) -> eyre::Result<String> { pub async fn endpoint(&self) -> Result<String, DaggerError> {
let query = self.selection.select("endpoint"); let query = self.selection.select("endpoint");
query.execute(self.graphql_client.clone()).await query.execute(self.graphql_client.clone()).await
@ -441,7 +442,10 @@ impl Container {
/// # Arguments /// # Arguments
/// ///
/// * `opt` - optional argument, see inner type for documentation, use <func>_opts to use /// * `opt` - optional argument, see inner type for documentation, use <func>_opts to use
pub async fn endpoint_opts<'a>(&self, opts: ContainerEndpointOpts<'a>) -> eyre::Result<String> { pub async fn endpoint_opts<'a>(
&self,
opts: ContainerEndpointOpts<'a>,
) -> Result<String, DaggerError> {
let mut query = self.selection.select("endpoint"); let mut query = self.selection.select("endpoint");
if let Some(port) = opts.port { if let Some(port) = opts.port {
@ -454,7 +458,7 @@ impl Container {
query.execute(self.graphql_client.clone()).await query.execute(self.graphql_client.clone()).await
} }
/// Retrieves entrypoint to be prepended to the arguments of all commands. /// Retrieves entrypoint to be prepended to the arguments of all commands.
pub async fn entrypoint(&self) -> eyre::Result<Vec<String>> { pub async fn entrypoint(&self) -> Result<Vec<String>, DaggerError> {
let query = self.selection.select("entrypoint"); let query = self.selection.select("entrypoint");
query.execute(self.graphql_client.clone()).await query.execute(self.graphql_client.clone()).await
@ -464,7 +468,7 @@ impl Container {
/// # Arguments /// # Arguments
/// ///
/// * `name` - The name of the environment variable to retrieve (e.g., "PATH"). /// * `name` - The name of the environment variable to retrieve (e.g., "PATH").
pub async fn env_variable(&self, name: impl Into<String>) -> eyre::Result<String> { pub async fn env_variable(&self, name: impl Into<String>) -> Result<String, DaggerError> {
let mut query = self.selection.select("envVariable"); let mut query = self.selection.select("envVariable");
query = query.arg("name", name.into()); query = query.arg("name", name.into());
@ -531,7 +535,7 @@ impl Container {
} }
/// Exit code of the last executed command. Zero means success. /// Exit code of the last executed command. Zero means success.
/// Will execute default command if none is set, or error if there's no default. /// Will execute default command if none is set, or error if there's no default.
pub async fn exit_code(&self) -> eyre::Result<isize> { pub async fn exit_code(&self) -> Result<isize, DaggerError> {
let query = self.selection.select("exitCode"); let query = self.selection.select("exitCode");
query.execute(self.graphql_client.clone()).await query.execute(self.graphql_client.clone()).await
@ -545,7 +549,7 @@ impl Container {
/// * `path` - Host's destination path (e.g., "./tarball"). /// * `path` - Host's destination path (e.g., "./tarball").
/// Path can be relative to the engine's workdir or absolute. /// Path can be relative to the engine's workdir or absolute.
/// * `opt` - optional argument, see inner type for documentation, use <func>_opts to use /// * `opt` - optional argument, see inner type for documentation, use <func>_opts to use
pub async fn export(&self, path: impl Into<String>) -> eyre::Result<bool> { pub async fn export(&self, path: impl Into<String>) -> Result<bool, DaggerError> {
let mut query = self.selection.select("export"); let mut query = self.selection.select("export");
query = query.arg("path", path.into()); query = query.arg("path", path.into());
@ -566,7 +570,7 @@ impl Container {
&self, &self,
path: impl Into<String>, path: impl Into<String>,
opts: ContainerExportOpts, opts: ContainerExportOpts,
) -> eyre::Result<bool> { ) -> Result<bool, DaggerError> {
let mut query = self.selection.select("export"); let mut query = self.selection.select("export");
query = query.arg("path", path.into()); query = query.arg("path", path.into());
@ -634,19 +638,19 @@ impl Container {
} }
/// Retrieves a hostname which can be used by clients to reach this container. /// Retrieves a hostname which can be used by clients to reach this container.
/// Currently experimental; set _EXPERIMENTAL_DAGGER_SERVICES_DNS=0 to disable. /// Currently experimental; set _EXPERIMENTAL_DAGGER_SERVICES_DNS=0 to disable.
pub async fn hostname(&self) -> eyre::Result<String> { pub async fn hostname(&self) -> Result<String, DaggerError> {
let query = self.selection.select("hostname"); let query = self.selection.select("hostname");
query.execute(self.graphql_client.clone()).await query.execute(self.graphql_client.clone()).await
} }
/// A unique identifier for this container. /// A unique identifier for this container.
pub async fn id(&self) -> eyre::Result<ContainerId> { pub async fn id(&self) -> Result<ContainerId, DaggerError> {
let query = self.selection.select("id"); let query = self.selection.select("id");
query.execute(self.graphql_client.clone()).await query.execute(self.graphql_client.clone()).await
} }
/// The unique image reference which can only be retrieved immediately after the 'Container.From' call. /// The unique image reference which can only be retrieved immediately after the 'Container.From' call.
pub async fn image_ref(&self) -> eyre::Result<String> { pub async fn image_ref(&self) -> Result<String, DaggerError> {
let query = self.selection.select("imageRef"); let query = self.selection.select("imageRef");
query.execute(self.graphql_client.clone()).await query.execute(self.graphql_client.clone()).await
@ -694,7 +698,7 @@ impl Container {
}; };
} }
/// Retrieves the value of the specified label. /// Retrieves the value of the specified label.
pub async fn label(&self, name: impl Into<String>) -> eyre::Result<String> { pub async fn label(&self, name: impl Into<String>) -> Result<String, DaggerError> {
let mut query = self.selection.select("label"); let mut query = self.selection.select("label");
query = query.arg("name", name.into()); query = query.arg("name", name.into());
@ -712,7 +716,7 @@ impl Container {
}]; }];
} }
/// Retrieves the list of paths where a directory is mounted. /// Retrieves the list of paths where a directory is mounted.
pub async fn mounts(&self) -> eyre::Result<Vec<String>> { pub async fn mounts(&self) -> Result<Vec<String>, DaggerError> {
let query = self.selection.select("mounts"); let query = self.selection.select("mounts");
query.execute(self.graphql_client.clone()).await query.execute(self.graphql_client.clone()).await
@ -763,7 +767,7 @@ impl Container {
}; };
} }
/// The platform this container executes and publishes as. /// The platform this container executes and publishes as.
pub async fn platform(&self) -> eyre::Result<Platform> { pub async fn platform(&self) -> Result<Platform, DaggerError> {
let query = self.selection.select("platform"); let query = self.selection.select("platform");
query.execute(self.graphql_client.clone()).await query.execute(self.graphql_client.clone()).await
@ -778,7 +782,7 @@ impl Container {
/// ///
/// Formatted as [host]/[user]/[repo]:[tag] (e.g. "docker.io/dagger/dagger:main"). /// Formatted as [host]/[user]/[repo]:[tag] (e.g. "docker.io/dagger/dagger:main").
/// * `opt` - optional argument, see inner type for documentation, use <func>_opts to use /// * `opt` - optional argument, see inner type for documentation, use <func>_opts to use
pub async fn publish(&self, address: impl Into<String>) -> eyre::Result<String> { pub async fn publish(&self, address: impl Into<String>) -> Result<String, DaggerError> {
let mut query = self.selection.select("publish"); let mut query = self.selection.select("publish");
query = query.arg("address", address.into()); query = query.arg("address", address.into());
@ -800,7 +804,7 @@ impl Container {
&self, &self,
address: impl Into<String>, address: impl Into<String>,
opts: ContainerPublishOpts, opts: ContainerPublishOpts,
) -> eyre::Result<String> { ) -> Result<String, DaggerError> {
let mut query = self.selection.select("publish"); let mut query = self.selection.select("publish");
query = query.arg("address", address.into()); query = query.arg("address", address.into());
@ -822,20 +826,20 @@ impl Container {
} }
/// The error stream of the last executed command. /// The error stream of the last executed command.
/// Will execute default command if none is set, or error if there's no default. /// Will execute default command if none is set, or error if there's no default.
pub async fn stderr(&self) -> eyre::Result<String> { pub async fn stderr(&self) -> Result<String, DaggerError> {
let query = self.selection.select("stderr"); let query = self.selection.select("stderr");
query.execute(self.graphql_client.clone()).await query.execute(self.graphql_client.clone()).await
} }
/// The output stream of the last executed command. /// The output stream of the last executed command.
/// Will execute default command if none is set, or error if there's no default. /// Will execute default command if none is set, or error if there's no default.
pub async fn stdout(&self) -> eyre::Result<String> { pub async fn stdout(&self) -> Result<String, DaggerError> {
let query = self.selection.select("stdout"); let query = self.selection.select("stdout");
query.execute(self.graphql_client.clone()).await query.execute(self.graphql_client.clone()).await
} }
/// Retrieves the user to be set for all commands. /// Retrieves the user to be set for all commands.
pub async fn user(&self) -> eyre::Result<String> { pub async fn user(&self) -> Result<String, DaggerError> {
let query = self.selection.select("user"); let query = self.selection.select("user");
query.execute(self.graphql_client.clone()).await query.execute(self.graphql_client.clone()).await
@ -1720,7 +1724,7 @@ impl Container {
}; };
} }
/// Retrieves the working directory for all commands. /// Retrieves the working directory for all commands.
pub async fn workdir(&self) -> eyre::Result<String> { pub async fn workdir(&self) -> Result<String, DaggerError> {
let query = self.selection.select("workdir"); let query = self.selection.select("workdir");
query.execute(self.graphql_client.clone()).await query.execute(self.graphql_client.clone()).await
@ -1882,7 +1886,7 @@ impl Directory {
/// # Arguments /// # Arguments
/// ///
/// * `opt` - optional argument, see inner type for documentation, use <func>_opts to use /// * `opt` - optional argument, see inner type for documentation, use <func>_opts to use
pub async fn entries(&self) -> eyre::Result<Vec<String>> { pub async fn entries(&self) -> Result<Vec<String>, DaggerError> {
let query = self.selection.select("entries"); let query = self.selection.select("entries");
query.execute(self.graphql_client.clone()).await query.execute(self.graphql_client.clone()).await
@ -1896,7 +1900,7 @@ impl Directory {
pub async fn entries_opts<'a>( pub async fn entries_opts<'a>(
&self, &self,
opts: DirectoryEntriesOpts<'a>, opts: DirectoryEntriesOpts<'a>,
) -> eyre::Result<Vec<String>> { ) -> Result<Vec<String>, DaggerError> {
let mut query = self.selection.select("entries"); let mut query = self.selection.select("entries");
if let Some(path) = opts.path { if let Some(path) = opts.path {
@ -1910,7 +1914,7 @@ impl Directory {
/// # Arguments /// # Arguments
/// ///
/// * `path` - Location of the copied directory (e.g., "logs/"). /// * `path` - Location of the copied directory (e.g., "logs/").
pub async fn export(&self, path: impl Into<String>) -> eyre::Result<bool> { pub async fn export(&self, path: impl Into<String>) -> Result<bool, DaggerError> {
let mut query = self.selection.select("export"); let mut query = self.selection.select("export");
query = query.arg("path", path.into()); query = query.arg("path", path.into());
@ -1934,7 +1938,7 @@ impl Directory {
}; };
} }
/// The content-addressed identifier of the directory. /// The content-addressed identifier of the directory.
pub async fn id(&self) -> eyre::Result<DirectoryId> { pub async fn id(&self) -> Result<DirectoryId, DaggerError> {
let query = self.selection.select("id"); let query = self.selection.select("id");
query.execute(self.graphql_client.clone()).await query.execute(self.graphql_client.clone()).await
@ -2242,13 +2246,13 @@ pub struct EnvVariable {
impl EnvVariable { impl EnvVariable {
/// The environment variable name. /// The environment variable name.
pub async fn name(&self) -> eyre::Result<String> { pub async fn name(&self) -> Result<String, DaggerError> {
let query = self.selection.select("name"); let query = self.selection.select("name");
query.execute(self.graphql_client.clone()).await query.execute(self.graphql_client.clone()).await
} }
/// The environment variable value. /// The environment variable value.
pub async fn value(&self) -> eyre::Result<String> { pub async fn value(&self) -> Result<String, DaggerError> {
let query = self.selection.select("value"); let query = self.selection.select("value");
query.execute(self.graphql_client.clone()).await query.execute(self.graphql_client.clone()).await
@ -2263,7 +2267,7 @@ pub struct File {
impl File { impl File {
/// Retrieves the contents of the file. /// Retrieves the contents of the file.
pub async fn contents(&self) -> eyre::Result<String> { pub async fn contents(&self) -> Result<String, DaggerError> {
let query = self.selection.select("contents"); let query = self.selection.select("contents");
query.execute(self.graphql_client.clone()).await query.execute(self.graphql_client.clone()).await
@ -2273,7 +2277,7 @@ impl File {
/// # Arguments /// # Arguments
/// ///
/// * `path` - Location of the written directory (e.g., "output.txt"). /// * `path` - Location of the written directory (e.g., "output.txt").
pub async fn export(&self, path: impl Into<String>) -> eyre::Result<bool> { pub async fn export(&self, path: impl Into<String>) -> Result<bool, DaggerError> {
let mut query = self.selection.select("export"); let mut query = self.selection.select("export");
query = query.arg("path", path.into()); query = query.arg("path", path.into());
@ -2281,7 +2285,7 @@ impl File {
query.execute(self.graphql_client.clone()).await query.execute(self.graphql_client.clone()).await
} }
/// Retrieves the content-addressed identifier of the file. /// Retrieves the content-addressed identifier of the file.
pub async fn id(&self) -> eyre::Result<FileId> { pub async fn id(&self) -> Result<FileId, DaggerError> {
let query = self.selection.select("id"); let query = self.selection.select("id");
query.execute(self.graphql_client.clone()).await query.execute(self.graphql_client.clone()).await
@ -2297,7 +2301,7 @@ impl File {
}; };
} }
/// Gets the size of the file, in bytes. /// Gets the size of the file, in bytes.
pub async fn size(&self) -> eyre::Result<isize> { pub async fn size(&self) -> Result<isize, DaggerError> {
let query = self.selection.select("size"); let query = self.selection.select("size");
query.execute(self.graphql_client.clone()).await query.execute(self.graphql_client.clone()).await
@ -2338,7 +2342,7 @@ pub struct GitRefTreeOpts<'a> {
impl GitRef { impl GitRef {
/// The digest of the current value of this ref. /// The digest of the current value of this ref.
pub async fn digest(&self) -> eyre::Result<String> { pub async fn digest(&self) -> Result<String, DaggerError> {
let query = self.selection.select("digest"); let query = self.selection.select("digest");
query.execute(self.graphql_client.clone()).await query.execute(self.graphql_client.clone()).await
@ -2405,7 +2409,7 @@ impl GitRepository {
}; };
} }
/// Lists of branches on the repository. /// Lists of branches on the repository.
pub async fn branches(&self) -> eyre::Result<Vec<String>> { pub async fn branches(&self) -> Result<Vec<String>, DaggerError> {
let query = self.selection.select("branches"); let query = self.selection.select("branches");
query.execute(self.graphql_client.clone()).await query.execute(self.graphql_client.clone()).await
@ -2443,7 +2447,7 @@ impl GitRepository {
}; };
} }
/// Lists of tags on the repository. /// Lists of tags on the repository.
pub async fn tags(&self) -> eyre::Result<Vec<String>> { pub async fn tags(&self) -> Result<Vec<String>, DaggerError> {
let query = self.selection.select("tags"); let query = self.selection.select("tags");
query.execute(self.graphql_client.clone()).await query.execute(self.graphql_client.clone()).await
@ -2609,7 +2613,7 @@ impl HostVariable {
}; };
} }
/// The value of this variable. /// The value of this variable.
pub async fn value(&self) -> eyre::Result<String> { pub async fn value(&self) -> Result<String, DaggerError> {
let query = self.selection.select("value"); let query = self.selection.select("value");
query.execute(self.graphql_client.clone()).await query.execute(self.graphql_client.clone()).await
@ -2624,13 +2628,13 @@ pub struct Label {
impl Label { impl Label {
/// The label name. /// The label name.
pub async fn name(&self) -> eyre::Result<String> { pub async fn name(&self) -> Result<String, DaggerError> {
let query = self.selection.select("name"); let query = self.selection.select("name");
query.execute(self.graphql_client.clone()).await query.execute(self.graphql_client.clone()).await
} }
/// The label value. /// The label value.
pub async fn value(&self) -> eyre::Result<String> { pub async fn value(&self) -> Result<String, DaggerError> {
let query = self.selection.select("value"); let query = self.selection.select("value");
query.execute(self.graphql_client.clone()).await query.execute(self.graphql_client.clone()).await
@ -2645,19 +2649,19 @@ pub struct Port {
impl Port { impl Port {
/// The port description. /// The port description.
pub async fn description(&self) -> eyre::Result<String> { pub async fn description(&self) -> Result<String, DaggerError> {
let query = self.selection.select("description"); let query = self.selection.select("description");
query.execute(self.graphql_client.clone()).await query.execute(self.graphql_client.clone()).await
} }
/// The port number. /// The port number.
pub async fn port(&self) -> eyre::Result<isize> { pub async fn port(&self) -> Result<isize, DaggerError> {
let query = self.selection.select("port"); let query = self.selection.select("port");
query.execute(self.graphql_client.clone()).await query.execute(self.graphql_client.clone()).await
} }
/// The transport layer network protocol. /// The transport layer network protocol.
pub async fn protocol(&self) -> eyre::Result<NetworkProtocol> { pub async fn protocol(&self) -> Result<NetworkProtocol, DaggerError> {
let query = self.selection.select("protocol"); let query = self.selection.select("protocol");
query.execute(self.graphql_client.clone()).await query.execute(self.graphql_client.clone()).await
@ -2692,25 +2696,25 @@ impl Project {
}; };
} }
/// install the project's schema /// install the project's schema
pub async fn install(&self) -> eyre::Result<bool> { pub async fn install(&self) -> Result<bool, DaggerError> {
let query = self.selection.select("install"); let query = self.selection.select("install");
query.execute(self.graphql_client.clone()).await query.execute(self.graphql_client.clone()).await
} }
/// name of the project /// name of the project
pub async fn name(&self) -> eyre::Result<String> { pub async fn name(&self) -> Result<String, DaggerError> {
let query = self.selection.select("name"); let query = self.selection.select("name");
query.execute(self.graphql_client.clone()).await query.execute(self.graphql_client.clone()).await
} }
/// schema provided by the project /// schema provided by the project
pub async fn schema(&self) -> eyre::Result<String> { pub async fn schema(&self) -> Result<String, DaggerError> {
let query = self.selection.select("schema"); let query = self.selection.select("schema");
query.execute(self.graphql_client.clone()).await query.execute(self.graphql_client.clone()).await
} }
/// sdk used to generate code for and/or execute this project /// sdk used to generate code for and/or execute this project
pub async fn sdk(&self) -> eyre::Result<String> { pub async fn sdk(&self) -> Result<String, DaggerError> {
let query = self.selection.select("sdk"); let query = self.selection.select("sdk");
query.execute(self.graphql_client.clone()).await query.execute(self.graphql_client.clone()).await
@ -2825,7 +2829,7 @@ impl Query {
}; };
} }
/// The default platform of the builder. /// The default platform of the builder.
pub async fn default_platform(&self) -> eyre::Result<Platform> { pub async fn default_platform(&self) -> Result<Platform, DaggerError> {
let query = self.selection.select("defaultPlatform"); let query = self.selection.select("defaultPlatform");
query.execute(self.graphql_client.clone()).await query.execute(self.graphql_client.clone()).await
@ -3094,13 +3098,13 @@ pub struct Secret {
impl Secret { impl Secret {
/// The identifier for this secret. /// The identifier for this secret.
pub async fn id(&self) -> eyre::Result<SecretId> { pub async fn id(&self) -> Result<SecretId, DaggerError> {
let query = self.selection.select("id"); let query = self.selection.select("id");
query.execute(self.graphql_client.clone()).await query.execute(self.graphql_client.clone()).await
} }
/// The value of this secret. /// The value of this secret.
pub async fn plaintext(&self) -> eyre::Result<String> { pub async fn plaintext(&self) -> Result<String, DaggerError> {
let query = self.selection.select("plaintext"); let query = self.selection.select("plaintext");
query.execute(self.graphql_client.clone()).await query.execute(self.graphql_client.clone()).await
@ -3115,7 +3119,7 @@ pub struct Socket {
impl Socket { impl Socket {
/// The content-addressed identifier of the socket. /// The content-addressed identifier of the socket.
pub async fn id(&self) -> eyre::Result<SocketId> { pub async fn id(&self) -> Result<SocketId, DaggerError> {
let query = self.selection.select("id"); let query = self.selection.select("id");
query.execute(self.graphql_client.clone()).await query.execute(self.graphql_client.clone()).await

View File

@ -1,6 +1,7 @@
#![deny(warnings)] #![deny(warnings)]
mod client; mod client;
pub mod errors;
mod gen; mod gen;
pub mod logging; pub mod logging;
mod querybuilder; mod querybuilder;

View File

@ -1,9 +1,10 @@
use std::{collections::HashMap, ops::Add, sync::Arc}; use std::{collections::HashMap, ops::Add, sync::Arc};
use dagger_core::graphql_client::DynGraphQLClient; use dagger_core::graphql_client::DynGraphQLClient;
use eyre::Context;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::errors::{DaggerError, DaggerUnpackError};
pub fn query() -> Selection { pub fn query() -> Selection {
Selection::default() Selection::default()
} }
@ -92,7 +93,7 @@ impl Selection {
s s
} }
pub fn build(&self) -> eyre::Result<String> { pub fn build(&self) -> Result<String, DaggerError> {
let mut fields = vec!["query".to_string()]; let mut fields = vec!["query".to_string()];
for sel in self.path() { for sel in self.path() {
@ -117,7 +118,7 @@ impl Selection {
Ok(fields.join("{") + &"}".repeat(fields.len() - 1)) Ok(fields.join("{") + &"}".repeat(fields.len() - 1))
} }
pub async fn execute<D>(&self, gql_client: DynGraphQLClient) -> eyre::Result<D> pub async fn execute<D>(&self, gql_client: DynGraphQLClient) -> Result<D, DaggerError>
where where
D: for<'de> Deserialize<'de>, D: for<'de> Deserialize<'de>,
{ {
@ -127,7 +128,7 @@ impl Selection {
let resp: Option<serde_json::Value> = match gql_client.query(&query).await { let resp: Option<serde_json::Value> = match gql_client.query(&query).await {
Ok(r) => r, Ok(r) => r,
Err(e) => eyre::bail!(e), Err(e) => return Err(DaggerError::Query(e)),
}; };
let resp: Option<D> = self.unpack_resp(resp)?; let resp: Option<D> = self.unpack_resp(resp)?;
@ -151,7 +152,10 @@ impl Selection {
selections selections
} }
pub(crate) fn unpack_resp<D>(&self, resp: Option<serde_json::Value>) -> eyre::Result<Option<D>> pub(crate) fn unpack_resp<D>(
&self,
resp: Option<serde_json::Value>,
) -> Result<Option<D>, DaggerError>
where where
D: for<'de> Deserialize<'de>, D: for<'de> Deserialize<'de>,
{ {
@ -161,21 +165,23 @@ impl Selection {
} }
} }
fn unpack_resp_value<D>(&self, r: serde_json::Value) -> eyre::Result<D> fn unpack_resp_value<D>(&self, r: serde_json::Value) -> Result<D, DaggerError>
where where
D: for<'de> Deserialize<'de>, D: for<'de> Deserialize<'de>,
{ {
if let Some(o) = r.as_object() { if let Some(o) = r.as_object() {
let keys = o.keys(); let keys = o.keys();
if keys.len() != 1 { if keys.len() != 1 {
eyre::bail!("too many nested objects inside graphql response") return Err(DaggerError::Unpack(DaggerUnpackError::TooManyNestedObjects));
} }
let first = keys.into_iter().next().unwrap(); let first = keys.into_iter().next().unwrap();
return self.unpack_resp_value(o.get(first).unwrap().clone()); return self.unpack_resp_value(o.get(first).unwrap().clone());
} }
serde_json::from_value::<D>(r).context("could not deserialize response") serde_json::from_value::<D>(r)
.map_err(DaggerUnpackError::Deserialize)
.map_err(DaggerError::Unpack)
} }
} }

View File

@ -111,9 +111,9 @@ async fn test_err_message() {
assert_eq!(alpine.is_err(), true); assert_eq!(alpine.is_err(), true);
let err = alpine.expect_err("Tests expect err"); let err = alpine.expect_err("Tests expect err");
let error_msg = r#" let error_msg = r#"failed to query dagger engine: domain error:
GQLClient Error: Look at json field for more details Look at json field for more details
Message: pull access denied, repository does not exist or may require authorization: server message: insufficient_scope: authorization failed pull access denied, repository does not exist or may require authorization: server message: insufficient_scope: authorization failed
"#; "#;
assert_eq!(err.to_string().as_str(), error_msg); assert_eq!(err.to_string().as_str(), error_msg);