nv_redfish/entity_link.rs
1// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16//! Generic lightweight entity link.
17//!
18//! [`EntityLink<B, T>`](crate::entity_link::EntityLink) is an owned handle that pairs a BMC client with a
19//! navigation property. It provides lazy access to any Redfish entity
20//! without eagerly fetching it.
21//!
22//! Capabilities are determined by trait bounds on the schema type `T`:
23//! - [`fetch`](crate::entity_link::EntityLink::fetch) — always available (requires `T: EntityTypeRef + Deserialize + Send + Sync`)
24//! - [`delete`](crate::entity_link::EntityLink::delete) — available when `T: Deletable`
25//!
26//! Concrete link types are defined as type aliases:
27//! ```ignore
28//! pub type SensorLink<B> = EntityLink<B, SchemaSensor>;
29//! pub type MetricReportLink<B> = EntityLink<B, MetricReportSchema>;
30//! ```
31
32use crate::Error;
33use crate::NvBmc;
34use nv_redfish_core::Bmc;
35use nv_redfish_core::Deletable;
36use nv_redfish_core::EntityTypeRef;
37use nv_redfish_core::ModificationResponse;
38use nv_redfish_core::NavProperty;
39use nv_redfish_core::ODataId;
40use serde::Deserialize;
41use std::future::Future;
42use std::sync::Arc;
43
44/// Lightweight owned handle to a Redfish entity.
45///
46/// Holds a cloned BMC client and a [`NavProperty<T>`] without eagerly
47/// fetching the entity. Use [`fetch`](Self::fetch) to retrieve the
48/// data on demand.
49///
50/// Operations like [`delete`](Self::delete) are available when the
51/// schema type `T` implements the corresponding core trait.
52pub struct EntityLink<B: Bmc, T: EntityTypeRef> {
53 bmc: NvBmc<B>,
54 nav: NavProperty<T>,
55}
56
57impl<B: Bmc, T: EntityTypeRef> EntityLink<B, T> {
58 /// Create a new entity link.
59 pub(crate) fn new(bmc: &NvBmc<B>, nav: NavProperty<T>) -> Self {
60 Self {
61 bmc: bmc.clone(),
62 nav,
63 }
64 }
65
66 /// `OData` identifier of this entity.
67 ///
68 /// Always available without network I/O.
69 #[must_use]
70 pub fn odata_id(&self) -> &ODataId {
71 self.nav.id()
72 }
73
74 /// Access the underlying navigation property.
75 #[must_use]
76 pub const fn nav(&self) -> &NavProperty<T> {
77 &self.nav
78 }
79}
80
81impl<B, T> EntityLink<B, T>
82where
83 B: Bmc,
84 T: EntityTypeRef + for<'de> Deserialize<'de> + Send + Sync + 'static,
85{
86 /// Fetch the entity from the BMC.
87 ///
88 /// If the navigation property is already expanded, returns the
89 /// cached value without network I/O.
90 ///
91 /// # Errors
92 ///
93 /// Returns an error if fetching the entity fails.
94 pub async fn fetch(&self) -> Result<Arc<T>, Error<B>> {
95 self.nav.get(self.bmc.as_ref()).await.map_err(Error::Bmc)
96 }
97
98 /// Construct a full wrapper from this link.
99 ///
100 /// Fetches the entity and wraps it in a higher-level type
101 /// that provides richer API access.
102 ///
103 /// # Errors
104 ///
105 /// Returns an error if fetching the entity fails.
106 pub async fn upgrade<W>(&self) -> Result<W, Error<B>>
107 where
108 W: FromLink<B, Schema = T>,
109 {
110 W::from_link(&self.bmc, &self.nav).await
111 }
112}
113
114impl<B, T> EntityLink<B, T>
115where
116 B: Bmc,
117 T: Deletable,
118{
119 /// Delete this entity.
120 ///
121 /// Only available when the schema type implements [`Deletable`].
122 ///
123 /// # Errors
124 ///
125 /// Returns an error if deleting the entity fails.
126 pub async fn delete(&self) -> Result<ModificationResponse<T>, Error<B>> {
127 self.bmc
128 .as_ref()
129 .delete(self.odata_id())
130 .await
131 .map_err(Error::Bmc)
132 }
133}
134
135/// Trait for full wrapper types that can be constructed from an entity link.
136///
137/// Implemented by wrapper types that need only `NvBmc<B>` and
138/// `NavProperty<T>` to be constructed (i.e., no extra context).
139pub trait FromLink<B: Bmc>: Sized {
140 /// The schema type this wrapper is built from.
141 type Schema: EntityTypeRef + for<'de> Deserialize<'de> + Send + Sync + 'static;
142
143 /// Construct the full wrapper by fetching entity data.
144 fn from_link(
145 bmc: &NvBmc<B>,
146 nav: &NavProperty<Self::Schema>,
147 ) -> impl Future<Output = Result<Self, Error<B>>> + Send;
148}