Skip to content

Commit 8375fa8

Browse files
committed
Merge branch 'master' into stability-indexing
* master: resize lightbow logo add lightbow logo add Lightbow as sponsor (#2822) [docs] Document new templating engine, adding ability to reference templates by file to comlete the example (#2773) [JAVA][KOTLIN] fix var Naming for all uppercase with Numbers (#2794) [Documentation] Add instructions to build the javascript client module (#2806) use mvn instead of run-in-docker (#2821) Better handling of form data (#2818) [haskell-servant] Add some missing types to the generated modules (#2675) Clarifies need to build project for sample output (#2819) [Elm] Bugfix .encode in modelTypeDiscriminator (#2807) Apply strict spec option to CodegenConfig instance (#2814)
2 parents 048a237 + 3c8d820 commit 8375fa8

File tree

25 files changed

+400
-32
lines changed

25 files changed

+400
-32
lines changed

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
### PR checklist
22

33
- [ ] Read the [contribution guidelines](https://github.com/openapitools/openapi-generator/blob/master/CONTRIBUTING.md).
4-
- [ ] Ran the shell script under `./bin/` to update Petstore sample so that CIs can verify the change. (For instance, only need to run `./bin/{LANG}-petstore.sh`, `./bin/openapi3/{LANG}-petstore.sh` if updating the {LANG} (e.g. php, ruby, python, etc) code generator or {LANG} client's mustache templates). Windows batch files can be found in `.\bin\windows\`.
4+
- [ ] Ran the shell script under `./bin/` to update Petstore sample so that CIs can verify the change. (For instance, only need to run `./bin/{LANG}-petstore.sh`, `./bin/openapi3/{LANG}-petstore.sh` if updating the {LANG} (e.g. php, ruby, python, etc) code generator or {LANG} client's mustache templates). Windows batch files can be found in `.\bin\windows\`. If contributing template-only or documentation-only changes which will change sample output, be sure to [build the project](https://github.com/OpenAPITools/openapi-generator#14---build-projects) first.
55
- [ ] Filed the PR against the [correct branch](https://github.com/OpenAPITools/openapi-generator/wiki/Git-Branches): `master`~~, `3.4.x`, `4.0.x`~~. Default: `master`.
66
- [ ] Copied the [technical committee](https://github.com/openapitools/openapi-generator/#62---openapi-generator-technical-committee) to review the pull request if your PR is targeting a particular programming language.
77

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ script:
121121
# fail if generators contain tab '\t'
122122
- /bin/bash ./bin/utils/detect_tab_in_java_class.sh
123123
# run integration tests defined in maven pom.xml
124-
- ./run-in-docker.sh mvn --quiet --batch-mode clean install
124+
- mvn --quiet --batch-mode clean install
125125
- mvn --quiet --batch-mode verify -Psamples
126126
after_success:
127127
# push to maven repo

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ If you find OpenAPI Generator useful for work, please consider asking your compa
4040
#### Thank you to our bronze sponsors!
4141

4242
[![NamSor](https://openapi-generator.tech/img/companies/namsor.png)](https://www.namsor.com/?utm_source=openapi_generator&utm_medium=github_webpage&utm_campaign=sponsor)
43-
43+
[![LightBow](https://openapi-generator.tech/img/companies/lightbow.png)](https://www.lightbow.net/?utm_source=openapi_generator&utm_medium=github_webpage&utm_campaign=sponsor)
4444

4545
## Overview
4646
OpenAPI Generator allows generation of API client libraries (SDK generation), server stubs, documentation and configuration automatically given an [OpenAPI Spec](https://github.com/OAI/OpenAPI-Specification) (both 2.0 and 3.0 are supported). Currently, the following languages/frameworks are supported:

docs/templating.md

Lines changed: 160 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ The transform logic needs to implement [CodegenConfig.java](https://github.com/o
1818
> * Maven Plugin: `templateDirectory`
1919
> * Gradle Plugin: `templateDir`
2020
21-
Built-in templates are written in Mustache and processed by [jmustache](https://github.com/samskivert/jmustache). We plan to eventually support Handlebars and user-defined template engines via plugins.
21+
Built-in templates are written in Mustache and processed by [jmustache](https://github.com/samskivert/jmustache). Beginning with version 4.0.0, we support experimental Handlebars and user-defined template engines via plugins.
2222

2323
OpenAPI Generator supports user-defined templates. This approach is often the easiest when creating a custom template. Our generators implement a combination of language and framework features, and it's fully possible to use an existing generator to implement a custom template for a different framework. Suppose you have internal utilities which you'd like to incorporate into generated code (e.g. logging, monitoring, fault-handling)... this is easy to add via custom templates.
2424

@@ -261,6 +261,165 @@ Congratulations! You've now modified one of the built-in templates to meet your
261261

262262
Adding/modifying template logic simply requires a little bit of [mustache](https://mustache.github.io/), for which you can use existing templates as a guide.
263263

264+
### Custom Engines
265+
266+
> Custom template engine support is *experimental*
267+
268+
If Mustache or the experimental Handlebars engines don't suit your needs, you can define an adapter to your templating engine of choice. To do this, you'll need to define a new project which consumes the `openapi-generator-core` artifact, and at a minimum implement `TemplatingEngineAdapter`.
269+
270+
This example:
271+
272+
* creates an adapter providing the fundamental logic to compile [Pebble Templates](https://pebbletemplates.io)
273+
* will be implemented in Kotlin to demonstrate ServiceLoader configuration specific to Kotlin (Java will be similar)
274+
* requires Gradle 5.0+
275+
* provides project setup instructions for IntelliJ
276+
277+
To begin, create a [new Gradle project](https://www.jetbrains.com/help/idea/getting-started-with-gradle.html) with Kotlin support. To do this, go to `File``New``Project`, choose "Gradle" and "Kotlin". Specify groupId `org.openapitools.examples` and artifactId `pebble-template-adapter`.
278+
279+
Ensure the new project uses Gradle 5.0. Navigate to the newly created directory and execute:
280+
281+
```bash
282+
gradle wrapper --gradle-version 5.0
283+
```
284+
285+
In `build.gradle`, we'll add a dependency for OpenAPI Tools core which defines the interface and an abstract helper type for implementing the adapter. We'll also pull in the Pebble artifact. We'll be evaluating this new artifact locally, so we'll also add the Maven plugin for installing to the local maven repository. We'll also create a fatjar using the `shadow` plugin to simplify our classpath.
286+
287+
Modifications to the new project's `build.gradle` should be made in the `plugins` and `dependencies` nodes:
288+
289+
```diff
290+
plugins {
291+
id 'org.jetbrains.kotlin.jvm' version '1.3.11'
292+
id "com.github.johnrengelman.shadow" version "5.0.0"
293+
}
294+
295+
dependencies {
296+
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
297+
compile "org.openapitools:openapi-generator-core:4.0.0-SNAPSHOT"
298+
compile "io.pebbletemplates:pebble:3.0.8"
299+
}
300+
```
301+
302+
The above configuration for the `shadow` plugin is strictly optional. It is not needed, for instance, if you plan to publish your adapter and consume it via the Maven or Gradle plugins.
303+
304+
Next, create a new class file called `PebbleTemplateEngineAdapter` under `src/kotlin`. We'll define the template adapter's name as `pebble` and we'll also list this as the only supported file extension. We'll implement the adapter by extending `AbstractTemplatingEngineAdapter`, which includes reusable logic, such as retrieving a list of all possible template names for our provided template extensions(s).
305+
306+
The class in its simplest form looks like this (with inline comments):
307+
308+
```kotlin
309+
// Allows specifying engine by class name
310+
// e.g. -e org.openapitools.examples.templating.PebbleTemplateAdapter
311+
@file:JvmName("PebbleTemplateAdapter")
312+
package org.openapitools.examples.templating
313+
314+
// imports
315+
316+
class PebbleTemplateAdapter : AbstractTemplatingEngineAdapter() {
317+
// initialize the template compilation engine
318+
private val engine: PebbleEngine = PebbleEngine.Builder()
319+
.cacheActive(false)
320+
.loader(DelegatingLoader(listOf(FileLoader(), ClasspathLoader())))
321+
.build()
322+
323+
// allows targeting engine by id/name: -e pebble
324+
override fun getIdentifier(): String = "pebble"
325+
326+
override fun compileTemplate(
327+
generator: TemplatingGenerator?,
328+
bundle: MutableMap<String, Any>?,
329+
templateFile: String?
330+
): String {
331+
// This will convert, for example, model.mustache to model.pebble
332+
val modifiedTemplate = this.getModifiedFileLocation(templateFile).first()
333+
334+
// Uses generator built-in template resolution strategy to find the full template file
335+
val filePath = generator?.getFullTemplatePath(modifiedTemplate)
336+
337+
val writer = StringWriter()
338+
// Conditionally writes out the template if found.
339+
if (filePath != null) {
340+
engine.getTemplate(filePath.toAbsolutePath().toString())?.evaluate(writer, bundle)
341+
}
342+
return writer.toString()
343+
}
344+
345+
override fun getFileExtensions(): Array<String> = arrayOf("pebble")
346+
}
347+
```
348+
349+
Lastly, create a file `resources/META-INF/services/org.openapitools.codegen.api.TemplatingEngineAdapter`, containing the full class path to the above class:
350+
351+
```
352+
org.openapitools.examples.templating.PebbleTemplateAdapter
353+
```
354+
355+
This allows the adapter to load via ServiceLoader, and to be referenced via the identifier `pebble`. This is optional; if you don't provide the above file and contents, you'll only be able to load the engine via full class name (explained in a bit).
356+
357+
Now, build the fatjar for this new adapter:
358+
359+
```bash
360+
./gradlew shadowJar
361+
```
362+
363+
To test compilation of some templates, we'll need to first create one or more template files. Create a temp directory at `/tmp/pebble-example/templates` and add the following files.
364+
365+
*api.pebble*
366+
367+
```
368+
package {{packageName}}
369+
370+
import (
371+
"net/http"
372+
{% for item in imports %}
373+
"{{item.import}}"
374+
{% endfor %}
375+
)
376+
377+
type Generated{{classname}}Servicer
378+
379+
// etc
380+
```
381+
382+
*model.pebble*
383+
384+
```
385+
package {{packageName}}
386+
387+
{% for item in models %}
388+
{% if item.isEnum %}
389+
// TODO: enum
390+
{% else %}
391+
{% if item.description is not empty %}// {{item.description}}{% endif %}
392+
type {{item.classname}} struct {
393+
{% for var in item.model.vars %}
394+
{% if var.description is not empty %}// {{var.description}}{% endif %}
395+
{{var.name}} {% if var.isNullable %}*{% endif %}{{var.dataType}} `json:"{{var.baseName}}{% if var.required == false %},omitempty{% endif %}"{% if var.withXml == true %} xml:"{{var.baseName}}{% if var.isXmlAttribute %},attr{% endif %}"{% endif %}`
396+
{% endfor %}
397+
}
398+
{% endif %}
399+
{{model.name}}
400+
{% endfor %}
401+
```
402+
403+
> Find object structures passed to templates later in this document's **Structures** section.
404+
405+
Finally, we can compile some code by explicitly defining our classpath and jar entrypoint for CLI (be sure to modify `/your/path` below)
406+
407+
```bash
408+
java $JAVA_OPTS -cp /your/path/build/libs/pebble-template-adapter-1.0-SNAPSHOT-all.jar:modules/openapi-generator-cli/target/openapi-generator-cli.jar \
409+
org.openapitools.codegen.OpenAPIGenerator \
410+
generate \
411+
-g go \
412+
-i https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/examples/v2.0/json/petstore-minimal.json \
413+
-e pebble \
414+
-o /tmp/pebble-example/out \
415+
-t /tmp/pebble-example/templates \
416+
-Dmodels -DmodelDocs=false -DmodelTests=false -Dapis -DapiTests=false -DapiDocs=false
417+
```
418+
419+
Notice how we've targeted our custom template engine adapter via `-e pebble`. If you don't include the SPI file under `META-INF/services`, you'll need to specify the exact classpath: `org.openapitools.examples.templating.PebbleTemplateAdapter`. Notice that the target class here matches the Kotlin class name. This is because of the `@file:JvmName` annotation.
420+
421+
Congratulations on creating a custom templating engine adapter!
422+
264423
## Structures
265424

266425
Aside from transforming an API document, the implementing class gets to decide how to apply the data structure to templates. We can decide which data structure to apply to which template files. You have the following structures at your disposal.

modules/openapi-generator-core/src/main/java/org/openapitools/codegen/api/TemplatingGenerator.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package org.openapitools.codegen.api;
1818

19+
import java.nio.file.Path;
20+
1921
/**
2022
* interface to the full template content
2123
* implementers might take into account the -t cli option,
@@ -25,9 +27,19 @@ public interface TemplatingGenerator {
2527

2628
/**
2729
* returns the template content by name
30+
*
2831
* @param name the template name (e.g. model.mustache)
32+
*
2933
* @return the contents of that template
3034
*/
3135
String getFullTemplateContents(String name);
3236

37+
/**
38+
* Returns the path of a template, allowing access to the template where consuming literal contents aren't desirable or possible.
39+
*
40+
* @param name the template name (e.g. model.mustache)
41+
*
42+
* @return The {@link Path} to the template
43+
*/
44+
Path getFullTemplatePath(String name);
3345
}

modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1308,6 +1308,7 @@ public String toDefaultValue(Schema schema) {
13081308

13091309
/**
13101310
* Return property value depending on property type.
1311+
*
13111312
* @param schema property type
13121313
* @return property value
13131314
*/
@@ -1472,7 +1473,8 @@ private static String getPrimitiveType(Schema schema) {
14721473
} else if (ModelUtils.isDoubleSchema(schema)) {
14731474
return SchemaTypeUtil.DOUBLE_FORMAT;
14741475
} else {
1475-
LOGGER.warn("Unknown `format` detected for " + schema.getName() + ": " + schema.getFormat());
1476+
LOGGER.warn("Unknown `format` {} detected for type `number`. Defaulting to `number`", schema.getFormat());
1477+
return "number";
14761478
}
14771479
} else if (ModelUtils.isIntegerSchema(schema)) {
14781480
if (ModelUtils.isLongSchema(schema)) {
@@ -1913,7 +1915,7 @@ public String getterAndSetterCapitalize(String name) {
19131915
*/
19141916
public CodegenProperty fromProperty(String name, Schema p) {
19151917
if (p == null) {
1916-
LOGGER.error("Undefined property/schema for `{}`. Default to type:string.", name);
1918+
LOGGER.error("Undefined property/schema for `{}`. Default to type:string.", name);
19171919
return null;
19181920
}
19191921
LOGGER.debug("debugging fromProperty for " + name + " : " + p);
@@ -2063,7 +2065,7 @@ public CodegenProperty fromProperty(String name, Schema p) {
20632065
if (innerSchema == null) {
20642066
LOGGER.error("Undefined array inner type for `{}`. Default to String.", p.getName());
20652067
innerSchema = new StringSchema().description("//TODO automatically added by openapi-generator due to undefined type");
2066-
((ArraySchema)p).setItems(innerSchema);
2068+
((ArraySchema) p).setItems(innerSchema);
20672069
}
20682070
} else if (ModelUtils.isMapSchema(p)) {
20692071
Schema innerSchema = ModelUtils.unaliasSchema(this.openAPI, ModelUtils.getAdditionalProperties(p));
@@ -2145,7 +2147,7 @@ public CodegenProperty fromProperty(String name, Schema p) {
21452147
if (innerSchema == null) {
21462148
LOGGER.error("Undefined array inner type for `{}`. Default to String.", p.getName());
21472149
innerSchema = new StringSchema().description("//TODO automatically added by openapi-generator due to undefined type");
2148-
((ArraySchema)p).setItems(innerSchema);
2150+
((ArraySchema) p).setItems(innerSchema);
21492151
}
21502152
CodegenProperty cp = fromProperty(itemName, innerSchema);
21512153
updatePropertyForArray(property, cp);
@@ -2537,8 +2539,8 @@ public CodegenOperation fromOperation(String path,
25372539
CodegenParameter bodyParam = null;
25382540
RequestBody requestBody = operation.getRequestBody();
25392541
if (requestBody != null) {
2540-
if ("application/x-www-form-urlencoded".equalsIgnoreCase(getContentType(requestBody)) ||
2541-
"multipart/form-data".equalsIgnoreCase(getContentType(requestBody))) {
2542+
if (getContentType(requestBody).toLowerCase(Locale.ROOT).startsWith("application/x-www-form-urlencoded") ||
2543+
getContentType(requestBody).toLowerCase(Locale.ROOT).startsWith("multipart/form-data")) {
25422544
// process form parameters
25432545
formParams = fromRequestBodyToFormParameters(requestBody, imports);
25442546
for (CodegenParameter cp : formParams) {
@@ -4216,7 +4218,8 @@ public void writePropertyBack(String propertyKey, boolean value) {
42164218

42174219
protected String getContentType(RequestBody requestBody) {
42184220
if (requestBody == null || requestBody.getContent() == null || requestBody.getContent().isEmpty()) {
4219-
return null;
4221+
LOGGER.warn("Cannot determine the content type. Default to UNKNOWN_CONTENT_TYPE.");
4222+
return "UNKNOWN_CONTENT_TYPE";
42204223
}
42214224
return new ArrayList<>(requestBody.getContent().keySet()).get(0);
42224225
}
@@ -4297,7 +4300,9 @@ public boolean hasFormParameter(OpenAPI openAPI, Operation operation) {
42974300
}
42984301

42994302
for (String consume : consumesInfo) {
4300-
if ("application/x-www-form-urlencoded".equalsIgnoreCase(consume) || "multipart/form-data".equalsIgnoreCase(consume)) {
4303+
if (consume != null &&
4304+
consume.toLowerCase(Locale.ROOT).startsWith("application/x-www-form-urlencoded") ||
4305+
consume.toLowerCase(Locale.ROOT).startsWith("multipart/form-data")) {
43014306
return true;
43024307
}
43034308
}
@@ -4871,7 +4876,7 @@ public List<CodegenServerVariable> fromServerVariables(Map<String, ServerVariabl
48714876
}
48724877

48734878
private void setParameterNullable(CodegenParameter parameter, CodegenProperty property) {
4874-
if(parameter == null || property == null) {
4879+
if (parameter == null || property == null) {
48754880
return;
48764881
}
48774882
parameter.isNullable = property.isNullable;
@@ -4920,7 +4925,7 @@ public boolean isEnableMinimalUpdate() {
49204925
/**
49214926
* Set the boolean value indicating the state of the option for updating only changed files
49224927
*
4923-
* @param enableMinimalUpdate true to enable minimal update
4928+
* @param enableMinimalUpdate true to enable minimal update
49244929
*/
49254930
@Override
49264931
public void setEnableMinimalUpdate(boolean enableMinimalUpdate) {

modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747

4848
import java.io.*;
4949
import java.net.URL;
50+
import java.nio.file.Path;
5051
import java.time.ZonedDateTime;
5152
import java.util.*;
5253

@@ -936,6 +937,19 @@ public String getFullTemplateContents(String templateName) {
936937
return readTemplate(getFullTemplateFile(config, templateName));
937938
}
938939

940+
/**
941+
* Returns the path of a template, allowing access to the template where consuming literal contents aren't desirable or possible.
942+
*
943+
* @param name the template name (e.g. model.mustache)
944+
*
945+
* @return The {@link Path} to the template
946+
*/
947+
@Override
948+
public Path getFullTemplatePath(String name) {
949+
String fullPath = getFullTemplateFile(config, name);
950+
return java.nio.file.Paths.get(fullPath);
951+
}
952+
939953
protected File processTemplateToFile(Map<String, Object> templateData, String templateName, String outputFilename) throws IOException {
940954
String adjustedOutputFilename = outputFilename.replaceAll("//", "/").replace('/', File.separatorChar);
941955
if (ignoreProcessor.allowsFile(new File(adjustedOutputFilename))) {

modules/openapi-generator/src/main/java/org/openapitools/codegen/config/CodegenConfigurator.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,8 @@ public ClientOptInput toClientOptInput() {
541541
config.languageSpecificPrimitives().addAll(languageSpecificPrimitives);
542542
config.reservedWordsMappings().putAll(reservedWordMappings);
543543

544+
config.setStrictSpecBehavior(isStrictSpecBehavior());
545+
544546
checkAndSetAdditionalProperty(apiPackage, CodegenConstants.API_PACKAGE);
545547
checkAndSetAdditionalProperty(modelPackage, CodegenConstants.MODEL_PACKAGE);
546548
checkAndSetAdditionalProperty(invokerPackage, CodegenConstants.INVOKER_PACKAGE);

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractJavaCodegen.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -607,7 +607,7 @@ public String toVarName(String name) {
607607
}
608608

609609
// if it's all uppper case, do nothing
610-
if (name.matches("^[A-Z_]*$")) {
610+
if (name.matches("^[A-Z0-9_]*$")) {
611611
return name;
612612
}
613613

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractKotlinCodegen.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -717,7 +717,7 @@ public String toVarName(String name) {
717717
}
718718

719719
// if it's all uppper case, do nothing
720-
if (name.matches("^[A-Z_]*$")) {
720+
if (name.matches("^[A-Z0-9_]*$")) {
721721
return name;
722722
}
723723

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/HaskellServantCodegen.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,6 @@ public void preprocessOpenAPI(OpenAPI openAPI) {
301301
*/
302302
private void setGenerateToSchema(CodegenModel model) {
303303
for (CodegenProperty var : model.vars) {
304-
LOGGER.warn(var.dataType);
305304
if (var.dataType.contentEquals("Value") || var.dataType.contains(" Value")) {
306305
additionalProperties.put("generateToSchema", false);
307306
}
@@ -349,7 +348,7 @@ public String getTypeDeclaration(Schema p) {
349348
@Override
350349
public String getSchemaType(Schema p) {
351350
String schemaType = super.getSchemaType(p);
352-
LOGGER.debug("debugging swager type: " + p.getType() + ", " + p.getFormat() + " => " + schemaType);
351+
LOGGER.debug("debugging OpenAPI type: " + p.getType() + ", " + p.getFormat() + " => " + schemaType);
353352
String type = null;
354353
if (typeMapping.containsKey(schemaType)) {
355354
type = typeMapping.get(schemaType);

0 commit comments

Comments
 (0)