1use crate::module_exports::{ModuleContext, ModuleExports, ModuleFunction, ModuleParam};
6use crate::type_schema::{SchemaId, TypeSchemaRegistry, nb_to_slot};
7use shape_value::heap_value::HeapValue;
8use shape_value::{ValueSlot, ValueWord};
9use std::sync::Arc;
10
11fn json_value_to_nanboxed(value: serde_json::Value) -> ValueWord {
13 match value {
14 serde_json::Value::Null => ValueWord::none(),
15 serde_json::Value::Bool(b) => ValueWord::from_bool(b),
16 serde_json::Value::Number(n) => ValueWord::from_f64(n.as_f64().unwrap_or(0.0)),
17 serde_json::Value::String(s) => ValueWord::from_string(Arc::new(s)),
18 serde_json::Value::Array(arr) => {
19 let items: Vec<ValueWord> = arr.into_iter().map(json_value_to_nanboxed).collect();
20 ValueWord::from_array(Arc::new(items))
21 }
22 serde_json::Value::Object(map) => {
23 let mut keys = Vec::with_capacity(map.len());
24 let mut values = Vec::with_capacity(map.len());
25 for (k, v) in map.into_iter() {
26 keys.push(ValueWord::from_string(Arc::new(k)));
27 values.push(json_value_to_nanboxed(v));
28 }
29 ValueWord::from_hashmap_pairs(keys, values)
30 }
31 }
32}
33
34const JSON_VARIANT_NULL: i64 = 0;
36const JSON_VARIANT_BOOL: i64 = 1;
37const JSON_VARIANT_NUMBER: i64 = 2;
38const JSON_VARIANT_STR: i64 = 3;
39const JSON_VARIANT_ARRAY: i64 = 4;
40const JSON_VARIANT_OBJECT: i64 = 5;
41
42fn make_json_enum(schema_id: u64, variant_id: i64, payload: Option<ValueWord>) -> ValueWord {
44 let variant_slot = ValueSlot::from_int(variant_id);
46 let (payload_slot, heap_mask) = if let Some(ref p) = payload {
47 let (slot, is_heap) = nb_to_slot(p);
48 (slot, if is_heap { 1u64 << 1 } else { 0u64 })
49 } else {
50 (ValueSlot::none(), 0u64)
51 };
52 let slots = vec![variant_slot, payload_slot].into_boxed_slice();
53 ValueWord::from_heap_value(HeapValue::TypedObject {
54 schema_id,
55 slots,
56 heap_mask,
57 })
58}
59
60fn json_value_to_enum(value: serde_json::Value, schema_id: u64) -> ValueWord {
62 match value {
63 serde_json::Value::Null => make_json_enum(schema_id, JSON_VARIANT_NULL, None),
64 serde_json::Value::Bool(b) => {
65 make_json_enum(schema_id, JSON_VARIANT_BOOL, Some(ValueWord::from_bool(b)))
66 }
67 serde_json::Value::Number(n) => make_json_enum(
68 schema_id,
69 JSON_VARIANT_NUMBER,
70 Some(ValueWord::from_f64(n.as_f64().unwrap_or(0.0))),
71 ),
72 serde_json::Value::String(s) => make_json_enum(
73 schema_id,
74 JSON_VARIANT_STR,
75 Some(ValueWord::from_string(Arc::new(s))),
76 ),
77 serde_json::Value::Array(arr) => {
78 let items: Vec<ValueWord> = arr
79 .into_iter()
80 .map(|v| json_value_to_enum(v, schema_id))
81 .collect();
82 make_json_enum(
83 schema_id,
84 JSON_VARIANT_ARRAY,
85 Some(ValueWord::from_array(Arc::new(items))),
86 )
87 }
88 serde_json::Value::Object(map) => {
89 let mut keys = Vec::with_capacity(map.len());
90 let mut values = Vec::with_capacity(map.len());
91 for (k, v) in map.into_iter() {
92 keys.push(ValueWord::from_string(Arc::new(k)));
93 values.push(json_value_to_enum(v, schema_id));
94 }
95 make_json_enum(
96 schema_id,
97 JSON_VARIANT_OBJECT,
98 Some(ValueWord::from_hashmap_pairs(keys, values)),
99 )
100 }
101 }
102}
103
104fn json_object_to_typed(
108 schema_id: SchemaId,
109 schema: &crate::type_schema::TypeSchema,
110 map: &serde_json::Map<String, serde_json::Value>,
111 registry: &TypeSchemaRegistry,
112) -> Result<ValueWord, String> {
113 use crate::type_schema::FieldType;
114
115 let num_fields = schema.fields.len();
116 let mut slots = vec![ValueSlot::none(); num_fields];
117 let mut heap_mask = 0u64;
118
119 for field in &schema.fields {
120 let wire = field.wire_name();
121 let json_val = map.get(wire);
122 let nb = if let Some(jv) = json_val {
123 json_value_to_typed_nb(jv, &field.field_type, registry)?
124 } else {
125 ValueWord::none()
126 };
127
128 let (slot, is_heap) = match &field.field_type {
130 FieldType::I64 => (
131 ValueSlot::from_int(
132 nb.as_i64()
133 .or_else(|| nb.as_f64().map(|n| n as i64))
134 .unwrap_or(0),
135 ),
136 false,
137 ),
138 FieldType::Bool => (ValueSlot::from_bool(nb.as_bool().unwrap_or(false)), false),
139 FieldType::F64 | FieldType::Decimal => (
140 ValueSlot::from_number(nb.as_number_coerce().unwrap_or(0.0)),
141 false,
142 ),
143 _ => nb_to_slot(&nb),
144 };
145
146 slots[field.index as usize] = slot;
147 if is_heap {
148 heap_mask |= 1u64 << field.index;
149 }
150 }
151
152 Ok(ValueWord::from_heap_value(HeapValue::TypedObject {
153 schema_id: schema_id as u64,
154 slots: slots.into_boxed_slice(),
155 heap_mask,
156 }))
157}
158
159fn json_value_to_typed_nb(
161 value: &serde_json::Value,
162 field_type: &crate::type_schema::FieldType,
163 registry: &TypeSchemaRegistry,
164) -> Result<ValueWord, String> {
165 use crate::type_schema::FieldType;
166 match (value, field_type) {
167 (serde_json::Value::Null, _) => Ok(ValueWord::none()),
168 (serde_json::Value::Bool(b), _) => Ok(ValueWord::from_bool(*b)),
169 (serde_json::Value::Number(n), FieldType::I64) => {
170 Ok(ValueWord::from_i64(n.as_i64().unwrap_or(0)))
171 }
172 (serde_json::Value::Number(n), _) => Ok(ValueWord::from_f64(n.as_f64().unwrap_or(0.0))),
173 (serde_json::Value::String(s), _) => Ok(ValueWord::from_string(Arc::new(s.clone()))),
174 (serde_json::Value::Array(arr), _) => {
175 let items: Vec<ValueWord> = arr
176 .iter()
177 .map(|v| json_value_to_typed_nb(v, &FieldType::Any, registry))
178 .collect::<Result<_, _>>()?;
179 Ok(ValueWord::from_array(Arc::new(items)))
180 }
181 (serde_json::Value::Object(obj), FieldType::Object(type_name)) => {
182 if let Some(nested_schema) = registry.get(type_name) {
183 json_object_to_typed(nested_schema.id, nested_schema, obj, registry)
184 } else {
185 Ok(json_value_to_nanboxed(serde_json::Value::Object(
187 obj.clone(),
188 )))
189 }
190 }
191 (serde_json::Value::Object(obj), _) => Ok(json_value_to_nanboxed(
192 serde_json::Value::Object(obj.clone()),
193 )),
194 }
195}
196
197pub fn create_json_module() -> ModuleExports {
199 let mut module = ModuleExports::new("std::core::json");
200 module.description = "JSON parsing and serialization".to_string();
201
202 module.add_function_with_schema(
205 "parse",
206 |args: &[ValueWord], ctx: &ModuleContext| {
207 let text = args
208 .first()
209 .and_then(|a| a.as_str())
210 .ok_or_else(|| "json.parse() requires a string argument".to_string())?;
211
212 let parsed: serde_json::Value =
213 serde_json::from_str(text).map_err(|e| format!("json.parse() failed: {}", e))?;
214
215 let result = if let Some(json_schema) = ctx.schemas.get("Json") {
216 json_value_to_enum(parsed, json_schema.id as u64)
217 } else {
218 json_value_to_nanboxed(parsed)
219 };
220
221 Ok(ValueWord::from_ok(result))
222 },
223 ModuleFunction {
224 description: "Parse a JSON string into Shape values".to_string(),
225 params: vec![ModuleParam {
226 name: "text".to_string(),
227 type_name: "string".to_string(),
228 required: true,
229 description: "JSON string to parse".to_string(),
230 ..Default::default()
231 }],
232 return_type: Some("Result<Json>".to_string()),
233 },
234 );
235
236 module.add_function_with_schema(
239 "__parse_typed",
240 |args: &[ValueWord], ctx: &ModuleContext| {
241 let text = args
242 .first()
243 .and_then(|a| a.as_str())
244 .ok_or_else(|| "json.__parse_typed() requires a string argument".to_string())?;
245 let schema_id = args
246 .get(1)
247 .and_then(|a| {
248 a.as_f64()
249 .map(|n| n as u32)
250 .or_else(|| a.as_i64().map(|n| n as u32))
251 })
252 .ok_or_else(|| "json.__parse_typed() requires a schema_id argument".to_string())?;
253
254 let parsed: serde_json::Value = serde_json::from_str(text)
255 .map_err(|e| format!("json.__parse_typed() failed: {}", e))?;
256
257 let map = match parsed {
258 serde_json::Value::Object(m) => m,
259 _ => {
260 return Err("json.__parse_typed() requires a JSON object".to_string());
261 }
262 };
263
264 let schema = ctx
265 .schemas
266 .get_by_id(schema_id)
267 .ok_or_else(|| format!("json.__parse_typed(): unknown schema id {}", schema_id))?;
268
269 let result = json_object_to_typed(schema_id, schema, &map, ctx.schemas)?;
270 Ok(ValueWord::from_ok(result))
271 },
272 ModuleFunction {
273 description: "Parse a JSON string into a typed struct".to_string(),
274 params: vec![
275 ModuleParam {
276 name: "text".to_string(),
277 type_name: "string".to_string(),
278 required: true,
279 description: "JSON string to parse".to_string(),
280 ..Default::default()
281 },
282 ModuleParam {
283 name: "schema_id".to_string(),
284 type_name: "number".to_string(),
285 required: true,
286 description: "Schema ID of the target type".to_string(),
287 ..Default::default()
288 },
289 ],
290 return_type: Some("Result<any>".to_string()),
291 },
292 );
293
294 module.add_function_with_schema(
296 "stringify",
297 |args: &[ValueWord], _ctx: &ModuleContext| {
298 let value = args
299 .first()
300 .ok_or_else(|| "json.stringify() requires a value argument".to_string())?;
301
302 let pretty = args.get(1).and_then(|a| a.as_bool()).unwrap_or(false);
303
304 let json_value = value.to_json_value();
305
306 let output = if pretty {
307 serde_json::to_string_pretty(&json_value)
308 } else {
309 serde_json::to_string(&json_value)
310 }
311 .map_err(|e| format!("json.stringify() failed: {}", e))?;
312
313 Ok(ValueWord::from_ok(ValueWord::from_string(Arc::new(output))))
314 },
315 ModuleFunction {
316 description: "Serialize a Shape value to a JSON string".to_string(),
317 params: vec![
318 ModuleParam {
319 name: "value".to_string(),
320 type_name: "any".to_string(),
321 required: true,
322 description: "Value to serialize".to_string(),
323 ..Default::default()
324 },
325 ModuleParam {
326 name: "pretty".to_string(),
327 type_name: "bool".to_string(),
328 required: false,
329 description: "Pretty-print with indentation (default: false)".to_string(),
330 default_snippet: Some("false".to_string()),
331 ..Default::default()
332 },
333 ],
334 return_type: Some("Result<string>".to_string()),
335 },
336 );
337
338 module.add_function_with_schema(
340 "is_valid",
341 |args: &[ValueWord], _ctx: &ModuleContext| {
342 let text = args
343 .first()
344 .and_then(|a| a.as_str())
345 .ok_or_else(|| "json.is_valid() requires a string argument".to_string())?;
346
347 let valid = serde_json::from_str::<serde_json::Value>(text).is_ok();
348 Ok(ValueWord::from_bool(valid))
349 },
350 ModuleFunction {
351 description: "Check if a string is valid JSON".to_string(),
352 params: vec![ModuleParam {
353 name: "text".to_string(),
354 type_name: "string".to_string(),
355 required: true,
356 description: "String to validate as JSON".to_string(),
357 ..Default::default()
358 }],
359 return_type: Some("bool".to_string()),
360 },
361 );
362
363 module
364}
365
366#[cfg(test)]
367mod tests {
368 use super::*;
369
370 fn test_ctx() -> crate::module_exports::ModuleContext<'static> {
371 let registry = Box::leak(Box::new(crate::type_schema::TypeSchemaRegistry::new()));
372 crate::module_exports::ModuleContext {
373 schemas: registry,
374 invoke_callable: None,
375 raw_invoker: None,
376 function_hashes: None,
377 vm_state: None,
378 granted_permissions: None,
379 scope_constraints: None,
380 set_pending_resume: None,
381 set_pending_frame_resume: None,
382 }
383 }
384
385 #[test]
386 fn test_json_module_creation() {
387 let module = create_json_module();
388 assert_eq!(module.name, "std::core::json");
389 assert!(module.has_export("parse"));
390 assert!(module.has_export("stringify"));
391 assert!(module.has_export("is_valid"));
392 }
393
394 #[test]
395 fn test_json_parse_string() {
396 let module = create_json_module();
397 let parse_fn = module.get_export("parse").unwrap();
398 let ctx = test_ctx();
399 let input = ValueWord::from_string(Arc::new(r#""hello""#.to_string()));
400 let result = parse_fn(&[input], &ctx).unwrap();
401 let inner = result.as_ok_inner().expect("should be Ok");
403 assert_eq!(inner.as_str(), Some("hello"));
404 }
405
406 #[test]
407 fn test_json_parse_number() {
408 let module = create_json_module();
409 let parse_fn = module.get_export("parse").unwrap();
410 let ctx = test_ctx();
411 let input = ValueWord::from_string(Arc::new("42.5".to_string()));
412 let result = parse_fn(&[input], &ctx).unwrap();
413 let inner = result.as_ok_inner().expect("should be Ok");
414 assert_eq!(inner.as_f64(), Some(42.5));
415 }
416
417 #[test]
418 fn test_json_parse_bool() {
419 let module = create_json_module();
420 let parse_fn = module.get_export("parse").unwrap();
421 let ctx = test_ctx();
422 let input = ValueWord::from_string(Arc::new("true".to_string()));
423 let result = parse_fn(&[input], &ctx).unwrap();
424 let inner = result.as_ok_inner().expect("should be Ok");
425 assert_eq!(inner.as_bool(), Some(true));
426 }
427
428 #[test]
429 fn test_json_parse_null() {
430 let module = create_json_module();
431 let parse_fn = module.get_export("parse").unwrap();
432 let ctx = test_ctx();
433 let input = ValueWord::from_string(Arc::new("null".to_string()));
434 let result = parse_fn(&[input], &ctx).unwrap();
435 let inner = result.as_ok_inner().expect("should be Ok");
436 assert!(inner.is_none());
437 }
438
439 #[test]
440 fn test_json_parse_array() {
441 let module = create_json_module();
442 let parse_fn = module.get_export("parse").unwrap();
443 let ctx = test_ctx();
444 let input = ValueWord::from_string(Arc::new("[1, 2, 3]".to_string()));
445 let result = parse_fn(&[input], &ctx).unwrap();
446 let inner = result.as_ok_inner().expect("should be Ok");
447 let arr = inner.as_any_array().expect("should be array").to_generic();
448 assert_eq!(arr.len(), 3);
449 assert_eq!(arr[0].as_f64(), Some(1.0));
450 assert_eq!(arr[1].as_f64(), Some(2.0));
451 assert_eq!(arr[2].as_f64(), Some(3.0));
452 }
453
454 #[test]
455 fn test_json_parse_object() {
456 let module = create_json_module();
457 let parse_fn = module.get_export("parse").unwrap();
458 let ctx = test_ctx();
459 let input = ValueWord::from_string(Arc::new(r#"{"a": 1, "b": "two"}"#.to_string()));
460 let result = parse_fn(&[input], &ctx).unwrap();
461 let inner = result.as_ok_inner().expect("should be Ok");
462 let (keys, _values, _index) = inner.as_hashmap().expect("should be hashmap");
463 assert_eq!(keys.len(), 2);
464 }
465
466 #[test]
467 fn test_json_parse_invalid() {
468 let module = create_json_module();
469 let parse_fn = module.get_export("parse").unwrap();
470 let ctx = test_ctx();
471 let input = ValueWord::from_string(Arc::new("{invalid}".to_string()));
472 let result = parse_fn(&[input], &ctx);
473 assert!(result.is_err());
474 }
475
476 #[test]
477 fn test_json_parse_requires_string() {
478 let module = create_json_module();
479 let parse_fn = module.get_export("parse").unwrap();
480 let ctx = test_ctx();
481 let result = parse_fn(&[ValueWord::from_f64(42.0)], &ctx);
482 assert!(result.is_err());
483 }
484
485 #[test]
486 fn test_json_stringify_number() {
487 let module = create_json_module();
488 let stringify_fn = module.get_export("stringify").unwrap();
489 let ctx = test_ctx();
490 let result = stringify_fn(&[ValueWord::from_f64(42.0)], &ctx).unwrap();
491 let inner = result.as_ok_inner().expect("should be Ok");
492 assert_eq!(inner.as_str(), Some("42.0"));
493 }
494
495 #[test]
496 fn test_json_stringify_string() {
497 let module = create_json_module();
498 let stringify_fn = module.get_export("stringify").unwrap();
499 let ctx = test_ctx();
500 let result = stringify_fn(
501 &[ValueWord::from_string(Arc::new("hello".to_string()))],
502 &ctx,
503 )
504 .unwrap();
505 let inner = result.as_ok_inner().expect("should be Ok");
506 assert_eq!(inner.as_str(), Some("\"hello\""));
507 }
508
509 #[test]
510 fn test_json_stringify_bool() {
511 let module = create_json_module();
512 let stringify_fn = module.get_export("stringify").unwrap();
513 let ctx = test_ctx();
514 let result = stringify_fn(&[ValueWord::from_bool(true)], &ctx).unwrap();
515 let inner = result.as_ok_inner().expect("should be Ok");
516 assert_eq!(inner.as_str(), Some("true"));
517 }
518
519 #[test]
520 fn test_json_stringify_none() {
521 let module = create_json_module();
522 let stringify_fn = module.get_export("stringify").unwrap();
523 let ctx = test_ctx();
524 let result = stringify_fn(&[ValueWord::none()], &ctx).unwrap();
525 let inner = result.as_ok_inner().expect("should be Ok");
526 assert_eq!(inner.as_str(), Some("null"));
527 }
528
529 #[test]
530 fn test_json_stringify_array() {
531 let module = create_json_module();
532 let stringify_fn = module.get_export("stringify").unwrap();
533 let ctx = test_ctx();
534 let arr = ValueWord::from_array(Arc::new(vec![
535 ValueWord::from_f64(1.0),
536 ValueWord::from_f64(2.0),
537 ]));
538 let result = stringify_fn(&[arr], &ctx).unwrap();
539 let inner = result.as_ok_inner().expect("should be Ok");
540 assert_eq!(inner.as_str(), Some("[1.0,2.0]"));
541 }
542
543 #[test]
544 fn test_json_stringify_pretty() {
545 let module = create_json_module();
546 let stringify_fn = module.get_export("stringify").unwrap();
547 let ctx = test_ctx();
548 let result = stringify_fn(
549 &[ValueWord::from_f64(42.0), ValueWord::from_bool(true)],
550 &ctx,
551 )
552 .unwrap();
553 let inner = result.as_ok_inner().expect("should be Ok");
554 assert_eq!(inner.as_str(), Some("42.0"));
556 }
557
558 #[test]
559 fn test_json_is_valid_true() {
560 let module = create_json_module();
561 let is_valid_fn = module.get_export("is_valid").unwrap();
562 let ctx = test_ctx();
563 let result = is_valid_fn(
564 &[ValueWord::from_string(Arc::new(
565 r#"{"key": "value"}"#.to_string(),
566 ))],
567 &ctx,
568 )
569 .unwrap();
570 assert_eq!(result.as_bool(), Some(true));
571 }
572
573 #[test]
574 fn test_json_is_valid_false() {
575 let module = create_json_module();
576 let is_valid_fn = module.get_export("is_valid").unwrap();
577 let ctx = test_ctx();
578 let result = is_valid_fn(
579 &[ValueWord::from_string(Arc::new(
580 "{not valid json".to_string(),
581 ))],
582 &ctx,
583 )
584 .unwrap();
585 assert_eq!(result.as_bool(), Some(false));
586 }
587
588 #[test]
589 fn test_json_is_valid_requires_string() {
590 let module = create_json_module();
591 let is_valid_fn = module.get_export("is_valid").unwrap();
592 let ctx = test_ctx();
593 let result = is_valid_fn(&[ValueWord::from_f64(42.0)], &ctx);
594 assert!(result.is_err());
595 }
596
597 #[test]
598 fn test_json_schemas() {
599 let module = create_json_module();
600
601 let parse_schema = module.get_schema("parse").unwrap();
602 assert_eq!(parse_schema.params.len(), 1);
603 assert_eq!(parse_schema.params[0].name, "text");
604 assert!(parse_schema.params[0].required);
605 assert_eq!(parse_schema.return_type.as_deref(), Some("Result<Json>"));
606
607 let stringify_schema = module.get_schema("stringify").unwrap();
608 assert_eq!(stringify_schema.params.len(), 2);
609 assert!(stringify_schema.params[0].required);
610 assert!(!stringify_schema.params[1].required);
611
612 let is_valid_schema = module.get_schema("is_valid").unwrap();
613 assert_eq!(is_valid_schema.params.len(), 1);
614 assert_eq!(is_valid_schema.return_type.as_deref(), Some("bool"));
615 }
616
617 #[test]
618 fn test_json_roundtrip_nested() {
619 let module = create_json_module();
620 let parse_fn = module.get_export("parse").unwrap();
621 let stringify_fn = module.get_export("stringify").unwrap();
622 let ctx = test_ctx();
623
624 let json_str = r#"{"name":"test","values":[1,2,3],"active":true,"meta":null}"#;
625 let parsed = parse_fn(
626 &[ValueWord::from_string(Arc::new(json_str.to_string()))],
627 &ctx,
628 )
629 .unwrap();
630 let inner = parsed.as_ok_inner().expect("should be Ok");
631
632 let re_stringified = stringify_fn(&[inner.clone()], &ctx).unwrap();
633 let re_str = re_stringified.as_ok_inner().expect("should be Ok");
634
635 let re_parsed = parse_fn(&[re_str.clone()], &ctx).unwrap();
637 assert!(re_parsed.as_ok_inner().is_some());
638 }
639
640 #[test]
642 fn test_json_value_to_enum_variants() {
643 use crate::type_schema::{EnumVariantInfo, TypeSchema};
644 let schema = TypeSchema::new_enum(
646 "Json",
647 vec![
648 EnumVariantInfo::new("Null", 0, 0),
649 EnumVariantInfo::new("Bool", 1, 1),
650 EnumVariantInfo::new("Number", 2, 1),
651 EnumVariantInfo::new("Str", 3, 1),
652 EnumVariantInfo::new("Array", 4, 1),
653 EnumVariantInfo::new("Object", 5, 1),
654 ],
655 );
656 let sid = schema.id as u64;
657
658 let null_nb = json_value_to_enum(serde_json::Value::Null, sid);
660 let (variant, _payload) = extract_enum_variant(&null_nb);
661 assert_eq!(variant, 0, "Null should be variant 0");
662
663 let bool_nb = json_value_to_enum(serde_json::Value::Bool(true), sid);
665 let (variant, _payload) = extract_enum_variant(&bool_nb);
666 assert_eq!(variant, 1, "Bool should be variant 1");
667
668 let num_nb = json_value_to_enum(serde_json::json!(42.5), sid);
670 let (variant, _payload) = extract_enum_variant(&num_nb);
671 assert_eq!(variant, 2, "Number should be variant 2");
672
673 let str_nb = json_value_to_enum(serde_json::json!("hello"), sid);
675 let (variant, _payload) = extract_enum_variant(&str_nb);
676 assert_eq!(variant, 3, "Str should be variant 3");
677
678 let arr_nb = json_value_to_enum(serde_json::json!([1, 2, 3]), sid);
680 let (variant, _payload) = extract_enum_variant(&arr_nb);
681 assert_eq!(variant, 4, "Array should be variant 4");
682
683 let obj_nb = json_value_to_enum(serde_json::json!({"a": 1}), sid);
685 let (variant, _payload) = extract_enum_variant(&obj_nb);
686 assert_eq!(variant, 5, "Object should be variant 5");
687 }
688
689 #[test]
691 fn test_parse_typed_with_alias() {
692 use crate::type_schema::{FieldAnnotation, TypeSchemaBuilder};
693 use shape_value::heap_value::HeapValue;
694
695 let mut registry = crate::type_schema::TypeSchemaRegistry::new();
696 let mut schema = TypeSchemaBuilder::new("Trade")
697 .f64_field("close")
698 .f64_field("volume")
699 .build();
700
701 schema.fields[0].annotations.push(FieldAnnotation {
703 name: "alias".to_string(),
704 args: vec!["Close Price".to_string()],
705 });
706 schema.fields[1].annotations.push(FieldAnnotation {
707 name: "alias".to_string(),
708 args: vec!["vol.".to_string()],
709 });
710 let trade_id = schema.id;
711 registry.register(schema);
712
713 let module = create_json_module();
714 let parse_typed_fn = module.get_export("__parse_typed").unwrap();
715 let ctx = crate::module_exports::ModuleContext {
716 schemas: ®istry,
717 invoke_callable: None,
718 raw_invoker: None,
719 function_hashes: None,
720 vm_state: None,
721 granted_permissions: None,
722 scope_constraints: None,
723 set_pending_resume: None,
724 set_pending_frame_resume: None,
725 };
726
727 let text = ValueWord::from_string(Arc::new(
728 r#"{"Close Price": 100.5, "vol.": 1000}"#.to_string(),
729 ));
730 let sid = ValueWord::from_f64(trade_id as f64);
731 let result = parse_typed_fn(&[text, sid], &ctx).unwrap();
732 let inner = result.as_ok_inner().expect("should be Ok");
733
734 if let Some(HeapValue::TypedObject { slots, .. }) = inner.as_heap_ref() {
736 let close_val = f64::from_bits(slots[0].raw());
738 assert!(
739 (close_val - 100.5).abs() < f64::EPSILON,
740 "close field should be 100.5, got {}",
741 close_val
742 );
743 let volume_val = f64::from_bits(slots[1].raw());
745 assert!(
746 (volume_val - 1000.0).abs() < f64::EPSILON,
747 "volume field should be 1000.0, got {}",
748 volume_val
749 );
750 } else {
751 panic!("expected TypedObject, got: {:?}", inner.type_name());
752 }
753 }
754
755 #[test]
757 fn test_register_type_with_annotations_alias() {
758 use crate::type_schema::{FieldAnnotation, FieldType};
759
760 let mut registry = crate::type_schema::TypeSchemaRegistry::new();
761 let annotations = vec![
762 vec![FieldAnnotation {
763 name: "alias".to_string(),
764 args: vec!["user_name".to_string()],
765 }],
766 vec![], ];
768 registry.register_type_with_annotations(
769 "User",
770 vec![
771 ("name".to_string(), FieldType::String),
772 ("age".to_string(), FieldType::I64),
773 ],
774 annotations,
775 );
776
777 let schema = registry.get("User").expect("schema should exist");
778 assert_eq!(schema.fields[0].wire_name(), "user_name");
779 assert_eq!(schema.fields[1].wire_name(), "age");
780 }
781
782 #[test]
784 fn test_parse_typed_alias_string_field() {
785 use crate::type_schema::{FieldAnnotation, FieldType};
786 use shape_value::heap_value::HeapValue;
787
788 let mut registry = crate::type_schema::TypeSchemaRegistry::new();
789 let annotations = vec![
790 vec![FieldAnnotation {
791 name: "alias".to_string(),
792 args: vec!["user_name".to_string()],
793 }],
794 vec![],
795 ];
796 let schema_id = registry.register_type_with_annotations(
797 "User",
798 vec![
799 ("name".to_string(), FieldType::String),
800 ("age".to_string(), FieldType::I64),
801 ],
802 annotations,
803 );
804
805 let module = create_json_module();
806 let parse_typed_fn = module.get_export("__parse_typed").unwrap();
807 let ctx = crate::module_exports::ModuleContext {
808 schemas: ®istry,
809 invoke_callable: None,
810 raw_invoker: None,
811 function_hashes: None,
812 vm_state: None,
813 granted_permissions: None,
814 scope_constraints: None,
815 set_pending_resume: None,
816 set_pending_frame_resume: None,
817 };
818
819 let text =
821 ValueWord::from_string(Arc::new(r#"{"user_name": "Bob", "age": 30}"#.to_string()));
822 let sid = ValueWord::from_f64(schema_id as f64);
823 let result = parse_typed_fn(&[text, sid], &ctx).unwrap();
824 let inner = result.as_ok_inner().expect("should be Ok");
825
826 if let Some(HeapValue::TypedObject { slots, .. }) = inner.as_heap_ref() {
828 let name_nb = slots[0].as_heap_nb();
830 assert_eq!(name_nb.as_str(), Some("Bob"), "name field should be 'Bob'");
831 let age_val = slots[1].as_i64();
833 assert_eq!(age_val, 30, "age field should be 30");
834 } else {
835 panic!("expected TypedObject, got: {:?}", inner.type_name());
836 }
837 }
838
839 #[test]
841 fn test_parse_typed_no_alias_uses_field_name() {
842 use crate::type_schema::FieldType;
843 use shape_value::heap_value::HeapValue;
844
845 let mut registry = crate::type_schema::TypeSchemaRegistry::new();
846 let schema_id = registry.register_type(
847 "Simple",
848 vec![
849 ("name".to_string(), FieldType::String),
850 ("value".to_string(), FieldType::F64),
851 ],
852 );
853
854 let module = create_json_module();
855 let parse_typed_fn = module.get_export("__parse_typed").unwrap();
856 let ctx = crate::module_exports::ModuleContext {
857 schemas: ®istry,
858 invoke_callable: None,
859 raw_invoker: None,
860 function_hashes: None,
861 vm_state: None,
862 granted_permissions: None,
863 scope_constraints: None,
864 set_pending_resume: None,
865 set_pending_frame_resume: None,
866 };
867
868 let text =
869 ValueWord::from_string(Arc::new(r#"{"name": "test", "value": 42.5}"#.to_string()));
870 let sid = ValueWord::from_f64(schema_id as f64);
871 let result = parse_typed_fn(&[text, sid], &ctx).unwrap();
872 let inner = result.as_ok_inner().expect("should be Ok");
873
874 if let Some(HeapValue::TypedObject { slots, .. }) = inner.as_heap_ref() {
875 let name_nb = slots[0].as_heap_nb();
876 assert_eq!(name_nb.as_str(), Some("test"));
877 let value_val = f64::from_bits(slots[1].raw());
878 assert!((value_val - 42.5).abs() < f64::EPSILON);
879 } else {
880 panic!("expected TypedObject");
881 }
882 }
883
884 fn extract_enum_variant(nb: &ValueWord) -> (i64, Option<ValueWord>) {
886 use shape_value::heap_value::HeapValue;
887 if let Some(HeapValue::TypedObject {
888 slots, heap_mask, ..
889 }) = nb.as_heap_ref()
890 {
891 let variant_id = slots[0].as_i64();
892 let payload = if slots.len() > 1 {
893 if heap_mask & (1u64 << 1) != 0 {
895 Some(slots[1].as_heap_nb())
896 } else if slots[1].raw() == 0 && variant_id == 0 {
897 None
899 } else {
900 Some(unsafe { ValueWord::clone_from_bits(slots[1].raw()) })
903 }
904 } else {
905 None
906 };
907 (variant_id, payload)
908 } else {
909 panic!("expected TypedObject, got: {:?}", nb.type_name())
910 }
911 }
912}