Skip to main content

nv_redfish_csdl_compiler/edmx/
attribute_values.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//! Types defined in 17 Attribute Values
17
18use crate::edmx::QualifiedTypeName;
19use crate::OneOrCollection;
20use serde::de::Error as DeError;
21use serde::de::Visitor;
22use serde::Deserialize;
23use serde::Deserializer;
24use std::fmt::Display;
25use std::fmt::Formatter;
26use std::fmt::Result as FmtResult;
27use std::str::FromStr;
28
29#[derive(Debug)]
30pub enum Error {
31    InvalidSimpleIdentifier(String),
32    InvalidQualifiedIdentifier(String),
33}
34
35impl Display for Error {
36    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
37        match self {
38            Self::InvalidSimpleIdentifier(id) => write!(f, "invalid simple identifier {id}"),
39            Self::InvalidQualifiedIdentifier(id) => write!(f, "invalid qualified identifier {id}"),
40        }
41    }
42}
43
44/// 17.1 `Namespace`
45#[derive(Clone, Debug, PartialEq, Eq, Hash)]
46pub struct Namespace {
47    pub ids: Vec<SimpleIdentifier>,
48}
49
50impl Namespace {
51    #[must_use]
52    pub fn is_edm(&self) -> bool {
53        self.ids.len() == 1 && self.ids[0].inner() == "Edm"
54    }
55}
56
57impl FromStr for Namespace {
58    type Err = Error;
59    fn from_str(s: &str) -> Result<Self, Self::Err> {
60        Ok(Self {
61            ids: s
62                .split('.')
63                .map(SimpleIdentifier::from_str)
64                .collect::<Result<Vec<_>, _>>()?,
65        })
66    }
67}
68
69impl Display for Namespace {
70    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
71        let mut iter = self.ids.iter();
72        if let Some(v) = iter.next() {
73            v.fmt(f)?;
74        }
75        for v in iter {
76            ".".fmt(f)?;
77            v.fmt(f)?;
78        }
79        Ok(())
80    }
81}
82
83impl<'de> Deserialize<'de> for Namespace {
84    fn deserialize<D: Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
85        struct NsVisitor {}
86        impl Visitor<'_> for NsVisitor {
87            type Value = Namespace;
88
89            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> FmtResult {
90                formatter.write_str("Namespace string")
91            }
92            fn visit_str<E: DeError>(self, value: &str) -> Result<Self::Value, E> {
93                value.parse().map_err(DeError::custom)
94            }
95        }
96
97        de.deserialize_string(NsVisitor {})
98    }
99}
100
101/// 17.2 `SimpleIdentifier`
102#[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
103pub struct SimpleIdentifier(String);
104
105impl SimpleIdentifier {
106    #[must_use]
107    pub const fn inner(&self) -> &String {
108        &self.0
109    }
110}
111
112impl Display for SimpleIdentifier {
113    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
114        self.0.fmt(f)
115    }
116}
117
118impl AsRef<str> for SimpleIdentifier {
119    fn as_ref(&self) -> &str {
120        &self.0
121    }
122}
123
124impl FromStr for SimpleIdentifier {
125    type Err = Error;
126    fn from_str(s: &str) -> Result<Self, Self::Err> {
127        let mut chars = s.chars();
128
129        // Normative: starts with a letter or underscore, followed by
130        // at most 127 letters, underscores or digits.
131        //
132        // Implementation: we don't check max length.
133        chars
134            .next()
135            .and_then(|first| {
136                if first.is_alphabetic() || first == '_' {
137                    Some(())
138                } else {
139                    None
140                }
141            })
142            .ok_or_else(|| Error::InvalidSimpleIdentifier(s.into()))?;
143
144        if chars.any(|c| !c.is_alphanumeric() && c != '_') {
145            Err(Error::InvalidSimpleIdentifier(s.into()))
146        } else {
147            Ok(Self(s.into()))
148        }
149    }
150}
151
152impl<'de> Deserialize<'de> for SimpleIdentifier {
153    fn deserialize<D: Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
154        struct SiVisitor {}
155        impl Visitor<'_> for SiVisitor {
156            type Value = SimpleIdentifier;
157
158            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> FmtResult {
159                formatter.write_str("SimpleIdentifier string")
160            }
161            fn visit_str<E: DeError>(self, value: &str) -> Result<Self::Value, E> {
162                value.parse().map_err(DeError::custom)
163            }
164        }
165
166        de.deserialize_string(SiVisitor {})
167    }
168}
169
170/// 17.3 `QualifiedName`
171#[derive(Debug, PartialEq, Eq, Hash)]
172pub struct QualifiedName {
173    pub namespace: Namespace,
174    pub name: SimpleIdentifier,
175}
176
177impl FromStr for QualifiedName {
178    type Err = Error;
179    fn from_str(s: &str) -> Result<Self, Self::Err> {
180        let mut ids = s
181            .split('.')
182            .map(SimpleIdentifier::from_str)
183            .collect::<Result<Vec<_>, _>>()
184            .map_err(|_| Error::InvalidQualifiedIdentifier(s.into()))?;
185        let name = ids
186            .pop()
187            .ok_or_else(|| Error::InvalidQualifiedIdentifier(s.into()))?;
188        Ok(Self {
189            namespace: Namespace { ids },
190            name,
191        })
192    }
193}
194
195impl<'de> Deserialize<'de> for QualifiedName {
196    fn deserialize<D: Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
197        struct QnVisitor {}
198        impl Visitor<'_> for QnVisitor {
199            type Value = QualifiedName;
200
201            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> FmtResult {
202                formatter.write_str("QualifiedName string")
203            }
204            fn visit_str<E: DeError>(self, value: &str) -> Result<Self::Value, E> {
205                value.parse().map_err(DeError::custom)
206            }
207        }
208
209        de.deserialize_string(QnVisitor {})
210    }
211}
212
213/// 17.4 `TypeName`
214pub type TypeName = OneOrCollection<QualifiedTypeName>;
215
216impl TypeName {
217    #[must_use]
218    pub const fn qualified_type_name(&self) -> &QualifiedTypeName {
219        self.inner()
220    }
221}
222
223impl FromStr for TypeName {
224    type Err = Error;
225    fn from_str(s: &str) -> Result<Self, Self::Err> {
226        const COLLECTION_PREFIX: &str = "Collection(";
227        const COLLECTION_SUFFIX: &str = ")";
228        if s.starts_with(COLLECTION_PREFIX) && s.ends_with(COLLECTION_SUFFIX) {
229            let qtype = s[COLLECTION_PREFIX.len()..s.len() - COLLECTION_SUFFIX.len()].parse()?;
230            Ok(Self::Collection(qtype))
231        } else {
232            Ok(Self::One(s.parse()?))
233        }
234    }
235}
236
237impl<'de> Deserialize<'de> for TypeName {
238    fn deserialize<D: Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
239        struct QnVisitor {}
240        impl Visitor<'_> for QnVisitor {
241            type Value = TypeName;
242
243            fn expecting(&self, formatter: &mut Formatter) -> FmtResult {
244                formatter.write_str("property type string")
245            }
246
247            fn visit_str<E: DeError>(self, value: &str) -> Result<Self::Value, E> {
248                value.parse().map_err(DeError::custom)
249            }
250        }
251
252        de.deserialize_string(QnVisitor {})
253    }
254}
255
256#[cfg(test)]
257mod tests {
258    use super::*;
259    use serde_json::from_str as json_from_str;
260
261    #[test]
262    fn test_namespace_valid() {
263        // According to 17.1 Namespace, it's dot-separated SimpleIdentifiers
264        let valid_cases = vec![
265            "Namespace",
266            "My.Namespace",
267            "My.Complex.Namespace",
268            "Edm", // Special case should work and be identified as EDM
269        ];
270
271        for case in valid_cases {
272            let ns = Namespace::from_str(case);
273            assert!(ns.is_ok(), "Failed to parse valid Namespace: {}", case);
274
275            // Verify the correct number of identifiers are parsed
276            let ns = ns.unwrap();
277            let expected_count = case.chars().filter(|c| *c == '.').count() + 1;
278            assert_eq!(ns.ids.len(), expected_count);
279        }
280    }
281
282    #[test]
283    fn test_namespace_invalid() {
284        let invalid_cases = vec![
285            "Invalid.123Name", // Invalid SimpleIdentifier
286            "Namespace.",      // Trailing dot
287            ".Namespace",      // Leading dot
288            "Namespace..Name", // Double dot
289            "",                // Empty string
290        ];
291
292        for case in invalid_cases {
293            assert!(
294                Namespace::from_str(case).is_err(),
295                "Should reject invalid Namespace: {}",
296                case
297            );
298        }
299    }
300
301    #[test]
302    fn test_namespace_is_edm() {
303        let edm = Namespace::from_str("Edm").unwrap();
304        assert!(edm.is_edm());
305
306        let not_edm = vec![
307            Namespace::from_str("NotEdm").unwrap(),
308            Namespace::from_str("Edm.Something").unwrap(),
309            Namespace::from_str("Something.Edm").unwrap(),
310        ];
311
312        for ns in not_edm {
313            assert!(!ns.is_edm());
314        }
315    }
316
317    #[test]
318    fn test_namespace_display() {
319        let test_cases = vec![
320            ("SingleNamespace", "SingleNamespace"),
321            ("My.Namespace", "My.Namespace"),
322            ("Complex.Name.Space", "Complex.Name.Space"),
323        ];
324
325        for (input, expected) in test_cases {
326            let ns = Namespace::from_str(input).unwrap();
327            assert_eq!(ns.to_string(), expected);
328        }
329    }
330
331    #[test]
332    fn test_namespace_deserialize() {
333        let json = r#""My.Valid.Namespace""#;
334        let ns: Namespace = json_from_str(json).expect("Should deserialize valid Namespace");
335        assert_eq!(ns.ids.len(), 3);
336
337        let json_invalid = r#""Invalid..Namespace""#;
338        let result: Result<Namespace, _> = json_from_str(json_invalid);
339        assert!(result.is_err());
340    }
341
342    #[test]
343    fn test_simple_identifier_valid() {
344        // According to 17.2 SimpleIdentifier, it must start with a letter or underscore
345        // and contains only alphanumeric characters and underscores
346        let valid_cases = vec![
347            "Name",
348            "name",
349            "_name",
350            "Name123",
351            "Name_with_underscores",
352            "a", // Single letter is valid
353        ];
354
355        for case in valid_cases {
356            assert!(
357                SimpleIdentifier::from_str(case).is_ok(),
358                "Failed to parse valid SimpleIdentifier: {}",
359                case
360            );
361        }
362    }
363
364    #[test]
365    fn test_simple_identifier_invalid() {
366        // Invalid cases: starting with digit, containing special characters
367        let invalid_cases = vec![
368            "123Name",           // Starts with digit
369            "Name-with-hyphens", // Contains hyphens
370            "Name.with.dots",    // Contains dots
371            "Name with spaces",  // Contains spaces
372            "",                  // Empty string
373            "$Name",             // Starts with special character
374        ];
375
376        for case in invalid_cases {
377            assert!(
378                SimpleIdentifier::from_str(case).is_err(),
379                "Should reject invalid SimpleIdentifier: {}",
380                case
381            );
382        }
383    }
384
385    #[test]
386    fn test_simple_identifier_display() {
387        let id = SimpleIdentifier::from_str("TestId").unwrap();
388        assert_eq!(id.to_string(), "TestId");
389    }
390
391    #[test]
392    fn test_simple_identifier_deserialize() {
393        // Test JSON deserialization
394        let json = r#""ValidName""#;
395        let id: SimpleIdentifier =
396            json_from_str(json).expect("Should deserialize valid SimpleIdentifier");
397        assert_eq!(id.inner(), "ValidName");
398
399        let json_invalid = r#""Invalid-Name""#;
400        let result: Result<SimpleIdentifier, _> = json_from_str(json_invalid);
401        assert!(result.is_err());
402    }
403
404    #[test]
405    fn test_qualified_name_valid() {
406        // According to 17.3 QualifiedName, it's a Namespace + SimpleIdentifier
407        let valid_cases = vec![
408            "Namespace.Name",
409            "My.Namespace.Name",
410            "Complex.Namespace.Structure.Name",
411        ];
412
413        for case in valid_cases {
414            let qn = QualifiedName::from_str(case);
415            assert!(qn.is_ok(), "Failed to parse valid QualifiedName: {}", case);
416
417            // Verify the name is the last part
418            let qn = qn.unwrap();
419            let parts: Vec<&str> = case.split('.').collect();
420            assert_eq!(qn.name.to_string(), *parts.last().unwrap());
421
422            // Verify namespace has correct number of parts
423            assert_eq!(qn.namespace.ids.len(), parts.len() - 1);
424        }
425    }
426
427    #[test]
428    fn test_qualified_name_invalid() {
429        let invalid_cases = vec![
430            "Invalid.123Name",      // Invalid SimpleIdentifier (starts with digit)
431            "Name-with-hyphens",    // Invalid SimpleIdentifier (contains hyphen)
432            "Namespace.",           // Trailing dot (empty SimpleIdentifier)
433            ".Namespace",           // Leading dot (empty SimpleIdentifier)
434            "Namespace..Name",      // Double dot (empty SimpleIdentifier)
435            "",                     // Empty string
436            "Name with spaces",     // Invalid SimpleIdentifier (contains space)
437            "Name.with.123invalid", // Invalid SimpleIdentifier in namespace
438        ];
439
440        for case in invalid_cases {
441            assert!(
442                QualifiedName::from_str(case).is_err(),
443                "Should reject invalid QualifiedName: {}",
444                case
445            );
446        }
447    }
448
449    #[test]
450    fn test_qualified_name_deserialize() {
451        let json = r#""My.Valid.Namespace.Name""#;
452        let qn: QualifiedName =
453            json_from_str(json).expect("Should deserialize valid QualifiedName");
454        assert_eq!(qn.name.to_string(), "Name");
455        assert_eq!(qn.namespace.ids.len(), 3);
456
457        let json_invalid = r#""Invalid..Name""#; // Double dot - invalid
458        let result: Result<QualifiedName, _> = json_from_str(json_invalid);
459        assert!(result.is_err());
460    }
461
462    #[test]
463    fn test_type_name_valid() {
464        // According to 17.4 TypeName can be a qualified name or Collection(qualified name)
465        let valid_simple_cases = vec!["Edm.String", "My.Namespace.Type"];
466
467        let valid_collection_cases =
468            vec!["Collection(Edm.String)", "Collection(My.Namespace.Type)"];
469
470        // Test simple type names
471        for case in valid_simple_cases {
472            let tn = TypeName::from_str(case);
473            assert!(tn.is_ok(), "Failed to parse valid TypeName: {}", case);
474
475            assert!(
476                matches!(tn.unwrap(), TypeName::One(_)),
477                "Simple TypeName parsed as Collection: {}",
478                case
479            );
480        }
481
482        // Test collection type names
483        for case in valid_collection_cases {
484            let tn = TypeName::from_str(case);
485            assert!(
486                tn.is_ok(),
487                "Failed to parse valid Collection TypeName: {}",
488                case
489            );
490
491            assert!(
492                matches!(tn.unwrap(), TypeName::Collection(_)),
493                "Collection TypeName parsed as simple: {}",
494                case
495            );
496        }
497    }
498
499    #[test]
500    fn test_type_name_invalid() {
501        let invalid_cases = vec![
502            "Collection()",            // Empty collection
503            "Collection(Edm/Invalid)", // Invalid qualified name
504            "Collection(Edm.String",   // Missing closing parenthesis
505            "CollectionEdm.String)",   // Invalid collection syntax
506            "Collection Edm.String",   // Space instead of parenthesis
507        ];
508
509        for case in invalid_cases {
510            assert!(
511                TypeName::from_str(case).is_err(),
512                "Should reject invalid TypeName: {}",
513                case
514            );
515        }
516    }
517
518    #[test]
519    fn test_type_name_deserialize() {
520        let simple_json = r#""Edm.String""#;
521        let simple: TypeName =
522            json_from_str(simple_json).expect("Should deserialize valid TypeName");
523
524        assert!(
525            matches!(simple, TypeName::One(_)),
526            "Simple TypeName deserialized as Collection"
527        );
528
529        let collection_json = r#""Collection(Edm.String)""#;
530        let collection: TypeName =
531            json_from_str(collection_json).expect("Should deserialize valid Collection TypeName");
532
533        assert!(
534            matches!(collection, TypeName::Collection(_)),
535            "Collection TypeName deserialized as simple"
536        );
537    }
538
539    // Test error display implementations
540    #[test]
541    fn test_error_display() {
542        let simple_id_error = Error::InvalidSimpleIdentifier("123invalid".to_string());
543        let qualified_id_error =
544            Error::InvalidQualifiedIdentifier("invalid..qualified".to_string());
545
546        assert_eq!(
547            simple_id_error.to_string(),
548            "invalid simple identifier 123invalid"
549        );
550        assert_eq!(
551            qualified_id_error.to_string(),
552            "invalid qualified identifier invalid..qualified"
553        );
554    }
555}