swaggo / swag

Automatically generate RESTful API documentation with Swagger 2.0 for Go.
MIT License
10.71k stars 1.2k forks source link

Model composition in response not working for slices with embedded structs #1294

Open blennster opened 2 years ago

blennster commented 2 years ago

Describe the bug When using the model composition in response feature it does not work when trying to assign an embedded struct as type for a slice. I am using this feature as composition for post and put operations and can provide some code examples from original project if needed. I have also prepared a minimal repro at blennster/swap-repro. In the repro I demonstrate that it is only embedded structs this is a problem for.

To Reproduce Download repro at blennster/swag-repro and see generated docs for /ex1 and /ex2

Expected behavior The embedded struct should be used as type but it is not.

Screenshots If applicable, add screenshots to help explain your problem.

Your swag version v1.8.4

Your go version 1.19

Desktop (please complete the following information):

sdghchj commented 2 years ago

Sorry, I haven't got your point. What's your expected result according to your repro?

blennster commented 2 years ago

The swagger ui says that /ex1 response is

{
  "field3": [
    {
      "field1": "string",
      "field2": "string"
    }
  ]
}

but what I expect it to be is

{
  "field3": [
    {
      "field1": "string"
    }
  ]
}

and I added /ex2 to show that it works correctly with primitive types.

Wierd thing is that the generated swagger.yaml look correct (based on how /ex2 looks) so the issue seems to be somewhere else than the doc generator.

felipe-colussi commented 2 years ago

I'm having the same problem here, i do have the following structs:

type OverrideDespesaParcelamentoRequest struct {
    controllerFinanceiro.DespesaParcelamentoRequestArray
    ChavesDeAcesso []string `json:"chavesDeAcesso"` // Devem ser informadas as chaves do monitor fiscal utilizadas.
} // @name OverrideDespesaInserirRequest

// on controllerFinanceiro package: 

type DespesaParcelamentoRequestArray struct {
    Data []DespesaParcelamentoRequest `json:"data" validate:"required"` // array com as despesas para parcelamento
} // @name DespesaParcelamentoRequestArray

type DespesaParcelamentoRequest struct {
    Descricao       string    `json:"descricao"         example:"Serviço de carpintaria"   validate:"required"` // Descrição
    CodigoCategoria int       `json:"codigo_categoria"  example:"658"                      validate:"required"` // Categoria
    CodigoConta     int       `json:"codigo_conta"      example:"345"                      validate:"required"` // Conta
    Valor           float64   `json:"valor"             example:"54.12"                    validate:"required"` // Valor
    DataCompetencia time.Time `json:"data_competencia"  example:"2021-04-20T00:00:00.000Z" validate:"required"` // Data de Competência
    DataVencimento  time.Time `json:"data_vencimento"   example:"2021-04-22T00:00:00.000Z" validate:"required"` // Data de Vencimento
    // Forma de Pagamento
    // * 1 Dinheiro
    // * 2 Cheque
    // * 3 Cartão de Débito
    // * 4 Cartão de Crédito
    // * 5 Boleto/Outros
    // * 6 Pix
    FormaPgto        int                     `json:"forma_pgto" example:"1" validate:"required"`
    Pago             bool                    `json:"pago"              example:"true"`                     // Identifica se a despesa está quitada
    DataPagamento    *time.Time              `json:"data_pagamento"    example:"2021-04-21T00:00:00.000Z"` // Data de Pagamento (somente se está quitada)
    Desconto         *float64                `json:"desconto"          example:"10.10"`                    // Valor do Desconto (somente se está quitada)
    Acrescimo        *float64                `json:"acrescimo"         example:"0.00"`                     // Valor do Acréscimo (somente se está quitada)
    ValorPago        *float64                `json:"valor_pago"        example:"44.12"`                    // Valor Pago (somente se está quitada)
    CodigoFornecedor *int                    `json:"codigo_fornecedor" example:"411"`                      // Fornecedor
    Observacoes      *string                 `json:"observacoes"       example:"Fornecedor levou recibo"`  // Observações adicionais
    Anexos           []models.AnexoPersistir `json:"anexos"`                                               // Anexos da Despesa
    //  Tipo de Documento
    // * 1  Nota Fiscal
    // * 2  Fatura
    // * 3  Recibo
    // * 4  Contrato
    // * 5  Folha de Pagamento
    // * 6  Outros
    TipoDocumento     *int    `json:"tipo_documento"         example:"1"`
    Documento         *string `json:"documento"              example:"1236"` // Documento
    CodigoPropriedade *int    `json:"codigo_propriedade"     example:"1"`    // Propriedade
    //  Tipo de Lançamento
    // * 1  Receita de Atividade Rural
    // * 2  Despesas de Custeio e Investimento
    // * 3  Receitas de produtos entregues no ano referente a adiantamento de recursos financeiros
    TipoLancamento       *int `json:"tipo_lancamento" example:"2"`
    CodigoNotaFiscalAgro *int `json:"codigo_nota_fiscal_agro" example:"2"` // Código da Nota Fiscal do Agronota
    CodigoMonitorAgro    *int `json:"codigo_monitor_agro" example:"2"`     // Código das Notas do Monitor do Agronota
} // @name DespesaParcelamentoRequest

The generated exemple is:

{
  "acrescimo": 0,
  "anexos": [
    {
      "codigo": 455,
      "conteudo": "data:image/png;base64,JVBERi0xLjYKJfbk/N8KMSAwIG9iago8PAovVHlw...",
      "descricao": "comprovante.png"
    }
  ],
  "chavesDeAcesso": [
    "string"
  ],
  "codigo_categoria": 658,
  "codigo_conta": 345,
  "codigo_fornecedor": 411,
  "codigo_monitor_agro": 2,
  "codigo_nota_fiscal_agro": 2,
  "codigo_propriedade": 1,
  "codigo_venda": 7,
  "data_competencia": "2021-04-20T00:00:00.000Z",
  "data_pagamento": "2021-04-21T00:00:00.000Z",
  "data_vencimento": "2021-04-22T00:00:00.000Z",
  "desconto": 10.1,
  "descricao": "Serviço de carpintaria",
  "documento": "1236",
  "forma_pgto": 1,
  "frequencia": 2,
  "observacoes": "Fornecedor levou recibo",
  "ocorrencia": 4,
  "pago": true,
  "repetir": true,
  "tipo_documento": 1,
  "tipo_lancamento": 2,
  "valor": 54.12,
  "valor_pago": 44.12
}

The expected exemple is :

{
    "data": [
        {
            "acrescimo": 0,
            "anexos": [
                {
                    "codigo": 455,
                    "conteudo": "data:image/png;base64,JVBERi0xLjYKJfbk/N8KMSAwIG9iago8PAovVHlw...",
                    "descricao": "comprovante.png"
                }
            ],
            "chavesDeAcesso": [
                "string"
            ],
            "codigo_categoria": 658,
            "codigo_conta": 345,
            "codigo_fornecedor": 411,
            "codigo_monitor_agro": 2,
            "codigo_nota_fiscal_agro": 2,
            "codigo_propriedade": 1,
            "codigo_venda": 7,
            "data_competencia": "2021-04-20T00:00:00.000Z",
            "data_pagamento": "2021-04-21T00:00:00.000Z",
            "data_vencimento": "2021-04-22T00:00:00.000Z",
            "desconto": 10.1,
            "descricao": "Serviço de carpintaria",
            "documento": "1236",
            "forma_pgto": 1,
            "frequencia": 2,
            "observacoes": "Fornecedor levou recibo",
            "ocorrencia": 4,
            "pago": true,
            "repetir": true,
            "tipo_documento": 1,
            "tipo_lancamento": 2,
            "valor": 54.12,
            "valor_pago": 44.12
        }
    ],
    "chavesDeAcesso": [
        "string"
    ]
}
sdghchj commented 2 years ago

The swagger ui says that /ex1 response is

{
  "field3": [
    {
      "field1": "string",
      "field2": "string"
    }
  ]
}

but what I expect it to be is

{
  "field3": [
    {
      "field1": "string"
    }
  ]
}

and I added /ex2 to show that it works correctly with primitive types.

Wierd thing is that the generated swagger.yaml look correct (based on how /ex2 looks) so the issue seems to be somewhere else than the doc generator.

I see。 In the past, generics had not been supported, so we alternatively used a struct field of interface{} type and implemented model composition according to "allof" in the swagger 2.0 specification.

I tested it and seems that if interface{} or any other primitive type is used, allof will behave like field overriding, or it behaves as fields/sub-fields combination.

So your struct3 is supposed to be:

type Struct3 struct {
    Field3 interface{}  `json:"field3"`
}