summaryrefslogtreecommitdiffstats
path: root/third_party/rust/devd-rs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /third_party/rust/devd-rs
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/devd-rs')
-rw-r--r--third_party/rust/devd-rs/.cargo-checksum.json1
-rw-r--r--third_party/rust/devd-rs/CODE_OF_CONDUCT.md74
-rw-r--r--third_party/rust/devd-rs/Cargo.lock39
-rw-r--r--third_party/rust/devd-rs/Cargo.toml37
-rw-r--r--third_party/rust/devd-rs/README.md27
-rw-r--r--third_party/rust/devd-rs/UNLICENSE24
-rw-r--r--third_party/rust/devd-rs/examples/main.rs12
-rw-r--r--third_party/rust/devd-rs/src/data.rs9
-rw-r--r--third_party/rust/devd-rs/src/lib.rs93
-rw-r--r--third_party/rust/devd-rs/src/parser.rs153
-rw-r--r--third_party/rust/devd-rs/src/result.rs26
11 files changed, 495 insertions, 0 deletions
diff --git a/third_party/rust/devd-rs/.cargo-checksum.json b/third_party/rust/devd-rs/.cargo-checksum.json
new file mode 100644
index 0000000000..213a5bf8ea
--- /dev/null
+++ b/third_party/rust/devd-rs/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"CODE_OF_CONDUCT.md":"22729f3c955ab53089f989d6ca8c84e575fa7fb68a8c86d2f7bbe9deef56cd2f","Cargo.lock":"9b04639cee0f4f014700a16aecb4b2a168e5d1261baf89747730cc4384dabe36","Cargo.toml":"1a56f2eed01dfef37968a3826b8b0c2cd3f77ab91977588001dc92419613ebdf","README.md":"edc8122b28f1948ca3e52b259363ec599ff97e2b29b32c328893605124203f22","UNLICENSE":"7e12e5df4bae12cb21581ba157ced20e1986a0508dd10d0e8a4ab9a4cf94e85c","examples/main.rs":"734a87846b61d09d2aaca444c69dc61765f66df34602f3a4acf1255f95404226","src/data.rs":"677b52a636deb1f0ffc623dbdc5ed7acd78d915117825ced7031c6fa6f0c861e","src/lib.rs":"ef2f27d9caf4ba1183364cd58a6fd8d0d5400cdb0de6350bcc7cf655eb557c2f","src/parser.rs":"495a155de17c4422cf11ff97026de681707156e211ed89693fd0546d8628127a","src/result.rs":"0e9064e4c912c7eae170a329277ff3811b0016a8f38d1f8f6f57a269f605b489"},"package":"9313f104b590510b46fc01c0a324fc76505c13871454d3c48490468d04c8d395"} \ No newline at end of file
diff --git a/third_party/rust/devd-rs/CODE_OF_CONDUCT.md b/third_party/rust/devd-rs/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000000..fae73b6736
--- /dev/null
+++ b/third_party/rust/devd-rs/CODE_OF_CONDUCT.md
@@ -0,0 +1,74 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to making participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, gender identity and expression, level of experience,
+nationality, personal appearance, race, religion, or sexual identity and
+orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or
+advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic
+ address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or
+reject comments, commits, code, wiki edits, issues, and other contributions
+that are not aligned to this Code of Conduct, or to ban temporarily or
+permanently any contributor for other behaviors that they deem inappropriate,
+threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community. Examples of
+representing a project or community include using an official project e-mail
+address, posting via an official social media account, or acting as an appointed
+representative at an online or offline event. Representation of a project may be
+further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported by contacting the project owner at hello@unrelenting.technology. All
+complaints will be reviewed and investigated and will result in a response that
+is deemed necessary and appropriate to the circumstances. The project owner is
+obligated to maintain confidentiality with regard to the reporter of an incident.
+Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good
+faith may face temporary or permanent repercussions as determined by other
+members of the project's leadership.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
+available at [http://contributor-covenant.org/version/1/4][version]
+
+[homepage]: http://contributor-covenant.org
+[version]: http://contributor-covenant.org/version/1/4/
diff --git a/third_party/rust/devd-rs/Cargo.lock b/third_party/rust/devd-rs/Cargo.lock
new file mode 100644
index 0000000000..73f68ee90e
--- /dev/null
+++ b/third_party/rust/devd-rs/Cargo.lock
@@ -0,0 +1,39 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "devd-rs"
+version = "0.3.6"
+dependencies = [
+ "libc",
+ "nom",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.134"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "329c933548736bc49fd575ee68c89e8be4d260064184389a5b77517cddd99ffb"
+
+[[package]]
+name = "memchr"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
+
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
+[[package]]
+name = "nom"
+version = "7.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
diff --git a/third_party/rust/devd-rs/Cargo.toml b/third_party/rust/devd-rs/Cargo.toml
new file mode 100644
index 0000000000..14a87f213a
--- /dev/null
+++ b/third_party/rust/devd-rs/Cargo.toml
@@ -0,0 +1,37 @@
+# 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 = "2018"
+name = "devd-rs"
+version = "0.3.6"
+authors = ["Val Packett <val@packett.cool>"]
+description = "An interface to devd, the device hotplug daemon on FreeBSD and DragonFlyBSD"
+homepage = "https://codeberg.org/valpackett/devd-rs"
+readme = "README.md"
+keywords = [
+ "System",
+ "FreeBSD",
+ "DragonFlyBSD",
+ "devd",
+ "hotplug",
+]
+categories = ["os::unix-apis"]
+license = "Unlicense/MIT"
+repository = "https://codeberg.org/valpackett/devd-rs"
+
+[dependencies.libc]
+version = "0"
+
+[dependencies.nom]
+version = "7"
+features = ["std"]
+default-features = false
diff --git a/third_party/rust/devd-rs/README.md b/third_party/rust/devd-rs/README.md
new file mode 100644
index 0000000000..9313848029
--- /dev/null
+++ b/third_party/rust/devd-rs/README.md
@@ -0,0 +1,27 @@
+[![crates.io](https://img.shields.io/crates/v/devd-rs.svg)](https://crates.io/crates/devd-rs)
+[![unlicense](https://img.shields.io/badge/un-license-green.svg?style=flat)](https://unlicense.org)
+
+# devd-rs
+
+A Rust library for listening to FreeBSD (also DragonFlyBSD) [devd](https://www.freebsd.org/cgi/man.cgi?devd)'s device attach-detach notifications.
+
+Listens on `/var/run/devd.seqpacket.pipe` and parses messages using [nom](https://github.com/Geal/nom).
+
+## Usage
+
+See [examples/main.rs](https://github.com/unrelentingtech/devd-rs/blob/master/examples/main.rs).
+
+## Contributing
+
+Please feel free to submit pull requests!
+
+By participating in this project you agree to follow the [Contributor Code of Conduct](https://www.contributor-covenant.org/version/1/4/code-of-conduct/) and to release your contributions under the Unlicense and the MIT License.
+
+[The list of contributors is available on GitHub](https://github.com/unrelentingtech/devd-rs/graphs/contributors).
+
+## License
+
+This is free and unencumbered software released into the public domain.
+For more information, please refer to the `UNLICENSE` file or [unlicense.org](https://unlicense.org).
+
+It is also available under the MIT License.
diff --git a/third_party/rust/devd-rs/UNLICENSE b/third_party/rust/devd-rs/UNLICENSE
new file mode 100644
index 0000000000..68a49daad8
--- /dev/null
+++ b/third_party/rust/devd-rs/UNLICENSE
@@ -0,0 +1,24 @@
+This is free and unencumbered software released into the public domain.
+
+Anyone is free to copy, modify, publish, use, compile, sell, or
+distribute this software, either in source code form or as a compiled
+binary, for any purpose, commercial or non-commercial, and by any
+means.
+
+In jurisdictions that recognize copyright laws, the author or authors
+of this software dedicate any and all copyright interest in the
+software to the public domain. We make this dedication for the benefit
+of the public at large and to the detriment of our heirs and
+successors. We intend this dedication to be an overt act of
+relinquishment in perpetuity of all present and future rights to this
+software under copyright law.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+For more information, please refer to <http://unlicense.org/>
diff --git a/third_party/rust/devd-rs/examples/main.rs b/third_party/rust/devd-rs/examples/main.rs
new file mode 100644
index 0000000000..fee8612f1e
--- /dev/null
+++ b/third_party/rust/devd-rs/examples/main.rs
@@ -0,0 +1,12 @@
+extern crate devd_rs;
+
+use devd_rs::*;
+
+fn main() {
+ let mut ctx = Context::new().unwrap();
+ loop {
+ if let Ok(ev) = ctx.wait_for_event(1000) {
+ println!("{:?}", ev);
+ }
+ }
+}
diff --git a/third_party/rust/devd-rs/src/data.rs b/third_party/rust/devd-rs/src/data.rs
new file mode 100644
index 0000000000..52084362e5
--- /dev/null
+++ b/third_party/rust/devd-rs/src/data.rs
@@ -0,0 +1,9 @@
+pub use std::collections::BTreeMap;
+
+#[derive(Debug, Clone, PartialEq)]
+pub enum Event {
+ Notify { system: String, subsystem: String, kind: String, data: BTreeMap<String, String> },
+ Attach { dev: String, parent: BTreeMap<String, String>, location: String },
+ Detach { dev: String, parent: BTreeMap<String, String>, location: String },
+ Nomatch { parent: BTreeMap<String, String>, location: String },
+}
diff --git a/third_party/rust/devd-rs/src/lib.rs b/third_party/rust/devd-rs/src/lib.rs
new file mode 100644
index 0000000000..8c863e856e
--- /dev/null
+++ b/third_party/rust/devd-rs/src/lib.rs
@@ -0,0 +1,93 @@
+pub mod data;
+pub mod parser;
+pub mod result;
+
+use io::{BufRead, BufReader};
+use libc::{c_int, connect, nfds_t, poll, pollfd, sockaddr_un, socket, AF_UNIX, POLLIN, SOCK_SEQPACKET};
+use std::cmp::Ordering;
+use std::os::unix::io::{FromRawFd, RawFd};
+use std::os::unix::net::UnixStream;
+use std::{io, mem, ptr};
+
+pub use data::*;
+pub use result::*;
+
+const SOCKET_PATH: &str = "/var/run/devd.seqpacket.pipe";
+
+pub fn parse_devd_event(event: &str) -> Result<Event> {
+ match parser::event(event) {
+ Ok((_, x)) => Ok(x),
+ _ => Err(Error::Parse),
+ }
+}
+
+#[derive(Debug)]
+pub struct Context {
+ sock: BufReader<UnixStream>,
+ sockfd: RawFd,
+ buffer: String,
+}
+
+impl Context {
+ pub fn new() -> Result<Context> {
+ unsafe {
+ let sockfd = socket(AF_UNIX, SOCK_SEQPACKET, 0);
+ if sockfd < 0 {
+ return Err(io::Error::last_os_error().into());
+ }
+ let mut sockaddr = sockaddr_un { sun_family: AF_UNIX as _, ..mem::zeroed() };
+ ptr::copy_nonoverlapping(SOCKET_PATH.as_ptr(), sockaddr.sun_path.as_mut_ptr() as *mut u8, SOCKET_PATH.len());
+ if connect(
+ sockfd,
+ &sockaddr as *const sockaddr_un as *const _,
+ (mem::size_of_val(&AF_UNIX) + SOCKET_PATH.len()) as _,
+ ) < 0
+ {
+ return Err(io::Error::last_os_error().into());
+ }
+ Ok(Context {
+ sock: BufReader::new(UnixStream::from_raw_fd(sockfd)),
+ sockfd,
+ buffer: String::new(),
+ })
+ }
+ }
+
+ pub fn wait_for_event_raw_internal(&mut self, timeout_ms: usize) -> Result<&str> {
+ let mut fds = [pollfd { fd: self.sockfd, events: POLLIN, revents: 0 }];
+ let x = unsafe { poll((&mut fds).as_mut_ptr(), fds.len() as nfds_t, timeout_ms as c_int) };
+
+ match x.cmp(&0) {
+ Ordering::Less => Err(io::Error::last_os_error().into()),
+ Ordering::Equal => Err(Error::Timeout),
+ Ordering::Greater => {
+ self.buffer.clear();
+ self.sock.read_line(&mut self.buffer)?;
+ Ok(&self.buffer)
+ }
+ }
+ }
+
+ /// Waits for an event using poll(), reads it but does not parse
+ pub fn wait_for_event_raw(&mut self, timeout_ms: usize) -> Result<String> {
+ self.wait_for_event_raw_internal(timeout_ms).map(ToOwned::to_owned)
+ }
+
+ /// Waits for an event using poll(), reads and parses it
+ pub fn wait_for_event(&mut self, timeout_ms: usize) -> Result<Event> {
+ self.wait_for_event_raw_internal(timeout_ms).and_then(parse_devd_event)
+ }
+
+ /// Returns the devd socket file descriptor in case you want to select/poll on it together with
+ /// other file descriptors
+ pub fn fd(&self) -> RawFd {
+ self.sockfd
+ }
+
+ /// Reads an event and parses it. Use when polling on the raw fd by yourself
+ pub fn read_event(&mut self) -> Result<Event> {
+ self.buffer.clear();
+ self.sock.read_line(&mut self.buffer)?;
+ parse_devd_event(&self.buffer)
+ }
+}
diff --git a/third_party/rust/devd-rs/src/parser.rs b/third_party/rust/devd-rs/src/parser.rs
new file mode 100644
index 0000000000..ee3c0511e9
--- /dev/null
+++ b/third_party/rust/devd-rs/src/parser.rs
@@ -0,0 +1,153 @@
+use crate::data::*;
+
+use nom::{
+ branch::alt,
+ bytes::complete::{tag, take_while},
+ character::complete::{alphanumeric1, char, multispace0, multispace1},
+ combinator::success,
+ multi::fold_many0,
+ sequence::{delimited, preceded, terminated, tuple},
+ IResult, Parser,
+};
+
+/// Parse a single value, which is either a quoted string, or a word without whitespace
+fn val(input: &str) -> IResult<&str, &str, ()> {
+ alt((delimited(char('"'), take_while(|c| c != '"'), char('"')), take_while(|c| c != '\n' && c != ' '))).parse(input)
+}
+
+/// Parse a key followed by a value, separated by =
+fn keyval(input: &str) -> IResult<&str, (&str, &str), ()> {
+ terminated(alphanumeric1, char('=')).and(val).parse(input)
+}
+
+/// Parser any number of key-value pairs, separated by 0 or more whitespace
+fn keyvals(input: &str) -> IResult<&str, BTreeMap<String, String>, ()> {
+ fold_many0(terminated(keyval, multispace0), BTreeMap::new, |mut map, (key, value)| {
+ map.insert(key.to_owned(), value.to_owned());
+ map
+ })
+ .parse(input)
+}
+
+/// Parse a key-value pair, where the key is a specific tag, separated by =,
+/// terminated by whitespace
+fn keyed_val<'i>(key: &'static str) -> impl Parser<&'i str, &'i str, ()> {
+ terminated(preceded(terminated(tag(key), char('=')), val), multispace1)
+}
+
+fn notify(input: &str) -> IResult<&str, Event, ()> {
+ preceded(char('!'), tuple((keyed_val("system"), keyed_val("subsystem"), keyed_val("type"), keyvals)))
+ .map(|(sys, subsys, kind, data)| Event::Notify {
+ system: sys.to_owned(),
+ subsystem: subsys.to_owned(),
+ kind: kind.to_owned(),
+ data,
+ })
+ .parse(input)
+}
+
+/// Parse a key-value pair, where the key is a specific tag, separated by
+/// whitespace
+fn event_param<'i, T>(key: &'static str, value: impl Parser<&'i str, T, ()>) -> impl Parser<&'i str, T, ()> {
+ preceded(terminated(tag(key), multispace1), value)
+}
+
+fn generic_event<'i, T>(prefix: char, dev: impl Parser<&'i str, T, ()>) -> impl Parser<&'i str, (T, BTreeMap<String, String>, &'i str), ()> {
+ tuple((
+ terminated(preceded(char(prefix), dev), multispace1),
+ event_param("at", keyvals),
+ event_param("on", val),
+ ))
+}
+
+fn attach(input: &str) -> IResult<&str, Event, ()> {
+ generic_event('+', alphanumeric1).map(|(dev, parent, loc)| Event::Attach { dev: dev.to_owned(), parent, location: loc.to_owned() }).parse(input)
+}
+
+fn detach(input: &str) -> IResult<&str, Event, ()> {
+ generic_event('-', alphanumeric1).map(|(dev, parent, loc)| Event::Detach { dev: dev.to_owned(), parent, location: loc.to_owned() }).parse(input)
+}
+
+fn nomatch(input: &str) -> IResult<&str, Event, ()> {
+ generic_event('?', success(())).map(|((), parent, loc)| Event::Nomatch { parent, location: loc.to_owned() }).parse(input)
+}
+
+pub fn event(input: &str) -> IResult<&str, Event, ()> {
+ alt((notify, attach, detach, nomatch)).parse(input)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_notify() {
+ let txt = "!system=USB subsystem=INTERFACE type=ATTACH ugen=ugen0.2 vendor=0x1050 sernum=\"\" mode=host\n";
+ let res = event(txt);
+ let mut data = BTreeMap::new();
+ data.insert("ugen".to_owned(), "ugen0.2".to_owned());
+ data.insert("vendor".to_owned(), "0x1050".to_owned());
+ data.insert("sernum".to_owned(), "".to_owned());
+ data.insert("mode".to_owned(), "host".to_owned());
+ assert_eq!(
+ res,
+ Ok((
+ "",
+ Event::Notify {
+ system: "USB".to_owned(),
+ subsystem: "INTERFACE".to_owned(),
+ kind: "ATTACH".to_owned(),
+ data,
+ }
+ ))
+ )
+ }
+
+ #[test]
+ fn test_attach() {
+ let txt = "+uhid1 at bus=0 sernum=\"\" on uhub1";
+ let res = event(txt);
+ let mut data = BTreeMap::new();
+ data.insert("bus".to_owned(), "0".to_owned());
+ data.insert("sernum".to_owned(), "".to_owned());
+ assert_eq!(
+ res,
+ Ok((
+ "",
+ Event::Attach {
+ dev: "uhid1".to_owned(),
+ parent: data,
+ location: "uhub1".to_owned(),
+ }
+ ))
+ )
+ }
+
+ #[test]
+ fn test_detach() {
+ let txt = "-uhid1 at on uhub1";
+ let res = event(txt);
+ let data = BTreeMap::new();
+ assert_eq!(
+ res,
+ Ok((
+ "",
+ Event::Detach {
+ dev: "uhid1".to_owned(),
+ parent: data,
+ location: "uhub1".to_owned(),
+ }
+ ))
+ )
+ }
+
+ #[test]
+ fn test_nomatch() {
+ let txt = "? at bus=0 on uhub1";
+ let res = event(txt);
+ let mut data = BTreeMap::new();
+ data.insert("bus".to_owned(), "0".to_owned());
+
+ assert_eq!(res, Ok(("", Event::Nomatch { parent: data, location: "uhub1".to_owned() })))
+ }
+}
diff --git a/third_party/rust/devd-rs/src/result.rs b/third_party/rust/devd-rs/src/result.rs
new file mode 100644
index 0000000000..2b47b8012c
--- /dev/null
+++ b/third_party/rust/devd-rs/src/result.rs
@@ -0,0 +1,26 @@
+use std::{io, result};
+
+#[derive(Debug)]
+pub enum Error {
+ IoError(io::Error),
+ Timeout,
+ Parse,
+}
+
+impl From<Error> for io::Error {
+ fn from(val: Error) -> Self {
+ match val {
+ Error::IoError(e) => e,
+ Error::Timeout => io::Error::new(io::ErrorKind::Other, "devd poll timeout"),
+ Error::Parse => io::Error::new(io::ErrorKind::Other, "devd parse error"),
+ }
+ }
+}
+
+impl From<io::Error> for Error {
+ fn from(err: io::Error) -> Error {
+ Error::IoError(err)
+ }
+}
+
+pub type Result<T> = result::Result<T, Error>;