Skip to content

Commit

Permalink
Add Option::as_ref_or_else helper (#717)
Browse files Browse the repository at this point in the history
* Modularize utils

* Implement Option::as_{de,}ref_or_else

* Changelog

* Fix broken doclink

* Use capturing format variables, thanks @NickLarsenNZ
  • Loading branch information
nightkr authored Jan 16, 2024
1 parent ddc57ad commit 998685a
Show file tree
Hide file tree
Showing 7 changed files with 255 additions and 157 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ All notable changes to this project will be documented in this file.

## [Unreleased]

### Added

- Added `Option::as_ref_or_else` to `utils` ([#717]).

### Changed

- Split `utils` into submodules ([#717]).

[#717]: https://github.com/stackabletech/operator-rs/pull/717

## [0.61.0] - 2024-01-15

### Added
Expand Down
157 changes: 0 additions & 157 deletions src/utils.rs

This file was deleted.

44 changes: 44 additions & 0 deletions src/utils/bash.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/// This is a bash snippet, which adds two functions out of interest:
///
/// 1. `prepare_signal_handlers` call this first to set up the needed traps
/// 2. `wait_for_termination` waits for the PID you passed as the first argument to terminate
///
/// An example use could be
/// ```text
/// {COMMON_BASH_TRAP_FUNCTIONS}
/// echo "Run before startup"
/// prepare_signal_handlers
/// {hadoop_home}/bin/hdfs {role} &
/// wait_for_termination $!
/// echo "Run after termination"
/// ```
pub const COMMON_BASH_TRAP_FUNCTIONS: &str = r#"
prepare_signal_handlers()
{
unset term_child_pid
unset term_kill_needed
trap 'handle_term_signal' TERM
}
handle_term_signal()
{
if [ "${term_child_pid}" ]; then
kill -TERM "${term_child_pid}" 2>/dev/null
else
term_kill_needed="yes"
fi
}
wait_for_termination()
{
set +e
term_child_pid=$1
if [[ -v term_kill_needed ]]; then
kill -TERM "${term_child_pid}" 2>/dev/null
fi
wait ${term_child_pid} 2>/dev/null
trap - TERM
wait ${term_child_pid} 2>/dev/null
set -e
}
"#;
46 changes: 46 additions & 0 deletions src/utils/logging.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
use tracing::info;

/// Prints helpful and standardized diagnostic messages.
///
/// This method is meant to be called first thing in the `main` method of an Operator.
///
/// # Usage
///
/// Use the [`built`](https://crates.io/crates/built) crate and include it in your `main.rs` like this:
///
/// ```text
/// mod built_info {
/// // The file has been placed there by the build script.
/// include!(concat!(env!("OUT_DIR"), "/built.rs"));
/// }
/// ```
///
/// Then call this method in your `main` method:
///
/// ```text
/// stackable_operator::utils::print_startup_string(
/// built_info::PKG_DESCRIPTION,
/// built_info::PKG_VERSION,
/// built_info::GIT_VERSION,
/// built_info::TARGET,
/// built_info::BUILT_TIME_UTC,
/// built_info::RUSTC_VERSION,
/// );
/// ```
pub fn print_startup_string(
pkg_description: &str,
pkg_version: &str,
git_version: Option<&str>,
target: &str,
built_time: &str,
rustc_version: &str,
) {
let git = match git_version {
None => "".to_string(),
Some(git) => format!(" (Git information: {git})"),
};
info!("Starting {pkg_description}");
info!(
"This is version {pkg_version}{git}, built for {target} by {rustc_version} at {built_time}",
)
}
26 changes: 26 additions & 0 deletions src/utils/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
pub mod bash;
pub mod logging;
mod option;
mod url;

#[deprecated(
note = "renamed to stackable_operator::utils::bash::COMMON_BASH_TRAP_FUNCTIONS",
since = "0.61.1"
)]
pub use self::bash::COMMON_BASH_TRAP_FUNCTIONS;
#[deprecated(
note = "renamed to stackable_operator::utils::logging::print_startup_string",
since = "0.61.1"
)]
pub use self::logging::print_startup_string;

pub use self::{option::OptionExt, url::UrlExt};

/// Returns the fully qualified controller name, which should be used when a single controller needs to be referred to uniquely.
///
/// `operator` should be a FQDN-style operator name (for example: `zookeeper.stackable.tech`).
/// `controller` should typically be the lower-case version of the primary resource that the
/// controller manages (for example: `zookeepercluster`).
pub(crate) fn format_full_controller_name(operator: &str, controller: &str) -> String {
format!("{operator}_{controller}")
}
74 changes: 74 additions & 0 deletions src/utils/option.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
use std::{borrow::Cow, ops::Deref};

#[cfg(doc)]
use std::path::PathBuf;

/// Extension methods for [`Option`].
pub trait OptionExt<T> {
/// Returns a reference to the value if [`Some`], otherwise evaluates `default()`.
///
/// Compared to [`Option::unwrap_or_else`], this saves having to [`Clone::clone`] the value to make the types line up.
///
/// Consider using [`Self::as_deref_or_else`] instead if the type implements [`Deref`] (such as [`String`] or [`PathBuf`]).
fn as_ref_or_else(&self, default: impl FnOnce() -> T) -> Cow<T>
where
T: Clone;

/// Returns a reference to `self` if [`Some`], otherwise evaluates `default()`.
///
/// Compared to [`Option::unwrap_or_else`], this saves having to [`Clone::clone`] the value to make the types line up.
///
/// Consider using [`Self::as_ref_or_else`] instead if the type does not implement [`Deref`].
fn as_deref_or_else(&self, default: impl FnOnce() -> T) -> Cow<T::Target>
where
T: Deref,
T::Target: ToOwned<Owned = T>;
}

impl<T> OptionExt<T> for Option<T> {
fn as_ref_or_else(&self, default: impl FnOnce() -> T) -> Cow<T>
where
T: Clone,
{
self.as_ref()
.map_or_else(|| Cow::Owned(default()), Cow::Borrowed)
}

fn as_deref_or_else(&self, default: impl FnOnce() -> T) -> Cow<<T>::Target>
where
T: Deref,
<T>::Target: ToOwned<Owned = T>,
{
self.as_deref()
.map_or_else(|| Cow::Owned(default()), Cow::Borrowed)
}
}

#[cfg(test)]
mod tests {
use std::borrow::Cow;

use crate::utils::OptionExt as _;

#[test]
fn test_as_ref_or_else() {
let maybe: Option<String> = None;
let defaulted: Cow<String> = maybe.as_ref_or_else(|| "foo".to_string());
assert_eq!(defaulted, Cow::<String>::Owned("foo".to_string()));

let maybe: Option<String> = Some("foo".to_string());
let defaulted: Cow<String> = maybe.as_ref_or_else(|| panic!());
assert_eq!(defaulted, Cow::<String>::Borrowed(&"foo".to_string()));
}

#[test]
fn test_as_deref_or_else() {
let maybe: Option<String> = None;
let defaulted: Cow<str> = maybe.as_deref_or_else(|| "foo".to_string());
assert_eq!(defaulted, Cow::<str>::Owned("foo".to_string()));

let maybe: Option<String> = Some("foo".to_string());
let defaulted: Cow<str> = maybe.as_deref_or_else(|| panic!());
assert_eq!(defaulted, Cow::<str>::Borrowed("foo"));
}
}
Loading

0 comments on commit 998685a

Please sign in to comment.