1use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
11#[serde(rename_all = "lowercase")]
12pub enum TypeKind {
13 Primitive,
15 Array,
17 Object,
19 Table,
21 Enum,
23 Union,
25 Function,
27 Result,
29 Optional,
31}
32
33#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
35pub struct TypeInfo {
36 pub name: String,
38
39 pub kind: TypeKind,
41
42 #[serde(skip_serializing_if = "Option::is_none")]
44 pub fields: Option<Vec<FieldInfo>>,
45
46 #[serde(skip_serializing_if = "Option::is_none")]
48 pub generic_params: Option<Vec<TypeInfo>>,
49
50 #[serde(skip_serializing_if = "Option::is_none")]
52 pub variants: Option<Vec<String>>,
53
54 #[serde(skip_serializing_if = "Option::is_none")]
56 pub description: Option<String>,
57
58 #[serde(skip_serializing_if = "Option::is_none")]
60 pub metadata: Option<TypeMetadata>,
61}
62
63#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
65pub struct FieldInfo {
66 pub name: String,
68
69 pub type_info: TypeInfo,
71
72 #[serde(default)]
74 pub optional: bool,
75
76 #[serde(skip_serializing_if = "Option::is_none")]
78 pub description: Option<String>,
79}
80
81#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
83pub struct TypeRegistry {
84 pub items: Vec<TypeMetadata>,
86
87 pub default_item: String,
89}
90
91#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
95pub struct TypeMetadata {
96 pub name: String,
98
99 pub description: String,
101
102 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
104 pub sections: HashMap<String, serde_json::Value>,
105}
106
107impl TypeInfo {
108 pub fn primitive(name: impl Into<String>) -> Self {
110 TypeInfo {
111 name: name.into(),
112 kind: TypeKind::Primitive,
113 fields: None,
114 generic_params: None,
115 variants: None,
116 description: None,
117 metadata: None,
118 }
119 }
120
121 pub fn array(element_type: TypeInfo) -> Self {
123 TypeInfo {
124 name: format!("Array<{}>", element_type.name),
125 kind: TypeKind::Array,
126 fields: None,
127 generic_params: Some(vec![element_type]),
128 variants: None,
129 description: None,
130 metadata: None,
131 }
132 }
133
134 pub fn object(name: impl Into<String>, fields: Vec<FieldInfo>) -> Self {
136 TypeInfo {
137 name: name.into(),
138 kind: TypeKind::Object,
139 fields: Some(fields),
140 generic_params: None,
141 variants: None,
142 description: None,
143 metadata: None,
144 }
145 }
146
147 pub fn table(element_type: TypeInfo) -> Self {
149 TypeInfo {
150 name: format!("Table<{}>", element_type.name),
151 kind: TypeKind::Table,
152 fields: None,
153 generic_params: Some(vec![element_type]),
154 variants: None,
155 description: None,
156 metadata: None,
157 }
158 }
159
160 pub fn table_with_index(element_type: TypeInfo, index_type: TypeInfo) -> Self {
162 TypeInfo {
163 name: format!("Table<{}, {}>", element_type.name, index_type.name),
164 kind: TypeKind::Table,
165 fields: None,
166 generic_params: Some(vec![element_type, index_type]),
167 variants: None,
168 description: None,
169 metadata: None,
170 }
171 }
172
173 pub fn with_description(mut self, desc: impl Into<String>) -> Self {
175 self.description = Some(desc.into());
176 self
177 }
178
179 pub fn with_metadata(mut self, metadata: TypeMetadata) -> Self {
181 self.metadata = Some(metadata);
182 self
183 }
184
185 pub fn number() -> Self {
187 Self::primitive("Number")
188 }
189
190 pub fn integer() -> Self {
191 Self::primitive("Integer")
192 }
193
194 pub fn string() -> Self {
195 Self::primitive("String")
196 }
197
198 pub fn bool() -> Self {
199 Self::primitive("Bool")
200 }
201
202 pub fn timestamp() -> Self {
203 Self::primitive("Timestamp")
204 }
205
206 pub fn null() -> Self {
207 Self::primitive("Null")
208 }
209}
210
211impl FieldInfo {
212 pub fn required(name: impl Into<String>, type_info: TypeInfo) -> Self {
214 FieldInfo {
215 name: name.into(),
216 type_info,
217 optional: false,
218 description: None,
219 }
220 }
221
222 pub fn optional(name: impl Into<String>, type_info: TypeInfo) -> Self {
224 FieldInfo {
225 name: name.into(),
226 type_info,
227 optional: true,
228 description: None,
229 }
230 }
231
232 pub fn with_description(mut self, desc: impl Into<String>) -> Self {
234 self.description = Some(desc.into());
235 self
236 }
237}
238
239impl TypeRegistry {
240 pub fn new(default_item: impl Into<String>) -> Self {
242 TypeRegistry {
243 items: Vec::new(),
244 default_item: default_item.into(),
245 }
246 }
247
248 pub fn with_item(mut self, item: TypeMetadata) -> Self {
250 self.items.push(item);
251 self
252 }
253
254 pub fn default_for_primitives() -> Self {
256 TypeRegistry {
257 items: vec![TypeMetadata::simple("Default", "Standard display format")],
258 default_item: "Default".to_string(),
259 }
260 }
261
262 pub fn for_number() -> Self {
264 TypeRegistry {
265 items: vec![
266 TypeMetadata::simple("Default", "Standard numeric display"),
267 TypeMetadata::simple("Fixed", "Fixed decimal places")
268 .with_section("params", serde_json::json!({"decimals": 2})),
269 TypeMetadata::simple("Scientific", "Scientific notation"),
270 TypeMetadata::simple("Percent", "Percentage format")
271 .with_section("params", serde_json::json!({"decimals": 2})),
272 TypeMetadata::simple("Currency", "Currency format")
273 .with_section("params", serde_json::json!({"symbol": "$", "decimals": 2})),
274 ],
275 default_item: "Default".to_string(),
276 }
277 }
278
279 pub fn for_timestamp() -> Self {
281 TypeRegistry {
282 items: vec![
283 TypeMetadata::simple("ISO8601", "ISO 8601 format (2024-01-15T10:30:00Z)")
284 .with_section("params", serde_json::json!({"timezone": "UTC"})),
285 TypeMetadata::simple("Unix", "Unix timestamp (seconds or milliseconds)")
286 .with_section("params", serde_json::json!({"milliseconds": true})),
287 TypeMetadata::simple("Relative", "Relative time (e.g., '2 hours ago')"),
288 TypeMetadata::simple("Custom", "Custom strftime pattern"),
289 ],
290 default_item: "ISO8601".to_string(),
291 }
292 }
293}
294
295impl TypeMetadata {
296 pub fn simple(name: impl Into<String>, description: impl Into<String>) -> Self {
298 TypeMetadata {
299 name: name.into(),
300 description: description.into(),
301 sections: HashMap::new(),
302 }
303 }
304
305 pub fn with_section(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
307 self.sections.insert(key.into(), value);
308 self
309 }
310
311 pub fn get_section(&self, key: &str) -> Option<&serde_json::Value> {
313 self.sections.get(key)
314 }
315
316 pub fn has_section(&self, key: &str) -> bool {
318 self.sections.contains_key(key)
319 }
320}
321
322#[cfg(test)]
323mod tests {
324 use super::*;
325
326 #[test]
327 fn test_type_info_primitive() {
328 let t = TypeInfo::number();
329 assert_eq!(t.name, "Number");
330 assert_eq!(t.kind, TypeKind::Primitive);
331 }
332
333 #[test]
334 fn test_type_info_with_metadata() {
335 let meta = TypeMetadata::simple("Percent", "Percentage format")
336 .with_section("format", serde_json::json!("(v) => v * 100 + '%'"));
337
338 let t = TypeInfo::number().with_metadata(meta);
339 assert!(t.metadata.is_some());
340 assert_eq!(t.metadata.as_ref().unwrap().name, "Percent");
341 }
342
343 #[test]
344 fn test_type_metadata_serialization() {
345 let mut meta = TypeMetadata::simple("Candle", "OHLCV candlestick");
346 meta = meta.with_section(
347 "plot",
348 serde_json::json!({
349 "type": "range_bar",
350 "mapping": {"start": "open", "max": "high", "min": "low", "end": "close"}
351 }),
352 );
353
354 let json = serde_json::to_string(&meta).unwrap();
355 let parsed: TypeMetadata = serde_json::from_str(&json).unwrap();
356
357 assert_eq!(parsed.name, "Candle");
358 assert!(parsed.has_section("plot"));
359
360 let plot = parsed.get_section("plot").unwrap();
361 assert_eq!(plot["type"], "range_bar");
362 }
363
364 #[test]
365 fn test_type_info_table() {
366 let table = TypeInfo::table(TypeInfo::number());
367 assert_eq!(table.name, "Table<Number>");
368 assert_eq!(table.kind, TypeKind::Table);
369 assert_eq!(table.generic_params.as_ref().unwrap().len(), 1);
370 }
371
372 #[test]
373 fn test_type_info_table_with_index() {
374 let table = TypeInfo::table_with_index(TypeInfo::number(), TypeInfo::integer());
375 assert_eq!(table.name, "Table<Number, Integer>");
376 assert_eq!(table.kind, TypeKind::Table);
377 assert_eq!(table.generic_params.as_ref().unwrap().len(), 2);
378 assert_eq!(table.generic_params.as_ref().unwrap()[0].name, "Number");
379 assert_eq!(table.generic_params.as_ref().unwrap()[1].name, "Integer");
380 }
381
382 #[test]
383 fn test_type_info_table_with_string_index() {
384 let table = TypeInfo::table_with_index(TypeInfo::number(), TypeInfo::string());
385 assert_eq!(table.name, "Table<Number, String>");
386 assert_eq!(table.generic_params.as_ref().unwrap()[1].name, "String");
387 }
388}