1use crate::{
25 markdown::{parser::OptionKey, position::Position},
26 option::{AttrOption, RawOption},
27 validation::BASIC_TYPES,
28 xmltype::XMLType,
29};
30use convert_case::{Case, Casing};
31use serde::{de::Visitor, Deserialize, Serialize};
32use std::{error::Error, fmt, str::FromStr};
33
34#[cfg(feature = "python")]
35use pyo3::{pyclass, pymethods};
36
37#[cfg(feature = "wasm")]
38use tsify_next::Tsify;
39
40#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)]
42#[cfg_attr(feature = "python", pyclass(get_all, from_py_object))]
43#[cfg_attr(feature = "wasm", derive(Tsify))]
44#[cfg_attr(feature = "wasm", tsify(into_wasm_abi))]
45pub struct Attribute {
46 pub name: String,
48 #[serde(rename = "multiple")]
50 pub is_array: bool,
51 pub is_id: bool,
53 pub dtypes: Vec<String>,
55 pub docstring: String,
57 pub options: Vec<AttrOption>,
59 pub term: Option<String>,
61 pub required: bool,
63 #[serde(skip_serializing_if = "Option::is_none")]
65 pub default: Option<DataType>,
66 #[serde(skip_serializing_if = "Option::is_none")]
68 pub xml: Option<XMLType>,
69 pub is_enum: bool,
71 pub position: Option<Position>,
73 #[serde(skip_serializing_if = "Option::is_none")]
75 pub import_prefix: Option<String>,
76}
77
78impl Attribute {
79 pub fn new(name: String, required: bool) -> Self {
86 Attribute {
87 name: name.clone().replace(" ", "_"),
88 dtypes: Vec::new(),
89 docstring: String::new(),
90 options: Vec::new(),
91 is_array: false,
92 is_id: false,
93 term: None,
94 required,
95 xml: Some(XMLType::from_str(name.as_str()).unwrap()),
96 default: None,
97 is_enum: false,
98 position: None,
99 import_prefix: None,
100 }
101 }
102
103 pub fn set_docstring(&mut self, docstring: String) {
109 self.docstring = docstring;
110 }
111
112 pub fn set_position(&mut self, position: Position) {
118 self.position = Some(position);
119 }
120
121 pub fn add_option(&mut self, option: RawOption) -> Result<(), Box<dyn Error>> {
127 match OptionKey::from_str(option.key.as_str()) {
128 OptionKey::Type => self.set_dtype(option.value)?,
129 OptionKey::Term => self.term = Some(option.value),
130 OptionKey::Description => self.docstring = option.value,
131 OptionKey::Default => self.default = Some(DataType::from_str(&option.value)?),
132 OptionKey::Multiple => self.is_array = option.value.to_lowercase() == "true",
133 OptionKey::Other => self.options.push(option.try_into()?),
134 OptionKey::Xml => {
135 self.set_xml(XMLType::from_str(&option.value).expect("Invalid XML type"))
136 }
137 }
138
139 Ok(())
140 }
141
142 pub(crate) fn set_dtype(&mut self, dtype: String) -> Result<(), Box<dyn Error>> {
148 let dtypes = self.break_up_dtypes(&dtype);
149
150 self.validate_dtypes(&dtypes)?;
151
152 let mut new_dtypes = Vec::new();
153
154 for mut dtype in dtypes {
155 dtype = dtype.trim().to_string();
156 if self.is_identifier(&dtype) {
157 dtype = self.process_identifier(&dtype).to_string();
158 }
159
160 if let Some((prefix, name)) = dtype.split_once('.') {
161 self.import_prefix = Some(prefix.to_string());
162 dtype = name.to_string();
163 }
164
165 if dtype.ends_with("[]") {
166 self.is_array = true;
167 dtype = dtype.trim_end_matches("[]").to_string();
168 }
169
170 if !BASIC_TYPES.contains(&dtype.as_str()) {
171 dtype = dtype.replace(" ", "_").to_case(Case::Pascal);
172 }
173
174 new_dtypes.push(dtype);
175 }
176
177 self.dtypes = new_dtypes;
178
179 Ok(())
180 }
181
182 fn break_up_dtypes(&self, dtype: &str) -> Vec<String> {
192 dtype.split(",").map(|s| s.to_string()).collect()
193 }
194
195 fn validate_dtypes(&self, dtypes: &[String]) -> Result<(), Box<dyn Error>> {
205 let has_multiple_dtypes = dtypes.len() > 1;
206 let contains_array_dtype = dtypes.iter().any(|dtype| dtype.ends_with("[]"));
207
208 if has_multiple_dtypes && contains_array_dtype {
209 return Err(
210 "If more than one dtype is provided, none can be array valued by []. \
211 Use the keyword 'Multiple' instead."
212 .into(),
213 );
214 }
215
216 Ok(())
217 }
218
219 fn is_identifier(&self, dtype: &str) -> bool {
229 dtype.to_lowercase().starts_with("identifier")
230 }
231
232 fn process_identifier(&mut self, dtype: &str) -> String {
242 self.is_id = true;
243 let pattern = regex::Regex::new(r"[I|i]dentifier").unwrap();
245 pattern.replace_all(dtype, "string").to_string()
246 }
247
248 pub fn to_json_schema(&self) -> String {
254 serde_json::to_string_pretty(&self).unwrap()
255 }
256
257 pub fn has_term(&self) -> bool {
263 self.term.is_some()
264 }
265
266 pub fn set_xml(&mut self, xml: XMLType) {
272 self.xml = Some(xml);
273 }
274}
275
276#[derive(Debug, Clone)]
277#[cfg_attr(feature = "python", pyclass(from_py_object))]
278#[cfg_attr(feature = "wasm", derive(Tsify))]
279#[cfg_attr(feature = "wasm", tsify(into_wasm_abi))]
280pub enum DataType {
281 Boolean(bool),
282 Integer(i64),
283 Float(f64),
284 String(String),
285}
286
287#[cfg_attr(feature = "python", pymethods)]
288impl DataType {
289 pub fn is_boolean(&self) -> bool {
290 matches!(self, DataType::Boolean(_))
291 }
292
293 pub fn is_integer(&self) -> bool {
294 matches!(self, DataType::Integer(_))
295 }
296
297 pub fn is_float(&self) -> bool {
298 matches!(self, DataType::Float(_))
299 }
300
301 pub fn is_string(&self) -> bool {
302 matches!(self, DataType::String(_))
303 }
304
305 pub fn as_boolean(&self) -> Option<bool> {
306 if let DataType::Boolean(value) = self {
307 Some(*value)
308 } else {
309 None
310 }
311 }
312
313 pub fn as_integer(&self) -> Option<i64> {
314 if let DataType::Integer(value) = self {
315 Some(*value)
316 } else {
317 None
318 }
319 }
320
321 pub fn as_float(&self) -> Option<f64> {
322 if let DataType::Float(value) = self {
323 Some(*value)
324 } else {
325 None
326 }
327 }
328
329 pub fn as_string(&self) -> Option<String> {
330 if let DataType::String(value) = self {
331 Some(value.clone())
332 } else {
333 None
334 }
335 }
336}
337
338impl PartialEq for DataType {
339 fn eq(&self, other: &Self) -> bool {
340 match (self, other) {
341 (DataType::Boolean(a), DataType::Boolean(b)) => a == b,
342 (DataType::Integer(a), DataType::Integer(b)) => a == b,
343 (DataType::Float(a), DataType::Float(b)) => a == b,
344 (DataType::String(a), DataType::String(b)) => a == b,
345 _ => false,
346 }
347 }
348}
349
350impl FromStr for DataType {
351 type Err = String;
352
353 fn from_str(s: &str) -> Result<Self, Self::Err> {
355 let lower_s = s.to_lowercase();
356 let lower_s = lower_s.trim_matches('"');
357
358 if let Ok(b) = lower_s.parse::<bool>() {
359 Ok(DataType::Boolean(b))
360 } else if let Ok(i) = lower_s.parse::<i64>() {
361 Ok(DataType::Integer(i))
362 } else if let Ok(f) = lower_s.parse::<f64>() {
363 Ok(DataType::Float(f))
364 } else if !lower_s.is_empty() {
365 Ok(DataType::String(format!("\"{s}\"")))
366 } else {
367 Err("Invalid data type".to_string())
368 }
369 }
370}
371
372impl Serialize for DataType {
373 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
375 where
376 S: serde::Serializer,
377 {
378 match self {
379 DataType::Boolean(b) => serializer.serialize_bool(*b),
380 DataType::Integer(i) => serializer.serialize_i64(*i),
381 DataType::Float(f) => serializer.serialize_f64(*f),
382 DataType::String(s) => serializer.serialize_str(s),
383 }
384 }
385}
386
387#[allow(clippy::needless_lifetimes)]
388impl<'de> Deserialize<'de> for DataType {
389 fn deserialize<D>(deserializer: D) -> Result<DataType, D::Error>
391 where
392 D: serde::Deserializer<'de>,
393 {
394 struct DataTypeVisitor;
395 impl<'de> Visitor<'de> for DataTypeVisitor {
396 type Value = DataType;
397
398 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
399 formatter.write_str("a boolean, integer, float, or string")
400 }
401
402 fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E> {
403 Ok(DataType::Boolean(v))
404 }
405
406 fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E> {
407 Ok(DataType::Integer(v))
408 }
409
410 fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E> {
411 Ok(DataType::Integer(v as i64))
412 }
413
414 fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E> {
415 Ok(DataType::Float(v))
416 }
417
418 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> {
419 Ok(DataType::String(v.to_string()))
420 }
421 }
422
423 deserializer.deserialize_any(DataTypeVisitor)
424 }
425}
426
427#[cfg(test)]
428mod tests {
429 use crate::xmltype::XMLType;
430 use pretty_assertions::assert_eq;
431
432 use super::*;
433
434 #[test]
435 fn test_attribute_new() {
436 let attr = Attribute::new("name".to_string(), false);
437 assert_eq!(attr.name, "name");
438 assert_eq!(attr.dtypes.len(), 0);
439 assert_eq!(attr.docstring, "");
440 assert_eq!(attr.options.len(), 0);
441 assert_eq!(attr.is_array, false);
442 assert_eq!(attr.term, None);
443 assert_eq!(attr.required, false);
444 }
445
446 #[test]
447 fn test_attribute_set_docstring() {
448 let mut attr = Attribute::new("name".to_string(), false);
449 attr.set_docstring("This is a test".to_string());
450 assert_eq!(attr.docstring, "This is a test");
451 assert_eq!(attr.required, false);
452 }
453
454 #[test]
455 fn test_attribute_add_type_option() {
456 let mut attr = Attribute::new("name".to_string(), false);
457 let option = RawOption::new("type".to_string(), "string".to_string());
458 attr.add_option(option).expect("Failed to add option");
459 assert_eq!(attr.dtypes.len(), 1);
460 assert_eq!(attr.dtypes[0], "string");
461 }
462
463 #[test]
464 fn test_attribute_add_term_option() {
465 let mut attr = Attribute::new("name".to_string(), false);
466 let option = RawOption::new("term".to_string(), "string".to_string());
467 attr.add_option(option).expect("Failed to add option");
468 assert_eq!(attr.term, Some("string".to_string()));
469 }
470
471 #[test]
472 fn test_attribute_add_option() {
473 let mut attr = Attribute::new("name".to_string(), false);
474 let option = RawOption::new("description".to_string(), "This is a test".to_string());
475 attr.add_option(option).expect("Failed to add option");
476 let option = RawOption::new("something".to_string(), "something".to_string());
477 attr.add_option(option).expect("Failed to add option");
478
479 assert_eq!(attr.options.len(), 1);
480
481 if let Some(option) = attr.options.first() {
482 if let AttrOption::Other { key, value } = option {
483 assert_eq!(key, "something");
484 assert_eq!(value, "something");
485 } else {
486 panic!("Option is not an AttributeOption::Other");
487 }
488 } else {
489 panic!("Option not found");
490 }
491
492 assert_eq!(attr.docstring, "This is a test");
493 }
494
495 #[test]
496 fn test_attribute_set_dtype() {
497 let mut attr = Attribute::new("name".to_string(), false);
498 attr.set_dtype("string".to_string())
499 .expect("Failed to set dtype");
500 assert_eq!(attr.dtypes.len(), 1);
501 assert_eq!(attr.dtypes[0], "string");
502 assert_eq!(attr.is_array, false);
503 }
504
505 #[test]
506 fn test_attribute_set_array_dtype() {
507 let mut attr = Attribute::new("name".to_string(), false);
508 attr.set_dtype("string[]".to_string())
509 .expect("Failed to set dtype");
510 assert_eq!(attr.dtypes.len(), 1);
511 assert_eq!(attr.dtypes[0], "string");
512 assert_eq!(attr.is_array, true);
513 }
514
515 #[test]
516 fn test_attribute_set_xml_attr() {
517 let mut attr = Attribute::new("name".to_string(), false);
518 let xml = XMLType::from_str("@name").expect("Could not parse XMLType");
519 attr.set_xml(xml);
520 assert_eq!(
521 attr.xml.expect("Could not find XML option"),
522 XMLType::Attribute {
523 is_attr: true,
524 name: "name".to_string(),
525 },
526 "XMLType is not correct. Expected an attribute type."
527 );
528 }
529
530 #[test]
531 fn test_attribute_set_xml_element() {
532 let mut attr = Attribute::new("name".to_string(), false);
533 let xml = XMLType::from_str("name").expect("Could not parse XMLType");
534 attr.set_xml(xml);
535 assert_eq!(
536 attr.xml.expect("Could not find XML option"),
537 XMLType::Element {
538 is_attr: false,
539 name: "name".to_string(),
540 },
541 "XMLType is not correct. Expected an element type."
542 );
543 }
544
545 #[test]
546 fn test_default_xml_type() {
547 let attr = Attribute::new("name".to_string(), false);
548 assert_eq!(
549 attr.xml.unwrap(),
550 XMLType::Element {
551 is_attr: false,
552 name: "name".to_string(),
553 }
554 );
555 }
556
557 #[test]
558 fn test_serialize_data_type() {
559 let dt = DataType::String("string".to_string());
561 let serialized = serde_json::to_string(&dt).expect("Failed to serialize DataType");
562 assert_eq!(serialized, "\"string\"");
563
564 let dt = DataType::Integer(1);
566 let serialized = serde_json::to_string(&dt).expect("Failed to serialize DataType");
567 assert_eq!(serialized, "1");
568
569 let dt = DataType::Float(1.0);
571 let serialized = serde_json::to_string(&dt).expect("Failed to serialize DataType");
572 assert_eq!(serialized, "1.0");
573
574 let dt = DataType::Boolean(true);
576 let serialized = serde_json::to_string(&dt).expect("Failed to serialize DataType");
577 assert_eq!(serialized, "true");
578 }
579
580 #[test]
581 fn test_deserialize_data_type() {
582 let deserialized: DataType =
584 serde_json::from_str("\"string\"").expect("Failed to deserialize string DataType");
585 assert_eq!(deserialized, DataType::String("string".to_string()));
586
587 let deserialized: DataType =
589 serde_json::from_str("1").expect("Failed to deserialize integer DataType");
590 assert_eq!(deserialized, DataType::Integer(1));
591
592 let deserialized: DataType =
594 serde_json::from_str("1.0").expect("Failed to deserialize float DataType");
595 assert_eq!(deserialized, DataType::Float(1.0));
596
597 let deserialized: DataType =
599 serde_json::from_str("true").expect("Failed to deserialize bool DataType");
600 assert_eq!(deserialized, DataType::Boolean(true));
601 }
602
603 #[test]
604 fn is_boolean_returns_true_for_boolean() {
605 let dt = DataType::Boolean(true);
606 assert!(dt.is_boolean());
607 }
608
609 #[test]
610 fn is_boolean_returns_false_for_non_boolean() {
611 let dt = DataType::Integer(1);
612 assert!(!dt.is_boolean());
613 }
614
615 #[test]
616 fn is_integer_returns_true_for_integer() {
617 let dt = DataType::Integer(1);
618 assert!(dt.is_integer());
619 }
620
621 #[test]
622 fn is_integer_returns_false_for_non_integer() {
623 let dt = DataType::Boolean(true);
624 assert!(!dt.is_integer());
625 }
626
627 #[test]
628 fn is_float_returns_true_for_float() {
629 let dt = DataType::Float(1.0);
630 assert!(dt.is_float());
631 }
632
633 #[test]
634 fn is_float_returns_false_for_non_float() {
635 let dt = DataType::String("string".to_string());
636 assert!(!dt.is_float());
637 }
638
639 #[test]
640 fn is_string_returns_true_for_string() {
641 let dt = DataType::String("string".to_string());
642 assert!(dt.is_string());
643 }
644
645 #[test]
646 fn is_string_returns_false_for_non_string() {
647 let dt = DataType::Float(1.0);
648 assert!(!dt.is_string());
649 }
650
651 #[test]
652 fn as_boolean_returns_some_for_boolean() {
653 let dt = DataType::Boolean(true);
654 assert_eq!(dt.as_boolean(), Some(true));
655 }
656
657 #[test]
658 fn as_boolean_returns_none_for_non_boolean() {
659 let dt = DataType::Integer(1);
660 assert_eq!(dt.as_boolean(), None);
661 }
662
663 #[test]
664 fn as_integer_returns_some_for_integer() {
665 let dt = DataType::Integer(1);
666 assert_eq!(dt.as_integer(), Some(1));
667 }
668
669 #[test]
670 fn as_integer_returns_none_for_non_integer() {
671 let dt = DataType::Boolean(true);
672 assert_eq!(dt.as_integer(), None);
673 }
674
675 #[test]
676 fn as_float_returns_some_for_float() {
677 let dt = DataType::Float(1.0);
678 assert_eq!(dt.as_float(), Some(1.0));
679 }
680
681 #[test]
682 fn as_float_returns_none_for_non_float() {
683 let dt = DataType::String("string".to_string());
684 assert_eq!(dt.as_float(), None);
685 }
686
687 #[test]
688 fn as_string_returns_some_for_string() {
689 let dt = DataType::String("string".to_string());
690 assert_eq!(dt.as_string(), Some("string".to_string()));
691 }
692
693 #[test]
694 fn as_string_returns_none_for_non_string() {
695 let dt = DataType::Float(1.0);
696 assert_eq!(dt.as_string(), None);
697 }
698
699 #[test]
700 fn from_str_parses_boolean() {
701 let dt = DataType::from_str("true").unwrap();
702 assert_eq!(dt, DataType::Boolean(true));
703 }
704
705 #[test]
706 fn from_str_parses_integer() {
707 let dt = DataType::from_str("42").unwrap();
708 assert_eq!(dt, DataType::Integer(42));
709 }
710
711 #[test]
712 fn from_str_parses_float() {
713 let dt = DataType::from_str("3.5").unwrap();
714 assert_eq!(dt, DataType::Float(3.5));
715 }
716
717 #[test]
718 fn from_str_parses_string() {
719 let dt = DataType::from_str("hello").unwrap();
720 assert_eq!(dt, DataType::String("\"hello\"".to_string()));
721 }
722
723 #[test]
724 fn from_str_returns_error_for_invalid_data_type() {
725 let dt = DataType::from_str("");
726 assert!(dt.is_err());
727 }
728}