alembic_engine/
mapping.rs1use alembic_core::{FieldSchema, FieldType};
4use anyhow::{anyhow, Result};
5use serde_json::Value;
6
7pub fn slugify(input: &str) -> String {
12 let mut out = String::new();
13 let mut last_dash = false;
14 for ch in input.chars() {
15 let lower = ch.to_ascii_lowercase();
16 if lower.is_ascii_alphanumeric() {
17 out.push(lower);
18 last_dash = false;
19 } else if !last_dash {
20 out.push('-');
21 last_dash = true;
22 }
23 }
24 while out.ends_with('-') {
25 out.pop();
26 }
27 while out.starts_with('-') {
28 out.remove(0);
29 }
30 out
31}
32
33pub fn custom_field_type_for_schema(field: &FieldSchema) -> String {
35 match field.r#type {
36 FieldType::Int => "integer".to_string(),
37 FieldType::Float => "decimal".to_string(),
38 FieldType::Bool => "boolean".to_string(),
39 FieldType::Date => "date".to_string(),
40 FieldType::Datetime => "datetime".to_string(),
41 FieldType::Json | FieldType::List { .. } | FieldType::Map { .. } => "json".to_string(),
42 _ => "text".to_string(),
43 }
44}
45
46pub fn tags_from_value(value: &Value) -> Result<Vec<String>> {
51 let items = match value {
52 Value::Array(items) => items,
53 Value::Null => return Ok(Vec::new()),
54 _ => return Err(anyhow!("tags must be an array")),
55 };
56 let mut tags = Vec::new();
57 for item in items {
58 match item {
59 Value::String(name) => tags.push(name.clone()),
60 Value::Object(map) => {
61 if let Some(Value::String(name)) = map.get("name") {
62 tags.push(name.clone());
63 } else if let Some(Value::String(slug)) = map.get("slug") {
64 tags.push(slug.clone());
65 }
66 }
67 _ => {}
68 }
69 }
70 Ok(tags)
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76 use serde_json::json;
77
78 #[test]
79 fn test_slugify() {
80 assert_eq!(slugify("Hello World"), "hello-world");
81 assert_eq!(slugify("EVPN Fabric!"), "evpn-fabric");
82 assert_eq!(slugify("---test---"), "test");
83 }
84
85 #[test]
86 fn test_custom_field_type_for_schema() {
87 let schema = |r#type| FieldSchema {
88 r#type,
89 required: false,
90 nullable: true,
91 description: None,
92 format: None,
93 pattern: None,
94 };
95 assert_eq!(
96 custom_field_type_for_schema(&schema(FieldType::String)),
97 "text"
98 );
99 assert_eq!(
100 custom_field_type_for_schema(&schema(FieldType::Int)),
101 "integer"
102 );
103 assert_eq!(
104 custom_field_type_for_schema(&schema(FieldType::Float)),
105 "decimal"
106 );
107 assert_eq!(
108 custom_field_type_for_schema(&schema(FieldType::Bool)),
109 "boolean"
110 );
111 assert_eq!(
112 custom_field_type_for_schema(&schema(FieldType::Json)),
113 "json"
114 );
115 }
116
117 #[test]
118 fn test_tags_from_value_array_of_strings() {
119 let val = json!(["tag1", "tag2"]);
120 assert_eq!(tags_from_value(&val).unwrap(), vec!["tag1", "tag2"]);
121 }
122
123 #[test]
124 fn test_tags_from_value_array_of_objects() {
125 let val = json!([{"name": "tag1"}, {"slug": "tag-2"}]);
126 assert_eq!(tags_from_value(&val).unwrap(), vec!["tag1", "tag-2"]);
127 }
128
129 #[test]
130 fn test_tags_from_value_null() {
131 let val = json!(null);
132 assert_eq!(tags_from_value(&val).unwrap(), Vec::<String>::new());
133 }
134
135 #[test]
136 fn test_tags_from_value_invalid() {
137 let val = json!("not an array");
138 assert!(tags_from_value(&val).is_err());
139 }
140}