proof_of_sql_parser/
resource_id.rs1use crate::{impl_serde_from_str, sql::ResourceIdParser, Identifier, ParseError, ParseResult};
3use alloc::{
4 format,
5 string::{String, ToString},
6 vec::Vec,
7};
8use core::{
9 fmt::{self, Display},
10 str::FromStr,
11};
12use sqlparser::ast::Ident;
13
14#[derive(Debug, PartialEq, Eq, Clone, Hash, Copy)]
16pub struct ResourceId {
17 schema: Identifier,
18 object_name: Identifier,
19}
20
21impl ResourceId {
22 #[must_use]
24 pub fn new(schema: Identifier, object_name: Identifier) -> Self {
25 Self {
26 schema,
27 object_name,
28 }
29 }
30
31 pub fn try_new(schema: &str, object_name: &str) -> ParseResult<Self> {
39 let schema = Identifier::try_new(schema)?;
40 let object_name = Identifier::try_new(object_name)?;
41
42 Ok(ResourceId {
43 schema,
44 object_name,
45 })
46 }
47
48 #[must_use]
50 pub fn schema(&self) -> Identifier {
51 self.schema
52 }
53
54 #[must_use]
56 pub fn object_name(&self) -> Identifier {
57 self.object_name
58 }
59
60 #[must_use]
72 pub fn storage_format(&self) -> String {
73 let ResourceId {
74 schema,
75 object_name,
76 } = self;
77
78 let schema = schema.name().to_string().to_uppercase();
79 let object_name = object_name.name().to_string().to_uppercase();
80
81 format!("{schema}:{object_name}")
82 }
83}
84
85impl Display for ResourceId {
86 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
87 let ResourceId {
88 schema,
89 object_name,
90 } = self;
91
92 formatter.write_fmt(format_args!("{schema}.{object_name}"))
93 }
94}
95
96impl FromStr for ResourceId {
97 type Err = ParseError;
98
99 fn from_str(string: &str) -> ParseResult<Self> {
100 let (schema, object_name) = ResourceIdParser::new().parse(string).map_err(|e| {
101 ParseError::ResourceIdParseError {
102 error: format!("{e:?}"),
103 }
104 })?;
105
106 Ok(ResourceId {
108 schema: Identifier::new(schema),
109 object_name: Identifier::new(object_name),
110 })
111 }
112}
113impl_serde_from_str!(ResourceId);
114
115impl TryFrom<Vec<Ident>> for ResourceId {
116 type Error = ParseError;
117
118 fn try_from(identifiers: Vec<Ident>) -> ParseResult<Self> {
119 if identifiers.len() != 2 {
120 return Err(ParseError::ResourceIdParseError {
121 error: "Expected exactly two identifiers for ResourceId".to_string(),
122 });
123 }
124
125 let schema = Identifier::try_from(identifiers[0].clone())?;
126 let object_name = Identifier::try_from(identifiers[1].clone())?;
127 Ok(ResourceId::new(schema, object_name))
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134
135 #[test]
136 fn try_new_resource_id() {
137 let resource_id =
138 ResourceId::try_new("G00d_identifier", "_can_start_with_underscore").unwrap();
139 assert_eq!(resource_id.schema().name(), "g00d_identifier");
140 assert_eq!(
141 resource_id.object_name().name(),
142 "_can_start_with_underscore"
143 );
144 }
145
146 #[test]
147 fn resource_id_from_str() {
148 let resource_id =
149 ResourceId::from_str("G00d_identifier._can_start_with_underscore").unwrap();
150 assert_eq!(resource_id.schema().name(), "g00d_identifier");
151 assert_eq!(
152 resource_id.object_name().name(),
153 "_can_start_with_underscore"
154 );
155 }
156
157 #[test]
158 fn try_new_resource_id_with_additional_characters_fails() {
159 assert!(ResourceId::try_new("GOOD_IDENTIFIER", "GOOD_IDENTIFIER.").is_err());
160 assert!(ResourceId::try_new("GOOD_IDENTIFIER.", "GOOD_IDENTIFIER").is_err());
161 assert!(ResourceId::try_new("BAD$IDENTIFIER", "GOOD_IDENTIFIER").is_err());
162 assert!(ResourceId::try_new("GOOD_IDENTIFIER", "BAD IDENTIFIER").is_err());
163 }
164
165 #[test]
166 fn we_can_parse_valid_resource_ids_with_white_spaces_at_beginning_or_end() {
167 let resource_id =
168 ResourceId::from_str(" GOOD_IDENTIFIER._can_start_with_underscore ").unwrap();
169 assert_eq!(resource_id.schema().name(), "good_identifier");
170 assert_eq!(
171 resource_id.object_name().name(),
172 "_can_start_with_underscore"
173 );
174
175 let resource_id = ResourceId::try_new(
176 " GOOD_IDENTIFIER ",
177 " _can_start_with_underscore ",
178 )
179 .unwrap();
180 assert_eq!(resource_id.schema().name(), "good_identifier");
181 assert_eq!(
182 resource_id.object_name().name(),
183 "_can_start_with_underscore"
184 );
185 }
186
187 #[test]
188 fn display_resource_id() {
189 assert_eq!(
190 ResourceId::try_new("GOOD_IDENTIFIER", "good_identifier")
191 .unwrap()
192 .to_string(),
193 "good_identifier.good_identifier"
194 );
195
196 assert_eq!(
197 ResourceId::try_new("g00d_identifier", "_can_Start_with_underscore")
198 .unwrap()
199 .to_string(),
200 "g00d_identifier._can_start_with_underscore"
201 );
202 }
203
204 #[test]
205 fn resource_id_storage_format() {
206 assert_eq!(
207 ResourceId::try_new("GOOD_IDENTIFIER", "good_identifier")
208 .unwrap()
209 .storage_format(),
210 "GOOD_IDENTIFIER:GOOD_IDENTIFIER"
211 );
212 assert_eq!(
213 ResourceId::try_new("g00d_identifier", "_can_Start_with_underscore")
214 .unwrap()
215 .storage_format(),
216 "G00D_IDENTIFIER:_CAN_START_WITH_UNDERSCORE"
217 );
218 }
219
220 #[test]
221 fn invalid_resource_id_parsing_fails() {
222 assert!(ResourceId::from_str("GOOD_IDENTIFIER").is_err());
223 assert!(ResourceId::from_str("GOOD_IDENTIFIER:GOOD_IDENTIFIER").is_err());
224 assert!(ResourceId::from_str("BAD$IDENTIFIER.GOOD_IDENTIFIER").is_err());
225 assert!(ResourceId::from_str("GOOD_IDENTIFIER.BAD_IDENT!FIER").is_err());
226 assert!(ResourceId::from_str("GOOD_IDENTIFIER.BAD IDENTIFIER").is_err());
227 assert!(ResourceId::from_str("GOOD_IDENTIFIER.13AD_IDENTIFIER").is_err());
228 assert!(ResourceId::from_str("13AD_IDENTIFIER.GOOD_IDENTIFIER").is_err());
229 assert!(ResourceId::from_str("GOOD_IDENTIFIER.").is_err());
230 assert!(ResourceId::from_str(".GOOD_IDENTIFIER").is_err());
231 assert!(ResourceId::from_str(".").is_err());
232 assert!(ResourceId::from_str("GOOD_IDENTIFIER").is_err());
233 assert!(ResourceId::from_str("GOOD_IDENTIFIER.GOOD_IDENTIFIER.GOOD_IDENTIFIER").is_err());
234 }
235
236 #[test]
237 fn resource_id_serializes_to_string() {
238 let resource_id = ResourceId::try_new("GOOD_IDENTIFIER", "good_identifier").unwrap();
239 let serialized = serde_json::to_string(&resource_id).unwrap();
240 assert_eq!(serialized, r#""good_identifier.good_identifier""#);
241 }
242
243 #[test]
244 fn resource_id_deserializes_from_string() {
245 let resource_id = ResourceId::try_new("GOOD_IDENTIFIER", "good_identifier").unwrap();
246 let deserialized: ResourceId =
247 serde_json::from_str(r#""good_identifier.good_identifier""#).unwrap();
248 assert_eq!(resource_id, deserialized);
249 }
250
251 #[test]
252 fn resource_id_fails_to_deserialize_with_invalid_identifier() {
253 let deserialized: Result<ResourceId, _> =
254 serde_json::from_str(r#""good_identifier.bad!identifier"#);
255 assert!(deserialized.is_err());
256 }
257
258 #[test]
259 fn test_try_from_vec_ident() {
260 let identifiers = alloc::vec![Ident::new("schema_name"), Ident::new("object_name")];
261 let resource_id = ResourceId::try_from(identifiers).unwrap();
262 assert_eq!(resource_id.schema().name(), "schema_name");
263 assert_eq!(resource_id.object_name().name(), "object_name");
264
265 let invalid_identifiers = alloc::vec![Ident::new("only_one_ident")];
266 assert!(ResourceId::try_from(invalid_identifiers).is_err());
267 }
268}