Skip to content

Commit

Permalink
feat(JavaScript): Implement Enum (#1321)
Browse files Browse the repository at this point in the history
Implement the Enum type which declare in SPEC.

Usage: 
```TypeScript
    enum Foo {
        f1 = 1,
        f2 = 2
    }
    const fury = new Fury({ refTracking: true });   
    const {serialize, deserialize} = fury.registerSerializer(Type.enum(Foo)) 
    const input = serialize(Foo.f1);
    const result = deserialize(
        input
    );
    expect(result).toEqual(Foo.f1)
```
  • Loading branch information
theweipeng authored Jan 8, 2024
1 parent bf092cf commit a07bf8c
Show file tree
Hide file tree
Showing 6 changed files with 205 additions and 2 deletions.
29 changes: 28 additions & 1 deletion javascript/packages/fury/lib/description.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ export interface ObjectTypeDescription extends TypeDescription {
}
}

export interface EnumTypeDescription extends TypeDescription {
options: {
inner: { [key: string]: any }
}
}

export interface ArrayTypeDescription extends TypeDescription {
options: {
inner: TypeDescription
Expand Down Expand Up @@ -92,6 +98,16 @@ type TupleProps<T> = T extends {
? { [K in keyof T2]: ToRecordType<T2[K]> }
: unknown;

type Value<T> = T extends { [s: string]: infer T2 } ? T2 : unknown;

type EnumProps<T> = T extends {
options: {
inner: infer T2
}
}
? Value<T2>
: unknown;

type SetProps<T> = T extends {
options: {
key: infer T2 extends TypeDescription
Expand Down Expand Up @@ -162,14 +178,25 @@ export type ToRecordType<T> = T extends {
type: InternalSerializerType.ANY
}
? any
: unknown;
: T extends {
type: InternalSerializerType.ENUM
}
? EnumProps<T> : unknown;

export const Type = {
any() {
return {
type: InternalSerializerType.ANY as const,
};
},
enum<T1 extends { [key: string]: any }>(t1: T1) {
return {
type: InternalSerializerType.ENUM as const,
options: {
inner: t1,
},
};
},
string() {
return {
type: InternalSerializerType.STRING as const,
Expand Down
88 changes: 88 additions & 0 deletions javascript/packages/fury/lib/gen/enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import { EnumTypeDescription, TypeDescription } from "../description";
import { CodecBuilder } from "./builder";
import { BaseSerializerGenerator } from "./serializer";
import { CodegenRegistry } from "./router";
import { InternalSerializerType, MaxUInt32 } from "../type";
import { Scope } from "./scope";

class EnumSerializerGenerator extends BaseSerializerGenerator {
description: EnumTypeDescription;

constructor(description: TypeDescription, builder: CodecBuilder, scope: Scope) {
super(description, builder, scope);
this.description = <EnumTypeDescription>description;
}

writeStmt(accessor: string): string {
if (Object.values(this.description.options.inner).length < 1) {
throw new Error("An enum must contain at least one field");
}
return `
${Object.values(this.description.options.inner).map((value, index) => {
if (typeof value !== "string" && typeof value !== "number") {
throw new Error("Enum value must be string or number");
}
if (typeof value === "number") {
if (value > MaxUInt32 || value < 0) {
throw new Error("Enum value must be a valid uint32");
}
}
const safeValue = typeof value === "string" ? `"${value}"` : value;
return ` if (${accessor} === ${safeValue}) {
${this.builder.writer.varUInt32(index)}
}`;
}).join(" else ")}
else {
throw new Error("Enum received an unexpected value: " + ${accessor});
}
`;
}

readStmt(accessor: (expr: string) => string): string {
const enumValue = this.scope.uniqueName("enum_v");
return `
const ${enumValue} = ${this.builder.reader.varUInt32()};
switch(${enumValue}) {
${Object.values(this.description.options.inner).map((value, index) => {
if (typeof value !== "string" && typeof value !== "number") {
throw new Error("Enum value must be string or number");
}
if (typeof value === "number") {
if (value > MaxUInt32 || value < 0) {
throw new Error("Enum value must be a valid uint32");
}
}
const safeValue = typeof value === "string" ? `"${value}"` : `${value}`;
return `
case ${index}:
${accessor(safeValue)}
break;
`;
}).join("\n")}
default:
throw new Error("Enum received an unexpected value: " + enumValue);
}
`;
}
}

CodegenRegistry.register(InternalSerializerType.ENUM, EnumSerializerGenerator);
1 change: 1 addition & 0 deletions javascript/packages/fury/lib/gen/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import "./set";
import "./any";
import "./tuple";
import "./typedArray";
import "./enum";

export const generate = (fury: Fury, description: TypeDescription) => {
const InnerGeneratorClass = CodegenRegistry.get(description.type);
Expand Down
5 changes: 5 additions & 0 deletions javascript/packages/fury/lib/meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,11 @@ export const getMeta = (description: TypeDescription, fury: Fury): Meta<any> =>
fixedSize: 11,
noneable: true,
};
case InternalSerializerType.ENUM:
return {
fixedSize: 7,
noneable: true,
};
default:
throw new Error(`Meta of ${description.type} not exists`);
}
Expand Down
4 changes: 3 additions & 1 deletion javascript/packages/fury/lib/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export enum InternalSerializerType {
BINARY = 14,
DATE = 16,
TIMESTAMP = 18,
ENUM = 19, // The cross-language enum has not yet been determined, this is not the final value, it will change later
FURY_TYPE_TAG = 256,
FURY_SET = 257,
FURY_PRIMITIVE_BOOL_ARRAY = 258,
Expand Down Expand Up @@ -86,7 +87,8 @@ export enum RefFlags {

export const MaxInt32 = 2147483647;
export const MinInt32 = -2147483648;

export const MaxUInt32 = 0xFFFFFFFF;
export const MinUInt32 = 0;
export const HalfMaxInt32 = MaxInt32 / 2;
export const HalfMinInt32 = MinInt32 / 2;

Expand Down
80 changes: 80 additions & 0 deletions javascript/test/enum.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import Fury, { TypeDescription, InternalSerializerType, Type } from '../packages/fury/index';
import {describe, expect, test} from '@jest/globals';

describe('enum', () => {
test('should javascript number enum work', () => {
const Foo = {
f1: 1,
f2: 2
}
const fury = new Fury({ refTracking: true });
const {serialize, deserialize} = fury.registerSerializer(Type.enum(Foo))
const input = serialize(Foo.f1);
const result = deserialize(
input
);
expect(result).toEqual(Foo.f1)
});

test('should javascript string enum work', () => {
const Foo = {
f1: "hello",
f2: "world"
}
const fury = new Fury({ refTracking: true });
fury.registerSerializer(Type.enum(Foo))
const input = fury.serialize(Foo.f1);
const result = fury.deserialize(
input
);
expect(result).toEqual(Foo.f1)
});
test('should typescript number enum work', () => {
enum Foo {
f1 = 1,
f2 = 2
}
const fury = new Fury({ refTracking: true });
const {serialize, deserialize} = fury.registerSerializer(Type.enum(Foo))
const input = serialize(Foo.f1);
const result = deserialize(
input
);
expect(result).toEqual(Foo.f1)
});

test('should typescript string enum work', () => {
enum Foo {
f1 = "hello",
f2 = "world"
}
const fury = new Fury({ refTracking: true });
fury.registerSerializer(Type.enum(Foo))
const input = fury.serialize(Foo.f1);
const result = fury.deserialize(
input
);
expect(result).toEqual(Foo.f1)
});
});


0 comments on commit a07bf8c

Please sign in to comment.