From 5cd111ce0bd00b42125491c216543a36077dcea6 Mon Sep 17 00:00:00 2001 From: Dario Cancelliere Date: Mon, 8 Jul 2024 19:17:40 +0200 Subject: [PATCH] first commit --- .gitignore | 1 + .idea/.gitignore | 5 ++ .idea/fallible_map.iml | 11 +++ .idea/modules.xml | 8 +++ .idea/vcs.xml | 6 ++ Cargo.lock | 7 ++ Cargo.toml | 19 +++++ LICENSE.md | 9 +++ README.md | 125 ++++++++++++++++++++++++++++++++ src/lib.rs | 157 +++++++++++++++++++++++++++++++++++++++++ 10 files changed, 348 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/fallible_map.iml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 src/lib.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..b58b603 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,5 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/fallible_map.iml b/.idea/fallible_map.iml new file mode 100644 index 0000000..cf84ae4 --- /dev/null +++ b/.idea/fallible_map.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..19c7c34 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..8b30ae2 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "fallible_map" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..3a03db4 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "fallible_map" +version = "0.1.0" +authors = ["Dario Cancelliere "] +edition = "2018" +license = "MIT" +description = "Utilities for fallible mapping over `Option` and iterators using functions that can return `Result`s." +repository = "https://github.com/tifaremosapere/fallible_map" +homepage = "https://github.com/tifaremosapere/fallible_map" +documentation = "https://docs.rs/fallible_map_ext" +readme = "README.md" +keywords = ["fallible", "mapping", "option", "result", "error-handling"] +categories = ["data-structures", "algorithms", "rust-patterns"] +exclude = ["/target"] + +[dependencies] + +[features] +default = [] diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..818e0c3 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) 2024 Ti faremo sapere + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +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 OR COPYRIGHT HOLDERS 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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..982e0d0 --- /dev/null +++ b/README.md @@ -0,0 +1,125 @@ +# Fallible Map + +[![Crates.io](https://img.shields.io/crates/v/fallible_map.svg)](https://crates.io/crates/fallible_map) +![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg) +![Version](https://img.shields.io/badge/version-0.1.0-green) +[![Repository](https://img.shields.io/badge/github-repository-orange)](https://github.com/tifaremosapere/fallible_map) +[![Homepage](https://img.shields.io/badge/homepage-Ti%20faremo%20sapere-brightgreen)](https://tifaremosapere.it) + +`fallible_map` provides utilities for fallible mapping over `Option` types and iterators, allowing the use of functions that can return `Result`s. + +This library includes traits to enhance `Option` and `Iterator` types with methods to handle fallible operations gracefully. + +## Overview + +This crate offers extensions for optional values and iterators to perform fallible mapping operations, returning results that properly reflect potential errors during computation. + +These extensions can be useful in scenarios where operations may fail, and error handling is required. + +## Features + +- **ExtractOption trait:** A helper trait to extract the inner value of an optional container; +- **FallibleMapExt trait:** Extends `Option` with methods for fallible operations, such as `try_map`, `try_unwrap_or`, and `try_and_then`; +- **TryMapIteratorExt trait:** Extends iterators with a `try_map` method, allowing the use of functions that return `Result`s during iteration. + +## Installation + +Add this to your `Cargo.toml`: + +```toml +[dependencies] +fallible_map = "^0.1" +``` + +## Usage + +### Examples + +#### Using FallibleMapExt with `Option` + +```rust +use fallible_map::FallibleMapExt; + +fn main() -> Result<(), String> { + let some_number: Option = Some(2); + + let result: Result, String> = some_number.try_map(|num| { + if num % 2 == 0 { + Ok(num * 2) + } else { + Err("Odd number".to_string()) + } + }); + + assert_eq!(result, Ok(Some(4))); + + Ok(()) +} +``` + +#### Using TryMapIteratorExt with `Iterator` + +```rust +use fallible_map::TryMapIteratorExt; + +fn main() -> Result<(), String> { + let numbers = vec![1, 2, 3, 4, 5]; + + let mapped_numbers: Result, String> = numbers.into_iter().try_map(|x| { + if x % 2 == 0 { + Ok(x * 2) + } else { + Err(format!("Failed to process {}", x)) + } + }); + + match mapped_numbers { + Ok(nums) => println!("Mapped successfully: {:?}", nums), + Err(e) => println!("Error occurred: {}", e), + } + + Ok(()) +} +``` + +#### Using FallibleMapExt with `try_and_then` + +```rust +use fallible_map::FallibleMapExt; + +fn main() -> Result<(), String> { + let some_number: Option = Some(2); + + let result: Result, String> = some_number.try_and_then(|num| { + if num % 2 == 0 { + Ok(Some(num * 2)) + } else { + Err("Odd number".to_string()) + } + }); + + assert_eq!(result, Ok(Some(4))); + + let none_number: Option = None; + + let result = none_number.try_and_then(|num| { + if num % 2 == 0 { + Ok(Some(num * 2)) + } else { + Err("Odd number".to_string()) + } + }); + + assert_eq!(result, Ok(None)); + + Ok(()) +} +``` + +## License + +This project is licensed under the MIT License. See the [LICENSE](LICENSE.md) file for details. + +## Contribution + +Contributions are welcome! Please feel free to submit a pull request, open an issue, or suggest features and improvements. \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..4247a55 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,157 @@ +/// `fallible_map_ext` provides utilities for fallible mapping over `Option` +/// types and iterators, allowing the use of functions that can return `Result`s. + +/// A helper trait to extract the inner value of an optional container. +pub trait ExtractOption { + /// Extract the inner value as an `Option`. + fn extract(self) -> Option; +} + +/// Implementation of `ExtractOption` for `Option`. +impl ExtractOption for Option { + fn extract(self) -> Option { + self + } +} + +/// Extend `Option` with fallible methods. +/// +/// Useful for mapping fallible operations (i.e., operations that return `Result`), +/// over an optional type. The result will be `Result>`, making it easy +/// to handle errors originating from inside the closure being mapped. +/// +/// # Type Parameters +/// +/// - `C`: The container type that implements `ExtractOption` +/// - `T`: The input container's value type +/// - `U`: The output container's value type +/// - `E`: The possible error type during the mapping +pub trait FallibleMapExt { + /// Attempt to map a function over an optional value. + /// + /// # Parameters + /// + /// - `f`: A function that takes a value of type `T` and returns a `Result`. + /// + /// # Returns + /// + /// A `Result` containing an `Option`, or an error `E`. + fn try_map(self, f: F) -> Result, E> + where + F: FnOnce(T) -> Result; + + /// Unwrap an optional value or compute a fallback. + /// + /// # Parameters + /// + /// - `f`: A function that returns a `Result`. + /// + /// # Returns + /// + /// A `Result` containing a value of type `T`, or an error `E`. + fn try_unwrap_or(self, f: F) -> Result + where + F: FnOnce() -> Result; + + /// Chain computation that returns another optional value. + /// + /// # Parameters + /// + /// - `f`: A function that takes a value of type `T` and returns a `Result, E>`. + /// + /// # Returns + /// + /// A `Result` containing an `Option`, or an error `E`. + fn try_and_then(self, f: F) -> Result, E> + where + F: FnOnce(T) -> Result, E>; +} + +/// Implementation of `FallibleMapExt` for types implementing `ExtractOption`. +impl FallibleMapExt for C +where + C: ExtractOption, +{ + fn try_map(self, f: F) -> Result, E> + where + F: FnOnce(T) -> Result, + { + match self.extract() { + Some(x) => f(x).map(Some), + None => Ok(None), + } + } + + fn try_unwrap_or(self, f: F) -> Result + where + F: FnOnce() -> Result, + { + match self.extract() { + Some(x) => Ok(x), + None => f(), + } + } + + fn try_and_then(self, f: F) -> Result, E> + where + F: FnOnce(T) -> Result, E>, + { + match self.extract() { + Some(x) => f(x), + None => Ok(None), + } + } +} + +/// Extend iterator with fallible map functionality. +/// +/// This trait provides a fallible version of the `map` method, allowing +/// the use of functions that return `Result`s during iteration. +/// +/// # Example +/// +/// ``` +/// use my_crate::TryMapIteratorExt; +/// +/// let numbers = vec![1, 2, 3, 4, 5]; +/// let result: Result, _> = numbers.into_iter().try_map(|num| { +/// if num % 2 == 0 { +/// Ok(num * 2) +/// } else { +/// Err("Odd number") +/// } +/// }); +/// assert_eq!(result, Err("Odd number")); +/// ``` +pub trait TryMapIteratorExt: Iterator { + /// Attempt to map a function over an iterator. + /// + /// # Parameters + /// + /// - `f`: A function that takes an item and returns a `Result`. + /// + /// # Returns + /// + /// A `Result` containing a `Vec`, or an error `E`. + fn try_map(self, f: F) -> Result, E> + where + Self: Sized, + F: FnMut(Self::Item) -> Result; +} + +/// Implementation of `TryMapIteratorExt` for all iterators. +impl TryMapIteratorExt for I +where + I: Iterator, +{ + fn try_map(mut self, mut f: F) -> Result, E> + where + Self: Sized, + F: FnMut(Self::Item) -> Result, + { + self.try_fold(Vec::new(), |mut results, item| { + results.push(f(item)?); + Ok(results) + }) + } +}