Skip to main content

nv_redfish_csdl_compiler/compiler/
properties.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
16use crate::compiler::ensure_type;
17use crate::compiler::redfish::RedfishProperty;
18use crate::compiler::Compiled;
19use crate::compiler::ComplexType;
20use crate::compiler::Context;
21use crate::compiler::EntityType;
22use crate::compiler::Error;
23use crate::compiler::MapType;
24use crate::compiler::MustHaveId;
25use crate::compiler::OData;
26use crate::compiler::QualifiedName;
27use crate::compiler::RigidArraySupport;
28use crate::compiler::Stack;
29use crate::compiler::TypeClass;
30use crate::edmx::property::Property as EdmxProperty;
31use crate::edmx::property::PropertyAttrs;
32use crate::edmx::NavigationProperty as EdmxNavigationProperty;
33use crate::edmx::PropertyName;
34use crate::odata::annotations::Permissions;
35use crate::IsNullable;
36use crate::OneOrCollection;
37
38/// Combined structural and navigation properties.
39#[derive(Default, Debug)]
40pub struct Properties<'a> {
41    /// Structural properties.
42    pub properties: Vec<Property<'a>>,
43    /// Navigation properties.
44    pub nav_properties: Vec<NavProperty<'a>>,
45}
46
47impl<'a> Properties<'a> {
48    /// Compile an entity/complex type's properties (structural and
49    /// navigation), along with their type dependencies.
50    ///
51    /// # Errors
52    ///
53    /// Returns an error if a property or its dependency fails to compile.
54    pub fn compile(
55        qtype: QualifiedName<'_>,
56        props: &'a [EdmxProperty],
57        ctx: &Context<'a>,
58        stack: Stack<'a, '_>,
59    ) -> Result<(Compiled<'a>, Self), Error<'a>> {
60        props
61            .iter()
62            .try_fold((stack, Properties::default()), |(stack, mut p), sp| {
63                let stack = match &sp.attrs {
64                    PropertyAttrs::StructuralProperty(v) => {
65                        let (compiled, typeinfo) = ensure_type(
66                            ctx.schema_index
67                                .find_child_type(v.ptype.qualified_type_name().into()),
68                            ctx,
69                            &stack,
70                        )
71                        .map_err(Box::new)
72                        .map_err(|e| Error::Property(&sp.name, e))?;
73                        p.properties.push(Property {
74                            name: &v.name,
75                            ptype: v.ptype.as_ref().map(|t| (typeinfo, t.into())),
76                            odata: OData::new(MustHaveId::new(false), v),
77                            redfish: RedfishProperty::new(v),
78                            nullable: v.nullable.unwrap_or(IsNullable::new(true)),
79                            rigid_array_support: RigidArraySupport::new(
80                                ctx.config.rigid_array_filter.matches(qtype, &v.name),
81                            ),
82                        });
83                        stack.merge(compiled)
84                    }
85                    PropertyAttrs::NavigationProperty(v) => {
86                        let compiled = Self::compile_nav_property(&mut p, v, ctx, &stack)
87                            .map_err(Box::new)
88                            .map_err(|e| Error::Property(&sp.name, e))?;
89                        stack.merge(compiled)
90                    }
91                };
92                Ok((stack, p))
93            })
94            .map(|(stack, p)| (stack.done(), p))
95    }
96
97    /// Join properties in reverse order (from parent to child).
98    /// Useful when combining properties collected across inheritance
99    /// from current type and its ancestors.
100    #[must_use]
101    pub fn rev_join(src: Vec<Self>) -> Self {
102        let (properties, nav_properties): (Vec<_>, Vec<_>) = src
103            .into_iter()
104            .map(|v| (v.properties, v.nav_properties))
105            .unzip();
106        Self {
107            properties: properties.into_iter().rev().flatten().collect(),
108            nav_properties: nav_properties.into_iter().rev().flatten().collect(),
109        }
110    }
111
112    /// No properties defined.
113    #[must_use]
114    pub const fn is_empty(&self) -> bool {
115        self.properties.is_empty() && self.nav_properties.is_empty()
116    }
117
118    fn compile_nav_property(
119        p: &mut Self,
120        v: &'a EdmxNavigationProperty,
121        ctx: &Context<'a>,
122        stack: &Stack<'a, '_>,
123    ) -> Result<Compiled<'a>, Error<'a>> {
124        let qname = v.ptype.qualified_type_name().into();
125        let redfish = RedfishProperty::new(v);
126        if ctx.root_set_entities.contains(&qname) || ctx.config.entity_type_filter.matches(&qname) {
127            // Find the deepest available child in the type hierarchy
128            // for the singleton, to target the most recent protocol
129            // version.
130            let (qtype, et) = ctx.schema_index.find_child_entity_type(qname)?;
131            let (ptype, compiled) = {
132                if stack.contains_entity(qtype) {
133                    // Already compiled entity
134                    Ok(Compiled::default())
135                } else {
136                    EntityType::compile(qtype, et, ctx, stack)
137                        .map_err(Box::new)
138                        .map_err(|e| Error::EntityType(qtype, e))
139                }
140                .map(|compiled| (qtype, compiled))
141            }?;
142            let compiled = if let Some(ec) = redfish.excerpt_copy.clone() {
143                compiled.merge(Compiled::new_excerpt_copy(qtype, ec))
144            } else {
145                compiled
146            };
147            p.nav_properties
148                .push(NavProperty::Expandable(NavPropertyExpandable {
149                    name: &v.name,
150                    ptype: v.ptype.as_ref().map(|_| ptype),
151                    odata: OData::new(MustHaveId::new(false), v),
152                    redfish,
153                    nullable: v.nullable.unwrap_or(IsNullable::new(false)),
154                }));
155            Ok(compiled)
156        } else {
157            if redfish.excerpt_copy.is_none() {
158                // Don't add excerpt copy of entities that are not
159                // included in entity pattern.
160                p.nav_properties
161                    .push(NavProperty::Reference(v.ptype.as_ref().map(|_| &v.name)));
162            }
163            Ok(Compiled::default())
164        }
165    }
166}
167
168/// Additional type information used by properties.
169#[derive(Clone, Copy, Debug)]
170pub struct TypeInfo {
171    /// Class of the type.
172    pub class: TypeClass,
173    /// Permissions associated with the type.
174    ///
175    /// In Redfish, type-level permissions are only used for two
176    /// complex types (`Status` and `Condition`) in the `Resource`
177    /// namespace, but supporting this is important as they sit in the
178    /// base class of all Redfish resources.
179    pub permissions: Option<Permissions>,
180}
181
182impl TypeInfo {
183    /// Create simple type info.
184    #[must_use]
185    pub const fn simple_type() -> Self {
186        Self {
187            class: TypeClass::SimpleType,
188            permissions: None,
189        }
190    }
191    /// Create enum type info.
192    #[must_use]
193    pub const fn enum_type() -> Self {
194        Self {
195            class: TypeClass::EnumType,
196            permissions: None,
197        }
198    }
199    /// Create type definition info.
200    #[must_use]
201    pub const fn type_definition() -> Self {
202        Self {
203            class: TypeClass::TypeDefinition,
204            permissions: None,
205        }
206    }
207    /// Complex type info.
208    #[must_use]
209    pub fn complex_type(ct: &ComplexType) -> Self {
210        Self {
211            class: TypeClass::ComplexType,
212            permissions: ct.odata.permissions.or_else(|| {
213                // Consider a complex type read-only if it has no
214                // properties, or all properties are ReadOnly. While
215                // we also track nested type info for complex-typed
216                // properties, folding that recursively requires care
217                // in the optimizer.
218                if ct.odata.additional_properties.is_none_or(|v| {
219                    // Redfish-specific heuristic: treat additional
220                    // properties of `OemActions` complex types as
221                    // read-only; we do this because the schema does not indicate their
222                    // immutability.
223                    !v.into_inner() || ct.name.name.inner().as_str() == "OemActions"
224                }) && (ct.properties.is_empty()
225                    || ct.properties.properties.iter().all(|p| {
226                        p.odata.permissions.is_some_and(|v| v == Permissions::Read)
227                            || *p
228                                .ptype
229                                .map(|v| v.0.permissions.is_some_and(|v| v == Permissions::Read))
230                                .inner()
231                    }))
232                {
233                    Some(Permissions::Read)
234                } else {
235                    None
236                }
237            }),
238        }
239    }
240}
241
242/// Structural property type (one or a collection).
243pub type PropertyType<'a> = OneOrCollection<(TypeInfo, QualifiedName<'a>)>;
244
245impl<'a> PropertyType<'a> {
246    /// Qualified type name of the property.
247    #[must_use]
248    pub const fn name(&self) -> QualifiedName<'a> {
249        self.inner().1
250    }
251}
252
253/// Structural property.
254#[derive(Debug)]
255pub struct Property<'a> {
256    /// Property identifier.
257    pub name: &'a PropertyName,
258    /// Property type (one or collection).
259    pub ptype: PropertyType<'a>,
260    /// Attached `OData` annotations.
261    pub odata: OData<'a>,
262    /// Redfish-specific property annotations.
263    pub redfish: RedfishProperty,
264    /// Whether the property is nullable.
265    pub nullable: IsNullable,
266    /// Redfish specification is not very specific about which
267    /// properties can be rigid and which cannot be. Rigid arrays
268    /// (collections) can contain null in JSON representation. In
269    /// practice only handful of properties used as rigid by BMC
270    /// implementors. This flag defines
271    pub rigid_array_support: RigidArraySupport,
272}
273
274impl<'a> MapType<'a> for Property<'a> {
275    fn map_type<F>(mut self, f: F) -> Self
276    where
277        F: FnOnce(QualifiedName<'a>) -> QualifiedName<'a>,
278    {
279        self.ptype = self.ptype.map(|(typeclass, t)| (typeclass, f(t)));
280        self
281    }
282}
283
284/// Navigation property target type (one or a collection).
285pub type NavPropertyType<'a> = OneOrCollection<QualifiedName<'a>>;
286
287impl<'a> NavPropertyType<'a> {
288    /// Qualified type name of the property.
289    #[must_use]
290    pub const fn name(&self) -> QualifiedName<'a> {
291        *self.inner()
292    }
293}
294
295/// Navigation property, either expandable or reference.
296#[derive(Debug)]
297pub enum NavProperty<'a> {
298    /// Expandable navigation property (with known type).
299    Expandable(NavPropertyExpandable<'a>),
300    /// Reference navigation property (type is left as reference).
301    Reference(OneOrCollection<&'a PropertyName>),
302}
303
304impl<'a> NavProperty<'a> {
305    /// Name of the property regardless of variant.
306    #[must_use]
307    pub const fn name(&'a self) -> &'a PropertyName {
308        match self {
309            Self::Expandable(v) => v.name,
310            Self::Reference(n) => n.inner(),
311        }
312    }
313}
314
315/// Expandable navigation property details.
316#[derive(Debug)]
317pub struct NavPropertyExpandable<'a> {
318    /// Property identifier.
319    pub name: &'a PropertyName,
320    /// Target type (one or collection).
321    pub ptype: NavPropertyType<'a>,
322    /// Attached `OData` annotations.
323    pub odata: OData<'a>,
324    /// Redfish-specific property annotations.
325    pub redfish: RedfishProperty,
326    /// Whether the property is nullable.
327    pub nullable: IsNullable,
328}
329
330impl<'a> MapType<'a> for NavProperty<'a> {
331    fn map_type<F>(self, f: F) -> Self
332    where
333        F: FnOnce(QualifiedName<'a>) -> QualifiedName<'a>,
334    {
335        match self {
336            Self::Expandable(mut exp) => {
337                exp.ptype = exp.ptype.map(f);
338                Self::Expandable(exp)
339            }
340            Self::Reference { .. } => self,
341        }
342    }
343}