Skip to main content

nv_redfish_csdl_compiler/
lib.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//! CSDL/Redfish schema compiler and code generator
17//!
18//! This crate parses EDMX/CSDL documents and compiles them into a
19//! stable intermediate representation, then generates ergonomic Rust
20//! code for Redfish-based APIs.
21//!
22//! At a glance
23//! - Parse: read one or more EDMX documents (`edmx`)
24//! - Compile: resolve types, properties, actions, and annotations into
25//!   `Compiled` (`compiler`); optionally optimize the set (`optimizer`)
26//! - Generate: produce Rust modules and types (`generator`)
27//!
28//! Key features
29//! - Understands `OData` annotations (permissions, insert/update/delete)
30//! - Handles Redfish specifics (required flags, settings, actions)
31//! - Supports selective compilation via patterns and root singletons
32//! - Deterministic output designed for straightforward codegen
33
34#![deny(
35    clippy::all,
36    clippy::pedantic,
37    clippy::nursery,
38    clippy::suspicious,
39    clippy::complexity,
40    clippy::perf
41)]
42#![deny(
43    clippy::absolute_paths,
44    clippy::todo,
45    clippy::unimplemented,
46    clippy::tests_outside_test_module,
47    clippy::panic,
48    clippy::unwrap_used,
49    clippy::unwrap_in_result,
50    clippy::unused_trait_names,
51    clippy::print_stdout,
52    clippy::print_stderr
53)]
54
55//#![deny(missing_docs)]
56
57/// High-level compiler commands.
58pub mod commands;
59/// Redfish schema compiler.
60pub mod compiler;
61/// Entity Data Model XML definitions.
62pub mod edmx;
63/// Compiler errors.
64pub mod error;
65/// Features manifest.
66pub mod features_manifest;
67/// Redfish code generator.
68pub mod generator;
69/// OData-related utilities.
70pub mod odata;
71/// Type or a collection of a type.
72pub mod one_or_collection;
73/// Optimizer for compiled data structures.
74pub mod optimizer;
75/// Redfish-specific utilities.
76pub mod redfish;
77
78use tagged_types::TaggedType;
79
80#[doc(inline)]
81pub use error::Error;
82#[doc(inline)]
83pub use one_or_collection::OneOrCollection;
84
85/// Whether an attribute is nullable.
86pub type IsNullable = TaggedType<bool, IsNullableTag>;
87#[doc(hidden)]
88#[derive(tagged_types::Tag)]
89#[implement(Copy, Clone)]
90#[transparent(Debug, Deserialize)]
91#[capability(inner_access)]
92pub enum IsNullableTag {}
93
94/// Whether an attribute is required.
95pub type IsRequired = TaggedType<bool, IsRequiredTag>;
96#[doc(hidden)]
97#[derive(tagged_types::Tag)]
98#[implement(Clone, Copy)]
99#[transparent(Display, Debug)]
100#[capability(inner_access)]
101pub enum IsRequiredTag {}
102
103/// Whether an attribute is required when an object is created.
104pub type IsRequiredOnCreate = TaggedType<bool, IsRequiredOnCreateTag>;
105#[doc(hidden)]
106#[derive(tagged_types::Tag)]
107#[implement(Clone, Copy)]
108#[transparent(Display, Debug)]
109#[capability(inner_access)]
110pub enum IsRequiredOnCreateTag {}
111
112/// Whether an attribute appears only in excerpt copies of resource.
113pub type IsExcerptCopyOnly = TaggedType<bool, IsExcerptCopyOnlyTag>;
114#[doc(hidden)]
115#[derive(tagged_types::Tag)]
116#[implement(Clone, Copy)]
117#[transparent(Display, Debug)]
118#[capability(inner_access)]
119pub enum IsExcerptCopyOnlyTag {}
120
121/// Whether a type is abstract.
122pub type IsAbstract = TaggedType<bool, IsAbstractTag>;
123#[doc(hidden)]
124#[derive(tagged_types::Tag)]
125#[implement(Clone, Copy)]
126#[transparent(Display, Debug, Deserialize)]
127#[capability(inner_access)]
128pub enum IsAbstractTag {}
129
130#[cfg(test)]
131mod test {
132    use super::edmx::attribute_values::SimpleIdentifier;
133    use super::edmx::Edmx;
134    use crate::Error;
135    use std::fs;
136    use std::path::Path;
137
138    fn crate_root() -> &'static Path {
139        Path::new(env!("CARGO_MANIFEST_DIR"))
140    }
141
142    #[test]
143    fn test_edmx_element() -> Result<(), Error> {
144        let data = r#"
145           <edmx:Edmx Version="4.0">
146             <edmx:Reference Uri="http://example.com/1.xml"></edmx:Reference>
147             <edmx:Reference Uri="http://example.com/2.xml"></edmx:Reference>
148             <edmx:DataServices></edmx:DataServices>
149           </edmx:Edmx>"#;
150        let edmx: Edmx = Edmx::parse(&data).map_err(|err| Error::Edmx("local".into(), err))?;
151        assert!(edmx.data_services.schemas.is_empty());
152        Ok(())
153    }
154
155    #[test]
156    fn test_trivial_data() -> Result<(), Error> {
157        let data = r#"
158           <edmx:Edmx Version="4.0">
159             <edmx:DataServices>
160               <Schema Namespace="Org.OData.Core.V1" Alias="Core">
161                  <Term Name="Computed" Type="Core.Tag" DefaultValue="true" AppliesTo="Property">
162                    <Annotation Term="Core.Description" String="A value for this property is generated on both insert and update"/>
163                  </Term>
164               </Schema>
165             </edmx:DataServices>
166           </edmx:Edmx>"#;
167        let computed: SimpleIdentifier = "Computed".parse().unwrap();
168        let edmx: Edmx = Edmx::parse(&data).map_err(|err| Error::Edmx("local".into(), err))?;
169        assert_eq!(edmx.data_services.schemas.len(), 1);
170        assert_eq!(edmx.data_services.schemas[0].terms.len(), 1);
171        assert!(edmx.data_services.schemas[0].terms.get(&computed).is_some());
172        if let Some(term) = &edmx.data_services.schemas[0].terms.get(&computed) {
173            assert_eq!(term.ttype.as_ref().unwrap(), &"Core.Tag".parse().unwrap());
174            assert_eq!(term.default_value.as_ref().unwrap(), "true");
175        }
176        Ok(())
177    }
178
179    #[ignore]
180    #[test]
181    fn test_read_odata() -> Result<(), Error> {
182        let fname = crate_root().join("test-data/edmx/odata-4.0.xml");
183        let fname_string = fname.display().to_string();
184        let data = fs::read_to_string(fname).map_err(|err| Error::Io(fname_string.clone(), err))?;
185        let _edmx: Edmx = Edmx::parse(&data).map_err(|err| Error::Edmx(fname_string, err))?;
186        Ok(())
187    }
188
189    #[ignore]
190    #[test]
191    fn test_read_redfish() -> Result<(), Error> {
192        let fname = crate_root().join("test-data/redfish-schema/CoolantConnector_v1.xml");
193        let fname_string = fname.display().to_string();
194        let data = fs::read_to_string(fname).map_err(|err| Error::Io(fname_string.clone(), err))?;
195        let edmx: Edmx = Edmx::parse(&data).map_err(|err| Error::Edmx(fname_string, err))?;
196        assert_eq!(edmx.data_services.schemas.len(), 6);
197        assert_eq!(edmx.data_services.schemas.get(1).unwrap().types.len(), 4);
198        assert_eq!(
199            edmx.data_services
200                .schemas
201                .get(1)
202                .unwrap()
203                .entity_types
204                .len(),
205            1
206        );
207        assert_eq!(
208            edmx.data_services.schemas.get(1).unwrap().annotations.len(),
209            2
210        );
211        Ok(())
212    }
213}