#![deny(
clippy::all,
clippy::pedantic,
clippy::nursery,
clippy::suspicious,
clippy::complexity,
clippy::perf
)]
#![deny(
clippy::absolute_paths,
clippy::todo,
clippy::unimplemented,
clippy::tests_outside_test_module,
clippy::panic,
clippy::unwrap_used,
clippy::unwrap_in_result,
clippy::unused_trait_names,
clippy::print_stdout,
clippy::print_stderr
)]
#![deny(missing_docs)]
pub mod action;
pub mod bmc;
pub mod deserialize;
pub mod dynamic_properties;
pub mod edm_date_time_offset;
pub mod edm_duration;
pub mod edm_primitive_type;
pub mod nav_property;
pub mod odata;
pub mod query;
pub mod upload;
use crate::query::ExpandQuery;
use futures_core::TryStream;
use serde::{Deserialize, Serialize};
use std::fmt;
use std::pin::Pin;
use std::time::Duration;
use std::{future::Future, sync::Arc};
#[doc(inline)]
pub use action::Action;
#[doc(inline)]
pub use action::ActionError;
#[doc(inline)]
pub use bmc::Bmc;
#[doc(inline)]
pub use deserialize::de_optional_nullable;
#[doc(inline)]
pub use deserialize::de_required_nullable;
#[doc(inline)]
pub use dynamic_properties::DynamicProperties;
#[doc(inline)]
pub use edm_date_time_offset::EdmDateTimeOffset;
#[doc(inline)]
pub use edm_duration::EdmDuration;
#[doc(inline)]
pub use edm_primitive_type::EdmPrimitiveType;
#[doc(inline)]
pub use nav_property::NavProperty;
#[doc(inline)]
pub use nav_property::Reference;
#[doc(inline)]
pub use nav_property::ReferenceLeaf;
#[doc(inline)]
pub use odata::ODataETag;
#[doc(inline)]
pub use odata::ODataId;
#[doc(inline)]
pub use query::FilterQuery;
#[doc(inline)]
pub use query::ToFilterLiteral;
#[doc(inline)]
pub use serde_json::Value as AdditionalProperties;
#[doc(inline)]
pub use upload::DataStream;
#[cfg(feature = "update-service-deprecated")]
#[doc(inline)]
pub use upload::HttpPushUriUpdateRequest;
#[doc(inline)]
pub use upload::MultipartUpdateRequest;
#[doc(inline)]
pub use upload::OemMultipartPart;
#[doc(inline)]
pub use upload::OemMultipartPartNameError;
#[doc(inline)]
pub use upload::OemMultipartPartReader;
#[doc(inline)]
pub use upload::UploadReader;
#[cfg(feature = "update-service-deprecated")]
#[doc(inline)]
pub use upload::UploadStream;
#[doc(inline)]
pub use uuid::Uuid as EdmGuid;
pub trait EntityTypeRef: Send + Sync + Sized {
fn odata_id(&self) -> &ODataId;
fn etag(&self) -> Option<&ODataETag>;
fn refresh<B: Bmc>(&self, bmc: &B) -> impl Future<Output = Result<Arc<Self>, B::Error>> + Send
where
Self: for<'de> Deserialize<'de> + 'static,
{
bmc.get::<Self>(self.odata_id())
}
}
pub trait Expandable: EntityTypeRef + for<'de> Deserialize<'de> + 'static {
fn expand<B: Bmc>(
&self,
bmc: &B,
query: ExpandQuery,
) -> impl Future<Output = Result<Arc<Self>, B::Error>> + Send {
bmc.expand::<Self>(self.odata_id(), query)
}
}
pub type BoxTryStream<T, E> =
Pin<Box<dyn TryStream<Ok = T, Error = E, Item = Result<T, E>> + Send>>;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct AsyncTaskLocation(
pub ODataId,
);
impl From<ODataId> for AsyncTaskLocation {
fn from(value: ODataId) -> Self {
Self(value)
}
}
#[derive(Debug)]
pub struct AsyncTask {
pub location: AsyncTaskLocation,
pub retry_after: Option<Duration>,
}
#[must_use = "mutating Redfish responses may contain an asynchronous task handle"]
#[derive(Debug)]
pub enum ModificationResponse<T> {
Entity(T),
Task(AsyncTask),
Empty,
}
impl<T> ModificationResponse<T> {
pub fn map_entity<U, F>(self, f: F) -> ModificationResponse<U>
where
F: FnOnce(T) -> U,
{
match self {
Self::Entity(entity) => ModificationResponse::Entity(f(entity)),
Self::Task(task) => ModificationResponse::Task(task),
Self::Empty => ModificationResponse::Empty,
}
}
pub fn try_map_entity<U, E, F>(self, f: F) -> Result<ModificationResponse<U>, E>
where
F: FnOnce(T) -> Result<U, E>,
{
match self {
Self::Entity(entity) => f(entity).map(ModificationResponse::Entity),
Self::Task(task) => Ok(ModificationResponse::Task(task)),
Self::Empty => Ok(ModificationResponse::Empty),
}
}
pub async fn try_map_entity_async<U, E, F, Fut>(
self,
f: F,
) -> Result<ModificationResponse<U>, E>
where
F: FnOnce(T) -> Fut,
Fut: Future<Output = Result<U, E>>,
{
match self {
Self::Entity(entity) => f(entity).await.map(ModificationResponse::Entity),
Self::Task(task) => Ok(ModificationResponse::Task(task)),
Self::Empty => Ok(ModificationResponse::Empty),
}
}
}
pub struct SessionCreateResponse<T> {
pub entity: T,
pub auth_token: String,
pub location: ODataId,
}
impl<T: fmt::Debug> fmt::Debug for SessionCreateResponse<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SessionCreateResponse")
.field("entity", &self.entity)
.field("auth_token", &"[REDACTED]")
.field("location", &self.location)
.finish()
}
}
pub trait Creatable<V: Send + Sync + Serialize, R: Send + Sync + for<'de> Deserialize<'de>>:
EntityTypeRef
{
fn create<B: Bmc>(
&self,
bmc: &B,
create: &V,
) -> impl Future<Output = Result<ModificationResponse<R>, B::Error>> + Send {
bmc.create::<V, R>(self.odata_id(), create)
}
}
pub trait Updatable<V: Sync + Send + Serialize>: EntityTypeRef + for<'de> Deserialize<'de> {
fn update<B: Bmc>(
&self,
bmc: &B,
update: &V,
) -> impl Future<Output = Result<ModificationResponse<Self>, B::Error>> + Send {
bmc.update::<V, Self>(self.odata_id(), self.etag(), update)
}
}
pub trait Deletable: EntityTypeRef + for<'de> Deserialize<'de> {
fn delete<B: Bmc>(
&self,
bmc: &B,
) -> impl Future<Output = Result<ModificationResponse<Self>, B::Error>> + Send {
bmc.delete::<Self>(self.odata_id())
}
}
pub trait RedfishSettings<E: EntityTypeRef>: Sized {
fn settings_object(&self) -> Option<NavProperty<E>>;
}
pub trait ToSnakeCase {
fn to_snake_case(&self) -> &'static str;
}
pub trait FilterProperty {
fn property_path(&self) -> &str;
}
#[cfg(test)]
mod tests {
use super::*;
fn assert_entity(
response: ModificationResponse<u32>,
expected: u32,
) -> Result<(), &'static str> {
let ModificationResponse::Entity(value) = response else {
return Err("expected an entity response");
};
assert_eq!(value, expected);
Ok(())
}
fn assert_task<T>(response: ModificationResponse<T>) -> Result<(), &'static str> {
let ModificationResponse::Task(task) = response else {
return Err("expected a task response");
};
assert_eq!(
task.location.0.to_string(),
"/redfish/v1/TaskService/Tasks/1"
);
Ok(())
}
fn assert_empty<T>(response: ModificationResponse<T>) -> Result<(), &'static str> {
if !matches!(response, ModificationResponse::Empty) {
return Err("expected an empty response");
}
Ok(())
}
fn task_response() -> ModificationResponse<()> {
ModificationResponse::Task(AsyncTask {
location: ODataId::from("/redfish/v1/TaskService/Tasks/1".to_string()).into(),
retry_after: None,
})
}
#[test]
fn map_entity_maps_entity_and_preserves_task_and_empty() -> Result<(), &'static str> {
assert_entity(
ModificationResponse::Entity(21_u32).map_entity(|value| value * 2),
42,
)?;
assert_task(task_response().map_entity(|()| 42_u32))?;
assert_empty(ModificationResponse::<()>::Empty.map_entity(|()| 42_u32))?;
Ok(())
}
#[test]
fn try_map_entity_maps_entity_and_propagates_error() -> Result<(), &'static str> {
assert_entity(
ModificationResponse::Entity(21_u32).try_map_entity(|value| Ok(value * 2))?,
42,
)?;
let error = ModificationResponse::Entity(21_u32)
.try_map_entity(|_| Err::<u32, _>("mapping failed"));
assert!(matches!(error, Err("mapping failed")));
Ok(())
}
#[test]
fn try_map_entity_preserves_task_and_empty() -> Result<(), &'static str> {
assert_task(task_response().try_map_entity(|()| Ok::<u32, &'static str>(42))?)?;
assert_empty(
ModificationResponse::<()>::Empty.try_map_entity(|()| Ok::<u32, &'static str>(42))?,
)?;
Ok(())
}
#[tokio::test]
async fn try_map_entity_async_maps_entity_and_preserves_task_and_empty(
) -> Result<(), &'static str> {
assert_entity(
ModificationResponse::Entity(21_u32)
.try_map_entity_async(|value| async move { Ok(value * 2) })
.await?,
42,
)?;
assert_task(
task_response()
.try_map_entity_async(|()| async { Ok::<u32, &'static str>(42) })
.await?,
)?;
assert_empty(
ModificationResponse::<()>::Empty
.try_map_entity_async(|()| async { Ok::<u32, &'static str>(42) })
.await?,
)?;
Ok(())
}
#[tokio::test]
async fn try_map_entity_async_propagates_mapper_error() {
let response = ModificationResponse::Entity(21_u32);
let mapped = response
.try_map_entity_async(|_| async { Err::<u32, _>("mapping failed") })
.await;
assert!(matches!(mapped, Err("mapping failed")));
}
}