Skip to content

Commit 998685a

Browse files
authored
Add Option::as_ref_or_else helper (#717)
* Modularize utils * Implement Option::as_{de,}ref_or_else * Changelog * Fix broken doclink * Use capturing format variables, thanks @NickLarsenNZ
1 parent ddc57ad commit 998685a

File tree

7 files changed

+255
-157
lines changed

7 files changed

+255
-157
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,16 @@ All notable changes to this project will be documented in this file.
44

55
## [Unreleased]
66

7+
### Added
8+
9+
- Added `Option::as_ref_or_else` to `utils` ([#717]).
10+
11+
### Changed
12+
13+
- Split `utils` into submodules ([#717]).
14+
15+
[#717]: https://github.com/stackabletech/operator-rs/pull/717
16+
717
## [0.61.0] - 2024-01-15
818

919
### Added

src/utils.rs

Lines changed: 0 additions & 157 deletions
This file was deleted.

src/utils/bash.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/// This is a bash snippet, which adds two functions out of interest:
2+
///
3+
/// 1. `prepare_signal_handlers` call this first to set up the needed traps
4+
/// 2. `wait_for_termination` waits for the PID you passed as the first argument to terminate
5+
///
6+
/// An example use could be
7+
/// ```text
8+
/// {COMMON_BASH_TRAP_FUNCTIONS}
9+
/// echo "Run before startup"
10+
/// prepare_signal_handlers
11+
/// {hadoop_home}/bin/hdfs {role} &
12+
/// wait_for_termination $!
13+
/// echo "Run after termination"
14+
/// ```
15+
pub const COMMON_BASH_TRAP_FUNCTIONS: &str = r#"
16+
prepare_signal_handlers()
17+
{
18+
unset term_child_pid
19+
unset term_kill_needed
20+
trap 'handle_term_signal' TERM
21+
}
22+
23+
handle_term_signal()
24+
{
25+
if [ "${term_child_pid}" ]; then
26+
kill -TERM "${term_child_pid}" 2>/dev/null
27+
else
28+
term_kill_needed="yes"
29+
fi
30+
}
31+
32+
wait_for_termination()
33+
{
34+
set +e
35+
term_child_pid=$1
36+
if [[ -v term_kill_needed ]]; then
37+
kill -TERM "${term_child_pid}" 2>/dev/null
38+
fi
39+
wait ${term_child_pid} 2>/dev/null
40+
trap - TERM
41+
wait ${term_child_pid} 2>/dev/null
42+
set -e
43+
}
44+
"#;

src/utils/logging.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
use tracing::info;
2+
3+
/// Prints helpful and standardized diagnostic messages.
4+
///
5+
/// This method is meant to be called first thing in the `main` method of an Operator.
6+
///
7+
/// # Usage
8+
///
9+
/// Use the [`built`](https://crates.io/crates/built) crate and include it in your `main.rs` like this:
10+
///
11+
/// ```text
12+
/// mod built_info {
13+
/// // The file has been placed there by the build script.
14+
/// include!(concat!(env!("OUT_DIR"), "/built.rs"));
15+
/// }
16+
/// ```
17+
///
18+
/// Then call this method in your `main` method:
19+
///
20+
/// ```text
21+
/// stackable_operator::utils::print_startup_string(
22+
/// built_info::PKG_DESCRIPTION,
23+
/// built_info::PKG_VERSION,
24+
/// built_info::GIT_VERSION,
25+
/// built_info::TARGET,
26+
/// built_info::BUILT_TIME_UTC,
27+
/// built_info::RUSTC_VERSION,
28+
/// );
29+
/// ```
30+
pub fn print_startup_string(
31+
pkg_description: &str,
32+
pkg_version: &str,
33+
git_version: Option<&str>,
34+
target: &str,
35+
built_time: &str,
36+
rustc_version: &str,
37+
) {
38+
let git = match git_version {
39+
None => "".to_string(),
40+
Some(git) => format!(" (Git information: {git})"),
41+
};
42+
info!("Starting {pkg_description}");
43+
info!(
44+
"This is version {pkg_version}{git}, built for {target} by {rustc_version} at {built_time}",
45+
)
46+
}

src/utils/mod.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
pub mod bash;
2+
pub mod logging;
3+
mod option;
4+
mod url;
5+
6+
#[deprecated(
7+
note = "renamed to stackable_operator::utils::bash::COMMON_BASH_TRAP_FUNCTIONS",
8+
since = "0.61.1"
9+
)]
10+
pub use self::bash::COMMON_BASH_TRAP_FUNCTIONS;
11+
#[deprecated(
12+
note = "renamed to stackable_operator::utils::logging::print_startup_string",
13+
since = "0.61.1"
14+
)]
15+
pub use self::logging::print_startup_string;
16+
17+
pub use self::{option::OptionExt, url::UrlExt};
18+
19+
/// Returns the fully qualified controller name, which should be used when a single controller needs to be referred to uniquely.
20+
///
21+
/// `operator` should be a FQDN-style operator name (for example: `zookeeper.stackable.tech`).
22+
/// `controller` should typically be the lower-case version of the primary resource that the
23+
/// controller manages (for example: `zookeepercluster`).
24+
pub(crate) fn format_full_controller_name(operator: &str, controller: &str) -> String {
25+
format!("{operator}_{controller}")
26+
}

src/utils/option.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
use std::{borrow::Cow, ops::Deref};
2+
3+
#[cfg(doc)]
4+
use std::path::PathBuf;
5+
6+
/// Extension methods for [`Option`].
7+
pub trait OptionExt<T> {
8+
/// Returns a reference to the value if [`Some`], otherwise evaluates `default()`.
9+
///
10+
/// Compared to [`Option::unwrap_or_else`], this saves having to [`Clone::clone`] the value to make the types line up.
11+
///
12+
/// Consider using [`Self::as_deref_or_else`] instead if the type implements [`Deref`] (such as [`String`] or [`PathBuf`]).
13+
fn as_ref_or_else(&self, default: impl FnOnce() -> T) -> Cow<T>
14+
where
15+
T: Clone;
16+
17+
/// Returns a reference to `self` if [`Some`], otherwise evaluates `default()`.
18+
///
19+
/// Compared to [`Option::unwrap_or_else`], this saves having to [`Clone::clone`] the value to make the types line up.
20+
///
21+
/// Consider using [`Self::as_ref_or_else`] instead if the type does not implement [`Deref`].
22+
fn as_deref_or_else(&self, default: impl FnOnce() -> T) -> Cow<T::Target>
23+
where
24+
T: Deref,
25+
T::Target: ToOwned<Owned = T>;
26+
}
27+
28+
impl<T> OptionExt<T> for Option<T> {
29+
fn as_ref_or_else(&self, default: impl FnOnce() -> T) -> Cow<T>
30+
where
31+
T: Clone,
32+
{
33+
self.as_ref()
34+
.map_or_else(|| Cow::Owned(default()), Cow::Borrowed)
35+
}
36+
37+
fn as_deref_or_else(&self, default: impl FnOnce() -> T) -> Cow<<T>::Target>
38+
where
39+
T: Deref,
40+
<T>::Target: ToOwned<Owned = T>,
41+
{
42+
self.as_deref()
43+
.map_or_else(|| Cow::Owned(default()), Cow::Borrowed)
44+
}
45+
}
46+
47+
#[cfg(test)]
48+
mod tests {
49+
use std::borrow::Cow;
50+
51+
use crate::utils::OptionExt as _;
52+
53+
#[test]
54+
fn test_as_ref_or_else() {
55+
let maybe: Option<String> = None;
56+
let defaulted: Cow<String> = maybe.as_ref_or_else(|| "foo".to_string());
57+
assert_eq!(defaulted, Cow::<String>::Owned("foo".to_string()));
58+
59+
let maybe: Option<String> = Some("foo".to_string());
60+
let defaulted: Cow<String> = maybe.as_ref_or_else(|| panic!());
61+
assert_eq!(defaulted, Cow::<String>::Borrowed(&"foo".to_string()));
62+
}
63+
64+
#[test]
65+
fn test_as_deref_or_else() {
66+
let maybe: Option<String> = None;
67+
let defaulted: Cow<str> = maybe.as_deref_or_else(|| "foo".to_string());
68+
assert_eq!(defaulted, Cow::<str>::Owned("foo".to_string()));
69+
70+
let maybe: Option<String> = Some("foo".to_string());
71+
let defaulted: Cow<str> = maybe.as_deref_or_else(|| panic!());
72+
assert_eq!(defaulted, Cow::<str>::Borrowed("foo"));
73+
}
74+
}

0 commit comments

Comments
 (0)