Skip to content

Commit

Permalink
fix(java): solve oneOf using a custom generator APIC-300 (#125)
Browse files Browse the repository at this point in the history
  • Loading branch information
millotp authored Mar 4, 2022
1 parent fd0e1e2 commit c2c1cf6
Show file tree
Hide file tree
Showing 90 changed files with 1,784 additions and 1,378 deletions.
7 changes: 6 additions & 1 deletion .github/actions/cache/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ runs:
if: ${{ inputs.job == 'cts' }}
uses: actions/cache@v2
with:
path: /home/runner/work/api-clients-automation/api-clients-automation/clients/algoliasearch-client-java-2/target
path: /home/runner/work/api-clients-automation/api-clients-automation/clients/algoliasearch-client-java-2
key: ${{ runner.os }}-${{ env.CACHE_VERSION }}-java-client-${{ hashFiles('clients/algoliasearch-client-java-2/**') }}-${{ hashFiles('specs/bundled/search.yml') }}

# setup yarn
Expand All @@ -134,3 +134,8 @@ runs:
if: ${{ inputs.language == 'java' || inputs.job == 'cts' }}
shell: bash
run: curl -L "https://github.com/google/google-java-format/releases/download/v1.13.0/google-java-format-1.13.0-all-deps.jar" > /tmp/java-formatter.jar

- name: Download openapi generator jar for java (TODO REMOVE)
if: ${{ inputs.language == 'java' || inputs.job == 'cts' }}
shell: bash
run: curl -L "https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/5.4.0/openapi-generator-cli-5.4.0.jar" > /tmp/openapi-generator-cli.jar
22 changes: 2 additions & 20 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -142,12 +142,6 @@ jobs:
if: steps.cache.outputs.cache-hit != 'true' && matrix.client.name != 'algoliasearch'
run: yarn cli generate javascript ${{ matrix.client.name }}

- name: Check diff with pushed client
if: steps.cache.outputs.cache-hit != 'true'
run: |
git status
exit $(git status --porcelain ${{ matrix.client.folder }} | wc -l)
- name: Build ${{ matrix.client.name }} client
if: steps.cache.outputs.cache-hit != 'true'
run: yarn cli build clients javascript ${{ matrix.client.name }}
Expand Down Expand Up @@ -178,19 +172,13 @@ jobs:
id: cache
uses: actions/cache@v2
with:
path: '/home/runner/work/api-clients-automation/api-clients-automation/${{ matrix.client.folder }}/target'
key: ${{ runner.os }}-${{ env.CACHE_VERSION }}-java-client-${{ matrix.client.name }}-${{ hashFiles(format('{0}/**', matrix.client.folder)) }}-${{ hashFiles(format('specs/bundled/{0}.yml', matrix.client.name)) }}
path: '/home/runner/work/api-clients-automation/api-clients-automation/${{ matrix.client.folder }}'
key: ${{ runner.os }}-${{ env.CACHE_VERSION }}-java-client-${{ hashFiles(format('{0}/**', matrix.client.folder)) }}-${{ hashFiles(format('specs/bundled/{0}.yml', matrix.client.name)) }}

- name: Generate ${{ matrix.client.name }} client
if: steps.cache.outputs.cache-hit != 'true'
run: yarn cli generate java ${{ matrix.client.name }}

- name: Check diff with pushed client
if: steps.cache.outputs.cache-hit != 'true'
run: |
git status
exit $(git status --porcelain ${{ matrix.client.folder }} | wc -l)
- name: Build ${{ matrix.client.name }} client
if: steps.cache.outputs.cache-hit != 'true'
run: yarn cli build clients java ${{ matrix.client.name }}
Expand Down Expand Up @@ -221,12 +209,6 @@ jobs:
if: steps.cache.outputs.cache-hit != 'true'
run: yarn cli generate php ${{ matrix.client.name }}

- name: Check diff with pushed client
if: steps.cache.outputs.cache-hit != 'true'
run: |
git status
exit $(git status --porcelain ${{ matrix.client.folder }} | wc -l)
- name: Build ${{ matrix.client.name }} client
if: steps.cache.outputs.cache-hit != 'true'
run: yarn cli build clients php ${{ matrix.client.name }}
Expand Down
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
dist
node_modules
3 changes: 3 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ ENV JAVA_HOME=/usr/lib/jvm/default-jvm
# Java formatter
ADD https://github.com/google/google-java-format/releases/download/v1.13.0/google-java-format-1.13.0-all-deps.jar /tmp/java-formatter.jar

# openapi generator jar (TODO: REMOVE)
ADD https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/5.4.0/openapi-generator-cli-5.4.0.jar /tmp/openapi-generator-cli.jar

# PHP dependencies
RUN apk add -U composer php8 php8-tokenizer php8-dom php8-xml php8-xmlwriter

Expand Down
2 changes: 1 addition & 1 deletion clients/algoliasearch-client-java-2
File renamed without changes.
File renamed without changes.
9 changes: 9 additions & 0 deletions config/openapitools-java-cts.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"generatorName": "algolia-cts",
"templateDir": "tests/CTS/methods/requests/templates/java",
"outputDir": "tests/output/java",
"artifactId": "java-tests",
"groupId": "com.algolia",
"invokerPackage": "com.algolia",
"inputSpec": "specs/bundled/search.yml"
}
21 changes: 21 additions & 0 deletions config/openapitools-java.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"generatorName": "algolia-java",
"templateDir": "templates/java/",
"outputDir": "clients/algoliasearch-client-java-2",
"artifactId": "algoliasearch-client-java-2",
"groupId": "com.algolia",
"apiPackage": "com.algolia.search",
"invokerPackage": "com.algolia",
"modelPackage": "com.algolia.model.search",
"library": "okhttp-gson",
"inputSpec": "specs/bundled/search.yml",
"gitHost": "algolia",
"gitUserId": "algolia",
"gitRepoId": "algoliasearch-client-java-2",
"additionalProperties": {
"sourceFolder": "algoliasearch-core",
"java8": true,
"dateLibrary": "java8",
"packageName": "algoliasearch-client-java-2"
}
}
File renamed without changes.
21 changes: 21 additions & 0 deletions generators/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
plugins {
id 'java'
}

group = 'org.openapitools'
version = '1.0.0'
description = 'algolia-java-openapi-generator'
java.sourceCompatibility = JavaVersion.VERSION_1_8

repositories {
mavenCentral()
}

dependencies {
compileOnly 'org.openapitools:openapi-generator:5.4.0'
compileOnly 'org.yaml:snakeyaml:1.19'
}

tasks.withType(JavaCompile) {
options.encoding = 'UTF-8'
}
1 change: 1 addition & 0 deletions generators/settings.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
rootProject.name = 'algolia-java-openapi-generator'
173 changes: 173 additions & 0 deletions generators/src/main/java/com/algolia/codegen/AlgoliaJavaGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
package com.algolia.codegen;

import org.openapitools.codegen.*;
import org.openapitools.codegen.languages.JavaClientCodegen;
import org.openapitools.codegen.utils.ModelUtils;
import org.yaml.snakeyaml.Yaml;

import java.util.*;
import java.util.Map.Entry;
import java.io.FileInputStream;
import java.net.URL;

import io.swagger.v3.oas.models.media.Schema;

@SuppressWarnings("unchecked")
public class AlgoliaJavaGenerator extends JavaClientCodegen {
/**
* Configures a friendly name for the generator. This will be used by the
* generator
* to select the library with the -g flag.
*
* @return the friendly name for the generator
*/
@Override
public String getName() {
return "algolia-java";
}

/**
* Inject server info into the client to generate the right URL
*/
private void generateServer(Map<String, Object> client) {
String clientName = (String) client.get("pathPrefix");
Yaml yaml = new Yaml();
try {
Map<String, Object> spec = yaml.load(new FileInputStream("specs/" + clientName + "/spec.yml"));
List<Map<String, Object>> servers = (List<Map<String, Object>>) spec.get("servers");

boolean hasRegionalHost = false;
boolean fallbackToAliasHost = false;

boolean isEuHost = false;
boolean isDeHost = false;
String host = "";
String topLevelDomain = "";

for (Map<String, Object> server : servers) {
if (!server.containsKey("url")) {
throw new GenerationException("Invalid server, does not contains 'url'");
}

if (!server.containsKey("variables")) {
continue;
}

Map<String, Map<String, Object>> variables = (Map<String, Map<String, Object>>) server.get("variables");

if (!variables.containsKey("region") || !variables.get("region").containsKey("enum")) {
continue;
}
ArrayList<String> enums = (ArrayList<String>) variables.get("region").get("enum");
hasRegionalHost = true;

URL url = new URL((String) server.get("url"));

if (!fallbackToAliasHost) {
// Determine if the current URL with `region` also have an alias without
// variables.
fallbackToAliasHost = true;
}

if (enums.contains("eu")) {
isEuHost = true;
}

if (enums.contains("de")) {
isDeHost = true;
}

// This is used for hosts like `insights` that uses `.io`
String[] hostParts = url.getHost().split("\\.");
host = hostParts[0];
topLevelDomain = hostParts[hostParts.length - 1];
}
client.put("hasRegionalHost", hasRegionalHost);
client.put("fallbackToAliasHost", fallbackToAliasHost);
client.put("isEuHost", isEuHost);
client.put("isDeHost", isDeHost);
client.put("host", host);
client.put("topLevelDomain", topLevelDomain);
} catch (Exception e) {
e.printStackTrace();
}
}

/**
* Provides an opportunity to inspect and modify operation data before the code
* is generated.
*/
@Override
public Map<String, Object> postProcessOperationsWithModels(Map<String, Object> objs, List<Object> allModels) {
Map<String, Object> results = super.postProcessOperationsWithModels(objs, allModels);
Map<String, Object> client = (Map<String, Object>) results.get("operations");

generateServer(client);

return results;
}

@Override
public Map<String, Object> postProcessAllModels(Map<String, Object> objs) {
Map<String, Object> models = super.postProcessAllModels(objs);

for (Object modelContainer : models.values()) {
CodegenModel model = ((Map<String, List<Map<String, CodegenModel>>>) modelContainer).get("models").get(0)
.get("model");
if (!model.oneOf.isEmpty()) {
model.vendorExtensions.put("x-is-one-of-interface", true);
}
}

return models;
}

@Override
public Map<String, Object> postProcessSupportingFileData(Map<String, Object> objs) {
Map<String, Object> bundle = super.postProcessSupportingFileData(objs);
List<Map<String, Object>> apis = ((Map<String, List<Map<String, Object>>>) bundle.get("apiInfo")).get("apis");
for (Map<String, Object> api : apis) {
List<CodegenOperation> operations = ((Map<String, List<CodegenOperation>>) api.get("operations"))
.get("operation");

for (CodegenOperation ope : operations) {
ope.returnType = ope.returnType.replace("Map<", "HashMap<").replace("List<", "ArrayList<");
}
}
return bundle;
}

/**
* 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 an algolia-java client library.";
}

public AlgoliaJavaGenerator() {
super();

supportingFiles.add(new SupportingFile("EchoResponse.mustache",
"algoliasearch-core/com/algolia/utils/echo",
"EchoResponse.java"));

// Prevent all useless file to generate
apiTestTemplateFiles.clear();
modelTestTemplateFiles.clear();
apiDocTemplateFiles.clear();
modelDocTemplateFiles.clear();
}

@Override
public String toDefaultValue(Schema schema) {
// Replace the {} from openapi with new Object()
if (ModelUtils.isObjectSchema(schema) && schema.getDefault() != null) {
return "new Object()";
}
return super.toDefaultValue(schema);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.algolia.codegen;

public class GenerationException extends Exception {
public GenerationException(String message) {
super(message);
}
}
Loading

0 comments on commit c2c1cf6

Please sign in to comment.