Skip to content

Commit

Permalink
feat: (poc) conversion handler
Browse files Browse the repository at this point in the history
Adding conversion handlers with original conversion as the default handler
  • Loading branch information
vincentgna committed Feb 20, 2024
1 parent 2728b08 commit d6f2401
Show file tree
Hide file tree
Showing 5 changed files with 397 additions and 148 deletions.
107 changes: 107 additions & 0 deletions typescriptify/typeconversionhandler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package typescriptify

import (
"reflect"
)

type TypeConversionHandler interface {
HandleTypeConversion(depth int, result string, t *TypeScriptify, builder *TypeScriptClassBuilder, typeOf reflect.Type, customCode map[string]string, field reflect.StructField, fldOpts TypeOptions, jsonFieldName string) (string, error)
}

type DefaultTypeConversionHandler struct {
}

func (handler *DefaultTypeConversionHandler) HandleTypeConversion(depth int, result string, t *TypeScriptify, builder *TypeScriptClassBuilder, typeOf reflect.Type, customCode map[string]string, field reflect.StructField, fldOpts TypeOptions, jsonFieldName string) (string, error) {
var err error

if fldOpts.TSDoc != "" {
builder.AddFieldDefinitionLine("/** " + fldOpts.TSDoc + " */")
}
if fldOpts.TSTransform != "" {
t.Logf(depth, "- simple field %s.%s", typeOf.Name(), field.Name)
err = builder.AddSimpleField(jsonFieldName, field, fldOpts)
} else if t.IsEnum(field) {
t.Logf(depth, "- enum field %s.%s", typeOf.Name(), field.Name)
builder.AddEnumField(jsonFieldName, field)
} else if fldOpts.TSType != "" { // Struct:
t.Logf(depth, "- simple field 1 %s.%s", typeOf.Name(), field.Name)
err = builder.AddSimpleField(jsonFieldName, field, fldOpts)
} else if field.Type.Kind() == reflect.Struct { // Struct:
t.Logf(depth, "- struct %s.%s (%s)", typeOf.Name(), field.Name, field.Type.String())
typeScriptChunk, err := t.ConvertType(depth+1, field.Type, customCode)
if err != nil {
return "", err
}
if typeScriptChunk != "" {
result = typeScriptChunk + "\n" + result
}
builder.AddStructField(jsonFieldName, field)
} else if field.Type.Kind() == reflect.Map {
t.Logf(depth, "- map field %s.%s", typeOf.Name(), field.Name)
// Also convert map key types if needed
var keyTypeToConvert reflect.Type
switch field.Type.Key().Kind() {
case reflect.Struct:
keyTypeToConvert = field.Type.Key()
case reflect.Ptr:
keyTypeToConvert = field.Type.Key().Elem()
}
if keyTypeToConvert != nil {
typeScriptChunk, err := t.ConvertType(depth+1, keyTypeToConvert, customCode)
if err != nil {
return "", err
}
if typeScriptChunk != "" {
result = typeScriptChunk + "\n" + result
}
}
// Also convert map value types if needed
var valueTypeToConvert reflect.Type
switch field.Type.Elem().Kind() {
case reflect.Struct:
valueTypeToConvert = field.Type.Elem()
case reflect.Ptr:
valueTypeToConvert = field.Type.Elem().Elem()
}
if valueTypeToConvert != nil {
typeScriptChunk, err := t.ConvertType(depth+1, valueTypeToConvert, customCode)
if err != nil {
return "", err
}
if typeScriptChunk != "" {
result = typeScriptChunk + "\n" + result
}
}

builder.AddMapField(jsonFieldName, field)
} else if field.Type.Kind() == reflect.Slice || field.Type.Kind() == reflect.Array { // Slice:
if field.Type.Elem().Kind() == reflect.Ptr { //extract ptr type
field.Type = field.Type.Elem()
}

arrayDepth := 1
for field.Type.Elem().Kind() == reflect.Slice { // Slice of slices:
field.Type = field.Type.Elem()
arrayDepth++
}

if field.Type.Elem().Kind() == reflect.Struct { // Slice of structs:
t.Logf(depth, "- struct slice %s.%s (%s)", typeOf.Name(), field.Name, field.Type.String())
typeScriptChunk, err := t.ConvertType(depth+1, field.Type.Elem(), customCode)
if err != nil {
return "", err
}
if typeScriptChunk != "" {
result = typeScriptChunk + "\n" + result
}
builder.AddArrayOfStructsField(jsonFieldName, field, arrayDepth)
} else { // Slice of simple fields:
t.Logf(depth, "- slice field %s.%s", typeOf.Name(), field.Name)
err = builder.AddSimpleArrayField(jsonFieldName, field, arrayDepth, fldOpts)
}
} else { // Simple field:
t.Logf(depth, "- simple field 2 %s.%s", typeOf.Name(), field.Name)
err = builder.AddSimpleField(jsonFieldName, field, fldOpts)
}
return result, err
}
118 changes: 118 additions & 0 deletions typescriptify/typeconversionhandler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package typescriptify

import (
"reflect"
"testing"
"time"
)

type customTypeConversionHandler struct {
}

func (handler *customTypeConversionHandler) HandleTypeConversion(depth int, result string, t *TypeScriptify, builder *TypeScriptClassBuilder, typeOf reflect.Type, customCode map[string]string, field reflect.StructField, fldOpts TypeOptions, jsonFieldName string) (string, error) {
var err error
t.Logf(depth, "CUSTOM HANDLER - %q - %q", typeOf.Name(), field.Name)
timeFieldOptions := TypeOptions{TSType: "Date", TSTransform: "new Date(__VALUE__)"}
if typeOf.Name() == "ConfigFile" {
switch field.Name {
case "OSVersion":
t.Logf(depth, "- (custom) simple field %s.%s", typeOf.Name(), field.Name)
err = builder.AddSimpleField("osVersion?", field, fldOpts)
return result, err
case "OSFeatures":
t.Logf(depth, "- (custom) simple field %s.%s", typeOf.Name(), field.Name)
err = builder.AddSimpleArrayField("osFeatures?", field, 1, fldOpts)
return result, err
case "Created":
t.Logf(depth, "- (custom) simple field %s.%s", typeOf.Name(), field.Name)
err = builder.AddSimpleField(jsonFieldName, field, timeFieldOptions)
return result, err
default:
t.Logf(depth, "- calling default conversion for %s.%s", typeOf.Name(), field.Name)
return t.DefaultTypeConversionHandler.HandleTypeConversion(depth, result, t, builder, typeOf, customCode, field, fldOpts, jsonFieldName)
}
}
if typeOf.Name() == "History" && field.Type.Name() == "Time" {
t.Logf(depth, "- (custom) simple field %s.%s (%s)", typeOf.Name(), field.Name, field.Type.Name())
err = builder.AddSimpleField(jsonFieldName, field, timeFieldOptions)
return result, err
}
return t.DefaultTypeConversionHandler.HandleTypeConversion(depth, result, t, builder, typeOf, customCode, field, fldOpts, jsonFieldName)
}

func TestCustomTypeConversionHandler(t *testing.T) {
t.Parallel()
// Time is a wrapper around time.Time to help with deep copying
type Time struct {
time.Time
}
type History struct {
Author string `json:"author,omitempty"`
Created Time `json:"created,omitempty"`
CreatedBy string `json:"created_by,omitempty"`
Comment string `json:"comment,omitempty"`
EmptyLayer bool `json:"empty_layer,omitempty"`
}
type ConfigFile struct {
Architecture string `json:"architecture"`
Author string `json:"author,omitempty"`
Container string `json:"container,omitempty"`
Created Time `json:"created,omitempty"`
DockerVersion string `json:"docker_version,omitempty"`
History []History `json:"history,omitempty"`
OS string `json:"os"`
OSVersion string `json:"os.version,omitempty"`
Variant string `json:"variant,omitempty"`
OSFeatures []string `json:"os.features,omitempty"`
}
type Metadata struct {
Size int64 `json:",omitempty"`

// Container image
ImageID string `json:",omitempty"`
DiffIDs []string `json:",omitempty"`
RepoTags []string `json:",omitempty"`
RepoDigests []string `json:",omitempty"`
ImageConfig ConfigFile `json:",omitempty"`
}

converter := New().WithCamelCaseFields(true, nil)

converter.ManageTypeConversion(&customTypeConversionHandler{}, reflect.TypeOf(ConfigFile{}))
converter.Add(
NewStruct(History{}).WithTypeHandler(reflect.TypeOf(Time{}), &customTypeConversionHandler{}),
)
converter.AddType(reflect.TypeOf(Metadata{}))
converter.BackupDir = ""
converter.ReadOnlyFields = true
converter.CreateInterface = true

desiredResult := `export interface History {
readonly author?: string;
readonly created?: Date;
readonly created_by?: string;
readonly comment?: string;
readonly empty_layer?: boolean;
}
export interface ConfigFile {
readonly architecture: string;
readonly author?: string;
readonly container?: string;
readonly created?: Date;
readonly docker_version?: string;
readonly history?: History[];
readonly os: string;
readonly osVersion?: string;
readonly variant?: string;
readonly osFeatures?: string[];
}
export interface Metadata {
readonly size?: number;
readonly imageID?: string;
readonly diffIDs?: string[];
readonly repoTags?: string[];
readonly repoDigests?: string[];
readonly imageConfig?: ConfigFile;
}`
testConverter(t, converter, false, desiredResult, nil)
}
Loading

0 comments on commit d6f2401

Please sign in to comment.