summaryrefslogtreecommitdiffstats
path: root/third_party/rust/error-support
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/error-support')
-rw-r--r--third_party/rust/error-support/.cargo-checksum.json1
-rw-r--r--third_party/rust/error-support/Cargo.toml40
-rw-r--r--third_party/rust/error-support/README.md89
-rw-r--r--third_party/rust/error-support/android/build.gradle7
-rw-r--r--third_party/rust/error-support/android/src/main/AndroidManifest.xml2
-rw-r--r--third_party/rust/error-support/build.rs8
-rw-r--r--third_party/rust/error-support/src/errorsupport.udl16
-rw-r--r--third_party/rust/error-support/src/handling.rs112
-rw-r--r--third_party/rust/error-support/src/lib.rs162
-rw-r--r--third_party/rust/error-support/src/macros.rs62
-rw-r--r--third_party/rust/error-support/src/redact.rs75
-rw-r--r--third_party/rust/error-support/src/reporting.rs71
-rw-r--r--third_party/rust/error-support/uniffi.toml8
13 files changed, 653 insertions, 0 deletions
diff --git a/third_party/rust/error-support/.cargo-checksum.json b/third_party/rust/error-support/.cargo-checksum.json
new file mode 100644
index 0000000000..ff145c497b
--- /dev/null
+++ b/third_party/rust/error-support/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"Cargo.toml":"54be1df819228db901e155bdf99a313c43f24df61f423093f03f2a42da394cfb","README.md":"8030b4a314b1be31ba018ac12c3b586bb736db5307c3c395f2857fffe0130322","android/build.gradle":"200fe9fcf26477ae4e941dd1e702c43deae9fb0a7252569bd7352eac1771efbe","android/src/main/AndroidManifest.xml":"4f8b16fa6a03120ac810c6438a3a60294075414d92e06caa7e85388e389e5d17","build.rs":"c8d3c38c1208eea36224662b284d8daf3e7ad1b07d22d750524f3da1cc66ccca","src/errorsupport.udl":"e793034d01a2608298528051757f38405e006ee1abc4cf65dc6f18c53590ace8","src/handling.rs":"545c969d71907d81cb5af93f435ba443508adda2ec57ac2a975fed7d9828ccea","src/lib.rs":"96ae3cc2c1077ae45442ace6b5b5311b86267d0b9067f3ff58396af30ccbbc07","src/macros.rs":"0d03f82fab20c96a182f941baf3fcf2a286b00fea871ee7fd8e339abc14f9522","src/redact.rs":"c9a4df1a87be68b15d583587bda941d4c60a1d0449e2d43ff99f3611a290a863","src/reporting.rs":"38efd24d86ba8facfb181cb27e8b698d2831db0afab85691ffda034a4dc68dfa","uniffi.toml":"644fe81c12fe3c01ee81e017ca3c00d0e611f014b7eade51aadaf208179a3450"},"package":null} \ No newline at end of file
diff --git a/third_party/rust/error-support/Cargo.toml b/third_party/rust/error-support/Cargo.toml
new file mode 100644
index 0000000000..35d911dd75
--- /dev/null
+++ b/third_party/rust/error-support/Cargo.toml
@@ -0,0 +1,40 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2021"
+name = "error-support"
+version = "0.1.0"
+authors = ["Thom Chiovoloni <tchiovoloni@mozilla.com>"]
+autotests = false
+readme = "README.md"
+license = "MPL-2.0"
+
+[dependencies]
+log = "0.4"
+uniffi = "0.23"
+
+[dependencies.backtrace]
+version = "0.3"
+optional = true
+
+[dependencies.error-support-macros]
+path = "macros"
+
+[dependencies.lazy_static]
+version = "1.4"
+
+[dependencies.parking_lot]
+version = ">=0.11,<=0.12"
+
+[build-dependencies.uniffi]
+version = "0.23"
+features = ["build"]
diff --git a/third_party/rust/error-support/README.md b/third_party/rust/error-support/README.md
new file mode 100644
index 0000000000..aed789ef7b
--- /dev/null
+++ b/third_party/rust/error-support/README.md
@@ -0,0 +1,89 @@
+# Application error handling support
+
+This crate provides support for other crates to effectively report errors.
+Because app-services components get embedded in various apps, written in
+multiple languages, we face several challenges:
+
+ - Rust stacktraces are generally not available so we often need to provide
+ extra context to help debug where an error is occuring.
+ - Without stack traces, Sentry and other error reporting systems don't do a
+ great job at auto-grouping errors together, so we need to manually group them.
+ - We can't hook directly into the error reporting system or even depend on a
+ particular error reporting system to be in use. This means the system
+ needs to be simple and flexible enough to plug in to multiple systems.
+
+## Breadcrumbs as context
+
+We use a breadcrumb system as the basis for adding context to errors.
+Breadcrumbs are individual messages that form a log-style stream, and the most
+recent breadcrumbs get attached to each error reported. There are a lot of
+other ways to provide context to an error, but we just use breadcrumbs for
+everything because it's a relatively simple system that's easy to hook up to
+error reporting platforms.
+
+## Basic error reporting tools
+
+Basic error reporting is handled using several macros:
+
+ - `report_error!()` creates an error report. It inputs a `type_name` as the
+ first parameter, and `format!` style arguments afterwards. `type_name` is
+ used to group errors together and show up as error titles/headers. Use the
+ format-style args to create is a long-form description for the issue. Most
+ of the time you won't need to call this directly, since can automatically
+ do it when converting internal results to public ones. However, it can be
+ useful in the case where you see an error that you want
+ to report, but want to try to recover rather than returning an error.
+ - `breadcrumb!()` creates a new breadcrumb that will show up on future errors.
+ - `trace_error!()` inputs a `Result<>` and creates a breadcrumb if it's an
+ `Err`. This is useful if when you're trying to track down where an error
+ originated from, since you can wrap each possible source of the error with
+ `trace_error!()`. `trace_error!()` returns the result passed in to it,
+ which makes wrapping function calls easy.
+
+
+## Public/Internal errors and converting between them
+
+Our components generally create 2 error enums: one for internal use and one for
+the public API. They are typically named `Error` and
+`[ComponentName]ApiError`. The internal error typically carries a lot of
+low-level details and lots of variants which is useful to us app-services
+developers. The public error typically has less variants with the variants
+often just storing a reason string rather than low-level error codes. There
+are also two `Result<>` types that correspond to these two errors, typically
+named `Result` and `ApiResult`.
+
+This means we need to convert from internal errors to public errors, which has
+the nice side benefit of giving us a centralized spot to make error reports for
+selected public errors. This is done with the `ErrorHandling` type and
+`GetErrorHandling` trait in `src/handling.rs`. The basic system is that you
+convert between one error to another and choose if you want to report the error
+and/or log a warning. When reporting an error you can choose a type name to
+group the error with. This system is extremely flexible, since you can inspect
+the internal error and use error codes or other data to determine if it should
+be reported or not, which type name to report it with, etc. Eventually we also
+hope to allow expected errors to be counted in telemetry (think things like
+network errors, shutdown errors, etc.).
+
+To assist this conversion, the `handle_error` procedural macro can be used to
+automatically convert between `Result` and `ApiResult` using
+`GetErrorHandling`. Note that this depends on having the `Result` type
+imported in your module with a `use` statement.
+
+See the `logins::errors` and `logins::store` modules for an example of how this
+all fits together.
+
+## ⚠️ Personally Identifiable Information ⚠️
+
+When converting internal errors to public errors, we should ensure that there
+is no personally identifying information (PII) in any error reports. We should
+also ensure that no PII is contained in the public error enum, since consumers
+may end up uses those for their own error reports.
+
+We operate on a best-effort basis to ensure this. Our error details often come
+from an error from one of our dependencies, which makes it very diffucult to be
+completely sure though. For example, `rusqlite::Error` could include data from
+a user's database in their errors, which would then appear in our error
+variants. However, we've never seen that in practice so we are comfortable
+including the `rusqlite` error message in our error reports, without attempting
+to sanitize them.
+
diff --git a/third_party/rust/error-support/android/build.gradle b/third_party/rust/error-support/android/build.gradle
new file mode 100644
index 0000000000..4ef6d7a11a
--- /dev/null
+++ b/third_party/rust/error-support/android/build.gradle
@@ -0,0 +1,7 @@
+
+apply from: "$rootDir/build-scripts/component-common.gradle"
+apply from: "$rootDir/publish.gradle"
+
+ext.configureUniFFIBindgen("../src/errorsupport.udl")
+ext.dependsOnTheMegazord()
+ext.configurePublish()
diff --git a/third_party/rust/error-support/android/src/main/AndroidManifest.xml b/third_party/rust/error-support/android/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..51c272b64e
--- /dev/null
+++ b/third_party/rust/error-support/android/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="org.mozilla.appservices.errorsupport" />
diff --git a/third_party/rust/error-support/build.rs b/third_party/rust/error-support/build.rs
new file mode 100644
index 0000000000..afa61ff66a
--- /dev/null
+++ b/third_party/rust/error-support/build.rs
@@ -0,0 +1,8 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+fn main() {
+ uniffi::generate_scaffolding("./src/errorsupport.udl").unwrap();
+}
diff --git a/third_party/rust/error-support/src/errorsupport.udl b/third_party/rust/error-support/src/errorsupport.udl
new file mode 100644
index 0000000000..40482bc4e9
--- /dev/null
+++ b/third_party/rust/error-support/src/errorsupport.udl
@@ -0,0 +1,16 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+namespace errorsupport {
+ // Set the global error reporter. This is typically done early in startup.
+ void set_application_error_reporter(ApplicationErrorReporter error_reporter);
+ // Unset the global error reporter. This is typically done at shutdown for
+ // platforms that want to cleanup references like Desktop.
+ void unset_application_error_reporter();
+};
+
+callback interface ApplicationErrorReporter {
+ void report_error(string type_name, string message);
+ void report_breadcrumb(string message, string module, u32 line, u32 column);
+};
diff --git a/third_party/rust/error-support/src/handling.rs b/third_party/rust/error-support/src/handling.rs
new file mode 100644
index 0000000000..f01fbc2b00
--- /dev/null
+++ b/third_party/rust/error-support/src/handling.rs
@@ -0,0 +1,112 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Helpers for components to "handle" errors.
+
+/// Describes what error reporting action should be taken.
+#[derive(Debug, Default)]
+pub struct ErrorReporting {
+ /// If Some(level), will write a log message at that level.
+ log_level: Option<log::Level>,
+ /// If Some(report_class) will call the error reporter with details.
+ report_class: Option<String>,
+}
+
+/// Specifies how an "internal" error is converted to an "external" public error and
+/// any logging or reporting that should happen.
+pub struct ErrorHandling<E> {
+ /// The external error that should be returned.
+ pub err: E,
+ /// How the error should be reported.
+ pub reporting: ErrorReporting,
+}
+
+impl<E> ErrorHandling<E> {
+ /// Create an ErrorHandling instance with an error conversion.
+ ///
+ /// ErrorHandling instance are created using a builder-style API. This is always the first
+ /// function in the chain, optionally followed by `log()`, `report()`, etc.
+ pub fn convert(err: E) -> Self {
+ Self {
+ err,
+ reporting: ErrorReporting::default(),
+ }
+ }
+
+ /// Add logging to an ErrorHandling instance
+ pub fn log(self, level: log::Level) -> Self {
+ Self {
+ err: self.err,
+ reporting: ErrorReporting {
+ log_level: Some(level),
+ ..self.reporting
+ },
+ }
+ }
+
+ /// Add reporting to an ErrorHandling instance
+ pub fn report(self, report_class: impl Into<String>) -> Self {
+ Self {
+ err: self.err,
+ reporting: ErrorReporting {
+ report_class: Some(report_class.into()),
+ ..self.reporting
+ },
+ }
+ }
+
+ // Convenience functions for the most common error reports
+
+ /// log a warning
+ pub fn log_warning(self) -> Self {
+ self.log(log::Level::Warn)
+ }
+
+ /// log an info
+ pub fn log_info(self) -> Self {
+ self.log(log::Level::Info)
+ }
+
+ /// Add reporting to an ErrorHandling instance and also log an Error
+ pub fn report_error(self, report_class: impl Into<String>) -> Self {
+ Self {
+ err: self.err,
+ reporting: ErrorReporting {
+ log_level: Some(log::Level::Error),
+ report_class: Some(report_class.into()),
+ },
+ }
+ }
+}
+
+/// A trait to define how errors are converted and reported.
+pub trait GetErrorHandling {
+ type ExternalError;
+
+ /// Return how to handle our internal errors
+ fn get_error_handling(&self) -> ErrorHandling<Self::ExternalError>;
+}
+
+/// Handle the specified "internal" error, taking any logging or error
+/// reporting actions and converting the error to the public error.
+/// Called by our `handle_error` macro so needs to be public.
+pub fn convert_log_report_error<IE, EE>(e: IE) -> EE
+where
+ IE: GetErrorHandling<ExternalError = EE> + std::error::Error,
+ EE: std::error::Error,
+{
+ let handling = e.get_error_handling();
+ let reporting = handling.reporting;
+ if let Some(level) = reporting.log_level {
+ log::log!(level, "{}", e.to_string());
+ }
+ if let Some(report_class) = reporting.report_class {
+ // notify the error reporter if the feature is enabled.
+ // XXX - should we arrange for the `report_class` to have the
+ // original crate calling this as a prefix, or will we still be
+ // able to identify that?
+ crate::report_error_to_app(report_class, e.to_string());
+ }
+ handling.err
+}
diff --git a/third_party/rust/error-support/src/lib.rs b/third_party/rust/error-support/src/lib.rs
new file mode 100644
index 0000000000..075e833f10
--- /dev/null
+++ b/third_party/rust/error-support/src/lib.rs
@@ -0,0 +1,162 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+mod macros;
+
+#[cfg(feature = "backtrace")]
+/// Re-export of the `backtrace` crate for use in macros and
+/// to ensure the needed version is kept in sync in dependents.
+pub use backtrace;
+
+#[cfg(not(feature = "backtrace"))]
+/// A compatibility shim for `backtrace`.
+pub mod backtrace {
+ use std::fmt;
+
+ pub struct Backtrace;
+
+ impl fmt::Debug for Backtrace {
+ #[cold]
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "Not available")
+ }
+ }
+}
+
+mod redact;
+pub use redact::*;
+
+mod reporting;
+pub use reporting::{
+ report_breadcrumb, report_error_to_app, set_application_error_reporter,
+ unset_application_error_reporter, ApplicationErrorReporter,
+};
+
+pub use error_support_macros::handle_error;
+
+mod handling;
+pub use handling::{convert_log_report_error, ErrorHandling, ErrorReporting, GetErrorHandling};
+
+/// XXX - Most of this is now considered deprecated - only FxA uses it, and
+/// should be replaced with the facilities in the `handling` module.
+
+/// Define a wrapper around the the provided ErrorKind type.
+/// See also `define_error` which is more likely to be what you want.
+#[macro_export]
+macro_rules! define_error_wrapper {
+ ($Kind:ty) => {
+ pub type Result<T, E = Error> = std::result::Result<T, E>;
+ struct ErrorData {
+ kind: $Kind,
+ backtrace: Option<std::sync::Mutex<$crate::backtrace::Backtrace>>,
+ }
+
+ impl ErrorData {
+ #[cold]
+ fn new(kind: $Kind) -> Self {
+ ErrorData {
+ kind,
+ #[cfg(feature = "backtrace")]
+ backtrace: Some(std::sync::Mutex::new(
+ $crate::backtrace::Backtrace::new_unresolved(),
+ )),
+ #[cfg(not(feature = "backtrace"))]
+ backtrace: None,
+ }
+ }
+
+ #[cfg(feature = "backtrace")]
+ #[cold]
+ fn get_backtrace(&self) -> Option<&std::sync::Mutex<$crate::backtrace::Backtrace>> {
+ self.backtrace.as_ref().map(|mutex| {
+ mutex.lock().unwrap().resolve();
+ mutex
+ })
+ }
+
+ #[cfg(not(feature = "backtrace"))]
+ #[cold]
+ fn get_backtrace(&self) -> Option<&std::sync::Mutex<$crate::backtrace::Backtrace>> {
+ None
+ }
+ }
+
+ impl std::fmt::Debug for ErrorData {
+ #[cfg(feature = "backtrace")]
+ #[cold]
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ let mut bt = self.backtrace.unwrap().lock().unwrap();
+ bt.resolve();
+ write!(f, "{:?}\n\n{}", bt, self.kind)
+ }
+
+ #[cfg(not(feature = "backtrace"))]
+ #[cold]
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(f, "{}", self.kind)
+ }
+ }
+
+ #[derive(Debug, thiserror::Error)]
+ pub struct Error(Box<ErrorData>);
+ impl Error {
+ #[cold]
+ pub fn kind(&self) -> &$Kind {
+ &self.0.kind
+ }
+
+ #[cold]
+ pub fn backtrace(&self) -> Option<&std::sync::Mutex<$crate::backtrace::Backtrace>> {
+ self.0.get_backtrace()
+ }
+ }
+
+ impl std::fmt::Display for Error {
+ #[cold]
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ std::fmt::Display::fmt(self.kind(), f)
+ }
+ }
+
+ impl From<$Kind> for Error {
+ // Cold to optimize in favor of non-error cases.
+ #[cold]
+ fn from(ctx: $Kind) -> Error {
+ Error(Box::new(ErrorData::new(ctx)))
+ }
+ }
+ };
+}
+
+/// Define a set of conversions from external error types into the provided
+/// error kind. Use `define_error` to do this at the same time as
+/// `define_error_wrapper`.
+#[macro_export]
+macro_rules! define_error_conversions {
+ ($Kind:ident { $(($variant:ident, $type:ty)),* $(,)? }) => ($(
+ impl From<$type> for Error {
+ // Cold to optimize in favor of non-error cases.
+ #[cold]
+ fn from(e: $type) -> Self {
+ Error::from($Kind::$variant(e))
+ }
+ }
+ )*);
+}
+
+/// All the error boilerplate (okay, with a couple exceptions in some cases) in
+/// one place.
+#[macro_export]
+macro_rules! define_error {
+ ($Kind:ident { $(($variant:ident, $type:ty)),* $(,)? }) => {
+ $crate::define_error_wrapper!($Kind);
+ $crate::define_error_conversions! {
+ $Kind {
+ $(($variant, $type)),*
+ }
+ }
+ };
+}
+
+uniffi::include_scaffolding!("errorsupport");
diff --git a/third_party/rust/error-support/src/macros.rs b/third_party/rust/error-support/src/macros.rs
new file mode 100644
index 0000000000..11bf6dbca5
--- /dev/null
+++ b/third_party/rust/error-support/src/macros.rs
@@ -0,0 +1,62 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/// Tell the application to report an error
+///
+/// If configured by the application, this sent to the application, which should report it to a
+/// Sentry-like system. This should only be used for errors that we don't expect to see and will
+/// work on fixing if we see a non-trivial volume of them.
+///
+/// type_name identifies the error. It should be the main text that gets shown to the
+/// user and also how the error system groups errors together. It should be in UpperCamelCase
+/// form.
+///
+/// Good type_names require some trial and error, for example:
+/// - Start with the error kind variant name
+/// - Add more text to distinguish errors more. For example an error code, or an extra word
+/// based on inspecting the error details
+#[macro_export]
+macro_rules! report_error {
+ ($type_name:expr, $($arg:tt)*) => {
+ let message = std::format!($($arg)*);
+ ::log::warn!("report {}: {}", $type_name, message);
+ $crate::report_error_to_app($type_name.to_string(), message.to_string());
+ };
+}
+
+/// Log a breadcrumb if we see an `Result::Err` value
+///
+/// Use this macro to wrap a function call that returns a `Result<>`. If that call returns an
+/// error, then we will log a breadcrumb for it. This can be used to track down the codepath where
+/// an error happened.
+#[macro_export]
+macro_rules! trace_error {
+ ($result:expr) => {{
+ let result = $result;
+ if let Err(e) = &result {
+ $crate::breadcrumb!("Saw error: {}", e);
+ };
+ result
+ }};
+}
+
+/// Tell the application to log a breadcrumb
+///
+/// Breadcrumbs are log-like entries that get tracked by the error reporting system. When we
+/// report an error, recent breadcrumbs will be associated with it.
+#[macro_export]
+macro_rules! breadcrumb {
+ ($($arg:tt)*) => {
+ {
+ let message = std::format!($($arg)*);
+ ::log::info!("breadcrumb: {}", message);
+ $crate::report_breadcrumb(
+ message,
+ std::module_path!().to_string(),
+ std::line!(),
+ std::column!(),
+ );
+ }
+ };
+}
diff --git a/third_party/rust/error-support/src/redact.rs b/third_party/rust/error-support/src/redact.rs
new file mode 100644
index 0000000000..01a333ee35
--- /dev/null
+++ b/third_party/rust/error-support/src/redact.rs
@@ -0,0 +1,75 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Functions to redact strings to remove PII before logging them
+
+/// Redact a URL.
+///
+/// It's tricky to redact an URL without revealing PII. We check for various known bad URL forms
+/// and report them, otherwise we just log "<URL>".
+pub fn redact_url(url: &str) -> String {
+ if url.is_empty() {
+ return "<URL (empty)>".to_string();
+ }
+ match url.find(':') {
+ None => "<URL (no scheme)>".to_string(),
+ Some(n) => {
+ let mut chars = url[0..n].chars();
+ match chars.next() {
+ // No characters in the scheme
+ None => return "<URL (empty scheme)>".to_string(),
+ Some(c) => {
+ // First character must be alphabetic
+ if !c.is_ascii_alphabetic() {
+ return "<URL (invalid scheme)>".to_string();
+ }
+ }
+ }
+ for c in chars {
+ // Subsequent characters must be in the set ( alpha | digit | "+" | "-" | "." )
+ if !(c.is_ascii_alphanumeric() || c == '+' || c == '-' || c == '.') {
+ return "<URL (invalid scheme)>".to_string();
+ }
+ }
+ "<URL>".to_string()
+ }
+ }
+}
+
+/// Redact compact jwe string (Five base64 segments, separated by `.` chars)
+pub fn redact_compact_jwe(url: &str) -> String {
+ url.replace(|ch| ch != '.', "x")
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn test_redact_url() {
+ assert_eq!(redact_url("http://some.website.com/index.html"), "<URL>");
+ assert_eq!(redact_url("about:config"), "<URL>");
+ assert_eq!(redact_url(""), "<URL (empty)>");
+ assert_eq!(redact_url("://some.website.com/"), "<URL (empty scheme)>");
+ assert_eq!(redact_url("some.website.com/"), "<URL (no scheme)>");
+ assert_eq!(redact_url("some.website.com/"), "<URL (no scheme)>");
+ assert_eq!(
+ redact_url("abc%@=://some.website.com/"),
+ "<URL (invalid scheme)>"
+ );
+ assert_eq!(
+ redact_url("0https://some.website.com/"),
+ "<URL (invalid scheme)>"
+ );
+ assert_eq!(
+ redact_url("a+weird-but.lega1-SCHEME://some.website.com/"),
+ "<URL>"
+ );
+ }
+
+ #[test]
+ fn test_redact_compact_jwe() {
+ assert_eq!(redact_compact_jwe("abc.1234.x3243"), "xxx.xxxx.xxxxx")
+ }
+}
diff --git a/third_party/rust/error-support/src/reporting.rs b/third_party/rust/error-support/src/reporting.rs
new file mode 100644
index 0000000000..cf0f1ebd0b
--- /dev/null
+++ b/third_party/rust/error-support/src/reporting.rs
@@ -0,0 +1,71 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use parking_lot::RwLock;
+use std::sync::atomic::{AtomicU32, Ordering};
+
+/// Counter for breadcrumb messages
+///
+/// We are currently seeing breadcrumbs that may indicate that the reporting is unreliable. In
+/// some reports, the breadcrumbs seem like they may be duplicated and/or out of order. This
+/// counter is a temporary measure to check out that theory.
+static BREADCRUMB_COUNTER: AtomicU32 = AtomicU32::new(0);
+
+fn get_breadcrumb_counter_value() -> u32 {
+ // Notes:
+ // - fetch_add is specified to wrap around in case of overflow, which seems okay.
+ // - By itself, this does not guarentee that breadcrumb logs will be ordered the same way as
+ // the counter values. If two threads are running at the same time, it's very possible
+ // that thread A gets the lower breadcrumb value, but thread B wins the race to report its
+ // breadcrumb. However, if we expect operations to be synchronized, like with places DB,
+ // then the breadcrumb counter values should always increase by 1.
+ BREADCRUMB_COUNTER.fetch_add(1, Ordering::Relaxed)
+}
+
+/// Application error reporting trait
+///
+/// The application that's consuming application-services implements this via a UniFFI callback
+/// interface, then calls `set_application_error_reporter()` to setup a global
+/// ApplicationErrorReporter.
+pub trait ApplicationErrorReporter: Sync + Send {
+ /// Send an error report to a Sentry-like error reporting system
+ ///
+ /// type_name should be used to group errors together
+ fn report_error(&self, type_name: String, message: String);
+ /// Send a breadcrumb to a Sentry-like error reporting system
+ fn report_breadcrumb(&self, message: String, module: String, line: u32, column: u32);
+}
+
+// ApplicationErrorReporter to use if the app doesn't set one
+struct DefaultApplicationErrorReporter;
+impl ApplicationErrorReporter for DefaultApplicationErrorReporter {
+ fn report_error(&self, _type_name: String, _message: String) {}
+ fn report_breadcrumb(&self, _message: String, _module: String, _line: u32, _column: u32) {}
+}
+
+lazy_static::lazy_static! {
+ // RwLock rather than a Mutex, since we only expect to set this once.
+ pub(crate) static ref APPLICATION_ERROR_REPORTER: RwLock<Box<dyn ApplicationErrorReporter>> = RwLock::new(Box::new(DefaultApplicationErrorReporter));
+}
+
+pub fn set_application_error_reporter(reporter: Box<dyn ApplicationErrorReporter>) {
+ *APPLICATION_ERROR_REPORTER.write() = reporter;
+}
+
+pub fn unset_application_error_reporter() {
+ *APPLICATION_ERROR_REPORTER.write() = Box::new(DefaultApplicationErrorReporter)
+}
+
+pub fn report_error_to_app(type_name: String, message: String) {
+ APPLICATION_ERROR_REPORTER
+ .read()
+ .report_error(type_name, message);
+}
+
+pub fn report_breadcrumb(message: String, module: String, line: u32, column: u32) {
+ let message = format!("{} ({})", message, get_breadcrumb_counter_value());
+ APPLICATION_ERROR_REPORTER
+ .read()
+ .report_breadcrumb(message, module, line, column);
+}
diff --git a/third_party/rust/error-support/uniffi.toml b/third_party/rust/error-support/uniffi.toml
new file mode 100644
index 0000000000..356cf9fd07
--- /dev/null
+++ b/third_party/rust/error-support/uniffi.toml
@@ -0,0 +1,8 @@
+[bindings.kotlin]
+package_name = "mozilla.appservices.errorsupport"
+cdylib_name = "megazord"
+
+[bindings.swift]
+ffi_module_name = "MozillaRustComponents"
+ffi_module_filename = "errorFFI"
+generate_module_map = false \ No newline at end of file