Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[KOGITO-9584] Ensure nested schemas are resolved for swagger doc #3122

Merged
merged 6 commits into from
Jul 20, 2023
Merged
Show file tree
Hide file tree
Changes from 3 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 @@ -28,6 +28,7 @@

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.NullNode;
import com.networknt.schema.JsonSchema;
import com.networknt.schema.JsonSchemaFactory;
import com.networknt.schema.SpecVersion.VersionFlag;
import com.networknt.schema.ValidationMessage;
Expand All @@ -43,7 +44,7 @@ public class JsonSchemaValidator implements WorkflowModelValidator {

protected final String schemaRef;
protected final boolean failOnValidationErrors;
private final AtomicReference<JsonNode> schemaObject = new AtomicReference<>();
private final AtomicReference<JsonSchema> schemaObject = new AtomicReference<>();

public JsonSchemaValidator(String schema, boolean failOnValidationErrors) {
this.schemaRef = schema;
Expand All @@ -54,7 +55,7 @@ public JsonSchemaValidator(String schema, boolean failOnValidationErrors) {
public void validate(Map<String, Object> model) {
try {
Set<ValidationMessage> report =
JsonSchemaFactory.getInstance(VersionFlag.V4).getSchema(schemaData()).validate((JsonNode) model.getOrDefault(SWFConstants.DEFAULT_WORKFLOW_VAR, NullNode.instance));
schema().validate((JsonNode) model.getOrDefault(SWFConstants.DEFAULT_WORKFLOW_VAR, NullNode.instance));
if (!report.isEmpty()) {
StringBuilder sb = new StringBuilder("There are JsonSchema validation errors:");
report.forEach(m -> sb.append(System.lineSeparator()).append(m.getMessage()));
Expand All @@ -70,9 +71,13 @@ public void validate(Map<String, Object> model) {
}

public JsonNode schemaData() throws IOException {
JsonNode result = schemaObject.get();
return schema().getSchemaNode();
}

private JsonSchema schema() throws IOException {
JsonSchema result = schemaObject.get();
if (result == null) {
result = ObjectMapperFactory.get().readTree(readAllBytes(runtimeLoader(schemaRef)));
result = JsonSchemaFactory.getInstance(VersionFlag.V7).getSchema(ObjectMapperFactory.get().readTree(readAllBytes(runtimeLoader(schemaRef))));
schemaObject.set(result);
}
return result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,26 @@

package org.kie.kogito.serverless.workflow.parser.schema;

import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import org.eclipse.microprofile.openapi.models.media.Schema;
import org.kie.kogito.jackson.utils.ObjectMapperFactory;
import org.kie.kogito.serverless.workflow.io.URIContentLoaderFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonSetter;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;

import io.smallrye.openapi.api.constants.OpenApiConstants;
import io.smallrye.openapi.api.models.media.SchemaImpl;

/**
Expand All @@ -35,6 +46,33 @@
@JsonInclude(JsonInclude.Include.NON_NULL)
public class JsonSchemaImpl extends SchemaImpl {

private static final Logger logger = LoggerFactory.getLogger(JsonSchemaImpl.class);

@JsonSetter("$ref")
@Override
public void setRef(String ref) {
if (ref != null && !ref.startsWith("#")) {
try (InputStream is = URIContentLoaderFactory.loader(new URI(ref), Optional.empty(), Optional.empty(), Optional.empty(), null).getInputStream()) {
JsonSchemaImpl schema = ObjectMapperFactory.get().readValue(is.readAllBytes(), JsonSchemaImpl.class);
String key;
if (schema.getTitle() == null) {
key = RefSchemas.getKey();
schema.title(key);
} else {
key = schema.getTitle();
}
if (key != null) {
RefSchemas.get().put(key, schema);
}
ref = OpenApiConstants.REF_PREFIX_SCHEMA + key;
} catch (URISyntaxException | IOException e) {
// if not a valid uri, let super handle it
fjtirado marked this conversation as resolved.
Show resolved Hide resolved
logger.info("Error loading ref {}", ref, e);
}
}
super.setRef(ref);
}

@JsonDeserialize(as = JsonSchemaImpl.class)
@Override
public Schema getItems() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ private OpenApiModelSchemaGenerator() {

public static void addOpenAPIModelSchema(KogitoWorkflowProcess workflow, Map<String, Schema> schemas) {
if (workflow instanceof WorkflowProcess) {
RefSchemas.init(workflow.getId());
fjtirado marked this conversation as resolved.
Show resolved Hide resolved
WorkflowProcess workflowProcess = (WorkflowProcess) workflow;
getSchema(workflowProcess.getInputValidator()).ifPresent(v -> {
String key = getSchemaName(workflow.getId(), INPUT_SUFFIX);
Expand All @@ -67,6 +68,8 @@ public static void addOpenAPIModelSchema(KogitoWorkflowProcess workflow, Map<Str
String key = getSchemaName(workflow.getId(), OUTPUT_SUFFIX);
schemas.put(key, createOutputSchema(schemaTitle(key, v)));
});
schemas.putAll(RefSchemas.get());
RefSchemas.reset();
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright 2023 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.kie.kogito.serverless.workflow.parser.schema;

import java.util.HashMap;
import java.util.Map;

import org.eclipse.microprofile.openapi.models.media.Schema;

class RefSchemas {

private RefSchemas() {
}

private static class ThreadInfo {
private final String id;
private final Map<String, Schema> map = new HashMap<>();
private int counter;

private ThreadInfo(String id) {
this.id = id;
}
}

private static ThreadLocal<ThreadInfo> threadInfo = new ThreadLocal<>();

public static void init(String id) {
threadInfo.set(new ThreadInfo(id));
}

public static Map<String, Schema> get() {
return threadInfo.get().map;
}

public static String getKey() {
ThreadInfo t = threadInfo.get();
return t.id + "_nested_" + ++t.counter;
}

public static void reset() {
threadInfo.remove();
}
}