swagger-api/swagger-codegen

text/plain results in an undefined response type with Golang Swagger

pen-gocaspi opened this issue · 1 comments

Description

Hello, when im generating Golang code for a simple endpoint that returns plain text the generated client runs in an error:
undefined response type . I added the code snippets below.

Swagger-codegen version

2.0

Swagger declaration file content or url
---
swagger: "2.0"
info:
  description: "something"
  version: "v2.6"
  title: "something"
  contact:
    email: "something@something.de"
host: "something.de"
tags:
- name: "administration"
  description: "Meta services"
schemes:
- "https"
- "http"
consumes:
- "application/json"
produces:
- "application/json"
security:
- basicAuth: []
paths:
  /about/version:
    get:
      tags:
      - "administration"
      summary: "current software version"
      description: "Returns the current something version"
      produces:
      - "text/plain"
      parameters: []
      responses:
        "200":
          description: "successful operation"
          schema:
            type: "string"
            example: "1.9.44-04345"
        "400":
          description: "bad request"
          schema:
            $ref: "#/definitions/ErrorMessage"
        "500":
          description: "internal server error"
          schema:
            $ref: "#/definitions/ErrorMessage"
securityDefinitions:
  basicAuth:
    type: "basic"
definitions:
  ErrorMessage:
    type: "object"
    properties:
      errorMessage:
        type: "string"
Command line used for generation

https://editor.swagger.io/

Steps to reproduce

Generate Go Client with Swagger generator

Related issues/PRs

#7751

Suggest a fix/enhancement

When it now comes to decoding the response of the endpoint the generate Golang code looks like:

func (a *AdministrationApiService) AboutVersionGet(ctx context.Context) (string, *http.Response, error) {
	var (
		localVarHttpMethod  = strings.ToUpper("Get")
		localVarPostBody    interface{}
		localVarFileName    string
		localVarFileBytes   []byte
		localVarReturnValue string
	)

	// create path and map variables
	localVarPath := a.client.cfg.BasePath + "/about/version"

	localVarHeaderParams := make(map[string]string)
	localVarQueryParams := url.Values{}
	localVarFormParams := url.Values{}

	// to determine the Content-Type header
	localVarHttpContentTypes := []string{"application/json"}

	// set Content-Type header
	localVarHttpContentType := selectHeaderContentType(localVarHttpContentTypes)
	if localVarHttpContentType != "" {
		localVarHeaderParams["Content-Type"] = localVarHttpContentType
	}

	// to determine the Accept header
	localVarHttpHeaderAccepts := []string{"text/plain"}

	// set Accept header
	localVarHttpHeaderAccept := selectHeaderAccept(localVarHttpHeaderAccepts)
	if localVarHttpHeaderAccept != "" {
		localVarHeaderParams["Accept"] = localVarHttpHeaderAccept
	}
	r, err := a.client.prepareRequest(ctx, localVarPath, localVarHttpMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, localVarFileName, localVarFileBytes)
	if err != nil {
		return localVarReturnValue, nil, err
	}

	localVarHttpResponse, err := a.client.callAPI(r)
	if err != nil || localVarHttpResponse == nil {
		return localVarReturnValue, localVarHttpResponse, err
	}

	localVarBody, err := ioutil.ReadAll(localVarHttpResponse.Body)
	localVarHttpResponse.Body.Close()
	if err != nil {
		return localVarReturnValue, localVarHttpResponse, err
	}

	if localVarHttpResponse.StatusCode < 300 {
		// If we succeed, return the data, otherwise pass on to decode error.
                //added by issue author: normal cast form byte to string would be enough if Content-Type is plain-text
		err = a.client.decode(&localVarReturnValue, localVarBody, localVarHttpResponse.Header.Get("Content-Type"))
		return localVarReturnValue, localVarHttpResponse, err
	}

	if localVarHttpResponse.StatusCode >= 300 {
		newErr := GenericSwaggerError{
			body:  localVarBody,
			error: localVarHttpResponse.Status,
		}

		if localVarHttpResponse.StatusCode == 200 { //added by issue author: is this even reachable?
			var v string
			err = a.client.decode(&v, localVarBody, localVarHttpResponse.Header.Get("Content-Type"))
			if err != nil {
				newErr.error = err.Error()
				return localVarReturnValue, localVarHttpResponse, newErr
			}
			newErr.model = v
			return localVarReturnValue, localVarHttpResponse, newErr
		}

		if localVarHttpResponse.StatusCode == 400 {
			var v ErrorMessage
			err = a.client.decode(&v, localVarBody, localVarHttpResponse.Header.Get("Content-Type"))
			if err != nil {
				newErr.error = err.Error()
				return localVarReturnValue, localVarHttpResponse, newErr
			}
			newErr.model = v
			return localVarReturnValue, localVarHttpResponse, newErr
		}

		if localVarHttpResponse.StatusCode == 500 {
			var v ErrorMessage
			err = a.client.decode(&v, localVarBody, localVarHttpResponse.Header.Get("Content-Type"))
			if err != nil {
				newErr.error = err.Error()
				return localVarReturnValue, localVarHttpResponse, newErr
			}
			newErr.model = v
			return localVarReturnValue, localVarHttpResponse, newErr
		}

		return localVarReturnValue, localVarHttpResponse, newErr
	}

	return localVarReturnValue, localVarHttpResponse, nil
}

The problem here is that the decode() function only is able to decode xml or JSON data.
What we need is cast from byte to string if the contenttype is text/plain

I had the same issue.

I think we should add the choice of strings.Contains(contentType, "text/plain") to the decode function in client.go that is generated by template file client.mustache.

It should be modified to look like this:

func (c *APIClient) decode(v interface{}, b []byte, contentType string) (err error) {
	if strings.Contains(contentType, "application/xml") {
		if err = xml.Unmarshal(b, v); err != nil {
			return err
		}
		return nil
	} else if strings.Contains(contentType, "application/json") {
		if err = json.Unmarshal(b, v); err != nil {
			return err
		}
		return nil
	} else if strings.Contains(contentType, "text/plain") {
		// I'm not sure that's going to cause panic
		*(v.(*string)) = string(b)
		return nil
	}
	return errors.New("undefined response type")
}

In order to return the message directly when the contentType is text/plain.