Skip to content

Commit 8a02910

Browse files
authored
fix: support empty messages (#78)
Empty messages should be supported
1 parent 51746ec commit 8a02910

13 files changed

+216
-100
lines changed

packages/protons/src/index.ts

+105-99
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,10 @@ export namespace ${messageDef.name} {
354354
let interfaceDef = ''
355355
let interfaceCodecDef = ''
356356

357-
if (interfaceFields !== '') {
357+
if (interfaceFields === '') {
358+
interfaceDef = `
359+
export interface ${messageDef.name} {}`
360+
} else {
358361
interfaceDef = `
359362
export interface ${messageDef.name} {
360363
${
@@ -363,72 +366,64 @@ export interface ${messageDef.name} {
363366
.trim()
364367
}
365368
}`
369+
}
366370

367-
interfaceCodecDef = `
368-
let _codec: Codec<${messageDef.name}>
369-
370-
export const codec = (): Codec<${messageDef.name}> => {
371-
if (_codec == null) {
372-
_codec = message<${messageDef.name}>((obj, w, opts = {}) => {
373-
if (opts.lengthDelimited !== false) {
374-
w.fork()
371+
const encodeFields = Object.entries(fields)
372+
.map(([name, fieldDef]) => {
373+
let codec: string = encoders[fieldDef.type]
374+
let type: string = fieldDef.map ? 'message' : fieldDef.type
375+
let typeName: string = ''
376+
377+
if (codec == null) {
378+
if (fieldDef.enum) {
379+
moduleDef.imports.add('enumeration')
380+
type = 'enum'
381+
} else {
382+
moduleDef.imports.add('message')
383+
type = 'message'
375384
}
376-
${Object.entries(fields)
377-
.map(([name, fieldDef]) => {
378-
let codec: string = encoders[fieldDef.type]
379-
let type: string = fieldDef.map ? 'message' : fieldDef.type
380-
let typeName: string = ''
381385

382-
if (codec == null) {
383-
if (fieldDef.enum) {
384-
moduleDef.imports.add('enumeration')
385-
type = 'enum'
386-
} else {
387-
moduleDef.imports.add('message')
388-
type = 'message'
389-
}
390-
391-
typeName = findTypeName(fieldDef.type, messageDef, moduleDef)
392-
codec = `${typeName}.codec()`
393-
}
386+
typeName = findTypeName(fieldDef.type, messageDef, moduleDef)
387+
codec = `${typeName}.codec()`
388+
}
394389

395-
let valueTest = `obj.${name} != null`
390+
let valueTest = `obj.${name} != null`
396391

397-
if (fieldDef.map) {
398-
valueTest = `obj.${name} != null && obj.${name}.size !== 0`
399-
} else if (!fieldDef.optional && !fieldDef.repeated) {
400-
// proto3 singular fields should only be written out if they are not the default value
401-
if (defaultValueTestGenerators[type] != null) {
402-
valueTest = `opts.writeDefaults === true || ${defaultValueTestGenerators[type](`obj.${name}`)}`
403-
} else if (type === 'enum') {
404-
// handle enums
405-
valueTest = `opts.writeDefaults === true || (obj.${name} != null && __${fieldDef.type}Values[obj.${name}] !== 0)`
406-
}
392+
if (fieldDef.map) {
393+
valueTest = `obj.${name} != null && obj.${name}.size !== 0`
394+
} else if (!fieldDef.optional && !fieldDef.repeated) {
395+
// proto3 singular fields should only be written out if they are not the default value
396+
if (defaultValueTestGenerators[type] != null) {
397+
valueTest = `opts.writeDefaults === true || ${defaultValueTestGenerators[type](`obj.${name}`)}`
398+
} else if (type === 'enum') {
399+
// handle enums
400+
valueTest = `opts.writeDefaults === true || (obj.${name} != null && __${fieldDef.type}Values[obj.${name}] !== 0)`
407401
}
402+
}
408403

409-
function createWriteField (valueVar: string): string {
410-
const id = (fieldDef.id << 3) | codecTypes[type]
404+
function createWriteField (valueVar: string): string {
405+
const id = (fieldDef.id << 3) | codecTypes[type]
411406

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

415-
if (type === 'message') {
416-
// message fields are only written if they have values
417-
writeField = `w.uint32(${id})
410+
if (type === 'message') {
411+
// message fields are only written if they have values
412+
writeField = `w.uint32(${id})
418413
${typeName}.codec().encode(${valueVar}, w, {
419414
writeDefaults: ${Boolean(fieldDef.repeated).toString()}
420415
})`
421-
}
422-
423-
return writeField
424416
}
425417

426-
let writeField = createWriteField(`obj.${name}`)
418+
return writeField
419+
}
427420

428-
if (fieldDef.repeated) {
429-
if (fieldDef.map) {
430-
writeField = `
431-
for (const [key, value] of obj.${name}.entries()) {
421+
let writeField = createWriteField(`obj.${name}`)
422+
423+
if (fieldDef.repeated) {
424+
if (fieldDef.map) {
425+
writeField = `
426+
for (const [key, value] of obj.${name}.entries()) {
432427
${
433428
createWriteField('{ key, value }')
434429
.split('\n')
@@ -440,9 +435,9 @@ ${Object.entries(fields)
440435
.join('\n')
441436
}
442437
}
443-
`.trim()
444-
} else {
445-
writeField = `
438+
`.trim()
439+
} else {
440+
writeField = `
446441
for (const value of obj.${name}) {
447442
${
448443
createWriteField('value')
@@ -455,69 +450,80 @@ ${Object.entries(fields)
455450
.join('\n')
456451
}
457452
}
458-
`.trim()
459-
}
453+
`.trim()
460454
}
455+
}
461456

462-
return `
457+
return `
463458
if (${valueTest}) {
464459
${writeField}
465460
}`
466-
}).join('\n')}
461+
}).join('\n')
467462

468-
if (opts.lengthDelimited !== false) {
469-
w.ldelim()
470-
}
471-
}, (reader, length) => {
472-
const obj: any = {${createDefaultObject(fields, messageDef, moduleDef)}}
463+
const decodeFields = Object.entries(fields)
464+
.map(([fieldName, fieldDef]) => {
465+
function createReadField (fieldName: string, fieldDef: FieldDef): string {
466+
let codec: string = encoders[fieldDef.type]
467+
let type: string = fieldDef.type
473468

474-
const end = length == null ? reader.len : reader.pos + length
469+
if (codec == null) {
470+
if (fieldDef.enum) {
471+
moduleDef.imports.add('enumeration')
472+
type = 'enum'
473+
} else {
474+
moduleDef.imports.add('message')
475+
type = 'message'
476+
}
475477

476-
while (reader.pos < end) {
477-
const tag = reader.uint32()
478+
const typeName = findTypeName(fieldDef.type, messageDef, moduleDef)
479+
codec = `${typeName}.codec()`
480+
}
481+
482+
const parseValue = `${decoderGenerators[type] == null ? `${codec}.decode(reader${type === 'message' ? ', reader.uint32()' : ''})` : decoderGenerators[type]()}`
478483

479-
switch (tag >>> 3) {
480-
${Object.entries(fields)
481-
.map(([fieldName, fieldDef]) => {
482-
function createReadField (fieldName: string, fieldDef: FieldDef): string {
483-
let codec: string = encoders[fieldDef.type]
484-
let type: string = fieldDef.type
485-
486-
if (codec == null) {
487-
if (fieldDef.enum) {
488-
moduleDef.imports.add('enumeration')
489-
type = 'enum'
490-
} else {
491-
moduleDef.imports.add('message')
492-
type = 'message'
493-
}
494-
495-
const typeName = findTypeName(fieldDef.type, messageDef, moduleDef)
496-
codec = `${typeName}.codec()`
497-
}
498-
499-
const parseValue = `${decoderGenerators[type] == null ? `${codec}.decode(reader${type === 'message' ? ', reader.uint32()' : ''})` : decoderGenerators[type]()}`
500-
501-
if (fieldDef.map) {
502-
return `case ${fieldDef.id}: {
484+
if (fieldDef.map) {
485+
return `case ${fieldDef.id}: {
503486
const entry = ${parseValue}
504487
obj.${fieldName}.set(entry.key, entry.value)
505488
break
506489
}`
507-
} else if (fieldDef.repeated) {
508-
return `case ${fieldDef.id}:
490+
} else if (fieldDef.repeated) {
491+
return `case ${fieldDef.id}:
509492
obj.${fieldName}.push(${parseValue})
510493
break`
511-
}
494+
}
512495

513-
return `case ${fieldDef.id}:
496+
return `case ${fieldDef.id}:
514497
obj.${fieldName} = ${parseValue}
515498
break`
516-
}
499+
}
517500

518-
return createReadField(fieldName, fieldDef)
519-
})
520-
.join('\n ')}
501+
return createReadField(fieldName, fieldDef)
502+
})
503+
.join('\n ')
504+
505+
interfaceCodecDef = `
506+
let _codec: Codec<${messageDef.name}>
507+
508+
export const codec = (): Codec<${messageDef.name}> => {
509+
if (_codec == null) {
510+
_codec = message<${messageDef.name}>((obj, w, opts = {}) => {
511+
if (opts.lengthDelimited !== false) {
512+
w.fork()
513+
}
514+
${encodeFields === '' ? '' : `${encodeFields}\n`}
515+
if (opts.lengthDelimited !== false) {
516+
w.ldelim()
517+
}
518+
}, (reader, length) => {
519+
const obj: any = {${createDefaultObject(fields, messageDef, moduleDef)}}
520+
521+
const end = length == null ? reader.len : reader.pos + length
522+
523+
while (reader.pos < end) {
524+
const tag = reader.uint32()
525+
526+
switch (tag >>> 3) {${decodeFields === '' ? '' : `\n ${decodeFields}`}
521527
default:
522528
reader.skipType(tag & 7)
523529
break
@@ -538,7 +544,6 @@ ${Object.entries(fields)
538544
export const decode = (buf: Uint8Array | Uint8ArrayList): ${messageDef.name} => {
539545
return decodeMessage(buf, ${messageDef.name}.codec())
540546
}`
541-
}
542547

543548
return `
544549
${interfaceDef}
@@ -682,6 +687,7 @@ export async function generate (source: string, flags: Flags): Promise<void> {
682687
'/* eslint-disable complexity */',
683688
'/* eslint-disable @typescript-eslint/no-namespace */',
684689
'/* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */',
690+
'/* eslint-disable @typescript-eslint/no-empty-interface */',
685691
''
686692
]
687693

packages/protons/test/fixtures/basic.proto

+4
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,7 @@ message Basic {
44
optional string foo = 1;
55
int32 num = 2;
66
}
7+
8+
message Empty {
9+
10+
}

packages/protons/test/fixtures/basic.ts

+47
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
/* eslint-disable complexity */
33
/* eslint-disable @typescript-eslint/no-namespace */
44
/* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */
5+
/* eslint-disable @typescript-eslint/no-empty-interface */
56

67
import { encodeMessage, decodeMessage, message } from 'protons-runtime'
78
import type { Uint8ArrayList } from 'uint8arraylist'
@@ -73,3 +74,49 @@ export namespace Basic {
7374
return decodeMessage(buf, Basic.codec())
7475
}
7576
}
77+
78+
export interface Empty {}
79+
80+
export namespace Empty {
81+
let _codec: Codec<Empty>
82+
83+
export const codec = (): Codec<Empty> => {
84+
if (_codec == null) {
85+
_codec = message<Empty>((obj, w, opts = {}) => {
86+
if (opts.lengthDelimited !== false) {
87+
w.fork()
88+
}
89+
90+
if (opts.lengthDelimited !== false) {
91+
w.ldelim()
92+
}
93+
}, (reader, length) => {
94+
const obj: any = {}
95+
96+
const end = length == null ? reader.len : reader.pos + length
97+
98+
while (reader.pos < end) {
99+
const tag = reader.uint32()
100+
101+
switch (tag >>> 3) {
102+
default:
103+
reader.skipType(tag & 7)
104+
break
105+
}
106+
}
107+
108+
return obj
109+
})
110+
}
111+
112+
return _codec
113+
}
114+
115+
export const encode = (obj: Empty): Uint8Array => {
116+
return encodeMessage(obj, Empty.codec())
117+
}
118+
119+
export const decode = (buf: Uint8Array | Uint8ArrayList): Empty => {
120+
return decodeMessage(buf, Empty.codec())
121+
}
122+
}

packages/protons/test/fixtures/circuit.ts

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
/* eslint-disable complexity */
33
/* eslint-disable @typescript-eslint/no-namespace */
44
/* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */
5+
/* eslint-disable @typescript-eslint/no-empty-interface */
56

67
import { enumeration, encodeMessage, decodeMessage, message } from 'protons-runtime'
78
import type { Uint8ArrayList } from 'uint8arraylist'

packages/protons/test/fixtures/daemon.ts

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
/* eslint-disable complexity */
33
/* eslint-disable @typescript-eslint/no-namespace */
44
/* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */
5+
/* eslint-disable @typescript-eslint/no-empty-interface */
56

67
import { enumeration, encodeMessage, decodeMessage, message } from 'protons-runtime'
78
import type { Uint8ArrayList } from 'uint8arraylist'

packages/protons/test/fixtures/dht.ts

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
/* eslint-disable complexity */
33
/* eslint-disable @typescript-eslint/no-namespace */
44
/* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */
5+
/* eslint-disable @typescript-eslint/no-empty-interface */
56

67
import { encodeMessage, decodeMessage, message, enumeration } from 'protons-runtime'
78
import type { Uint8ArrayList } from 'uint8arraylist'

packages/protons/test/fixtures/maps.ts

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
/* eslint-disable complexity */
33
/* eslint-disable @typescript-eslint/no-namespace */
44
/* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */
5+
/* eslint-disable @typescript-eslint/no-empty-interface */
56

67
import { encodeMessage, decodeMessage, message } from 'protons-runtime'
78
import type { Uint8ArrayList } from 'uint8arraylist'

0 commit comments

Comments
 (0)