Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Complete docs and Debug coverage #315

Merged
merged 2 commits into from
Mar 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion ic-agent/src/agent/agent_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@ use crate::{
use std::{sync::Arc, time::Duration};

/// A configuration for an agent.

#[derive(Debug)]
pub struct AgentConfig {
/// See [`with_nonce_factory`](super::AgentBuilder::with_nonce_factory).
pub nonce_factory: Arc<dyn NonceGenerator>,
/// See [`with_identity`](super::AgentBuilder::with_identity).
pub identity: Arc<dyn Identity>,
/// See [`with_ingress_expiry`](super::AgentBuilder::with_ingress_expiry).
pub ingress_expiry_duration: Option<Duration>,
/// The [`with_transport`](super::AgentBuilder::with_transport).
pub transport: Option<Arc<dyn ReplicaV2Transport>>,
}

Expand Down
62 changes: 60 additions & 2 deletions ic-agent/src/agent/agent_error.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! Errors that can occur when using the replica agent.

use crate::{agent::status::Status, hash_tree::Label, RequestIdError};
use leb128::read;
use std::{
Expand All @@ -6,122 +8,174 @@ use std::{
};
use thiserror::Error;

/// An error that occurred when using the agent.
#[derive(Error, Debug)]
pub enum AgentError {
/// The replica URL was invalid.
#[error(r#"Invalid Replica URL: "{0}""#)]
InvalidReplicaUrl(String),

/// The request timed out.
#[error("The request timed out.")]
TimeoutWaitingForResponse(),

/// The waiter was restarted without being started first.
#[error("The waiter was restarted without being started first.")]
WaiterRestartError(),

/// An error occurred when signing with the identity.
#[error("Identity had a signing error: {0}")]
SigningError(String),

/// The data fetched was invalid CBOR.
#[error("Invalid CBOR data, could not deserialize: {0}")]
InvalidCborData(#[from] serde_cbor::Error),

/// There was an error calculating a request ID.
#[error("Cannot calculate a RequestID: {0}")]
CannotCalculateRequestId(#[from] RequestIdError),

/// There was an error when de/serializing with Candid.
#[error("Candid returned an error: {0}")]
CandidError(Box<dyn Send + Sync + std::error::Error>),

/// There was an error parsing a URL.
#[error(r#"Cannot parse url: "{0}""#)]
UrlParseError(#[from] url::ParseError),

/// The HTTP method was invalid.
#[error(r#"Invalid method: "{0}""#)]
InvalidMethodError(#[from] http::method::InvalidMethod),

/// The principal string was not a valid principal.
#[error("Cannot parse Principal: {0}")]
PrincipalError(#[from] crate::export::PrincipalError),

/// The replica rejected the message.
#[error(r#"The Replica returned an error: code {reject_code}, message: "{reject_message}""#)]
ReplicaError {
/// The [reject code](https://smartcontracts.org/docs/interface-spec/index.html#reject-codes) returned by the replica.
reject_code: u64,
/// The rejection message.
reject_message: String,
},

/// The replica returned an HTTP error.
#[error("The replica returned an HTTP Error: {0}")]
HttpError(HttpErrorPayload),

/// Attempted to use HTTP authentication in a non-secure URL (either HTTPS or localhost).
#[error("HTTP Authentication cannot be used in a non-secure URL (either HTTPS or localhost)")]
CannotUseAuthenticationOnNonSecureUrl(),

/// The password manager returned an error.
#[error("Password Manager returned an error: {0}")]
AuthenticationError(String),

/// The status endpoint returned an invalid status.
#[error("Status endpoint returned an invalid status.")]
InvalidReplicaStatus,

/// The call was marked done, but no reply was provided.
#[error("Call was marked as done but we never saw the reply. Request ID: {0}")]
RequestStatusDoneNoReply(String),

/// A string error occurred in an external tool.
#[error("A tool returned a string message error: {0}")]
MessageError(String),

/// An error occurred in an external tool.
#[error("A tool returned a custom error: {0}")]
CustomError(#[from] Box<dyn Send + Sync + std::error::Error>),

/// There was an error reading a LEB128 value.
#[error("Error reading LEB128 value: {0}")]
Leb128ReadError(#[from] read::Error),

/// A string was invalid UTF-8.
#[error("Error in UTF-8 string: {0}")]
Utf8ReadError(#[from] Utf8Error),

/// The lookup path was absent in the certificate.
#[error("The lookup path ({0:?}) is absent in the certificate.")]
LookupPathAbsent(Vec<Label>),

/// The lookup path was unknown in the certificate.
#[error("The lookup path ({0:?}) is unknown in the certificate.")]
LookupPathUnknown(Vec<Label>),

/// The lookup path did not make sense for the certificate.
#[error("The lookup path ({0:?}) does not make sense for the certificate.")]
LookupPathError(Vec<Label>),

/// The request status at the requested path was invalid.
#[error("The request status ({1}) at path {0:?} is invalid.")]
InvalidRequestStatus(Vec<Label>, String),

/// The certificate verification failed.
#[error("Certificate verification failed.")]
CertificateVerificationFailed(),

/// There was a length mismatch between the expected and actual length of the BLS DER-encoded public key.
#[error(
r#"BLS DER-encoded public key must be ${expected} bytes long, but is {actual} bytes long."#
)]
DerKeyLengthMismatch { expected: usize, actual: usize },
DerKeyLengthMismatch {
/// The expected length of the key.
expected: usize,
/// The actual length of the key.
actual: usize,
},

/// There was a mismatch between the expected and actual prefix of the BLS DER-encoded public key.
#[error("BLS DER-encoded public key is invalid. Expected the following prefix: ${expected:?}, but got ${actual:?}")]
DerPrefixMismatch { expected: Vec<u8>, actual: Vec<u8> },
DerPrefixMismatch {
/// The expected key prefix.
expected: Vec<u8>,
/// The actual key prefix.
actual: Vec<u8>,
},

/// The status response did not contain a root key.
#[error("The status response did not contain a root key. Status: {0}")]
NoRootKeyInStatus(Status),

/// Could not read the replica root key.
#[error("Could not read the root key")]
CouldNotReadRootKey(),

/// Failed to initialize the BLS library.
#[error("Failed to initialize the BLS library")]
BlsInitializationFailure(),

/// The invocation to the wallet call forward method failed with an error.
#[error("The invocation to the wallet call forward method failed with the error: {0}")]
WalletCallFailed(String),

/// The wallet operation failed.
#[error("The wallet operation failed: {0}")]
WalletError(String),

/// The wallet canister must be upgraded. See [`dfx wallet upgrade`](https://smartcontracts.org/docs/developers-guide/cli-reference/dfx-wallet.html)
#[error("The wallet canister must be upgraded: {0}")]
WalletUpgradeRequired(String),

/// The transport was not specified in the [`AgentBuilder`](super::AgentBuilder).
#[error("Missing replica transport in the Agent Builder.")]
MissingReplicaTransport(),

/// An unknown error occurred during communication with the replica.
#[error("An error happened during communication with the replica: {0}")]
TransportError(Box<dyn std::error::Error + Send + Sync>),

/// There was a mismatch between the expected and actual CBOR data during inspection.
#[error("There is a mismatch between the CBOR encoded call and the arguments: field {field}, value in argument is {value_arg}, value in CBOR is {value_cbor}")]
CallDataMismatch {
/// The field that was mismatched.
field: String,
/// The value that was expected to be in the CBOR.
value_arg: String,
/// The value that was actually in the CBOR.
value_cbor: String,
},
}
Expand All @@ -134,9 +188,13 @@ impl PartialEq for AgentError {
}
}

/// A HTTP error from the replica.
pub struct HttpErrorPayload {
/// The HTTP status code.
pub status: u16,
/// The MIME type of `content`.
pub content_type: Option<String>,
/// The body of the error.
pub content: Vec<u8>,
}

Expand Down
2 changes: 2 additions & 0 deletions ic-agent/src/agent/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ use crate::{
};
use std::sync::Arc;

/// A builder for an [`Agent`].
#[derive(Debug)]
pub struct AgentBuilder {
config: AgentConfig,
}
Expand Down
7 changes: 7 additions & 0 deletions ic-agent/src/agent/http_transport.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ pub trait PasswordManager: Send + Sync {
fn required(&self, url: &str) -> Result<(String, String), String>;
}

impl_debug_empty!(dyn PasswordManager);

/// A [ReplicaV2Transport] using Reqwest to make HTTP calls to the internet computer.
#[derive(Debug)]
pub struct ReqwestHttpReplicaV2Transport {
url: reqwest::Url,
client: reqwest::Client,
Expand All @@ -33,6 +36,7 @@ const IC0_DOMAIN: &str = "ic0.app";
const IC0_SUB_DOMAIN: &str = ".ic0.app";

impl ReqwestHttpReplicaV2Transport {
/// Creates a replica transport from a HTTP URL.
pub fn create<U: Into<String>>(url: U) -> Result<Self, AgentError> {
let mut tls_config = rustls::ClientConfig::builder()
.with_safe_defaults()
Expand Down Expand Up @@ -64,6 +68,7 @@ impl ReqwestHttpReplicaV2Transport {
})
}

/// Sets a password manager to use with HTTP authentication.
pub fn with_password_manager<P: 'static + PasswordManager>(self, password_manager: P) -> Self {
ReqwestHttpReplicaV2Transport {
password_manager: Some(Arc::new(password_manager)),
Expand All @@ -72,6 +77,7 @@ impl ReqwestHttpReplicaV2Transport {
}
}

/// Same as [`with_password_manager`], but providing the Arc so one does not have to be created.
pub fn with_arc_password_manager(self, password_manager: Arc<dyn PasswordManager>) -> Self {
ReqwestHttpReplicaV2Transport {
password_manager: Some(password_manager),
Expand All @@ -80,6 +86,7 @@ impl ReqwestHttpReplicaV2Transport {
}
}

/// Gets the set password manager, if one exists. Otherwise returns None.
pub fn password_manager(&self) -> Option<&dyn PasswordManager> {
self.password_manager.as_deref()
}
Expand Down
Loading