nv_redfish_core/odata.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//! OData identifiers used by generated types
17//!
18//! Minimal wrappers for Redfish/OData identifiers used throughout generated code:
19//! - [`ODataId`]: value of `@odata.id`, the canonical resource path (opaque string)
20//! - [`ODataETag`]: value of `@odata.etag`, the HTTP entity tag (opaque string)
21//!
22//! Notes
23//! - These types are intentionally semantic‑unaware; they do not validate content.
24//! - [`ODataId::service_root()`] returns the conventional Redfish service root path.
25//! - Formatting/Display returns the raw underlying string.
26//!
27//! Example
28//! ```rust
29//! use nv_redfish_core::ODataId;
30//!
31//! let root = ODataId::service_root();
32//! assert_eq!(root.to_string(), "/redfish/v1");
33//! ```
34//!
35//! References:
36//! - OASIS OData 4.01 — `@odata.id`, `@odata.etag`
37//! - DMTF Redfish Specification DSP0266 — `https://www.dmtf.org/standards/redfish`
38//!
39
40use core::fmt::Display;
41use core::fmt::Formatter;
42use core::fmt::Result as FmtResult;
43use serde::Deserialize;
44use serde::Serialize;
45
46/// Type for `@odata.id` identifier.
47#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
48#[repr(transparent)]
49pub struct ODataId(String);
50
51impl ODataId {
52 /// Redfish service root id.
53 #[must_use]
54 pub fn service_root() -> Self {
55 Self("/redfish/v1".into())
56 }
57}
58
59impl From<String> for ODataId {
60 fn from(s: String) -> Self {
61 Self(s)
62 }
63}
64
65impl Display for ODataId {
66 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
67 self.0.fmt(f)
68 }
69}
70
71/// Type for `@odata.etag` identifier.
72#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
73#[repr(transparent)]
74pub struct ODataETag(String);
75
76impl From<String> for ODataETag {
77 fn from(value: String) -> Self {
78 Self(value)
79 }
80}
81
82impl Display for ODataETag {
83 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
84 self.0.fmt(f)
85 }
86}
87
88/// Type for retrieving `@odata.type` from a JSON payload.
89pub struct ODataType<'a> {
90 /// Namespace of the data type. For example: `["Chassis", "v1_22_0"]`.
91 pub namespace: Vec<&'a str>,
92 /// Name of the type. For example "Chassis".
93 pub type_name: &'a str,
94}
95
96impl ODataType<'_> {
97 /// Get `@odata.type` from a JSON payload and parse it.
98 #[must_use]
99 pub fn parse_from(v: &serde_json::Value) -> Option<ODataType<'_>> {
100 v.get("@odata.type")
101 .and_then(|v| v.as_str())
102 .and_then(|v| v.starts_with('#').then_some(&v[1..]))
103 .and_then(|v| {
104 let mut all = v.split('.').collect::<Vec<_>>();
105 all.pop().map(|type_name| ODataType {
106 namespace: all,
107 type_name,
108 })
109 })
110 }
111}