tkrajina / typescriptify-golang-structs

A Golang struct to TypeScript class/interface converter
Apache License 2.0
505 stars 87 forks source link

Possible bug with `.ManageType` #35

Closed shackra closed 3 years ago

shackra commented 3 years ago

so I get'd the new version of this package and generate a few more types for my project, but for some reasons empty interfaces like Time and Decimal (that one was new) were getting generated despite having ts_type set on some fields of some structs and also using the new feature you recently merged and polished.

This is the generated file:

/* Do not change, this code is generated from Golang structs */

import { Decimal } from 'decimal.js'

export interface UpdateInvoicesRequest {
    claves: string[];
    estado: number;
}
export interface InformacionReferencia {
    tipo_doc: number;
    numero: string;
    fecha_emision: Date;
    codigo: number;
    razon: string;
}
export interface OtroCargo {
    tipo_documento: number;
    detalle: string;
    porcentaje: Decimal;
    monto_cargo: Decimal;
}
export interface Exoneracion {
    tipo_documento: number;
    numero_documento: string;
    nombre_institucion: string;
    fecha_emision: Date;
    porcentaje_exoneracion: Decimal;
    monto_exoneracion: Decimal;
}
export interface Impuesto {
    codigo: number;
    codigo_tarifa: number;
    tarifa: Decimal;
    factor_iva?: Decimal;
    monto: Decimal;
    exoneracion?: Exoneracion;
}
export interface CodigoComercial {
    tipo: number;
    codigo: string;
}
export interface LineaDetalle {
    numero_linea: number;
    partida_arancelaria?: string;
    codigos_comerciales: CodigoComercial[];
    codigo_producto: string;
    cantidad: Decimal;
    unidad_medida: string;
    unidad_medida_comercial?: string;
    detalle: string;
    impuesto: Impuesto[];
    impuesto_neto: Decimal;
    precio_unitario: Decimal;
    monto_descuento?: Decimal;
    naturaleza_descuento?: string;
    monto_total: Decimal;
    sub_total: Decimal;
    base_imponible?: Decimal;
    monto_total_linea: Decimal;
}
export interface Receptor {
    nombre: string;
    nombre_comercial: string;
    identificacion_tipo: number;
    identificacion_numero: string;
    identificacion_extranjero: string;
    provincia: number;
    canton: number;
    distrito: number;
    barrio: number;
    otras_senas: string;
    otras_senas_extranjero: string;
    correo_electronico: string;
    telefono_codigo: number;
    telefono_numero: string;
    fax_codigo: number;
    fax_numero: string;
}
export interface InvoiceCreateRequest {
    emisor: string;
    email: string;
    tipo: number;
    codigo_moneda: string;
    tipo_cambio: Decimal;
    actividad_economica: string;
    condicion_venta: number;
    plazo_credito: number;
    caja: number;
    sucursal: number;
    receptor?: Receptor;
    linea_detalle: LineaDetalle[];
    otros_cargos: OtroCargo[];
    informacion_referencia: InformacionReferencia[];
}
export interface UpdatePasswordRequest {
    clave: string;
    token: string;
}
export interface RefreshTokenRequest {
    token: string;
}
export interface CredentialsRequest {
    cuenta: string;
    clave: string;
}
export interface PasswordResetRequest {
    cuenta: string;
}
export interface ForwardInvoiceTo {
    email: string;
}
export interface Consecutivo {
    id: number;
    tipo: number;
    sucursal: number;
    caja: number;
    contador: number;
}
export interface GetCountersResp {
    consecutivos: Consecutivo[];
    cantidad: number;
}
export interface Emisor {
    nombre: string;
    nombre_comercial: string;
    identificacion_tipo: number;
    identificacion_numero: string;
    provincia: number;
    canton: number;
    distrito: number;
    barrio: number;
    otras_senas: string;
    correo_electronico: string;
    telefono_codigo: number;
    telefono_numero: string;
    fax_codigo: number;
    fax_numero: string;
}
export interface SearchIssuersResp {
    issuers: Emisor[];
}
export interface UpdateCounterReq {
    emisor: string;
    tipo: number;
    sucursal: number;
    caja: number;
    top: number;
}
export interface UpdateIssuerInfoReq {
    nombre?: string;
    nombre_comercial?: string;
    id_tipo?: number;
    id_num?: string;
    correo?: string;
    telf_codigo?: number;
    telf_num?: string;
    fax_codigo?: number;
    fax_num?: string;
}
export interface UpdateIssuerLocationReq {
    otras_senas?: string;
    provincia?: number;
    canton?: number;
    distrito?: number;
    barrio?: number;
}
export interface CreateSubscriptionReq {
    identificacion: string;
    identificacion_tipo: number;
    tipo: number;
    nombre: string;
    correo: string;
    primer_usuario_correo: string;
}
export interface SubscriptionIsDueResponse {
    yes: boolean;
}
export interface Decimal { // HERE!!

}
export interface Subscription {
    tipo: number;
    identificacion_tipo: number;
    identificacion_numero: string;
    nombre: string;
    email: string;
    primer_pago?: Date;
    siguiente_pago?: Date;
    fecha_creado: Date;
    fecha_actualizado: Date;
    costo: Decimal;
    adicional: Decimal;
}
export interface ListSubscriptionsResponse {
    suscripciones: Subscription[];
    cantidad: number;
}
export interface UpdateSubscriptionPrice {
    base?: Decimal;
    adicional?: Decimal;
}
export interface UpdateSubscriptionStatus {
    emisores: string[];
    estado: number;
}
export interface Time { // HERE TOO!

}
export interface Token {
    iss?: string;
    sub?: string;
    aud?: string[];
    exp?: Time;
    nbf?: Time;
    iat?: Time;
    jti?: string;
    user: string;
    subcripcion: string;
    rules: {[key: string]: string[]};
}
export interface Resumen {
    total_servicios_gravados: Decimal;
    total_servicios_exentos: Decimal;
    total_servicios_exonerado: Decimal;
    total_mercancias_gravadas: Decimal;
    total_mercancias_exentas: Decimal;
    total_mercancias_exonerada: Decimal;
    total_gravado: Decimal;
    total_exento: Decimal;
    total_exonerado: Decimal;
    total_venta: Decimal;
    total_descuentos: Decimal;
    total_venta_neta: Decimal;
    total_impuesto: Decimal;
    total_iva_devuelto: Decimal;
    total_otros_cargos: Decimal;
    total_comprobante: Decimal;
}
export interface FormaPago {
    nombre: string;
    codigo: number;
}
export interface Documento {
    id: number;
    clave: string;
    consecutivo: string;
    tipo: number;
    caja: number;
    sucursal: number;
    estado: number;
    emisor: Emisor;
    receptor?: Receptor;
    actividad: string;
    fecha_emision: Date;
    condicion_venta: number;
    plazo_credito: number;
    forma_pago: FormaPago[];
    linea_detalle: LineaDetalle[];
    otros_cargos: OtroCargo[];
    informacion_referencia: InformacionReferencia[];
    codigo_moneda: string;
    tipo_cambio: Decimal;
    normativa: string;
    resumenes?: Resumen;
}
export interface Tokens {
    access: string;
    refresh: string;
}

and this is the Golang file:

package main

import (
    "flag"
    "os"
    "path/filepath"
    "time"

    "github.com/sirupsen/logrus"
    "github.com/tkrajina/typescriptify-golang-structs/typescriptify"
    http_transport "gitlab.com/kuecr/fero/backend/monolith/pkg/http"
    "gitlab.com/kuecr/fero/backend/monolith/pkg/json_web_token"
    "gitlab.com/kuecr/fero/backend/monolith/pkg/service"
)

func main() {
    ubicacionPtr := flag.String("salida", "", "archivo ts donde iran los tipos generados")

    flag.Parse()

    archivo, err := filepath.Abs(*ubicacionPtr)
    if err != nil {
        logrus.Panicf("no se pudo obtener ubicacion absoluta: %v", err)
    }

    converter := typescriptify.New()
    // convierte time.Time en Date para Typescript
    converter = converter.ManageType(time.Time{}, typescriptify.TypeOptions{TSType: "Date"})

    converter.Add(http_transport.UpdateInvoicesRequest{})
    converter.Add(http_transport.InvoiceCreateRequest{})
    converter.Add(http_transport.UpdatePasswordRequest{})
    converter.Add(http_transport.RefreshTokenRequest{})
    converter.Add(http_transport.CredentialsRequest{})
    converter.Add(http_transport.PasswordResetRequest{})
    converter.Add(http_transport.ForwardInvoiceTo{})
    converter.Add(http_transport.GetCountersResp{})
    converter.Add(http_transport.SearchIssuersResp{})
    converter.Add(http_transport.UpdateCounterReq{})
    converter.Add(http_transport.UpdateIssuerInfoReq{})
    converter.Add(http_transport.UpdateIssuerLocationReq{})
    converter.Add(http_transport.CreateSubscriptionReq{})
    converter.Add(http_transport.SubscriptionIsDueResponse{})
    converter.Add(http_transport.ListSubscriptionsResponse{})
    converter.Add(http_transport.UpdateSubscriptionPrice{})
    converter.Add(http_transport.UpdateSubscriptionStatus{})
    converter.Add(json_web_token.Token{})
    converter.Add(service.Documento{})
    converter.Add(service.Tokens{})

    converter.AddImport("import { Decimal } from 'decimal.js'")
    converter.BackupDir = ""
    converter.CreateInterface = true

    err = converter.ConvertToFile(archivo)
    if err != nil {
        logrus.Errorf("no se pudo convertir structs a tipos typescript: %v", err)
        os.Exit(1)
    }
}

I want through each struct there and checked that each field with a decimal.Decimal type has its ts_type:"Decimal" tag set and converter = converter.ManageType(time.Time{}, typescriptify.TypeOptions{TSType: "Date"}) seems correct to me, but, yeah, despite that I get empty interfaces (and Decimal as an empty interface is a new error, I've never see that behavior before)

In my fork I tried to reproduce the error with what I thought was the culprid: https://github.com/shackra/typescriptify-golang-structs/blob/weird-bug/typescriptify/typescriptify_test.go#L444

But surprisingly the test runs fine:

➜ go test ./... -run TestAnonymousStructWithManagedType
?       _/home/jorge/code/typescriptify-golang-structs/example  [no test files]
?       _/home/jorge/code/typescriptify-golang-structs/tscriptify   [no test files]
----------------------------------------------------------------------------------------------------

export interface Target {
    my_time: Date;
    price: Decimal;
}
----------------------------------------------------------------------------------------------------
OK:       export interface Target {
OK:           my_time: Date;
OK:           price: Decimal;
OK:       }
tmp ts:  /tmp/728430900.ts
executing: /tmp/728430900.js
--- FAIL: TestAnonymousStructWithManagedType (1.30s)
    typescriptify_test.go:342:
            Error Trace:    typescriptify_test.go:342
                                        typescriptify_test.go:319
                                        typescriptify_test.go:469
            Error:          Expected nil, but got: &exec.ExitError{ProcessState:(*os.ProcessState)(0xc00000e640), Stderr:[]uint8(nil)}
            Test:           TestAnonymousStructWithManagedType
            Messages:       ../../../../../tmp/728430900.ts(3,12): error TS2304: Cannot find name 'Decimal'.
FAIL
FAIL    _/home/jorge/code/typescriptify-golang-structs/typescriptify    1.300s
FAIL
tkrajina commented 3 years ago

I pushed another branch debug-logging. With it you can add some additional debug logging by setting converter.Debug = true. With that, you'll get the fields which cause the converter to create new models, for example:

Converting struct typescriptify.TestCustomType
Struct TestCustomType.Time (time.Time) => convert

So here, the Test field in TestCustomType is causing a new Time class/interface to be created. Then, find which field is causing the empty Time, and try to write a minimal reproducible test with that struct.

Also, add this:

    converter.AddImport("type Decimal = number;") // Not an import, I know :)

in your TestAnonymousStructWithManagedType not make the generated script to compile (and the test to finish without errors).

shackra commented 3 years ago

I use Go modules in my project, to use your branch I need to clone the repository in vendors/ maybe? I'll try to figure that out tonight

tkrajina commented 3 years ago

Yes, you can clone it temporary. But you can also:

go get github.com/tkrajina/typescriptify-golang-structs@ddc6a2f51e3cde9d0c1dae7b9f3e50b433f6e9d3
go mod vendor

ddc6a2f51e3cde9d0c1dae7b9f3e50b433f6e9d3 is the last commit in debug-logging. Or, you can specify a branch:

go get github.com/tkrajina/typescriptify-golang-structs@debug-logging
go mod vendor
shackra commented 3 years ago

I get the following:

Converting struct http_transport.UpdateInvoicesRequest
FromMethod METHOD IS DEPRECATED AND WILL BE REMOVED!!!!!!
Converting struct http_transport.InvoiceCreateRequest
Struct InvoiceCreateRequest.Receptor (service.Receptor) => convert
Converting struct service.Receptor
FromMethod METHOD IS DEPRECATED AND WILL BE REMOVED!!!!!!
Struct InvoiceCreateRequest.LineaDetalle ([]service.LineaDetalle) => convert
Converting struct service.LineaDetalle
Struct LineaDetalle.CodigosComerciales ([]service.CodigoComercial) => convert
Converting struct service.CodigoComercial
FromMethod METHOD IS DEPRECATED AND WILL BE REMOVED!!!!!!
Struct LineaDetalle.Impuesto ([]service.Impuesto) => convert
Converting struct service.Impuesto
Struct Impuesto.Exoneracion (service.Exoneracion) => convert
Converting struct service.Exoneracion
FromMethod METHOD IS DEPRECATED AND WILL BE REMOVED!!!!!!
FromMethod METHOD IS DEPRECATED AND WILL BE REMOVED!!!!!!
FromMethod METHOD IS DEPRECATED AND WILL BE REMOVED!!!!!!
Struct InvoiceCreateRequest.OtrosCargos ([]service.OtroCargo) => convert
Converting struct service.OtroCargo
FromMethod METHOD IS DEPRECATED AND WILL BE REMOVED!!!!!!
Struct InvoiceCreateRequest.InformacionReferencia ([]service.InformacionReferencia) => convert
Converting struct service.InformacionReferencia
FromMethod METHOD IS DEPRECATED AND WILL BE REMOVED!!!!!!
FromMethod METHOD IS DEPRECATED AND WILL BE REMOVED!!!!!!
Converting struct http_transport.UpdatePasswordRequest
FromMethod METHOD IS DEPRECATED AND WILL BE REMOVED!!!!!!
Converting struct http_transport.RefreshTokenRequest
FromMethod METHOD IS DEPRECATED AND WILL BE REMOVED!!!!!!
Converting struct http_transport.CredentialsRequest
FromMethod METHOD IS DEPRECATED AND WILL BE REMOVED!!!!!!
Converting struct http_transport.PasswordResetRequest
FromMethod METHOD IS DEPRECATED AND WILL BE REMOVED!!!!!!
Converting struct http_transport.ForwardInvoiceTo
FromMethod METHOD IS DEPRECATED AND WILL BE REMOVED!!!!!!
Converting struct http_transport.GetCountersResp
Struct GetCountersResp.Consecutivos ([]service.Consecutivo) => convert
Converting struct service.Consecutivo
FromMethod METHOD IS DEPRECATED AND WILL BE REMOVED!!!!!!
FromMethod METHOD IS DEPRECATED AND WILL BE REMOVED!!!!!!
Converting struct http_transport.SearchIssuersResp
Struct SearchIssuersResp.Issuers ([]service.Emisor) => convert
Converting struct service.Emisor
FromMethod METHOD IS DEPRECATED AND WILL BE REMOVED!!!!!!
FromMethod METHOD IS DEPRECATED AND WILL BE REMOVED!!!!!!
Converting struct http_transport.UpdateCounterReq
FromMethod METHOD IS DEPRECATED AND WILL BE REMOVED!!!!!!
Converting struct http_transport.UpdateIssuerInfoReq
FromMethod METHOD IS DEPRECATED AND WILL BE REMOVED!!!!!!
Converting struct http_transport.UpdateIssuerLocationReq
FromMethod METHOD IS DEPRECATED AND WILL BE REMOVED!!!!!!
Converting struct http_transport.CreateSubscriptionReq
FromMethod METHOD IS DEPRECATED AND WILL BE REMOVED!!!!!!
Converting struct http_transport.SubscriptionIsDueResponse
FromMethod METHOD IS DEPRECATED AND WILL BE REMOVED!!!!!!
Converting struct http_transport.ListSubscriptionsResponse
Struct ListSubscriptionsResponse.Suscripciones ([]service.Subscription) => convert
Converting struct service.Subscription
Struct Subscription.Costo (decimal.Decimal) => convert
Converting struct decimal.Decimal
FromMethod METHOD IS DEPRECATED AND WILL BE REMOVED!!!!!!
Struct Subscription.Adicional (decimal.Decimal) => convert
Converting struct decimal.Decimal
FromMethod METHOD IS DEPRECATED AND WILL BE REMOVED!!!!!!
FromMethod METHOD IS DEPRECATED AND WILL BE REMOVED!!!!!!
Converting struct http_transport.UpdateSubscriptionPrice
FromMethod METHOD IS DEPRECATED AND WILL BE REMOVED!!!!!!
Converting struct http_transport.UpdateSubscriptionStatus
FromMethod METHOD IS DEPRECATED AND WILL BE REMOVED!!!!!!
Converting struct json_web_token.Token
Struct Token.ExpirationTime (jwt.Time) => convert
Converting struct jwt.Time
FromMethod METHOD IS DEPRECATED AND WILL BE REMOVED!!!!!!
Struct Token.NotBefore (jwt.Time) => convert
Converting struct jwt.Time
Struct Token.IssuedAt (jwt.Time) => convert
Converting struct jwt.Time
FromMethod METHOD IS DEPRECATED AND WILL BE REMOVED!!!!!!
Converting struct service.Documento
Struct Documento.Emisor (service.Emisor) => convert
Converting struct service.Emisor
Struct Documento.Receptor (service.Receptor) => convert
Converting struct service.Receptor
Struct Documento.FormaPago ([]service.FormaPago) => convert
Converting struct service.FormaPago
FromMethod METHOD IS DEPRECATED AND WILL BE REMOVED!!!!!!
Struct Documento.LineaDetalle ([]service.LineaDetalle) => convert
Converting struct service.LineaDetalle
Struct Documento.OtrosCargos ([]service.OtroCargo) => convert
Converting struct service.OtroCargo
Struct Documento.InformacionReferencia ([]service.InformacionReferencia) => convert
Converting struct service.InformacionReferencia
Struct Documento.Resumenes (service.Resumen) => convert
Converting struct service.Resumen
FromMethod METHOD IS DEPRECATED AND WILL BE REMOVED!!!!!!
FromMethod METHOD IS DEPRECATED AND WILL BE REMOVED!!!!!!
Converting struct service.Tokens
FromMethod METHOD IS DEPRECATED AND WILL BE REMOVED!!!!!!

I think I can see the offender

Converting struct service.Subscription
Struct Subscription.Costo (decimal.Decimal) => convert
Converting struct decimal.Decimal
shackra commented 3 years ago

Indeed, there is no bug, there was another struct called Time that needed to be managed, and I was missing tags in some of my structs too.