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

feat: Custom applications for DIAL #575

Merged
merged 115 commits into from
Dec 30, 2024
Merged
Show file tree
Hide file tree
Changes from 99 commits
Commits
Show all changes
115 commits
Select commit Hold shift + click to select a range
3106b4f
AppendCustomApplicationPropertiesFn added and docker-entrypoint fixed
sergey-zinchenko Oct 30, 2024
42079da
Serialization and Deserialization for custom app schemas in Config
sergey-zinchenko Oct 30, 2024
2b39fd2
AppSchemasController based on string schemas
sergey-zinchenko Oct 31, 2024
1dd7a32
AppSchemasController based on string schemas
sergey-zinchenko Oct 31, 2024
fe79c32
JsonArrayToSchemaMapDeserializer lowering code complexity
sergey-zinchenko Oct 31, 2024
a9269f2
ConformToSchemaValidator on a bean
sergey-zinchenko Nov 4, 2024
0c6d298
ConformToMetaSchemaValidator and ApplicationsConformToSchemasValidato…
sergey-zinchenko Nov 4, 2024
4db73c3
JsonArrayToSchemaMapDeserializer and MapToJsonArraySerializer for Config
sergey-zinchenko Nov 4, 2024
037aff4
MAPPER_WITH_VALIDATION in ProxyUtil
sergey-zinchenko Nov 4, 2024
191523a
CustomApplicationPropertiesUtils used in AppendCustomApplicationPrope…
sergey-zinchenko Nov 4, 2024
69792d7
Fixes for custom application validation
sergey-zinchenko Nov 5, 2024
1da5a35
Application copy constructor
sergey-zinchenko Nov 5, 2024
4043983
ApplicationData structure change
sergey-zinchenko Nov 5, 2024
b0adf2f
filterCustomClientProperties 1st option with separate filtering of wr…
sergey-zinchenko Nov 7, 2024
627d965
filterCustomClientPropertiesWhenNoWriteAccess 2st option with combine…
sergey-zinchenko Nov 7, 2024
12e9274
changes respecting properties filtering for deployment controller fai…
sergey-zinchenko Nov 7, 2024
042f44b
CustomApplicationPropertiesUtils refactoring
sergey-zinchenko Nov 7, 2024
bbdb423
refactoring
sergey-zinchenko Nov 11, 2024
a280d1a
Custom application utils
sergey-zinchenko Nov 11, 2024
89d4102
validateCustomApplication
sergey-zinchenko Nov 12, 2024
1427e05
validateCustomApplication in controller
sergey-zinchenko Nov 12, 2024
6f84b31
CustomApplicationUtils with custom exception
sergey-zinchenko Nov 13, 2024
a98a8c7
compile fix
sergey-zinchenko Nov 13, 2024
aacddaa
CustomAppValidationException introduced
sergey-zinchenko Nov 13, 2024
58d1b25
dial-file format
sergey-zinchenko Nov 14, 2024
a6401f2
addCustomApplicationRelatedFiles in publication service
sergey-zinchenko Nov 14, 2024
1cd7376
publication with approval of custom apps works
sergey-zinchenko Nov 14, 2024
1497614
replaceLinksInJsonNode refactoring
sergey-zinchenko Nov 14, 2024
4c3d78c
fix for target path of publication and inked resources
sergey-zinchenko Nov 14, 2024
421f887
ShareService has support for linked files
sergey-zinchenko Nov 15, 2024
053d59f
CustomApplicationUtils split up
sergey-zinchenko Nov 15, 2024
c149684
CustomApplicationUtils refactoring
sergey-zinchenko Nov 15, 2024
0148a82
schemas refactoring
sergey-zinchenko Nov 15, 2024
81f60c7
MetaSchemaHolder
sergey-zinchenko Nov 15, 2024
4057279
CustomApplicationUtils changes to MetaSchemaHolder.CUSTOM_APPLICATION…
sergey-zinchenko Nov 15, 2024
99c594f
CustomApplicationUtils DIAL_META_SCHEMA fix warning of missing keywords
sergey-zinchenko Nov 15, 2024
e4afda4
removed unnecessary exceptions handling
sergey-zinchenko Nov 15, 2024
5f2c766
Merge branch 'development' into feature/custom-apps
sergey-zinchenko Nov 15, 2024
a3b9d5e
JsonArrayToSchemaMapDeserializer fixes after review
sergey-zinchenko Nov 19, 2024
8987d4a
MetaSchemaHolder fixes after review
sergey-zinchenko Nov 19, 2024
2e9628c
getCustomApplicationMetaSchema calls inside executeBlocking in AppSch…
sergey-zinchenko Nov 19, 2024
e441c14
AppSchemasController constants moved to top
sergey-zinchenko Nov 19, 2024
a018da4
removed unnecessary url decode from AppSchemaController
sergey-zinchenko Nov 19, 2024
a4e6baa
ResourceController removed duplicate of resourceService
sergey-zinchenko Nov 19, 2024
939bfc3
AppSchemaController api moved to ops
sergey-zinchenko Nov 19, 2024
09fe5c6
validateCustomApplication moved to executeBlocking. unnecessary log r…
sergey-zinchenko Nov 19, 2024
8be9aca
validateCustomApplication moved to executeBlocking application service
sergey-zinchenko Nov 19, 2024
45f6fe1
removed duplicate resourceService.hasResource(resource) check in vali…
sergey-zinchenko Nov 19, 2024
aeecbf5
refactoring of filtering after review
sergey-zinchenko Nov 20, 2024
5b1d8ff
Merge branch 'development' into feature/custom-apps
sergey-zinchenko Nov 20, 2024
c2a9127
refactoring after review. not modify endpoint while posting the app. …
sergey-zinchenko Nov 20, 2024
55d6743
fixes after review. CustomAppValidationException moved.
sergey-zinchenko Nov 20, 2024
12c9773
fixes after review. BeanDeserializerWithValidation moved.
sergey-zinchenko Nov 20, 2024
dbdd938
fixes after review. replaceLinksInJsonNode moved to utils.
sergey-zinchenko Nov 20, 2024
53cbc0a
fixes after review. validateCustomApplication moved back.
sergey-zinchenko Nov 20, 2024
e9f03f8
fixes after review. com.google.code.findbugs removed.
sergey-zinchenko Nov 20, 2024
b402557
fixes after review. getCustomApplicationSchema doesnt throws.
sergey-zinchenko Nov 20, 2024
7f1e659
fixes after review. ConformToMetaSchemaValidator param renamed
sergey-zinchenko Nov 20, 2024
38f5dcb
fixes after review. common code moved to getMetaschemaBuilder
sergey-zinchenko Nov 20, 2024
91f8823
fixes after review. targetFolder construction moved to function
sergey-zinchenko Nov 20, 2024
312bb73
fixes after review. addCustomApplicationRelatedFiles refactoring
sergey-zinchenko Nov 20, 2024
25ec1c4
fixes after review. DeploymentController apply modifications of appli…
sergey-zinchenko Nov 21, 2024
36faed5
fixes after review. CustomApplicationUtils collectors become more thr…
sergey-zinchenko Nov 21, 2024
7425119
Revert "fixes after review. CustomApplicationUtils collectors become …
sergey-zinchenko Nov 21, 2024
d280c10
fixes after review. removed synchronization in ListCollector
sergey-zinchenko Nov 21, 2024
b6018a8
Merge branch 'refs/heads/development' into feature/custom-apps
sergey-zinchenko Nov 21, 2024
8ff611f
fox after review. api rename.
sergey-zinchenko Nov 25, 2024
8a8cb72
fox after review. filter public files before add them to the publicat…
sergey-zinchenko Nov 25, 2024
2a3b5b4
fox after review. replaceCustomAppFiles moved.
sergey-zinchenko Nov 26, 2024
9c94820
change schema namings after proposal approval
sergey-zinchenko Dec 17, 2024
7d7b5fc
change schema namings after proposal approval
sergey-zinchenko Dec 19, 2024
3e361eb
Merge branch 'development' into feature/custom-apps
sergey-zinchenko Dec 19, 2024
a412602
fix misprint in exceptions of resource controller
sergey-zinchenko Dec 19, 2024
fd69776
fix for folders
sergey-zinchenko Dec 19, 2024
3823553
PublicationService changes to correct destination folder of custom ap…
sergey-zinchenko Dec 19, 2024
69077a6
PublicationService dedup of files with the same name for custom apps
sergey-zinchenko Dec 20, 2024
60d6ee0
PublicationService dedup of files with the same name for custom apps.…
sergey-zinchenko Dec 20, 2024
05bc80c
Basic test fixup
sergey-zinchenko Dec 20, 2024
364393a
fix of merge resolution error of copyHeaders
sergey-zinchenko Dec 23, 2024
25e15c6
Merge branch 'development' into feature/custom-apps
sergey-zinchenko Dec 23, 2024
dc30e4c
Dial file format and meta schema tests
sergey-zinchenko Dec 24, 2024
15e59e4
JsonArrayToSchemaMapDeserializer tests
sergey-zinchenko Dec 24, 2024
05284c1
checkstyle fix
sergey-zinchenko Dec 24, 2024
9a18418
ConformToMetaSchemaValidator tests
sergey-zinchenko Dec 24, 2024
2c6cc32
CustomApplicationsConformToTypeSchemasValidator tests
sergey-zinchenko Dec 24, 2024
39d9d99
ApplicationTypeSchemaController tests
sergey-zinchenko Dec 25, 2024
a2e7fac
CustomApplicationTypeSchemaUtils tests
sergey-zinchenko Dec 25, 2024
874baa1
more CustomApplicationTypeSchemaUtils tests
sergey-zinchenko Dec 25, 2024
d694d14
more CustomApplicationTypeSchemaUtils tests
sergey-zinchenko Dec 25, 2024
fd12ab0
more CustomApplicationTypeSchemaUtils tests
sergey-zinchenko Dec 26, 2024
a358f4a
more CustomApplicationTypeSchemaUtils tests
sergey-zinchenko Dec 26, 2024
2baa10f
more CustomApplicationTypeSchemaUtils tests
sergey-zinchenko Dec 26, 2024
30b59b8
more CustomApplicationTypeSchemaUtils tests
sergey-zinchenko Dec 26, 2024
57eb90b
more CustomApplicationTypeSchemaUtils tests
sergey-zinchenko Dec 26, 2024
a1e8194
fix for regex in controller selector
sergey-zinchenko Dec 26, 2024
e45f5b4
fixes for comments
sergey-zinchenko Dec 26, 2024
54c97a0
Merge branch 'refs/heads/development' into feature/custom-apps
sergey-zinchenko Dec 26, 2024
02cbbf8
resource api tests
sergey-zinchenko Dec 26, 2024
39a8067
resource api tests
sergey-zinchenko Dec 26, 2024
cb5bc91
ApplicationTypeSchema api tests
sergey-zinchenko Dec 27, 2024
cc3b05f
ApplicationTypeSchema api tests
sergey-zinchenko Dec 27, 2024
1bb6176
Publication api tests
sergey-zinchenko Dec 27, 2024
3104d04
Publication api tests
sergey-zinchenko Dec 27, 2024
6ee1871
Share api tests
sergey-zinchenko Dec 27, 2024
c2131ba
Share api tests
sergey-zinchenko Dec 27, 2024
cd96ebf
fix for application validation of custom app with schema to disallow …
sergey-zinchenko Dec 27, 2024
b5e68c7
fix for application sharing of custom app with schema to disallow pub…
sergey-zinchenko Dec 27, 2024
1412b22
fix for application sharing of custom app with schema to disallow sha…
sergey-zinchenko Dec 27, 2024
8a94921
application api tests
sergey-zinchenko Dec 27, 2024
245a951
AppendCustomApplicationPropertiesFn tests
sergey-zinchenko Dec 27, 2024
7f6a24d
Publication api test cleanup
sergey-zinchenko Dec 27, 2024
3a52a1c
bug fix for DeploymentFeatureController
sergey-zinchenko Dec 27, 2024
c39c60e
revert
sergey-zinchenko Dec 27, 2024
b77839a
AppendCustomApplicationPropertiesFn tests
sergey-zinchenko Dec 27, 2024
c13f872
Merge branch 'development' into feature/custom-apps
sergey-zinchenko Dec 27, 2024
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
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM gradle:8.2.0-jdk17-alpine as builder
FROM gradle:8.2.0-jdk17-alpine AS builder

sergey-zinchenko marked this conversation as resolved.
Show resolved Hide resolved
#COPY --from=cache /cache /home/gradle/.gradle
COPY --chown=gradle:gradle . /home/gradle/src
Expand Down
4 changes: 4 additions & 0 deletions config/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ dependencies {
implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.2'
testImplementation platform('org.junit:junit-bom:5.9.1')
testImplementation 'org.junit.jupiter:junit-jupiter'
testImplementation 'org.mockito:mockito-core:5.7.0'
testImplementation 'org.mockito:mockito-junit-jupiter:5.7.0'
implementation 'com.networknt:json-schema-validator:1.5.2'
implementation 'org.hibernate.validator:hibernate-validator:8.0.0.Final'
}

test {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
package com.epam.aidial.core.config;

import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

import java.net.URI;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

@Data
Expand All @@ -20,6 +25,22 @@ public class Application extends Deployment {

private Function function;

@JsonIgnore
private Map<String, Object> customProperties = new HashMap<>(); //all custom application properties will land there

@JsonAnySetter
public void setCustomProperty(String key, Object value) { //all custom application properties will land there
customProperties.put(key, value);
}

@JsonAnyGetter
public Map<String, Object> getCustomProperties() {
return customProperties;
}

@JsonAlias({"customAppSchemaId", "custom_app_schema_id"})
private URI customAppSchemaId;

@Data
@Accessors(chain = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
Expand Down Expand Up @@ -91,4 +112,30 @@ public static class Log {
private String instance;
private String content;
}

public Application() {
super();
}

public Application(Application source) {
super();
this.setName(source.getName());
this.setEndpoint(source.getEndpoint());
this.setDisplayName(source.getDisplayName());
this.setDisplayVersion(source.getDisplayVersion());
this.setIconUrl(source.getIconUrl());
this.setDescription(source.getDescription());
this.setReference(source.getReference());
this.setUserRoles(source.getUserRoles());
this.setForwardAuthToken(source.isForwardAuthToken());
this.setFeatures(source.getFeatures());
this.setInputAttachmentTypes(source.getInputAttachmentTypes());
this.setMaxInputAttachments(source.getMaxInputAttachments());
this.setDefaults(source.getDefaults());
this.setInterceptors(source.getInterceptors());
this.setDescriptionKeywords(source.getDescriptionKeywords());
this.setFunction(source.getFunction());
this.setCustomProperties(source.getCustomProperties());
this.setCustomAppSchemaId(source.getCustomAppSchemaId());
}
}
21 changes: 21 additions & 0 deletions config/src/main/java/com/epam/aidial/core/config/Config.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
package com.epam.aidial.core.config;

import com.epam.aidial.core.config.databind.JsonArrayToSchemaMapDeserializer;
import com.epam.aidial.core.config.databind.MapToJsonArraySerializer;
import com.epam.aidial.core.config.validation.ConformToMetaSchema;
import com.epam.aidial.core.config.validation.CustomApplicationsConformToTypeSchemas;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import lombok.Data;

import java.net.URI;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

@Data
@JsonIgnoreProperties(ignoreUnknown = true)
@CustomApplicationsConformToTypeSchemas(message = "All custom schema-rich applications should conform to their schemas")
public class Config {
public static final String ASSISTANT = "assistant";

Expand All @@ -24,6 +32,11 @@ public class Config {
private Set<Integer> retriableErrorCodes = Set.of();
private Map<String, Interceptor> interceptors = Map.of();

@JsonDeserialize(using = JsonArrayToSchemaMapDeserializer.class)
@JsonSerialize(using = MapToJsonArraySerializer.class)
@ConformToMetaSchema(message = "All custom application type schemas should conform to meta schema")
private Map<String, String> applicationTypeSchemas = Map.of();


public Deployment selectDeployment(String deploymentId) {
Application application = applications.get(deploymentId);
Expand All @@ -39,4 +52,12 @@ public Deployment selectDeployment(String deploymentId) {
Assistants assistants = assistant;
return assistants.getAssistants().get(deploymentId);
}


public String getCustomApplicationSchema(URI schemaId) {
if (schemaId == null) {
astsiapanay marked this conversation as resolved.
Show resolved Hide resolved
return null;
}
return applicationTypeSchemas.get(schemaId.toString());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.epam.aidial.core.config.databind;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.TreeNode;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import com.fasterxml.jackson.databind.node.NullNode;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

public class JsonArrayToSchemaMapDeserializer extends JsonDeserializer<Map<String, String>> {

@Override
public Map<String, String> deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
TreeNode tree = jsonParser.readValueAsTree();
if (!tree.isArray()) {
throw InvalidFormatException.from(jsonParser, "Expected a JSON array of schemas", tree.toString(), Map.class);
}
Map<String, String> result = new HashMap<>();
for (int i = 0; i < tree.size(); i++) {
TreeNode value = tree.get(i);
if (value instanceof NullNode) {
throw InvalidFormatException.from(jsonParser, Map.class, "Null value is not expected in schema array");
}
if (!value.isObject()) {
artsiomkorzun marked this conversation as resolved.
Show resolved Hide resolved
throw InvalidFormatException.from(jsonParser, Map.class, "Non object value is not expected in schema array");
}
JsonNode valueNode = (JsonNode) value;
if (!valueNode.has("$id")) {
throw new InvalidFormatException(jsonParser, "JSON Schema for the custom app should have $id property",
valueNode.toPrettyString(), Map.class);
}
String schemaId = valueNode.get("$id").asText();
result.put(schemaId, valueNode.toString());
}
return result;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.epam.aidial.core.config.databind;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;

import java.io.IOException;
import java.net.URI;
import java.util.Map;

public class MapToJsonArraySerializer extends JsonSerializer<Map<URI, String>> {
astsiapanay marked this conversation as resolved.
Show resolved Hide resolved

@Override
public void serialize(Map<URI, String> uriStringMap, JsonGenerator jsonGenerator,
SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeStartArray();
for (Map.Entry<URI, String> entry : uriStringMap.entrySet()) {
jsonGenerator.writeRaw(entry.getValue());
jsonGenerator.writeRaw(",");
}
jsonGenerator.writeEndArray();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.epam.aidial.core.config.validation;

import jakarta.validation.Constraint;
import jakarta.validation.ReportAsSingleViolation;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.FIELD;

@Documented
@Constraint(validatedBy = { ConformToMetaSchemaValidator.class})
@Target({ FIELD })
@Retention(RetentionPolicy.RUNTIME)
@ReportAsSingleViolation
public @interface ConformToMetaSchema {
String message() default "Schemas should comply with the meta schema";
Class<?>[] groups() default {};
Class<? extends jakarta.validation.Payload>[] payload() default {};
}


Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.epam.aidial.core.config.validation;

import com.epam.aidial.core.metaschemas.MetaSchemaHolder;
import com.networknt.schema.InputFormat;
import com.networknt.schema.JsonSchema;
import com.networknt.schema.JsonSchemaFactory;
import com.networknt.schema.SpecVersion;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;

import java.util.Map;

public class ConformToMetaSchemaValidator implements ConstraintValidator<ConformToMetaSchema, Map<String, String>> {

private static final JsonSchemaFactory SCHEMA_FACTORY = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7);
private static final JsonSchema SCHEMA = SCHEMA_FACTORY.getSchema(MetaSchemaHolder.getCustomApplicationMetaSchema());

@Override
public boolean isValid(Map<String, String> idSchemaMap, ConstraintValidatorContext context) {
if (idSchemaMap == null) {
return true;
}
for (Map.Entry<String, String> entry : idSchemaMap.entrySet()) {
if (!SCHEMA.validate(entry.getValue(), InputFormat.JSON).isEmpty()) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate())
.addBeanNode()
.inContainer(Map.class, 1)
.inIterable().atKey(entry.getKey())
.addConstraintViolation();
return false;
}
}
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.epam.aidial.core.config.validation;

import jakarta.validation.Constraint;
import jakarta.validation.ReportAsSingleViolation;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.TYPE;

@Documented
@Constraint(validatedBy = { CustomApplicationsConformToTypeSchemasValidator.class})
@Target({ TYPE })
@Retention(RetentionPolicy.RUNTIME)
@ReportAsSingleViolation
public @interface CustomApplicationsConformToTypeSchemas {
String message() default "Custom applications should comply with their schemas";
Class<?>[] groups() default {};
Class<? extends jakarta.validation.Payload>[] payload() default {};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.epam.aidial.core.config.validation;

import com.epam.aidial.core.config.Application;
import com.epam.aidial.core.config.Config;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.networknt.schema.JsonMetaSchema;
import com.networknt.schema.JsonSchema;
import com.networknt.schema.JsonSchemaFactory;
import com.networknt.schema.NonValidationKeyword;
import com.networknt.schema.SpecVersion;
import com.networknt.schema.ValidationMessage;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import lombok.extern.slf4j.Slf4j;

import java.net.URI;
import java.util.Map;
import java.util.Set;

import static com.epam.aidial.core.metaschemas.MetaSchemaHolder.getMetaschemaBuilder;

@Slf4j
public class CustomApplicationsConformToTypeSchemasValidator implements ConstraintValidator<CustomApplicationsConformToTypeSchemas, Config> {


private static final JsonMetaSchema DIAL_META_SCHEMA = getMetaschemaBuilder()
.keyword(new NonValidationKeyword("dial:meta"))
.keyword(new NonValidationKeyword("dial:file"))
.build();

@Override
public boolean isValid(Config value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
JsonSchemaFactory schemaFactory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7, builder ->
builder.schemaLoaders(loaders -> loaders.schemas(value.getApplicationTypeSchemas()))
.metaSchema(DIAL_META_SCHEMA)
);

ObjectMapper mapper = new ObjectMapper();
for (Map.Entry<String, Application> entry : value.getApplications().entrySet()) {
Application application = entry.getValue();
URI schemaId = application.getCustomAppSchemaId();
if (schemaId == null) {
continue;
}

JsonSchema schema = schemaFactory.getSchema(schemaId);
JsonNode applicationNode = mapper.valueToTree(application);
Set<ValidationMessage> validationResults = schema.validate(applicationNode);
if (!validationResults.isEmpty()) {
String logMessage = validationResults.stream()
.map(ValidationMessage::getMessage)
.reduce((a, b) -> a + ", " + b)
.orElse("Unknown validation error");
log.error("Application {} does not conform to schema {}: {}", entry.getKey(), schemaId, logMessage);
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate())
.addPropertyNode("applications")
.addContainerElementNode(entry.getKey(), Map.class, 0)
.addConstraintViolation();
return false;
}
}
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.epam.aidial.core.metaschemas;

import com.fasterxml.jackson.databind.JsonNode;
import com.networknt.schema.ExecutionContext;
import com.networknt.schema.Format;
import com.networknt.schema.JsonType;
import com.networknt.schema.TypeFactory;
import com.networknt.schema.ValidationContext;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class DialFileFormat implements Format {

private static final Pattern PATTERN = Pattern.compile("^files/([a-zA-Z0-9]+)/((?:(?:[a-zA-Z0-9_\\-.~]|%[a-zA-Z0-9]{2})+/?)+)$");

@Override
public boolean matches(ExecutionContext executionContext, ValidationContext validationContext, JsonNode value) {
JsonType nodeType = TypeFactory.getValueNodeType(value, validationContext.getConfig());
if (nodeType != JsonType.STRING) {
return false;
}
String nodeValue = value.textValue();
Matcher matcher = PATTERN.matcher(nodeValue);
return matcher.matches();
}

@Override
public String getName() {
return "dial-file-encoded";
}
}
Loading
Loading