summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormdecimus <mauro@stalw.art>2024-07-17 18:33:22 +0200
committermdecimus <mauro@stalw.art>2024-07-17 18:33:22 +0200
commitd2ad44cf9f085db5b067338d560291db667aeaa9 (patch)
treec5fd2b3859a70f0cda86668379d954b4c1a92d25
parente74b29189a45c4afb6b204fced1b07032b16af61 (diff)
Improved error handling (part 3)
-rw-r--r--Cargo.lock10
-rw-r--r--crates/common/src/config/smtp/auth.rs2
-rw-r--r--crates/common/src/lib.rs10
-rw-r--r--crates/common/src/listener/acme/cache.rs7
-rw-r--r--crates/common/src/listener/acme/directory.rs53
-rw-r--r--crates/common/src/listener/acme/jose.rs11
-rw-r--r--crates/common/src/listener/acme/order.rs33
-rw-r--r--crates/common/src/listener/mod.rs7
-rw-r--r--crates/common/src/manager/backup.rs6
-rw-r--r--crates/common/src/manager/config.rs3
-rw-r--r--crates/common/src/manager/webadmin.rs30
-rw-r--r--crates/directory/src/backend/imap/lookup.rs12
-rw-r--r--crates/directory/src/backend/internal/manage.rs28
-rw-r--r--crates/directory/src/backend/internal/mod.rs6
-rw-r--r--crates/directory/src/backend/ldap/lookup.rs2
-rw-r--r--crates/directory/src/backend/smtp/lookup.rs10
-rw-r--r--crates/directory/src/core/secret.rs18
-rw-r--r--crates/directory/src/lib.rs30
-rw-r--r--crates/imap/src/op/acl.rs2
-rw-r--r--crates/imap/src/op/append.rs2
-rw-r--r--crates/imap/src/op/authenticate.rs8
-rw-r--r--crates/imap/src/op/expunge.rs2
-rw-r--r--crates/imap/src/op/fetch.rs2
-rw-r--r--crates/imap/src/op/status.rs2
-rw-r--r--crates/imap/src/op/store.rs2
-rw-r--r--crates/jmap-proto/src/error/method.rs10
-rw-r--r--crates/jmap-proto/src/method/changes.rs12
-rw-r--r--crates/jmap-proto/src/method/copy.rs15
-rw-r--r--crates/jmap-proto/src/method/get.rs17
-rw-r--r--crates/jmap-proto/src/method/import.rs4
-rw-r--r--crates/jmap-proto/src/method/lookup.rs2
-rw-r--r--crates/jmap-proto/src/method/parse.rs2
-rw-r--r--crates/jmap-proto/src/method/query.rs30
-rw-r--r--crates/jmap-proto/src/method/query_changes.rs12
-rw-r--r--crates/jmap-proto/src/method/search_snippet.rs2
-rw-r--r--crates/jmap-proto/src/method/set.rs19
-rw-r--r--crates/jmap-proto/src/method/upload.rs6
-rw-r--r--crates/jmap-proto/src/method/validate.rs2
-rw-r--r--crates/jmap-proto/src/object/blob.rs2
-rw-r--r--crates/jmap-proto/src/object/email.rs4
-rw-r--r--crates/jmap-proto/src/object/email_submission.rs2
-rw-r--r--crates/jmap-proto/src/object/mailbox.rs4
-rw-r--r--crates/jmap-proto/src/object/mod.rs4
-rw-r--r--crates/jmap-proto/src/object/sieve.rs2
-rw-r--r--crates/jmap-proto/src/parser/base32.rs4
-rw-r--r--crates/jmap-proto/src/parser/impls.rs20
-rw-r--r--crates/jmap-proto/src/parser/json.rs44
-rw-r--r--crates/jmap-proto/src/parser/mod.rs54
-rw-r--r--crates/jmap-proto/src/request/capability.rs18
-rw-r--r--crates/jmap-proto/src/request/echo.rs2
-rw-r--r--crates/jmap-proto/src/request/method.rs2
-rw-r--r--crates/jmap-proto/src/request/mod.rs4
-rw-r--r--crates/jmap-proto/src/request/parser.rs57
-rw-r--r--crates/jmap-proto/src/request/reference.rs13
-rw-r--r--crates/jmap-proto/src/request/websocket.rs20
-rw-r--r--crates/jmap-proto/src/types/acl.rs2
-rw-r--r--crates/jmap-proto/src/types/any_id.rs2
-rw-r--r--crates/jmap-proto/src/types/blob.rs2
-rw-r--r--crates/jmap-proto/src/types/date.rs2
-rw-r--r--crates/jmap-proto/src/types/id.rs2
-rw-r--r--crates/jmap-proto/src/types/keyword.rs2
-rw-r--r--crates/jmap-proto/src/types/mod.rs2
-rw-r--r--crates/jmap-proto/src/types/pointer.rs2
-rw-r--r--crates/jmap-proto/src/types/property.rs20
-rw-r--r--crates/jmap-proto/src/types/state.rs2
-rw-r--r--crates/jmap-proto/src/types/type_state.rs2
-rw-r--r--crates/jmap-proto/src/types/value.rs6
-rw-r--r--crates/jmap/src/api/autoconfig.rs78
-rw-r--r--crates/jmap/src/api/event_source.rs26
-rw-r--r--crates/jmap/src/api/http.rs239
-rw-r--r--crates/jmap/src/api/management/dkim.rs74
-rw-r--r--crates/jmap/src/api/management/domain.rs123
-rw-r--r--crates/jmap/src/api/management/enterprise.rs274
-rw-r--r--crates/jmap/src/api/management/log.rs56
-rw-r--r--crates/jmap/src/api/management/mod.rs42
-rw-r--r--crates/jmap/src/api/management/principal.rs481
-rw-r--r--crates/jmap/src/api/management/queue.rs65
-rw-r--r--crates/jmap/src/api/management/reload.rs144
-rw-r--r--crates/jmap/src/api/management/report.rs71
-rw-r--r--crates/jmap/src/api/management/settings.rs375
-rw-r--r--crates/jmap/src/api/management/sieve.rs9
-rw-r--r--crates/jmap/src/api/management/stores.rs108
-rw-r--r--crates/jmap/src/api/request.rs6
-rw-r--r--crates/jmap/src/api/session.rs9
-rw-r--r--crates/jmap/src/auth/acl.rs2
-rw-r--r--crates/jmap/src/auth/authenticate.rs10
-rw-r--r--crates/jmap/src/auth/oauth/auth.rs270
-rw-r--r--crates/jmap/src/auth/oauth/mod.rs17
-rw-r--r--crates/jmap/src/auth/oauth/token.rs113
-rw-r--r--crates/jmap/src/auth/rate_limit.rs12
-rw-r--r--crates/jmap/src/blob/upload.rs3
-rw-r--r--crates/jmap/src/changes/write.rs5
-rw-r--r--crates/jmap/src/email/crypto.rs144
-rw-r--r--crates/jmap/src/email/delete.rs3
-rw-r--r--crates/jmap/src/email/import.rs2
-rw-r--r--crates/jmap/src/email/ingest.rs9
-rw-r--r--crates/jmap/src/email/set.rs4
-rw-r--r--crates/jmap/src/mailbox/set.rs8
-rw-r--r--crates/jmap/src/push/get.rs9
-rw-r--r--crates/jmap/src/services/index.rs2
-rw-r--r--crates/jmap/src/services/ingest.rs2
-rw-r--r--crates/jmap/src/sieve/get.rs14
-rw-r--r--crates/jmap/src/sieve/set.rs13
-rw-r--r--crates/jmap/src/vacation/set.rs11
-rw-r--r--crates/jmap/src/websocket/stream.rs23
-rw-r--r--crates/jmap/src/websocket/upgrade.rs37
-rw-r--r--crates/managesieve/Cargo.toml1
-rw-r--r--crates/managesieve/src/core/client.rs184
-rw-r--r--crates/managesieve/src/core/mod.rs77
-rw-r--r--crates/managesieve/src/core/session.rs8
-rw-r--r--crates/managesieve/src/op/authenticate.rs145
-rw-r--r--crates/managesieve/src/op/capability.rs2
-rw-r--r--crates/managesieve/src/op/checkscript.rs8
-rw-r--r--crates/managesieve/src/op/deletescript.rs23
-rw-r--r--crates/managesieve/src/op/getscript.rs30
-rw-r--r--crates/managesieve/src/op/havespace.rs33
-rw-r--r--crates/managesieve/src/op/listscripts.rs9
-rw-r--r--crates/managesieve/src/op/logout.rs7
-rw-r--r--crates/managesieve/src/op/mod.rs12
-rw-r--r--crates/managesieve/src/op/noop.rs2
-rw-r--r--crates/managesieve/src/op/putscript.rs90
-rw-r--r--crates/managesieve/src/op/renamescript.rs58
-rw-r--r--crates/managesieve/src/op/setactive.rs17
-rw-r--r--crates/pop3/src/client.rs116
-rw-r--r--crates/pop3/src/mailbox.rs36
-rw-r--r--crates/pop3/src/op/authenticate.rs132
-rw-r--r--crates/pop3/src/op/delete.rs66
-rw-r--r--crates/pop3/src/op/fetch.rs60
-rw-r--r--crates/pop3/src/op/list.rs16
-rw-r--r--crates/pop3/src/protocol/response.rs19
-rw-r--r--crates/pop3/src/session.rs38
-rw-r--r--crates/smtp/src/inbound/auth.rs6
-rw-r--r--crates/smtp/src/inbound/vrfy.rs4
-rw-r--r--crates/smtp/src/queue/spool.rs2
-rw-r--r--crates/smtp/src/reporting/scheduler.rs2
-rw-r--r--crates/store/src/backend/elastic/mod.rs6
-rw-r--r--crates/store/src/backend/elastic/query.rs6
-rw-r--r--crates/store/src/backend/foundationdb/mod.rs2
-rw-r--r--crates/store/src/backend/foundationdb/write.rs4
-rw-r--r--crates/store/src/backend/fs/mod.rs26
-rw-r--r--crates/store/src/backend/mysql/mod.rs2
-rw-r--r--crates/store/src/backend/mysql/write.rs6
-rw-r--r--crates/store/src/backend/postgres/mod.rs2
-rw-r--r--crates/store/src/backend/postgres/write.rs8
-rw-r--r--crates/store/src/backend/redis/mod.rs2
-rw-r--r--crates/store/src/backend/redis/pool.rs4
-rw-r--r--crates/store/src/backend/rocksdb/mod.rs2
-rw-r--r--crates/store/src/backend/rocksdb/write.rs2
-rw-r--r--crates/store/src/backend/s3/mod.rs8
-rw-r--r--crates/store/src/backend/sqlite/mod.rs2
-rw-r--r--crates/store/src/backend/sqlite/write.rs2
-rw-r--r--crates/store/src/dispatch/blob.rs10
-rw-r--r--crates/store/src/dispatch/lookup.rs51
-rw-r--r--crates/store/src/dispatch/store.rs66
-rw-r--r--crates/store/src/query/acl.rs10
-rw-r--r--crates/store/src/write/key.rs10
-rw-r--r--crates/store/src/write/mod.rs20
-rw-r--r--crates/trc/src/conv.rs74
-rw-r--r--crates/trc/src/imple.rs152
-rw-r--r--crates/trc/src/lib.rs131
-rw-r--r--tests/Cargo.toml1
-rw-r--r--tests/src/directory/internal.rs2
-rw-r--r--tests/src/directory/mod.rs4
-rw-r--r--tests/src/directory/smtp.rs38
-rw-r--r--tests/src/jmap/thread_merge.rs28
-rw-r--r--tests/src/jmap/webhooks.rs2
166 files changed, 2813 insertions, 2699 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 56ed2849..4cc29e42 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3623,6 +3623,7 @@ dependencies = [
"tokio",
"tokio-rustls 0.26.0",
"tracing",
+ "trc",
"utils",
]
@@ -5572,9 +5573,9 @@ checksum = "ece8e78b2f38ec51c51f5d475df0a7187ba5111b2a28bdc761ee05b075d40a71"
[[package]]
name = "scc"
-version = "2.1.1"
+version = "2.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "76ad2bbb0ae5100a07b7a6f2ed7ab5fd0045551a4c507989b7a620046ea3efdc"
+checksum = "a4465c22496331e20eb047ff46e7366455bc01c0c02015c4a376de0b2cd3a1af"
dependencies = [
"sdd",
]
@@ -5627,9 +5628,9 @@ dependencies = [
[[package]]
name = "sdd"
-version = "0.2.0"
+version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b84345e4c9bd703274a082fb80caaa99b7612be48dfaa1dd9266577ec412309d"
+checksum = "1e806d6633ef141556fef75e345275e35652e9c045bbbc21e6ecfce3e9aa2638"
[[package]]
name = "seahash"
@@ -6416,6 +6417,7 @@ dependencies = [
"tokio-rustls 0.26.0",
"tracing",
"tracing-subscriber",
+ "trc",
"utils",
]
diff --git a/crates/common/src/config/smtp/auth.rs b/crates/common/src/config/smtp/auth.rs
index 9bf809d5..99a6f1f8 100644
--- a/crates/common/src/config/smtp/auth.rs
+++ b/crates/common/src/config/smtp/auth.rs
@@ -142,7 +142,7 @@ impl Default for MailAuthConfig {
},
iprev: IpRevAuthConfig {
verify: IfBlock::new::<VerifyStrategy>(
- "auth.ipref.verify",
+ "auth.iprev.verify",
[("local_port == 25", "relaxed")],
#[cfg(not(feature = "test_mode"))]
"disable",
diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs
index 9e7812c3..26e901dc 100644
--- a/crates/common/src/lib.rs
+++ b/crates/common/src/lib.rs
@@ -255,7 +255,7 @@ impl Core {
}
Ok(None) => Ok(()),
Err(err) => {
- if err.matches(trc::Cause::MissingTotp) {
+ if err.matches(trc::Cause::Auth(trc::AuthCause::MissingTotp)) {
return Err(err);
} else {
Err(err)
@@ -331,7 +331,7 @@ impl Core {
.await;
}
- Err(trc::Cause::Authentication.into())
+ Err(trc::AuthCause::Failed.into())
};
}
}
@@ -377,7 +377,7 @@ impl Core {
.await;
}
- Err(trc::Cause::Banned.into())
+ Err(trc::AuthCause::Banned.into())
} else {
// Send webhook event
if self.has_webhook_subscribers(WebhookType::AuthFailure) {
@@ -394,7 +394,7 @@ impl Core {
.await;
}
- Err(trc::Cause::Authentication.into())
+ Err(trc::AuthCause::Failed.into())
}
} else {
// Send webhook event
@@ -411,7 +411,7 @@ impl Core {
)
.await;
}
- Err(trc::Cause::Authentication.into())
+ Err(trc::AuthCause::Failed.into())
}
}
}
diff --git a/crates/common/src/listener/acme/cache.rs b/crates/common/src/listener/acme/cache.rs
index 95b9d046..99421d43 100644
--- a/crates/common/src/listener/acme/cache.rs
+++ b/crates/common/src/listener/acme/cache.rs
@@ -75,7 +75,12 @@ impl Core {
{
URL_SAFE_NO_PAD
.decode(content.as_bytes())
- .map_err(Into::into)
+ .map_err(|err| {
+ trc::Cause::Acme
+ .caused_by(trc::location!())
+ .reason(err)
+ .details("failed to decode certificate")
+ })
.map(Some)
} else {
Ok(None)
diff --git a/crates/common/src/listener/acme/directory.rs b/crates/common/src/listener/acme/directory.rs
index ed5b9357..2c1e5ece 100644
--- a/crates/common/src/listener/acme/directory.rs
+++ b/crates/common/src/listener/acme/directory.rs
@@ -60,7 +60,7 @@ impl Account {
I: IntoIterator<Item = &'a S>,
{
let key_pair = EcdsaKeyPair::from_pkcs8(ALG, key_pair, &SystemRandom::new())
- .map_err(|err| trc::Cause::Crypto.reason(err).caused_by(trc::location!()))?;
+ .map_err(|err| trc::Cause::Acme.reason(err).caused_by(trc::location!()))?;
let contact: Vec<&'a str> = contact.into_iter().map(AsRef::<str>::as_ref).collect();
let payload = json!({
"termsOfServiceAgreed": true,
@@ -97,13 +97,19 @@ impl Account {
)?;
let response = https(url.as_ref(), Method::POST, Some(body)).await?;
let location = get_header(&response, "Location").ok();
- let body = response.text().await?;
+ let body = response
+ .text()
+ .await
+ .map_err(|err| trc::Cause::Acme.from_http_error(err))?;
Ok((location, body))
}
pub async fn new_order(&self, domains: Vec<String>) -> trc::Result<(String, Order)> {
let domains: Vec<Identifier> = domains.into_iter().map(Identifier::Dns).collect();
- let payload = format!("{{\"identifiers\":{}}}", serde_json::to_string(&domains)?);
+ let payload = format!(
+ "{{\"identifiers\":{}}}",
+ serde_json::to_string(&domains).map_err(|err| trc::Cause::Acme.from_json_error(err))?
+ );
let response = self.request(&self.directory.new_order, &payload).await?;
let url = response.0.ok_or(
trc::Cause::Acme
@@ -111,13 +117,14 @@ impl Account {
.details("Missing header")
.ctx(trc::Key::Id, "Location"),
)?;
- let order = serde_json::from_str(&response.1)?;
+ let order = serde_json::from_str(&response.1)
+ .map_err(|err| trc::Cause::Acme.from_json_error(err))?;
Ok((url, order))
}
pub async fn auth(&self, url: impl AsRef<str>) -> trc::Result<Auth> {
let response = self.request(url, "").await?;
- serde_json::from_str(&response.1).map_err(Into::into)
+ serde_json::from_str(&response.1).map_err(|err| trc::Cause::Acme.from_json_error(err))
}
pub async fn challenge(&self, url: impl AsRef<str>) -> trc::Result<()> {
@@ -126,13 +133,13 @@ impl Account {
pub async fn order(&self, url: impl AsRef<str>) -> trc::Result<Order> {
let response = self.request(&url, "").await?;
- serde_json::from_str(&response.1).map_err(Into::into)
+ serde_json::from_str(&response.1).map_err(|err| trc::Cause::Acme.from_json_error(err))
}
pub async fn finalize(&self, url: impl AsRef<str>, csr: Vec<u8>) -> trc::Result<Order> {
let payload = format!("{{\"csr\":\"{}\"}}", URL_SAFE_NO_PAD.encode(csr));
let response = self.request(&url, &payload).await?;
- serde_json::from_str(&response.1).map_err(Into::into)
+ serde_json::from_str(&response.1).map_err(|err| trc::Cause::Acme.from_json_error(err))
}
pub async fn certificate(&self, url: impl AsRef<str>) -> trc::Result<String> {
@@ -155,12 +162,12 @@ impl Account {
params.alg = &PKCS_ECDSA_P256_SHA256;
params.custom_extensions = vec![CustomExtension::new_acme_identifier(key_auth.as_ref())];
let cert = Certificate::from_params(params)
- .map_err(|err| trc::Cause::Crypto.caused_by(trc::location!()).reason(err))?;
+ .map_err(|err| trc::Cause::Acme.caused_by(trc::location!()).reason(err))?;
Ok(Bincode::new(SerializedCert {
certificate: cert
.serialize_der()
- .map_err(|err| trc::Cause::Crypto.caused_by(trc::location!()).reason(err))?,
+ .map_err(|err| trc::Cause::Acme.caused_by(trc::location!()).reason(err))?,
private_key: cert.serialize_private_key_der(),
})
.serialize())
@@ -183,9 +190,14 @@ pub struct Directory {
impl Directory {
pub async fn discover(url: impl AsRef<str>) -> trc::Result<Self> {
- Ok(serde_json::from_str(
- &https(url, Method::GET, None).await?.text().await?,
- )?)
+ serde_json::from_str(
+ &https(url, Method::GET, None)
+ .await?
+ .text()
+ .await
+ .map_err(|err| trc::Cause::Acme.from_http_error(err))?,
+ )
+ .map_err(|err| trc::Cause::Acme.from_json_error(err))
}
pub async fn nonce(&self) -> trc::Result<String> {
get_header(
@@ -286,7 +298,10 @@ async fn https(
);
}
- let mut request = builder.build()?.request(method, url);
+ let mut request = builder
+ .build()
+ .map_err(|err| trc::Cause::Acme.from_http_error(err))?
+ .request(method, url);
if let Some(body) = body {
request = request
@@ -294,12 +309,20 @@ async fn https(
.body(body);
}
- request.send().await?.assert_success().await
+ request
+ .send()
+ .await
+ .map_err(|err| trc::Cause::Acme.from_http_error(err))?
+ .assert_success(trc::Cause::Acme)
+ .await
}
fn get_header(response: &Response, header: &'static str) -> trc::Result<String> {
match response.headers().get_all(header).iter().last() {
- Some(value) => Ok(value.to_str()?.to_string()),
+ Some(value) => Ok(value
+ .to_str()
+ .map_err(|err| trc::Cause::Acme.from_http_str_error(err))?
+ .to_string()),
None => Err(trc::Cause::Acme
.caused_by(trc::location!())
.details("Missing header")
diff --git a/crates/common/src/listener/acme/jose.rs b/crates/common/src/listener/acme/jose.rs
index 805218d7..612a37a5 100644
--- a/crates/common/src/listener/acme/jose.rs
+++ b/crates/common/src/listener/acme/jose.rs
@@ -23,14 +23,15 @@ pub(crate) fn sign(
let combined = format!("{}.{}", &protected, &payload);
let signature = key
.sign(&SystemRandom::new(), combined.as_bytes())
- .map_err(|err| trc::Cause::Crypto.caused_by(trc::location!()).reason(err))?;
+ .map_err(|err| trc::Cause::Acme.caused_by(trc::location!()).reason(err))?;
let signature = URL_SAFE_NO_PAD.encode(signature.as_ref());
let body = Body {
protected,
payload,
signature,
};
- Ok(serde_json::to_string(&body)?)
+
+ serde_json::to_string(&body).map_err(|err| trc::Cause::Acme.from_json_error(err))
}
pub(crate) fn key_authorization(key: &EcdsaKeyPair, token: &str) -> trc::Result<String> {
@@ -84,7 +85,8 @@ impl<'a> Protected<'a> {
nonce,
url,
};
- let protected = serde_json::to_vec(&protected)?;
+ let protected =
+ serde_json::to_vec(&protected).map_err(|err| trc::Cause::Acme.from_json_error(err))?;
Ok(URL_SAFE_NO_PAD.encode(protected))
}
}
@@ -119,7 +121,8 @@ impl Jwk {
x: &self.x,
y: &self.y,
};
- let json = serde_json::to_vec(&jwk_thumb)?;
+ let json =
+ serde_json::to_vec(&jwk_thumb).map_err(|err| trc::Cause::Acme.from_json_error(err))?;
let hash = digest(&SHA256, &json);
Ok(URL_SAFE_NO_PAD.encode(hash))
}
diff --git a/crates/common/src/listener/acme/order.rs b/crates/common/src/listener/acme/order.rs
index 152e71a0..12a92bb5 100644
--- a/crates/common/src/listener/acme/order.rs
+++ b/crates/common/src/listener/acme/order.rs
@@ -87,7 +87,7 @@ impl Core {
params.distinguished_name = DistinguishedName::new();
params.alg = &PKCS_ECDSA_P256_SHA256;
let cert = rcgen::Certificate::from_params(params)
- .map_err(|err| trc::Cause::Crypto.caused_by(trc::location!()).reason(err))?;
+ .map_err(|err| trc::Cause::Acme.caused_by(trc::location!()).reason(err))?;
let (order_url, mut order) = account.new_order(provider.domains.clone()).await?;
loop {
@@ -122,7 +122,7 @@ impl Core {
}
}
if order.status == OrderStatus::Processing {
- return Err(trc::Cause::Timeout
+ return Err(trc::Cause::Acme
.caused_by(trc::location!())
.details("Order processing timed out"));
}
@@ -135,9 +135,9 @@ impl Core {
"Sending CSR"
);
- let csr = cert.serialize_request_der().map_err(|err| {
- trc::Cause::Crypto.caused_by(trc::location!()).reason(err)
- })?;
+ let csr = cert
+ .serialize_request_der()
+ .map_err(|err| trc::Cause::Acme.caused_by(trc::location!()).reason(err))?;
order = account.finalize(order.finalize, csr).await?
}
OrderStatus::Valid { certificate } => {
@@ -165,7 +165,7 @@ impl Core {
"Invalid order"
);
- return Err(trc::Cause::Invalid.into_err().details("Invalid ACME order"));
+ return Err(trc::Cause::Acme.into_err().details("Invalid ACME order"));
}
}
}
@@ -194,8 +194,9 @@ impl Core {
.iter()
.find(|c| c.typ == challenge_type)
.ok_or(
- trc::Cause::MissingParameter
+ trc::Cause::Acme
.into_err()
+ .details("Missing Parameter")
.ctx(trc::Key::Id, challenge_type.as_str()),
)?;
@@ -342,8 +343,9 @@ impl Core {
}
AuthStatus::Valid => return Ok(()),
_ => {
- return Err(trc::Cause::Authentication
+ return Err(trc::Cause::Acme
.into_err()
+ .details("Authentication error")
.ctx(trc::Key::Status, auth.status.as_str()))
}
};
@@ -373,24 +375,25 @@ impl Core {
return Ok(());
}
_ => {
- return Err(trc::Cause::Authentication
+ return Err(trc::Cause::Acme
.into_err()
+ .details("Authentication error")
.ctx(trc::Key::Status, auth.status.as_str()))
}
}
}
- Err(trc::Cause::Authentication
+ Err(trc::Cause::Acme
.into_err()
- .details("Too many attempts")
+ .details("Too many authentication attempts")
.ctx(trc::Key::Id, domain))
}
}
fn parse_cert(pem: &[u8]) -> trc::Result<(CertifiedKey, [DateTime<Utc>; 2])> {
let mut pems = pem::parse_many(pem)
- .map_err(|err| trc::Cause::Crypto.reason(err).caused_by(trc::location!()))?;
+ .map_err(|err| trc::Cause::Acme.reason(err).caused_by(trc::location!()))?;
if pems.len() < 2 {
- return Err(trc::Cause::Crypto
+ return Err(trc::Cause::Acme
.caused_by(trc::location!())
.ctx(trc::Key::Size, pems.len())
.details("Too few PEMs"));
@@ -399,7 +402,7 @@ fn parse_cert(pem: &[u8]) -> trc::Result<(CertifiedKey, [DateTime<Utc>; 2])> {
pems.remove(0).contents(),
))) {
Ok(pk) => pk,
- Err(err) => return Err(trc::Cause::Crypto.reason(err).caused_by(trc::location!())),
+ Err(err) => return Err(trc::Cause::Acme.reason(err).caused_by(trc::location!())),
};
let cert_chain: Vec<CertificateDer> = pems
.into_iter()
@@ -414,7 +417,7 @@ fn parse_cert(pem: &[u8]) -> trc::Result<(CertifiedKey, [DateTime<Utc>; 2])> {
.unwrap_or_default()
})
}
- Err(err) => return Err(trc::Cause::Crypto.reason(err).caused_by(trc::location!())),
+ Err(err) => return Err(trc::Cause::Acme.reason(err).caused_by(trc::location!())),
};
let cert = CertifiedKey::new(cert_chain, pk);
Ok((cert, validity))
diff --git a/crates/common/src/listener/mod.rs b/crates/common/src/listener/mod.rs
index 45c74ba3..2d051346 100644
--- a/crates/common/src/listener/mod.rs
+++ b/crates/common/src/listener/mod.rs
@@ -77,6 +77,13 @@ pub trait SessionStream: AsyncRead + AsyncWrite + Unpin + 'static + Sync + Send
fn tls_version_and_cipher(&self) -> (Cow<'static, str>, Cow<'static, str>);
}
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum SessionResult {
+ Continue,
+ Close,
+ UpgradeTls,
+}
+
pub trait SessionManager: Sync + Send + 'static + Clone {
fn spawn<T: SessionStream>(
&self,
diff --git a/crates/common/src/manager/backup.rs b/crates/common/src/manager/backup.rs
index bcfc61aa..4838c3b7 100644
--- a/crates/common/src/manager/backup.rs
+++ b/crates/common/src/manager/backup.rs
@@ -1117,19 +1117,19 @@ pub(super) trait DeserializeBytes {
impl DeserializeBytes for &[u8] {
fn range(&self, range: Range<usize>) -> trc::Result<&[u8]> {
self.get(range.start..std::cmp::min(range.end, self.len()))
- .ok_or_else(|| trc::Cause::DataCorruption.caused_by(trc::location!()))
+ .ok_or_else(|| trc::StoreCause::DataCorruption.caused_by(trc::location!()))
}
fn deserialize_u8(&self, offset: usize) -> trc::Result<u8> {
self.get(offset)
.copied()
- .ok_or_else(|| trc::Cause::DataCorruption.caused_by(trc::location!()))
+ .ok_or_else(|| trc::StoreCause::DataCorruption.caused_by(trc::location!()))
}
fn deserialize_leb128<U: Leb128_>(&self) -> trc::Result<U> {
self.read_leb128::<U>()
.map(|(v, _)| v)
- .ok_or_else(|| trc::Cause::DataCorruption.caused_by(trc::location!()))
+ .ok_or_else(|| trc::StoreCause::DataCorruption.caused_by(trc::location!()))
}
}
diff --git a/crates/common/src/manager/config.rs b/crates/common/src/manager/config.rs
index 7a9460e5..dcd9e4cb 100644
--- a/crates/common/src/manager/config.rs
+++ b/crates/common/src/manager/config.rs
@@ -316,7 +316,8 @@ impl ConfigManager {
.await
.map_err(|err| {
trc::Cause::Configuration
- .caused_by(trc::Error::from(err))
+ .reason(err)
+ .details("Failed to write local configuration")
.ctx(trc::Key::Path, self.cfg_local_path.display().to_string())
})
}
diff --git a/crates/common/src/manager/webadmin.rs b/crates/common/src/manager/webadmin.rs
index 89324b52..6571e4fc 100644
--- a/crates/common/src/manager/webadmin.rs
+++ b/crates/common/src/manager/webadmin.rs
@@ -36,7 +36,7 @@ impl WebAdminManager {
}
}
- pub async fn get(&self, path: &str) -> io::Result<Resource<Vec<u8>>> {
+ pub async fn get(&self, path: &str) -> trc::Result<Resource<Vec<u8>>> {
let routes = self.routes.load();
if let Some(resource) = routes.get(path).or_else(|| routes.get("index.html")) {
tokio::fs::read(&resource.contents)
@@ -45,6 +45,12 @@ impl WebAdminManager {
content_type: resource.content_type,
contents,
})
+ .map_err(|err| {
+ trc::ResourceCause::Error
+ .reason(err)
+ .ctx(trc::Key::Path, path.to_string())
+ .caused_by(trc::location!())
+ })
} else {
Ok(Resource::default())
}
@@ -52,42 +58,46 @@ impl WebAdminManager {
pub async fn unpack(&self, blob_store: &BlobStore) -> trc::Result<()> {
// Delete any existing bundles
- self.bundle_path.clean().await?;
+ self.bundle_path.clean().await.map_err(unpack_error)?;
// Obtain webadmin bundle
let bundle = blob_store
.get_blob(WEBADMIN_KEY, 0..usize::MAX)
.await?
.ok_or_else(|| {
- trc::Cause::NotFound
+ trc::ResourceCause::NotFound
.caused_by(trc::location!())
.details("Webadmin bundle not found")
})?;
// Uncompress
let mut bundle = zip::ZipArchive::new(Cursor::new(bundle)).map_err(|err| {
- trc::Cause::Decompress
+ trc::ResourceCause::Error
.caused_by(trc::location!())
.reason(err)
+ .details("Failed to decompress webadmin bundle")
})?;
let mut routes = AHashMap::new();
for i in 0..bundle.len() {
let (file_name, contents) = {
let mut file = bundle.by_index(i).map_err(|err| {
- trc::Cause::Decompress
+ trc::ResourceCause::Error
.caused_by(trc::location!())
.reason(err)
+ .details("Failed to read file from webadmin bundle")
})?;
if file.is_dir() {
continue;
}
let mut contents = Vec::new();
- file.read_to_end(&mut contents)?;
+ file.read_to_end(&mut contents).map_err(unpack_error)?;
(file.name().to_string(), contents)
};
let path = self.bundle_path.path.join(format!("{i:02}"));
- tokio::fs::write(&path, contents).await?;
+ tokio::fs::write(&path, contents)
+ .await
+ .map_err(unpack_error)?;
let resource = Resource {
content_type: match file_name
@@ -164,6 +174,12 @@ impl TempDir {
}
}
+fn unpack_error(err: std::io::Error) -> trc::Error {
+ trc::ResourceCause::Error
+ .reason(err)
+ .details("Failed to unpack webadmin bundle")
+}
+
impl Default for WebAdminManager {
fn default() -> Self {
Self::new()
diff --git a/crates/directory/src/backend/imap/lookup.rs b/crates/directory/src/backend/imap/lookup.rs
index e5a427b4..02613054 100644
--- a/crates/directory/src/backend/imap/lookup.rs
+++ b/crates/directory/src/backend/imap/lookup.rs
@@ -38,7 +38,7 @@ impl ImapDirectory {
AUTH_XOAUTH2
}
_ => {
- trc::bail!(trc::Cause::Unsupported
+ trc::bail!(trc::StoreCause::NotSupported
.ctx(
trc::Key::Reason,
"IMAP server does not offer any supported auth mechanisms."
@@ -58,32 +58,32 @@ impl ImapDirectory {
},
}
} else {
- Err(trc::Cause::Unsupported
+ Err(trc::StoreCause::NotSupported
.caused_by(trc::location!())
.protocol(trc::Protocol::Imap))
}
}
pub async fn email_to_ids(&self, _address: &str) -> trc::Result<Vec<u32>> {
- Err(trc::Cause::Unsupported
+ Err(trc::StoreCause::NotSupported
.caused_by(trc::location!())
.protocol(trc::Protocol::Imap))
}
pub async fn rcpt(&self, _address: &str) -> trc::Result<bool> {
- Err(trc::Cause::Unsupported
+ Err(trc::StoreCause::NotSupported
.caused_by(trc::location!())
.protocol(trc::Protocol::Imap))
}
pub async fn vrfy(&self, _address: &str) -> trc::Result<Vec<String>> {
- Err(trc::Cause::Unsupported
+ Err(trc::StoreCause::NotSupported
.caused_by(trc::location!())
.protocol(trc::Protocol::Imap))
}
pub async fn expn(&self, _address: &str) -> trc::Result<Vec<String>> {
- Err(trc::Cause::Unsupported
+ Err(trc::StoreCause::NotSupported
.caused_by(trc::location!())
.protocol(trc::Protocol::Imap))
}
diff --git a/crates/directory/src/backend/internal/manage.rs b/crates/directory/src/backend/internal/manage.rs
index 7518b296..de88d7fa 100644
--- a/crates/directory/src/backend/internal/manage.rs
+++ b/crates/directory/src/backend/internal/manage.rs
@@ -122,7 +122,7 @@ impl ManageDirectory for Store {
return Ok(account_id);
}
Err(err) => {
- if err.matches(trc::Cause::AssertValue) && try_count < 3 {
+ if err.is_assertion_failure() && try_count < 3 {
try_count += 1;
continue;
} else {
@@ -422,7 +422,7 @@ impl ManageDirectory for Store {
continue;
}
}
- return Err(trc::Cause::Unsupported.caused_by(trc::location!()));
+ return Err(trc::ManageCause::NotSupported.caused_by(trc::location!()));
}
(
PrincipalAction::Set,
@@ -762,7 +762,7 @@ impl ManageDirectory for Store {
}
_ => {
- return Err(trc::Cause::Unsupported.caused_by(trc::location!()));
+ return Err(trc::StoreCause::NotSupported.caused_by(trc::location!()));
}
}
}
@@ -1055,18 +1055,28 @@ impl From<Principal<String>> for Principal<u32> {
}
}
-fn err_missing(field: impl Into<trc::Value>) -> trc::Error {
- trc::Cause::MissingParameter.ctx(trc::Key::Key, field)
+pub fn err_missing(field: impl Into<trc::Value>) -> trc::Error {
+ trc::ManageCause::MissingParameter.ctx(trc::Key::Key, field)
}
-fn err_exists(field: impl Into<trc::Value>, value: impl Into<trc::Value>) -> trc::Error {
- trc::Cause::AlreadyExists
+pub fn err_exists(field: impl Into<trc::Value>, value: impl Into<trc::Value>) -> trc::Error {
+ trc::ManageCause::AlreadyExists
.ctx(trc::Key::Key, field)
.ctx(trc::Key::Value, value)
}
-fn not_found(value: impl Into<trc::Value>) -> trc::Error {
- trc::Cause::NotFound.ctx(trc::Key::Key, value)
+pub fn not_found(value: impl Into<trc::Value>) -> trc::Error {
+ trc::ManageCause::NotFound.ctx(trc::Key::Key, value)
+}
+
+pub fn unsupported(details: impl Into<trc::Value>) -> trc::Error {
+ trc::ManageCause::NotSupported.ctx(trc::Key::Details, details)
+}
+
+pub fn error(details: impl Into<trc::Value>, reason: Option<impl Into<trc::Value>>) -> trc::Error {
+ trc::ManageCause::Error
+ .ctx(trc::Key::Details, details)
+ .ctx_opt(trc::Key::Reason, reason)
}
impl From<PrincipalField> for trc::Value {
diff --git a/crates/directory/src/backend/internal/mod.rs b/crates/directory/src/backend/internal/mod.rs
index 4dba13a5..4aa0013f 100644
--- a/crates/directory/src/backend/internal/mod.rs
+++ b/crates/directory/src/backend/internal/mod.rs
@@ -58,7 +58,7 @@ impl Serialize for &Principal<u32> {
impl Deserialize for Principal<u32> {
fn deserialize(bytes: &[u8]) -> trc::Result<Self> {
deserialize(bytes).ok_or_else(|| {
- trc::Cause::DataCorruption
+ trc::StoreCause::DataCorruption
.caused_by(trc::location!())
.ctx(trc::Key::Value, bytes)
})
@@ -79,12 +79,12 @@ impl Deserialize for PrincipalIdType {
let mut bytes = bytes_.iter();
Ok(PrincipalIdType {
account_id: bytes.next_leb128().ok_or_else(|| {
- trc::Cause::DataCorruption
+ trc::StoreCause::DataCorruption
.caused_by(trc::location!())
.ctx(trc::Key::Value, bytes_)
})?,
typ: Type::from_u8(*bytes.next().ok_or_else(|| {
- trc::Cause::DataCorruption
+ trc::StoreCause::DataCorruption
.caused_by(trc::location!())
.ctx(trc::Key::Value, bytes_)
})?),
diff --git a/crates/directory/src/backend/ldap/lookup.rs b/crates/directory/src/backend/ldap/lookup.rs
index d837cfda..7191735f 100644
--- a/crates/directory/src/backend/ldap/lookup.rs
+++ b/crates/directory/src/backend/ldap/lookup.rs
@@ -79,7 +79,7 @@ impl LdapDirectory {
{
Ok(Some(principal)) => principal,
Err(err)
- if err.matches(trc::Cause::Ldap)
+ if err.matches(trc::Cause::Store(trc::StoreCause::Ldap))
&& err
.value(trc::Key::Code)
.and_then(|v| v.to_uint())
diff --git a/crates/directory/src/backend/smtp/lookup.rs b/crates/directory/src/backend/smtp/lookup.rs
index 298c4db2..f879b8ed 100644
--- a/crates/directory/src/backend/smtp/lookup.rs
+++ b/crates/directory/src/backend/smtp/lookup.rs
@@ -21,14 +21,14 @@ impl SmtpDirectory {
.authenticate(credentials)
.await
} else {
- Err(trc::Cause::Unsupported
+ Err(trc::StoreCause::NotSupported
.caused_by(trc::location!())
.protocol(trc::Protocol::Smtp))
}
}
pub async fn email_to_ids(&self, _address: &str) -> trc::Result<Vec<u32>> {
- Err(trc::Cause::Unsupported
+ Err(trc::StoreCause::NotSupported
.caused_by(trc::location!())
.protocol(trc::Protocol::Smtp))
}
@@ -64,7 +64,7 @@ impl SmtpDirectory {
Ok(true)
}
Severity::PermanentNegativeCompletion => Ok(false),
- _ => Err(trc::Cause::Unexpected
+ _ => Err(trc::StoreCause::Unexpected
.ctx(trc::Key::Protocol, trc::Protocol::Smtp)
.ctx(trc::Key::Code, reply.code())
.ctx(trc::Key::Details, reply.message)),
@@ -127,10 +127,10 @@ impl SmtpClient {
.split('\n')
.map(|p| p.to_string())
.collect::<Vec<String>>()),
- code @ (550 | 551 | 553 | 500 | 502) => Err(trc::Cause::Unsupported
+ code @ (550 | 551 | 553 | 500 | 502) => Err(trc::StoreCause::NotSupported
.ctx(trc::Key::Protocol, trc::Protocol::Smtp)
.ctx(trc::Key::Code, code)),
- code => Err(trc::Cause::Unexpected
+ code => Err(trc::StoreCause::Unexpected
.ctx(trc::Key::Protocol, trc::Protocol::Smtp)
.ctx(trc::Key::Code, code)
.ctx(trc::Key::Details, reply.message)),
diff --git a/crates/directory/src/core/secret.rs b/crates/directory/src/core/secret.rs
index fb27ca97..136f1657 100644
--- a/crates/directory/src/core/secret.rs
+++ b/crates/directory/src/core/secret.rs
@@ -58,7 +58,11 @@ impl<T: serde::Serialize + serde::de::DeserializeOwned> Principal<T> {
// Token needs to validate with at least one of the TOTP secrets
is_totp_verified = TOTP::from_url(secret)
- .map_err(|err| trc::Cause::Invalid.reason(err).details(secret.to_string()))?
+ .map_err(|err| {
+ trc::AuthCause::Invalid
+ .reason(err)
+ .details(secret.to_string())
+ })?
.check_current(totp_token)
.unwrap_or(false);
}
@@ -82,7 +86,7 @@ impl<T: serde::Serialize + serde::de::DeserializeOwned> Principal<T> {
// Only let the client know if the TOTP code is missing
// if the password is correct
- Err(trc::Cause::MissingTotp.into_err())
+ Err(trc::AuthCause::MissingTotp.into_err())
} else {
// Return the TOTP verification status
@@ -124,7 +128,9 @@ async fn verify_hash_prefix(hashed_secret: &str, secret: &str) -> trc::Result<bo
.ok();
}
Err(err) => {
- tx.send(Err(trc::Cause::Invalid.reason(err).details(hashed_secret)))
+ tx.send(Err(trc::AuthCause::Invalid
+ .reason(err)
+ .details(hashed_secret)))
.ok();
}
});
@@ -149,7 +155,7 @@ async fn verify_hash_prefix(hashed_secret: &str, secret: &str) -> trc::Result<bo
// MD5 based hash
Ok(md5_crypt::verify(secret, hashed_secret))
} else {
- Err(trc::Cause::Invalid
+ Err(trc::AuthCause::Invalid
.into_err()
.details(hashed_secret.to_string()))
}
@@ -250,12 +256,12 @@ pub async fn verify_secret_hash(hashed_secret: &str, secret: &str) -> trc::Resul
}
}
"PLAIN" | "plain" | "CLEAR" | "clear" => Ok(hashed_secret == secret),
- _ => Err(trc::Cause::Invalid
+ _ => Err(trc::AuthCause::Invalid
.ctx(trc::Key::Reason, "Unsupported algorithm")
.details(hashed_secret.to_string())),
}
} else {
- Err(trc::Cause::Invalid
+ Err(trc::AuthCause::Invalid
.into_err()
.details(hashed_secret.to_string()))
}
diff --git a/crates/directory/src/lib.rs b/crates/directory/src/lib.rs
index 9697cd63..8de4aee1 100644
--- a/crates/directory/src/lib.rs
+++ b/crates/directory/src/lib.rs
@@ -158,10 +158,10 @@ impl IntoError for PoolError<LdapError> {
fn into_error(self) -> trc::Error {
match self {
PoolError::Backend(error) => error.into_error(),
- PoolError::Timeout(_) => {
- trc::Cause::Timeout.ctx(trc::Key::Protocol, trc::Protocol::Ldap)
- }
- err => trc::Cause::Pool
+ PoolError::Timeout(_) => trc::StoreCause::Pool
+ .ctx(trc::Key::Protocol, trc::Protocol::Ldap)
+ .details("Connection timed out"),
+ err => trc::StoreCause::Pool
.ctx(trc::Key::Protocol, trc::Protocol::Ldap)
.reason(err),
}
@@ -172,10 +172,10 @@ impl IntoError for PoolError<ImapError> {
fn into_error(self) -> trc::Error {
match self {
PoolError::Backend(error) => error.into_error(),
- PoolError::Timeout(_) => {
- trc::Cause::Timeout.ctx(trc::Key::Protocol, trc::Protocol::Imap)
- }
- err => trc::Cause::Pool
+ PoolError::Timeout(_) => trc::StoreCause::Pool
+ .ctx(trc::Key::Protocol, trc::Protocol::Imap)
+ .details("Connection timed out"),
+ err => trc::StoreCause::Pool
.ctx(trc::Key::Protocol, trc::Protocol::Imap)
.reason(err),
}
@@ -186,10 +186,10 @@ impl IntoError for PoolError<mail_send::Error> {
fn into_error(self) -> trc::Error {
match self {
PoolError::Backend(error) => error.into_error(),
- PoolError::Timeout(_) => {
- trc::Cause::Timeout.ctx(trc::Key::Protocol, trc::Protocol::Smtp)
- }
- err => trc::Cause::Pool
+ PoolError::Timeout(_) => trc::StoreCause::Pool
+ .ctx(trc::Key::Protocol, trc::Protocol::Smtp)
+ .details("Connection timed out"),
+ err => trc::StoreCause::Pool
.ctx(trc::Key::Protocol, trc::Protocol::Smtp)
.reason(err),
}
@@ -211,9 +211,11 @@ impl IntoError for mail_send::Error {
impl IntoError for LdapError {
fn into_error(self) -> trc::Error {
if let LdapError::LdapResult { result } = &self {
- trc::Cause::Ldap.ctx(trc::Key::Code, result.rc).reason(self)
+ trc::StoreCause::Ldap
+ .ctx(trc::Key::Code, result.rc)
+ .reason(self)
} else {
- trc::Cause::Ldap.reason(self)
+ trc::StoreCause::Ldap.reason(self)
}
}
}
diff --git a/crates/imap/src/op/acl.rs b/crates/imap/src/op/acl.rs
index f088921c..32cd9ef0 100644
--- a/crates/imap/src/op/acl.rs
+++ b/crates/imap/src/op/acl.rs
@@ -250,7 +250,7 @@ impl<T: SessionStream> Session<T> {
}) {
acl
} else {
- return Err(trc::Cause::DataCorruption
+ return Err(trc::StoreCause::DataCorruption
.into_err()
.id(arguments.tag)
.ctx(trc::Key::Reason, "Invalid mailbox ACL")
diff --git a/crates/imap/src/op/append.rs b/crates/imap/src/op/append.rs
index 01aac285..b923e83c 100644
--- a/crates/imap/src/op/append.rs
+++ b/crates/imap/src/op/append.rs
@@ -116,7 +116,7 @@ impl<T: SessionStream> SessionData<T> {
last_change_id = Some(email.change_id);
}
Err(err) => {
- return Err(if err.matches(trc::Cause::OverQuota) {
+ return Err(if err.matches(trc::Cause::Limit(trc::LimitCause::Quota)) {
err.details("Disk quota exceeded.")
.code(ResponseCode::OverQuota)
} else {
diff --git a/crates/imap/src/op/authenticate.rs b/crates/imap/src/op/authenticate.rs
index 337179c5..9096146b 100644
--- a/crates/imap/src/op/authenticate.rs
+++ b/crates/imap/src/op/authenticate.rs
@@ -25,7 +25,7 @@ impl<T: SessionStream> Session<T> {
if !args.params.is_empty() {
let challenge = base64_decode(args.params.pop().unwrap().as_bytes())
.ok_or_else(|| {
- trc::Cause::Authentication
+ trc::AuthCause::Error
.into_err()
.details("Failed to decode challenge.")
.id(args.tag.clone())
@@ -38,7 +38,7 @@ impl<T: SessionStream> Session<T> {
decode_challenge_oauth(&challenge)
}
.map_err(|err| {
- trc::Cause::Authentication
+ trc::AuthCause::Error
.into_err()
.details(err)
.id(args.tag.clone())
@@ -55,7 +55,7 @@ impl<T: SessionStream> Session<T> {
self.write_bytes(b"+ \"\"\r\n".to_vec()).await
}
}
- _ => Err(trc::Cause::Authentication
+ _ => Err(trc::AuthCause::Error
.into_err()
.details("Authentication mechanism not supported.")
.id(args.tag)
@@ -96,7 +96,7 @@ impl<T: SessionStream> Session<T> {
Some(Some(limiter)) => Some(limiter),
None => None,
Some(None) => {
- return Err(trc::Cause::TooManyConcurrentRequests.into());
+ return Err(trc::LimitCause::ConcurrentRequest.into_err());
}
};
diff --git a/crates/imap/src/op/expunge.rs b/crates/imap/src/op/expunge.rs
index aeacac9e..093868ba 100644
--- a/crates/imap/src/op/expunge.rs
+++ b/crates/imap/src/op/expunge.rs
@@ -239,7 +239,7 @@ impl<T: SessionStream> SessionData<T> {
changelog.log_child_update(Collection::Mailbox, mailbox_id.mailbox_id);
}
Err(err) => {
- if !err.matches(trc::Cause::AssertValue) {
+ if !err.is_assertion_failure() {
return Err(err.caused_by(trc::location!()));
}
}
diff --git a/crates/imap/src/op/fetch.rs b/crates/imap/src/op/fetch.rs
index f1f38716..634b45fe 100644
--- a/crates/imap/src/op/fetch.rs
+++ b/crates/imap/src/op/fetch.rs
@@ -516,7 +516,7 @@ impl<T: SessionStream> SessionData<T> {
changelog.log_update(Collection::Email, id);
}
Err(err) => {
- if !err.matches(trc::Cause::AssertValue) {
+ if !err.is_assertion_failure() {
return Err(err.id(arguments.tag));
}
}
diff --git a/crates/imap/src/op/status.rs b/crates/imap/src/op/status.rs
index 933e7ad1..d4fa855e 100644
--- a/crates/imap/src/op/status.rs
+++ b/crates/imap/src/op/status.rs
@@ -239,7 +239,7 @@ impl<T: SessionStream> SessionData<T> {
.await?
.and_then(|obj| obj.get(&Property::Cid).as_uint())
.ok_or_else(|| {
- trc::Cause::Unexpected
+ trc::StoreCause::Unexpected
.into_err()
.details("Mailbox unavailable")
.ctx(trc::Key::Reason, "Failed to obtain uid validity")
diff --git a/crates/imap/src/op/store.rs b/crates/imap/src/op/store.rs
index 044dbf19..608c2c5d 100644
--- a/crates/imap/src/op/store.rs
+++ b/crates/imap/src/op/store.rs
@@ -288,7 +288,7 @@ impl<T: SessionStream> SessionData<T> {
});
}
}
- Err(err) if err.matches(trc::Cause::AssertValue) => {
+ Err(err) if err.is_assertion_failure() => {
if try_count < MAX_RETRIES {
try_count += 1;
continue;
diff --git a/crates/jmap-proto/src/error/method.rs b/crates/jmap-proto/src/error/method.rs
index 93879b30..b94c4789 100644
--- a/crates/jmap-proto/src/error/method.rs
+++ b/crates/jmap-proto/src/error/method.rs
@@ -136,7 +136,9 @@ impl From<MethodError> for trc::Error {
),
};
- trc::Cause::Jmap
+ let todo = "fix";
+
+ trc::JmapCause::RequestTooLarge
.ctx(trc::Key::Type, typ)
.ctx(trc::Key::Details, description)
}
@@ -184,7 +186,11 @@ impl Serialize for MethodErrorWrapper {
{
let mut map = serializer.serialize_map(2.into())?;
- let (error_type, description) = if self.0.matches(trc::Cause::Jmap) {
+ let todo = "fix";
+ let (error_type, description) = if self
+ .0
+ .matches(trc::Cause::Jmap(trc::JmapCause::RequestTooLarge))
+ {
(
self.0
.value(trc::Key::Type)
diff --git a/crates/jmap-proto/src/method/changes.rs b/crates/jmap-proto/src/method/changes.rs
index 9993779e..1450b2c1 100644
--- a/crates/jmap-proto/src/method/changes.rs
+++ b/crates/jmap-proto/src/method/changes.rs
@@ -5,8 +5,7 @@
*/
use crate::{
- error::method::MethodError,
- parser::{json::Parser, Error, Ignore, JsonObjectParser, Token},
+ parser::{json::Parser, Ignore, JsonObjectParser, Token},
request::{method::MethodObject, RequestProperty},
types::{id::Id, property::Property, state::State},
};
@@ -55,7 +54,7 @@ pub enum RequestArguments {
}
impl JsonObjectParser for ChangesRequest {
- fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
+ fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
where
Self: Sized,
{
@@ -68,10 +67,9 @@ impl JsonObjectParser for ChangesRequest {
MethodObject::EmailSubmission => RequestArguments::EmailSubmission,
MethodObject::Quota => RequestArguments::Quota,
_ => {
- return Err(Error::Method(MethodError::UnknownMethod(format!(
- "{}/changes",
- parser.ctx
- ))))
+ return Err(trc::JmapCause::UnknownMethod
+ .into_err()
+ .details(format!("{}/changes", parser.ctx)))
}
},
account_id: Id::default(),
diff --git a/crates/jmap-proto/src/method/copy.rs b/crates/jmap-proto/src/method/copy.rs
index bd0cea63..7e69bfa2 100644
--- a/crates/jmap-proto/src/method/copy.rs
+++ b/crates/jmap-proto/src/method/copy.rs
@@ -8,9 +8,9 @@ use serde::Serialize;
use utils::map::vec_map::VecMap;
use crate::{
- error::{method::MethodError, set::SetError},
+ error::set::SetError,
object::Object,
- parser::{json::Parser, Error, JsonObjectParser, Token},
+ parser::{json::Parser, JsonObjectParser, Token},
request::{method::MethodObject, reference::MaybeReference, RequestProperty},
types::{
blob::BlobId,
@@ -88,7 +88,7 @@ pub enum RequestArguments {
}
impl JsonObjectParser for CopyRequest<RequestArguments> {
- fn parse(parser: &mut Parser) -> crate::parser::Result<Self>
+ fn parse(parser: &mut Parser) -> trc::Result<Self>
where
Self: Sized,
{
@@ -96,10 +96,9 @@ impl JsonObjectParser for CopyRequest<RequestArguments> {
arguments: match &parser.ctx {
MethodObject::Email => RequestArguments::Email,
_ => {
- return Err(Error::Method(MethodError::UnknownMethod(format!(
- "{}/copy",
- parser.ctx
- ))))
+ return Err(trc::JmapCause::UnknownMethod
+ .into_err()
+ .details(format!("{}/copy", parser.ctx)))
}
},
account_id: Id::default(),
@@ -159,7 +158,7 @@ impl JsonObjectParser for CopyRequest<RequestArguments> {
}
impl JsonObjectParser for CopyBlobRequest {
- fn parse(parser: &mut Parser) -> crate::parser::Result<Self>
+ fn parse(parser: &mut Parser) -> trc::Result<Self>
where
Self: Sized,
{
diff --git a/crates/jmap-proto/src/method/get.rs b/crates/jmap-proto/src/method/get.rs
index 62e49b90..5a9c9905 100644
--- a/crates/jmap-proto/src/method/get.rs
+++ b/crates/jmap-proto/src/method/get.rs
@@ -7,7 +7,7 @@
use crate::{
error::method::MethodError,
object::{blob, email, Object},
- parser::{json::Parser, Error, JsonObjectParser, Token},
+ parser::{json::Parser, JsonObjectParser, Token},
request::{
method::MethodObject,
reference::{MaybeReference, ResultReference},
@@ -55,7 +55,7 @@ pub struct GetResponse {
}
impl JsonObjectParser for GetRequest<RequestArguments> {
- fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
+ fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
where
Self: Sized,
{
@@ -73,10 +73,9 @@ impl JsonObjectParser for GetRequest<RequestArguments> {
MethodObject::Blob => RequestArguments::Blob(Default::default()),
MethodObject::Quota => RequestArguments::Quota,
_ => {
- return Err(Error::Method(MethodError::UnknownMethod(format!(
- "{}/get",
- parser.ctx
- ))))
+ return Err(trc::JmapCause::UnknownMethod
+ .into_err()
+ .details(format!("{}/get", parser.ctx)))
}
},
account_id: Id::default(),
@@ -130,11 +129,7 @@ impl JsonObjectParser for GetRequest<RequestArguments> {
}
impl RequestPropertyParser for RequestArguments {
- fn parse(
- &mut self,
- parser: &mut Parser,
- property: RequestProperty,
- ) -> crate::parser::Result<bool> {
+ fn parse(&mut self, parser: &mut Parser, property: RequestProperty) -> trc::Result<bool> {
match self {
RequestArguments::Email(arguments) => arguments.parse(parser, property),
RequestArguments::Blob(arguments) => arguments.parse(parser, property),
diff --git a/crates/jmap-proto/src/method/import.rs b/crates/jmap-proto/src/method/import.rs
index c13daad5..447892f9 100644
--- a/crates/jmap-proto/src/method/import.rs
+++ b/crates/jmap-proto/src/method/import.rs
@@ -66,7 +66,7 @@ pub struct ImportEmailResponse {
}
impl JsonObjectParser for ImportEmailRequest {
- fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
+ fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
where
Self: Sized,
{
@@ -104,7 +104,7 @@ impl JsonObjectParser for ImportEmailRequest {
}
impl JsonObjectParser for ImportEmail {
- fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
+ fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
where
Self: Sized,
{
diff --git a/crates/jmap-proto/src/method/lookup.rs b/crates/jmap-proto/src/method/lookup.rs
index 7e823ee6..3148d984 100644
--- a/crates/jmap-proto/src/method/lookup.rs
+++ b/crates/jmap-proto/src/method/lookup.rs
@@ -39,7 +39,7 @@ pub struct BlobInfo {
}
impl JsonObjectParser for BlobLookupRequest {
- fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
+ fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
where
Self: Sized,
{
diff --git a/crates/jmap-proto/src/method/parse.rs b/crates/jmap-proto/src/method/parse.rs
index ca1715fc..c33d84fd 100644
--- a/crates/jmap-proto/src/method/parse.rs
+++ b/crates/jmap-proto/src/method/parse.rs
@@ -44,7 +44,7 @@ pub struct ParseEmailResponse {
}
impl JsonObjectParser for ParseEmailRequest {
- fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
+ fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
where
Self: Sized,
{
diff --git a/crates/jmap-proto/src/method/query.rs b/crates/jmap-proto/src/method/query.rs
index 8f531d45..515a3cbf 100644
--- a/crates/jmap-proto/src/method/query.rs
+++ b/crates/jmap-proto/src/method/query.rs
@@ -9,9 +9,8 @@ use std::fmt::Display;
use store::fts::{FilterItem, FilterType, FtsFilter};
use crate::{
- error::method::MethodError,
object::{email, mailbox},
- parser::{json::Parser, Error, Ignore, JsonObjectParser, Token},
+ parser::{json::Parser, Ignore, JsonObjectParser, Token},
request::{method::MethodObject, RequestProperty, RequestPropertyParser},
types::{date::UTCDate, id::Id, keyword::Keyword, state::State},
};
@@ -151,7 +150,7 @@ pub enum RequestArguments {
}
impl JsonObjectParser for QueryRequest<RequestArguments> {
- fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
+ fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
where
Self: Sized,
{
@@ -164,10 +163,9 @@ impl JsonObjectParser for QueryRequest<RequestArguments> {
MethodObject::Principal => RequestArguments::Principal,
MethodObject::Quota => RequestArguments::Quota,
_ => {
- return Err(Error::Method(MethodError::UnknownMethod(format!(
- "{}/query",
- parser.ctx
- ))))
+ return Err(trc::JmapCause::UnknownMethod
+ .into_err()
+ .details(format!("{}/query", parser.ctx)))
}
},
filter: vec![],
@@ -243,7 +241,7 @@ impl JsonObjectParser for QueryRequest<RequestArguments> {
}
}
-pub fn parse_filter(parser: &mut Parser) -> crate::parser::Result<Vec<Filter>> {
+pub fn parse_filter(parser: &mut Parser) -> trc::Result<Vec<Filter>> {
let mut filter = vec![Filter::Close];
let mut pos_stack = vec![0];
@@ -453,9 +451,9 @@ pub fn parse_filter(parser: &mut Parser) -> crate::parser::Result<Vec<Filter>> {
break;
}
} else {
- return Err(Error::Method(MethodError::InvalidArguments(
- "Malformed filter".to_string(),
- )));
+ return Err(trc::JmapCause::InvalidArguments
+ .into_err()
+ .details("Malformed filter"));
}
}
Token::ArrayEnd => {
@@ -471,7 +469,7 @@ pub fn parse_filter(parser: &mut Parser) -> crate::parser::Result<Vec<Filter>> {
Ok(filter)
}
-pub fn parse_sort(parser: &mut Parser) -> crate::parser::Result<Vec<Comparator>> {
+pub fn parse_sort(parser: &mut Parser) -> trc::Result<Vec<Comparator>> {
let mut sort = vec![];
loop {
@@ -527,7 +525,7 @@ pub fn parse_sort(parser: &mut Parser) -> crate::parser::Result<Vec<Comparator>>
}
impl JsonObjectParser for SortProperty {
- fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
+ fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
where
Self: Sized,
{
@@ -667,11 +665,7 @@ impl Display for SortProperty {
}
impl RequestPropertyParser for RequestArguments {
- fn parse(
- &mut self,
- parser: &mut Parser,
- property: RequestProperty,
- ) -> crate::parser::Result<bool> {
+ fn parse(&mut self, parser: &mut Parser, property: RequestProperty) -> trc::Result<bool> {
match self {
RequestArguments::Email(args) => args.parse(parser, property),
RequestArguments::Mailbox(args) => args.parse(parser, property),
diff --git a/crates/jmap-proto/src/method/query_changes.rs b/crates/jmap-proto/src/method/query_changes.rs
index 290321bc..f4181840 100644
--- a/crates/jmap-proto/src/method/query_changes.rs
+++ b/crates/jmap-proto/src/method/query_changes.rs
@@ -5,8 +5,7 @@
*/
use crate::{
- error::method::MethodError,
- parser::{json::Parser, Error, Ignore, JsonObjectParser, Token},
+ parser::{json::Parser, Ignore, JsonObjectParser, Token},
request::{method::MethodObject, RequestProperty, RequestPropertyParser},
types::{id::Id, state::State},
};
@@ -60,7 +59,7 @@ impl AddedItem {
}
impl JsonObjectParser for QueryChangesRequest {
- fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
+ fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
where
Self: Sized,
{
@@ -71,10 +70,9 @@ impl JsonObjectParser for QueryChangesRequest {
MethodObject::EmailSubmission => RequestArguments::EmailSubmission,
MethodObject::Quota => RequestArguments::Quota,
_ => {
- return Err(Error::Method(MethodError::UnknownMethod(format!(
- "{}/queryChanges",
- parser.ctx
- ))))
+ return Err(trc::JmapCause::UnknownMethod
+ .into_err()
+ .details(format!("{}/queryChanges", parser.ctx)))
}
},
filter: vec![],
diff --git a/crates/jmap-proto/src/method/search_snippet.rs b/crates/jmap-proto/src/method/search_snippet.rs
index e73cd514..203ad226 100644
--- a/crates/jmap-proto/src/method/search_snippet.rs
+++ b/crates/jmap-proto/src/method/search_snippet.rs
@@ -48,7 +48,7 @@ pub struct SearchSnippet {
}
impl JsonObjectParser for GetSearchSnippetRequest {
- fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
+ fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
where
Self: Sized,
{
diff --git a/crates/jmap-proto/src/method/set.rs b/crates/jmap-proto/src/method/set.rs
index 28421cad..4efa7bbd 100644
--- a/crates/jmap-proto/src/method/set.rs
+++ b/crates/jmap-proto/src/method/set.rs
@@ -13,7 +13,7 @@ use crate::{
set::{InvalidProperty, SetError},
},
object::{email_submission, mailbox, sieve, Object},
- parser::{json::Parser, Error, JsonObjectParser, Token},
+ parser::{json::Parser, JsonObjectParser, Token},
request::{
method::MethodObject,
reference::{MaybeReference, ResultReference},
@@ -99,7 +99,7 @@ pub struct SetResponse {
}
impl JsonObjectParser for SetRequest<RequestArguments> {
- fn parse(parser: &mut Parser) -> crate::parser::Result<Self>
+ fn parse(parser: &mut Parser) -> trc::Result<Self>
where
Self: Sized,
{
@@ -115,10 +115,9 @@ impl JsonObjectParser for SetRequest<RequestArguments> {
MethodObject::VacationResponse => RequestArguments::VacationResponse,
MethodObject::SieveScript => RequestArguments::SieveScript(Default::default()),
_ => {
- return Err(Error::Method(MethodError::UnknownMethod(format!(
- "{}/set",
- parser.ctx
- ))))
+ return Err(trc::JmapCause::UnknownMethod
+ .into_err()
+ .details(format!("{}/set", parser.ctx)))
}
},
account_id: Id::default(),
@@ -168,7 +167,7 @@ impl JsonObjectParser for SetRequest<RequestArguments> {
}
impl JsonObjectParser for Object<SetValue> {
- fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
+ fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
where
Self: Sized,
{
@@ -385,11 +384,7 @@ impl<T: Into<AnyId>> From<Vec<MaybeReference<T, String>>> for SetValue {
}
impl RequestPropertyParser for RequestArguments {
- fn parse(
- &mut self,
- parser: &mut Parser,
- property: RequestProperty,
- ) -> crate::parser::Result<bool> {
+ fn parse(&mut self, parser: &mut Parser, property: RequestProperty) -> trc::Result<bool> {
match self {
RequestArguments::Mailbox(args) => args.parse(parser, property),
RequestArguments::EmailSubmission(args) => args.parse(parser, property),
diff --git a/crates/jmap-proto/src/method/upload.rs b/crates/jmap-proto/src/method/upload.rs
index 25d037f9..e8be489d 100644
--- a/crates/jmap-proto/src/method/upload.rs
+++ b/crates/jmap-proto/src/method/upload.rs
@@ -64,7 +64,7 @@ pub struct BlobUploadResponseObject {
}
impl JsonObjectParser for BlobUploadRequest {
- fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
+ fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
where
Self: Sized,
{
@@ -96,7 +96,7 @@ impl JsonObjectParser for BlobUploadRequest {
}
impl JsonObjectParser for UploadObject {
- fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
+ fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
where
Self: Sized,
{
@@ -140,7 +140,7 @@ impl JsonObjectParser for UploadObject {
}
impl JsonObjectParser for DataSourceObject {
- fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
+ fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
where
Self: Sized,
{
diff --git a/crates/jmap-proto/src/method/validate.rs b/crates/jmap-proto/src/method/validate.rs
index 3dcbd405..19d05ea8 100644
--- a/crates/jmap-proto/src/method/validate.rs
+++ b/crates/jmap-proto/src/method/validate.rs
@@ -27,7 +27,7 @@ pub struct ValidateSieveScriptResponse {
}
impl JsonObjectParser for ValidateSieveScriptRequest {
- fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
+ fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
where
Self: Sized,
{
diff --git a/crates/jmap-proto/src/object/blob.rs b/crates/jmap-proto/src/object/blob.rs
index 0a44e4bf..d38b0664 100644
--- a/crates/jmap-proto/src/object/blob.rs
+++ b/crates/jmap-proto/src/object/blob.rs
@@ -20,7 +20,7 @@ impl RequestPropertyParser for GetArguments {
&mut self,
parser: &mut Parser,
property: RequestProperty,
- ) -> crate::parser::Result<bool> {
+ ) -> trc::Result<bool> {
match &property.hash[0] {
0x7465_7366_666f => {
self.offset = parser
diff --git a/crates/jmap-proto/src/object/email.rs b/crates/jmap-proto/src/object/email.rs
index 64661b17..28876fcd 100644
--- a/crates/jmap-proto/src/object/email.rs
+++ b/crates/jmap-proto/src/object/email.rs
@@ -29,7 +29,7 @@ impl RequestPropertyParser for GetArguments {
&mut self,
parser: &mut Parser,
property: RequestProperty,
- ) -> crate::parser::Result<bool> {
+ ) -> trc::Result<bool> {
match (&property.hash[0], &property.hash[1]) {
(0x7365_6974_7265_706f_7250_7964_6f62, _) => {
self.body_properties = <Option<Vec<Property>>>::parse(parser)?;
@@ -66,7 +66,7 @@ impl RequestPropertyParser for QueryArguments {
&mut self,
parser: &mut Parser,
property: RequestProperty,
- ) -> crate::parser::Result<bool> {
+ ) -> trc::Result<bool> {
if property.hash[0] == 0x0073_6461_6572_6854_6573_7061_6c6c_6f63 {
self.collapse_threads = parser
.next_token::<Ignore>()?
diff --git a/crates/jmap-proto/src/object/email_submission.rs b/crates/jmap-proto/src/object/email_submission.rs
index 7191a5e5..2f429748 100644
--- a/crates/jmap-proto/src/object/email_submission.rs
+++ b/crates/jmap-proto/src/object/email_submission.rs
@@ -25,7 +25,7 @@ impl RequestPropertyParser for SetArguments {
&mut self,
parser: &mut Parser,
property: RequestProperty,
- ) -> crate::parser::Result<bool> {
+ ) -> trc::Result<bool> {
if property.hash[0] == 0x4565_7461_6470_5573_7365_6363_7553_6e6f
&& property.hash[1] == 0x6c69_616d
{
diff --git a/crates/jmap-proto/src/object/mailbox.rs b/crates/jmap-proto/src/object/mailbox.rs
index ad3e8a4b..fd34573a 100644
--- a/crates/jmap-proto/src/object/mailbox.rs
+++ b/crates/jmap-proto/src/object/mailbox.rs
@@ -25,7 +25,7 @@ impl RequestPropertyParser for SetArguments {
&mut self,
parser: &mut Parser,
property: RequestProperty,
- ) -> crate::parser::Result<bool> {
+ ) -> trc::Result<bool> {
if property.hash[0] == 0x4565_766f_6d65_5279_6f72_7473_6544_6e6f
&& property.hash[1] == 0x0073_6c69_616d
{
@@ -44,7 +44,7 @@ impl RequestPropertyParser for QueryArguments {
&mut self,
parser: &mut Parser,
property: RequestProperty,
- ) -> crate::parser::Result<bool> {
+ ) -> trc::Result<bool> {
match &property.hash[0] {
0x6565_7254_7341_7472_6f73 => {
self.sort_as_tree = parser
diff --git a/crates/jmap-proto/src/object/mod.rs b/crates/jmap-proto/src/object/mod.rs
index 13c70b67..78157198 100644
--- a/crates/jmap-proto/src/object/mod.rs
+++ b/crates/jmap-proto/src/object/mod.rs
@@ -124,7 +124,7 @@ impl Serialize for Value {
impl Deserialize for Value {
fn deserialize(bytes: &[u8]) -> trc::Result<Self> {
Self::deserialize_from(&mut bytes.iter()).ok_or_else(|| {
- trc::Cause::DataCorruption
+ trc::StoreCause::DataCorruption
.caused_by(trc::location!())
.ctx(trc::Key::Value, bytes)
})
@@ -148,7 +148,7 @@ impl Serialize for &Object<Value> {
impl Deserialize for Object<Value> {
fn deserialize(bytes: &[u8]) -> trc::Result<Self> {
Object::deserialize_from(&mut bytes.iter()).ok_or_else(|| {
- trc::Cause::DataCorruption
+ trc::StoreCause::DataCorruption
.caused_by(trc::location!())
.ctx(trc::Key::Value, bytes)
})
diff --git a/crates/jmap-proto/src/object/sieve.rs b/crates/jmap-proto/src/object/sieve.rs
index 1f05a7f9..bca31205 100644
--- a/crates/jmap-proto/src/object/sieve.rs
+++ b/crates/jmap-proto/src/object/sieve.rs
@@ -21,7 +21,7 @@ impl RequestPropertyParser for SetArguments {
&mut self,
parser: &mut Parser,
property: RequestProperty,
- ) -> crate::parser::Result<bool> {
+ ) -> trc::Result<bool> {
if property.hash[0] == 0x7461_7669_7463_4173_7365_6363_7553_6e6f
&& property.hash[1] == 0x0074_7069_7263_5365
{
diff --git a/crates/jmap-proto/src/parser/base32.rs b/crates/jmap-proto/src/parser/base32.rs
index 597d6448..999b6980 100644
--- a/crates/jmap-proto/src/parser/base32.rs
+++ b/crates/jmap-proto/src/parser/base32.rs
@@ -6,7 +6,7 @@
use utils::codec::{base32_custom::BASE32_INVERSE, leb128::Leb128Iterator};
-use super::{json::Parser, Error};
+use super::json::Parser;
#[derive(Debug)]
pub struct JsonBase32Reader<'x, 'y> {
@@ -38,7 +38,7 @@ impl<'x, 'y> JsonBase32Reader<'x, 'y> {
}
}
- pub fn error(&mut self) -> Error {
+ pub fn error(&mut self) -> trc::Error {
self.bytes.error_value()
}
}
diff --git a/crates/jmap-proto/src/parser/impls.rs b/crates/jmap-proto/src/parser/impls.rs
index 677aaf34..6f984d09 100644
--- a/crates/jmap-proto/src/parser/impls.rs
+++ b/crates/jmap-proto/src/parser/impls.rs
@@ -14,7 +14,7 @@ use utils::map::{
use super::{json::Parser, Ignore, JsonObjectParser, Token};
impl JsonObjectParser for u64 {
- fn parse(parser: &mut Parser<'_>) -> super::Result<Self>
+ fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
where
Self: Sized,
{
@@ -36,7 +36,7 @@ impl JsonObjectParser for u64 {
}
impl JsonObjectParser for u128 {
- fn parse(parser: &mut Parser<'_>) -> super::Result<Self>
+ fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
where
Self: Sized,
{
@@ -58,7 +58,7 @@ impl JsonObjectParser for u128 {
}
impl JsonObjectParser for String {
- fn parse(parser: &mut Parser<'_>) -> super::Result<Self>
+ fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
where
Self: Sized,
{
@@ -177,7 +177,7 @@ impl JsonObjectParser for String {
}
impl<T: JsonObjectParser + Eq> JsonObjectParser for Vec<T> {
- fn parse(parser: &mut Parser<'_>) -> super::Result<Self>
+ fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
where
Self: Sized,
{
@@ -197,7 +197,7 @@ impl<T: JsonObjectParser + Eq> JsonObjectParser for Vec<T> {
}
impl<T: JsonObjectParser + Eq> JsonObjectParser for Option<Vec<T>> {
- fn parse(parser: &mut Parser<'_>) -> super::Result<Self>
+ fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
where
Self: Sized,
{
@@ -221,7 +221,7 @@ impl<T: JsonObjectParser + Eq> JsonObjectParser for Option<Vec<T>> {
}
impl<T: JsonObjectParser + Eq + BitmapItem> JsonObjectParser for Bitmap<T> {
- fn parse(parser: &mut Parser<'_>) -> super::Result<Self>
+ fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
where
Self: Sized,
{
@@ -245,7 +245,7 @@ impl<T: JsonObjectParser + Eq + BitmapItem> JsonObjectParser for Bitmap<T> {
}
impl<K: JsonObjectParser + Eq + Display, V: JsonObjectParser> JsonObjectParser for VecMap<K, V> {
- fn parse(parser: &mut Parser<'_>) -> super::Result<Self>
+ fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
where
Self: Sized,
{
@@ -263,7 +263,7 @@ impl<K: JsonObjectParser + Eq + Display, V: JsonObjectParser> JsonObjectParser f
impl<K: JsonObjectParser + Eq + Display, V: JsonObjectParser> JsonObjectParser
for Option<VecMap<K, V>>
{
- fn parse(parser: &mut Parser<'_>) -> super::Result<Self>
+ fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
where
Self: Sized,
{
@@ -284,7 +284,7 @@ impl<K: JsonObjectParser + Eq + Display, V: JsonObjectParser> JsonObjectParser
}
impl JsonObjectParser for bool {
- fn parse(parser: &mut Parser<'_>) -> super::Result<Self>
+ fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
where
Self: Sized,
{
@@ -297,7 +297,7 @@ impl JsonObjectParser for bool {
}
impl JsonObjectParser for Ignore {
- fn parse(parser: &mut Parser<'_>) -> super::Result<Self>
+ fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
where
Self: Sized,
{
diff --git a/crates/jmap-proto/src/parser/json.rs b/crates/jmap-proto/src/parser/json.rs
index ce790e6f..5d60aee8 100644
--- a/crates/jmap-proto/src/parser/json.rs
+++ b/crates/jmap-proto/src/parser/json.rs
@@ -6,9 +6,9 @@
use std::{fmt::Display, iter::Peekable, slice::Iter};
-use crate::{error::method::MethodError, request::method::MethodObject};
+use crate::request::method::MethodObject;
-use super::{Error, Ignore, JsonObjectParser, Token};
+use super::{Ignore, JsonObjectParser, Token};
const MAX_NESTED_LEVELS: u32 = 16;
@@ -40,25 +40,33 @@ impl<'x> Parser<'x> {
}
}
- pub fn error(&self, message: &str) -> Error {
- format!("{message} at position {}.", self.pos).into()
+ pub fn error(&self, message: &str) -> trc::Error {
+ trc::JmapCause::NotJSON
+ .into_err()
+ .details(format!("{message} at position {}.", self.pos))
}
- pub fn error_unterminated(&self) -> Error {
- format!("Unterminated string at position {pos}.", pos = self.pos).into()
+ pub fn error_unterminated(&self) -> trc::Error {
+ trc::JmapCause::NotJSON.into_err().details(format!(
+ "Unterminated string at position {pos}.",
+ pos = self.pos
+ ))
}
- pub fn error_utf8(&self) -> Error {
- format!("Invalid UTF-8 sequence at position {pos}.", pos = self.pos).into()
+ pub fn error_utf8(&self) -> trc::Error {
+ trc::JmapCause::NotJSON.into_err().details(format!(
+ "Invalid UTF-8 sequence at position {pos}.",
+ pos = self.pos
+ ))
}
- pub fn error_value(&mut self) -> Error {
+ pub fn error_value(&mut self) -> trc::Error {
if self.is_eof || self.skip_string() {
- Error::Method(MethodError::InvalidArguments(format!(
+ trc::JmapCause::InvalidArguments.into_err().details(format!(
"Invalid value {:?} at position {}.",
String::from_utf8_lossy(self.bytes[self.pos_marker..self.pos - 1].as_ref()),
self.pos
- )))
+ ))
} else {
self.error_unterminated()
}
@@ -76,7 +84,7 @@ impl<'x> Parser<'x> {
}
#[inline(always)]
- pub fn next_unescaped(&mut self) -> super::Result<Option<u8>> {
+ pub fn next_unescaped(&mut self) -> trc::Result<Option<u8>> {
match self.next_char() {
Some(b'"') => {
self.is_eof = true;
@@ -112,7 +120,7 @@ impl<'x> Parser<'x> {
false
}
- pub fn next_token<T: JsonObjectParser>(&mut self) -> super::Result<Token<T>> {
+ pub fn next_token<T: JsonObjectParser>(&mut self) -> trc::Result<Token<T>> {
let mut next_ch = self.next_ch.take().or_else(|| self.next_char());
while let Some(mut ch) = next_ch {
@@ -263,9 +271,7 @@ impl<'x> Parser<'x> {
Err(self.error("Unexpected EOF"))
}
- pub fn next_dict_key<T: JsonObjectParser + Display + Eq>(
- &mut self,
- ) -> super::Result<Option<T>> {
+ pub fn next_dict_key<T: JsonObjectParser + Display + Eq>(&mut self) -> trc::Result<Option<T>> {
loop {
match self.next_token::<T>()? {
Token::String(k) => {
@@ -281,11 +287,7 @@ impl<'x> Parser<'x> {
}
}
- pub fn skip_token(
- &mut self,
- start_depth_array: u32,
- start_depth_dict: u32,
- ) -> super::Result<()> {
+ pub fn skip_token(&mut self, start_depth_array: u32, start_depth_dict: u32) -> trc::Result<()> {
while {
self.next_token::<Ignore>()?;
start_depth_array != self.depth_array || start_depth_dict != self.depth_dict
diff --git a/crates/jmap-proto/src/parser/mod.rs b/crates/jmap-proto/src/parser/mod.rs
index a21133a2..56fca203 100644
--- a/crates/jmap-proto/src/parser/mod.rs
+++ b/crates/jmap-proto/src/parser/mod.rs
@@ -6,8 +6,6 @@
use std::fmt::Display;
-use crate::error::{method::MethodError, request::RequestError};
-
use self::json::Parser;
pub mod base32;
@@ -32,31 +30,23 @@ pub enum Token<T> {
impl<T: PartialEq> Eq for Token<T> {}
pub trait JsonObjectParser {
- fn parse(parser: &mut Parser<'_>) -> Result<Self>
+ fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
where
Self: Sized;
}
-pub type Result<T> = std::result::Result<T, Error>;
-
-#[derive(Debug)]
-pub enum Error {
- Request(RequestError),
- Method(MethodError),
-}
-
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Ignore {}
impl<T: Eq> Token<T> {
- pub fn unwrap_string(self, property: &str) -> Result<T> {
+ pub fn unwrap_string(self, property: &str) -> trc::Result<T> {
match self {
Token::String(s) => Ok(s),
token => Err(token.error(property, "string")),
}
}
- pub fn unwrap_string_or_null(self, property: &str) -> Result<Option<T>> {
+ pub fn unwrap_string_or_null(self, property: &str) -> trc::Result<Option<T>> {
match self {
Token::String(s) => Ok(Some(s)),
Token::Null => Ok(None),
@@ -64,14 +54,14 @@ impl<T: Eq> Token<T> {
}
}
- pub fn unwrap_bool(self, property: &str) -> Result<bool> {
+ pub fn unwrap_bool(self, property: &str) -> trc::Result<bool> {
match self {
Token::Boolean(v) => Ok(v),
token => Err(token.error(property, "boolean")),
}
}
- pub fn unwrap_bool_or_null(self, property: &str) -> Result<Option<bool>> {
+ pub fn unwrap_bool_or_null(self, property: &str) -> trc::Result<Option<bool>> {
match self {
Token::Boolean(v) => Ok(Some(v)),
Token::Null => Ok(None),
@@ -79,7 +69,7 @@ impl<T: Eq> Token<T> {
}
}
- pub fn unwrap_usize_or_null(self, property: &str) -> Result<Option<usize>> {
+ pub fn unwrap_usize_or_null(self, property: &str) -> trc::Result<Option<usize>> {
match self {
Token::Integer(v) if v >= 0 => Ok(Some(v as usize)),
Token::Float(v) if v >= 0.0 => Ok(Some(v as usize)),
@@ -88,7 +78,7 @@ impl<T: Eq> Token<T> {
}
}
- pub fn unwrap_uint_or_null(self, property: &str) -> Result<Option<u64>> {
+ pub fn unwrap_uint_or_null(self, property: &str) -> trc::Result<Option<u64>> {
match self {
Token::Integer(v) if v >= 0 => Ok(Some(v as u64)),
Token::Float(v) if v >= 0.0 => Ok(Some(v as u64)),
@@ -97,7 +87,7 @@ impl<T: Eq> Token<T> {
}
}
- pub fn unwrap_int_or_null(self, property: &str) -> Result<Option<i64>> {
+ pub fn unwrap_int_or_null(self, property: &str) -> trc::Result<Option<i64>> {
match self {
Token::Integer(v) => Ok(Some(v)),
Token::Float(v) => Ok(Some(v as i64)),
@@ -106,7 +96,7 @@ impl<T: Eq> Token<T> {
}
}
- pub fn unwrap_ints_or_null(self, property: &str) -> Result<Option<i32>> {
+ pub fn unwrap_ints_or_null(self, property: &str) -> trc::Result<Option<i32>> {
match self {
Token::Integer(v) => Ok(Some(v as i32)),
Token::Float(v) => Ok(Some(v as i32)),
@@ -115,7 +105,7 @@ impl<T: Eq> Token<T> {
}
}
- pub fn assert(self, token: Token<T>) -> Result<()> {
+ pub fn assert(self, token: Token<T>) -> trc::Result<()> {
if self == token {
Ok(())
} else {
@@ -123,22 +113,22 @@ impl<T: Eq> Token<T> {
}
}
- pub fn assert_jmap(self, token: Token<T>) -> Result<()> {
+ pub fn assert_jmap(self, token: Token<T>) -> trc::Result<()> {
if self == token {
Ok(())
} else {
- Err(Error::Request(RequestError::not_request(format!(
+ Err(trc::JmapCause::NotRequest.into_err().details(format!(
"Invalid JMAP request: expected '{token}', got '{self}'."
- ))))
+ )))
}
}
- pub fn error(&self, property: &str, expected: &str) -> Error {
- Error::Method(MethodError::InvalidArguments(if !property.is_empty() {
+ pub fn error(&self, property: &str, expected: &str) -> trc::Error {
+ trc::JmapCause::InvalidArguments.into_err().details(if !property.is_empty() {
format!("Invalid argument for '{property:?}': expected '{expected}', got '{self}'.",)
} else {
format!("Invalid argument: expected '{expected}', got '{self}'.")
- }))
+ })
}
}
@@ -148,18 +138,6 @@ impl Display for Ignore {
}
}
-impl From<String> for Error {
- fn from(s: String) -> Self {
- Error::Request(RequestError::not_json(&s))
- }
-}
-
-impl From<&str> for Error {
- fn from(s: &str) -> Self {
- Error::Request(RequestError::not_json(s))
- }
-}
-
impl<T> Display for Token<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
diff --git a/crates/jmap-proto/src/request/capability.rs b/crates/jmap-proto/src/request/capability.rs
index 7ec457ef..79d8e58e 100644
--- a/crates/jmap-proto/src/request/capability.rs
+++ b/crates/jmap-proto/src/request/capability.rs
@@ -7,8 +7,7 @@
use utils::map::vec_map::VecMap;
use crate::{
- error::request::RequestError,
- parser::{json::Parser, Error, JsonObjectParser},
+ parser::{json::Parser, JsonObjectParser},
response::serialize::serialize_hex,
types::{id::Id, type_state::DataType},
};
@@ -315,7 +314,7 @@ impl WebSocketCapabilities {
}
impl JsonObjectParser for Capability {
- fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
+ fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
where
Self: Sized,
{
@@ -343,18 +342,19 @@ impl JsonObjectParser for Capability {
0x0061_746f_7571 => Ok(Capability::Quota),
_ => Err(parser.error_capability()),
},
- Err(Error::Method(_)) => Err(parser.error_capability()),
- Err(err @ Error::Request(_)) => Err(err),
+ Err(err) if err.is_jmap_method_error() => Err(parser.error_capability()),
+ Err(err) => Err(err),
}
}
}
impl<'x> Parser<'x> {
- fn error_capability(&mut self) -> Error {
+ fn error_capability(&mut self) -> trc::Error {
if self.is_eof || self.skip_string() {
- Error::Request(RequestError::unknown_capability(&String::from_utf8_lossy(
- self.bytes[self.pos_marker..self.pos - 1].as_ref(),
- )))
+ trc::JmapCause::UnknownCapability.into_err().details(
+ String::from_utf8_lossy(self.bytes[self.pos_marker..self.pos - 1].as_ref())
+ .into_owned(),
+ )
} else {
self.error_unterminated()
}
diff --git a/crates/jmap-proto/src/request/echo.rs b/crates/jmap-proto/src/request/echo.rs
index b82a4d86..561bf088 100644
--- a/crates/jmap-proto/src/request/echo.rs
+++ b/crates/jmap-proto/src/request/echo.rs
@@ -15,7 +15,7 @@ pub struct Echo {
}
impl JsonObjectParser for Echo {
- fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
+ fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
where
Self: Sized,
{
diff --git a/crates/jmap-proto/src/request/method.rs b/crates/jmap-proto/src/request/method.rs
index b9b51594..be2f4f84 100644
--- a/crates/jmap-proto/src/request/method.rs
+++ b/crates/jmap-proto/src/request/method.rs
@@ -48,7 +48,7 @@ pub enum MethodFunction {
}
impl JsonObjectParser for MethodName {
- fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
+ fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
where
Self: Sized,
{
diff --git a/crates/jmap-proto/src/request/mod.rs b/crates/jmap-proto/src/request/mod.rs
index 79468a9d..a223b528 100644
--- a/crates/jmap-proto/src/request/mod.rs
+++ b/crates/jmap-proto/src/request/mod.rs
@@ -77,7 +77,7 @@ pub enum RequestMethod {
}
impl JsonObjectParser for RequestProperty {
- fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
+ fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
where
Self: Sized,
{
@@ -116,5 +116,5 @@ pub trait RequestPropertyParser {
&mut self,
parser: &mut Parser,
property: RequestProperty,
- ) -> crate::parser::Result<bool>;
+ ) -> trc::Result<bool>;
}
diff --git a/crates/jmap-proto/src/request/parser.rs b/crates/jmap-proto/src/request/parser.rs
index 74306337..b25327f5 100644
--- a/crates/jmap-proto/src/request/parser.rs
+++ b/crates/jmap-proto/src/request/parser.rs
@@ -7,10 +7,6 @@
use std::collections::HashMap;
use crate::{
- error::{
- method::MethodError,
- request::{RequestError, RequestLimitError},
- },
method::{
changes::ChangesRequest,
copy::{CopyBlobRequest, CopyRequest},
@@ -25,7 +21,7 @@ use crate::{
upload::BlobUploadRequest,
validate::ValidateSieveScriptRequest,
},
- parser::{json::Parser, Error, Ignore, JsonObjectParser, Token},
+ parser::{json::Parser, Ignore, JsonObjectParser, Token},
types::any_id::AnyId,
};
@@ -37,7 +33,7 @@ use super::{
};
impl Request {
- pub fn parse(json: &[u8], max_calls: usize, max_size: usize) -> Result<Self, RequestError> {
+ pub fn parse(json: &[u8], max_calls: usize, max_size: usize) -> trc::Result<Self> {
if json.len() <= max_size {
let mut request = Request {
using: 0,
@@ -54,10 +50,12 @@ impl Request {
if found_valid_keys {
Ok(request)
} else {
- Err(RequestError::not_request("Invalid JMAP request"))
+ Err(trc::JmapCause::NotRequest
+ .into_err()
+ .details("Invalid JMAP request"))
}
} else {
- Err(RequestError::limit(RequestLimitError::SizeRequest))
+ Err(trc::LimitCause::SizeRequest.into_err())
}
}
@@ -66,7 +64,7 @@ impl Request {
parser: &mut Parser,
max_calls: usize,
key: u128,
- ) -> Result<bool, RequestError> {
+ ) -> trc::Result<bool> {
match key {
0x0067_6e69_7375 => {
parser.next_token::<Ignore>()?.assert(Token::ArrayStart)?;
@@ -77,7 +75,7 @@ impl Request {
}
Token::Comma => (),
Token::ArrayEnd => break,
- token => return Err(token.error("capability", &token.to_string()).into()),
+ token => return Err(token.error("capability", &token.to_string())),
}
}
Ok(true)
@@ -92,20 +90,28 @@ impl Request {
Token::Comma => continue,
Token::ArrayEnd => break,
_ => {
- return Err(RequestError::not_request("Invalid JMAP request"));
+ return Err(trc::JmapCause::NotRequest
+ .into_err()
+ .details("Invalid JMAP request"));
}
};
if self.method_calls.len() < max_calls {
let method_name = match parser.next_token::<MethodName>() {
Ok(Token::String(method)) => method,
Ok(_) => {
- return Err(RequestError::not_request("Invalid JMAP request"));
+ return Err(trc::JmapCause::NotRequest
+ .into_err()
+ .details("Invalid JMAP request"));
}
- Err(Error::Method(MethodError::InvalidArguments(_))) => {
+ Err(err)
+ if err.matches(trc::Cause::Jmap(
+ trc::JmapCause::InvalidArguments,
+ )) =>
+ {
MethodName::error()
}
Err(err) => {
- return Err(err.into());
+ return Err(err);
}
};
parser.next_token::<Ignore>()?.assert_jmap(Token::Comma)?;
@@ -169,19 +175,19 @@ impl Request {
(MethodFunction::Echo, MethodObject::Core) => {
Echo::parse(parser).map(RequestMethod::Echo)
}
- _ => Err(Error::Method(MethodError::UnknownMethod(
- method_name.to_string(),
- ))),
+ _ => Err(trc::JmapCause::UnknownMethod
+ .into_err()
+ .details(method_name.to_string())),
};
let method = match method {
Ok(method) => method,
- Err(Error::Method(err)) => {
+ Err(err) if !err.is_jmap_method_error() => {
parser.skip_token(start_depth_array, start_depth_dict)?;
- RequestMethod::Error(err.into())
+ RequestMethod::Error(err)
}
Err(err) => {
- return Err(err.into());
+ return Err(err);
}
};
@@ -196,7 +202,7 @@ impl Request {
name: method_name,
});
} else {
- return Err(RequestError::limit(RequestLimitError::CallsIn));
+ return Err(trc::LimitCause::CallsIn.into_err());
}
}
Ok(true)
@@ -221,15 +227,6 @@ impl Request {
}
}
-impl From<Error> for RequestError {
- fn from(value: Error) -> Self {
- match value {
- Error::Request(err) => err,
- Error::Method(err) => RequestError::not_request(err.to_string()),
- }
- }
-}
-
#[cfg(test)]
mod tests {
use crate::request::Request;
diff --git a/crates/jmap-proto/src/request/reference.rs b/crates/jmap-proto/src/request/reference.rs
index 62ca66ba..cd61b941 100644
--- a/crates/jmap-proto/src/request/reference.rs
+++ b/crates/jmap-proto/src/request/reference.rs
@@ -7,8 +7,7 @@
use std::fmt::Display;
use crate::{
- error::method::MethodError,
- parser::{json::Parser, Error, JsonObjectParser, Token},
+ parser::{json::Parser, JsonObjectParser, Token},
types::{id::Id, pointer::JSONPointer},
};
@@ -45,7 +44,7 @@ impl<V, R> MaybeReference<V, R> {
}
impl JsonObjectParser for ResultReference {
- fn parse(parser: &mut Parser) -> crate::parser::Result<Self>
+ fn parse(parser: &mut Parser) -> trc::Result<Self>
where
Self: Sized,
{
@@ -81,15 +80,15 @@ impl JsonObjectParser for ResultReference {
path,
})
} else {
- Err(Error::Method(MethodError::InvalidResultReference(
- "Missing required fields".into(),
- )))
+ Err(trc::JmapCause::InvalidResultReference
+ .into_err()
+ .details("Missing required fields"))
}
}
}
impl<T: JsonObjectParser> JsonObjectParser for MaybeReference<T, String> {
- fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
+ fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
where
Self: Sized,
{
diff --git a/crates/jmap-proto/src/request/websocket.rs b/crates/jmap-proto/src/request/websocket.rs
index c2a42851..b1fcbe4b 100644
--- a/crates/jmap-proto/src/request/websocket.rs
+++ b/crates/jmap-proto/src/request/websocket.rs
@@ -8,7 +8,7 @@ use std::{borrow::Cow, collections::HashMap};
use crate::{
error::request::{RequestError, RequestErrorType, RequestLimitError},
- parser::{json::Parser, Error, JsonObjectParser, Token},
+ parser::{json::Parser, JsonObjectParser, Token},
request::Call,
response::{serialize::serialize_hex, Response, ResponseMethod},
types::{any_id::AnyId, id::Id, state::State, type_state::DataType},
@@ -108,11 +108,7 @@ enum MessageType {
}
impl WebSocketMessage {
- pub fn parse(
- json: &[u8],
- max_calls: usize,
- max_size: usize,
- ) -> Result<Self, WebSocketRequestError> {
+ pub fn parse(json: &[u8], max_calls: usize, max_size: usize) -> trc::Result<Self> {
if json.len() <= max_size {
let mut message_type = MessageType::None;
let mut request = WebSocketRequest {
@@ -174,10 +170,12 @@ impl WebSocketMessage {
MessageType::PushDisable if !found_request_keys && !found_push_keys => {
Ok(WebSocketMessage::PushDisable)
}
- _ => Err(RequestError::not_request("Invalid WebSocket JMAP request").into()),
+ _ => Err(trc::JmapCause::NotRequest
+ .into_err()
+ .details("Invalid WebSocket JMAP request")),
}
} else {
- Err(RequestError::limit(RequestLimitError::SizeRequest).into())
+ Err(trc::LimitCause::SizeRequest.into_err())
}
}
}
@@ -205,12 +203,6 @@ impl From<RequestError> for WebSocketRequestError {
}
}
-impl From<Error> for WebSocketRequestError {
- fn from(value: Error) -> Self {
- RequestError::from(value).into()
- }
-}
-
impl WebSocketResponse {
pub fn from_response(response: Response, request_id: Option<String>) -> Self {
Self {
diff --git a/crates/jmap-proto/src/types/acl.rs b/crates/jmap-proto/src/types/acl.rs
index 8b898a4b..f6a7efcd 100644
--- a/crates/jmap-proto/src/types/acl.rs
+++ b/crates/jmap-proto/src/types/acl.rs
@@ -27,7 +27,7 @@ pub enum Acl {
}
impl JsonObjectParser for Acl {
- fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
+ fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
where
Self: Sized,
{
diff --git a/crates/jmap-proto/src/types/any_id.rs b/crates/jmap-proto/src/types/any_id.rs
index 62ec2397..3adb36f0 100644
--- a/crates/jmap-proto/src/types/any_id.rs
+++ b/crates/jmap-proto/src/types/any_id.rs
@@ -96,7 +96,7 @@ impl From<&AnyId> for Value {
}
impl JsonObjectParser for AnyId {
- fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
+ fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
where
Self: Sized,
{
diff --git a/crates/jmap-proto/src/types/blob.rs b/crates/jmap-proto/src/types/blob.rs
index 7c920823..86b577da 100644
--- a/crates/jmap-proto/src/types/blob.rs
+++ b/crates/jmap-proto/src/types/blob.rs
@@ -38,7 +38,7 @@ pub struct BlobSection {
}
impl JsonObjectParser for BlobId {
- fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
+ fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
where
Self: Sized,
{
diff --git a/crates/jmap-proto/src/types/date.rs b/crates/jmap-proto/src/types/date.rs
index 1c72a8d3..923bc3f7 100644
--- a/crates/jmap-proto/src/types/date.rs
+++ b/crates/jmap-proto/src/types/date.rs
@@ -24,7 +24,7 @@ pub struct UTCDate {
}
impl JsonObjectParser for UTCDate {
- fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
+ fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
where
Self: Sized,
{
diff --git a/crates/jmap-proto/src/types/id.rs b/crates/jmap-proto/src/types/id.rs
index e820ca95..da3498d1 100644
--- a/crates/jmap-proto/src/types/id.rs
+++ b/crates/jmap-proto/src/types/id.rs
@@ -24,7 +24,7 @@ impl Default for Id {
}
impl JsonObjectParser for Id {
- fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
+ fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
where
Self: Sized,
{
diff --git a/crates/jmap-proto/src/types/keyword.rs b/crates/jmap-proto/src/types/keyword.rs
index 737ea3e0..701baf81 100644
--- a/crates/jmap-proto/src/types/keyword.rs
+++ b/crates/jmap-proto/src/types/keyword.rs
@@ -61,7 +61,7 @@ pub enum Keyword {
}
impl JsonObjectParser for Keyword {
- fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
+ fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
where
Self: Sized,
{
diff --git a/crates/jmap-proto/src/types/mod.rs b/crates/jmap-proto/src/types/mod.rs
index 4315d0ad..199bc8ac 100644
--- a/crates/jmap-proto/src/types/mod.rs
+++ b/crates/jmap-proto/src/types/mod.rs
@@ -29,7 +29,7 @@ pub enum MaybeUnparsable<V> {
}
impl<V: JsonObjectParser> JsonObjectParser for MaybeUnparsable<V> {
- fn parse(parser: &mut Parser) -> crate::parser::Result<Self> {
+ fn parse(parser: &mut Parser) -> trc::Result<Self> {
match V::parse(parser) {
Ok(value) => Ok(MaybeUnparsable::Value(value)),
Err(_) if parser.is_eof || parser.skip_string() => Ok(MaybeUnparsable::ParseError(
diff --git a/crates/jmap-proto/src/types/pointer.rs b/crates/jmap-proto/src/types/pointer.rs
index 2d6731b3..2062e6f5 100644
--- a/crates/jmap-proto/src/types/pointer.rs
+++ b/crates/jmap-proto/src/types/pointer.rs
@@ -26,7 +26,7 @@ enum TokenType {
}
impl JsonObjectParser for JSONPointer {
- fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
+ fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
where
Self: Sized,
{
diff --git a/crates/jmap-proto/src/types/property.rs b/crates/jmap-proto/src/types/property.rs
index 40214d59..bc76da48 100644
--- a/crates/jmap-proto/src/types/property.rs
+++ b/crates/jmap-proto/src/types/property.rs
@@ -10,7 +10,7 @@ use mail_parser::HeaderName;
use serde::Serialize;
use store::write::{DeserializeFrom, SerializeInto};
-use crate::parser::{json::Parser, Error, JsonObjectParser};
+use crate::parser::{json::Parser, JsonObjectParser};
use super::{acl::Acl, id::Id, keyword::Keyword, value::Value};
@@ -153,7 +153,7 @@ pub trait IntoProperty: Eq + Display {
}
impl JsonObjectParser for Property {
- fn parse(parser: &mut Parser) -> crate::parser::Result<Self> {
+ fn parse(parser: &mut Parser) -> trc::Result<Self> {
let mut first_char = 0;
let mut hash = 0;
let mut shift = 0;
@@ -190,7 +190,7 @@ impl JsonObjectParser for Property {
}
impl JsonObjectParser for SetProperty {
- fn parse(parser: &mut Parser) -> crate::parser::Result<Self> {
+ fn parse(parser: &mut Parser) -> trc::Result<Self> {
let mut first_char = 0;
let mut hash = 0;
let mut shift = 0;
@@ -251,7 +251,7 @@ impl JsonObjectParser for SetProperty {
Ok(id) => {
patch.push(Value::Id(id));
}
- Err(Error::Method(_)) => {
+ Err(err) if err.is_jmap_method_error() => {
property = parser.invalid_property()?;
}
Err(err) => {
@@ -262,7 +262,7 @@ impl JsonObjectParser for SetProperty {
Ok(keyword) => {
patch.push(Value::Keyword(keyword));
}
- Err(Error::Method(_)) => {
+ Err(err) if err.is_jmap_method_error() => {
property = parser.invalid_property()?;
}
Err(err) => {
@@ -290,7 +290,7 @@ impl JsonObjectParser for SetProperty {
Ok(acl) => {
patch.push(Value::UnsignedInt(acl as u64));
}
- Err(Error::Method(_)) => {
+ Err(err) if err.is_jmap_method_error() => {
property = parser.invalid_property()?;
}
Err(err) => {
@@ -469,7 +469,7 @@ fn parse_property(first_char: u8, hash: u128) -> Option<Property> {
})
}
-fn parse_header_property(parser: &mut Parser) -> crate::parser::Result<Property> {
+fn parse_header_property(parser: &mut Parser) -> trc::Result<Property> {
let hdr_start_pos = parser.pos;
let mut has_next = false;
@@ -553,7 +553,7 @@ fn parse_sub_property(
parser: &mut Parser,
first_char: u8,
parent_hash: u128,
-) -> crate::parser::Result<Property> {
+) -> trc::Result<Property> {
let mut hash = 0;
let mut shift = 0;
@@ -585,7 +585,7 @@ fn parse_sub_property(
}
impl JsonObjectParser for ObjectProperty {
- fn parse(parser: &mut Parser) -> crate::parser::Result<Self> {
+ fn parse(parser: &mut Parser) -> trc::Result<Self> {
let mut first_char = 0;
let mut hash = 0;
let mut shift = 0;
@@ -707,7 +707,7 @@ impl JsonObjectParser for ObjectProperty {
}
impl<'x> Parser<'x> {
- fn invalid_property(&mut self) -> crate::parser::Result<Property> {
+ fn invalid_property(&mut self) -> trc::Result<Property> {
if self.is_eof || self.skip_string() {
Ok(Property::_T(
String::from_utf8_lossy(self.bytes[self.pos_marker..self.pos - 1].as_ref())
diff --git a/crates/jmap-proto/src/types/state.rs b/crates/jmap-proto/src/types/state.rs
index 20da85dc..422c0e97 100644
--- a/crates/jmap-proto/src/types/state.rs
+++ b/crates/jmap-proto/src/types/state.rs
@@ -72,7 +72,7 @@ impl From<Option<ChangeId>> for State {
}
impl JsonObjectParser for State {
- fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
+ fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
where
Self: Sized,
{
diff --git a/crates/jmap-proto/src/types/type_state.rs b/crates/jmap-proto/src/types/type_state.rs
index 44bd7aaf..563dc14d 100644
--- a/crates/jmap-proto/src/types/type_state.rs
+++ b/crates/jmap-proto/src/types/type_state.rs
@@ -85,7 +85,7 @@ impl From<DataType> for u64 {
}
impl JsonObjectParser for DataType {
- fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
+ fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
where
Self: Sized,
{
diff --git a/crates/jmap-proto/src/types/value.rs b/crates/jmap-proto/src/types/value.rs
index 90a2952d..65c83d88 100644
--- a/crates/jmap-proto/src/types/value.rs
+++ b/crates/jmap-proto/src/types/value.rs
@@ -78,7 +78,7 @@ impl Value {
pub fn parse<K: JsonObjectParser + IntoProperty, V: JsonObjectParser + IntoValue>(
token: Token<V>,
parser: &mut Parser<'_>,
- ) -> crate::parser::Result<Self> {
+ ) -> trc::Result<Self> {
Ok(match token {
Token::String(v) => v.into_value(),
Token::DictStart => {
@@ -114,7 +114,7 @@ impl Value {
pub fn from_property(
parser: &mut Parser<'_>,
property: &Property,
- ) -> crate::parser::Result<Self> {
+ ) -> trc::Result<Self> {
match &property {
Property::BlobId => Ok(parser
.next_token::<BlobId>()?
@@ -327,7 +327,7 @@ impl Value {
}
impl<T: JsonObjectParser + Display + Eq> JsonObjectParser for SetValueMap<T> {
- fn parse(parser: &mut Parser<'_>) -> crate::parser::Result<Self>
+ fn parse(parser: &mut Parser<'_>) -> trc::Result<Self>
where
Self: Sized,
{
diff --git a/crates/jmap/src/api/autoconfig.rs b/crates/jmap/src/api/autoconfig.rs
index c280e3f4..d18470d5 100644
--- a/crates/jmap/src/api/autoconfig.rs
+++ b/crates/jmap/src/api/autoconfig.rs
@@ -8,7 +8,6 @@ use std::fmt::Write;
use common::manager::webadmin::Resource;
use directory::QueryBy;
-use jmap_proto::error::request::RequestError;
use quick_xml::events::Event;
use quick_xml::Reader;
use utils::url_params::UrlParams;
@@ -18,22 +17,15 @@ use crate::{api::http::ToHttpResponse, JMAP};
use super::{HttpRequest, HttpResponse};
impl JMAP {
- pub async fn handle_autoconfig_request(&self, req: &HttpRequest) -> HttpResponse {
+ pub async fn handle_autoconfig_request(&self, req: &HttpRequest) -> trc::Result<HttpResponse> {
// Obtain parameters
let params = UrlParams::new(req.uri().query());
let emailaddress = params
.get("emailaddress")
.unwrap_or_default()
.to_lowercase();
- let (account_name, server_name, domain) =
- match self.autoconfig_parameters(&emailaddress).await {
- Ok(result) => result,
- Err(err) => return err.into_http_response(),
- };
- let services = match self.core.storage.config.get_services().await {
- Ok(services) => services,
- Err(err) => return err.into_http_response(),
- };
+ let (account_name, server_name, domain) = self.autoconfig_parameters(&emailaddress).await?;
+ let services = self.core.storage.config.get_services().await?;
// Build XML response
let mut config = String::with_capacity(1024);
@@ -75,30 +67,27 @@ impl JMAP {
);
config.push_str("</clientConfig>\n");
- Resource {
+ Ok(Resource {
content_type: "application/xml; charset=utf-8",
contents: config.into_bytes(),
}
- .into_http_response()
+ .into_http_response())
}
- pub async fn handle_autodiscover_request(&self, body: Option<Vec<u8>>) -> HttpResponse {
+ pub async fn handle_autodiscover_request(
+ &self,
+ body: Option<Vec<u8>>,
+ ) -> trc::Result<HttpResponse> {
// Obtain parameters
- let emailaddress = match parse_autodiscover_request(body.as_deref().unwrap_or_default()) {
- Ok(emailaddress) => emailaddress,
- Err(err) => {
- return RequestError::blank(400, "Failed to parse autodiscover request", err)
- .into_http_response()
- }
- };
- let (account_name, server_name, _) = match self.autoconfig_parameters(&emailaddress).await {
- Ok(result) => result,
- Err(err) => return err.into_http_response(),
- };
- let services = match self.core.storage.config.get_services().await {
- Ok(services) => services,
- Err(err) => return err.into_http_response(),
- };
+ let emailaddress = parse_autodiscover_request(body.as_deref().unwrap_or_default())
+ .map_err(|err| {
+ trc::ResourceCause::BadParameters
+ .into_err()
+ .details("Failed to parse autodiscover request")
+ .ctx(trc::Key::Reason, err)
+ })?;
+ let (account_name, server_name, _) = self.autoconfig_parameters(&emailaddress).await?;
+ let services = self.core.storage.config.get_services().await?;
// Build XML response
let mut config = String::with_capacity(1024);
@@ -158,36 +147,35 @@ impl JMAP {
let _ = writeln!(&mut config, "\t</Response>");
let _ = writeln!(&mut config, "</Autodiscover>");
- Resource {
+ Ok(Resource {
content_type: "application/xml; charset=utf-8",
contents: config.into_bytes(),
}
- .into_http_response()
+ .into_http_response())
}
async fn autoconfig_parameters<'x>(
&self,
emailaddress: &'x str,
- ) -> Result<(String, String, &'x str), RequestError> {
- let domain = if let Some((_, domain)) = emailaddress.rsplit_once('@') {
- domain
- } else {
- return Err(RequestError::invalid_parameters());
- };
+ ) -> trc::Result<(String, String, &'x str)> {
+ let (_, domain) = emailaddress.rsplit_once('@').ok_or_else(|| {
+ trc::ResourceCause::BadParameters
+ .into_err()
+ .details("Missing domain in email address")
+ })?;
// Obtain server name
- let server_name = if let Ok(Some(server_name)) = self
+ let server_name = self
.core
.storage
.config
.get("lookup.default.hostname")
- .await
- {
- server_name
- } else {
- tracing::error!("Autoconfig request failed: Server name not configured");
- return Err(RequestError::internal_server_error());
- };
+ .await?
+ .ok_or_else(|| {
+ trc::Cause::Configuration
+ .into_err()
+ .details("Server name not configured")
+ })?;
// Find the account name by e-mail address
let mut account_name = emailaddress.to_string();
diff --git a/crates/jmap/src/api/event_source.rs b/crates/jmap/src/api/event_source.rs
index e1076a33..66d1d5cb 100644
--- a/crates/jmap/src/api/event_source.rs
+++ b/crates/jmap/src/api/event_source.rs
@@ -14,12 +14,12 @@ use hyper::{
body::{Bytes, Frame},
header, StatusCode,
};
-use jmap_proto::{error::request::RequestError, types::type_state::DataType};
+use jmap_proto::types::type_state::DataType;
use utils::map::bitmap::Bitmap;
use crate::{auth::AccessToken, JMAP, LONG_SLUMBER};
-use super::{http::ToHttpResponse, HttpRequest, HttpResponse, StateChangeResponse};
+use super::{HttpRequest, HttpResponse, StateChangeResponse};
struct Ping {
interval: Duration,
@@ -32,7 +32,7 @@ impl JMAP {
&self,
req: HttpRequest,
access_token: Arc<AccessToken>,
- ) -> HttpResponse {
+ ) -> trc::Result<HttpResponse> {
// Parse query
let mut ping = 0;
let mut types = Bitmap::default();
@@ -49,7 +49,7 @@ impl JMAP {
} else if let Ok(type_state) = DataType::try_from(type_state) {
types.insert(type_state);
} else {
- return RequestError::invalid_parameters().into_http_response();
+ return Err(trc::ResourceCause::BadParameters.into_err());
}
}
}
@@ -58,13 +58,13 @@ impl JMAP {
close_after_state = true;
}
"no" => {}
- _ => return RequestError::invalid_parameters().into_http_response(),
+ _ => return Err(trc::ResourceCause::BadParameters.into_err()),
},
"ping" => match value.parse::<u32>() {
Ok(value) => {
ping = value;
}
- Err(_) => return RequestError::invalid_parameters().into_http_response(),
+ Err(_) => return Err(trc::ResourceCause::BadParameters.into_err()),
},
_ => {}
}
@@ -92,17 +92,11 @@ impl JMAP {
let throttle = self.core.jmap.event_source_throttle;
// Register with state manager
- let mut change_rx = if let Ok(change_rx) = self
+ let mut change_rx = self
.subscribe_state_manager(access_token.primary_id(), types)
- .await
- {
- let todo = "return error";
- change_rx
- } else {
- return RequestError::internal_server_error().into_http_response();
- };
+ .await?;
- hyper::Response::builder()
+ Ok(hyper::Response::builder()
.status(StatusCode::OK)
.header(header::CONTENT_TYPE, "text/event-stream")
.header(header::CACHE_CONTROL, "no-store")
@@ -160,6 +154,6 @@ impl JMAP {
};
}
})))
- .unwrap()
+ .unwrap())
}
}
diff --git a/crates/jmap/src/api/http.rs b/crates/jmap/src/api/http.rs
index 0e279dbc..de1c94dd 100644
--- a/crates/jmap/src/api/http.rs
+++ b/crates/jmap/src/api/http.rs
@@ -22,7 +22,7 @@ use hyper::{
};
use hyper_util::rt::TokioIo;
use jmap_proto::{
- error::request::{RequestError, RequestLimitError},
+ error::request::RequestError,
request::{capability::Session, Request},
response::Response,
types::{blob::BlobId, id::Id},
@@ -51,28 +51,19 @@ impl JMAP {
&self,
mut req: HttpRequest,
session: HttpSessionData,
- ) -> HttpResponse {
+ ) -> trc::Result<HttpResponse> {
let mut path = req.uri().path().split('/');
path.next();
match path.next().unwrap_or_default() {
"jmap" => {
- // Authenticate request
- let (_in_flight, access_token) =
- match self.authenticate_headers(&req, session.remote_ip).await {
- Ok(session) => session,
- Err(err) => {
- return if req.method() != Method::OPTIONS {
- err.into_http_response()
- } else {
- StatusCode::NO_CONTENT.into_http_response()
- }
- }
- };
-
match (path.next().unwrap_or_default(), req.method()) {
("", &Method::POST) => {
- return match fetch_body(
+ // Authenticate request
+ let (_in_flight, access_token) =
+ self.authenticate_headers(&req, session.remote_ip).await?;
+
+ let request = fetch_body(
&mut req,
if !access_token.is_super_user() {
self.core.jmap.upload_max_size
@@ -81,7 +72,7 @@ impl JMAP {
},
)
.await
- .ok_or_else(|| RequestError::limit(RequestLimitError::SizeRequest))
+ .ok_or_else(|| trc::LimitCause::SizeRequest.into_err())
.and_then(|bytes| {
//let c = println!("<- {}", String::from_utf8_lossy(&bytes));
@@ -90,34 +81,25 @@ impl JMAP {
self.core.jmap.request_max_calls,
self.core.jmap.request_max_size,
)
- }) {
- Ok(request) => {
- match self
- .handle_request(request, access_token, &session.instance)
- .await
- {
- Ok(response) => {
- /*let c = println!(
- "-> {}",
- serde_json::to_string_pretty(&response).unwrap()
- );*/
-
- response.into_http_response()
- }
- Err(err) => err.into_http_response(),
- }
- }
- Err(err) => err.into_http_response(),
- };
+ })?;
+
+ return Ok(self
+ .handle_request(request, access_token, &session.instance)
+ .await
+ .into_http_response());
}
("download", &Method::GET) => {
+ // Authenticate request
+ let (_in_flight, access_token) =
+ self.authenticate_headers(&req, session.remote_ip).await?;
+
if let (Some(_), Some(blob_id), Some(name)) = (
path.next().and_then(|p| Id::from_bytes(p.as_bytes())),
path.next().and_then(BlobId::from_base32),
path.next(),
) {
- return match self.blob_download(&blob_id, &access_token).await {
- Ok(Some(blob)) => DownloadResponse {
+ return match self.blob_download(&blob_id, &access_token).await? {
+ Some(blob) => Ok(DownloadResponse {
filename: name.to_string(),
content_type: req
.uri()
@@ -130,15 +112,16 @@ impl JMAP {
.unwrap_or("application/octet-stream".to_string()),
blob,
}
- .into_http_response(),
- Ok(None) => RequestError::not_found().into_http_response(),
- Err(_) => {
- RequestError::internal_server_error().into_http_response()
- }
+ .into_http_response()),
+ None => Err(trc::ResourceCause::NotFound.into_err()),
};
}
}
("upload", &Method::POST) => {
+ // Authenticate request
+ let (_in_flight, access_token) =
+ self.authenticate_headers(&req, session.remote_ip).await?;
+
if let Some(account_id) =
path.next().and_then(|p| Id::from_bytes(p.as_bytes()))
{
@@ -152,32 +135,34 @@ impl JMAP {
)
.await
{
- Some(bytes) => {
- match self
- .blob_upload(
- account_id,
- req.headers()
- .get(CONTENT_TYPE)
- .and_then(|h| h.to_str().ok())
- .unwrap_or("application/octet-stream"),
- &bytes,
- access_token,
- )
- .await
- {
- Ok(response) => response.into_http_response(),
- Err(err) => err.into_http_response(),
- }
- }
- None => RequestError::limit(RequestLimitError::SizeUpload)
- .into_http_response(),
+ Some(bytes) => Ok(self
+ .blob_upload(
+ account_id,
+ req.headers()
+ .get(CONTENT_TYPE)
+ .and_then(|h| h.to_str().ok())
+ .unwrap_or("application/octet-stream"),
+ &bytes,
+ access_token,
+ )
+ .await?
+ .into_http_response()),
+ None => Err(trc::LimitCause::SizeUpload.into_err()),
};
}
}
("eventsource", &Method::GET) => {
- return self.handle_event_source(req, access_token).await
+ // Authenticate request
+ let (_in_flight, access_token) =
+ self.authenticate_headers(&req, session.remote_ip).await?;
+
+ return self.handle_event_source(req, access_token).await;
}
("ws", &Method::GET) => {
+ // Authenticate request
+ let (_in_flight, access_token) =
+ self.authenticate_headers(&req, session.remote_ip).await?;
+
return self
.upgrade_websocket_connection(
req,
@@ -187,7 +172,7 @@ impl JMAP {
.await;
}
(_, &Method::OPTIONS) => {
- return StatusCode::NO_CONTENT.into_http_response();
+ return Ok(StatusCode::NO_CONTENT.into_http_response());
}
_ => (),
}
@@ -196,31 +181,24 @@ impl JMAP {
("jmap", &Method::GET) => {
// Authenticate request
let (_in_flight, access_token) =
- match self.authenticate_headers(&req, session.remote_ip).await {
- Ok(session) => session,
- Err(err) => return err.into_http_response(),
- };
+ self.authenticate_headers(&req, session.remote_ip).await?;
- return match self
+ return Ok(self
.handle_session_resource(
session.resolve_url(&self.core).await,
access_token,
)
- .await
- {
- Ok(session) => session.into_http_response(),
- Err(err) => err.into_http_response(),
- };
+ .await?
+ .into_http_response());
}
("oauth-authorization-server", &Method::GET) => {
// Limit anonymous requests
- return match self.is_anonymous_allowed(&session.remote_ip).await {
- Ok(_) => JsonResponse::new(OAuthMetadata::new(
- session.resolve_url(&self.core).await,
- ))
- .into_http_response(),
- Err(err) => err.into_http_response(),
- };
+ self.is_anonymous_allowed(&session.remote_ip).await?;
+
+ return Ok(JsonResponse::new(OAuthMetadata::new(
+ session.resolve_url(&self.core).await,
+ ))
+ .into_http_response());
}
("acme-challenge", &Method::GET) if self.core.has_acme_http_providers() => {
if let Some(token) = path.next() {
@@ -229,27 +207,26 @@ impl JMAP {
.storage
.lookup
.key_get::<String>(format!("acme:{token}").into_bytes())
- .await
+ .await?
{
- Ok(Some(proof)) => Resource {
+ Some(proof) => Ok(Resource {
content_type: "text/plain",
contents: proof.into_bytes(),
}
- .into_http_response(),
- Ok(None) => RequestError::not_found().into_http_response(),
- Err(err) => err.into_http_response(),
+ .into_http_response()),
+ None => Err(trc::ResourceCause::NotFound.into_err()),
};
}
}
("mta-sts.txt", &Method::GET) => {
if let Some(policy) = self.core.build_mta_sts_policy() {
- return Resource {
+ return Ok(Resource {
content_type: "text/plain",
contents: policy.to_string().into_bytes(),
}
- .into_http_response();
+ .into_http_response());
} else {
- return RequestError::not_found().into_http_response();
+ return Err(trc::ResourceCause::NotFound.into_err());
}
}
("mail-v1.xml", &Method::GET) => {
@@ -263,46 +240,40 @@ impl JMAP {
}
}
(_, &Method::OPTIONS) => {
- return StatusCode::NO_CONTENT.into_http_response();
+ return Ok(StatusCode::NO_CONTENT.into_http_response());
}
_ => (),
},
"auth" => match (path.next().unwrap_or_default(), req.method()) {
("device", &Method::POST) => {
- return match self.is_anonymous_allowed(&session.remote_ip).await {
- Ok(_) => {
- self.handle_device_auth(&mut req, session.resolve_url(&self.core).await)
- .await
- }
- Err(err) => err.into_http_response(),
- }
+ self.is_anonymous_allowed(&session.remote_ip).await?;
+
+ return self
+ .handle_device_auth(&mut req, session.resolve_url(&self.core).await)
+ .await;
}
("token", &Method::POST) => {
- return match self.is_anonymous_allowed(&session.remote_ip).await {
- Ok(_) => self.handle_token_request(&mut req).await,
- Err(err) => err.into_http_response(),
- }
+ self.is_anonymous_allowed(&session.remote_ip).await?;
+
+ return self.handle_token_request(&mut req).await;
}
(_, &Method::OPTIONS) => {
- return StatusCode::NO_CONTENT.into_http_response();
+ return Ok(StatusCode::NO_CONTENT.into_http_response());
}
_ => (),
},
"api" => {
// Allow CORS preflight requests
if req.method() == Method::OPTIONS {
- return StatusCode::NO_CONTENT.into_http_response();
+ return Ok(StatusCode::NO_CONTENT.into_http_response());
}
// Authenticate user
- return match self.authenticate_headers(&req, session.remote_ip).await {
- Ok((_, access_token)) => {
- let body = fetch_body(&mut req, 1024 * 1024).await;
- self.handle_api_manage_request(&req, body, access_token)
- .await
- }
- Err(err) => err.into_http_response(),
- };
+ let (_, access_token) = self.authenticate_headers(&req, session.remote_ip).await?;
+ let body = fetch_body(&mut req, 1024 * 1024).await;
+ return self
+ .handle_api_manage_request(&req, body, access_token)
+ .await;
}
"mail" => {
if req.method() == Method::GET
@@ -321,43 +292,45 @@ impl JMAP {
}
}
"robots.txt" => {
- return Resource {
+ return Ok(Resource {
content_type: "text/plain",
contents: b"User-agent: *\nDisallow: /\n".to_vec(),
}
- .into_http_response();
+ .into_http_response());
}
"healthz" => match path.next().unwrap_or_default() {
"live" => {
- return StatusCode::OK.into_http_response();
+ return Ok(StatusCode::OK.into_http_response());
}
"ready" => {
- return {
+ return Ok({
if !self.core.storage.data.is_none() {
StatusCode::OK
} else {
StatusCode::SERVICE_UNAVAILABLE
}
}
- .into_http_response();
+ .into_http_response());
}
_ => (),
},
_ => {
let path = req.uri().path();
- return match self
+ let resource = self
.inner
.webadmin
.get(path.strip_prefix('/').unwrap_or(path))
- .await
- {
- Ok(resource) if !resource.is_empty() => resource.into_http_response(),
- Err(err) => err.into_http_response(),
- _ => RequestError::not_found().into_http_response(),
+ .await?;
+
+ return if !resource.is_empty() {
+ Ok(resource.into_http_response())
+ } else {
+ Err(trc::ResourceCause::NotFound.into_err())
};
}
}
- RequestError::not_found().into_http_response()
+
+ Err(trc::ResourceCause::NotFound.into_err())
}
}
@@ -403,7 +376,7 @@ impl JmapInstance {
};
// Parse HTTP request
- let mut response = jmap
+ let mut response = match jmap
.parse_http_request(
req,
HttpSessionData {
@@ -415,7 +388,19 @@ impl JmapInstance {
is_tls,
},
)
- .await;
+ .await
+ {
+ Ok(response) => response,
+ Err(err) => {
+ tracing::error!(
+ parent: &span,
+ event = "error",
+ context = "http",
+ reason = %err,
+ );
+ err.into_http_response()
+ }
+ };
// Add custom headers
if !jmap.core.jmap.http_headers.is_empty() {
@@ -534,7 +519,7 @@ impl ToHttpResponse for trc::Error {
}
}
-impl ToHttpResponse for std::io::Error {
+/*impl ToHttpResponse for std::io::Error {
fn into_http_response(self) -> HttpResponse {
tracing::error!(context = "i/o", error = %self, "I/O error");
@@ -551,7 +536,7 @@ impl ToHttpResponse for serde_json::Error {
)
.into_http_response()
}
-}
+}*/
impl<T: serde::Serialize> JsonResponse<T> {
pub fn new(inner: T) -> Self {
diff --git a/crates/jmap/src/api/management/dkim.rs b/crates/jmap/src/api/management/dkim.rs
index cda3af68..cba78295 100644
--- a/crates/jmap/src/api/management/dkim.rs
+++ b/crates/jmap/src/api/management/dkim.rs
@@ -7,8 +7,8 @@
use std::str::FromStr;
use common::config::smtp::auth::simple_pem_parse;
+use directory::backend::internal::manage;
use hyper::Method;
-use jmap_proto::error::request::RequestError;
use mail_auth::{
common::crypto::{Ed25519Key, RsaKey, Sha256},
dkim::generate::DkimKeyPair,
@@ -22,10 +22,7 @@ use serde_json::json;
use store::write::now;
use crate::{
- api::{
- http::ToHttpResponse, management::ManagementApiError, HttpRequest, HttpResponse,
- JsonResponse,
- },
+ api::{http::ToHttpResponse, HttpRequest, HttpResponse, JsonResponse},
JMAP,
};
@@ -51,22 +48,21 @@ impl JMAP {
req: &HttpRequest,
path: Vec<&str>,
body: Option<Vec<u8>>,
- ) -> HttpResponse {
+ ) -> trc::Result<HttpResponse> {
match *req.method() {
Method::GET => self.handle_get_public_key(path).await,
Method::POST => self.handle_create_signature(body).await,
- _ => RequestError::not_found().into_http_response(),
+ _ => Err(trc::ResourceCause::NotFound.into_err()),
}
}
- async fn handle_get_public_key(&self, path: Vec<&str>) -> HttpResponse {
+ async fn handle_get_public_key(&self, path: Vec<&str>) -> trc::Result<HttpResponse> {
let signature_id = match path.get(1) {
Some(signature_id) => decode_path_element(signature_id),
None => {
- return RequestError::not_found().into_http_response();
+ return Err(trc::ResourceCause::NotFound.into_err());
}
};
- let todo = "bubble up error and log them";
let (pk, algo) = match (
self.core
@@ -82,27 +78,26 @@ impl JMAP {
.map(|algo| algo.and_then(|algo| algo.parse::<Algorithm>().ok())),
) {
(Ok(Some(pk)), Ok(Some(algorithm))) => (pk, algorithm),
- (Err(err), _) | (_, Err(err)) => return err.into_http_response(),
- _ => return RequestError::not_found().into_http_response(),
+ (Err(err), _) | (_, Err(err)) => return Err(err.caused_by(trc::location!())),
+ _ => return Err(trc::ResourceCause::NotFound.into_err()),
};
match obtain_dkim_public_key(algo, &pk) {
- Ok(data) => JsonResponse::new(json!({
+ Ok(data) => Ok(JsonResponse::new(json!({
"data": data,
}))
- .into_http_response(),
- Err(err) => ManagementApiError::Other {
- details: err.into(),
- }
- .into_http_response(),
+ .into_http_response()),
+ Err(details) => Err(manage::error(details, None::<u32>)),
}
}
- async fn handle_create_signature(&self, body: Option<Vec<u8>>) -> HttpResponse {
+ async fn handle_create_signature(&self, body: Option<Vec<u8>>) -> trc::Result<HttpResponse> {
let request =
match serde_json::from_slice::<DkimSignature>(body.as_deref().unwrap_or_default()) {
Ok(request) => request,
- Err(err) => return err.into_http_response(),
+ Err(err) => {
+ return Err(trc::Cause::Resource(trc::ResourceCause::BadParameters).reason(err))
+ }
};
let algo_str = match request.algorithm {
@@ -127,35 +122,27 @@ impl JMAP {
});
// Make sure the signature does not exist already
- match self
+ if let Some(value) = self
.core
.storage
.config
.get(&format!("signature.{id}.private-key"))
- .await
+ .await?
{
- Ok(None) => (),
- Ok(Some(value)) => {
- return ManagementApiError::FieldAlreadyExists {
- field: format!("signature.{id}.private-key").into(),
- value: value.into(),
- }
- .into_http_response();
- }
- Err(err) => return err.into_http_response(),
+ return Err(manage::err_exists(
+ format!("signature.{id}.private-key"),
+ value,
+ ));
}
// Create signature
- match self
- .create_dkim_key(request.algorithm, id, request.domain, selector)
- .await
- {
- Ok(_) => JsonResponse::new(json!({
- "data": (),
- }))
- .into_http_response(),
- Err(err) => err.into_http_response(),
- }
+ self.create_dkim_key(request.algorithm, id, request.domain, selector)
+ .await?;
+
+ Ok(JsonResponse::new(json!({
+ "data": (),
+ }))
+ .into_http_response())
}
async fn create_dkim_key(
@@ -177,7 +164,10 @@ impl JMAP {
Algorithm::Rsa => DkimKeyPair::generate_rsa(2048),
Algorithm::Ed25519 => DkimKeyPair::generate_ed25519(),
}
- .map_err(|err| trc::Cause::Crypto.reason(err).caused_by(trc::location!()))?
+ .map_err(|err| {
+ manage::error("Failed to generate key", err.to_string().into())
+ .caused_by(trc::location!())
+ })?
.private_key(),
)
.unwrap_or_default()
diff --git a/crates/jmap/src/api/management/domain.rs b/crates/jmap/src/api/management/domain.rs
index 4ff4d247..e7a007c5 100644
--- a/crates/jmap/src/api/management/domain.rs
+++ b/crates/jmap/src/api/management/domain.rs
@@ -7,7 +7,6 @@
use directory::backend::internal::manage::ManageDirectory;
use hyper::Method;
-use jmap_proto::error::request::RequestError;
use serde::{Deserialize, Serialize};
use serde_json::json;
use sha1::Digest;
@@ -35,7 +34,11 @@ struct DnsRecord {
}
impl JMAP {
- pub async fn handle_manage_domain(&self, req: &HttpRequest, path: Vec<&str>) -> HttpResponse {
+ pub async fn handle_manage_domain(
+ &self,
+ req: &HttpRequest,
+ path: Vec<&str>,
+ ) -> trc::Result<HttpResponse> {
match (path.get(1), req.method()) {
(None, &Method::GET) => {
// List domains
@@ -44,82 +47,78 @@ impl JMAP {
let page: usize = params.parse("page").unwrap_or(0);
let limit: usize = params.parse("limit").unwrap_or(0);
- match self.core.storage.data.list_domains(filter).await {
- Ok(domains) => {
- let (total, domains) = if limit > 0 {
- let offset = page.saturating_sub(1) * limit;
- (
- domains.len(),
- domains.into_iter().skip(offset).take(limit).collect(),
- )
- } else {
- (domains.len(), domains)
- };
+ let domains = self.core.storage.data.list_domains(filter).await?;
+ let (total, domains) = if limit > 0 {
+ let offset = page.saturating_sub(1) * limit;
+ (
+ domains.len(),
+ domains.into_iter().skip(offset).take(limit).collect(),
+ )
+ } else {
+ (domains.len(), domains)
+ };
- JsonResponse::new(json!({
- "data": {
- "items": domains,
- "total": total,
- },
- }))
- .into_http_response()
- }
- Err(err) => err.into_http_response(),
- }
+ Ok(JsonResponse::new(json!({
+ "data": {
+ "items": domains,
+ "total": total,
+ },
+ }))
+ .into_http_response())
}
(Some(domain), &Method::GET) => {
// Obtain DNS records
let domain = decode_path_element(domain);
- match self.build_dns_records(domain.as_ref()).await {
- Ok(records) => JsonResponse::new(json!({
- "data": records,
- }))
- .into_http_response(),
- Err(err) => err.into_http_response(),
- }
+ Ok(JsonResponse::new(json!({
+ "data": self.build_dns_records(domain.as_ref()).await?,
+ }))
+ .into_http_response())
}
(Some(domain), &Method::POST) => {
// Create domain
let domain = decode_path_element(domain);
- match self.core.storage.data.create_domain(domain.as_ref()).await {
- Ok(_) => {
- // Set default domain name if missing
- if matches!(
- self.core.storage.config.get("lookup.default.domain").await,
- Ok(None)
- ) {
- if let Err(err) = self
- .core
- .storage
- .config
- .set([("lookup.default.domain", domain.as_ref())])
- .await
- {
- tracing::error!("Failed to set default domain name: {}", err);
- }
- }
-
- JsonResponse::new(json!({
- "data": (),
- }))
- .into_http_response()
- }
- Err(err) => err.into_http_response(),
+ self.core
+ .storage
+ .data
+ .create_domain(domain.as_ref())
+ .await?;
+ // Set default domain name if missing
+ if self
+ .core
+ .storage
+ .config
+ .get("lookup.default.domain")
+ .await?
+ .is_none()
+ {
+ self.core
+ .storage
+ .config
+ .set([("lookup.default.domain", domain.as_ref())])
+ .await?;
}
+
+ Ok(JsonResponse::new(json!({
+ "data": (),
+ }))
+ .into_http_response())
}
(Some(domain), &Method::DELETE) => {
// Delete domain
let domain = decode_path_element(domain);
- match self.core.storage.data.delete_domain(domain.as_ref()).await {
- Ok(_) => JsonResponse::new(json!({
- "data": (),
- }))
- .into_http_response(),
- Err(err) => err.into_http_response(),
- }
+ self.core
+ .storage
+ .data
+ .delete_domain(domain.as_ref())
+ .await?;
+
+ Ok(JsonResponse::new(json!({
+ "data": (),
+ }))
+ .into_http_response())
}
- _ => RequestError::not_found().into_http_response(),
+ _ => Err(trc::ResourceCause::NotFound.into_err()),
}
}
diff --git a/crates/jmap/src/api/management/enterprise.rs b/crates/jmap/src/api/management/enterprise.rs
index 1931c70b..7ba9c2fb 100644
--- a/crates/jmap/src/api/management/enterprise.rs
+++ b/crates/jmap/src/api/management/enterprise.rs
@@ -14,10 +14,11 @@ use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
use common::enterprise::undelete::DeletedBlob;
use directory::backend::internal::manage::ManageDirectory;
use hyper::Method;
-use jmap_proto::{error::request::RequestError, types::collection::Collection};
+use jmap_proto::types::collection::Collection;
use mail_parser::{DateTime, MessageParser};
use serde_json::json;
use store::write::{BatchBuilder, BlobOp, ValueClass};
+use trc::AddContext;
use utils::{url_params::UrlParams, BlobHash};
use crate::{
@@ -53,10 +54,10 @@ impl JMAP {
req: &HttpRequest,
path: Vec<&str>,
body: Option<Vec<u8>>,
- ) -> HttpResponse {
+ ) -> trc::Result<HttpResponse> {
match path.get(1).copied().unwrap_or_default() {
"undelete" => self.handle_undelete_api_request(req, path, body).await,
- _ => RequestError::not_found().into_http_response(),
+ _ => Err(trc::ResourceCause::NotFound.into_err()),
}
}
@@ -65,72 +66,76 @@ impl JMAP {
req: &HttpRequest,
path: Vec<&str>,
body: Option<Vec<u8>>,
- ) -> HttpResponse {
+ ) -> trc::Result<HttpResponse> {
match (path.get(2).copied(), req.method()) {
(Some(account_name), &Method::GET) => {
- match self.core.storage.data.get_account_id(account_name).await {
- Ok(Some(account_id)) => match self.core.list_deleted(account_id).await {
- Ok(mut deleted) => {
- let params = UrlParams::new(req.uri().query());
- let limit = params.parse::<usize>("limit").unwrap_or_default();
- let mut offset = params
- .parse::<usize>("page")
- .unwrap_or_default()
- .saturating_sub(1)
- * limit;
+ let account_id = self
+ .core
+ .storage
+ .data
+ .get_account_id(account_name)
+ .await?
+ .ok_or_else(|| trc::ResourceCause::NotFound.into_err())?;
+ let mut deleted = self.core.list_deleted(account_id).await?;
- // Sort ascending by deleted_at
- let total = deleted.len();
- deleted.sort_by(|a, b| a.deleted_at.cmp(&b.deleted_at));
- let mut results =
- Vec::with_capacity(if limit > 0 { limit } else { total });
+ let params = UrlParams::new(req.uri().query());
+ let limit = params.parse::<usize>("limit").unwrap_or_default();
+ let mut offset = params
+ .parse::<usize>("page")
+ .unwrap_or_default()
+ .saturating_sub(1)
+ * limit;
- for blob in deleted {
- if offset == 0 {
- results.push(DeletedBlob {
- hash: URL_SAFE_NO_PAD.encode(blob.hash.as_slice()),
- size: blob.size,
- deleted_at: DateTime::from_timestamp(
- blob.deleted_at as i64,
- )
- .to_rfc3339(),
- expires_at: DateTime::from_timestamp(
- blob.expires_at as i64,
- )
- .to_rfc3339(),
- collection: Collection::from(blob.collection).to_string(),
- });
- if results.len() == limit {
- break;
- }
- } else {
- offset -= 1;
- }
- }
+ // Sort ascending by deleted_at
+ let total = deleted.len();
+ deleted.sort_by(|a, b| a.deleted_at.cmp(&b.deleted_at));
+ let mut results = Vec::with_capacity(if limit > 0 { limit } else { total });
- JsonResponse::new(json!({
- "data":{
- "items": results,
- "total": total,
- },
- }))
- .into_http_response()
+ for blob in deleted {
+ if offset == 0 {
+ results.push(DeletedBlob {
+ hash: URL_SAFE_NO_PAD.encode(blob.hash.as_slice()),
+ size: blob.size,
+ deleted_at: DateTime::from_timestamp(blob.deleted_at as i64)
+ .to_rfc3339(),
+ expires_at: DateTime::from_timestamp(blob.expires_at as i64)
+ .to_rfc3339(),
+ collection: Collection::from(blob.collection).to_string(),
+ });
+ if results.len() == limit {
+ break;
}
- Err(err) => err.into_http_response(),
- },
- Ok(None) => RequestError::not_found().into_http_response(),
- Err(err) => err.into_http_response(),
+ } else {
+ offset -= 1;
+ }
}
+
+ Ok(JsonResponse::new(json!({
+ "data":{
+ "items": results,
+ "total": total,
+ },
+ }))
+ .into_http_response())
}
(Some(account_name), &Method::POST) => {
- match self.core.storage.data.get_account_id(account_name).await {
- Ok(Some(account_id)) => {
- match serde_json::from_slice::<Vec<UndeleteRequest<String, String, String>>>(
- body.as_deref().unwrap_or_default(),
- )
- .ok()
- .and_then(|request| {
- request.into_iter().map(|request| {
+ let account_id = self
+ .core
+ .storage
+ .data
+ .get_account_id(account_name)
+ .await?
+ .ok_or_else(|| trc::ResourceCause::NotFound.into_err())?;
+
+ let requests =
+ serde_json::from_slice::<Vec<UndeleteRequest<String, String, String>>>(
+ body.as_deref().unwrap_or_default(),
+ )
+ .ok()
+ .and_then(|request| {
+ request
+ .into_iter()
+ .map(|request| {
UndeleteRequest {
hash: BlobHash::try_from_hash_slice(
URL_SAFE_NO_PAD
@@ -139,99 +144,100 @@ impl JMAP {
.as_slice(),
)
.ok()?,
- collection: Collection::from_str(
- request.collection.as_str(),
- )
- .ok()?,
+ collection: Collection::from_str(request.collection.as_str())
+ .ok()?,
time: DateTime::parse_rfc3339(request.time.as_str())?
.to_timestamp(),
- cancel_deletion: if let Some(cancel_deletion) = request.cancel_deletion {
+ cancel_deletion: if let Some(cancel_deletion) =
+ request.cancel_deletion
+ {
DateTime::parse_rfc3339(cancel_deletion.as_str())?
- .to_timestamp().into()
+ .to_timestamp()
+ .into()
} else {
None
- }
+ },
}
.into()
- }).collect::<Option<Vec<_>>>()
- }) {
- Some(requests) => {
- let mut results = Vec::with_capacity(requests.len());
- let mut batch = BatchBuilder::new();
- batch.with_account_id(account_id);
- for request in requests {
- match request.collection {
- Collection::Email => {
- match self.get_blob(&request.hash, 0..usize::MAX).await
- {
- Ok(Some(bytes)) => {
- match self
- .email_ingest(IngestEmail {
- raw_message: &bytes,
- message: MessageParser::new().parse(&bytes),
- account_id,
- account_quota: 0,
- mailbox_ids: vec![INBOX_ID],
- keywords: vec![],
- received_at: (request.time as u64).into(),
- source: IngestSource::Smtp,
- encrypt: false,
- })
- .await
- {
- Ok(_) => {
- results.push(UndeleteResponse::Success);
- if let Some(cancel_deletion) = request.cancel_deletion {
- batch.clear(ValueClass::Blob(BlobOp::Reserve { hash: request.hash, until: cancel_deletion as u64 }));
- }
- },
- Err(mut err) if err.matches(trc::Cause::Ingest) => {
- results.push(UndeleteResponse::Error { reason: err.take_value(trc::Key::Reason)
- .and_then(|v| v.into_string())
- .unwrap().into_owned() });
- }
- Err(_) => {
- return RequestError::internal_server_error().into_http_response();
- },
- }
- }
- Ok(None) => {
- results.push(UndeleteResponse::NotFound);
- },
- Err(_) => {
- return RequestError::internal_server_error().into_http_response();
- },
+ })
+ .collect::<Option<Vec<_>>>()
+ })
+ .ok_or_else(|| trc::ResourceCause::BadParameters.into_err())?;
+
+ let mut results = Vec::with_capacity(requests.len());
+ let mut batch = BatchBuilder::new();
+ batch.with_account_id(account_id);
+ for request in requests {
+ match request.collection {
+ Collection::Email => {
+ match self.get_blob(&request.hash, 0..usize::MAX).await? {
+ Some(bytes) => {
+ match self
+ .email_ingest(IngestEmail {
+ raw_message: &bytes,
+ message: MessageParser::new().parse(&bytes),
+ account_id,
+ account_quota: 0,
+ mailbox_ids: vec![INBOX_ID],
+ keywords: vec![],
+ received_at: (request.time as u64).into(),
+ source: IngestSource::Smtp,
+ encrypt: false,
+ })
+ .await
+ {
+ Ok(_) => {
+ results.push(UndeleteResponse::Success);
+ if let Some(cancel_deletion) = request.cancel_deletion {
+ batch.clear(ValueClass::Blob(BlobOp::Reserve {
+ hash: request.hash,
+ until: cancel_deletion as u64,
+ }));
}
}
- _ => {
+ Err(mut err) if err.matches(trc::Cause::Ingest) => {
results.push(UndeleteResponse::Error {
- reason: "Unsupported collection".to_string(),
+ reason: err
+ .take_value(trc::Key::Reason)
+ .and_then(|v| v.into_string())
+ .unwrap()
+ .into_owned(),
});
}
+ Err(err) => {
+ return Err(err.caused_by(trc::location!()));
+ }
}
}
-
- // Commit batch
- if !batch.is_empty() {
- match self.core.storage.data.write(batch.build()).await {
- Ok(_) => (),
- Err(err) => return err.into_http_response(),
- }
+ None => {
+ results.push(UndeleteResponse::NotFound);
}
-
- JsonResponse::new(json!({
- "data": results,
- }))
- .into_http_response()
- },
- None => RequestError::invalid_parameters().into_http_response(),
+ }
+ }
+ _ => {
+ results.push(UndeleteResponse::Error {
+ reason: "Unsupported collection".to_string(),
+ });
}
}
- Ok(None) => RequestError::not_found().into_http_response(),
- Err(err) => err.into_http_response(),
}
+
+ // Commit batch
+ if !batch.is_empty() {
+ self.core
+ .storage
+ .data
+ .write(batch.build())
+ .await
+ .caused_by(trc::location!())?;
+ }
+
+ Ok(JsonResponse::new(json!({
+ "data": results,
+ }))
+ .into_http_response())
}
- _ => RequestError::not_found().into_http_response(),
+ _ => Err(trc::ResourceCause::NotFound.into_err()),
}
}
}
diff --git a/crates/jmap/src/api/management/log.rs b/crates/jmap/src/api/management/log.rs
index 0e029bc0..557b8cd8 100644
--- a/crates/jmap/src/api/management/log.rs
+++ b/crates/jmap/src/api/management/log.rs
@@ -5,6 +5,7 @@ use std::{
};
use chrono::DateTime;
+use directory::backend::internal::manage;
use rev_lines::RevLines;
use serde::Serialize;
use serde_json::json;
@@ -16,8 +17,6 @@ use crate::{
JMAP,
};
-use super::ManagementApiError;
-
#[derive(Serialize)]
struct LogEntry {
timestamp: String,
@@ -26,18 +25,15 @@ struct LogEntry {
}
impl JMAP {
- pub async fn handle_view_logs(&self, req: &HttpRequest) -> HttpResponse {
+ pub async fn handle_view_logs(&self, req: &HttpRequest) -> trc::Result<HttpResponse> {
// Obtain log file path
- let path = match self.core.storage.config.get("tracer.log.path").await {
- Ok(Some(path)) => path,
- Ok(None) => {
- return ManagementApiError::Unsupported {
- details: "Tracer log path not configured".into(),
- }
- .into_http_response()
- }
- Err(err) => return err.into_http_response(),
- };
+ let path = self
+ .core
+ .storage
+ .config
+ .get("tracer.log.path")
+ .await?
+ .ok_or_else(|| manage::unsupported("Tracer log path not configured"))?;
let params = UrlParams::new(req.uri().query());
let filter = params.get("filter").unwrap_or_default().to_string();
@@ -51,25 +47,23 @@ impl JMAP {
let _ = tx.send(read_log_files(path, &filter, offset, limit));
});
- match rx.await {
- Ok(result) => match result {
- Ok((total, items)) => JsonResponse::new(json!({
- "data": {
- "items": items,
- "total": total,
- },
- }))
- .into_http_response(),
- Err(err) => err.into_http_response(),
+ let (total, items) = rx
+ .await
+ .map_err(|err| trc::Cause::Thread.reason(err).caused_by(trc::location!()))?
+ .map_err(|err| {
+ trc::ManageCause::Error
+ .reason(err)
+ .details("Failed to read log files")
+ .caused_by(trc::location!())
+ })?;
+
+ Ok(JsonResponse::new(json!({
+ "data": {
+ "items": items,
+ "total": total,
},
- Err(_) => {
- tracing::warn!(context = "view_logs", event = "error", "Thread join error");
- ManagementApiError::Other {
- details: "Thread join error".into(),
- }
- .into_http_response()
- }
- }
+ }))
+ .into_http_response())
}
}
diff --git a/crates/jmap/src/api/management/mod.rs b/crates/jmap/src/api/management/mod.rs
index 9297b2b4..a3e8d9fe 100644
--- a/crates/jmap/src/api/management/mod.rs
+++ b/crates/jmap/src/api/management/mod.rs
@@ -19,11 +19,11 @@ pub mod stores;
use std::{borrow::Cow, sync::Arc};
+use directory::backend::internal::manage;
use hyper::Method;
-use jmap_proto::error::request::RequestError;
use serde::Serialize;
-use super::{http::ToHttpResponse, HttpRequest, HttpResponse, JsonResponse};
+use super::{HttpRequest, HttpResponse};
use crate::{auth::AccessToken, JMAP};
#[derive(Serialize)]
@@ -57,7 +57,7 @@ impl JMAP {
req: &HttpRequest,
body: Option<Vec<u8>>,
access_token: Arc<AccessToken>,
- ) -> HttpResponse {
+ ) -> trc::Result<HttpResponse> {
let path = req.uri().path().split('/').skip(2).collect::<Vec<_>>();
let is_superuser = access_token.is_super_user();
@@ -76,10 +76,7 @@ impl JMAP {
}
"sieve" if is_superuser => self.handle_run_sieve(req, path, body).await,
"restart" if is_superuser && req.method() == Method::GET => {
- ManagementApiError::Unsupported {
- details: "Restart is not yet supported".into(),
- }
- .into_http_response()
+ Err(manage::unsupported("Restart is not yet supported"))
}
"oauth" => self.handle_oauth_api_request(access_token, body).await,
"account" => match (path.get(1).copied().unwrap_or_default(), req.method()) {
@@ -89,7 +86,7 @@ impl JMAP {
("auth", &Method::POST) => {
self.handle_account_auth_post(req, access_token, body).await
}
- _ => RequestError::not_found().into_http_response(),
+ _ => Err(trc::ResourceCause::NotFound.into_err()),
},
// SPDX-SnippetBegin
@@ -109,34 +106,13 @@ impl JMAP {
if self.core.is_enterprise_edition() {
self.handle_enterprise_api_request(req, path, body).await
} else {
- ManagementApiError::Unsupported {
- details: "This feature is only available in the Enterprise version".into(),
- }
- .into_http_response()
+ Err(manage::unsupported(
+ "This feature is only available in the Enterprise version",
+ ))
}
}
// SPDX-SnippetEnd
- _ => RequestError::not_found().into_http_response(),
- }
- }
-}
-
-impl ToHttpResponse for ManagementApiError {
- fn into_http_response(self) -> super::HttpResponse {
- JsonResponse::new(self).into_http_response()
- }
-}
-
-impl From<Cow<'static, str>> for ManagementApiError {
- fn from(details: Cow<'static, str>) -> Self {
- ManagementApiError::Other { details }
- }
-}
-
-impl From<String> for ManagementApiError {
- fn from(details: String) -> Self {
- ManagementApiError::Other {
- details: details.into(),
+ _ => Err(trc::ResourceCause::NotFound.into_err()),
}
}
}
diff --git a/crates/jmap/src/api/management/principal.rs b/crates/jmap/src/api/management/principal.rs
index 6da26b8e..e197c017 100644
--- a/crates/jmap/src/api/management/principal.rs
+++ b/crates/jmap/src/api/management/principal.rs
@@ -8,14 +8,14 @@ use std::sync::Arc;
use directory::{
backend::internal::{
- lookup::DirectoryStore, manage::ManageDirectory, PrincipalAction, PrincipalField,
- PrincipalUpdate, PrincipalValue, SpecialSecrets,
+ lookup::DirectoryStore,
+ manage::{self, ManageDirectory},
+ PrincipalAction, PrincipalField, PrincipalUpdate, PrincipalValue, SpecialSecrets,
},
DirectoryInner, Principal, QueryBy, Type,
};
-use hyper::{header, Method, StatusCode};
-use jmap_proto::error::request::RequestError;
+use hyper::{header, Method};
use serde_json::json;
use utils::url_params::UrlParams;
@@ -25,7 +25,7 @@ use crate::{
JMAP,
};
-use super::{decode_path_element, ManagementApiError};
+use super::decode_path_element;
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct PrincipalResponse {
@@ -80,47 +80,41 @@ impl JMAP {
req: &HttpRequest,
path: Vec<&str>,
body: Option<Vec<u8>>,
- ) -> HttpResponse {
+ ) -> trc::Result<HttpResponse> {
match (path.get(1), req.method()) {
(None, &Method::POST) => {
// Make sure the current directory supports updates
- if let Some(response) = self.assert_supported_directory() {
- return response;
- }
+ self.assert_supported_directory()?;
// Create principal
- match serde_json::from_slice::<PrincipalResponse>(
+ let principal = serde_json::from_slice::<PrincipalResponse>(
body.as_deref().unwrap_or_default(),
- ) {
- Ok(principal) => {
- match self
- .core
- .storage
- .data
- .create_account(
- Principal {
- id: principal.id,
- typ: principal.typ,
- quota: principal.quota,
- name: principal.name,
- secrets: principal.secrets,
- emails: principal.emails,
- member_of: principal.member_of,
- description: principal.description,
- },
- principal.members,
- )
- .await
- {
- Ok(account_id) => JsonResponse::new(json!({
- "data": account_id,
- }))
- .into_http_response(),
- Err(err) => into_directory_response(err),
- }
- }
- Err(err) => err.into_http_response(),
- }
+ )
+ .map_err(|err| {
+ trc::Cause::Resource(trc::ResourceCause::BadParameters).from_json_error(err)
+ })?;
+
+ Ok(JsonResponse::new(json!({
+ "data": self
+ .core
+ .storage
+ .data
+ .create_account(
+ Principal {
+ id: principal.id,
+ typ: principal.typ,
+ quota: principal.quota,
+ name: principal.name,
+ secrets: principal.secrets,
+ emails: principal.emails,
+ member_of: principal.member_of,
+ description: principal.description,
+ },
+ principal.members,
+ )
+ .await?,
+ }))
+ .into_http_response())
}
(None, &Method::GET) => {
// List principal ids
@@ -130,187 +124,137 @@ impl JMAP {
let page: usize = params.parse("page").unwrap_or(0);
let limit: usize = params.parse("limit").unwrap_or(0);
- match self.core.storage.data.list_accounts(filter, typ).await {
- Ok(accounts) => {
- let (total, accounts) = if limit > 0 {
- let offset = page.saturating_sub(1) * limit;
- (
- accounts.len(),
- accounts.into_iter().skip(offset).take(limit).collect(),
- )
- } else {
- (accounts.len(), accounts)
- };
-
- JsonResponse::new(json!({
- "data": {
- "items": accounts,
- "total": total,
- },
- }))
- .into_http_response()
- }
- Err(err) => into_directory_response(err),
- }
+ let accounts = self.core.storage.data.list_accounts(filter, typ).await?;
+ let (total, accounts) = if limit > 0 {
+ let offset = page.saturating_sub(1) * limit;
+ (
+ accounts.len(),
+ accounts.into_iter().skip(offset).take(limit).collect(),
+ )
+ } else {
+ (accounts.len(), accounts)
+ };
+
+ Ok(JsonResponse::new(json!({
+ "data": {
+ "items": accounts,
+ "total": total,
+ },
+ }))
+ .into_http_response())
}
(Some(name), method) => {
// Fetch, update or delete principal
let name = decode_path_element(name);
- let account_id = match self.core.storage.data.get_account_id(name.as_ref()).await {
- Ok(Some(account_id)) => account_id,
- Ok(None) => {
- return RequestError::blank(
- StatusCode::NOT_FOUND.as_u16(),
- "Not found",
- "Account not found.",
- )
- .into_http_response();
- }
- Err(err) => {
- return into_directory_response(err);
- }
- };
+ let account_id = self
+ .core
+ .storage
+ .data
+ .get_account_id(name.as_ref())
+ .await?
+ .ok_or_else(|| trc::ManageCause::NotFound.into_err())?;
match *method {
Method::GET => {
- let result = match self
+ let principal = self
.core
.storage
.data
.query(QueryBy::Id(account_id), true)
- .await
- {
- Ok(Some(principal)) => {
- self.core.storage.data.map_group_ids(principal).await
- }
- Ok(None) => {
- return RequestError::blank(
- StatusCode::NOT_FOUND.as_u16(),
- "Not found",
- "Account not found.",
- )
- .into_http_response()
- }
- Err(err) => Err(err),
- };
-
- match result {
- Ok(principal) => {
- // Obtain quota usage
- let mut principal = PrincipalResponse::from(principal);
- principal.used_quota =
- self.get_used_quota(account_id).await.unwrap_or_default()
- as u64;
-
- // Obtain member names
- for member_id in self
- .core
- .storage
- .data
- .get_members(account_id)
- .await
- .unwrap_or_default()
- {
- if let Ok(Some(member_principal)) = self
- .core
- .storage
- .data
- .query(QueryBy::Id(member_id), false)
- .await
- {
- principal.members.push(member_principal.name);
- }
- }
-
- JsonResponse::new(json!({
- "data": principal,
- }))
- .into_http_response()
+ .await?
+ .ok_or_else(|| trc::ManageCause::NotFound.into_err())?;
+ let principal = self.core.storage.data.map_group_ids(principal).await?;
+
+ // Obtain quota usage
+ let mut principal = PrincipalResponse::from(principal);
+ principal.used_quota = self.get_used_quota(account_id).await? as u64;
+
+ // Obtain member names
+ for member_id in self.core.storage.data.get_members(account_id).await? {
+ if let Some(member_principal) = self
+ .core
+ .storage
+ .data
+ .query(QueryBy::Id(member_id), false)
+ .await?
+ {
+ principal.members.push(member_principal.name);
}
- Err(err) => into_directory_response(err),
}
+
+ Ok(JsonResponse::new(json!({
+ "data": principal,
+ }))
+ .into_http_response())
}
Method::DELETE => {
// Remove FTS index
- if let Err(err) = self.core.storage.fts.remove_all(account_id).await {
- return err.into_http_response();
- }
+ self.core.storage.fts.remove_all(account_id).await;
// Delete account
- match self
- .core
+ self.core
.storage
.data
.delete_account(QueryBy::Id(account_id))
- .await
- {
- Ok(_) => {
- // Remove entries from cache
- self.inner.sessions.retain(|_, id| id.item != account_id);
-
- JsonResponse::new(json!({
- "data": (),
- }))
- .into_http_response()
- }
- Err(err) => into_directory_response(err),
- }
+ .await?;
+ // Remove entries from cache
+ self.inner.sessions.retain(|_, id| id.item != account_id);
+
+ Ok(JsonResponse::new(json!({
+ "data": (),
+ }))
+ .into_http_response())
}
Method::PATCH => {
- match serde_json::from_slice::<Vec<PrincipalUpdate>>(
+ let changes = serde_json::from_slice::<Vec<PrincipalUpdate>>(
body.as_deref().unwrap_or_default(),
- ) {
- Ok(changes) => {
- // Make sure the current directory supports updates
- if let Some(response) = self.assert_supported_directory() {
- if changes.iter().any(|change| {
- !matches!(
- change.field,
- PrincipalField::Quota | PrincipalField::Description
- )
- }) {
- return response;
- }
- }
- let is_password_change = changes
- .iter()
- .any(|change| matches!(change.field, PrincipalField::Secrets));
-
- match self
- .core
- .storage
- .data
- .update_account(QueryBy::Id(account_id), changes)
- .await
- {
- Ok(_) => {
- if is_password_change {
- // Remove entries from cache
- self.inner
- .sessions
- .retain(|_, id| id.item != account_id);
- }
-
- JsonResponse::new(json!({
- "data": (),
- }))
- .into_http_response()
- }
- Err(err) => into_directory_response(err),
- }
- }
- Err(err) => err.into_http_response(),
+ )
+ .map_err(|err| {
+ trc::Cause::Resource(trc::ResourceCause::BadParameters)
+ .from_json_error(err)
+ })?;
+
+ // Make sure the current directory supports updates
+ if changes.iter().any(|change| {
+ !matches!(
+ change.field,
+ PrincipalField::Quota | PrincipalField::Description
+ )
+ }) {
+ self.assert_supported_directory()?;
+ }
+
+ let is_password_change = changes
+ .iter()
+ .any(|change| matches!(change.field, PrincipalField::Secrets));
+
+ self.core
+ .storage
+ .data
+ .update_account(QueryBy::Id(account_id), changes)
+ .await?;
+ if is_password_change {
+ // Remove entries from cache
+ self.inner.sessions.retain(|_, id| id.item != account_id);
}
+
+ Ok(JsonResponse::new(json!({
+ "data": (),
+ }))
+ .into_http_response())
}
- _ => RequestError::not_found().into_http_response(),
+ _ => Err(trc::ResourceCause::NotFound.into_err()),
}
}
- _ => RequestError::not_found().into_http_response(),
+ _ => Err(trc::ResourceCause::NotFound.into_err()),
}
}
- pub async fn handle_account_auth_get(&self, access_token: Arc<AccessToken>) -> HttpResponse {
+ pub async fn handle_account_auth_get(
+ &self,
+ access_token: Arc<AccessToken>,
+ ) -> trc::Result<HttpResponse> {
let mut response = AccountAuthResponse {
otp_auth: false,
is_admin: access_token.is_super_user(),
@@ -318,35 +262,29 @@ impl JMAP {
};
if access_token.primary_id() != u32::MAX {
- match self
+ let principal = self
.core
.storage
.directory
.query(QueryBy::Id(access_token.primary_id()), false)
- .await
- {
- Ok(Some(principal)) => {
- for secret in principal.secrets {
- if secret.is_otp_auth() {
- response.otp_auth = true;
- } else if let Some((app_name, _)) =
- secret.strip_prefix("$app$").and_then(|s| s.split_once('$'))
- {
- response.app_passwords.push(app_name.to_string());
- }
- }
+ .await?
+ .ok_or_else(|| trc::ManageCause::NotFound.into_err())?;
+
+ for secret in principal.secrets {
+ if secret.is_otp_auth() {
+ response.otp_auth = true;
+ } else if let Some((app_name, _)) =
+ secret.strip_prefix("$app$").and_then(|s| s.split_once('$'))
+ {
+ response.app_passwords.push(app_name.to_string());
}
- Ok(None) => {
- return RequestError::not_found().into_http_response();
- }
- Err(err) => return into_directory_response(err),
}
}
- JsonResponse::new(json!({
+ Ok(JsonResponse::new(json!({
"data": response,
}))
- .into_http_response()
+ .into_http_response())
}
pub async fn handle_account_auth_post(
@@ -354,16 +292,18 @@ impl JMAP {
req: &HttpRequest,
access_token: Arc<AccessToken>,
body: Option<Vec<u8>>,
- ) -> HttpResponse {
+ ) -> trc::Result<HttpResponse> {
// Parse request
- let requests = match serde_json::from_slice::<Vec<AccountAuthRequest>>(
- body.as_deref().unwrap_or_default(),
- ) {
- Ok(request) => request,
- Err(err) => return err.into_http_response(),
- };
+ let requests =
+ serde_json::from_slice::<Vec<AccountAuthRequest>>(body.as_deref().unwrap_or_default())
+ .map_err(|err| {
+ trc::Cause::Resource(trc::ResourceCause::BadParameters).from_json_error(err)
+ })?;
+
if requests.is_empty() {
- return RequestError::invalid_parameters().into_http_response();
+ return Err(trc::Cause::Resource(trc::ResourceCause::BadParameters)
+ .into_err()
+ .details("Empty request"));
}
// Make sure the user authenticated using Basic auth
@@ -380,50 +320,41 @@ impl JMAP {
.and_then(|h| h.to_str().ok())
.map_or(true, |header| !header.to_lowercase().starts_with("basic "))
{
- return ManagementApiError::Other {
- details: "Password changes only allowed using Basic auth".into(),
- }
- .into_http_response();
+ return Err(manage::error(
+ "Password changes only allowed using Basic auth",
+ None::<u32>,
+ ));
}
// Handle Fallback admin password changes
if access_token.is_super_user() && access_token.primary_id() == u32::MAX {
match requests.into_iter().next().unwrap() {
AccountAuthRequest::SetPassword { password } => {
- return match self
- .core
+ self.core
.storage
.config
.set([("authentication.fallback-admin.secret", password)])
- .await
- {
- Ok(_) => {
- // Remove entries from cache
- self.inner.sessions.retain(|_, id| id.item != u32::MAX);
+ .await?;
- JsonResponse::new(json!({
- "data": (),
- }))
- .into_http_response()
- }
- Err(err) => err.into_http_response(),
- };
+ // Remove entries from cache
+ self.inner.sessions.retain(|_, id| id.item != u32::MAX);
+
+ return Ok(JsonResponse::new(json!({
+ "data": (),
+ }))
+ .into_http_response());
}
_ => {
- return ManagementApiError::Other {
- details:
- "Fallback administrator accounts do not support 2FA or AppPasswords"
- .into(),
- }
- .into_http_response()
+ return Err(manage::error(
+ "Fallback administrator accounts do not support 2FA or AppPasswords",
+ None::<u32>,
+ ));
}
}
}
// Make sure the current directory supports updates
- if let Some(response) = self.assert_supported_directory() {
- return response;
- }
+ self.assert_supported_directory()?;
// Build actions
let mut actions = Vec::with_capacity(requests.len());
@@ -459,42 +390,38 @@ impl JMAP {
}
// Update password
- match self
- .core
+ self.core
.storage
.data
.update_account(QueryBy::Id(access_token.primary_id()), actions)
- .await
- {
- Ok(_) => {
- // Remove entries from cache
- self.inner
- .sessions
- .retain(|_, id| id.item != access_token.primary_id());
-
- JsonResponse::new(json!({
- "data": (),
- }))
- .into_http_response()
- }
- Err(err) => into_directory_response(err),
- }
+ .await?;
+
+ // Remove entries from cache
+ self.inner
+ .sessions
+ .retain(|_, id| id.item != access_token.primary_id());
+
+ Ok(JsonResponse::new(json!({
+ "data": (),
+ }))
+ .into_http_response())
}
- pub fn assert_supported_directory(&self) -> Option<HttpResponse> {
- ManagementApiError::UnsupportedDirectoryOperation {
- class: match &self.core.storage.directory.store {
- DirectoryInner::Internal(_) => return None,
- DirectoryInner::Ldap(_) => "LDAP",
- DirectoryInner::Sql(_) => "SQL",
- DirectoryInner::Imap(_) => "IMAP",
- DirectoryInner::Smtp(_) => "SMTP",
- DirectoryInner::Memory(_) => "In-Memory",
- }
- .into(),
- }
- .into_http_response()
- .into()
+ pub fn assert_supported_directory(&self) -> trc::Result<()> {
+ let todo = "update webadmin";
+
+ let class = match &self.core.storage.directory.store {
+ DirectoryInner::Internal(_) => return Ok(()),
+ DirectoryInner::Ldap(_) => "LDAP",
+ DirectoryInner::Sql(_) => "SQL",
+ DirectoryInner::Imap(_) => "IMAP",
+ DirectoryInner::Smtp(_) => "SMTP",
+ DirectoryInner::Memory(_) => "In-Memory",
+ };
+
+ Err(manage::unsupported(format!(
+ "Requested action is unsupported for {class} directories.",
+ )))
}
}
@@ -515,7 +442,8 @@ impl From<Principal<String>> for PrincipalResponse {
}
}
-fn into_directory_response(mut error: trc::Error) -> HttpResponse {
+/*
+fn into_directory_response(mut error: trc::Error) -> trc::Result<HttpResponse> {
let response = match error.as_ref() {
trc::Cause::MissingParameter => ManagementApiError::FieldMissing {
field: error
@@ -533,7 +461,7 @@ fn into_directory_response(mut error: trc::Error) -> HttpResponse {
.and_then(|v| v.into_string())
.unwrap_or_default(),
},
- trc::Cause::NotFound => ManagementApiError::NotFound {
+ trc::StoreCause::NotFound.into_err() => ManagementApiError::NotFound {
item: error
.take_value(trc::Key::Key)
.and_then(|v| v.into_string())
@@ -559,3 +487,4 @@ fn into_directory_response(mut error: trc::Error) -> HttpResponse {
JsonResponse::new(response).into_http_response()
}
+*/
diff --git a/crates/jmap/src/api/management/queue.rs b/crates/jmap/src/api/management/queue.rs
index d5db8b76..923a20e2 100644
--- a/crates/jmap/src/api/management/queue.rs
+++ b/crates/jmap/src/api/management/queue.rs
@@ -8,7 +8,6 @@ use std::str::FromStr;
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
use hyper::Method;
-use jmap_proto::error::request::RequestError;
use mail_auth::{
dmarc::URI,
mta_sts::ReportUri,
@@ -104,7 +103,11 @@ pub enum Report {
}
impl JMAP {
- pub async fn handle_manage_queue(&self, req: &HttpRequest, path: Vec<&str>) -> HttpResponse {
+ pub async fn handle_manage_queue(
+ &self,
+ req: &HttpRequest,
+ path: Vec<&str>,
+ ) -> trc::Result<HttpResponse> {
let params = UrlParams::new(req.uri().query());
match (
@@ -138,8 +141,7 @@ impl JMAP {
let mut offset = page.saturating_sub(1) * limit;
let mut total = 0;
let mut total_returned = 0;
- let _ = self
- .core
+ self.core
.storage
.data
.iterate(
@@ -193,9 +195,9 @@ impl JMAP {
Ok(max_total == 0 || total < max_total)
},
)
- .await;
+ .await?;
- if values {
+ Ok(if values {
JsonResponse::new(json!({
"data":{
"items": result_values,
@@ -210,7 +212,7 @@ impl JMAP {
},
}))
}
- .into_http_response()
+ .into_http_response())
}
("messages", Some(queue_id), &Method::GET) => {
if let Some(message) = self
@@ -218,12 +220,12 @@ impl JMAP {
.read_message(queue_id.parse().unwrap_or_default())
.await
{
- JsonResponse::new(json!({
+ Ok(JsonResponse::new(json!({
"data": Message::from(&message),
}))
- .into_http_response()
+ .into_http_response())
} else {
- RequestError::not_found().into_http_response()
+ Err(trc::ResourceCause::NotFound.into_err())
}
}
("messages", Some(queue_id), &Method::PATCH) => {
@@ -265,12 +267,12 @@ impl JMAP {
let _ = self.smtp.inner.queue_tx.send(queue::Event::Reload).await;
}
- JsonResponse::new(json!({
+ Ok(JsonResponse::new(json!({
"data": found,
}))
- .into_http_response()
+ .into_http_response())
} else {
- RequestError::not_found().into_http_response()
+ Err(trc::ResourceCause::NotFound.into_err())
}
}
("messages", Some(queue_id), &Method::DELETE) => {
@@ -345,12 +347,12 @@ impl JMAP {
found = true;
}
- JsonResponse::new(json!({
+ Ok(JsonResponse::new(json!({
"data": found,
}))
- .into_http_response()
+ .into_http_response())
} else {
- RequestError::not_found().into_http_response()
+ Err(trc::ResourceCause::NotFound.into_err())
}
}
("reports", None, &Method::GET) => {
@@ -387,8 +389,7 @@ impl JMAP {
let mut offset = page.saturating_sub(1) * limit;
let mut total = 0;
let mut total_returned = 0;
- let _ = self
- .core
+ self.core
.storage
.data
.iterate(
@@ -422,15 +423,15 @@ impl JMAP {
Ok(max_total == 0 || total < max_total)
},
)
- .await;
+ .await?;
- JsonResponse::new(json!({
+ Ok(JsonResponse::new(json!({
"data": {
"items": result,
"total": total,
},
}))
- .into_http_response()
+ .into_http_response())
}
("reports", Some(report_id), &Method::GET) => {
let mut result = None;
@@ -438,20 +439,20 @@ impl JMAP {
match report_id {
QueueClass::DmarcReportHeader(event) => {
let mut rua = Vec::new();
- if let Ok(Some(report)) = self
+ if let Some(report) = self
.smtp
.generate_dmarc_aggregate_report(&event, &mut rua, None)
- .await
+ .await?
{
result = Report::dmarc(event, report, rua).into();
}
}
QueueClass::TlsReportHeader(event) => {
let mut rua = Vec::new();
- if let Ok(Some(report)) = self
+ if let Some(report) = self
.smtp
.generate_tls_aggregate_report(&[event.clone()], &mut rua, None)
- .await
+ .await?
{
result = Report::tls(event, report, rua).into();
}
@@ -461,12 +462,12 @@ impl JMAP {
}
if let Some(result) = result {
- JsonResponse::new(json!({
+ Ok(JsonResponse::new(json!({
"data": result,
}))
- .into_http_response()
+ .into_http_response())
} else {
- RequestError::not_found().into_http_response()
+ Err(trc::ResourceCause::NotFound.into_err())
}
}
("reports", Some(report_id), &Method::DELETE) => {
@@ -481,15 +482,15 @@ impl JMAP {
_ => (),
}
- JsonResponse::new(json!({
+ Ok(JsonResponse::new(json!({
"data": true,
}))
- .into_http_response()
+ .into_http_response())
} else {
- RequestError::not_found().into_http_response()
+ Err(trc::ResourceCause::NotFound.into_err())
}
}
- _ => RequestError::not_found().into_http_response(),
+ _ => Err(trc::ResourceCause::NotFound.into_err()),
}
}
}
diff --git a/crates/jmap/src/api/management/reload.rs b/crates/jmap/src/api/management/reload.rs
index 52721e10..bca1d9ec 100644
--- a/crates/jmap/src/api/management/reload.rs
+++ b/crates/jmap/src/api/management/reload.rs
@@ -5,7 +5,6 @@
*/
use hyper::Method;
-use jmap_proto::error::request::RequestError;
use serde_json::json;
use utils::url_params::UrlParams;
@@ -16,107 +15,88 @@ use crate::{
};
impl JMAP {
- pub async fn handle_manage_reload(&self, req: &HttpRequest, path: Vec<&str>) -> HttpResponse {
+ pub async fn handle_manage_reload(
+ &self,
+ req: &HttpRequest,
+ path: Vec<&str>,
+ ) -> trc::Result<HttpResponse> {
match (path.get(1).copied(), req.method()) {
(Some("lookup"), &Method::GET) => {
- match self.core.reload_lookups().await {
- Ok(result) => {
- // Update core
- if let Some(core) = result.new_core {
- self.shared_core.store(core.into());
- }
-
- JsonResponse::new(json!({
- "data": result.config,
- }))
- .into_http_response()
- }
- Err(err) => err.into_http_response(),
+ let result = self.core.reload_lookups().await?;
+ // Update core
+ if let Some(core) = result.new_core {
+ self.shared_core.store(core.into());
}
- }
- (Some("certificate"), &Method::GET) => match self.core.reload_certificates().await {
- Ok(result) => JsonResponse::new(json!({
+
+ Ok(JsonResponse::new(json!({
"data": result.config,
}))
- .into_http_response(),
- Err(err) => err.into_http_response(),
- },
+ .into_http_response())
+ }
+ (Some("certificate"), &Method::GET) => Ok(JsonResponse::new(json!({
+ "data": self.core.reload_certificates().await?.config,
+ }))
+ .into_http_response()),
(Some("server.blocked-ip"), &Method::GET) => {
- match self.core.reload_blocked_ips().await {
- Ok(result) => {
- // Increment version counter
- self.core.network.blocked_ips.increment_version();
+ let result = self.core.reload_blocked_ips().await?;
+ // Increment version counter
+ self.core.network.blocked_ips.increment_version();
- JsonResponse::new(json!({
- "data": result.config,
- }))
- .into_http_response()
- }
- Err(err) => err.into_http_response(),
- }
+ Ok(JsonResponse::new(json!({
+ "data": result.config,
+ }))
+ .into_http_response())
}
(_, &Method::GET) => {
- match self.core.reload().await {
- Ok(result) => {
- if !UrlParams::new(req.uri().query()).has_key("dry-run") {
- if let Some(core) = result.new_core {
- // Update core
- self.shared_core.store(core.into());
-
- // Increment version counter
- self.inner.increment_config_version();
- }
+ let result = self.core.reload().await?;
+ if !UrlParams::new(req.uri().query()).has_key("dry-run") {
+ if let Some(core) = result.new_core {
+ // Update core
+ self.shared_core.store(core.into());
- // Reload ACME
- if let Err(err) =
- self.inner.housekeeper_tx.send(Event::AcmeReload).await
- {
- tracing::warn!(
- "Failed to send ACME reload event to housekeeper: {}",
- err
- );
- }
- }
+ // Increment version counter
+ self.inner.increment_config_version();
+ }
- JsonResponse::new(json!({
- "data": result.config,
- }))
- .into_http_response()
+ // Reload ACME
+ if let Err(err) = self.inner.housekeeper_tx.send(Event::AcmeReload).await {
+ tracing::warn!("Failed to send ACME reload event to housekeeper: {}", err);
}
- Err(err) => err.into_http_response(),
}
+
+ Ok(JsonResponse::new(json!({
+ "data": result.config,
+ }))
+ .into_http_response())
}
- _ => RequestError::not_found().into_http_response(),
+ _ => Err(trc::ResourceCause::NotFound.into_err()),
}
}
- pub async fn handle_manage_update(&self, req: &HttpRequest, path: Vec<&str>) -> HttpResponse {
+ pub async fn handle_manage_update(
+ &self,
+ req: &HttpRequest,
+ path: Vec<&str>,
+ ) -> trc::Result<HttpResponse> {
match (path.get(1).copied(), req.method()) {
- (Some("spam-filter"), &Method::GET) => {
- match self
- .core
- .storage
- .config
- .update_config_resource("spam-filter")
- .await
- {
- Ok(result) => JsonResponse::new(json!({
- "data": result,
- }))
- .into_http_response(),
- Err(err) => err.into_http_response(),
- }
- }
+ (Some("spam-filter"), &Method::GET) => Ok(JsonResponse::new(json!({
+ "data": self
+ .core
+ .storage
+ .config
+ .update_config_resource("spam-filter")
+ .await?,
+ }))
+ .into_http_response()),
(Some("webadmin"), &Method::GET) => {
- match self.inner.webadmin.update_and_unpack(&self.core).await {
- Ok(_) => JsonResponse::new(json!({
- "data": (),
- }))
- .into_http_response(),
- Err(err) => err.into_http_response(),
- }
+ self.inner.webadmin.update_and_unpack(&self.core).await?;
+
+ Ok(JsonResponse::new(json!({
+ "data": (),
+ }))
+ .into_http_response())
}
- _ => RequestError::not_found().into_http_response(),
+ _ => Err(trc::ResourceCause::NotFound.into_err()),
}
}
}
diff --git a/crates/jmap/src/api/management/report.rs b/crates/jmap/src/api/management/report.rs
index 1bed1e1b..a07f62eb 100644
--- a/crates/jmap/src/api/management/report.rs
+++ b/crates/jmap/src/api/management/report.rs
@@ -5,7 +5,6 @@
*/
use hyper::Method;
-use jmap_proto::error::request::RequestError;
use mail_auth::report::{
tlsrpt::{FailureDetails, Policy, TlsReport},
Feedback,
@@ -32,7 +31,11 @@ enum ReportType {
}
impl JMAP {
- pub async fn handle_manage_reports(&self, req: &HttpRequest, path: Vec<&str>) -> HttpResponse {
+ pub async fn handle_manage_reports(
+ &self,
+ req: &HttpRequest,
+ path: Vec<&str>,
+ ) -> trc::Result<HttpResponse> {
match (
path.get(1).copied().unwrap_or_default(),
path.get(2).copied().map(decode_path_element),
@@ -89,8 +92,7 @@ impl JMAP {
let mut offset = page.saturating_sub(1) * limit;
let mut total = 0;
let mut last_id = 0;
- let result = self
- .core
+ self.core
.storage
.data
.iterate(
@@ -141,17 +143,15 @@ impl JMAP {
Ok(max_total == 0 || total < max_total)
},
)
- .await;
- match result {
- Ok(_) => JsonResponse::new(json!({
- "data": {
- "items": results,
- "total": total,
- },
- }))
- .into_http_response(),
- Err(err) => err.into_http_response(),
- }
+ .await?;
+
+ Ok(JsonResponse::new(json!({
+ "data": {
+ "items": results,
+ "total": total,
+ },
+ }))
+ .into_http_response())
}
(class @ ("dmarc" | "tls" | "arf"), Some(report_id), &Method::GET) => {
if let Some(report_id) = parse_incoming_report_id(class, report_id.as_ref()) {
@@ -163,14 +163,13 @@ impl JMAP {
.get_value::<Bincode<IncomingReport<TlsReport>>>(ValueKey::from(
ValueClass::Report(report_id),
))
- .await
+ .await?
{
- Ok(Some(report)) => JsonResponse::new(json!({
+ Some(report) => Ok(JsonResponse::new(json!({
"data": report.inner,
}))
- .into_http_response(),
- Ok(None) => RequestError::not_found().into_http_response(),
- Err(err) => err.into_http_response(),
+ .into_http_response()),
+ None => Err(trc::ResourceCause::NotFound.into_err()),
},
ReportClass::Dmarc { .. } => match self
.core
@@ -179,14 +178,13 @@ impl JMAP {
.get_value::<Bincode<IncomingReport<mail_auth::report::Report>>>(
ValueKey::from(ValueClass::Report(report_id)),
)
- .await
+ .await?
{
- Ok(Some(report)) => JsonResponse::new(json!({
+ Some(report) => Ok(JsonResponse::new(json!({
"data": report.inner,
}))
- .into_http_response(),
- Ok(None) => RequestError::not_found().into_http_response(),
- Err(err) => err.into_http_response(),
+ .into_http_response()),
+ None => Err(trc::ResourceCause::NotFound.into_err()),
},
ReportClass::Arf { .. } => match self
.core
@@ -195,35 +193,34 @@ impl JMAP {
.get_value::<Bincode<IncomingReport<Feedback>>>(ValueKey::from(
ValueClass::Report(report_id),
))
- .await
+ .await?
{
- Ok(Some(report)) => JsonResponse::new(json!({
+ Some(report) => Ok(JsonResponse::new(json!({
"data": report.inner,
}))
- .into_http_response(),
- Ok(None) => RequestError::not_found().into_http_response(),
- Err(err) => err.into_http_response(),
+ .into_http_response()),
+ None => Err(trc::ResourceCause::NotFound.into_err()),
},
}
} else {
- RequestError::not_found().into_http_response()
+ Err(trc::ResourceCause::NotFound.into_err())
}
}
(class @ ("dmarc" | "tls" | "arf"), Some(report_id), &Method::DELETE) => {
if let Some(report_id) = parse_incoming_report_id(class, report_id.as_ref()) {
let mut batch = BatchBuilder::new();
batch.clear(ValueClass::Report(report_id));
- let result = self.core.storage.data.write(batch.build()).await.is_ok();
+ self.core.storage.data.write(batch.build()).await?;
- JsonResponse::new(json!({
- "data": result,
+ Ok(JsonResponse::new(json!({
+ "data": true,
}))
- .into_http_response()
+ .into_http_response())
} else {
- RequestError::not_found().into_http_response()
+ Err(trc::ResourceCause::NotFound.into_err())
}
}
- _ => RequestError::not_found().into_http_response(),
+ _ => Err(trc::ResourceCause::NotFound.into_err()),
}
}
}
diff --git a/crates/jmap/src/api/management/settings.rs b/crates/jmap/src/api/management/settings.rs
index 931ff52e..86fd0f0c 100644
--- a/crates/jmap/src/api/management/settings.rs
+++ b/crates/jmap/src/api/management/settings.rs
@@ -5,7 +5,6 @@
*/
use hyper::Method;
-use jmap_proto::error::request::RequestError;
use serde_json::json;
use store::ahash::AHashMap;
use utils::{config::ConfigKey, url_params::UrlParams};
@@ -15,7 +14,7 @@ use crate::{
JMAP,
};
-use super::{decode_path_element, ManagementApiError};
+use super::decode_path_element;
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(tag = "type")]
@@ -39,7 +38,7 @@ impl JMAP {
req: &HttpRequest,
path: Vec<&str>,
body: Option<Vec<u8>>,
- ) -> HttpResponse {
+ ) -> trc::Result<HttpResponse> {
match (path.get(1).copied(), req.method()) {
(Some("group"), &Method::GET) => {
// List settings
@@ -71,103 +70,101 @@ impl JMAP {
params.parse::<usize>("page").unwrap_or(0).saturating_sub(1) * limit;
let has_filter = !filter.is_empty();
- match self.core.storage.config.list(&prefix, true).await {
- Ok(settings) => if !suffix.is_empty() && !settings.is_empty() {
- // Obtain record ids
- let mut total = 0;
- let mut ids = Vec::new();
- for (key, _) in &settings {
- if let Some(id) = key.strip_suffix(&suffix) {
- if !id.is_empty() {
- if !has_filter {
- if offset == 0 {
- if limit == 0 || ids.len() < limit {
- ids.push(id);
- }
- } else {
- offset -= 1;
+ let settings = self.core.storage.config.list(&prefix, true).await?;
+ if !suffix.is_empty() && !settings.is_empty() {
+ // Obtain record ids
+ let mut total = 0;
+ let mut ids = Vec::new();
+ for (key, _) in &settings {
+ if let Some(id) = key.strip_suffix(&suffix) {
+ if !id.is_empty() {
+ if !has_filter {
+ if offset == 0 {
+ if limit == 0 || ids.len() < limit {
+ ids.push(id);
}
- total += 1;
} else {
- ids.push(id);
+ offset -= 1;
}
+ total += 1;
+ } else {
+ ids.push(id);
}
}
}
+ }
- // Group settings by record id
- let mut records = Vec::new();
- for id in ids {
- let mut record = AHashMap::new();
- let prefix = format!("{id}.");
- record.insert("_id".to_string(), id.to_string());
- for (k, v) in &settings {
- if let Some(k) = k.strip_prefix(&prefix) {
- if field.map_or(true, |field| field == k) {
- record.insert(k.to_string(), v.to_string());
- }
- } else if record.len() > 1 {
- break;
+ // Group settings by record id
+ let mut records = Vec::new();
+ for id in ids {
+ let mut record = AHashMap::new();
+ let prefix = format!("{id}.");
+ record.insert("_id".to_string(), id.to_string());
+ for (k, v) in &settings {
+ if let Some(k) = k.strip_prefix(&prefix) {
+ if field.map_or(true, |field| field == k) {
+ record.insert(k.to_string(), v.to_string());
}
+ } else if record.len() > 1 {
+ break;
}
+ }
- if has_filter {
- if record
- .iter()
- .any(|(_, v)| v.to_lowercase().contains(&filter))
- {
- if offset == 0 {
- if limit == 0 || records.len() < limit {
- records.push(record);
- }
- } else {
- offset -= 1;
+ if has_filter {
+ if record
+ .iter()
+ .any(|(_, v)| v.to_lowercase().contains(&filter))
+ {
+ if offset == 0 {
+ if limit == 0 || records.len() < limit {
+ records.push(record);
}
- total += 1;
+ } else {
+ offset -= 1;
}
- } else {
- records.push(record);
+ total += 1;
}
+ } else {
+ records.push(record);
}
+ }
- JsonResponse::new(json!({
- "data": {
- "total": total,
- "items": records,
- },
- }))
- } else {
- let total = settings.len();
- let items = settings
- .into_iter()
- .filter_map(|(k, v)| {
- if filter.is_empty()
- || k.to_lowercase().contains(&filter)
- || v.to_lowercase().contains(&filter)
- {
- let k =
- k.strip_prefix(&prefix).map(|k| k.to_string()).unwrap_or(k);
- Some(json!({
- "_id": k,
- "_value": v,
- }))
- } else {
- None
- }
- })
- .skip(offset)
- .take(if limit == 0 { total } else { limit })
- .collect::<Vec<_>>();
+ Ok(JsonResponse::new(json!({
+ "data": {
+ "total": total,
+ "items": records,
+ },
+ }))
+ .into_http_response())
+ } else {
+ let total = settings.len();
+ let items = settings
+ .into_iter()
+ .filter_map(|(k, v)| {
+ if filter.is_empty()
+ || k.to_lowercase().contains(&filter)
+ || v.to_lowercase().contains(&filter)
+ {
+ let k = k.strip_prefix(&prefix).map(|k| k.to_string()).unwrap_or(k);
+ Some(json!({
+ "_id": k,
+ "_value": v,
+ }))
+ } else {
+ None
+ }
+ })
+ .skip(offset)
+ .take(if limit == 0 { total } else { limit })
+ .collect::<Vec<_>>();
- JsonResponse::new(json!({
- "data": {
- "total": total,
- "items": items,
- },
- }))
- }
- .into_http_response(),
- Err(err) => err.into_http_response(),
+ Ok(JsonResponse::new(json!({
+ "data": {
+ "total": total,
+ "items": items,
+ },
+ }))
+ .into_http_response())
}
}
(Some("list"), &Method::GET) => {
@@ -186,25 +183,21 @@ impl JMAP {
let limit: usize = params.parse("limit").unwrap_or(0);
let offset = params.parse::<usize>("page").unwrap_or(0).saturating_sub(1) * limit;
- match self.core.storage.config.list(&prefix, true).await {
- Ok(settings) => {
- let total = settings.len();
- let items = settings
- .into_iter()
- .skip(offset)
- .take(if limit == 0 { total } else { limit })
- .collect::<AHashMap<_, _>>();
+ let settings = self.core.storage.config.list(&prefix, true).await?;
+ let total = settings.len();
+ let items = settings
+ .into_iter()
+ .skip(offset)
+ .take(if limit == 0 { total } else { limit })
+ .collect::<AHashMap<_, _>>();
- JsonResponse::new(json!({
- "data": {
- "total": total,
- "items": items,
- },
- }))
- .into_http_response()
- }
- Err(err) => err.into_http_response(),
- }
+ Ok(JsonResponse::new(json!({
+ "data": {
+ "total": total,
+ "items": items,
+ },
+ }))
+ .into_http_response())
}
(Some("keys"), &Method::GET) => {
// Obtain keys
@@ -217,19 +210,11 @@ impl JMAP {
.get("prefixes")
.map(|s| s.split(',').collect::<Vec<_>>())
.unwrap_or_default();
- let mut err = None;
let mut results = AHashMap::with_capacity(keys.len());
for key in keys {
- match self.core.storage.config.get(key).await {
- Ok(Some(value)) => {
- results.insert(key.to_string(), value);
- }
- Ok(None) => {}
- Err(err_) => {
- err = err_.into();
- break;
- }
+ if let Some(value) = self.core.storage.config.get(key).await? {
+ results.insert(key.to_string(), value);
}
}
for prefix in prefixes {
@@ -238,134 +223,88 @@ impl JMAP {
} else {
prefix.to_string()
};
- match self.core.storage.config.list(&prefix, false).await {
- Ok(values) => {
- results.extend(values);
- }
- Err(err_) => {
- err = err_.into();
- break;
- }
- }
+ results.extend(self.core.storage.config.list(&prefix, false).await?);
}
- match err {
- None => JsonResponse::new(json!({
- "data": results,
- }))
- .into_http_response(),
- Some(err) => err.into_http_response(),
- }
+ Ok(JsonResponse::new(json!({
+ "data": results,
+ }))
+ .into_http_response())
}
(Some(prefix), &Method::DELETE) if !prefix.is_empty() => {
let prefix = decode_path_element(prefix);
- match self.core.storage.config.clear(prefix.as_ref()).await {
- Ok(_) => JsonResponse::new(json!({
- "data": (),
- }))
- .into_http_response(),
- Err(err) => err.into_http_response(),
- }
+ self.core.storage.config.clear(prefix.as_ref()).await?;
+
+ Ok(JsonResponse::new(json!({
+ "data": (),
+ }))
+ .into_http_response())
}
(None, &Method::POST) => {
- match serde_json::from_slice::<Vec<UpdateSettings>>(
+ let changes = serde_json::from_slice::<Vec<UpdateSettings>>(
body.as_deref().unwrap_or_default(),
- ) {
- Ok(changes) => {
- let mut result = Ok(true);
+ )
+ .map_err(|err| {
+ trc::Cause::Resource(trc::ResourceCause::BadParameters).from_json_error(err)
+ })?;
- 'next: for change in changes {
- match change {
- UpdateSettings::Delete { keys } => {
- for key in keys {
- result =
- self.core.storage.config.clear(key).await.map(|_| true);
- if result.is_err() {
- break 'next;
- }
- }
- }
- UpdateSettings::Clear { prefix } => {
- result = self
+ for change in changes {
+ match change {
+ UpdateSettings::Delete { keys } => {
+ for key in keys {
+ self.core.storage.config.clear(key).await?;
+ }
+ }
+ UpdateSettings::Clear { prefix } => {
+ self.core.storage.config.clear_prefix(&prefix).await?;
+ }
+ UpdateSettings::Insert {
+ prefix,
+ values,
+ assert_empty,
+ } => {
+ if assert_empty {
+ if let Some(prefix) = &prefix {
+ if !self
.core
.storage
.config
- .clear_prefix(&prefix)
- .await
- .map(|_| true);
- if result.is_err() {
- break;
+ .list(&format!("{prefix}."), true)
+ .await?
+ .is_empty()
+ {
+ return Err(trc::ManageCause::AssertFailed.into_err());
}
- }
- UpdateSettings::Insert {
- prefix,
- values,
- assert_empty,
- } => {
- if assert_empty {
- if let Some(prefix) = &prefix {
- result = self
- .core
- .storage
- .config
- .list(&format!("{prefix}."), true)
- .await
- .map(|items| items.is_empty());
-
- if matches!(result, Ok(false) | Err(_)) {
- break;
- }
- } else if let Some((key, _)) = values.first() {
- result = self
- .core
- .storage
- .config
- .get(key)
- .await
- .map(|items| items.is_none());
-
- if matches!(result, Ok(false) | Err(_)) {
- break;
- }
- }
- }
-
- result = self
- .core
- .storage
- .config
- .set(values.into_iter().map(|(key, value)| ConfigKey {
- key: if let Some(prefix) = &prefix {
- format!("{prefix}.{key}")
- } else {
- key
- },
- value,
- }))
- .await
- .map(|_| true);
- if result.is_err() {
- break;
+ } else if let Some((key, _)) = values.first() {
+ if self.core.storage.config.get(key).await?.is_some() {
+ return Err(trc::ManageCause::AssertFailed.into_err());
}
}
}
- }
- match result {
- Ok(true) => JsonResponse::new(json!({
- "data": (),
- }))
- .into_http_response(),
- Ok(false) => JsonResponse::new(ManagementApiError::AssertFailed)
- .into_http_response(),
- Err(err) => err.into_http_response(),
+ self.core
+ .storage
+ .config
+ .set(values.into_iter().map(|(key, value)| ConfigKey {
+ key: if let Some(prefix) = &prefix {
+ format!("{prefix}.{key}")
+ } else {
+ key
+ },
+ value,
+ }))
+ .await?;
}
}
- Err(err) => err.into_http_response(),
}
+
+ Ok(JsonResponse::new(json!({
+ "data": (),
+ }))
+ .into_http_response())
}
- _ => RequestError::not_found().into_http_response(),
+ _ => Err(trc::ResourceCause::NotFound.into_err()),
}
}
}
diff --git a/crates/jmap/src/api/management/sieve.rs b/crates/jmap/src/api/management/sieve.rs
index fbcc25d2..33bb3f03 100644
--- a/crates/jmap/src/api/management/sieve.rs
+++ b/crates/jmap/src/api/management/sieve.rs
@@ -8,7 +8,6 @@ use std::time::SystemTime;
use common::{scripts::ScriptModification, IntoString};
use hyper::Method;
-use jmap_proto::error::request::RequestError;
use serde_json::json;
use sieve::{runtime::Variable, Envelope};
use smtp::scripts::{ScriptParameters, ScriptResult};
@@ -42,7 +41,7 @@ impl JMAP {
req: &HttpRequest,
path: Vec<&str>,
body: Option<Vec<u8>>,
- ) -> HttpResponse {
+ ) -> trc::Result<HttpResponse> {
let script = match (
path.get(1)
.and_then(|name| self.core.sieve.scripts.get(*name))
@@ -51,7 +50,7 @@ impl JMAP {
) {
(Some(script), &Method::POST) => script,
_ => {
- return RequestError::not_found().into_http_response();
+ return Err(trc::ResourceCause::NotFound.into_err());
}
};
@@ -112,9 +111,9 @@ impl JMAP {
ScriptResult::Discard => Response::Discard,
};
- JsonResponse::new(json!({
+ Ok(JsonResponse::new(json!({
"data": result,
}))
- .into_http_response()
+ .into_http_response())
}
}
diff --git a/crates/jmap/src/api/management/stores.rs b/crates/jmap/src/api/management/stores.rs
index c31ac884..3c82c127 100644
--- a/crates/jmap/src/api/management/stores.rs
+++ b/crates/jmap/src/api/management/stores.rs
@@ -8,7 +8,6 @@ use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
use common::manager::webadmin::Resource;
use directory::backend::internal::manage::ManageDirectory;
use hyper::Method;
-use jmap_proto::error::request::RequestError;
use serde_json::json;
use utils::url_params::UrlParams;
@@ -21,7 +20,11 @@ use crate::{
use super::decode_path_element;
impl JMAP {
- pub async fn handle_manage_store(&self, req: &HttpRequest, path: Vec<&str>) -> HttpResponse {
+ pub async fn handle_manage_store(
+ &self,
+ req: &HttpRequest,
+ path: Vec<&str>,
+ ) -> trc::Result<HttpResponse> {
match (
path.get(1).copied(),
path.get(2).copied(),
@@ -29,41 +32,36 @@ impl JMAP {
req.method(),
) {
(Some("blobs"), Some(blob_hash), _, &Method::GET) => {
- match URL_SAFE_NO_PAD.decode(decode_path_element(blob_hash).as_bytes()) {
- Ok(blob_hash) => {
- match self
- .core
- .storage
- .blob
- .get_blob(&blob_hash, 0..usize::MAX)
- .await
- {
- Ok(Some(contents)) => {
- let params = UrlParams::new(req.uri().query());
- let offset = params.parse("offset").unwrap_or(0);
- let limit = params.parse("limit").unwrap_or(usize::MAX);
-
- let contents = if offset == 0 && limit == usize::MAX {
- contents
- } else {
- contents
- .get(offset..std::cmp::min(offset + limit, contents.len()))
- .unwrap_or_default()
- .to_vec()
- };
+ let blob_hash = URL_SAFE_NO_PAD
+ .decode(decode_path_element(blob_hash).as_bytes())
+ .map_err(|err| {
+ trc::Cause::Resource(trc::ResourceCause::BadParameters)
+ .from_base64_error(err)
+ })?;
+ let contents = self
+ .core
+ .storage
+ .blob
+ .get_blob(&blob_hash, 0..usize::MAX)
+ .await?
+ .ok_or_else(|| trc::ManageCause::NotFound.into_err())?;
+ let params = UrlParams::new(req.uri().query());
+ let offset = params.parse("offset").unwrap_or(0);
+ let limit = params.parse("limit").unwrap_or(usize::MAX);
+ let contents = if offset == 0 && limit == usize::MAX {
+ contents
+ } else {
+ contents
+ .get(offset..std::cmp::min(offset + limit, contents.len()))
+ .unwrap_or_default()
+ .to_vec()
+ };
- Resource {
- content_type: "application/octet-stream",
- contents,
- }
- .into_http_response()
- }
- Ok(None) => RequestError::not_found().into_http_response(),
- Err(err) => err.into_http_response(),
- }
- }
- Err(_) => RequestError::invalid_parameters().into_http_response(),
+ Ok(Resource {
+ content_type: "application/octet-stream",
+ contents,
}
+ .into_http_response())
}
(Some("purge"), Some("blob"), _, &Method::GET) => {
self.housekeeper_request(Event::Purge(PurgeType::Blobs {
@@ -77,7 +75,7 @@ impl JMAP {
if let Some(store) = self.core.storage.stores.get(id) {
store.clone()
} else {
- return RequestError::not_found().into_http_response();
+ return Err(trc::ResourceCause::NotFound.into_err());
}
} else {
self.core.storage.data.clone()
@@ -91,7 +89,7 @@ impl JMAP {
if let Some(store) = self.core.storage.lookups.get(id) {
store.clone()
} else {
- return RequestError::not_found().into_http_response();
+ return Err(trc::ResourceCause::NotFound.into_err());
}
} else {
self.core.storage.lookup.clone()
@@ -102,17 +100,13 @@ impl JMAP {
}
(Some("purge"), Some("account"), id, &Method::GET) => {
let account_id = if let Some(id) = id {
- match self
- .core
+ self.core
.storage
.data
.get_account_id(decode_path_element(id).as_ref())
- .await
- {
- Ok(Some(id)) => id.into(),
- Ok(None) => return RequestError::not_found().into_http_response(),
- Err(err) => return err.into_http_response(),
- }
+ .await?
+ .ok_or_else(|| trc::ManageCause::NotFound.into_err())?
+ .into()
} else {
None
};
@@ -120,20 +114,20 @@ impl JMAP {
self.housekeeper_request(Event::Purge(PurgeType::Account(account_id)))
.await
}
- _ => RequestError::not_found().into_http_response(),
+ _ => Err(trc::ResourceCause::NotFound.into_err()),
}
}
- async fn housekeeper_request(&self, event: Event) -> HttpResponse {
- match self.inner.housekeeper_tx.send(event).await {
- Ok(_) => JsonResponse::new(json!({
- "data": (),
- }))
- .into_http_response(),
- Err(_) => {
- tracing::error!("Failed to send housekeeper event");
- RequestError::internal_server_error().into_http_response()
- }
- }
+ async fn housekeeper_request(&self, event: Event) -> trc::Result<HttpResponse> {
+ self.inner.housekeeper_tx.send(event).await.map_err(|err| {
+ trc::Cause::Thread
+ .reason(err)
+ .details("Failed to send housekeeper event")
+ })?;
+
+ Ok(JsonResponse::new(json!({
+ "data": (),
+ }))
+ .into_http_response())
}
}
diff --git a/crates/jmap/src/api/request.rs b/crates/jmap/src/api/request.rs
index 32a79704..58a9afc0 100644
--- a/crates/jmap/src/api/request.rs
+++ b/crates/jmap/src/api/request.rs
@@ -8,7 +8,7 @@ use std::sync::Arc;
use common::listener::ServerInstance;
use jmap_proto::{
- error::{method::MethodError, request::RequestError},
+ error::method::MethodError,
method::{
get, query,
set::{self},
@@ -26,7 +26,7 @@ impl JMAP {
request: Request,
access_token: Arc<AccessToken>,
instance: &Arc<ServerInstance>,
- ) -> Result<Response, RequestError> {
+ ) -> Response {
let mut response = Response::new(
access_token.state(),
request.created_ids.unwrap_or_default(),
@@ -104,7 +104,7 @@ impl JMAP {
response.created_ids.clear();
}
- Ok(response)
+ response
}
async fn handle_method_call(
diff --git a/crates/jmap/src/api/session.rs b/crates/jmap/src/api/session.rs
index 34687cbf..15c342d2 100644
--- a/crates/jmap/src/api/session.rs
+++ b/crates/jmap/src/api/session.rs
@@ -8,10 +8,10 @@ use std::sync::Arc;
use directory::QueryBy;
use jmap_proto::{
- error::request::RequestError,
request::capability::{Capability, Session},
types::{acl::Acl, collection::Collection, id::Id},
};
+use trc::AddContext;
use crate::{auth::AccessToken, JMAP};
@@ -20,7 +20,7 @@ impl JMAP {
&self,
base_url: String,
access_token: Arc<AccessToken>,
- ) -> Result<Session, RequestError> {
+ ) -> trc::Result<Session> {
let mut session = Session::new(base_url, &self.core.jmap.capabilities);
session.set_state(access_token.state());
session.set_primary_account(
@@ -41,7 +41,8 @@ impl JMAP {
&& self
.shared_documents(&access_token, *id, Collection::Mailbox, Acl::AddItems)
.await
- .map_or(true, |ids| ids.is_empty());
+ .caused_by(trc::location!())?
+ .is_empty();
session.add_account(
(*id).into(),
@@ -50,7 +51,7 @@ impl JMAP {
.directory
.query(QueryBy::Id(*id), false)
.await
- .unwrap_or_default()
+ .caused_by(trc::location!())?
.map(|p| p.name)
.unwrap_or_else(|| Id::from(*id).to_string()),
is_personal,
diff --git a/crates/jmap/src/auth/acl.rs b/crates/jmap/src/auth/acl.rs
index 39104a5e..676e77ef 100644
--- a/crates/jmap/src/auth/acl.rs
+++ b/crates/jmap/src/auth/acl.rs
@@ -49,7 +49,7 @@ impl JMAP {
let acl = Bitmap::<Acl>::from(acl_item.permissions);
let collection = Collection::from(acl_item.to_collection);
if !collection.is_valid() {
- return Err(trc::Cause::DataCorruption
+ return Err(trc::StoreCause::DataCorruption
.ctx(trc::Key::Reason, "Corrupted collection found in ACL key.")
.details(format!("{acl_item:?}"))
.account_id(grant_account_id)
diff --git a/crates/jmap/src/auth/authenticate.rs b/crates/jmap/src/auth/authenticate.rs
index e50ff679..86883455 100644
--- a/crates/jmap/src/auth/authenticate.rs
+++ b/crates/jmap/src/auth/authenticate.rs
@@ -48,7 +48,7 @@ impl JMAP {
self.authenticate_plain(&account, &secret, remote_ip, ServerProtocol::Http)
.await?
} else {
- return Err(trc::Cause::Authentication
+ return Err(trc::AuthCause::Error
.into_err()
.details("Failed to decode Basic auth request.")
.id(token)
@@ -65,7 +65,7 @@ impl JMAP {
} else {
// Enforce anonymous rate limit
self.is_anonymous_allowed(&remote_ip).await?;
- return Err(trc::Cause::Authentication
+ return Err(trc::AuthCause::Error
.into_err()
.reason("Unsupported authentication mechanism.")
.details(token)
@@ -87,7 +87,7 @@ impl JMAP {
// Enforce anonymous rate limit
self.is_anonymous_allowed(&remote_ip).await?;
- Err(trc::Cause::Authentication
+ Err(trc::AuthCause::Error
.into_err()
.details("Missing Authorization header.")
.caused_by(trc::location!()))
@@ -147,7 +147,7 @@ impl JMAP {
{
Ok(principal) => Ok(AccessToken::new(principal)),
Err(err) => {
- if !err.matches(trc::Cause::MissingTotp) {
+ if !err.matches(trc::Cause::Auth(trc::AuthCause::MissingTotp)) {
let _ = self.is_auth_allowed_hard(&remote_ip).await;
}
Err(err)
@@ -164,7 +164,7 @@ impl JMAP {
.await
{
Ok(Some(principal)) => self.update_access_token(AccessToken::new(principal)).await,
- Ok(None) => Err(trc::Cause::Authentication
+ Ok(None) => Err(trc::AuthCause::Error
.into_err()
.details("Account not found.")
.caused_by(trc::location!())),
diff --git a/crates/jmap/src/auth/oauth/auth.rs b/crates/jmap/src/auth/oauth/auth.rs
index ebdc49f5..22cc020b 100644
--- a/crates/jmap/src/auth/oauth/auth.rs
+++ b/crates/jmap/src/auth/oauth/auth.rs
@@ -6,7 +6,6 @@
use std::sync::Arc;
-use hyper::StatusCode;
use rand::distributions::Standard;
use serde_json::json;
use store::{
@@ -16,10 +15,7 @@ use store::{
};
use crate::{
- api::{
- http::ToHttpResponse, management::ManagementApiError, HtmlResponse, HttpRequest,
- HttpResponse, JsonResponse,
- },
+ api::{http::ToHttpResponse, HttpRequest, HttpResponse, JsonResponse},
auth::{oauth::OAuthStatus, AccessToken},
JMAP,
};
@@ -34,155 +30,133 @@ impl JMAP {
&self,
access_token: Arc<AccessToken>,
body: Option<Vec<u8>>,
- ) -> HttpResponse {
- match serde_json::from_slice::<OAuthCodeRequest>(body.as_deref().unwrap_or_default()) {
- Ok(request) => {
- let response = match request {
- OAuthCodeRequest::Code {
- client_id,
- redirect_uri,
- } => {
- // Validate clientId
- if client_id.len() > CLIENT_ID_MAX_LEN {
- return ManagementApiError::Other {
- details: "Client ID is invalid.".into(),
- }
- .into_http_response();
- } else if redirect_uri
- .as_ref()
- .map_or(false, |uri| !uri.starts_with("https://"))
- {
- return ManagementApiError::Other {
- details: "Redirect URI must be HTTPS.".into(),
- }
- .into_http_response();
- }
-
- // Generate client code
- let client_code = thread_rng()
- .sample_iter(Alphanumeric)
- .take(DEVICE_CODE_LEN)
- .map(char::from)
- .collect::<String>();
-
- // Serialize OAuth code
- let value = Bincode::new(OAuthCode {
- status: OAuthStatus::Authorized,
- account_id: access_token.primary_id(),
- client_id,
- params: redirect_uri.unwrap_or_default(),
- })
- .serialize();
+ ) -> trc::Result<HttpResponse> {
+ let request =
+ serde_json::from_slice::<OAuthCodeRequest>(body.as_deref().unwrap_or_default())
+ .map_err(|err| {
+ trc::Cause::Resource(trc::ResourceCause::BadParameters).from_json_error(err)
+ })?;
+
+ let response = match request {
+ OAuthCodeRequest::Code {
+ client_id,
+ redirect_uri,
+ } => {
+ // Validate clientId
+ if client_id.len() > CLIENT_ID_MAX_LEN {
+ return Err(trc::ManageCause::Error
+ .into_err()
+ .details("Client ID is invalid."));
+ } else if redirect_uri
+ .as_ref()
+ .map_or(false, |uri| !uri.starts_with("https://"))
+ {
+ return Err(trc::ManageCause::Error
+ .into_err()
+ .details("Redirect URI must be HTTPS."));
+ }
+
+ // Generate client code
+ let client_code = thread_rng()
+ .sample_iter(Alphanumeric)
+ .take(DEVICE_CODE_LEN)
+ .map(char::from)
+ .collect::<String>();
+
+ // Serialize OAuth code
+ let value = Bincode::new(OAuthCode {
+ status: OAuthStatus::Authorized,
+ account_id: access_token.primary_id(),
+ client_id,
+ params: redirect_uri.unwrap_or_default(),
+ })
+ .serialize();
+
+ // Insert client code
+ self.core
+ .storage
+ .lookup
+ .key_set(
+ format!("oauth:{client_code}").into_bytes(),
+ value,
+ self.core.jmap.oauth_expiry_auth_code.into(),
+ )
+ .await?;
+
+ #[cfg(not(feature = "enterprise"))]
+ let is_enterprise = false;
+ #[cfg(feature = "enterprise")]
+ let is_enterprise = self.core.is_enterprise_edition();
+
+ json!({
+ "data": {
+ "code": client_code,
+ "is_admin": access_token.is_super_user(),
+ "is_enterprise": is_enterprise,
+ },
+ })
+ }
+ OAuthCodeRequest::Device { code } => {
+ let mut success = false;
+
+ // Obtain code
+ if let Some(mut auth_code) = self
+ .core
+ .storage
+ .lookup
+ .key_get::<Bincode<OAuthCode>>(format!("oauth:{code}").into_bytes())
+ .await?
+ {
+ if auth_code.inner.status == OAuthStatus::Pending {
+ auth_code.inner.status = OAuthStatus::Authorized;
+ auth_code.inner.account_id = access_token.primary_id();
+ let device_code = std::mem::take(&mut auth_code.inner.params);
+ success = true;
+
+ // Delete issued user code
+ self.core
+ .storage
+ .lookup
+ .key_delete(format!("oauth:{code}").into_bytes())
+ .await?;
- // Insert client code
- if let Err(err) = self
- .core
+ // Update device code status
+ self.core
.storage
.lookup
.key_set(
- format!("oauth:{client_code}").into_bytes(),
- value,
+ format!("oauth:{device_code}").into_bytes(),
+ auth_code.serialize(),
self.core.jmap.oauth_expiry_auth_code.into(),
)
- .await
- {
- return err.into_http_response();
- }
-
- #[cfg(not(feature = "enterprise"))]
- let is_enterprise = false;
- #[cfg(feature = "enterprise")]
- let is_enterprise = self.core.is_enterprise_edition();
-
- json!({
- "data": {
- "code": client_code,
- "is_admin": access_token.is_super_user(),
- "is_enterprise": is_enterprise,
- },
- })
- }
- OAuthCodeRequest::Device { code } => {
- let mut success = false;
-
- // Obtain code
- match self
- .core
- .storage
- .lookup
- .key_get::<Bincode<OAuthCode>>(format!("oauth:{code}").into_bytes())
- .await
- {
- Ok(Some(mut auth_code))
- if auth_code.inner.status == OAuthStatus::Pending =>
- {
- auth_code.inner.status = OAuthStatus::Authorized;
- auth_code.inner.account_id = access_token.primary_id();
- let device_code = std::mem::take(&mut auth_code.inner.params);
- success = true;
-
- // Delete issued user code
- if let Err(err) = self
- .core
- .storage
- .lookup
- .key_delete(format!("oauth:{code}").into_bytes())
- .await
- {
- return err.into_http_response();
- }
-
- // Update device code status
- if let Err(err) = self
- .core
- .storage
- .lookup
- .key_set(
- format!("oauth:{device_code}").into_bytes(),
- auth_code.serialize(),
- self.core.jmap.oauth_expiry_auth_code.into(),
- )
- .await
- {
- return err.into_http_response();
- }
- }
- Err(err) => return err.into_http_response(),
- _ => (),
- }
-
- json!({
- "data": success,
- })
+ .await?;
}
- };
+ }
- JsonResponse::new(response).into_http_response()
+ json!({
+ "data": success,
+ })
}
- Err(err) => err.into_http_response(),
- }
+ };
+
+ Ok(JsonResponse::new(response).into_http_response())
}
pub async fn handle_device_auth(
&self,
req: &mut HttpRequest,
base_url: impl AsRef<str>,
- ) -> HttpResponse {
+ ) -> trc::Result<HttpResponse> {
// Parse form
- let client_id = match FormData::from_request(req, MAX_POST_LEN)
- .await
- .map(|mut p| p.remove("client_id"))
- {
- Ok(Some(client_id)) if client_id.len() < CLIENT_ID_MAX_LEN => client_id,
- Err(err) => return err,
- _ => {
- return HtmlResponse::with_status(
- StatusCode::BAD_REQUEST,
- "Client ID is invalid.".to_string(),
- )
- .into_http_response();
- }
- };
+ let client_id = FormData::from_request(req, MAX_POST_LEN)
+ .await?
+ .remove("client_id")
+ .filter(|client_id| client_id.len() < CLIENT_ID_MAX_LEN)
+ .ok_or_else(|| {
+ trc::ResourceCause::BadParameters
+ .into_err()
+ .details("Client ID is missing.")
+ })?;
// Generate device code
let device_code = thread_rng()
@@ -215,8 +189,7 @@ impl JMAP {
.serialize();
// Insert device code
- if let Err(err) = self
- .core
+ self.core
.storage
.lookup
.key_set(
@@ -224,14 +197,10 @@ impl JMAP {
oauth_code.clone(),
self.core.jmap.oauth_expiry_user_code.into(),
)
- .await
- {
- return err.into_http_response();
- }
+ .await?;
// Insert user code
- if let Err(err) = self
- .core
+ self.core
.storage
.lookup
.key_set(
@@ -239,14 +208,11 @@ impl JMAP {
oauth_code,
self.core.jmap.oauth_expiry_user_code.into(),
)
- .await
- {
- return err.into_http_response();
- }
+ .await?;
// Build response
let base_url = base_url.as_ref();
- JsonResponse::new(DeviceAuthResponse {
+ Ok(JsonResponse::new(DeviceAuthResponse {
verification_uri: format!("{}/authorize", base_url),
verification_uri_complete: format!("{}/authorize/?code={}", base_url, user_code),
device_code,
@@ -254,6 +220,6 @@ impl JMAP {
expires_in: self.core.jmap.oauth_expiry_user_code,
interval: 5,
})
- .into_http_response()
+ .into_http_response())
}
}
diff --git a/crates/jmap/src/auth/oauth/mod.rs b/crates/jmap/src/auth/oauth/mod.rs
index 6ff31a53..65c74e6f 100644
--- a/crates/jmap/src/auth/oauth/mod.rs
+++ b/crates/jmap/src/auth/oauth/mod.rs
@@ -6,13 +6,10 @@
use std::collections::HashMap;
-use hyper::{header::CONTENT_TYPE, StatusCode};
+use hyper::header::CONTENT_TYPE;
use serde::{Deserialize, Serialize};
-use crate::api::{
- http::{fetch_body, ToHttpResponse},
- HtmlResponse, HttpRequest, HttpResponse,
-};
+use crate::api::{http::fetch_body, HttpRequest};
pub mod auth;
pub mod token;
@@ -205,7 +202,7 @@ pub struct FormData {
}
impl FormData {
- pub async fn from_request(req: &mut HttpRequest, max_len: usize) -> Result<Self, HttpResponse> {
+ pub async fn from_request(req: &mut HttpRequest, max_len: usize) -> trc::Result<Self> {
match (
req.headers()
.get(CONTENT_TYPE)
@@ -229,11 +226,9 @@ impl FormData {
}
Ok(FormData { fields })
}
- _ => Err(HtmlResponse::with_status(
- StatusCode::BAD_REQUEST,
- "Invalid post request".to_string(),
- )
- .into_http_response()),
+ _ => Err(trc::ResourceCause::BadParameters
+ .into_err()
+ .details("Invalid post request")),
}
}
diff --git a/crates/jmap/src/auth/oauth/token.rs b/crates/jmap/src/auth/oauth/token.rs
index eda4230a..de44353e 100644
--- a/crates/jmap/src/auth/oauth/token.rs
+++ b/crates/jmap/src/auth/oauth/token.rs
@@ -30,12 +30,9 @@ use super::{
impl JMAP {
// Token endpoint
- pub async fn handle_token_request(&self, req: &mut HttpRequest) -> HttpResponse {
+ pub async fn handle_token_request(&self, req: &mut HttpRequest) -> trc::Result<HttpResponse> {
// Parse form
- let params = match FormData::from_request(req, MAX_POST_LEN).await {
- Ok(params) => params,
- Err(err) => return err,
- };
+ let params = FormData::from_request(req, MAX_POST_LEN).await?;
let grant_type = params.get("grant_type").unwrap_or_default();
let mut response = TokenResponse::error(ErrorType::InvalidGrant);
@@ -52,38 +49,35 @@ impl JMAP {
.storage
.lookup
.key_get::<Bincode<OAuthCode>>(format!("oauth:{code}").into_bytes())
- .await
+ .await?
{
- Ok(Some(auth_code)) => {
+ Some(auth_code) => {
let oauth = auth_code.inner;
if client_id != oauth.client_id || redirect_uri != oauth.params {
TokenResponse::error(ErrorType::InvalidClient)
} else if oauth.status == OAuthStatus::Authorized {
// Mark this token as issued
- if let Err(err) = self
- .core
+ self.core
.storage
.lookup
.key_delete(format!("oauth:{code}").into_bytes())
- .await
- {
- return err.into_http_response();
- }
+ .await?;
// Issue token
self.issue_token(oauth.account_id, &oauth.client_id, true)
.await
.map(TokenResponse::Granted)
- .unwrap_or_else(|err| {
- tracing::error!("Failed to generate OAuth token: {}", err);
- TokenResponse::error(ErrorType::InvalidRequest)
- })
+ .map_err(|err| {
+ trc::AuthCause::Error
+ .into_err()
+ .details(err)
+ .caused_by(trc::location!())
+ })?
} else {
TokenResponse::error(ErrorType::InvalidGrant)
}
}
- Ok(None) => TokenResponse::error(ErrorType::AccessDenied),
- Err(err) => return err.into_http_response(),
+ None => TokenResponse::error(ErrorType::AccessDenied),
}
} else {
TokenResponse::error(ErrorType::InvalidClient)
@@ -100,9 +94,9 @@ impl JMAP {
.storage
.lookup
.key_get::<Bincode<OAuthCode>>(format!("oauth:{device_code}").into_bytes())
- .await
+ .await?
{
- Ok(Some(auth_code)) => {
+ Some(auth_code) => {
let oauth = auth_code.inner;
response = if oauth.client_id != client_id {
TokenResponse::error(ErrorType::InvalidClient)
@@ -110,27 +104,22 @@ impl JMAP {
match oauth.status {
OAuthStatus::Authorized => {
// Mark this token as issued
- if let Err(err) = self
- .core
+ self.core
.storage
.lookup
.key_delete(format!("oauth:{device_code}").into_bytes())
- .await
- {
- return err.into_http_response();
- }
+ .await?;
// Issue token
self.issue_token(oauth.account_id, &oauth.client_id, true)
.await
.map(TokenResponse::Granted)
- .unwrap_or_else(|err| {
- tracing::error!(
- "Failed to generate OAuth token: {}",
- err
- );
- TokenResponse::error(ErrorType::InvalidRequest)
- })
+ .map_err(|err| {
+ trc::AuthCause::Error
+ .into_err()
+ .details(err)
+ .caused_by(trc::location!())
+ })?
}
OAuthStatus::Pending => {
TokenResponse::error(ErrorType::AuthorizationPending)
@@ -141,37 +130,36 @@ impl JMAP {
}
};
}
- Ok(None) => (),
- Err(err) => return err.into_http_response(),
+ None => (),
}
}
} else if grant_type.eq_ignore_ascii_case("refresh_token") {
- let todo = "return error";
if let Some(refresh_token) = params.get("refresh_token") {
- if let Ok((account_id, client_id, time_left)) = self
+ let (account_id, client_id, time_left) = self
.validate_access_token("refresh_token", refresh_token)
+ .await?;
+
+ // TODO: implement revoking client ids
+ response = self
+ .issue_token(
+ account_id,
+ &client_id,
+ time_left <= self.core.jmap.oauth_expiry_refresh_token_renew,
+ )
.await
- {
- // TODO: implement revoking client ids
- response = self
- .issue_token(
- account_id,
- &client_id,
- time_left <= self.core.jmap.oauth_expiry_refresh_token_renew,
- )
- .await
- .map(TokenResponse::Granted)
- .unwrap_or_else(|err| {
- tracing::debug!("Failed to refresh OAuth token: {}", err);
- TokenResponse::error(ErrorType::InvalidGrant)
- });
- }
+ .map(TokenResponse::Granted)
+ .map_err(|err| {
+ trc::AuthCause::Error
+ .into_err()
+ .details(err)
+ .caused_by(trc::location!())
+ })?;
} else {
response = TokenResponse::error(ErrorType::InvalidRequest);
}
}
- JsonResponse::with_status(
+ Ok(JsonResponse::with_status(
if response.is_error() {
StatusCode::BAD_REQUEST
} else {
@@ -179,7 +167,7 @@ impl JMAP {
},
response,
)
- .into_http_response()
+ .into_http_response())
}
async fn password_hash(&self, account_id: u32) -> Result<String, &'static str> {
@@ -293,7 +281,8 @@ impl JMAP {
) -> trc::Result<(u32, String, u64)> {
// Base64 decode token
let token = base64_decode(token_.as_bytes()).ok_or_else(|| {
- trc::Cause::Authentication
+ trc::AuthCause::Error
+ .into_err()
.ctx(trc::Key::Reason, "Failed to decode token")
.details(token_.to_string())
})?;
@@ -309,7 +298,8 @@ impl JMAP {
.into()
})
.ok_or_else(|| {
- trc::Cause::Authentication
+ trc::AuthCause::Error
+ .into_err()
.ctx(trc::Key::Reason, "Failed to decode token")
.details(token_.to_string())
})?;
@@ -321,14 +311,16 @@ impl JMAP {
.unwrap_or(0)
.saturating_sub(946684800); // Jan 1, 2000
if expiry <= now {
- return Err(trc::Cause::Authentication.ctx(trc::Key::Reason, "Token expired"));
+ return Err(trc::AuthCause::Error
+ .into_err()
+ .ctx(trc::Key::Reason, "Token expired"));
}
// Obtain password hash
let password_hash = self
.password_hash(account_id)
.await
- .map_err(|err| trc::Cause::Authentication.ctx(trc::Key::Details, err))?;
+ .map_err(|err| trc::AuthCause::Error.into_err().ctx(trc::Key::Details, err))?;
// Build context
let key = self.core.jmap.oauth_key.clone();
@@ -357,7 +349,8 @@ impl JMAP {
&nonce,
)
.map_err(|err| {
- trc::Cause::Authentication
+ trc::AuthCause::Error
+ .into_err()
.ctx(trc::Key::Details, "Failed to decode token")
.reason(err)
})?;
diff --git a/crates/jmap/src/auth/rate_limit.rs b/crates/jmap/src/auth/rate_limit.rs
index 78877688..3462b5cf 100644
--- a/crates/jmap/src/auth/rate_limit.rs
+++ b/crates/jmap/src/auth/rate_limit.rs
@@ -64,12 +64,12 @@ impl JMAP {
} else if access_token.is_super_user() {
Ok(InFlight::default())
} else {
- Err(trc::Cause::TooManyConcurrentRequests.into())
+ Err(trc::LimitCause::ConcurrentRequest.into_err())
}
} else if access_token.is_super_user() {
Ok(InFlight::default())
} else {
- Err(trc::Cause::TooManyRequests.into())
+ Err(trc::LimitCause::TooManyRequests.into_err())
}
}
@@ -84,7 +84,7 @@ impl JMAP {
.caused_by(trc::location!())?
.is_some()
{
- return Err(trc::Cause::TooManyRequests.into());
+ return Err(trc::LimitCause::TooManyRequests.into_err());
}
}
Ok(())
@@ -100,7 +100,7 @@ impl JMAP {
} else if access_token.is_super_user() {
Ok(InFlight::default())
} else {
- Err(trc::Cause::TooManyConcurrentUploads.into())
+ Err(trc::LimitCause::ConcurrentUpload.into_err())
}
}
@@ -117,7 +117,7 @@ impl JMAP {
.caused_by(trc::location!())?
.is_some()
{
- return Err(trc::Cause::TooManyAuthAttempts.into());
+ return Err(trc::AuthCause::TooManyAttempts.into_err());
}
}
Ok(())
@@ -134,7 +134,7 @@ impl JMAP {
.caused_by(trc::location!())?
.is_some()
{
- return Err(trc::Cause::TooManyAuthAttempts.into());
+ return Err(trc::AuthCause::TooManyAttempts.into_err());
}
}
Ok(())
diff --git a/crates/jmap/src/blob/upload.rs b/crates/jmap/src/blob/upload.rs
index ab841d91..13d1cf2b 100644
--- a/crates/jmap/src/blob/upload.rs
+++ b/crates/jmap/src/blob/upload.rs
@@ -211,7 +211,8 @@ impl JMAP {
&& used.count + 1 > self.core.jmap.upload_tmp_quota_amount))
&& !access_token.is_super_user()
{
- let err = Err(trc::Cause::OverBlobQuota
+ let err = Err(trc::LimitCause::BlobQuota
+ .into_err()
.ctx(trc::Key::Size, self.core.jmap.upload_tmp_quota_size)
.ctx(trc::Key::Total, self.core.jmap.upload_tmp_quota_amount));
diff --git a/crates/jmap/src/changes/write.rs b/crates/jmap/src/changes/write.rs
index 36ebd9ef..2047a300 100644
--- a/crates/jmap/src/changes/write.rs
+++ b/crates/jmap/src/changes/write.rs
@@ -28,7 +28,8 @@ impl JMAP {
pub fn generate_snowflake_id(&self) -> trc::Result<u64> {
self.inner.snowflake_id.generate().ok_or_else(|| {
- trc::Cause::Unexpected
+ trc::StoreCause::Unexpected
+ .into_err()
.caused_by(trc::location!())
.ctx(trc::Key::Reason, "Failed to generate snowflake id.")
})
@@ -57,7 +58,7 @@ impl JMAP {
pub async fn delete_changes(&self, account_id: u32, before: Duration) -> trc::Result<()> {
let reference_cid = self.inner.snowflake_id.past_id(before).ok_or_else(|| {
- trc::Cause::Unexpected
+ trc::StoreCause::Unexpected
.caused_by(trc::location!())
.ctx(trc::Key::Reason, "Failed to generate reference change id.")
})?;
diff --git a/crates/jmap/src/email/crypto.rs b/crates/jmap/src/email/crypto.rs
index 0119ad80..b3c779b4 100644
--- a/crates/jmap/src/email/crypto.rs
+++ b/crates/jmap/src/email/crypto.rs
@@ -7,15 +7,13 @@
use std::{borrow::Cow, collections::BTreeSet, fmt::Display, io::Cursor, sync::Arc};
use crate::{
- api::{http::ToHttpResponse, management::ManagementApiError, HttpResponse, JsonResponse},
+ api::{http::ToHttpResponse, HttpResponse, JsonResponse},
auth::AccessToken,
JMAP,
};
use aes::cipher::{block_padding::Pkcs7, BlockEncryptMut, KeyIvInit};
-use jmap_proto::{
- error::request::RequestError,
- types::{collection::Collection, property::Property},
-};
+use directory::backend::internal::manage;
+use jmap_proto::types::{collection::Collection, property::Property};
use mail_builder::{encoders::base64::base64_encode_mime, mime::make_boundary};
use mail_parser::{decoders::base64::base64_decode, Message, MessageParser, MimeHeaders};
use openpgp::{
@@ -608,12 +606,16 @@ impl Deserialize for EncryptionParams {
fn deserialize(bytes: &[u8]) -> trc::Result<Self> {
let version = *bytes
.first()
- .ok_or_else(|| trc::Cause::DataCorruption.caused_by(trc::location!()))?;
+ .ok_or_else(|| trc::StoreCause::DataCorruption.caused_by(trc::location!()))?;
match version {
- 1 if bytes.len() > 1 => bincode::deserialize(&bytes[1..])
- .map_err(|err| trc::Error::from(err).caused_by(trc::location!())),
-
- _ => Err(trc::Cause::Deserialize
+ 1 if bytes.len() > 1 => bincode::deserialize(&bytes[1..]).map_err(|err| {
+ trc::Cause::Store(trc::StoreCause::Deserialize)
+ .from_bincode_error(err)
+ .caused_by(trc::location!())
+ }),
+
+ _ => Err(trc::StoreCause::Deserialize
+ .into_err()
.caused_by(trc::location!())
.ctx(trc::Key::Value, version as u64)),
}
@@ -627,66 +629,48 @@ impl ToBitmaps for &EncryptionParams {
}
impl JMAP {
- pub async fn handle_crypto_get(&self, access_token: Arc<AccessToken>) -> HttpResponse {
- match self
+ pub async fn handle_crypto_get(
+ &self,
+ access_token: Arc<AccessToken>,
+ ) -> trc::Result<HttpResponse> {
+ let params = self
.get_property::<EncryptionParams>(
access_token.primary_id(),
Collection::Principal,
0,
Property::Parameters,
)
- .await
- {
- Ok(params) => {
- let ec = params
- .map(|params| {
- let method = params.method;
- let algo = params.algo;
- let mut certs = Vec::new();
- certs.extend_from_slice(b"-----STALWART CERTIFICATE-----\r\n");
- let _ = base64_encode_mime(
- &Bincode::new(params).serialize(),
- &mut certs,
- false,
- );
- certs.extend_from_slice(b"\r\n");
- let certs = String::from_utf8(certs).unwrap_or_default();
-
- match method {
- EncryptionMethod::PGP => EncryptionType::PGP { algo, certs },
- EncryptionMethod::SMIME => EncryptionType::SMIME { algo, certs },
- }
- })
- .unwrap_or(EncryptionType::Disabled);
-
- JsonResponse::new(json!({
- "data": ec,
- }))
- .into_http_response()
- }
- Err(err) => {
- tracing::warn!(
- context = "store",
- event = "error",
- reason = ?err,
- "Database error while fetching encryption parameters"
- );
+ .await?;
+ let ec = params
+ .map(|params| {
+ let method = params.method;
+ let algo = params.algo;
+ let mut certs = Vec::new();
+ certs.extend_from_slice(b"-----STALWART CERTIFICATE-----\r\n");
+ let _ = base64_encode_mime(&Bincode::new(params).serialize(), &mut certs, false);
+ certs.extend_from_slice(b"\r\n");
+ let certs = String::from_utf8(certs).unwrap_or_default();
+
+ match method {
+ EncryptionMethod::PGP => EncryptionType::PGP { algo, certs },
+ EncryptionMethod::SMIME => EncryptionType::SMIME { algo, certs },
+ }
+ })
+ .unwrap_or(EncryptionType::Disabled);
- RequestError::internal_server_error().into_http_response()
- }
- }
+ Ok(JsonResponse::new(json!({
+ "data": ec,
+ }))
+ .into_http_response())
}
pub async fn handle_crypto_post(
&self,
access_token: Arc<AccessToken>,
body: Option<Vec<u8>>,
- ) -> HttpResponse {
- let request =
- match serde_json::from_slice::<EncryptionType>(body.as_deref().unwrap_or_default()) {
- Ok(request) => request,
- Err(err) => return err.into_http_response(),
- };
+ ) -> trc::Result<HttpResponse> {
+ let request = serde_json::from_slice::<EncryptionType>(body.as_deref().unwrap_or_default())
+ .map_err(|err| trc::ResourceCause::BadParameters.into_err().reason(err))?;
let (method, algo, certs) = match request {
EncryptionType::PGP { algo, certs } => (EncryptionMethod::PGP, algo, certs),
@@ -699,32 +683,27 @@ impl JMAP {
.with_collection(Collection::Principal)
.update_document(0)
.value(Property::Parameters, (), F_VALUE | F_CLEAR);
- return match self.core.storage.data.write(batch.build()).await {
- Ok(_) => JsonResponse::new(json!({
- "data": (),
- }))
- .into_http_response(),
- Err(err) => err.into_http_response(),
- };
+ self.core.storage.data.write(batch.build()).await?;
+ return Ok(JsonResponse::new(json!({
+ "data": (),
+ }))
+ .into_http_response());
}
};
// Make sure Encryption is enabled
if !self.core.jmap.encrypt {
- return ManagementApiError::Unsupported {
- details: "Encryption-at-rest has been disabled by the system administrator".into(),
- }
- .into_http_response();
+ return Err(manage::unsupported(
+ "Encryption-at-rest has been disabled by the system administrator",
+ ));
}
// Parse certificates
- let params = match try_parse_certs(method, certs.into_bytes()) {
- Ok(certs) => EncryptionParams {
- method,
- algo,
- certs,
- },
- Err(err) => return ManagementApiError::from(err).into_http_response(),
+ let params = EncryptionParams {
+ method,
+ algo,
+ certs: try_parse_certs(method, certs.into_bytes())
+ .map_err(|err| manage::error(err, None::<u32>))?,
};
// Try a test encryption
@@ -734,7 +713,7 @@ impl JMAP {
.encrypt(&params)
.await
{
- return ManagementApiError::from(message).into_http_response();
+ return Err(manage::error(message, None::<u32>));
}
// Save encryption params
@@ -745,13 +724,12 @@ impl JMAP {
.with_collection(Collection::Principal)
.update_document(0)
.value(Property::Parameters, &params, F_VALUE);
- match self.core.storage.data.write(batch.build()).await {
- Ok(_) => JsonResponse::new(json!({
- "data": num_certs,
- }))
- .into_http_response(),
- Err(err) => err.into_http_response(),
- }
+ self.core.storage.data.write(batch.build()).await?;
+
+ Ok(JsonResponse::new(json!({
+ "data": num_certs,
+ }))
+ .into_http_response())
}
}
diff --git a/crates/jmap/src/email/delete.rs b/crates/jmap/src/email/delete.rs
index 7edebd9d..87c1ebd0 100644
--- a/crates/jmap/src/email/delete.rs
+++ b/crates/jmap/src/email/delete.rs
@@ -337,7 +337,8 @@ impl JMAP {
return Ok(());
}
let reference_cid = self.inner.snowflake_id.past_id(period).ok_or_else(|| {
- trc::Cause::Unexpected
+ trc::StoreCause::Unexpected
+ .into_err()
.caused_by(trc::location!())
.ctx(trc::Key::Reason, "Failed to generate reference cid.")
})?;
diff --git a/crates/jmap/src/email/import.rs b/crates/jmap/src/email/import.rs
index c154181c..07c7bbbc 100644
--- a/crates/jmap/src/email/import.rs
+++ b/crates/jmap/src/email/import.rs
@@ -129,7 +129,7 @@ impl JMAP {
response.created.append(id, email.into());
}
Err(mut err) => match err.as_ref() {
- trc::Cause::OverQuota => {
+ trc::Cause::Limit(trc::LimitCause::Quota) => {
response.not_created.append(
id,
SetError::new(SetErrorType::OverQuota)
diff --git a/crates/jmap/src/email/ingest.rs b/crates/jmap/src/email/ingest.rs
index 2e08ba5c..2c9bada6 100644
--- a/crates/jmap/src/email/ingest.rs
+++ b/crates/jmap/src/email/ingest.rs
@@ -84,7 +84,7 @@ impl JMAP {
.await
.caused_by(trc::location!())?
{
- return Err(trc::Cause::OverQuota.into_err());
+ return Err(trc::LimitCause::Quota.into_err());
}
// Parse message
@@ -224,7 +224,10 @@ impl JMAP {
}
}
Err(EncryptMessageError::Error(err)) => {
- trc::bail!(trc::Cause::Crypto.caused_by(trc::location!()).reason(err));
+ trc::bail!(trc::StoreCause::Crypto
+ .into_err()
+ .caused_by(trc::location!())
+ .reason(err));
}
_ => unreachable!(),
}
@@ -494,7 +497,7 @@ impl JMAP {
match self.core.storage.data.write(batch.build()).await {
Ok(_) => return Ok(Some(thread_id)),
- Err(err) if err.matches(trc::Cause::AssertValue) && try_count < MAX_RETRIES => {
+ Err(err) if err.is_assertion_failure() && try_count < MAX_RETRIES => {
let backoff = rand::thread_rng().gen_range(50..=300);
tokio::time::sleep(Duration::from_millis(backoff)).await;
try_count += 1;
diff --git a/crates/jmap/src/email/set.rs b/crates/jmap/src/email/set.rs
index 9990ec41..e491a41a 100644
--- a/crates/jmap/src/email/set.rs
+++ b/crates/jmap/src/email/set.rs
@@ -726,7 +726,7 @@ impl JMAP {
Ok(message) => {
response.created.insert(id, message.into());
}
- Err(err) if err.matches(trc::Cause::OverQuota) => {
+ Err(err) if err.matches(trc::Cause::Limit(trc::LimitCause::Quota)) => {
response.not_created.append(
id,
SetError::new(SetErrorType::OverQuota)
@@ -962,7 +962,7 @@ impl JMAP {
// Add to updated list
response.updated.append(id, None);
}
- Err(err) if err.matches(trc::Cause::AssertValue) => {
+ Err(err) if err.is_assertion_failure() => {
response.not_updated.append(
id,
SetError::forbidden().with_description(
diff --git a/crates/jmap/src/mailbox/set.rs b/crates/jmap/src/mailbox/set.rs
index d95187a7..da94a808 100644
--- a/crates/jmap/src/mailbox/set.rs
+++ b/crates/jmap/src/mailbox/set.rs
@@ -128,7 +128,7 @@ impl JMAP {
ctx.mailbox_ids.insert(document_id);
ctx.response.created(id, document_id);
}
- Err(err) if err.matches(trc::Cause::AssertValue) => {
+ Err(err) if err.is_assertion_failure() => {
ctx.response.not_created.append(
id,
SetError::forbidden().with_description(
@@ -221,7 +221,7 @@ impl JMAP {
Ok(_) => {
changes.log_update(Collection::Mailbox, document_id);
}
- Err(err) if err.matches(trc::Cause::AssertValue) => {
+ Err(err) if err.is_assertion_failure() => {
ctx.response.not_updated.append(id, SetError::forbidden().with_description(
"Another process modified this mailbox, please try again.",
));
@@ -391,7 +391,7 @@ impl JMAP {
Collection::Email,
Id::from_parts(thread_id, message_id),
),
- Err(err) if err.matches(trc::Cause::AssertValue) => {
+ Err(err) if err.is_assertion_failure() => {
return Ok(Err(SetError::forbidden().with_description(
concat!(
"Another process modified a message in this mailbox ",
@@ -469,7 +469,7 @@ impl JMAP {
changes.log_delete(Collection::Mailbox, document_id);
Ok(Ok(did_remove_emails))
}
- Err(err) if err.matches(trc::Cause::AssertValue) => Ok(Err(SetError::forbidden()
+ Err(err) if err.is_assertion_failure() => Ok(Err(SetError::forbidden()
.with_description(concat!(
"Another process modified this mailbox ",
"while deleting it, please try again."
diff --git a/crates/jmap/src/push/get.rs b/crates/jmap/src/push/get.rs
index a5f9268a..c3d35eaa 100644
--- a/crates/jmap/src/push/get.rs
+++ b/crates/jmap/src/push/get.rs
@@ -128,7 +128,8 @@ impl JMAP {
})
.await?
.ok_or_else(|| {
- trc::Cause::NotFound
+ trc::StoreCause::NotFound
+ .into_err()
.caused_by(trc::location!())
.document_id(document_id)
})?;
@@ -138,7 +139,7 @@ impl JMAP {
.get(&Property::Expires)
.and_then(|p| p.as_date())
.ok_or_else(|| {
- trc::Cause::Unexpected
+ trc::StoreCause::Unexpected
.caused_by(trc::location!())
.document_id(document_id)
})?
@@ -174,7 +175,7 @@ impl JMAP {
.remove(&Property::Value)
.and_then(|p| p.try_unwrap_string())
.ok_or_else(|| {
- trc::Cause::Unexpected
+ trc::StoreCause::Unexpected
.caused_by(trc::location!())
.document_id(document_id)
})?;
@@ -183,7 +184,7 @@ impl JMAP {
.remove(&Property::Url)
.and_then(|p| p.try_unwrap_string())
.ok_or_else(|| {
- trc::Cause::Unexpected
+ trc::StoreCause::Unexpected
.caused_by(trc::location!())
.document_id(document_id)
})?;
diff --git a/crates/jmap/src/services/index.rs b/crates/jmap/src/services/index.rs
index 22f9de05..9e712c9d 100644
--- a/crates/jmap/src/services/index.rs
+++ b/crates/jmap/src/services/index.rs
@@ -222,7 +222,7 @@ impl JMAP {
.set(event.value_class(), (now() + INDEX_LOCK_EXPIRY).serialize());
match self.core.storage.data.write(batch.build()).await {
Ok(_) => true,
- Err(err) if err.matches(trc::Cause::AssertValue) => {
+ Err(err) if err.is_assertion_failure() => {
tracing::trace!(
context = "queue",
event = "locked",
diff --git a/crates/jmap/src/services/ingest.rs b/crates/jmap/src/services/ingest.rs
index 49021621..8df7aea0 100644
--- a/crates/jmap/src/services/ingest.rs
+++ b/crates/jmap/src/services/ingest.rs
@@ -140,7 +140,7 @@ impl JMAP {
}
}
Err(mut err) => match err.as_ref() {
- trc::Cause::OverQuota => {
+ trc::Cause::Limit(trc::LimitCause::Quota) => {
*status = DeliveryResult::TemporaryFailure {
reason: "Mailbox over quota.".into(),
}
diff --git a/crates/jmap/src/sieve/get.rs b/crates/jmap/src/sieve/get.rs
index 4e4c6d51..a1882ace 100644
--- a/crates/jmap/src/sieve/get.rs
+++ b/crates/jmap/src/sieve/get.rs
@@ -192,7 +192,8 @@ impl JMAP {
)
.await?
.ok_or_else(|| {
- trc::Cause::NotFound
+ trc::StoreCause::NotFound
+ .into_err()
.caused_by(trc::location!())
.document_id(document_id)
})?;
@@ -205,7 +206,8 @@ impl JMAP {
.and_then(|v| v.as_blob_id())
.and_then(|v| (v.section.as_ref()?.size, v).into())
.ok_or_else(|| {
- trc::Cause::NotFound
+ trc::StoreCause::NotFound
+ .into_err()
.caused_by(trc::location!())
.document_id(document_id)
})?;
@@ -215,7 +217,8 @@ impl JMAP {
.get_blob(&blob_id.hash, 0..usize::MAX)
.await?
.ok_or_else(|| {
- trc::Cause::NotFound
+ trc::StoreCause::NotFound
+ .into_err()
.caused_by(trc::location!())
.document_id(document_id)
})?;
@@ -230,7 +233,8 @@ impl JMAP {
// Deserialization failed, probably because the script compiler version changed
match self.core.sieve.untrusted_compiler.compile(
script_bytes.get(0..script_offset).ok_or_else(|| {
- trc::Cause::NotFound
+ trc::StoreCause::NotFound
+ .into_err()
.caused_by(trc::location!())
.document_id(document_id)
})?,
@@ -274,7 +278,7 @@ impl JMAP {
Ok((sieve.inner, new_script_object))
}
- Err(error) => Err(trc::Cause::Unexpected
+ Err(error) => Err(trc::StoreCause::Unexpected
.caused_by(trc::location!())
.reason(error)
.details("Failed to compile Sieve script")),
diff --git a/crates/jmap/src/sieve/set.rs b/crates/jmap/src/sieve/set.rs
index 3512b950..7569c788 100644
--- a/crates/jmap/src/sieve/set.rs
+++ b/crates/jmap/src/sieve/set.rs
@@ -159,7 +159,8 @@ impl JMAP {
.inner
.blob_id()
.ok_or_else(|| {
- trc::Cause::NotFound
+ trc::StoreCause::NotFound
+ .into_err()
.caused_by(trc::location!())
.document_id(document_id)
})?
@@ -225,7 +226,7 @@ impl JMAP {
changes.log_update(Collection::SieveScript, document_id);
match self.core.storage.data.write(batch.build()).await {
Ok(_) => (),
- Err(err) if err.matches(trc::Cause::AssertValue) => {
+ Err(err) if err.is_assertion_failure() => {
ctx.response.not_updated.append(id, SetError::forbidden().with_description(
"Another process modified this sieve, please try again.",
));
@@ -336,7 +337,8 @@ impl JMAP {
)
.await?
.ok_or_else(|| {
- trc::Cause::NotFound
+ trc::StoreCause::NotFound
+ .into_err()
.caused_by(trc::location!())
.document_id(document_id)
})?;
@@ -354,7 +356,8 @@ impl JMAP {
// Delete record
let mut batch = BatchBuilder::new();
let blob_id = obj.inner.blob_id().ok_or_else(|| {
- trc::Cause::NotFound
+ trc::StoreCause::NotFound
+ .into_err()
.caused_by(trc::location!())
.document_id(document_id)
})?;
@@ -616,7 +619,7 @@ impl JMAP {
if !changed_ids.is_empty() {
match self.core.storage.data.write(batch.build()).await {
Ok(_) => (),
- Err(err) if err.matches(trc::Cause::AssertValue) => {
+ Err(err) if err.is_assertion_failure() => {
return Ok(vec![]);
}
Err(err) => {
diff --git a/crates/jmap/src/vacation/set.rs b/crates/jmap/src/vacation/set.rs
index 749e5ac5..791e39f1 100644
--- a/crates/jmap/src/vacation/set.rs
+++ b/crates/jmap/src/vacation/set.rs
@@ -206,7 +206,11 @@ impl JMAP {
== Some(&Value::Bool(true));
value
})
- .ok_or_else(|| trc::Cause::NotFound.caused_by(trc::location!()))?
+ .ok_or_else(|| {
+ trc::StoreCause::NotFound
+ .into_err()
+ .caused_by(trc::location!())
+ })?
.into()
} else {
None
@@ -250,7 +254,8 @@ impl JMAP {
if let Some(current) = obj.current() {
let current_blob_id = current.inner.blob_id().ok_or_else(|| {
- trc::Cause::NotFound
+ trc::StoreCause::NotFound
+ .into_err()
.caused_by(trc::location!())
.document_id(document_id.unwrap_or(u32::MAX))
})?;
@@ -429,7 +434,7 @@ impl JMAP {
Ok(script)
}
- Err(err) => Err(trc::Cause::Unexpected
+ Err(err) => Err(trc::StoreCause::Unexpected
.caused_by(trc::location!())
.reason(err)
.details("Vacation Sieve Script failed to compile.")),
diff --git a/crates/jmap/src/websocket/stream.rs b/crates/jmap/src/websocket/stream.rs
index 852a9e21..2a138425 100644
--- a/crates/jmap/src/websocket/stream.rs
+++ b/crates/jmap/src/websocket/stream.rs
@@ -75,23 +75,16 @@ impl JMAP {
self.core.jmap.request_max_size,
) {
Ok(WebSocketMessage::Request(request)) => {
- match self
+ let response = self
.handle_request(
request.request,
access_token.clone(),
&instance,
)
- .await
- {
- Ok(response) => {
- WebSocketResponse::from_response(response, request.id)
- .to_json()
- }
- Err(err) => {
- WebSocketRequestError::from_error(err, request.id)
- .to_json()
- }
- }
+ .await;
+
+ WebSocketResponse::from_response(response, request.id)
+ .to_json()
}
Ok(WebSocketMessage::PushEnable(push_enable)) => {
change_types = if !push_enable.data_types.is_empty() {
@@ -105,7 +98,11 @@ impl JMAP {
change_types = Bitmap::new();
continue;
}
- Err(err) => err.to_json(),
+ Err(err) => {
+ let todo = "fix";
+ //err.to_json()
+ todo!()
+ },
};
if let Err(err) = stream.send(Message::Text(response)).await {
tracing::debug!(parent: &span, error = ?err, "Failed to send text message");
diff --git a/crates/jmap/src/websocket/upgrade.rs b/crates/jmap/src/websocket/upgrade.rs
index 5c47e0eb..b2494995 100644
--- a/crates/jmap/src/websocket/upgrade.rs
+++ b/crates/jmap/src/websocket/upgrade.rs
@@ -8,14 +8,13 @@ use std::sync::Arc;
use common::listener::ServerInstance;
use http_body_util::{BodyExt, Full};
-use hyper::{body::Bytes, Response, StatusCode};
+use hyper::{body::Bytes, Response};
use hyper_util::rt::TokioIo;
-use jmap_proto::error::request::RequestError;
use tokio_tungstenite::WebSocketStream;
use tungstenite::{handshake::derive_accept_key, protocol::Role};
use crate::{
- api::{http::ToHttpResponse, HttpRequest, HttpResponse},
+ api::{HttpRequest, HttpResponse},
auth::AccessToken,
JMAP,
};
@@ -26,7 +25,7 @@ impl JMAP {
req: HttpRequest,
access_token: Arc<AccessToken>,
instance: Arc<ServerInstance>,
- ) -> HttpResponse {
+ ) -> trc::Result<HttpResponse> {
let headers = req.headers();
if headers
.get(hyper::header::CONNECTION)
@@ -37,12 +36,13 @@ impl JMAP {
.and_then(|h| h.to_str().ok())
!= Some("websocket")
{
- return RequestError::blank(
- StatusCode::BAD_REQUEST.as_u16(),
- "WebSocket upgrade failed",
- "Missing or Invalid Connection or Upgrade headers.",
- )
- .into_http_response();
+ return Err(trc::ResourceCause::BadParameters
+ .into_err()
+ .details("WebSocket upgrade failed")
+ .ctx(
+ trc::Key::Reason,
+ "Missing or Invalid Connection or Upgrade headers.",
+ ));
}
let derived_key = match (
headers
@@ -54,12 +54,13 @@ impl JMAP {
) {
(Some(key), Some("13")) => derive_accept_key(key.as_bytes()),
_ => {
- return RequestError::blank(
- StatusCode::BAD_REQUEST.as_u16(),
- "WebSocket upgrade failed",
- "Missing or Invalid Sec-WebSocket-Key headers.",
- )
- .into_http_response();
+ return Err(trc::ResourceCause::BadParameters
+ .into_err()
+ .details("WebSocket upgrade failed")
+ .ctx(
+ trc::Key::Reason,
+ "Missing or Invalid Sec-WebSocket-Key headers.",
+ ));
}
};
@@ -87,7 +88,7 @@ impl JMAP {
}
});
- Response::builder()
+ Ok(Response::builder()
.status(hyper::StatusCode::SWITCHING_PROTOCOLS)
.header(hyper::header::CONNECTION, "upgrade")
.header(hyper::header::UPGRADE, "websocket")
@@ -98,6 +99,6 @@ impl JMAP {
.map_err(|never| match never {})
.boxed(),
)
- .unwrap()
+ .unwrap())
}
}
diff --git a/crates/managesieve/Cargo.toml b/crates/managesieve/Cargo.toml
index c9510411..eb3801f9 100644
--- a/crates/managesieve/Cargo.toml
+++ b/crates/managesieve/Cargo.toml
@@ -13,6 +13,7 @@ directory = { path = "../directory" }
common = { path = "../common" }
store = { path = "../store" }
utils = { path = "../utils" }
+trc = { path = "../trc" }
mail-parser = { version = "0.9", features = ["full_encoding", "ludicrous_mode"] }
mail-send = { version = "0.4", default-features = false, features = ["cram-md5", "ring", "tls12"] }
sieve-rs = { version = "0.5" }
diff --git a/crates/managesieve/src/core/client.rs b/crates/managesieve/src/core/client.rs
index 53791536..71e33d5b 100644
--- a/crates/managesieve/src/core/client.rs
+++ b/crates/managesieve/src/core/client.rs
@@ -4,16 +4,17 @@
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
-use common::listener::SessionStream;
+use common::listener::{SessionResult, SessionStream};
use imap_proto::receiver::{self, Request};
use jmap_proto::types::{collection::Collection, property::Property};
use store::query::Filter;
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
+use trc::AddContext;
-use super::{Command, ResponseCode, ResponseType, Session, State, StatusResponse};
+use super::{Command, ResponseCode, SerializeResponse, Session, State, StatusResponse};
impl<T: SessionStream> Session<T> {
- pub async fn ingest(&mut self, bytes: &[u8]) -> Result<bool, ()> {
+ pub async fn ingest(&mut self, bytes: &[u8]) -> SessionResult {
/*let tmp = "dd";
for line in String::from_utf8_lossy(bytes).split("\r\n") {
println!("<- {:?}", &line[..std::cmp::min(line.len(), 100)]);
@@ -29,8 +30,11 @@ impl<T: SessionStream> Session<T> {
Ok(request) => {
requests.push(request);
}
- Err(response) => {
- self.write(&response.into_bytes()).await?;
+ Err(err) => {
+ if let Err(err) = self.write_error(err).await {
+ tracing::error!(parent: &self.span, event = "error", error = ?err);
+ return SessionResult::Close;
+ }
}
},
Err(receiver::Error::NeedsMoreData) => {
@@ -41,15 +45,21 @@ impl<T: SessionStream> Session<T> {
break;
}
Err(receiver::Error::Error { response }) => {
- self.write(&StatusResponse::no(response.message).into_bytes())
- .await?;
+ if let Err(err) = self
+ .write(&StatusResponse::no(response.message).into_bytes())
+ .await
+ {
+ tracing::error!(parent: &self.span, event = "error", error = ?err);
+ return SessionResult::Close;
+ }
break;
}
}
}
for request in requests {
- match match request.command {
+ let command = request.command;
+ match match command {
Command::ListScripts => self.handle_listscripts().await,
Command::PutScript => self.handle_putscript(request).await,
Command::SetActive => self.handle_setactive(request).await,
@@ -60,39 +70,46 @@ impl<T: SessionStream> Session<T> {
Command::HaveSpace => self.handle_havespace(request).await,
Command::Capability => self.handle_capability("").await,
Command::Authenticate => self.handle_authenticate(request).await,
- Command::StartTls => {
- self.write(b"OK Begin TLS negotiation now\r\n").await?;
- return Ok(false);
- }
+ Command::StartTls => self.handle_start_tls().await,
Command::Logout => self.handle_logout().await,
Command::Noop => self.handle_noop(request).await,
Command::Unauthenticate => self.handle_unauthenticate().await,
} {
Ok(response) => {
- self.write(&response).await?;
+ if let Err(err) = self.write(&response).await {
+ tracing::error!(parent: &self.span, event = "error", error = ?err);
+ return SessionResult::Close;
+ }
+
+ match command {
+ Command::Logout => return SessionResult::Close,
+ Command::StartTls => return SessionResult::UpgradeTls,
+ _ => (),
+ }
}
Err(err) => {
- let disconnect = matches!(err.rtype, ResponseType::Bye | ResponseType::Ok);
- self.write(&err.into_bytes()).await?;
- if disconnect {
- return Err(());
+ if let Err(err) = self.write_error(err).await {
+ tracing::error!(parent: &self.span, event = "error", error = ?err);
+ return SessionResult::Close;
}
}
}
}
if let Some(needs_literal) = needs_literal {
- self.write(format!("OK Ready for {} bytes.\r\n", needs_literal).as_bytes())
- .await?;
+ if let Err(err) = self
+ .write(format!("OK Ready for {} bytes.\r\n", needs_literal).as_bytes())
+ .await
+ {
+ tracing::error!(parent: &self.span, event = "error", error = ?err);
+ return SessionResult::Close;
+ }
}
- Ok(true)
+ SessionResult::Continue
}
- async fn validate_request(
- &self,
- command: Request<Command>,
- ) -> Result<Request<Command>, StatusResponse> {
+ async fn validate_request(&self, command: Request<Command>) -> trc::Result<Request<Command>> {
match &command.command {
Command::Capability | Command::Logout | Command::Noop => Ok(command),
Command::Authenticate => {
@@ -100,18 +117,24 @@ impl<T: SessionStream> Session<T> {
if self.stream.is_tls() || self.jmap.core.imap.allow_plain_auth {
Ok(command)
} else {
- Err(StatusResponse::no("Cannot authenticate over plain-text.")
- .with_code(ResponseCode::EncryptNeeded))
+ Err(trc::Cause::ManageSieve
+ .into_err()
+ .code(ResponseCode::EncryptNeeded)
+ .details("Cannot authenticate over plain-text."))
}
} else {
- Err(StatusResponse::no("Already authenticated."))
+ Err(trc::Cause::ManageSieve
+ .into_err()
+ .details("Already authenticated."))
}
}
Command::StartTls => {
if !self.stream.is_tls() {
Ok(command)
} else {
- Err(StatusResponse::no("Already in TLS mode."))
+ Err(trc::Cause::ManageSieve
+ .into_err()
+ .details("Already in TLS mode."))
}
}
Command::HaveSpace
@@ -125,7 +148,7 @@ impl<T: SessionStream> Session<T> {
| Command::Unauthenticate => {
if let State::Authenticated { access_token, .. } = &self.state {
if let Some(rate) = &self.jmap.core.imap.rate_requests {
- match self
+ if self
.jmap
.core
.storage
@@ -136,18 +159,22 @@ impl<T: SessionStream> Session<T> {
true,
)
.await
+ .caused_by(trc::location!())?
+ .is_none()
{
- Ok(None) => Ok(command),
- Ok(Some(_)) => Err(StatusResponse::no("Too many requests")
- .with_code(ResponseCode::TryLater)),
- Err(_) => Err(StatusResponse::no("Internal server error")
- .with_code(ResponseCode::TryLater)),
+ Ok(command)
+ } else {
+ Err(trc::LimitCause::TooManyRequests
+ .into_err()
+ .code(ResponseCode::TryLater))
}
} else {
Ok(command)
}
} else {
- Err(StatusResponse::no("Not authenticated."))
+ Err(trc::Cause::ManageSieve
+ .into_err()
+ .details("Not authenticated."))
}
}
}
@@ -156,54 +183,51 @@ impl<T: SessionStream> Session<T> {
impl<T: AsyncWrite + AsyncRead + Unpin> Session<T> {
#[inline(always)]
- pub async fn write(&mut self, bytes: &[u8]) -> Result<(), ()> {
- let err = match self.stream.write_all(bytes).await {
- Ok(_) => match self.stream.flush().await {
- Ok(_) => {
- tracing::trace!(parent: &self.span,
- event = "write",
- data = std::str::from_utf8(bytes).unwrap_or_default() ,
- size = bytes.len());
- return Ok(());
- }
- Err(err) => err,
- },
- Err(err) => err,
- };
-
- tracing::debug!(parent: &self.span,
- event = "error",
- "Failed to write to stream: {:?}", err);
- Err(())
+ pub async fn write(&mut self, bytes: &[u8]) -> trc::Result<()> {
+ self.stream
+ .write_all(bytes)
+ .await
+ .map_err(|err| trc::Cause::Network.reason(err).caused_by(trc::location!()))?;
+ self.stream
+ .flush()
+ .await
+ .map_err(|err| trc::Cause::Network.reason(err).caused_by(trc::location!()))?;
+
+ tracing::trace!(parent: &self.span,
+ event = "write",
+ data = std::str::from_utf8(bytes).unwrap_or_default() ,
+ size = bytes.len());
+
+ Ok(())
+ }
+
+ pub async fn write_error(&mut self, error: trc::Error) -> trc::Result<()> {
+ tracing::error!(parent: &self.span, event = "error", error = ?error);
+ self.write(&error.serialize()).await
}
#[inline(always)]
- pub async fn read(&mut self, bytes: &mut [u8]) -> Result<usize, ()> {
- match self.stream.read(bytes).await {
- Ok(len) => {
- tracing::trace!(parent: &self.span,
- event = "read",
- data = bytes
- .get(0..len)
- .and_then(|bytes| std::str::from_utf8(bytes).ok())
- .unwrap_or("[invalid UTF8]"),
- size = len);
- Ok(len)
- }
- Err(err) => {
- tracing::trace!(
- parent: &self.span,
- event = "error",
- "Failed to read from stream: {:?}", err
- );
- Err(())
- }
- }
+ pub async fn read(&mut self, bytes: &mut [u8]) -> trc::Result<usize> {
+ let len = self
+ .stream
+ .read(bytes)
+ .await
+ .map_err(|err| trc::Cause::Network.reason(err).caused_by(trc::location!()))?;
+
+ tracing::trace!(parent: &self.span,
+ event = "read",
+ data = bytes
+ .get(0..len)
+ .and_then(|bytes| std::str::from_utf8(bytes).ok())
+ .unwrap_or("[invalid UTF8]"),
+ size = len);
+
+ Ok(len)
}
}
impl<T: AsyncWrite + AsyncRead> Session<T> {
- pub async fn get_script_id(&self, account_id: u32, name: &str) -> Result<u32, StatusResponse> {
+ pub async fn get_script_id(&self, account_id: u32, name: &str) -> trc::Result<u32> {
self.jmap
.core
.storage
@@ -214,11 +238,13 @@ impl<T: AsyncWrite + AsyncRead> Session<T> {
vec![Filter::eq(Property::Name, name)],
)
.await
- .map_err(|_| StatusResponse::database_failure())
+ .caused_by(trc::location!())
.and_then(|results| {
results.results.min().ok_or_else(|| {
- StatusResponse::no("There is no script by that name")
- .with_code(ResponseCode::NonExistent)
+ trc::Cause::ManageSieve
+ .into_err()
+ .code(ResponseCode::NonExistent)
+ .reason("There is no script by that name")
})
})
}
diff --git a/crates/managesieve/src/core/mod.rs b/crates/managesieve/src/core/mod.rs
index 822f3fc4..db30c63c 100644
--- a/crates/managesieve/src/core/mod.rs
+++ b/crates/managesieve/src/core/mod.rs
@@ -159,15 +159,38 @@ impl ResponseCode {
ResponseCode::Warnings => b"WARNINGS",
});
}
+
+ pub fn as_str(&self) -> &'static str {
+ match self {
+ ResponseCode::AuthTooWeak => "AUTH-TOO-WEAK",
+ ResponseCode::EncryptNeeded => "ENCRYPT-NEEDED",
+ ResponseCode::Quota => "QUOTA",
+ ResponseCode::QuotaMaxScripts => "QUOTA/MAXSCRIPTS",
+ ResponseCode::QuotaMaxSize => "QUOTA/MAXSIZE",
+ ResponseCode::Referral => "REFERRAL",
+ ResponseCode::Sasl => "SASL",
+ ResponseCode::TransitionNeeded => "TRANSITION-NEEDED",
+ ResponseCode::TryLater => "TRYLATER",
+ ResponseCode::Active => "ACTIVE",
+ ResponseCode::NonExistent => "NONEXISTENT",
+ ResponseCode::AlreadyExists => "ALREADYEXISTS",
+ ResponseCode::Tag(_) => "TAG",
+ ResponseCode::Warnings => "WARNINGS",
+ }
+ }
}
impl ResponseType {
pub fn serialize(&self, buf: &mut Vec<u8>) {
- buf.extend_from_slice(match self {
- ResponseType::Ok => b"OK",
- ResponseType::No => b"NO",
- ResponseType::Bye => b"BYE",
- });
+ buf.extend_from_slice(self.as_str().as_bytes());
+ }
+
+ pub fn as_str(&self) -> &'static str {
+ match self {
+ ResponseType::Ok => "OK",
+ ResponseType::No => "NO",
+ ResponseType::Bye => "BYE",
+ }
}
}
@@ -234,3 +257,47 @@ impl StatusResponse {
}
}
}
+
+pub trait SerializeResponse {
+ fn serialize(&self) -> Vec<u8>;
+}
+
+impl SerializeResponse for trc::Error {
+ fn serialize(&self) -> Vec<u8> {
+ let todo = "serialize messages properly in all protocols";
+ let mut buf = Vec::with_capacity(64);
+ buf.extend_from_slice(self.value_as_str(trc::Key::Type).unwrap_or("NO").as_bytes());
+ if let Some(code) = self.value_as_str(trc::Key::Code) {
+ buf.extend_from_slice(b" (");
+ buf.extend_from_slice(code.as_bytes());
+ buf.push(b')');
+ }
+ if let Some(message) = self
+ .value_as_str(trc::Key::Details)
+ .or_else(|| self.value_as_str(trc::Key::Reason))
+ {
+ buf.extend_from_slice(b" \"");
+ for ch in message.as_bytes() {
+ if [b'\"', b'\\'].contains(ch) {
+ buf.push(b'\\');
+ }
+ buf.push(*ch);
+ }
+ buf.push(b'\"');
+ }
+ buf.extend_from_slice(b"\r\n");
+ buf
+ }
+}
+
+impl From<ResponseCode> for trc::Value {
+ fn from(value: ResponseCode) -> Self {
+ trc::Value::Static(value.as_str())
+ }
+}
+
+impl From<ResponseType> for trc::Value {
+ fn from(value: ResponseType) -> Self {
+ trc::Value::Static(value.as_str())
+ }
+}
diff --git a/crates/managesieve/src/core/session.rs b/crates/managesieve/src/core/session.rs
index da8c50b2..78b927e1 100644
--- a/crates/managesieve/src/core/session.rs
+++ b/crates/managesieve/src/core/session.rs
@@ -4,7 +4,7 @@
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
-use common::listener::{SessionData, SessionManager, SessionStream};
+use common::listener::{SessionData, SessionManager, SessionResult, SessionStream};
use imap_proto::receiver::{self, Receiver};
use jmap::JMAP;
use tokio_rustls::server::TlsStream;
@@ -76,11 +76,11 @@ impl<T: SessionStream> Session<T> {
Ok(Ok(bytes_read)) => {
if bytes_read > 0 {
match self.ingest(&buf[..bytes_read]).await {
- Ok(true) => (),
- Ok(false) => {
+ SessionResult::Continue => (),
+ SessionResult::UpgradeTls => {
return true;
}
- Err(_) => {
+ SessionResult::Close => {
break;
}
}
diff --git a/crates/managesieve/src/op/authenticate.rs b/crates/managesieve/src/op/authenticate.rs
index a65bafcd..aa9f10bf 100644
--- a/crates/managesieve/src/op/authenticate.rs
+++ b/crates/managesieve/src/op/authenticate.rs
@@ -7,7 +7,6 @@
use common::{
config::server::ServerProtocol,
listener::{limiter::ConcurrencyLimiter, SessionStream},
- AuthFailureReason, AuthResult,
};
use imap::op::authenticate::{decode_challenge_oauth, decode_challenge_plain};
use imap_proto::{
@@ -22,14 +21,16 @@ use std::sync::Arc;
use crate::core::{Command, Session, State, StatusResponse};
impl<T: SessionStream> Session<T> {
- pub async fn handle_authenticate(&mut self, request: Request<Command>) -> crate::op::OpResult {
+ pub async fn handle_authenticate(&mut self, request: Request<Command>) -> trc::Result<Vec<u8>> {
if request.tokens.is_empty() {
- return Err(StatusResponse::no("Authentication mechanism missing."));
+ return Err(trc::AuthCause::Error
+ .into_err()
+ .details("Authentication mechanism missing."));
}
let mut tokens = request.tokens.into_iter();
- let mechanism =
- Mechanism::parse(&tokens.next().unwrap().unwrap_bytes()).map_err(StatusResponse::no)?;
+ let mechanism = Mechanism::parse(&tokens.next().unwrap().unwrap_bytes())
+ .map_err(|err| trc::AuthCause::Error.into_err().details(err))?;
let mut params: Vec<String> = tokens
.filter_map(|token| token.unwrap_string().ok())
.collect();
@@ -37,14 +38,18 @@ impl<T: SessionStream> Session<T> {
let credentials = match mechanism {
Mechanism::Plain | Mechanism::OAuthBearer => {
if !params.is_empty() {
- let challenge = base64_decode(params.pop().unwrap().as_bytes())
- .ok_or_else(|| StatusResponse::no("Failed to decode challenge."))?;
+ let challenge =
+ base64_decode(params.pop().unwrap().as_bytes()).ok_or_else(|| {
+ trc::AuthCause::Error
+ .into_err()
+ .details("Failed to decode challenge.")
+ })?;
(if mechanism == Mechanism::Plain {
decode_challenge_plain(&challenge)
} else {
decode_challenge_oauth(&challenge)
}
- .map_err(StatusResponse::no))?
+ .map_err(|err| trc::AuthCause::Error.into_err().details(err)))?
} else {
self.receiver.request = receiver::Request {
tag: String::new(),
@@ -56,106 +61,63 @@ impl<T: SessionStream> Session<T> {
}
}
_ => {
- return Err(StatusResponse::no(
- "Authentication mechanism not supported.",
- ))
+ return Err(trc::AuthCause::Error
+ .into_err()
+ .details("Authentication mechanism not supported."))
}
};
// Throttle authentication requests
- if self
- .jmap
- .is_auth_allowed_soft(&self.remote_addr)
- .await
- .is_err()
- {
- tracing::debug!(parent: &self.span,
- event = "disconnect",
- "Too many authentication attempts, disconnecting.",
- );
- return Err(StatusResponse::bye(
- "Too many authentication requests from this IP address.",
- ));
- }
+ self.jmap.is_auth_allowed_soft(&self.remote_addr).await?;
// Authenticate
let mut is_totp_error = false;
let access_token = match credentials {
Credentials::Plain { username, secret } | Credentials::XOauth2 { username, secret } => {
- match self
- .jmap
+ self.jmap
.authenticate_plain(
&username,
&secret,
self.remote_addr,
ServerProtocol::ManageSieve,
)
- .await
- {
- AuthResult::Success(token) => Some(token),
- AuthResult::Failure(
- AuthFailureReason::InvalidCredentials | AuthFailureReason::InternalError(_),
- ) => None,
- AuthResult::Failure(AuthFailureReason::MissingTotp) => {
- is_totp_error = true;
- None
- }
- AuthResult::Failure(AuthFailureReason::Banned) => {
- return Err(StatusResponse::bye(
- "Too many authentication requests from this IP address.",
- ))
- }
- }
+ .await?
}
Credentials::OAuthBearer { token } => {
- match self
+ let (account_id, _, _) = self
.jmap
.validate_access_token("access_token", &token)
- .await
- {
- Ok((account_id, _, _)) => self.jmap.get_access_token(account_id).await,
- Err(err) => {
- tracing::debug!(
- parent: &self.span,
- context = "authenticate",
- err = err,
- "Failed to validate access token."
- );
- None
- }
- }
+ .await?;
+ self.jmap.get_access_token(account_id).await?
}
};
- if let Some(access_token) = access_token {
- // Enforce concurrency limits
- let in_flight = match self
- .get_concurrency_limiter(access_token.primary_id())
- .map(|limiter| limiter.concurrent_requests.is_allowed())
- {
- Some(Some(limiter)) => Some(limiter),
- None => None,
- Some(None) => {
- tracing::debug!(parent: &self.span,
- event = "disconnect",
- "Too many concurrent connection.",
- );
- return Err(StatusResponse::bye("Too many concurrent connections."));
- }
- };
+ // Enforce concurrency limits
+ let in_flight = match self
+ .get_concurrency_limiter(access_token.primary_id())
+ .map(|limiter| limiter.concurrent_requests.is_allowed())
+ {
+ Some(Some(limiter)) => Some(limiter),
+ None => None,
+ Some(None) => {
+ return Err(trc::LimitCause::ConcurrentRequest.into_err());
+ }
+ };
+
+ // Cache access token
+ let access_token = Arc::new(access_token);
+ self.jmap.cache_access_token(access_token.clone());
- // Cache access token
- let access_token = Arc::new(access_token);
- self.jmap.cache_access_token(access_token.clone());
+ // Create session
+ self.state = State::Authenticated {
+ access_token,
+ in_flight,
+ };
- // Create session
- self.state = State::Authenticated {
- access_token,
- in_flight,
- };
+ let todo = "implement this";
- Ok(StatusResponse::ok("Authentication successful").into_bytes())
- } else {
+ Ok(StatusResponse::ok("Authentication successful").into_bytes())
+ /*} else {
match &self.state {
State::NotAuthenticated { auth_failures }
if *auth_failures < self.jmap.core.imap.max_auth_failures =>
@@ -163,12 +125,13 @@ impl<T: SessionStream> Session<T> {
self.state = State::NotAuthenticated {
auth_failures: auth_failures + 1,
};
- Ok(StatusResponse::no(if is_totp_error {
- "Missing TOTP code, try with 'secret$totp_code'."
- } else {
- "Authentication failed."
- })
- .into_bytes())
+ Err(trc::Cause::Authentication
+ .into_err()
+ .details(if is_totp_error {
+ "Missing TOTP code, try with 'secret$totp_code'."
+ } else {
+ "Authentication failed."
+ }))
}
_ => {
tracing::debug!(
@@ -179,10 +142,10 @@ impl<T: SessionStream> Session<T> {
Err(StatusResponse::bye("Too many authentication failures"))
}
}
- }
+ }*/
}
- pub async fn handle_unauthenticate(&mut self) -> super::OpResult {
+ pub async fn handle_unauthenticate(&mut self) -> trc::Result<Vec<u8>> {
self.state = State::NotAuthenticated { auth_failures: 0 };
Ok(StatusResponse::ok("Unauthenticate successful.").into_bytes())
diff --git a/crates/managesieve/src/op/capability.rs b/crates/managesieve/src/op/capability.rs
index 45d7273a..82c2e092 100644
--- a/crates/managesieve/src/op/capability.rs
+++ b/crates/managesieve/src/op/capability.rs
@@ -10,7 +10,7 @@ use jmap_proto::request::capability::Capabilities;
use crate::core::{Session, StatusResponse};
impl<T: SessionStream> Session<T> {
- pub async fn handle_capability(&self, message: &'static str) -> super::OpResult {
+ pub async fn handle_capability(&self, message: &'static str) -> trc::Result<Vec<u8>> {
let mut response = Vec::with_capacity(128);
response.extend_from_slice(b"\"IMPLEMENTATION\" \"Stalwart ManageSieve\"\r\n");
response.extend_from_slice(b"\"VERSION\" \"1.0\"\r\n");
diff --git a/crates/managesieve/src/op/checkscript.rs b/crates/managesieve/src/op/checkscript.rs
index 4fb0717b..5d0bd9ca 100644
--- a/crates/managesieve/src/op/checkscript.rs
+++ b/crates/managesieve/src/op/checkscript.rs
@@ -10,9 +10,11 @@ use tokio::io::{AsyncRead, AsyncWrite};
use crate::core::{Command, Session, StatusResponse};
impl<T: AsyncRead + AsyncWrite> Session<T> {
- pub async fn handle_checkscript(&mut self, request: Request<Command>) -> super::OpResult {
+ pub async fn handle_checkscript(&mut self, request: Request<Command>) -> trc::Result<Vec<u8>> {
if request.tokens.is_empty() {
- return Err(StatusResponse::no("Expected script as a parameter."));
+ return Err(trc::Cause::ManageSieve
+ .into_err()
+ .details("Expected script as a parameter."));
}
self.jmap
@@ -21,6 +23,6 @@ impl<T: AsyncRead + AsyncWrite> Session<T> {
.untrusted_compiler
.compile(&request.tokens.into_iter().next().unwrap().unwrap_bytes())
.map(|_| StatusResponse::ok("Script is valid.").into_bytes())
- .map_err(|err| StatusResponse::no(err.to_string()))
+ .map_err(|err| trc::Cause::ManageSieve.into_err().details(err.to_string()))
}
}
diff --git a/crates/managesieve/src/op/deletescript.rs b/crates/managesieve/src/op/deletescript.rs
index 8d322f3a..f2ad1048 100644
--- a/crates/managesieve/src/op/deletescript.rs
+++ b/crates/managesieve/src/op/deletescript.rs
@@ -8,34 +8,45 @@ use imap_proto::receiver::Request;
use jmap_proto::types::collection::Collection;
use store::write::log::ChangeLogBuilder;
use tokio::io::{AsyncRead, AsyncWrite};
+use trc::AddContext;
use crate::core::{Command, ResponseCode, Session, StatusResponse};
impl<T: AsyncRead + AsyncWrite> Session<T> {
- pub async fn handle_deletescript(&mut self, request: Request<Command>) -> super::OpResult {
+ pub async fn handle_deletescript(&mut self, request: Request<Command>) -> trc::Result<Vec<u8>> {
let name = request
.tokens
.into_iter()
.next()
.and_then(|s| s.unwrap_string().ok())
- .ok_or_else(|| StatusResponse::no("Expected script name as a parameter."))?;
+ .ok_or_else(|| {
+ trc::Cause::ManageSieve
+ .into_err()
+ .details("Expected script name as a parameter.")
+ })?;
let account_id = self.state.access_token().primary_id();
let document_id = self.get_script_id(account_id, &name).await?;
if self
.jmap
.sieve_script_delete(account_id, document_id, true)
- .await?
+ .await
+ .caused_by(trc::location!())?
{
// Write changes
let mut changelog = ChangeLogBuilder::new();
changelog.log_delete(Collection::SieveScript, document_id);
- self.jmap.commit_changes(account_id, changelog).await?;
+ self.jmap
+ .commit_changes(account_id, changelog)
+ .await
+ .caused_by(trc::location!())?;
Ok(StatusResponse::ok("Deleted.").into_bytes())
} else {
- Err(StatusResponse::no("You may not delete an active script")
- .with_code(ResponseCode::Active))
+ Err(trc::Cause::ManageSieve
+ .into_err()
+ .details("You may not delete an active script")
+ .code(ResponseCode::Active))
}
}
}
diff --git a/crates/managesieve/src/op/getscript.rs b/crates/managesieve/src/op/getscript.rs
index 5988502f..ac3056aa 100644
--- a/crates/managesieve/src/op/getscript.rs
+++ b/crates/managesieve/src/op/getscript.rs
@@ -11,17 +11,22 @@ use jmap_proto::{
types::{collection::Collection, property::Property, value::Value},
};
use tokio::io::{AsyncRead, AsyncWrite};
+use trc::AddContext;
use crate::core::{Command, ResponseCode, Session, StatusResponse};
impl<T: AsyncRead + AsyncWrite> Session<T> {
- pub async fn handle_getscript(&mut self, request: Request<Command>) -> super::OpResult {
+ pub async fn handle_getscript(&mut self, request: Request<Command>) -> trc::Result<Vec<u8>> {
let name = request
.tokens
.into_iter()
.next()
.and_then(|s| s.unwrap_string().ok())
- .ok_or_else(|| StatusResponse::no("Expected script name as a parameter."))?;
+ .ok_or_else(|| {
+ trc::Cause::ManageSieve
+ .into_err()
+ .details("Expected script name as a parameter.")
+ })?;
let account_id = self.state.access_token().primary_id();
let document_id = self.get_script_id(account_id, &name).await?;
let (blob_section, blob_hash) = self
@@ -32,21 +37,32 @@ impl<T: AsyncRead + AsyncWrite> Session<T> {
document_id,
Property::Value,
)
- .await?
+ .await
+ .caused_by(trc::location!())?
.ok_or_else(|| {
- StatusResponse::no("Script not found").with_code(ResponseCode::NonExistent)
+ trc::Cause::ManageSieve
+ .into_err()
+ .details("Script not found")
+ .code(ResponseCode::NonExistent)
})?
.blob_id()
.and_then(|id| (id.section.as_ref()?.clone(), id.hash.clone()).into())
.ok_or_else(|| {
- StatusResponse::no("Failed to retrieve blobId").with_code(ResponseCode::TryLater)
+ trc::Cause::ManageSieve
+ .into_err()
+ .details("Failed to retrieve blobId")
+ .code(ResponseCode::TryLater)
})?;
let script = self
.jmap
.get_blob_section(&blob_hash, &blob_section)
- .await?
+ .await
+ .caused_by(trc::location!())?
.ok_or_else(|| {
- StatusResponse::no("Script blob not found").with_code(ResponseCode::NonExistent)
+ trc::Cause::ManageSieve
+ .into_err()
+ .details("Script blob not found")
+ .code(ResponseCode::NonExistent)
})?;
debug_assert_eq!(script.len(), blob_section.size);
diff --git a/crates/managesieve/src/op/havespace.rs b/crates/managesieve/src/op/havespace.rs
index a8237f68..5534b8d1 100644
--- a/crates/managesieve/src/op/havespace.rs
+++ b/crates/managesieve/src/op/havespace.rs
@@ -6,22 +6,35 @@
use imap_proto::receiver::Request;
use tokio::io::{AsyncRead, AsyncWrite};
+use trc::AddContext;
use crate::core::{Command, ResponseCode, Session, StatusResponse};
impl<T: AsyncRead + AsyncWrite> Session<T> {
- pub async fn handle_havespace(&mut self, request: Request<Command>) -> crate::op::OpResult {
+ pub async fn handle_havespace(&mut self, request: Request<Command>) -> trc::Result<Vec<u8>> {
let mut tokens = request.tokens.into_iter();
let name = tokens
.next()
.and_then(|s| s.unwrap_string().ok())
- .ok_or_else(|| StatusResponse::no("Expected script name as a parameter."))?;
+ .ok_or_else(|| {
+ trc::Cause::ManageSieve
+ .into_err()
+ .details("Expected script name as a parameter.")
+ })?;
let size: usize = tokens
.next()
.and_then(|s| s.unwrap_string().ok())
- .ok_or_else(|| StatusResponse::no("Expected script size as a parameter."))?
+ .ok_or_else(|| {
+ trc::Cause::ManageSieve
+ .into_err()
+ .details("Expected script size as a parameter.")
+ })?
.parse::<usize>()
- .map_err(|_| StatusResponse::no("Invalid size parameter."))?;
+ .map_err(|_| {
+ trc::Cause::ManageSieve
+ .into_err()
+ .details("Invalid size parameter.")
+ })?;
// Validate name
let access_token = self.state.access_token();
@@ -30,12 +43,20 @@ impl<T: AsyncRead + AsyncWrite> Session<T> {
// Validate quota
if access_token.quota == 0
- || size as i64 + self.jmap.get_used_quota(account_id).await?
+ || size as i64
+ + self
+ .jmap
+ .get_used_quota(account_id)
+ .await
+ .caused_by(trc::location!())?
<= access_token.quota as i64
{
Ok(StatusResponse::ok("").into_bytes())
} else {
- Err(StatusResponse::no("Quota exceeded.").with_code(ResponseCode::QuotaMaxSize))
+ Err(trc::Cause::ManageSieve
+ .into_err()
+ .details("Quota exceeded.")
+ .code(ResponseCode::QuotaMaxSize))
}
}
}
diff --git a/crates/managesieve/src/op/listscripts.rs b/crates/managesieve/src/op/listscripts.rs
index a4dbe622..93e9653b 100644
--- a/crates/managesieve/src/op/listscripts.rs
+++ b/crates/managesieve/src/op/listscripts.rs
@@ -9,16 +9,18 @@ use jmap_proto::{
types::{collection::Collection, property::Property, value::Value},
};
use tokio::io::{AsyncRead, AsyncWrite};
+use trc::AddContext;
use crate::core::{Session, StatusResponse};
impl<T: AsyncRead + AsyncWrite> Session<T> {
- pub async fn handle_listscripts(&mut self) -> super::OpResult {
+ pub async fn handle_listscripts(&mut self) -> trc::Result<Vec<u8>> {
let account_id = self.state.access_token().primary_id();
let document_ids = self
.jmap
.get_document_ids(account_id, Collection::SieveScript)
- .await?
+ .await
+ .caused_by(trc::location!())?
.unwrap_or_default();
if document_ids.is_empty() {
@@ -36,7 +38,8 @@ impl<T: AsyncRead + AsyncWrite> Session<T> {
document_id,
Property::Value,
)
- .await?
+ .await
+ .caused_by(trc::location!())?
{
response.push(b'\"');
if let Some(name) = script.get(&Property::Name).as_string() {
diff --git a/crates/managesieve/src/op/logout.rs b/crates/managesieve/src/op/logout.rs
index 60e68df2..05550827 100644
--- a/crates/managesieve/src/op/logout.rs
+++ b/crates/managesieve/src/op/logout.rs
@@ -9,11 +9,12 @@ use tokio::io::{AsyncRead, AsyncWrite};
use crate::core::{Session, StatusResponse};
impl<T: AsyncRead + AsyncWrite> Session<T> {
- pub async fn handle_logout(&mut self) -> super::OpResult {
- Err(StatusResponse::ok(concat!(
+ pub async fn handle_logout(&mut self) -> trc::Result<Vec<u8>> {
+ Ok(StatusResponse::ok(concat!(
"Stalwart ManageSieve v",
env!("CARGO_PKG_VERSION"),
" bids you farewell."
- )))
+ ))
+ .into_bytes())
}
}
diff --git a/crates/managesieve/src/op/mod.rs b/crates/managesieve/src/op/mod.rs
index 3412f3c7..ff3808f4 100644
--- a/crates/managesieve/src/op/mod.rs
+++ b/crates/managesieve/src/op/mod.rs
@@ -4,9 +4,9 @@
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
-use jmap_proto::error::method::MethodError;
+use common::listener::SessionStream;
-use crate::core::StatusResponse;
+use crate::core::{Session, StatusResponse};
pub mod authenticate;
pub mod capability;
@@ -21,10 +21,8 @@ pub mod putscript;
pub mod renamescript;
pub mod setactive;
-impl From<MethodError> for StatusResponse {
- fn from(_: MethodError) -> Self {
- StatusResponse::database_failure()
+impl<T: SessionStream> Session<T> {
+ pub async fn handle_start_tls(&self) -> trc::Result<Vec<u8>> {
+ Ok(StatusResponse::ok("Begin TLS negotiation now").into_bytes())
}
}
-
-pub type OpResult = std::result::Result<Vec<u8>, StatusResponse>;
diff --git a/crates/managesieve/src/op/noop.rs b/crates/managesieve/src/op/noop.rs
index 7137d08e..c1d13669 100644
--- a/crates/managesieve/src/op/noop.rs
+++ b/crates/managesieve/src/op/noop.rs
@@ -10,7 +10,7 @@ use tokio::io::{AsyncRead, AsyncWrite};
use crate::core::{Command, ResponseCode, Session, StatusResponse};
impl<T: AsyncRead + AsyncWrite> Session<T> {
- pub async fn handle_noop(&mut self, request: Request<Command>) -> super::OpResult {
+ pub async fn handle_noop(&mut self, request: Request<Command>) -> trc::Result<Vec<u8>> {
Ok(if let Some(tag) = request
.tokens
.into_iter()
diff --git a/crates/managesieve/src/op/putscript.rs b/crates/managesieve/src/op/putscript.rs
index 2e16021f..07847ece 100644
--- a/crates/managesieve/src/op/putscript.rs
+++ b/crates/managesieve/src/op/putscript.rs
@@ -17,21 +17,30 @@ use store::{
BlobClass,
};
use tokio::io::{AsyncRead, AsyncWrite};
+use trc::AddContext;
use crate::core::{Command, ResponseCode, Session, StatusResponse};
impl<T: AsyncRead + AsyncWrite> Session<T> {
- pub async fn handle_putscript(&mut self, request: Request<Command>) -> super::OpResult {
+ pub async fn handle_putscript(&mut self, request: Request<Command>) -> trc::Result<Vec<u8>> {
let mut tokens = request.tokens.into_iter();
let name = tokens
.next()
.and_then(|s| s.unwrap_string().ok())
- .ok_or_else(|| StatusResponse::no("Expected script name as a parameter."))?
+ .ok_or_else(|| {
+ trc::Cause::ManageSieve
+ .into_err()
+ .details("Expected script name as a parameter.")
+ })?
.trim()
.to_string();
let mut script_bytes = tokens
.next()
- .ok_or_else(|| StatusResponse::no("Expected script as a parameter."))?
+ .ok_or_else(|| {
+ trc::Cause::ManageSieve
+ .into_err()
+ .details("Expected script as a parameter.")
+ })?
.unwrap_bytes();
let script_size = script_bytes.len() as i64;
@@ -45,22 +54,28 @@ impl<T: AsyncRead + AsyncWrite> Session<T> {
access_token.quota as i64,
script_bytes.len() as i64,
)
- .await?
+ .await
+ .caused_by(trc::location!())?
{
- return Err(StatusResponse::no("Quota exceeded.").with_code(ResponseCode::Quota));
+ return Err(trc::Cause::ManageSieve
+ .into_err()
+ .details("Quota exceeded.")
+ .code(ResponseCode::Quota));
}
if self
.jmap
.get_document_ids(account_id, Collection::SieveScript)
- .await?
+ .await
+ .caused_by(trc::location!())?
.map(|ids| ids.len() as usize)
.unwrap_or(0)
> self.jmap.core.jmap.sieve_max_scripts
{
- return Err(
- StatusResponse::no("Too many scripts.").with_code(ResponseCode::QuotaMaxScripts)
- );
+ return Err(trc::Cause::ManageSieve
+ .into_err()
+ .details("Too many scripts.")
+ .code(ResponseCode::QuotaMaxScripts));
}
// Compile script
@@ -76,9 +91,12 @@ impl<T: AsyncRead + AsyncWrite> Session<T> {
}
Err(err) => {
return Err(if let ErrorType::ScriptTooLong = &err.error_type() {
- StatusResponse::no(err.to_string()).with_code(ResponseCode::QuotaMaxSize)
+ trc::Cause::ManageSieve
+ .into_err()
+ .details(err.to_string())
+ .code(ResponseCode::QuotaMaxSize)
} else {
- StatusResponse::no(err.to_string())
+ trc::Cause::ManageSieve.into_err().details(err.to_string())
});
}
}
@@ -94,20 +112,27 @@ impl<T: AsyncRead + AsyncWrite> Session<T> {
document_id,
Property::Value,
)
- .await?
+ .await
+ .caused_by(trc::location!())?
.ok_or_else(|| {
- StatusResponse::no("Script not found").with_code(ResponseCode::NonExistent)
+ trc::Cause::ManageSieve
+ .into_err()
+ .details("Script not found")
+ .code(ResponseCode::NonExistent)
})?;
let prev_blob_id = script.inner.blob_id().ok_or_else(|| {
- StatusResponse::no("Internal error while obtaining blobId")
- .with_code(ResponseCode::TryLater)
+ trc::Cause::ManageSieve
+ .into_err()
+ .details("Internal error while obtaining blobId")
+ .code(ResponseCode::TryLater)
})?;
// Write script blob
let blob_id = BlobId::new(
self.jmap
.put_blob(account_id, &script_bytes, false)
- .await?
+ .await
+ .caused_by(trc::location!())?
.hash,
BlobClass::Linked {
account_id,
@@ -152,7 +177,10 @@ impl<T: AsyncRead + AsyncWrite> Session<T> {
.with_property(Property::BlobId, Value::BlobId(blob_id)),
),
);
- self.jmap.write_batch(batch).await?;
+ self.jmap
+ .write_batch(batch)
+ .await
+ .caused_by(trc::location!())?;
} else {
// Write script blob
let blob_id = BlobId::new(
@@ -190,25 +218,28 @@ impl<T: AsyncRead + AsyncWrite> Session<T> {
.with_property(Property::BlobId, Value::BlobId(blob_id)),
),
);
- self.jmap.write_batch(batch).await?;
+ self.jmap
+ .write_batch(batch)
+ .await
+ .caused_by(trc::location!())?;
}
Ok(StatusResponse::ok("Success.").into_bytes())
}
- pub async fn validate_name(
- &self,
- account_id: u32,
- name: &str,
- ) -> Result<Option<u32>, StatusResponse> {
+ pub async fn validate_name(&self, account_id: u32, name: &str) -> trc::Result<Option<u32>> {
if name.is_empty() {
- Err(StatusResponse::no("Script name cannot be empty."))
+ Err(trc::Cause::ManageSieve
+ .into_err()
+ .details("Script name cannot be empty."))
} else if name.len() > self.jmap.core.jmap.sieve_max_script_name {
- Err(StatusResponse::no("Script name is too long."))
+ Err(trc::Cause::ManageSieve
+ .into_err()
+ .details("Script name is too long."))
} else if name.eq_ignore_ascii_case("vacation") {
- Err(StatusResponse::no(
- "The 'vacation' name is reserved, please use a different name.",
- ))
+ Err(trc::Cause::ManageSieve
+ .into_err()
+ .details("The 'vacation' name is reserved, please use a different name."))
} else {
Ok(self
.jmap
@@ -217,7 +248,8 @@ impl<T: AsyncRead + AsyncWrite> Session<T> {
Collection::SieveScript,
vec![Filter::eq(Property::Name, name)],
)
- .await?
+ .await
+ .caused_by(trc::location!())?
.results
.min())
}
diff --git a/crates/managesieve/src/op/renamescript.rs b/crates/managesieve/src/op/renamescript.rs
index d29d8e8f..48871e68 100644
--- a/crates/managesieve/src/op/renamescript.rs
+++ b/crates/managesieve/src/op/renamescript.rs
@@ -7,28 +7,36 @@
use imap_proto::receiver::Request;
use jmap::sieve::set::SCHEMA;
use jmap_proto::{
- error::method::MethodError,
object::{index::ObjectIndexBuilder, Object},
types::{collection::Collection, property::Property, value::Value},
};
use store::write::{assert::HashedValue, log::ChangeLogBuilder, BatchBuilder};
use tokio::io::{AsyncRead, AsyncWrite};
+use trc::AddContext;
use crate::core::{Command, ResponseCode, Session, StatusResponse};
impl<T: AsyncRead + AsyncWrite> Session<T> {
- pub async fn handle_renamescript(&mut self, request: Request<Command>) -> super::OpResult {
+ pub async fn handle_renamescript(&mut self, request: Request<Command>) -> trc::Result<Vec<u8>> {
let mut tokens = request.tokens.into_iter();
let name = tokens
.next()
.and_then(|s| s.unwrap_string().ok())
- .ok_or_else(|| StatusResponse::no("Expected old script name as a parameter."))?
+ .ok_or_else(|| {
+ trc::Cause::ManageSieve
+ .into_err()
+ .details("Expected old script name as a parameter.")
+ })?
.trim()
.to_string();
let new_name = tokens
.next()
.and_then(|s| s.unwrap_string().ok())
- .ok_or_else(|| StatusResponse::no("Expected new script name as a parameter."))?
+ .ok_or_else(|| {
+ trc::Cause::ManageSieve
+ .into_err()
+ .details("Expected new script name as a parameter.")
+ })?
.trim()
.to_string();
@@ -39,10 +47,10 @@ impl<T: AsyncRead + AsyncWrite> Session<T> {
let account_id = self.state.access_token().primary_id();
let document_id = self.get_script_id(account_id, &name).await?;
if self.validate_name(account_id, &new_name).await?.is_some() {
- return Err(StatusResponse::no(format!(
- "A sieve script with name '{name}' already exists.",
- ))
- .with_code(ResponseCode::AlreadyExists));
+ return Err(trc::Cause::ManageSieve
+ .into_err()
+ .details(format!("A sieve script with name '{name}' already exists.",))
+ .code(ResponseCode::AlreadyExists));
}
// Obtain script values
@@ -54,9 +62,13 @@ impl<T: AsyncRead + AsyncWrite> Session<T> {
document_id,
Property::Value,
)
- .await?
+ .await
+ .caused_by(trc::location!())?
.ok_or_else(|| {
- StatusResponse::no("Script not found").with_code(ResponseCode::NonExistent)
+ trc::Cause::ManageSieve
+ .into_err()
+ .details("Script not found")
+ .code(ResponseCode::NonExistent)
})?;
// Write record
@@ -71,22 +83,16 @@ impl<T: AsyncRead + AsyncWrite> Session<T> {
.with_changes(Object::with_capacity(1).with_property(Property::Name, new_name)),
);
if !batch.is_empty() {
- match self.jmap.write_batch(batch).await {
- Ok(_) => {
- let mut changelog = ChangeLogBuilder::new();
- changelog.log_update(Collection::SieveScript, document_id);
- self.jmap.commit_changes(account_id, changelog).await?;
- }
- Err(MethodError::ServerUnavailable) => {
- return Err(StatusResponse::no(
- "Another process modified this script, please try again.",
- )
- .with_code(ResponseCode::TryLater));
- }
- Err(_) => {
- return Err(StatusResponse::database_failure());
- }
- }
+ self.jmap
+ .write_batch(batch)
+ .await
+ .caused_by(trc::location!())?;
+ let mut changelog = ChangeLogBuilder::new();
+ changelog.log_update(Collection::SieveScript, document_id);
+ self.jmap
+ .commit_changes(account_id, changelog)
+ .await
+ .caused_by(trc::location!())?;
}
Ok(StatusResponse::ok("Success.").into_bytes())
diff --git a/crates/managesieve/src/op/setactive.rs b/crates/managesieve/src/op/setactive.rs
index 82dd51ac..5cad960e 100644
--- a/crates/managesieve/src/op/setactive.rs
+++ b/crates/managesieve/src/op/setactive.rs
@@ -8,17 +8,22 @@ use imap_proto::receiver::Request;
use jmap_proto::types::collection::Collection;
use store::write::log::ChangeLogBuilder;
use tokio::io::{AsyncRead, AsyncWrite};
+use trc::AddContext;
use crate::core::{Command, Session, StatusResponse};
impl<T: AsyncRead + AsyncWrite> Session<T> {
- pub async fn handle_setactive(&mut self, request: Request<Command>) -> super::OpResult {
+ pub async fn handle_setactive(&mut self, request: Request<Command>) -> trc::Result<Vec<u8>> {
let name = request
.tokens
.into_iter()
.next()
.and_then(|s| s.unwrap_string().ok())
- .ok_or_else(|| StatusResponse::no("Expected script name as a parameter."))?;
+ .ok_or_else(|| {
+ trc::Cause::ManageSieve
+ .into_err()
+ .details("Expected script name as a parameter.")
+ })?;
// De/activate script
let account_id = self.state.access_token().primary_id();
@@ -32,7 +37,8 @@ impl<T: AsyncRead + AsyncWrite> Session<T> {
None
},
)
- .await?;
+ .await
+ .caused_by(trc::location!())?;
// Write changes
if !changes.is_empty() {
@@ -40,7 +46,10 @@ impl<T: AsyncRead + AsyncWrite> Session<T> {
for (document_id, _) in changes {
changelog.log_update(Collection::SieveScript, document_id);
}
- self.jmap.commit_changes(account_id, changelog).await?;
+ self.jmap
+ .commit_changes(account_id, changelog)
+ .await
+ .caused_by(trc::location!())?;
}
Ok(StatusResponse::ok("Success").into_bytes())
}
diff --git a/crates/pop3/src/client.rs b/crates/pop3/src/client.rs
index 6f5dd035..1bf7f5b5 100644
--- a/crates/pop3/src/client.rs
+++ b/crates/pop3/src/client.rs
@@ -4,8 +4,9 @@
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
-use common::listener::SessionStream;
+use common::listener::{SessionResult, SessionStream};
use mail_send::Credentials;
+use trc::AddContext;
use crate::{
protocol::{request::Error, response::Response, Command, Mechanism},
@@ -13,7 +14,7 @@ use crate::{
};
impl<T: SessionStream> Session<T> {
- pub async fn ingest(&mut self, bytes: &[u8]) -> Result<bool, ()> {
+ pub async fn ingest(&mut self, bytes: &[u8]) -> SessionResult {
/*let tmp = "dd";
for line in String::from_utf8_lossy(bytes).split("\r\n") {
println!("<- {:?}", &line[..std::cmp::min(line.len(), 100)]);
@@ -46,20 +47,21 @@ impl<T: SessionStream> Session<T> {
break;
}
Err(Error::Parse(err)) => {
- requests.push(Err(err));
+ requests.push(Err(trc::Cause::Pop3.into_err().details(err)));
}
}
}
for request in requests {
- match request {
+ let mut result = None;
+ let maybe_err = match request {
Ok(command) => match self.validate_request(command).await {
Ok(command) => match command {
Command::User { name } => {
if let State::NotAuthenticated { username, .. } = &mut self.state {
let response = format!("{name} is a valid mailbox");
*username = Some(name);
- self.write_ok(response).await?;
+ self.write_ok(response).await
} else {
unreachable!();
}
@@ -75,30 +77,21 @@ impl<T: SessionStream> Session<T> {
username,
secret: string,
})
- .await?;
+ .await
}
Command::Quit => {
- self.handle_quit().await?;
- }
- Command::Stat => self.handle_stat().await?,
- Command::List { msg } => {
- self.handle_list(msg).await?;
- }
- Command::Retr { msg } => {
- self.handle_fetch(msg, None).await?;
- }
- Command::Dele { msg } => self.handle_dele(vec![msg]).await?,
- Command::DeleMany { msgs } => self.handle_dele(msgs).await?,
- Command::Top { msg, n } => {
- self.handle_fetch(msg, n.into()).await?;
- }
- Command::Uidl { msg } => self.handle_uidl(msg).await?,
- Command::Noop => {
- self.write_ok("NOOP").await?;
- }
- Command::Rset => {
- self.handle_rset().await?;
- }
+ result = SessionResult::Close.into();
+ self.handle_quit().await
+ }
+ Command::Stat => self.handle_stat().await,
+ Command::List { msg } => self.handle_list(msg).await,
+ Command::Retr { msg } => self.handle_fetch(msg, None).await,
+ Command::Dele { msg } => self.handle_dele(vec![msg]).await,
+ Command::DeleMany { msgs } => self.handle_dele(msgs).await,
+ Command::Top { msg, n } => self.handle_fetch(msg, n.into()).await,
+ Command::Uidl { msg } => self.handle_uidl(msg).await,
+ Command::Noop => self.write_ok("NOOP").await,
+ Command::Rset => self.handle_rset().await,
Command::Capa => {
let mechanisms =
if self.stream.is_tls() || self.jmap.core.imap.allow_plain_auth {
@@ -114,39 +107,48 @@ impl<T: SessionStream> Session<T> {
}
.serialize(),
)
- .await?;
+ .await
}
Command::Stls => {
- self.write_ok("Begin TLS negotiation now").await?;
- return Ok(false);
- }
- Command::Utf8 => {
- self.write_ok("UTF8 enabled").await?;
+ result = SessionResult::UpgradeTls.into();
+ self.write_ok("Begin TLS negotiation now").await
}
+ Command::Utf8 => self.write_ok("UTF8 enabled").await,
Command::Auth { mechanism, params } => {
- self.handle_sasl(mechanism, params).await?;
+ self.handle_sasl(mechanism, params).await
}
Command::Apop { .. } => {
- self.write_err("APOP not supported.").await?;
+ self.write_err(
+ trc::Cause::Pop3.into_err().details("APOP not supported."),
+ )
+ .await
}
},
- Err(err) => {
- self.write_err(err).await?;
- }
+ Err(err) => self.write_err(err).await,
},
- Err(err) => {
- self.write_err(err).await?;
+ Err(err) => self.write_err(err).await,
+ };
+
+ if let Err(err) = maybe_err {
+ tracing::error!(parent: &self.span, "Error: {:?}", err);
+ if err.matches(trc::Cause::Network) {
+ return SessionResult::Close;
+ } else if let Err(err) = self.write_err(err).await {
+ tracing::error!(parent: &self.span, "Error: {:?}", err);
+ return SessionResult::Close;
}
+ } else if let Some(result) = result {
+ return result;
}
}
- Ok(true)
+ SessionResult::Continue
}
async fn validate_request(
&self,
command: Command<String, Mechanism>,
- ) -> Result<Command<String, Mechanism>, &'static str> {
+ ) -> trc::Result<Command<String, Mechanism>> {
match &command {
Command::Capa | Command::Quit | Command::Noop => Ok(command),
Command::Auth {
@@ -161,27 +163,35 @@ impl<T: SessionStream> Session<T> {
if !matches!(command, Command::Pass { .. }) || username.is_some() {
Ok(command)
} else {
- Err("Username was not provided.")
+ Err(trc::Cause::Pop3
+ .into_err()
+ .details("Username was not provided."))
}
} else {
- Err("Cannot authenticate over plain-text.")
+ Err(trc::Cause::Pop3
+ .into_err()
+ .details("Cannot authenticate over plain-text."))
}
} else {
- Err("Already authenticated.")
+ Err(trc::Cause::Pop3
+ .into_err()
+ .details("Already authenticated."))
}
}
Command::Auth { .. } => {
if let State::NotAuthenticated { .. } = &self.state {
Ok(command)
} else {
- Err("Already authenticated.")
+ Err(trc::Cause::Pop3
+ .into_err()
+ .details("Already authenticated."))
}
}
Command::Stls => {
if !self.stream.is_tls() {
Ok(command)
} else {
- Err("Already in TLS mode.")
+ Err(trc::Cause::Pop3.into_err().details("Already in TLS mode."))
}
}
@@ -196,7 +206,7 @@ impl<T: SessionStream> Session<T> {
| Command::Rset => {
if let State::Authenticated { mailbox, .. } = &self.state {
if let Some(rate) = &self.jmap.core.imap.rate_requests {
- match self
+ if self
.jmap
.core
.storage
@@ -207,16 +217,18 @@ impl<T: SessionStream> Session<T> {
true,
)
.await
+ .caused_by(trc::location!())?
+ .is_none()
{
- Ok(None) => Ok(command),
- Ok(Some(_)) => Err("Too many requests"),
- Err(_) => Err("Internal server error"),
+ Ok(command)
+ } else {
+ Err(trc::LimitCause::TooManyRequests.into_err())
}
} else {
Ok(command)
}
} else {
- Err("Not authenticated.")
+ Err(trc::Cause::Pop3.into_err().details("Not authenticated."))
}
}
}
diff --git a/crates/pop3/src/mailbox.rs b/crates/pop3/src/mailbox.rs
index 157164f3..b3e1d06a 100644
--- a/crates/pop3/src/mailbox.rs
+++ b/crates/pop3/src/mailbox.rs
@@ -9,13 +9,13 @@ use std::collections::BTreeMap;
use common::listener::SessionStream;
use jmap::mailbox::{UidMailbox, INBOX_ID};
use jmap_proto::{
- error::method::MethodError,
object::Object,
types::{collection::Collection, property::Property, value::Value},
};
use store::{
ahash::AHashMap, write::key::DeserializeBigEndian, IndexKey, IterateParams, Serialize, U32_LEN,
};
+use trc::AddContext;
use crate::Session;
@@ -46,7 +46,8 @@ impl<T: SessionStream> Session<T> {
Property::MailboxIds,
INBOX_ID,
)
- .await?
+ .await
+ .caused_by(trc::location!())?
.unwrap_or_default();
if message_ids.is_empty() {
@@ -57,7 +58,10 @@ impl<T: SessionStream> Session<T> {
let mut message_sizes = AHashMap::new();
// Obtain UID validity
- self.jmap.mailbox_get_or_create(account_id).await?;
+ self.jmap
+ .mailbox_get_or_create(account_id)
+ .await
+ .caused_by(trc::location!())?;
let uid_validity = self
.jmap
.get_property::<Object<Value>>(
@@ -66,16 +70,15 @@ impl<T: SessionStream> Session<T> {
INBOX_ID,
&Property::Value,
)
- .await?
+ .await
+ .caused_by(trc::location!())?
.and_then(|obj| obj.get(&Property::Cid).as_uint())
.ok_or_else(|| {
- tracing::debug!(event = "error",
- context = "store",
- account_id = account_id,
- collection = ?Collection::Mailbox,
- mailbox_id = INBOX_ID,
- "Failed to obtain uid validity");
- MethodError::ServerPartialFail
+ trc::StoreCause::Unexpected
+ .caused_by(trc::location!())
+ .details("Failed to obtain UID validity")
+ .account_id(account_id)
+ .document_id(INBOX_ID)
})
.map(|v| v as u32)?;
@@ -115,13 +118,7 @@ impl<T: SessionStream> Session<T> {
},
)
.await
- .map_err(|err| {
- tracing::error!(context = "fetch_mailbox",
- reason = ?err,
- "Failed to iterate message sizes");
-
- MethodError::ServerPartialFail
- })?;
+ .caused_by(trc::location!())?;
// Sort by UID
for (message_id, uid_mailbox) in self
@@ -132,7 +129,8 @@ impl<T: SessionStream> Session<T> {
&message_ids,
Property::MailboxIds,
)
- .await?
+ .await
+ .caused_by(trc::location!())?
.into_iter()
{
// Make sure the message is still in Inbox
diff --git a/crates/pop3/src/op/authenticate.rs b/crates/pop3/src/op/authenticate.rs
index 123f0dd9..93bc2422 100644
--- a/crates/pop3/src/op/authenticate.rs
+++ b/crates/pop3/src/op/authenticate.rs
@@ -7,7 +7,6 @@
use common::{
config::server::ServerProtocol,
listener::{limiter::ConcurrencyLimiter, SessionStream},
- AuthFailureReason, AuthResult,
};
use imap::op::authenticate::{decode_challenge_oauth, decode_challenge_plain};
use jmap::auth::rate_limit::ConcurrencyLimiters;
@@ -25,11 +24,11 @@ impl<T: SessionStream> Session<T> {
&mut self,
mechanism: Mechanism,
mut params: Vec<String>,
- ) -> Result<(), ()> {
+ ) -> trc::Result<()> {
match mechanism {
Mechanism::Plain | Mechanism::OAuthBearer => {
if !params.is_empty() {
- let result = base64_decode(params.pop().unwrap().as_bytes())
+ let credentials = base64_decode(params.pop().unwrap().as_bytes())
.ok_or("Failed to decode challenge.")
.and_then(|challenge| {
if mechanism == Mechanism::Plain {
@@ -37,12 +36,10 @@ impl<T: SessionStream> Session<T> {
} else {
decode_challenge_oauth(&challenge)
}
- });
+ })
+ .map_err(|err| trc::AuthCause::Error.into_err().details(err))?;
- match result {
- Ok(credentials) => self.handle_auth(credentials).await,
- Err(err) => self.write_err(err).await,
- }
+ self.handle_auth(credentials).await
} else {
// TODO: This hack is temporary until the SASL library is developed
self.receiver.state = request::State::Argument {
@@ -57,111 +54,58 @@ impl<T: SessionStream> Session<T> {
self.write_bytes("+\r\n").await
}
}
- _ => {
- self.write_err("Authentication mechanism not supported.")
- .await
- }
+ _ => Err(trc::AuthCause::Error
+ .into_err()
+ .details("Authentication mechanism not supported.")),
}
}
- pub async fn handle_auth(&mut self, credentials: Credentials<String>) -> Result<(), ()> {
+ pub async fn handle_auth(&mut self, credentials: Credentials<String>) -> trc::Result<()> {
// Throttle authentication requests
- if self
- .jmap
- .is_auth_allowed_soft(&self.remote_addr)
- .await
- .is_err()
- {
- tracing::debug!(parent: &self.span,
- event = "disconnect",
- "Too many authentication attempts, disconnecting.",
- );
-
- self.write_err("Too many authentication requests from this IP address.")
- .await?;
- return Err(());
- }
+ let todo = "disconnect in all protocols when this error is returned";
+ self.jmap.is_auth_allowed_soft(&self.remote_addr).await?;
// Authenticate
let mut is_totp_error = false;
let access_token = match credentials {
Credentials::Plain { username, secret } | Credentials::XOauth2 { username, secret } => {
- match self
- .jmap
+ self.jmap
.authenticate_plain(&username, &secret, self.remote_addr, ServerProtocol::Pop3)
- .await
- {
- AuthResult::Success(token) => Some(token),
- AuthResult::Failure(
- AuthFailureReason::InvalidCredentials | AuthFailureReason::InternalError(_),
- ) => None,
- AuthResult::Failure(AuthFailureReason::MissingTotp) => {
- is_totp_error = true;
- None
- }
- AuthResult::Failure(AuthFailureReason::Banned) => {
- self.write_err("Too many authentication requests from this IP address.")
- .await?;
- return Err(());
- }
- }
+ .await?
}
Credentials::OAuthBearer { token } => {
- match self
+ let (account_id, _, _) = self
.jmap
.validate_access_token("access_token", &token)
- .await
- {
- Ok((account_id, _, _)) => self.jmap.get_access_token(account_id).await,
- Err(err) => {
- tracing::debug!(
- parent: &self.span,
- context = "authenticate",
- err = err,
- "Failed to validate access token."
- );
- None
- }
- }
+ .await?;
+ self.jmap.get_access_token(account_id).await?
}
};
- if let Some(access_token) = access_token {
- // Enforce concurrency limits
- let in_flight = match self
- .get_concurrency_limiter(access_token.primary_id())
- .map(|limiter| limiter.concurrent_requests.is_allowed())
- {
- Some(Some(limiter)) => Some(limiter),
- None => None,
- Some(None) => {
- tracing::debug!(parent: &self.span,
- event = "disconnect",
- "Too many concurrent connection.",
- );
- self.write_err("Too many concurrent connections.").await?;
- return Err(());
- }
- };
+ // Enforce concurrency limits
+ let in_flight = match self
+ .get_concurrency_limiter(access_token.primary_id())
+ .map(|limiter| limiter.concurrent_requests.is_allowed())
+ {
+ Some(Some(limiter)) => Some(limiter),
+ None => None,
+ Some(None) => {
+ return Err(trc::LimitCause::ConcurrentRequest.into_err());
+ }
+ };
- // Cache access token
- let access_token = Arc::new(access_token);
- self.jmap.cache_access_token(access_token.clone());
+ // Cache access token
+ let access_token = Arc::new(access_token);
+ self.jmap.cache_access_token(access_token.clone());
- // Fetch mailbox
- match self.fetch_mailbox(access_token.primary_id()).await {
- Ok(mailbox) => {
- // Create session
- self.state = State::Authenticated { in_flight, mailbox };
+ // Fetch mailbox
+ let mailbox = self.fetch_mailbox(access_token.primary_id()).await?;
- self.write_ok("Authentication successful").await
- }
- Err(_) => {
- self.write_err("Temporary server failure").await?;
- Err(())
- }
- }
- } else {
+ // Create session
+ let todo = "fix below";
+ self.state = State::Authenticated { in_flight, mailbox };
+ self.write_ok("Authentication successful").await
+ /*} else {
match &self.state {
State::NotAuthenticated {
auth_failures,
@@ -188,7 +132,7 @@ impl<T: SessionStream> Session<T> {
Err(())
}
}
- }
+ }*/
}
pub fn get_concurrency_limiter(&self, account_id: u32) -> Option<Arc<ConcurrencyLimiters>> {
diff --git a/crates/pop3/src/op/delete.rs b/crates/pop3/src/op/delete.rs
index 7f127b0a..fba19ddc 100644
--- a/crates/pop3/src/op/delete.rs
+++ b/crates/pop3/src/op/delete.rs
@@ -7,11 +7,12 @@
use common::listener::SessionStream;
use jmap_proto::types::{state::StateChange, type_state::DataType};
use store::roaring::RoaringBitmap;
+use trc::AddContext;
-use crate::{Session, State};
+use crate::{protocol::response::Response, Session, State};
impl<T: SessionStream> Session<T> {
- pub async fn handle_dele(&mut self, msgs: Vec<u32>) -> Result<(), ()> {
+ pub async fn handle_dele(&mut self, msgs: Vec<u32>) -> trc::Result<()> {
let mailbox = self.state.mailbox_mut();
let mut response = Vec::new();
@@ -33,7 +34,7 @@ impl<T: SessionStream> Session<T> {
self.write_bytes(response).await
}
- pub async fn handle_rset(&mut self) -> Result<(), ()> {
+ pub async fn handle_rset(&mut self) -> trc::Result<()> {
let mut count = 0;
let mailbox = self.state.mailbox_mut();
for message in &mut mailbox.messages {
@@ -45,7 +46,7 @@ impl<T: SessionStream> Session<T> {
self.write_ok(format!("{count} messages undeleted")).await
}
- pub async fn handle_quit(&mut self) -> Result<(), ()> {
+ pub async fn handle_quit(&mut self) -> trc::Result<()> {
if let State::Authenticated { mailbox, .. } = &self.state {
let mut deleted = RoaringBitmap::new();
for message in &mailbox.messages {
@@ -56,39 +57,38 @@ impl<T: SessionStream> Session<T> {
if !deleted.is_empty() {
let num_deleted = deleted.len();
- match self
+ let (changes, not_deleted) = self
.jmap
.emails_tombstone(mailbox.account_id, deleted)
.await
- {
- Ok((changes, not_deleted)) => {
- if !changes.is_empty() {
- if let Ok(change_id) =
- self.jmap.commit_changes(mailbox.account_id, changes).await
- {
- self.jmap
- .broadcast_state_change(
- StateChange::new(mailbox.account_id)
- .with_change(DataType::Email, change_id)
- .with_change(DataType::Mailbox, change_id)
- .with_change(DataType::Thread, change_id),
- )
- .await;
- }
- }
- if not_deleted.is_empty() {
- self.write_ok(format!(
- "Stalwart POP3 bids you farewell ({num_deleted} messages deleted)."
- ))
- .await?;
- } else {
- self.write_err("Some messages could not be deleted").await?;
- }
- }
- Err(_) => {
- self.write_err("Failed to delete messages").await?;
+ .caused_by(trc::location!())?;
+
+ if !changes.is_empty() {
+ if let Ok(change_id) =
+ self.jmap.commit_changes(mailbox.account_id, changes).await
+ {
+ self.jmap
+ .broadcast_state_change(
+ StateChange::new(mailbox.account_id)
+ .with_change(DataType::Email, change_id)
+ .with_change(DataType::Mailbox, change_id)
+ .with_change(DataType::Thread, change_id),
+ )
+ .await;
}
}
+ if not_deleted.is_empty() {
+ self.write_ok(format!(
+ "Stalwart POP3 bids you farewell ({num_deleted} messages deleted)."
+ ))
+ .await?;
+ } else {
+ self.write_bytes(
+ Response::Err::<u32>("Some messages could not be deleted".into())
+ .serialize(),
+ )
+ .await?;
+ }
} else {
self.write_ok("Stalwart POP3 bids you farewell (no messages deleted).")
.await?;
@@ -97,6 +97,6 @@ impl<T: SessionStream> Session<T> {
self.write_ok("Stalwart POP3 bids you farewell.").await?;
}
- Err(())
+ Ok(())
}
}
diff --git a/crates/pop3/src/op/fetch.rs b/crates/pop3/src/op/fetch.rs
index 19b48fe0..91f8043d 100644
--- a/crates/pop3/src/op/fetch.rs
+++ b/crates/pop3/src/op/fetch.rs
@@ -8,14 +8,15 @@ use common::listener::SessionStream;
use jmap::email::metadata::MessageMetadata;
use jmap_proto::types::{collection::Collection, property::Property};
use store::write::Bincode;
+use trc::AddContext;
use crate::{protocol::response::Response, Session};
impl<T: SessionStream> Session<T> {
- pub async fn handle_fetch(&mut self, msg: u32, lines: Option<u32>) -> Result<(), ()> {
+ pub async fn handle_fetch(&mut self, msg: u32, lines: Option<u32>) -> trc::Result<()> {
let mailbox = self.state.mailbox();
if let Some(message) = mailbox.messages.get(msg.saturating_sub(1) as usize) {
- match self
+ if let Some(metadata) = self
.jmap
.get_property::<Bincode<MessageMetadata>>(
mailbox.account_id,
@@ -24,38 +25,39 @@ impl<T: SessionStream> Session<T> {
&Property::BodyStructure,
)
.await
+ .caused_by(trc::location!())?
{
- Ok(Some(metadata)) => {
- match self
- .jmap
- .get_blob(&metadata.inner.blob_hash, 0..usize::MAX)
- .await
- {
- Ok(Some(bytes)) => {
- self.write_bytes(
- Response::Message::<u32> {
- bytes,
- lines: lines.unwrap_or(0),
- }
- .serialize(),
- )
- .await
+ if let Some(bytes) = self
+ .jmap
+ .get_blob(&metadata.inner.blob_hash, 0..usize::MAX)
+ .await
+ .caused_by(trc::location!())?
+ {
+ self.write_bytes(
+ Response::Message::<u32> {
+ bytes,
+ lines: lines.unwrap_or(0),
}
- _ => {
- self.write_err(
- "Failed to fetch message. Perhaps another session deleted it?",
- )
- .await
- }
- }
- }
- _ => {
- self.write_err("Failed to fetch message. Perhaps another session deleted it?")
- .await
+ .serialize(),
+ )
+ .await
+ } else {
+ Err(trc::Cause::Pop3
+ .into_err()
+ .details("Failed to fetch message. Perhaps another session deleted it?")
+ .caused_by(trc::location!()))
}
+ } else {
+ Err(trc::Cause::Pop3
+ .into_err()
+ .details("Failed to fetch message. Perhaps another session deleted it?")
+ .caused_by(trc::location!()))
}
} else {
- self.write_err("No such message").await
+ Err(trc::Cause::Pop3
+ .into_err()
+ .details("No such message.")
+ .caused_by(trc::location!()))
}
}
}
diff --git a/crates/pop3/src/op/list.rs b/crates/pop3/src/op/list.rs
index 0ee40097..ced3014d 100644
--- a/crates/pop3/src/op/list.rs
+++ b/crates/pop3/src/op/list.rs
@@ -9,13 +9,16 @@ use common::listener::SessionStream;
use crate::{protocol::response::Response, Session};
impl<T: SessionStream> Session<T> {
- pub async fn handle_list(&mut self, msg: Option<u32>) -> Result<(), ()> {
+ pub async fn handle_list(&mut self, msg: Option<u32>) -> trc::Result<()> {
let mailbox = self.state.mailbox();
if let Some(msg) = msg {
if let Some(message) = mailbox.messages.get(msg.saturating_sub(1) as usize) {
self.write_ok(format!("{} {}", msg, message.size)).await
} else {
- self.write_err("No such message").await
+ Err(trc::Cause::Pop3
+ .into_err()
+ .details("No such message.")
+ .caused_by(trc::location!()))
}
} else {
self.write_bytes(
@@ -26,14 +29,17 @@ impl<T: SessionStream> Session<T> {
}
}
- pub async fn handle_uidl(&mut self, msg: Option<u32>) -> Result<(), ()> {
+ pub async fn handle_uidl(&mut self, msg: Option<u32>) -> trc::Result<()> {
let mailbox = self.state.mailbox();
if let Some(msg) = msg {
if let Some(message) = mailbox.messages.get(msg.saturating_sub(1) as usize) {
self.write_ok(format!("{} {}{}", msg, mailbox.uid_validity, message.uid))
.await
} else {
- self.write_err("No such message").await
+ Err(trc::Cause::Pop3
+ .into_err()
+ .details("No such message.")
+ .caused_by(trc::location!()))
}
} else {
self.write_bytes(
@@ -50,7 +56,7 @@ impl<T: SessionStream> Session<T> {
}
}
- pub async fn handle_stat(&mut self) -> Result<(), ()> {
+ pub async fn handle_stat(&mut self) -> trc::Result<()> {
let mailbox = self.state.mailbox();
self.write_ok(format!("{} {}", mailbox.total, mailbox.size))
.await
diff --git a/crates/pop3/src/protocol/response.rs b/crates/pop3/src/protocol/response.rs
index f1f9ec69..2b026a8f 100644
--- a/crates/pop3/src/protocol/response.rs
+++ b/crates/pop3/src/protocol/response.rs
@@ -146,6 +146,25 @@ impl Mechanism {
}
}
+pub trait SerializeResponse {
+ fn serialize(&self) -> Vec<u8>;
+}
+
+impl SerializeResponse for trc::Error {
+ fn serialize(&self) -> Vec<u8> {
+ let todo = "serialize messages properly in all protocols";
+ let message = self
+ .value_as_str(trc::Key::Details)
+ .or_else(|| self.value_as_str(trc::Key::Reason))
+ .unwrap_or("Internal Server Error");
+ let mut buf = Vec::with_capacity(message.len() + 6);
+ buf.extend_from_slice(b"-ERR ");
+ buf.extend_from_slice(message.as_bytes());
+ buf.extend_from_slice(b"\r\n");
+ buf
+ }
+}
+
#[cfg(test)]
mod tests {
diff --git a/crates/pop3/src/session.rs b/crates/pop3/src/session.rs
index ff3f785d..9db33f56 100644
--- a/crates/pop3/src/session.rs
+++ b/crates/pop3/src/session.rs
@@ -6,12 +6,15 @@
use std::borrow::Cow;
-use common::listener::{SessionData, SessionManager, SessionStream};
+use common::listener::{SessionData, SessionManager, SessionResult, SessionStream};
use jmap::JMAP;
use tokio_rustls::server::TlsStream;
use crate::{
- protocol::{request::Parser, response::Response},
+ protocol::{
+ request::Parser,
+ response::{Response, SerializeResponse},
+ },
Pop3SessionManager, Session, State, SERVER_GREETING,
};
@@ -77,11 +80,11 @@ impl<T: SessionStream> Session<T> {
Ok(Ok(bytes_read)) => {
if bytes_read > 0 {
match self.ingest(&buf[..bytes_read]).await {
- Ok(true) => (),
- Ok(false) => {
+ SessionResult::Continue => (),
+ SessionResult::UpgradeTls => {
return true;
}
- Err(_) => {
+ SessionResult::Close => {
tracing::debug!(parent: &self.span, event = "disconnect", "Disconnecting client.");
break;
}
@@ -129,7 +132,7 @@ impl<T: SessionStream> Session<T> {
}
impl<T: SessionStream> Session<T> {
- pub async fn write_bytes(&mut self, bytes: impl AsRef<[u8]>) -> Result<(), ()> {
+ pub async fn write_bytes(&mut self, bytes: impl AsRef<[u8]>) -> trc::Result<()> {
let bytes = bytes.as_ref();
/*for line in String::from_utf8_lossy(bytes.as_ref()).split("\r\n") {
let c = println!("{}", line);
@@ -141,22 +144,23 @@ impl<T: SessionStream> Session<T> {
size = bytes.len()
);
- if let Err(err) = self.stream.write_all(bytes.as_ref()).await {
- tracing::trace!(parent: &self.span, "Failed to write to stream: {}", err);
- Err(())
- } else {
- let _ = self.stream.flush().await;
- Ok(())
- }
+ self.stream
+ .write_all(bytes.as_ref())
+ .await
+ .map_err(|err| trc::Cause::Network.reason(err).caused_by(trc::location!()))?;
+ self.stream
+ .flush()
+ .await
+ .map_err(|err| trc::Cause::Network.reason(err).caused_by(trc::location!()))
}
- pub async fn write_ok(&mut self, message: impl Into<Cow<'static, str>>) -> Result<(), ()> {
+ pub async fn write_ok(&mut self, message: impl Into<Cow<'static, str>>) -> trc::Result<()> {
self.write_bytes(Response::Ok::<u32>(message.into()).serialize())
.await
}
- pub async fn write_err(&mut self, message: impl Into<Cow<'static, str>>) -> Result<(), ()> {
- self.write_bytes(Response::Err::<u32>(message.into()).serialize())
- .await
+ pub async fn write_err(&mut self, err: trc::Error) -> trc::Result<()> {
+ tracing::error!(parent: &self.span, "POP3 error: {}", err);
+ self.write_bytes(err.serialize()).await
}
}
diff --git a/crates/smtp/src/inbound/auth.rs b/crates/smtp/src/inbound/auth.rs
index ec33d866..052235e2 100644
--- a/crates/smtp/src/inbound/auth.rs
+++ b/crates/smtp/src/inbound/auth.rs
@@ -196,7 +196,7 @@ impl<T: SessionStream> Session<T> {
return Ok(false);
}
Err(err) => match err.as_ref() {
- trc::Cause::Authentication => {
+ trc::Cause::Auth(trc::AuthCause::Failed) => {
tracing::debug!(
parent: &self.span,
context = "auth",
@@ -208,7 +208,7 @@ impl<T: SessionStream> Session<T> {
.auth_error(b"535 5.7.8 Authentication credentials invalid.\r\n")
.await;
}
- trc::Cause::MissingTotp => {
+ trc::Cause::Auth(trc::AuthCause::MissingTotp) => {
tracing::debug!(
parent: &self.span,
context = "auth",
@@ -222,7 +222,7 @@ impl<T: SessionStream> Session<T> {
)
.await;
}
- trc::Cause::Banned => {
+ trc::Cause::Auth(trc::AuthCause::Banned) => {
tracing::debug!(
parent: &self.span,
context = "auth",
diff --git a/crates/smtp/src/inbound/vrfy.rs b/crates/smtp/src/inbound/vrfy.rs
index dfc722b7..bf6126fa 100644
--- a/crates/smtp/src/inbound/vrfy.rs
+++ b/crates/smtp/src/inbound/vrfy.rs
@@ -57,7 +57,7 @@ impl<T: SessionStream> Session<T> {
event = "temp-fail",
address = &address);
- if !err.matches(trc::Cause::Unsupported) {
+ if !err.matches(trc::Cause::Store(trc::StoreCause::NotSupported)) {
self.write(b"252 2.4.3 Unable to verify address at this time.\r\n")
.await
} else {
@@ -122,7 +122,7 @@ impl<T: SessionStream> Session<T> {
event = "temp-fail",
address = &address);
- if !err.matches(trc::Cause::Unsupported) {
+ if !err.matches(trc::Cause::Store(trc::StoreCause::NotSupported)) {
self.write(b"252 2.4.3 Unable to expand mailing list at this time.\r\n")
.await
} else {
diff --git a/crates/smtp/src/queue/spool.rs b/crates/smtp/src/queue/spool.rs
index a6758ff1..e3ea8f10 100644
--- a/crates/smtp/src/queue/spool.rs
+++ b/crates/smtp/src/queue/spool.rs
@@ -127,7 +127,7 @@ impl SMTP {
);
match self.core.storage.data.write(batch.build()).await {
Ok(_) => Some(event),
- Err(err) if err.matches(trc::Cause::AssertValue) => {
+ Err(err) if err.is_assertion_failure() => {
tracing::debug!(
context = "queue",
event = "locked",
diff --git a/crates/smtp/src/reporting/scheduler.rs b/crates/smtp/src/reporting/scheduler.rs
index 3b198928..7000f376 100644
--- a/crates/smtp/src/reporting/scheduler.rs
+++ b/crates/smtp/src/reporting/scheduler.rs
@@ -173,7 +173,7 @@ impl SMTP {
);
match self.core.storage.data.write(batch.build()).await {
Ok(_) => true,
- Err(err) if err.matches(trc::Cause::AssertValue) => {
+ Err(err) if err.is_assertion_failure() => {
tracing::debug!(
context = "queue",
event = "locked",
diff --git a/crates/store/src/backend/elastic/mod.rs b/crates/store/src/backend/elastic/mod.rs
index 99cb998d..abcf1ba0 100644
--- a/crates/store/src/backend/elastic/mod.rs
+++ b/crates/store/src/backend/elastic/mod.rs
@@ -111,7 +111,7 @@ impl ElasticSearchStore {
.exists(IndicesExistsParts::Index(&[INDEX_NAMES[0]]))
.send()
.await
- .map_err(|err| trc::Cause::ElasticSearch.reason(err))?;
+ .map_err(|err| trc::StoreCause::ElasticSearch.reason(err))?;
if exists.status_code() == StatusCode::NOT_FOUND {
let response = self
@@ -183,11 +183,11 @@ pub(crate) async fn assert_success(response: Result<Response, Error>) -> trc::Re
if status.is_success() {
Ok(response)
} else {
- Err(trc::Cause::ElasticSearch
+ Err(trc::StoreCause::ElasticSearch
.reason(response.text().await.unwrap_or_default())
.ctx(trc::Key::Code, status.as_u16()))
}
}
- Err(err) => Err(trc::Cause::ElasticSearch.reason(err)),
+ Err(err) => Err(trc::StoreCause::ElasticSearch.reason(err)),
}
}
diff --git a/crates/store/src/backend/elastic/query.rs b/crates/store/src/backend/elastic/query.rs
index b2448f7d..624ae8a5 100644
--- a/crates/store/src/backend/elastic/query.rs
+++ b/crates/store/src/backend/elastic/query.rs
@@ -107,14 +107,14 @@ impl ElasticSearchStore {
let json: Value = response
.json()
.await
- .map_err(|err| trc::Cause::ElasticSearch.reason(err))?;
+ .map_err(|err| trc::StoreCause::ElasticSearch.reason(err))?;
let mut results = RoaringBitmap::new();
for hit in json["hits"]["hits"].as_array().ok_or_else(|| {
- trc::Cause::ElasticSearch.reason("Invalid response from ElasticSearch")
+ trc::StoreCause::ElasticSearch.reason("Invalid response from ElasticSearch")
})? {
results.insert(hit["_source"]["document_id"].as_u64().ok_or_else(|| {
- trc::Cause::ElasticSearch.reason("Invalid response from ElasticSearch")
+ trc::StoreCause::ElasticSearch.reason("Invalid response from ElasticSearch")
})? as u32);
}
diff --git a/crates/store/src/backend/foundationdb/mod.rs b/crates/store/src/backend/foundationdb/mod.rs
index 3ea02014..52a28f7f 100644
--- a/crates/store/src/backend/foundationdb/mod.rs
+++ b/crates/store/src/backend/foundationdb/mod.rs
@@ -77,7 +77,7 @@ impl TimedTransaction {
#[inline(always)]
fn into_error(error: FdbError) -> trc::Error {
- trc::Cause::FoundationDB
+ trc::StoreCause::FoundationDB
.reason(error.message())
.ctx(trc::Key::Code, error.code())
}
diff --git a/crates/store/src/backend/foundationdb/write.rs b/crates/store/src/backend/foundationdb/write.rs
index 4a498995..9fd10f75 100644
--- a/crates/store/src/backend/foundationdb/write.rs
+++ b/crates/store/src/backend/foundationdb/write.rs
@@ -94,7 +94,7 @@ impl FdbStore {
*key.last_mut().unwrap() += 1;
} else {
trx.cancel();
- return Err(trc::Cause::FoundationDB.ctx(
+ return Err(trc::StoreCause::FoundationDB.ctx(
trc::Key::Reason,
"Value is too large",
));
@@ -257,7 +257,7 @@ impl FdbStore {
if !matches {
trx.cancel();
- return Err(trc::Cause::AssertValue.into());
+ return Err(trc::StoreCause::AssertValue.into());
}
}
}
diff --git a/crates/store/src/backend/fs/mod.rs b/crates/store/src/backend/fs/mod.rs
index f2695f2e..bc522c1b 100644
--- a/crates/store/src/backend/fs/mod.rs
+++ b/crates/store/src/backend/fs/mod.rs
@@ -57,7 +57,7 @@ impl FsStore {
Ok(m) => m.len() as usize,
Err(_) => return Ok(None),
};
- let mut blob = File::open(&blob_path).await?;
+ let mut blob = File::open(&blob_path).await.map_err(into_error)?;
Ok(Some(if range.start != 0 || range.end != usize::MAX {
let from_offset = if range.start < blob_size {
@@ -68,13 +68,15 @@ impl FsStore {
let mut buf = vec![0; (std::cmp::min(range.end, blob_size) - from_offset) as usize];
if from_offset > 0 {
- blob.seek(SeekFrom::Start(from_offset as u64)).await?;
+ blob.seek(SeekFrom::Start(from_offset as u64))
+ .await
+ .map_err(into_error)?;
}
- blob.read_exact(&mut buf).await?;
+ blob.read_exact(&mut buf).await.map_err(into_error)?;
buf
} else {
let mut buf = Vec::with_capacity(blob_size as usize);
- blob.read_to_end(&mut buf).await?;
+ blob.read_to_end(&mut buf).await.map_err(into_error)?;
buf
}))
}
@@ -86,10 +88,12 @@ impl FsStore {
.await
.map_or(true, |m| m.len() as usize != data.len())
{
- fs::create_dir_all(blob_path.parent().unwrap()).await?;
- let mut blob_file = File::create(&blob_path).await?;
- blob_file.write_all(data).await?;
- blob_file.flush().await?;
+ fs::create_dir_all(blob_path.parent().unwrap())
+ .await
+ .map_err(into_error)?;
+ let mut blob_file = File::create(&blob_path).await.map_err(into_error)?;
+ blob_file.write_all(data).await.map_err(into_error)?;
+ blob_file.flush().await.map_err(into_error)?;
}
Ok(())
@@ -98,7 +102,7 @@ impl FsStore {
pub(crate) async fn delete_blob(&self, key: &[u8]) -> trc::Result<bool> {
let blob_path = self.build_path(key);
if fs::metadata(&blob_path).await.is_ok() {
- fs::remove_file(&blob_path).await?;
+ fs::remove_file(&blob_path).await.map_err(into_error)?;
Ok(true)
} else {
Ok(false)
@@ -115,3 +119,7 @@ impl FsStore {
path
}
}
+
+fn into_error(err: std::io::Error) -> trc::Error {
+ trc::StoreCause::Filesystem.reason(err)
+}
diff --git a/crates/store/src/backend/mysql/mod.rs b/crates/store/src/backend/mysql/mod.rs
index 2a3d2d90..fa28d1bc 100644
--- a/crates/store/src/backend/mysql/mod.rs
+++ b/crates/store/src/backend/mysql/mod.rs
@@ -20,5 +20,5 @@ pub struct MysqlStore {
#[inline(always)]
fn into_error(err: impl Display) -> trc::Error {
- trc::Cause::MySQL.reason(err)
+ trc::StoreCause::MySQL.reason(err)
}
diff --git a/crates/store/src/backend/mysql/write.rs b/crates/store/src/backend/mysql/write.rs
index 5c7e7c08..ca20ba20 100644
--- a/crates/store/src/backend/mysql/write.rs
+++ b/crates/store/src/backend/mysql/write.rs
@@ -46,7 +46,7 @@ impl MysqlStore {
&& start.elapsed() < MAX_COMMIT_TIME => {}
Err(CommitError::Retry) => {
if retry_count > MAX_COMMIT_ATTEMPTS || start.elapsed() > MAX_COMMIT_TIME {
- return Err(trc::Cause::AssertValue.into());
+ return Err(trc::StoreCause::AssertValue.into());
}
}
Err(CommitError::Mysql(err)) => {
@@ -135,7 +135,7 @@ impl MysqlStore {
Ok(_) => {
if exists.is_some() && trx.affected_rows() == 0 {
trx.rollback().await?;
- return Err(trc::Cause::AssertValue.into_err().into());
+ return Err(trc::StoreCause::AssertValue.into_err().into());
}
}
Err(err) => {
@@ -308,7 +308,7 @@ impl MysqlStore {
.unwrap_or_else(|| (false, assert_value.is_none()));
if !matches {
trx.rollback().await?;
- return Err(trc::Cause::AssertValue.into_err().into());
+ return Err(trc::StoreCause::AssertValue.into_err().into());
}
asserted_values.insert(key, exists);
}
diff --git a/crates/store/src/backend/postgres/mod.rs b/crates/store/src/backend/postgres/mod.rs
index 81031e5d..63093154 100644
--- a/crates/store/src/backend/postgres/mod.rs
+++ b/crates/store/src/backend/postgres/mod.rs
@@ -21,5 +21,5 @@ pub struct PostgresStore {
#[inline(always)]
fn into_error(err: impl Display) -> trc::Error {
- trc::Cause::PostgreSQL.reason(err)
+ trc::StoreCause::PostgreSQL.reason(err)
}
diff --git a/crates/store/src/backend/postgres/write.rs b/crates/store/src/backend/postgres/write.rs
index 04b19d32..aaf4e825 100644
--- a/crates/store/src/backend/postgres/write.rs
+++ b/crates/store/src/backend/postgres/write.rs
@@ -50,7 +50,7 @@ impl PostgresStore {
) if retry_count < MAX_COMMIT_ATTEMPTS
&& start.elapsed() < MAX_COMMIT_TIME => {}
Some(&SqlState::UNIQUE_VIOLATION) => {
- return Err(trc::Cause::AssertValue.into());
+ return Err(trc::StoreCause::AssertValue.into());
}
_ => return Err(into_error(err)),
},
@@ -59,7 +59,7 @@ impl PostgresStore {
if retry_count > MAX_COMMIT_ATTEMPTS
|| start.elapsed() > MAX_COMMIT_TIME
{
- return Err(trc::Cause::AssertValue.into());
+ return Err(trc::StoreCause::AssertValue.into());
}
}
}
@@ -148,7 +148,7 @@ impl PostgresStore {
.await?
== 0
{
- return Err(trc::Cause::AssertValue.into_err().into());
+ return Err(trc::StoreCause::AssertValue.into_err().into());
}
}
ValueOp::AtomicAdd(by) => {
@@ -322,7 +322,7 @@ impl PostgresStore {
})
.unwrap_or_else(|| (false, assert_value.is_none()));
if !matches {
- return Err(trc::Cause::AssertValue.into_err().into());
+ return Err(trc::StoreCause::AssertValue.into_err().into());
}
asserted_values.insert(key, exists);
}
diff --git a/crates/store/src/backend/redis/mod.rs b/crates/store/src/backend/redis/mod.rs
index 479b2c5e..6ce007e2 100644
--- a/crates/store/src/backend/redis/mod.rs
+++ b/crates/store/src/backend/redis/mod.rs
@@ -184,5 +184,5 @@ fn build_pool<M: Manager>(
#[inline(always)]
fn into_error(err: impl Display) -> trc::Error {
- trc::Cause::Redis.reason(err)
+ trc::StoreCause::Redis.reason(err)
}
diff --git a/crates/store/src/backend/redis/pool.rs b/crates/store/src/backend/redis/pool.rs
index b8f01863..c322584e 100644
--- a/crates/store/src/backend/redis/pool.rs
+++ b/crates/store/src/backend/redis/pool.rs
@@ -21,7 +21,7 @@ impl managed::Manager for RedisConnectionManager {
.await
{
Ok(conn) => conn.map_err(into_error),
- Err(_) => Err(trc::Cause::Timeout.into()),
+ Err(_) => Err(trc::StoreCause::Redis.ctx(trc::Key::Details, "Connection Timeout")),
}
}
@@ -44,7 +44,7 @@ impl managed::Manager for RedisClusterConnectionManager {
async fn create(&self) -> Result<ClusterConnection, trc::Error> {
match tokio::time::timeout(self.timeout, self.client.get_async_connection()).await {
Ok(conn) => conn.map_err(into_error),
- Err(_) => Err(trc::Cause::Timeout.into()),
+ Err(_) => Err(trc::StoreCause::Redis.ctx(trc::Key::Details, "Connection Timeout")),
}
}
diff --git a/crates/store/src/backend/rocksdb/mod.rs b/crates/store/src/backend/rocksdb/mod.rs
index 33d273a9..78b80e86 100644
--- a/crates/store/src/backend/rocksdb/mod.rs
+++ b/crates/store/src/backend/rocksdb/mod.rs
@@ -38,5 +38,5 @@ pub struct RocksDbStore {
#[inline(always)]
fn into_error(err: rocksdb::Error) -> trc::Error {
- trc::Cause::RocksDB.reason(err)
+ trc::StoreCause::RocksDB.reason(err)
}
diff --git a/crates/store/src/backend/rocksdb/write.rs b/crates/store/src/backend/rocksdb/write.rs
index 8639e2b7..d8c47b2b 100644
--- a/crates/store/src/backend/rocksdb/write.rs
+++ b/crates/store/src/backend/rocksdb/write.rs
@@ -308,7 +308,7 @@ impl<'x> RocksDBTransaction<'x> {
if !matches {
txn.rollback()?;
- return Err(CommitError::Internal(trc::Cause::AssertValue.into()));
+ return Err(CommitError::Internal(trc::StoreCause::AssertValue.into()));
}
}
}
diff --git a/crates/store/src/backend/s3/mod.rs b/crates/store/src/backend/s3/mod.rs
index 70b314e1..dee52205 100644
--- a/crates/store/src/backend/s3/mod.rs
+++ b/crates/store/src/backend/s3/mod.rs
@@ -90,7 +90,7 @@ impl S3Store {
match response.status_code() {
200..=299 => Ok(Some(response.to_vec())),
404 => Ok(None),
- code => Err(trc::Cause::S3
+ code => Err(trc::StoreCause::S3
.reason(String::from_utf8_lossy(response.as_slice()))
.ctx(trc::Key::Code, code)),
}
@@ -105,7 +105,7 @@ impl S3Store {
match response.status_code() {
200..=299 => Ok(()),
- code => Err(trc::Cause::S3
+ code => Err(trc::StoreCause::S3
.reason(String::from_utf8_lossy(response.as_slice()))
.ctx(trc::Key::Code, code)),
}
@@ -121,7 +121,7 @@ impl S3Store {
match response.status_code() {
200..=299 => Ok(true),
404 => Ok(false),
- code => Err(trc::Cause::S3
+ code => Err(trc::StoreCause::S3
.reason(String::from_utf8_lossy(response.as_slice()))
.ctx(trc::Key::Code, code)),
}
@@ -142,5 +142,5 @@ impl S3Store {
#[inline(always)]
fn into_error(err: impl Display) -> trc::Error {
- trc::Cause::S3.reason(err)
+ trc::StoreCause::S3.reason(err)
}
diff --git a/crates/store/src/backend/sqlite/mod.rs b/crates/store/src/backend/sqlite/mod.rs
index 6cd07502..3bc632f8 100644
--- a/crates/store/src/backend/sqlite/mod.rs
+++ b/crates/store/src/backend/sqlite/mod.rs
@@ -24,5 +24,5 @@ pub struct SqliteStore {
#[inline(always)]
fn into_error(err: impl Display) -> trc::Error {
- trc::Cause::SQLite.reason(err)
+ trc::StoreCause::SQLite.reason(err)
}
diff --git a/crates/store/src/backend/sqlite/write.rs b/crates/store/src/backend/sqlite/write.rs
index c5050462..c07f42df 100644
--- a/crates/store/src/backend/sqlite/write.rs
+++ b/crates/store/src/backend/sqlite/write.rs
@@ -245,7 +245,7 @@ impl SqliteStore {
.unwrap_or_else(|| assert_value.is_none());
if !matches {
trx.rollback().map_err(into_error)?;
- return Err(trc::Cause::AssertValue.into());
+ return Err(trc::StoreCause::AssertValue.into());
}
}
}
diff --git a/crates/store/src/dispatch/blob.rs b/crates/store/src/dispatch/blob.rs
index f774b855..68303596 100644
--- a/crates/store/src/dispatch/blob.rs
+++ b/crates/store/src/dispatch/blob.rs
@@ -30,7 +30,7 @@ impl BlobStore {
Store::MySQL(store) => store.get_blob(key, read_range).await,
#[cfg(feature = "rocks")]
Store::RocksDb(store) => store.get_blob(key, read_range).await,
- Store::None => Err(trc::Cause::NotConfigured.into()),
+ Store::None => Err(trc::StoreCause::NotConfigured.into()),
},
BlobBackend::Fs(store) => store.get_blob(key, read_range).await,
#[cfg(feature = "s3")]
@@ -47,14 +47,14 @@ impl BlobStore {
data.get(..data.len() - 1).unwrap_or_default(),
)
.map_err(|err| {
- trc::Cause::Decompress
+ trc::StoreCause::Decompress
.reason(err)
.ctx(trc::Key::Key, key)
.ctx(trc::Key::CausedBy, trc::location!())
})?
}
Some(data) => {
- trc::error!(BlobMissingMarker, Details = key);
+ let todo = "log";
data
}
None => return Ok(None),
@@ -96,7 +96,7 @@ impl BlobStore {
Store::MySQL(store) => store.put_blob(key, data.as_ref()).await,
#[cfg(feature = "rocks")]
Store::RocksDb(store) => store.put_blob(key, data.as_ref()).await,
- Store::None => Err(trc::Cause::NotConfigured.into()),
+ Store::None => Err(trc::StoreCause::NotConfigured.into()),
},
BlobBackend::Fs(store) => store.put_blob(key, data.as_ref()).await,
#[cfg(feature = "s3")]
@@ -118,7 +118,7 @@ impl BlobStore {
Store::MySQL(store) => store.delete_blob(key).await,
#[cfg(feature = "rocks")]
Store::RocksDb(store) => store.delete_blob(key).await,
- Store::None => Err(trc::Cause::NotConfigured.into()),
+ Store::None => Err(trc::StoreCause::NotConfigured.into()),
},
BlobBackend::Fs(store) => store.delete_blob(key).await,
#[cfg(feature = "s3")]
diff --git a/crates/store/src/dispatch/lookup.rs b/crates/store/src/dispatch/lookup.rs
index 8ab3aaed..afe02835 100644
--- a/crates/store/src/dispatch/lookup.rs
+++ b/crates/store/src/dispatch/lookup.rs
@@ -32,7 +32,7 @@ impl LookupStore {
LookupStore::Store(Store::PostgreSQL(store)) => store.query(query, &params).await,
#[cfg(feature = "mysql")]
LookupStore::Store(Store::MySQL(store)) => store.query(query, &params).await,
- _ => Err(trc::Cause::Unsupported.into_err()),
+ _ => Err(trc::StoreCause::NotSupported.into_err()),
};
trc::trace!(
@@ -42,7 +42,7 @@ impl LookupStore {
Result = &result,
);
- result.caused_by( trc::location!())
+ result.caused_by(trc::location!())
}
pub async fn key_set(
@@ -76,9 +76,9 @@ impl LookupStore {
)
.await
.map(|_| ()),
- LookupStore::Memory(_) => Err(trc::Cause::Unsupported.into_err()),
+ LookupStore::Memory(_) => Err(trc::StoreCause::NotSupported.into_err()),
}
- .caused_by( trc::location!())
+ .caused_by(trc::location!())
}
pub async fn counter_incr(
@@ -125,10 +125,10 @@ impl LookupStore {
#[cfg(feature = "redis")]
LookupStore::Redis(store) => store.key_incr(key, value, expires).await,
LookupStore::Query(_) | LookupStore::Memory(_) => {
- Err(trc::Cause::Unsupported.into_err())
+ Err(trc::StoreCause::NotSupported.into_err())
}
}
- .caused_by( trc::location!())
+ .caused_by(trc::location!())
}
pub async fn key_delete(&self, key: Vec<u8>) -> trc::Result<()> {
@@ -144,10 +144,10 @@ impl LookupStore {
#[cfg(feature = "redis")]
LookupStore::Redis(store) => store.key_delete(key).await,
LookupStore::Query(_) | LookupStore::Memory(_) => {
- Err(trc::Cause::Unsupported.into_err())
+ Err(trc::StoreCause::NotSupported.into_err())
}
}
- .caused_by( trc::location!())
+ .caused_by(trc::location!())
}
pub async fn counter_delete(&self, key: Vec<u8>) -> trc::Result<()> {
@@ -163,10 +163,10 @@ impl LookupStore {
#[cfg(feature = "redis")]
LookupStore::Redis(store) => store.key_delete(key).await,
LookupStore::Query(_) | LookupStore::Memory(_) => {
- Err(trc::Cause::Unsupported.into_err())
+ Err(trc::StoreCause::NotSupported.into_err())
}
}
- .caused_by( trc::location!())
+ .caused_by(trc::location!())
}
pub async fn key_get<T: Deserialize + From<Value<'static>> + std::fmt::Debug + 'static>(
@@ -197,7 +197,7 @@ impl LookupStore {
.get(std::str::from_utf8(&key).unwrap_or_default())
.map(|value| T::from(value.clone()))),
}
- .caused_by( trc::location!())
+ .caused_by(trc::location!())
}
pub async fn counter_get(&self, key: Vec<u8>) -> trc::Result<i64> {
@@ -212,10 +212,10 @@ impl LookupStore {
#[cfg(feature = "redis")]
LookupStore::Redis(store) => store.counter_get(key).await,
LookupStore::Query(_) | LookupStore::Memory(_) => {
- Err(trc::Cause::Unsupported.into_err())
+ Err(trc::StoreCause::NotSupported.into_err())
}
}
- .caused_by( trc::location!())
+ .caused_by(trc::location!())
}
pub async fn key_exists(&self, key: Vec<u8>) -> trc::Result<bool> {
@@ -240,7 +240,7 @@ impl LookupStore {
.get(std::str::from_utf8(&key).unwrap_or_default())
.is_some()),
}
- .caused_by( trc::location!())
+ .caused_by(trc::location!())
}
pub async fn is_rate_allowed(
@@ -261,12 +261,9 @@ impl LookupStore {
let requests = if !soft_check {
self.counter_incr(bucket, 1, expires_in.into(), true)
.await
- .caused_by( trc::location!())?
+ .caused_by(trc::location!())?
} else {
- self.counter_get(bucket)
- .await
- .caused_by( trc::location!())?
- + 1
+ self.counter_get(bucket).await.caused_by(trc::location!())? + 1
};
if requests <= rate.requests as i64 {
@@ -289,11 +286,11 @@ impl LookupStore {
let mut expired_counters = Vec::new();
store
.iterate(IterateParams::new(from_key, to_key), |key, value| {
- let expiry = value.deserialize_be_u64(0).caused_by( trc::location!())?;
+ let expiry = value.deserialize_be_u64(0).caused_by(trc::location!())?;
if expiry == 0 {
if value
.deserialize_be_u64(U64_LEN)
- .caused_by( trc::location!())?
+ .caused_by(trc::location!())?
<= current_time
{
expired_counters.push(key.to_vec());
@@ -304,7 +301,7 @@ impl LookupStore {
Ok(true)
})
.await
- .caused_by( trc::location!())?;
+ .caused_by(trc::location!())?;
if !expired_keys.is_empty() {
let mut batch = BatchBuilder::new();
@@ -317,7 +314,7 @@ impl LookupStore {
store
.write(batch.build())
.await
- .caused_by( trc::location!())?;
+ .caused_by(trc::location!())?;
batch = BatchBuilder::new();
}
}
@@ -325,7 +322,7 @@ impl LookupStore {
store
.write(batch.build())
.await
- .caused_by( trc::location!())?;
+ .caused_by(trc::location!())?;
}
}
@@ -344,7 +341,7 @@ impl LookupStore {
store
.write(batch.build())
.await
- .caused_by( trc::location!())?;
+ .caused_by(trc::location!())?;
batch = BatchBuilder::new();
}
}
@@ -352,7 +349,7 @@ impl LookupStore {
store
.write(batch.build())
.await
- .caused_by( trc::location!())?;
+ .caused_by(trc::location!())?;
}
}
}
@@ -383,7 +380,7 @@ impl<T: Deserialize> Deserialize for LookupValue<T> {
Ok(if expires > now() {
LookupValue::Value(
T::deserialize(bytes.get(U64_LEN..).unwrap_or_default())
- .caused_by( trc::location!())?,
+ .caused_by(trc::location!())?,
)
} else {
LookupValue::None
diff --git a/crates/store/src/dispatch/store.rs b/crates/store/src/dispatch/store.rs
index 8981a5da..badccd41 100644
--- a/crates/store/src/dispatch/store.rs
+++ b/crates/store/src/dispatch/store.rs
@@ -43,9 +43,9 @@ impl Store {
Self::MySQL(store) => store.get_value(key).await,
#[cfg(feature = "rocks")]
Self::RocksDb(store) => store.get_value(key).await,
- Self::None => Err(trc::Cause::NotConfigured.into()),
+ Self::None => Err(trc::StoreCause::NotConfigured.into()),
}
- .caused_by( trc::location!())
+ .caused_by(trc::location!())
}
pub async fn get_bitmap(
@@ -63,9 +63,9 @@ impl Store {
Self::MySQL(store) => store.get_bitmap(key).await,
#[cfg(feature = "rocks")]
Self::RocksDb(store) => store.get_bitmap(key).await,
- Self::None => Err(trc::Cause::NotConfigured.into()),
+ Self::None => Err(trc::StoreCause::NotConfigured.into()),
}
- .caused_by( trc::location!())
+ .caused_by(trc::location!())
}
pub async fn get_bitmaps_intersection(
@@ -74,7 +74,7 @@ impl Store {
) -> trc::Result<Option<RoaringBitmap>> {
let mut result: Option<RoaringBitmap> = None;
for key in keys {
- if let Some(bitmap) = self.get_bitmap(key).await.caused_by( trc::location!())? {
+ if let Some(bitmap) = self.get_bitmap(key).await.caused_by(trc::location!())? {
if let Some(result) = &mut result {
result.bitand_assign(&bitmap);
if result.is_empty() {
@@ -106,9 +106,9 @@ impl Store {
Self::MySQL(store) => store.iterate(params, cb).await,
#[cfg(feature = "rocks")]
Self::RocksDb(store) => store.iterate(params, cb).await,
- Self::None => Err(trc::Cause::NotConfigured.into()),
+ Self::None => Err(trc::StoreCause::NotConfigured.into()),
}
- .caused_by( trc::location!())
+ .caused_by(trc::location!())
}
pub async fn get_counter(
@@ -126,9 +126,9 @@ impl Store {
Self::MySQL(store) => store.get_counter(key).await,
#[cfg(feature = "rocks")]
Self::RocksDb(store) => store.get_counter(key).await,
- Self::None => Err(trc::Cause::NotConfigured.into()),
+ Self::None => Err(trc::StoreCause::NotConfigured.into()),
}
- .caused_by( trc::location!())
+ .caused_by(trc::location!())
}
pub async fn write(&self, batch: Batch) -> trc::Result<AssignedIds> {
@@ -189,9 +189,9 @@ impl Store {
Self::MySQL(store) => store.write(batch).await,
#[cfg(feature = "rocks")]
Self::RocksDb(store) => store.write(batch).await,
- Self::None => Err(trc::Cause::NotConfigured.into()),
+ Self::None => Err(trc::StoreCause::NotConfigured.into()),
}
- .caused_by( trc::location!())?;
+ .caused_by(trc::location!())?;
for (key, class, document_id, set) in bitmaps {
let mut bitmaps = BITMAPS.lock();
@@ -231,7 +231,7 @@ impl Store {
Self::MySQL(store) => store.write(batch).await,
#[cfg(feature = "rocks")]
Self::RocksDb(store) => store.write(batch).await,
- Self::None => Err(trc::Cause::NotConfigured.into()),
+ Self::None => Err(trc::StoreCause::NotConfigured.into()),
}
}
@@ -246,7 +246,7 @@ impl Store {
})),
)
.await
- .caused_by( trc::location!())?;
+ .caused_by(trc::location!())?;
self.delete_range(
ValueKey::from(ValueClass::Report(ReportClass::Tls { id: 0, expires: 0 })),
ValueKey::from(ValueClass::Report(ReportClass::Tls {
@@ -255,7 +255,7 @@ impl Store {
})),
)
.await
- .caused_by( trc::location!())?;
+ .caused_by(trc::location!())?;
self.delete_range(
ValueKey::from(ValueClass::Report(ReportClass::Arf { id: 0, expires: 0 })),
ValueKey::from(ValueClass::Report(ReportClass::Arf {
@@ -264,7 +264,7 @@ impl Store {
})),
)
.await
- .caused_by( trc::location!())?;
+ .caused_by(trc::location!())?;
match self {
#[cfg(feature = "sqlite")]
@@ -277,9 +277,9 @@ impl Store {
Self::MySQL(store) => store.purge_store().await,
#[cfg(feature = "rocks")]
Self::RocksDb(store) => store.purge_store().await,
- Self::None => Err(trc::Cause::NotConfigured.into()),
+ Self::None => Err(trc::StoreCause::NotConfigured.into()),
}
- .caused_by( trc::location!())
+ .caused_by(trc::location!())
}
pub async fn delete_range(&self, from: impl Key, to: impl Key) -> trc::Result<()> {
@@ -294,9 +294,9 @@ impl Store {
Self::MySQL(store) => store.delete_range(from, to).await,
#[cfg(feature = "rocks")]
Self::RocksDb(store) => store.delete_range(from, to).await,
- Self::None => Err(trc::Cause::NotConfigured.into()),
+ Self::None => Err(trc::StoreCause::NotConfigured.into()),
}
- .caused_by( trc::location!())
+ .caused_by(trc::location!())
}
pub async fn delete_documents(
@@ -352,7 +352,7 @@ impl Store {
},
)
.await
- .caused_by( trc::location!())?;
+ .caused_by(trc::location!())?;
// Remove keys
let mut batch = BatchBuilder::new();
@@ -361,7 +361,7 @@ impl Store {
if batch.ops.len() >= 1000 {
self.write(std::mem::take(&mut batch).build())
.await
- .caused_by( trc::location!())?;
+ .caused_by(trc::location!())?;
}
batch.ops.push(Operation::Value {
class: ValueClass::Any(AnyClass { subspace, key }),
@@ -372,7 +372,7 @@ impl Store {
if !batch.is_empty() {
self.write(batch.build())
.await
- .caused_by( trc::location!())?;
+ .caused_by(trc::location!())?;
}
Ok(())
@@ -397,7 +397,7 @@ impl Store {
},
)
.await
- .caused_by( trc::location!())?;
+ .caused_by(trc::location!())?;
}
for (from_class, to_class) in [
@@ -429,7 +429,7 @@ impl Store {
},
)
.await
- .caused_by( trc::location!())?;
+ .caused_by(trc::location!())?;
}
Ok(())
@@ -447,9 +447,9 @@ impl Store {
Self::MySQL(store) => store.get_blob(key, range).await,
#[cfg(feature = "rocks")]
Self::RocksDb(store) => store.get_blob(key, range).await,
- Self::None => Err(trc::Cause::NotConfigured.into()),
+ Self::None => Err(trc::StoreCause::NotConfigured.into()),
}
- .caused_by( trc::location!())
+ .caused_by(trc::location!())
}
pub async fn put_blob(&self, key: &[u8], data: &[u8]) -> trc::Result<()> {
@@ -464,9 +464,9 @@ impl Store {
Self::MySQL(store) => store.put_blob(key, data).await,
#[cfg(feature = "rocks")]
Self::RocksDb(store) => store.put_blob(key, data).await,
- Self::None => Err(trc::Cause::NotConfigured.into()),
+ Self::None => Err(trc::StoreCause::NotConfigured.into()),
}
- .caused_by( trc::location!())
+ .caused_by(trc::location!())
}
pub async fn delete_blob(&self, key: &[u8]) -> trc::Result<bool> {
@@ -481,9 +481,9 @@ impl Store {
Self::MySQL(store) => store.delete_blob(key).await,
#[cfg(feature = "rocks")]
Self::RocksDb(store) => store.delete_blob(key).await,
- Self::None => Err(trc::Cause::NotConfigured.into()),
+ Self::None => Err(trc::StoreCause::NotConfigured.into()),
}
- .caused_by( trc::location!())
+ .caused_by(trc::location!())
}
#[cfg(feature = "test_mode")]
@@ -568,7 +568,7 @@ impl Store {
self.iterate(
IterateParams::new(from_key, to_key).ascending().no_values(),
|key, _| {
- let account_id = key.deserialize_be_u32(0).caused_by( trc::location!())?;
+ let account_id = key.deserialize_be_u32(0).caused_by(trc::location!())?;
if account_id != last_account_id {
last_account_id = account_id;
batch.with_account_id(account_id);
@@ -582,7 +582,7 @@ impl Store {
.unwrap(),
until: key
.deserialize_be_u64(key.len() - U64_LEN)
- .caused_by( trc::location!())?,
+ .caused_by(trc::location!())?,
}),
op: ValueOp::Clear,
});
@@ -607,7 +607,7 @@ impl Store {
let mut expired_counters = Vec::new();
self.iterate(IterateParams::new(from_key, to_key), |key, value| {
- let expiry = value.deserialize_be_u64(0).caused_by( trc::location!())?;
+ let expiry = value.deserialize_be_u64(0).caused_by(trc::location!())?;
if expiry == 0 {
expired_counters.push(key.to_vec());
} else if expiry != u64::MAX {
diff --git a/crates/store/src/query/acl.rs b/crates/store/src/query/acl.rs
index bd6f0d16..53d5c088 100644
--- a/crates/store/src/query/acl.rs
+++ b/crates/store/src/query/acl.rs
@@ -75,7 +75,7 @@ impl Store {
},
)
.await
- .caused_by( trc::location!())
+ .caused_by(trc::location!())
.map(|_| results)
}
@@ -108,7 +108,7 @@ impl Store {
},
)
.await
- .caused_by( trc::location!())?;
+ .caused_by(trc::location!())?;
// Remove permissions
let mut batch = BatchBuilder::new();
@@ -118,7 +118,7 @@ impl Store {
if batch.ops.len() >= 1000 {
self.write(batch.build())
.await
- .caused_by( trc::location!())?;
+ .caused_by(trc::location!())?;
batch = BatchBuilder::new();
batch.with_account_id(account_id);
last_collection = u8::MAX;
@@ -136,7 +136,7 @@ impl Store {
if !batch.is_empty() {
self.write(batch.build())
.await
- .caused_by( trc::location!())?;
+ .caused_by(trc::location!())?;
}
Ok(())
@@ -149,7 +149,7 @@ impl Deserialize for AclItem {
to_account_id: bytes.deserialize_be_u32(U32_LEN)?,
to_collection: *bytes
.get(U32_LEN * 2)
- .ok_or_else(|| trc::Cause::DataCorruption.caused_by(trc::location!()))?,
+ .ok_or_else(|| trc::StoreCause::DataCorruption.caused_by(trc::location!()))?,
to_document_id: bytes.deserialize_be_u32((U32_LEN * 2) + 1)?,
permissions: 0,
})
diff --git a/crates/store/src/write/key.rs b/crates/store/src/write/key.rs
index 22ff00e0..fff9a816 100644
--- a/crates/store/src/write/key.rs
+++ b/crates/store/src/write/key.rs
@@ -102,13 +102,13 @@ impl DeserializeBigEndian for &[u8] {
fn deserialize_be_u32(&self, index: usize) -> trc::Result<u32> {
self.get(index..index + U32_LEN)
.ok_or_else(|| {
- trc::Cause::DataCorruption
+ trc::StoreCause::DataCorruption
.caused_by(trc::location!())
.ctx(trc::Key::Value, *self)
})
.and_then(|bytes| {
bytes.try_into().map_err(|_| {
- trc::Cause::DataCorruption
+ trc::StoreCause::DataCorruption
.caused_by(trc::location!())
.ctx(trc::Key::Value, *self)
})
@@ -119,13 +119,13 @@ impl DeserializeBigEndian for &[u8] {
fn deserialize_be_u64(&self, index: usize) -> trc::Result<u64> {
self.get(index..index + U64_LEN)
.ok_or_else(|| {
- trc::Cause::DataCorruption
+ trc::StoreCause::DataCorruption
.caused_by(trc::location!())
.ctx(trc::Key::Value, *self)
})
.and_then(|bytes| {
bytes.try_into().map_err(|_| {
- trc::Cause::DataCorruption
+ trc::StoreCause::DataCorruption
.caused_by(trc::location!())
.ctx(trc::Key::Value, *self)
})
@@ -644,7 +644,7 @@ impl Deserialize for ReportEvent {
.and_then(|domain| std::str::from_utf8(domain).ok())
.map(|s| s.to_string())
.ok_or_else(|| {
- trc::Cause::DataCorruption
+ trc::StoreCause::DataCorruption
.caused_by(trc::location!())
.ctx(trc::Key::Key, key)
})?,
diff --git a/crates/store/src/write/mod.rs b/crates/store/src/write/mod.rs
index b014637a..f59fdc0f 100644
--- a/crates/store/src/write/mod.rs
+++ b/crates/store/src/write/mod.rs
@@ -346,7 +346,7 @@ impl Deserialize for String {
impl Deserialize for u64 {
fn deserialize(bytes: &[u8]) -> trc::Result<Self> {
Ok(u64::from_be_bytes(bytes.try_into().map_err(|_| {
- trc::Cause::DataCorruption.caused_by(trc::location!())
+ trc::StoreCause::DataCorruption.caused_by(trc::location!())
})?))
}
}
@@ -354,7 +354,7 @@ impl Deserialize for u64 {
impl Deserialize for i64 {
fn deserialize(bytes: &[u8]) -> trc::Result<Self> {
Ok(i64::from_be_bytes(bytes.try_into().map_err(|_| {
- trc::Cause::DataCorruption.caused_by(trc::location!())
+ trc::StoreCause::DataCorruption.caused_by(trc::location!())
})?))
}
}
@@ -362,7 +362,7 @@ impl Deserialize for i64 {
impl Deserialize for u32 {
fn deserialize(bytes: &[u8]) -> trc::Result<Self> {
Ok(u32::from_be_bytes(bytes.try_into().map_err(|_| {
- trc::Cause::DataCorruption.caused_by(trc::location!())
+ trc::StoreCause::DataCorruption.caused_by(trc::location!())
})?))
}
}
@@ -456,12 +456,12 @@ impl<T: DeserializeFrom + Sync + Send> Deserialize for Vec<T> {
let mut bytes = bytes.iter();
let len: usize = bytes
.next_leb128()
- .ok_or_else(|| trc::Cause::DataCorruption.caused_by(trc::location!()))?;
+ .ok_or_else(|| trc::StoreCause::DataCorruption.caused_by(trc::location!()))?;
let mut list = Vec::with_capacity(len);
for _ in 0..len {
list.push(
T::deserialize_from(&mut bytes)
- .ok_or_else(|| trc::Cause::DataCorruption.caused_by(trc::location!()))?,
+ .ok_or_else(|| trc::StoreCause::DataCorruption.caused_by(trc::location!()))?,
);
}
Ok(list)
@@ -686,13 +686,13 @@ impl<T: serde::Serialize + serde::de::DeserializeOwned + Sized + Sync + Send> De
fn deserialize(bytes: &[u8]) -> trc::Result<Self> {
lz4_flex::decompress_size_prepended(bytes)
.map_err(|err| {
- trc::Cause::Decompress
+ trc::StoreCause::Decompress
.caused_by(trc::location!())
.reason(err)
})
.and_then(|result| {
bincode::deserialize(&result).map_err(|err| {
- trc::Cause::DataCorruption
+ trc::StoreCause::DataCorruption
.caused_by(trc::location!())
.reason(err)
})
@@ -724,7 +724,7 @@ impl AssignedIds {
pub fn get_document_id(&self, idx: usize) -> trc::Result<u32> {
self.document_ids.get(idx).copied().ok_or_else(|| {
- trc::Cause::Unexpected
+ trc::StoreCause::Unexpected
.caused_by(trc::location!())
.ctx(trc::Key::Reason, "No document ids were created")
})
@@ -736,7 +736,7 @@ impl AssignedIds {
pub fn last_document_id(&self) -> trc::Result<u32> {
self.document_ids.last().copied().ok_or_else(|| {
- trc::Cause::Unexpected
+ trc::StoreCause::Unexpected
.caused_by(trc::location!())
.ctx(trc::Key::Reason, "No document ids were created")
})
@@ -744,7 +744,7 @@ impl AssignedIds {
pub fn last_counter_id(&self) -> trc::Result<i64> {
self.counter_ids.last().copied().ok_or_else(|| {
- trc::Cause::Unexpected
+ trc::StoreCause::Unexpected
.caused_by(trc::location!())
.ctx(trc::Key::Reason, "No document ids were created")
})
diff --git a/crates/trc/src/conv.rs b/crates/trc/src/conv.rs
index 298428e8..f65eadd8 100644
--- a/crates/trc/src/conv.rs
+++ b/crates/trc/src/conv.rs
@@ -83,18 +83,24 @@ impl From<Error> for Value {
}
}
-impl From<ErrorKind> for Value {
- fn from(value: ErrorKind) -> Self {
- Self::ErrorKind(value)
- }
-}
-
impl From<Cause> for Error {
fn from(value: Cause) -> Self {
Error::new(value)
}
}
+impl From<StoreCause> for Error {
+ fn from(value: StoreCause) -> Self {
+ Error::new(Cause::Store(value))
+ }
+}
+
+impl From<AuthCause> for Error {
+ fn from(value: AuthCause) -> Self {
+ Error::new(Cause::Auth(value))
+ }
+}
+
impl From<Protocol> for Value {
fn from(value: Protocol) -> Self {
Self::Protocol(value)
@@ -152,52 +158,32 @@ where
}
}
-impl From<std::io::Error> for Error {
- fn from(err: std::io::Error) -> Self {
- Cause::Io
- .ctx(Key::Reason, err.kind())
- .ctx(Key::Details, err.to_string())
+impl Cause {
+ pub fn from_io_error(self, err: std::io::Error) -> Error {
+ self.reason(err).details("I/O error")
}
-}
-impl From<serde_json::Error> for Error {
- fn from(err: serde_json::Error) -> Self {
- Cause::Deserialize
- .reason(err)
- .details("JSON deserialization failed")
+ pub fn from_json_error(self, err: serde_json::Error) -> Error {
+ self.reason(err).details("JSON deserialization failed")
}
-}
-impl From<base64::DecodeError> for Error {
- fn from(err: base64::DecodeError) -> Self {
- Cause::DataCorruption
- .reason(err)
- .details("Base64 decoding failed")
+ pub fn from_base64_error(self, err: base64::DecodeError) -> Error {
+ self.reason(err).details("Base64 decoding failed")
}
-}
-impl From<reqwest::Error> for Error {
- fn from(err: reqwest::Error) -> Self {
- Cause::Http
- .into_err()
+ pub fn from_http_error(self, err: reqwest::Error) -> Error {
+ self.into_err()
.ctx_opt(Key::Url, err.url().map(|url| url.as_ref().to_string()))
.ctx_opt(Key::Code, err.status().map(|status| status.as_u16()))
.reason(err)
}
-}
-impl From<bincode::Error> for Error {
- fn from(value: bincode::Error) -> Self {
- Cause::Deserialize
- .reason(value)
- .details("Bincode deserialization failed")
+ pub fn from_bincode_error(self, err: bincode::Error) -> Error {
+ self.reason(err).details("Bincode deserialization failed")
}
-}
-impl From<reqwest::header::ToStrError> for Error {
- fn from(value: reqwest::header::ToStrError) -> Self {
- Cause::Http
- .reason(value)
+ pub fn from_http_str_error(self, err: reqwest::header::ToStrError) -> Error {
+ self.reason(err)
.details("Failed to convert header to string")
}
}
@@ -206,17 +192,21 @@ pub trait AssertSuccess
where
Self: Sized,
{
- fn assert_success(self) -> impl std::future::Future<Output = crate::Result<Self>> + Send;
+ fn assert_success(
+ self,
+ cause: Cause,
+ ) -> impl std::future::Future<Output = crate::Result<Self>> + Send;
}
impl AssertSuccess for reqwest::Response {
- async fn assert_success(self) -> crate::Result<Self> {
+ async fn assert_success(self, cause: Cause) -> crate::Result<Self> {
let status = self.status();
if status.is_success() {
Ok(self)
} else {
- Err(Cause::Http
+ Err(cause
.ctx(Key::Code, status.as_u16())
+ .details("HTTP request failed")
.ctx_opt(Key::Reason, self.text().await.ok()))
}
}
diff --git a/crates/trc/src/imple.rs b/crates/trc/src/imple.rs
index 8278ccac..2596d35a 100644
--- a/crates/trc/src/imple.rs
+++ b/crates/trc/src/imple.rs
@@ -129,7 +129,7 @@ where
}
pub fn corrupted_key(key: &[u8], value: Option<&[u8]>, caused_by: &'static str) -> Error {
- Cause::DataCorruption
+ Cause::Store(StoreCause::DataCorruption)
.ctx(Key::Key, key)
.ctx_opt(Key::Value, value)
.ctx(Key::CausedBy, caused_by)
@@ -139,17 +139,17 @@ where
impl Cause {
#[inline(always)]
pub fn ctx(self, key: Key, value: impl Into<Value>) -> Error {
- Error::new(self).ctx(key, value)
+ self.into_err().ctx(key, value)
}
#[inline(always)]
pub fn caused_by(self, error: impl Into<Value>) -> Error {
- Error::new(self).caused_by(error)
+ self.into_err().caused_by(error)
}
#[inline(always)]
pub fn reason(self, error: impl Display) -> Error {
- Error::new(self).reason(error)
+ self.into_err().reason(error)
}
#[inline(always)]
@@ -158,11 +158,155 @@ impl Cause {
}
}
+impl StoreCause {
+ #[inline(always)]
+ pub fn ctx(self, key: Key, value: impl Into<Value>) -> Error {
+ self.into_err().ctx(key, value)
+ }
+
+ #[inline(always)]
+ pub fn caused_by(self, error: impl Into<Value>) -> Error {
+ self.into_err().caused_by(error)
+ }
+
+ #[inline(always)]
+ pub fn reason(self, error: impl Display) -> Error {
+ self.into_err().reason(error)
+ }
+
+ #[inline(always)]
+ pub fn into_err(self) -> Error {
+ Error::new(Cause::Store(self))
+ }
+}
+
+impl AuthCause {
+ #[inline(always)]
+ pub fn ctx(self, key: Key, value: impl Into<Value>) -> Error {
+ self.into_err().ctx(key, value)
+ }
+
+ #[inline(always)]
+ pub fn caused_by(self, error: impl Into<Value>) -> Error {
+ self.into_err().caused_by(error)
+ }
+
+ #[inline(always)]
+ pub fn reason(self, error: impl Display) -> Error {
+ self.into_err().reason(error)
+ }
+
+ #[inline(always)]
+ pub fn into_err(self) -> Error {
+ Error::new(Cause::Auth(self))
+ }
+}
+
+impl ManageCause {
+ #[inline(always)]
+ pub fn ctx(self, key: Key, value: impl Into<Value>) -> Error {
+ self.into_err().ctx(key, value)
+ }
+
+ #[inline(always)]
+ pub fn caused_by(self, error: impl Into<Value>) -> Error {
+ self.into_err().caused_by(error)
+ }
+
+ #[inline(always)]
+ pub fn reason(self, error: impl Display) -> Error {
+ self.into_err().reason(error)
+ }
+
+ #[inline(always)]
+ pub fn into_err(self) -> Error {
+ Error::new(Cause::Manage(self))
+ }
+}
+
+impl JmapCause {
+ #[inline(always)]
+ pub fn ctx(self, key: Key, value: impl Into<Value>) -> Error {
+ self.into_err().ctx(key, value)
+ }
+
+ #[inline(always)]
+ pub fn caused_by(self, error: impl Into<Value>) -> Error {
+ self.into_err().caused_by(error)
+ }
+
+ #[inline(always)]
+ pub fn reason(self, error: impl Display) -> Error {
+ self.into_err().reason(error)
+ }
+
+ #[inline(always)]
+ pub fn into_err(self) -> Error {
+ Error::new(Cause::Jmap(self))
+ }
+}
+
+impl LimitCause {
+ #[inline(always)]
+ pub fn ctx(self, key: Key, value: impl Into<Value>) -> Error {
+ self.into_err().ctx(key, value)
+ }
+
+ #[inline(always)]
+ pub fn caused_by(self, error: impl Into<Value>) -> Error {
+ self.into_err().caused_by(error)
+ }
+
+ #[inline(always)]
+ pub fn reason(self, error: impl Display) -> Error {
+ self.into_err().reason(error)
+ }
+
+ #[inline(always)]
+ pub fn into_err(self) -> Error {
+ Error::new(Cause::Limit(self))
+ }
+}
+
+impl ResourceCause {
+ #[inline(always)]
+ pub fn ctx(self, key: Key, value: impl Into<Value>) -> Error {
+ self.into_err().ctx(key, value)
+ }
+
+ #[inline(always)]
+ pub fn caused_by(self, error: impl Into<Value>) -> Error {
+ self.into_err().caused_by(error)
+ }
+
+ #[inline(always)]
+ pub fn reason(self, error: impl Display) -> Error {
+ self.into_err().reason(error)
+ }
+
+ #[inline(always)]
+ pub fn into_err(self) -> Error {
+ Error::new(Cause::Resource(self))
+ }
+}
+
impl Error {
#[inline(always)]
pub fn wrap(self, cause: Cause) -> Self {
Error::new(cause).caused_by(self)
}
+
+ #[inline(always)]
+ pub fn is_assertion_failure(&self) -> bool {
+ self.inner == Cause::Store(StoreCause::AssertValue)
+ }
+
+ pub fn is_jmap_method_error(&self) -> bool {
+ !matches!(
+ self.inner,
+ Cause::Jmap(JmapCause::UnknownCapability | JmapCause::NotJSON | JmapCause::NotRequest)
+ )
+ }
}
impl Value {
diff --git a/crates/trc/src/lib.rs b/crates/trc/src/lib.rs
index 08b15440..351960ba 100644
--- a/crates/trc/src/lib.rs
+++ b/crates/trc/src/lib.rs
@@ -8,10 +8,7 @@ pub mod conv;
pub mod imple;
pub mod macros;
-use std::{
- io::ErrorKind,
- net::{IpAddr, Ipv4Addr, Ipv6Addr},
-};
+use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
pub type Result<T> = std::result::Result<T, Error>;
pub type Error = Context<Cause, ERROR_CONTEXT_SIZE>;
@@ -33,7 +30,6 @@ pub enum Value {
Ipv6(Box<Ipv6Addr>),
Protocol(Protocol),
Error(Box<Error>),
- ErrorKind(ErrorKind),
Array(Vec<Value>),
#[default]
None,
@@ -79,53 +75,124 @@ pub enum Event {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Cause {
+ Store(StoreCause),
+ Jmap(JmapCause),
+ Imap,
+ ManageSieve,
+ Pop3,
+ Smtp,
+ Thread,
+ Fetch,
+ Acme,
+ Dns,
+ Ingest,
+ Network,
+ Limit(LimitCause),
+ Manage(ManageCause),
+ Auth(AuthCause),
+ Purge,
+ Configuration,
+ Resource(ResourceCause),
+}
+
+/*
+
+ Http,
+ Crypto,
+ Timeout,
+ Configuration,
+ Unknown,
+
+*/
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum StoreCause {
+ AssertValue,
+ BlobMissingMarker,
FoundationDB,
MySQL,
PostgreSQL,
RocksDB,
SQLite,
+ Ldap,
ElasticSearch,
Redis,
S3,
- Io,
- Imap,
- Smtp,
- Ldap,
- BlobMissingMarker,
- Unknown,
- Purge,
- AssertValue,
- Timeout,
- Thread,
+ Filesystem,
Pool,
DataCorruption,
Decompress,
Deserialize,
+ NotFound,
NotConfigured,
- Unsupported,
+ NotSupported,
Unexpected,
+ Crypto,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum JmapCause {
+ // Method errors
+ InvalidArguments,
+ RequestTooLarge,
+ StateMismatch,
+ AnchorNotFound,
+ UnsupportedFilter,
+ UnsupportedSort,
+ UnknownMethod,
+ InvalidResultReference,
+ Forbidden,
+ AccountNotFound,
+ AccountNotSupportedByMethod,
+ AccountReadOnly,
+ NotFound,
+ CannotCalculateChanges,
+ UnknownDataType,
+
+ // Request errors
+ UnknownCapability,
+ NotJSON,
+ NotRequest,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum LimitCause {
+ SizeRequest,
+ SizeUpload,
+ CallsIn,
+ ConcurrentRequest, //RequestError::limit(RequestLimitError::ConcurrentRequest) StatusResponse::bye("Too many concurrent IMAP connections.").into_bytes(),
+ ConcurrentUpload, //RequestError::limit(RequestLimitError::ConcurrentUpload)
+ Quota,
+ BlobQuota, //RequestError::over_blob_quota
+ TooManyRequests, //RequestError::too_many_requests() + disconnect imap StatusResponse::bye("Too many authentication requests from this IP address.")
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum ManageCause {
MissingParameter,
Invalid,
AlreadyExists,
+ AssertFailed,
NotFound,
- Configuration,
- Fetch,
- Acme,
- Http,
- Crypto,
- Dns,
- Authentication,
+ NotSupported,
+ Error,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum AuthCause {
+ Failed,
MissingTotp,
- Jmap,
- OverQuota,
- OverBlobQuota, //RequestError::over_blob_quota
- Ingest,
- Network,
- TooManyRequests, //RequestError::too_many_requests() + disconnect imap StatusResponse::bye("Too many authentication requests from this IP address.")
- TooManyConcurrentRequests, //RequestError::limit(RequestLimitError::ConcurrentRequest) StatusResponse::bye("Too many concurrent IMAP connections.").into_bytes(),
- TooManyConcurrentUploads, //RequestError::limit(RequestLimitError::ConcurrentUpload)
- TooManyAuthAttempts, //RequestError::too_many_auth_attempts() + disconnect imap
+ TooManyAttempts, //RequestError::too_many_auth_attempts() + disconnect imap
Banned,
+ Invalid,
+ Error,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum ResourceCause {
+ NotFound,
+ BadParameters,
+ Error,
}
/*
diff --git a/tests/Cargo.toml b/tests/Cargo.toml
index 47e52e76..1fd16a7e 100644
--- a/tests/Cargo.toml
+++ b/tests/Cargo.toml
@@ -27,6 +27,7 @@ imap_proto = { path = "../crates/imap-proto" }
pop3 = { path = "../crates/pop3", features = ["test_mode"] }
smtp = { path = "../crates/smtp", features = ["test_mode"] }
common = { path = "../crates/common", features = ["test_mode"] }
+trc = { path = "../crates/trc" }
managesieve = { path = "../crates/managesieve", features = ["test_mode"] }
smtp-proto = { version = "0.1" }
mail-send = { version = "0.4", default-features = false, features = ["cram-md5", "ring", "tls12"] }
diff --git a/tests/src/directory/internal.rs b/tests/src/directory/internal.rs
index 2d42cf31..7efc2017 100644
--- a/tests/src/directory/internal.rs
+++ b/tests/src/directory/internal.rs
@@ -10,7 +10,7 @@ use directory::{
lookup::DirectoryStore, manage::ManageDirectory, PrincipalField, PrincipalUpdate,
PrincipalValue,
},
- DirectoryError, ManagementError, Principal, QueryBy, Type,
+ Principal, QueryBy, Type,
};
use jmap_proto::types::collection::Collection;
use mail_send::Credentials;
diff --git a/tests/src/directory/mod.rs b/tests/src/directory/mod.rs
index d6efc3e2..2b36b8a2 100644
--- a/tests/src/directory/mod.rs
+++ b/tests/src/directory/mod.rs
@@ -5,7 +5,7 @@
*/
pub mod imap;
-pub mod internal;
+//pub mod internal;
pub mod ldap;
pub mod smtp;
pub mod sql;
@@ -392,6 +392,8 @@ pub fn dummy_tls_acceptor() -> Arc<TlsAcceptor> {
let cert_file = &mut BufReader::new(CERT.as_bytes());
let key_file = &mut BufReader::new(PK.as_bytes());
+ let todo = "fix interkal";
+
// convert files to key/cert objects
let cert_chain = certs(cert_file).map(|r| r.unwrap()).collect();
let mut keys: Vec<PrivateKeyDer> = pkcs8_private_keys(key_file)
diff --git a/tests/src/directory/smtp.rs b/tests/src/directory/smtp.rs
index 07b1a8a6..87aa5c37 100644
--- a/tests/src/directory/smtp.rs
+++ b/tests/src/directory/smtp.rs
@@ -7,7 +7,7 @@
use std::sync::Arc;
use common::listener::limiter::{ConcurrencyLimiter, InFlight};
-use directory::{DirectoryError, QueryBy};
+use directory::QueryBy;
use mail_parser::decoders::base64::base64_decode;
use mail_send::Credentials;
use tokio::{
@@ -87,13 +87,23 @@ async fn lmtp_directory() {
.into(),
Item::Verify(v) => match core.vrfy(&handle, v).await {
Ok(v) => v.into(),
- Err(DirectoryError::Unsupported) => LookupResult::False,
- Err(e) => panic!("Unexpected error: {e:?}"),
+ Err(e) => {
+ if e.matches(trc::StoreCause::NotSupported) {
+ LookupResult::False
+ } else {
+ panic!("Unexpected error: {e:?}")
+ }
+ }
},
Item::Expand(v) => match core.expn(&handle, v).await {
Ok(v) => v.into(),
- Err(DirectoryError::Unsupported) => LookupResult::False,
- Err(e) => panic!("Unexpected error: {e:?}"),
+ Err(e) => {
+ if e.matches(trc::StoreCause::NotSupported) {
+ LookupResult::False
+ } else {
+ panic!("Unexpected error: {e:?}")
+ }
+ }
},
};
@@ -121,13 +131,23 @@ async fn lmtp_directory() {
.into(),
Item::Verify(v) => match core.vrfy(&handle, v).await {
Ok(v) => v.into(),
- Err(DirectoryError::Unsupported) => LookupResult::False,
- Err(e) => panic!("Unexpected error: {e:?}"),
+ Err(e) => {
+ if e.matches(trc::StoreCause::NotSupported) {
+ LookupResult::False
+ } else {
+ panic!("Unexpected error: {e:?}")
+ }
+ }
},
Item::Expand(v) => match core.expn(&handle, v).await {
Ok(v) => v.into(),
- Err(DirectoryError::Unsupported) => LookupResult::False,
- Err(e) => panic!("Unexpected error: {e:?}"),
+ Err(e) => {
+ if e.matches(trc::StoreCause::NotSupported) {
+ LookupResult::False
+ } else {
+ panic!("Unexpected error: {e:?}")
+ }
+ }
},
};
diff --git a/tests/src/jmap/thread_merge.rs b/tests/src/jmap/thread_merge.rs
index cec7dbbd..2531ad9c 100644
--- a/tests/src/jmap/thread_merge.rs
+++ b/tests/src/jmap/thread_merge.rs
@@ -10,10 +10,7 @@ use crate::{
jmap::{assert_is_empty, mailbox::destroy_all_mailboxes},
store::deflate_test_resource,
};
-use jmap::{
- email::ingest::{IngestEmail, IngestSource},
- IngestError,
-};
+use jmap::email::ingest::{IngestEmail, IngestSource};
use jmap_client::{email, mailbox::Role};
use jmap_proto::types::{collection::Collection, id::Id};
use mail_parser::{mailbox::mbox::MessageIterator, MessageParser};
@@ -256,21 +253,16 @@ async fn test_multi_thread(params: &mut JMAPTest) {
.await
{
Ok(_) => break,
- Err(IngestError::Temporary) if retry_count < 10 => {
- //println!("Retrying ingest for {}...", message.from());
- let backoff = rand::thread_rng().gen_range(50..=300);
- tokio::time::sleep(Duration::from_millis(backoff)).await;
- retry_count += 1;
- continue;
- }
- Err(IngestError::Permanent { .. }) => {
- panic!(
- "Failed to ingest message: {:?} {}",
- message.from(),
- String::from_utf8_lossy(message.contents())
- );
+ Err(err) => {
+ if err.is_assertion_failure() && retry_count < 10 {
+ //println!("Retrying ingest for {}...", message.from());
+ let backoff = rand::thread_rng().gen_range(50..=300);
+ tokio::time::sleep(Duration::from_millis(backoff)).await;
+ retry_count += 1;
+ continue;
+ }
+ panic!("Failed to ingest message: {:?}", err);
}
- Err(err) => panic!("Failed to ingest message: {:?}", err),
}
}
}));
diff --git a/tests/src/jmap/webhooks.rs b/tests/src/jmap/webhooks.rs
index bffcf98d..7ac11622 100644
--- a/tests/src/jmap/webhooks.rs
+++ b/tests/src/jmap/webhooks.rs
@@ -140,7 +140,7 @@ pub fn spawn_mock_webhook_endpoint() -> Arc<MockWebhookEndpoint> {
//let c = print!("rejected webhook: {}", serde_json::to_string_pretty(&request).unwrap());
Ok::<_, hyper::Error>(
- RequestError::not_found().into_http_response()
+ Err(trc::ResourceCause::NotFound.into_err())
)
}