Skip to main content

shape_ext_typescript/
marshaling.rs

1//! Shape <-> V8 type conversion via MessagePack.
2//!
3//! Converts between msgpack-encoded Shape wire values and V8 JavaScript values.
4
5use deno_core::v8;
6
7// ============================================================================
8// Shape type -> TypeScript type hint (for generated wrappers)
9// ============================================================================
10
11/// Convert a Shape type name to a TypeScript type annotation string.
12pub fn shape_type_to_ts_hint(shape_type: &str) -> String {
13    match shape_type {
14        "number" => "number".to_string(),
15        "int" => "number".to_string(),
16        "bool" => "boolean".to_string(),
17        "string" => "string".to_string(),
18        "none" => "void".to_string(),
19        s if s.starts_with("Array<") => {
20            let inner = &s[6..s.len() - 1];
21            format!("Array<{}>", shape_type_to_ts_hint(inner))
22        }
23        _ => "object".to_string(),
24    }
25}
26
27// ============================================================================
28// msgpack -> V8
29// ============================================================================
30
31/// Deserialize a msgpack byte buffer into a vector of V8 values.
32///
33/// The buffer is expected to contain a msgpack array whose elements
34/// correspond 1:1 with the function's positional parameters.
35pub fn msgpack_to_v8<'s>(
36    scope: &mut v8::HandleScope<'s>,
37    bytes: &[u8],
38) -> Result<Vec<v8::Local<'s, v8::Value>>, String> {
39    if bytes.is_empty() {
40        return Ok(Vec::new());
41    }
42
43    let values: Vec<rmpv::Value> =
44        rmp_serde::from_slice(bytes).map_err(|e| format!("Failed to deserialize args: {}", e))?;
45
46    values
47        .iter()
48        .map(|v| rmpv_to_v8(scope, v))
49        .collect::<Result<Vec<_>, _>>()
50}
51
52/// Convert a single rmpv::Value to a V8 value.
53fn rmpv_to_v8<'s>(
54    scope: &mut v8::HandleScope<'s>,
55    value: &rmpv::Value,
56) -> Result<v8::Local<'s, v8::Value>, String> {
57    match value {
58        rmpv::Value::Nil => Ok(v8::null(scope).into()),
59
60        rmpv::Value::Boolean(b) => Ok(v8::Boolean::new(scope, *b).into()),
61
62        rmpv::Value::Integer(i) => {
63            if let Some(n) = i.as_i64() {
64                // Use Integer if it fits in i32, otherwise Number (f64)
65                if n >= i32::MIN as i64 && n <= i32::MAX as i64 {
66                    Ok(v8::Integer::new(scope, n as i32).into())
67                } else {
68                    Ok(v8::Number::new(scope, n as f64).into())
69                }
70            } else if let Some(n) = i.as_u64() {
71                if n <= i32::MAX as u64 {
72                    Ok(v8::Integer::new_from_unsigned(scope, n as u32).into())
73                } else {
74                    Ok(v8::Number::new(scope, n as f64).into())
75                }
76            } else {
77                Ok(v8::null(scope).into())
78            }
79        }
80
81        rmpv::Value::F32(f) => Ok(v8::Number::new(scope, *f as f64).into()),
82
83        rmpv::Value::F64(f) => Ok(v8::Number::new(scope, *f).into()),
84
85        rmpv::Value::String(s) => {
86            if let Some(s) = s.as_str() {
87                let v8_str = v8::String::new(scope, s)
88                    .ok_or_else(|| "Failed to create V8 string".to_string())?;
89                Ok(v8_str.into())
90            } else {
91                Ok(v8::null(scope).into())
92            }
93        }
94
95        rmpv::Value::Array(arr) => {
96            let v8_arr = v8::Array::new(scope, arr.len() as i32);
97            for (i, elem) in arr.iter().enumerate() {
98                let v8_val = rmpv_to_v8(scope, elem)?;
99                v8_arr.set_index(scope, i as u32, v8_val);
100            }
101            Ok(v8_arr.into())
102        }
103
104        rmpv::Value::Map(entries) => {
105            let v8_obj = v8::Object::new(scope);
106            for (k, v) in entries {
107                let v8_key = rmpv_to_v8(scope, k)?;
108                let v8_val = rmpv_to_v8(scope, v)?;
109                v8_obj.set(scope, v8_key, v8_val);
110            }
111            Ok(v8_obj.into())
112        }
113
114        rmpv::Value::Binary(_) | rmpv::Value::Ext(_, _) => Ok(v8::null(scope).into()),
115    }
116}
117
118// ============================================================================
119// V8 -> msgpack
120// ============================================================================
121
122/// Convert a V8 value to a msgpack byte buffer.
123pub fn v8_to_msgpack(
124    scope: &mut v8::HandleScope,
125    value: v8::Local<v8::Value>,
126) -> Result<Vec<u8>, String> {
127    let rmpv_val = v8_to_rmpv(scope, value)?;
128    rmp_serde::to_vec(&rmpv_val).map_err(|e| format!("Failed to serialize result: {}", e))
129}
130
131/// Convert a V8 value to an rmpv::Value.
132fn v8_to_rmpv(
133    scope: &mut v8::HandleScope,
134    value: v8::Local<v8::Value>,
135) -> Result<rmpv::Value, String> {
136    if value.is_null_or_undefined() {
137        return Ok(rmpv::Value::Nil);
138    }
139
140    if value.is_boolean() {
141        let b = value.boolean_value(scope);
142        return Ok(rmpv::Value::Boolean(b));
143    }
144
145    // Check integer before general number (V8 integers are also numbers)
146    if value.is_int32() {
147        let i = value.int32_value(scope).unwrap_or(0);
148        return Ok(rmpv::Value::Integer(rmpv::Integer::from(i as i64)));
149    }
150
151    if value.is_uint32() {
152        let u = value.uint32_value(scope).unwrap_or(0);
153        return Ok(rmpv::Value::Integer(rmpv::Integer::from(u as i64)));
154    }
155
156    if value.is_number() {
157        let f = value.number_value(scope).unwrap_or(0.0);
158        // If the float is an exact integer that fits in i64, encode as integer
159        if f.fract() == 0.0 && f >= i64::MIN as f64 && f <= i64::MAX as f64 {
160            return Ok(rmpv::Value::Integer(rmpv::Integer::from(f as i64)));
161        }
162        return Ok(rmpv::Value::F64(f));
163    }
164
165    if value.is_string() {
166        let s = value.to_rust_string_lossy(scope);
167        return Ok(rmpv::Value::String(rmpv::Utf8String::from(s)));
168    }
169
170    if value.is_array() {
171        let arr = v8::Local::<v8::Array>::try_from(value)
172            .map_err(|_| "Failed to cast to Array".to_string())?;
173        let len = arr.length();
174        let mut items = Vec::with_capacity(len as usize);
175        for i in 0..len {
176            if let Some(elem) = arr.get_index(scope, i) {
177                items.push(v8_to_rmpv(scope, elem)?);
178            } else {
179                items.push(rmpv::Value::Nil);
180            }
181        }
182        return Ok(rmpv::Value::Array(items));
183    }
184
185    if value.is_object() {
186        let obj = value.to_object(scope).unwrap();
187        let property_names = obj
188            .get_own_property_names(scope, v8::GetPropertyNamesArgs::default())
189            .ok_or_else(|| "Failed to get property names".to_string())?;
190        let len = property_names.length();
191        let mut entries = Vec::with_capacity(len as usize);
192        for i in 0..len {
193            let key = property_names.get_index(scope, i).unwrap();
194            let val = obj
195                .get(scope, key)
196                .unwrap_or_else(|| v8::null(scope).into());
197            let rmpv_key = v8_to_rmpv(scope, key)?;
198            let rmpv_val = v8_to_rmpv(scope, val)?;
199            entries.push((rmpv_key, rmpv_val));
200        }
201        return Ok(rmpv::Value::Map(entries));
202    }
203
204    // Fallback for symbols, bigints, functions, etc.
205    Ok(rmpv::Value::Nil)
206}