Skip to content

Commit

Permalink
feat: use early overrides to allow converting unsupported types. (#1209)
Browse files Browse the repository at this point in the history
  • Loading branch information
smatric committed Jun 17, 2022
1 parent a780e45 commit 0e2ec6c
Show file tree
Hide file tree
Showing 8 changed files with 290 additions and 1 deletion.
2 changes: 2 additions & 0 deletions example/override/.swaggo
@@ -0,0 +1,2 @@
replace sql.NullString string
replace sql.NullInt64 int64
89 changes: 89 additions & 0 deletions example/override/docs/docs.go
@@ -0,0 +1,89 @@
// Package docs GENERATED BY SWAG; DO NOT EDIT
// This file was generated by swaggo/swag
package docs

import "github.com/swaggo/swag"

const docTemplate = `{
"schemes": {{ marshal .Schemes }},
"swagger": "2.0",
"info": {
"description": "{{escape .Description}}",
"title": "{{.Title}}",
"termsOfService": "http://swagger.io/terms/",
"contact": {
"name": "API Support",
"url": "http://www.swagger.io/support",
"email": "support@swagger.io"
},
"license": {
"name": "Apache 2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
},
"version": "{{.Version}}"
},
"host": "{{.Host}}",
"basePath": "{{.BasePath}}",
"paths": {
"/testapi/update-product/{product_id}": {
"post": {
"consumes": [
"application/json"
],
"summary": "Update product attributes",
"operationId": "update-product",
"parameters": [
{
"type": "integer",
"description": "Product ID",
"name": "product_id",
"in": "path",
"required": true
},
{
"description": " ",
"name": "_",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/main.ProductUpdates"
}
}
],
"responses": {}
}
}
},
"definitions": {
"main.ProductUpdates": {
"type": "object",
"properties": {
"description": {
"type": "string"
},
"stock": {
"type": "integer"
},
"type": {
"type": "string"
}
}
}
}
}`

// SwaggerInfo holds exported Swagger Info so clients can modify it
var SwaggerInfo = &swag.Spec{
Version: "1.0",
Host: "product_info.swagger.io",
BasePath: "/v2",
Schemes: []string{},
Title: "Swagger Example API",
Description: "This is a sample server for updating product information.",
InfoInstanceName: "swagger",
SwaggerTemplate: docTemplate,
}

func init() {
swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
}
66 changes: 66 additions & 0 deletions example/override/docs/swagger.json
@@ -0,0 +1,66 @@
{
"swagger": "2.0",
"info": {
"description": "This is a sample server for updating product information.",
"title": "Swagger Example API",
"termsOfService": "http://swagger.io/terms/",
"contact": {
"name": "API Support",
"url": "http://www.swagger.io/support",
"email": "support@swagger.io"
},
"license": {
"name": "Apache 2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
},
"version": "1.0"
},
"host": "product_info.swagger.io",
"basePath": "/v2",
"paths": {
"/testapi/update-product/{product_id}": {
"post": {
"consumes": [
"application/json"
],
"summary": "Update product attributes",
"operationId": "update-product",
"parameters": [
{
"type": "integer",
"description": "Product ID",
"name": "product_id",
"in": "path",
"required": true
},
{
"description": " ",
"name": "_",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/main.ProductUpdates"
}
}
],
"responses": {}
}
}
},
"definitions": {
"main.ProductUpdates": {
"type": "object",
"properties": {
"description": {
"type": "string"
},
"stock": {
"type": "integer"
},
"type": {
"type": "string"
}
}
}
}
}
45 changes: 45 additions & 0 deletions example/override/docs/swagger.yaml
@@ -0,0 +1,45 @@
basePath: /v2
definitions:
main.ProductUpdates:
properties:
description:
type: string
stock:
type: integer
type:
type: string
type: object
host: product_info.swagger.io
info:
contact:
email: support@swagger.io
name: API Support
url: http://www.swagger.io/support
description: This is a sample server for updating product information.
license:
name: Apache 2.0
url: http://www.apache.org/licenses/LICENSE-2.0.html
termsOfService: http://swagger.io/terms/
title: Swagger Example API
version: "1.0"
paths:
/testapi/update-product/{product_id}:
post:
consumes:
- application/json
operationId: update-product
parameters:
- description: Product ID
in: path
name: product_id
required: true
type: integer
- description: ' '
in: body
name: _
required: true
schema:
$ref: '#/definitions/main.ProductUpdates'
responses: {}
summary: Update product attributes
swagger: "2.0"
30 changes: 30 additions & 0 deletions example/override/handler.go
@@ -0,0 +1,30 @@
package main

import (
"database/sql"
"encoding/json"
"net/http"
)

type ProductUpdates struct {
Type sql.NullString `json:"type"`
Description sql.NullString `json:"description"`
Stock sql.NullInt64 `json:"stock"`
}

// UpdateProduct example
// @Summary Update product attributes
// @ID update-product
// @Accept json
// @Param product_id path int true "Product ID"
// @Param _ body ProductUpdates true " "
// @Router /testapi/update-product/{product_id} [post]
func UpdateProduct(w http.ResponseWriter, r *http.Request) {
var pUpdates ProductUpdates
if err := json.NewDecoder(r.Body).Decode(&pUpdates); err != nil {
// write your code
return
}

// write your code
}
24 changes: 24 additions & 0 deletions example/override/main.go
@@ -0,0 +1,24 @@
package main

import (
"net/http"
)

// @title Swagger Example API
// @version 1.0
// @description This is a sample server with null types overridden with primitive types.
// @termsOfService http://swagger.io/terms/

// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io

// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html

// @host product_info.swagger.io
// @BasePath /v2
func main() {
http.HandleFunc("/testapi/update-product", UpdateProduct)
http.ListenAndServe(":8080", nil)
}
5 changes: 5 additions & 0 deletions parser.go
Expand Up @@ -871,6 +871,11 @@ func convertFromSpecificToPrimitive(typeName string) (string, error) {
}

func (parser *Parser) getTypeSchema(typeName string, file *ast.File, ref bool) (*spec.Schema, error) {
if override, ok := parser.Overrides[typeName]; ok {
parser.debug.Printf("Override detected for %s: using %s instead", typeName, override)
typeName = override
}

if IsInterfaceLike(typeName) {
return &spec.Schema{}, nil
}
Expand Down
30 changes: 29 additions & 1 deletion parser_test.go
Expand Up @@ -78,6 +78,34 @@ func TestSetOverrides(t *testing.T) {
assert.Equal(t, overrides, p.Overrides)
}

func TestOverrides_getTypeSchema(t *testing.T) {
t.Parallel()

overrides := map[string]string{
"sql.NullString": "string",
}

p := New(SetOverrides(overrides))

t.Run("Override sql.NullString by string", func(t *testing.T) {
t.Parallel()

s, err := p.getTypeSchema("sql.NullString", nil, false)
if assert.NoError(t, err) {
assert.Truef(t, s.Type.Contains("string"), "type sql.NullString should be overridden by string")
}
})

t.Run("Missing Override for sql.NullInt64", func(t *testing.T) {
t.Parallel()

_, err := p.getTypeSchema("sql.NullInt64", nil, false)
if assert.Error(t, err) {
assert.Equal(t, "cannot find type definition: sql.NullInt64", err.Error())
}
})
}

func TestParser_ParseDefinition(t *testing.T) {
p := New()

Expand Down Expand Up @@ -479,7 +507,7 @@ func TestParser_ParseProduceComment(t *testing.T) {
assert.Equal(t, parser.swagger.Produces, expected)
}

func TestParser_ParseGeneralAPIInfoCollectionFromat(t *testing.T) {
func TestParser_ParseGeneralAPIInfoCollectionFormat(t *testing.T) {
t.Parallel()

parser := New()
Expand Down

0 comments on commit 0e2ec6c

Please sign in to comment.