use crate::{identifier::Identifier, schema};
use std::{
collections::BTreeMap,
fmt::{self, Write},
path::PathBuf,
};
pub const COMMON_FILE_STEM: &str = "common";
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, 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};
struct DeclarationFunctionNames {
atlas: String,
size: String,
serialize_with_atlas_unsafe: String,
deserialize_unsafe: String,
serialize: String,
deserialize: String,
}
impl DeclarationFunctionNames {
fn new(identifier: &Identifier) -> Self {
Self {
atlas: helper_name(identifier, "atlas"),
size: helper_name(identifier, "size"),
serialize_with_atlas_unsafe: helper_name(identifier, "serialize_with_atlas_unsafe"),
deserialize_unsafe: helper_name(identifier, "deserialize_unsafe"),
serialize: helper_name(identifier, "serialize"),
deserialize: helper_name(identifier, "deserialize"),
}
}
}
pub fn generate(
typical_version: &str,
schemas: &BTreeMap<schema::Namespace, (schema::Schema, PathBuf, String)>,
) -> BTreeMap<PathBuf, String> {
let mut files = BTreeMap::new();
let mut common_buffer = String::new();
write_common_file(&mut common_buffer, typical_version).unwrap();
files.insert(
PathBuf::from(COMMON_FILE_STEM).with_extension("ts"),
common_buffer,
);
for (namespace, (schema, _, _)) in schemas {
let mut buffer = String::new();
write_schema_file(&mut buffer, typical_version, namespace, schema).unwrap();
let mut path = PathBuf::new();
for component in &namespace.components {
path.push(component.snake_case());
}
path.set_extension("ts");
files.insert(path, buffer);
}
files
}
fn write_generated_file_header<T: Write>(
buffer: &mut T,
typical_version: &str,
) -> Result<(), fmt::Error> {
writeln!(
buffer,
"\
// This file was automatically generated by Typical {typical_version}.
// Visit https://github.com/stepchowfun/typical for more information.
/* eslint-disable */",
)
}
#[allow(clippy::too_many_lines)]
fn write_common_file<T: Write>(buffer: &mut T, typical_version: &str) -> Result<(), fmt::Error> {
write_generated_file_header(buffer, typical_version)?;
writeln!(buffer)?;
writeln!(
buffer,
"\
export type Deserializable =
| ArrayBuffer
| DataView
| Int8Array
| Uint8Array
| Uint8ClampedArray
| Int16Array
| Uint16Array
| Int32Array
| Uint32Array
| Float32Array
| Float64Array
| BigInt64Array
| BigUint64Array;
export function unreachable(x: never): never {{
return x;
}}
export function dataViewFromDeserializable(bytes: Deserializable): DataView {{
if (bytes instanceof ArrayBuffer) {{
return new DataView(bytes);
}}
const buffer = bytes.buffer;
if (!(buffer instanceof ArrayBuffer)) {{
throw new Error('Expected ArrayBuffer or ArrayBuffer-backed view.');
}}
return new DataView(buffer, bytes.byteOffset, bytes.byteLength);
}}
export function zigzagEncode(value: bigint): bigint {{
const twice = value << 1n;
return value < 0n ? -1n - twice : twice;
}}
export function zigzagDecode(value: bigint): bigint {{
const half = (value + 1n) >> 1n;
return (value & 1n) === 0n ? half : -half;
}}
export 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;
}}
export 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 varintSizeFromFirstByte(firstByte: number): number {{
let trailingZeros = 0;
while (trailingZeros < 8 && (firstByte & 1) !== 1) {{
trailingZeros += 1;
firstByte >>= 1;
}}
return trailingZeros + 1;
}}
export 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,
];
}}
}}
export 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))
);
}}
}}
export 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));
}}
}}
export 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 dataView64 = new DataView(new ArrayBuffer(8));
export const missingFieldsErrorMessage = 'Struct missing one or more required field(s).';
export const textEncoder = new TextEncoder();
export const textDecoder = new TextDecoder('utf-8', {{ fatal: true, ignoreBOM: true }});",
)
}
fn write_schema_file<T: Write>(
buffer: &mut T,
typical_version: &str,
namespace: &schema::Namespace,
schema: &schema::Schema,
) -> Result<(), fmt::Error> {
write_generated_file_header(buffer, typical_version)?;
if schema.declarations.is_empty() {
return Ok(());
}
writeln!(buffer)?;
write_common_import(buffer, namespace)?;
if !schema.imports.is_empty() {
writeln!(buffer)?;
for (name, import) in &schema.imports {
let import_namespace = import.namespace.clone().unwrap();
let binding_name = import_binding_name(name);
let specifier = relative_module_specifier(
&namespace_parent_components(namespace),
&import_namespace
.components
.iter()
.map(Identifier::snake_case)
.collect::<Vec<_>>(),
);
writeln!(buffer, "import * as {binding_name} from '{specifier}';")?;
}
}
writeln!(buffer)?;
write_schema(buffer, 0, schema)?;
Ok(())
}
fn write_common_import<T: Write>(
buffer: &mut T,
namespace: &schema::Namespace,
) -> Result<(), fmt::Error> {
let specifier = relative_module_specifier(
&namespace_parent_components(namespace),
&[COMMON_FILE_STEM.to_owned()],
);
writeln!(
buffer,
"import {{
Deserializable,
dataViewFromDeserializable,
deserializeFieldHeader,
deserializeVarint,
fieldHeaderSize,
missingFieldsErrorMessage,
serializeFieldHeader,
serializeVarint,
textDecoder,
textEncoder,
unreachable,
varintSizeFromValue,
zigzagDecode,
zigzagEncode,
}} from '{specifier}';",
)
}
#[allow(clippy::too_many_lines)]
fn write_schema<T: Write>(
buffer: &mut T,
indentation: usize,
schema: &schema::Schema,
) -> Result<(), fmt::Error> {
let mut iter = schema.declarations.iter().peekable();
while let Some(declaration) = iter.next() {
let function_names = DeclarationFunctionNames::new(&declaration.name);
match &declaration.variant {
schema::DeclarationVariant::Struct => {
write_struct(
buffer,
indentation,
&declaration.name,
&declaration.fields,
Atlas,
)?;
writeln!(buffer)?;
write_struct(
buffer,
indentation,
&declaration.name,
&declaration.fields,
Out,
)?;
writeln!(buffer)?;
write_struct(
buffer,
indentation,
&declaration.name,
&declaration.fields,
In,
)?;
writeln!(buffer)?;
write_struct_atlas_function(
buffer,
indentation,
&declaration.name,
&declaration.fields,
&function_names.atlas,
)?;
writeln!(buffer)?;
write_size_function(
buffer,
indentation,
&declaration.name,
&declaration.fields,
&function_names.size,
&function_names.atlas,
)?;
writeln!(buffer)?;
write_struct_serialize_with_atlas_unsafe_function(
buffer,
indentation,
&declaration.name,
&declaration.fields,
&function_names.serialize_with_atlas_unsafe,
)?;
writeln!(buffer)?;
write_struct_deserialize_unsafe_function(
buffer,
indentation,
&declaration.name,
&declaration.fields,
&function_names.deserialize_unsafe,
)?;
writeln!(buffer)?;
write_serialize_function(
buffer,
indentation,
&declaration.variant,
&declaration.name,
&declaration.fields,
&function_names,
)?;
writeln!(buffer)?;
write_deserialize_function(
buffer,
indentation,
&declaration.name,
&function_names.deserialize,
&function_names.deserialize_unsafe,
)?;
}
schema::DeclarationVariant::Choice => {
write_choice(
buffer,
indentation,
&declaration.name,
&declaration.fields,
Atlas,
)?;
writeln!(buffer)?;
write_choice(
buffer,
indentation,
&declaration.name,
&declaration.fields,
Out,
)?;
writeln!(buffer)?;
write_choice(
buffer,
indentation,
&declaration.name,
&declaration.fields,
In,
)?;
writeln!(buffer)?;
write_choice_atlas_function(
buffer,
indentation,
&declaration.name,
&declaration.fields,
&function_names.atlas,
)?;
writeln!(buffer)?;
write_size_function(
buffer,
indentation,
&declaration.name,
&declaration.fields,
&function_names.size,
&function_names.atlas,
)?;
writeln!(buffer)?;
write_choice_serialize_with_atlas_unsafe_function(
buffer,
indentation,
&declaration.name,
&declaration.fields,
&function_names.serialize_with_atlas_unsafe,
)?;
writeln!(buffer)?;
write_choice_deserialize_unsafe_function(
buffer,
indentation,
&declaration.name,
&declaration.fields,
&function_names.deserialize_unsafe,
)?;
writeln!(buffer)?;
write_serialize_function(
buffer,
indentation,
&declaration.variant,
&declaration.name,
&declaration.fields,
&function_names,
)?;
writeln!(buffer)?;
write_deserialize_function(
buffer,
indentation,
&declaration.name,
&function_names.deserialize,
&function_names.deserialize_unsafe,
)?;
}
}
writeln!(buffer)?;
write_indentation(buffer, indentation)?;
write!(buffer, "export const ")?;
write_identifier(buffer, &declaration.name, Pascal, None)?;
writeln!(buffer, " = {{")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "atlas: {},", function_names.atlas)?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "size: {},", function_names.size)?;
write_indentation(buffer, indentation + 1)?;
writeln!(
buffer,
"serializeWithAtlasUnsafe: {},",
function_names.serialize_with_atlas_unsafe,
)?;
write_indentation(buffer, indentation + 1)?;
writeln!(
buffer,
"deserializeUnsafe: {},",
function_names.deserialize_unsafe,
)?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "serialize: {},", function_names.serialize)?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "deserialize: {},", function_names.deserialize)?;
write_indentation(buffer, indentation)?;
writeln!(buffer, "}};")?;
if iter.peek().is_some() {
writeln!(buffer)?;
}
}
Ok(())
}
#[allow(clippy::too_many_lines)]
fn write_struct_atlas_function<T: Write>(
buffer: &mut T,
indentation: usize,
name: &Identifier,
fields: &[schema::Field],
function_name: &str,
) -> Result<(), fmt::Error> {
write_indentation(buffer, indentation)?;
write!(buffer, "function {function_name}(message: ")?;
write_identifier(buffer, name, Pascal, Some(Out))?;
write!(buffer, "): ")?;
write_identifier(buffer, name, Pascal, Some(Atlas))?;
writeln!(buffer, " {{")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "let size = 0;")?;
if !fields.is_empty() {
writeln!(buffer)?;
for field in fields {
write_indentation(buffer, indentation + 1)?;
write!(buffer, "let $")?;
write_identifier(buffer, &field.name, Camel, None)?;
write!(buffer, ": ")?;
write_type(buffer, &field.r#type.variant, Atlas)?;
if matches!(field.rule, schema::Rule::Optional) {
write!(buffer, " | undefined")?;
}
writeln!(buffer, ";")?;
}
}
for field in fields {
writeln!(buffer)?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "{{")?;
write_indentation(buffer, indentation + 2)?;
write!(buffer, "let payloadAtlas: ")?;
write_type(buffer, &field.r#type.variant, Atlas)?;
writeln!(buffer, ";")?;
write_indentation(buffer, indentation + 2)?;
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 + 2,
schema::Rule::Optional => {
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "if (payload !== undefined) {{")?;
indentation + 3
}
};
write_atlas_calculation(buffer, conditional_indentation, &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),
)?;
if let schema::Rule::Optional = field.rule {
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "}}")?;
}
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "}}")?;
}
writeln!(buffer)?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "return {{")?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "$size: size,")?;
for field in fields {
write_indentation(buffer, indentation + 2)?;
write_identifier(buffer, &field.name, Camel, None)?;
write!(buffer, ": $")?;
write_identifier(buffer, &field.name, Camel, None)?;
writeln!(buffer, ",")?;
}
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "}};")?;
write_indentation(buffer, indentation)?;
writeln!(buffer, "}}")
}
#[allow(clippy::too_many_lines)]
fn write_struct_serialize_with_atlas_unsafe_function<T: Write>(
buffer: &mut T,
indentation: usize,
name: &Identifier,
fields: &[schema::Field],
function_name: &str,
) -> Result<(), fmt::Error> {
write_indentation(buffer, indentation)?;
writeln!(buffer, "function {function_name}(")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "dataView: DataView,")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "offset: number,")?;
write_indentation(buffer, indentation + 1)?;
write!(buffer, "message: ")?;
write_identifier(buffer, name, Pascal, Some(Out))?;
writeln!(buffer, ",")?;
write_indentation(buffer, indentation + 1)?;
write!(buffer, "atlas: ")?;
write_identifier(buffer, name, Pascal, Some(Atlas))?;
writeln!(buffer, ",")?;
write_indentation(buffer, indentation)?;
writeln!(buffer, "): number {{")?;
for field in fields {
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "{{")?;
write_indentation(buffer, indentation + 2)?;
write!(buffer, "const payload = message.")?;
write_identifier(buffer, &field.name, Camel, None)?;
writeln!(buffer, ";")?;
write_indentation(buffer, indentation + 2)?;
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 + 2,
schema::Rule::Optional => {
write_indentation(buffer, indentation + 2)?;
writeln!(
buffer,
"if (payload !== undefined && payloadAtlas !== undefined) {{",
)?;
indentation + 3
}
};
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,
&field.r#type.variant,
true,
)?;
if let schema::Rule::Optional = field.rule {
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "}}")?;
}
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "}}")?;
writeln!(buffer)?;
}
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "return offset;")?;
write_indentation(buffer, indentation)?;
writeln!(buffer, "}}")
}
#[allow(clippy::too_many_lines)]
fn write_struct_deserialize_unsafe_function<T: Write>(
buffer: &mut T,
indentation: usize,
name: &Identifier,
fields: &[schema::Field],
function_name: &str,
) -> Result<(), fmt::Error> {
write_indentation(buffer, indentation)?;
write!(buffer, "function {function_name}(dataView: DataView): ")?;
write_identifier(buffer, name, Pascal, Some(In))?;
writeln!(buffer, " {{")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "const dataViewAlias = dataView;")?;
writeln!(buffer)?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "let offset = 0;")?;
writeln!(buffer)?;
if !fields.is_empty() {
for field in fields {
write_indentation(buffer, indentation + 1)?;
write!(buffer, "let $")?;
write_identifier(buffer, &field.name, Camel, None)?;
write!(buffer, ": ")?;
write_type(buffer, &field.r#type.variant, In)?;
write!(buffer, " | undefined")?;
writeln!(buffer, ";")?;
}
writeln!(buffer)?;
}
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "while (true) {{")?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "let index: bigint;")?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "let payloadSize: number;")?;
writeln!(buffer)?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "try {{")?;
write_indentation(buffer, indentation + 3)?;
writeln!(
buffer,
"[offset, index, payloadSize] = deserializeFieldHeader(dataViewAlias, offset);",
)?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "}} catch (e) {{")?;
write_indentation(buffer, indentation + 3)?;
writeln!(buffer, "if (e instanceof RangeError) {{")?;
write_indentation(buffer, indentation + 4)?;
writeln!(buffer, "break;")?;
write_indentation(buffer, indentation + 3)?;
writeln!(buffer, "}} else {{")?;
write_indentation(buffer, indentation + 4)?;
writeln!(buffer, "throw e;")?;
write_indentation(buffer, indentation + 3)?;
writeln!(buffer, "}}")?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "}}")?;
writeln!(buffer)?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "switch (index) {{")?;
for field in fields {
write_indentation(buffer, indentation + 3)?;
writeln!(buffer, "case {}n: {{", field.index)?;
write_indentation(buffer, indentation + 4)?;
writeln!(buffer, "const dataView = new DataView(")?;
write_indentation(buffer, indentation + 5)?;
writeln!(buffer, "dataViewAlias.buffer as ArrayBuffer,")?;
write_indentation(buffer, indentation + 5)?;
writeln!(buffer, "dataViewAlias.byteOffset + offset,")?;
write_indentation(buffer, indentation + 5)?;
writeln!(buffer, "payloadSize,")?;
write_indentation(buffer, indentation + 4)?;
writeln!(buffer, ");")?;
write_indentation(buffer, indentation + 4)?;
writeln!(buffer, "const oldOffset = offset;")?;
write_indentation(buffer, indentation + 4)?;
writeln!(buffer, "offset = 0;")?;
write_deserialization_invocation(buffer, indentation + 4, &field.r#type.variant, true)?;
write_indentation(buffer, indentation + 4)?;
writeln!(buffer, "offset += oldOffset;")?;
write_indentation(buffer, indentation + 4)?;
write!(buffer, "$")?;
write_identifier(buffer, &field.name, Camel, None)?;
writeln!(buffer, " = payload;")?;
write_indentation(buffer, indentation + 4)?;
writeln!(buffer, "break;")?;
write_indentation(buffer, indentation + 3)?;
writeln!(buffer, "}}")?;
}
write_indentation(buffer, indentation + 3)?;
writeln!(buffer, "default:")?;
write_indentation(buffer, indentation + 4)?;
writeln!(buffer, "offset += payloadSize;")?;
write_indentation(buffer, indentation + 4)?;
writeln!(buffer, "break;")?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "}}")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "}}")?;
writeln!(buffer)?;
if fields.iter().any(|field| is_required(&field.rule)) {
write_indentation(buffer, indentation + 1)?;
write!(buffer, "if (")?;
let mut first = true;
for field in fields {
if is_required(&field.rule) {
if first {
first = false;
} else {
writeln!(buffer)?;
write_indentation(buffer, indentation + 2)?;
write!(buffer, "|| ")?;
}
write!(buffer, "$")?;
write_identifier(buffer, &field.name, Camel, None)?;
write!(buffer, " === undefined")?;
}
}
writeln!(buffer, ") {{")?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "throw new Error(missingFieldsErrorMessage);")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "}}")?;
writeln!(buffer)?;
}
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "return {{")?;
for field in fields {
write_indentation(buffer, indentation + 2)?;
write_identifier(buffer, &field.name, Camel, None)?;
write!(buffer, ": $")?;
write_identifier(buffer, &field.name, Camel, None)?;
writeln!(buffer, ",")?;
}
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "}};")?;
write_indentation(buffer, indentation)?;
writeln!(buffer, "}}")
}
#[allow(clippy::too_many_lines)]
fn write_choice_atlas_function<T: Write>(
buffer: &mut T,
indentation: usize,
name: &Identifier,
fields: &[schema::Field],
function_name: &str,
) -> Result<(), fmt::Error> {
write_indentation(buffer, indentation)?;
write!(buffer, "function {function_name}(message: ")?;
write_identifier(buffer, name, Pascal, Some(Out))?;
write!(buffer, "): ")?;
write_identifier(buffer, name, Pascal, Some(Atlas))?;
writeln!(buffer, " {{")?;
if fields.is_empty() {
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "return unreachable(message);")?;
} else {
for field in fields {
write_indentation(buffer, indentation + 1)?;
write!(buffer, "if ('")?;
write_identifier(buffer, &field.name, Camel, None)?;
writeln!(buffer, "' in message) {{")?;
write_indentation(buffer, indentation + 2)?;
write!(buffer, "let payloadAtlas: ")?;
write_type(buffer, &field.r#type.variant, Atlas)?;
writeln!(buffer, ";")?;
write_indentation(buffer, indentation + 2)?;
write!(buffer, "const payload = message.")?;
write_identifier(buffer, &field.name, Camel, None)?;
writeln!(buffer, ";")?;
write_atlas_calculation(buffer, indentation + 2, &field.r#type.variant, true)?;
write_indentation(buffer, indentation + 2)?;
write!(buffer, "const payloadSize = ")?;
write_atlas_lookup(buffer, &field.r#type.variant)?;
writeln!(buffer, ";")?;
write_indentation(buffer, indentation + 2)?;
match field.rule {
schema::Rule::Asymmetric | schema::Rule::Optional => {
writeln!(
buffer,
"const fallbackAtlas = {function_name}(message.$fallback);",
)?;
write_indentation(buffer, indentation + 2)?;
write!(
buffer,
"return {{ $size: fieldHeaderSize({}n, payloadSize, {}) + ",
field.index,
integer_encoded(&field.r#type),
)?;
write!(buffer, "payloadSize + fallbackAtlas.$size, ")?;
}
schema::Rule::Required => {
write!(
buffer,
"return {{ $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 + 1)?;
writeln!(buffer, "}}")?;
writeln!(buffer)?;
}
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "return unreachable(message);")?;
}
write_indentation(buffer, indentation)?;
writeln!(buffer, "}}")
}
#[allow(clippy::too_many_lines)]
fn write_choice_serialize_with_atlas_unsafe_function<T: Write>(
buffer: &mut T,
indentation: usize,
name: &Identifier,
fields: &[schema::Field],
function_name: &str,
) -> Result<(), fmt::Error> {
write_indentation(buffer, indentation)?;
writeln!(buffer, "function {function_name}(")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "dataView: DataView,")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "offset: number,")?;
write_indentation(buffer, indentation + 1)?;
write!(buffer, "message: ")?;
write_identifier(buffer, name, Pascal, Some(Out))?;
writeln!(buffer, ",")?;
write_indentation(buffer, indentation + 1)?;
write!(buffer, "atlas: ")?;
write_identifier(buffer, name, Pascal, Some(Atlas))?;
writeln!(buffer, ",")?;
write_indentation(buffer, indentation)?;
writeln!(buffer, "): number {{")?;
if fields.is_empty() {
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "return unreachable(message);")?;
} else {
for field in fields {
write_indentation(buffer, indentation + 1)?;
write!(buffer, "if ('")?;
write_identifier(buffer, &field.name, Camel, None)?;
writeln!(buffer, "' in message) {{")?;
write_indentation(buffer, indentation + 2)?;
write!(buffer, "const payload = message.")?;
write_identifier(buffer, &field.name, Camel, None)?;
writeln!(buffer, ";")?;
write_indentation(buffer, indentation + 2)?;
write!(buffer, "const payloadAtlas = (atlas as any).")?;
write_identifier(buffer, &field.name, Camel, None)?;
write!(buffer, " as ")?;
write_type(buffer, &field.r#type.variant, Direction::Atlas)?;
writeln!(buffer, ";")?;
write_indentation(buffer, indentation + 2)?;
write!(buffer, "const payloadSize = ")?;
write_atlas_lookup(buffer, &field.r#type.variant)?;
writeln!(buffer, ";")?;
write_indentation(buffer, indentation + 2)?;
writeln!(
buffer,
"offset = serializeFieldHeader(dataView, offset, {}n, payloadSize, {});",
field.index,
integer_encoded(&field.r#type),
)?;
write_serialization_invocation(buffer, indentation + 2, &field.r#type.variant, true)?;
if matches!(
field.rule,
schema::Rule::Asymmetric | schema::Rule::Optional,
) {
write_indentation(buffer, indentation + 2)?;
write!(buffer, "offset = {function_name}(dataView, offset, ")?;
write!(buffer, "message.$fallback, (atlas as any).$fallback as ")?;
write_identifier(buffer, name, Pascal, Some(Atlas))?;
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)?;
writeln!(buffer, "return unreachable(message);")?;
}
write_indentation(buffer, indentation)?;
writeln!(buffer, "}}")
}
#[allow(clippy::too_many_lines)]
fn write_choice_deserialize_unsafe_function<T: Write>(
buffer: &mut T,
indentation: usize,
name: &Identifier,
fields: &[schema::Field],
function_name: &str,
) -> Result<(), fmt::Error> {
write_indentation(buffer, indentation)?;
write!(buffer, "function {function_name}(dataView: DataView): ")?;
write_identifier(buffer, name, Pascal, Some(In))?;
writeln!(buffer, " {{")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "const dataViewAlias = dataView;")?;
writeln!(buffer)?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "let offset = 0;")?;
writeln!(buffer)?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "while (true) {{")?;
write_indentation(buffer, indentation + 2)?;
writeln!(
buffer,
"const [newOffset, index, payloadSize] = deserializeFieldHeader(dataViewAlias, offset);",
)?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "offset = newOffset;")?;
writeln!(buffer)?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "switch (index) {{")?;
for field in fields {
write_indentation(buffer, indentation + 3)?;
writeln!(buffer, "case {}n: {{", field.index)?;
write_indentation(buffer, indentation + 4)?;
writeln!(buffer, "const dataView = new DataView(")?;
write_indentation(buffer, indentation + 5)?;
writeln!(buffer, "dataViewAlias.buffer as ArrayBuffer,")?;
write_indentation(buffer, indentation + 5)?;
writeln!(buffer, "dataViewAlias.byteOffset + offset,")?;
write_indentation(buffer, indentation + 5)?;
writeln!(buffer, "payloadSize,")?;
write_indentation(buffer, indentation + 4)?;
writeln!(buffer, ");")?;
write_indentation(buffer, indentation + 4)?;
writeln!(buffer, "const oldOffset = offset;")?;
write_indentation(buffer, indentation + 4)?;
writeln!(buffer, "offset = 0;")?;
write_deserialization_invocation(buffer, indentation + 4, &field.r#type.variant, true)?;
if let schema::Rule::Optional = field.rule {
write_indentation(buffer, indentation + 4)?;
writeln!(buffer, "offset += oldOffset;")?;
write_indentation(buffer, indentation + 4)?;
writeln!(buffer, "const $fallback = {function_name}(")?;
write_indentation(buffer, indentation + 5)?;
writeln!(buffer, "new DataView(")?;
write_indentation(buffer, indentation + 6)?;
writeln!(buffer, "dataViewAlias.buffer as ArrayBuffer,")?;
write_indentation(buffer, indentation + 6)?;
writeln!(buffer, "dataViewAlias.byteOffset + offset,")?;
write_indentation(buffer, indentation + 6)?;
writeln!(buffer, "dataViewAlias.byteLength - offset,")?;
write_indentation(buffer, indentation + 5)?;
writeln!(buffer, "),")?;
write_indentation(buffer, indentation + 4)?;
writeln!(buffer, ");")?;
}
write_indentation(buffer, indentation + 4)?;
writeln!(buffer, "return {{")?;
write_indentation(buffer, indentation + 5)?;
write!(buffer, "$field: '")?;
write_identifier(buffer, &field.name, Camel, None)?;
writeln!(buffer, "',")?;
write_indentation(buffer, indentation + 5)?;
write_identifier(buffer, &field.name, Camel, None)?;
writeln!(buffer, ": payload,")?;
if let schema::Rule::Optional = field.rule {
write_indentation(buffer, indentation + 5)?;
writeln!(buffer, "$fallback,")?;
}
write_indentation(buffer, indentation + 4)?;
writeln!(buffer, "}};")?;
write_indentation(buffer, indentation + 3)?;
writeln!(buffer, "}}")?;
}
write_indentation(buffer, indentation + 3)?;
writeln!(buffer, "default:")?;
write_indentation(buffer, indentation + 4)?;
writeln!(buffer, "offset += payloadSize;")?;
write_indentation(buffer, indentation + 4)?;
writeln!(buffer, "break;")?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "}}")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "}}")?;
write_indentation(buffer, indentation)?;
writeln!(buffer, "}}")
}
fn write_struct<T: Write>(
buffer: &mut T,
indentation: usize,
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)?;
write!(buffer, ": ")?;
write_type(buffer, &field.r#type.variant, direction)?;
match field.rule {
schema::Rule::Asymmetric => match direction {
Direction::Atlas | Direction::Out => {}
Direction::In => {
write!(buffer, " | undefined")?;
}
},
schema::Rule::Optional => {
write!(buffer, " | undefined")?;
}
schema::Rule::Required => {}
}
writeln!(buffer, ";")?;
}
write_indentation(buffer, indentation)?;
writeln!(buffer, "}};")?;
Ok(())
}
fn write_choice<T: Write>(
buffer: &mut T,
indentation: usize,
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, "| {{ ")?;
match direction {
Direction::Atlas => {
write!(buffer, "$size: number; ")?;
write_identifier(buffer, &field.name, Camel, None)?;
write!(buffer, ": ")?;
write_type(buffer, &field.r#type.variant, direction)?;
}
Direction::In => {
write!(buffer, "$field: '")?;
write_identifier(buffer, &field.name, Camel, None)?;
write!(buffer, "'; ")?;
write_identifier(buffer, &field.name, Camel, None)?;
write!(buffer, ": ")?;
write_type(buffer, &field.r#type.variant, direction)?;
}
Direction::Out => {
write_identifier(buffer, &field.name, Camel, None)?;
write!(buffer, ": ")?;
write_type(buffer, &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::Out => {}
Direction::In => {
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],
function_name: &str,
atlas_function_name: &str,
) -> Result<(), fmt::Error> {
write_indentation(buffer, indentation)?;
write!(buffer, "function {function_name}(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_function_name}(message).$size;")?;
}
write_indentation(buffer, indentation)?;
writeln!(buffer, "}}")
}
fn write_serialize_function<T: Write>(
buffer: &mut T,
indentation: usize,
declaration_variant: &schema::DeclarationVariant,
name: &Identifier,
fields: &[schema::Field],
function_names: &DeclarationFunctionNames,
) -> Result<(), fmt::Error> {
write_indentation(buffer, indentation)?;
write!(buffer, "function {}(message: ", function_names.serialize)?;
write_identifier(buffer, name, Pascal, Some(Out))?;
writeln!(buffer, "): ArrayBuffer {{")?;
if let (schema::DeclarationVariant::Choice, true) = (declaration_variant, fields.is_empty()) {
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "return unreachable(message);")?;
} else {
write_indentation(buffer, indentation + 1)?;
writeln!(
buffer,
"const atlasValue = {}(message);",
function_names.atlas,
)?;
write_indentation(buffer, indentation + 1)?;
writeln!(
buffer,
"const arrayBuffer = new ArrayBuffer(atlasValue.$size);",
)?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "const dataView = new DataView(arrayBuffer);")?;
write_indentation(buffer, indentation + 1)?;
writeln!(
buffer,
"{}(dataView, 0, message, atlasValue);",
function_names.serialize_with_atlas_unsafe,
)?;
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,
function_name: &str,
deserialize_unsafe_function_name: &str,
) -> Result<(), fmt::Error> {
write_indentation(buffer, indentation)?;
write!(buffer, "function {function_name}(bytes: Deserializable): ")?;
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 {deserialize_unsafe_function_name}(dataViewFromDeserializable(bytes));",
)?;
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,
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, &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, &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, import.as_ref(), 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,
import: Option<&Identifier>,
name: &Identifier,
direction: Option<Direction>,
) -> Result<(), fmt::Error> {
if let Some(import) = import {
write!(buffer, "{}.", import_binding_name(import))?;
}
write_identifier(buffer, name, Pascal, direction)
}
fn write_identifier<T: Write>(
buffer: &mut T,
identifier: &Identifier,
case: CaseConvention,
suffix: Option<Direction>,
) -> Result<(), fmt::Error> {
write!(buffer, "{}", format_identifier(identifier, case, suffix))
}
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,
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)?;
write!(buffer, "let elements: ")?;
write_type(buffer, &inner_type.variant, Atlas)?;
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_indentation(buffer, indentation + 2)?;
write!(buffer, "let payloadAtlas: ")?;
write_type(buffer, &inner_type.variant, Atlas)?;
writeln!(buffer, ";")?;
write_atlas_calculation(buffer, indentation + 2, &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, &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,
&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, import.as_ref(), 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,
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,
&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,
&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 as ArrayBuffer,")?;
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, import.as_ref(), 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 as ArrayBuffer,")?;
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,
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, &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: bigint;")?;
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 as ArrayBuffer,")?;
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,
&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, &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,
&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: null[];")?;
write_indentation(buffer, indentation)?;
writeln!(buffer, "{{")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "let newPayload: null[];")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "{{")?;
write_deserialization_invocation(
buffer,
indentation + 2,
&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: boolean;")?;
write_indentation(buffer, indentation)?;
writeln!(buffer, "{{")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "let newPayload: boolean;")?;
write_indentation(buffer, indentation + 1)?;
writeln!(buffer, "{{")?;
write_deserialization_invocation(
buffer,
indentation + 2,
&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 as ArrayBuffer).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, import.as_ref(), 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: number;")?;
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,
&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 DataView(")?;
write_indentation(buffer, indentation + 2)?;
writeln!(buffer, "dataView.buffer as ArrayBuffer,")?;
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: bigint;")?;
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,
}
}
fn format_identifier(
identifier: &Identifier,
case: CaseConvention,
suffix: Option<Direction>,
) -> String {
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)
{
format!("_{converted_identifier}")
} else {
converted_identifier
}
}
fn is_required(rule: &schema::Rule) -> bool {
match rule {
schema::Rule::Required => true,
schema::Rule::Asymmetric | schema::Rule::Optional => false,
}
}
fn helper_name(identifier: &Identifier, suffix: &str) -> String {
format_identifier(&identifier.join(&suffix.into()), Camel, None)
}
fn namespace_parent_components(namespace: &schema::Namespace) -> Vec<String> {
namespace
.components
.iter()
.take(namespace.components.len().saturating_sub(1))
.map(Identifier::snake_case)
.collect()
}
fn relative_module_specifier(from_directory: &[String], to: &[String]) -> String {
let common_components = from_directory
.iter()
.zip(to.iter())
.take_while(|(left, right)| left == right)
.count();
let mut components = vec![];
if common_components == from_directory.len() {
components.push(String::from("."));
}
for _ in common_components..from_directory.len() {
components.push(String::from(".."));
}
for component in &to[common_components..] {
components.push(component.clone());
}
components.join("/")
}
fn import_binding_name(import: &Identifier) -> String {
format!("_{}", format_identifier(import, Pascal, None))
}
#[cfg(test)]
mod tests {
use crate::{
error::SourceRange,
generate_typescript::{COMMON_FILE_STEM, generate},
schema::{self, Namespace},
schema_loader::load_schemas,
validator::validate,
};
use std::{
collections::{BTreeMap, BTreeSet},
fs::read_to_string,
path::{Path, PathBuf},
};
#[test]
fn generate_example() {
let schemas = load_schemas(Path::new("integration_tests/types/types.t")).unwrap();
validate(&schemas).unwrap();
let fixture_root = Path::new("test_data/typescript");
let mut expected = BTreeMap::new();
let common_path = PathBuf::from(COMMON_FILE_STEM).with_extension("ts");
let common_contents = read_to_string(fixture_root.join(&common_path)).unwrap();
expected.insert(common_path, common_contents);
for namespace in schemas.keys() {
let mut path = PathBuf::new();
for component in &namespace.components {
path.push(component.snake_case());
}
path.set_extension("ts");
let contents = read_to_string(fixture_root.join(&path)).unwrap();
expected.insert(path, contents);
}
assert_eq!(generate("0.0.0", &schemas), expected);
}
#[test]
fn generate_snake_case_paths() {
let dependency_namespace = Namespace {
components: vec!["Foo".into(), "FirstSchema".into()],
};
let types_namespace = Namespace {
components: vec!["Bar".into(), "SecondSchema".into()],
};
let source_range = SourceRange { start: 0, end: 0 };
let mut imports = BTreeMap::new();
imports.insert(
"first_schema".into(),
schema::Import {
source_range,
path: PathBuf::from("../Foo/FirstSchema.t"),
namespace: Some(dependency_namespace.clone()),
},
);
let mut schemas = BTreeMap::new();
schemas.insert(
dependency_namespace,
(
schema::Schema {
comment: vec![],
imports: BTreeMap::new(),
declarations: vec![],
},
PathBuf::from("Foo/FirstSchema.t"),
String::new(),
),
);
schemas.insert(
types_namespace,
(
schema::Schema {
comment: vec![],
imports,
declarations: vec![schema::Declaration {
source_range,
comment: vec![],
variant: schema::DeclarationVariant::Struct,
name: "SomeStruct".into(),
fields: vec![],
deleted: BTreeSet::new(),
}],
},
PathBuf::from("Bar/SecondSchema.t"),
String::new(),
),
);
let generated = generate("0.0.0", &schemas);
assert!(generated.contains_key(Path::new("foo/first_schema.ts")));
assert!(generated.contains_key(Path::new("bar/second_schema.ts")));
assert!(
generated
.get(Path::new("bar/second_schema.ts"))
.unwrap()
.contains("import * as _FirstSchema from '../foo/first_schema';"),
);
}
}