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}