Skip to main content

shape_wire/
metadata.rs

1//! Wire-format type metadata definitions
2//!
3//! This module defines the metadata structures that accompany wire TYPES,
4//! enabling type-aware display, parsing, and visualization.
5
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9/// Kind of type
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
11#[serde(rename_all = "lowercase")]
12pub enum TypeKind {
13    /// Primitive type (Number, String, Bool, etc.)
14    Primitive,
15    /// Array type
16    Array,
17    /// Object/struct type
18    Object,
19    /// Table type
20    Table,
21    /// Enum type
22    Enum,
23    /// Union type
24    Union,
25    /// Function type
26    Function,
27    /// Result type
28    Result,
29    /// Optional type
30    Optional,
31}
32
33/// Type information for a value
34#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
35pub struct TypeInfo {
36    /// Type name (e.g., "Number", "Candle", "Array<Number>")
37    pub name: String,
38
39    /// Kind of type
40    pub kind: TypeKind,
41
42    /// For object types: field definitions
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub fields: Option<Vec<FieldInfo>>,
45
46    /// For generic types: type parameters
47    #[serde(skip_serializing_if = "Option::is_none")]
48    pub generic_params: Option<Vec<TypeInfo>>,
49
50    /// For enum types: variant names
51    #[serde(skip_serializing_if = "Option::is_none")]
52    pub variants: Option<Vec<String>>,
53
54    /// Human-readable description
55    #[serde(skip_serializing_if = "Option::is_none")]
56    pub description: Option<String>,
57
58    /// Metadata for the type (formatting, plotting, etc.)
59    #[serde(skip_serializing_if = "Option::is_none")]
60    pub metadata: Option<TypeMetadata>,
61}
62
63/// Information about a field in an object type
64#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
65pub struct FieldInfo {
66    /// Field name
67    pub name: String,
68
69    /// Field type
70    pub type_info: TypeInfo,
71
72    /// Whether the field is optional
73    #[serde(default)]
74    pub optional: bool,
75
76    /// Human-readable description
77    #[serde(skip_serializing_if = "Option::is_none")]
78    pub description: Option<String>,
79}
80
81/// Registry of available metadata/formats for a type
82#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
83pub struct TypeRegistry {
84    /// Available metadata items
85    pub items: Vec<TypeMetadata>,
86
87    /// Name of the default item
88    pub default_item: String,
89}
90
91/// Generic metadata for a type
92///
93/// Stores arbitrary metadata sections (e.g., "plot", "format", "params", "validate").
94#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
95pub struct TypeMetadata {
96    /// Meta name (e.g., "Candle", "Percent", "ISO8601")
97    pub name: String,
98
99    /// Human-readable description
100    pub description: String,
101
102    /// Arbitrary metadata sections
103    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
104    pub sections: HashMap<String, serde_json::Value>,
105}
106
107impl TypeInfo {
108    /// Create a primitive type info
109    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    /// Create an array type info
122    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    /// Create an object type info
135    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    /// Create a table type info (with default Timestamp index)
148    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    /// Create a table type info with explicit index type
161    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    /// Add a description
174    pub fn with_description(mut self, desc: impl Into<String>) -> Self {
175        self.description = Some(desc.into());
176        self
177    }
178
179    /// Add metadata
180    pub fn with_metadata(mut self, metadata: TypeMetadata) -> Self {
181        self.metadata = Some(metadata);
182        self
183    }
184
185    // Common primitive types
186    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    /// Create a required field
213    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    /// Create an optional field
223    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    /// Add a description
233    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    /// Create an empty registry with a default item
241    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    /// Add a metadata item
249    pub fn with_item(mut self, item: TypeMetadata) -> Self {
250        self.items.push(item);
251        self
252    }
253
254    /// Create a default registry for basic types (JSON-like display)
255    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    /// Create a registry for number types
263    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    /// Create a registry for timestamp types
280    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    /// Create a simple metadata item
297    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    /// Add a metadata section
306    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    /// Get a metadata section
312    pub fn get_section(&self, key: &str) -> Option<&serde_json::Value> {
313        self.sections.get(key)
314    }
315
316    /// Check if this meta has a specific section
317    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}