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::String(format!("{}", node)),
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 fn nb_extract_content(
587 nb: &ValueWord,
588) -> (Option<serde_json::Value>, Option<String>, Option<String>) {
589 use crate::content_renderer::ContentRenderer;
590
591 let node: Option<shape_value::content::ContentNode> =
593 if let Some(HeapValue::Content(node)) = nb.as_heap_ref() {
594 Some(node.as_ref().clone())
595 } else if let Some(HeapValue::DataTable(dt)) = nb.as_heap_ref() {
596 Some(crate::content_dispatch::datatable_to_content_node(dt, None))
598 } else if let Some(HeapValue::TypedTable { table, .. }) = nb.as_heap_ref() {
599 Some(crate::content_dispatch::datatable_to_content_node(
600 table, None,
601 ))
602 } else {
603 None
604 };
605
606 if let Some(ref node) = node {
607 let json_renderer = crate::renderers::json::JsonRenderer;
608 let html_renderer = crate::renderers::html::HtmlRenderer::new();
609 let terminal_renderer = crate::renderers::terminal::TerminalRenderer::new();
610
611 let json_str = json_renderer.render(node);
612 let json_value = serde_json::from_str(&json_str).unwrap_or(serde_json::Value::Null);
613 let html_str = html_renderer.render(node);
614 let terminal_str = terminal_renderer.render(node);
615
616 (Some(json_value), Some(html_str), Some(terminal_str))
617 } else {
618 (None, None, None)
619 }
620}
621
622pub fn wire_to_nb(wire: &WireValue) -> ValueWord {
627 match wire {
628 WireValue::Null => ValueWord::none(),
629 WireValue::Bool(b) => ValueWord::from_bool(*b),
630 WireValue::Number(n) => ValueWord::from_f64(*n),
631 WireValue::Integer(i) => ValueWord::from_i64(*i),
632 WireValue::I8(n) => ValueWord::from_native_i8(*n),
633 WireValue::U8(n) => ValueWord::from_native_u8(*n),
634 WireValue::I16(n) => ValueWord::from_native_i16(*n),
635 WireValue::U16(n) => ValueWord::from_native_u16(*n),
636 WireValue::I32(n) => ValueWord::from_native_i32(*n),
637 WireValue::U32(n) => ValueWord::from_native_u32(*n),
638 WireValue::I64(n) => {
639 ValueWord::from_native_scalar(shape_value::heap_value::NativeScalar::I64(*n))
640 }
641 WireValue::U64(n) => ValueWord::from_native_u64(*n),
642 WireValue::Isize(n) => match isize::try_from(*n) {
643 Ok(v) => ValueWord::from_native_isize(v),
644 Err(_) => ValueWord::none(),
645 },
646 WireValue::Usize(n) => match usize::try_from(*n) {
647 Ok(v) => ValueWord::from_native_usize(v),
648 Err(_) => ValueWord::none(),
649 },
650 WireValue::Ptr(n) => match usize::try_from(*n) {
651 Ok(v) => ValueWord::from_native_scalar(shape_value::heap_value::NativeScalar::Ptr(v)),
652 Err(_) => ValueWord::none(),
653 },
654 WireValue::F32(n) => ValueWord::from_native_f32(*n),
655 WireValue::String(s) => ValueWord::from_string(Arc::new(s.clone())),
656
657 WireValue::Timestamp(ts) => match chrono::DateTime::from_timestamp_millis(*ts) {
658 Some(dt) => ValueWord::from_time_utc(dt),
659 None => ValueWord::none(),
660 },
661
662 WireValue::Duration { value, unit } => {
663 let (ast_value, ast_unit) = match unit {
664 WireDurationUnit::Nanoseconds => (
665 *value / 1_000_000_000.0,
666 shape_ast::ast::DurationUnit::Seconds,
667 ),
668 WireDurationUnit::Microseconds => {
669 (*value / 1_000_000.0, shape_ast::ast::DurationUnit::Seconds)
670 }
671 WireDurationUnit::Milliseconds => {
672 (*value / 1_000.0, shape_ast::ast::DurationUnit::Seconds)
673 }
674 WireDurationUnit::Seconds => (*value, shape_ast::ast::DurationUnit::Seconds),
675 WireDurationUnit::Minutes => (*value, shape_ast::ast::DurationUnit::Minutes),
676 WireDurationUnit::Hours => (*value, shape_ast::ast::DurationUnit::Hours),
677 WireDurationUnit::Days => (*value, shape_ast::ast::DurationUnit::Days),
678 WireDurationUnit::Weeks => (*value, shape_ast::ast::DurationUnit::Weeks),
679 };
680 ValueWord::from_duration(shape_ast::ast::Duration {
681 value: ast_value,
682 unit: ast_unit,
683 })
684 }
685
686 WireValue::Array(arr) => {
687 let elements: Vec<ValueWord> = arr.iter().map(wire_to_nb).collect();
688 ValueWord::from_array(Arc::new(elements))
689 }
690
691 WireValue::Object(obj) => {
692 let enum_name = obj.get("__enum").and_then(|v| match v {
694 WireValue::String(s) => Some(s.clone()),
695 _ => None,
696 });
697 let variant = obj.get("__variant").and_then(|v| match v {
698 WireValue::String(s) => Some(s.clone()),
699 _ => None,
700 });
701
702 if let (Some(enum_name), Some(variant)) = (enum_name, variant) {
703 let payload = match obj.get("__fields") {
704 None => shape_value::EnumPayload::Unit,
705 Some(WireValue::Array(values)) => {
706 shape_value::EnumPayload::Tuple(values.iter().map(wire_to_nb).collect())
707 }
708 Some(WireValue::Object(fields)) => {
709 let map: std::collections::HashMap<String, ValueWord> = fields
710 .iter()
711 .map(|(k, v)| (k.clone(), wire_to_nb(v)))
712 .collect();
713 shape_value::EnumPayload::Struct(map)
714 }
715 _ => shape_value::EnumPayload::Unit,
716 };
717 ValueWord::from_enum(shape_value::EnumValue {
718 enum_name,
719 variant,
720 payload,
721 })
722 } else {
723 let pairs: Vec<(String, ValueWord)> = obj
725 .iter()
726 .map(|(k, v)| (k.clone(), wire_to_nb(v)))
727 .collect();
728 let pair_refs: Vec<(&str, ValueWord)> =
729 pairs.iter().map(|(k, v)| (k.as_str(), v.clone())).collect();
730 crate::type_schema::typed_object_from_nb_pairs(&pair_refs)
731 }
732 }
733
734 WireValue::Table(table) => {
735 match datatable_from_ipc_bytes(
736 &table.ipc_bytes,
737 table.type_name.as_deref(),
738 table.schema_id,
739 ) {
740 Ok(dt) => ValueWord::from_datatable(Arc::new(dt)),
741 Err(_) => ValueWord::none(),
742 }
743 }
744
745 WireValue::Result { ok, value } => {
746 let inner = wire_to_nb(value);
747 if *ok {
748 ValueWord::from_ok(inner)
749 } else {
750 ValueWord::from_err(inner)
751 }
752 }
753
754 WireValue::Range {
755 start,
756 end,
757 inclusive,
758 } => ValueWord::from_range(
759 start.as_ref().map(|v| wire_to_nb(v)),
760 end.as_ref().map(|v| wire_to_nb(v)),
761 *inclusive,
762 ),
763
764 WireValue::FunctionRef { name } => ValueWord::from_function_ref(name.clone(), None),
765
766 WireValue::PrintResult(result) => {
767 ValueWord::from_string(Arc::new(result.rendered.clone()))
769 }
770 }
771}
772
773pub fn nb_to_envelope(nb: &ValueWord, type_name: &str, ctx: &Context) -> ValueEnvelope {
775 let wire_value = nb_to_wire(nb, ctx);
776 let type_info = TypeInfo::primitive(type_name);
777 let registry = TypeRegistry::new("Default");
778 ValueEnvelope::new(wire_value, type_info, registry)
779}
780
781pub fn nb_extract_typed_value(nb: &ValueWord, ctx: &Context) -> (WireValue, Option<TypeInfo>) {
785 let wire_value = nb_to_wire(nb, ctx);
786 let type_name = nb.type_name();
787 let type_info = TypeInfo::primitive(type_name);
788 (wire_value, Some(type_info))
789}
790
791pub fn nb_typed_value_to_envelope(nb: &ValueWord, ctx: &Context) -> ValueEnvelope {
795 let type_name = nb.type_name();
796 nb_to_envelope(nb, type_name, ctx)
797}
798
799fn enum_to_wire(enum_value: &shape_value::EnumValue, ctx: &Context) -> WireValue {
801 let mut obj = BTreeMap::new();
802 obj.insert(
803 "__enum".to_string(),
804 WireValue::String(enum_value.enum_name.clone()),
805 );
806 obj.insert(
807 "__variant".to_string(),
808 WireValue::String(enum_value.variant.clone()),
809 );
810 match &enum_value.payload {
811 shape_value::EnumPayload::Unit => {}
812 shape_value::EnumPayload::Tuple(values) => {
813 obj.insert(
814 "__fields".to_string(),
815 WireValue::Array(values.iter().map(|v| nb_to_wire(v, ctx)).collect()),
816 );
817 }
818 shape_value::EnumPayload::Struct(fields) => {
819 let field_map: BTreeMap<String, WireValue> = fields
820 .iter()
821 .map(|(k, v)| (k.clone(), nb_to_wire(v, ctx)))
822 .collect();
823 obj.insert("__fields".to_string(), WireValue::Object(field_map));
824 }
825 }
826 WireValue::Object(obj)
827}
828
829fn datatable_to_wire(dt: &DataTable) -> WireValue {
830 datatable_to_wire_with_schema(dt, dt.schema_id())
831}
832
833fn datatable_to_wire_with_schema(dt: &DataTable, schema_id: Option<u32>) -> WireValue {
834 match datatable_to_ipc_bytes(dt) {
835 Ok(ipc_bytes) => WireValue::Table(WireTable {
836 ipc_bytes,
837 type_name: dt.type_name().map(|s| s.to_string()),
838 schema_id,
839 row_count: dt.row_count(),
840 column_count: dt.column_count(),
841 }),
842 Err(_) => WireValue::String(format!("{}", dt)),
843 }
844}
845
846pub fn datatable_to_ipc_bytes(dt: &DataTable) -> std::result::Result<Vec<u8>, String> {
848 let mut buf = Vec::new();
849 let schema = dt.inner().schema();
850 let mut writer = FileWriter::try_new(&mut buf, schema.as_ref())
851 .map_err(|e| format!("failed to create Arrow IPC writer: {e}"))?;
852 writer
853 .write(dt.inner())
854 .map_err(|e| format!("failed to write Arrow IPC batch: {e}"))?;
855 writer
856 .finish()
857 .map_err(|e| format!("failed to finalize Arrow IPC writer: {e}"))?;
858 Ok(buf)
859}
860
861pub fn datatable_from_ipc_bytes(
863 ipc_bytes: &[u8],
864 type_name: Option<&str>,
865 schema_id: Option<u32>,
866) -> std::result::Result<DataTable, String> {
867 if ipc_bytes.is_empty() {
868 return Err("empty Arrow IPC payload".to_string());
869 }
870
871 let cursor = std::io::Cursor::new(ipc_bytes);
872 let mut reader = FileReader::try_new(cursor, None)
873 .map_err(|e| format!("failed to create Arrow IPC reader: {e}"))?;
874 let batch = reader
875 .next()
876 .transpose()
877 .map_err(|e| format!("failed reading Arrow IPC batch: {e}"))?
878 .ok_or_else(|| "Arrow IPC payload has no record batches".to_string())?;
879
880 let mut dt = DataTable::new(batch);
881 if let Some(name) = type_name {
882 dt = DataTable::with_type_name(dt.into_inner(), name.to_string());
883 }
884 if let Some(id) = schema_id {
885 dt = dt.with_schema_id(id);
886 }
887 Ok(dt)
888}
889
890fn infer_metadata_with_ctx(
893 _value: &ValueWord,
894 type_name: &str,
895 _ctx: &Context,
896) -> (TypeInfo, TypeRegistry) {
897 let type_info = TypeInfo::primitive(type_name);
898 let registry = TypeRegistry::new("Default");
899 (type_info, registry)
900}
901
902#[cfg(test)]
903mod tests {
904 use super::*;
905 use crate::type_methods::TypeMethodRegistry;
906 use crate::type_schema::typed_object_to_hashmap_nb;
907 use shape_value::ValueSlot;
908 use shape_value::heap_value::HeapValue;
909 use std::sync::Arc;
910
911 fn get_dummy_context() -> Context {
912 Context::new_empty_with_registry(Arc::new(TypeMethodRegistry::new()))
913 }
914
915 #[test]
916 fn test_basic_value_conversion() {
917 let ctx = get_dummy_context();
918 let wire = value_to_wire(&ValueWord::from_f64(42.5), &ctx);
920 assert_eq!(wire, WireValue::Number(42.5));
921
922 let wire = value_to_wire(&ValueWord::from_f64(42.0), &ctx);
924 assert_eq!(wire, WireValue::Number(42.0));
925
926 let wire = value_to_wire(&ValueWord::from_string(Arc::new("hello".to_string())), &ctx);
928 assert_eq!(wire, WireValue::String("hello".to_string()));
929
930 let wire = value_to_wire(&ValueWord::from_bool(true), &ctx);
932 assert_eq!(wire, WireValue::Bool(true));
933
934 let wire = value_to_wire(&ValueWord::none(), &ctx);
936 assert_eq!(wire, WireValue::Null);
937 }
938
939 #[test]
940 fn test_array_conversion() {
941 let ctx = get_dummy_context();
942 let arr = ValueWord::from_array(Arc::new(vec![
943 ValueWord::from_f64(1.0),
944 ValueWord::from_f64(2.0),
945 ValueWord::from_f64(3.0),
946 ]));
947 let wire = value_to_wire(&arr, &ctx);
948
949 if let WireValue::Array(items) = wire {
950 assert_eq!(items.len(), 3);
951 assert_eq!(items[0], WireValue::Number(1.0));
952 assert_eq!(items[1], WireValue::Number(2.0));
953 assert_eq!(items[2], WireValue::Number(3.0));
954 } else {
955 panic!("Expected Array");
956 }
957 }
958
959 #[test]
960 fn test_object_conversion() {
961 let ctx = get_dummy_context();
962 let obj = crate::type_schema::typed_object_from_pairs(&[
963 ("x", ValueWord::from_f64(10.0)),
964 ("y", ValueWord::from_f64(20.0)),
965 ]);
966
967 let wire = value_to_wire(&obj, &ctx);
968
969 if let WireValue::Object(map) = wire {
970 assert_eq!(map.get("x"), Some(&WireValue::Number(10.0)));
971 assert_eq!(map.get("y"), Some(&WireValue::Number(20.0)));
972 } else {
973 panic!("Expected Object");
974 }
975 }
976
977 #[test]
978 fn test_builtin_typed_object_converts_to_wire_object() {
979 let ctx = get_dummy_context();
980 let any_error_schema_id = ctx
981 .type_schema_registry()
982 .get("__AnyError")
983 .expect("__AnyError schema should exist")
984 .id as u64;
985
986 let slots = vec![
987 ValueSlot::from_heap(HeapValue::String(Arc::new("AnyError".to_string()))),
988 ValueSlot::from_heap(HeapValue::String(Arc::new("boom".to_string()))),
989 ValueSlot::none(), ValueSlot::none(), ValueSlot::from_heap(HeapValue::String(Arc::new("boom".to_string()))),
992 ValueSlot::from_heap(HeapValue::String(Arc::new("E_BANG".to_string()))),
993 ];
994
995 let nb = ValueWord::from_heap_value(HeapValue::TypedObject {
996 schema_id: any_error_schema_id,
997 slots: slots.into_boxed_slice(),
998 heap_mask: 0b11_0011, });
1000
1001 let wire = nb_to_wire(&nb, &ctx);
1002 match wire {
1003 WireValue::Object(map) => {
1004 assert_eq!(
1005 map.get("category"),
1006 Some(&WireValue::String("AnyError".into()))
1007 );
1008 assert_eq!(map.get("message"), Some(&WireValue::String("boom".into())));
1009 assert_eq!(map.get("code"), Some(&WireValue::String("E_BANG".into())));
1010 }
1011 other => panic!("Expected WireValue::Object, got {:?}", other),
1012 }
1013 }
1014
1015 #[test]
1016 fn test_unknown_schema_anyerror_uses_builtin_fallback_decoder() {
1017 let ctx = get_dummy_context();
1018
1019 let slots = vec![
1020 ValueSlot::from_heap(HeapValue::String(Arc::new("AnyError".to_string()))),
1021 ValueSlot::from_heap(HeapValue::String(Arc::new("boom".to_string()))),
1022 ValueSlot::none(), ValueSlot::none(), ValueSlot::from_heap(HeapValue::String(Arc::new("boom".to_string()))),
1025 ValueSlot::from_heap(HeapValue::String(Arc::new("E_BANG".to_string()))),
1026 ];
1027
1028 let nb = ValueWord::from_heap_value(HeapValue::TypedObject {
1029 schema_id: 9_999_999,
1030 slots: slots.into_boxed_slice(),
1031 heap_mask: 0b11_0011, });
1033
1034 let wire = nb_to_wire(&nb, &ctx);
1035 match wire {
1036 WireValue::Object(map) => {
1037 assert_eq!(
1038 map.get("category"),
1039 Some(&WireValue::String("AnyError".into()))
1040 );
1041 assert_eq!(map.get("message"), Some(&WireValue::String("boom".into())));
1042 assert_eq!(map.get("code"), Some(&WireValue::String("E_BANG".into())));
1043 }
1044 other => panic!("Expected WireValue::Object, got {:?}", other),
1045 }
1046 }
1047
1048 #[test]
1049 fn test_timestamp_conversion() {
1050 let ctx = get_dummy_context();
1051 use chrono::TimeZone;
1052 let dt = chrono::Utc
1053 .with_ymd_and_hms(2024, 1, 15, 10, 30, 0)
1054 .unwrap();
1055 let wire = value_to_wire(&ValueWord::from_time_utc(dt), &ctx);
1056
1057 if let WireValue::Timestamp(ts) = wire {
1058 assert_eq!(ts, 1705314600000);
1059 } else {
1060 panic!("Expected Timestamp");
1061 }
1062 }
1063
1064 #[test]
1065 fn test_result_conversion() {
1066 let ctx = get_dummy_context();
1067 let ok_val = ValueWord::from_ok(ValueWord::from_f64(42.0));
1068 let wire = value_to_wire(&ok_val, &ctx);
1069
1070 if let WireValue::Result { ok, value } = wire {
1071 assert!(ok);
1072 assert_eq!(*value, WireValue::Number(42.0));
1073 } else {
1074 panic!("Expected Result");
1075 }
1076 }
1077
1078 #[test]
1079 fn test_wire_to_nb_anyerror_trace_frame_key_order_is_stable() {
1080 use std::collections::BTreeMap;
1081
1082 let mut frame = BTreeMap::new();
1083 frame.insert(
1084 "function".to_string(),
1085 WireValue::String("duckdb.connect".to_string()),
1086 );
1087 frame.insert(
1088 "file".to_string(),
1089 WireValue::String("extension:duckdb".to_string()),
1090 );
1091 frame.insert("line".to_string(), WireValue::Null);
1092 frame.insert("ip".to_string(), WireValue::Null);
1093
1094 let nb = wire_to_nb(&WireValue::Object(frame));
1095 let decoded =
1096 typed_object_to_hashmap_nb(&nb).expect("Trace frame wire object should decode");
1097 assert_eq!(
1098 decoded.get("function").and_then(|v| v.as_str()),
1099 Some("duckdb.connect")
1100 );
1101 assert_eq!(
1102 decoded.get("file").and_then(|v| v.as_str()),
1103 Some("extension:duckdb")
1104 );
1105 }
1106
1107 #[test]
1108 fn test_any_error_result_roundtrip() {
1109 use crate::type_schema::typed_object_from_pairs;
1110 let ctx = get_dummy_context();
1111
1112 let empty_trace = typed_object_from_pairs(&[]);
1113 let cause = typed_object_from_pairs(&[
1114 (
1115 "category",
1116 ValueWord::from_string(Arc::new("AnyError".to_string())),
1117 ),
1118 (
1119 "payload",
1120 ValueWord::from_string(Arc::new("low level".to_string())),
1121 ),
1122 ("cause", ValueWord::none()),
1123 ("trace_info", empty_trace),
1124 ]);
1125
1126 let empty_trace2 = typed_object_from_pairs(&[]);
1127 let outer = typed_object_from_pairs(&[
1128 (
1129 "category",
1130 ValueWord::from_string(Arc::new("AnyError".to_string())),
1131 ),
1132 (
1133 "payload",
1134 ValueWord::from_string(Arc::new("high level".to_string())),
1135 ),
1136 ("cause", cause),
1137 ("trace_info", empty_trace2),
1138 (
1139 "code",
1140 ValueWord::from_string(Arc::new("OPTION_NONE".to_string())),
1141 ),
1142 ]);
1143
1144 let err = ValueWord::from_err(outer);
1145 let wire = value_to_wire(&err, &ctx);
1146
1147 let WireValue::Result { ok, value } = &wire else {
1148 panic!("Expected wire Result");
1149 };
1150 assert!(!ok);
1151 match value.as_ref() {
1152 WireValue::Object(map) => {
1153 assert_eq!(
1154 map.get("category"),
1155 Some(&WireValue::String("AnyError".to_string()))
1156 );
1157 assert_eq!(
1158 map.get("code"),
1159 Some(&WireValue::String("OPTION_NONE".to_string()))
1160 );
1161 }
1162 other => panic!("Expected AnyError object payload, got {:?}", other),
1163 }
1164
1165 let roundtrip = wire_to_nb(&wire);
1166 let hv = roundtrip.as_heap_ref().expect("Expected heap value");
1167 match hv {
1168 HeapValue::Err(inner) => {
1169 let inner_hv = inner.as_heap_ref().expect("Expected heap inner");
1170 assert!(
1171 matches!(inner_hv, HeapValue::TypedObject { .. }),
1172 "Expected TypedObject inside Err"
1173 );
1174 }
1175 other => panic!("Expected Err, got {:?}", other.kind()),
1176 }
1177 }
1178
1179 #[test]
1180 fn test_envelope_creation() {
1181 let ctx = get_dummy_context();
1182 let nb = ValueWord::from_f64(3.14);
1183 let envelope = nb_to_envelope(&nb, "number", &ctx);
1184
1185 match &envelope.value {
1186 WireValue::Number(n) => assert!((*n - 3.14).abs() < f64::EPSILON),
1187 other => panic!("Expected Number, got {:?}", other),
1188 }
1189 }
1190
1191 #[test]
1192 fn test_roundtrip_basic() {
1193 let ctx = get_dummy_context();
1194 let nb = ValueWord::from_string(Arc::new("test".to_string()));
1195 let wire = nb_to_wire(&nb, &ctx);
1196 let back = wire_to_nb(&wire);
1197
1198 if let Some(s) = back.as_str() {
1199 assert_eq!(s, "test");
1200 } else {
1201 panic!("Expected String");
1202 }
1203 }
1204
1205 #[test]
1208 fn test_nb_to_wire_basic_types() {
1209 let ctx = get_dummy_context();
1210
1211 let wire = nb_to_wire(&ValueWord::from_f64(42.5), &ctx);
1213 assert_eq!(wire, WireValue::Number(42.5));
1214
1215 let wire = nb_to_wire(&ValueWord::from_f64(42.0), &ctx);
1217 assert_eq!(wire, WireValue::Number(42.0));
1218
1219 let wire = nb_to_wire(&ValueWord::from_i64(99), &ctx);
1221 assert_eq!(wire, WireValue::Integer(99));
1222
1223 let wire = nb_to_wire(&ValueWord::from_i64(-7), &ctx);
1225 assert_eq!(wire, WireValue::Integer(-7));
1226
1227 let wire = nb_to_wire(&ValueWord::from_bool(true), &ctx);
1229 assert_eq!(wire, WireValue::Bool(true));
1230
1231 let wire = nb_to_wire(&ValueWord::from_bool(false), &ctx);
1232 assert_eq!(wire, WireValue::Bool(false));
1233
1234 let wire = nb_to_wire(&ValueWord::none(), &ctx);
1236 assert_eq!(wire, WireValue::Null);
1237
1238 let wire = nb_to_wire(&ValueWord::unit(), &ctx);
1240 assert_eq!(wire, WireValue::Null);
1241 }
1242
1243 #[test]
1244 fn test_nb_to_wire_string() {
1245 let ctx = get_dummy_context();
1246 let nb = ValueWord::from_string(Arc::new("hello".to_string()));
1247 let wire = nb_to_wire(&nb, &ctx);
1248 assert_eq!(wire, WireValue::String("hello".to_string()));
1249 }
1250
1251 #[test]
1252 fn test_nb_to_wire_array() {
1253 let ctx = get_dummy_context();
1254 let nb = ValueWord::from_array(Arc::new(vec![
1255 ValueWord::from_f64(1.0),
1256 ValueWord::from_i64(2),
1257 ValueWord::from_bool(true),
1258 ]));
1259 let wire = nb_to_wire(&nb, &ctx);
1260
1261 if let WireValue::Array(items) = wire {
1262 assert_eq!(items.len(), 3);
1263 assert_eq!(items[0], WireValue::Number(1.0));
1264 assert_eq!(items[1], WireValue::Integer(2));
1265 assert_eq!(items[2], WireValue::Bool(true));
1266 } else {
1267 panic!("Expected Array");
1268 }
1269 }
1270
1271 #[test]
1272 fn test_nb_to_wire_result() {
1273 let ctx = get_dummy_context();
1274
1275 let ok = ValueWord::from_ok(ValueWord::from_i64(42));
1277 let wire = nb_to_wire(&ok, &ctx);
1278 if let WireValue::Result { ok, value } = wire {
1279 assert!(ok);
1280 assert_eq!(*value, WireValue::Integer(42));
1281 } else {
1282 panic!("Expected Result");
1283 }
1284
1285 let err = ValueWord::from_err(ValueWord::from_string(Arc::new("oops".to_string())));
1287 let wire = nb_to_wire(&err, &ctx);
1288 if let WireValue::Result { ok, value } = wire {
1289 assert!(!ok);
1290 assert_eq!(*value, WireValue::String("oops".to_string()));
1291 } else {
1292 panic!("Expected Result");
1293 }
1294 }
1295
1296 #[test]
1297 fn test_nb_to_wire_some() {
1298 let ctx = get_dummy_context();
1299 let some = ValueWord::from_some(ValueWord::from_f64(3.14));
1300 let wire = nb_to_wire(&some, &ctx);
1301 assert_eq!(wire, WireValue::Number(3.14));
1303 }
1304
1305 #[test]
1306 fn test_nb_to_wire_matches_vmvalue() {
1307 let ctx = get_dummy_context();
1309
1310 let test_values: Vec<ValueWord> = vec![
1311 ValueWord::from_f64(42.5),
1312 ValueWord::from_f64(42.0),
1313 ValueWord::from_i64(100),
1314 ValueWord::from_i64(-100),
1315 ValueWord::from_bool(true),
1316 ValueWord::from_bool(false),
1317 ValueWord::none(),
1318 ValueWord::unit(),
1319 ValueWord::from_string(Arc::new("test".to_string())),
1320 ValueWord::from_array(Arc::new(vec![
1321 ValueWord::from_f64(1.0),
1322 ValueWord::from_i64(2),
1323 ])),
1324 ];
1325
1326 for nb in &test_values {
1327 let vmv = nb.clone();
1328 let wire_from_vmv = value_to_wire(&vmv, &ctx);
1329 let wire_from_nb = nb_to_wire(nb, &ctx);
1330 assert_eq!(
1331 wire_from_vmv,
1332 wire_from_nb,
1333 "Mismatch for ValueWord type {:?}: ValueWord path = {:?}, ValueWord path = {:?}",
1334 nb.tag(),
1335 wire_from_vmv,
1336 wire_from_nb
1337 );
1338 }
1339 }
1340
1341 #[test]
1342 fn test_wire_to_nb_basic_types() {
1343 let nb = wire_to_nb(&WireValue::Null);
1345 assert!(nb.is_none());
1346
1347 let nb = wire_to_nb(&WireValue::Bool(true));
1349 assert_eq!(nb.as_bool(), Some(true));
1350
1351 let nb = wire_to_nb(&WireValue::Number(3.14));
1353 assert_eq!(nb.as_f64(), Some(3.14));
1354
1355 let nb = wire_to_nb(&WireValue::Integer(42));
1357 assert_eq!(nb.as_i64(), Some(42));
1358
1359 let nb = wire_to_nb(&WireValue::String("hello".to_string()));
1361 assert_eq!(nb.as_str(), Some("hello"));
1362 }
1363
1364 #[test]
1365 fn test_wire_to_nb_array() {
1366 let wire = WireValue::Array(vec![
1367 WireValue::Integer(1),
1368 WireValue::Number(2.5),
1369 WireValue::Bool(false),
1370 ]);
1371 let nb = wire_to_nb(&wire);
1372 let arr = nb.as_any_array().expect("Expected array").to_generic();
1373 assert_eq!(arr.len(), 3);
1374 assert_eq!(arr[0].as_i64(), Some(1));
1375 assert_eq!(arr[1].as_f64(), Some(2.5));
1376 assert_eq!(arr[2].as_bool(), Some(false));
1377 }
1378
1379 #[test]
1380 fn test_wire_to_nb_result() {
1381 let wire = WireValue::Result {
1383 ok: true,
1384 value: Box::new(WireValue::Integer(7)),
1385 };
1386 let nb = wire_to_nb(&wire);
1387 let inner = nb.as_ok_inner().expect("Expected Ok");
1388 assert_eq!(inner.as_i64(), Some(7));
1389
1390 let wire = WireValue::Result {
1392 ok: false,
1393 value: Box::new(WireValue::String("fail".to_string())),
1394 };
1395 let nb = wire_to_nb(&wire);
1396 let inner = nb.as_err_inner().expect("Expected Err");
1397 assert_eq!(inner.as_str(), Some("fail"));
1398 }
1399
1400 #[test]
1401 fn test_nb_roundtrip_basic() {
1402 let ctx = get_dummy_context();
1403
1404 let original = ValueWord::from_string(Arc::new("roundtrip".to_string()));
1406 let wire = nb_to_wire(&original, &ctx);
1407 let back = wire_to_nb(&wire);
1408 assert_eq!(back.as_str(), Some("roundtrip"));
1409
1410 let original = ValueWord::from_i64(12345);
1412 let wire = nb_to_wire(&original, &ctx);
1413 let back = wire_to_nb(&wire);
1414 assert_eq!(back.as_i64(), Some(12345));
1415
1416 let original = ValueWord::from_f64(2.718);
1418 let wire = nb_to_wire(&original, &ctx);
1419 let back = wire_to_nb(&wire);
1420 assert_eq!(back.as_f64(), Some(2.718));
1421
1422 let original = ValueWord::from_bool(true);
1424 let wire = nb_to_wire(&original, &ctx);
1425 let back = wire_to_nb(&wire);
1426 assert_eq!(back.as_bool(), Some(true));
1427
1428 let wire = nb_to_wire(&ValueWord::none(), &ctx);
1430 let back = wire_to_nb(&wire);
1431 assert!(back.is_none());
1432 }
1433
1434 #[test]
1435 fn test_nb_roundtrip_array() {
1436 let ctx = get_dummy_context();
1437 let original = ValueWord::from_array(Arc::new(vec![
1438 ValueWord::from_i64(10),
1439 ValueWord::from_f64(20.5),
1440 ValueWord::from_string(Arc::new("x".to_string())),
1441 ]));
1442 let wire = nb_to_wire(&original, &ctx);
1443 let back = wire_to_nb(&wire);
1444 let arr = back.as_any_array().expect("Expected array").to_generic();
1445 assert_eq!(arr.len(), 3);
1446 assert_eq!(arr[0].as_i64(), Some(10));
1447 assert_eq!(arr[1].as_f64(), Some(20.5));
1448 assert_eq!(arr[2].as_str(), Some("x"));
1449 }
1450
1451 #[test]
1452 fn test_nb_to_wire_decimal() {
1453 let ctx = get_dummy_context();
1454 let d = rust_decimal::Decimal::new(314, 2); let nb = ValueWord::from_decimal(d);
1456 let wire = nb_to_wire(&nb, &ctx);
1457 assert_eq!(wire, WireValue::Number(3.14));
1458 }
1459
1460 #[test]
1461 fn test_nb_to_wire_typed_object() {
1462 let ctx = get_dummy_context();
1463 let obj = crate::type_schema::typed_object_from_pairs(&[
1464 ("a", ValueWord::from_f64(1.0)),
1465 ("b", ValueWord::from_string(Arc::new("two".to_string()))),
1466 ]);
1467 let nb = obj;
1468 let wire = nb_to_wire(&nb, &ctx);
1469
1470 if let WireValue::Object(map) = wire {
1471 assert_eq!(map.get("a"), Some(&WireValue::Number(1.0)));
1472 assert_eq!(map.get("b"), Some(&WireValue::String("two".to_string())));
1473 } else {
1474 panic!("Expected Object, got {:?}", wire);
1475 }
1476 }
1477
1478 #[test]
1479 fn test_nb_envelope_creation() {
1480 let ctx = get_dummy_context();
1481 let nb = ValueWord::from_f64(3.14);
1482 let envelope = nb_to_envelope(&nb, "Number", &ctx);
1483 assert_eq!(envelope.type_info.name, "Number");
1484 }
1485
1486 #[test]
1487 fn test_native_scalar_wire_roundtrip_preserves_width() {
1488 let ctx = get_dummy_context();
1489 let cases = vec![
1490 (
1491 ValueWord::from_native_i8(-8),
1492 WireValue::I8(-8),
1493 shape_value::heap_value::NativeScalar::I8(-8),
1494 ),
1495 (
1496 ValueWord::from_native_u8(255),
1497 WireValue::U8(255),
1498 shape_value::heap_value::NativeScalar::U8(255),
1499 ),
1500 (
1501 ValueWord::from_native_i16(-1024),
1502 WireValue::I16(-1024),
1503 shape_value::heap_value::NativeScalar::I16(-1024),
1504 ),
1505 (
1506 ValueWord::from_native_u16(65530),
1507 WireValue::U16(65530),
1508 shape_value::heap_value::NativeScalar::U16(65530),
1509 ),
1510 (
1511 ValueWord::from_native_i32(-123_456),
1512 WireValue::I32(-123_456),
1513 shape_value::heap_value::NativeScalar::I32(-123_456),
1514 ),
1515 (
1516 ValueWord::from_native_u32(4_000_000_000),
1517 WireValue::U32(4_000_000_000),
1518 shape_value::heap_value::NativeScalar::U32(4_000_000_000),
1519 ),
1520 (
1521 ValueWord::from_native_scalar(shape_value::heap_value::NativeScalar::I64(
1522 -9_223_372_036_854_775_000,
1523 )),
1524 WireValue::I64(-9_223_372_036_854_775_000),
1525 shape_value::heap_value::NativeScalar::I64(-9_223_372_036_854_775_000),
1526 ),
1527 (
1528 ValueWord::from_native_u64(18_000_000_000),
1529 WireValue::U64(18_000_000_000),
1530 shape_value::heap_value::NativeScalar::U64(18_000_000_000),
1531 ),
1532 (
1533 ValueWord::from_native_isize(12345isize),
1534 WireValue::Isize(12345),
1535 shape_value::heap_value::NativeScalar::Isize(12345isize),
1536 ),
1537 (
1538 ValueWord::from_native_usize(54321usize),
1539 WireValue::Usize(54321),
1540 shape_value::heap_value::NativeScalar::Usize(54321usize),
1541 ),
1542 (
1543 ValueWord::from_native_ptr(0x1234usize),
1544 WireValue::Ptr(0x1234),
1545 shape_value::heap_value::NativeScalar::Ptr(0x1234usize),
1546 ),
1547 (
1548 ValueWord::from_native_f32(3.5f32),
1549 WireValue::F32(3.5f32),
1550 shape_value::heap_value::NativeScalar::F32(3.5f32),
1551 ),
1552 ];
1553
1554 for (nb, expected_wire, expected_scalar) in cases {
1555 let wire = nb_to_wire(&nb, &ctx);
1556 assert_eq!(wire, expected_wire);
1557
1558 let roundtrip = wire_to_nb(&wire);
1559 assert_eq!(roundtrip.as_native_scalar(), Some(expected_scalar));
1560 }
1561 }
1562}