1use crate::Context;
14use arrow_ipc::{reader::FileReader, writer::FileWriter};
15use shape_value::heap_value::HeapValue;
16use shape_value::{DataTable, ValueSlot, ValueWord};
17use shape_wire::{
18 DurationUnit as WireDurationUnit, ValueEnvelope, WireTable, WireValue,
19 metadata::{TypeInfo, TypeRegistry},
20};
21use std::collections::BTreeMap;
22use std::sync::Arc;
23
24#[inline]
25fn slot_to_nb(
26 slots: &[ValueSlot],
27 heap_mask: u64,
28 idx: usize,
29 _field_type: &crate::type_schema::FieldType,
30) -> ValueWord {
31 if idx >= slots.len() {
32 return ValueWord::none();
33 }
34 if heap_mask & (1u64 << idx) != 0 {
35 return slots[idx].as_heap_nb();
36 }
37
38 unsafe { ValueWord::clone_from_bits(slots[idx].raw()) }
42}
43
44#[inline]
45fn slot_to_nb_raw(slots: &[ValueSlot], heap_mask: u64, idx: usize) -> ValueWord {
46 if idx >= slots.len() {
47 return ValueWord::none();
48 }
49 if heap_mask & (1u64 << idx) != 0 {
50 slots[idx].as_heap_nb()
51 } else {
52 unsafe { ValueWord::clone_from_bits(slots[idx].raw()) }
55 }
56}
57
58fn nb_to_wire_with_builtin_fallback(nb: &ValueWord, ctx: &Context) -> WireValue {
59 if let Some(HeapValue::TypedObject {
60 slots, heap_mask, ..
61 }) = nb.as_heap_ref()
62 && let Some(value) = fallback_builtin_typedobject_to_wire(slots, *heap_mask, ctx)
63 {
64 return value;
65 }
66 nb_to_wire(nb, ctx)
67}
68
69fn fallback_builtin_typedobject_to_wire(
70 slots: &[ValueSlot],
71 heap_mask: u64,
72 ctx: &Context,
73) -> Option<WireValue> {
74 use crate::type_schema::builtin_schemas::*;
75
76 if slots.len() >= 6 {
78 let category = slot_to_nb_raw(slots, heap_mask, ANYERROR_CATEGORY);
79 if category.as_str() == Some("AnyError") {
80 let mut obj = BTreeMap::new();
81 obj.insert(
82 "category".to_string(),
83 WireValue::String("AnyError".to_string()),
84 );
85 obj.insert(
86 "payload".to_string(),
87 nb_to_wire_with_builtin_fallback(
88 &slot_to_nb_raw(slots, heap_mask, ANYERROR_PAYLOAD),
89 ctx,
90 ),
91 );
92
93 let cause_nb = slot_to_nb_raw(slots, heap_mask, ANYERROR_CAUSE);
94 obj.insert(
95 "cause".to_string(),
96 if cause_nb.is_none() {
97 WireValue::Null
98 } else {
99 nb_to_wire_with_builtin_fallback(&cause_nb, ctx)
100 },
101 );
102 obj.insert(
103 "trace_info".to_string(),
104 nb_to_wire_with_builtin_fallback(
105 &slot_to_nb_raw(slots, heap_mask, ANYERROR_TRACE_INFO),
106 ctx,
107 ),
108 );
109 obj.insert(
110 "message".to_string(),
111 nb_to_wire_with_builtin_fallback(
112 &slot_to_nb_raw(slots, heap_mask, ANYERROR_MESSAGE),
113 ctx,
114 ),
115 );
116 obj.insert(
117 "code".to_string(),
118 nb_to_wire_with_builtin_fallback(
119 &slot_to_nb_raw(slots, heap_mask, ANYERROR_CODE),
120 ctx,
121 ),
122 );
123 return Some(WireValue::Object(obj));
124 }
125 }
126
127 if slots.len() >= 2 {
129 let kind_nb = slot_to_nb_raw(slots, heap_mask, TRACEINFO_FULL_KIND);
130 if let Some(kind) = kind_nb.as_str()
131 && (kind == "full" || kind == "single")
132 {
133 let mut obj = BTreeMap::new();
134 obj.insert("kind".to_string(), WireValue::String(kind.to_string()));
135 if kind == "single" {
136 obj.insert(
137 "frame".to_string(),
138 nb_to_wire_with_builtin_fallback(
139 &slot_to_nb_raw(slots, heap_mask, TRACEINFO_SINGLE_FRAME),
140 ctx,
141 ),
142 );
143 } else {
144 obj.insert(
145 "frames".to_string(),
146 nb_to_wire_with_builtin_fallback(
147 &slot_to_nb_raw(slots, heap_mask, TRACEINFO_FULL_FRAMES),
148 ctx,
149 ),
150 );
151 }
152 return Some(WireValue::Object(obj));
153 }
154 }
155
156 if slots.len() >= 4 {
158 let mut obj = BTreeMap::new();
159 obj.insert(
160 "ip".to_string(),
161 nb_to_wire_with_builtin_fallback(&slot_to_nb_raw(slots, heap_mask, TRACEFRAME_IP), ctx),
162 );
163 obj.insert(
164 "line".to_string(),
165 nb_to_wire_with_builtin_fallback(
166 &slot_to_nb_raw(slots, heap_mask, TRACEFRAME_LINE),
167 ctx,
168 ),
169 );
170 obj.insert(
171 "file".to_string(),
172 nb_to_wire_with_builtin_fallback(
173 &slot_to_nb_raw(slots, heap_mask, TRACEFRAME_FILE),
174 ctx,
175 ),
176 );
177 obj.insert(
178 "function".to_string(),
179 nb_to_wire_with_builtin_fallback(
180 &slot_to_nb_raw(slots, heap_mask, TRACEFRAME_FUNCTION),
181 ctx,
182 ),
183 );
184 return Some(WireValue::Object(obj));
185 }
186
187 None
188}
189
190fn value_to_wire(value: &ValueWord, ctx: &Context) -> WireValue {
195 nb_to_wire(&value.clone(), ctx)
196}
197
198pub fn nb_to_wire(nb: &ValueWord, ctx: &Context) -> WireValue {
204 use shape_value::value_word::NanTag;
205
206 match nb.tag() {
207 NanTag::None | NanTag::Unit => WireValue::Null,
208
209 NanTag::Bool => WireValue::Bool(nb.as_bool().unwrap_or(false)),
210
211 NanTag::F64 => {
212 let n = nb.as_f64().unwrap_or(0.0);
213 WireValue::Number(n)
214 }
215
216 NanTag::I48 => WireValue::Integer(nb.as_i64().unwrap_or(0)),
217
218 NanTag::Function => {
219 let id = nb.as_function().unwrap_or(0);
220 WireValue::String(format!("<function#{}>", id))
221 }
222
223 NanTag::ModuleFunction => {
224 let idx = nb.as_module_function().unwrap_or(0);
225 WireValue::String(format!("<native:{}>", idx))
226 }
227
228 NanTag::Heap => nb_heap_to_wire(nb, ctx),
229
230 NanTag::Ref => WireValue::Null, }
232}
233
234fn nb_heap_to_wire(nb: &ValueWord, ctx: &Context) -> WireValue {
236 let hv = match nb.as_heap_ref() {
237 Some(hv) => hv,
238 None => return WireValue::Null,
239 };
240 match hv {
241 HeapValue::String(s) => WireValue::String((**s).clone()),
242
243 HeapValue::Array(arr) => {
244 WireValue::Array(arr.iter().map(|elem| nb_to_wire(elem, ctx)).collect())
245 }
246
247 HeapValue::Decimal(d) => WireValue::Number(d.to_string().parse().unwrap_or(0.0)),
248
249 HeapValue::BigInt(i) => WireValue::Integer(*i),
250
251 HeapValue::Time(dt) => WireValue::Timestamp(dt.timestamp_millis()),
252
253 HeapValue::TimeSpan(duration) => {
254 let millis = duration.num_milliseconds();
255 WireValue::Duration {
256 value: millis as f64,
257 unit: WireDurationUnit::Milliseconds,
258 }
259 }
260
261 HeapValue::Duration(duration) => {
262 let value = duration.value;
263 let wire_unit = match duration.unit {
264 shape_ast::ast::DurationUnit::Seconds => WireDurationUnit::Seconds,
265 shape_ast::ast::DurationUnit::Minutes => WireDurationUnit::Minutes,
266 shape_ast::ast::DurationUnit::Hours => WireDurationUnit::Hours,
267 shape_ast::ast::DurationUnit::Days => WireDurationUnit::Days,
268 shape_ast::ast::DurationUnit::Weeks => WireDurationUnit::Weeks,
269 shape_ast::ast::DurationUnit::Months => {
270 return WireValue::Duration {
271 value: value * 30.0,
272 unit: WireDurationUnit::Days,
273 };
274 }
275 shape_ast::ast::DurationUnit::Years => {
276 return WireValue::Duration {
277 value: value * 365.0,
278 unit: WireDurationUnit::Days,
279 };
280 }
281 shape_ast::ast::DurationUnit::Samples => {
282 return WireValue::Duration {
283 value,
284 unit: WireDurationUnit::Days,
285 };
286 }
287 };
288 WireValue::Duration {
289 value,
290 unit: wire_unit,
291 }
292 }
293
294 HeapValue::Enum(enum_value) => enum_to_wire(enum_value, ctx),
295
296 HeapValue::Some(inner) => nb_to_wire(inner, ctx),
297
298 HeapValue::Ok(inner) => WireValue::Result {
299 ok: true,
300 value: Box::new(nb_to_wire(inner, ctx)),
301 },
302
303 HeapValue::Err(inner) => WireValue::Result {
304 ok: false,
305 value: Box::new(nb_to_wire(inner, ctx)),
306 },
307
308 HeapValue::Range {
309 start,
310 end,
311 inclusive,
312 } => WireValue::Range {
313 start: start.as_ref().map(|v| Box::new(nb_to_wire(v, ctx))),
314 end: end.as_ref().map(|v| Box::new(nb_to_wire(v, ctx))),
315 inclusive: *inclusive,
316 },
317
318 HeapValue::FunctionRef { name, .. } => WireValue::FunctionRef { name: name.clone() },
319
320 HeapValue::Closure { .. } => WireValue::FunctionRef {
321 name: "<closure>".to_string(),
322 },
323
324 HeapValue::Timeframe(tf) => WireValue::String(format!("{}", tf)),
325
326 HeapValue::DataReference(data) => {
327 let mut obj = BTreeMap::new();
328 obj.insert(
329 "datetime".to_string(),
330 WireValue::Timestamp(data.datetime.timestamp_millis()),
331 );
332 obj.insert("id".to_string(), WireValue::String(data.id.clone()));
333 obj.insert(
334 "timeframe".to_string(),
335 WireValue::String(format!("{}", data.timeframe)),
336 );
337 WireValue::Object(obj)
338 }
339
340 HeapValue::SimulationCall(data) => {
341 let mut obj = BTreeMap::new();
342 obj.insert(
343 "__type".to_string(),
344 WireValue::String("SimulationCall".to_string()),
345 );
346 obj.insert("name".to_string(), WireValue::String(data.name.clone()));
347 let params_wire: BTreeMap<String, WireValue> = data
349 .params
350 .iter()
351 .map(|(k, v)| (k.clone(), value_to_wire(v, ctx)))
352 .collect();
353 obj.insert("params".to_string(), WireValue::Object(params_wire));
354 WireValue::Object(obj)
355 }
356
357 HeapValue::PrintResult(result) => {
358 use shape_wire::print_result::{WirePrintResult, WirePrintSpan};
359
360 let wire_spans: Vec<WirePrintSpan> = result
361 .spans
362 .iter()
363 .map(|span| match span {
364 shape_value::PrintSpan::Literal {
365 text,
366 start,
367 end,
368 span_id,
369 } => WirePrintSpan::Literal {
370 text: text.clone(),
371 start: *start,
372 end: *end,
373 span_id: span_id.clone(),
374 },
375 shape_value::PrintSpan::Value {
376 text,
377 start,
378 end,
379 span_id,
380 variable_name,
381 raw_value,
382 type_name,
383 current_format,
384 format_params,
385 } => {
386 let raw_wire = value_to_wire(raw_value, ctx);
388 let (type_info, type_registry) =
389 infer_metadata_with_ctx(raw_value, type_name, ctx);
390
391 let params_wire: std::collections::HashMap<String, WireValue> =
392 format_params
393 .iter()
394 .map(|(k, v)| (k.clone(), value_to_wire(v, ctx)))
395 .collect();
396
397 WirePrintSpan::Value {
398 text: text.clone(),
399 start: *start,
400 end: *end,
401 span_id: span_id.clone(),
402 variable_name: variable_name.clone(),
403 raw_value: Box::new(raw_wire),
404 type_info: Box::new(type_info),
405 current_format: current_format.clone(),
406 type_registry,
407 format_params: params_wire,
408 }
409 }
410 })
411 .collect();
412
413 WireValue::PrintResult(WirePrintResult {
414 rendered: result.rendered.clone(),
415 spans: wire_spans,
416 })
417 }
418
419 HeapValue::TypedObject {
420 schema_id,
421 slots,
422 heap_mask,
423 } => {
424 let schema = ctx
425 .type_schema_registry()
426 .get_by_id(*schema_id as u32)
427 .cloned()
428 .or_else(|| crate::type_schema::lookup_schema_by_id_public(*schema_id as u32));
429
430 if let Some(schema) = schema {
431 let mut map = BTreeMap::new();
432 for field_def in &schema.fields {
433 let idx = field_def.index as usize;
434 if idx < slots.len() {
435 let field_nb = slot_to_nb(slots, *heap_mask, idx, &field_def.field_type);
436 map.insert(
437 field_def.name.clone(),
438 nb_to_wire_with_builtin_fallback(&field_nb, ctx),
439 );
440 }
441 }
442 WireValue::Object(map)
443 } else if let Some(fallback) =
444 fallback_builtin_typedobject_to_wire(slots, *heap_mask, ctx)
445 {
446 fallback
447 } else {
448 WireValue::String(format!("<typed_object:schema#{}>", schema_id))
449 }
450 }
451
452 HeapValue::TypeAnnotatedValue { value, .. } => nb_to_wire(value, ctx),
453
454 HeapValue::Future(id) => WireValue::String(format!("<future:{}>", id)),
455
456 HeapValue::DataTable(dt) => datatable_to_wire(dt.as_ref()),
457
458 HeapValue::TypedTable { table, schema_id } => {
459 datatable_to_wire_with_schema(table.as_ref(), Some(*schema_id as u32))
460 }
461
462 HeapValue::RowView { row_idx, .. } => WireValue::String(format!("<Row:{}>", row_idx)),
463 HeapValue::ColumnRef { col_id, .. } => WireValue::String(format!("<ColumnRef:{}>", col_id)),
464 HeapValue::IndexedTable { table, .. } => datatable_to_wire(table.as_ref()),
465 HeapValue::HostClosure(_) => WireValue::String("<HostClosure>".to_string()),
466 HeapValue::ExprProxy(col) => WireValue::String(format!("<ExprProxy:{}>", col)),
467 HeapValue::FilterExpr(_) => WireValue::String("<FilterExpr>".to_string()),
468 HeapValue::TaskGroup { .. } => WireValue::String("<TaskGroup>".to_string()),
469 HeapValue::TraitObject { value, .. } => nb_to_wire(value, ctx),
470
471 HeapValue::TimeReference(tr) => WireValue::String(format!("{:?}", tr)),
473 HeapValue::DateTimeExpr(expr) => WireValue::String(format!("{:?}", expr)),
474 HeapValue::DataDateTimeRef(dref) => WireValue::String(format!("{:?}", dref)),
475 HeapValue::TypeAnnotation(ann) => WireValue::String(format!("{:?}", ann)),
476
477 HeapValue::NativeScalar(v) => match v {
478 shape_value::heap_value::NativeScalar::I8(n) => WireValue::I8(*n),
479 shape_value::heap_value::NativeScalar::U8(n) => WireValue::U8(*n),
480 shape_value::heap_value::NativeScalar::I16(n) => WireValue::I16(*n),
481 shape_value::heap_value::NativeScalar::U16(n) => WireValue::U16(*n),
482 shape_value::heap_value::NativeScalar::I32(n) => WireValue::I32(*n),
483 shape_value::heap_value::NativeScalar::I64(n) => WireValue::I64(*n),
484 shape_value::heap_value::NativeScalar::U32(n) => WireValue::U32(*n),
485 shape_value::heap_value::NativeScalar::U64(n) => WireValue::U64(*n),
486 shape_value::heap_value::NativeScalar::Isize(n) => WireValue::Isize(*n as i64),
487 shape_value::heap_value::NativeScalar::Usize(n) => WireValue::Usize(*n as u64),
488 shape_value::heap_value::NativeScalar::Ptr(n) => WireValue::Ptr(*n as u64),
489 shape_value::heap_value::NativeScalar::F32(n) => WireValue::F32(*n),
490 },
491 HeapValue::NativeView(v) => WireValue::Object(
492 [
493 (
494 "__type".to_string(),
495 WireValue::String(if v.mutable { "cmut" } else { "cview" }.to_string()),
496 ),
497 (
498 "layout".to_string(),
499 WireValue::String(v.layout.name.clone()),
500 ),
501 (
502 "ptr".to_string(),
503 WireValue::String(format!("0x{:x}", v.ptr)),
504 ),
505 ]
506 .into_iter()
507 .collect(),
508 ),
509 HeapValue::HashMap(d) => {
510 let pairs: Vec<(String, WireValue)> = d
511 .keys
512 .iter()
513 .zip(d.values.iter())
514 .map(|(k, v)| (format!("{}", k), nb_to_wire(v, ctx)))
515 .collect();
516 WireValue::Object(pairs.into_iter().collect())
517 }
518 HeapValue::Set(d) => WireValue::Array(d.items.iter().map(|v| nb_to_wire(v, ctx)).collect()),
519 HeapValue::Deque(d) => {
520 WireValue::Array(d.items.iter().map(|v| nb_to_wire(v, ctx)).collect())
521 }
522 HeapValue::PriorityQueue(d) => {
523 WireValue::Array(d.items.iter().map(|v| nb_to_wire(v, ctx)).collect())
524 }
525 HeapValue::Content(node) => WireValue::Content(node.as_ref().clone()),
526 HeapValue::Instant(t) => WireValue::String(format!("<instant:{:?}>", t.elapsed())),
527 HeapValue::IoHandle(data) => {
528 let status = if data.is_open() { "open" } else { "closed" };
529 WireValue::String(format!("<io_handle:{}:{}>", data.path, status))
530 }
531 HeapValue::SharedCell(arc) => nb_to_wire(&arc.read().unwrap(), ctx),
532 HeapValue::IntArray(a) => {
533 WireValue::Array(a.iter().map(|&v| WireValue::Integer(v)).collect())
534 }
535 HeapValue::FloatArray(a) => {
536 WireValue::Array(a.iter().map(|&v| WireValue::Number(v)).collect())
537 }
538 HeapValue::BoolArray(a) => {
539 WireValue::Array(a.iter().map(|&v| WireValue::Bool(v != 0)).collect())
540 }
541 HeapValue::I8Array(a) => {
542 WireValue::Array(a.iter().map(|&v| WireValue::Integer(v as i64)).collect())
543 }
544 HeapValue::I16Array(a) => {
545 WireValue::Array(a.iter().map(|&v| WireValue::Integer(v as i64)).collect())
546 }
547 HeapValue::I32Array(a) => {
548 WireValue::Array(a.iter().map(|&v| WireValue::Integer(v as i64)).collect())
549 }
550 HeapValue::U8Array(a) => {
551 WireValue::Array(a.iter().map(|&v| WireValue::Integer(v as i64)).collect())
552 }
553 HeapValue::U16Array(a) => {
554 WireValue::Array(a.iter().map(|&v| WireValue::Integer(v as i64)).collect())
555 }
556 HeapValue::U32Array(a) => {
557 WireValue::Array(a.iter().map(|&v| WireValue::Integer(v as i64)).collect())
558 }
559 HeapValue::U64Array(a) => {
560 WireValue::Array(a.iter().map(|&v| WireValue::Integer(v as i64)).collect())
561 }
562 HeapValue::F32Array(a) => {
563 WireValue::Array(a.iter().map(|&v| WireValue::Number(v as f64)).collect())
564 }
565 HeapValue::Matrix(m) => WireValue::String(format!("<Mat<number>:{}x{}>", m.rows, m.cols)),
566 HeapValue::Iterator(_) => WireValue::String("<iterator>".to_string()),
567 HeapValue::Generator(_) => WireValue::String("<generator>".to_string()),
568 HeapValue::Mutex(_) => WireValue::String("<mutex>".to_string()),
569 HeapValue::Atomic(a) => {
570 WireValue::Integer(a.inner.load(std::sync::atomic::Ordering::Relaxed))
571 }
572 HeapValue::Lazy(_) => WireValue::String("<lazy>".to_string()),
573 HeapValue::Channel(c) => {
574 if c.is_sender() {
575 WireValue::String("<channel:sender>".to_string())
576 } else {
577 WireValue::String("<channel:receiver>".to_string())
578 }
579 }
580 }
581}
582
583pub struct ExtractedContent {
585 pub content_node: shape_value::content::ContentNode,
587 pub content_json: serde_json::Value,
589 pub content_html: String,
591 pub content_terminal: String,
593}
594
595pub fn nb_extract_content(
599 nb: &ValueWord,
600) -> (Option<serde_json::Value>, Option<String>, Option<String>) {
601 let extracted = nb_extract_content_full(nb);
602 match extracted {
603 Some(e) => (Some(e.content_json), Some(e.content_html), Some(e.content_terminal)),
604 None => (None, None, None),
605 }
606}
607
608pub fn nb_extract_content_full(nb: &ValueWord) -> Option<ExtractedContent> {
610 use crate::content_renderer::ContentRenderer;
611
612 let node: Option<shape_value::content::ContentNode> =
614 if let Some(HeapValue::Content(node)) = nb.as_heap_ref() {
615 Some(node.as_ref().clone())
616 } else if let Some(HeapValue::DataTable(dt)) = nb.as_heap_ref() {
617 Some(crate::content_dispatch::datatable_to_content_node(dt, None))
619 } else if let Some(HeapValue::TypedTable { table, .. }) = nb.as_heap_ref() {
620 Some(crate::content_dispatch::datatable_to_content_node(
621 table, None,
622 ))
623 } else {
624 None
625 };
626
627 let node = node?;
628
629 let json_renderer = crate::renderers::json::JsonRenderer;
630 let html_renderer = crate::renderers::html::HtmlRenderer::new();
631 let terminal_renderer = crate::renderers::terminal::TerminalRenderer::new();
632
633 let json_str = json_renderer.render(&node);
634 let content_json = serde_json::from_str(&json_str).unwrap_or(serde_json::Value::Null);
635 let content_html = html_renderer.render(&node);
636 let content_terminal = terminal_renderer.render(&node);
637
638 Some(ExtractedContent {
639 content_node: node,
640 content_json,
641 content_html,
642 content_terminal,
643 })
644}
645
646pub fn wire_to_nb(wire: &WireValue) -> ValueWord {
651 match wire {
652 WireValue::Null => ValueWord::none(),
653 WireValue::Bool(b) => ValueWord::from_bool(*b),
654 WireValue::Number(n) => ValueWord::from_f64(*n),
655 WireValue::Integer(i) => ValueWord::from_i64(*i),
656 WireValue::I8(n) => ValueWord::from_native_i8(*n),
657 WireValue::U8(n) => ValueWord::from_native_u8(*n),
658 WireValue::I16(n) => ValueWord::from_native_i16(*n),
659 WireValue::U16(n) => ValueWord::from_native_u16(*n),
660 WireValue::I32(n) => ValueWord::from_native_i32(*n),
661 WireValue::U32(n) => ValueWord::from_native_u32(*n),
662 WireValue::I64(n) => {
663 ValueWord::from_native_scalar(shape_value::heap_value::NativeScalar::I64(*n))
664 }
665 WireValue::U64(n) => ValueWord::from_native_u64(*n),
666 WireValue::Isize(n) => match isize::try_from(*n) {
667 Ok(v) => ValueWord::from_native_isize(v),
668 Err(_) => ValueWord::none(),
669 },
670 WireValue::Usize(n) => match usize::try_from(*n) {
671 Ok(v) => ValueWord::from_native_usize(v),
672 Err(_) => ValueWord::none(),
673 },
674 WireValue::Ptr(n) => match usize::try_from(*n) {
675 Ok(v) => ValueWord::from_native_scalar(shape_value::heap_value::NativeScalar::Ptr(v)),
676 Err(_) => ValueWord::none(),
677 },
678 WireValue::F32(n) => ValueWord::from_native_f32(*n),
679 WireValue::String(s) => ValueWord::from_string(Arc::new(s.clone())),
680
681 WireValue::Timestamp(ts) => match chrono::DateTime::from_timestamp_millis(*ts) {
682 Some(dt) => ValueWord::from_time_utc(dt),
683 None => ValueWord::none(),
684 },
685
686 WireValue::Duration { value, unit } => {
687 let (ast_value, ast_unit) = match unit {
688 WireDurationUnit::Nanoseconds => (
689 *value / 1_000_000_000.0,
690 shape_ast::ast::DurationUnit::Seconds,
691 ),
692 WireDurationUnit::Microseconds => {
693 (*value / 1_000_000.0, shape_ast::ast::DurationUnit::Seconds)
694 }
695 WireDurationUnit::Milliseconds => {
696 (*value / 1_000.0, shape_ast::ast::DurationUnit::Seconds)
697 }
698 WireDurationUnit::Seconds => (*value, shape_ast::ast::DurationUnit::Seconds),
699 WireDurationUnit::Minutes => (*value, shape_ast::ast::DurationUnit::Minutes),
700 WireDurationUnit::Hours => (*value, shape_ast::ast::DurationUnit::Hours),
701 WireDurationUnit::Days => (*value, shape_ast::ast::DurationUnit::Days),
702 WireDurationUnit::Weeks => (*value, shape_ast::ast::DurationUnit::Weeks),
703 };
704 ValueWord::from_duration(shape_ast::ast::Duration {
705 value: ast_value,
706 unit: ast_unit,
707 })
708 }
709
710 WireValue::Array(arr) => {
711 let elements: Vec<ValueWord> = arr.iter().map(wire_to_nb).collect();
712 ValueWord::from_array(Arc::new(elements))
713 }
714
715 WireValue::Object(obj) => {
716 let enum_name = obj.get("__enum").and_then(|v| match v {
718 WireValue::String(s) => Some(s.clone()),
719 _ => None,
720 });
721 let variant = obj.get("__variant").and_then(|v| match v {
722 WireValue::String(s) => Some(s.clone()),
723 _ => None,
724 });
725
726 if let (Some(enum_name), Some(variant)) = (enum_name, variant) {
727 let payload = match obj.get("__fields") {
728 None => shape_value::EnumPayload::Unit,
729 Some(WireValue::Array(values)) => {
730 shape_value::EnumPayload::Tuple(values.iter().map(wire_to_nb).collect())
731 }
732 Some(WireValue::Object(fields)) => {
733 let map: std::collections::HashMap<String, ValueWord> = fields
734 .iter()
735 .map(|(k, v)| (k.clone(), wire_to_nb(v)))
736 .collect();
737 shape_value::EnumPayload::Struct(map)
738 }
739 _ => shape_value::EnumPayload::Unit,
740 };
741 ValueWord::from_enum(shape_value::EnumValue {
742 enum_name,
743 variant,
744 payload,
745 })
746 } else {
747 let pairs: Vec<(String, ValueWord)> = obj
749 .iter()
750 .map(|(k, v)| (k.clone(), wire_to_nb(v)))
751 .collect();
752 let pair_refs: Vec<(&str, ValueWord)> =
753 pairs.iter().map(|(k, v)| (k.as_str(), v.clone())).collect();
754 crate::type_schema::typed_object_from_nb_pairs(&pair_refs)
755 }
756 }
757
758 WireValue::Table(table) => {
759 match datatable_from_ipc_bytes(
760 &table.ipc_bytes,
761 table.type_name.as_deref(),
762 table.schema_id,
763 ) {
764 Ok(dt) => ValueWord::from_datatable(Arc::new(dt)),
765 Err(_) => ValueWord::none(),
766 }
767 }
768
769 WireValue::Result { ok, value } => {
770 let inner = wire_to_nb(value);
771 if *ok {
772 ValueWord::from_ok(inner)
773 } else {
774 ValueWord::from_err(inner)
775 }
776 }
777
778 WireValue::Range {
779 start,
780 end,
781 inclusive,
782 } => ValueWord::from_range(
783 start.as_ref().map(|v| wire_to_nb(v)),
784 end.as_ref().map(|v| wire_to_nb(v)),
785 *inclusive,
786 ),
787
788 WireValue::FunctionRef { name } => ValueWord::from_function_ref(name.clone(), None),
789
790 WireValue::PrintResult(result) => {
791 ValueWord::from_string(Arc::new(result.rendered.clone()))
793 }
794
795 WireValue::Content(node) => {
796 ValueWord::from_heap_value(HeapValue::Content(Box::new(node.clone())))
797 }
798 }
799}
800
801pub fn nb_to_envelope(nb: &ValueWord, type_name: &str, ctx: &Context) -> ValueEnvelope {
803 let wire_value = nb_to_wire(nb, ctx);
804 let type_info = TypeInfo::primitive(type_name);
805 let registry = TypeRegistry::new("Default");
806 ValueEnvelope::new(wire_value, type_info, registry)
807}
808
809pub fn nb_extract_typed_value(nb: &ValueWord, ctx: &Context) -> (WireValue, Option<TypeInfo>) {
813 let wire_value = nb_to_wire(nb, ctx);
814 let type_name = nb.type_name();
815 let type_info = TypeInfo::primitive(type_name);
816 (wire_value, Some(type_info))
817}
818
819pub fn nb_typed_value_to_envelope(nb: &ValueWord, ctx: &Context) -> ValueEnvelope {
823 let type_name = nb.type_name();
824 nb_to_envelope(nb, type_name, ctx)
825}
826
827fn enum_to_wire(enum_value: &shape_value::EnumValue, ctx: &Context) -> WireValue {
829 let mut obj = BTreeMap::new();
830 obj.insert(
831 "__enum".to_string(),
832 WireValue::String(enum_value.enum_name.clone()),
833 );
834 obj.insert(
835 "__variant".to_string(),
836 WireValue::String(enum_value.variant.clone()),
837 );
838 match &enum_value.payload {
839 shape_value::EnumPayload::Unit => {}
840 shape_value::EnumPayload::Tuple(values) => {
841 obj.insert(
842 "__fields".to_string(),
843 WireValue::Array(values.iter().map(|v| nb_to_wire(v, ctx)).collect()),
844 );
845 }
846 shape_value::EnumPayload::Struct(fields) => {
847 let field_map: BTreeMap<String, WireValue> = fields
848 .iter()
849 .map(|(k, v)| (k.clone(), nb_to_wire(v, ctx)))
850 .collect();
851 obj.insert("__fields".to_string(), WireValue::Object(field_map));
852 }
853 }
854 WireValue::Object(obj)
855}
856
857fn datatable_to_wire(dt: &DataTable) -> WireValue {
858 datatable_to_wire_with_schema(dt, dt.schema_id())
859}
860
861fn datatable_to_wire_with_schema(dt: &DataTable, schema_id: Option<u32>) -> WireValue {
862 match datatable_to_ipc_bytes(dt) {
863 Ok(ipc_bytes) => WireValue::Table(WireTable {
864 ipc_bytes,
865 type_name: dt.type_name().map(|s| s.to_string()),
866 schema_id,
867 row_count: dt.row_count(),
868 column_count: dt.column_count(),
869 }),
870 Err(_) => WireValue::String(format!("{}", dt)),
871 }
872}
873
874pub fn datatable_to_ipc_bytes(dt: &DataTable) -> std::result::Result<Vec<u8>, String> {
876 let mut buf = Vec::new();
877 let schema = dt.inner().schema();
878 let mut writer = FileWriter::try_new(&mut buf, schema.as_ref())
879 .map_err(|e| format!("failed to create Arrow IPC writer: {e}"))?;
880 writer
881 .write(dt.inner())
882 .map_err(|e| format!("failed to write Arrow IPC batch: {e}"))?;
883 writer
884 .finish()
885 .map_err(|e| format!("failed to finalize Arrow IPC writer: {e}"))?;
886 Ok(buf)
887}
888
889pub fn datatable_from_ipc_bytes(
891 ipc_bytes: &[u8],
892 type_name: Option<&str>,
893 schema_id: Option<u32>,
894) -> std::result::Result<DataTable, String> {
895 if ipc_bytes.is_empty() {
896 return Err("empty Arrow IPC payload".to_string());
897 }
898
899 let cursor = std::io::Cursor::new(ipc_bytes);
900 let mut reader = FileReader::try_new(cursor, None)
901 .map_err(|e| format!("failed to create Arrow IPC reader: {e}"))?;
902 let batch = reader
903 .next()
904 .transpose()
905 .map_err(|e| format!("failed reading Arrow IPC batch: {e}"))?
906 .ok_or_else(|| "Arrow IPC payload has no record batches".to_string())?;
907
908 let mut dt = DataTable::new(batch);
909 if let Some(name) = type_name {
910 dt = DataTable::with_type_name(dt.into_inner(), name.to_string());
911 }
912 if let Some(id) = schema_id {
913 dt = dt.with_schema_id(id);
914 }
915 Ok(dt)
916}
917
918fn infer_metadata_with_ctx(
921 _value: &ValueWord,
922 type_name: &str,
923 _ctx: &Context,
924) -> (TypeInfo, TypeRegistry) {
925 let type_info = TypeInfo::primitive(type_name);
926 let registry = TypeRegistry::new("Default");
927 (type_info, registry)
928}
929
930#[cfg(test)]
931mod tests {
932 use super::*;
933 use crate::type_methods::TypeMethodRegistry;
934 use crate::type_schema::typed_object_to_hashmap_nb;
935 use shape_value::ValueSlot;
936 use shape_value::heap_value::HeapValue;
937 use std::sync::Arc;
938
939 fn get_dummy_context() -> Context {
940 Context::new_empty_with_registry(Arc::new(TypeMethodRegistry::new()))
941 }
942
943 #[test]
944 fn test_basic_value_conversion() {
945 let ctx = get_dummy_context();
946 let wire = value_to_wire(&ValueWord::from_f64(42.5), &ctx);
948 assert_eq!(wire, WireValue::Number(42.5));
949
950 let wire = value_to_wire(&ValueWord::from_f64(42.0), &ctx);
952 assert_eq!(wire, WireValue::Number(42.0));
953
954 let wire = value_to_wire(&ValueWord::from_string(Arc::new("hello".to_string())), &ctx);
956 assert_eq!(wire, WireValue::String("hello".to_string()));
957
958 let wire = value_to_wire(&ValueWord::from_bool(true), &ctx);
960 assert_eq!(wire, WireValue::Bool(true));
961
962 let wire = value_to_wire(&ValueWord::none(), &ctx);
964 assert_eq!(wire, WireValue::Null);
965 }
966
967 #[test]
968 fn test_array_conversion() {
969 let ctx = get_dummy_context();
970 let arr = ValueWord::from_array(Arc::new(vec![
971 ValueWord::from_f64(1.0),
972 ValueWord::from_f64(2.0),
973 ValueWord::from_f64(3.0),
974 ]));
975 let wire = value_to_wire(&arr, &ctx);
976
977 if let WireValue::Array(items) = wire {
978 assert_eq!(items.len(), 3);
979 assert_eq!(items[0], WireValue::Number(1.0));
980 assert_eq!(items[1], WireValue::Number(2.0));
981 assert_eq!(items[2], WireValue::Number(3.0));
982 } else {
983 panic!("Expected Array");
984 }
985 }
986
987 #[test]
988 fn test_object_conversion() {
989 let ctx = get_dummy_context();
990 let obj = crate::type_schema::typed_object_from_pairs(&[
991 ("x", ValueWord::from_f64(10.0)),
992 ("y", ValueWord::from_f64(20.0)),
993 ]);
994
995 let wire = value_to_wire(&obj, &ctx);
996
997 if let WireValue::Object(map) = wire {
998 assert_eq!(map.get("x"), Some(&WireValue::Number(10.0)));
999 assert_eq!(map.get("y"), Some(&WireValue::Number(20.0)));
1000 } else {
1001 panic!("Expected Object");
1002 }
1003 }
1004
1005 #[test]
1006 fn test_builtin_typed_object_converts_to_wire_object() {
1007 let ctx = get_dummy_context();
1008 let any_error_schema_id = ctx
1009 .type_schema_registry()
1010 .get("__AnyError")
1011 .expect("__AnyError schema should exist")
1012 .id as u64;
1013
1014 let slots = vec![
1015 ValueSlot::from_heap(HeapValue::String(Arc::new("AnyError".to_string()))),
1016 ValueSlot::from_heap(HeapValue::String(Arc::new("boom".to_string()))),
1017 ValueSlot::none(), ValueSlot::none(), ValueSlot::from_heap(HeapValue::String(Arc::new("boom".to_string()))),
1020 ValueSlot::from_heap(HeapValue::String(Arc::new("E_BANG".to_string()))),
1021 ];
1022
1023 let nb = ValueWord::from_heap_value(HeapValue::TypedObject {
1024 schema_id: any_error_schema_id,
1025 slots: slots.into_boxed_slice(),
1026 heap_mask: 0b11_0011, });
1028
1029 let wire = nb_to_wire(&nb, &ctx);
1030 match wire {
1031 WireValue::Object(map) => {
1032 assert_eq!(
1033 map.get("category"),
1034 Some(&WireValue::String("AnyError".into()))
1035 );
1036 assert_eq!(map.get("message"), Some(&WireValue::String("boom".into())));
1037 assert_eq!(map.get("code"), Some(&WireValue::String("E_BANG".into())));
1038 }
1039 other => panic!("Expected WireValue::Object, got {:?}", other),
1040 }
1041 }
1042
1043 #[test]
1044 fn test_unknown_schema_anyerror_uses_builtin_fallback_decoder() {
1045 let ctx = get_dummy_context();
1046
1047 let slots = vec![
1048 ValueSlot::from_heap(HeapValue::String(Arc::new("AnyError".to_string()))),
1049 ValueSlot::from_heap(HeapValue::String(Arc::new("boom".to_string()))),
1050 ValueSlot::none(), ValueSlot::none(), ValueSlot::from_heap(HeapValue::String(Arc::new("boom".to_string()))),
1053 ValueSlot::from_heap(HeapValue::String(Arc::new("E_BANG".to_string()))),
1054 ];
1055
1056 let nb = ValueWord::from_heap_value(HeapValue::TypedObject {
1057 schema_id: 9_999_999,
1058 slots: slots.into_boxed_slice(),
1059 heap_mask: 0b11_0011, });
1061
1062 let wire = nb_to_wire(&nb, &ctx);
1063 match wire {
1064 WireValue::Object(map) => {
1065 assert_eq!(
1066 map.get("category"),
1067 Some(&WireValue::String("AnyError".into()))
1068 );
1069 assert_eq!(map.get("message"), Some(&WireValue::String("boom".into())));
1070 assert_eq!(map.get("code"), Some(&WireValue::String("E_BANG".into())));
1071 }
1072 other => panic!("Expected WireValue::Object, got {:?}", other),
1073 }
1074 }
1075
1076 #[test]
1077 fn test_timestamp_conversion() {
1078 let ctx = get_dummy_context();
1079 use chrono::TimeZone;
1080 let dt = chrono::Utc
1081 .with_ymd_and_hms(2024, 1, 15, 10, 30, 0)
1082 .unwrap();
1083 let wire = value_to_wire(&ValueWord::from_time_utc(dt), &ctx);
1084
1085 if let WireValue::Timestamp(ts) = wire {
1086 assert_eq!(ts, 1705314600000);
1087 } else {
1088 panic!("Expected Timestamp");
1089 }
1090 }
1091
1092 #[test]
1093 fn test_result_conversion() {
1094 let ctx = get_dummy_context();
1095 let ok_val = ValueWord::from_ok(ValueWord::from_f64(42.0));
1096 let wire = value_to_wire(&ok_val, &ctx);
1097
1098 if let WireValue::Result { ok, value } = wire {
1099 assert!(ok);
1100 assert_eq!(*value, WireValue::Number(42.0));
1101 } else {
1102 panic!("Expected Result");
1103 }
1104 }
1105
1106 #[test]
1107 fn test_wire_to_nb_anyerror_trace_frame_key_order_is_stable() {
1108 use std::collections::BTreeMap;
1109
1110 let mut frame = BTreeMap::new();
1111 frame.insert(
1112 "function".to_string(),
1113 WireValue::String("duckdb.connect".to_string()),
1114 );
1115 frame.insert(
1116 "file".to_string(),
1117 WireValue::String("extension:duckdb".to_string()),
1118 );
1119 frame.insert("line".to_string(), WireValue::Null);
1120 frame.insert("ip".to_string(), WireValue::Null);
1121
1122 let nb = wire_to_nb(&WireValue::Object(frame));
1123 let decoded =
1124 typed_object_to_hashmap_nb(&nb).expect("Trace frame wire object should decode");
1125 assert_eq!(
1126 decoded.get("function").and_then(|v| v.as_str()),
1127 Some("duckdb.connect")
1128 );
1129 assert_eq!(
1130 decoded.get("file").and_then(|v| v.as_str()),
1131 Some("extension:duckdb")
1132 );
1133 }
1134
1135 #[test]
1136 fn test_any_error_result_roundtrip() {
1137 use crate::type_schema::typed_object_from_pairs;
1138 let ctx = get_dummy_context();
1139
1140 let empty_trace = typed_object_from_pairs(&[]);
1141 let cause = typed_object_from_pairs(&[
1142 (
1143 "category",
1144 ValueWord::from_string(Arc::new("AnyError".to_string())),
1145 ),
1146 (
1147 "payload",
1148 ValueWord::from_string(Arc::new("low level".to_string())),
1149 ),
1150 ("cause", ValueWord::none()),
1151 ("trace_info", empty_trace),
1152 ]);
1153
1154 let empty_trace2 = typed_object_from_pairs(&[]);
1155 let outer = typed_object_from_pairs(&[
1156 (
1157 "category",
1158 ValueWord::from_string(Arc::new("AnyError".to_string())),
1159 ),
1160 (
1161 "payload",
1162 ValueWord::from_string(Arc::new("high level".to_string())),
1163 ),
1164 ("cause", cause),
1165 ("trace_info", empty_trace2),
1166 (
1167 "code",
1168 ValueWord::from_string(Arc::new("OPTION_NONE".to_string())),
1169 ),
1170 ]);
1171
1172 let err = ValueWord::from_err(outer);
1173 let wire = value_to_wire(&err, &ctx);
1174
1175 let WireValue::Result { ok, value } = &wire else {
1176 panic!("Expected wire Result");
1177 };
1178 assert!(!ok);
1179 match value.as_ref() {
1180 WireValue::Object(map) => {
1181 assert_eq!(
1182 map.get("category"),
1183 Some(&WireValue::String("AnyError".to_string()))
1184 );
1185 assert_eq!(
1186 map.get("code"),
1187 Some(&WireValue::String("OPTION_NONE".to_string()))
1188 );
1189 }
1190 other => panic!("Expected AnyError object payload, got {:?}", other),
1191 }
1192
1193 let roundtrip = wire_to_nb(&wire);
1194 let hv = roundtrip.as_heap_ref().expect("Expected heap value");
1195 match hv {
1196 HeapValue::Err(inner) => {
1197 let inner_hv = inner.as_heap_ref().expect("Expected heap inner");
1198 assert!(
1199 matches!(inner_hv, HeapValue::TypedObject { .. }),
1200 "Expected TypedObject inside Err"
1201 );
1202 }
1203 other => panic!("Expected Err, got {:?}", other.kind()),
1204 }
1205 }
1206
1207 #[test]
1208 fn test_envelope_creation() {
1209 let ctx = get_dummy_context();
1210 let nb = ValueWord::from_f64(3.14);
1211 let envelope = nb_to_envelope(&nb, "number", &ctx);
1212
1213 match &envelope.value {
1214 WireValue::Number(n) => assert!((*n - 3.14).abs() < f64::EPSILON),
1215 other => panic!("Expected Number, got {:?}", other),
1216 }
1217 }
1218
1219 #[test]
1220 fn test_roundtrip_basic() {
1221 let ctx = get_dummy_context();
1222 let nb = ValueWord::from_string(Arc::new("test".to_string()));
1223 let wire = nb_to_wire(&nb, &ctx);
1224 let back = wire_to_nb(&wire);
1225
1226 if let Some(s) = back.as_str() {
1227 assert_eq!(s, "test");
1228 } else {
1229 panic!("Expected String");
1230 }
1231 }
1232
1233 #[test]
1236 fn test_nb_to_wire_basic_types() {
1237 let ctx = get_dummy_context();
1238
1239 let wire = nb_to_wire(&ValueWord::from_f64(42.5), &ctx);
1241 assert_eq!(wire, WireValue::Number(42.5));
1242
1243 let wire = nb_to_wire(&ValueWord::from_f64(42.0), &ctx);
1245 assert_eq!(wire, WireValue::Number(42.0));
1246
1247 let wire = nb_to_wire(&ValueWord::from_i64(99), &ctx);
1249 assert_eq!(wire, WireValue::Integer(99));
1250
1251 let wire = nb_to_wire(&ValueWord::from_i64(-7), &ctx);
1253 assert_eq!(wire, WireValue::Integer(-7));
1254
1255 let wire = nb_to_wire(&ValueWord::from_bool(true), &ctx);
1257 assert_eq!(wire, WireValue::Bool(true));
1258
1259 let wire = nb_to_wire(&ValueWord::from_bool(false), &ctx);
1260 assert_eq!(wire, WireValue::Bool(false));
1261
1262 let wire = nb_to_wire(&ValueWord::none(), &ctx);
1264 assert_eq!(wire, WireValue::Null);
1265
1266 let wire = nb_to_wire(&ValueWord::unit(), &ctx);
1268 assert_eq!(wire, WireValue::Null);
1269 }
1270
1271 #[test]
1272 fn test_nb_to_wire_string() {
1273 let ctx = get_dummy_context();
1274 let nb = ValueWord::from_string(Arc::new("hello".to_string()));
1275 let wire = nb_to_wire(&nb, &ctx);
1276 assert_eq!(wire, WireValue::String("hello".to_string()));
1277 }
1278
1279 #[test]
1280 fn test_nb_to_wire_array() {
1281 let ctx = get_dummy_context();
1282 let nb = ValueWord::from_array(Arc::new(vec![
1283 ValueWord::from_f64(1.0),
1284 ValueWord::from_i64(2),
1285 ValueWord::from_bool(true),
1286 ]));
1287 let wire = nb_to_wire(&nb, &ctx);
1288
1289 if let WireValue::Array(items) = wire {
1290 assert_eq!(items.len(), 3);
1291 assert_eq!(items[0], WireValue::Number(1.0));
1292 assert_eq!(items[1], WireValue::Integer(2));
1293 assert_eq!(items[2], WireValue::Bool(true));
1294 } else {
1295 panic!("Expected Array");
1296 }
1297 }
1298
1299 #[test]
1300 fn test_nb_to_wire_result() {
1301 let ctx = get_dummy_context();
1302
1303 let ok = ValueWord::from_ok(ValueWord::from_i64(42));
1305 let wire = nb_to_wire(&ok, &ctx);
1306 if let WireValue::Result { ok, value } = wire {
1307 assert!(ok);
1308 assert_eq!(*value, WireValue::Integer(42));
1309 } else {
1310 panic!("Expected Result");
1311 }
1312
1313 let err = ValueWord::from_err(ValueWord::from_string(Arc::new("oops".to_string())));
1315 let wire = nb_to_wire(&err, &ctx);
1316 if let WireValue::Result { ok, value } = wire {
1317 assert!(!ok);
1318 assert_eq!(*value, WireValue::String("oops".to_string()));
1319 } else {
1320 panic!("Expected Result");
1321 }
1322 }
1323
1324 #[test]
1325 fn test_nb_to_wire_some() {
1326 let ctx = get_dummy_context();
1327 let some = ValueWord::from_some(ValueWord::from_f64(3.14));
1328 let wire = nb_to_wire(&some, &ctx);
1329 assert_eq!(wire, WireValue::Number(3.14));
1331 }
1332
1333 #[test]
1334 fn test_nb_to_wire_matches_vmvalue() {
1335 let ctx = get_dummy_context();
1337
1338 let test_values: Vec<ValueWord> = vec![
1339 ValueWord::from_f64(42.5),
1340 ValueWord::from_f64(42.0),
1341 ValueWord::from_i64(100),
1342 ValueWord::from_i64(-100),
1343 ValueWord::from_bool(true),
1344 ValueWord::from_bool(false),
1345 ValueWord::none(),
1346 ValueWord::unit(),
1347 ValueWord::from_string(Arc::new("test".to_string())),
1348 ValueWord::from_array(Arc::new(vec![
1349 ValueWord::from_f64(1.0),
1350 ValueWord::from_i64(2),
1351 ])),
1352 ];
1353
1354 for nb in &test_values {
1355 let vmv = nb.clone();
1356 let wire_from_vmv = value_to_wire(&vmv, &ctx);
1357 let wire_from_nb = nb_to_wire(nb, &ctx);
1358 assert_eq!(
1359 wire_from_vmv,
1360 wire_from_nb,
1361 "Mismatch for ValueWord type {:?}: ValueWord path = {:?}, ValueWord path = {:?}",
1362 nb.tag(),
1363 wire_from_vmv,
1364 wire_from_nb
1365 );
1366 }
1367 }
1368
1369 #[test]
1370 fn test_wire_to_nb_basic_types() {
1371 let nb = wire_to_nb(&WireValue::Null);
1373 assert!(nb.is_none());
1374
1375 let nb = wire_to_nb(&WireValue::Bool(true));
1377 assert_eq!(nb.as_bool(), Some(true));
1378
1379 let nb = wire_to_nb(&WireValue::Number(3.14));
1381 assert_eq!(nb.as_f64(), Some(3.14));
1382
1383 let nb = wire_to_nb(&WireValue::Integer(42));
1385 assert_eq!(nb.as_i64(), Some(42));
1386
1387 let nb = wire_to_nb(&WireValue::String("hello".to_string()));
1389 assert_eq!(nb.as_str(), Some("hello"));
1390 }
1391
1392 #[test]
1393 fn test_wire_to_nb_array() {
1394 let wire = WireValue::Array(vec![
1395 WireValue::Integer(1),
1396 WireValue::Number(2.5),
1397 WireValue::Bool(false),
1398 ]);
1399 let nb = wire_to_nb(&wire);
1400 let arr = nb.as_any_array().expect("Expected array").to_generic();
1401 assert_eq!(arr.len(), 3);
1402 assert_eq!(arr[0].as_i64(), Some(1));
1403 assert_eq!(arr[1].as_f64(), Some(2.5));
1404 assert_eq!(arr[2].as_bool(), Some(false));
1405 }
1406
1407 #[test]
1408 fn test_wire_to_nb_result() {
1409 let wire = WireValue::Result {
1411 ok: true,
1412 value: Box::new(WireValue::Integer(7)),
1413 };
1414 let nb = wire_to_nb(&wire);
1415 let inner = nb.as_ok_inner().expect("Expected Ok");
1416 assert_eq!(inner.as_i64(), Some(7));
1417
1418 let wire = WireValue::Result {
1420 ok: false,
1421 value: Box::new(WireValue::String("fail".to_string())),
1422 };
1423 let nb = wire_to_nb(&wire);
1424 let inner = nb.as_err_inner().expect("Expected Err");
1425 assert_eq!(inner.as_str(), Some("fail"));
1426 }
1427
1428 #[test]
1429 fn test_nb_roundtrip_basic() {
1430 let ctx = get_dummy_context();
1431
1432 let original = ValueWord::from_string(Arc::new("roundtrip".to_string()));
1434 let wire = nb_to_wire(&original, &ctx);
1435 let back = wire_to_nb(&wire);
1436 assert_eq!(back.as_str(), Some("roundtrip"));
1437
1438 let original = ValueWord::from_i64(12345);
1440 let wire = nb_to_wire(&original, &ctx);
1441 let back = wire_to_nb(&wire);
1442 assert_eq!(back.as_i64(), Some(12345));
1443
1444 let original = ValueWord::from_f64(2.718);
1446 let wire = nb_to_wire(&original, &ctx);
1447 let back = wire_to_nb(&wire);
1448 assert_eq!(back.as_f64(), Some(2.718));
1449
1450 let original = ValueWord::from_bool(true);
1452 let wire = nb_to_wire(&original, &ctx);
1453 let back = wire_to_nb(&wire);
1454 assert_eq!(back.as_bool(), Some(true));
1455
1456 let wire = nb_to_wire(&ValueWord::none(), &ctx);
1458 let back = wire_to_nb(&wire);
1459 assert!(back.is_none());
1460 }
1461
1462 #[test]
1463 fn test_nb_roundtrip_array() {
1464 let ctx = get_dummy_context();
1465 let original = ValueWord::from_array(Arc::new(vec![
1466 ValueWord::from_i64(10),
1467 ValueWord::from_f64(20.5),
1468 ValueWord::from_string(Arc::new("x".to_string())),
1469 ]));
1470 let wire = nb_to_wire(&original, &ctx);
1471 let back = wire_to_nb(&wire);
1472 let arr = back.as_any_array().expect("Expected array").to_generic();
1473 assert_eq!(arr.len(), 3);
1474 assert_eq!(arr[0].as_i64(), Some(10));
1475 assert_eq!(arr[1].as_f64(), Some(20.5));
1476 assert_eq!(arr[2].as_str(), Some("x"));
1477 }
1478
1479 #[test]
1480 fn test_nb_to_wire_decimal() {
1481 let ctx = get_dummy_context();
1482 let d = rust_decimal::Decimal::new(314, 2); let nb = ValueWord::from_decimal(d);
1484 let wire = nb_to_wire(&nb, &ctx);
1485 assert_eq!(wire, WireValue::Number(3.14));
1486 }
1487
1488 #[test]
1489 fn test_nb_to_wire_typed_object() {
1490 let ctx = get_dummy_context();
1491 let obj = crate::type_schema::typed_object_from_pairs(&[
1492 ("a", ValueWord::from_f64(1.0)),
1493 ("b", ValueWord::from_string(Arc::new("two".to_string()))),
1494 ]);
1495 let nb = obj;
1496 let wire = nb_to_wire(&nb, &ctx);
1497
1498 if let WireValue::Object(map) = wire {
1499 assert_eq!(map.get("a"), Some(&WireValue::Number(1.0)));
1500 assert_eq!(map.get("b"), Some(&WireValue::String("two".to_string())));
1501 } else {
1502 panic!("Expected Object, got {:?}", wire);
1503 }
1504 }
1505
1506 #[test]
1507 fn test_nb_envelope_creation() {
1508 let ctx = get_dummy_context();
1509 let nb = ValueWord::from_f64(3.14);
1510 let envelope = nb_to_envelope(&nb, "Number", &ctx);
1511 assert_eq!(envelope.type_info.name, "Number");
1512 }
1513
1514 #[test]
1515 fn test_native_scalar_wire_roundtrip_preserves_width() {
1516 let ctx = get_dummy_context();
1517 let cases = vec![
1518 (
1519 ValueWord::from_native_i8(-8),
1520 WireValue::I8(-8),
1521 shape_value::heap_value::NativeScalar::I8(-8),
1522 ),
1523 (
1524 ValueWord::from_native_u8(255),
1525 WireValue::U8(255),
1526 shape_value::heap_value::NativeScalar::U8(255),
1527 ),
1528 (
1529 ValueWord::from_native_i16(-1024),
1530 WireValue::I16(-1024),
1531 shape_value::heap_value::NativeScalar::I16(-1024),
1532 ),
1533 (
1534 ValueWord::from_native_u16(65530),
1535 WireValue::U16(65530),
1536 shape_value::heap_value::NativeScalar::U16(65530),
1537 ),
1538 (
1539 ValueWord::from_native_i32(-123_456),
1540 WireValue::I32(-123_456),
1541 shape_value::heap_value::NativeScalar::I32(-123_456),
1542 ),
1543 (
1544 ValueWord::from_native_u32(4_000_000_000),
1545 WireValue::U32(4_000_000_000),
1546 shape_value::heap_value::NativeScalar::U32(4_000_000_000),
1547 ),
1548 (
1549 ValueWord::from_native_scalar(shape_value::heap_value::NativeScalar::I64(
1550 -9_223_372_036_854_775_000,
1551 )),
1552 WireValue::I64(-9_223_372_036_854_775_000),
1553 shape_value::heap_value::NativeScalar::I64(-9_223_372_036_854_775_000),
1554 ),
1555 (
1556 ValueWord::from_native_u64(18_000_000_000),
1557 WireValue::U64(18_000_000_000),
1558 shape_value::heap_value::NativeScalar::U64(18_000_000_000),
1559 ),
1560 (
1561 ValueWord::from_native_isize(12345isize),
1562 WireValue::Isize(12345),
1563 shape_value::heap_value::NativeScalar::Isize(12345isize),
1564 ),
1565 (
1566 ValueWord::from_native_usize(54321usize),
1567 WireValue::Usize(54321),
1568 shape_value::heap_value::NativeScalar::Usize(54321usize),
1569 ),
1570 (
1571 ValueWord::from_native_ptr(0x1234usize),
1572 WireValue::Ptr(0x1234),
1573 shape_value::heap_value::NativeScalar::Ptr(0x1234usize),
1574 ),
1575 (
1576 ValueWord::from_native_f32(3.5f32),
1577 WireValue::F32(3.5f32),
1578 shape_value::heap_value::NativeScalar::F32(3.5f32),
1579 ),
1580 ];
1581
1582 for (nb, expected_wire, expected_scalar) in cases {
1583 let wire = nb_to_wire(&nb, &ctx);
1584 assert_eq!(wire, expected_wire);
1585
1586 let roundtrip = wire_to_nb(&wire);
1587 assert_eq!(roundtrip.as_native_scalar(), Some(expected_scalar));
1588 }
1589 }
1590}