use {
crate::{identifier::Identifier, schema},
std::{
collections::BTreeMap,
fmt::{self, Write},
path::PathBuf,
},
};
const INDENTATION: &str = " ";
const TYPESCRIPT_KEYWORDS: &[&str] = &[
"abstract",
"any",
"as",
"assert",
"asserts",
"async",
"await",
"bigint",
"boolean",
"break",
"case",
"catch",
"class",
"const",
"constructor",
"continue",
"debugger",
"declare",
"default",
"delete",
"do",
"else",
"enum",
"export",
"extends",
"false",
"finally",
"for",
"from",
"function",
"get",
"global",
"if",
"implements",
"import",
"in",
"infer",
"instanceof",
"interface",
"intrinsic",
"is",
"keyof",
"let",
"module",
"namespace",
"never",
"new",
"null",
"number",
"object",
"of",
"override",
"package",
"private",
"protected",
"public",
"readonly",
"require",
"return",
"set",
"static",
"string",
"super",
"switch",
"symbol",
"this",
"throw",
"true",
"try",
"type",
"typeof",
"undefined",
"unique",
"unknown",
"var",
"void",
"while",
"with",
"yield",
];
#[derive(Clone, Debug)]
struct Module {
children: BTreeMap<Identifier, Module>,
schema: schema::Schema,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum CaseConvention {
Camel,
Pascal,
}
use CaseConvention::{Camel, Pascal};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum Direction {
Atlas,
In,
Out,
}
use Direction::{Atlas, In, Out};
#[allow(clippy::too_many_lines)]
pub fn generate(
typical_version: &str,
schemas: &BTreeMap<schema::Namespace, (schema::Schema, PathBuf, String)>,
) -> String {
let mut tree = Module {
children: BTreeMap::new(),
schema: schema::Schema {
comment: vec![],
imports: BTreeMap::new(),
declarations: vec![],
},
};
for (namespace, (schema, _, _)) in schemas {
insert_schema(&mut tree, namespace, schema);
}
let mut buffer = String::new();
if !tree.children.is_empty() || !tree.schema.declarations.is_empty() {
writeln!(
&mut buffer,
"\
// This file was automatically generated by Typical {}.
// Visit https://github.com/stepchowfun/typical for more information.
/* eslint-disable */
export function unreachable(x: never): never {{
return x;
}}
function zigzagEncode(value: bigint): bigint {{
const twice = value << 1n;
return value < 0n ? -1n - twice : twice;
}}
function zigzagDecode(value: bigint): bigint {{
const half = (value + 1n) >> 1n;
return (value & 1n) === 0n ? half : -half;
}}
function varintSizeFromValue(value: bigint): number {{
if (value < 128n) {{
return 1;
}}
if (value < 16_512n) {{
return 2;
}}
if (value < 2_113_664n) {{
return 3;
}}
if (value < 270_549_120n) {{
return 4;
}}
if (value < 34_630_287_488n) {{
return 5;
}}
if (value < 4_432_676_798_592n) {{
return 6;
}}
if (value < 567_382_630_219_904n) {{
return 7;
}}
if (value < 72_624_976_668_147_840n) {{
return 8;
}}
return 9;
}}
function varintSizeFromFirstByte(firstByte: number): number {{
let trailingZeros = 0;
while (trailingZeros < 8 && (firstByte & 1) !== 1) {{
trailingZeros += 1;
firstByte >>= 1;
}}
return trailingZeros + 1;
}}
function serializeVarint(
dataView: DataView,
offset: number,
value: bigint,
): number {{
if (value < 128n) {{
dataView.setUint8(offset, Number(value << 1n) | 0b0000_0001);
return offset + 1;
}}
if (value < 16_512n) {{
value -= 128n;
dataView.setUint8(offset, Number((value << 2n) % 256n) | 0b0000_0010);
dataView.setUint8(offset + 1, Number(value >> 6n));
return offset + 2;
}}
if (value < 2_113_664n) {{
value -= 16_512n;
dataView.setUint8(offset, Number((value << 3n) % 256n) | 0b0000_0100);
dataView.setUint16(offset + 1, Number((value >> 5n) % 65_536n), true);
return offset + 3;
}}
if (value < 270_549_120n) {{
value -= 2_113_664n;
dataView.setUint8(offset, Number((value << 4n) % 256n) | 0b0000_1000);
dataView.setUint8(offset + 1, Number((value >> 4n) % 256n));
dataView.setUint16(offset + 2, Number((value >> 12n) % 65_536n), true);
return offset + 4;
}}
if (value < 34_630_287_488n) {{
value -= 270_549_120n;
dataView.setUint8(offset, Number((value << 5n) % 256n) | 0b0001_0000);
dataView.setUint32(
offset + 1,
Number((value >> 3n) % 4_294_967_296n),
true,
);
return offset + 5;
}}
if (value < 4_432_676_798_592n) {{
value -= 34_630_287_488n;
dataView.setUint8(offset, Number((value << 6n) % 256n) | 0b0010_0000);
dataView.setUint8(offset + 1, Number((value >> 2n) % 256n));
dataView.setUint32(
offset + 2,
Number((value >> 10n) % 4_294_967_296n),
true,
);
return offset + 6;
}}
if (value < 567_382_630_219_904n) {{
value -= 4_432_676_798_592n;
dataView.setUint8(offset, Number((value << 7n) % 256n) | 0b0100_0000);
dataView.setUint16(offset + 1, Number((value >> 1n) % 65_536n), true);
dataView.setUint32(
offset + 3,
Number((value >> 17n) % 4_294_967_296n),
true,
);
return offset + 7;
}}
if (value < 72_624_976_668_147_840n) {{
value -= 567_382_630_219_904n;
dataView.setUint8(offset, 0b1000_0000);
dataView.setUint8(offset + 1, Number(value % 256n));
dataView.setUint16(offset + 2, Number((value >> 8n) % 65_536n), true);
dataView.setUint32(
offset + 4,
Number((value >> 24n) % 4_294_967_296n),
true,
);
return offset + 8;
}}
value -= 72_624_976_668_147_840n;
dataView.setUint8(offset, 0b0000_0000);
dataView.setBigUint64(offset + 1, value, true);
return offset + 9;
}}
function deserializeVarint(
dataView: DataView,
offset: number,
): [number, bigint] {{
const firstByte = dataView.getUint8(offset);
const sizeMinusOne = varintSizeFromFirstByte(firstByte) - 1;
const offsetPlusOne = offset + 1;
dataView64.setBigUint64(0, 0n, true);
for (let i = 0; i < sizeMinusOne; i += 1) {{
dataView64.setUint8(i, dataView.getUint8(offsetPlusOne + i));
}}
const remainingBytesValue = dataView64.getBigUint64(0, true);
switch (sizeMinusOne) {{
case 0:
return [offset + 1, BigInt(firstByte >> 1)];
case 1:
return [
offset + 2,
128n + BigInt(firstByte >> 2) + (remainingBytesValue << 6n),
];
case 2:
return [
offset + 3,
16_512n + BigInt(firstByte >> 3) + (remainingBytesValue << 5n),
];
case 3:
return [
offset + 4,
2_113_664n + BigInt(firstByte >> 4) + (remainingBytesValue << 4n),
];
case 4:
return [
offset + 5,
270_549_120n + BigInt(firstByte >> 5) + (remainingBytesValue << 3n),
];
case 5:
return [
offset + 6,
34_630_287_488n + BigInt(firstByte >> 6) + (remainingBytesValue << 2n),
];
case 6:
return [
offset + 7,
4_432_676_798_592n +
BigInt(firstByte >> 7) +
(remainingBytesValue << 1n),
];
case 7:
return [offset + 8, 567_382_630_219_904n + remainingBytesValue];
default:
return [
offset + 9,
(72_624_976_668_147_840n + remainingBytesValue) %
18_446_744_073_709_551_616n,
];
}}
}}
function fieldHeaderSize(
index: bigint,
payloadSize: number,
integerEncoded: boolean,
): number {{
switch (payloadSize) {{
case 0:
return varintSizeFromValue(index << 2n);
case 8:
return varintSizeFromValue((index << 2n) | 1n);
default:
if (integerEncoded) {{
return varintSizeFromValue((index << 2n) | 2n);
}}
return (
varintSizeFromValue((index << 2n) | 3n) +
varintSizeFromValue(BigInt(payloadSize))
);
}}
}}
function serializeFieldHeader(
dataView: DataView,
offset: number,
index: bigint,
payloadSize: number,
integerEncoded: boolean,
): number {{
switch (payloadSize) {{
case 0:
return serializeVarint(dataView, offset, index << 2n);
case 8:
return serializeVarint(dataView, offset, (index << 2n) | 1n);
default:
if (integerEncoded) {{
return serializeVarint(dataView, offset, (index << 2n) | 2n);
}}
offset = serializeVarint(dataView, offset, (index << 2n) | 3n);
return serializeVarint(dataView, offset, BigInt(payloadSize));
}}
}}
function deserializeFieldHeader(
dataView: DataView,
offset: number,
): [number, bigint, number] {{
const [newOffset, tag] = deserializeVarint(dataView, offset);
const index = tag >> 2n;
switch (tag & 3n) {{
case 0n:
return [newOffset, index, 0];
case 1n:
return [newOffset, index, 8];
case 2n:
return [newOffset, index, varintSizeFromFirstByte(dataView.getUint8(newOffset))];
default: {{
const [newNewOffset, sizeValue] = deserializeVarint(dataView, newOffset);
return [newNewOffset, index, Number(sizeValue)];
}}
}}
}}
const missingFieldsErrorMessage = 'Struct missing one or more required field(s).';
const dataView64 = new DataView(new ArrayBuffer(8));
const textEncoder = new TextEncoder();
const textDecoder = new TextDecoder();",
typical_version,
)
.unwrap();
writeln!(&mut buffer).unwrap();
write_module_contents(
&mut buffer,
0,
&schema::Namespace { components: vec![] },
&tree.children,
&tree.schema,
)
.unwrap();
}
buffer
}
fn insert_schema(module: &mut Module, namespace: &schema::Namespace, schema: &schema::Schema) {
let mut iter = namespace.components.iter();
if let Some(head) = iter.next() {
if let Some(child) = module.children.get_mut(head) {
insert_schema(
child,
&schema::Namespace {
components: iter.cloned().collect(),
},
schema,
);
} else {
let mut child = Module {
children: BTreeMap::new(),
schema: schema::Schema {
comment: vec![],
imports: BTreeMap::new(),
declarations: vec![],
},
};
insert_schema(
&mut child,
&schema::Namespace {
components: iter.cloned().collect(),
},
schema,
);
module.children.insert(head.clone(), child);
}
} else {
module.schema = schema.clone();
}
}
fn write_module<T: Write>(
buffer: &mut T,
indentation: usize,
namespace: &schema::Namespace,
name: &Identifier,
module: &Module,
) -> Result<(), fmt::Error> {
write_indentation(buffer, indentation)?;
write!(buffer, "export namespace ")?;
write_identifier(buffer, name, Pascal, None)?;
writeln!(buffer, " {{")?;
let mut new_namespace = namespace.clone();
new_namespace.components.push(name.clone());
write_module_contents(
buffer,
indentation + 1,
&new_namespace,
&module.children,
&module.schema,
)?;
write_indentation(buffer, indentation)?;
writeln!(buffer, "}}")?;
Ok(())
}
fn write_module_contents<T: Write>(
buffer: &mut T,
indentation: usize,
namespace: &schema::Namespace,
children: &BTreeMap<Identifier, Module>,
schema: &schema::Schema,
) -> Result<(), fmt::Error> {
let schema_empty = schema.declarations.is_empty();
for (i, (child_name, child)) in children.iter().enumerate() {
write_module(buffer, indentation, namespace, child_name, child)?;
if i < children.len() - 1 || !schema_empty {
writeln!(buffer)?;
}
}
write_schema(buffer, indentation, namespace, schema)?;
Ok(())
}
#[allow(clippy::too_many_lines)]
fn write_schema<T: Write>(
buffer: &mut T,
indentation: usize,
namespace: &schema::Namespace,
schema: &schema::Schema,
) -> Result<(), fmt::Error> {
let mut imports = BTreeMap::new();
for (name, import) in &schema.imports {
imports.insert(name.clone(), import.namespace.clone().unwrap());
}
let mut iter = schema.declarations.iter().peekable();
while let Some(declaration) = iter.next() {
match &declaration.variant {
schema::DeclarationVariant::Struct => {
write_struct(
buffer,
indentation,
&imports,
namespace,
&declaration.name,
&declaration.fields,
Atlas,
)?;
writeln!(buffer)?;
write_struct(
buffer,
indentation,
&imports,
namespace,
&declaration.name,
&declaration.fields,
Out,
)?;
writeln!(buffer)?;
write_struct(
buffer,
indentation,
&imports,
namespace,
&declaration.name,
&declaration.fields,
In,
)?;
writeln!(buffer)?;
write_indentation(buffer, indentation)?;
write!(buffer, "export namespace ")?;
write_identifier(buffer, &declaration.name, Pascal, None)?;
writeln!(buffer, " {{")?;
write_size_function(
buffer,
indentation + 1,
&declaration.name,
&declaration.fields,
)?;
writeln!(buffer)?;
write_serialize_function(buffer, indentation + 1, &declaration.name)?;
writeln!(buffer)?;
write_deserialize_function(buffer, indentation + 1, &declaration.name)?;
writeln!(buffer)?;
write_indentation(buffer, indentation + 1)?;
write!(buffer, "export function outToIn(message: ")?;
write_identifier(buffer, &declaration.name, Pascal, Some(Out))?;
write!(buffer, "): ")?;
write_identifier(buffer, &declaration.name, Pascal, Some(In))?;
writeln!(buffer, " {{")?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "return message;")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "}}")?;
writeln!(buffer)?;
write_indentation(buffer, indentation + 1)?;
write!(buffer, "export function atlas(message: ")?;
write_identifier(buffer, &declaration.name, Pascal, Some(Out))?;
write!(buffer, "): ")?;
write_identifier(buffer, &declaration.name, Pascal, Some(Atlas))?;
writeln!(buffer, " {{")?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "let size = 0;")?;
if !declaration.fields.is_empty() {
writeln!(buffer)?;
write_indentation(buffer, indentation + 2)?;
write!(buffer, "let ")?;
let mut first = true;
for field in &declaration.fields {
if first {
first = false;
} else {
write!(buffer, ", ")?;
}
write!(buffer, "$")?;
write_identifier(buffer, &field.name, Camel, None)?;
}
writeln!(buffer, ";")?;
}
for field in &declaration.fields {
writeln!(buffer)?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "{{")?;
write_indentation(buffer, indentation + 3)?;
writeln!(buffer, "let payloadAtlas;")?;
write_indentation(buffer, indentation + 3)?;
write!(buffer, "const payload = message.")?;
write_identifier(buffer, &field.name, Camel, None)?;
writeln!(buffer, ";")?;
let conditional_indentation = match field.rule {
schema::Rule::Asymmetric | schema::Rule::Required => indentation + 3,
schema::Rule::Optional => {
write_indentation(buffer, indentation + 3)?;
writeln!(buffer, "if (payload !== undefined) {{")?;
indentation + 4
}
};
write_atlas_calculation(
buffer,
conditional_indentation,
&imports,
namespace,
&field.r#type.variant,
true,
)?;
write_indentation(buffer, conditional_indentation)?;
write!(buffer, "$")?;
write_identifier(buffer, &field.name, Camel, None)?;
writeln!(buffer, " = payloadAtlas;")?;
write_indentation(buffer, conditional_indentation)?;
write!(buffer, "const payloadSize = ")?;
write_atlas_lookup(buffer, &field.r#type.variant)?;
writeln!(buffer, ";")?;
write_indentation(buffer, conditional_indentation)?;
writeln!(
buffer,
"size += fieldHeaderSize({}n, payloadSize, {}) + payloadSize;",
field.index,
integer_encoded(&field.r#type),
)?;
match field.rule {
schema::Rule::Asymmetric | schema::Rule::Required => {}
schema::Rule::Optional => {
write_indentation(buffer, indentation + 3)?;
writeln!(buffer, "}}")?;
}
}
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "}}")?;
}
writeln!(buffer)?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "return {{")?;
write_indentation(buffer, indentation + 3)?;
writeln!(buffer, "$size: size,")?;
for field in &declaration.fields {
write_indentation(buffer, indentation + 3)?;
write_identifier(buffer, &field.name, Camel, None)?;
write!(buffer, ": $")?;
write_identifier(buffer, &field.name, Camel, None)?;
writeln!(buffer, ",")?;
}
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "}};")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "}}")?;
writeln!(buffer)?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "export function serializeWithAtlasUnsafe(")?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "dataView: DataView,")?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "offset: number,")?;
write_indentation(buffer, indentation + 2)?;
write!(buffer, "message: ")?;
write_identifier(buffer, &declaration.name, Pascal, Some(Out))?;
writeln!(buffer, ",")?;
write_indentation(buffer, indentation + 2)?;
write!(buffer, "atlas: ")?;
write_identifier(buffer, &declaration.name, Pascal, Some(Atlas))?;
writeln!(buffer, ",")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "): number {{")?;
for field in &declaration.fields {
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "{{")?;
write_indentation(buffer, indentation + 3)?;
write!(buffer, "const payload = message.")?;
write_identifier(buffer, &field.name, Camel, None)?;
writeln!(buffer, ";")?;
write_indentation(buffer, indentation + 3)?;
write!(buffer, "const payloadAtlas = atlas.")?;
write_identifier(buffer, &field.name, Camel, None)?;
writeln!(buffer, ";")?;
let conditional_indentation = match field.rule {
schema::Rule::Asymmetric | schema::Rule::Required => indentation + 3,
schema::Rule::Optional => {
write_indentation(buffer, indentation + 3)?;
writeln!(
buffer,
"if (payload !== undefined && payloadAtlas !== undefined) {{",
)?;
indentation + 4
}
};
write_indentation(buffer, conditional_indentation)?;
write!(buffer, "const payloadSize = ")?;
write_atlas_lookup(buffer, &field.r#type.variant)?;
writeln!(buffer, ";")?;
write_indentation(buffer, conditional_indentation)?;
writeln!(
buffer,
"offset = serializeFieldHeader(dataView, offset, {}n, \
payloadSize, {});",
field.index,
integer_encoded(&field.r#type),
)?;
write_serialization_invocation(
buffer,
conditional_indentation,
&imports,
namespace,
&field.r#type.variant,
true,
)?;
match field.rule {
schema::Rule::Asymmetric | schema::Rule::Required => {}
schema::Rule::Optional => {
write_indentation(buffer, indentation + 3)?;
writeln!(buffer, "}}")?;
}
}
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "}}")?;
writeln!(buffer)?;
}
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "return offset;")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "}}")?;
writeln!(buffer)?;
write_indentation(buffer, indentation + 1)?;
write!(
buffer,
"export function deserializeUnsafe(dataView: DataView): ",
)?;
write_identifier(buffer, &declaration.name, Pascal, Some(In))?;
writeln!(buffer, " {{")?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "const dataViewAlias = dataView;")?;
writeln!(buffer)?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "let offset = 0;")?;
writeln!(buffer)?;
if !declaration.fields.is_empty() {
write_indentation(buffer, indentation + 2)?;
write!(buffer, "let ")?;
let mut first = true;
for field in &declaration.fields {
if first {
first = false;
} else {
write!(buffer, ", ")?;
}
write!(buffer, "$")?;
write_identifier(buffer, &field.name, Camel, None)?;
}
writeln!(buffer, ";")?;
writeln!(buffer)?;
}
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "while (true) {{")?;
write_indentation(buffer, indentation + 3)?;
writeln!(buffer, "let index, payloadSize;")?;
writeln!(buffer)?;
write_indentation(buffer, indentation + 3)?;
writeln!(buffer, "try {{")?;
write_indentation(buffer, indentation + 4)?;
writeln!(
buffer,
"[offset, index, payloadSize] = \
deserializeFieldHeader(dataViewAlias, offset);",
)?;
write_indentation(buffer, indentation + 3)?;
writeln!(buffer, "}} catch (e) {{")?;
write_indentation(buffer, indentation + 4)?;
writeln!(buffer, "if (e instanceof RangeError) {{")?;
write_indentation(buffer, indentation + 5)?;
writeln!(buffer, "break;")?;
write_indentation(buffer, indentation + 4)?;
writeln!(buffer, "}} else {{")?;
write_indentation(buffer, indentation + 5)?;
writeln!(buffer, "throw e;")?;
write_indentation(buffer, indentation + 4)?;
writeln!(buffer, "}}")?;
write_indentation(buffer, indentation + 3)?;
writeln!(buffer, "}}")?;
writeln!(buffer)?;
write_indentation(buffer, indentation + 3)?;
writeln!(buffer, "switch (index) {{")?;
for field in &declaration.fields {
write_indentation(buffer, indentation + 4)?;
writeln!(buffer, "case {}n: {{", field.index)?;
write_indentation(buffer, indentation + 5)?;
writeln!(buffer, "const dataView = new DataView(")?;
write_indentation(buffer, indentation + 6)?;
writeln!(buffer, "dataViewAlias.buffer,")?;
write_indentation(buffer, indentation + 6)?;
writeln!(buffer, "dataViewAlias.byteOffset + offset,")?;
write_indentation(buffer, indentation + 6)?;
writeln!(buffer, "payloadSize,")?;
write_indentation(buffer, indentation + 5)?;
writeln!(buffer, ");")?;
write_indentation(buffer, indentation + 5)?;
writeln!(buffer, "const oldOffset = offset;")?;
write_indentation(buffer, indentation + 5)?;
writeln!(buffer, "offset = 0;")?;
write_deserialization_invocation(
buffer,
indentation + 5,
&imports,
namespace,
&field.r#type.variant,
true,
)?;
write_indentation(buffer, indentation + 5)?;
writeln!(buffer, "offset += oldOffset;")?;
write_indentation(buffer, indentation + 5)?;
write!(buffer, "$")?;
write_identifier(buffer, &field.name, Camel, None)?;
writeln!(buffer, " = payload;")?;
write_indentation(buffer, indentation + 5)?;
writeln!(buffer, "break;")?;
write_indentation(buffer, indentation + 4)?;
writeln!(buffer, "}}")?;
}
write_indentation(buffer, indentation + 4)?;
writeln!(buffer, "default:")?;
write_indentation(buffer, indentation + 5)?;
writeln!(buffer, "offset += payloadSize;")?;
write_indentation(buffer, indentation + 5)?;
writeln!(buffer, "break;")?;
write_indentation(buffer, indentation + 3)?;
writeln!(buffer, "}}")?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "}}")?;
writeln!(buffer)?;
if declaration.fields.iter().any(|field| match field.rule {
schema::Rule::Asymmetric | schema::Rule::Optional => false,
schema::Rule::Required => true,
}) {
write_indentation(buffer, indentation + 2)?;
write!(buffer, "if (")?;
let mut first = true;
for field in &declaration.fields {
match field.rule {
schema::Rule::Asymmetric | schema::Rule::Optional => {}
schema::Rule::Required => {
if first {
first = false;
} else {
writeln!(buffer)?;
write_indentation(buffer, indentation + 3)?;
write!(buffer, "|| ")?;
}
write!(buffer, "$")?;
write_identifier(buffer, &field.name, Camel, None)?;
write!(buffer, " === undefined")?;
}
}
}
writeln!(buffer, ") {{")?;
write_indentation(buffer, indentation + 3)?;
writeln!(buffer, "throw new Error(missingFieldsErrorMessage);")?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "}}")?;
writeln!(buffer)?;
}
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "return {{")?;
for field in &declaration.fields {
write_indentation(buffer, indentation + 3)?;
write_identifier(buffer, &field.name, Camel, None)?;
write!(buffer, ": $")?;
write_identifier(buffer, &field.name, Camel, None)?;
writeln!(buffer, ",")?;
}
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "}};")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "}}")?;
write_indentation(buffer, indentation)?;
writeln!(buffer, "}}")?;
}
schema::DeclarationVariant::Choice => {
write_choice(
buffer,
indentation,
&imports,
namespace,
&declaration.name,
&declaration.fields,
Atlas,
)?;
writeln!(buffer)?;
write_choice(
buffer,
indentation,
&imports,
namespace,
&declaration.name,
&declaration.fields,
Out,
)?;
writeln!(buffer)?;
write_choice(
buffer,
indentation,
&imports,
namespace,
&declaration.name,
&declaration.fields,
In,
)?;
writeln!(buffer)?;
write_indentation(buffer, indentation)?;
write!(buffer, "export namespace ")?;
write_identifier(buffer, &declaration.name, Pascal, None)?;
writeln!(buffer, " {{")?;
write_size_function(
buffer,
indentation + 1,
&declaration.name,
&declaration.fields,
)?;
writeln!(buffer)?;
write_serialize_function(buffer, indentation + 1, &declaration.name)?;
writeln!(buffer)?;
write_deserialize_function(buffer, indentation + 1, &declaration.name)?;
writeln!(buffer)?;
write_indentation(buffer, indentation + 1)?;
write!(buffer, "export function outToIn(message: ")?;
write_identifier(buffer, &declaration.name, Pascal, Some(Out))?;
write!(buffer, "): ")?;
write_identifier(buffer, &declaration.name, Pascal, Some(In))?;
writeln!(buffer, " {{")?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "return message;")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "}}")?;
writeln!(buffer)?;
write_indentation(buffer, indentation + 1)?;
write!(buffer, "export function atlas(message: ")?;
write_identifier(buffer, &declaration.name, Pascal, Some(Out))?;
write!(buffer, "): ")?;
write_identifier(buffer, &declaration.name, Pascal, Some(Atlas))?;
writeln!(buffer, " {{")?;
write_indentation(buffer, indentation + 2)?;
if declaration.fields.is_empty() {
writeln!(buffer, "return unreachable(message);")?;
} else {
writeln!(buffer, "switch (message.$field) {{")?;
for field in &declaration.fields {
write_indentation(buffer, indentation + 3)?;
write!(buffer, "case '")?;
write_identifier(buffer, &field.name, Camel, None)?;
writeln!(buffer, "': {{")?;
write_indentation(buffer, indentation + 4)?;
writeln!(buffer, "let payloadAtlas;")?;
write_indentation(buffer, indentation + 4)?;
if matches!(field.r#type.variant, schema::TypeVariant::Unit) {
writeln!(buffer, "const payload = null;")?;
} else {
write!(buffer, "const payload = message.")?;
write_identifier(buffer, &field.name, Camel, None)?;
writeln!(buffer, ";")?;
}
write_atlas_calculation(
buffer,
indentation + 4,
&imports,
namespace,
&field.r#type.variant,
true,
)?;
write_indentation(buffer, indentation + 4)?;
write!(buffer, "const payloadSize = ")?;
write_atlas_lookup(buffer, &field.r#type.variant)?;
writeln!(buffer, ";")?;
write_indentation(buffer, indentation + 4)?;
match field.rule {
schema::Rule::Asymmetric | schema::Rule::Optional => {
writeln!(
buffer,
"const fallbackAtlas = atlas(message.$fallback);",
)?;
write_indentation(buffer, indentation + 4)?;
write!(buffer, "return {{ $field: '")?;
write_identifier(buffer, &field.name, Camel, None)?;
write!(
buffer,
"', $size: fieldHeaderSize({}n, payloadSize, {}) + \
payloadSize + fallbackAtlas.$size, ",
field.index,
integer_encoded(&field.r#type),
)?;
}
schema::Rule::Required => {
write!(buffer, "return {{ $field: '")?;
write_identifier(buffer, &field.name, Camel, None)?;
write!(
buffer,
"', $size: fieldHeaderSize({}n, payloadSize, {}) + \
payloadSize, ",
field.index,
integer_encoded(&field.r#type),
)?;
}
}
write_identifier(buffer, &field.name, Camel, None)?;
write!(buffer, ": payloadAtlas")?;
match field.rule {
schema::Rule::Asymmetric | schema::Rule::Optional => {
writeln!(buffer, ", $fallback: fallbackAtlas }};")?;
}
schema::Rule::Required => {
writeln!(buffer, " }};")?;
}
}
write_indentation(buffer, indentation + 3)?;
writeln!(buffer, "}}")?;
}
write_indentation(buffer, indentation + 3)?;
writeln!(buffer, "default:")?;
write_indentation(buffer, indentation + 4)?;
writeln!(buffer, "return unreachable(message);")?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "}}")?;
}
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "}}")?;
writeln!(buffer)?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "export function serializeWithAtlasUnsafe(")?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "dataView: DataView,")?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "offset: number,")?;
write_indentation(buffer, indentation + 2)?;
write!(buffer, "message: ")?;
write_identifier(buffer, &declaration.name, Pascal, Some(Out))?;
writeln!(buffer, ",")?;
write_indentation(buffer, indentation + 2)?;
write!(buffer, "atlas: ")?;
write_identifier(buffer, &declaration.name, Pascal, Some(Atlas))?;
writeln!(buffer, ",")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "): number {{")?;
write_indentation(buffer, indentation + 2)?;
if declaration.fields.is_empty() {
writeln!(buffer, "return unreachable(message);")?;
} else {
writeln!(buffer, "switch (message.$field) {{")?;
for field in &declaration.fields {
write_indentation(buffer, indentation + 3)?;
write!(buffer, "case '")?;
write_identifier(buffer, &field.name, Camel, None)?;
writeln!(buffer, "': {{")?;
write_indentation(buffer, indentation + 4)?;
if matches!(field.r#type.variant, schema::TypeVariant::Unit) {
writeln!(buffer, "const payload = null;")?;
write_indentation(buffer, indentation + 4)?;
writeln!(buffer, "const payloadAtlas = 0;")?;
} else {
write!(buffer, "const payload = message.")?;
write_identifier(buffer, &field.name, Camel, None)?;
writeln!(buffer, ";")?;
write_indentation(buffer, indentation + 4)?;
write!(buffer, "const payloadAtlas = (atlas as any).")?;
write_identifier(buffer, &field.name, Camel, None)?;
writeln!(buffer, ";")?;
}
write_indentation(buffer, indentation + 4)?;
write!(buffer, "const payloadSize = ")?;
write_atlas_lookup(buffer, &field.r#type.variant)?;
writeln!(buffer, ";")?;
write_indentation(buffer, indentation + 4)?;
writeln!(
buffer,
"offset = serializeFieldHeader(dataView, offset, {}n, \
payloadSize, {});",
field.index,
integer_encoded(&field.r#type),
)?;
write_serialization_invocation(
buffer,
indentation + 4,
&imports,
namespace,
&field.r#type.variant,
true,
)?;
match field.rule {
schema::Rule::Asymmetric | schema::Rule::Optional => {
write_indentation(buffer, indentation + 4)?;
writeln!(
buffer,
"offset = serializeWithAtlasUnsafe(dataView, offset, \
message.$fallback, (atlas as any).$fallback);",
)?;
}
schema::Rule::Required => {}
}
write_indentation(buffer, indentation + 4)?;
writeln!(buffer, "return offset;")?;
write_indentation(buffer, indentation + 3)?;
writeln!(buffer, "}}")?;
}
write_indentation(buffer, indentation + 3)?;
writeln!(buffer, "default:")?;
write_indentation(buffer, indentation + 4)?;
writeln!(buffer, "return unreachable(message);")?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "}}")?;
}
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "}}")?;
writeln!(buffer)?;
write_indentation(buffer, indentation + 1)?;
write!(
buffer,
"export function deserializeUnsafe(dataView: DataView): ",
)?;
write_identifier(buffer, &declaration.name, Pascal, Some(In))?;
writeln!(buffer, " {{")?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "const dataViewAlias = dataView;")?;
writeln!(buffer)?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "let offset = 0;")?;
writeln!(buffer)?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "while (true) {{")?;
write_indentation(buffer, indentation + 3)?;
writeln!(
buffer,
"const [newOffset, index, payloadSize] = \
deserializeFieldHeader(dataViewAlias, offset);",
)?;
write_indentation(buffer, indentation + 3)?;
writeln!(buffer, "offset = newOffset;")?;
writeln!(buffer)?;
write_indentation(buffer, indentation + 3)?;
writeln!(buffer, "switch (index) {{")?;
for field in &declaration.fields {
write_indentation(buffer, indentation + 4)?;
writeln!(buffer, "case {}n: {{", field.index)?;
write_indentation(buffer, indentation + 5)?;
writeln!(buffer, "const dataView = new DataView(")?;
write_indentation(buffer, indentation + 6)?;
writeln!(buffer, "dataViewAlias.buffer,")?;
write_indentation(buffer, indentation + 6)?;
writeln!(buffer, "dataViewAlias.byteOffset + offset,")?;
write_indentation(buffer, indentation + 6)?;
writeln!(buffer, "payloadSize,")?;
write_indentation(buffer, indentation + 5)?;
writeln!(buffer, ");")?;
write_indentation(buffer, indentation + 5)?;
writeln!(buffer, "const oldOffset = offset;")?;
write_indentation(buffer, indentation + 5)?;
writeln!(buffer, "offset = 0;")?;
write_deserialization_invocation(
buffer,
indentation + 5,
&imports,
namespace,
&field.r#type.variant,
true,
)?;
match field.rule {
schema::Rule::Asymmetric | schema::Rule::Required => {}
schema::Rule::Optional => {
write_indentation(buffer, indentation + 5)?;
writeln!(buffer, "offset += oldOffset;")?;
write_indentation(buffer, indentation + 5)?;
writeln!(buffer, "const $fallback = deserializeUnsafe(")?;
write_indentation(buffer, indentation + 6)?;
writeln!(buffer, "new DataView(")?;
write_indentation(buffer, indentation + 7)?;
writeln!(buffer, "dataViewAlias.buffer,")?;
write_indentation(buffer, indentation + 7)?;
writeln!(buffer, "dataViewAlias.byteOffset + offset,")?;
write_indentation(buffer, indentation + 7)?;
writeln!(buffer, "dataViewAlias.byteLength - offset,")?;
write_indentation(buffer, indentation + 6)?;
writeln!(buffer, "),")?;
write_indentation(buffer, indentation + 5)?;
writeln!(buffer, ");")?;
}
}
write_indentation(buffer, indentation + 5)?;
writeln!(buffer, "return {{")?;
write_indentation(buffer, indentation + 6)?;
write!(buffer, "$field: '")?;
write_identifier(buffer, &field.name, Camel, None)?;
writeln!(buffer, "',")?;
if !matches!(field.r#type.variant, schema::TypeVariant::Unit) {
write_indentation(buffer, indentation + 6)?;
write_identifier(buffer, &field.name, Camel, None)?;
writeln!(buffer, ": payload,")?;
}
match field.rule {
schema::Rule::Asymmetric | schema::Rule::Required => {}
schema::Rule::Optional => {
write_indentation(buffer, indentation + 6)?;
writeln!(buffer, "$fallback,")?;
}
}
write_indentation(buffer, indentation + 5)?;
writeln!(buffer, "}};")?;
write_indentation(buffer, indentation + 4)?;
writeln!(buffer, "}}")?;
}
write_indentation(buffer, indentation + 4)?;
writeln!(buffer, "default:")?;
write_indentation(buffer, indentation + 5)?;
writeln!(buffer, "offset += payloadSize;")?;
write_indentation(buffer, indentation + 5)?;
writeln!(buffer, "break;")?;
write_indentation(buffer, indentation + 3)?;
writeln!(buffer, "}}")?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "}}")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "}}")?;
write_indentation(buffer, indentation)?;
writeln!(buffer, "}}")?;
}
}
if iter.peek().is_some() {
writeln!(buffer)?;
}
}
Ok(())
}
fn write_struct<T: Write>(
buffer: &mut T,
indentation: usize,
imports: &BTreeMap<Identifier, schema::Namespace>,
namespace: &schema::Namespace,
name: &Identifier,
fields: &[schema::Field],
direction: Direction,
) -> Result<(), fmt::Error> {
write_indentation(buffer, indentation)?;
write!(buffer, "export type ")?;
write_identifier(buffer, name, Pascal, Some(direction))?;
writeln!(buffer, " = {{")?;
match direction {
Direction::Atlas => {
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "$size: number;")?;
}
Direction::In | Direction::Out => {}
}
for field in fields {
write_indentation(buffer, indentation + 1)?;
write_identifier(buffer, &field.name, Camel, None)?;
match field.rule {
schema::Rule::Asymmetric => match direction {
Direction::Atlas | Direction::Out => {}
Direction::In => {
write!(buffer, "?")?;
}
},
schema::Rule::Optional => {
write!(buffer, "?")?;
}
schema::Rule::Required => {}
}
write!(buffer, ": ")?;
write_type(buffer, imports, namespace, &field.r#type.variant, direction)?;
writeln!(buffer, ";")?;
}
write_indentation(buffer, indentation)?;
writeln!(buffer, "}};")?;
Ok(())
}
fn write_choice<T: Write>(
buffer: &mut T,
indentation: usize,
imports: &BTreeMap<Identifier, schema::Namespace>,
namespace: &schema::Namespace,
name: &Identifier,
fields: &[schema::Field],
direction: Direction,
) -> Result<(), fmt::Error> {
write_indentation(buffer, indentation)?;
write!(buffer, "export type ")?;
write_identifier(buffer, name, Pascal, Some(direction))?;
write!(buffer, " =")?;
for field in fields {
writeln!(buffer)?;
write_indentation(buffer, indentation + 1)?;
write!(buffer, "| {{ $field: '")?;
write_identifier(buffer, &field.name, Camel, None)?;
write!(buffer, "'")?;
match direction {
Direction::Atlas => {
write!(buffer, "; $size: number; ")?;
write_identifier(buffer, &field.name, Camel, None)?;
write!(buffer, ": ")?;
write_type(buffer, imports, namespace, &field.r#type.variant, direction)?;
}
Direction::In | Direction::Out => {
if !matches!(field.r#type.variant, schema::TypeVariant::Unit) {
write!(buffer, "; ")?;
write_identifier(buffer, &field.name, Camel, None)?;
write!(buffer, ": ")?;
write_type(buffer, imports, namespace, &field.r#type.variant, direction)?;
}
}
};
if match field.rule {
schema::Rule::Asymmetric => match direction {
Direction::Atlas | Direction::Out => true,
Direction::In => false,
},
schema::Rule::Optional => true,
schema::Rule::Required => false,
} {
write!(buffer, "; $fallback: ")?;
write_identifier(buffer, name, Pascal, Some(direction))?;
}
write!(buffer, " }}")?;
}
match direction {
Direction::Atlas => {}
Direction::In | Direction::Out => {
if fields.len() == 1 {
writeln!(buffer)?;
write_indentation(buffer, indentation + 1)?;
write!(buffer, "| {{ $field: never }}")?;
}
}
}
if fields.is_empty() {
write!(buffer, " never")?;
}
writeln!(buffer, ";")?;
Ok(())
}
fn write_size_function<T: Write>(
buffer: &mut T,
indentation: usize,
name: &Identifier,
fields: &[schema::Field],
) -> Result<(), fmt::Error> {
write_indentation(buffer, indentation)?;
write!(buffer, "export function size(message: ")?;
write_identifier(buffer, name, Pascal, Some(Out))?;
writeln!(buffer, "): number {{")?;
write_indentation(buffer, indentation + 1)?;
if fields.is_empty() {
writeln!(buffer, "return 0;")?;
} else {
writeln!(buffer, "return atlas(message).$size;")?;
}
write_indentation(buffer, indentation)?;
writeln!(buffer, "}}")
}
fn write_serialize_function<T: Write>(
buffer: &mut T,
indentation: usize,
name: &Identifier,
) -> Result<(), fmt::Error> {
write_indentation(buffer, indentation)?;
write!(buffer, "export function serialize(message: ")?;
write_identifier(buffer, name, Pascal, Some(Out))?;
writeln!(buffer, "): ArrayBuffer {{")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "const messageAtlas = atlas(message);")?;
write_indentation(buffer, indentation + 1)?;
writeln!(
buffer,
"const arrayBuffer = \
new ArrayBuffer((messageAtlas as {{ $size: number }}).$size);",
)?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "const dataView = new DataView(arrayBuffer);")?;
write_indentation(buffer, indentation + 1)?;
writeln!(
buffer,
"serializeWithAtlasUnsafe(dataView, 0, message, messageAtlas);",
)?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "return arrayBuffer;")?;
write_indentation(buffer, indentation)?;
writeln!(buffer, "}}")
}
fn write_deserialize_function<T: Write>(
buffer: &mut T,
indentation: usize,
name: &Identifier,
) -> Result<(), fmt::Error> {
write_indentation(buffer, indentation)?;
write!(buffer, "export function deserialize(dataView: DataView): ")?;
write_identifier(buffer, name, Pascal, Some(In))?;
writeln!(buffer, " | Error {{")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "try {{")?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "return deserializeUnsafe(dataView);")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "}} catch (e) {{")?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "return e as Error;")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "}}")?;
write_indentation(buffer, indentation)?;
writeln!(buffer, "}}")
}
fn write_type<T: Write>(
buffer: &mut T,
imports: &BTreeMap<Identifier, schema::Namespace>,
namespace: &schema::Namespace,
type_variant: &schema::TypeVariant,
direction: Direction,
) -> Result<(), fmt::Error> {
match type_variant {
schema::TypeVariant::Array(inner_type) => match direction {
Direction::Atlas => match &inner_type.variant {
schema::TypeVariant::Array(_)
| schema::TypeVariant::Bytes
| schema::TypeVariant::Custom(_, _)
| schema::TypeVariant::String => {
write!(buffer, "{{ $size: number; $elements: ")?;
write_type(buffer, imports, namespace, &inner_type.variant, direction)?;
write!(buffer, "[] }}")?;
}
schema::TypeVariant::Bool
| schema::TypeVariant::F64
| schema::TypeVariant::S64
| schema::TypeVariant::U64
| schema::TypeVariant::Unit => {
write!(buffer, "number")?;
}
},
Direction::In | Direction::Out => {
write_type(buffer, imports, namespace, &inner_type.variant, direction)?;
write!(buffer, "[]")?;
}
},
schema::TypeVariant::Bool => match direction {
Direction::Atlas => {
write!(buffer, "number")?;
}
Direction::In | Direction::Out => {
write!(buffer, "boolean")?;
}
},
schema::TypeVariant::Bytes => match direction {
Direction::Atlas => {
write!(buffer, "number")?;
}
Direction::In | Direction::Out => {
write!(buffer, "ArrayBuffer")?;
}
},
schema::TypeVariant::Custom(import, name) => {
write_custom_type(buffer, imports, namespace, import, name, Some(direction))?;
}
schema::TypeVariant::F64 => {
write!(buffer, "number")?;
}
schema::TypeVariant::S64 | schema::TypeVariant::U64 => match direction {
Direction::Atlas => {
write!(buffer, "number")?;
}
Direction::In | Direction::Out => {
write!(buffer, "bigint")?;
}
},
schema::TypeVariant::String => match direction {
Direction::Atlas => {
write!(buffer, "Uint8Array")?;
}
Direction::In | Direction::Out => {
write!(buffer, "string")?;
}
},
schema::TypeVariant::Unit => match direction {
Direction::Atlas => {
write!(buffer, "number")?;
}
Direction::In | Direction::Out => {
write!(buffer, "null")?;
}
},
}
Ok(())
}
fn write_custom_type<T: Write>(
buffer: &mut T,
imports: &BTreeMap<Identifier, schema::Namespace>,
namespace: &schema::Namespace,
import: &Option<Identifier>,
name: &Identifier,
direction: Option<Direction>,
) -> Result<(), fmt::Error> {
let type_namespace = schema::Namespace {
components: import.as_ref().map_or_else(
|| namespace.components.clone(),
|import| imports[import].components.clone(),
),
};
for component in type_namespace.components {
write_identifier(buffer, &component, Pascal, None)?;
write!(buffer, ".")?;
}
write_identifier(buffer, name, Pascal, direction)
}
fn write_identifier<T: Write>(
buffer: &mut T,
identifier: &Identifier,
case: CaseConvention,
suffix: Option<Direction>,
) -> Result<(), fmt::Error> {
let identifier_with_suffix = suffix.map_or_else(
|| identifier.clone(),
|suffix| {
identifier.join(
&match suffix {
Direction::Atlas => "Atlas",
Direction::In => "In",
Direction::Out => "Out",
}
.into(),
)
},
);
let converted_identifier = match case {
CaseConvention::Camel => identifier_with_suffix.camel_case(),
CaseConvention::Pascal => identifier_with_suffix.pascal_case(),
};
if TYPESCRIPT_KEYWORDS
.iter()
.any(|keyword| converted_identifier == *keyword)
{
write!(buffer, "_")?;
}
write!(buffer, "{}", converted_identifier)?;
Ok(())
}
fn write_indentation<T: Write>(buffer: &mut T, indentation: usize) -> Result<(), fmt::Error> {
for _ in 0..indentation {
write!(buffer, "{}", INDENTATION)?;
}
Ok(())
}
#[allow(clippy::too_many_lines)]
fn write_atlas_calculation<T: Write>(
buffer: &mut T,
indentation: usize,
imports: &BTreeMap<Identifier, schema::Namespace>,
namespace: &schema::Namespace,
type_variant: &schema::TypeVariant,
is_field: bool,
) -> Result<(), fmt::Error> {
match type_variant {
schema::TypeVariant::Array(inner_type) => match &inner_type.variant {
schema::TypeVariant::Array(_)
| schema::TypeVariant::Bytes
| schema::TypeVariant::Custom(_, _)
| schema::TypeVariant::String => {
write_indentation(buffer, indentation)?;
writeln!(buffer, "{{")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "let size = 0;")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "let elements = [];")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "const oldPayload = payload;")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "for (let i = 0; i < oldPayload.length; i += 1) {{")?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "const payload = oldPayload[i];")?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "let payloadAtlas;")?;
write_atlas_calculation(
buffer,
indentation + 2,
imports,
namespace,
&inner_type.variant,
false,
)?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "elements.push(payloadAtlas);")?;
write_indentation(buffer, indentation + 2)?;
write!(buffer, "const payloadSize = ")?;
write_atlas_lookup(buffer, &inner_type.variant)?;
writeln!(buffer, ";")?;
write_indentation(buffer, indentation + 2)?;
writeln!(
buffer,
"size += varintSizeFromValue(BigInt(payloadSize)) + payloadSize;",
)?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "}}")?;
write_indentation(buffer, indentation + 1)?;
writeln!(
buffer,
"payloadAtlas = {{ $size: size, $elements: elements }};",
)?;
write_indentation(buffer, indentation)?;
writeln!(buffer, "}}")
}
schema::TypeVariant::Bool | schema::TypeVariant::S64 | schema::TypeVariant::U64 => {
write_indentation(buffer, indentation)?;
writeln!(buffer, "{{")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "let arraySize = 0;")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "const oldPayload = payload;")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "for (let i = 0; i < oldPayload.length; i += 1) {{")?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "const payload = oldPayload[i];")?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "let payloadAtlas = 0;")?;
write_atlas_calculation(
buffer,
indentation + 2,
imports,
namespace,
&inner_type.variant,
false,
)?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "arraySize += payloadAtlas;")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "}}")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "payloadAtlas = arraySize;")?;
write_indentation(buffer, indentation)?;
writeln!(buffer, "}}")
}
schema::TypeVariant::F64 => {
write_indentation(buffer, indentation)?;
writeln!(buffer, "payloadAtlas = 8 * payload.length;")
}
schema::TypeVariant::Unit => {
write_indentation(buffer, indentation)?;
writeln!(buffer, "{{")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "const oldPayload = payload;")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "{{")?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "const payload = BigInt(oldPayload.length);")?;
write_atlas_calculation(
buffer,
indentation + 2,
imports,
namespace,
&schema::TypeVariant::U64,
is_field,
)?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "}}")?;
write_indentation(buffer, indentation)?;
writeln!(buffer, "}}")
}
},
schema::TypeVariant::Bool => {
write_indentation(buffer, indentation)?;
if is_field {
writeln!(buffer, "if (payload) {{")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "payloadAtlas = 1;")?;
write_indentation(buffer, indentation)?;
writeln!(buffer, "}} else {{")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "payloadAtlas = 0;")?;
write_indentation(buffer, indentation)?;
writeln!(buffer, "}}")
} else {
writeln!(buffer, "payloadAtlas = 1;")
}
}
schema::TypeVariant::Bytes => {
write_indentation(buffer, indentation)?;
writeln!(buffer, "payloadAtlas = payload.byteLength;")
}
schema::TypeVariant::String => {
write_indentation(buffer, indentation)?;
writeln!(buffer, "payloadAtlas = textEncoder.encode(payload);")
}
schema::TypeVariant::Custom(import, name) => {
write_indentation(buffer, indentation)?;
write!(buffer, "payloadAtlas = ")?;
write_custom_type(buffer, imports, namespace, import, name, None)?;
writeln!(buffer, ".atlas(payload);")
}
schema::TypeVariant::F64 => {
write_indentation(buffer, indentation)?;
if is_field {
writeln!(buffer, "if (Object.is(payload, 0)) {{")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "payloadAtlas = 0;")?;
write_indentation(buffer, indentation)?;
writeln!(buffer, "}} else {{")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "payloadAtlas = 8;")?;
write_indentation(buffer, indentation)?;
writeln!(buffer, "}}")
} else {
writeln!(buffer, "payloadAtlas = 8;")
}
}
schema::TypeVariant::S64 => {
write_indentation(buffer, indentation)?;
if is_field {
writeln!(buffer, "{{")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "const zigzag = zigzagEncode(payload);")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "if (zigzag === 0n) {{")?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "payloadAtlas = 0;")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "}} else if (zigzag < 567_382_630_219_904n) {{")?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "payloadAtlas = varintSizeFromValue(zigzag);")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "}} else {{")?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "payloadAtlas = 8;")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "}}")?;
write_indentation(buffer, indentation)?;
writeln!(buffer, "}}")
} else {
writeln!(
buffer,
"payloadAtlas = varintSizeFromValue(zigzagEncode(payload));",
)
}
}
schema::TypeVariant::U64 => {
write_indentation(buffer, indentation)?;
if is_field {
writeln!(buffer, "if (payload === 0n) {{")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "payloadAtlas = 0;")?;
write_indentation(buffer, indentation)?;
writeln!(buffer, "}} else if (payload < 567_382_630_219_904n) {{")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "payloadAtlas = varintSizeFromValue(payload);")?;
write_indentation(buffer, indentation)?;
writeln!(buffer, "}} else {{")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "payloadAtlas = 8;")?;
write_indentation(buffer, indentation)?;
writeln!(buffer, "}}")
} else {
writeln!(buffer, "payloadAtlas = varintSizeFromValue(payload);")
}
}
schema::TypeVariant::Unit => {
write_indentation(buffer, indentation)?;
writeln!(buffer, "payloadAtlas = 0;")
}
}
}
fn write_atlas_lookup<T: Write>(
buffer: &mut T,
type_variant: &schema::TypeVariant,
) -> Result<(), fmt::Error> {
match type_variant {
schema::TypeVariant::Array(inner_type) => match &inner_type.variant {
schema::TypeVariant::Array(_)
| schema::TypeVariant::Bytes
| schema::TypeVariant::Custom(_, _)
| schema::TypeVariant::String => write!(buffer, "payloadAtlas.$size"),
schema::TypeVariant::Bool
| schema::TypeVariant::F64
| schema::TypeVariant::S64
| schema::TypeVariant::U64
| schema::TypeVariant::Unit => write!(buffer, "payloadAtlas"),
},
schema::TypeVariant::Bool
| schema::TypeVariant::Bytes
| schema::TypeVariant::F64
| schema::TypeVariant::S64
| schema::TypeVariant::U64
| schema::TypeVariant::Unit => write!(buffer, "payloadAtlas"),
schema::TypeVariant::Custom(_, _) => {
write!(buffer, "(payloadAtlas as {{ $size: number }}).$size")
}
schema::TypeVariant::String => write!(buffer, "payloadAtlas.byteLength"),
}
}
#[allow(clippy::too_many_lines)]
fn write_serialization_invocation<T: Write>(
buffer: &mut T,
indentation: usize,
imports: &BTreeMap<Identifier, schema::Namespace>,
namespace: &schema::Namespace,
type_variant: &schema::TypeVariant,
is_field: bool,
) -> Result<(), fmt::Error> {
match type_variant {
schema::TypeVariant::Array(inner_type) => match &inner_type.variant {
schema::TypeVariant::Array(_)
| schema::TypeVariant::Bytes
| schema::TypeVariant::Custom(_, _)
| schema::TypeVariant::String => {
write_indentation(buffer, indentation)?;
writeln!(buffer, "{{")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "const oldPayload = payload;")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "const oldPayloadAtlas = payloadAtlas;")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "for (let i = 0; i < oldPayload.length; i += 1) {{")?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "const payload = oldPayload[i];")?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "const payloadAtlas = oldPayloadAtlas.$elements[i];")?;
write_indentation(buffer, indentation + 2)?;
write!(buffer, "offset = serializeVarint(dataView, offset, BigInt(")?;
write_atlas_lookup(buffer, &inner_type.variant)?;
writeln!(buffer, "));")?;
write_serialization_invocation(
buffer,
indentation + 2,
imports,
namespace,
&inner_type.variant,
false,
)?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "}}")?;
write_indentation(buffer, indentation)?;
writeln!(buffer, "}}")
}
schema::TypeVariant::Bool
| schema::TypeVariant::S64
| schema::TypeVariant::U64
| schema::TypeVariant::F64 => {
write_indentation(buffer, indentation)?;
writeln!(buffer, "{{")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "const oldPayload = payload;")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "for (let i = 0; i < oldPayload.length; i += 1) {{")?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "const payload = oldPayload[i];")?;
write_serialization_invocation(
buffer,
indentation + 2,
imports,
namespace,
&inner_type.variant,
false,
)?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "}}")?;
write_indentation(buffer, indentation)?;
writeln!(buffer, "}}")
}
schema::TypeVariant::Unit => {
write_indentation(buffer, indentation)?;
writeln!(buffer, "{{")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "const varint = BigInt(payload.length);")?;
write_u64_serialization_invocation(buffer, indentation + 1, is_field)?;
write_indentation(buffer, indentation)?;
writeln!(buffer, "}}")
}
},
schema::TypeVariant::Bool => {
write_indentation(buffer, indentation)?;
writeln!(buffer, "{{")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "const varint = payload ? 1n : 0n;")?;
write_u64_serialization_invocation(buffer, indentation + 1, is_field)?;
write_indentation(buffer, indentation)?;
writeln!(buffer, "}}")
}
schema::TypeVariant::Bytes => {
write_indentation(buffer, indentation)?;
writeln!(buffer, "{{")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "const sourceBuffer = new Uint8Array(payload);")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "const targetBuffer = new Uint8Array(")?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "dataView.buffer,")?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "dataView.byteOffset,")?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "dataView.byteLength,")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, ");")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "targetBuffer.set(sourceBuffer, offset);")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "offset += sourceBuffer.byteLength;")?;
write_indentation(buffer, indentation)?;
writeln!(buffer, "}}")
}
schema::TypeVariant::Custom(import, name) => {
write_indentation(buffer, indentation)?;
write!(buffer, "offset = ")?;
write_custom_type(buffer, imports, namespace, import, name, None)?;
writeln!(
buffer,
".serializeWithAtlasUnsafe(dataView, offset, payload, payloadAtlas);",
)
}
schema::TypeVariant::F64 => {
write_indentation(buffer, indentation)?;
if is_field {
write!(buffer, "if (")?;
write_atlas_lookup(buffer, type_variant)?;
writeln!(buffer, " !== 0) {{")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "dataView.setFloat64(offset, payload, true);")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "offset += 8;")?;
write_indentation(buffer, indentation)?;
writeln!(buffer, "}}")
} else {
writeln!(buffer, "dataView.setFloat64(offset, payload, true);")?;
write_indentation(buffer, indentation)?;
writeln!(buffer, "offset += 8;")
}
}
schema::TypeVariant::S64 => {
write_indentation(buffer, indentation)?;
writeln!(buffer, "{{")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "const varint = zigzagEncode(payload);")?;
write_u64_serialization_invocation(buffer, indentation + 1, is_field)?;
write_indentation(buffer, indentation)?;
writeln!(buffer, "}}")
}
schema::TypeVariant::String => {
write_indentation(buffer, indentation)?;
writeln!(buffer, "{{")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "const targetBuffer = new Uint8Array(")?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "dataView.buffer,")?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "dataView.byteOffset,")?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "dataView.byteLength,")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, ");")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "targetBuffer.set(payloadAtlas, offset);")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "offset += payloadAtlas.byteLength;")?;
write_indentation(buffer, indentation)?;
writeln!(buffer, "}}")
}
schema::TypeVariant::U64 => {
write_indentation(buffer, indentation)?;
writeln!(buffer, "{{")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "const varint = payload;")?;
write_u64_serialization_invocation(buffer, indentation + 1, is_field)?;
write_indentation(buffer, indentation)?;
writeln!(buffer, "}}")
}
schema::TypeVariant::Unit => Ok(()),
}
}
fn write_u64_serialization_invocation<T: Write>(
buffer: &mut T,
indentation: usize,
is_field: bool,
) -> Result<(), fmt::Error> {
write_indentation(buffer, indentation)?;
if is_field {
writeln!(buffer, "if (varint > 567_382_630_219_903n) {{")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "dataView.setBigUint64(offset, varint, true);")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "offset += 8;")?;
write_indentation(buffer, indentation)?;
writeln!(buffer, "}} else if (varint !== 0n) {{")?;
write_indentation(buffer, indentation + 1)?;
writeln!(
buffer,
"offset = serializeVarint(dataView, offset, varint);",
)?;
write_indentation(buffer, indentation)?;
writeln!(buffer, "}}")
} else {
writeln!(
buffer,
"offset = serializeVarint(dataView, offset, varint);",
)
}
}
#[allow(clippy::too_many_lines)]
fn write_deserialization_invocation<T: Write>(
buffer: &mut T,
indentation: usize,
imports: &BTreeMap<Identifier, schema::Namespace>,
namespace: &schema::Namespace,
type_variant: &schema::TypeVariant,
is_field: bool,
) -> Result<(), fmt::Error> {
match type_variant {
schema::TypeVariant::Array(inner_type) => match &inner_type.variant {
schema::TypeVariant::Array(_)
| schema::TypeVariant::Bytes
| schema::TypeVariant::Custom(_, _)
| schema::TypeVariant::String => {
write_indentation(buffer, indentation)?;
write!(buffer, "let payload: ")?;
write_type(buffer, imports, namespace, &inner_type.variant, In)?;
writeln!(buffer, "[] = [];")?;
write_indentation(buffer, indentation)?;
writeln!(buffer, "{{")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "const dataViewAlias = dataView;")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "const payloadAlias = payload;")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "{{")?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "while (true) {{")?;
write_indentation(buffer, indentation + 3)?;
writeln!(buffer, "let payloadSizeBig;")?;
write_indentation(buffer, indentation + 3)?;
writeln!(buffer, "try {{")?;
write_indentation(buffer, indentation + 4)?;
writeln!(
buffer,
"[offset, payloadSizeBig] = deserializeVarint(dataViewAlias, offset);",
)?;
write_indentation(buffer, indentation + 3)?;
writeln!(buffer, "}} catch (e) {{")?;
write_indentation(buffer, indentation + 4)?;
writeln!(buffer, "if (e instanceof RangeError) {{")?;
write_indentation(buffer, indentation + 5)?;
writeln!(buffer, "break;")?;
write_indentation(buffer, indentation + 4)?;
writeln!(buffer, "}} else {{")?;
write_indentation(buffer, indentation + 5)?;
writeln!(buffer, "throw e;")?;
write_indentation(buffer, indentation + 4)?;
writeln!(buffer, "}}")?;
write_indentation(buffer, indentation + 3)?;
writeln!(buffer, "}}")?;
write_indentation(buffer, indentation + 3)?;
writeln!(buffer, "const dataView = new DataView(")?;
write_indentation(buffer, indentation + 4)?;
writeln!(buffer, "dataViewAlias.buffer,")?;
write_indentation(buffer, indentation + 4)?;
writeln!(buffer, "dataViewAlias.byteOffset + offset,")?;
write_indentation(buffer, indentation + 4)?;
writeln!(buffer, "Number(payloadSizeBig),")?;
write_indentation(buffer, indentation + 3)?;
writeln!(buffer, ");")?;
write_indentation(buffer, indentation + 3)?;
writeln!(buffer, "const oldOffset = offset;")?;
write_indentation(buffer, indentation + 3)?;
writeln!(buffer, "offset = 0;")?;
write_deserialization_invocation(
buffer,
indentation + 3,
imports,
namespace,
&inner_type.variant,
false,
)?;
write_indentation(buffer, indentation + 3)?;
writeln!(buffer, "offset += oldOffset;")?;
write_indentation(buffer, indentation + 3)?;
writeln!(buffer, "payloadAlias.push(payload);")?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "}}")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "}}")?;
write_indentation(buffer, indentation)?;
writeln!(buffer, "}}")
}
schema::TypeVariant::Bool
| schema::TypeVariant::S64
| schema::TypeVariant::U64
| schema::TypeVariant::F64 => {
write_indentation(buffer, indentation)?;
write!(buffer, "let payload: ")?;
write_type(buffer, imports, namespace, &inner_type.variant, In)?;
writeln!(buffer, "[] = [];")?;
write_indentation(buffer, indentation)?;
writeln!(buffer, "{{")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "const payloadAlias = payload;")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "{{")?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "while (true) {{")?;
write_indentation(buffer, indentation + 3)?;
writeln!(buffer, "try {{")?;
write_deserialization_invocation(
buffer,
indentation + 4,
imports,
namespace,
&inner_type.variant,
false,
)?;
write_indentation(buffer, indentation + 4)?;
writeln!(buffer, "payloadAlias.push(payload);")?;
write_indentation(buffer, indentation + 3)?;
writeln!(buffer, "}} catch (e) {{")?;
write_indentation(buffer, indentation + 4)?;
writeln!(buffer, "if (e instanceof RangeError) {{")?;
write_indentation(buffer, indentation + 5)?;
writeln!(buffer, "break;")?;
write_indentation(buffer, indentation + 4)?;
writeln!(buffer, "}} else {{")?;
write_indentation(buffer, indentation + 5)?;
writeln!(buffer, "throw e;")?;
write_indentation(buffer, indentation + 4)?;
writeln!(buffer, "}}")?;
write_indentation(buffer, indentation + 3)?;
writeln!(buffer, "}}")?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "}}")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "}}")?;
write_indentation(buffer, indentation)?;
writeln!(buffer, "}}")
}
schema::TypeVariant::Unit => {
write_indentation(buffer, indentation)?;
writeln!(buffer, "let payload;")?;
write_indentation(buffer, indentation)?;
writeln!(buffer, "{{")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "let newPayload;")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "{{")?;
write_deserialization_invocation(
buffer,
indentation + 2,
imports,
namespace,
&schema::TypeVariant::U64,
is_field,
)?;
write_indentation(buffer, indentation + 2)?;
writeln!(
buffer,
"newPayload = Array(Number(payload)).fill(null) as null[];",
)?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "}}")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "payload = newPayload;")?;
write_indentation(buffer, indentation)?;
writeln!(buffer, "}}")
}
},
schema::TypeVariant::Bool => {
write_indentation(buffer, indentation)?;
writeln!(buffer, "let payload;")?;
write_indentation(buffer, indentation)?;
writeln!(buffer, "{{")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "let newPayload;")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "{{")?;
write_deserialization_invocation(
buffer,
indentation + 2,
imports,
namespace,
&schema::TypeVariant::U64,
is_field,
)?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "newPayload = payload !== 0n;")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "}}")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "payload = newPayload;")?;
write_indentation(buffer, indentation)?;
writeln!(buffer, "}}")
}
schema::TypeVariant::Bytes => {
write_indentation(buffer, indentation)?;
writeln!(buffer, "let payload = dataView.buffer.slice(")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "dataView.byteOffset + offset,")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "dataView.byteOffset + dataView.byteLength,")?;
write_indentation(buffer, indentation)?;
writeln!(buffer, ");")?;
write_indentation(buffer, indentation)?;
writeln!(buffer, "offset = dataView.byteLength;")
}
schema::TypeVariant::Custom(import, name) => {
write_indentation(buffer, indentation)?;
write!(buffer, "let payload = ")?;
write_custom_type(buffer, imports, namespace, import, name, None)?;
writeln!(buffer, ".deserializeUnsafe(dataView);")?;
write_indentation(buffer, indentation)?;
writeln!(buffer, "offset = dataView.byteLength;")
}
schema::TypeVariant::F64 => {
write_indentation(buffer, indentation)?;
if is_field {
writeln!(buffer, "let payload;")?;
write_indentation(buffer, indentation)?;
writeln!(buffer, "{{")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "switch (payloadSize) {{")?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "case 0:")?;
write_indentation(buffer, indentation + 3)?;
writeln!(buffer, "payload = 0;")?;
write_indentation(buffer, indentation + 3)?;
writeln!(buffer, "break;")?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "default:")?;
write_indentation(buffer, indentation + 3)?;
writeln!(buffer, "payload = dataView.getFloat64(offset, true);")?;
write_indentation(buffer, indentation + 3)?;
writeln!(buffer, "offset += 8;")?;
write_indentation(buffer, indentation + 3)?;
writeln!(buffer, "break;")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "}}")?;
write_indentation(buffer, indentation)?;
writeln!(buffer, "}}")
} else {
writeln!(buffer, "let payload = dataView.getFloat64(offset, true);")?;
write_indentation(buffer, indentation)?;
writeln!(buffer, "offset += 8;")
}
}
schema::TypeVariant::S64 => {
write_deserialization_invocation(
buffer,
indentation,
imports,
namespace,
&schema::TypeVariant::U64,
is_field,
)?;
write_indentation(buffer, indentation)?;
writeln!(buffer, "payload = zigzagDecode(payload);")
}
schema::TypeVariant::String => {
write_indentation(buffer, indentation)?;
writeln!(buffer, "let payload = textDecoder.decode(")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "new Uint8Array(")?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "dataView.buffer,")?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "dataView.byteOffset + offset,")?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "dataView.byteLength - offset,")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "),")?;
write_indentation(buffer, indentation)?;
writeln!(buffer, ");")?;
write_indentation(buffer, indentation)?;
writeln!(buffer, "offset = dataView.byteLength;")
}
schema::TypeVariant::U64 => {
write_indentation(buffer, indentation)?;
writeln!(buffer, "let payload;")?;
write_indentation(buffer, indentation)?;
if is_field {
writeln!(buffer, "{{")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "switch (payloadSize) {{")?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "case 0:")?;
write_indentation(buffer, indentation + 3)?;
writeln!(buffer, "payload = 0n;")?;
write_indentation(buffer, indentation + 3)?;
writeln!(buffer, "break;")?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "case 8:")?;
write_indentation(buffer, indentation + 3)?;
writeln!(buffer, "payload = dataView.getBigUint64(offset, true);")?;
write_indentation(buffer, indentation + 3)?;
writeln!(buffer, "offset += 8;")?;
write_indentation(buffer, indentation + 3)?;
writeln!(buffer, "break;")?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "default:")?;
write_indentation(buffer, indentation + 3)?;
writeln!(
buffer,
"[offset, payload] = deserializeVarint(dataView, offset);",
)?;
write_indentation(buffer, indentation + 3)?;
writeln!(buffer, "break;")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "}}")?;
write_indentation(buffer, indentation)?;
writeln!(buffer, "}}")
} else {
writeln!(
buffer,
"[offset, payload] = deserializeVarint(dataView, offset);",
)
}
}
schema::TypeVariant::Unit => {
write_indentation(buffer, indentation)?;
writeln!(buffer, "let payload = null;")
}
}
}
fn integer_encoded(r#type: &schema::Type) -> bool {
match &r#type.variant {
schema::TypeVariant::Bool | schema::TypeVariant::S64 | schema::TypeVariant::U64 => true,
schema::TypeVariant::Array(_)
| schema::TypeVariant::Bytes
| schema::TypeVariant::Custom(_, _)
| schema::TypeVariant::F64
| schema::TypeVariant::String
| schema::TypeVariant::Unit => false,
}
}
#[cfg(test)]
mod tests {
use {
crate::{generate_typescript::generate, schema_loader::load_schemas, validator::validate},
std::{fs::read_to_string, path::Path},
};
#[test]
fn generate_example() {
let schemas = load_schemas(Path::new("integration_tests/types/types.t")).unwrap();
validate(&schemas).unwrap();
assert_eq!(
generate("0.0.0", &schemas),
read_to_string("test_data/types.ts").unwrap(),
);
}
}