Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: (poc) conversion handler #2

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading