Skip to content

Commit

Permalink
feat: cli option to not encode/decode default values
Browse files Browse the repository at this point in the history
Resolves #43
  • Loading branch information
D4nte committed Jul 29, 2022
1 parent b6a9c18 commit c9ffd99
Show file tree
Hide file tree
Showing 30 changed files with 336 additions and 82 deletions.
10 changes: 5 additions & 5 deletions packages/protons-benchmark/src/protons/bench.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export namespace Foo {
export const codec = (): Codec<Foo> => {
return message<Foo>({
1: { name: 'baz', codec: uint32 }
})
}, false)
}

export const encode = (obj: Foo): Uint8ArrayList => {
Expand All @@ -33,7 +33,7 @@ export namespace Bar {
export const codec = (): Codec<Bar> => {
return message<Bar>({
1: { name: 'tmp', codec: Foo.codec() }
})
}, false)
}

export const encode = (obj: Bar): Uint8ArrayList => {
Expand Down Expand Up @@ -68,7 +68,7 @@ export namespace Yo {
export const codec = (): Codec<Yo> => {
return message<Yo>({
1: { name: 'lol', codec: FOO.codec(), repeats: true }
})
}, false)
}

export const encode = (obj: Yo): Uint8ArrayList => {
Expand All @@ -90,7 +90,7 @@ export namespace Lol {
return message<Lol>({
1: { name: 'lol', codec: string },
2: { name: 'b', codec: Bar.codec() }
})
}, false)
}

export const encode = (obj: Lol): Uint8ArrayList => {
Expand All @@ -116,7 +116,7 @@ export namespace Test {
3: { name: 'hello', codec: uint32 },
1: { name: 'foo', codec: string },
7: { name: 'payload', codec: bytes }
})
}, false)
}

export const encode = (obj: Test): Uint8ArrayList => {
Expand Down
16 changes: 14 additions & 2 deletions packages/protons-runtime/src/codec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,32 @@ export interface EncodingLengthFunction<T> {
(value: T): number
}

export interface DefaultValueFunction<T> {
(): T
}

export interface IsDefaultValueFunction<T> {
(value: T): boolean
}

export interface Codec<T> {
name: string
type: CODEC_TYPES
encode: EncodeFunction<T>
decode: DecodeFunction<T>
encodingLength: EncodingLengthFunction<T>
defaultValue: DefaultValueFunction<T>
isDefaultValue: IsDefaultValueFunction<T>
}

export function createCodec <T> (name: string, type: CODEC_TYPES, encode: EncodeFunction<T>, decode: DecodeFunction<T>, encodingLength: EncodingLengthFunction<T>): Codec<T> {
export function createCodec<T> (name: string, type: CODEC_TYPES, encode: EncodeFunction<T>, decode: DecodeFunction<T>, encodingLength: EncodingLengthFunction<T>, defaultValue: DefaultValueFunction<T>, isDefaultValue: IsDefaultValueFunction<T>): Codec<T> {
return {
name,
type,
encode,
decode,
encodingLength
encodingLength,
defaultValue,
isDefaultValue
}
}
8 changes: 6 additions & 2 deletions packages/protons-runtime/src/codecs/bool.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createCodec, CODEC_TYPES } from '../codec.js'
import { createCodec, CODEC_TYPES, DefaultValueFunction, IsDefaultValueFunction } from '../codec.js'
import type { DecodeFunction, EncodeFunction, EncodingLengthFunction } from '../codec.js'

const encodingLength: EncodingLengthFunction<boolean> = function boolEncodingLength () {
Expand All @@ -13,4 +13,8 @@ const decode: DecodeFunction<boolean> = function boolDecode (buffer, offset) {
return buffer.get(offset) > 0
}

export const bool = createCodec('bool', CODEC_TYPES.VARINT, encode, decode, encodingLength)
const defaultValue: DefaultValueFunction<boolean> = () => false

const isDefaultValue: IsDefaultValueFunction<boolean> = (value: boolean) => !value

export const bool = createCodec('bool', CODEC_TYPES.VARINT, encode, decode, encodingLength, defaultValue, isDefaultValue)
9 changes: 7 additions & 2 deletions packages/protons-runtime/src/codecs/bytes.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@

import { Uint8ArrayList } from 'uint8arraylist'
import { unsigned } from '../utils/varint.js'
import { createCodec, CODEC_TYPES } from '../codec.js'
import { createCodec, CODEC_TYPES, DefaultValueFunction, IsDefaultValueFunction } from '../codec.js'
import type { DecodeFunction, EncodeFunction, EncodingLengthFunction } from '../codec.js'
import { equals } from 'uint8arrays/equals'

const encodingLength: EncodingLengthFunction<Uint8Array> = function bytesEncodingLength (val) {
const len = val.byteLength
Expand All @@ -24,4 +25,8 @@ const decode: DecodeFunction<Uint8Array> = function bytesDecode (buf, offset) {
return buf.subarray(offset, offset + byteLength)
}

export const bytes = createCodec('bytes', CODEC_TYPES.LENGTH_DELIMITED, encode, decode, encodingLength)
const defaultValue: DefaultValueFunction<Uint8Array> = () => new Uint8Array()

const isDefaultValue: IsDefaultValueFunction<Uint8Array> = (value: Uint8Array) => equals(value, defaultValue())

export const bytes = createCodec('bytes', CODEC_TYPES.LENGTH_DELIMITED, encode, decode, encodingLength, defaultValue, isDefaultValue)
8 changes: 6 additions & 2 deletions packages/protons-runtime/src/codecs/double.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Uint8ArrayList } from 'uint8arraylist'
import { createCodec, CODEC_TYPES } from '../codec.js'
import { createCodec, CODEC_TYPES, DefaultValueFunction, IsDefaultValueFunction } from '../codec.js'
import type { DecodeFunction, EncodeFunction, EncodingLengthFunction } from '../codec.js'

const encodingLength: EncodingLengthFunction<number> = function doubleEncodingLength () {
Expand All @@ -17,4 +17,8 @@ const decode: DecodeFunction<number> = function doubleDecode (buf, offset) {
return buf.getFloat64(offset, true)
}

export const double = createCodec('double', CODEC_TYPES.BIT64, encode, decode, encodingLength)
const defaultValue: DefaultValueFunction<number> = () => 0

const isDefaultValue: IsDefaultValueFunction<number> = (value) => value === defaultValue()

export const double = createCodec('double', CODEC_TYPES.BIT64, encode, decode, encodingLength, defaultValue, isDefaultValue)
12 changes: 10 additions & 2 deletions packages/protons-runtime/src/codecs/enum.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

import { unsigned } from '../utils/varint.js'
import { createCodec, CODEC_TYPES } from '../codec.js'
import { createCodec, CODEC_TYPES, DefaultValueFunction, IsDefaultValueFunction } from '../codec.js'
import type { DecodeFunction, EncodeFunction, EncodingLengthFunction, Codec } from '../codec.js'

export function enumeration <T> (v: any): Codec<T> {
Expand Down Expand Up @@ -42,6 +42,14 @@ export function enumeration <T> (v: any): Codec<T> {
return v[strValue]
}

const defaultValue: DefaultValueFunction<number | string> = function defaultValue () {
return Object.values(v)[0] as (string | number)
}

const isDefaultValue: IsDefaultValueFunction<number| string> = function isDefaultValue (val) {
return val === defaultValue()
}

// @ts-expect-error yeah yeah
return createCodec('enum', CODEC_TYPES.VARINT, encode, decode, encodingLength)
return createCodec('enum', CODEC_TYPES.VARINT, encode, decode, encodingLength, defaultValue, isDefaultValue)
}
8 changes: 6 additions & 2 deletions packages/protons-runtime/src/codecs/fixed32.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Uint8ArrayList } from 'uint8arraylist'
import { createCodec, CODEC_TYPES } from '../codec.js'
import { createCodec, CODEC_TYPES, DefaultValueFunction, IsDefaultValueFunction } from '../codec.js'
import type { DecodeFunction, EncodeFunction, EncodingLengthFunction } from '../codec.js'

const encodingLength: EncodingLengthFunction<number> = function fixed32EncodingLength () {
Expand All @@ -17,4 +17,8 @@ const decode: DecodeFunction<number> = function fixed32Decode (buf, offset) {
return buf.getInt32(offset, true)
}

export const fixed32 = createCodec('fixed32', CODEC_TYPES.BIT32, encode, decode, encodingLength)
const defaultValue: DefaultValueFunction<number> = () => 0

const isDefaultValue: IsDefaultValueFunction<number> = (val) => val === defaultValue()

export const fixed32 = createCodec('fixed32', CODEC_TYPES.BIT32, encode, decode, encodingLength, defaultValue, isDefaultValue)
8 changes: 6 additions & 2 deletions packages/protons-runtime/src/codecs/fixed64.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Uint8ArrayList } from 'uint8arraylist'
import { createCodec, CODEC_TYPES } from '../codec.js'
import { createCodec, CODEC_TYPES, DefaultValueFunction, IsDefaultValueFunction } from '../codec.js'
import type { DecodeFunction, EncodeFunction, EncodingLengthFunction } from '../codec.js'

const encodingLength: EncodingLengthFunction<bigint> = function int64EncodingLength (val) {
Expand All @@ -17,4 +17,8 @@ const decode: DecodeFunction<bigint> = function int64Decode (buf, offset) {
return buf.getBigInt64(offset, true)
}

export const fixed64 = createCodec('fixed64', CODEC_TYPES.BIT64, encode, decode, encodingLength)
const defaultValue: DefaultValueFunction<bigint> = () => 0n

const isDefaultValue: IsDefaultValueFunction<bigint> = (val) => val === defaultValue()

export const fixed64 = createCodec('fixed64', CODEC_TYPES.BIT64, encode, decode, encodingLength, defaultValue, isDefaultValue)
8 changes: 6 additions & 2 deletions packages/protons-runtime/src/codecs/float.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Uint8ArrayList } from 'uint8arraylist'
import { createCodec, CODEC_TYPES } from '../codec.js'
import { createCodec, CODEC_TYPES, DefaultValueFunction, IsDefaultValueFunction } from '../codec.js'
import type { DecodeFunction, EncodeFunction, EncodingLengthFunction } from '../codec.js'

const encodingLength: EncodingLengthFunction<number> = function floatEncodingLength () {
Expand All @@ -17,4 +17,8 @@ const decode: DecodeFunction<number> = function floatDecode (buf, offset) {
return buf.getFloat32(offset, true)
}

export const float = createCodec('float', CODEC_TYPES.BIT32, encode, decode, encodingLength)
const defaultValue: DefaultValueFunction<number> = () => 0

const isDefaultValue: IsDefaultValueFunction<number> = (val) => val === defaultValue()

export const float = createCodec('float', CODEC_TYPES.BIT32, encode, decode, encodingLength, defaultValue, isDefaultValue)
8 changes: 6 additions & 2 deletions packages/protons-runtime/src/codecs/int32.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { signed } from '../utils/varint.js'
import { createCodec, CODEC_TYPES } from '../codec.js'
import { createCodec, CODEC_TYPES, DefaultValueFunction, IsDefaultValueFunction } from '../codec.js'
import type { DecodeFunction, EncodeFunction, EncodingLengthFunction } from '../codec.js'

const encodingLength: EncodingLengthFunction<number> = function int32EncodingLength (val) {
Expand All @@ -17,4 +17,8 @@ const decode: DecodeFunction<number> = function int32Decode (buf, offset) {
return signed.decode(buf, offset)
}

export const int32 = createCodec('int32', CODEC_TYPES.VARINT, encode, decode, encodingLength)
const defaultValue: DefaultValueFunction<number> = () => 0

const isDefaultValue: IsDefaultValueFunction<number> = (val) => val === defaultValue()

export const int32 = createCodec('int32', CODEC_TYPES.VARINT, encode, decode, encodingLength, defaultValue, isDefaultValue)
8 changes: 6 additions & 2 deletions packages/protons-runtime/src/codecs/int64.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { signed } from '../utils/big-varint.js'
import { createCodec, CODEC_TYPES } from '../codec.js'
import { createCodec, CODEC_TYPES, DefaultValueFunction, IsDefaultValueFunction } from '../codec.js'
import type { DecodeFunction, EncodeFunction, EncodingLengthFunction } from '../codec.js'

const encodingLength: EncodingLengthFunction<bigint> = function int64EncodingLength (val) {
Expand All @@ -17,4 +17,8 @@ const decode: DecodeFunction<bigint> = function int64Decode (buf, offset) {
return signed.decode(buf, offset) | 0n
}

export const int64 = createCodec('int64', CODEC_TYPES.VARINT, encode, decode, encodingLength)
const defaultValue: DefaultValueFunction<bigint> = () => 0n

const isDefaultValue: IsDefaultValueFunction<bigint> = (val) => val === defaultValue()

export const int64 = createCodec('int64', CODEC_TYPES.VARINT, encode, decode, encodingLength, defaultValue, isDefaultValue)
55 changes: 48 additions & 7 deletions packages/protons-runtime/src/codecs/message.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
import { unsigned } from '../utils/varint.js'
import { createCodec, CODEC_TYPES } from '../codec.js'
import type { DecodeFunction, EncodeFunction, EncodingLengthFunction, Codec } from '../codec.js'
import type {
Codec,
DecodeFunction,
DefaultValueFunction,
EncodeFunction,
EncodingLengthFunction,
IsDefaultValueFunction
} from '../codec.js'
import { CODEC_TYPES, createCodec } from '../codec.js'
import { Uint8ArrayList } from 'uint8arraylist'
import type { FieldDefs, FieldDef } from '../index.js'
import type { FieldDef, FieldDefs } from '../index.js'

export interface Factory<A, T> {
new (obj: A): T
}

export function message <T> (fieldDefs: FieldDefs): Codec<T> {
export function message <T> (fieldDefs: FieldDefs, noDefaultOnWire: boolean): Codec<T> {
const encodingLength: EncodingLengthFunction<T> = function messageEncodingLength (val: Record<string, any>) {
let length = 0

for (const fieldDef of Object.values(fieldDefs)) {
length += fieldDef.codec.encodingLength(val[fieldDef.name])
if (!noDefaultOnWire || !fieldDef.codec.isDefaultValue(val[fieldDef.name])) {
length += fieldDef.codec.encodingLength(val[fieldDef.name])
}
}

return unsigned.encodingLength(length) + length
Expand All @@ -31,6 +40,10 @@ export function message <T> (fieldDefs: FieldDefs): Codec<T> {
throw new Error(`Non optional field "${fieldDef.name}" was ${value === null ? 'null' : 'undefined'}`)
}

if (noDefaultOnWire && fieldDef.codec.isDefaultValue(value)) {
return
}

const key = (fieldNumber << 3) | fieldDef.codec.type
const prefix = new Uint8Array(unsigned.encodingLength(key))
unsigned.encode(key, prefix)
Expand Down Expand Up @@ -116,15 +129,43 @@ export function message <T> (fieldDefs: FieldDefs): Codec<T> {
offset += fieldLength
}

// make sure repeated fields have an array if not set
for (const fieldDef of Object.values(fieldDefs)) {
// make sure repeated fields have an array if not set
if (fieldDef.repeats === true && fields[fieldDef.name] == null) {
fields[fieldDef.name] = []
} else if (noDefaultOnWire && fields[fieldDef.name] == null) {
// apply default values if not set
fields[fieldDef.name] = fieldDef.codec.defaultValue()
}
}

return fields
}

return createCodec('message', CODEC_TYPES.LENGTH_DELIMITED, encode, decode, encodingLength)
// Need to initialized sub messages as default value.
const defaultValue: DefaultValueFunction<T> = function defaultValue () {
const defaultValue: any = {}

for (const fieldDef of Object.values(fieldDefs)) {
if (fieldDef.codec.type === CODEC_TYPES.LENGTH_DELIMITED) {
defaultValue[fieldDef.name] = fieldDef.codec.defaultValue()
}
}

return defaultValue
}

const isDefaultValue: IsDefaultValueFunction<T> = function isDefaultValue (val) {
for (const fieldDef of Object.values(fieldDefs)) {
// @ts-expect-error
const fieldValue = val[fieldDef.name]

if (!fieldDef.codec.isDefaultValue(fieldValue)) {
return false
}
}
return true
}

return createCodec('message', CODEC_TYPES.LENGTH_DELIMITED, encode, decode, encodingLength, defaultValue, isDefaultValue)
}
8 changes: 6 additions & 2 deletions packages/protons-runtime/src/codecs/sfixed32.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Uint8ArrayList } from 'uint8arraylist'
import { createCodec, CODEC_TYPES } from '../codec.js'
import { createCodec, CODEC_TYPES, DefaultValueFunction, IsDefaultValueFunction } from '../codec.js'
import type { DecodeFunction, EncodeFunction, EncodingLengthFunction } from '../codec.js'

const encodingLength: EncodingLengthFunction<number> = function sfixed32EncodingLength () {
Expand All @@ -17,4 +17,8 @@ const decode: DecodeFunction<number> = function sfixed32Decode (buf, offset) {
return buf.getInt32(offset, true)
}

export const sfixed32 = createCodec('sfixed32', CODEC_TYPES.BIT32, encode, decode, encodingLength)
const defaultValue: DefaultValueFunction<number> = () => 0

const isDefaultValue: IsDefaultValueFunction<number> = (val) => val === defaultValue()

export const sfixed32 = createCodec('sfixed32', CODEC_TYPES.BIT32, encode, decode, encodingLength, defaultValue, isDefaultValue)
8 changes: 6 additions & 2 deletions packages/protons-runtime/src/codecs/sfixed64.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Uint8ArrayList } from 'uint8arraylist'
import { createCodec, CODEC_TYPES } from '../codec.js'
import { createCodec, CODEC_TYPES, DefaultValueFunction, IsDefaultValueFunction } from '../codec.js'
import type { DecodeFunction, EncodeFunction, EncodingLengthFunction } from '../codec.js'

const encodingLength: EncodingLengthFunction<bigint> = function sfixed64EncodingLength () {
Expand All @@ -17,4 +17,8 @@ const decode: DecodeFunction<bigint> = function sfixed64Decode (buf, offset) {
return buf.getBigInt64(offset, true)
}

export const sfixed64 = createCodec('sfixed64', CODEC_TYPES.BIT64, encode, decode, encodingLength)
const defaultValue: DefaultValueFunction<bigint> = () => 0n

const isDefaultValue: IsDefaultValueFunction<bigint> = (val) => val === defaultValue()

export const sfixed64 = createCodec('sfixed64', CODEC_TYPES.BIT64, encode, decode, encodingLength, defaultValue, isDefaultValue)
8 changes: 6 additions & 2 deletions packages/protons-runtime/src/codecs/sint32.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { zigzag } from '../utils/varint.js'
import { createCodec, CODEC_TYPES } from '../codec.js'
import { createCodec, CODEC_TYPES, DefaultValueFunction, IsDefaultValueFunction } from '../codec.js'
import type { DecodeFunction, EncodeFunction, EncodingLengthFunction } from '../codec.js'

const encodingLength: EncodingLengthFunction<number> = function sint32EncodingLength (val) {
Expand All @@ -18,4 +18,8 @@ const decode: DecodeFunction<number> = function svarintDecode (buf, offset) {
return zigzag.decode(buf, offset)
}

export const sint32 = createCodec('sint32', CODEC_TYPES.VARINT, encode, decode, encodingLength)
const defaultValue: DefaultValueFunction<number> = () => 0

const isDefaultValue: IsDefaultValueFunction<number> = (val) => val === defaultValue()

export const sint32 = createCodec('sint32', CODEC_TYPES.VARINT, encode, decode, encodingLength, defaultValue, isDefaultValue)
Loading

0 comments on commit c9ffd99

Please sign in to comment.