hcl/
ident.rs

1use crate::expr::Variable;
2use crate::{Error, InternalString, Result};
3use hcl_primitives::Ident;
4use serde::{Deserialize, Serialize};
5use std::borrow::{Borrow, Cow};
6use std::fmt;
7use std::ops;
8
9// @NOTE(mohmann): Eventually, the `Identifier` type should be entirely replaced with
10// `hcl_primitives::Ident`, but this is a breaking change because of various intentional API
11// changes.
12
13/// Represents an HCL identifier.
14#[derive(Deserialize, Serialize, Clone, PartialEq, Eq, Hash)]
15#[serde(transparent)]
16pub struct Identifier(Ident);
17
18impl Identifier {
19    /// Create a new `Identifier` after validating that it only contains characters that are
20    /// allowed in HCL identifiers.
21    ///
22    /// See [`Identifier::sanitized`][Identifier::sanitized] for an infallible alternative to this
23    /// function.
24    ///
25    /// # Example
26    ///
27    /// ```
28    /// # use hcl::Identifier;
29    /// assert!(Identifier::new("some_ident").is_ok());
30    /// assert!(Identifier::new("").is_err());
31    /// assert!(Identifier::new("1two3").is_err());
32    /// assert!(Identifier::new("with whitespace").is_err());
33    /// ```
34    ///
35    /// # Errors
36    ///
37    /// If `ident` contains characters that are not allowed in HCL identifiers or if it is empty an
38    /// error will be returned.
39    pub fn new<T>(ident: T) -> Result<Self>
40    where
41        T: Into<InternalString>,
42    {
43        Ident::try_new(ident).map(Identifier).map_err(Error::new)
44    }
45
46    /// Create a new `Identifier` after sanitizing the input if necessary.
47    ///
48    /// If `ident` contains characters that are not allowed in HCL identifiers will be sanitized
49    /// according to the following rules:
50    ///
51    /// - An empty `ident` results in an identifier containing a single underscore.
52    /// - Invalid characters in `ident` will be replaced with underscores.
53    /// - If `ident` starts with a character that is invalid in the first position but would be
54    ///   valid in the rest of an HCL identifier it is prefixed with an underscore.
55    ///
56    /// See [`Identifier::new`][Identifier::new] for a fallible alternative to this function if
57    /// you prefer rejecting invalid identifiers instead of sanitizing them.
58    ///
59    /// # Example
60    ///
61    /// ```
62    /// # use hcl::Identifier;
63    /// assert_eq!(Identifier::sanitized("some_ident").as_str(), "some_ident");
64    /// assert_eq!(Identifier::sanitized("").as_str(), "_");
65    /// assert_eq!(Identifier::sanitized("1two3").as_str(), "_1two3");
66    /// assert_eq!(Identifier::sanitized("with whitespace").as_str(), "with_whitespace");
67    /// ```
68    pub fn sanitized<T>(ident: T) -> Self
69    where
70        T: AsRef<str>,
71    {
72        Identifier(Ident::new_sanitized(ident))
73    }
74
75    /// Create a new `Identifier` without checking if it is valid.
76    ///
77    /// It is the caller's responsibility to ensure that the identifier is valid.
78    ///
79    /// For most use cases [`Identifier::new`][Identifier::new] or
80    /// [`Identifier::sanitized`][Identifier::sanitized] should be preferred.
81    ///
82    /// # Safety
83    ///
84    /// This function is not marked as unsafe because it does not cause undefined behaviour.
85    /// However, attempting to serialize an invalid identifier to HCL will produce invalid output.
86    pub fn unchecked<T>(ident: T) -> Self
87    where
88        T: Into<InternalString>,
89    {
90        Identifier(Ident::new_unchecked(ident))
91    }
92
93    /// Consume `self` and return the wrapped `String`.
94    pub fn into_inner(self) -> String {
95        self.0.into_string()
96    }
97
98    /// Return a reference to the wrapped `str`.
99    pub fn as_str(&self) -> &str {
100        self.0.as_str()
101    }
102}
103
104impl From<Ident> for Identifier {
105    fn from(ident: Ident) -> Self {
106        Identifier(ident)
107    }
108}
109
110impl From<Identifier> for Ident {
111    fn from(ident: Identifier) -> Self {
112        ident.0
113    }
114}
115
116impl From<String> for Identifier {
117    fn from(s: String) -> Self {
118        Identifier::sanitized(s)
119    }
120}
121
122impl From<&str> for Identifier {
123    fn from(s: &str) -> Self {
124        Identifier::sanitized(s)
125    }
126}
127
128impl<'a> From<Cow<'a, str>> for Identifier {
129    fn from(s: Cow<'a, str>) -> Self {
130        Identifier::sanitized(s)
131    }
132}
133
134impl From<Variable> for Identifier {
135    fn from(variable: Variable) -> Self {
136        variable.into_inner()
137    }
138}
139
140impl fmt::Debug for Identifier {
141    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
142        write!(f, "Identifier({self})")
143    }
144}
145
146impl fmt::Display for Identifier {
147    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
148        f.write_str(self)
149    }
150}
151
152impl ops::Deref for Identifier {
153    type Target = str;
154
155    fn deref(&self) -> &Self::Target {
156        self.as_str()
157    }
158}
159
160impl AsRef<str> for Identifier {
161    fn as_ref(&self) -> &str {
162        self.as_str()
163    }
164}
165
166impl Borrow<str> for Identifier {
167    fn borrow(&self) -> &str {
168        self.as_str()
169    }
170}