Skip to main content

nv_redfish_core/
nav_property.rs

1// SPDX-FileCopyrightText: Copyright (c) 2025 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//! Navigation property wrapper for generated types
17//!
18//! Represents Redfish/OData navigation properties which may appear either as
19//! a reference (only `@odata.id`) or as an expanded object. Generated code wraps
20//! navigation properties in [`NavProperty<T>`], allowing code to work uniformly
21//! with both forms and resolve references on demand.
22//!
23//! - Reference form: `{ "@odata.id": "/redfish/v1/Chassis/1/Thermal" }`
24//! - Expanded form: full object payload for `T` (includes `@odata.id` and fields)
25//!
26//! Key points
27//! - [`NavProperty<T>::id`] is always available (delegates to inner entity for expanded form).
28//! - [`NavProperty<T>::get`] returns `Arc<T>`; if already expanded, it clones the `Arc` without I/O.
29//! - [`EntityTypeRef::etag`] is `None` for reference form.
30//!
31//! References:
32//! - DMTF Redfish Specification DSP0266 — `https://www.dmtf.org/standards/redfish`
33//! - OASIS OData 4.01 — navigation properties in CSDL
34//!
35
36use crate::Bmc;
37use crate::Creatable;
38use crate::Deletable;
39use crate::EntityTypeRef;
40use crate::Expandable;
41use crate::FilterQuery;
42use crate::ODataETag;
43use crate::ODataId;
44use crate::Updatable;
45use serde::Deserialize;
46use serde::Deserializer;
47use serde::Serialize;
48use std::sync::Arc;
49
50/// Reference variant of the navigation property (only `@odata.id`
51/// property is specified).
52#[derive(Serialize, Deserialize, Debug, Clone)]
53#[serde(deny_unknown_fields)]
54pub struct Reference {
55    #[serde(rename = "@odata.id")]
56    odata_id: ODataId,
57}
58
59impl<T: EntityTypeRef> From<&NavProperty<T>> for Reference {
60    fn from(v: &NavProperty<T>) -> Self {
61        Self {
62            odata_id: v.id().clone(),
63        }
64    }
65}
66
67impl From<&Self> for Reference {
68    fn from(v: &Self) -> Self {
69        Self {
70            odata_id: v.odata_id.clone(),
71        }
72    }
73}
74
75impl From<&ReferenceLeaf> for Reference {
76    fn from(v: &ReferenceLeaf) -> Self {
77        Self {
78            odata_id: v.odata_id.clone(),
79        }
80    }
81}
82
83/// `ReferenceLeaf` is special type that is used for navigation
84/// properties that if corresponding `EntityType` was not compiled to
85/// the tree.
86#[derive(Serialize, Deserialize, Debug, Clone)]
87pub struct ReferenceLeaf {
88    /// `OData` identifier for of the property.
89    #[serde(rename = "@odata.id")]
90    pub odata_id: ODataId,
91}
92
93/// Container struct for the expanded property variant.
94#[derive(Debug)]
95pub struct Expanded<T>(Arc<T>);
96
97/// Deserializer that wraps the expanded property value into an `Arc`.
98impl<'de, T> Deserialize<'de> for Expanded<T>
99where
100    T: Deserialize<'de>,
101{
102    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
103    where
104        D: Deserializer<'de>,
105    {
106        T::deserialize(deserializer).map(Arc::new).map(Expanded)
107    }
108}
109
110/// Navigation property variants. All navigation properties in
111/// generated code are wrapped with this type.
112#[derive(Deserialize, Debug)]
113#[serde(untagged)]
114pub enum NavProperty<T: EntityTypeRef> {
115    /// Expanded property variant (content included in the
116    /// response).
117    Expanded(Expanded<T>),
118    /// Reference variant (only `@odata.id` is included in the
119    /// response).
120    Reference(Reference),
121}
122
123impl<T: EntityTypeRef> EntityTypeRef for NavProperty<T> {
124    fn id(&self) -> &ODataId {
125        match self {
126            Self::Expanded(v) => v.0.id(),
127            Self::Reference(r) => &r.odata_id,
128        }
129    }
130
131    fn etag(&self) -> Option<&ODataETag> {
132        match self {
133            Self::Expanded(v) => v.0.etag(),
134            Self::Reference(_) => None,
135        }
136    }
137}
138
139impl<C, R, T: Creatable<C, R>> Creatable<C, R> for NavProperty<T>
140where
141    C: Sync + Send + Sized + Serialize,
142    R: Sync + Send + Sized + for<'de> Deserialize<'de>,
143{
144}
145impl<U, T: Updatable<U>> Updatable<U> for NavProperty<T> where U: Sync + Send + Sized + Serialize {}
146impl<T: Deletable> Deletable for NavProperty<T> {}
147impl<T: Expandable> Expandable for NavProperty<T> {}
148
149impl<T: EntityTypeRef> NavProperty<T> {
150    /// Create a navigation property with a reference using the `OData`
151    /// identifier.
152    #[must_use]
153    pub const fn new_reference(odata_id: ODataId) -> Self {
154        Self::Reference(Reference { odata_id })
155    }
156
157    /// Downcast to descendant type `D`.
158    #[must_use]
159    pub fn downcast<D: EntityTypeRef>(&self) -> NavProperty<D> {
160        NavProperty::<D>::new_reference(self.id().clone())
161    }
162}
163
164impl<T: EntityTypeRef> NavProperty<T> {
165    /// Extract the identifier from a navigation property.
166    #[must_use]
167    pub fn id(&self) -> &ODataId {
168        match self {
169            Self::Reference(v) => &v.odata_id,
170            Self::Expanded(v) => v.0.id(),
171        }
172    }
173}
174
175impl<T: EntityTypeRef + Sized + for<'a> Deserialize<'a> + 'static + Send + Sync> NavProperty<T> {
176    /// Get the property value.
177    ///
178    /// # Errors
179    ///
180    /// If the navigation property is already expanded then no error is returned.
181    ///
182    /// If the navigation is a reference then a BMC error may be returned if
183    /// retrieval of the entity fails.
184    pub async fn get<B: Bmc>(&self, bmc: &B) -> Result<Arc<T>, B::Error> {
185        match self {
186            Self::Expanded(v) => Ok(v.0.clone()),
187            Self::Reference(_) => bmc.get::<T>(self.id()).await,
188        }
189    }
190
191    /// Filter the property value using the provided query.
192    ///
193    /// # Errors
194    ///
195    /// Returns a BMC error if filtering the entity fails.
196    #[allow(missing_docs)]
197    pub async fn filter<B: Bmc>(&self, bmc: &B, query: FilterQuery) -> Result<Arc<T>, B::Error> {
198        bmc.filter::<T>(self.id(), query).await
199    }
200}