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