Skip to content

Commit

Permalink
Merge pull request #2 from swagger-api/master
Browse files Browse the repository at this point in the history
Update fork
crazyk2 authored Oct 29, 2019

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
2 parents f4fb131 + ed4cf61 commit 7a128bc
Showing 25 changed files with 1,128 additions and 7 deletions.
6 changes: 3 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -12,7 +12,7 @@

<groupId>io.swagger.codegen.v3</groupId>
<artifactId>swagger-codegen-generators</artifactId>
<version>1.0.13-SNAPSHOT</version>
<version>1.0.14-SNAPSHOT</version>
<packaging>jar</packaging>

<build>
@@ -246,8 +246,8 @@
</dependency>
</dependencies>
<properties>
<swagger-codegen-version>3.0.12</swagger-codegen-version>
<swagger-parser-version>2.0.15</swagger-parser-version>
<swagger-codegen-version>3.0.14-SNAPSHOT</swagger-codegen-version>
<swagger-parser-version>2.0.16-SNAPSHOT</swagger-parser-version>
<swagger-core-version>2.0.10</swagger-core-version>
<jackson-version>2.9.10</jackson-version>
<scala-version>2.11.1</scala-version>
Original file line number Diff line number Diff line change
@@ -169,6 +169,7 @@ public abstract class DefaultCodegenConfig implements CodegenConfig {

protected String ignoreFilePathOverride;
protected boolean useOas2 = false;
protected boolean copyFistAllOfProperties = false;

public List<CliOption> cliOptions() {
return cliOptions;
@@ -1353,7 +1354,10 @@ else if (schema instanceof ComposedSchema) {
if (codegenModel.interfaces == null) {
codegenModel.interfaces = new ArrayList<String>();
}
for (int i = 1; i < allOf.size(); i++) {
for (int i = 0; i < allOf.size(); i++) {
if (i == 0 && !copyFistAllOfProperties) {
continue;
}
Schema interfaceSchema = allOf.get(i);
if (StringUtils.isBlank(interfaceSchema.get$ref())) {
continue;
@@ -2599,7 +2603,7 @@ public CodegenParameter fromRequestBody(RequestBody body, String name, Schema sc
schema.setName(name);
codegenModel = fromModel(name, schema, schemas);
}
if (codegenModel != null && !codegenModel.emptyVars) {
if (codegenModel != null) {
codegenParameter.baseType = codegenModel.classname;
codegenParameter.dataType = getTypeDeclaration(codegenModel.classname);
imports.add(codegenParameter.dataType);

Large diffs are not rendered by default.

163 changes: 163 additions & 0 deletions src/main/java/io/swagger/codegen/v3/generators/go/GoServerCodegen.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package io.swagger.codegen.v3.generators.go;

import io.swagger.codegen.v3.CodegenConstants;
import io.swagger.codegen.v3.CodegenProperty;
import io.swagger.codegen.v3.CodegenType;
import io.swagger.codegen.v3.SupportingFile;
import io.swagger.v3.oas.models.media.ArraySchema;
import io.swagger.v3.oas.models.media.MapSchema;
import io.swagger.v3.oas.models.media.Schema;

import java.io.File;
import java.util.Arrays;

import org.apache.commons.lang3.StringUtils;

public class GoServerCodegen extends AbstractGoCodegen {

protected String apiVersion = "1.0.0";
protected int serverPort = 8080;
protected String projectName = "swagger-server";
protected String apiPath = "go";

public GoServerCodegen() {
super();

// set the output folder here
outputFolder = "generated-code/go";

/*
* Models. You can write model files using the modelTemplateFiles map.
* if you want to create one template for file, you can do so here.
* for multiple files for model, just put another entry in the `modelTemplateFiles` with
* a different extension
*/
modelTemplateFiles.put(
"model.mustache",
".go");

/*
* Api classes. You can write classes for each Api file with the apiTemplateFiles map.
* as with models, add multiple entries with different extensions for multiple files per
* class
*/
apiTemplateFiles.put(
"controller-api.mustache", // the template to use
".go"); // the extension for each file to write

/*
* Reserved words. Override this with reserved words specific to your language
*/
setReservedWordsLowerCase(
Arrays.asList(
// data type
"string", "bool", "uint", "uint8", "uint16", "uint32", "uint64",
"int", "int8", "int16", "int32", "int64", "float32", "float64",
"complex64", "complex128", "rune", "byte", "uintptr",

"break", "default", "func", "interface", "select",
"case", "defer", "go", "map", "struct",
"chan", "else", "goto", "package", "switch",
"const", "fallthrough", "if", "range", "type",
"continue", "for", "import", "return", "var", "error", "nil")
// Added "error" as it's used so frequently that it may as well be a keyword
);
}

@Override
public String getDefaultTemplateDir() {
return "go-server";
}

@Override
public void processOpts() {
super.processOpts();

if (StringUtils.isBlank(templateDir)) {
embeddedTemplateDir = templateDir = getTemplateDir();
}

if (additionalProperties.containsKey(CodegenConstants.PACKAGE_NAME)) {
setPackageName((String) additionalProperties.get(CodegenConstants.PACKAGE_NAME));
}
else {
setPackageName("swagger");
}

/*
* Additional Properties. These values can be passed to the templates and
* are available in models, apis, and supporting files
*/
additionalProperties.put("apiVersion", apiVersion);
additionalProperties.put("serverPort", serverPort);
additionalProperties.put("apiPath", apiPath);
additionalProperties.put(CodegenConstants.PACKAGE_NAME, packageName);

modelPackage = packageName;
apiPackage = packageName;

/*
* Supporting Files. You can write single files for the generator with the
* entire object tree available. If the input file has a suffix of `.mustache
* it will be processed by the template engine. Otherwise, it will be copied
*/
supportingFiles.add(new SupportingFile("swagger.mustache", "api", "swagger.yaml"));
supportingFiles.add(new SupportingFile("main.mustache", "", "main.go"));
supportingFiles.add(new SupportingFile("routers.mustache", apiPath, "routers.go"));
supportingFiles.add(new SupportingFile("logger.mustache", apiPath, "logger.go"));
writeOptional(outputFolder, new SupportingFile("README.mustache", apiPath, "README.md"));
}

@Override
public String apiPackage() {
return apiPath;
}

/**
* Configures the type of generator.
*
* @return the CodegenType for this generator
* @see io.swagger.codegen.CodegenType
*/
@Override
public CodegenType getTag() {
return CodegenType.SERVER;
}

/**
* Configures a friendly name for the generator. This will be used by the generator
* to select the library with the -l flag.
*
* @return the friendly name for the generator
*/
@Override
public String getName() {
return "go-server";
}

/**
* Returns human-friendly help for the generator. Provide the consumer with help
* tips, parameters here
*
* @return A string value for the help message
*/
@Override
public String getHelp() {
return "Generates a Go server library using the swagger-tools project. By default, " +
"it will also generate service classes--which you can disable with the `-Dnoservice` environment variable.";
}

/**
* Location to write api files. You can use the apiPackage() as defined when the class is
* instantiated
*/
@Override
public String apiFileFolder() {
return outputFolder + File.separator + apiPackage().replace('.', File.separatorChar);
}

@Override
public String modelFileFolder() {
return outputFolder + File.separator + apiPackage().replace('.', File.separatorChar);
}
}
Original file line number Diff line number Diff line change
@@ -35,6 +35,7 @@
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Pattern;

import static io.swagger.codegen.v3.CodegenConstants.HAS_ENUMS_EXT_NAME;
@@ -1182,7 +1183,12 @@ private static CodegenModel reconcileInlineEnums(CodegenModel codegenModel, Code
while (iterator.hasNext()) {
CodegenProperty codegenProperty = iterator.next();
isEnum = getBooleanValue(codegenProperty, IS_ENUM_EXT_NAME);
if (isEnum && codegenProperty.equals(parentModelCodegenPropery)) {
// we don't check for the full set of properties as they could be overridden
// e.g. in the child; if we used codegenProperty.equals, the result in this
// case would be `false` resulting on 2 different enums created on parent and
// child classes, used in same method. This means that the child class will use
// the enum defined in the parent, loosing any overridden property
if (isEnum && isSameEnum(codegenProperty, parentModelCodegenPropery)) {
// We found an enum in the child class that is
// a duplicate of the one in the parent, so remove it.
iterator.remove();
@@ -1199,11 +1205,48 @@ private static CodegenModel reconcileInlineEnums(CodegenModel codegenModel, Code
count += 1;
codegenProperty.getVendorExtensions().put(CodegenConstants.HAS_MORE_EXT_NAME, (count < numVars) ? true : false);
}

if (!codegenProperties.isEmpty()) {
codegenModel.getVendorExtensions().put(CodegenConstants.HAS_VARS_EXT_NAME, true);
codegenModel.getVendorExtensions().put(CodegenConstants.HAS_ENUMS_EXT_NAME, false);
} else {
codegenModel.emptyVars = true;
codegenModel.getVendorExtensions().put(CodegenConstants.HAS_VARS_EXT_NAME, false);
codegenModel.getVendorExtensions().put(CodegenConstants.HAS_ENUMS_EXT_NAME, false);
}


codegenModel.vars = codegenProperties;
}
return codegenModel;


}

protected static boolean isSameEnum(CodegenProperty actual, CodegenProperty other) {
if (actual == null && other == null) {
return true;
}
if ((actual.name == null) ? (other.name != null) : !actual.name.equals(other.name)) {
return false;
}
if ((actual.baseName == null) ? (other.baseName != null) : !actual.baseName.equals(other.baseName)) {
return false;
}
if ((actual.datatype == null) ? (other.datatype != null) : !actual.datatype.equals(other.datatype)) {
return false;
}
if ((actual.datatypeWithEnum == null) ? (other.datatypeWithEnum != null) : !actual.datatypeWithEnum.equals(other.datatypeWithEnum)) {
return false;
}
if ((actual.baseType == null) ? (other.baseType != null) : !actual.baseType.equals(other.baseType)) {
return false;
}
if (!Objects.equals(actual.enumName, other.enumName)) {
return false;
}
return true;
}
private static String sanitizePackageName(String packageName) {
packageName = packageName.trim(); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'.
packageName = packageName.replaceAll("[^a-zA-Z0-9_\\.]", "_");
Original file line number Diff line number Diff line change
@@ -157,7 +157,7 @@ public void processOpts() {

if (additionalProperties.containsKey(DATE_LIBRARY)) {
if (additionalProperties.get(DATE_LIBRARY).toString().startsWith("java8")) {
this.setJava8(Boolean.valueOf(additionalProperties.get(JAVA8_MODE).toString()));
this.setJava8(true);
}
}
if (this.java8) {
Original file line number Diff line number Diff line change
@@ -86,6 +86,7 @@ public PythonClientCodegen() {
languageSpecificPrimitives.add("datetime");
languageSpecificPrimitives.add("date");
languageSpecificPrimitives.add("object");
languageSpecificPrimitives.add("binary_type");

instantiationTypes.put("map", "dict");

@@ -236,6 +237,8 @@ public void processOpts() {
modelPackage = packageName + "." + modelPackage;
apiPackage = packageName + "." + apiPackage;

copyFistAllOfProperties = true;

}

private static String dropDots(String str) {
Original file line number Diff line number Diff line change
@@ -70,6 +70,7 @@ public PythonFlaskConnexionCodegen() {
languageSpecificPrimitives.add("object");
languageSpecificPrimitives.add("byte");
languageSpecificPrimitives.add("bytearray");
languageSpecificPrimitives.add("binary_type");

typeMapping.clear();
typeMapping.put("integer", "int");
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
io.swagger.codegen.v3.generators.dotnet.AspNetCoreServerCodegen
io.swagger.codegen.v3.generators.dotnet.CSharpClientCodegen
io.swagger.codegen.v3.generators.dotnet.CsharpDotNet2ClientCodegen
io.swagger.codegen.v3.generators.go.GoServerCodegen
io.swagger.codegen.v3.generators.html.StaticDocCodegen
io.swagger.codegen.v3.generators.html.StaticHtmlCodegen
io.swagger.codegen.v3.generators.html.StaticHtml2Codegen
30 changes: 30 additions & 0 deletions src/main/resources/handlebars/go-server/README.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Go API Server for {{packageName}}

{{#appDescription}}
{{{appDescription}}}
{{/appDescription}}

## Overview
This server was generated by the [swagger-codegen]
(https://github.com/swagger-api/swagger-codegen) project.
By using the [OpenAPI-Spec](https://github.com/OAI/OpenAPI-Specification) from a remote server, you can easily generate a server stub.
-

To see how to make this your own, look here:

[README](https://github.com/swagger-api/swagger-codegen/blob/master/README.md)

- API version: {{appVersion}}{{^hideGenerationTimestamp}}
- Build date: {{generatedDate}}{{/hideGenerationTimestamp}}
{{#infoUrl}}
For more information, please visit [{{{infoUrl}}}]({{{infoUrl}}})
{{/infoUrl}}


### Running the server
To run the server, follow these simple steps:

```
go run main.go
```

12 changes: 12 additions & 0 deletions src/main/resources/handlebars/go-server/controller-api.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{{>partial_header}}
package {{packageName}}

{{#operations}}
import (
"net/http"
){{#operation}}

func {{nickname}}(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)
}{{/operation}}{{/operations}}
24 changes: 24 additions & 0 deletions src/main/resources/handlebars/go-server/logger.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{{>partial_header}}
package {{packageName}}

import (
"log"
"net/http"
"time"
)

func Logger(inner http.Handler, name string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
inner.ServeHTTP(w, r)
log.Printf(
"%s %s %s %s",
r.Method,
r.RequestURI,
name,
time.Since(start),
)
})
}
24 changes: 24 additions & 0 deletions src/main/resources/handlebars/go-server/main.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{{>partial_header}}
package main

import (
"log"
"net/http"

// WARNING!
// Change this to a fully-qualified import path
// once you place this file into your project.
// For example,
//
// sw "github.com/myname/myrepo/{{apiPath}}"
//
sw "./{{apiPath}}"
)

func main() {
log.Printf("Server started")
router := sw.NewRouter()
log.Fatal(http.ListenAndServe(":{{serverPort}}", router))
}
24 changes: 24 additions & 0 deletions src/main/resources/handlebars/go-server/model.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{{>partial_header}}
package {{packageName}}
{{#models}}{{#imports}}
import ({{/imports}}{{#imports}}
"{{import}}"{{/imports}}{{#imports}}
)
{{/imports}}{{#model}}{{#isEnum}}{{#description}}// {{{classname}}} : {{{description}}}{{/description}}
type {{{name}}} {{^format}}{{dataType}}{{/format}}{{#format}}{{{format}}}{{/format}}

// List of {{{name}}}
const (
{{#allowableValues}}
{{#enumVars}}
{{name}} {{{classname}}} = "{{{value}}}"
{{/enumVars}}
{{/allowableValues}}
){{/isEnum}}{{^isEnum}}{{#description}}
// {{{description}}}{{/description}}
type {{classname}} struct {
{{#vars}}{{#description}}
// {{{description}}}{{/description}}
{{name}} {{^isEnum}}{{^isPrimitiveType}}{{^isContainer}}{{^isDateTime}}*{{/isDateTime}}{{/isContainer}}{{/isPrimitiveType}}{{/isEnum}}{{{datatype}}} `json:"{{baseName}}{{^required}},omitempty{{/required}}"`
{{/vars}}
}{{/isEnum}}{{/model}}{{/models}}
17 changes: 17 additions & 0 deletions src/main/resources/handlebars/go-server/partial_header.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
{{#appName}}
* {{{appName}}}
*
{{/appName}}
{{#appDescription}}
* {{{appDescription}}}
*
{{/appDescription}}
{{#version}}
* API version: {{{version}}}
{{/version}}
{{#infoEmail}}
* Contact: {{{infoEmail}}}
{{/infoEmail}}
* Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git)
*/
56 changes: 56 additions & 0 deletions src/main/resources/handlebars/go-server/routers.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
{{>partial_header}}
package {{packageName}}

import (
"fmt"
"net/http"
"strings"

"github.com/gorilla/mux"
)

type Route struct {
Name string
Method string
Pattern string
HandlerFunc http.HandlerFunc
}

type Routes []Route

func NewRouter() *mux.Router {
router := mux.NewRouter().StrictSlash(true)
for _, route := range routes {
var handler http.Handler
handler = route.HandlerFunc
handler = Logger(handler, route.Name)
router.
Methods(route.Method).
Path(route.Pattern).
Name(route.Name).
Handler(handler)
}

return router
}

func Index(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello World!")
}

var routes = Routes{
Route{
"Index",
"GET",
"{{{basePathWithoutHost}}}/",
Index,
},{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}

Route{
"{{operationId}}",
strings.ToUpper("{{httpMethod}}"),
"{{{basePathWithoutHost}}}{{{path}}}",
{{operationId}},
},{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
}
1 change: 1 addition & 0 deletions src/main/resources/handlebars/go-server/swagger.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{{{swagger-yaml}}}
30 changes: 30 additions & 0 deletions src/main/resources/mustache/go-server/README.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Go API Server for {{packageName}}

{{#appDescription}}
{{{appDescription}}}
{{/appDescription}}

## Overview
This server was generated by the [swagger-codegen]
(https://github.com/swagger-api/swagger-codegen) project.
By using the [OpenAPI-Spec](https://github.com/OAI/OpenAPI-Specification) from a remote server, you can easily generate a server stub.
-

To see how to make this your own, look here:

[README](https://github.com/swagger-api/swagger-codegen/blob/master/README.md)

- API version: {{appVersion}}{{^hideGenerationTimestamp}}
- Build date: {{generatedDate}}{{/hideGenerationTimestamp}}
{{#infoUrl}}
For more information, please visit [{{{infoUrl}}}]({{{infoUrl}}})
{{/infoUrl}}


### Running the server
To run the server, follow these simple steps:

```
go run main.go
```

12 changes: 12 additions & 0 deletions src/main/resources/mustache/go-server/controller-api.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{{>partial_header}}
package {{packageName}}

{{#operations}}
import (
"net/http"
){{#operation}}

func {{nickname}}(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)
}{{/operation}}{{/operations}}
24 changes: 24 additions & 0 deletions src/main/resources/mustache/go-server/logger.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{{>partial_header}}
package {{packageName}}

import (
"log"
"net/http"
"time"
)

func Logger(inner http.Handler, name string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
inner.ServeHTTP(w, r)
log.Printf(
"%s %s %s %s",
r.Method,
r.RequestURI,
name,
time.Since(start),
)
})
}
24 changes: 24 additions & 0 deletions src/main/resources/mustache/go-server/main.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{{>partial_header}}
package main

import (
"log"
"net/http"

// WARNING!
// Change this to a fully-qualified import path
// once you place this file into your project.
// For example,
//
// sw "github.com/myname/myrepo/{{apiPath}}"
//
sw "./{{apiPath}}"
)

func main() {
log.Printf("Server started")
router := sw.NewRouter()
log.Fatal(http.ListenAndServe(":{{serverPort}}", router))
}
24 changes: 24 additions & 0 deletions src/main/resources/mustache/go-server/model.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{{>partial_header}}
package {{packageName}}
{{#models}}{{#imports}}
import ({{/imports}}{{#imports}}
"{{import}}"{{/imports}}{{#imports}}
)
{{/imports}}{{#model}}{{#isEnum}}{{#description}}// {{{classname}}} : {{{description}}}{{/description}}
type {{{name}}} {{^format}}{{dataType}}{{/format}}{{#format}}{{{format}}}{{/format}}

// List of {{{name}}}
const (
{{#allowableValues}}
{{#enumVars}}
{{name}} {{{classname}}} = "{{{value}}}"
{{/enumVars}}
{{/allowableValues}}
){{/isEnum}}{{^isEnum}}{{#description}}
// {{{description}}}{{/description}}
type {{classname}} struct {
{{#vars}}{{#description}}
// {{{description}}}{{/description}}
{{name}} {{^isEnum}}{{^isPrimitiveType}}{{^isContainer}}{{^isDateTime}}*{{/isDateTime}}{{/isContainer}}{{/isPrimitiveType}}{{/isEnum}}{{{datatype}}} `json:"{{baseName}}{{^required}},omitempty{{/required}}"`
{{/vars}}
}{{/isEnum}}{{/model}}{{/models}}
17 changes: 17 additions & 0 deletions src/main/resources/mustache/go-server/partial_header.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
{{#appName}}
* {{{appName}}}
*
{{/appName}}
{{#appDescription}}
* {{{appDescription}}}
*
{{/appDescription}}
{{#version}}
* API version: {{{version}}}
{{/version}}
{{#infoEmail}}
* Contact: {{{infoEmail}}}
{{/infoEmail}}
* Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git)
*/
56 changes: 56 additions & 0 deletions src/main/resources/mustache/go-server/routers.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
{{>partial_header}}
package {{packageName}}

import (
"fmt"
"net/http"
"strings"

"github.com/gorilla/mux"
)

type Route struct {
Name string
Method string
Pattern string
HandlerFunc http.HandlerFunc
}

type Routes []Route

func NewRouter() *mux.Router {
router := mux.NewRouter().StrictSlash(true)
for _, route := range routes {
var handler http.Handler
handler = route.HandlerFunc
handler = Logger(handler, route.Name)
router.
Methods(route.Method).
Path(route.Pattern).
Name(route.Name).
Handler(handler)
}

return router
}

func Index(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello World!")
}

var routes = Routes{
Route{
"Index",
"GET",
"{{{basePathWithoutHost}}}/",
Index,
},{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}

Route{
"{{operationId}}",
strings.ToUpper("{{httpMethod}}"),
"{{{basePathWithoutHost}}}{{{path}}}",
{{operationId}},
},{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
}
1 change: 1 addition & 0 deletions src/main/resources/mustache/go-server/swagger.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{{{swagger-yaml}}}

0 comments on commit 7a128bc

Please sign in to comment.