Skip to content

Commit 6ea4534

Browse files
committed
feat(generator): rework custom generators, support java overrides
1 parent a9a6f6b commit 6ea4534

18 files changed

Lines changed: 2243 additions & 164 deletions

CustomRegionGenerator.java

Lines changed: 0 additions & 156 deletions
This file was deleted.

scripts/generate-sdk/languages/go.sh

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -109,10 +109,8 @@ generate_go_sdk() {
109109

110110
# compile custom generator
111111
cd ${ROOT_DIR}
112-
mkdir -p custom/cloud/stackit/codegen
113-
javac -cp "${GENERATOR_JAR_PATH}" CustomRegionGenerator.java
114-
mv ./*.class custom/cloud/stackit/codegen
115-
cp languages/golang/overrides/* custom/cloud/stackit/codegen
112+
mkdir -p custom
113+
javac -cp "${GENERATOR_JAR_PATH}" -d custom $(find ./scripts/generators -type f -name '*.java' | grep -v overrides)
116114

117115
warning=""
118116

@@ -168,9 +166,9 @@ generate_go_sdk() {
168166
cp "${ROOT_DIR}/languages/golang/.openapi-generator-ignore" "${SERVICES_FOLDER}/${service}/${version}api/.openapi-generator-ignore"
169167

170168
# Run the generator for Go
171-
java -Dlog.level=${GENERATOR_LOG_LEVEL} -cp "custom:scripts/bin/openapi-generator-cli.jar" \
169+
java -Dlog.level=${GENERATOR_LOG_LEVEL} -cp "custom:scripts/bin/openapi-generator-cli.jar:scripts/generators" \
172170
org.openapitools.codegen.OpenAPIGenerator generate \
173-
-g cloud.stackit.codegen.CustomRegionGenerator \
171+
-g GoGenerator \
174172
--input-spec "${service_version_json}" \
175173
--output "${SERVICES_FOLDER}/${service}/${version}api" \
176174
--package-name "${version}api" \

scripts/generate-sdk/languages/java.sh

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@ generate_java_sdk() {
7575
rm -rf "${SERVICES_FOLDER}"
7676
mkdir -p "${SERVICES_FOLDER}"
7777

78+
# compile custom generator
79+
cd ${ROOT_DIR}
80+
mkdir -p custom
81+
javac -cp "${GENERATOR_JAR_PATH}" -d custom $(find ./scripts/generators -type f -name '*.java' | grep -v overrides)
82+
7883
warning=""
7984

8085
# Generate SDK for each service
@@ -129,13 +134,15 @@ generate_java_sdk() {
129134
SERVICE_DESCRIPTION=$(cat "${service_version_json}" | jq .info.title --raw-output)
130135

131136
# Run the generator
132-
java -Dlog.level="${GENERATOR_LOG_LEVEL}" -jar "${GENERATOR_JAR_PATH}" generate \
133-
--generator-name java \
137+
java -Dlog.level="${GENERATOR_LOG_LEVEL}" -cp "custom:${GENERATOR_JAR_PATH}:scripts/generators" \
138+
org.openapitools.codegen.OpenAPIGenerator generate \
139+
--generator-name JavaGenerator \
134140
--input-spec "${service_version_json}" \
135141
--output "${SERVICES_FOLDER}/${service}" \
136142
--git-host "${GIT_HOST}" \
137143
--git-user-id "${GIT_USER_ID}" \
138144
--git-repo-id "${GIT_REPO_ID}" \
145+
--enable-post-process-file \
139146
--global-property apis,models,modelTests=false,modelDocs=false,apiDocs=false,apiTests=true,supportingFiles \
140147
--additional-properties="artifactId=${service},artifactDescription=${SERVICE_DESCRIPTION},invokerPackage=cloud.stackit.sdk.${service}.${version}api,modelPackage=cloud.stackit.sdk.${service}.${version}api.model,apiPackage=cloud.stackit.sdk.${service}.${version}api.api,serviceName=${service_pascal_case}" \
141148
--inline-schema-options "SKIP_SCHEMA_REUSE=true" \
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import io.swagger.v3.oas.models.media.Schema;
2+
import io.swagger.v3.oas.models.parameters.Parameter;
3+
import org.openapitools.codegen.CodegenParameter;
4+
import org.openapitools.codegen.CodegenProperty;
5+
import org.openapitools.codegen.languages.GoClientCodegen;
6+
import shared.PostProcessFileReplace;
7+
8+
import java.io.File;
9+
import java.util.Set;
10+
11+
public class GoGenerator extends GoClientCodegen {
12+
private static RegionFix regionFix = new RegionFix();
13+
private static PostProcessFileReplace postProcessFileReplace = new PostProcessFileReplace("overrides/go");
14+
15+
@Override
16+
public String getName() {
17+
return "GoClientCodegen";
18+
}
19+
20+
public GoGenerator(){
21+
super();
22+
System.out.println("=== Custom Go Generator initialized ===");
23+
}
24+
25+
@Override
26+
public CodegenProperty fromProperty(String name, Schema p, boolean required) {
27+
return regionFix.fromProperty(super.fromProperty(name, p, required));
28+
}
29+
30+
@Override
31+
public CodegenParameter fromParameter(Parameter parameter, Set<String> imports) {
32+
return regionFix.fromParameter(super.fromParameter(parameter, imports));
33+
}
34+
35+
@Override
36+
public void postProcessFile(File file, String fileType) {
37+
postProcessFileReplace.process(file);
38+
super.postProcessFile(file, fileType);
39+
}
40+
}
41+
42+
class RegionFix {
43+
CodegenProperty fromProperty(CodegenProperty property) {
44+
if (isRegionField(property.name)) {
45+
property.dataType = "string";
46+
property.datatypeWithEnum = "string";
47+
property.baseType = "string";
48+
49+
// Force template engine to treat this as a string
50+
property.isString = true;
51+
property.isInteger = false;
52+
property.isLong = false;
53+
property.isNumber = false;
54+
property.isNumeric = false;
55+
}
56+
return property;
57+
}
58+
59+
CodegenParameter fromParameter(CodegenParameter parameter) {
60+
if (isRegionField(parameter.paramName)) {
61+
parameter.dataType = "string";
62+
63+
// Force template engine to treat this as a string
64+
parameter.isString = true;
65+
parameter.isInteger = false;
66+
parameter.isLong = false;
67+
parameter.isNumber = false;
68+
// If it was previously an enum or another complex type, clear it
69+
parameter.isEnum = false;
70+
}
71+
return parameter;
72+
}
73+
74+
private static boolean isRegionField(String name) {
75+
if (name == null) {
76+
return false;
77+
}
78+
return name.equalsIgnoreCase("region") || name.equalsIgnoreCase("regionId");
79+
}
80+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import org.openapitools.codegen.languages.JavaClientCodegen;
2+
import shared.PostProcessFileReplace;
3+
4+
import java.io.File;
5+
6+
public class JavaGenerator extends JavaClientCodegen {
7+
private static PostProcessFileReplace postProcessFileReplace = new PostProcessFileReplace("overrides/java");
8+
9+
@Override
10+
public String getName() {
11+
return "JavaClientCodegen";
12+
}
13+
14+
public JavaGenerator(){
15+
super();
16+
System.out.println("=== Custom Java Generator initialized ===");
17+
}
18+
19+
@Override
20+
public void postProcessFile(File file, String fileType) {
21+
postProcessFileReplace.process(file);
22+
super.postProcessFile(file, fileType);
23+
}
24+
}

scripts/generators/README.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Custom Generators
2+
3+
Customized templates are painful when updating the generator. Each template has to be compared with upstream and adapted
4+
accordingly.
5+
6+
We use custom generators for introducing workarounds and fixes to not further customize our templates.
7+
8+
## Structure
9+
10+
Custom generators live in this directory. For each language with a custom generator there should be a `<lang>Generator.java`
11+
file. Each fix/workaround should live in its own class and be called from the `<lang>Generator.java` file. This allows
12+
easy removal of such fixes, when they are no longer needed.
13+
14+
Fixes, applicable to multiple languages, should be implemented in the `shared` package.
15+
16+
We try to only use the JDK and no external libraries besides the `openapi-generator` itself to keep a simple build step.
17+
18+
## How Custom Generators are executed
19+
20+
Each language with a custom generator, needs an adapted `<lang>.sh` script. Before building each service the custom
21+
generator has to be built:
22+
23+
```bash
24+
# compile custom generator
25+
cd ${ROOT_DIR}
26+
mkdir -p custom
27+
javac -cp "${GENERATOR_JAR_PATH}" -d custom $(find ./scripts/generators -type f -name '*.java' | grep -v overrides)
28+
```
29+
30+
This compiles all java files into the `custom` directory. Here we also exclude java files in the `overrides` directory.
31+
These are resources and should not be compiled in this step.
32+
33+
The generator call itself has to be adapted:
34+
35+
```bash
36+
java -Dlog.level="${GENERATOR_LOG_LEVEL}" -cp "custom:${GENERATOR_JAR_PATH}:scripts/generators" \
37+
org.openapitools.codegen.OpenAPIGenerator generate \
38+
--generator-name JavaGenerator \
39+
--enable-post-process-file \
40+
# more params
41+
```
42+
43+
We have to:
44+
- extend the classpath: `custom` makes the compiled custom generator available, `scripts/genrators` the `overrides`
45+
resources
46+
- the `generator-name` needs to be changed to the fully-qualified class name of the custom generator
47+
- `enable-post-process-file` has to be activated to support `overrides`
48+
49+
## Implemented Fixes
50+
51+
### Go: Region Param Adjustment
52+
53+
- we use `RESOLVE_INLINE_ENUMS`
54+
- this creates model classes for inline enums
55+
- this leads to a lot of different types for region enums with the same options
56+
- which makes the API hard to use
57+
- we force a string parameter instead
58+
59+
### Go/Java: Overrides
60+
61+
- there are some usages of `oneOf` schemas in our API specs, that are correct schema wise, but the generated code fails
62+
- example: `{ "type": "cidrv4", "value": string }`, `{ "type": "cidrv6", "value": string }` both, have the same shape
63+
- but they use different patterns for `value`
64+
- some language generators do not apply the pattern when validating the `oneOf` and report a false positive violation
65+
- fixing this upstream would be very involved
66+
- we fix it by replacing after generating sources, by replacing files
67+
68+
How to configure an override:
69+
- edit the `overrides.properties` file for the language in question
70+
- to override a single file you need 3 properties: `<name>.path`, `<name>.replacementPath` and `<name>.hash`
71+
- `<name>` has to be equal for all 3 properties
72+
- `path` is used to match the generated file, which should be replaced
73+
- `hash` is used to check that the generated file still has the same content as when the override was configured
74+
- `replacementPaht` specifies the file in resources, that should be used as replacemen
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)