roam_codegen/targets/typescript/
encode.rs

1//! TypeScript encoding expression generation.
2//!
3//! Generates TypeScript code that encodes Rust types into byte arrays.
4
5use facet_core::{ScalarType, Shape, StructKind};
6use roam_schema::{
7    EnumInfo, ShapeKind, StructInfo, VariantKind, classify_shape, classify_variant, is_bytes,
8};
9
10use super::types::ts_field_access;
11
12/// Generate a TypeScript expression that encodes a value of the given type.
13/// `expr` is the JavaScript expression to encode.
14pub fn generate_encode_expr(shape: &'static Shape, expr: &str) -> String {
15    // Check for bytes first (Vec<u8>)
16    if is_bytes(shape) {
17        return format!("encodeBytes({expr})");
18    }
19
20    match classify_shape(shape) {
21        ShapeKind::Scalar(scalar) => encode_scalar_expr(scalar, expr),
22        ShapeKind::List { element } => {
23            let item_encode = generate_encode_expr(element, "item");
24            format!("encodeVec({expr}, (item) => {item_encode})")
25        }
26        ShapeKind::Option { inner } => {
27            let inner_encode = generate_encode_expr(inner, "v");
28            format!("encodeOption({expr}, (v) => {inner_encode})")
29        }
30        ShapeKind::Array { element, .. } => {
31            // Encode as vec for now
32            let item_encode = generate_encode_expr(element, "item");
33            format!("encodeVec({expr}, (item) => {item_encode})")
34        }
35        ShapeKind::Slice { element } => {
36            let item_encode = generate_encode_expr(element, "item");
37            format!("encodeVec({expr}, (item) => {item_encode})")
38        }
39        ShapeKind::Map { key, value } => {
40            // Encode as vec of tuples
41            let k_enc = generate_encode_expr(key, "k");
42            let v_enc = generate_encode_expr(value, "v");
43            format!("encodeVec(Array.from({expr}.entries()), ([k, v]) => concat({k_enc}, {v_enc}))")
44        }
45        ShapeKind::Set { element } => {
46            let item_encode = generate_encode_expr(element, "item");
47            format!("encodeVec(Array.from({expr}), (item) => {item_encode})")
48        }
49        ShapeKind::Tuple { elements } => {
50            if elements.len() == 2 {
51                let a_enc = generate_encode_expr(elements[0].shape, &format!("{expr}[0]"));
52                let b_enc = generate_encode_expr(elements[1].shape, &format!("{expr}[1]"));
53                format!("concat({a_enc}, {b_enc})")
54            } else if elements.len() == 3 {
55                let a_enc = generate_encode_expr(elements[0].shape, &format!("{expr}[0]"));
56                let b_enc = generate_encode_expr(elements[1].shape, &format!("{expr}[1]"));
57                let c_enc = generate_encode_expr(elements[2].shape, &format!("{expr}[2]"));
58                format!("concat({a_enc}, {b_enc}, {c_enc})")
59            } else if elements.is_empty() {
60                "new Uint8Array(0)".into()
61            } else {
62                // Fallback: concat all
63                let parts: Vec<_> = elements
64                    .iter()
65                    .enumerate()
66                    .map(|(i, p)| generate_encode_expr(p.shape, &format!("{expr}[{i}]")))
67                    .collect();
68                format!("concat({})", parts.join(", "))
69            }
70        }
71        ShapeKind::Struct(StructInfo { fields, kind, .. }) => {
72            if fields.is_empty() || kind == StructKind::Unit {
73                "new Uint8Array(0)".into()
74            } else {
75                let parts: Vec<_> = fields
76                    .iter()
77                    .map(|f| generate_encode_expr(f.shape(), &ts_field_access(expr, f.name)))
78                    .collect();
79                format!("concat({})", parts.join(", "))
80            }
81        }
82        ShapeKind::Enum(EnumInfo { variants, .. }) => {
83            // Generate switch on tag
84            let mut cases = String::new();
85            for (i, v) in variants.iter().enumerate() {
86                cases.push_str(&format!("      case '{}': ", v.name));
87                match classify_variant(v) {
88                    VariantKind::Unit => {
89                        cases.push_str(&format!("return encodeEnumVariant({i});\n"));
90                    }
91                    VariantKind::Newtype { inner } => {
92                        let inner_enc = generate_encode_expr(inner, &format!("{expr}.value"));
93                        cases.push_str(&format!(
94                            "return concat(encodeEnumVariant({i}), {inner_enc});\n"
95                        ));
96                    }
97                    VariantKind::Tuple { fields } | VariantKind::Struct { fields } => {
98                        let field_encs: Vec<_> = fields
99                            .iter()
100                            .map(|f| {
101                                generate_encode_expr(f.shape(), &ts_field_access(expr, f.name))
102                            })
103                            .collect();
104                        cases.push_str(&format!(
105                            "return concat(encodeEnumVariant({i}), {});\n",
106                            field_encs.join(", ")
107                        ));
108                    }
109                }
110            }
111            format!(
112                "(() => {{ switch ({expr}.tag) {{\n{cases}      default: throw new Error('unknown enum variant'); }} }})()"
113            )
114        }
115        ShapeKind::Tx { .. } | ShapeKind::Rx { .. } => {
116            // Streaming types encode as u64 stream ID (varint)
117            // r[impl channeling.type] - Tx/Rx serialize as channel_id on wire.
118            format!("encodeU64({expr}.channelId)")
119        }
120        ShapeKind::Pointer { pointee } => generate_encode_expr(pointee, expr),
121        ShapeKind::Result { .. } => {
122            "/* Result type encoding not yet implemented */ new Uint8Array(0)".to_string()
123        }
124        ShapeKind::TupleStruct { fields } => {
125            let field_encodes: Vec<String> = fields
126                .iter()
127                .enumerate()
128                .map(|(i, f)| generate_encode_expr(f.shape(), &format!("{expr}[{i}]")))
129                .collect();
130            format!("concat({})", field_encodes.join(", "))
131        }
132        ShapeKind::Opaque => "/* unsupported type */ new Uint8Array(0)".to_string(),
133    }
134}
135
136/// Generate encode expression for scalar types.
137fn encode_scalar_expr(scalar: ScalarType, expr: &str) -> String {
138    match scalar {
139        ScalarType::Bool => format!("encodeBool({expr})"),
140        ScalarType::U8 => format!("encodeU8({expr})"),
141        ScalarType::I8 => format!("encodeI8({expr})"),
142        ScalarType::U16 => format!("encodeU16({expr})"),
143        ScalarType::I16 => format!("encodeI16({expr})"),
144        ScalarType::U32 => format!("encodeU32({expr})"),
145        ScalarType::I32 => format!("encodeI32({expr})"),
146        ScalarType::U64 | ScalarType::USize => format!("encodeU64({expr})"),
147        ScalarType::I64 | ScalarType::ISize => format!("encodeI64({expr})"),
148        ScalarType::U128 => format!("encodeU64({expr})"), // Use u64 for now
149        ScalarType::I128 => format!("encodeI64({expr})"), // Use i64 for now
150        ScalarType::F32 => format!("encodeF32({expr})"),
151        ScalarType::F64 => format!("encodeF64({expr})"),
152        ScalarType::Char | ScalarType::Str | ScalarType::String | ScalarType::CowStr => {
153            format!("encodeString({expr})")
154        }
155        ScalarType::Unit => "new Uint8Array(0)".into(),
156        _ => "/* unsupported scalar */ new Uint8Array(0)".to_string(),
157    }
158}
159
160/// Generate an inline encode function for a type.
161pub fn generate_encode_fn_inline(shape: &'static Shape) -> String {
162    // Check for bytes first
163    if is_bytes(shape) {
164        return "(v: Uint8Array) => v".into();
165    }
166
167    match classify_shape(shape) {
168        ShapeKind::Scalar(scalar) => encode_scalar_fn_inline(scalar),
169        ShapeKind::Struct(StructInfo { fields, .. }) => {
170            if fields.is_empty() {
171                return "(v: any) => new Uint8Array(0)".into();
172            }
173            let parts: Vec<_> = fields
174                .iter()
175                .map(|f| generate_encode_expr(f.shape(), &format!("v.{}", f.name)))
176                .collect();
177            if parts.len() == 1 {
178                format!("(v: any) => {}", parts[0])
179            } else {
180                format!("(v: any) => concat({})", parts.join(", "))
181            }
182        }
183        _ => {
184            // Fallback: generate inline encode expression
185            let encode_expr = generate_encode_expr(shape, "v");
186            format!("(v: any) => {encode_expr}")
187        }
188    }
189}
190
191/// Generate inline encode function for scalars.
192fn encode_scalar_fn_inline(scalar: ScalarType) -> String {
193    match scalar {
194        ScalarType::Bool => "(v: boolean) => encodeBool(v)".into(),
195        ScalarType::U8 => "(v: number) => encodeU8(v)".into(),
196        ScalarType::I8 => "(v: number) => encodeI8(v)".into(),
197        ScalarType::U16 => "(v: number) => encodeU16(v)".into(),
198        ScalarType::I16 => "(v: number) => encodeI16(v)".into(),
199        ScalarType::U32 => "(v: number) => encodeU32(v)".into(),
200        ScalarType::I32 => "(v: number) => encodeI32(v)".into(),
201        ScalarType::U64 | ScalarType::USize => "(v: bigint) => encodeU64(v)".into(),
202        ScalarType::I64 | ScalarType::ISize => "(v: bigint) => encodeI64(v)".into(),
203        ScalarType::F32 => "(v: number) => encodeF32(v)".into(),
204        ScalarType::F64 => "(v: number) => encodeF64(v)".into(),
205        ScalarType::Char | ScalarType::Str | ScalarType::String | ScalarType::CowStr => {
206            "(v: string) => encodeString(v)".into()
207        }
208        ScalarType::Unit => "(v: void) => new Uint8Array(0)".into(),
209        _ => "(v: any) => new Uint8Array(0)".into(),
210    }
211}