Skip to content

Commit

Permalink
feat: JOIN-34594 add field masks support (#67)
Browse files Browse the repository at this point in the history
* feat: JOIN-34594 add field masks support
  • Loading branch information
kirpichenko authored Aug 10, 2023
1 parent efa59c2 commit b32914f
Show file tree
Hide file tree
Showing 24 changed files with 527 additions and 19 deletions.
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ commands:
description: Install protobuf's protoc generator
steps:
- run:
command: sudo apt install -y protobuf-compiler
command: sudo apt update && sudo apt install -y protobuf-compiler
compile_and_test:
description: Compile & test protoc-gen-ts
steps:
Expand Down
11 changes: 8 additions & 3 deletions internal/generator/interface_fields.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,19 @@ func (r *Runner) getMessageFieldDecorator(fieldSpec *descriptorpb.FieldDescripto

func (r *Runner) getInterfaceFieldType(fieldSpec *descriptorpb.FieldDescriptorProto) string {
fieldOptions := fieldSpec.GetOptions()
found, flavorName := false, ""
flavorFound, flavorName := false, ""
maskFound, maskName := false, ""

if fieldOptions != nil {
flavorName, found = join_proto.GetStringCustomFieldOption("typescript_flavor", fieldOptions, r.extensionTypes)
flavorName, flavorFound = join_proto.GetStringCustomFieldOption("typescript_flavor", fieldOptions, r.extensionTypes)
maskName, maskFound = join_proto.GetStringCustomFieldOption("typescript_mask", fieldOptions, r.extensionTypes)
}

baseType := "unknown"
if found && flavorName != "" {
if flavorFound && flavorName != "" {
baseType = flavorName
} else if maskFound && maskName != "" {
baseType = "IFieldMask<I" + maskName + ">"
} else {
switch fieldSpec.GetType() {
case descriptorpb.FieldDescriptorProto_TYPE_BOOL:
Expand Down
27 changes: 27 additions & 0 deletions internal/generator/runner_generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,24 @@ func (r *Runner) fileHasFlavors(generatedFileStream *protogen.GeneratedFile, pro
return false
}

func (r *Runner) fileHasFieldMasks(generatedFileStream *protogen.GeneratedFile, protoFile *protogen.File) bool {
for _, messageSpec := range protoFile.Proto.GetMessageType() {
for _, fieldSpec := range messageSpec.GetField() {
fieldOptions := fieldSpec.GetOptions()
if fieldOptions == nil {
continue
}

maskName, found := join_proto.GetStringCustomFieldOption("typescript_mask", fieldOptions, r.extensionTypes)
if found && maskName != "" {
return true
}
}
}

return false
}

func (r *Runner) generateImportName(currentSourcePath string, importSourcePath string) string {
packageName, validPkgName := r.packagesByFile[importSourcePath]
if !validPkgName {
Expand Down Expand Up @@ -143,6 +161,15 @@ func (r *Runner) generateTypescriptNamespace(generatedFileStream *protogen.Gener
"}\n",
)

if r.fileHasFieldMasks(generatedFileStream, protoFile) {
r.P(
generatedFileStream,
"interface IFieldMask<T> {",
" paths?: (keyof T)[] | string[]",
"}\n",
)
}

r.generateTypescriptFlavors(generatedFileStream, protoFile)
r.generateTypescriptEnums(generatedFileStream, protoFile)
r.generateTypescriptMessageInterfaces(generatedFileStream, protoFile)
Expand Down
172 changes: 172 additions & 0 deletions tests/__tests__/generated/FieldMasks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
// GENERATED CODE -- DO NOT EDIT!
// GENERATOR VERSION: 2.3.0.efa59c2.1691588972
/* eslint-disable @typescript-eslint/no-non-null-assertion */

import * as joinGRPC from '@join-com/grpc'
import * as protobufjs from 'protobufjs/light'

import { GoogleProtobuf } from './google/protobuf/FieldMask'
import { grpc } from '@join-com/grpc'
import { root } from './root'

protobufjs.roots['decorated'] = root

// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace FieldMasks {
interface ConvertibleTo<T> {
asInterface(): T
}

interface IFieldMask<T> {
paths?: (keyof T)[] | string[]
}

export interface IGetRequest {
id?: number
readMask?: IFieldMask<IUser>
}

export interface IUser {
id?: number
name?: string
email?: string
}

@protobufjs.Type.d('field_masks_GetRequest')
export class GetRequest extends protobufjs.Message<GetRequest> implements ConvertibleTo<IGetRequest>, IGetRequest {
@protobufjs.Field.d(1, 'int32', 'optional')
public id?: number

@protobufjs.Field.d(2, GoogleProtobuf.FieldMask, 'optional')
public readMask?: GoogleProtobuf.FieldMask

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

public static fromInterface(this: void, value: IGetRequest): GetRequest {
return GetRequest.fromObject(value)
}

public static decodePatched(this: void, reader: protobufjs.Reader | Uint8Array): IGetRequest {
return GetRequest.decode(reader).asInterface()
}

public static encodePatched(this: void, message: IGetRequest, writer?: protobufjs.Writer): protobufjs.Writer {
return GetRequest.encode(message, writer)
}
}

@protobufjs.Type.d('field_masks_User')
export class User extends protobufjs.Message<User> implements ConvertibleTo<IUser>, IUser {
@protobufjs.Field.d(1, 'int32', 'optional')
public id?: number

@protobufjs.Field.d(2, 'string', 'optional')
public name?: string

@protobufjs.Field.d(3, 'string', 'optional')
public email?: string

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

public static fromInterface(this: void, value: IUser): User {
return User.fromObject(value)
}

public static decodePatched(this: void, reader: protobufjs.Reader | Uint8Array): IUser {
return User.decode(reader).asInterface()
}

public static encodePatched(this: void, message: IUser, writer?: protobufjs.Writer): protobufjs.Writer {
return User.encode(message, writer)
}
}

export interface IUsersServiceImplementation {
Get: grpc.handleUnaryCall<IGetRequest, IUser>
}

export const usersServiceDefinition: grpc.ServiceDefinition<IUsersServiceImplementation> = {
Get: {
path: '/field_masks.Users/Get',
requestStream: false,
responseStream: false,
requestSerialize: (request: IGetRequest) => GetRequest.encodePatched(request).finish() as Buffer,
requestDeserialize: GetRequest.decodePatched,
responseSerialize: (response: IUser) => User.encodePatched(response).finish() as Buffer,
responseDeserialize: User.decodePatched,
},
}

export abstract class AbstractUsersService extends joinGRPC.Service<IUsersServiceImplementation> {
constructor(
protected readonly logger?: joinGRPC.INoDebugLogger,
protected readonly errorHandler?: joinGRPC.IServiceErrorHandler,
) {
super(
usersServiceDefinition,
{
get: call => this.Get(call),
},
logger,
errorHandler,
)
}

public abstract Get(call: grpc.ServerUnaryCall<IGetRequest, IUser>): Promise<IUser>
}

export interface IUsersClient extends joinGRPC.IExtendedClient<IUsersServiceImplementation, 'field_masks.Users'> {
get(
request: IGetRequest,
metadata?: Record<string, string>,
options?: grpc.CallOptions,
): joinGRPC.IUnaryRequest<IUser>
}

export class UsersClient
extends joinGRPC.Client<IUsersServiceImplementation, 'field_masks.Users'>
implements IUsersClient
{
constructor(config: joinGRPC.ISimplifiedClientConfig<IUsersServiceImplementation>) {
super(
{
...config,
serviceDefinition: usersServiceDefinition,
credentials: config?.credentials ?? grpc.credentials.createInsecure(),
},
'field_masks.Users',
)
}

public get(
request: IGetRequest,
metadata?: Record<string, string>,
options?: grpc.CallOptions,
): joinGRPC.IUnaryRequest<IUser> {
return this.makeUnaryRequest('Get', request, metadata, options)
}
}
}
2 changes: 1 addition & 1 deletion tests/__tests__/generated/Flavors.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// GENERATED CODE -- DO NOT EDIT!
// GENERATOR VERSION: 2.1.0.e9910b5.1646051017
// GENERATOR VERSION: 2.3.0.efa59c2.1691588972
/* eslint-disable @typescript-eslint/no-non-null-assertion */

import * as joinGRPC from '@join-com/grpc'
Expand Down
2 changes: 1 addition & 1 deletion tests/__tests__/generated/Regressions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// GENERATED CODE -- DO NOT EDIT!
// GENERATOR VERSION: 2.1.0.e9910b5.1646051017
// GENERATOR VERSION: 2.3.0.efa59c2.1691588972
/* eslint-disable @typescript-eslint/no-non-null-assertion */

import * as protobufjs from 'protobufjs/light'
Expand Down
2 changes: 1 addition & 1 deletion tests/__tests__/generated/Test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// GENERATED CODE -- DO NOT EDIT!
// GENERATOR VERSION: 2.1.0.e9910b5.1646051017
// GENERATOR VERSION: 2.3.0.efa59c2.1691588972
/* eslint-disable @typescript-eslint/no-non-null-assertion */

import * as joinGRPC from '@join-com/grpc'
Expand Down
2 changes: 1 addition & 1 deletion tests/__tests__/generated/common/Common.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// GENERATED CODE -- DO NOT EDIT!
// GENERATOR VERSION: 2.1.0.e9910b5.1646051017
// GENERATOR VERSION: 2.3.0.efa59c2.1691588972
/* eslint-disable @typescript-eslint/no-non-null-assertion */

import * as protobufjs from 'protobufjs/light'
Expand Down
2 changes: 1 addition & 1 deletion tests/__tests__/generated/common/Extra.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// GENERATED CODE -- DO NOT EDIT!
// GENERATOR VERSION: 2.1.0.e9910b5.1646051017
// GENERATOR VERSION: 2.3.0.efa59c2.1691588972
/* eslint-disable @typescript-eslint/no-non-null-assertion */

import * as protobufjs from 'protobufjs/light'
Expand Down
2 changes: 1 addition & 1 deletion tests/__tests__/generated/google/protobuf/Empty.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// GENERATED CODE -- DO NOT EDIT!
// GENERATOR VERSION: 2.1.0.e9910b5.1646051017
// GENERATOR VERSION: 2.3.0.efa59c2.1691588972
/* eslint-disable @typescript-eslint/no-non-null-assertion */

import * as protobufjs from 'protobufjs/light'
Expand Down
52 changes: 52 additions & 0 deletions tests/__tests__/generated/google/protobuf/FieldMask.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// GENERATED CODE -- DO NOT EDIT!
// GENERATOR VERSION: 2.3.0.efa59c2.1691588972
/* eslint-disable @typescript-eslint/no-non-null-assertion */

import * as protobufjs from 'protobufjs/light'

import { root } from '../../root'

protobufjs.roots['decorated'] = root

// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace GoogleProtobuf {
interface ConvertibleTo<T> {
asInterface(): T
}

export interface IFieldMask {
paths?: string[]
}

@protobufjs.Type.d('google_protobuf_FieldMask')
export class FieldMask extends protobufjs.Message<FieldMask> implements ConvertibleTo<IFieldMask>, IFieldMask {
@protobufjs.Field.d(1, 'string', 'repeated')
public paths?: string[]

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

public static fromInterface(this: void, value: IFieldMask): FieldMask {
return FieldMask.fromObject(value)
}

public static decodePatched(this: void, reader: protobufjs.Reader | Uint8Array): IFieldMask {
return FieldMask.decode(reader).asInterface()
}

public static encodePatched(this: void, message: IFieldMask, writer?: protobufjs.Writer): protobufjs.Writer {
return FieldMask.encode(message, writer)
}
}
}
2 changes: 1 addition & 1 deletion tests/__tests__/generated/google/protobuf/Timestamp.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// GENERATED CODE -- DO NOT EDIT!
// GENERATOR VERSION: 2.1.0.e9910b5.1646051017
// GENERATOR VERSION: 2.3.0.efa59c2.1691588972
/* eslint-disable @typescript-eslint/no-non-null-assertion */

import * as protobufjs from 'protobufjs/light'
Expand Down
Loading

0 comments on commit b32914f

Please sign in to comment.