Skip to content

Commit

Permalink
fix: support empty messages (#78)
Browse files Browse the repository at this point in the history
Empty messages should be supported
  • Loading branch information
achingbrain committed Jan 17, 2023
1 parent 51746ec commit 8a02910
Show file tree
Hide file tree
Showing 13 changed files with 216 additions and 100 deletions.
204 changes: 105 additions & 99 deletions packages/protons/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,10 @@ export namespace ${messageDef.name} {
let interfaceDef = ''
let interfaceCodecDef = ''

if (interfaceFields !== '') {
if (interfaceFields === '') {
interfaceDef = `
export interface ${messageDef.name} {}`
} else {
interfaceDef = `
export interface ${messageDef.name} {
${
Expand All @@ -363,72 +366,64 @@ export interface ${messageDef.name} {
.trim()
}
}`
}

interfaceCodecDef = `
let _codec: Codec<${messageDef.name}>
export const codec = (): Codec<${messageDef.name}> => {
if (_codec == null) {
_codec = message<${messageDef.name}>((obj, w, opts = {}) => {
if (opts.lengthDelimited !== false) {
w.fork()
const encodeFields = Object.entries(fields)
.map(([name, fieldDef]) => {
let codec: string = encoders[fieldDef.type]
let type: string = fieldDef.map ? 'message' : fieldDef.type
let typeName: string = ''

if (codec == null) {
if (fieldDef.enum) {
moduleDef.imports.add('enumeration')
type = 'enum'
} else {
moduleDef.imports.add('message')
type = 'message'
}
${Object.entries(fields)
.map(([name, fieldDef]) => {
let codec: string = encoders[fieldDef.type]
let type: string = fieldDef.map ? 'message' : fieldDef.type
let typeName: string = ''

if (codec == null) {
if (fieldDef.enum) {
moduleDef.imports.add('enumeration')
type = 'enum'
} else {
moduleDef.imports.add('message')
type = 'message'
}
typeName = findTypeName(fieldDef.type, messageDef, moduleDef)
codec = `${typeName}.codec()`
}
typeName = findTypeName(fieldDef.type, messageDef, moduleDef)
codec = `${typeName}.codec()`
}

let valueTest = `obj.${name} != null`
let valueTest = `obj.${name} != null`

if (fieldDef.map) {
valueTest = `obj.${name} != null && obj.${name}.size !== 0`
} else if (!fieldDef.optional && !fieldDef.repeated) {
// proto3 singular fields should only be written out if they are not the default value
if (defaultValueTestGenerators[type] != null) {
valueTest = `opts.writeDefaults === true || ${defaultValueTestGenerators[type](`obj.${name}`)}`
} else if (type === 'enum') {
// handle enums
valueTest = `opts.writeDefaults === true || (obj.${name} != null && __${fieldDef.type}Values[obj.${name}] !== 0)`
}
if (fieldDef.map) {
valueTest = `obj.${name} != null && obj.${name}.size !== 0`
} else if (!fieldDef.optional && !fieldDef.repeated) {
// proto3 singular fields should only be written out if they are not the default value
if (defaultValueTestGenerators[type] != null) {
valueTest = `opts.writeDefaults === true || ${defaultValueTestGenerators[type](`obj.${name}`)}`
} else if (type === 'enum') {
// handle enums
valueTest = `opts.writeDefaults === true || (obj.${name} != null && __${fieldDef.type}Values[obj.${name}] !== 0)`
}
}

function createWriteField (valueVar: string): string {
const id = (fieldDef.id << 3) | codecTypes[type]
function createWriteField (valueVar: string): string {
const id = (fieldDef.id << 3) | codecTypes[type]

let writeField = `w.uint32(${id})
let writeField = `w.uint32(${id})
${encoderGenerators[type] == null ? `${codec}.encode(${valueVar}, w)` : encoderGenerators[type](valueVar)}`

if (type === 'message') {
// message fields are only written if they have values
writeField = `w.uint32(${id})
if (type === 'message') {
// message fields are only written if they have values
writeField = `w.uint32(${id})
${typeName}.codec().encode(${valueVar}, w, {
writeDefaults: ${Boolean(fieldDef.repeated).toString()}
})`
}
return writeField
}

let writeField = createWriteField(`obj.${name}`)
return writeField
}

if (fieldDef.repeated) {
if (fieldDef.map) {
writeField = `
for (const [key, value] of obj.${name}.entries()) {
let writeField = createWriteField(`obj.${name}`)

if (fieldDef.repeated) {
if (fieldDef.map) {
writeField = `
for (const [key, value] of obj.${name}.entries()) {
${
createWriteField('{ key, value }')
.split('\n')
Expand All @@ -440,9 +435,9 @@ ${Object.entries(fields)
.join('\n')
}
}
`.trim()
} else {
writeField = `
`.trim()
} else {
writeField = `
for (const value of obj.${name}) {
${
createWriteField('value')
Expand All @@ -455,69 +450,80 @@ ${Object.entries(fields)
.join('\n')
}
}
`.trim()
}
`.trim()
}
}

return `
return `
if (${valueTest}) {
${writeField}
}`
}).join('\n')}
}).join('\n')

if (opts.lengthDelimited !== false) {
w.ldelim()
}
}, (reader, length) => {
const obj: any = {${createDefaultObject(fields, messageDef, moduleDef)}}
const decodeFields = Object.entries(fields)
.map(([fieldName, fieldDef]) => {
function createReadField (fieldName: string, fieldDef: FieldDef): string {
let codec: string = encoders[fieldDef.type]
let type: string = fieldDef.type

const end = length == null ? reader.len : reader.pos + length
if (codec == null) {
if (fieldDef.enum) {
moduleDef.imports.add('enumeration')
type = 'enum'
} else {
moduleDef.imports.add('message')
type = 'message'
}

while (reader.pos < end) {
const tag = reader.uint32()
const typeName = findTypeName(fieldDef.type, messageDef, moduleDef)
codec = `${typeName}.codec()`
}

const parseValue = `${decoderGenerators[type] == null ? `${codec}.decode(reader${type === 'message' ? ', reader.uint32()' : ''})` : decoderGenerators[type]()}`

switch (tag >>> 3) {
${Object.entries(fields)
.map(([fieldName, fieldDef]) => {
function createReadField (fieldName: string, fieldDef: FieldDef): string {
let codec: string = encoders[fieldDef.type]
let type: string = fieldDef.type
if (codec == null) {
if (fieldDef.enum) {
moduleDef.imports.add('enumeration')
type = 'enum'
} else {
moduleDef.imports.add('message')
type = 'message'
}
const typeName = findTypeName(fieldDef.type, messageDef, moduleDef)
codec = `${typeName}.codec()`
}
const parseValue = `${decoderGenerators[type] == null ? `${codec}.decode(reader${type === 'message' ? ', reader.uint32()' : ''})` : decoderGenerators[type]()}`
if (fieldDef.map) {
return `case ${fieldDef.id}: {
if (fieldDef.map) {
return `case ${fieldDef.id}: {
const entry = ${parseValue}
obj.${fieldName}.set(entry.key, entry.value)
break
}`
} else if (fieldDef.repeated) {
return `case ${fieldDef.id}:
} else if (fieldDef.repeated) {
return `case ${fieldDef.id}:
obj.${fieldName}.push(${parseValue})
break`
}
}

return `case ${fieldDef.id}:
return `case ${fieldDef.id}:
obj.${fieldName} = ${parseValue}
break`
}
}

return createReadField(fieldName, fieldDef)
})
.join('\n ')}
return createReadField(fieldName, fieldDef)
})
.join('\n ')

interfaceCodecDef = `
let _codec: Codec<${messageDef.name}>
export const codec = (): Codec<${messageDef.name}> => {
if (_codec == null) {
_codec = message<${messageDef.name}>((obj, w, opts = {}) => {
if (opts.lengthDelimited !== false) {
w.fork()
}
${encodeFields === '' ? '' : `${encodeFields}\n`}
if (opts.lengthDelimited !== false) {
w.ldelim()
}
}, (reader, length) => {
const obj: any = {${createDefaultObject(fields, messageDef, moduleDef)}}
const end = length == null ? reader.len : reader.pos + length
while (reader.pos < end) {
const tag = reader.uint32()
switch (tag >>> 3) {${decodeFields === '' ? '' : `\n ${decodeFields}`}
default:
reader.skipType(tag & 7)
break
Expand All @@ -538,7 +544,6 @@ ${Object.entries(fields)
export const decode = (buf: Uint8Array | Uint8ArrayList): ${messageDef.name} => {
return decodeMessage(buf, ${messageDef.name}.codec())
}`
}

return `
${interfaceDef}
Expand Down Expand Up @@ -682,6 +687,7 @@ export async function generate (source: string, flags: Flags): Promise<void> {
'/* eslint-disable complexity */',
'/* eslint-disable @typescript-eslint/no-namespace */',
'/* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */',
'/* eslint-disable @typescript-eslint/no-empty-interface */',
''
]

Expand Down
4 changes: 4 additions & 0 deletions packages/protons/test/fixtures/basic.proto
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,7 @@ message Basic {
optional string foo = 1;
int32 num = 2;
}

message Empty {

}
47 changes: 47 additions & 0 deletions packages/protons/test/fixtures/basic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
/* eslint-disable complexity */
/* eslint-disable @typescript-eslint/no-namespace */
/* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */
/* eslint-disable @typescript-eslint/no-empty-interface */

import { encodeMessage, decodeMessage, message } from 'protons-runtime'
import type { Uint8ArrayList } from 'uint8arraylist'
Expand Down Expand Up @@ -73,3 +74,49 @@ export namespace Basic {
return decodeMessage(buf, Basic.codec())
}
}

export interface Empty {}

export namespace Empty {
let _codec: Codec<Empty>

export const codec = (): Codec<Empty> => {
if (_codec == null) {
_codec = message<Empty>((obj, w, opts = {}) => {
if (opts.lengthDelimited !== false) {
w.fork()
}

if (opts.lengthDelimited !== false) {
w.ldelim()
}
}, (reader, length) => {
const obj: any = {}

const end = length == null ? reader.len : reader.pos + length

while (reader.pos < end) {
const tag = reader.uint32()

switch (tag >>> 3) {
default:
reader.skipType(tag & 7)
break
}
}

return obj
})
}

return _codec
}

export const encode = (obj: Empty): Uint8Array => {
return encodeMessage(obj, Empty.codec())
}

export const decode = (buf: Uint8Array | Uint8ArrayList): Empty => {
return decodeMessage(buf, Empty.codec())
}
}
1 change: 1 addition & 0 deletions packages/protons/test/fixtures/circuit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
/* eslint-disable complexity */
/* eslint-disable @typescript-eslint/no-namespace */
/* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */
/* eslint-disable @typescript-eslint/no-empty-interface */

import { enumeration, encodeMessage, decodeMessage, message } from 'protons-runtime'
import type { Uint8ArrayList } from 'uint8arraylist'
Expand Down
1 change: 1 addition & 0 deletions packages/protons/test/fixtures/daemon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
/* eslint-disable complexity */
/* eslint-disable @typescript-eslint/no-namespace */
/* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */
/* eslint-disable @typescript-eslint/no-empty-interface */

import { enumeration, encodeMessage, decodeMessage, message } from 'protons-runtime'
import type { Uint8ArrayList } from 'uint8arraylist'
Expand Down
1 change: 1 addition & 0 deletions packages/protons/test/fixtures/dht.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
/* eslint-disable complexity */
/* eslint-disable @typescript-eslint/no-namespace */
/* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */
/* eslint-disable @typescript-eslint/no-empty-interface */

import { encodeMessage, decodeMessage, message, enumeration } from 'protons-runtime'
import type { Uint8ArrayList } from 'uint8arraylist'
Expand Down
1 change: 1 addition & 0 deletions packages/protons/test/fixtures/maps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
/* eslint-disable complexity */
/* eslint-disable @typescript-eslint/no-namespace */
/* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */
/* eslint-disable @typescript-eslint/no-empty-interface */

import { encodeMessage, decodeMessage, message } from 'protons-runtime'
import type { Uint8ArrayList } from 'uint8arraylist'
Expand Down
Loading

0 comments on commit 8a02910

Please sign in to comment.