Skip to content

Commit

Permalink
Merge pull request #35 from join-com/join-11772-delete-undefined-fields
Browse files Browse the repository at this point in the history
[JOIN-11772] feat: delete undefined fields in decode function
  • Loading branch information
castarco authored May 4, 2021
2 parents 83bb5d9 + 92a76da commit 6162ce2
Show file tree
Hide file tree
Showing 7 changed files with 151 additions and 37 deletions.
44 changes: 26 additions & 18 deletions internal/generator/runner_generate_class.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ func (r *Runner) generateTypescriptClassPatchedMethods(generatedFileStream *prot
r.generateAsInterfaceMethod(generatedFileStream, messageSpec, requiredFields, hasEnums)
r.generateFromInterfaceMethod(generatedFileStream, messageSpec, requiredFields, hasEnums)

r.generateDecodePatchedMethod(generatedFileStream, messageSpec, requiredFields, hasEnums)
r.generateDecodePatchedMethod(generatedFileStream, messageSpec, requiredFields)
r.generateEncodePatchedMethod(generatedFileStream, messageSpec, requiredFields, hasEnums)
}

Expand All @@ -192,7 +192,7 @@ func (r *Runner) generateAsInterfaceMethod(generatedFileStream *protogen.Generat
r.indentLevel += 2

if hasEnums {
r.P(generatedFileStream, "return {")
r.P(generatedFileStream, "const message = {")
r.indentLevel += 2

r.P(generatedFileStream, "...this,")
Expand All @@ -203,9 +203,27 @@ func (r *Runner) generateAsInterfaceMethod(generatedFileStream *protogen.Generat
r.indentLevel -= 2
r.P(generatedFileStream, "}")
} else {
r.P(generatedFileStream, "return this")
r.P(
generatedFileStream,
"/* eslint-disable @typescript-eslint/no-this-alias */",
"// tslint:disable-next-line: no-this-assignment",
"const message = this",
"/* eslint-enable @typescript-eslint/no-this-alias */",
)
}

r.P(
generatedFileStream,
"for (const fieldName of Object.keys(message)) {",
" if (message[fieldName as keyof I"+className+"] == null) {",
" // We remove the key to avoid problems with code making too many assumptions",
" delete message[fieldName as keyof I"+className+"]",
" }",
"}",
)

r.P(generatedFileStream, "return message")

r.indentLevel -= 2
r.P(generatedFileStream, "}\n")
}
Expand Down Expand Up @@ -236,36 +254,26 @@ func (r *Runner) generateFromInterfaceMethod(generatedFileStream *protogen.Gener
r.P(generatedFileStream, "}\n")
}

func (r *Runner) generateDecodePatchedMethod(generatedFileStream *protogen.GeneratedFile, messageSpec *descriptorpb.DescriptorProto, requiredFields bool, hasEnums bool) {
func (r *Runner) generateDecodePatchedMethod(generatedFileStream *protogen.GeneratedFile, messageSpec *descriptorpb.DescriptorProto, requiredFields bool) {
className := strcase.ToCamel(messageSpec.GetName())
r.P(generatedFileStream, "public static decodePatched(this: void, reader: protobufjs.Reader | Uint8Array): I"+className+" {")
r.indentLevel += 2

messageRequiredFields := r.getMessageRequiredFields(messageSpec, requiredFields)
if len(messageRequiredFields) > 0 {
var keyofPlaceholder string
if hasEnums {
r.P(generatedFileStream, "const message = "+className+".decode(reader).asInterface()")
keyofPlaceholder = "I" + className
} else {
r.P(generatedFileStream, "const message = "+className+".decode(reader)")
keyofPlaceholder = className
}

r.P(generatedFileStream, "const message = "+className+".decode(reader).asInterface()")
r.P(
generatedFileStream,
"for (const fieldName of ["+strings.Join(messageRequiredFields, ", ")+"] as (keyof "+keyofPlaceholder+")[]) {",
"for (const fieldName of ["+strings.Join(messageRequiredFields, ", ")+"] as (keyof I"+className+")[]) {",
" if (message[fieldName] == null) {",
" throw new Error(`Required field ${fieldName} in "+className+" is null or undefined`)",
" }",
"}",
"return message",
)
} else {
if hasEnums {
r.P(generatedFileStream, "return "+className+".decode(reader).asInterface()")
} else {
r.P(generatedFileStream, "return "+className+".decode(reader)")
}
r.P(generatedFileStream, "return "+className+".decode(reader).asInterface()")
}

r.indentLevel -= 2
Expand Down
15 changes: 15 additions & 0 deletions tests/__tests__/classes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,19 @@ describe('(v2) classes', () => {
// and defined in the interface).
expect(reconstructedComplex).toMatchObject(complex)
})

it('is able to deal with timestamps', () => {
const testObj: Foo.ITest = {
timestamp: new Date('2021-05-05 18:44:00'),
}

const buffer = Foo.Test.encodePatched(testObj).finish()
const reconstructed = Foo.Test.decodePatched(buffer)

expect(reconstructed.timestamp).not.toBeUndefined()
expect(reconstructed.timestamp).not.toBeNull()
expect(testObj.timestamp?.getTime()).toEqual(
reconstructed.timestamp?.getTime()
)
})
})
30 changes: 25 additions & 5 deletions tests/__tests__/generated/Flavors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,17 @@ export namespace Flavors {
public emails!: Email[]

public asInterface(): IUserProfile {
return this
/* eslint-disable @typescript-eslint/no-this-alias */
// tslint:disable-next-line: no-this-assignment
const message = this
/* eslint-enable @typescript-eslint/no-this-alias */
for (const fieldName of Object.keys(message)) {
if (message[fieldName as keyof IUserProfile] == null) {
// We remove the key to avoid problems with code making too many assumptions
delete message[fieldName as keyof IUserProfile]
}
}
return message
}

public static fromInterface(this: void, value: IUserProfile): UserProfile {
Expand All @@ -52,12 +62,12 @@ export namespace Flavors {
this: void,
reader: protobufjs.Reader | Uint8Array
): IUserProfile {
const message = UserProfile.decode(reader)
const message = UserProfile.decode(reader).asInterface()
for (const fieldName of [
'id',
'username',
'emails',
] as (keyof UserProfile)[]) {
] as (keyof IUserProfile)[]) {
if (message[fieldName] == null) {
throw new Error(
`Required field ${fieldName} in UserProfile is null or undefined`
Expand Down Expand Up @@ -95,7 +105,17 @@ export namespace Flavors {
public userId?: UserId

public asInterface(): IUserRequest {
return this
/* eslint-disable @typescript-eslint/no-this-alias */
// tslint:disable-next-line: no-this-assignment
const message = this
/* eslint-enable @typescript-eslint/no-this-alias */
for (const fieldName of Object.keys(message)) {
if (message[fieldName as keyof IUserRequest] == null) {
// We remove the key to avoid problems with code making too many assumptions
delete message[fieldName as keyof IUserRequest]
}
}
return message
}

public static fromInterface(this: void, value: IUserRequest): UserRequest {
Expand All @@ -106,7 +126,7 @@ export namespace Flavors {
this: void,
reader: protobufjs.Reader | Uint8Array
): IUserRequest {
return UserRequest.decode(reader)
return UserRequest.decode(reader).asInterface()
}

public static encodePatched(
Expand Down
62 changes: 53 additions & 9 deletions tests/__tests__/generated/Test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,17 @@ export namespace Foo {
public customOptionalField?: number

public asInterface(): ICustomOptionsTest {
return {
const message = {
...this,
requiredField: this.requiredField.asInterface(),
}
for (const fieldName of Object.keys(message)) {
if (message[fieldName as keyof ICustomOptionsTest] == null) {
// We remove the key to avoid problems with code making too many assumptions
delete message[fieldName as keyof ICustomOptionsTest]
}
}
return message
}

public static fromInterface(
Expand Down Expand Up @@ -174,7 +181,17 @@ export namespace Foo {
public title?: string

public asInterface(): INested {
return this
/* eslint-disable @typescript-eslint/no-this-alias */
// tslint:disable-next-line: no-this-assignment
const message = this
/* eslint-enable @typescript-eslint/no-this-alias */
for (const fieldName of Object.keys(message)) {
if (message[fieldName as keyof INested] == null) {
// We remove the key to avoid problems with code making too many assumptions
delete message[fieldName as keyof INested]
}
}
return message
}

public static fromInterface(this: void, value: INested): Nested {
Expand All @@ -185,7 +202,7 @@ export namespace Foo {
this: void,
reader: protobufjs.Reader | Uint8Array
): INested {
return Nested.decode(reader)
return Nested.decode(reader).asInterface()
}

public static encodePatched(
Expand All @@ -205,7 +222,17 @@ export namespace Foo {
public id?: number

public asInterface(): IRequest {
return this
/* eslint-disable @typescript-eslint/no-this-alias */
// tslint:disable-next-line: no-this-assignment
const message = this
/* eslint-enable @typescript-eslint/no-this-alias */
for (const fieldName of Object.keys(message)) {
if (message[fieldName as keyof IRequest] == null) {
// We remove the key to avoid problems with code making too many assumptions
delete message[fieldName as keyof IRequest]
}
}
return message
}

public static fromInterface(this: void, value: IRequest): Request {
Expand All @@ -216,7 +243,7 @@ export namespace Foo {
this: void,
reader: protobufjs.Reader | Uint8Array
): IRequest {
return Request.decode(reader)
return Request.decode(reader).asInterface()
}

public static encodePatched(
Expand All @@ -242,7 +269,17 @@ export namespace Foo {
public optionalField?: number

public asInterface(): IRequiredPropertiesTest {
return this
/* eslint-disable @typescript-eslint/no-this-alias */
// tslint:disable-next-line: no-this-assignment
const message = this
/* eslint-enable @typescript-eslint/no-this-alias */
for (const fieldName of Object.keys(message)) {
if (message[fieldName as keyof IRequiredPropertiesTest] == null) {
// We remove the key to avoid problems with code making too many assumptions
delete message[fieldName as keyof IRequiredPropertiesTest]
}
}
return message
}

public static fromInterface(
Expand All @@ -256,11 +293,11 @@ export namespace Foo {
this: void,
reader: protobufjs.Reader | Uint8Array
): IRequiredPropertiesTest {
const message = RequiredPropertiesTest.decode(reader)
const message = RequiredPropertiesTest.decode(reader).asInterface()
for (const fieldName of [
'requiredField',
'customRequiredField',
] as (keyof RequiredPropertiesTest)[]) {
] as (keyof IRequiredPropertiesTest)[]) {
if (message[fieldName] == null) {
throw new Error(
`Required field ${fieldName} in RequiredPropertiesTest is null or undefined`
Expand Down Expand Up @@ -408,7 +445,7 @@ export namespace Foo {
public fieldInt64Repeated?: number[]

public asInterface(): ITest {
return {
const message = {
...this,
fieldEnum: (this.fieldEnum != null
? EnumType_Enum[this.fieldEnum]!
Expand All @@ -427,6 +464,13 @@ export namespace Foo {
(ts) => new Date((ts.seconds ?? 0) * 1000 + (ts.nanos ?? 0) / 1000000)
),
}
for (const fieldName of Object.keys(message)) {
if (message[fieldName as keyof ITest] == null) {
// We remove the key to avoid problems with code making too many assumptions
delete message[fieldName as keyof ITest]
}
}
return message
}

public static fromInterface(this: void, value: ITest): Test {
Expand Down
14 changes: 12 additions & 2 deletions tests/__tests__/generated/common/Common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,17 @@ export namespace Common {
public latsName?: string

public asInterface(): IOtherPkgMessage {
return this
/* eslint-disable @typescript-eslint/no-this-alias */
// tslint:disable-next-line: no-this-assignment
const message = this
/* eslint-enable @typescript-eslint/no-this-alias */
for (const fieldName of Object.keys(message)) {
if (message[fieldName as keyof IOtherPkgMessage] == null) {
// We remove the key to avoid problems with code making too many assumptions
delete message[fieldName as keyof IOtherPkgMessage]
}
}
return message
}

public static fromInterface(
Expand All @@ -39,7 +49,7 @@ export namespace Common {
this: void,
reader: protobufjs.Reader | Uint8Array
): IOtherPkgMessage {
return OtherPkgMessage.decode(reader)
return OtherPkgMessage.decode(reader).asInterface()
}

public static encodePatched(
Expand Down
9 changes: 8 additions & 1 deletion tests/__tests__/generated/common/Extra.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export namespace Common {
public birthDate?: GoogleProtobuf.Timestamp

public asInterface(): IExtraPkgMessage {
return {
const message = {
...this,
birthDate:
this.birthDate != null
Expand All @@ -41,6 +41,13 @@ export namespace Common {
)
: undefined,
}
for (const fieldName of Object.keys(message)) {
if (message[fieldName as keyof IExtraPkgMessage] == null) {
// We remove the key to avoid problems with code making too many assumptions
delete message[fieldName as keyof IExtraPkgMessage]
}
}
return message
}

public static fromInterface(
Expand Down
14 changes: 12 additions & 2 deletions tests/__tests__/generated/google/protobuf/Timestamp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,17 @@ export namespace GoogleProtobuf {
public nanos?: number

public asInterface(): ITimestamp {
return this
/* eslint-disable @typescript-eslint/no-this-alias */
// tslint:disable-next-line: no-this-assignment
const message = this
/* eslint-enable @typescript-eslint/no-this-alias */
for (const fieldName of Object.keys(message)) {
if (message[fieldName as keyof ITimestamp] == null) {
// We remove the key to avoid problems with code making too many assumptions
delete message[fieldName as keyof ITimestamp]
}
}
return message
}

public static fromInterface(this: void, value: ITimestamp): Timestamp {
Expand All @@ -36,7 +46,7 @@ export namespace GoogleProtobuf {
this: void,
reader: protobufjs.Reader | Uint8Array
): ITimestamp {
return Timestamp.decode(reader)
return Timestamp.decode(reader).asInterface()
}

public static encodePatched(
Expand Down

0 comments on commit 6162ce2

Please sign in to comment.