Skip to content

Commit

Permalink
Merge pull request #11 from join-com/services-integration
Browse files Browse the repository at this point in the history
[JOIN-11423] Bug fixes
  • Loading branch information
castarco authored Apr 14, 2021
2 parents 145acd2 + 6b0ae42 commit 43da5a7
Show file tree
Hide file tree
Showing 12 changed files with 300 additions and 222 deletions.
48 changes: 44 additions & 4 deletions internal/generator/paths.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,54 @@ import (
"github.com/iancoleman/strcase"
)

func fromProtoPathToGeneratedPath(protoPath string) string {
outputDirectory := filepath.Dir(protoPath)
outputFileName := strcase.ToCamel(strings.TrimSuffix(filepath.Base(protoPath), filepath.Ext(protoPath)))
generatedPath := fmt.Sprintf("%s/%s", outputDirectory, outputFileName)
func fromProtoPathToGeneratedPath(protoImportPath string, currentSourcePath string) string {

importDirectory := filepath.Dir(protoImportPath)
currentDirectory := filepath.Dir(currentSourcePath)

if !strings.HasPrefix(importDirectory, ".") {
importDirectory = "./" + importDirectory
}
if !strings.HasPrefix(currentDirectory, ".") {
currentDirectory = "./" + currentDirectory
}

importDirectoryParts := strings.Split(importDirectory, "/")
currentDirectoryParts := strings.Split(currentDirectory, "/")

minPathLength := min(len(importDirectoryParts), len(currentDirectoryParts))

for i := 0; i < minPathLength; i++ {
if importDirectoryParts[0] == currentDirectoryParts[0] {
importDirectoryParts = importDirectoryParts[1:]
currentDirectoryParts = currentDirectoryParts[1:]
} else {
break
}
}

var outputDirectory string

numStepsBack := len(currentDirectoryParts)
if numStepsBack == 0 {
outputDirectory = "./" + strings.Join(importDirectoryParts, "/") + "/"
} else {
outputDirectory = strings.Repeat("../", numStepsBack) + strings.Join(importDirectoryParts, "/") + "/"
}

outputFileName := strcase.ToCamel(strings.TrimSuffix(filepath.Base(protoImportPath), filepath.Ext(protoImportPath)))
generatedPath := fmt.Sprintf("%s%s", outputDirectory, outputFileName)

return generatedPath
}

func getNamespaceFromProtoPackage(protoPackage string) string {
return strcase.ToCamel(protoPackage)
}

func min(a, b int) int {
if a <= b {
return a
}
return b
}
2 changes: 1 addition & 1 deletion internal/generator/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func (r *Runner) Run(plugin *protogen.Plugin) error {
continue
}

outputPath := fromProtoPathToGeneratedPath(file.Desc.Path()) + ".ts"
outputPath := fromProtoPathToGeneratedPath(file.Desc.Path(), ".") + ".ts"
generatedFileStream := plugin.NewGeneratedFile(outputPath, "")
r.generateTypescriptFile(file, generatedFileStream)
}
Expand Down
10 changes: 7 additions & 3 deletions internal/generator/runner_generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ func (r *Runner) generateTypescriptImports(currentSourcePath string, importSourc
}

generatedFileStream.P(fmt.Sprintf(
"import { %s } from './%s'",
"import { %s } from '%s'",
r.generateImportName(currentSourcePath, importSourcePath),
fromProtoPathToGeneratedPath(importSourcePath),
fromProtoPathToGeneratedPath(importSourcePath, currentSourcePath),
))
}
generatedFileStream.P("")
Expand Down Expand Up @@ -72,7 +72,11 @@ func (r *Runner) generateImportName(currentSourcePath string, importSourcePath s

func (r *Runner) generateTypescriptNamespace(generatedFileStream *protogen.GeneratedFile, protoFile *protogen.File) {
namespace := getNamespaceFromProtoPackage(protoFile.Proto.GetPackage())
r.P(generatedFileStream, "export namespace "+namespace+" {\n")
r.P(
generatedFileStream,
"// eslint-disable-next-line @typescript-eslint/no-namespace",
"export namespace "+namespace+" {\n",
)
r.indentLevel += 2
r.currentNamespace = namespace

Expand Down
45 changes: 19 additions & 26 deletions internal/generator/runner_generate_class.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func (r *Runner) generateTypescriptMessageClass(generatedFileStream *protogen.Ge
}

className := strcase.ToCamel(messageSpec.GetName())
hasEnums := messageHasEnumsOrDates(messageSpec)
hasEnums := r.messageHasEnumsOrDates(messageSpec)
implementedInterfaces := "ConvertibleTo<I" + className + ">"
if !hasEnums {
implementedInterfaces += ", I" + className
Expand Down Expand Up @@ -188,18 +188,13 @@ func (r *Runner) generatePatchedInterfaceField(
}

fieldOptions := fieldSpec.GetOptions()
separator := "?: "
requiredField, foundRequired := join_proto.GetBooleanCustomFieldOption("typescript_required", fieldOptions, r.extensionTypes)
optionalField, foundOptional := join_proto.GetBooleanCustomFieldOption("typescript_optional", fieldOptions, r.extensionTypes)
if foundRequired && requiredField && foundOptional && optionalField {
utils.LogError("incompatible options for field " + fieldSpec.GetName() + " in " + messageSpec.GetName())
}
confirmRequired := false
if requiredFields && !(foundOptional && optionalField) || foundRequired && requiredField {
confirmRequired = true
separator = ": "
}

confirmRequired := requiredFields && !(foundOptional && optionalField) || foundRequired && requiredField
isRepeated := fieldSpec.GetLabel() == descriptorpb.FieldDescriptorProto_LABEL_REPEATED

value := "this." + fieldSpec.GetJsonName()
Expand All @@ -225,7 +220,7 @@ func (r *Runner) generatePatchedInterfaceField(
if !ok || nestedMessageSpec == nil {
utils.LogError("Unable to retrieve message spec for " + fieldTypeName)
}
if fieldTypeName != ".google.protobuf.Timestamp" && !messageHasEnumsOrDates(nestedMessageSpec) {
if fieldTypeName != ".google.protobuf.Timestamp" && !r.messageHasEnumsOrDates(nestedMessageSpec) {
return
}

Expand Down Expand Up @@ -260,7 +255,7 @@ func (r *Runner) generatePatchedInterfaceField(
}
}

r.P(generatedFileStream, fieldSpec.GetJsonName()+separator+value)
r.P(generatedFileStream, fieldSpec.GetJsonName()+": "+value)
}

func (r *Runner) generateUnpatchedInterfaceField(
Expand All @@ -275,18 +270,13 @@ func (r *Runner) generateUnpatchedInterfaceField(
}

fieldOptions := fieldSpec.GetOptions()
separator := "?: "
requiredField, foundRequired := join_proto.GetBooleanCustomFieldOption("typescript_required", fieldOptions, r.extensionTypes)
optionalField, foundOptional := join_proto.GetBooleanCustomFieldOption("typescript_optional", fieldOptions, r.extensionTypes)
if foundRequired && requiredField && foundOptional && optionalField {
utils.LogError("incompatible options for field " + fieldSpec.GetName() + " in " + messageSpec.GetName())
}
confirmRequired := false
if requiredFields && !(foundOptional && optionalField) || foundRequired && requiredField {
confirmRequired = true
separator = ": "
}

confirmRequired := requiredFields && !(foundOptional && optionalField) || foundRequired && requiredField
isRepeated := fieldSpec.GetLabel() == descriptorpb.FieldDescriptorProto_LABEL_REPEATED

value := "value." + fieldSpec.GetJsonName()
Expand All @@ -312,7 +302,7 @@ func (r *Runner) generateUnpatchedInterfaceField(
if !ok || nestedMessageSpec == nil {
utils.LogError("Unable to retrieve message spec for " + fieldTypeName)
}
if fieldTypeName != ".google.protobuf.Timestamp" && !messageHasEnumsOrDates(nestedMessageSpec) {
if fieldTypeName != ".google.protobuf.Timestamp" && !r.messageHasEnumsOrDates(nestedMessageSpec) {
return
}

Expand All @@ -328,7 +318,7 @@ func (r *Runner) generateUnpatchedInterfaceField(
if isRepeated {
value += ".map((o) => " + className + ".fromInterface(o)),"
} else {
value = className + ".fromInterface(" + value + ")),"
value = className + ".fromInterface(" + value + "),"
}
}
} else {
Expand All @@ -348,25 +338,28 @@ func (r *Runner) generateUnpatchedInterfaceField(
}
}

r.P(generatedFileStream, fieldSpec.GetJsonName()+separator+value)
r.P(generatedFileStream, fieldSpec.GetJsonName()+": "+value)
}

func messageHasEnumsOrDates(messageSpec *descriptorpb.DescriptorProto) bool {
func (r *Runner) messageHasEnumsOrDates(messageSpec *descriptorpb.DescriptorProto) bool {
for _, fieldSpec := range messageSpec.GetField() {
switch t := fieldSpec.GetType(); t {
case descriptorpb.FieldDescriptorProto_TYPE_ENUM:
return true
case descriptorpb.FieldDescriptorProto_TYPE_MESSAGE:
if fieldSpec.GetTypeName() == ".google.protobuf.Timestamp" {
fieldTypeName := fieldSpec.GetTypeName()
if fieldTypeName == ".google.protobuf.Timestamp" {
return true
}
}
}

for _, nestedMessageSpec := range messageSpec.GetNestedType() {
hasEnums := messageHasEnumsOrDates(nestedMessageSpec)
if hasEnums {
return true
nestedMessageSpec, ok := r.messageSpecsByFQN[fieldTypeName]
if !ok || nestedMessageSpec == nil {
utils.LogError("Unable to retrieve message spec for " + fieldTypeName)
}

if r.messageHasEnumsOrDates(nestedMessageSpec) {
return true
}
}
}

Expand Down
22 changes: 17 additions & 5 deletions tests/__tests__/v2/generated/Test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { GoogleProtobuf } from './google/protobuf/Timestamp'
import { Common as Common_Common } from './common/Common'
import { Common as Common_Extra } from './common/Extra'

// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace Foo {
interface ConvertibleTo<T> {
asInterface(): T
Expand Down Expand Up @@ -332,7 +333,7 @@ export namespace Foo {
@protobufjs.Type.d('CustomOptionsTest')
export class CustomOptionsTest
extends protobufjs.Message<CustomOptionsTest>
implements ConvertibleTo<ICustomOptionsTest>, ICustomOptionsTest {
implements ConvertibleTo<ICustomOptionsTest> {
@protobufjs.Field.d(1, Common_Extra.ExtraPkgMessage)
public requiredField!: Common_Extra.ExtraPkgMessage

Expand All @@ -343,24 +344,35 @@ export namespace Foo {
public customOptionalField?: number

public asInterface(): ICustomOptionsTest {
return this
return {
...this,
requiredField: this.requiredField.asInterface(),
}
}

public static fromInterface(value: ICustomOptionsTest): CustomOptionsTest {
return CustomOptionsTest.fromObject(value)
const patchedValue = {
...value,
requiredField: Common_Extra.ExtraPkgMessage.fromInterface(
value.requiredField
),
}

return CustomOptionsTest.fromObject(patchedValue)
}

public static decodePatched(
reader: protobufjs.Reader | Uint8Array
): ICustomOptionsTest {
return CustomOptionsTest.decode(reader)
return CustomOptionsTest.decode(reader).asInterface()
}

public static encodePatched(
message: ICustomOptionsTest,
writer?: protobufjs.Writer
): protobufjs.Writer {
return CustomOptionsTest.encode(message, writer)
const transformedMessage = CustomOptionsTest.fromInterface(message)
return CustomOptionsTest.encode(transformedMessage, writer)
}
}

Expand Down
1 change: 1 addition & 0 deletions tests/__tests__/v2/generated/common/Common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
// import * as nodeTrace from '@join-com/node-trace'
import * as protobufjs from 'protobufjs/light'

// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace Common {
interface ConvertibleTo<T> {
asInterface(): T
Expand Down
42 changes: 35 additions & 7 deletions tests/__tests__/v2/generated/common/Extra.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,45 +4,73 @@
// import * as nodeTrace from '@join-com/node-trace'
import * as protobufjs from 'protobufjs/light'

import { GoogleProtobuf } from '../google/protobuf/Timestamp'

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

export interface IExtraPkgMessage {
firstName?: string
latsName?: string
lastName?: string
birthDate?: Date
}

@protobufjs.Type.d('ExtraPkgMessage')
export class ExtraPkgMessage
extends protobufjs.Message<ExtraPkgMessage>
implements ConvertibleTo<IExtraPkgMessage>, IExtraPkgMessage {
implements ConvertibleTo<IExtraPkgMessage> {
@protobufjs.Field.d(1, 'string')
public firstName?: string

@protobufjs.Field.d(2, 'string')
public latsName?: string
public lastName?: string

@protobufjs.Field.d(3, GoogleProtobuf.Timestamp)
public birthDate?: GoogleProtobuf.Timestamp

public asInterface(): IExtraPkgMessage {
return this
return {
...this,
birthDate:
this.birthDate != null
? new Date(
(this.birthDate.seconds ?? 0) * 1000 +
(this.birthDate.nanos ?? 0) / 1000000
)
: undefined,
}
}

public static fromInterface(value: IExtraPkgMessage): ExtraPkgMessage {
return ExtraPkgMessage.fromObject(value)
const patchedValue = {
...value,
birthDate:
value.birthDate != null
? GoogleProtobuf.Timestamp.fromInterface({
seconds: Math.floor(value.birthDate.getTime() / 1000),
nanos: value.birthDate.getMilliseconds() * 1000000,
})
: undefined,
}

return ExtraPkgMessage.fromObject(patchedValue)
}

public static decodePatched(
reader: protobufjs.Reader | Uint8Array
): IExtraPkgMessage {
return ExtraPkgMessage.decode(reader)
return ExtraPkgMessage.decode(reader).asInterface()
}

public static encodePatched(
message: IExtraPkgMessage,
writer?: protobufjs.Writer
): protobufjs.Writer {
return ExtraPkgMessage.encode(message, writer)
const transformedMessage = ExtraPkgMessage.fromInterface(message)
return ExtraPkgMessage.encode(transformedMessage, writer)
}
}
}
1 change: 1 addition & 0 deletions tests/__tests__/v2/generated/google/protobuf/Timestamp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
// import * as nodeTrace from '@join-com/node-trace'
import * as protobufjs from 'protobufjs/light'

// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace GoogleProtobuf {
interface ConvertibleTo<T> {
asInterface(): T
Expand Down
2 changes: 1 addition & 1 deletion tests/__tests__/v2/proto/common/common.proto
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
syntax = "proto3";

// "Dummy" option to make protoc happy (useful for Golang code generation, not for TS)
// Makes protoc happy (useful for Golang code generation, not for TS)
option go_package = "github.com/join-com/protoc-gen-ts/foo/common";

package common;
Expand Down
9 changes: 7 additions & 2 deletions tests/__tests__/v2/proto/common/extra.proto
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
syntax = "proto3";

// "Dummy" option to make protoc happy (useful for Golang code generation, not for TS)
// Makes protoc happy (useful for Golang code generation, not for TS)
option go_package = "github.com/join-com/protoc-gen-ts/foo/common";

package common;

import "google/protobuf/timestamp.proto";

message ExtraPkgMessage {
string first_name = 1[deprecated=true];
string lats_name = 2;
string last_name = 2;

// We add this field to test imports from packages not nested in this same directory
google.protobuf.Timestamp birth_date = 3;
}
Loading

0 comments on commit 43da5a7

Please sign in to comment.