Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Stream;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.maven.plugin.MojoExecutionException;
Expand Down Expand Up @@ -506,6 +507,11 @@ public void execute() throws MojoExecutionException, MojoFailureException {
break;

default:
String message = format("Input spec %s matches more than one single file", inputSpec);
getLog().error(message);
Stream.of(files).forEach(f -> {
getLog().error(format(" %s", f));
});
throw new MojoExecutionException(
format("Input spec %s matches more than one single file", inputSpec));
}
Expand Down
28 changes: 28 additions & 0 deletions boat-scaffold/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Boat OpenAPI generator

The Boat OpenAPI generator is based on the official Open API Generator, version 4.0.3 and it provides several fixes and additional features.
The `boat` plugin has multiple goals:

## Spring Code Generator

| Option | Default | Description |
|-|-|-|
| `addBindingResult` | `false` | Adds BindingResult to Api method definitions' request bodies if UseBeanValidation true, for this to be effective you must configure UseBeanValidation, this is not done automatically |
| `addServletRequest` | `false` | Adds ServletRequest objects to API method definitions |
| `useClassLevelBeanValidation` | `false` | Adds @Validated annotation to API interfaces |
| `useLombokAnnotations` | `false` | Use Lombok annotations to generate properties accessors and `hashCode`/`equals`/`toString` methods |
| `useSetForUniqueItems` | `false` | Use `java.util.Set` for arrays that has the attribute `uniqueItems` to `true` |
| `openApiNullable` | `true` | Whether to use the `jackson-databind-nullable` library |
| `useWithModifiers` | `false` | Generates bean `with` modifiers for fluent style |
| `useProtectedFields` | `false` | Whether to use protected visibility for model fields |

## Java Code Generator

| Option | Default | Description |
|-|-|-|
| `createApiComponent` | `true` | Whether to generate the client as a Spring component (`resttemplate` only) |
| `restTemplateBeanName` | `none` | The qualifier of the `RestTemplate` used by the `ApiClient` (`resttemplate` only) |
| `useClassLevelBeanValidation` | `false` | Adds @Validated annotation to API interfaces |
| `useJacksonConversion` | `false` | Use Jackson to convert query parameters (`resttemplate` only) |
| `useSetForUniqueItems` | `false` | Use `java.util.Set` for arrays that has the attribute `uniqueItems` to `true` |
| `useProtectedFields` | `false` | "Whether to use protected visibility for model fields |
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ public class BoatJavaCodeGen extends JavaClientCodegen {

public static final String USE_DEFAULT_API_CLIENT = "useDefaultApiClient";
public static final String REST_TEMPLATE_BEAN_NAME = "restTemplateBeanName";
public static final String CREATE_API_COMPONENT = "createApiComponent";
public static final String USE_PROTECTED_FIELDS = "useProtectedFields";

private static final String JAVA_UTIL_SET_NEW = "new " + "java.util.LinkedHashSet<>()";
private static final String JAVA_UTIL_SET = "java.util.Set";
Expand All @@ -45,6 +47,9 @@ public class BoatJavaCodeGen extends JavaClientCodegen {
@Getter
@Setter
protected String restTemplateBeanName;
@Getter
@Setter
protected boolean createApiComponent = true;

public BoatJavaCodeGen() {
this.embeddedTemplateDir = this.templateDir = NAME;
Expand All @@ -61,6 +66,10 @@ public BoatJavaCodeGen() {
"Whether to use a default ApiClient with a builtin template", this.useDefaultApiClient));
this.cliOptions.add(CliOption.newString(REST_TEMPLATE_BEAN_NAME,
"An optional RestTemplate bean name"));
this.cliOptions.add(CliOption.newString(CREATE_API_COMPONENT,
"Whether to generate the client as a Spring component"));
this.cliOptions.add(CliOption.newString(USE_PROTECTED_FIELDS,
"Whether to use protected visibility for model fields"));
}

@Override
Expand Down Expand Up @@ -113,6 +122,16 @@ public void processOpts() {
writePropertyBack(USE_DEFAULT_API_CLIENT, this.useDefaultApiClient);

this.restTemplateBeanName = (String) this.additionalProperties.get(REST_TEMPLATE_BEAN_NAME);

if (this.additionalProperties.containsKey(CREATE_API_COMPONENT)) {
this.createApiComponent = convertPropertyToBoolean(CREATE_API_COMPONENT);
}
writePropertyBack(CREATE_API_COMPONENT, this.createApiComponent);
}
if (this.additionalProperties.containsKey(USE_PROTECTED_FIELDS)) {
this.additionalProperties.put("modelFieldsVisibility", "protected");
} else {
this.additionalProperties.put("modelFieldsVisibility", "private");
}

if (!getLibrary().startsWith("jersey")) {
Expand Down Expand Up @@ -142,11 +161,11 @@ public void postProcessParameter(CodegenParameter p) {
if (p.isContainer && this.useSetForUniqueItems && p.getUniqueItems()) {
// XXX the model set baseType to the container type, why is this different?

p.baseType = p.dataType.replaceAll("^([^<]+)<.+>$", "$1");
p.baseType = JAVA_UTIL_SET;
p.dataType = format(JAVA_UTIL_SET_GEN, p.items.dataType);
p.datatypeWithEnum = format(JAVA_UTIL_SET_GEN, p.items.datatypeWithEnum);
p.defaultValue = JAVA_UTIL_SET_NEW;
p.baseType = p.dataType.replaceAll("^([^<]+)<.+>$", "$1");
p.baseType = JAVA_UTIL_SET;
p.dataType = format(JAVA_UTIL_SET_GEN, p.items.dataType);
p.datatypeWithEnum = format(JAVA_UTIL_SET_GEN, p.items.datatypeWithEnum);
p.defaultValue = JAVA_UTIL_SET_NEW;

}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ public class BoatSpringCodeGen extends SpringCodegen {
public static final String USE_SET_FOR_UNIQUE_ITEMS = "useSetForUniqueItems";
public static final String OPENAPI_NULLABLE = "openApiNullable";
public static final String USE_WITH_MODIFIERS = "useWithModifiers";
public static final String BASE_TYPE = "java.util.Set";
public static final String USE_PROTECTED_FIELDS = "useProtectedFields";
public static final String UNIQUE_BASE_TYPE = "java.util.Set";

static class NewLineIndent implements Mustache.Lambda {

Expand Down Expand Up @@ -136,7 +137,8 @@ public BoatSpringCodeGen() {
this.cliOptions.add(CliOption.newBoolean(ADD_SERVLET_REQUEST,
"Adds a HttpServletRequest object to the API definition method.", this.addServletRequest));
this.cliOptions.add(CliOption.newBoolean(ADD_BINDING_RESULT,
"Adds a Binding result as method perimeter. Only implemented if @validate is being used.", this.addBindingResult));
"Adds a Binding result as method perimeter. Only implemented if @validate is being used.",
this.addBindingResult));
this.cliOptions.add(CliOption.newBoolean(USE_LOMBOK_ANNOTATIONS,
"Add Lombok to class-level Api models. Defaults to false.", this.useLombokAnnotations));
this.cliOptions.add(CliOption.newBoolean(USE_SET_FOR_UNIQUE_ITEMS,
Expand All @@ -145,6 +147,8 @@ public BoatSpringCodeGen() {
"Enable OpenAPI Jackson Nullable library.", this.openApiNullable));
this.cliOptions.add(CliOption.newBoolean(USE_WITH_MODIFIERS,
"Whether to use \"with\" prefix for POJO modifiers.", this.useWithModifiers));
this.cliOptions.add(CliOption.newString(USE_PROTECTED_FIELDS,
"Whether to use protected visibility for model fields"));

this.apiNameSuffix = "Api";
}
Expand Down Expand Up @@ -213,6 +217,11 @@ public void processOpts() {
if (this.additionalProperties.containsKey(USE_WITH_MODIFIERS)) {
this.useWithModifiers = convertPropertyToBoolean(USE_WITH_MODIFIERS);
}
if (this.additionalProperties.containsKey(USE_PROTECTED_FIELDS)) {
this.additionalProperties.put("modelFieldsVisibility", "protected");
} else {
this.additionalProperties.put("modelFieldsVisibility", "private");
}

writePropertyBack(USE_CLASS_LEVEL_BEAN_VALIDATION, this.useClassLevelBeanValidation);
writePropertyBack(ADD_SERVLET_REQUEST, this.addServletRequest);
Expand All @@ -223,9 +232,9 @@ public void processOpts() {
writePropertyBack(USE_WITH_MODIFIERS, this.useWithModifiers);

if (this.useSetForUniqueItems) {
this.typeMapping.put("set", BASE_TYPE);
this.typeMapping.put("set", UNIQUE_BASE_TYPE);

this.importMapping.put("Set", BASE_TYPE);
this.importMapping.put("Set", UNIQUE_BASE_TYPE);
this.importMapping.put("LinkedHashSet", "java.util.LinkedHashSet");
}

Expand All @@ -241,9 +250,9 @@ public void postProcessModelProperty(CodegenModel model, CodegenProperty p) {

if (p.isContainer && this.useSetForUniqueItems && p.getUniqueItems()) {
p.containerType = "set";
p.baseType = BASE_TYPE;
p.dataType = BASE_TYPE + "<" + p.items.dataType + ">";
p.datatypeWithEnum = BASE_TYPE + "<" + p.items.datatypeWithEnum + ">";
p.baseType = UNIQUE_BASE_TYPE;
p.dataType = UNIQUE_BASE_TYPE + "<" + p.items.dataType + ">";
p.datatypeWithEnum = UNIQUE_BASE_TYPE + "<" + p.items.datatypeWithEnum + ">";
p.defaultValue = "new " + "java.util.LinkedHashSet<>()";
}
}
Expand All @@ -258,9 +267,9 @@ public void postProcessParameter(CodegenParameter p) {
}

if (this.useSetForUniqueItems && p.getUniqueItems()) {
p.baseType = BASE_TYPE;
p.dataType = BASE_TYPE + "<" + p.items.dataType + ">";
p.datatypeWithEnum = BASE_TYPE + "<" + p.items.datatypeWithEnum + ">";
p.baseType = UNIQUE_BASE_TYPE;
p.dataType = UNIQUE_BASE_TYPE + "<" + p.items.dataType + ">";
p.datatypeWithEnum = UNIQUE_BASE_TYPE + "<" + p.items.datatypeWithEnum + ">";
p.defaultValue = "new " + "java.util.LinkedHashSet<>()";
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;{{/fullJavaUtil}}

import org.springframework.beans.factory.annotation.Autowired;
{{#createApiComponent}}import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
{{/createApiComponent}}
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestClientException;
Expand All @@ -33,24 +34,24 @@ import org.springframework.validation.annotation.Validated;
{{/useBeanValidation}}

{{>generatedAnnotation}}
@Component("{{package}}.{{classname}}")
{{#operations}}
{{#useBeanValidation}}{{!
{{#createApiComponent}}@Component("{{package}}.{{classname}}")
{{/createApiComponent}}{{!
}}{{#useBeanValidation}}{{!
}}{{#useClassLevelBeanValidation}}{{!
}}@Validated
{{/useClassLevelBeanValidation}}{{!
}}{{/useBeanValidation}}
public class {{classname}} {
private {{^useDefaultApiClient}}final {{/useDefaultApiClient}}ApiClient apiClient;
private {{#useDefaultApiClient}}final {{/useDefaultApiClient}}ApiClient apiClient;

{{^useDefaultApiClient}}
public {{classname}}() {
this(new ApiClient());
}
{{/useDefaultApiClient}}

@Autowired
public {{classname}}(ApiClient apiClient) {
{{#createApiComponent}} @Autowired
{{/createApiComponent}} public {{classname}}(ApiClient apiClient) {
this.apiClient = apiClient;
}

Expand All @@ -64,6 +65,7 @@ public class {{classname}} {
}
{{/useDefaultApiClient}}

{{#operations}}
{{#operation}}
/**
* {{summary}}
Expand Down Expand Up @@ -162,5 +164,5 @@ public class {{classname}} {
return apiClient.invokeAPI(localVarPath, HttpMethod.{{httpMethod}}, localVarQueryParams, localVarPostBody, localVarHeaderParams, localVarCookieParams, localVarFormParams, localVarAccept, localVarContentType, localVarAuthNames, localVarReturnType);
}
{{/operation}}
}
{{/operations}}
}
10 changes: 5 additions & 5 deletions boat-scaffold/src/main/templates/boat-java/pojo.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
})
{{/jackson}}
{{>additionalModelTypeAnnotations}}{{>generatedAnnotation}}{{#discriminator}}{{>typeInfoAnnotation}}{{/discriminator}}{{>xmlAnnotation}}
public class {{classname}} {{#parent}}extends {{{parent}}} {{/parent}}{{#vendorExtensions.x-implements}}{{#-first}}implements {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{#-last}} {{/-last}}{{/vendorExtensions.x-implements}}{
public {{#vendorExtensions.x-abstract}}abstract {{/vendorExtensions.x-abstract}} class {{classname}} {{#parent}}extends {{{parent}}} {{/parent}}{{#vendorExtensions.x-implements}}{{#-first}}implements {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{#-last}} {{/-last}}{{/vendorExtensions.x-implements}}{
{{#serializableModel}}
private static final long serialVersionUID = 1L;

Expand Down Expand Up @@ -58,18 +58,18 @@ public class {{classname}} {{#parent}}extends {{{parent}}} {{/parent}}{{#vendorE
{{/gson}}
{{#vendorExtensions.x-is-jackson-optional-nullable}}
{{#isContainer}}
private JsonNullable<{{{datatypeWithEnum}}}> {{name}} = JsonNullable.<{{{datatypeWithEnum}}}>undefined();
{{modelFieldsVisibility}} JsonNullable<{{{datatypeWithEnum}}}> {{name}} = JsonNullable.<{{{datatypeWithEnum}}}>undefined();
{{/isContainer}}
{{^isContainer}}
private JsonNullable<{{{datatypeWithEnum}}}> {{name}} = JsonNullable.<{{{datatypeWithEnum}}}>{{#defaultValue}}of({{{.}}}){{/defaultValue}}{{^defaultValue}}undefined(){{/defaultValue}};
{{modelFieldsVisibility}} JsonNullable<{{{datatypeWithEnum}}}> {{name}} = JsonNullable.<{{{datatypeWithEnum}}}>{{#defaultValue}}of({{{.}}}){{/defaultValue}}{{^defaultValue}}undefined(){{/defaultValue}};
{{/isContainer}}
{{/vendorExtensions.x-is-jackson-optional-nullable}}
{{^vendorExtensions.x-is-jackson-optional-nullable}}
{{#isContainer}}
private {{{datatypeWithEnum}}} {{name}}{{#required}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}{{/required}}{{^required}} = null{{/required}};
{{modelFieldsVisibility}} {{{datatypeWithEnum}}} {{name}}{{#required}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}{{/required}}{{^required}} = null{{/required}};
{{/isContainer}}
{{^isContainer}}
{{#isDiscriminator}}protected{{/isDiscriminator}}{{^isDiscriminator}}private{{/isDiscriminator}} {{{datatypeWithEnum}}} {{name}}{{#defaultValue}} = {{{.}}}{{/defaultValue}};
{{#isDiscriminator}}protected{{/isDiscriminator}}{{^isDiscriminator}}{{modelFieldsVisibility}}{{/isDiscriminator}} {{{datatypeWithEnum}}} {{name}}{{#defaultValue}} = {{{.}}}{{/defaultValue}};
{{/isContainer}}
{{/vendorExtensions.x-is-jackson-optional-nullable}}

Expand Down
8 changes: 4 additions & 4 deletions boat-scaffold/src/main/templates/boat-spring/pojo.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,10 @@ public {{#vendorExtensions.x-abstract}}abstract {{/vendorExtensions.x-abstract}}
{{/useLombokAnnotations}}
{{#isContainer}}
{{#openApiNullable}}
private {{>nullableDataType}} {{name}} = {{#isNullable}}JsonNullable.undefined(){{/isNullable}}{{^isNullable}}{{#required}}{{{defaultValue}}}{{/required}}{{^required}}null{{/required}}{{/isNullable}};
{{modelFieldsVisibility}} {{>nullableDataType}} {{name}} = {{#isNullable}}JsonNullable.undefined(){{/isNullable}}{{^isNullable}}{{#required}}{{{defaultValue}}}{{/required}}{{^required}}null{{/required}}{{/isNullable}};
{{/openApiNullable}}
{{^openApiNullable}}
private {{>nullableDataType}} {{name}} = {{#required}}{{{defaultValue}}}{{/required}}{{^required}}null{{/required}};
{{modelFieldsVisibility}} {{>nullableDataType}} {{name}} = {{#required}}{{{defaultValue}}}{{/required}}{{^required}}null{{/required}};
{{/openApiNullable}}
{{/isContainer}}
{{^isContainer}}
Expand All @@ -60,10 +60,10 @@ public {{#vendorExtensions.x-abstract}}abstract {{/vendorExtensions.x-abstract}}
@org.springframework.format.annotation.DateTimeFormat(iso = org.springframework.format.annotation.DateTimeFormat.ISO.DATE_TIME)
{{/isDateTime}}
{{#openApiNullable}}
private {{>nullableDataType}} {{name}}{{#isNullable}} = JsonNullable.undefined(){{/isNullable}}{{^isNullable}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}{{/isNullable}};
{{modelFieldsVisibility}} {{>nullableDataType}} {{name}}{{#isNullable}} = JsonNullable.undefined(){{/isNullable}}{{^isNullable}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}{{/isNullable}};
{{/openApiNullable}}
{{^openApiNullable}}
private {{>nullableDataType}} {{name}}{{#isNullable}} = null{{/isNullable}}{{^isNullable}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}{{/isNullable}};
{{modelFieldsVisibility}} {{>nullableDataType}} {{name}}{{#isNullable}} = null{{/isNullable}}{{^isNullable}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}{{/isNullable}};
{{/openApiNullable}}
{{/isContainer}}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.util.Map;
import static java.util.stream.Collectors.groupingBy;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasEntry;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.jupiter.api.Assertions.assertEquals;
Expand Down Expand Up @@ -89,6 +90,31 @@ void processOptsWithoutRestTemplate() {
assertThat(gen.restTemplateBeanName, is(nullValue()));
}

@Test
void processOptsCreateApiComponent() {
final BoatJavaCodeGen gen = new BoatJavaCodeGen();
final Map<String, Object> options = gen.additionalProperties();

options.put(CREATE_API_COMPONENT, "false");

gen.setLibrary("resttemplate");
gen.processOpts();

assertThat(gen.createApiComponent, is(false));
}

@Test
void processOptsUseProtectedFields() {
final BoatJavaCodeGen gen = new BoatJavaCodeGen();
final Map<String, Object> options = gen.additionalProperties();

options.put(USE_PROTECTED_FIELDS, "true");

gen.processOpts();

assertThat(gen.additionalProperties(), hasEntry("modelFieldsVisibility", "protected"));
}

@Test
void uniquePropertyToSet() {
final BoatJavaCodeGen gen = new BoatJavaCodeGen();
Expand Down Expand Up @@ -127,4 +153,5 @@ void uniqueParameterToSet() {
assertThat(param.baseType, is("java.util.Set"));
assertThat(param.dataType, is("java.util.Set<String>"));
}

}
Loading