1use crate::load::ModuleLoader;
5use crate::model::modules::Module;
6use crate::model::{HasSourceSpan, Span};
7use convert_case::{Case, Casing};
8use lazy_static::lazy_static;
9use regex::Regex;
10use sdml_errors::diagnostics::functions::{
11 identifier_not_preferred_case, invalid_identifier, IdentifierCaseConvention,
12};
13use std::{
14 fmt::{Debug, Display},
15 hash::Hash,
16 str::FromStr,
17};
18use tracing::error;
19
20#[cfg(feature = "serde")]
21use serde::{Deserialize, Serialize};
22
23#[derive(Clone, Debug)]
31#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
32pub struct Identifier {
33 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
34 span: Option<Box<Span>>,
35 value: String,
36}
37
38#[derive(Clone, Debug)]
42#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
43pub struct QualifiedIdentifier {
44 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
45 span: Option<Box<Span>>,
46 module: Identifier,
47 member: Identifier,
48}
49
50#[derive(Clone, Debug)]
54#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
55pub enum IdentifierReference {
56 Identifier(Identifier),
57 QualifiedIdentifier(QualifiedIdentifier),
58}
59
60lazy_static! {
65 static ref IDENTIFIER: Regex =
66 Regex::new(r"^[\p{Lu}\p{Ll}][\p{Lu}\p{Ll}\p{Nd}]*(?:_+[\p{Lu}\p{Ll}\p{Nd}]+)*$").unwrap();
67}
68
69const RESERVED_KEYWORDS: [&str; 19] = [
70 "as",
71 "base",
72 "datatype",
73 "end",
74 "entity",
75 "enum",
76 "event",
77 "group",
78 "identity",
79 "import",
80 "is",
81 "module",
82 "of",
83 "property",
84 "ref",
85 "source",
86 "structure",
87 "union",
88 "unknown",
89];
90const RESERVED_TYPES: [&str; 6] = ["string", "double", "decimal", "integer", "boolean", "iri"];
91const RESERVED_MODULES: [&str; 12] = [
92 "dc", "dc_am", "dc_terms", "dc_types", "iso_3166", "iso_4217", "owl", "rdf", "rdfs", "sdml",
93 "skos", "xsd",
94];
95
96impl Display for Identifier {
99 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100 write!(f, "{}", self.value)
101 }
102}
103
104impl FromStr for Identifier {
105 type Err = crate::error::Error;
106
107 fn from_str(s: &str) -> Result<Self, Self::Err> {
108 if Self::is_valid(s) {
109 Ok(Self {
110 span: None,
111 value: s.to_string(),
112 })
113 } else {
114 error!("Identifier::from_str({s}) is invalid");
115 Err(invalid_identifier(0, None, s).into())
116 }
117 }
118}
119
120impl From<Identifier> for String {
121 fn from(value: Identifier) -> Self {
122 value.value
123 }
124}
125
126impl From<&Identifier> for String {
127 fn from(value: &Identifier) -> Self {
128 value.value.clone()
129 }
130}
131
132impl AsRef<str> for Identifier {
133 fn as_ref(&self) -> &str {
134 self.value.as_str()
135 }
136}
137
138impl PartialEq<str> for Identifier {
139 fn eq(&self, other: &str) -> bool {
140 self.value.as_str() == other
141 }
142}
143
144impl PartialEq for Identifier {
145 fn eq(&self, other: &Self) -> bool {
146 self.value == other.value
147 }
148}
149
150impl Eq for Identifier {}
151
152impl PartialOrd for Identifier {
153 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
154 Some(self.cmp(other))
155 }
156}
157
158impl Ord for Identifier {
159 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
160 self.value.cmp(&other.value)
161 }
162}
163
164impl Hash for Identifier {
165 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
166 self.value.hash(state);
168 }
169}
170
171impl_has_source_span_for!(Identifier);
172
173impl Identifier {
174 pub fn new_unchecked(s: &str) -> Self {
179 Self {
180 span: None,
181 value: s.to_string(),
182 }
183 }
184
185 #[inline(always)]
186 pub fn with_module(&self, module: Identifier) -> QualifiedIdentifier {
187 QualifiedIdentifier::new(module, self.clone())
188 }
189
190 #[inline(always)]
191 pub fn with_member(&self, member: Identifier) -> QualifiedIdentifier {
192 QualifiedIdentifier::new(self.clone(), member)
193 }
194
195 pub fn validate(
200 &self,
201 top: &Module,
202 loader: &impl ModuleLoader,
203 as_case: Option<IdentifierCaseConvention>,
204 ) {
205 if !Self::is_valid(&self.value) {
206 loader
207 .report(&invalid_identifier(
208 top.file_id().copied().unwrap_or_default(),
209 self.span.as_ref().map(|s| s.as_ref().into()),
210 &self.value,
211 ))
212 .unwrap();
213 }
214 if let Some(case) = as_case {
215 if !case.is_valid(self) {
216 loader
217 .report(&identifier_not_preferred_case(
218 top.file_id().copied().unwrap_or_default(),
219 self.source_span().map(|span| span.byte_range()),
220 self,
221 case,
222 ))
223 .unwrap();
224 }
225 }
226 }
227
228 #[inline(always)]
229 pub fn is_valid<S>(s: S) -> bool
230 where
231 S: AsRef<str>,
232 {
233 let s = s.as_ref();
234 IDENTIFIER.is_match(s) && !Self::is_keyword(s)
235 }
236
237 #[inline(always)]
238 pub fn is_keyword<S>(s: S) -> bool
239 where
240 S: AsRef<str>,
241 {
242 RESERVED_KEYWORDS.contains(&s.as_ref())
243 }
244
245 #[inline(always)]
246 pub fn is_type_name<S>(s: S) -> bool
247 where
248 S: AsRef<str>,
249 {
250 RESERVED_TYPES.contains(&s.as_ref())
251 }
252
253 #[inline(always)]
254 pub fn is_library_module_name<S>(s: S) -> bool
255 where
256 S: AsRef<str>,
257 {
258 RESERVED_MODULES.contains(&s.as_ref())
259 }
260
261 #[inline(always)]
262 pub fn eq_with_span(&self, other: &Self) -> bool {
263 self.span == other.span && self.value == other.value
264 }
265
266 #[inline(always)]
267 pub fn to_type_label(&self) -> String {
268 self.value.to_case(Case::Title)
269 }
270
271 #[inline(always)]
272 pub fn to_variant_label(&self) -> String {
273 self.to_type_label()
274 }
275
276 #[inline(always)]
277 pub fn to_member_label(&self) -> String {
278 self.value.to_case(Case::Lower)
279 }
280
281 #[inline(always)]
282 pub fn to_module_label(&self) -> String {
283 self.to_member_label()
284 }
285}
286
287impl Display for QualifiedIdentifier {
290 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
291 write!(f, "{}:{}", self.module, self.member)
292 }
293}
294
295impl From<QualifiedIdentifier> for String {
296 fn from(value: QualifiedIdentifier) -> Self {
297 String::from(&value)
298 }
299}
300
301impl From<&QualifiedIdentifier> for String {
302 fn from(value: &QualifiedIdentifier) -> Self {
303 value.to_string()
304 }
305}
306
307impl From<(Identifier, Identifier)> for QualifiedIdentifier {
308 fn from(value: (Identifier, Identifier)) -> Self {
309 Self::new(value.0, value.1)
310 }
311}
312
313impl FromStr for QualifiedIdentifier {
314 type Err = crate::error::Error;
315
316 fn from_str(s: &str) -> Result<Self, Self::Err> {
317 let parts = s.split(Self::SEPARATOR_STR).collect::<Vec<&str>>();
318 if parts.len() == 2 {
319 Ok(Self::new(
320 Identifier::from_str(parts[0])?,
321 Identifier::from_str(parts[1])?,
322 ))
323 } else {
324 error!("QualifiedIdentifier::from_str({s:?}) is invalid");
325 Err(invalid_identifier(0, None, s).into())
326 }
327 }
328}
329
330impl PartialEq<str> for QualifiedIdentifier {
331 fn eq(&self, other: &str) -> bool {
332 self.to_string().as_str() == other
333 }
334}
335
336impl PartialEq for QualifiedIdentifier {
337 fn eq(&self, other: &Self) -> bool {
338 self.module == other.module && self.member == other.member
339 }
340}
341
342impl Eq for QualifiedIdentifier {}
343
344impl Hash for QualifiedIdentifier {
345 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
346 self.module.hash(state);
348 self.member.hash(state);
349 }
350}
351
352impl_has_source_span_for!(QualifiedIdentifier, span);
353
354impl QualifiedIdentifier {
355 const SEPARATOR_STR: &'static str = ":";
356
357 pub const fn new(module: Identifier, member: Identifier) -> Self {
362 Self {
363 span: None,
364 module,
365 member,
366 }
367 }
368
369 getter!(pub module => Identifier);
374
375 getter!(pub member => Identifier);
376
377 pub fn validate(&self, top: &Module, loader: &impl ModuleLoader) {
382 self.module
383 .validate(top, loader, Some(IdentifierCaseConvention::Module));
384 self.member
385 .validate(top, loader, Some(IdentifierCaseConvention::ImportedMember));
386 }
387
388 pub fn eq_with_span(&self, other: &Self) -> bool {
389 self.span == other.span && self.module == other.module && self.member == other.member
390 }
391}
392
393impl From<IdentifierReference> for String {
396 fn from(value: IdentifierReference) -> Self {
397 String::from(&value)
398 }
399}
400
401impl From<&IdentifierReference> for String {
402 fn from(value: &IdentifierReference) -> Self {
403 match value {
404 IdentifierReference::Identifier(v) => v.to_string(),
405 IdentifierReference::QualifiedIdentifier(v) => v.to_string(),
406 }
407 }
408}
409
410impl FromStr for IdentifierReference {
411 type Err = crate::error::Error;
412
413 fn from_str(s: &str) -> Result<Self, Self::Err> {
414 let parts = s
415 .split(QualifiedIdentifier::SEPARATOR_STR)
416 .collect::<Vec<&str>>();
417 if parts.len() == 1 {
418 Ok(Self::Identifier(Identifier::from_str(parts[0])?))
419 } else if parts.len() == 2 {
420 Ok(Self::QualifiedIdentifier(QualifiedIdentifier::new(
421 Identifier::from_str(parts[0])?,
422 Identifier::from_str(parts[1])?,
423 )))
424 } else {
425 error!("QualifiedIdentifier::from_str({s:?}) is invalid");
426 Err(invalid_identifier(0, None, s).into())
427 }
428 }
429}
430
431impl PartialEq<str> for IdentifierReference {
432 fn eq(&self, other: &str) -> bool {
433 self.to_string().as_str() == other
434 }
435}
436
437impl PartialEq for IdentifierReference {
438 fn eq(&self, other: &Self) -> bool {
439 match (self, other) {
440 (Self::Identifier(l0), Self::Identifier(r0)) => l0.eq(r0),
441 (Self::QualifiedIdentifier(l0), Self::QualifiedIdentifier(r0)) => l0.eq(r0),
442 _ => false,
443 }
444 }
445}
446
447impl Eq for IdentifierReference {}
448
449impl Hash for IdentifierReference {
450 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
451 core::mem::discriminant(self).hash(state);
452 }
453}
454
455enum_display_impl!(IdentifierReference => Identifier, QualifiedIdentifier);
456
457impl_from_for_variant!(IdentifierReference, Identifier, Identifier);
458impl_from_for_variant!(
459 IdentifierReference,
460 QualifiedIdentifier,
461 QualifiedIdentifier
462);
463
464impl_has_source_span_for!(IdentifierReference => variants Identifier, QualifiedIdentifier);
465
466impl IdentifierReference {
467 is_as_variant!(Identifier (Identifier) => is_identifier, as_identifier);
472
473 is_as_variant!(QualifiedIdentifier (QualifiedIdentifier) => is_qualified_identifier, as_qualified_identifier);
474
475 pub const fn module(&self) -> Option<&Identifier> {
480 match self {
481 IdentifierReference::Identifier(_) => None,
482 IdentifierReference::QualifiedIdentifier(v) => Some(v.module()),
483 }
484 }
485
486 pub const fn member(&self) -> &Identifier {
487 match self {
488 IdentifierReference::Identifier(v) => v,
489 IdentifierReference::QualifiedIdentifier(v) => v.member(),
490 }
491 }
492
493 pub fn validate(&self, top: &Module, loader: &impl ModuleLoader) {
498 match self {
499 IdentifierReference::Identifier(v) => v.validate(top, loader, None),
500 IdentifierReference::QualifiedIdentifier(v) => v.validate(top, loader),
501 };
502 }
503
504 pub fn eq_with_span(&self, other: &Self) -> bool {
505 match (self, other) {
506 (Self::Identifier(l0), Self::Identifier(r0)) => l0.eq_with_span(r0),
507 (Self::QualifiedIdentifier(l0), Self::QualifiedIdentifier(r0)) => l0.eq_with_span(r0),
508 _ => false,
509 }
510 }
511}
512
513#[cfg(test)]
518mod tests {
519 use super::Identifier;
520 use pretty_assertions::assert_eq;
521
522 #[test]
523 fn test_type_label() {
524 assert_eq!("Foo", Identifier::new_unchecked("Foo").to_type_label());
525 assert_eq!(
526 "Foo Bar",
527 Identifier::new_unchecked("FooBar").to_type_label()
528 );
529 assert_eq!(
530 "Foo Bar Baz",
531 Identifier::new_unchecked("FooBarBaz").to_type_label()
532 );
533 assert_eq!(
534 "Foo Bar Baz",
535 Identifier::new_unchecked("Foo_Bar_Baz").to_type_label()
536 );
537 }
538
539 #[test]
540 fn test_member_label() {
541 assert_eq!("foo", Identifier::new_unchecked("Foo").to_member_label());
542 assert_eq!(
543 "foo bar",
544 Identifier::new_unchecked("FooBar").to_member_label()
545 );
546 assert_eq!(
547 "foo bar baz",
548 Identifier::new_unchecked("FooBarBaz").to_member_label()
549 );
550 assert_eq!(
551 "foo bar baz",
552 Identifier::new_unchecked("Foo_Bar_Baz").to_member_label()
553 );
554 }
555}