not_found_error/lib.rs
1//! # Overview
2//!
3//! ```
4//! # use not_found_error::{NotFoundError, Require};
5//! // Convert Option<i32> to Result<i32, NotFoundError<i32>>
6//!
7//! assert_eq!(Some(10).require(), Ok(10));
8//!
9//! assert_eq!(None.require(), Err(NotFoundError::<i32>::new()));
10//! ```
11//!
12//! This crate provides a generic `NotFoundError<T>` type and associated
13//! utilities for handling "not found" scenarios in a type-safe and ergonomic manner.
14//!
15//! You can convert `Option<T>` to `Result<T, NotFoundError<T>` using [`require`](require) function or [`Require`](Require) extension trait.
16//!
17//! You can convert `Option<T>` to `Result<T, NotFoundError<AnotherType>` using [`not_found`](not_found) function or [`OkOrNotFound`](OkOrNotFound) extension trait.
18//!
19//! # Features
20//!
21//! * [x] Generic `NotFoundError<T>` type
22//! * [x] Conversion functions and traits to transform `Option<T>` into `Result<T, NotFoundError<T>>`
23//! * [x] Conversion functions and traits to transform `Option<T>` into `Result<T, NotFoundError<AnotherType>>`
24//!
25//! # Examples
26//!
27//! ```
28//! use not_found_error::{NotFoundError, Require, locate, require};
29//!
30//! // Using the `require` function
31//! let item = require([1, 2, 3].into_iter().next());
32//! assert_eq!(item, Ok(1));
33//!
34//! // Using the `require` function
35//! let item = require([].into_iter().next());
36//! assert_eq!(item, Err(NotFoundError::<i32>::new()));
37//!
38//! // Using the `require` extension method
39//! let item = [1, 2, 3].into_iter().next().require();
40//! assert_eq!(item, Ok(1));
41//!
42//! // Using the `require` extension method
43//! let item = [].into_iter().next().require();
44//! assert_eq!(item, Err(NotFoundError::<i32>::new()));
45//!
46//! // Try to find a number greater than 10 (which doesn't exist in our list)
47//! let numbers = &[1, 2, 3];
48//! let result = locate(numbers, |&&n| n == 0);
49//! assert_eq!(result, Err(NotFoundError::new()));
50//! ```
51
52use std::any::type_name;
53use std::error::Error;
54use std::fmt::{Debug, Formatter};
55use std::marker::PhantomData;
56
57/// Represents an error indicating that a value was not found.
58///
59/// This struct is generic over the type `T` that was not found.
60///
61/// # Examples
62///
63/// ```
64/// use not_found_error::NotFoundError;
65///
66/// let error: NotFoundError<i32> = NotFoundError::new();
67/// assert_eq!(error.to_string(), "i32 not found");
68/// ```
69#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy, Debug)]
70pub struct NotFoundError<T>(pub PhantomData<T>);
71
72impl<T> NotFoundError<T> {
73 /// Creates a new `NotFoundError`.
74 ///
75 /// # Examples
76 ///
77 /// ```
78 /// use not_found_error::NotFoundError;
79 ///
80 /// let error: NotFoundError<String> = NotFoundError::new();
81 /// ```
82 pub fn new() -> Self {
83 Self(PhantomData)
84 }
85
86 /// Convenience method to automatically convert the error to a result.
87 ///
88 /// # Examples
89 ///
90 /// ```
91 /// use not_found_error::NotFoundError;
92 ///
93 /// let result: Result<i32, NotFoundError<i32>> = NotFoundError::result();
94 /// assert!(result.is_err());
95 /// ```
96 pub fn result() -> Result<T> {
97 Err(Self::new())
98 }
99}
100
101impl<T> Default for NotFoundError<T> {
102 fn default() -> Self {
103 Self::new()
104 }
105}
106
107impl<T> std::fmt::Display for NotFoundError<T> {
108 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
109 write!(f, "{} not found", type_name::<T>())
110 }
111}
112
113impl<T: Debug> Error for NotFoundError<T> {}
114
115/// A type alias for `Result<T, NotFoundError<T>>`
116pub type Result<T> = core::result::Result<T, NotFoundError<T>>;
117
118/// Converts `Option<T>` to `Result<T, NotFoundError<T>>`
119///
120/// # Examples
121///
122/// ```
123/// # use not_found_error::require;
124/// # let items = [0, 1, 2];
125/// let item = require(items.first());
126/// ```
127///
128/// # See also
129///
130/// - [`Require`]: Trait for converting `Option<T>` to `Result<T, NotFoundError<T>>`
131/// - [`OkOrNotFound`]: Trait for converting `Option<T>` to `Result<T, NotFoundError<AnotherType>>`
132#[inline(always)]
133pub fn require<T>(option: Option<T>) -> Result<T> {
134 option.ok_or(NotFoundError(PhantomData))
135}
136
137/// A shorter version of `NotFoundError::new()`.
138///
139/// Useful in places where you need to convert `Option<T>` into `Result<T, NotFoundError<AnotherType>>` (notice that `T != AnotherType`).
140///
141/// # Examples
142///
143/// ```
144/// # use std::path::Path;
145/// # use not_found_error::{not_found, NotFoundError};
146/// # pub struct WorkspaceRoot;
147/// pub fn get_root(path: &Path) -> Result<&Path, NotFoundError<WorkspaceRoot>> {
148/// find_root(path).ok_or(not_found())
149/// }
150/// # pub fn find_root(path: &Path) -> Option<&Path> { todo!() }
151/// ```
152///
153/// # See also
154///
155/// - [`require`]: Function to convert `Option<T>` to `Result<T, NotFoundError<T>>`
156/// - [`OkOrNotFound`]: Trait for converting `Option<T>` to `Result<T, NotFoundError<AnotherType>>`
157#[inline(always)]
158pub fn not_found<AnotherType>() -> NotFoundError<AnotherType> {
159 NotFoundError(PhantomData)
160}
161
162/// An extension trait for `Option<T>` to convert it to `Result<T, NotFoundError<T>>`
163///
164/// # Examples
165///
166/// ```
167/// # use not_found_error::Require;
168/// # let items = [0, 1, 2];
169/// let item = items.first().require();
170/// ```
171///
172/// # See also
173///
174/// - [`require`]: Function to convert `Option<T>` to `Result<T, NotFoundError<T>>`
175/// - [`OkOrNotFound`]: Trait for converting `Option<T>` to `Result<T, NotFoundError<AnotherType>>`
176pub trait Require {
177 type T;
178
179 fn require(self) -> Result<Self::T>;
180}
181
182impl<T> Require for Option<T> {
183 type T = T;
184
185 #[inline(always)]
186 fn require(self) -> Result<Self::T> {
187 self.ok_or(NotFoundError(PhantomData))
188 }
189}
190
191/// An extension trait for `Option<T>` to convert it to `Result<T, NotFoundError<AnotherType>>`
192///
193/// Useful in places where you need `NotFoundError<AnotherType>` instead of `NotFoundError<T>`.
194///
195/// # Examples
196///
197/// ```
198/// # use std::path::Path;
199/// # use not_found_error::{NotFoundError, OkOrNotFound};
200/// # pub struct WorkspaceRoot;
201/// pub fn get_root(path: &Path) -> Result<&Path, NotFoundError<WorkspaceRoot>> {
202/// find_root(path).ok_or_not_found()
203/// }
204/// # pub fn find_root(path: &Path) -> Option<&Path> { todo!() }
205/// ```
206///
207/// # See also
208///
209/// - [`Require`]: Trait for converting `Option<T>` to `Result<T, NotFoundError<T>>`
210/// - [`require`]: Function to convert `Option<T>` to `Result<T, NotFoundError<T>>`
211pub trait OkOrNotFound {
212 type T;
213
214 fn ok_or_not_found<B>(self) -> core::result::Result<Self::T, NotFoundError<B>>;
215}
216
217impl<T> OkOrNotFound for Option<T> {
218 type T = T;
219
220 #[inline(always)]
221 fn ok_or_not_found<B>(self) -> core::result::Result<Self::T, NotFoundError<B>> {
222 self.ok_or(NotFoundError(PhantomData))
223 }
224}
225
226/// Searches an iterator for an element that satisfies a given predicate and returns a reference to it.
227///
228/// This function is different from [`Iterator::find`] because it returns `Result<&T, NotFoundError<&T>>` (not `Option<&T>`).
229///
230/// # Examples
231///
232/// ```
233/// # use not_found_error::{locate, NotFoundError};
234///
235/// let numbers = &[1, 2, 3, 4, 5];
236///
237/// // Find the first even number
238/// let result = locate(numbers, |&&n| n % 2 == 0);
239/// assert_eq!(result, Ok(&2));
240///
241/// // Try to find a number greater than 10 (which doesn't exist in our list)
242/// let result = locate(numbers, |&&n| n > 10);
243/// assert_eq!(result, Err(NotFoundError::new()));
244/// ```
245pub fn locate<'a, T>(iter: impl IntoIterator<Item = &'a T>, f: impl FnMut(&&T) -> bool) -> core::result::Result<&'a T, NotFoundError<T>> {
246 iter.into_iter().find(f).ok_or_not_found()
247}