cedar_policy_core/from_normalized_str.rs
1/*
2 * Copyright Cedar Contributors
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 * https://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
17use crate::parser::err::{ParseError, ParseErrors, ToASTError, ToASTErrorKind};
18use crate::parser::Loc;
19use std::fmt::Display;
20use std::str::FromStr;
21
22/// Trait for parsing "normalized" strings only, throwing an error if a
23/// non-normalized string is encountered. See docs on the
24/// [`FromNormalizedStr::from_normalized_str`] trait function.
25pub trait FromNormalizedStr: FromStr<Err = ParseErrors> + Display {
26 /// Create a `Self` by parsing a string, which is required to be normalized.
27 /// That is, the input is required to roundtrip with the `Display` impl on `Self`:
28 /// `Self::from_normalized_str(x).to_string() == x` must hold.
29 ///
30 /// In Cedar's context, that means that `from_normalized_str()` will not
31 /// accept strings with spurious whitespace (e.g. `A :: B :: C::"foo"`),
32 /// Cedar comments (e.g. `A::B::"bar" // comment`), etc. See
33 /// [RFC 9](https://github.com/cedar-policy/rfcs/blob/main/text/0009-disallow-whitespace-in-entityuid.md)
34 /// for more details and justification.
35 ///
36 /// For the version that accepts whitespace and Cedar comments, use the
37 /// actual `FromStr` implementations.
38 fn from_normalized_str(s: &str) -> Result<Self, ParseErrors> {
39 let parsed = Self::from_str(s)?;
40 let normalized_src = parsed.to_string();
41 if normalized_src == s {
42 // the normalized representation is indeed the one that was provided
43 Ok(parsed)
44 } else {
45 let diff_byte = s
46 .bytes()
47 .zip(normalized_src.bytes())
48 .enumerate()
49 .find(|(_, (b0, b1))| b0 != b1)
50 .map(|(idx, _)| idx)
51 .unwrap_or_else(|| s.len().min(normalized_src.len()));
52
53 Err(ParseErrors::singleton(ParseError::ToAST(ToASTError::new(
54 ToASTErrorKind::NonNormalizedString {
55 kind: Self::describe_self(),
56 src: s.to_string(),
57 normalized_src,
58 },
59 Loc::new(diff_byte, s.into()),
60 ))))
61 }
62 }
63
64 /// Short string description of the `Self` type, to be used in error messages.
65 /// What are we trying to parse?
66 fn describe_self() -> &'static str;
67}