From 54a62a6a1131f0562b62f888960669630b918895 Mon Sep 17 00:00:00 2001
From: Katerina_Charakhovich
Date: Fri, 31 Jul 2026 15:04:26 +0200
Subject: [PATCH 1/9] initial commit
---
.../configuration/LocalizationProperties.java | 37 +
.../cfg/dao/jpa/ApplicationJpaRepository.java | 11 +-
.../cfg/dao/jpa/InterceptorJpaRepository.java | 4 +-
.../cfg/dao/jpa/ModelJpaRepository.java | 10 +-
.../cfg/dao/jpa/ToolSetJpaRepository.java | 4 +-
.../dao/mapper/ApplicationEntityMapper.java | 2 +-
.../dao/mapper/InterceptorEntityMapper.java | 3 +-
.../mapper/LocalizedValueEntityMapper.java | 50 +
.../cfg/dao/mapper/ModelEntityMapper.java | 4 +-
.../cfg/dao/mapper/ToolSetEntityMapper.java | 2 +-
.../cfg/dao/model/ApplicationEntity.java | 41 +-
.../cfg/dao/model/InterceptorEntity.java | 28 +-
.../cfg/dao/model/LocalizedValueEntity.java | 17 +
.../aidial/cfg/dao/model/ModelEntity.java | 41 +-
.../aidial/cfg/dao/model/ToolSetEntity.java | 42 +-
.../cfg/domain/mapper/AddonCoreMapper.java | 3 +
.../domain/mapper/AssistantCoreMapper.java | 3 +-
.../domain/mapper/InterceptorCoreMapper.java | 2 +-
.../domain/mapper/LocalizedValueMapper.java | 91 ++
.../cfg/domain/mapper/ToolSetCoreMapper.java | 2 +
.../aidial/cfg/domain/model/Application.java | 7 +-
.../domain/model/ApplicationComparator.java | 24 +
.../aidial/cfg/domain/model/Interceptor.java | 5 +-
.../domain/model/InterceptorComparator.java | 22 +
.../epam/aidial/cfg/domain/model/Model.java | 7 +-
.../cfg/domain/model/ModelComparator.java | 24 +
.../epam/aidial/cfg/domain/model/ToolSet.java | 7 +-
.../cfg/domain/model/ToolSetComparator.java | 22 +
.../normalizer/ApplicationNormalizer.java | 9 +-
.../domain/normalizer/ModelNormalizer.java | 9 +-
.../domain/service/ApplicationService.java | 37 +-
.../domain/service/InterceptorService.java | 8 +-
.../cfg/domain/service/ModelService.java | 32 +-
.../cfg/domain/service/ToolSetService.java | 8 +-
.../validator/DisplayFieldsValidator.java | 20 +-
.../cfg/domain/value/LocalizedValue.java | 123 ++
.../value/LocalizedValueComparator.java | 26 +
.../epam/aidial/cfg/dto/ApplicationDto.java | 10 +-
.../aidial/cfg/dto/ApplicationInfoDto.java | 5 +-
.../epam/aidial/cfg/dto/InterceptorDto.java | 8 +-
.../com/epam/aidial/cfg/dto/ModelDto.java | 10 +-
.../com/epam/aidial/cfg/dto/ModelInfoDto.java | 5 +-
.../com/epam/aidial/cfg/dto/ToolSetDto.java | 9 +-
.../databind/LocalizedValueDeserializer.java | 53 +
.../databind/LocalizedValueSerializer.java | 45 +
.../epam/aidial/cfg/service/Assertions.java | 15 +-
.../exporter/ApplicationExporter.java | 6 +-
.../exporter/InterceptorExporter.java | 6 +-
.../transfer/exporter/ModelExporter.java | 6 +-
.../transfer/exporter/ToolSetExporter.java | 6 +-
...ApplicationToLatestVersionTransformer.java | 6 +-
...InterceptorToLatestVersionTransformer.java | 6 +-
.../ModelToLatestVersionTransformer.java | 6 +-
.../ToolSetToLatestVersionTransformer.java | 6 +-
.../epam/aidial/core/config/Deployment.java | 7 +-
src/main/resources/application.properties | 3 +
.../core-config-schemas/schema-v0.47.0.json | 1101 +++++++++++++++++
...18__AddI18nColumnsToDeploymentEntities.sql | 74 ++
...18__AddI18nColumnsToDeploymentEntities.sql | 83 ++
...18__AddI18nColumnsToDeploymentEntities.sql | 74 ++
.../ApplicationValidityStateResolverTest.java | 7 +-
.../validator/ApplicationValidatorTest.java | 53 +-
.../validator/DisplayFieldsValidatorTest.java | 26 +-
.../domain/validator/ModelValidatorTest.java | 9 +-
.../validator/ToolSetValidatorTest.java | 21 +-
.../annotation/EndpointValidationTest.java | 17 +-
.../config/FunctionalTestConfiguration.java | 11 +-
.../tests/ApplicationFunctionalTest.java | 27 +-
.../ApplicationTypeSchemaFunctionalTest.java | 3 +-
.../tests/ConfigTransferFunctionalTest.java | 11 +-
...igAutoImportOnBootstrapFunctionalTest.java | 5 +-
.../tests/InterceptorFunctionalTest.java | 7 +-
.../InterceptorRunnerFunctionalTest.java | 5 +-
.../functional/tests/ModelFunctionalTest.java | 29 +-
.../tests/ToolSetFunctionalTest.java | 19 +-
.../history/ActivityAuditFunctionalTest.java | 3 +-
.../ApplicationHistoryFunctionalTest.java | 5 +-
.../InterceptorHistoryFunctionalTest.java | 7 +-
.../history/ModelHistoryFunctionalTest.java | 7 +-
.../history/ToolSetHistoryFunctionalTest.java | 5 +-
.../utils/FunctionalTestHelper.java | 17 +-
.../aidial/cfg/service/AssertionsTest.java | 49 +-
.../ConfigExportServiceSecuredImplTest.java | 11 +-
.../config/export/ConfigServiceTest.java | 4 +
.../config/transfer/JsonConfigMergerTest.java | 2 +-
.../controller/none/ConfigControllerTest.java | 5 +-
src/test/resources/import/import_preview.json | 2 +-
87 files changed, 2427 insertions(+), 277 deletions(-)
create mode 100644 src/main/java/com/epam/aidial/cfg/configuration/LocalizationProperties.java
create mode 100644 src/main/java/com/epam/aidial/cfg/dao/mapper/LocalizedValueEntityMapper.java
create mode 100644 src/main/java/com/epam/aidial/cfg/dao/model/LocalizedValueEntity.java
create mode 100644 src/main/java/com/epam/aidial/cfg/domain/mapper/LocalizedValueMapper.java
create mode 100644 src/main/java/com/epam/aidial/cfg/domain/model/ApplicationComparator.java
create mode 100644 src/main/java/com/epam/aidial/cfg/domain/model/InterceptorComparator.java
create mode 100644 src/main/java/com/epam/aidial/cfg/domain/model/ModelComparator.java
create mode 100644 src/main/java/com/epam/aidial/cfg/domain/model/ToolSetComparator.java
create mode 100644 src/main/java/com/epam/aidial/cfg/domain/value/LocalizedValue.java
create mode 100644 src/main/java/com/epam/aidial/cfg/domain/value/LocalizedValueComparator.java
create mode 100644 src/main/java/com/epam/aidial/cfg/dto/databind/LocalizedValueDeserializer.java
create mode 100644 src/main/java/com/epam/aidial/cfg/dto/databind/LocalizedValueSerializer.java
create mode 100644 src/main/resources/core-config-schemas/schema-v0.47.0.json
create mode 100644 src/main/resources/db/migration/H2/V1.118__AddI18nColumnsToDeploymentEntities.sql
create mode 100644 src/main/resources/db/migration/MS_SQL_SERVER/V1.118__AddI18nColumnsToDeploymentEntities.sql
create mode 100644 src/main/resources/db/migration/POSTGRES/V1.118__AddI18nColumnsToDeploymentEntities.sql
diff --git a/src/main/java/com/epam/aidial/cfg/configuration/LocalizationProperties.java b/src/main/java/com/epam/aidial/cfg/configuration/LocalizationProperties.java
new file mode 100644
index 000000000..74aaa1409
--- /dev/null
+++ b/src/main/java/com/epam/aidial/cfg/configuration/LocalizationProperties.java
@@ -0,0 +1,37 @@
+package com.epam.aidial.cfg.configuration;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * Configuration properties for localization settings.
+ * Defines the default locale used throughout the application for resolving {@link com.epam.aidial.cfg.domain.value.LocalizedValue}.
+ *
+ * Usage:
+ *
+ * - Database VARCHAR column storage (for sorting/querying)
+ * - Uniqueness constraint checks
+ * - Validation (checking if displayName is blank)
+ * - Fallback when requested locale not available
+ *
+ *
+ * Configuration:
+ *
+ * # application.properties
+ * localization.default.locale=${LOCALIZATION_DEFAULT_LOCALE:en}
+ *
+ *
+ * @since 0.47.0
+ */
+@Data
+@Component
+@ConfigurationProperties(prefix = "localization.default")
+public class LocalizationProperties {
+
+ /**
+ * Default locale for resolving LocalizedValue to single string.
+ * Default: "en"
+ */
+ private String locale = "en";
+}
diff --git a/src/main/java/com/epam/aidial/cfg/dao/jpa/ApplicationJpaRepository.java b/src/main/java/com/epam/aidial/cfg/dao/jpa/ApplicationJpaRepository.java
index 109ddbd7c..03d6fc31e 100644
--- a/src/main/java/com/epam/aidial/cfg/dao/jpa/ApplicationJpaRepository.java
+++ b/src/main/java/com/epam/aidial/cfg/dao/jpa/ApplicationJpaRepository.java
@@ -7,18 +7,13 @@
import java.util.List;
public interface ApplicationJpaRepository extends JpaRepository {
-
- boolean existsByDisplayNameAndDisplayVersion(String displayName, String displayVersion);
+ List findByIdIn(Collection ids);
List findByIdNotIn(Collection ids);
List findAllByValidityStateIsValidTrue();
- List findAllByOrderByDisplayNameAscDisplayVersionAscIdAsc();
-
- List findByValidityStateIsValidTrueOrderByDisplayNameAscDisplayVersionAscIdAsc();
-
- List findByIdInOrderByDisplayNameAscDisplayVersionAscIdAsc(Collection ids);
-
List findByApplicationContainerIsNotNull();
+
+ List findByDisplayVersion(String displayVersion);
}
diff --git a/src/main/java/com/epam/aidial/cfg/dao/jpa/InterceptorJpaRepository.java b/src/main/java/com/epam/aidial/cfg/dao/jpa/InterceptorJpaRepository.java
index 42e9a878d..32e13ea6d 100644
--- a/src/main/java/com/epam/aidial/cfg/dao/jpa/InterceptorJpaRepository.java
+++ b/src/main/java/com/epam/aidial/cfg/dao/jpa/InterceptorJpaRepository.java
@@ -18,7 +18,5 @@ public interface InterceptorJpaRepository extends JpaRepository findAllNames();
- List findAllByOrderByDisplayNameAscIdAsc();
-
- List findByIdInOrderByDisplayNameAscIdAsc(Collection ids);
+ List findByIdIn(Collection ids);
}
diff --git a/src/main/java/com/epam/aidial/cfg/dao/jpa/ModelJpaRepository.java b/src/main/java/com/epam/aidial/cfg/dao/jpa/ModelJpaRepository.java
index d1cc5956f..c32cc088d 100644
--- a/src/main/java/com/epam/aidial/cfg/dao/jpa/ModelJpaRepository.java
+++ b/src/main/java/com/epam/aidial/cfg/dao/jpa/ModelJpaRepository.java
@@ -9,14 +9,12 @@
public interface ModelJpaRepository extends JpaRepository {
- boolean existsByDisplayNameAndDisplayVersion(String displayName, String displayVersion);
-
- List findByIdNotIn(Collection ids);
-
@Query("SELECT m FROM ModelEntity m WHERE m.modelContainer IS NOT NULL")
List findByContainerIdIsNotNull();
- List findAllByOrderByDisplayNameAscDisplayVersionAscIdAsc();
+ List findByIdNotIn(Collection ids);
+
+ List findByDisplayVersion(String displayVersion);
- List findByIdInOrderByDisplayNameAscDisplayVersionAscIdAsc(Collection ids);
+ List findByIdIn(Collection names);
}
diff --git a/src/main/java/com/epam/aidial/cfg/dao/jpa/ToolSetJpaRepository.java b/src/main/java/com/epam/aidial/cfg/dao/jpa/ToolSetJpaRepository.java
index a831edde1..8ae69ed8f 100644
--- a/src/main/java/com/epam/aidial/cfg/dao/jpa/ToolSetJpaRepository.java
+++ b/src/main/java/com/epam/aidial/cfg/dao/jpa/ToolSetJpaRepository.java
@@ -14,7 +14,5 @@ public interface ToolSetJpaRepository extends JpaRepository findByContainerIdIsNotNull();
- List findAllByOrderByDisplayNameAscIdAsc();
-
- List findByIdInOrderByDisplayNameAscIdAsc(Collection ids);
+ List findByIdIn(Collection ids);
}
diff --git a/src/main/java/com/epam/aidial/cfg/dao/mapper/ApplicationEntityMapper.java b/src/main/java/com/epam/aidial/cfg/dao/mapper/ApplicationEntityMapper.java
index acfa76d11..fd576a8d7 100644
--- a/src/main/java/com/epam/aidial/cfg/dao/mapper/ApplicationEntityMapper.java
+++ b/src/main/java/com/epam/aidial/cfg/dao/mapper/ApplicationEntityMapper.java
@@ -27,7 +27,7 @@
@Mapper(componentModel = "spring", uses = {
DeploymentEntityMapper.class, MapPropertiesMapper.class, DependentRouteEntityMapper.class,
FeaturesEntityMapper.class, ValidityStateEntityMapper.class, DeploymentInterfacesEntityMapper.class,
- ExternalServicesEntityMapper.class
+ ExternalServicesEntityMapper.class, LocalizedValueEntityMapper.class
})
public abstract class ApplicationEntityMapper {
diff --git a/src/main/java/com/epam/aidial/cfg/dao/mapper/InterceptorEntityMapper.java b/src/main/java/com/epam/aidial/cfg/dao/mapper/InterceptorEntityMapper.java
index 1c676b7b3..b1f0414d1 100644
--- a/src/main/java/com/epam/aidial/cfg/dao/mapper/InterceptorEntityMapper.java
+++ b/src/main/java/com/epam/aidial/cfg/dao/mapper/InterceptorEntityMapper.java
@@ -25,7 +25,8 @@
import java.util.stream.Stream;
@Mapper(componentModel = "spring", uses = {
- FeaturesEntityMapper.class, MapPropertiesMapper.class, DeploymentInterfacesEntityMapper.class
+ FeaturesEntityMapper.class, MapPropertiesMapper.class, DeploymentInterfacesEntityMapper.class,
+ LocalizedValueEntityMapper.class
})
public abstract class InterceptorEntityMapper {
diff --git a/src/main/java/com/epam/aidial/cfg/dao/mapper/LocalizedValueEntityMapper.java b/src/main/java/com/epam/aidial/cfg/dao/mapper/LocalizedValueEntityMapper.java
new file mode 100644
index 000000000..c06992216
--- /dev/null
+++ b/src/main/java/com/epam/aidial/cfg/dao/mapper/LocalizedValueEntityMapper.java
@@ -0,0 +1,50 @@
+package com.epam.aidial.cfg.dao.mapper;
+
+import com.epam.aidial.cfg.dao.model.LocalizedValueEntity;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.SneakyThrows;
+import org.mapstruct.Mapper;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import java.util.Map;
+
+@Mapper(componentModel = "spring", uses = {MapPropertiesMapper.class})
+public class LocalizedValueEntityMapper {
+ @Autowired
+ protected ObjectMapper objectMapper;
+
+ @SneakyThrows
+ public LocalizedValue toDomain(LocalizedValueEntity entity) {
+ if (entity == null) {
+ return null;
+ }
+
+ if (entity.getPlainValue() != null) {
+ return LocalizedValue.of(entity.getPlainValue());
+ }
+
+ if (entity.getLocaleMap() == null) {
+ return null;
+ }
+
+ Map map = objectMapper.readValue(entity.getLocaleMap(), new TypeReference<>() {
+ });
+
+ return LocalizedValue.of(map);
+ }
+
+ @SneakyThrows
+ public LocalizedValueEntity toEntity(LocalizedValue value) {
+ if (value == null) {
+ return null;
+ }
+
+ if (!value.isMap()) {
+ return new LocalizedValueEntity(value.getPlainValue(), null);
+ }
+
+ return new LocalizedValueEntity(null, objectMapper.writeValueAsString(value.getLocaleMap()));
+ }
+}
diff --git a/src/main/java/com/epam/aidial/cfg/dao/mapper/ModelEntityMapper.java b/src/main/java/com/epam/aidial/cfg/dao/mapper/ModelEntityMapper.java
index 8da3425c0..da2b33ce1 100644
--- a/src/main/java/com/epam/aidial/cfg/dao/mapper/ModelEntityMapper.java
+++ b/src/main/java/com/epam/aidial/cfg/dao/mapper/ModelEntityMapper.java
@@ -25,8 +25,10 @@
@Mapper(componentModel = "spring", uses = {
DeploymentEntityMapper.class, MapPropertiesMapper.class, UpstreamEntityMapper.class,
- PropertiesEntityMapper.class, FeaturesEntityMapper.class, DeploymentInterfacesEntityMapper.class
+ PropertiesEntityMapper.class, FeaturesEntityMapper.class, DeploymentInterfacesEntityMapper.class,
+ LocalizedValueEntityMapper.class
})
+
public abstract class ModelEntityMapper {
@Autowired
diff --git a/src/main/java/com/epam/aidial/cfg/dao/mapper/ToolSetEntityMapper.java b/src/main/java/com/epam/aidial/cfg/dao/mapper/ToolSetEntityMapper.java
index 4261ebc3b..3c0397eb6 100755
--- a/src/main/java/com/epam/aidial/cfg/dao/mapper/ToolSetEntityMapper.java
+++ b/src/main/java/com/epam/aidial/cfg/dao/mapper/ToolSetEntityMapper.java
@@ -18,7 +18,7 @@
import java.util.List;
@Mapper(componentModel = "spring", uses = {
- DeploymentEntityMapper.class
+ DeploymentEntityMapper.class, LocalizedValueEntityMapper.class
})
public abstract class ToolSetEntityMapper {
diff --git a/src/main/java/com/epam/aidial/cfg/dao/model/ApplicationEntity.java b/src/main/java/com/epam/aidial/cfg/dao/model/ApplicationEntity.java
index 46f8d67e8..f347de083 100644
--- a/src/main/java/com/epam/aidial/cfg/dao/model/ApplicationEntity.java
+++ b/src/main/java/com/epam/aidial/cfg/dao/model/ApplicationEntity.java
@@ -1,5 +1,7 @@
package com.epam.aidial.cfg.dao.model;
+import jakarta.persistence.AttributeOverride;
+import jakarta.persistence.AttributeOverrides;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Embedded;
@@ -44,9 +46,42 @@ public class ApplicationEntity extends ValidityStateAwareEntity {
private String interfaces;
private String iconUrl;
private String reference;
- private String description;
- private String intro;
- private String displayName;
+ @Embedded
+ @AttributeOverrides({
+ @AttributeOverride(
+ name = "plainValue",
+ column = @Column(name = "description")
+ ),
+ @AttributeOverride(
+ name = "localeMap",
+ column = @Column(name = "description_i18n")
+ )
+ })
+ private LocalizedValueEntity description;
+ @Embedded
+ @AttributeOverrides({
+ @AttributeOverride(
+ name = "plainValue",
+ column = @Column(name = "intro")
+ ),
+ @AttributeOverride(
+ name = "localeMap",
+ column = @Column(name = "intro_i18n")
+ )
+ })
+ private LocalizedValueEntity intro;
+ @Embedded
+ @AttributeOverrides({
+ @AttributeOverride(
+ name = "plainValue",
+ column = @Column(name = "display_name")
+ ),
+ @AttributeOverride(
+ name = "localeMap",
+ column = @Column(name = "display_name_i18n")
+ )
+ })
+ private LocalizedValueEntity displayName;
private String displayVersion;
private List inputAttachmentTypes;
private Integer maxInputAttachments;
diff --git a/src/main/java/com/epam/aidial/cfg/dao/model/InterceptorEntity.java b/src/main/java/com/epam/aidial/cfg/dao/model/InterceptorEntity.java
index ee9ca7908..248f86e21 100644
--- a/src/main/java/com/epam/aidial/cfg/dao/model/InterceptorEntity.java
+++ b/src/main/java/com/epam/aidial/cfg/dao/model/InterceptorEntity.java
@@ -1,5 +1,7 @@
package com.epam.aidial.cfg.dao.model;
+import jakarta.persistence.AttributeOverride;
+import jakarta.persistence.AttributeOverrides;
import jakarta.persistence.Column;
import jakarta.persistence.Embedded;
import jakarta.persistence.Entity;
@@ -31,8 +33,30 @@ public class InterceptorEntity extends TimeTrackableEntity {
@Column(columnDefinition = "CLOB")
private String interfaces;
private String iconUrl;
- private String description;
- private String displayName;
+ @Embedded
+ @AttributeOverrides({
+ @AttributeOverride(
+ name = "plainValue",
+ column = @Column(name = "description")
+ ),
+ @AttributeOverride(
+ name = "localeMap",
+ column = @Column(name = "description_i18n")
+ )
+ })
+ private LocalizedValueEntity description;
+ @Embedded
+ @AttributeOverrides({
+ @AttributeOverride(
+ name = "plainValue",
+ column = @Column(name = "display_name")
+ ),
+ @AttributeOverride(
+ name = "localeMap",
+ column = @Column(name = "display_name_i18n")
+ )
+ })
+ private LocalizedValueEntity displayName;
private Boolean forwardAuthToken;
private String author;
private List dependencies;
diff --git a/src/main/java/com/epam/aidial/cfg/dao/model/LocalizedValueEntity.java b/src/main/java/com/epam/aidial/cfg/dao/model/LocalizedValueEntity.java
new file mode 100644
index 000000000..273ce1216
--- /dev/null
+++ b/src/main/java/com/epam/aidial/cfg/dao/model/LocalizedValueEntity.java
@@ -0,0 +1,17 @@
+package com.epam.aidial.cfg.dao.model;
+
+import jakarta.persistence.Embeddable;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+@Embeddable
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@EqualsAndHashCode
+public class LocalizedValueEntity {
+ private String plainValue;
+ private String localeMap;
+}
diff --git a/src/main/java/com/epam/aidial/cfg/dao/model/ModelEntity.java b/src/main/java/com/epam/aidial/cfg/dao/model/ModelEntity.java
index 08e725cdd..aab65eeef 100644
--- a/src/main/java/com/epam/aidial/cfg/dao/model/ModelEntity.java
+++ b/src/main/java/com/epam/aidial/cfg/dao/model/ModelEntity.java
@@ -1,5 +1,7 @@
package com.epam.aidial.cfg.dao.model;
+import jakarta.persistence.AttributeOverride;
+import jakarta.persistence.AttributeOverrides;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Embedded;
@@ -37,9 +39,42 @@ public class ModelEntity extends TimeTrackableEntity {
@JoinColumn(name = "deployment_name", unique = true)
@OneToOne(targetEntity = DeploymentEntity.class, fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private DeploymentEntity deployment;
- private String description;
- private String intro;
- private String displayName;
+ @Embedded
+ @AttributeOverrides({
+ @AttributeOverride(
+ name = "plainValue",
+ column = @Column(name = "description")
+ ),
+ @AttributeOverride(
+ name = "localeMap",
+ column = @Column(name = "description_i18n")
+ )
+ })
+ private LocalizedValueEntity description;
+ @Embedded
+ @AttributeOverrides({
+ @AttributeOverride(
+ name = "plainValue",
+ column = @Column(name = "intro")
+ ),
+ @AttributeOverride(
+ name = "localeMap",
+ column = @Column(name = "intro_i18n")
+ )
+ })
+ private LocalizedValueEntity intro;
+ @Embedded
+ @AttributeOverrides({
+ @AttributeOverride(
+ name = "plainValue",
+ column = @Column(name = "display_name")
+ ),
+ @AttributeOverride(
+ name = "localeMap",
+ column = @Column(name = "display_name_i18n")
+ )
+ })
+ private LocalizedValueEntity displayName;
private String displayVersion;
private String iconUrl;
private String reference;
diff --git a/src/main/java/com/epam/aidial/cfg/dao/model/ToolSetEntity.java b/src/main/java/com/epam/aidial/cfg/dao/model/ToolSetEntity.java
index eeda63ad0..ae446a194 100644
--- a/src/main/java/com/epam/aidial/cfg/dao/model/ToolSetEntity.java
+++ b/src/main/java/com/epam/aidial/cfg/dao/model/ToolSetEntity.java
@@ -1,6 +1,9 @@
package com.epam.aidial.cfg.dao.model;
+import jakarta.persistence.AttributeOverride;
+import jakarta.persistence.AttributeOverrides;
import jakarta.persistence.CascadeType;
+import jakarta.persistence.Column;
import jakarta.persistence.Embedded;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
@@ -35,9 +38,42 @@ public class ToolSetEntity extends TimeTrackableEntity {
private String endpoint;
private String iconUrl;
- private String description;
- private String intro;
- private String displayName;
+ @Embedded
+ @AttributeOverrides({
+ @AttributeOverride(
+ name = "plainValue",
+ column = @Column(name = "description")
+ ),
+ @AttributeOverride(
+ name = "localeMap",
+ column = @Column(name = "description_i18n")
+ )
+ })
+ private LocalizedValueEntity description;
+ @Embedded
+ @AttributeOverrides({
+ @AttributeOverride(
+ name = "plainValue",
+ column = @Column(name = "intro")
+ ),
+ @AttributeOverride(
+ name = "localeMap",
+ column = @Column(name = "intro_i18n")
+ )
+ })
+ private LocalizedValueEntity intro;
+ @Embedded
+ @AttributeOverrides({
+ @AttributeOverride(
+ name = "plainValue",
+ column = @Column(name = "display_name")
+ ),
+ @AttributeOverride(
+ name = "localeMap",
+ column = @Column(name = "display_name_i18n")
+ )
+ })
+ private LocalizedValueEntity displayName;
private String vendorWebsite;
private Set descriptionKeywords;
private int maxRetryAttempts = 1;
diff --git a/src/main/java/com/epam/aidial/cfg/domain/mapper/AddonCoreMapper.java b/src/main/java/com/epam/aidial/cfg/domain/mapper/AddonCoreMapper.java
index 8e74c7960..16349447d 100644
--- a/src/main/java/com/epam/aidial/cfg/domain/mapper/AddonCoreMapper.java
+++ b/src/main/java/com/epam/aidial/cfg/domain/mapper/AddonCoreMapper.java
@@ -16,6 +16,7 @@
componentModel = "spring",
uses = {
DeploymentCoreMapper.class,
+ LocalizedValueMapper.class
}
)
public abstract class AddonCoreMapper {
@@ -23,6 +24,8 @@ public abstract class AddonCoreMapper {
public abstract Addon copy(Addon addon);
@Mapping(target = "deployment", source = "coreAddon", qualifiedByName = "toDeployment")
+ @Mapping(target = "displayName", source = "coreAddon.displayName", qualifiedByName = "localizedValueToString")
+ @Mapping(target = "description", source = "coreAddon.description", qualifiedByName = "localizedValueToString")
@Mapping(target = "createdAt", ignore = true)
@Mapping(target = "updatedAt", ignore = true)
@BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
diff --git a/src/main/java/com/epam/aidial/cfg/domain/mapper/AssistantCoreMapper.java b/src/main/java/com/epam/aidial/cfg/domain/mapper/AssistantCoreMapper.java
index a83e23594..d1d50e5b4 100644
--- a/src/main/java/com/epam/aidial/cfg/domain/mapper/AssistantCoreMapper.java
+++ b/src/main/java/com/epam/aidial/cfg/domain/mapper/AssistantCoreMapper.java
@@ -14,13 +14,14 @@
import java.util.List;
-@Mapper(componentModel = "spring", uses = {DeploymentCoreMapper.class, FeatureCoreMapper.class})
+@Mapper(componentModel = "spring", uses = {DeploymentCoreMapper.class, FeatureCoreMapper.class, LocalizedValueMapper.class})
public abstract class AssistantCoreMapper {
public abstract Assistant copy(Assistant assistant);
@Mapping(target = "deployment", source = "coreAssistant", qualifiedByName = "toDeployment")
@Mapping(target = "topics", source = "coreAssistant.descriptionKeywords")
+ @Mapping(target = "displayName", source = "coreAssistant.displayName", qualifiedByName = "localizedValueToString")
@Mapping(target = "createdAt", ignore = true)
@Mapping(target = "updatedAt", ignore = true)
@BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
diff --git a/src/main/java/com/epam/aidial/cfg/domain/mapper/InterceptorCoreMapper.java b/src/main/java/com/epam/aidial/cfg/domain/mapper/InterceptorCoreMapper.java
index 22554ebe8..baf336301 100644
--- a/src/main/java/com/epam/aidial/cfg/domain/mapper/InterceptorCoreMapper.java
+++ b/src/main/java/com/epam/aidial/cfg/domain/mapper/InterceptorCoreMapper.java
@@ -5,7 +5,7 @@
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
-@Mapper(componentModel = "spring", uses = {FeatureCoreMapper.class, DeploymentInterfaceCoreMapper.class})
+@Mapper(componentModel = "spring", uses = {FeatureCoreMapper.class, DeploymentInterfaceCoreMapper.class, LocalizedValueMapper.class})
public interface InterceptorCoreMapper {
@Mapping(target = "userRoles", ignore = true)
diff --git a/src/main/java/com/epam/aidial/cfg/domain/mapper/LocalizedValueMapper.java b/src/main/java/com/epam/aidial/cfg/domain/mapper/LocalizedValueMapper.java
new file mode 100644
index 000000000..a2a744136
--- /dev/null
+++ b/src/main/java/com/epam/aidial/cfg/domain/mapper/LocalizedValueMapper.java
@@ -0,0 +1,91 @@
+package com.epam.aidial.cfg.domain.mapper;
+
+import com.epam.aidial.cfg.configuration.LocalizationProperties;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.RequiredArgsConstructor;
+import org.mapstruct.Named;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+
+/**
+ * Mapper helper for converting LocalizedValue to/from entity fields.
+ * Handles bi-directional mapping between domain LocalizedValue and entity String columns.
+ *
+ * @since 0.47.0
+ */
+@Component
+@RequiredArgsConstructor
+public class LocalizedValueMapper {
+
+ private final LocalizationProperties localizationProperties;
+ private final ObjectMapper objectMapper;
+
+ /**
+ * Resolves LocalizedValue to string for existing VARCHAR column storage.
+ * Uses default locale to determine which value to store.
+ *
+ * @param value the LocalizedValue to resolve
+ * @return resolved string value for database storage, or null
+ */
+ @Named("localizedValueToString")
+ public String toString(LocalizedValue value) {
+ if (value == null) {
+ return null;
+ }
+ return value.resolve(null, localizationProperties.getLocale());
+ }
+
+ /**
+ * Serializes LocalizedValue map to JSON for i18n column storage.
+ * Returns null for plain string values (no i18n data).
+ *
+ * @param value the LocalizedValue to serialize
+ * @return JSON string of locale map, or null if plain value
+ */
+ public String toJson(LocalizedValue value) {
+ if (value == null || !value.isMap()) {
+ return null;
+ }
+ try {
+ return objectMapper.writeValueAsString(value.getLocaleMap());
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException("Failed to serialize LocalizedValue", e);
+ }
+ }
+
+ /**
+ * Reconstructs LocalizedValue from entity VARCHAR and JSON columns.
+ * Prefers JSON column if present, falls back to plain value.
+ *
+ * @param plainValue resolved string from VARCHAR column
+ * @param jsonValue JSON string from i18n column
+ * @return reconstructed LocalizedValue, or null
+ */
+ public LocalizedValue fromStrings(String plainValue, String jsonValue) {
+ if (jsonValue != null && !jsonValue.isBlank()) {
+ try {
+ @SuppressWarnings("unchecked")
+ Map map = objectMapper.readValue(jsonValue, Map.class);
+ return LocalizedValue.of(map);
+ } catch (JsonProcessingException e) {
+ // Fallback to plain value if JSON parse fails
+ }
+ }
+ return plainValue != null ? LocalizedValue.of(plainValue) : null;
+ }
+
+ /**
+ * Maps plain String to LocalizedValue for Core mappers.
+ * Used when mapping from String displayName to LocalizedValue displayName.
+ *
+ * @param value plain string value
+ * @return LocalizedValue wrapping the string, or null
+ */
+ @Named("fromString")
+ public LocalizedValue fromString(String value) {
+ return value != null ? LocalizedValue.of(value) : null;
+ }
+}
diff --git a/src/main/java/com/epam/aidial/cfg/domain/mapper/ToolSetCoreMapper.java b/src/main/java/com/epam/aidial/cfg/domain/mapper/ToolSetCoreMapper.java
index e3269e7c0..734d90aa8 100644
--- a/src/main/java/com/epam/aidial/cfg/domain/mapper/ToolSetCoreMapper.java
+++ b/src/main/java/com/epam/aidial/cfg/domain/mapper/ToolSetCoreMapper.java
@@ -37,12 +37,14 @@ public abstract class ToolSetCoreMapper {
@Mapping(target = "interfaces", ignore = true)
@Mapping(target = "authSettings", source = "deployment.authSettings")
@Mapping(target = "forwardPerRequestKey", source = "deployment.forwardPerRequestKey")
+ @Mapping(target = "description", expression = "java(toolSet.getDescription())")
public abstract CoreToolSet mapToolSet(ToolSet toolSet);
@Mapping(target = "deployment", source = "coreToolSet", qualifiedByName = "toSecuredResource")
@Mapping(target = "source", ignore = true)
@Mapping(target = "createdAt", ignore = true)
@Mapping(target = "updatedAt", ignore = true)
+ @Mapping(target = "description", expression = "java(coreToolSet.getDescription())")
@BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
public abstract ToolSet mapToolSet(CoreToolSet coreToolSet, @Context List roleLimits, @MappingTarget ToolSet toolSet);
diff --git a/src/main/java/com/epam/aidial/cfg/domain/model/Application.java b/src/main/java/com/epam/aidial/cfg/domain/model/Application.java
index 7cf374b98..00c2e273f 100644
--- a/src/main/java/com/epam/aidial/cfg/domain/model/Application.java
+++ b/src/main/java/com/epam/aidial/cfg/domain/model/Application.java
@@ -2,6 +2,7 @@
import com.epam.aidial.cfg.domain.model.route.DependentRoute;
import com.epam.aidial.cfg.domain.model.source.ApplicationSource;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -22,9 +23,9 @@ public class Application extends RoleBased {
private Map interfaces;
private String iconUrl;
private String reference;
- private String description;
- private String intro;
- private String displayName;
+ private LocalizedValue description;
+ private LocalizedValue intro;
+ private LocalizedValue displayName;
private String displayVersion;
private List inputAttachmentTypes;
private Integer maxInputAttachments;
diff --git a/src/main/java/com/epam/aidial/cfg/domain/model/ApplicationComparator.java b/src/main/java/com/epam/aidial/cfg/domain/model/ApplicationComparator.java
new file mode 100644
index 000000000..302f46eee
--- /dev/null
+++ b/src/main/java/com/epam/aidial/cfg/domain/model/ApplicationComparator.java
@@ -0,0 +1,24 @@
+package com.epam.aidial.cfg.domain.model;
+
+import com.epam.aidial.cfg.domain.value.LocalizedValueComparator;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Component;
+
+import java.util.Comparator;
+
+@RequiredArgsConstructor
+@Component
+public class ApplicationComparator implements Comparator {
+
+ private final LocalizedValueComparator localizedValueComparator;
+
+ @Override
+ public int compare(Application left, Application right) {
+ return Comparator
+ .comparing(Application::getDisplayName, localizedValueComparator)
+ .thenComparing(Application::getDisplayVersion,
+ Comparator.nullsFirst(String.CASE_INSENSITIVE_ORDER))
+ .thenComparing(t -> t.getDeployment().getName())
+ .compare(left, right);
+ }
+}
diff --git a/src/main/java/com/epam/aidial/cfg/domain/model/Interceptor.java b/src/main/java/com/epam/aidial/cfg/domain/model/Interceptor.java
index c80108f91..247d2d1bb 100644
--- a/src/main/java/com/epam/aidial/cfg/domain/model/Interceptor.java
+++ b/src/main/java/com/epam/aidial/cfg/domain/model/Interceptor.java
@@ -1,6 +1,7 @@
package com.epam.aidial.cfg.domain.model;
import com.epam.aidial.cfg.domain.model.source.InterceptorSource;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
import lombok.Data;
import java.util.List;
@@ -14,9 +15,9 @@ public class Interceptor {
private String endpoint;
private Map interfaces;
private String iconUrl;
- private String description;
+ private LocalizedValue description;
private Set topics;
- private String displayName;
+ private LocalizedValue displayName;
private Boolean forwardAuthToken;
private String author;
private Long createdAt;
diff --git a/src/main/java/com/epam/aidial/cfg/domain/model/InterceptorComparator.java b/src/main/java/com/epam/aidial/cfg/domain/model/InterceptorComparator.java
new file mode 100644
index 000000000..72fb2bf75
--- /dev/null
+++ b/src/main/java/com/epam/aidial/cfg/domain/model/InterceptorComparator.java
@@ -0,0 +1,22 @@
+package com.epam.aidial.cfg.domain.model;
+
+import com.epam.aidial.cfg.domain.value.LocalizedValueComparator;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Component;
+
+import java.util.Comparator;
+
+@RequiredArgsConstructor
+@Component
+public class InterceptorComparator implements Comparator {
+
+ private final LocalizedValueComparator localizedValueComparator;
+
+ @Override
+ public int compare(Interceptor left, Interceptor right) {
+ return Comparator
+ .comparing(Interceptor::getDisplayName, localizedValueComparator)
+ .thenComparing(Interceptor::getName)
+ .compare(left, right);
+ }
+}
diff --git a/src/main/java/com/epam/aidial/cfg/domain/model/Model.java b/src/main/java/com/epam/aidial/cfg/domain/model/Model.java
index 4ee4f911f..48013127d 100644
--- a/src/main/java/com/epam/aidial/cfg/domain/model/Model.java
+++ b/src/main/java/com/epam/aidial/cfg/domain/model/Model.java
@@ -1,6 +1,7 @@
package com.epam.aidial.cfg.domain.model;
import com.epam.aidial.cfg.domain.model.source.ModelSource;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@@ -17,9 +18,9 @@ public class Model extends RoleBased {
private String endpoint;
private String responsesEndpoint;
private Map interfaces;
- private String description;
- private String intro;
- private String displayName;
+ private LocalizedValue description;
+ private LocalizedValue intro;
+ private LocalizedValue displayName;
private String displayVersion;
private String iconUrl;
private String reference;
diff --git a/src/main/java/com/epam/aidial/cfg/domain/model/ModelComparator.java b/src/main/java/com/epam/aidial/cfg/domain/model/ModelComparator.java
new file mode 100644
index 000000000..4c9e6eb7e
--- /dev/null
+++ b/src/main/java/com/epam/aidial/cfg/domain/model/ModelComparator.java
@@ -0,0 +1,24 @@
+package com.epam.aidial.cfg.domain.model;
+
+import com.epam.aidial.cfg.domain.value.LocalizedValueComparator;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Component;
+
+import java.util.Comparator;
+
+@RequiredArgsConstructor
+@Component
+public class ModelComparator implements Comparator {
+
+ private final LocalizedValueComparator localizedValueComparator;
+
+ @Override
+ public int compare(Model left, Model right) {
+ return Comparator
+ .comparing(Model::getDisplayName, localizedValueComparator)
+ .thenComparing(Model::getDisplayVersion,
+ Comparator.nullsFirst(String.CASE_INSENSITIVE_ORDER))
+ .thenComparing(t -> t.getDeployment().getName())
+ .compare(left, right);
+ }
+}
diff --git a/src/main/java/com/epam/aidial/cfg/domain/model/ToolSet.java b/src/main/java/com/epam/aidial/cfg/domain/model/ToolSet.java
index ba10d8d15..932113e17 100644
--- a/src/main/java/com/epam/aidial/cfg/domain/model/ToolSet.java
+++ b/src/main/java/com/epam/aidial/cfg/domain/model/ToolSet.java
@@ -1,6 +1,7 @@
package com.epam.aidial.cfg.domain.model;
import com.epam.aidial.cfg.domain.model.source.ToolSetSource;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@@ -16,9 +17,9 @@ public class ToolSet extends SecuredRoleBased {
private String endpoint;
private String iconUrl;
- private String description;
- private String intro;
- private String displayName;
+ private LocalizedValue description;
+ private LocalizedValue intro;
+ private LocalizedValue displayName;
private String vendorWebsite;
private Set descriptionKeywords;
private Integer maxRetryAttempts;
diff --git a/src/main/java/com/epam/aidial/cfg/domain/model/ToolSetComparator.java b/src/main/java/com/epam/aidial/cfg/domain/model/ToolSetComparator.java
new file mode 100644
index 000000000..e3455e56b
--- /dev/null
+++ b/src/main/java/com/epam/aidial/cfg/domain/model/ToolSetComparator.java
@@ -0,0 +1,22 @@
+package com.epam.aidial.cfg.domain.model;
+
+import com.epam.aidial.cfg.domain.value.LocalizedValueComparator;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Component;
+
+import java.util.Comparator;
+
+@RequiredArgsConstructor
+@Component
+public class ToolSetComparator implements Comparator {
+
+ private final LocalizedValueComparator localizedValueComparator;
+
+ @Override
+ public int compare(ToolSet left, ToolSet right) {
+ return Comparator
+ .comparing(ToolSet::getDisplayName, localizedValueComparator)
+ .thenComparing(t -> t.getDeployment().getName())
+ .compare(left, right);
+ }
+}
diff --git a/src/main/java/com/epam/aidial/cfg/domain/normalizer/ApplicationNormalizer.java b/src/main/java/com/epam/aidial/cfg/domain/normalizer/ApplicationNormalizer.java
index a9df88703..6e0f592f7 100644
--- a/src/main/java/com/epam/aidial/cfg/domain/normalizer/ApplicationNormalizer.java
+++ b/src/main/java/com/epam/aidial/cfg/domain/normalizer/ApplicationNormalizer.java
@@ -1,20 +1,21 @@
package com.epam.aidial.cfg.domain.normalizer;
import com.epam.aidial.cfg.domain.model.Application;
+import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
@Component
+@RequiredArgsConstructor
public class ApplicationNormalizer {
public void normalize(Application application) {
- setDisplayVersionToNullIfBlank(application);
+ normalizeDisplayVersion(application);
setEndpointToNullIfBlank(application);
}
- private void setDisplayVersionToNullIfBlank(Application application) {
- String displayVersion = application.getDisplayVersion();
- if (StringUtils.isBlank(displayVersion)) {
+ private void normalizeDisplayVersion(Application application) {
+ if (StringUtils.isBlank(application.getDisplayVersion())) {
application.setDisplayVersion(null);
}
}
diff --git a/src/main/java/com/epam/aidial/cfg/domain/normalizer/ModelNormalizer.java b/src/main/java/com/epam/aidial/cfg/domain/normalizer/ModelNormalizer.java
index 8f0a675db..59f11ede3 100644
--- a/src/main/java/com/epam/aidial/cfg/domain/normalizer/ModelNormalizer.java
+++ b/src/main/java/com/epam/aidial/cfg/domain/normalizer/ModelNormalizer.java
@@ -1,19 +1,20 @@
package com.epam.aidial.cfg.domain.normalizer;
import com.epam.aidial.cfg.domain.model.Model;
+import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
@Component
+@RequiredArgsConstructor
public class ModelNormalizer {
public void normalize(Model model) {
- setDisplayVersionToNullIfBlank(model);
+ normalizeDisplayVersion(model);
}
- private void setDisplayVersionToNullIfBlank(Model model) {
- String displayVersion = model.getDisplayVersion();
- if (StringUtils.isBlank(displayVersion)) {
+ private void normalizeDisplayVersion(Model model) {
+ if (StringUtils.isBlank(model.getDisplayVersion())) {
model.setDisplayVersion(null);
}
}
diff --git a/src/main/java/com/epam/aidial/cfg/domain/service/ApplicationService.java b/src/main/java/com/epam/aidial/cfg/domain/service/ApplicationService.java
index 87a68784f..5f39306f1 100644
--- a/src/main/java/com/epam/aidial/cfg/domain/service/ApplicationService.java
+++ b/src/main/java/com/epam/aidial/cfg/domain/service/ApplicationService.java
@@ -1,18 +1,21 @@
package com.epam.aidial.cfg.domain.service;
import com.epam.aidial.cfg.client.ToolsClient;
+import com.epam.aidial.cfg.configuration.LocalizationProperties;
import com.epam.aidial.cfg.configuration.logging.LogExecution;
import com.epam.aidial.cfg.dao.jpa.ApplicationJpaRepository;
import com.epam.aidial.cfg.dao.jpa.ApplicationTypeSchemaJpaRepository;
import com.epam.aidial.cfg.dao.jpa.InterceptorJpaRepository;
import com.epam.aidial.cfg.dao.mapper.ApplicationContainerEntityMapper;
import com.epam.aidial.cfg.dao.mapper.ApplicationEntityMapper;
+import com.epam.aidial.cfg.dao.mapper.LocalizedValueEntityMapper;
import com.epam.aidial.cfg.dao.model.ApplicationContainerEntity;
import com.epam.aidial.cfg.dao.model.ApplicationEntity;
import com.epam.aidial.cfg.dao.model.ApplicationTypeSchemaEntity;
import com.epam.aidial.cfg.dao.model.InterceptorEntity;
import com.epam.aidial.cfg.dao.model.RoleEntity;
import com.epam.aidial.cfg.domain.model.Application;
+import com.epam.aidial.cfg.domain.model.ApplicationComparator;
import com.epam.aidial.cfg.domain.model.Deployment;
import com.epam.aidial.cfg.domain.model.DomainObjectWithHash;
import com.epam.aidial.cfg.domain.model.RoleBased;
@@ -78,6 +81,9 @@ public class ApplicationService {
private final ContainerEndpointResolver endpointResolver;
private final ApplicationRefreshService applicationRefreshService;
private final ToolsClient toolsClient;
+ private final LocalizationProperties localizationProperties;
+ private final ApplicationComparator applicationComparator;
+ private final LocalizedValueEntityMapper localizedValueEntityMapper;
@Transactional(readOnly = true)
public Collection getAllApplications() {
@@ -95,16 +101,18 @@ public Collection getAllByNames(List names) {
@Transactional(readOnly = true)
public List getAllApplicationsOrderedByDisplayNameAscDisplayVersionAscNameAsc() {
- return applicationJpaRepository.findAllByOrderByDisplayNameAscDisplayVersionAscIdAsc().stream()
+ return applicationJpaRepository.findAll().stream()
.map(mapper::toDomain)
- .collect(Collectors.toList());
+ .sorted(applicationComparator)
+ .toList();
}
@Transactional(readOnly = true)
public List getAllValidApplicationsOrderedByDisplayNameAscDisplayVersionAscNameAsc() {
- return applicationJpaRepository.findByValidityStateIsValidTrueOrderByDisplayNameAscDisplayVersionAscIdAsc().stream()
+ return applicationJpaRepository.findAllByValidityStateIsValidTrue().stream()
.map(mapper::toDomain)
- .collect(Collectors.toList());
+ .sorted(applicationComparator)
+ .toList();
}
@Transactional(readOnly = true)
@@ -112,8 +120,9 @@ public List getAllByNamesOrderedByDisplayNameAscDisplayVersionAscNa
if (CollectionUtils.isEmpty(names)) {
return Collections.emptyList();
}
- return applicationJpaRepository.findByIdInOrderByDisplayNameAscDisplayVersionAscIdAsc(names).stream()
+ return applicationJpaRepository.findByIdIn(names).stream()
.map(mapper::toDomain)
+ .sorted(applicationComparator)
.collect(Collectors.toList());
}
@@ -142,7 +151,9 @@ public void createApplication(Application application) {
applicationValidator.validateCreation(application);
deploymentService.assertDeploymentNotExists(application.getDeployment().getName());
deploymentService.assertInterceptorNotExists(application.getDeployment().getName());
- assertNotExists(application.getDisplayName(), application.getDisplayVersion());
+ String displayName = application.getDisplayName() != null ? application.getDisplayName().resolve(null, localizationProperties.getLocale()) : null;
+ String displayVersion = application.getDisplayVersion();
+ assertNotExists(displayName, displayVersion);
resolveEndpointsIfContainerSource(application);
Optional.of(application)
.map(domainModel -> toEntity(domainModel, new ApplicationEntity()))
@@ -346,15 +357,23 @@ private void assertExists(String name) {
}
private void assertNotExists(String displayName, String displayVersion) {
- if ((displayName != null || displayVersion != null) && applicationJpaRepository.existsByDisplayNameAndDisplayVersion(displayName, displayVersion)) {
+ if ((displayName != null || displayVersion != null) && applicationJpaRepository
+ .findByDisplayVersion(displayVersion)
+ .stream()
+ .map(mapper::toDomain)
+ .anyMatch(application ->
+ Objects.equals(
+ application.getDisplayName().resolve(null, localizationProperties.getLocale()),
+ displayName))) {
throw new EntityAlreadyExistsException("Application with display name: '" + displayName + "' and display version: '" + displayVersion + "' already exists");
}
}
private void assertNewApplicationDisplayNameAndDisplayVersion(ApplicationEntity entity, Application domain) {
- String displayName = entity.getDisplayName();
+ var entityDisplayName = localizedValueEntityMapper.toDomain(entity.getDisplayName());
+ String displayName = entityDisplayName != null ? entityDisplayName.resolve(null, localizationProperties.getLocale()) : null;
String displayVersion = entity.getDisplayVersion();
- String newDisplayName = domain.getDisplayName();
+ String newDisplayName = domain.getDisplayName() != null ? domain.getDisplayName().resolve(null, localizationProperties.getLocale()) : null;
String newDisplayVersion = domain.getDisplayVersion();
if (!Objects.equals(displayName, newDisplayName) || !Objects.equals(displayVersion, newDisplayVersion)) {
diff --git a/src/main/java/com/epam/aidial/cfg/domain/service/InterceptorService.java b/src/main/java/com/epam/aidial/cfg/domain/service/InterceptorService.java
index 120984303..5adb14f42 100644
--- a/src/main/java/com/epam/aidial/cfg/domain/service/InterceptorService.java
+++ b/src/main/java/com/epam/aidial/cfg/domain/service/InterceptorService.java
@@ -16,6 +16,7 @@
import com.epam.aidial.cfg.dao.model.ModelEntity;
import com.epam.aidial.cfg.domain.model.DomainObjectWithHash;
import com.epam.aidial.cfg.domain.model.Interceptor;
+import com.epam.aidial.cfg.domain.model.InterceptorComparator;
import com.epam.aidial.cfg.domain.model.source.InterceptorContainerSource;
import com.epam.aidial.cfg.domain.model.source.InterceptorRunnerSource;
import com.epam.aidial.cfg.domain.model.source.InterceptorSource;
@@ -70,6 +71,7 @@ public class InterceptorService {
private final GlobalSettingsService globalSettingsService;
private final DeploymentService deploymentService;
private final HashCalculator calculator;
+ private final InterceptorComparator interceptorComparator;
@Transactional(readOnly = true)
public void ensureExists(String interceptorName) {
@@ -94,8 +96,9 @@ public Collection getAllByNames(List names) {
@Transactional(readOnly = true)
public List getAllOrderedByDisplayNameAscNameAsc() {
- return interceptorJpaRepository.findAllByOrderByDisplayNameAscIdAsc().stream()
+ return interceptorJpaRepository.findAll().stream()
.map(mapper::toDomain)
+ .sorted(interceptorComparator)
.collect(Collectors.toList());
}
@@ -104,8 +107,9 @@ public List getAllByNamesOrderedByDisplayNameAscNameAsc(Collection<
if (CollectionUtils.isEmpty(names)) {
return Collections.emptyList();
}
- return interceptorJpaRepository.findByIdInOrderByDisplayNameAscIdAsc(names).stream()
+ return interceptorJpaRepository.findByIdIn(names).stream()
.map(mapper::toDomain)
+ .sorted(interceptorComparator)
.collect(Collectors.toList());
}
diff --git a/src/main/java/com/epam/aidial/cfg/domain/service/ModelService.java b/src/main/java/com/epam/aidial/cfg/domain/service/ModelService.java
index 32bc25af4..d8cf23ac6 100644
--- a/src/main/java/com/epam/aidial/cfg/domain/service/ModelService.java
+++ b/src/main/java/com/epam/aidial/cfg/domain/service/ModelService.java
@@ -1,9 +1,11 @@
package com.epam.aidial.cfg.domain.service;
+import com.epam.aidial.cfg.configuration.LocalizationProperties;
import com.epam.aidial.cfg.configuration.logging.LogExecution;
import com.epam.aidial.cfg.dao.jpa.AdapterJpaRepository;
import com.epam.aidial.cfg.dao.jpa.InterceptorJpaRepository;
import com.epam.aidial.cfg.dao.jpa.ModelJpaRepository;
+import com.epam.aidial.cfg.dao.mapper.LocalizedValueEntityMapper;
import com.epam.aidial.cfg.dao.mapper.ModelContainerEntityMapper;
import com.epam.aidial.cfg.dao.mapper.ModelEntityMapper;
import com.epam.aidial.cfg.dao.model.AdapterEntity;
@@ -14,6 +16,7 @@
import com.epam.aidial.cfg.domain.model.Deployment;
import com.epam.aidial.cfg.domain.model.DomainObjectWithHash;
import com.epam.aidial.cfg.domain.model.Model;
+import com.epam.aidial.cfg.domain.model.ModelComparator;
import com.epam.aidial.cfg.domain.model.RoleBased;
import com.epam.aidial.cfg.domain.model.RoleLimit;
import com.epam.aidial.cfg.domain.model.source.ModelAdapterSource;
@@ -69,6 +72,9 @@ public class ModelService {
private final ModelRefreshService refreshService;
private final ContainerEndpointResolver endpointResolver;
private final HashCalculator calculator;
+ private final LocalizationProperties localizationProperties;
+ private final ModelComparator modelComparator;
+ private final LocalizedValueEntityMapper localizedValueEntityMapper;
@Transactional(readOnly = true)
public Collection getAll() {
@@ -86,9 +92,10 @@ public Collection getAllByNames(List names) {
@Transactional(readOnly = true)
public List getAllOrderedByDisplayNameAscDisplayVersionAscNameAsc() {
- return modelJpaRepository.findAllByOrderByDisplayNameAscDisplayVersionAscIdAsc().stream()
+ return modelJpaRepository.findAll().stream()
.map(mapper::toDomain)
- .collect(Collectors.toList());
+ .sorted(modelComparator)
+ .toList();
}
@Transactional(readOnly = true)
@@ -96,8 +103,9 @@ public List getAllByNamesOrderedByDisplayNameAscDisplayVersionAscNameAsc(
if (CollectionUtils.isEmpty(names)) {
return Collections.emptyList();
}
- return modelJpaRepository.findByIdInOrderByDisplayNameAscDisplayVersionAscIdAsc(names).stream()
+ return modelJpaRepository.findByIdIn(names).stream()
.map(mapper::toDomain)
+ .sorted(modelComparator)
.collect(Collectors.toList());
}
@@ -130,7 +138,9 @@ public void createModel(Model model) {
modelValidator.validateCreation(model);
deploymentService.assertDeploymentNotExists(model.getDeployment().getName());
deploymentService.assertInterceptorNotExists(model.getDeployment().getName());
- assertNotExists(model.getDisplayName(), model.getDisplayVersion());
+ String displayName = model.getDisplayName() != null ? model.getDisplayName().resolve(null, localizationProperties.getLocale()) : null;
+ String displayVersion = model.getDisplayVersion();
+ assertNotExists(displayName, displayVersion);
resolveEndpointsIfContainerSource(model);
Optional.of(model)
.map(domainModel -> toEntity(domainModel, new ModelEntity()))
@@ -260,15 +270,23 @@ private void assertExists(String name) {
}
private void assertNotExists(String displayName, String displayVersion) {
- if ((displayName != null || displayVersion != null) && modelJpaRepository.existsByDisplayNameAndDisplayVersion(displayName, displayVersion)) {
+ if ((displayName != null || displayVersion != null) && modelJpaRepository
+ .findByDisplayVersion(displayVersion)
+ .stream()
+ .map(mapper::toDomain)
+ .anyMatch(application ->
+ Objects.equals(
+ application.getDisplayName().resolve(null, localizationProperties.getLocale()),
+ displayName))) {
throw new EntityAlreadyExistsException("Model with display name: '" + displayName + "' and display version: '" + displayVersion + "' already exists");
}
}
private void assertNewModelDisplayNameAndDisplayVersion(ModelEntity entity, Model domain) {
- String displayName = entity.getDisplayName();
+ var entityDisplayName = localizedValueEntityMapper.toDomain(entity.getDisplayName());
+ String displayName = entityDisplayName != null ? entityDisplayName.resolve(null, localizationProperties.getLocale()) : null;
String displayVersion = entity.getDisplayVersion();
- String newDisplayName = domain.getDisplayName();
+ String newDisplayName = domain.getDisplayName() != null ? domain.getDisplayName().resolve(null, localizationProperties.getLocale()) : null;
String newDisplayVersion = domain.getDisplayVersion();
if (!Objects.equals(displayName, newDisplayName) || !Objects.equals(displayVersion, newDisplayVersion)) {
diff --git a/src/main/java/com/epam/aidial/cfg/domain/service/ToolSetService.java b/src/main/java/com/epam/aidial/cfg/domain/service/ToolSetService.java
index fb1790528..60e9d14cd 100644
--- a/src/main/java/com/epam/aidial/cfg/domain/service/ToolSetService.java
+++ b/src/main/java/com/epam/aidial/cfg/domain/service/ToolSetService.java
@@ -15,6 +15,7 @@
import com.epam.aidial.cfg.domain.model.SecuredResource;
import com.epam.aidial.cfg.domain.model.SecuredRoleBased;
import com.epam.aidial.cfg.domain.model.ToolSet;
+import com.epam.aidial.cfg.domain.model.ToolSetComparator;
import com.epam.aidial.cfg.domain.model.source.ToolSetContainerSource;
import com.epam.aidial.cfg.domain.model.source.ToolSetMcpRegistrySource;
import com.epam.aidial.cfg.domain.model.source.ToolSetSource;
@@ -66,6 +67,7 @@ public class ToolSetService {
private final HashCalculator calculator;
private final CoreClientUrlUtils coreClientUrlUtils;
private final ToolsClient toolsClient;
+ private final ToolSetComparator toolSetComparator;
@Transactional(readOnly = true)
public Collection getAll() {
@@ -83,8 +85,9 @@ public Collection getAllByNames(List names) {
@Transactional(readOnly = true)
public List getAllOrderedByDisplayNameAscNameAsc() {
- return toolSetJpaRepository.findAllByOrderByDisplayNameAscIdAsc().stream()
+ return toolSetJpaRepository.findAll().stream()
.map(mapper::toDomain)
+ .sorted(toolSetComparator)
.collect(Collectors.toList());
}
@@ -93,8 +96,9 @@ public List getAllByNamesOrderedByDisplayNameAscNameAsc(CollectionExamples:
+ *
+ * // Plain string
+ * LocalizedValue.of("GPT-4")
+ *
+ * // Locale map
+ * LocalizedValue.of(Map.of("en", "GPT-4", "fr", "GPT-4", "de", "GPT-4"))
+ *
+ *
+ * @since 0.47.0
+ */
+@Getter
+@JsonDeserialize(using = LocalizedValueDeserializer.class)
+@JsonSerialize(using = LocalizedValueSerializer.class)
+@EqualsAndHashCode
+public final class LocalizedValue {
+
+ private final String plainValue;
+ private final Map localeMap;
+
+ private LocalizedValue(String plainValue, Map localeMap) {
+ this.plainValue = plainValue;
+ this.localeMap = localeMap;
+ }
+
+ /**
+ * Creates a LocalizedValue from a plain string.
+ *
+ * @param value the string value
+ * @return LocalizedValue instance or null if value is null
+ */
+ public static LocalizedValue of(String value) {
+ return value == null ? null : new LocalizedValue(value, null);
+ }
+
+ /**
+ * Creates a LocalizedValue from a map of locale codes to localized strings.
+ *
+ * @param localeMap map of locale codes (e.g., "en") to their localized strings
+ * @return LocalizedValue instance or null if localeMap is null
+ */
+ public static LocalizedValue of(Map localeMap) {
+ return localeMap == null ? null : new LocalizedValue(null, new LinkedHashMap<>(localeMap));
+ }
+
+ /**
+ * Checks if this LocalizedValue stores a map of locales.
+ *
+ * @return true if this is a locale map, false if it's a plain string
+ */
+ @JsonIgnore
+ public boolean isMap() {
+ return localeMap != null;
+ }
+
+ /**
+ * Collapses a single-entry map keyed by {@code defaultLocale} down to a plain string, leaving
+ * every other shape (plain string, empty/multi-entry map) untouched. Used to keep
+ * single-language deployments wire-identical to their pre-localization representation.
+ *
+ * @param defaultLocale the default locale code (e.g., "en")
+ * @return normalized LocalizedValue perhaps the same instance or a new plain string instance
+ */
+ public LocalizedValue normalize(String defaultLocale) {
+ if (localeMap != null && localeMap.size() == 1 && localeMap.containsKey(defaultLocale)) {
+ return LocalizedValue.of(localeMap.get(defaultLocale));
+ }
+ return this;
+ }
+
+ /**
+ * Resolves a definite string value for internal (non-localized) Core usages: the requested
+ * locale if present, else the default locale, else the first available value.
+ *
+ * @param locale the requested locale code
+ * @param defaultLocale the fallback locale code (e.g., "en")
+ * @return resolved string value, or null if no values available
+ */
+ public String resolve(String locale, String defaultLocale) {
+ if (plainValue != null) {
+ return plainValue;
+ }
+ if (locale != null && localeMap.containsKey(locale)) {
+ return localeMap.get(locale);
+ }
+ if (defaultLocale != null && localeMap.containsKey(defaultLocale)) {
+ return localeMap.get(defaultLocale);
+ }
+ return localeMap.values().stream().findFirst().orElse(null);
+ }
+
+ @Override
+ public String toString() {
+ return plainValue != null ? plainValue : String.valueOf(localeMap);
+ }
+
+ @JsonIgnore
+ public boolean isPlain() {
+ return plainValue != null;
+ }
+
+ @JsonIgnore
+ public boolean isLocalized() {
+ return localeMap != null;
+ }
+}
diff --git a/src/main/java/com/epam/aidial/cfg/domain/value/LocalizedValueComparator.java b/src/main/java/com/epam/aidial/cfg/domain/value/LocalizedValueComparator.java
new file mode 100644
index 000000000..8bdc35e48
--- /dev/null
+++ b/src/main/java/com/epam/aidial/cfg/domain/value/LocalizedValueComparator.java
@@ -0,0 +1,26 @@
+package com.epam.aidial.cfg.domain.value;
+
+import com.epam.aidial.cfg.configuration.LocalizationProperties;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Component;
+
+import java.util.Comparator;
+
+@Component
+@RequiredArgsConstructor
+public class LocalizedValueComparator
+ implements Comparator {
+
+ private final LocalizationProperties localizationProperties;
+
+ @Override
+ public int compare(LocalizedValue left,
+ LocalizedValue right) {
+
+ return Comparator
+ .nullsFirst(String.CASE_INSENSITIVE_ORDER)
+ .compare(
+ left == null ? null : left.resolve(null, localizationProperties.getLocale()),
+ right == null ? null : right.resolve(null, localizationProperties.getLocale()));
+ }
+}
diff --git a/src/main/java/com/epam/aidial/cfg/dto/ApplicationDto.java b/src/main/java/com/epam/aidial/cfg/dto/ApplicationDto.java
index 2f255ffdc..c0001d6ff 100644
--- a/src/main/java/com/epam/aidial/cfg/dto/ApplicationDto.java
+++ b/src/main/java/com/epam/aidial/cfg/dto/ApplicationDto.java
@@ -1,11 +1,13 @@
package com.epam.aidial.cfg.dto;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
import com.epam.aidial.cfg.dto.route.DependentRouteDto;
import com.epam.aidial.cfg.dto.source.ApplicationSourceDto;
import com.epam.aidial.cfg.dto.validation.annotation.Endpoint;
import com.fasterxml.jackson.annotation.JsonAlias;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -28,12 +30,12 @@ public class ApplicationDto extends RoleBasedDto {
private String responsesEndpoint;
@Valid
private Map interfaces;
- @NotBlank(message = "DisplayName is required")
- private String displayName;
+ @NotNull(message = "DisplayName is required")
+ private LocalizedValue displayName;
private String displayVersion;
private String iconUrl;
- private String description;
- private String intro;
+ private LocalizedValue description;
+ private LocalizedValue intro;
private String reference;
private Boolean forwardAuthToken;
private FeaturesDto features = new FeaturesDto();
diff --git a/src/main/java/com/epam/aidial/cfg/dto/ApplicationInfoDto.java b/src/main/java/com/epam/aidial/cfg/dto/ApplicationInfoDto.java
index 8e82fce19..464d0b762 100644
--- a/src/main/java/com/epam/aidial/cfg/dto/ApplicationInfoDto.java
+++ b/src/main/java/com/epam/aidial/cfg/dto/ApplicationInfoDto.java
@@ -1,5 +1,6 @@
package com.epam.aidial.cfg.dto;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
import com.epam.aidial.cfg.dto.source.ApplicationSourceDto;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -12,10 +13,10 @@ public class ApplicationInfoDto {
private String name;
private String endpoint;
- private String displayName;
+ private LocalizedValue displayName;
private String displayVersion;
private String iconUrl;
- private String description;
+ private LocalizedValue description;
private Boolean forwardAuthToken;
private List inputAttachmentTypes;
private Integer maxInputAttachments;
diff --git a/src/main/java/com/epam/aidial/cfg/dto/InterceptorDto.java b/src/main/java/com/epam/aidial/cfg/dto/InterceptorDto.java
index 21878484c..b61e833ac 100644
--- a/src/main/java/com/epam/aidial/cfg/dto/InterceptorDto.java
+++ b/src/main/java/com/epam/aidial/cfg/dto/InterceptorDto.java
@@ -1,9 +1,11 @@
package com.epam.aidial.cfg.dto;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
import com.epam.aidial.cfg.dto.source.InterceptorSourceDto;
import com.epam.aidial.cfg.dto.validation.annotation.Endpoint;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -22,9 +24,9 @@ public class InterceptorDto {
@Valid
private Map interfaces;
private String iconUrl;
- private String description;
- @NotBlank(message = "DisplayName is required")
- private String displayName;
+ private LocalizedValue description;
+ @NotNull(message = "DisplayName is required")
+ private LocalizedValue displayName;
private Boolean forwardAuthToken;
private List entities;
private List applicationTypeSchemas;
diff --git a/src/main/java/com/epam/aidial/cfg/dto/ModelDto.java b/src/main/java/com/epam/aidial/cfg/dto/ModelDto.java
index 4cd067653..4b3368439 100644
--- a/src/main/java/com/epam/aidial/cfg/dto/ModelDto.java
+++ b/src/main/java/com/epam/aidial/cfg/dto/ModelDto.java
@@ -1,9 +1,11 @@
package com.epam.aidial.cfg.dto;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
import com.epam.aidial.cfg.dto.source.ModelSourceDto;
import com.epam.aidial.cfg.dto.validation.annotation.Endpoint;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -28,12 +30,12 @@ public class ModelDto extends RoleBasedDto {
private String responsesEndpoint;
@Valid
private Map interfaces;
- @NotBlank(message = "DisplayName is required")
- private String displayName;
+ @NotNull(message = "DisplayName is required")
+ private LocalizedValue displayName;
private String displayVersion;
private String iconUrl;
- private String description;
- private String intro;
+ private LocalizedValue description;
+ private LocalizedValue intro;
private String reference;
private Boolean forwardAuthToken;
private FeaturesDto features = new FeaturesDto();
diff --git a/src/main/java/com/epam/aidial/cfg/dto/ModelInfoDto.java b/src/main/java/com/epam/aidial/cfg/dto/ModelInfoDto.java
index c13028abc..fb535b9d8 100644
--- a/src/main/java/com/epam/aidial/cfg/dto/ModelInfoDto.java
+++ b/src/main/java/com/epam/aidial/cfg/dto/ModelInfoDto.java
@@ -1,5 +1,6 @@
package com.epam.aidial.cfg.dto;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
import lombok.Data;
import java.time.Instant;
@@ -11,8 +12,8 @@ public class ModelInfoDto {
private String name;
private String overrideName;
private String endpoint;
- private String displayName;
- private String displayVersion;
+ private LocalizedValue displayName;
+ private LocalizedValue displayVersion;
private String description;
private Boolean forwardAuthToken;
private List inputAttachmentTypes;
diff --git a/src/main/java/com/epam/aidial/cfg/dto/ToolSetDto.java b/src/main/java/com/epam/aidial/cfg/dto/ToolSetDto.java
index 40ff3eadc..e6ac5df6e 100644
--- a/src/main/java/com/epam/aidial/cfg/dto/ToolSetDto.java
+++ b/src/main/java/com/epam/aidial/cfg/dto/ToolSetDto.java
@@ -1,5 +1,6 @@
package com.epam.aidial.cfg.dto;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
import com.epam.aidial.cfg.dto.source.ToolSetSourceDto;
import com.epam.aidial.cfg.dto.validation.annotation.Endpoint;
import jakarta.validation.constraints.NotBlank;
@@ -21,10 +22,10 @@ public class ToolSetDto extends RoleBasedDto {
@Endpoint
private String endpoint;
private String iconUrl;
- private String description;
- private String intro;
- @NotBlank(message = "DisplayName is required")
- private String displayName;
+ private LocalizedValue description;
+ private LocalizedValue intro;
+ @NotNull(message = "DisplayName is required")
+ private LocalizedValue displayName;
private String vendorWebsite;
private TreeSet descriptionKeywords;
@Positive(message = "Max retry attempts should be greater than 0")
diff --git a/src/main/java/com/epam/aidial/cfg/dto/databind/LocalizedValueDeserializer.java b/src/main/java/com/epam/aidial/cfg/dto/databind/LocalizedValueDeserializer.java
new file mode 100644
index 000000000..ff7c2e806
--- /dev/null
+++ b/src/main/java/com/epam/aidial/cfg/dto/databind/LocalizedValueDeserializer.java
@@ -0,0 +1,53 @@
+package com.epam.aidial.cfg.dto.databind;
+
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+
+import java.io.IOException;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Custom Jackson deserializer for {@link LocalizedValue}.
+ * Supports both plain string and locale map formats for backward compatibility.
+ *
+ * Accepted formats:
+ *
+ * // Plain string
+ * "GPT-4"
+ *
+ * // Locale map
+ * {"en": "GPT-4", "fr": "GPT-4", "de": "GPT-4"}
+ *
+ *
+ * @since 0.47.0
+ */
+public class LocalizedValueDeserializer extends JsonDeserializer {
+
+ @Override
+ public LocalizedValue deserialize(JsonParser p, DeserializationContext ctx) throws IOException {
+ if (p.getCurrentToken() == JsonToken.VALUE_STRING) {
+ return LocalizedValue.of(p.getValueAsString());
+ }
+
+ if (p.getCurrentToken() == JsonToken.START_OBJECT) {
+ Map localeMap = new LinkedHashMap<>();
+ while (p.nextToken() != JsonToken.END_OBJECT) {
+ String locale = p.getCurrentName();
+ p.nextToken();
+ localeMap.put(locale, p.getValueAsString());
+ }
+ return LocalizedValue.of(localeMap);
+ }
+
+ if (p.getCurrentToken() == JsonToken.VALUE_NULL) {
+ return null;
+ }
+
+ return ctx.reportInputMismatch(LocalizedValue.class,
+ "Expected a string or a locale-to-value object, got %s", p.getCurrentToken());
+ }
+}
diff --git a/src/main/java/com/epam/aidial/cfg/dto/databind/LocalizedValueSerializer.java b/src/main/java/com/epam/aidial/cfg/dto/databind/LocalizedValueSerializer.java
new file mode 100644
index 000000000..007a72b6e
--- /dev/null
+++ b/src/main/java/com/epam/aidial/cfg/dto/databind/LocalizedValueSerializer.java
@@ -0,0 +1,45 @@
+package com.epam.aidial.cfg.dto.databind;
+
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+
+import java.io.IOException;
+
+/**
+ * Custom Jackson serializer for {@link LocalizedValue}.
+ * Outputs either a plain string or a locale map object depending on the internal representation.
+ *
+ * Output formats:
+ *
+ * // Plain string or single-locale map (collapsed)
+ * "GPT-4"
+ *
+ * // Multi-locale map
+ * {"en": "GPT-4", "fr": "GPT-4", "de": "GPT-4"}
+ *
+ *
+ * @since 0.47.0
+ */
+public class LocalizedValueSerializer extends JsonSerializer {
+
+ @Override
+ public void serialize(LocalizedValue value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
+ if (!value.isMap()) {
+ gen.writeString(value.getPlainValue());
+ return;
+ }
+
+ if (value.getLocaleMap().size() == 1) {
+ gen.writeString(value.getLocaleMap().values().iterator().next());
+ return;
+ }
+
+ gen.writeStartObject();
+ for (var entry : value.getLocaleMap().entrySet()) {
+ gen.writeStringField(entry.getKey(), entry.getValue());
+ }
+ gen.writeEndObject();
+ }
+}
diff --git a/src/main/java/com/epam/aidial/cfg/service/Assertions.java b/src/main/java/com/epam/aidial/cfg/service/Assertions.java
index eaf9147cc..2c19cc5d7 100644
--- a/src/main/java/com/epam/aidial/cfg/service/Assertions.java
+++ b/src/main/java/com/epam/aidial/cfg/service/Assertions.java
@@ -1,29 +1,36 @@
package com.epam.aidial.cfg.service;
+import com.epam.aidial.cfg.configuration.LocalizationProperties;
import com.epam.aidial.cfg.exception.ValidationException;
import com.epam.aidial.core.config.Deployment;
+import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.Objects;
+@Component
+@RequiredArgsConstructor
public class Assertions {
- public static void assertUniqueDisplayName(Map deployments, String newDisplayName) {
+ private final LocalizationProperties localizationProperties;
+
+ public void assertUniqueDisplayName(Map deployments, String newDisplayName) {
if (StringUtils.isEmpty(newDisplayName)) {
return;
}
for (Map.Entry entry : deployments.entrySet()) {
String entityName = entry.getKey();
Deployment deployment = entry.getValue();
- String currentValue = deployment.getDisplayName();
+ String currentValue = deployment.getDisplayName() != null ? deployment.getDisplayName().resolve(null, localizationProperties.getLocale()) : null;
if (StringUtils.isNotEmpty(currentValue) && Objects.equals(newDisplayName, currentValue)) {
throw new ValidationException("displayName is not unique. displayName '" + newDisplayName + "' already exists (" + entityName + ")");
}
}
}
- public static void assertUniqueDisplayNameAndVersion(Map deployments, String newDisplayName, String newDisplayVersion) {
+ public void assertUniqueDisplayNameAndVersion(Map deployments, String newDisplayName, String newDisplayVersion) {
if (StringUtils.isEmpty(newDisplayName) && StringUtils.isEmpty(newDisplayVersion)) {
return;
}
@@ -32,7 +39,7 @@ public static void assertUniqueDisplayNameAndVersion(Map getApplications(ExportRequest request) {
if (request instanceof FullExportRequest fullExportRequest) {
@@ -58,9 +60,9 @@ protected Collection preview(ExportRequest request) {
return getApplications(request).values().stream()
.map(component -> ExportComponentInfo.builder()
.name(component.getDeployment().getName())
- .displayName(component.getDisplayName())
+ .displayName(component.getDisplayName() != null ? component.getDisplayName().resolve(null, localizationProperties.getLocale()) : null)
.displayVersion(component.getDisplayVersion())
- .description(component.getDescription())
+ .description(component.getDescription() != null ? component.getDescription().resolve(null, localizationProperties.getLocale()) : null)
.type(ExportConfigComponentType.APPLICATION)
.build())
.collect(Collectors.toList());
diff --git a/src/main/java/com/epam/aidial/cfg/service/config/transfer/exporter/InterceptorExporter.java b/src/main/java/com/epam/aidial/cfg/service/config/transfer/exporter/InterceptorExporter.java
index 8ecfb37bd..9e09a9cf7 100644
--- a/src/main/java/com/epam/aidial/cfg/service/config/transfer/exporter/InterceptorExporter.java
+++ b/src/main/java/com/epam/aidial/cfg/service/config/transfer/exporter/InterceptorExporter.java
@@ -1,5 +1,6 @@
package com.epam.aidial.cfg.service.config.transfer.exporter;
+import com.epam.aidial.cfg.configuration.LocalizationProperties;
import com.epam.aidial.cfg.configuration.logging.LogExecution;
import com.epam.aidial.cfg.domain.model.ExportComponentInfo;
import com.epam.aidial.cfg.domain.model.ExportConfigComponentType;
@@ -32,6 +33,7 @@ public class InterceptorExporter {
private final InterceptorService interceptorService;
private final InterceptorRunnerService interceptorRunnerService;
+ private final LocalizationProperties localizationProperties;
protected Map getInterceptors(ExportRequest request) {
if (request instanceof FullExportRequest fullExportRequest) {
@@ -86,8 +88,8 @@ protected Collection preview(ExportRequest request) {
return getInterceptors(request).values().stream()
.map(component -> ExportComponentInfo.builder()
.name(component.getName())
- .displayName(component.getDisplayName())
- .description(component.getDescription())
+ .displayName(component.getDisplayName() != null ? component.getDisplayName().resolve(null, localizationProperties.getLocale()) : null)
+ .description(component.getDisplayName() != null ? component.getDisplayName().resolve(null, localizationProperties.getLocale()) : null)
.type(ExportConfigComponentType.INTERCEPTOR)
.build())
.collect(Collectors.toList());
diff --git a/src/main/java/com/epam/aidial/cfg/service/config/transfer/exporter/ModelExporter.java b/src/main/java/com/epam/aidial/cfg/service/config/transfer/exporter/ModelExporter.java
index e2222a51d..66fb00c66 100644
--- a/src/main/java/com/epam/aidial/cfg/service/config/transfer/exporter/ModelExporter.java
+++ b/src/main/java/com/epam/aidial/cfg/service/config/transfer/exporter/ModelExporter.java
@@ -1,5 +1,6 @@
package com.epam.aidial.cfg.service.config.transfer.exporter;
+import com.epam.aidial.cfg.configuration.LocalizationProperties;
import com.epam.aidial.cfg.configuration.logging.LogExecution;
import com.epam.aidial.cfg.domain.model.ExportComponentInfo;
import com.epam.aidial.cfg.domain.model.ExportConfigComponentType;
@@ -35,6 +36,7 @@ public class ModelExporter {
private final ModelService modelService;
private final AdapterService adapterService;
+ private final LocalizationProperties localizationProperties;
protected Map getModels(ExportRequest request) {
if (request instanceof FullExportRequest fullExportRequest) {
@@ -78,9 +80,9 @@ protected Collection preview(ExportRequest request) {
return getModels(request).values().stream()
.map(component -> ExportComponentInfo.builder()
.name(component.getDeployment().getName())
- .displayName(component.getDisplayName())
+ .displayName(component.getDisplayName() != null ? component.getDisplayName().resolve(null, localizationProperties.getLocale()) : null)
.displayVersion(component.getDisplayVersion())
- .description(component.getDescription())
+ .description(component.getDescription() != null ? component.getDescription().resolve(null, localizationProperties.getLocale()) : null)
.type(ExportConfigComponentType.MODEL)
.build())
.collect(Collectors.toList());
diff --git a/src/main/java/com/epam/aidial/cfg/service/config/transfer/exporter/ToolSetExporter.java b/src/main/java/com/epam/aidial/cfg/service/config/transfer/exporter/ToolSetExporter.java
index a06e53c0b..2094edeee 100644
--- a/src/main/java/com/epam/aidial/cfg/service/config/transfer/exporter/ToolSetExporter.java
+++ b/src/main/java/com/epam/aidial/cfg/service/config/transfer/exporter/ToolSetExporter.java
@@ -1,5 +1,6 @@
package com.epam.aidial.cfg.service.config.transfer.exporter;
+import com.epam.aidial.cfg.configuration.LocalizationProperties;
import com.epam.aidial.cfg.configuration.logging.LogExecution;
import com.epam.aidial.cfg.domain.model.ExportComponentInfo;
import com.epam.aidial.cfg.domain.model.ExportConfigComponentType;
@@ -28,6 +29,7 @@
public class ToolSetExporter {
private final ToolSetService toolSetService;
+ private final LocalizationProperties localizationProperties;
protected Map getToolSets(ExportRequest request) {
if (request instanceof FullExportRequest fullExportRequest) {
@@ -69,9 +71,9 @@ protected Collection preview(ExportRequest request) {
return getToolSets(request).values().stream()
.map(component -> ExportComponentInfo.builder()
.name(component.getDeployment().getName())
- .displayName(component.getDisplayName())
+ .displayName(component.getDisplayName() != null ? component.getDisplayName().resolve(null, localizationProperties.getLocale()) : null)
.type(ExportConfigComponentType.TOOL_SET)
- .description(component.getDescription())
+ .description(component.getDescription() != null ? component.getDescription().resolve(null, localizationProperties.getLocale()) : null)
.build())
.collect(Collectors.toList());
}
diff --git a/src/main/java/com/epam/aidial/cfg/service/config/transfer/importer/compatibility/backward/transformer/ApplicationToLatestVersionTransformer.java b/src/main/java/com/epam/aidial/cfg/service/config/transfer/importer/compatibility/backward/transformer/ApplicationToLatestVersionTransformer.java
index c271b9c16..23501d634 100644
--- a/src/main/java/com/epam/aidial/cfg/service/config/transfer/importer/compatibility/backward/transformer/ApplicationToLatestVersionTransformer.java
+++ b/src/main/java/com/epam/aidial/cfg/service/config/transfer/importer/compatibility/backward/transformer/ApplicationToLatestVersionTransformer.java
@@ -4,8 +4,8 @@
import com.epam.aidial.cfg.domain.model.Application;
import com.epam.aidial.cfg.domain.model.source.ApplicationEndpointsSource;
import com.epam.aidial.cfg.domain.model.source.ApplicationSchemaSource;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
import org.apache.commons.collections4.MapUtils;
-import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import java.util.Map;
@@ -20,8 +20,8 @@ public void transform(Map applications) {
}
private void transform(Application application) {
- if (StringUtils.isBlank(application.getDisplayName())) {
- application.setDisplayName(application.getDeployment().getName());
+ if (application.getDisplayName() == null) {
+ application.setDisplayName(LocalizedValue.of(application.getDeployment().getName()));
}
if (application.getSource() == null) {
if (application.getApplicationTypeSchemaId() != null) {
diff --git a/src/main/java/com/epam/aidial/cfg/service/config/transfer/importer/compatibility/backward/transformer/InterceptorToLatestVersionTransformer.java b/src/main/java/com/epam/aidial/cfg/service/config/transfer/importer/compatibility/backward/transformer/InterceptorToLatestVersionTransformer.java
index d490630f2..7d3e4a91b 100644
--- a/src/main/java/com/epam/aidial/cfg/service/config/transfer/importer/compatibility/backward/transformer/InterceptorToLatestVersionTransformer.java
+++ b/src/main/java/com/epam/aidial/cfg/service/config/transfer/importer/compatibility/backward/transformer/InterceptorToLatestVersionTransformer.java
@@ -2,8 +2,8 @@
import com.epam.aidial.cfg.configuration.logging.LogExecution;
import com.epam.aidial.cfg.domain.model.Interceptor;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
import org.apache.commons.collections4.MapUtils;
-import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import java.util.Map;
@@ -18,8 +18,8 @@ public void transform(Map interceptors) {
}
private void transform(Interceptor interceptor) {
- if (StringUtils.isBlank(interceptor.getDisplayName())) {
- interceptor.setDisplayName(interceptor.getName());
+ if (interceptor.getDisplayName() == null) {
+ interceptor.setDisplayName(LocalizedValue.of(interceptor.getName()));
}
}
}
diff --git a/src/main/java/com/epam/aidial/cfg/service/config/transfer/importer/compatibility/backward/transformer/ModelToLatestVersionTransformer.java b/src/main/java/com/epam/aidial/cfg/service/config/transfer/importer/compatibility/backward/transformer/ModelToLatestVersionTransformer.java
index 08138e08b..c55d0b0e4 100644
--- a/src/main/java/com/epam/aidial/cfg/service/config/transfer/importer/compatibility/backward/transformer/ModelToLatestVersionTransformer.java
+++ b/src/main/java/com/epam/aidial/cfg/service/config/transfer/importer/compatibility/backward/transformer/ModelToLatestVersionTransformer.java
@@ -2,8 +2,8 @@
import com.epam.aidial.cfg.configuration.logging.LogExecution;
import com.epam.aidial.cfg.domain.model.Model;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
import org.apache.commons.collections4.MapUtils;
-import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import java.util.Map;
@@ -18,8 +18,8 @@ public void transform(Map models) {
}
private void transform(Model model) {
- if (StringUtils.isBlank(model.getDisplayName())) {
- model.setDisplayName(model.getDeployment().getName());
+ if (model.getDisplayName() == null) {
+ model.setDisplayName(LocalizedValue.of(model.getDeployment().getName()));
}
}
}
diff --git a/src/main/java/com/epam/aidial/cfg/service/config/transfer/importer/compatibility/backward/transformer/ToolSetToLatestVersionTransformer.java b/src/main/java/com/epam/aidial/cfg/service/config/transfer/importer/compatibility/backward/transformer/ToolSetToLatestVersionTransformer.java
index 4eaffb1fa..7572f9af6 100644
--- a/src/main/java/com/epam/aidial/cfg/service/config/transfer/importer/compatibility/backward/transformer/ToolSetToLatestVersionTransformer.java
+++ b/src/main/java/com/epam/aidial/cfg/service/config/transfer/importer/compatibility/backward/transformer/ToolSetToLatestVersionTransformer.java
@@ -2,8 +2,8 @@
import com.epam.aidial.cfg.configuration.logging.LogExecution;
import com.epam.aidial.cfg.domain.model.ToolSet;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
import org.apache.commons.collections4.MapUtils;
-import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import java.util.Map;
@@ -18,8 +18,8 @@ public void transform(Map toolSets) {
}
private void transform(ToolSet toolSet) {
- if (StringUtils.isBlank(toolSet.getDisplayName())) {
- toolSet.setDisplayName(toolSet.getDeployment().getName());
+ if (toolSet.getDisplayName() == null) {
+ toolSet.setDisplayName(LocalizedValue.of(toolSet.getDeployment().getName()));
}
}
}
diff --git a/src/main/java/com/epam/aidial/core/config/Deployment.java b/src/main/java/com/epam/aidial/core/config/Deployment.java
index 60a2944fe..1f34b71ca 100644
--- a/src/main/java/com/epam/aidial/core/config/Deployment.java
+++ b/src/main/java/com/epam/aidial/core/config/Deployment.java
@@ -1,5 +1,6 @@
package com.epam.aidial.core.config;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
@@ -17,14 +18,14 @@ public abstract class Deployment extends RoleBasedEntity {
@JsonAlias({"responsesEndpoint", "responses_endpoint"})
private String responsesEndpoint; //0.42.0
@JsonAlias({"displayName", "display_name"})
- private String displayName;
+ private LocalizedValue displayName; //0.47.0
@JsonAlias({"displayVersion", "display_version"})
private String displayVersion;
@JsonAlias({"iconUrl", "icon_url"})
private String iconUrl;
- private String description;
+ private LocalizedValue description; //0.47.0
private String reference;
- private String intro; //0.46.0
+ private LocalizedValue intro; //0.47.0
/**
* Forward Http header with authorization token when request is sent to deployment.
* Authorization token is NOT forwarded by default.
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 274ba1986..116705384 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -195,6 +195,9 @@ files.import.consecutiveErrorsThreshold=${FILES_IMPORT_CONSECUTIVE_ERRORS_THRESH
features.flags.addonsSupported=${ADDONS_SUPPORTED:false}
features.flags.assistantsSupported=${ASSISTANTS_SUPPORTED:false}
+# Localization settings
+# Default locale used for resolving LocalizedValue in validation, storage, and sorting
+localization.default.locale=${LOCALIZATION_DEFAULT_LOCALE:en}
plugins.deployment.manager.client.url=${PLUGINS_DEPLOYMENT_MANAGER_CLIENT_URL:url-placeholder}
plugins.deployment.manager.cache.expiration.interval=${PLUGINS_DEPLOYMENT_MANAGER_CACHE_EXPIRATION_INTERVAL_MS:300000}
diff --git a/src/main/resources/core-config-schemas/schema-v0.47.0.json b/src/main/resources/core-config-schemas/schema-v0.47.0.json
new file mode 100644
index 000000000..c5b5510dd
--- /dev/null
+++ b/src/main/resources/core-config-schemas/schema-v0.47.0.json
@@ -0,0 +1,1101 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "title": "Core Config Schema v0.47.0",
+ "type": "object",
+ "properties": {
+ "models": {
+ "type": "object",
+ "patternProperties": {
+ "^[a-zA-Z0-9_-]+$": {
+ "$ref": "#/definitions/Model"
+ }
+ }
+ },
+ "applications": {
+ "type": "object",
+ "patternProperties": {
+ "^[a-zA-Z0-9_-]+$": {
+ "$ref": "#/definitions/Application"
+ }
+ }
+ },
+ "assistant": {
+ "type": "object",
+ "patternProperties": {
+ "^[a-zA-Z0-9_-]+$": {
+ "$ref": "#/definitions/Assistants"
+ }
+ }
+ },
+ "addons": {
+ "type": "object",
+ "patternProperties": {
+ "^[a-zA-Z0-9_-]+$": {
+ "$ref": "#/definitions/Addon"
+ }
+ }
+ },
+ "roles": {
+ "type": "object",
+ "patternProperties": {
+ "^[a-zA-Z0-9_-]+$": {
+ "$ref": "#/definitions/Role"
+ }
+ }
+ },
+ "keys": {
+ "type": "object",
+ "patternProperties": {
+ "^[a-zA-Z0-9_-]+$": {
+ "$ref": "#/definitions/Key"
+ }
+ }
+ },
+ "interceptors": {
+ "type": "object",
+ "patternProperties": {
+ "^[a-zA-Z0-9_-]+$": {
+ "$ref": "#/definitions/Interceptor"
+ }
+ }
+ },
+ "applicationTypeSchemas": {
+ "type": "object",
+ "patternProperties": {
+ "^[a-zA-Z0-9_-]+$": {
+ "$ref": "#/definitions/ApplicationTypeSchema"
+ }
+ }
+ },
+ "routes": {
+ "type": "object",
+ "patternProperties": {
+ "^[a-zA-Z0-9_-]+$": {
+ "$ref": "#/definitions/Route"
+ }
+ }
+ },
+ "toolsets": {
+ "type": "object",
+ "patternProperties": {
+ "^[a-zA-Z0-9_-]+$": {
+ "$ref": "#/definitions/ToolSet"
+ }
+ }
+ },
+ "retriableErrorCodes": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
+ "globalInterceptors": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ },
+ "definitions": {
+ "RoleBasedEntity": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "userRoles": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "LocalizedValue": {
+ "oneOf": [
+ {
+ "type": "string",
+ "description": "Plain string value (backward compatible)"
+ },
+ {
+ "type": "object",
+ "patternProperties": {
+ "^[a-z]{2}(-[A-Z]{2})?$": {
+ "type": "string"
+ }
+ },
+ "additionalProperties": false,
+ "minProperties": 1,
+ "description": "Map of locale codes to localized strings (e.g. {\"en\": \"GPT-4\", \"fr\": \"GPT-4\"})"
+ }
+ ]
+ },
+ "Deployment": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/definitions/RoleBasedEntity"
+ },
+ {
+ "properties": {
+ "endpoint": {
+ "type": "string"
+ },
+ "responsesEndpoint": {
+ "type": "string"
+ },
+ "interfaces": {
+ "type": "object",
+ "additionalProperties": {
+ "$ref": "#/definitions/DeploymentInterface"
+ }
+ },
+ "displayName": {
+ "$ref": "#/definitions/LocalizedValue"
+ },
+ "displayVersion": {
+ "$ref": "#/definitions/LocalizedValue"
+ },
+ "iconUrl": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "intro": {
+ "$ref": "#/definitions/LocalizedValue"
+ },
+ "reference": {
+ "type": "string"
+ },
+ "forwardAuthToken": {
+ "type": "boolean"
+ },
+ "features": {
+ "$ref": "#/definitions/CoreFeatures"
+ },
+ "inputAttachmentTypes": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "maxInputAttachments": {
+ "type": "integer"
+ },
+ "defaults": {
+ "type": "object"
+ },
+ "responsesDefaults": {
+ "type": "object"
+ },
+ "interceptors": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "descriptionKeywords": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "maxRetryAttempts": {
+ "type": "integer"
+ },
+ "author": {
+ "type": "string"
+ },
+ "createdAt": {
+ "type": "integer"
+ },
+ "updatedAt": {
+ "type": "integer"
+ },
+ "dependencies": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ ]
+ },
+ "Model": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/definitions/Deployment"
+ },
+ {
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "CHAT",
+ "COMPLETION",
+ "EMBEDDING"
+ ]
+ },
+ "tokenizerModel": {
+ "type": "string"
+ },
+ "limits": {
+ "$ref": "#/definitions/TokenLimits"
+ },
+ "pricing": {
+ "$ref": "#/definitions/Pricing"
+ },
+ "upstreams": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/Upstream"
+ }
+ },
+ "overrideName": {
+ "type": "string"
+ },
+ "fieldsHashingOrder": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "embeddingDimensions": {
+ "type": "integer"
+ },
+ "interfaces": {
+ "type": "object",
+ "properties": {
+ "openaiChatCompletions": {
+ "$ref": "#/definitions/DeploymentInterface"
+ },
+ "openaiResponses": {
+ "$ref": "#/definitions/DeploymentInterface"
+ },
+ "anthropicMessages": {
+ "$ref": "#/definitions/DeploymentInterface"
+ }
+ },
+ "additionalProperties": false
+ }
+ }
+ }
+ ]
+ },
+ "Application": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/definitions/Deployment"
+ },
+ {
+ "properties": {
+ "function": {
+ "$ref": "#/definitions/ApplicationFunction"
+ },
+ "applicationProperties": {
+ "type": "object"
+ },
+ "applicationTypeSchemaId": {
+ "type": "string",
+ "format": "uri"
+ },
+ "viewerUrl": {
+ "type": "string"
+ },
+ "editorUrl": {
+ "type": "string"
+ },
+ "routes": {
+ "type": "object"
+ },
+ "mcp": {
+ "$ref": "#/definitions/Mcp"
+ },
+ "interfaces": {
+ "type": "object",
+ "properties": {
+ "openaiChatCompletions": {
+ "$ref": "#/definitions/DeploymentInterface"
+ }
+ },
+ "additionalProperties": false
+ },
+ "externalServices": {
+ "type": "object",
+ "patternProperties": {
+ ".*": {
+ "$ref": "#/definitions/ExternalService"
+ }
+ }
+ },
+ "appIdentity": {
+ "type": "string"
+ },
+ "allowUserExternalServices": {
+ "type": "boolean"
+ }
+ }
+ }
+ ]
+ },
+ "ApplicationFunction": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "runtime": {
+ "type": "string"
+ },
+ "authorBucket": {
+ "type": "string"
+ },
+ "sourceFolder": {
+ "type": "string"
+ },
+ "targetFolder": {
+ "type": "string"
+ },
+ "status": {
+ "type": "string",
+ "enum": [
+ "DEPLOYING",
+ "UNDEPLOYING",
+ "DEPLOYED",
+ "UNDEPLOYED",
+ "FAILED",
+ "STARTING",
+ "STOPPING",
+ "STARTED",
+ "CREATED",
+ "STOPPED"
+ ]
+ },
+ "error": {
+ "type": "string"
+ },
+ "mapping": {
+ "$ref": "#/definitions/ApplicationFunctionMapping"
+ },
+ "env": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "ApplicationFunctionMapping": {
+ "type": "object",
+ "properties": {
+ "chatCompletion": {
+ "type": "string"
+ },
+ "rate": {
+ "type": "string"
+ },
+ "tokenize": {
+ "type": "string"
+ },
+ "truncatePrompt": {
+ "type": "string"
+ },
+ "configuration": {
+ "type": "string"
+ }
+ }
+ },
+ "Assistants": {
+ "type": "object",
+ "allOf": [
+ {
+ "properties": {
+ "endpoint": {
+ "type": "string"
+ },
+ "features": {
+ "$ref": "#/definitions/CoreFeatures"
+ },
+ "assistants": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ ]
+ },
+ "Assistant": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/definitions/Deployment"
+ },
+ {
+ "properties": {
+ "prompt": {
+ "type": "string"
+ },
+ "addons": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ ]
+ },
+ "Addon": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/definitions/Deployment"
+ },
+ {
+ "properties": {
+ "token": {
+ "type": "string"
+ }
+ }
+ }
+ ]
+ },
+ "Role": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "limits": {
+ "type": "object"
+ },
+ "share": {
+ "type": "object"
+ },
+ "costLimit": {
+ "$ref": "#/definitions/CoreCostLimit"
+ }
+ }
+ },
+ "Key": {
+ "type": "object",
+ "properties": {
+ "key": {
+ "type": "string"
+ },
+ "project": {
+ "type": "string"
+ },
+ "role": {
+ "type": "string"
+ },
+ "secured": {
+ "type": "boolean"
+ },
+ "roles": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "allowedIpAddressRanges": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "Interceptor": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/definitions/Deployment"
+ },
+ {
+ "properties": {
+ }
+ }
+ ]
+ },
+ "ApplicationTypeSchema": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/definitions/RoleBasedEntity"
+ },
+ {
+ "properties": {
+ "$schema": {
+ "type": "string"
+ },
+ "$id": {
+ "type": "string"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "OBJECT",
+ "BOOLEAN"
+ ]
+ },
+ "title": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "dial:applicationTypeEditorUrl": {
+ "type": "string"
+ },
+ "dial:applicationTypeViewerUrl": {
+ "type": "string"
+ },
+ "dial:applicationTypeDisplayName": {
+ "type": "string"
+ },
+ "dial:applicationTypeCompletionEndpoint": {
+ "type": "string"
+ },
+ "dial:applicationTypeConfigurationEndpoint": {
+ "type": "string"
+ },
+ "dial:applicationTypeRateEndpoint": {
+ "type": "string"
+ },
+ "dial:applicationTypeTokenizeEndpoint": {
+ "type": "string"
+ },
+ "dial:applicationTypeTruncatePromptEndpoint": {
+ "type": "string"
+ },
+ "dial:appendApplicationPropertiesHeader": {
+ "type": "boolean"
+ },
+ "dial:applicationTypeIconUrl": {
+ "type": "string"
+ },
+ "dial:applicationTypeRoutes": {
+ "type": "object"
+ },
+ "dial:applicationTypePlaybackSupport": {
+ "type": "boolean"
+ },
+ "dial:applicationTypeBucketCopy": {
+ "type": "string",
+ "enum": [
+ "ENABLED",
+ "DISABLED"
+ ]
+ },
+ "dial:applicationTypeAssistantAttachmentsInRequestSupported": {
+ "type": "boolean"
+ },
+ "dial:applicationTypeSchemaEndpoint": {
+ "type": "string"
+ },
+ "dial:applicationTypeResponsesEndpoint": {
+ "type": "string"
+ },
+ "dial:applicationTypeMcp": {
+ "$ref": "#/definitions/ApplicationTypeMcp"
+ },
+ "dial:applicationTypeInterceptors": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "List of application type interceptors"
+ },
+ "$defs": {
+ "type": "object"
+ },
+ "properties": {
+ "type": "object"
+ },
+ "required": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ ]
+ },
+ "Pricing": {
+ "type": "object",
+ "properties": {
+ "unit": {
+ "type": "string"
+ },
+ "prompt": {
+ "type": "number"
+ },
+ "completion": {
+ "type": "number"
+ }
+ }
+ },
+ "TokenLimits": {
+ "type": "object",
+ "properties": {
+ "maxTotalTokens": {
+ "type": "number"
+ },
+ "maxPromptTokens": {
+ "type": "number"
+ },
+ "maxCompletionTokens": {
+ "type": "number"
+ }
+ }
+ },
+ "Route": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/definitions/RoleBasedEntity"
+ },
+ {
+ "properties": {
+ "response": {
+ "$ref": "#/definitions/Response"
+ },
+ "rewritePath": {
+ "type": "boolean"
+ },
+ "paths": {
+ "type": "array",
+ "items": {
+ "type": "object"
+ }
+ },
+ "methods": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "upstreams": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/Upstream"
+ }
+ },
+ "maxRetryAttempts": {
+ "type": "integer"
+ },
+ "order": {
+ "type": "integer"
+ },
+ "permissions": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "attachmentPaths": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/AttachmentPath"
+ }
+ }
+ }
+ }
+ ]
+ },
+ "Response": {
+ "type": "object",
+ "properties": {
+ "status": {
+ "type": "integer"
+ },
+ "body": {
+ "type": "string"
+ }
+ }
+ },
+ "Upstream": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "endpoint": {
+ "type": "string"
+ },
+ "responsesEndpoint": {
+ "type": "string"
+ },
+ "key": {
+ "type": "string"
+ },
+ "extraData": {
+ "type": "string"
+ },
+ "weight": {
+ "type": "integer"
+ },
+ "tier": {
+ "type": "integer"
+ },
+ "secretExtraData": {
+ "type": "string"
+ }
+ }
+ },
+ "AttachmentPath": {
+ "type": "object",
+ "properties": {
+ "requestBody": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "responseBody": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "CoreFeatures": {
+ "type": "object",
+ "properties": {
+ "rateEndpoint": {
+ "type": "string"
+ },
+ "tokenizeEndpoint": {
+ "type": "string"
+ },
+ "truncatePromptEndpoint": {
+ "type": "string"
+ },
+ "configurationEndpoint": {
+ "type": "string"
+ },
+ "systemPromptSupported": {
+ "type": "boolean"
+ },
+ "toolsSupported": {
+ "type": "boolean"
+ },
+ "seedSupported": {
+ "type": "boolean"
+ },
+ "urlAttachmentsSupported": {
+ "type": "boolean"
+ },
+ "folderAttachmentsSupported": {
+ "type": "boolean"
+ },
+ "allowResume": {
+ "type": "boolean"
+ },
+ "accessibleByPerRequestKey": {
+ "type": "boolean"
+ },
+ "contentPartsSupported": {
+ "type": "boolean"
+ },
+ "temperatureSupported": {
+ "type": "boolean"
+ },
+ "addonsSupported": {
+ "type": "boolean"
+ },
+ "cacheSupported": {
+ "type": "boolean"
+ },
+ "autoCachingSupported": {
+ "type": "boolean"
+ },
+ "consentRequired": {
+ "type": "boolean"
+ },
+ "parallelToolCallsSupported": {
+ "type": "boolean"
+ },
+ "assistantAttachmentsInRequestSupported": {
+ "type": "boolean"
+ },
+ "supportCommentInRateResponse": {
+ "type": "boolean"
+ },
+ "maxTokensSupported": {
+ "type": "boolean"
+ },
+ "maxCompletionTokensSupported": {
+ "type": "boolean"
+ },
+ "customTemperatureSupported": {
+ "type": "boolean"
+ },
+ "reasoningEfforts": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "CoreSecuredResource": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/definitions/Deployment"
+ },
+ {
+ "properties": {
+ "authSettings": {
+ "$ref": "#/definitions/CoreResourceAuthSettings"
+ },
+ "forwardPerRequestKey": {
+ "type": "boolean",
+ "default": false
+ }
+ }
+ }
+ ]
+ },
+ "CoreResourceAuthSettings": {
+ "type": "object",
+ "allOf": [
+ {
+ "properties": {
+ "authenticationType": {
+ "type": "string",
+ "enum": [
+ "OAUTH",
+ "API_KEY",
+ "NONE"
+ ]
+ },
+ "clientId": {
+ "type": "string"
+ },
+ "clientSecret": {
+ "type": "string"
+ },
+ "authorizationEndpoint": {
+ "type": "string"
+ },
+ "tokenEndpoint": {
+ "type": "string"
+ },
+ "redirectUri": {
+ "type": "string"
+ },
+ "codeChallenge": {
+ "type": "string"
+ },
+ "codeChallengeMethod": {
+ "type": "string"
+ },
+ "codeVerifier": {
+ "type": "string"
+ },
+ "apiKeyHeader": {
+ "type": "string"
+ },
+ "globalAuthStatus": {
+ "type": "string",
+ "enum": [
+ "SIGNED_IN",
+ "SIGNED_OUT",
+ "FAILED"
+ ]
+ },
+ "userLevelAuthStatus": {
+ "type": "string",
+ "enum": [
+ "SIGNED_IN",
+ "SIGNED_OUT",
+ "FAILED"
+ ]
+ },
+ "appLevelAuthStatus": {
+ "type": "string",
+ "enum": [
+ "SIGNED_IN",
+ "SIGNED_OUT",
+ "FAILED"
+ ]
+ },
+ "scopesSupported": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "tokenEndpointAuthMethod": {
+ "type": "string"
+ },
+ "dynamicallyRegistered": {
+ "type": "boolean"
+ }
+ }
+ }
+ ]
+ },
+ "ToolSet": {
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/definitions/CoreSecuredResource"
+ },
+ {
+ "properties": {
+ "transport": {
+ "type": "string",
+ "enum": [
+ "HTTP",
+ "SSE"
+ ]
+ },
+ "allowedTools": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "provider": {
+ "type": "string"
+ },
+ "vendorWebsite": {
+ "type": "string"
+ }
+ }
+ }
+ ]
+ },
+ "CoreCostLimit": {
+ "type": "object",
+ "properties": {
+ "minute": {
+ "type": "number"
+ },
+ "day": {
+ "type": "number"
+ },
+ "week": {
+ "type": "number"
+ },
+ "month": {
+ "type": "number"
+ }
+ }
+ },
+ "ApplicationTypeMcp": {
+ "type": "object",
+ "properties": {
+ "dial:endpoint": {
+ "type": "string",
+ "format": "uri",
+ "description": "MCP server endpoint for MCP protocol calls"
+ },
+ "dial:transport": {
+ "type": "string",
+ "enum": [
+ "HTTP"
+ ],
+ "description": "Transport supported by MCP server. Currently only HTTP (Streamable HTTP) is supported."
+ },
+ "dial:allowedTools": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Optional. Metadata for DIAL Core that defines the allowed tool names. When specified, DIAL Core filters the tools/list response from the MCP server to only include tools whose names match this list. When omitted, DIAL Core proxies the tools/list response from the MCP server without filtering."
+ },
+ "dial:mcpConfigDelivery": {
+ "type": "string",
+ "enum": [
+ "HEADER",
+ "META"
+ ],
+ "default": "META",
+ "description": "Method for delivering application configuration to MCP server. 'header' sends config in HTTP header, 'meta' sends config in params._meta.ai_dial_config field of MCP protocol message."
+ },
+ "dial:forwardPerRequestKey": {
+ "type": "boolean",
+ "default": true,
+ "description": "If this flag is set to true, the per-request API key will be forwarded in DIAL-API-KEY HTTP header with every MCP protocol request to the toolset endpoint. This allows schema-rich applications to access DIAL Core APIs (e.g., file storage, other DIAL resources) using the per-request API key."
+ }
+ },
+ "required": [
+ "dial:endpoint"
+ ]
+ },
+ "Mcp": {
+ "type": "object",
+ "properties": {
+ "endpoint": {
+ "type": "string",
+ "format": "uri"
+ },
+ "transport": {
+ "type": "string",
+ "enum": [
+ "HTTP"
+ ]
+ },
+ "allowedTools": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "configDelivery": {
+ "type": "string",
+ "enum": [
+ "HEADER",
+ "META"
+ ],
+ "default": "META"
+ },
+ "forwardPerRequestKey": {
+ "type": "boolean",
+ "default": true
+ }
+ },
+ "required": [
+ "endpoint"
+ ]
+ },
+ "DeploymentInterface": {
+ "type": "object",
+ "properties": {
+ "base_url": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "base_url"
+ ]
+ },
+ "ExternalService": {
+ "type": "object",
+ "properties": {
+ "displayName": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "authSettings": {
+ "$ref": "#/definitions/CoreResourceAuthSettings"
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/resources/db/migration/H2/V1.118__AddI18nColumnsToDeploymentEntities.sql b/src/main/resources/db/migration/H2/V1.118__AddI18nColumnsToDeploymentEntities.sql
new file mode 100644
index 000000000..5fc61d903
--- /dev/null
+++ b/src/main/resources/db/migration/H2/V1.118__AddI18nColumnsToDeploymentEntities.sql
@@ -0,0 +1,74 @@
+-- Add i18n columns for LocalizedValue storage (v0.47.0)
+-- LocalizedValue stores either plain strings or JSON locale maps in single column
+-- Examples: "GPT-4" or {"en":"GPT-4","fr":"GPT-4"}
+
+-- Model entity
+-- Drop unique constraint (will not be recreated - uniqueness enforced at application level)
+alter table model_entity drop constraint if exists uk_model_entity_display_name_display_version;
+
+-- Drop NOT NULL constraint from display_name
+alter table model_entity alter column display_name drop not null;
+
+-- Add i18n columns
+alter table if exists model_entity add column if not exists display_name_i18n text;
+alter table if exists model_entity add column if not exists description_i18n text;
+alter table if exists model_entity add column if not exists intro_i18n text;
+
+alter table if exists model_entity_aud add column if not exists display_name_i18n text;
+alter table if exists model_entity_aud add column if not exists description_i18n text;
+alter table if exists model_entity_aud add column if not exists intro_i18n text;
+
+-- Add check constraint: at least one of display_name or display_name_i18n must not be null
+alter table model_entity add constraint chk_model_entity_display_name_not_all_null
+ check (display_name is not null or display_name_i18n is not null);
+
+-- Application entity
+-- Drop unique constraint (will not be recreated - uniqueness enforced at application level)
+alter table application_entity drop constraint if exists uk_application_entity_display_name_display_version;
+
+-- Drop NOT NULL constraint from display_name
+alter table application_entity alter column display_name drop not null;
+
+-- Add i18n columns
+alter table if exists application_entity add column if not exists display_name_i18n text;
+alter table if exists application_entity add column if not exists description_i18n text;
+alter table if exists application_entity add column if not exists intro_i18n text;
+
+alter table if exists application_entity_aud add column if not exists display_name_i18n text;
+alter table if exists application_entity_aud add column if not exists description_i18n text;
+alter table if exists application_entity_aud add column if not exists intro_i18n text;
+
+-- Add check constraint: at least one of display_name or display_name_i18n must not be null
+alter table application_entity add constraint chk_application_entity_display_name_not_all_null
+ check (display_name is not null or display_name_i18n is not null);
+
+-- ToolSet entity
+-- Drop NOT NULL constraint from display_name
+alter table tool_set_entity alter column display_name drop not null;
+
+-- Add i18n columns
+alter table if exists tool_set_entity add column if not exists display_name_i18n text;
+alter table if exists tool_set_entity add column if not exists description_i18n text;
+alter table if exists tool_set_entity add column if not exists intro_i18n text;
+
+alter table if exists tool_set_entity_aud add column if not exists display_name_i18n text;
+alter table if exists tool_set_entity_aud add column if not exists description_i18n text;
+alter table if exists tool_set_entity_aud add column if not exists intro_i18n text;
+
+-- Add check constraint: at least one of display_name or display_name_i18n must not be null
+alter table tool_set_entity add constraint chk_tool_set_entity_display_name_not_all_null
+ check (display_name is not null or display_name_i18n is not null);
+
+-- Interceptor entity
+-- Drop NOT NULL constraint from display_name
+alter table interceptor_entity alter column display_name drop not null;
+
+alter table if exists interceptor_entity add column if not exists display_name_i18n text;
+alter table if exists interceptor_entity add column if not exists description_i18n text;
+
+alter table if exists interceptor_entity_aud add column if not exists display_name_i18n text;
+alter table if exists interceptor_entity_aud add column if not exists description_i18n text;
+
+-- Add check constraint: at least one of display_name or display_name_i18n must not be null
+alter table interceptor_entity add constraint chk_interceptor_entity_display_name_not_all_null
+ check (display_name is not null or display_name_i18n is not null);
diff --git a/src/main/resources/db/migration/MS_SQL_SERVER/V1.118__AddI18nColumnsToDeploymentEntities.sql b/src/main/resources/db/migration/MS_SQL_SERVER/V1.118__AddI18nColumnsToDeploymentEntities.sql
new file mode 100644
index 000000000..a0614969c
--- /dev/null
+++ b/src/main/resources/db/migration/MS_SQL_SERVER/V1.118__AddI18nColumnsToDeploymentEntities.sql
@@ -0,0 +1,83 @@
+-- Add i18n columns for LocalizedValue storage (v0.47.0)
+-- LocalizedValue stores either plain strings or JSON locale maps in single column
+-- Examples: "GPT-4" or {"en":"GPT-4","fr":"GPT-4"}
+
+-- Model entity
+-- Drop unique index before altering column (SQL Server requirement)
+-- Will not be recreated - uniqueness enforced at application level
+if exists (select 1 from sys.indexes where name = 'ux_model_entity_display_name_display_version_not_null' and object_id = object_id('model_entity'))
+begin
+ drop index ux_model_entity_display_name_display_version_not_null on model_entity;
+end
+
+-- Drop NOT NULL constraint from display_name
+alter table model_entity alter column display_name nvarchar(255) null;
+alter table model_entity_aud alter column display_name nvarchar(255) null;
+
+-- Add i18n columns
+alter table model_entity add display_name_i18n nvarchar(max);
+alter table model_entity add description_i18n nvarchar(max);
+alter table model_entity add intro_i18n nvarchar(max);
+
+alter table model_entity_aud add display_name_i18n nvarchar(max);
+alter table model_entity_aud add description_i18n nvarchar(max);
+alter table model_entity_aud add intro_i18n nvarchar(max);
+
+-- Add check constraint using dynamic SQL to avoid parse-time validation
+exec('alter table model_entity add constraint chk_model_entity_display_name_not_all_null check (display_name is not null or display_name_i18n is not null)');
+
+-- Application entity
+-- Drop unique index before altering column (SQL Server requirement)
+-- Will not be recreated - uniqueness enforced at application level
+if exists (select 1 from sys.indexes where name = 'ux_application_entity_display_name_display_version_not_null' and object_id = object_id('application_entity'))
+begin
+ drop index ux_application_entity_display_name_display_version_not_null on application_entity;
+end
+
+-- Drop NOT NULL constraint from display_name
+alter table application_entity alter column display_name nvarchar(255) null;
+alter table application_entity_aud alter column display_name nvarchar(255) null;
+
+-- Add i18n columns
+alter table application_entity add display_name_i18n nvarchar(max);
+alter table application_entity add description_i18n nvarchar(max);
+alter table application_entity add intro_i18n nvarchar(max);
+
+alter table application_entity_aud add display_name_i18n nvarchar(max);
+alter table application_entity_aud add description_i18n nvarchar(max);
+alter table application_entity_aud add intro_i18n nvarchar(max);
+
+-- Add check constraint using dynamic SQL to avoid parse-time validation
+exec('alter table application_entity add constraint chk_application_entity_display_name_not_all_null check (display_name is not null or display_name_i18n is not null)');
+
+-- ToolSet entity (no display_version field)
+-- Drop NOT NULL constraint from display_name
+alter table tool_set_entity alter column display_name nvarchar(max) null;
+alter table tool_set_entity_aud alter column display_name nvarchar(max) null;
+
+-- Add i18n columns
+alter table tool_set_entity add display_name_i18n nvarchar(max);
+alter table tool_set_entity add description_i18n nvarchar(max);
+alter table tool_set_entity add intro_i18n nvarchar(max);
+
+alter table tool_set_entity_aud add display_name_i18n nvarchar(max);
+alter table tool_set_entity_aud add description_i18n nvarchar(max);
+alter table tool_set_entity_aud add intro_i18n nvarchar(max);
+
+-- Add check constraint using dynamic SQL to avoid parse-time validation
+exec('alter table tool_set_entity add constraint chk_tool_set_entity_display_name_not_all_null check (display_name is not null or display_name_i18n is not null)');
+
+-- Interceptor entity (no display_version field)
+-- Drop NOT NULL constraint from display_name
+alter table interceptor_entity alter column display_name nvarchar(max) null;
+alter table interceptor_entity_aud alter column display_name nvarchar(max) null;
+
+-- Add i18n columns
+alter table interceptor_entity add display_name_i18n nvarchar(max);
+alter table interceptor_entity add description_i18n nvarchar(max);
+
+alter table interceptor_entity_aud add display_name_i18n nvarchar(max);
+alter table interceptor_entity_aud add description_i18n nvarchar(max);
+
+-- Add check constraint using dynamic SQL to avoid parse-time validation
+exec('alter table interceptor_entity add constraint chk_interceptor_entity_display_name_not_all_null check (display_name is not null or display_name_i18n is not null)');
diff --git a/src/main/resources/db/migration/POSTGRES/V1.118__AddI18nColumnsToDeploymentEntities.sql b/src/main/resources/db/migration/POSTGRES/V1.118__AddI18nColumnsToDeploymentEntities.sql
new file mode 100644
index 000000000..5fc61d903
--- /dev/null
+++ b/src/main/resources/db/migration/POSTGRES/V1.118__AddI18nColumnsToDeploymentEntities.sql
@@ -0,0 +1,74 @@
+-- Add i18n columns for LocalizedValue storage (v0.47.0)
+-- LocalizedValue stores either plain strings or JSON locale maps in single column
+-- Examples: "GPT-4" or {"en":"GPT-4","fr":"GPT-4"}
+
+-- Model entity
+-- Drop unique constraint (will not be recreated - uniqueness enforced at application level)
+alter table model_entity drop constraint if exists uk_model_entity_display_name_display_version;
+
+-- Drop NOT NULL constraint from display_name
+alter table model_entity alter column display_name drop not null;
+
+-- Add i18n columns
+alter table if exists model_entity add column if not exists display_name_i18n text;
+alter table if exists model_entity add column if not exists description_i18n text;
+alter table if exists model_entity add column if not exists intro_i18n text;
+
+alter table if exists model_entity_aud add column if not exists display_name_i18n text;
+alter table if exists model_entity_aud add column if not exists description_i18n text;
+alter table if exists model_entity_aud add column if not exists intro_i18n text;
+
+-- Add check constraint: at least one of display_name or display_name_i18n must not be null
+alter table model_entity add constraint chk_model_entity_display_name_not_all_null
+ check (display_name is not null or display_name_i18n is not null);
+
+-- Application entity
+-- Drop unique constraint (will not be recreated - uniqueness enforced at application level)
+alter table application_entity drop constraint if exists uk_application_entity_display_name_display_version;
+
+-- Drop NOT NULL constraint from display_name
+alter table application_entity alter column display_name drop not null;
+
+-- Add i18n columns
+alter table if exists application_entity add column if not exists display_name_i18n text;
+alter table if exists application_entity add column if not exists description_i18n text;
+alter table if exists application_entity add column if not exists intro_i18n text;
+
+alter table if exists application_entity_aud add column if not exists display_name_i18n text;
+alter table if exists application_entity_aud add column if not exists description_i18n text;
+alter table if exists application_entity_aud add column if not exists intro_i18n text;
+
+-- Add check constraint: at least one of display_name or display_name_i18n must not be null
+alter table application_entity add constraint chk_application_entity_display_name_not_all_null
+ check (display_name is not null or display_name_i18n is not null);
+
+-- ToolSet entity
+-- Drop NOT NULL constraint from display_name
+alter table tool_set_entity alter column display_name drop not null;
+
+-- Add i18n columns
+alter table if exists tool_set_entity add column if not exists display_name_i18n text;
+alter table if exists tool_set_entity add column if not exists description_i18n text;
+alter table if exists tool_set_entity add column if not exists intro_i18n text;
+
+alter table if exists tool_set_entity_aud add column if not exists display_name_i18n text;
+alter table if exists tool_set_entity_aud add column if not exists description_i18n text;
+alter table if exists tool_set_entity_aud add column if not exists intro_i18n text;
+
+-- Add check constraint: at least one of display_name or display_name_i18n must not be null
+alter table tool_set_entity add constraint chk_tool_set_entity_display_name_not_all_null
+ check (display_name is not null or display_name_i18n is not null);
+
+-- Interceptor entity
+-- Drop NOT NULL constraint from display_name
+alter table interceptor_entity alter column display_name drop not null;
+
+alter table if exists interceptor_entity add column if not exists display_name_i18n text;
+alter table if exists interceptor_entity add column if not exists description_i18n text;
+
+alter table if exists interceptor_entity_aud add column if not exists display_name_i18n text;
+alter table if exists interceptor_entity_aud add column if not exists description_i18n text;
+
+-- Add check constraint: at least one of display_name or display_name_i18n must not be null
+alter table interceptor_entity add constraint chk_interceptor_entity_display_name_not_all_null
+ check (display_name is not null or display_name_i18n is not null);
diff --git a/src/test/java/com/epam/aidial/cfg/dao/listener/validitystate/resolver/ApplicationValidityStateResolverTest.java b/src/test/java/com/epam/aidial/cfg/dao/listener/validitystate/resolver/ApplicationValidityStateResolverTest.java
index e8f799d17..b47de8a81 100644
--- a/src/test/java/com/epam/aidial/cfg/dao/listener/validitystate/resolver/ApplicationValidityStateResolverTest.java
+++ b/src/test/java/com/epam/aidial/cfg/dao/listener/validitystate/resolver/ApplicationValidityStateResolverTest.java
@@ -11,6 +11,8 @@
import com.epam.aidial.cfg.dao.mapper.ExternalServicesEntityMapperImpl;
import com.epam.aidial.cfg.dao.mapper.FeaturesEntityMapperImpl;
import com.epam.aidial.cfg.dao.mapper.LimitEntityMapperImpl;
+import com.epam.aidial.cfg.dao.mapper.LocalizedValueEntityMapper;
+import com.epam.aidial.cfg.dao.mapper.LocalizedValueEntityMapperImpl;
import com.epam.aidial.cfg.dao.mapper.MapPropertiesMapperImpl;
import com.epam.aidial.cfg.dao.mapper.PropertiesEntityMapperImpl;
import com.epam.aidial.cfg.dao.mapper.ResourceAuthSettingsEntityMapperImpl;
@@ -56,7 +58,8 @@
ApplicationTypeSchemaEntityMapperImpl.class,
PropertiesEntityMapperImpl.class,
ApplicationTypeSchemaCoreMapperImpl.class,
- ApplicationTypeSchemaRouteCoreMapperImpl.class
+ ApplicationTypeSchemaRouteCoreMapperImpl.class,
+ LocalizedValueEntityMapperImpl.class
})
class ApplicationValidityStateResolverTest {
@@ -66,6 +69,8 @@ class ApplicationValidityStateResolverTest {
private ApplicationTypeSchemaEntityMapper applicationTypeSchemaEntityMapper;
@Autowired
private ApplicationTypeSchemaCoreMapper applicationTypeSchemaCoreMapper;
+ @Autowired
+ private LocalizedValueEntityMapper localizedValueEntityMapper;
private ApplicationValidityStateResolver applicationValidityStateResolver;
diff --git a/src/test/java/com/epam/aidial/cfg/domain/validator/ApplicationValidatorTest.java b/src/test/java/com/epam/aidial/cfg/domain/validator/ApplicationValidatorTest.java
index 7df25ab00..b9f4a12f8 100644
--- a/src/test/java/com/epam/aidial/cfg/domain/validator/ApplicationValidatorTest.java
+++ b/src/test/java/com/epam/aidial/cfg/domain/validator/ApplicationValidatorTest.java
@@ -9,6 +9,7 @@
import com.epam.aidial.cfg.domain.model.ResourceAuthSettings;
import com.epam.aidial.cfg.domain.model.source.ApplicationEndpointsSource;
import com.epam.aidial.cfg.domain.model.source.ApplicationSchemaSource;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -50,7 +51,7 @@ void setUp() {
void validateCreation_shouldDelegateToDisplayFieldsValidator() {
// given
Application application = new Application();
- application.setDisplayName("display name");
+ application.setDisplayName(LocalizedValue.of("display name"));
application.setDisplayVersion("1.0");
application.setEndpoint("test");
application.setSource(new ApplicationEndpointsSource());
@@ -61,7 +62,7 @@ void validateCreation_shouldDelegateToDisplayFieldsValidator() {
applicationValidator.validateCreation(application);
// then
- verify(displayFieldsValidator).validateDisplayNameDisplayVersion("display name", "1.0", "Application", "text");
+ verify(displayFieldsValidator).validateDisplayNameDisplayVersion(LocalizedValue.of("display name"), "1.0", "Application", "text");
}
@ParameterizedTest
@@ -69,7 +70,7 @@ void validateCreation_shouldDelegateToDisplayFieldsValidator() {
void validateCreation_shouldThrowExceptionWhenEndpointIsNotNullButBlank(String endpoint) {
// given
Application application = new Application();
- application.setDisplayName("display name");
+ application.setDisplayName(LocalizedValue.of("display name"));
application.setDisplayVersion("1.0");
application.setEndpoint(endpoint);
application.setSource(new ApplicationEndpointsSource());
@@ -81,14 +82,15 @@ void validateCreation_shouldThrowExceptionWhenEndpointIsNotNullButBlank(String e
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Invalid endpoint: '" + endpoint + "'. Application: text");
- verify(displayFieldsValidator).validateDisplayNameDisplayVersion("display name", "1.0", "Application", "text");
+ verify(displayFieldsValidator).validateDisplayNameDisplayVersion(
+ application.getDisplayName(), application.getDisplayVersion(), "Application", "text");
}
@Test
void validateCreation_shouldThrowExceptionWhenEndpointsSourceWithNoEndpoints() {
// given
Application application = new Application();
- application.setDisplayName("display name");
+ application.setDisplayName(LocalizedValue.of("display name"));
application.setDisplayVersion("1.0");
application.setSource(new ApplicationEndpointsSource());
@@ -100,13 +102,14 @@ void validateCreation_shouldThrowExceptionWhenEndpointsSourceWithNoEndpoints() {
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("At least application endpoint, MCP endpoint or interfaces must be provided. Application: deploymentName");
- verify(displayFieldsValidator).validateDisplayNameDisplayVersion("display name", "1.0", "Application", "deploymentName");
+ verify(displayFieldsValidator).validateDisplayNameDisplayVersion(
+ application.getDisplayName(), application.getDisplayVersion(), "Application", "deploymentName");
}
@Test
void validateCreation_shouldNotThrowWhenEndpointsSourceWithOnlyInterfaces() {
Application application = new Application();
- application.setDisplayName("display name");
+ application.setDisplayName(LocalizedValue.of("display name"));
application.setDisplayVersion("1.0");
application.setSource(new ApplicationEndpointsSource());
application.setInterfaces(interfaces("openaiChatCompletions", "http://app.adapter.test.com"));
@@ -120,7 +123,7 @@ void validateCreation_shouldNotThrowWhenEndpointsSourceWithOnlyInterfaces() {
@Test
void validateCreation_shouldThrowWhenApplicationHasUnsupportedInterfaceType() {
Application application = new Application();
- application.setDisplayName("display name");
+ application.setDisplayName(LocalizedValue.of("display name"));
application.setDisplayVersion("1.0");
application.setSource(new ApplicationEndpointsSource());
application.setInterfaces(interfaces("anthropicMessages", "http://app.adapter.test.com"));
@@ -136,7 +139,7 @@ void validateCreation_shouldThrowWhenApplicationHasUnsupportedInterfaceType() {
@Test
void validateCreation_shouldThrowExceptionWhenSchemaSourceWithInterfacesSet() {
Application application = new Application();
- application.setDisplayName("text");
+ application.setDisplayName(LocalizedValue.of("text"));
application.setDisplayVersion("1.0");
application.setInterfaces(interfaces("openaiChatCompletions", "http://app.adapter.test.com"));
application.setSource(new ApplicationSchemaSource(URI.create("https://test.com")));
@@ -157,7 +160,7 @@ private static Map interfaces(String type, String b
@Test
void validateCreation_shouldThrowExceptionWhenSchemaSourceWithNullSchemaId() {
Application application = new Application();
- application.setDisplayName("text");
+ application.setDisplayName(LocalizedValue.of("text"));
application.setDisplayVersion("1.0");
application.setSource(new ApplicationSchemaSource(null));
Deployment deployment = new Deployment("deploymentName");
@@ -172,7 +175,7 @@ void validateCreation_shouldThrowExceptionWhenSchemaSourceWithNullSchemaId() {
void validateCreation_shouldThrowExceptionWhenSchemaSourceWithEndpointSet() {
// given
Application application = new Application();
- application.setDisplayName("text");
+ application.setDisplayName(LocalizedValue.of("text"));
application.setDisplayVersion("1.0");
application.setEndpoint("test");
application.setSource(new ApplicationSchemaSource(URI.create("https://test.com")));
@@ -188,7 +191,7 @@ void validateCreation_shouldThrowExceptionWhenSchemaSourceWithEndpointSet() {
@Test
void validateCreation_shouldThrowExceptionWhenSchemaSourceWithMcpProvided() {
Application application = new Application();
- application.setDisplayName("text");
+ application.setDisplayName(LocalizedValue.of("text"));
application.setDisplayVersion("1.0");
application.setSource(new ApplicationSchemaSource(URI.create("https://test.com")));
@@ -207,7 +210,7 @@ void validateCreation_shouldThrowExceptionWhenSchemaSourceWithMcpProvided() {
@Test
void validateCreation_shouldThrowExceptionWhenSourceIsNull() {
Application application = new Application();
- application.setDisplayName("text");
+ application.setDisplayName(LocalizedValue.of("text"));
application.setDisplayVersion("1.0");
application.setEndpoint("test");
@@ -227,7 +230,7 @@ void validateUpdate_shouldDelegateToDisplayFieldsAndDeploymentValidators() {
Deployment deployment = new Deployment(deploymentName);
Application application = new Application();
- application.setDisplayName("display name");
+ application.setDisplayName(LocalizedValue.of("display name"));
application.setDisplayVersion("1.0");
application.setDeployment(deployment);
application.setEndpoint("test");
@@ -238,7 +241,8 @@ void validateUpdate_shouldDelegateToDisplayFieldsAndDeploymentValidators() {
// then
verify(deploymentValidator).validateUpdate(deploymentName, deployment, "Application");
- verify(displayFieldsValidator).validateDisplayNameDisplayVersion("display name", "1.0", "Application", deploymentName);
+ verify(displayFieldsValidator).validateDisplayNameDisplayVersion(
+ application.getDisplayName(), application.getDisplayVersion(), "Application", deploymentName);
}
@ParameterizedTest
@@ -246,7 +250,7 @@ void validateUpdate_shouldDelegateToDisplayFieldsAndDeploymentValidators() {
void validateUpdate_shouldThrowExceptionWhenEndpointIsNotNullButBlank(String endpoint) {
// given
Application application = new Application();
- application.setDisplayName("display name");
+ application.setDisplayName(LocalizedValue.of("display name"));
application.setDisplayVersion("1.0");
application.setEndpoint(endpoint);
application.setSource(new ApplicationEndpointsSource());
@@ -259,7 +263,8 @@ void validateUpdate_shouldThrowExceptionWhenEndpointIsNotNullButBlank(String end
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Invalid endpoint: '" + endpoint + "'. Application: deploymentName");
- verify(displayFieldsValidator).validateDisplayNameDisplayVersion("display name", "1.0", "Application", "deploymentName");
+ verify(displayFieldsValidator).validateDisplayNameDisplayVersion(
+ application.getDisplayName(), application.getDisplayVersion(), "Application", "deploymentName");
}
@Test
@@ -271,7 +276,7 @@ void validateUpdate_shouldThrowExceptionWhenEndpointsSourceWithNoEndpoints() {
Application application = new Application();
application.setDeployment(deployment);
- application.setDisplayName("text");
+ application.setDisplayName(LocalizedValue.of("text"));
application.setDisplayVersion("1.0");
application.setSource(new ApplicationEndpointsSource());
@@ -285,7 +290,7 @@ void validateUpdate_shouldThrowExceptionWhenEndpointsSourceWithNoEndpoints() {
void validateUpdate_shouldThrowExceptionWhenSchemaSourceWithEndpointSet() {
// given
Application application = new Application();
- application.setDisplayName("text");
+ application.setDisplayName(LocalizedValue.of("text"));
application.setDisplayVersion("1.0");
application.setEndpoint("test");
application.setSource(new ApplicationSchemaSource(URI.create("https://test.com")));
@@ -337,7 +342,7 @@ void validateCreation_shouldThrowExceptionForInvalidName(String name) {
@Test
void validateCreation_shouldThrowExceptionWhenExternalServiceHasIncompleteOauthAuthSettings() {
Application application = new Application();
- application.setDisplayName("display name");
+ application.setDisplayName(LocalizedValue.of("display name"));
application.setDisplayVersion("1.0");
application.setEndpoint("test");
application.setSource(new ApplicationEndpointsSource());
@@ -352,7 +357,7 @@ void validateCreation_shouldThrowExceptionWhenExternalServiceHasIncompleteOauthA
@Test
void validateCreation_shouldThrowExceptionWhenExternalServiceHasIncompleteApiKeyAuthSettings() {
Application application = new Application();
- application.setDisplayName("display name");
+ application.setDisplayName(LocalizedValue.of("display name"));
application.setDisplayVersion("1.0");
application.setEndpoint("test");
application.setSource(new ApplicationEndpointsSource());
@@ -368,7 +373,7 @@ void validateCreation_shouldThrowExceptionWhenExternalServiceHasIncompleteApiKey
@Test
void validateUpdate_shouldThrowExceptionWhenExternalServiceHasIncompleteOauthAuthSettings() {
Application application = new Application();
- application.setDisplayName("display name");
+ application.setDisplayName(LocalizedValue.of("display name"));
application.setDisplayVersion("1.0");
application.setEndpoint("test");
application.setSource(new ApplicationEndpointsSource());
@@ -393,7 +398,7 @@ void validateCreation_shouldNotThrowWhenExternalServiceAuthSettingsAreValid() {
externalService.setAuthSettings(authSettings);
Application application = new Application();
- application.setDisplayName("display name");
+ application.setDisplayName(LocalizedValue.of("display name"));
application.setDisplayVersion("1.0");
application.setEndpoint("test");
application.setSource(new ApplicationEndpointsSource());
@@ -408,7 +413,7 @@ void validateCreation_shouldValidateExternalServiceDisplayName() {
ExternalService externalService = externalService(AuthenticationType.NONE);
Application application = new Application();
- application.setDisplayName("display name");
+ application.setDisplayName(LocalizedValue.of("display name"));
application.setDisplayVersion("1.0");
application.setEndpoint("test");
application.setSource(new ApplicationEndpointsSource());
diff --git a/src/test/java/com/epam/aidial/cfg/domain/validator/DisplayFieldsValidatorTest.java b/src/test/java/com/epam/aidial/cfg/domain/validator/DisplayFieldsValidatorTest.java
index 0ca29c764..1313c69bf 100644
--- a/src/test/java/com/epam/aidial/cfg/domain/validator/DisplayFieldsValidatorTest.java
+++ b/src/test/java/com/epam/aidial/cfg/domain/validator/DisplayFieldsValidatorTest.java
@@ -1,5 +1,7 @@
package com.epam.aidial.cfg.domain.validator;
+import com.epam.aidial.cfg.configuration.LocalizationProperties;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
@@ -14,50 +16,54 @@ class DisplayFieldsValidatorTest {
@BeforeEach
void setUp() {
- displayFieldsValidator = new DisplayFieldsValidator();
+ LocalizationProperties props = new LocalizationProperties();
+ props.setLocale("en");
+ displayFieldsValidator = new DisplayFieldsValidator(props);
}
@ParameterizedTest
@CsvSource({"''", "' '"})
void validateDisplayNameDisplayVersion_shouldThrowExceptionWhenDisplayNameIsBlank(String displayName) {
- assertThatThrownBy(() -> displayFieldsValidator.validateDisplayNameDisplayVersion(displayName, null, "DomainObjectType", "name"))
+ assertThatThrownBy(() -> displayFieldsValidator.validateDisplayNameDisplayVersion(LocalizedValue.of(displayName), null, "DomainObjectType", "name"))
.isInstanceOf(IllegalArgumentException.class)
- .hasMessage("Display name: '" + displayName + "'" + " must not be blank for DomainObjectType with id:'name'");
+ .hasMessageContaining("must not be blank for DomainObjectType with id:'name'");
}
@ParameterizedTest
@CsvSource({"''", "' '"})
void validateDisplayNameDisplayVersion_shouldThrowExceptionWhenDisplayVersionIsBlank(String displayVersion) {
- assertThatThrownBy(() -> displayFieldsValidator.validateDisplayNameDisplayVersion("test", displayVersion, "DomainObjectType", "name"))
+ assertThatThrownBy(() -> displayFieldsValidator.validateDisplayNameDisplayVersion(LocalizedValue.of("test"), displayVersion, "DomainObjectType", "name"))
.isInstanceOf(IllegalArgumentException.class)
- .hasMessage("Display version: '" + displayVersion + "'" + " must not be blank for DomainObjectType with id:'name'");
+ .hasMessageContaining("must not be blank for DomainObjectType with id:'name'");
}
@ParameterizedTest
@CsvSource(value = {"null", "''", "' '"}, nullValues = "null")
void validateDisplayName_shouldThrowExceptionWhenDisplayNameIsNullOrBlank(String displayName) {
- assertThatThrownBy(() -> displayFieldsValidator.validateDisplayName(displayName, "DomainObjectType", "name"))
+ assertThatThrownBy(() -> displayFieldsValidator.validateDisplayName(displayName != null ? LocalizedValue.of(displayName) : null, "DomainObjectType", "name"))
.isInstanceOf(IllegalArgumentException.class)
- .hasMessage("Display name: '" + displayName + "'" + " must not be blank for DomainObjectType with id:'name'");
+ .hasMessageContaining("must not be blank for DomainObjectType with id:'name'");
}
@Test
void validateDisplayNameDisplayVersion_shouldThrowExceptionWhenDisplayNameIsNullAndDisplayVersionIsNotNull() {
assertThatThrownBy(() -> displayFieldsValidator.validateDisplayNameDisplayVersion(null, "1.0", "DomainObjectType", "name"))
.isInstanceOf(IllegalArgumentException.class)
- .hasMessage("Display name: 'null'" + " must not be blank for DomainObjectType with id:'name'");
+ .hasMessageContaining("must not be blank for DomainObjectType with id:'name'");
}
@Test
void validateDisplayNameDisplayVersion_shouldDoNothingWhenDisplayNameIsNotNullAndDisplayVersionIsNull() {
- assertThatNoException().isThrownBy(() -> displayFieldsValidator.validateDisplayNameDisplayVersion("text", null, "DomainObjectType", "name"));
+ assertThatNoException().isThrownBy(() -> displayFieldsValidator.validateDisplayNameDisplayVersion(LocalizedValue.of("text"), null, "DomainObjectType", "name"));
}
@Test
void validateDisplayNameDisplayVersion_shouldDoNothingWhenDisplayNameIsNotNullAndDisplayVersionIsNotNull() {
- assertThatNoException().isThrownBy(() -> displayFieldsValidator.validateDisplayNameDisplayVersion("text", "1.0", "DomainObjectType", "name"));
+ assertThatNoException().isThrownBy(() ->
+ displayFieldsValidator.validateDisplayNameDisplayVersion(
+ LocalizedValue.of("text"), "1.0", "DomainObjectType", "name"));
}
}
\ No newline at end of file
diff --git a/src/test/java/com/epam/aidial/cfg/domain/validator/ModelValidatorTest.java b/src/test/java/com/epam/aidial/cfg/domain/validator/ModelValidatorTest.java
index 63015e7b9..ac5248037 100644
--- a/src/test/java/com/epam/aidial/cfg/domain/validator/ModelValidatorTest.java
+++ b/src/test/java/com/epam/aidial/cfg/domain/validator/ModelValidatorTest.java
@@ -9,6 +9,7 @@
import com.epam.aidial.cfg.domain.model.source.ModelEndpointsSource;
import com.epam.aidial.cfg.domain.model.source.ModelSource;
import com.epam.aidial.cfg.domain.utils.ModelEndpointUtils;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -78,7 +79,7 @@ void setUp() {
void validateCreation_shouldDelegateToDisplayFieldsValidator() {
// given
Model model = new Model();
- model.setDisplayName("display name");
+ model.setDisplayName(LocalizedValue.of("display name"));
model.setDisplayVersion("1.0");
Deployment deployment = new Deployment("text");
model.setDeployment(deployment);
@@ -87,7 +88,7 @@ void validateCreation_shouldDelegateToDisplayFieldsValidator() {
modelValidator.validateCreation(model);
// then
- verify(displayFieldsValidator).validateDisplayNameDisplayVersion("display name", "1.0", "Model", "text");
+ verify(displayFieldsValidator).validateDisplayNameDisplayVersion(model.getDisplayName(), model.getDisplayVersion(), "Model", "text");
}
@Test
@@ -98,7 +99,7 @@ void validateUpdate_shouldDelegateToDisplayFieldsAndDeploymentValidators() {
Deployment deployment = new Deployment(deploymentName);
Model model = new Model();
- model.setDisplayName("display name");
+ model.setDisplayName(LocalizedValue.of("display name"));
model.setDisplayVersion("1.0");
model.setDeployment(deployment);
@@ -107,7 +108,7 @@ void validateUpdate_shouldDelegateToDisplayFieldsAndDeploymentValidators() {
// then
verify(deploymentValidator).validateUpdate(deploymentName, deployment, "Model");
- verify(displayFieldsValidator).validateDisplayNameDisplayVersion("display name", "1.0", "Model", "deploymentName");
+ verify(displayFieldsValidator).validateDisplayNameDisplayVersion(model.getDisplayName(), model.getDisplayVersion(), "Model", "deploymentName");
}
@ParameterizedTest
diff --git a/src/test/java/com/epam/aidial/cfg/domain/validator/ToolSetValidatorTest.java b/src/test/java/com/epam/aidial/cfg/domain/validator/ToolSetValidatorTest.java
index 26ef96232..83f0620ef 100644
--- a/src/test/java/com/epam/aidial/cfg/domain/validator/ToolSetValidatorTest.java
+++ b/src/test/java/com/epam/aidial/cfg/domain/validator/ToolSetValidatorTest.java
@@ -10,6 +10,7 @@
import com.epam.aidial.cfg.domain.model.source.ToolSetEndpointsSource;
import com.epam.aidial.cfg.domain.model.source.ToolSetMcpRegistrySource;
import com.epam.aidial.cfg.domain.service.DeploymentManagerService;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -62,13 +63,13 @@ void setUp() {
void validateCreation_shouldDelegateToDeploymentValidator() {
// given
ToolSet toolSet = new ToolSet();
- toolSet.setDisplayName(TEST_TOOLSET_NAME);
+ toolSet.setDisplayName(LocalizedValue.of(TEST_TOOLSET_NAME));
SecuredResource deployment = new SecuredResource(TEST_TOOLSET_NAME);
toolSet.setDeployment(deployment);
// when & then
assertThatNoException().isThrownBy(() -> toolSetValidator.validateCreation(toolSet));
- verify(deploymentValidator).validateCreation("ToolSet", toolSet.getDisplayName());
+ verify(deploymentValidator).validateCreation("ToolSet", TEST_TOOLSET_NAME);
}
@ParameterizedTest
@@ -76,7 +77,7 @@ void validateCreation_shouldDelegateToDeploymentValidator() {
void validateCreation_shouldThrowExceptionForInvalidName(String toolSetName) {
// given
ToolSet toolSet = new ToolSet();
- toolSet.setDisplayName(toolSetName);
+ toolSet.setDisplayName(LocalizedValue.of(toolSetName));
SecuredResource deployment = new SecuredResource(toolSetName);
toolSet.setDeployment(deployment);
@@ -90,7 +91,7 @@ void validateCreation_shouldThrowExceptionForInvalidName(String toolSetName) {
void validateCreation_shouldNotThrowExceptionForValidName() {
// given
ToolSet toolSet = new ToolSet();
- toolSet.setDisplayName(TEST_TOOLSET_NAME);
+ toolSet.setDisplayName(LocalizedValue.of(TEST_TOOLSET_NAME));
SecuredResource deployment = new SecuredResource(TEST_TOOLSET_NAME);
toolSet.setDeployment(deployment);
ReflectionTestUtils.setField(toolSetValidator, "toolSetNameValidationPattern", NAME_VALIDATION_PATTERN);
@@ -103,7 +104,7 @@ void validateCreation_shouldNotThrowExceptionForValidName() {
void validateCreation_withNameValidationPattern_shouldValidateNameAgainstPattern() {
// given
ToolSet toolSet = new ToolSet();
- toolSet.setDisplayName(TEST_TOOLSET_NAME);
+ toolSet.setDisplayName(LocalizedValue.of(TEST_TOOLSET_NAME));
SecuredResource deployment = new SecuredResource(TEST_TOOLSET_NAME);
toolSet.setDeployment(deployment);
ReflectionTestUtils.setField(toolSetValidator, "toolSetNameValidationPattern", NAME_VALIDATION_PATTERN);
@@ -117,7 +118,7 @@ void validateCreation_withNameValidationPattern_shouldValidateNameAgainstPattern
void validateCreation_withInvalidNameAgainstPattern_shouldThrowException(String invalidName) {
// given
ToolSet toolSet = new ToolSet();
- toolSet.setDisplayName(invalidName);
+ toolSet.setDisplayName(LocalizedValue.of(invalidName));
SecuredResource deployment = new SecuredResource(invalidName);
toolSet.setDeployment(deployment);
ReflectionTestUtils.setField(toolSetValidator, "toolSetNameValidationPattern", NAME_VALIDATION_PATTERN);
@@ -132,7 +133,7 @@ void validateCreation_withInvalidNameAgainstPattern_shouldThrowException(String
void validateUpdate_shouldDelegateToDeploymentValidator() {
// given
ToolSet toolSet = new ToolSet();
- toolSet.setDisplayName(TEST_TOOLSET_NAME);
+ toolSet.setDisplayName(LocalizedValue.of(TEST_TOOLSET_NAME));
SecuredResource deployment = new SecuredResource(TEST_TOOLSET_NAME);
toolSet.setDeployment(deployment);
@@ -145,7 +146,7 @@ void validateUpdate_shouldDelegateToDeploymentValidator() {
void validateToolSetFields_withNullEndpoint_shouldNotThrowException() {
// given
ToolSet toolSet = new ToolSet();
- toolSet.setDisplayName(TEST_TOOLSET_NAME);
+ toolSet.setDisplayName(LocalizedValue.of(TEST_TOOLSET_NAME));
toolSet.setEndpoint(null);
SecuredResource deployment = new SecuredResource(TEST_TOOLSET_NAME);
toolSet.setDeployment(deployment);
@@ -158,7 +159,7 @@ void validateToolSetFields_withNullEndpoint_shouldNotThrowException() {
void validateToolSetFields_withValidEndpoint_shouldNotThrowException() {
// given
ToolSet toolSet = new ToolSet();
- toolSet.setDisplayName(TEST_TOOLSET_NAME);
+ toolSet.setDisplayName(LocalizedValue.of(TEST_TOOLSET_NAME));
toolSet.setEndpoint("https://example.com/api");
SecuredResource deployment = new SecuredResource(TEST_TOOLSET_NAME);
toolSet.setDeployment(deployment);
@@ -172,7 +173,7 @@ void validateToolSetFields_withValidEndpoint_shouldNotThrowException() {
void validateToolSetFields_withBlankEndpoint_shouldThrowException(String blankEndpoint) {
// given
ToolSet toolSet = new ToolSet();
- toolSet.setDisplayName(TEST_TOOLSET_NAME);
+ toolSet.setDisplayName(LocalizedValue.of(TEST_TOOLSET_NAME));
toolSet.setEndpoint(blankEndpoint);
SecuredResource deployment = new SecuredResource(TEST_TOOLSET_NAME);
toolSet.setDeployment(deployment);
diff --git a/src/test/java/com/epam/aidial/cfg/dto/validation/annotation/EndpointValidationTest.java b/src/test/java/com/epam/aidial/cfg/dto/validation/annotation/EndpointValidationTest.java
index 85b6243f1..4b67b9c7d 100644
--- a/src/test/java/com/epam/aidial/cfg/dto/validation/annotation/EndpointValidationTest.java
+++ b/src/test/java/com/epam/aidial/cfg/dto/validation/annotation/EndpointValidationTest.java
@@ -1,5 +1,6 @@
package com.epam.aidial.cfg.dto.validation.annotation;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
import com.epam.aidial.cfg.dto.AdapterDto;
import com.epam.aidial.cfg.dto.ApplicationDto;
import com.epam.aidial.cfg.dto.CreateApplicationResourceDto;
@@ -37,7 +38,7 @@ void init() {
void testApplicationDto_ValidEndpoint(String endpoint) {
ApplicationDto dto = new ApplicationDto();
dto.setName("test-app");
- dto.setDisplayName("Test App");
+ dto.setDisplayName(LocalizedValue.of("Test App"));
dto.setEndpoint(endpoint);
Set> violations = validator.validate(dto);
@@ -49,7 +50,7 @@ void testApplicationDto_ValidEndpoint(String endpoint) {
void testApplicationDto_InvalidEndpoint(String endpoint) {
ApplicationDto dto = new ApplicationDto();
dto.setName("test-app");
- dto.setDisplayName("Test App");
+ dto.setDisplayName(LocalizedValue.of("Test App"));
dto.setEndpoint(endpoint);
Set> violations = validator.validate(dto);
@@ -93,7 +94,7 @@ void testCreateApplicationResourceDto_InvalidEndpoint(String endpoint) {
void testModelDto_ValidEndpoint(String endpoint) {
ModelDto dto = new ModelDto();
dto.setName("test-model");
- dto.setDisplayName("Test Model");
+ dto.setDisplayName(LocalizedValue.of("Test Model"));
dto.setEndpoint(endpoint);
Set> violations = validator.validate(dto);
@@ -105,7 +106,7 @@ void testModelDto_ValidEndpoint(String endpoint) {
void testModelDto_InvalidEndpoint(String endpoint) {
ModelDto dto = new ModelDto();
dto.setName("test-model");
- dto.setDisplayName("Test Model");
+ dto.setDisplayName(LocalizedValue.of("Test Model"));
dto.setEndpoint(endpoint);
Set> violations = validator.validate(dto);
@@ -120,7 +121,7 @@ void testModelDto_InvalidEndpoint(String endpoint) {
void testInterceptorDto_ValidEndpoint(String endpoint) {
InterceptorDto dto = new InterceptorDto();
dto.setName("test-interceptor");
- dto.setDisplayName("Test Interceptor");
+ dto.setDisplayName(LocalizedValue.of("Test Interceptor"));
dto.setEndpoint(endpoint);
Set> violations = validator.validate(dto);
@@ -132,7 +133,7 @@ void testInterceptorDto_ValidEndpoint(String endpoint) {
void testInterceptorDto_InvalidEndpoint(String endpoint) {
InterceptorDto dto = new InterceptorDto();
dto.setName("test-interceptor");
- dto.setDisplayName("Test Interceptor");
+ dto.setDisplayName(LocalizedValue.of("Test Interceptor"));
dto.setEndpoint(endpoint);
Set> violations = validator.validate(dto);
@@ -147,7 +148,7 @@ void testInterceptorDto_InvalidEndpoint(String endpoint) {
void testToolSetDto_ValidEndpoint(String endpoint) {
ToolSetDto dto = new ToolSetDto();
dto.setName("test-toolset");
- dto.setDisplayName("Test ToolSet");
+ dto.setDisplayName(LocalizedValue.of("Test ToolSet"));
dto.setTransport(ToolSetDto.TransportDto.HTTP);
dto.setEndpoint(endpoint);
@@ -160,7 +161,7 @@ void testToolSetDto_ValidEndpoint(String endpoint) {
void testToolSetDto_InvalidEndpoint(String endpoint) {
ToolSetDto dto = new ToolSetDto();
dto.setName("test-toolset");
- dto.setDisplayName("Test ToolSet");
+ dto.setDisplayName(LocalizedValue.of("Test ToolSet"));
dto.setTransport(ToolSetDto.TransportDto.HTTP);
dto.setEndpoint(endpoint);
diff --git a/src/test/java/com/epam/aidial/cfg/functional/config/FunctionalTestConfiguration.java b/src/test/java/com/epam/aidial/cfg/functional/config/FunctionalTestConfiguration.java
index 529875276..371875407 100644
--- a/src/test/java/com/epam/aidial/cfg/functional/config/FunctionalTestConfiguration.java
+++ b/src/test/java/com/epam/aidial/cfg/functional/config/FunctionalTestConfiguration.java
@@ -39,6 +39,7 @@
import com.epam.aidial.cfg.domain.service.RoleService;
import com.epam.aidial.cfg.domain.service.RouteService;
import com.epam.aidial.cfg.domain.service.ToolSetService;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
import com.epam.aidial.cfg.features.flag.aspect.FeatureFlagGateEvaluationAspect;
import com.epam.aidial.cfg.functional.tests.history.TestHistoryFacade;
import com.epam.aidial.cfg.mapper.ResourceCredentialMapper;
@@ -116,6 +117,14 @@ public AutoImportOnBootstrapProperties autoImportOnBootstrapProperties() {
return new AutoImportOnBootstrapProperties();
}
+ @Bean
+ public com.epam.aidial.cfg.configuration.LocalizationProperties localizationProperties() {
+ com.epam.aidial.cfg.configuration.LocalizationProperties properties =
+ new com.epam.aidial.cfg.configuration.LocalizationProperties();
+ properties.setLocale("en");
+ return properties;
+ }
+
@Bean
public ConfigExportProperties configExportProperties() {
return new ConfigExportProperties();
@@ -125,7 +134,7 @@ public ConfigExportProperties configExportProperties() {
public CoreConfigRetriever configSource() {
CoreModel model = new CoreModel();
model.setName("testModel");
- model.setDisplayName("testModel displayName");
+ model.setDisplayName(LocalizedValue.of("testModel displayName"));
model.setEndpoint("https://endpoint1/chat/completions");
Config config = new Config();
diff --git a/src/test/java/com/epam/aidial/cfg/functional/tests/ApplicationFunctionalTest.java b/src/test/java/com/epam/aidial/cfg/functional/tests/ApplicationFunctionalTest.java
index 6e9327bd6..539cb2caa 100644
--- a/src/test/java/com/epam/aidial/cfg/functional/tests/ApplicationFunctionalTest.java
+++ b/src/test/java/com/epam/aidial/cfg/functional/tests/ApplicationFunctionalTest.java
@@ -6,6 +6,7 @@
import com.epam.aidial.cfg.configuration.JsonMapperConfiguration;
import com.epam.aidial.cfg.domain.model.ToolSet;
import com.epam.aidial.cfg.domain.service.DeploymentManagerService;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
import com.epam.aidial.cfg.dto.ApplicationDto;
import com.epam.aidial.cfg.dto.ApplicationInfoDto;
import com.epam.aidial.cfg.dto.AuthenticationTypeDto;
@@ -158,13 +159,13 @@ public void shouldSuccessfullyCreateAndUpdateApplication() {
ApplicationDto applicationDto = createApplicationDtoWithEndpointAndLimits("1");
applicationFacade.createApplication(applicationDto);
ApplicationDto updatedApplication = createApplicationDtoWithEndpointAndLimits("1");
- updatedApplication.setDescription("new application description");
+ updatedApplication.setDescription(LocalizedValue.of(Map.of("en", "new application description")));
applicationFacade.updateApplication(applicationDto.getName(), updatedApplication, "*");
ApplicationDto actual = applicationFacade.getApplication(applicationDto.getName());
var expected = createApplicationDtoWithEndpointAndLimits("1");
- expected.setDescription("new application description");
+ expected.setDescription(LocalizedValue.of(Map.of("en", "new application description")));
assertApplication(actual, expected);
}
@@ -179,7 +180,7 @@ public void shouldSuccessfullyCreateAndAddInterceptor() {
applicationFacade.createApplication(applicationDto);
ApplicationDto updatedApplication = createApplicationDtoWithEndpointAndLimits("1");
- updatedApplication.setDescription("new model description");
+ updatedApplication.setDescription(LocalizedValue.of("New ToolSet description"));
updatedApplication.setDefaults(Map.of());
updatedApplication.setInterceptors(List.of("interceptor1"));
@@ -196,7 +197,7 @@ public void shouldThrowExceptionWhenRenameApplication() {
ApplicationDto applicationDto = createApplicationDtoWithEndpointAndLimits("1");
applicationFacade.createApplication(applicationDto);
ApplicationDto updatedApplication = createApplicationDtoWithEndpointAndLimits("2");
- updatedApplication.setDescription("new application description");
+ updatedApplication.setDescription(LocalizedValue.of("New ToolSet description"));
IllegalArgumentException exception = Assertions.assertThrows(
IllegalArgumentException.class,
@@ -240,7 +241,7 @@ public void shouldSuccessfullyUpdateApplicationWithCorrectHash() {
ApplicationDto applicationDto = createApplicationDtoWithEndpointAndLimits("1");
applicationFacade.createApplication(applicationDto);
ApplicationDto updatedApplication = createApplicationDtoWithEndpointAndLimits("1");
- updatedApplication.setDescription("new application description");
+ updatedApplication.setDescription(LocalizedValue.of("New ToolSet description"));
var hash = applicationFacade.getApplicationWithHash(applicationDto.getName()).hash();
@@ -248,7 +249,7 @@ public void shouldSuccessfullyUpdateApplicationWithCorrectHash() {
var actual = applicationFacade.getApplication(applicationDto.getName());
var expected = createApplicationDtoWithEndpointAndLimits("1");
- expected.setDescription("new application description");
+ expected.setDescription(LocalizedValue.of("New ToolSet description"));
assertApplication(actual, expected);
}
@@ -268,7 +269,7 @@ public void shouldSuccessfullyCreateWithInterceptor() {
InterceptorDto interceptorDto = createInterceptorDto("1");
interceptorDto.setName("int1");
- interceptorDto.setDescription("int1_dsc");
+ interceptorDto.setDescription(LocalizedValue.of("int1_dsc"));
interceptorDto.setEndpoint("https://endpoint.test.com/interceptor");
interceptorFacade.createInterceptor(interceptorDto);
@@ -406,12 +407,12 @@ public void shouldThrowExceptionWhenCreateApplicationWithExistingDisplayNameAndD
initRoles();
ApplicationDto applicationDto = createApplicationDtoWithEndpointAndLimits("1");
- applicationDto.setDisplayName("display_name");
+ applicationDto.setDisplayName(LocalizedValue.of("display_name"));
applicationDto.setDisplayVersion("1.0");
applicationFacade.createApplication(applicationDto);
ApplicationDto applicationDto2 = createApplicationDtoWithEndpointAndLimits("2");
- applicationDto2.setDisplayName("display_name");
+ applicationDto2.setDisplayName(LocalizedValue.of("display_name"));
applicationDto2.setDisplayVersion("1.0");
EntityAlreadyExistsException exception = Assertions.assertThrows(
@@ -426,14 +427,14 @@ public void shouldThrowExceptionWhenUpdateApplicationWithExistingDisplayNameAndD
initRoles();
ApplicationDto applicationDto = createApplicationDtoWithEndpointAndLimits("1");
- applicationDto.setDisplayName("display_name");
+ applicationDto.setDisplayName(LocalizedValue.of("display_name"));
applicationFacade.createApplication(applicationDto);
ApplicationDto applicationDto2 = createApplicationDtoWithEndpointAndLimits("2");
- applicationDto2.setDisplayName("display_name_2");
+ applicationDto2.setDisplayName(LocalizedValue.of("display_name_2"));
applicationFacade.createApplication(applicationDto2);
- applicationDto.setDisplayName("display_name_2");
+ applicationDto.setDisplayName(LocalizedValue.of("display_name_2"));
EntityAlreadyExistsException exception = Assertions.assertThrows(
EntityAlreadyExistsException.class,
@@ -517,7 +518,7 @@ public void shouldSuccessfullyGetFullySyncedEntitySyncStateWhenApplicationIsEqua
public void shouldSuccessfullyGetInProgressTooLongEntitySyncStateWhenApplicationIsNotEqualToConfigApplicationAndUpdatedLongAgo() throws JsonProcessingException {
doReturn(1000L).when(transactionTimestampContext).getTimestamp();
ApplicationDto applicationDto = createApplicationDtoWithEndpoint("1");
- applicationDto.setDescription("description OLD");
+ applicationDto.setDescription(LocalizedValue.of("description OLD"));
applicationFacade.createApplication(applicationDto);
JsonNode config = coreConfig();
diff --git a/src/test/java/com/epam/aidial/cfg/functional/tests/ApplicationTypeSchemaFunctionalTest.java b/src/test/java/com/epam/aidial/cfg/functional/tests/ApplicationTypeSchemaFunctionalTest.java
index 8a89eb3f7..dcfee6f8b 100644
--- a/src/test/java/com/epam/aidial/cfg/functional/tests/ApplicationTypeSchemaFunctionalTest.java
+++ b/src/test/java/com/epam/aidial/cfg/functional/tests/ApplicationTypeSchemaFunctionalTest.java
@@ -3,6 +3,7 @@
import com.epam.aidial.cfg.configuration.JsonMapperConfiguration;
import com.epam.aidial.cfg.domain.model.ExternalSchema;
import com.epam.aidial.cfg.domain.service.ExternalSchemaLoader;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
import com.epam.aidial.cfg.dto.ApplicationDto;
import com.epam.aidial.cfg.dto.ApplicationInfoDto;
import com.epam.aidial.cfg.dto.ApplicationTypeSchemaDto;
@@ -295,7 +296,7 @@ public void shouldSuccessfullyCreateAndDeleteApplicationTypeSchemaAndApplication
ApplicationDto applicationDto = new ApplicationDto();
applicationDto.setName("application");
- applicationDto.setDisplayName("application");
+ applicationDto.setDisplayName(LocalizedValue.of("application"));
applicationDto.setSource(new ApplicationSchemaSourceDto(new URI("https://test-schema.example")));
applicationFacade.createApplication(applicationDto);
// when
diff --git a/src/test/java/com/epam/aidial/cfg/functional/tests/ConfigTransferFunctionalTest.java b/src/test/java/com/epam/aidial/cfg/functional/tests/ConfigTransferFunctionalTest.java
index 39bbc715d..f1e328a64 100644
--- a/src/test/java/com/epam/aidial/cfg/functional/tests/ConfigTransferFunctionalTest.java
+++ b/src/test/java/com/epam/aidial/cfg/functional/tests/ConfigTransferFunctionalTest.java
@@ -18,6 +18,7 @@
import com.epam.aidial.cfg.domain.model.source.InterceptorRunnerSource;
import com.epam.aidial.cfg.domain.model.source.ModelAdapterSource;
import com.epam.aidial.cfg.domain.service.DatabaseService;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
import com.epam.aidial.cfg.dto.AdapterDto;
import com.epam.aidial.cfg.dto.AddonDto;
import com.epam.aidial.cfg.dto.ApplicationDto;
@@ -285,7 +286,7 @@ void testImport_WithConflict() throws IOException {
Map models = modelFacade.getAll().stream().collect(Collectors.toMap(ModelDto::getName, a -> a));
Assertions.assertThat(models).containsOnlyKeys("testModel1", "testModel2");
Assertions.assertThat(models.get("testModel1")).satisfies(model -> {
- Assertions.assertThat(model.getDisplayName()).isEqualTo("Test Model1");
+ Assertions.assertThat(model.getDisplayName()).isEqualTo(LocalizedValue.of("Test Model1"));
Assertions.assertThat(model.getDisplayVersion()).isEqualTo("2.0.0");
Assertions.assertThat(model.getEndpoint()).isNull();
Assertions.assertThat(model.getSource() instanceof ModelAdapterSource);
@@ -495,7 +496,7 @@ void testImport_TwoModelsAndRoleExist_ImportModelWithNewUserRolesAndExistingRole
// given
var dial22ModelDto = new ModelDto();
dial22ModelDto.setName("DIAL22");
- dial22ModelDto.setDisplayName("DIAL22");
+ dial22ModelDto.setDisplayName(LocalizedValue.of("DIAL22"));
dial22ModelDto.setIsPublic(true);
modelFacade.createModel(dial22ModelDto);
@@ -2461,7 +2462,7 @@ void testImportZip() throws IOException {
Assertions.assertThat(interceptors.get("testInterceptor1")).satisfies(i -> {
Assertions.assertThat(i.getEntities()).containsExactlyInAnyOrder("testModel1", "testApplication1");
- Assertions.assertThat(i.getDisplayName()).isEqualTo("testInterceptor1");
+ Assertions.assertThat(i.getDisplayName()).isEqualTo(LocalizedValue.of("testInterceptor1"));
});
Assertions.assertThat(interceptors.get("testInterceptor2")).satisfies(i ->
Assertions.assertThat(((InterceptorRunnerSourceDto) i.getSource()).runnerName()).isEqualTo("testRunner1")
@@ -2560,7 +2561,7 @@ void testImportZip_WithConflict() throws IOException {
Assertions.assertThat(interceptors.get("testInterceptor1")).satisfies(i -> {
Assertions.assertThat(i.getEntities()).containsExactlyInAnyOrder("testModel1", "testApplication1");
- Assertions.assertThat(i.getDisplayName()).isEqualTo("testInterceptor1");
+ Assertions.assertThat(i.getDisplayName()).isEqualTo(LocalizedValue.of("testInterceptor1"));
});
Assertions.assertThat(interceptors.get("testInterceptor2")).satisfies(i ->
Assertions.assertThat(((InterceptorRunnerSourceDto) i.getSource()).runnerName()).isEqualTo("testRunner1")
@@ -3070,7 +3071,7 @@ void testExportCoreConfig_VersionFieldSerialization() throws IOException {
var modelDto = new ModelDto();
modelDto.setName(modelName);
- modelDto.setDisplayName(modelName);
+ modelDto.setDisplayName(LocalizedValue.of(modelName));
modelDto.setAuthor(author);
modelDto.setCreatedAt(createdAt);
modelDto.setUpdatedAt(updatedAt);
diff --git a/src/test/java/com/epam/aidial/cfg/functional/tests/CoreConfigAutoImportOnBootstrapFunctionalTest.java b/src/test/java/com/epam/aidial/cfg/functional/tests/CoreConfigAutoImportOnBootstrapFunctionalTest.java
index e373fc3ad..2bd386f0d 100644
--- a/src/test/java/com/epam/aidial/cfg/functional/tests/CoreConfigAutoImportOnBootstrapFunctionalTest.java
+++ b/src/test/java/com/epam/aidial/cfg/functional/tests/CoreConfigAutoImportOnBootstrapFunctionalTest.java
@@ -1,5 +1,6 @@
package com.epam.aidial.cfg.functional.tests;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
import com.epam.aidial.cfg.dto.ConfigRevisionDto;
import com.epam.aidial.cfg.dto.ModelDto;
import com.epam.aidial.cfg.functional.tests.history.TestHistoryFacade;
@@ -33,7 +34,7 @@ public void testCoreConfigAutoImportedSuccessfully() {
assertThat(models).hasSize(1).first().satisfies(modelDto -> {
assertThat(modelDto.getName()).isEqualTo("testModel");
- assertThat(modelDto.getDisplayName()).isEqualTo("testModel displayName");
+ assertThat(modelDto.getDisplayName()).isEqualTo(LocalizedValue.of("testModel displayName"));
});
ConfigRevisionDto latestRevision = CollectionUtils.lastElement(historyFacade.getRevisionsList());
@@ -60,7 +61,7 @@ public void testMergeJson_laterFileWins() {
.containsExactlyInAnyOrder("model-a", "model-b", "model-shared");
assertThat(models).filteredOn(m -> "model-shared".equals(m.getName()))
.first().extracting(ModelDto::getDisplayName)
- .isEqualTo("Shared from B (wins)");
+ .isEqualTo(LocalizedValue.of("Shared from B (wins)"));
}
}
diff --git a/src/test/java/com/epam/aidial/cfg/functional/tests/InterceptorFunctionalTest.java b/src/test/java/com/epam/aidial/cfg/functional/tests/InterceptorFunctionalTest.java
index de4590438..28aade36c 100644
--- a/src/test/java/com/epam/aidial/cfg/functional/tests/InterceptorFunctionalTest.java
+++ b/src/test/java/com/epam/aidial/cfg/functional/tests/InterceptorFunctionalTest.java
@@ -5,6 +5,7 @@
import com.epam.aidial.cfg.client.dto.InterceptorDeploymentInfoDto;
import com.epam.aidial.cfg.configuration.JsonMapperConfiguration;
import com.epam.aidial.cfg.domain.service.DeploymentManagerService;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
import com.epam.aidial.cfg.dto.ApplicationDto;
import com.epam.aidial.cfg.dto.ApplicationTypeSchemaDto;
import com.epam.aidial.cfg.dto.DeploymentInterfaceDto;
@@ -472,8 +473,8 @@ public void shouldResolveEndpointsForContainerSource() {
InterceptorDto interceptorDto = new InterceptorDto();
interceptorDto.setName("container-interceptor");
- interceptorDto.setDisplayName("container-interceptor");
- interceptorDto.setDescription("Container interceptor");
+ interceptorDto.setDisplayName(LocalizedValue.of("container-interceptor"));
+ interceptorDto.setDescription(LocalizedValue.of("Container interceptor"));
InterceptorContainerSourceDto sourceDto = new InterceptorContainerSourceDto(
containerId,
@@ -653,7 +654,7 @@ public void shouldSuccessfullyGetFullySyncedEntitySyncStateWhenInterceptorIsEqua
public void shouldSuccessfullyGetInProgressTooLongEntitySyncStateWhenInterceptorIsNotEqualToConfigInterceptorAndUpdatedLongAgo() throws JsonProcessingException {
doReturn(1000L).when(transactionTimestampContext).getTimestamp();
InterceptorDto interceptorDto = createInterceptorDto("1");
- interceptorDto.setDescription("description OLD");
+ interceptorDto.setDescription(LocalizedValue.of("description OLD"));
interceptorFacade.createInterceptor(interceptorDto);
JsonNode config = coreConfig();
diff --git a/src/test/java/com/epam/aidial/cfg/functional/tests/InterceptorRunnerFunctionalTest.java b/src/test/java/com/epam/aidial/cfg/functional/tests/InterceptorRunnerFunctionalTest.java
index 9a8452a8f..218ca6e27 100644
--- a/src/test/java/com/epam/aidial/cfg/functional/tests/InterceptorRunnerFunctionalTest.java
+++ b/src/test/java/com/epam/aidial/cfg/functional/tests/InterceptorRunnerFunctionalTest.java
@@ -1,5 +1,6 @@
package com.epam.aidial.cfg.functional.tests;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
import com.epam.aidial.cfg.dto.InterceptorDto;
import com.epam.aidial.cfg.dto.InterceptorRunnerDto;
import com.epam.aidial.cfg.dto.source.InterceptorEndpointsSourceDto;
@@ -228,8 +229,8 @@ private InterceptorRunnerDto createDto(String suffix) {
private InterceptorDto createInterceptorDto(String suffix, String runnerName) {
InterceptorDto interceptorDto = new InterceptorDto();
interceptorDto.setName("interceptor" + suffix);
- interceptorDto.setDisplayName("interceptor" + suffix);
- interceptorDto.setDescription("description" + suffix);
+ interceptorDto.setDisplayName(LocalizedValue.of("interceptor" + suffix));
+ interceptorDto.setDescription(LocalizedValue.of("description" + suffix));
interceptorDto.setSource(new InterceptorRunnerSourceDto(runnerName));
return interceptorDto;
}
diff --git a/src/test/java/com/epam/aidial/cfg/functional/tests/ModelFunctionalTest.java b/src/test/java/com/epam/aidial/cfg/functional/tests/ModelFunctionalTest.java
index 9f6782576..a5a8e5c9a 100644
--- a/src/test/java/com/epam/aidial/cfg/functional/tests/ModelFunctionalTest.java
+++ b/src/test/java/com/epam/aidial/cfg/functional/tests/ModelFunctionalTest.java
@@ -4,6 +4,7 @@
import com.epam.aidial.cfg.client.dto.InferenceDeploymentInfoDto;
import com.epam.aidial.cfg.configuration.JsonMapperConfiguration;
import com.epam.aidial.cfg.domain.service.DeploymentManagerService;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
import com.epam.aidial.cfg.dto.AdapterDto;
import com.epam.aidial.cfg.dto.DeploymentInterfaceDto;
import com.epam.aidial.cfg.dto.EntitySyncStateDto;
@@ -142,13 +143,13 @@ public void shouldSuccessfullyCreateAndUpdateModel() {
ModelDto updatedModel = createModelDtoWithLimitsAndEndpoint("1");
updatedModel.setSource(new ModelAdapterSourceDto("adapter2", "/newEndpointDeploymentName/chat/completions"));
- updatedModel.setDescription("new model description");
+ updatedModel.setDescription(LocalizedValue.of("new model description"));
updatedModel.setDefaults(Map.of());
modelFacade.updateModel(modelDto.getName(), updatedModel, "*");
ModelDto actual = modelFacade.getModel(modelDto.getName());
var expected = createModelDtoWithLimitsAndEndpoint("1");
- expected.setDescription("new model description");
+ expected.setDescription(LocalizedValue.of("new model description"));
expected.setDefaults(Map.of());
expected.setMaxRetryAttempts(1);
expected.setDefaultRoleLimit(new LimitDto());
@@ -189,7 +190,7 @@ public void shouldThrowExceptionWhenRenameModel() {
ModelDto modelDto = createModelDtoWithLimitsAndEndpoint("1");
modelFacade.createModel(modelDto);
ModelDto updatedModel = createModelDtoWithLimitsAndEndpoint("2");
- updatedModel.setDescription("new model description");
+ updatedModel.setDescription(LocalizedValue.of("New ToolSet description"));
IllegalArgumentException exception = Assertions.assertThrows(
IllegalArgumentException.class,
@@ -209,7 +210,7 @@ public void shouldSuccessfullyCreateAndAddInterceptor() {
ModelDto modelDto = createModelDtoWithLimitsAndEndpoint("1");
modelFacade.createModel(modelDto);
ModelDto updatedModel = createModelDtoWithLimitsAndEndpoint("1");
- updatedModel.setDescription("new model description");
+ updatedModel.setDescription(LocalizedValue.of("New ToolSet description"));
updatedModel.setDefaults(Map.of());
updatedModel.setInterceptors(List.of("interceptor1"));
@@ -380,12 +381,12 @@ public void shouldThrowExceptionWhenCreateModelWithExistingDisplayNameAndDisplay
initRoles();
ModelDto modelDto = createModelDtoWithLimitsAndEndpoint("1");
- modelDto.setDisplayName("display_name");
+ modelDto.setDisplayName(LocalizedValue.of("display_name"));
modelDto.setDisplayVersion("1.0");
modelFacade.createModel(modelDto);
ModelDto modelDto2 = createModelDtoWithLimitsAndEndpoint("2");
- modelDto2.setDisplayName("display_name");
+ modelDto2.setDisplayName(LocalizedValue.of("display_name"));
modelDto2.setDisplayVersion("1.0");
EntityAlreadyExistsException exception = Assertions.assertThrows(
@@ -400,14 +401,14 @@ public void shouldThrowExceptionWhenUpdateModelWithExistingDisplayNameAndDisplay
initRoles();
ModelDto modelDto = createModelDtoWithLimitsAndEndpoint("1");
- modelDto.setDisplayName("display_name");
+ modelDto.setDisplayName(LocalizedValue.of("display_name"));
modelFacade.createModel(modelDto);
ModelDto modelDto2 = createModelDtoWithLimitsAndEndpoint("2");
- modelDto2.setDisplayName("display_name_2");
+ modelDto2.setDisplayName(LocalizedValue.of("display_name_2"));
modelFacade.createModel(modelDto2);
- modelDto.setDisplayName("display_name_2");
+ modelDto.setDisplayName(LocalizedValue.of("display_name_2"));
EntityAlreadyExistsException exception = Assertions.assertThrows(
EntityAlreadyExistsException.class,
@@ -490,14 +491,14 @@ public void shouldSuccessfullyUpdateCoreModel() {
modelFacade.createModel(modelDto);
CoreModel coreModel = modelFacade.getCoreModelWithHash(modelDto.getName()).core();
- coreModel.setDescription("New description");
+ coreModel.setDescription(LocalizedValue.of("New description"));
modelFacade.updateModel(modelDto.getName(), coreModel, "*");
ModelDto actual = modelFacade.getModel(modelDto.getName());
Assertions.assertEquals("model1", actual.getName());
Assertions.assertEquals(adapterSourceDto, actual.getSource());
- Assertions.assertEquals("New description", actual.getDescription());
+ Assertions.assertEquals(LocalizedValue.of("New description"), actual.getDescription());
}
@Test
@@ -619,7 +620,7 @@ public void shouldSuccessfullyGetInProgressTooLongEntitySyncStateWhenModelIsNotE
ModelDto modelDto = createModelDto("1");
modelDto.setUpstreams(List.of(secretUpstreamDto, publicUpstreamDto));
- modelDto.setDescription("description OLD");
+ modelDto.setDescription(LocalizedValue.of("description OLD"));
modelDto.setEmbeddingDimensions(1536);
modelFacade.createModel(modelDto);
@@ -668,8 +669,8 @@ private void assertModel(ModelDto actual, ModelDto expected) {
private ModelDto expectedDto1() {
ModelDto modelDto = new ModelDto();
modelDto.setName("model1");
- modelDto.setDisplayName("model1");
- modelDto.setDescription("description1");
+ modelDto.setDisplayName(LocalizedValue.of("model1"));
+ modelDto.setDescription(LocalizedValue.of("description1"));
modelDto.setRoleLimits(Map.of(
"role1", new LimitDto()
));
diff --git a/src/test/java/com/epam/aidial/cfg/functional/tests/ToolSetFunctionalTest.java b/src/test/java/com/epam/aidial/cfg/functional/tests/ToolSetFunctionalTest.java
index 53613cc5d..3851407a6 100644
--- a/src/test/java/com/epam/aidial/cfg/functional/tests/ToolSetFunctionalTest.java
+++ b/src/test/java/com/epam/aidial/cfg/functional/tests/ToolSetFunctionalTest.java
@@ -8,6 +8,7 @@
import com.epam.aidial.cfg.configuration.JsonMapperConfiguration;
import com.epam.aidial.cfg.domain.model.ToolSet.Transport;
import com.epam.aidial.cfg.domain.service.DeploymentManagerService;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
import com.epam.aidial.cfg.dto.AuthenticationTypeDto;
import com.epam.aidial.cfg.dto.EntitySyncStateDto;
import com.epam.aidial.cfg.dto.EntitySyncStateStatusDto;
@@ -209,14 +210,14 @@ public void shouldSuccessfullyCreateAndUpdateToolSet() {
toolSetFacade.createToolSet(toolSetDto);
ToolSetDto updatedToolSet = createToolSetDto("1");
- updatedToolSet.setDescription("New ToolSet description");
+ updatedToolSet.setDescription(LocalizedValue.of("New ToolSet description"));
toolSetFacade.updateToolSet(toolSetDto.getName(), updatedToolSet, "*");
ToolSetDto actual = toolSetFacade.getToolSet(toolSetDto.getName());
var expected = createToolSetDto("1");
- expected.setDescription("New ToolSet description");
+ expected.setDescription(LocalizedValue.of("New ToolSet description"));
assertToolSet(actual, expected);
}
@@ -257,14 +258,14 @@ public void shouldSuccessfullyUpdateToolSetWithCorrectHash() {
var hash = toolSetFacade.getToolSetWithHash(toolSetDto.getName()).hash();
ToolSetDto updatedToolSet = createToolSetDto("1");
- updatedToolSet.setDescription("New ToolSet description");
+ updatedToolSet.setDescription(LocalizedValue.of("New ToolSet description"));
toolSetFacade.updateToolSet(toolSetDto.getName(), updatedToolSet, hash);
ToolSetDto actual = toolSetFacade.getToolSet(toolSetDto.getName());
var expected = createToolSetDto("1");
- expected.setDescription("New ToolSet description");
+ expected.setDescription(LocalizedValue.of("New ToolSet description"));
assertToolSet(actual, expected);
}
@@ -275,7 +276,7 @@ public void shouldThrowWhenUpdateToolSetWithIncorrectHash() {
toolSetFacade.createToolSet(toolSetDto);
ToolSetDto updatedToolSet = createToolSetDto("1");
- updatedToolSet.setDescription("New ToolSet description");
+ updatedToolSet.setDescription(LocalizedValue.of("New ToolSet description"));
Assertions.assertThrows(OptimisticLockConflictException.class,
() -> toolSetFacade.updateToolSet(toolSetDto.getName(), updatedToolSet, "test"));
@@ -286,7 +287,7 @@ public void shouldThrowExceptionWhenRenameToolSet() {
ToolSetDto toolSetDto = createToolSetDto("1");
toolSetFacade.createToolSet(toolSetDto);
ToolSetDto updatedToolSet = createToolSetDto("2");
- updatedToolSet.setDescription("New ToolSet description");
+ updatedToolSet.setDescription(LocalizedValue.of("New ToolSet description"));
IllegalArgumentException exception = Assertions.assertThrows(
IllegalArgumentException.class,
@@ -385,7 +386,7 @@ public void shouldResolveEndpointsForContainerSource() {
ToolSetDto toolSetDto = createToolSetDto("1");
toolSetDto.setName("container-toolset");
- toolSetDto.setDescription("Container toolset");
+ toolSetDto.setDescription(LocalizedValue.of("Container toolset"));
ToolSetContainerSourceDto sourceDto = new ToolSetContainerSourceDto(
containerId,
@@ -438,7 +439,7 @@ public void shouldRefreshEndpointsForContainerSource() {
ToolSetDto toolSetDto = createToolSetDto("1");
toolSetDto.setName(refreshedToolSetName);
- toolSetDto.setDescription("Refresh toolset");
+ toolSetDto.setDescription(LocalizedValue.of("Refresh toolset"));
ToolSetContainerSourceDto sourceDto = new ToolSetContainerSourceDto(
containerId,
@@ -520,7 +521,7 @@ public void shouldSuccessfullyGetFullySyncedEntitySyncStateWhenToolSetIsEqualToC
public void shouldSuccessfullyGetInProgressTooLongEntitySyncStateWhenToolSetIsNotEqualToConfigToolSetAndUpdatedLongAgo() throws JsonProcessingException {
doReturn(1000L).when(transactionTimestampContext).getTimestamp();
ToolSetDto toolSetDto = createToolSetDto("1");
- toolSetDto.setDescription("description OLD");
+ toolSetDto.setDescription(LocalizedValue.of("description OLD"));
toolSetFacade.createToolSet(toolSetDto);
JsonNode config = coreConfig();
diff --git a/src/test/java/com/epam/aidial/cfg/functional/tests/history/ActivityAuditFunctionalTest.java b/src/test/java/com/epam/aidial/cfg/functional/tests/history/ActivityAuditFunctionalTest.java
index 177120f3b..6e84afaa9 100644
--- a/src/test/java/com/epam/aidial/cfg/functional/tests/history/ActivityAuditFunctionalTest.java
+++ b/src/test/java/com/epam/aidial/cfg/functional/tests/history/ActivityAuditFunctionalTest.java
@@ -1,6 +1,7 @@
package com.epam.aidial.cfg.functional.tests.history;
import com.epam.aidial.cfg.domain.model.page.SortDirection;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
import com.epam.aidial.cfg.dto.AuditActivityDto;
import com.epam.aidial.cfg.dto.LimitDto;
import com.epam.aidial.cfg.dto.ModelDto;
@@ -79,7 +80,7 @@ public void shouldSuccessfullyLogAuditActivities() {
// update model1 description
ModelDto updatedModel = createModelDtoWithLimitsAndEndpoint("ActivityAudit1");
- updatedModel.setDescription("new model description");
+ updatedModel.setDescription(LocalizedValue.of("new model description"));
updatedModel.setDefaults(Map.of());
modelFacade.updateModel(modelDto.getName(), updatedModel, "*");
diff --git a/src/test/java/com/epam/aidial/cfg/functional/tests/history/ApplicationHistoryFunctionalTest.java b/src/test/java/com/epam/aidial/cfg/functional/tests/history/ApplicationHistoryFunctionalTest.java
index ef188be5a..deed1c36a 100644
--- a/src/test/java/com/epam/aidial/cfg/functional/tests/history/ApplicationHistoryFunctionalTest.java
+++ b/src/test/java/com/epam/aidial/cfg/functional/tests/history/ApplicationHistoryFunctionalTest.java
@@ -1,5 +1,6 @@
package com.epam.aidial.cfg.functional.tests.history;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
import com.epam.aidial.cfg.dto.ApplicationDto;
import com.epam.aidial.cfg.dto.ApplicationInfoDto;
import com.epam.aidial.cfg.dto.ApplicationTypeSchemaDto;
@@ -62,7 +63,7 @@ public void shouldSuccessfullyCreateAndUpdateApplication() {
// 2 update application1 description
ApplicationDto updatedApplication = createApplicationDtoWithEndpointAndLimits("1");
- updatedApplication.setDescription("new application description");
+ updatedApplication.setDescription(LocalizedValue.of("new application description"));
updatedApplication.setEndpoint("endpoint2");
applicationFacade.updateApplication(applicationDto.getName(), updatedApplication, "*");
@@ -71,7 +72,7 @@ public void shouldSuccessfullyCreateAndUpdateApplication() {
var expected = createApplicationDtoWithEndpointAndLimits("1");
ShareResourceLimitDto defaultShareResourceLimitDto = new ShareResourceLimitDto();
defaultShareResourceLimitDto.setMaxAcceptedUsers(10);
- expected.setDescription("new application description");
+ expected.setDescription(LocalizedValue.of("new application description"));
expected.setDefaultRoleLimit(new LimitDto());
expected.setInterceptors(List.of());
expected.setEndpoint("endpoint2");
diff --git a/src/test/java/com/epam/aidial/cfg/functional/tests/history/InterceptorHistoryFunctionalTest.java b/src/test/java/com/epam/aidial/cfg/functional/tests/history/InterceptorHistoryFunctionalTest.java
index 78357278c..28d20978e 100644
--- a/src/test/java/com/epam/aidial/cfg/functional/tests/history/InterceptorHistoryFunctionalTest.java
+++ b/src/test/java/com/epam/aidial/cfg/functional/tests/history/InterceptorHistoryFunctionalTest.java
@@ -1,5 +1,6 @@
package com.epam.aidial.cfg.functional.tests.history;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
import com.epam.aidial.cfg.dto.ApplicationDto;
import com.epam.aidial.cfg.dto.ConfigRevisionDto;
import com.epam.aidial.cfg.dto.GlobalSettingsDto;
@@ -55,13 +56,13 @@ public void shouldSuccessfullyCreateAndUpdateInterceptor() {
// update interceptor1 description
InterceptorDto updatedInterceptor = createInterceptorDto("1");
- updatedInterceptor.setDescription("new interceptor description");
+ updatedInterceptor.setDescription(LocalizedValue.of("new interceptor description"));
interceptorFacade.updateInterceptor(interceptorDto.getName(), updatedInterceptor, "*");
// verify interceptor1
InterceptorDto actual = interceptorFacade.getInterceptor(interceptorDto.getName());
var expected = createInterceptorDto("1");
- expected.setDescription("new interceptor description");
+ expected.setDescription(LocalizedValue.of("new interceptor description"));
expected.setSource(new InterceptorEndpointsSourceDto());
expected.setApplicationTypeSchemas(List.of());
assertInterceptor(actual, expected);
@@ -69,7 +70,7 @@ public void shouldSuccessfullyCreateAndUpdateInterceptor() {
var actualAtRevision = actual;
final Integer revNumberToRollback = CollectionUtils.lastElement(historyFacade.getRevisionsList()).getId();
- updatedInterceptor.setDescription("new new interceptor description");
+ updatedInterceptor.setDescription(LocalizedValue.of("new new interceptor description"));
updatedInterceptor.setDefaults(Map.of("key1", "val1"));
interceptorFacade.updateInterceptor(interceptorDto.getName(), updatedInterceptor, "*");
diff --git a/src/test/java/com/epam/aidial/cfg/functional/tests/history/ModelHistoryFunctionalTest.java b/src/test/java/com/epam/aidial/cfg/functional/tests/history/ModelHistoryFunctionalTest.java
index ffc28fe23..55bd8396e 100644
--- a/src/test/java/com/epam/aidial/cfg/functional/tests/history/ModelHistoryFunctionalTest.java
+++ b/src/test/java/com/epam/aidial/cfg/functional/tests/history/ModelHistoryFunctionalTest.java
@@ -1,5 +1,6 @@
package com.epam.aidial.cfg.functional.tests.history;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
import com.epam.aidial.cfg.dto.AdapterDto;
import com.epam.aidial.cfg.dto.ConfigRevisionDto;
import com.epam.aidial.cfg.dto.InterceptorDto;
@@ -61,14 +62,14 @@ public void shouldSuccessfullyRollbackModels() {
// update model1 description
ModelDto updatedModel = createModelDtoWithLimitsAndEndpoint("1");
- updatedModel.setDescription("new model description");
+ updatedModel.setDescription(LocalizedValue.of("new model description"));
updatedModel.setDefaults(Map.of());
modelFacade.updateModel(modelDto.getName(), updatedModel, "*");
// verify model1
ModelDto actual = modelFacade.getModel(modelDto.getName());
var expected = createModelDtoWithLimitsAndEndpoint("1");
- expected.setDescription("new model description");
+ expected.setDescription(LocalizedValue.of("new model description"));
expected.setDefaults(Map.of());
expected.setDefaultRoleLimit(new LimitDto());
expected.setMaxRetryAttempts(1);
@@ -174,7 +175,7 @@ public void shouldSuccessfullyRollbackModelsWithInterceptorsWhenInterceptorDelet
final Integer revNumberToRollback = CollectionUtils.lastElement(historyFacade.getRevisionsList()).getId();
var actualAtRevision = modelFacade.getAll();
- modelDto.setDescription("new description");
+ modelDto.setDescription(LocalizedValue.of("new description"));
modelFacade.updateModel(modelDto.getName(), modelDto, "*");
interceptorFacade.deleteInterceptor(interceptor1.getName());
diff --git a/src/test/java/com/epam/aidial/cfg/functional/tests/history/ToolSetHistoryFunctionalTest.java b/src/test/java/com/epam/aidial/cfg/functional/tests/history/ToolSetHistoryFunctionalTest.java
index a87bc8eae..765ab2dd0 100644
--- a/src/test/java/com/epam/aidial/cfg/functional/tests/history/ToolSetHistoryFunctionalTest.java
+++ b/src/test/java/com/epam/aidial/cfg/functional/tests/history/ToolSetHistoryFunctionalTest.java
@@ -2,6 +2,7 @@
import com.epam.aidial.cfg.client.dto.McpDeploymentInfoDto;
import com.epam.aidial.cfg.domain.service.DeploymentManagerService;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
import com.epam.aidial.cfg.dto.AuthenticationTypeDto;
import com.epam.aidial.cfg.dto.ConfigRevisionDto;
import com.epam.aidial.cfg.dto.LimitDto;
@@ -80,7 +81,7 @@ public void shouldSuccessfullyCreateAndUpdateToolSet() {
// 2. Update ToolSet1 description
ToolSetDto updatedToolSet = createToolSetDto("1");
- updatedToolSet.setDescription("New ToolSet description");
+ updatedToolSet.setDescription(LocalizedValue.of("New ToolSet description"));
updatedToolSet.setSource(containerSourceDto);
updatedToolSet.setAuthSettings(authSettingsDto);
toolSetFacade.updateToolSet(toolSetDto.getName(), updatedToolSet, "*");
@@ -88,7 +89,7 @@ public void shouldSuccessfullyCreateAndUpdateToolSet() {
// 3. Verify ToolSet1
ToolSetDto actual = toolSetFacade.getToolSet(toolSetDto.getName());
var expected = createToolSetDto("1");
- expected.setDescription("New ToolSet description");
+ expected.setDescription(LocalizedValue.of("New ToolSet description"));
expected.setDefaultRoleLimit(new LimitDto());
expected.setEndpoint(containerUrl + endpointPath);
expected.setSource(containerSourceDto);
diff --git a/src/test/java/com/epam/aidial/cfg/functional/utils/FunctionalTestHelper.java b/src/test/java/com/epam/aidial/cfg/functional/utils/FunctionalTestHelper.java
index ae8db9ead..a9494ddeb 100644
--- a/src/test/java/com/epam/aidial/cfg/functional/utils/FunctionalTestHelper.java
+++ b/src/test/java/com/epam/aidial/cfg/functional/utils/FunctionalTestHelper.java
@@ -1,5 +1,6 @@
package com.epam.aidial.cfg.functional.utils;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
import com.epam.aidial.cfg.dto.AdapterDto;
import com.epam.aidial.cfg.dto.AddonDto;
import com.epam.aidial.cfg.dto.AdminSettingsDto;
@@ -43,8 +44,8 @@ public static RoleDto createRoleDto(String suffix) {
public static ModelDto createModelDto(String suffix) {
ModelDto modelDto = new ModelDto();
modelDto.setName("model" + suffix);
- modelDto.setDisplayName("model" + suffix);
- modelDto.setDescription("description" + suffix);
+ modelDto.setDisplayName(LocalizedValue.of("model" + suffix));
+ modelDto.setDescription(LocalizedValue.of("description" + suffix));
modelDto.setMaxRetryAttempts(1);
return modelDto;
}
@@ -85,8 +86,8 @@ public static AdapterDto createAdapterDto(String suffix) {
public static ToolSetDto createToolSetDtoWithoutRoleLimits(String suffix) {
ToolSetDto toolSet = new ToolSetDto();
toolSet.setName("ToolSet" + suffix);
- toolSet.setDisplayName("ToolSet" + suffix);
- toolSet.setDescription("description" + suffix);
+ toolSet.setDisplayName(LocalizedValue.of("ToolSet" + suffix));
+ toolSet.setDescription(LocalizedValue.of("description" + suffix));
toolSet.setEndpoint("https://endpoint.test.com/toolset" + suffix);
toolSet.setTransport(ToolSetDto.TransportDto.HTTP);
toolSet.setMaxRetryAttempts(1);
@@ -169,8 +170,8 @@ public static KeyDto createKeyDtoWithRole(String suffix) {
public static InterceptorDto createInterceptorDto(String suffix) {
InterceptorDto interceptorDto = new InterceptorDto();
interceptorDto.setName("interceptor" + suffix);
- interceptorDto.setDescription("description" + suffix);
- interceptorDto.setDisplayName("displayName" + suffix);
+ interceptorDto.setDescription(LocalizedValue.of("description" + suffix));
+ interceptorDto.setDisplayName(LocalizedValue.of("displayName" + suffix));
interceptorDto.setEndpoint("https://endpoint.test.com/interceptor" + suffix);
interceptorDto.setEntities(List.of());
interceptorDto.setTopics(new TreeSet<>(Set.of("topic1", "topic2")));
@@ -187,8 +188,8 @@ public static InterceptorDto createInterceptorDtoWithEntities(String suffix) {
public static ApplicationDto createBaseApplicationDto(String suffix) {
ApplicationDto applicationDto = new ApplicationDto();
applicationDto.setName("application" + suffix);
- applicationDto.setDisplayName("application" + suffix);
- applicationDto.setDescription("description" + suffix);
+ applicationDto.setDisplayName(LocalizedValue.of("application" + suffix));
+ applicationDto.setDescription(LocalizedValue.of("description" + suffix));
applicationDto.setSource(new ApplicationEndpointsSourceDto());
return applicationDto;
}
diff --git a/src/test/java/com/epam/aidial/cfg/service/AssertionsTest.java b/src/test/java/com/epam/aidial/cfg/service/AssertionsTest.java
index bae7b7d38..3b238b281 100644
--- a/src/test/java/com/epam/aidial/cfg/service/AssertionsTest.java
+++ b/src/test/java/com/epam/aidial/cfg/service/AssertionsTest.java
@@ -1,23 +1,34 @@
package com.epam.aidial.cfg.service;
+import com.epam.aidial.cfg.configuration.LocalizationProperties;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
import com.epam.aidial.cfg.exception.ValidationException;
import com.epam.aidial.core.config.CoreModel;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.Map;
-import static com.epam.aidial.cfg.service.Assertions.assertUniqueDisplayName;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
class AssertionsTest {
+ private Assertions assertions;
+
+ @BeforeEach
+ void setUp() {
+ LocalizationProperties properties = new LocalizationProperties();
+ properties.setLocale("en");
+ assertions = new Assertions(properties);
+ }
+
@Test
void testAssertUniqueDisplayName_DisplayNameEmpty() {
// given
CoreModel model = new CoreModel();
- model.setDisplayName("testModel");
+ model.setDisplayName(LocalizedValue.of("testModel"));
// when
- assertUniqueDisplayName(Map.of("testModel", model), null);
+ assertions.assertUniqueDisplayName(Map.of("testModel", model), null);
// then
}
@@ -25,9 +36,9 @@ void testAssertUniqueDisplayName_DisplayNameEmpty() {
void testAssertUniqueDisplayName_NewDisplayName() {
// given
CoreModel model = new CoreModel();
- model.setDisplayName("testModel");
+ model.setDisplayName(LocalizedValue.of("testModel"));
// when
- assertUniqueDisplayName(Map.of("testModel", model), "newDisplayName");
+ assertions.assertUniqueDisplayName(Map.of("testModel", model), "newDisplayName");
// then
}
@@ -35,10 +46,10 @@ void testAssertUniqueDisplayName_NewDisplayName() {
void testAssertUniqueDisplayName_DisplayNameIsNotUnique() {
// given
CoreModel model = new CoreModel();
- model.setDisplayName("testModel");
+ model.setDisplayName(LocalizedValue.of("testModel"));
Map models = Map.of("testModel", model);
// when
- assertThatThrownBy(() -> assertUniqueDisplayName(models, "testModel"))
+ assertThatThrownBy(() -> assertions.assertUniqueDisplayName(models, "testModel"))
// then
.isInstanceOf(ValidationException.class)
.hasMessageContaining("displayName is not unique");
@@ -48,11 +59,11 @@ void testAssertUniqueDisplayName_DisplayNameIsNotUnique() {
void testAssertUniqueDisplayNameAndVersion_DisplayNameEmptyAndDisplayVersionEmpty() {
// given
CoreModel model = new CoreModel();
- model.setDisplayName("testModel");
+ model.setDisplayName(LocalizedValue.of("testModel"));
model.setDisplayVersion("1.0.0");
Map models = Map.of("testModel", model);
// when
- Assertions.assertUniqueDisplayNameAndVersion(models, null, null);
+ assertions.assertUniqueDisplayNameAndVersion(models, null, null);
// then
}
@@ -63,7 +74,7 @@ void testAssertUniqueDisplayNameAndVersion_DisplayNameEmptyAndDisplayVersionNotE
model.setDisplayVersion("1.0.0");
Map models = Map.of("testModel", model);
// when
- assertThatThrownBy(() -> Assertions.assertUniqueDisplayNameAndVersion(models, null, "1.0.0"))
+ assertThatThrownBy(() -> assertions.assertUniqueDisplayNameAndVersion(models, null, "1.0.0"))
// then
.isInstanceOf(ValidationException.class)
.hasMessageContaining("displayVersion is not unique");
@@ -76,7 +87,7 @@ void testAssertUniqueDisplayNameAndVersion_DisplayNameEmptyAndDisplayVersionNotE
model.setDisplayVersion("1.0.0");
Map models = Map.of("testModel", model);
// when
- Assertions.assertUniqueDisplayNameAndVersion(models, null, "2.0.0");
+ assertions.assertUniqueDisplayNameAndVersion(models, null, "2.0.0");
// then
}
@@ -84,10 +95,10 @@ void testAssertUniqueDisplayNameAndVersion_DisplayNameEmptyAndDisplayVersionNotE
void testAssertUniqueDisplayNameAndVersion_DisplayNameNotEmptyAndDisplayVersionEmpty_Exception() {
// given
CoreModel model = new CoreModel();
- model.setDisplayName("testModel");
+ model.setDisplayName(LocalizedValue.of("testModel"));
Map models = Map.of("testModel", model);
// when
- assertThatThrownBy(() -> Assertions.assertUniqueDisplayNameAndVersion(models, "testModel", null))
+ assertThatThrownBy(() -> assertions.assertUniqueDisplayNameAndVersion(models, "testModel", null))
// then
.isInstanceOf(ValidationException.class)
.hasMessageContaining("displayName is not unique");
@@ -97,11 +108,11 @@ void testAssertUniqueDisplayNameAndVersion_DisplayNameNotEmptyAndDisplayVersionE
void testAssertUniqueDisplayNameAndVersion_DisplayNameNotEmptyAndDisplayVersionEmpty_Success() {
// given
CoreModel model = new CoreModel();
- model.setDisplayName("testModel");
+ model.setDisplayName(LocalizedValue.of("testModel"));
model.setDisplayVersion("1.0.0");
Map models = Map.of("testModel", model);
// when
- Assertions.assertUniqueDisplayNameAndVersion(models, "testModel", null);
+ assertions.assertUniqueDisplayNameAndVersion(models, "testModel", null);
// then
}
@@ -109,11 +120,11 @@ void testAssertUniqueDisplayNameAndVersion_DisplayNameNotEmptyAndDisplayVersionE
void testAssertUniqueDisplayNameAndVersion_DisplayNameNotEmptyAndDisplayVersionNotEmpty_Exception() {
// given
CoreModel model = new CoreModel();
- model.setDisplayName("testModel");
+ model.setDisplayName(LocalizedValue.of("testModel"));
model.setDisplayVersion("1.0.0");
Map models = Map.of("testModel", model);
// when
- assertThatThrownBy(() -> Assertions.assertUniqueDisplayNameAndVersion(models, "testModel", "1.0.0"))
+ assertThatThrownBy(() -> assertions.assertUniqueDisplayNameAndVersion(models, "testModel", "1.0.0"))
// then
.isInstanceOf(ValidationException.class)
.hasMessageContaining("displayName and displayVersion are not unique");
@@ -123,11 +134,11 @@ void testAssertUniqueDisplayNameAndVersion_DisplayNameNotEmptyAndDisplayVersionN
void testAssertUniqueDisplayNameAndVersion_DisplayNameNotEmptyAndDisplayVersionNotEmpty_Success() {
// given
CoreModel model = new CoreModel();
- model.setDisplayName("testModel");
+ model.setDisplayName(LocalizedValue.of("testModel"));
model.setDisplayVersion("1.0.0");
Map models = Map.of("testModel", model);
// when
- Assertions.assertUniqueDisplayNameAndVersion(models, "testModel", "2.0.0");
+ assertions.assertUniqueDisplayNameAndVersion(models, "testModel", "2.0.0");
// then
}
}
\ No newline at end of file
diff --git a/src/test/java/com/epam/aidial/cfg/service/config/export/ConfigExportServiceSecuredImplTest.java b/src/test/java/com/epam/aidial/cfg/service/config/export/ConfigExportServiceSecuredImplTest.java
index b34e3fcf6..c853f86e7 100644
--- a/src/test/java/com/epam/aidial/cfg/service/config/export/ConfigExportServiceSecuredImplTest.java
+++ b/src/test/java/com/epam/aidial/cfg/service/config/export/ConfigExportServiceSecuredImplTest.java
@@ -1,5 +1,6 @@
package com.epam.aidial.cfg.service.config.export;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
import com.epam.aidial.cfg.service.config.impl.storage.ConfigSource;
import com.epam.aidial.cfg.service.config.transfer.ConfigTransferLock;
import com.epam.aidial.core.config.Config;
@@ -322,7 +323,7 @@ private Config createTestConfig() {
Map models = new HashMap<>();
CoreModel model = new CoreModel();
model.setName("model1");
- model.setDisplayName("Test Model");
+ model.setDisplayName(LocalizedValue.of("Test Model"));
CoreUpstream upstream = new CoreUpstream();
upstream.setEndpoint("https://api.example.com");
@@ -336,7 +337,7 @@ private Config createTestConfig() {
Map toolsets = new HashMap<>();
CoreToolSet toolSet = new CoreToolSet();
toolSet.setName("toolset1");
- toolSet.setDisplayName("Test ToolSet");
+ toolSet.setDisplayName(LocalizedValue.of("Test ToolSet"));
CoreResourceAuthSettings authSettings = new CoreResourceAuthSettings();
authSettings.setClientSecret("client-secret-1");
@@ -354,8 +355,8 @@ private Config createMixedSecretsConfig() {
// Add a second model with both secret and non-secret data
CoreModel model2 = new CoreModel();
model2.setName("model2");
- model2.setDisplayName("Test Model 2");
-
+ model2.setDisplayName(LocalizedValue.of("Test Model 2"));
+
CoreUpstream upstream2 = new CoreUpstream();
upstream2.setEndpoint("https://api2.example.com");
model2.setUpstreams(List.of(upstream2));
@@ -365,7 +366,7 @@ private Config createMixedSecretsConfig() {
// Add a toolset without secrets
CoreToolSet toolSet2 = new CoreToolSet();
toolSet2.setName("toolset2");
- toolSet2.setDisplayName("Test ToolSet 2");
+ toolSet2.setDisplayName(LocalizedValue.of("Test ToolSet 2"));
toolSet2.setAuthSettings(new CoreResourceAuthSettings());
config.getToolsets().put("toolset2", toolSet2);
diff --git a/src/test/java/com/epam/aidial/cfg/service/config/export/ConfigServiceTest.java b/src/test/java/com/epam/aidial/cfg/service/config/export/ConfigServiceTest.java
index 1b9072170..70c47854f 100644
--- a/src/test/java/com/epam/aidial/cfg/service/config/export/ConfigServiceTest.java
+++ b/src/test/java/com/epam/aidial/cfg/service/config/export/ConfigServiceTest.java
@@ -9,6 +9,7 @@
import com.epam.aidial.cfg.dao.mapper.RouteEntityMapperImpl;
import com.epam.aidial.cfg.dao.mapper.ShareResourceLimitMapperImpl;
import com.epam.aidial.cfg.dao.mapper.UpstreamEntityMapperImpl;
+import com.epam.aidial.cfg.domain.mapper.LocalizedValueMapper;
import com.epam.aidial.cfg.domain.mapper.MapperPackage;
import com.epam.aidial.cfg.domain.model.Application;
import com.epam.aidial.cfg.domain.model.ApplicationTypeSchema;
@@ -107,6 +108,9 @@ static class ComponentScanConfig {
@Autowired
private CoreConfigAggregatorService configService;
+ @MockitoBean
+ private LocalizedValueMapper localizedValueMapper;
+
@BeforeEach
void setUp() {
when(applicationService.getAllApplications()).thenReturn(List.of());
diff --git a/src/test/java/com/epam/aidial/cfg/service/config/transfer/JsonConfigMergerTest.java b/src/test/java/com/epam/aidial/cfg/service/config/transfer/JsonConfigMergerTest.java
index 5a977d09d..69ee3bb61 100644
--- a/src/test/java/com/epam/aidial/cfg/service/config/transfer/JsonConfigMergerTest.java
+++ b/src/test/java/com/epam/aidial/cfg/service/config/transfer/JsonConfigMergerTest.java
@@ -29,7 +29,7 @@ void laterFileWins_onSameModelKey() throws Exception {
Path file1 = tempFile("{\"models\":{\"gpt-4\":{\"displayName\":\"old\",\"endpoint\":\"https://old\"}}}");
Path file2 = tempFile("{\"models\":{\"gpt-4\":{\"displayName\":\"new\",\"endpoint\":\"https://new\"}}}");
Config result = merger.merge(List.of(file1.toString(), file2.toString()));
- assertThat(result.getModels().get("gpt-4").getDisplayName()).isEqualTo("new");
+ assertThat(result.getModels().get("gpt-4").getDisplayName().getPlainValue()).isEqualTo("new");
}
@Test
diff --git a/src/test/java/com/epam/aidial/cfg/web/controller/none/ConfigControllerTest.java b/src/test/java/com/epam/aidial/cfg/web/controller/none/ConfigControllerTest.java
index 18cd0c687..ae9c526f8 100644
--- a/src/test/java/com/epam/aidial/cfg/web/controller/none/ConfigControllerTest.java
+++ b/src/test/java/com/epam/aidial/cfg/web/controller/none/ConfigControllerTest.java
@@ -13,6 +13,7 @@
import com.epam.aidial.cfg.domain.model.ImportConfigPreview;
import com.epam.aidial.cfg.domain.model.Model;
import com.epam.aidial.cfg.domain.utils.ModelEndpointUtils;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
import com.epam.aidial.cfg.dto.ExportConfigComponentTypeDto;
import com.epam.aidial.cfg.dto.ExportFormatDto;
import com.epam.aidial.cfg.dto.FullExportRequestDto;
@@ -179,7 +180,7 @@ void testImportPreview() throws Exception {
var model = new Model();
var deployment = new Deployment("testModel1");
model.setDeployment(deployment);
- model.setDisplayName("testModel1");
+ model.setDisplayName(LocalizedValue.of("testModel1"));
model.setDisplayVersion("1.0.0");
model.setInterceptors(List.of("testInterceptor1"));
model.setAuthor("test-author");
@@ -218,7 +219,7 @@ void testImportPreviewZip() throws Exception {
var model = new Model();
var deployment = new Deployment("testModel1");
model.setDeployment(deployment);
- model.setDisplayName("testModel1");
+ model.setDisplayName(LocalizedValue.of("testModel1"));
model.setDisplayVersion("1.0.0");
model.setInterceptors(List.of("testInterceptor1"));
model.setAuthor("test-author");
diff --git a/src/test/resources/import/import_preview.json b/src/test/resources/import/import_preview.json
index 7082de56d..e6efdf6ab 100644
--- a/src/test/resources/import/import_preview.json
+++ b/src/test/resources/import/import_preview.json
@@ -650,7 +650,7 @@
"interceptors": [
"testInterceptor1"
],
- "topics": ["test1", "test2"],
+ "topics": ["test2", "test1"],
"maxRetryAttempts": 5,
"type": "embedding",
"upstreams": [],
From 11c1ffd83838000ad543606c3b0e3449232f1d19 Mon Sep 17 00:00:00 2001
From: Katerina_Charakhovich
Date: Fri, 7 Aug 2026 07:34:49 +0200
Subject: [PATCH 2/9] merge to development
---
build.gradle | 9 ++++++++
.../cfg/client/dto/ApplicationDataDto.java | 2 ++
.../client/dto/ApplicationResourceDto.java | 2 ++
.../aidial/cfg/client/dto/ModelDataDto.java | 4 ++++
.../cfg/client/dto/ModelResourceDto.java | 4 ++++
.../aidial/cfg/client/dto/ToolSetDataDto.java | 3 +++
.../cfg/client/dto/ToolSetResourceDto.java | 3 +++
.../dao/mapper/ApplicationEntityMapper.java | 2 +-
.../cfg/dao/mapper/ModelEntityMapper.java | 2 +-
.../cfg/dao/mapper/ToolSetEntityMapper.java | 2 +-
.../epam/aidial/cfg/dao/mapper/UriMapper.java | 22 +++++++++++++++++++
.../cfg/dao/model/ApplicationEntity.java | 5 +++++
.../aidial/cfg/dao/model/ModelEntity.java | 5 +++++
.../aidial/cfg/dao/model/ToolSetEntity.java | 5 +++++
.../domain/mapper/InterceptorCoreMapper.java | 2 ++
.../aidial/cfg/domain/model/Application.java | 3 +++
.../epam/aidial/cfg/domain/model/Model.java | 3 +++
.../epam/aidial/cfg/domain/model/ToolSet.java | 4 ++++
.../epam/aidial/cfg/dto/ApplicationDto.java | 4 ++++
.../aidial/cfg/dto/ApplicationEximDto.java | 2 ++
.../cfg/dto/ApplicationResourceDto.java | 2 ++
.../cfg/dto/CreateApplicationResourceDto.java | 2 ++
.../cfg/dto/CreateToolSetResourceDto.java | 3 +++
.../com/epam/aidial/cfg/dto/ModelDto.java | 3 +++
.../epam/aidial/cfg/dto/ModelResourceDto.java | 4 ++++
.../com/epam/aidial/cfg/dto/ToolSetDto.java | 4 ++++
.../epam/aidial/cfg/dto/ToolSetEximDto.java | 3 +++
.../aidial/cfg/dto/ToolSetResourceDto.java | 3 +++
.../aidial/cfg/model/ApplicationExim.java | 2 ++
.../aidial/cfg/model/ApplicationResource.java | 2 ++
.../cfg/model/CreateApplicationResource.java | 2 ++
.../cfg/model/CreateToolSetResource.java | 3 +++
.../epam/aidial/cfg/model/ModelResource.java | 4 ++++
.../epam/aidial/cfg/model/ToolSetExim.java | 3 +++
.../aidial/cfg/model/ToolSetResource.java | 3 +++
.../aidial/core/config/CoreApplication.java | 2 ++
.../epam/aidial/core/config/CoreModel.java | 3 ++-
.../epam/aidial/core/config/CoreToolSet.java | 3 ++-
.../epam/aidial/core/config/Deployment.java | 14 ++++++++++++
.../core-config-schemas/properties-tracker.md | 1 +
.../core-config-schemas/schema-v0.47.0.json | 8 +++++++
.../H2/V1.117__CreateCatalogSchemaEntity.sql | 17 ++++++++++++++
.../V1.117__CreateCatalogSchemaEntity.sql | 17 ++++++++++++++
.../V1.117__CreateCatalogSchemaEntity.sql | 17 ++++++++++++++
.../ApplicationValidityStateResolverTest.java | 4 +++-
.../tests/InterceptorFunctionalTest.java | 4 +++-
.../functional/tests/ModelFunctionalTest.java | 4 +++-
.../config/config_only_applications.json | 8 ++++++-
.../domain/config/config_only_models.json | 8 ++++++-
.../domain/config/config_only_toolsets.json | 8 ++++++-
.../resources/domain/model/applications.json | 8 ++++++-
src/test/resources/domain/model/model.json | 4 +++-
src/test/resources/domain/model/models.json | 8 ++++++-
src/test/resources/domain/model/toolsets.json | 8 ++++++-
54 files changed, 262 insertions(+), 15 deletions(-)
create mode 100644 src/main/java/com/epam/aidial/cfg/dao/mapper/UriMapper.java
create mode 100644 src/main/resources/db/migration/H2/V1.117__CreateCatalogSchemaEntity.sql
create mode 100644 src/main/resources/db/migration/MS_SQL_SERVER/V1.117__CreateCatalogSchemaEntity.sql
create mode 100644 src/main/resources/db/migration/POSTGRES/V1.117__CreateCatalogSchemaEntity.sql
diff --git a/build.gradle b/build.gradle
index f9babf3bd..4c69d6146 100644
--- a/build.gradle
+++ b/build.gradle
@@ -165,6 +165,15 @@ dependencies {
testImplementation 'org.testcontainers:influxdb'
constraints {
+ implementation('org.springframework.data:spring-data-commons:3.5.12') {
+ because("previous versions have vulnerabilities")
+ }
+ implementation('org.springframework:spring-expression:6.2.19') {
+ because("previous versions have vulnerabilities")
+ }
+ implementation('org.springframework:spring-webmvc:6.2.19') {
+ because("previous versions have vulnerabilities")
+ }
implementation ('com.fasterxml.jackson.core:jackson-databind:2.22.0') {
because("previous versions have vulnerabilities")
}
diff --git a/src/main/java/com/epam/aidial/cfg/client/dto/ApplicationDataDto.java b/src/main/java/com/epam/aidial/cfg/client/dto/ApplicationDataDto.java
index 04841e649..81e4b2c9a 100644
--- a/src/main/java/com/epam/aidial/cfg/client/dto/ApplicationDataDto.java
+++ b/src/main/java/com/epam/aidial/cfg/client/dto/ApplicationDataDto.java
@@ -23,4 +23,6 @@ public class ApplicationDataDto extends DeploymentDataDto {
private Map routes;
private String viewerUrl;
private String editorUrl;
+ private String catalogSchemaId;
+ private Map catalogProperties;
}
\ No newline at end of file
diff --git a/src/main/java/com/epam/aidial/cfg/client/dto/ApplicationResourceDto.java b/src/main/java/com/epam/aidial/cfg/client/dto/ApplicationResourceDto.java
index 28d326768..490c05c32 100644
--- a/src/main/java/com/epam/aidial/cfg/client/dto/ApplicationResourceDto.java
+++ b/src/main/java/com/epam/aidial/cfg/client/dto/ApplicationResourceDto.java
@@ -51,4 +51,6 @@ public class ApplicationResourceDto {
private String appIdentity;
private boolean allowUserExternalServices;
private Map externalServices;
+ private String catalogSchemaId;
+ private Map catalogProperties;
}
\ No newline at end of file
diff --git a/src/main/java/com/epam/aidial/cfg/client/dto/ModelDataDto.java b/src/main/java/com/epam/aidial/cfg/client/dto/ModelDataDto.java
index b23681737..5eb84f71f 100644
--- a/src/main/java/com/epam/aidial/cfg/client/dto/ModelDataDto.java
+++ b/src/main/java/com/epam/aidial/cfg/client/dto/ModelDataDto.java
@@ -8,6 +8,8 @@
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
+import java.util.Map;
+
@Data
@SuperBuilder
@AllArgsConstructor
@@ -20,4 +22,6 @@ public class ModelDataDto extends DeploymentDataDto {
private ModelLimitsDataDto limits;
private ModelPricingDataDto pricing;
private Integer embeddingDimensions;
+ private String catalogSchemaId;
+ private Map catalogProperties;
}
\ No newline at end of file
diff --git a/src/main/java/com/epam/aidial/cfg/client/dto/ModelResourceDto.java b/src/main/java/com/epam/aidial/cfg/client/dto/ModelResourceDto.java
index 85a65e135..30c297d49 100644
--- a/src/main/java/com/epam/aidial/cfg/client/dto/ModelResourceDto.java
+++ b/src/main/java/com/epam/aidial/cfg/client/dto/ModelResourceDto.java
@@ -5,6 +5,8 @@
import lombok.Data;
import lombok.NoArgsConstructor;
+import java.util.Map;
+
@Data
@Builder
@AllArgsConstructor
@@ -12,4 +14,6 @@
public class ModelResourceDto {
private String id;
+ private String catalogSchemaId;
+ private Map catalogProperties;
}
diff --git a/src/main/java/com/epam/aidial/cfg/client/dto/ToolSetDataDto.java b/src/main/java/com/epam/aidial/cfg/client/dto/ToolSetDataDto.java
index d9e542dde..e78385f20 100644
--- a/src/main/java/com/epam/aidial/cfg/client/dto/ToolSetDataDto.java
+++ b/src/main/java/com/epam/aidial/cfg/client/dto/ToolSetDataDto.java
@@ -9,6 +9,7 @@
import lombok.experimental.SuperBuilder;
import java.util.List;
+import java.util.Map;
@Data
@SuperBuilder
@@ -23,4 +24,6 @@ public class ToolSetDataDto extends DeploymentDataDto {
private String provider;
private ResourceAuthSettingsDto authSettings;
private String vendorWebsite;
+ private String catalogSchemaId;
+ private Map catalogProperties;
}
\ No newline at end of file
diff --git a/src/main/java/com/epam/aidial/cfg/client/dto/ToolSetResourceDto.java b/src/main/java/com/epam/aidial/cfg/client/dto/ToolSetResourceDto.java
index 2bd5cfd11..b5af3d61b 100644
--- a/src/main/java/com/epam/aidial/cfg/client/dto/ToolSetResourceDto.java
+++ b/src/main/java/com/epam/aidial/cfg/client/dto/ToolSetResourceDto.java
@@ -8,6 +8,7 @@
import lombok.NoArgsConstructor;
import java.util.List;
+import java.util.Map;
@Data
@Builder
@@ -35,6 +36,8 @@ public class ToolSetResourceDto {
private ResourceAuthSettingsDto authSettings;
private boolean forwardPerRequestKey;
private boolean forwardAuthToken;
+ private String catalogSchemaId;
+ private Map catalogProperties;
public enum Transport {
HTTP, SSE
diff --git a/src/main/java/com/epam/aidial/cfg/dao/mapper/ApplicationEntityMapper.java b/src/main/java/com/epam/aidial/cfg/dao/mapper/ApplicationEntityMapper.java
index fd576a8d7..edd19b0d9 100644
--- a/src/main/java/com/epam/aidial/cfg/dao/mapper/ApplicationEntityMapper.java
+++ b/src/main/java/com/epam/aidial/cfg/dao/mapper/ApplicationEntityMapper.java
@@ -27,7 +27,7 @@
@Mapper(componentModel = "spring", uses = {
DeploymentEntityMapper.class, MapPropertiesMapper.class, DependentRouteEntityMapper.class,
FeaturesEntityMapper.class, ValidityStateEntityMapper.class, DeploymentInterfacesEntityMapper.class,
- ExternalServicesEntityMapper.class, LocalizedValueEntityMapper.class
+ ExternalServicesEntityMapper.class, UriMapper.class, LocalizedValueEntityMapper.class
})
public abstract class ApplicationEntityMapper {
diff --git a/src/main/java/com/epam/aidial/cfg/dao/mapper/ModelEntityMapper.java b/src/main/java/com/epam/aidial/cfg/dao/mapper/ModelEntityMapper.java
index da2b33ce1..4307a66b7 100644
--- a/src/main/java/com/epam/aidial/cfg/dao/mapper/ModelEntityMapper.java
+++ b/src/main/java/com/epam/aidial/cfg/dao/mapper/ModelEntityMapper.java
@@ -26,7 +26,7 @@
@Mapper(componentModel = "spring", uses = {
DeploymentEntityMapper.class, MapPropertiesMapper.class, UpstreamEntityMapper.class,
PropertiesEntityMapper.class, FeaturesEntityMapper.class, DeploymentInterfacesEntityMapper.class,
- LocalizedValueEntityMapper.class
+ UriMapper.class, LocalizedValueEntityMapper.class
})
public abstract class ModelEntityMapper {
diff --git a/src/main/java/com/epam/aidial/cfg/dao/mapper/ToolSetEntityMapper.java b/src/main/java/com/epam/aidial/cfg/dao/mapper/ToolSetEntityMapper.java
index 3c0397eb6..f57bcd6d1 100755
--- a/src/main/java/com/epam/aidial/cfg/dao/mapper/ToolSetEntityMapper.java
+++ b/src/main/java/com/epam/aidial/cfg/dao/mapper/ToolSetEntityMapper.java
@@ -18,7 +18,7 @@
import java.util.List;
@Mapper(componentModel = "spring", uses = {
- DeploymentEntityMapper.class, LocalizedValueEntityMapper.class
+ DeploymentEntityMapper.class, MapPropertiesMapper.class, UriMapper.class, LocalizedValueEntityMapper.class
})
public abstract class ToolSetEntityMapper {
diff --git a/src/main/java/com/epam/aidial/cfg/dao/mapper/UriMapper.java b/src/main/java/com/epam/aidial/cfg/dao/mapper/UriMapper.java
new file mode 100644
index 000000000..564674fd8
--- /dev/null
+++ b/src/main/java/com/epam/aidial/cfg/dao/mapper/UriMapper.java
@@ -0,0 +1,22 @@
+package com.epam.aidial.cfg.dao.mapper;
+
+import org.mapstruct.Mapper;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+
+@Mapper(componentModel = "spring")
+public interface UriMapper {
+
+ default String map(URI uri) {
+ return uri == null ? null : uri.toString();
+ }
+
+ default URI map(String uriString) {
+ try {
+ return uriString == null ? null : new URI(uriString);
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException("Invalid URI: " + uriString);
+ }
+ }
+}
diff --git a/src/main/java/com/epam/aidial/cfg/dao/model/ApplicationEntity.java b/src/main/java/com/epam/aidial/cfg/dao/model/ApplicationEntity.java
index f347de083..66694b33b 100644
--- a/src/main/java/com/epam/aidial/cfg/dao/model/ApplicationEntity.java
+++ b/src/main/java/com/epam/aidial/cfg/dao/model/ApplicationEntity.java
@@ -126,6 +126,11 @@ public class ApplicationEntity extends ValidityStateAwareEntity {
private String appIdentity;
private boolean allowUserExternalServices;
+ private String catalogSchemaId;
+
+ @Column(columnDefinition = "CLOB")
+ private String catalogProperties;
+
@PreRemove
public void preRemove() {
for (InterceptorEntity interceptor : interceptors) {
diff --git a/src/main/java/com/epam/aidial/cfg/dao/model/ModelEntity.java b/src/main/java/com/epam/aidial/cfg/dao/model/ModelEntity.java
index aab65eeef..ee6db1a8b 100644
--- a/src/main/java/com/epam/aidial/cfg/dao/model/ModelEntity.java
+++ b/src/main/java/com/epam/aidial/cfg/dao/model/ModelEntity.java
@@ -124,6 +124,11 @@ public class ModelEntity extends TimeTrackableEntity {
private AdapterEntity adapter;
private String adapterCompletionEndpointPath;
+ private String catalogSchemaId;
+
+ @Column(columnDefinition = "CLOB")
+ private String catalogProperties;
+
@PreRemove
public void preRemove() {
for (InterceptorEntity interceptor : interceptors) {
diff --git a/src/main/java/com/epam/aidial/cfg/dao/model/ToolSetEntity.java b/src/main/java/com/epam/aidial/cfg/dao/model/ToolSetEntity.java
index ae446a194..0356f03c0 100644
--- a/src/main/java/com/epam/aidial/cfg/dao/model/ToolSetEntity.java
+++ b/src/main/java/com/epam/aidial/cfg/dao/model/ToolSetEntity.java
@@ -93,6 +93,11 @@ public class ToolSetEntity extends TimeTrackableEntity {
private String provider;
+ private String catalogSchemaId;
+
+ @Column(columnDefinition = "CLOB")
+ private String catalogProperties;
+
public enum TransportEntity {
HTTP, SSE
}
diff --git a/src/main/java/com/epam/aidial/cfg/domain/mapper/InterceptorCoreMapper.java b/src/main/java/com/epam/aidial/cfg/domain/mapper/InterceptorCoreMapper.java
index baf336301..bbbd84cac 100644
--- a/src/main/java/com/epam/aidial/cfg/domain/mapper/InterceptorCoreMapper.java
+++ b/src/main/java/com/epam/aidial/cfg/domain/mapper/InterceptorCoreMapper.java
@@ -19,6 +19,8 @@ public interface InterceptorCoreMapper {
@Mapping(target = "responsesEndpoint", ignore = true)
@Mapping(target = "responsesDefaults", ignore = true)
@Mapping(target = "intro", ignore = true)
+ @Mapping(target = "catalogSchemaId", ignore = true)
+ @Mapping(target = "catalogProperties", ignore = true)
CoreInterceptor mapInterceptor(Interceptor interceptor);
@Mapping(target = "entities", ignore = true)
diff --git a/src/main/java/com/epam/aidial/cfg/domain/model/Application.java b/src/main/java/com/epam/aidial/cfg/domain/model/Application.java
index 00c2e273f..6145a32d1 100644
--- a/src/main/java/com/epam/aidial/cfg/domain/model/Application.java
+++ b/src/main/java/com/epam/aidial/cfg/domain/model/Application.java
@@ -54,4 +54,7 @@ public class Application extends RoleBased {
@Deprecated
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private URI applicationTypeSchemaId;
+
+ private URI catalogSchemaId;
+ private Map catalogProperties;
}
\ No newline at end of file
diff --git a/src/main/java/com/epam/aidial/cfg/domain/model/Model.java b/src/main/java/com/epam/aidial/cfg/domain/model/Model.java
index 48013127d..6c0f1c877 100644
--- a/src/main/java/com/epam/aidial/cfg/domain/model/Model.java
+++ b/src/main/java/com/epam/aidial/cfg/domain/model/Model.java
@@ -6,6 +6,7 @@
import lombok.EqualsAndHashCode;
import lombok.ToString;
+import java.net.URI;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -46,4 +47,6 @@ public class Model extends RoleBased {
private List fieldsHashingOrder;
private Integer embeddingDimensions;
private ModelSource source;
+ private URI catalogSchemaId;
+ private Map catalogProperties;
}
\ No newline at end of file
diff --git a/src/main/java/com/epam/aidial/cfg/domain/model/ToolSet.java b/src/main/java/com/epam/aidial/cfg/domain/model/ToolSet.java
index 932113e17..0e6030bf1 100644
--- a/src/main/java/com/epam/aidial/cfg/domain/model/ToolSet.java
+++ b/src/main/java/com/epam/aidial/cfg/domain/model/ToolSet.java
@@ -6,8 +6,10 @@
import lombok.EqualsAndHashCode;
import lombok.ToString;
+import java.net.URI;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
import java.util.Set;
@Data
@@ -32,6 +34,8 @@ public class ToolSet extends SecuredRoleBased {
private Transport transport;
private List allowedTools = new ArrayList<>();
private String provider;
+ private URI catalogSchemaId;
+ private Map catalogProperties;
public enum Transport {
HTTP, SSE
diff --git a/src/main/java/com/epam/aidial/cfg/dto/ApplicationDto.java b/src/main/java/com/epam/aidial/cfg/dto/ApplicationDto.java
index c0001d6ff..4d4bbf63a 100644
--- a/src/main/java/com/epam/aidial/cfg/dto/ApplicationDto.java
+++ b/src/main/java/com/epam/aidial/cfg/dto/ApplicationDto.java
@@ -12,6 +12,7 @@
import lombok.Data;
import lombok.EqualsAndHashCode;
+import java.net.URI;
import java.time.Instant;
import java.util.HashMap;
import java.util.List;
@@ -71,6 +72,9 @@ public class ApplicationDto extends RoleBasedDto {
private String appIdentity;
private boolean allowUserExternalServices;
+ private URI catalogSchemaId;
+ private Map catalogProperties;
+
public void setFunction(FunctionDto function) {
if (function != null) {
throw new UnsupportedOperationException("application function is not supported");
diff --git a/src/main/java/com/epam/aidial/cfg/dto/ApplicationEximDto.java b/src/main/java/com/epam/aidial/cfg/dto/ApplicationEximDto.java
index a8f7f3936..327661452 100644
--- a/src/main/java/com/epam/aidial/cfg/dto/ApplicationEximDto.java
+++ b/src/main/java/com/epam/aidial/cfg/dto/ApplicationEximDto.java
@@ -47,4 +47,6 @@ public class ApplicationEximDto {
private String appIdentity;
private boolean allowUserExternalServices;
private Map externalServices;
+ private String catalogSchemaId;
+ private Map catalogProperties;
}
\ No newline at end of file
diff --git a/src/main/java/com/epam/aidial/cfg/dto/ApplicationResourceDto.java b/src/main/java/com/epam/aidial/cfg/dto/ApplicationResourceDto.java
index b5f0ea26a..9ca5b9431 100644
--- a/src/main/java/com/epam/aidial/cfg/dto/ApplicationResourceDto.java
+++ b/src/main/java/com/epam/aidial/cfg/dto/ApplicationResourceDto.java
@@ -49,4 +49,6 @@ public class ApplicationResourceDto {
private String appIdentity;
private boolean allowUserExternalServices;
private Map externalServices;
+ private String catalogSchemaId;
+ private Map catalogProperties;
}
\ No newline at end of file
diff --git a/src/main/java/com/epam/aidial/cfg/dto/CreateApplicationResourceDto.java b/src/main/java/com/epam/aidial/cfg/dto/CreateApplicationResourceDto.java
index 72a0ec98c..d187ecbbf 100644
--- a/src/main/java/com/epam/aidial/cfg/dto/CreateApplicationResourceDto.java
+++ b/src/main/java/com/epam/aidial/cfg/dto/CreateApplicationResourceDto.java
@@ -60,4 +60,6 @@ public class CreateApplicationResourceDto {
private Map externalServices;
private String appIdentity;
private boolean allowUserExternalServices;
+ private String catalogSchemaId;
+ private Map catalogProperties;
}
\ No newline at end of file
diff --git a/src/main/java/com/epam/aidial/cfg/dto/CreateToolSetResourceDto.java b/src/main/java/com/epam/aidial/cfg/dto/CreateToolSetResourceDto.java
index cbe76b7b6..bf2905d72 100644
--- a/src/main/java/com/epam/aidial/cfg/dto/CreateToolSetResourceDto.java
+++ b/src/main/java/com/epam/aidial/cfg/dto/CreateToolSetResourceDto.java
@@ -6,6 +6,7 @@
import lombok.Data;
import java.util.List;
+import java.util.Map;
@Data
public class CreateToolSetResourceDto {
@@ -34,6 +35,8 @@ public class CreateToolSetResourceDto {
private CoreResourceAuthSettingsDto authSettings;
private boolean forwardPerRequestKey;
private boolean forwardAuthToken;
+ private String catalogSchemaId;
+ private Map catalogProperties;
public enum TransportDto {
HTTP, SSE
diff --git a/src/main/java/com/epam/aidial/cfg/dto/ModelDto.java b/src/main/java/com/epam/aidial/cfg/dto/ModelDto.java
index 4b3368439..104839aa4 100644
--- a/src/main/java/com/epam/aidial/cfg/dto/ModelDto.java
+++ b/src/main/java/com/epam/aidial/cfg/dto/ModelDto.java
@@ -10,6 +10,7 @@
import lombok.Data;
import lombok.EqualsAndHashCode;
+import java.net.URI;
import java.time.Instant;
import java.util.List;
import java.util.Map;
@@ -63,5 +64,7 @@ public class ModelDto extends RoleBasedDto {
private List fieldsHashingOrder;
private Integer embeddingDimensions;
private ModelSourceDto source;
+ private URI catalogSchemaId;
+ private Map catalogProperties;
}
\ No newline at end of file
diff --git a/src/main/java/com/epam/aidial/cfg/dto/ModelResourceDto.java b/src/main/java/com/epam/aidial/cfg/dto/ModelResourceDto.java
index efb0ef143..9dc00619c 100644
--- a/src/main/java/com/epam/aidial/cfg/dto/ModelResourceDto.java
+++ b/src/main/java/com/epam/aidial/cfg/dto/ModelResourceDto.java
@@ -2,8 +2,12 @@
import lombok.Data;
+import java.util.Map;
+
@Data
public class ModelResourceDto {
private String id;
+ private String catalogSchemaId;
+ private Map catalogProperties;
}
diff --git a/src/main/java/com/epam/aidial/cfg/dto/ToolSetDto.java b/src/main/java/com/epam/aidial/cfg/dto/ToolSetDto.java
index e6ac5df6e..6e34320d0 100644
--- a/src/main/java/com/epam/aidial/cfg/dto/ToolSetDto.java
+++ b/src/main/java/com/epam/aidial/cfg/dto/ToolSetDto.java
@@ -9,8 +9,10 @@
import lombok.Data;
import lombok.EqualsAndHashCode;
+import java.net.URI;
import java.time.Instant;
import java.util.List;
+import java.util.Map;
import java.util.TreeSet;
@Data
@@ -45,6 +47,8 @@ public class ToolSetDto extends RoleBasedDto {
private ResourceAuthSettingsDto authSettings;
private boolean forwardPerRequestKey;
private boolean forwardAuthToken;
+ private URI catalogSchemaId;
+ private Map catalogProperties;
public enum TransportDto {
HTTP, SSE
diff --git a/src/main/java/com/epam/aidial/cfg/dto/ToolSetEximDto.java b/src/main/java/com/epam/aidial/cfg/dto/ToolSetEximDto.java
index 75da2fde6..7c5c07093 100644
--- a/src/main/java/com/epam/aidial/cfg/dto/ToolSetEximDto.java
+++ b/src/main/java/com/epam/aidial/cfg/dto/ToolSetEximDto.java
@@ -6,6 +6,7 @@
import lombok.NoArgsConstructor;
import java.util.List;
+import java.util.Map;
@Data
@Builder
@@ -31,6 +32,8 @@ public class ToolSetEximDto {
private CoreResourceAuthSettingsDto authSettings;
private boolean forwardPerRequestKey;
private boolean forwardAuthToken;
+ private String catalogSchemaId;
+ private Map catalogProperties;
public enum Transport {
HTTP, SSE
diff --git a/src/main/java/com/epam/aidial/cfg/dto/ToolSetResourceDto.java b/src/main/java/com/epam/aidial/cfg/dto/ToolSetResourceDto.java
index b5b2c62b5..c8a50d254 100644
--- a/src/main/java/com/epam/aidial/cfg/dto/ToolSetResourceDto.java
+++ b/src/main/java/com/epam/aidial/cfg/dto/ToolSetResourceDto.java
@@ -3,6 +3,7 @@
import lombok.Data;
import java.util.List;
+import java.util.Map;
@Data
public class ToolSetResourceDto {
@@ -28,6 +29,8 @@ public class ToolSetResourceDto {
private CoreResourceAuthSettingsDto authSettings;
private boolean forwardPerRequestKey;
private boolean forwardAuthToken;
+ private String catalogSchemaId;
+ private Map catalogProperties;
public enum Transport {
HTTP, SSE
diff --git a/src/main/java/com/epam/aidial/cfg/model/ApplicationExim.java b/src/main/java/com/epam/aidial/cfg/model/ApplicationExim.java
index e93d0d3d8..de152d97b 100644
--- a/src/main/java/com/epam/aidial/cfg/model/ApplicationExim.java
+++ b/src/main/java/com/epam/aidial/cfg/model/ApplicationExim.java
@@ -47,4 +47,6 @@ public class ApplicationExim {
private String appIdentity;
private boolean allowUserExternalServices;
private Map externalServices;
+ private String catalogSchemaId;
+ private Map catalogProperties;
}
\ No newline at end of file
diff --git a/src/main/java/com/epam/aidial/cfg/model/ApplicationResource.java b/src/main/java/com/epam/aidial/cfg/model/ApplicationResource.java
index 82a2c4775..ab3b92f85 100644
--- a/src/main/java/com/epam/aidial/cfg/model/ApplicationResource.java
+++ b/src/main/java/com/epam/aidial/cfg/model/ApplicationResource.java
@@ -53,4 +53,6 @@ public class ApplicationResource {
private String intro;
private String appIdentity;
private boolean allowUserExternalServices;
+ private String catalogSchemaId;
+ private Map catalogProperties;
}
\ No newline at end of file
diff --git a/src/main/java/com/epam/aidial/cfg/model/CreateApplicationResource.java b/src/main/java/com/epam/aidial/cfg/model/CreateApplicationResource.java
index 969eb1317..fdf0129e7 100644
--- a/src/main/java/com/epam/aidial/cfg/model/CreateApplicationResource.java
+++ b/src/main/java/com/epam/aidial/cfg/model/CreateApplicationResource.java
@@ -46,4 +46,6 @@ public class CreateApplicationResource {
private String appIdentity;
private boolean allowUserExternalServices;
private Map externalServices;
+ private String catalogSchemaId;
+ private Map catalogProperties;
}
\ No newline at end of file
diff --git a/src/main/java/com/epam/aidial/cfg/model/CreateToolSetResource.java b/src/main/java/com/epam/aidial/cfg/model/CreateToolSetResource.java
index 5e25c04a7..c53c7d9dc 100644
--- a/src/main/java/com/epam/aidial/cfg/model/CreateToolSetResource.java
+++ b/src/main/java/com/epam/aidial/cfg/model/CreateToolSetResource.java
@@ -6,6 +6,7 @@
import lombok.NoArgsConstructor;
import java.util.List;
+import java.util.Map;
@Data
@Builder
@@ -30,6 +31,8 @@ public class CreateToolSetResource {
private boolean forwardPerRequestKey;
private boolean forwardAuthToken;
private String intro;
+ private String catalogSchemaId;
+ private Map catalogProperties;
public enum Transport {
HTTP, SSE
diff --git a/src/main/java/com/epam/aidial/cfg/model/ModelResource.java b/src/main/java/com/epam/aidial/cfg/model/ModelResource.java
index 0eaba59df..94f2123fc 100644
--- a/src/main/java/com/epam/aidial/cfg/model/ModelResource.java
+++ b/src/main/java/com/epam/aidial/cfg/model/ModelResource.java
@@ -5,6 +5,8 @@
import lombok.Data;
import lombok.NoArgsConstructor;
+import java.util.Map;
+
@Data
@Builder
@AllArgsConstructor
@@ -12,4 +14,6 @@
public class ModelResource {
private String id;
+ private String catalogSchemaId;
+ private Map catalogProperties;
}
diff --git a/src/main/java/com/epam/aidial/cfg/model/ToolSetExim.java b/src/main/java/com/epam/aidial/cfg/model/ToolSetExim.java
index 5459a937b..e28287126 100644
--- a/src/main/java/com/epam/aidial/cfg/model/ToolSetExim.java
+++ b/src/main/java/com/epam/aidial/cfg/model/ToolSetExim.java
@@ -6,6 +6,7 @@
import lombok.NoArgsConstructor;
import java.util.List;
+import java.util.Map;
@Data
@Builder
@@ -32,6 +33,8 @@ public class ToolSetExim {
private boolean forwardPerRequestKey;
private boolean forwardAuthToken;
private String intro;
+ private String catalogSchemaId;
+ private Map catalogProperties;
public enum Transport {
HTTP, SSE
diff --git a/src/main/java/com/epam/aidial/cfg/model/ToolSetResource.java b/src/main/java/com/epam/aidial/cfg/model/ToolSetResource.java
index 6bfa4923f..4f8590fd8 100644
--- a/src/main/java/com/epam/aidial/cfg/model/ToolSetResource.java
+++ b/src/main/java/com/epam/aidial/cfg/model/ToolSetResource.java
@@ -6,6 +6,7 @@
import lombok.NoArgsConstructor;
import java.util.List;
+import java.util.Map;
@Data
@Builder
@@ -36,6 +37,8 @@ public class ToolSetResource {
private boolean forwardAuthToken;
private String url;
private String intro;
+ private String catalogSchemaId;
+ private Map catalogProperties;
public enum Transport {
HTTP, SSE
diff --git a/src/main/java/com/epam/aidial/core/config/CoreApplication.java b/src/main/java/com/epam/aidial/core/config/CoreApplication.java
index d3accb71e..a1106ebb1 100644
--- a/src/main/java/com/epam/aidial/core/config/CoreApplication.java
+++ b/src/main/java/com/epam/aidial/core/config/CoreApplication.java
@@ -194,6 +194,8 @@ public static CoreApplication empty() {
coreApplication.setIntro(null);
coreApplication.setAppIdentity(null);
coreApplication.setAllowUserExternalServices(null);
+ coreApplication.setCatalogSchemaId(null);
+ coreApplication.setCatalogProperties(null);
return coreApplication;
}
}
\ No newline at end of file
diff --git a/src/main/java/com/epam/aidial/core/config/CoreModel.java b/src/main/java/com/epam/aidial/core/config/CoreModel.java
index 8a58c4b8a..2d4782a85 100644
--- a/src/main/java/com/epam/aidial/core/config/CoreModel.java
+++ b/src/main/java/com/epam/aidial/core/config/CoreModel.java
@@ -45,7 +45,8 @@ public static CoreModel empty() {
coreModel.setDependencies(null);
coreModel.setInterfaces(null);
coreModel.setIntro(null);
-
+ coreModel.setCatalogSchemaId(null);
+ coreModel.setCatalogProperties(null);
return coreModel;
}
}
\ No newline at end of file
diff --git a/src/main/java/com/epam/aidial/core/config/CoreToolSet.java b/src/main/java/com/epam/aidial/core/config/CoreToolSet.java
index eacf00589..ed94ed5da 100644
--- a/src/main/java/com/epam/aidial/core/config/CoreToolSet.java
+++ b/src/main/java/com/epam/aidial/core/config/CoreToolSet.java
@@ -55,7 +55,8 @@ public static CoreToolSet empty() {
coreToolSet.setProvider(null);
coreToolSet.setInterfaces(null);
coreToolSet.setVendorWebsite(null);
-
+ coreToolSet.setCatalogSchemaId(null);
+ coreToolSet.setCatalogProperties(null);
return coreToolSet;
}
}
diff --git a/src/main/java/com/epam/aidial/core/config/Deployment.java b/src/main/java/com/epam/aidial/core/config/Deployment.java
index 1f34b71ca..2f09174ad 100644
--- a/src/main/java/com/epam/aidial/core/config/Deployment.java
+++ b/src/main/java/com/epam/aidial/core/config/Deployment.java
@@ -6,6 +6,7 @@
import lombok.Data;
import lombok.EqualsAndHashCode;
+import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -82,4 +83,17 @@ public abstract class Deployment extends RoleBasedEntity {
* Dependent deployments
*/
private List dependencies = List.of(); // 0.27.0
+
+ /**
+ * Catalog schema reference for marketplace/catalog display metadata validation.
+ */
+ @JsonAlias({"catalogSchemaId", "catalog_schema_id"})
+ private URI catalogSchemaId; // 0.47.0
+
+ /**
+ * Curated marketplace/catalog display metadata, validated against {@link #catalogSchemaId}.
+ */
+ @JsonAlias({"catalogProperties", "catalog_properties"})
+ private Map catalogProperties; // 0.47.0
+
}
\ No newline at end of file
diff --git a/src/main/resources/core-config-schemas/properties-tracker.md b/src/main/resources/core-config-schemas/properties-tracker.md
index 52690dd27..9287959aa 100644
--- a/src/main/resources/core-config-schemas/properties-tracker.md
+++ b/src/main/resources/core-config-schemas/properties-tracker.md
@@ -23,3 +23,4 @@ This document tracks new properties added to the Core Config schema across diffe
| 0.44.0 | - `tokenEndpointAuthMethod` in ResourceAuthSettings, `id` in Upstream |
| 0.45.0 | - `maxTokensSupported`, `maxCompletionTokensSupported`,`customTemperatureSupported`,`reasoningEfforts` in Features
- `secretExtraData` in Upstream
- `embeddingDimensions` in Model
- `provider` in ToolSet |
| 0.46.0 | - `interfaces` in Model (keys: `openaiChatCompletions`, `openaiResponses`, `anthropicMessages`), Application and Interceptor (key: `openaiChatCompletions` only); each value is an object with required `base_url`
- `externalServices` in Application (map of `displayName`, `description`, `authSettings`), appIdentity and allowUserExternalServices in Application, dynamicallyRegistered in ResourceAuthSettings, intro in Model, Application, ToolSet, vendorWebsite in ToolSet, ToolSetData |
+| 0.47.0 | - `catalogSchemaId` in Deployment (String reference to a catalog schema)
- `catalogProperties` in Deployment (curated marketplace/catalog display metadata, validated against catalogSchemaId) |
diff --git a/src/main/resources/core-config-schemas/schema-v0.47.0.json b/src/main/resources/core-config-schemas/schema-v0.47.0.json
index c5b5510dd..eed1d6a6e 100644
--- a/src/main/resources/core-config-schemas/schema-v0.47.0.json
+++ b/src/main/resources/core-config-schemas/schema-v0.47.0.json
@@ -218,6 +218,14 @@
"items": {
"type": "string"
}
+ },
+ "catalogSchemaId": {
+ "type": "string",
+ "format": "uri"
+ },
+ "catalogProperties": {
+ "type": "object",
+ "description": "Curated marketplace/catalog display metadata, validated against catalogSchemaId"
}
}
}
diff --git a/src/main/resources/db/migration/H2/V1.117__CreateCatalogSchemaEntity.sql b/src/main/resources/db/migration/H2/V1.117__CreateCatalogSchemaEntity.sql
new file mode 100644
index 000000000..b6311f094
--- /dev/null
+++ b/src/main/resources/db/migration/H2/V1.117__CreateCatalogSchemaEntity.sql
@@ -0,0 +1,17 @@
+ALTER TABLE application_entity ADD COLUMN catalog_schema_id varchar(850);
+ALTER TABLE application_entity ADD COLUMN catalog_properties text;
+
+ALTER TABLE model_entity ADD COLUMN catalog_schema_id varchar(850);
+ALTER TABLE model_entity ADD COLUMN catalog_properties text;
+
+ALTER TABLE tool_set_entity ADD COLUMN catalog_schema_id varchar(850);
+ALTER TABLE tool_set_entity ADD COLUMN catalog_properties text;
+
+ALTER TABLE application_entity_aud ADD COLUMN catalog_schema_id varchar(850);
+ALTER TABLE application_entity_aud ADD COLUMN catalog_properties text;
+
+ALTER TABLE model_entity_aud ADD COLUMN catalog_schema_id varchar(850);
+ALTER TABLE model_entity_aud ADD COLUMN catalog_properties text;
+
+ALTER TABLE tool_set_entity_aud ADD COLUMN catalog_schema_id varchar(850);
+ALTER TABLE tool_set_entity_aud ADD COLUMN catalog_properties text;
diff --git a/src/main/resources/db/migration/MS_SQL_SERVER/V1.117__CreateCatalogSchemaEntity.sql b/src/main/resources/db/migration/MS_SQL_SERVER/V1.117__CreateCatalogSchemaEntity.sql
new file mode 100644
index 000000000..9ed0c16c2
--- /dev/null
+++ b/src/main/resources/db/migration/MS_SQL_SERVER/V1.117__CreateCatalogSchemaEntity.sql
@@ -0,0 +1,17 @@
+ALTER TABLE application_entity ADD catalog_schema_id nvarchar(850);
+ALTER TABLE application_entity ADD catalog_properties nvarchar(max);
+
+ALTER TABLE model_entity ADD catalog_schema_id nvarchar(850);
+ALTER TABLE model_entity ADD catalog_properties nvarchar(max);
+
+ALTER TABLE tool_set_entity ADD catalog_schema_id nvarchar(850);
+ALTER TABLE tool_set_entity ADD catalog_properties nvarchar(max);
+
+ALTER TABLE application_entity_aud ADD catalog_schema_id nvarchar(850);
+ALTER TABLE application_entity_aud ADD catalog_properties nvarchar(max);
+
+ALTER TABLE model_entity_aud ADD catalog_schema_id nvarchar(850);
+ALTER TABLE model_entity_aud ADD catalog_properties nvarchar(max);
+
+ALTER TABLE tool_set_entity_aud ADD catalog_schema_id nvarchar(850);
+ALTER TABLE tool_set_entity_aud ADD catalog_properties nvarchar(max);
diff --git a/src/main/resources/db/migration/POSTGRES/V1.117__CreateCatalogSchemaEntity.sql b/src/main/resources/db/migration/POSTGRES/V1.117__CreateCatalogSchemaEntity.sql
new file mode 100644
index 000000000..b6311f094
--- /dev/null
+++ b/src/main/resources/db/migration/POSTGRES/V1.117__CreateCatalogSchemaEntity.sql
@@ -0,0 +1,17 @@
+ALTER TABLE application_entity ADD COLUMN catalog_schema_id varchar(850);
+ALTER TABLE application_entity ADD COLUMN catalog_properties text;
+
+ALTER TABLE model_entity ADD COLUMN catalog_schema_id varchar(850);
+ALTER TABLE model_entity ADD COLUMN catalog_properties text;
+
+ALTER TABLE tool_set_entity ADD COLUMN catalog_schema_id varchar(850);
+ALTER TABLE tool_set_entity ADD COLUMN catalog_properties text;
+
+ALTER TABLE application_entity_aud ADD COLUMN catalog_schema_id varchar(850);
+ALTER TABLE application_entity_aud ADD COLUMN catalog_properties text;
+
+ALTER TABLE model_entity_aud ADD COLUMN catalog_schema_id varchar(850);
+ALTER TABLE model_entity_aud ADD COLUMN catalog_properties text;
+
+ALTER TABLE tool_set_entity_aud ADD COLUMN catalog_schema_id varchar(850);
+ALTER TABLE tool_set_entity_aud ADD COLUMN catalog_properties text;
diff --git a/src/test/java/com/epam/aidial/cfg/dao/listener/validitystate/resolver/ApplicationValidityStateResolverTest.java b/src/test/java/com/epam/aidial/cfg/dao/listener/validitystate/resolver/ApplicationValidityStateResolverTest.java
index b47de8a81..a4638aa0c 100644
--- a/src/test/java/com/epam/aidial/cfg/dao/listener/validitystate/resolver/ApplicationValidityStateResolverTest.java
+++ b/src/test/java/com/epam/aidial/cfg/dao/listener/validitystate/resolver/ApplicationValidityStateResolverTest.java
@@ -17,6 +17,7 @@
import com.epam.aidial.cfg.dao.mapper.PropertiesEntityMapperImpl;
import com.epam.aidial.cfg.dao.mapper.ResourceAuthSettingsEntityMapperImpl;
import com.epam.aidial.cfg.dao.mapper.RoleLimitEntityMapperImpl;
+import com.epam.aidial.cfg.dao.mapper.UriMapperImpl;
import com.epam.aidial.cfg.dao.mapper.ValidityStateEntityMapperImpl;
import com.epam.aidial.cfg.dao.model.ApplicationEntity;
import com.epam.aidial.cfg.dao.model.ApplicationTypeSchemaEntity;
@@ -59,7 +60,8 @@
PropertiesEntityMapperImpl.class,
ApplicationTypeSchemaCoreMapperImpl.class,
ApplicationTypeSchemaRouteCoreMapperImpl.class,
- LocalizedValueEntityMapperImpl.class
+ LocalizedValueEntityMapperImpl.class,
+ UriMapperImpl.class
})
class ApplicationValidityStateResolverTest {
diff --git a/src/test/java/com/epam/aidial/cfg/functional/tests/InterceptorFunctionalTest.java b/src/test/java/com/epam/aidial/cfg/functional/tests/InterceptorFunctionalTest.java
index 28aade36c..c012f0d76 100644
--- a/src/test/java/com/epam/aidial/cfg/functional/tests/InterceptorFunctionalTest.java
+++ b/src/test/java/com/epam/aidial/cfg/functional/tests/InterceptorFunctionalTest.java
@@ -713,7 +713,9 @@ private JsonNode coreConfig() throws JsonProcessingException {
"intro": null,
"createdAt": 1000,
"updatedAt": 1000,
- "dependencies": []
+ "dependencies": [],
+ "catalogSchemaId": null,
+ "catalogProperties": null
}
}
}
diff --git a/src/test/java/com/epam/aidial/cfg/functional/tests/ModelFunctionalTest.java b/src/test/java/com/epam/aidial/cfg/functional/tests/ModelFunctionalTest.java
index a5a8e5c9a..b831c0f6e 100644
--- a/src/test/java/com/epam/aidial/cfg/functional/tests/ModelFunctionalTest.java
+++ b/src/test/java/com/epam/aidial/cfg/functional/tests/ModelFunctionalTest.java
@@ -780,7 +780,9 @@ private JsonNode coreConfig() throws JsonProcessingException {
],
"overrideName": null,
"fieldsHashingOrder": ["prefix.body.tools","prefix.body.messages"],
- "embeddingDimensions": 1536
+ "embeddingDimensions": 1536,
+ "catalogSchemaId": null,
+ "catalogProperties": null
}
}
}
diff --git a/src/test/resources/domain/config/config_only_applications.json b/src/test/resources/domain/config/config_only_applications.json
index c702a0707..100d41e48 100644
--- a/src/test/resources/domain/config/config_only_applications.json
+++ b/src/test/resources/domain/config/config_only_applications.json
@@ -89,7 +89,13 @@
"author": "test-author",
"created_at": 12345,
"updated_at": 12345,
- "dependencies": ["dep1", "dep2"]
+ "dependencies": ["dep1", "dep2"],
+ "catalog_schema_id": "https://test-catalog-schema.example",
+ "catalog_properties": {
+ "category": "AI Application",
+ "vendor": "Test Vendor",
+ "tags": ["automation", "workflow"]
+ }
}
},
"assistant": {
diff --git a/src/test/resources/domain/config/config_only_models.json b/src/test/resources/domain/config/config_only_models.json
index 490926eac..3ba8cea04 100644
--- a/src/test/resources/domain/config/config_only_models.json
+++ b/src/test/resources/domain/config/config_only_models.json
@@ -101,7 +101,13 @@
"createdAt": 12345,
"updatedAt": 12345,
"dependencies": ["dep1", "dep2"],
- "embeddingDimensions": 1536
+ "embeddingDimensions": 1536,
+ "catalogSchemaId": "https://test-catalog-schema.example",
+ "catalogProperties": {
+ "category": "AI Model",
+ "vendor": "Test Vendor",
+ "tags": ["chat", "completion"]
+ }
}
},
"addons": {},
diff --git a/src/test/resources/domain/config/config_only_toolsets.json b/src/test/resources/domain/config/config_only_toolsets.json
index b739e987e..3fb73bdf6 100644
--- a/src/test/resources/domain/config/config_only_toolsets.json
+++ b/src/test/resources/domain/config/config_only_toolsets.json
@@ -61,7 +61,13 @@
"dynamically_registered": true
},
"forward_per_request_key": false,
- "dependencies": []
+ "dependencies": [],
+ "catalog_schema_id": "https://test-catalog-schema.example",
+ "catalog_properties": {
+ "category": "AI ToolSet",
+ "vendor": "Test Vendor",
+ "tags": ["git", "mcp"]
+ }
}
}
}
\ No newline at end of file
diff --git a/src/test/resources/domain/model/applications.json b/src/test/resources/domain/model/applications.json
index 8b6b39eff..f5d21ab23 100644
--- a/src/test/resources/domain/model/applications.json
+++ b/src/test/resources/domain/model/applications.json
@@ -124,6 +124,12 @@
"author": "test-author",
"createdAt": 12345,
"updatedAt": 12345,
- "dependencies": ["dep1", "dep2"]
+ "dependencies": ["dep1", "dep2"],
+ "catalogSchemaId": "https://test-catalog-schema.example",
+ "catalogProperties": {
+ "category": "AI Application",
+ "vendor": "Test Vendor",
+ "tags": ["automation", "workflow"]
+ }
}
]
\ No newline at end of file
diff --git a/src/test/resources/domain/model/model.json b/src/test/resources/domain/model/model.json
index 4f2e1d1b4..7ce83b7bf 100644
--- a/src/test/resources/domain/model/model.json
+++ b/src/test/resources/domain/model/model.json
@@ -128,5 +128,7 @@
"createdAt": 12345,
"updatedAt": 12345,
"dependencies": ["dep1", "dep2"],
- "endpointDeploymentName": "sampleDeployment"
+ "endpointDeploymentName": "sampleDeployment",
+ "catalogSchemaId": null,
+ "catalogProperties": null
}
\ No newline at end of file
diff --git a/src/test/resources/domain/model/models.json b/src/test/resources/domain/model/models.json
index 0fb0f8685..b13fa4d91 100644
--- a/src/test/resources/domain/model/models.json
+++ b/src/test/resources/domain/model/models.json
@@ -141,6 +141,12 @@
"openaiChatCompletions": {"baseUrl": "https://model.adapter.example.com"},
"anthropicMessages": {"baseUrl": "https://model.adapter.example.com"}
},
- "embeddingDimensions": 1536
+ "embeddingDimensions": 1536,
+ "catalogSchemaId": "https://test-catalog-schema.example",
+ "catalogProperties": {
+ "category": "AI Model",
+ "vendor": "Test Vendor",
+ "tags": ["chat", "completion"]
+ }
}
]
\ No newline at end of file
diff --git a/src/test/resources/domain/model/toolsets.json b/src/test/resources/domain/model/toolsets.json
index 1d26705b1..01e1edb36 100644
--- a/src/test/resources/domain/model/toolsets.json
+++ b/src/test/resources/domain/model/toolsets.json
@@ -78,6 +78,12 @@
},
"author": "test-author",
"createdAt": 12345,
- "updatedAt": 12345
+ "updatedAt": 12345,
+ "catalogSchemaId": "https://test-catalog-schema.example",
+ "catalogProperties": {
+ "category": "AI ToolSet",
+ "vendor": "Test Vendor",
+ "tags": ["git", "mcp"]
+ }
}
]
\ No newline at end of file
From 387bd3a116be212ad63211d27e628342f7eccacb Mon Sep 17 00:00:00 2001
From: Katerina_Charakhovich
Date: Fri, 7 Aug 2026 08:02:34 +0200
Subject: [PATCH 3/9] update properties-tracker.md and README.md
---
README.md | 6 ++++++
.../cfg/domain/normalizer/ApplicationNormalizer.java | 9 ++++-----
.../aidial/cfg/domain/normalizer/ModelNormalizer.java | 9 ++++-----
.../resources/core-config-schemas/properties-tracker.md | 2 +-
4 files changed, 15 insertions(+), 11 deletions(-)
diff --git a/README.md b/README.md
index 5965a6c7c..9a4ff3ed5 100644
--- a/README.md
+++ b/README.md
@@ -114,6 +114,12 @@ The system creates an empty configuration. To utilize existing Dial Core configu
1. Import configuration file in the AIDIAL admin panel using special import endpoint
+**Compatibility Note**
+>
+> Since **v0.47.0**, the Admin Panel supports multilingual metadata through the `LocalizedValue` type.
+> Exported configurations that contain multiple localized values are compatible only with **AIDIAL Core v0.47.0+**.
+> Configurations containing only plain string values remain backward compatible with earlier Core versions.
+
## Getting Started
### Run Application with Gradle
diff --git a/src/main/java/com/epam/aidial/cfg/domain/normalizer/ApplicationNormalizer.java b/src/main/java/com/epam/aidial/cfg/domain/normalizer/ApplicationNormalizer.java
index 6e0f592f7..a9df88703 100644
--- a/src/main/java/com/epam/aidial/cfg/domain/normalizer/ApplicationNormalizer.java
+++ b/src/main/java/com/epam/aidial/cfg/domain/normalizer/ApplicationNormalizer.java
@@ -1,21 +1,20 @@
package com.epam.aidial.cfg.domain.normalizer;
import com.epam.aidial.cfg.domain.model.Application;
-import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
@Component
-@RequiredArgsConstructor
public class ApplicationNormalizer {
public void normalize(Application application) {
- normalizeDisplayVersion(application);
+ setDisplayVersionToNullIfBlank(application);
setEndpointToNullIfBlank(application);
}
- private void normalizeDisplayVersion(Application application) {
- if (StringUtils.isBlank(application.getDisplayVersion())) {
+ private void setDisplayVersionToNullIfBlank(Application application) {
+ String displayVersion = application.getDisplayVersion();
+ if (StringUtils.isBlank(displayVersion)) {
application.setDisplayVersion(null);
}
}
diff --git a/src/main/java/com/epam/aidial/cfg/domain/normalizer/ModelNormalizer.java b/src/main/java/com/epam/aidial/cfg/domain/normalizer/ModelNormalizer.java
index 59f11ede3..8f0a675db 100644
--- a/src/main/java/com/epam/aidial/cfg/domain/normalizer/ModelNormalizer.java
+++ b/src/main/java/com/epam/aidial/cfg/domain/normalizer/ModelNormalizer.java
@@ -1,20 +1,19 @@
package com.epam.aidial.cfg.domain.normalizer;
import com.epam.aidial.cfg.domain.model.Model;
-import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
@Component
-@RequiredArgsConstructor
public class ModelNormalizer {
public void normalize(Model model) {
- normalizeDisplayVersion(model);
+ setDisplayVersionToNullIfBlank(model);
}
- private void normalizeDisplayVersion(Model model) {
- if (StringUtils.isBlank(model.getDisplayVersion())) {
+ private void setDisplayVersionToNullIfBlank(Model model) {
+ String displayVersion = model.getDisplayVersion();
+ if (StringUtils.isBlank(displayVersion)) {
model.setDisplayVersion(null);
}
}
diff --git a/src/main/resources/core-config-schemas/properties-tracker.md b/src/main/resources/core-config-schemas/properties-tracker.md
index 9287959aa..27d2454f4 100644
--- a/src/main/resources/core-config-schemas/properties-tracker.md
+++ b/src/main/resources/core-config-schemas/properties-tracker.md
@@ -23,4 +23,4 @@ This document tracks new properties added to the Core Config schema across diffe
| 0.44.0 | - `tokenEndpointAuthMethod` in ResourceAuthSettings, `id` in Upstream |
| 0.45.0 | - `maxTokensSupported`, `maxCompletionTokensSupported`,`customTemperatureSupported`,`reasoningEfforts` in Features
- `secretExtraData` in Upstream
- `embeddingDimensions` in Model
- `provider` in ToolSet |
| 0.46.0 | - `interfaces` in Model (keys: `openaiChatCompletions`, `openaiResponses`, `anthropicMessages`), Application and Interceptor (key: `openaiChatCompletions` only); each value is an object with required `base_url`
- `externalServices` in Application (map of `displayName`, `description`, `authSettings`), appIdentity and allowUserExternalServices in Application, dynamicallyRegistered in ResourceAuthSettings, intro in Model, Application, ToolSet, vendorWebsite in ToolSet, ToolSetData |
-| 0.47.0 | - `catalogSchemaId` in Deployment (String reference to a catalog schema)
- `catalogProperties` in Deployment (curated marketplace/catalog display metadata, validated against catalogSchemaId) |
+| 0.47.0 | - `catalogSchemaId` in Deployment (String reference to a catalog schema)
- `catalogProperties` in Deployment (curated marketplace/catalog display metadata, validated against catalogSchemaId)
- **Type change**: `displayName`, `description`, `intro` in Deployment changed to `LocalizedValue` type (stores either plain strings or JSON locale maps: `"GPT-4"` or `{"en":"GPT-4","fr":"GPT-4"}`) |
From c06b35cfc665e16dcd4ef1a14db72d338f0fc620 Mon Sep 17 00:00:00 2001
From: Katerina_Charakhovich
Date: Fri, 7 Aug 2026 10:50:43 +0200
Subject: [PATCH 4/9] add localization for resources, archive
---
.../mapper/ApplicationClientMapper.java | 3 +-
.../client/mapper/ToolSetClientMapper.java | 3 +-
.../domain/mapper/LocalizedValueMapper.java | 23 ++++++++++++
.../cfg/domain/mapper/ToolSetCoreMapper.java | 2 --
.../aidial/cfg/dto/ApplicationEximDto.java | 7 ++--
.../cfg/dto/ApplicationResourceDto.java | 7 ++--
.../cfg/dto/CreateApplicationResourceDto.java | 7 ++--
.../cfg/dto/CreateToolSetResourceDto.java | 9 ++---
.../epam/aidial/cfg/dto/ToolSetEximDto.java | 7 ++--
.../aidial/cfg/dto/ToolSetResourceDto.java | 7 ++--
.../cfg/mapper/ApplicationResourceMapper.java | 3 +-
.../cfg/mapper/ToolSetResourceMapper.java | 3 +-
.../aidial/cfg/model/ApplicationExim.java | 7 ++--
.../aidial/cfg/model/ApplicationResource.java | 7 ++--
.../cfg/model/CreateApplicationResource.java | 7 ++--
.../cfg/model/CreateToolSetResource.java | 7 ++--
.../epam/aidial/cfg/model/ToolSetExim.java | 7 ++--
.../aidial/cfg/model/ToolSetResource.java | 7 ++--
.../service/ApplicationEximServiceTest.java | 36 ++++++++++++-------
.../cfg/service/ToolSetEximServiceTest.java | 30 ++++++++++------
.../ZipApplicationEximServiceTest.java | 21 ++++++-----
.../service/ZipToolSetEximServiceTest.java | 21 ++++++-----
.../none/ToolSetResourceControllerTest.java | 18 ----------
23 files changed, 149 insertions(+), 100 deletions(-)
diff --git a/src/main/java/com/epam/aidial/cfg/client/mapper/ApplicationClientMapper.java b/src/main/java/com/epam/aidial/cfg/client/mapper/ApplicationClientMapper.java
index 9fc05d62f..34aa03cbe 100644
--- a/src/main/java/com/epam/aidial/cfg/client/mapper/ApplicationClientMapper.java
+++ b/src/main/java/com/epam/aidial/cfg/client/mapper/ApplicationClientMapper.java
@@ -3,6 +3,7 @@
import com.epam.aidial.cfg.client.dto.ApplicationMetadataDto;
import com.epam.aidial.cfg.client.dto.ApplicationResourceDto;
import com.epam.aidial.cfg.client.dto.NodeTypeDto;
+import com.epam.aidial.cfg.domain.mapper.LocalizedValueMapper;
import com.epam.aidial.cfg.dto.ApplicationEximDto;
import com.epam.aidial.cfg.model.ApplicationExim;
import com.epam.aidial.cfg.model.ApplicationResource;
@@ -20,7 +21,7 @@
import static com.epam.aidial.cfg.client.mapper.CoreMetadataUtils.extractPath;
import static com.epam.aidial.cfg.client.mapper.CoreMetadataUtils.parseEncodedVersionedPath;
-@Mapper(componentModel = "spring", uses = {RouteMapper.class})
+@Mapper(componentModel = "spring", uses = {RouteMapper.class, LocalizedValueMapper.class})
@Slf4j
public abstract class ApplicationClientMapper {
public static final String APPLICATIONS_PREFIX = "applications/";
diff --git a/src/main/java/com/epam/aidial/cfg/client/mapper/ToolSetClientMapper.java b/src/main/java/com/epam/aidial/cfg/client/mapper/ToolSetClientMapper.java
index ec34c3eb3..7cacb0113 100644
--- a/src/main/java/com/epam/aidial/cfg/client/mapper/ToolSetClientMapper.java
+++ b/src/main/java/com/epam/aidial/cfg/client/mapper/ToolSetClientMapper.java
@@ -3,6 +3,7 @@
import com.epam.aidial.cfg.client.dto.NodeTypeDto;
import com.epam.aidial.cfg.client.dto.ToolSetMetadataDto;
import com.epam.aidial.cfg.client.dto.ToolSetResourceDto;
+import com.epam.aidial.cfg.domain.mapper.LocalizedValueMapper;
import com.epam.aidial.cfg.dto.ToolSetEximDto;
import com.epam.aidial.cfg.model.CreateToolSetResource;
import com.epam.aidial.cfg.model.NodeType;
@@ -19,7 +20,7 @@
import static com.epam.aidial.cfg.client.mapper.CoreMetadataUtils.extractPath;
import static com.epam.aidial.cfg.client.mapper.CoreMetadataUtils.parseEncodedVersionedPath;
-@Mapper(componentModel = "spring")
+@Mapper(componentModel = "spring", uses = {LocalizedValueMapper.class})
@Slf4j
public abstract class ToolSetClientMapper {
public static final String TOOLSETS_PREFIX = "toolsets/";
diff --git a/src/main/java/com/epam/aidial/cfg/domain/mapper/LocalizedValueMapper.java b/src/main/java/com/epam/aidial/cfg/domain/mapper/LocalizedValueMapper.java
index a2a744136..7f14aaf97 100644
--- a/src/main/java/com/epam/aidial/cfg/domain/mapper/LocalizedValueMapper.java
+++ b/src/main/java/com/epam/aidial/cfg/domain/mapper/LocalizedValueMapper.java
@@ -45,6 +45,7 @@ public String toString(LocalizedValue value) {
* @param value the LocalizedValue to serialize
* @return JSON string of locale map, or null if plain value
*/
+ @Named("toJson")
public String toJson(LocalizedValue value) {
if (value == null || !value.isMap()) {
return null;
@@ -88,4 +89,26 @@ public LocalizedValue fromStrings(String plainValue, String jsonValue) {
public LocalizedValue fromString(String value) {
return value != null ? LocalizedValue.of(value) : null;
}
+
+ /**
+ * Maps plain String to LocalizedValue (used by MapStruct for automatic conversion).
+ * This is the method signature MapStruct looks for by default.
+ *
+ * @param value plain string value
+ * @return LocalizedValue wrapping the string, or null
+ */
+ public LocalizedValue map(String value) {
+ return fromString(value);
+ }
+
+ /**
+ * Maps LocalizedValue to String (used by MapStruct for automatic conversion).
+ * Resolves to default locale for backward compatibility.
+ *
+ * @param value LocalizedValue to resolve
+ * @return resolved string value, or null
+ */
+ public String map(LocalizedValue value) {
+ return toString(value);
+ }
}
diff --git a/src/main/java/com/epam/aidial/cfg/domain/mapper/ToolSetCoreMapper.java b/src/main/java/com/epam/aidial/cfg/domain/mapper/ToolSetCoreMapper.java
index 734d90aa8..e3269e7c0 100644
--- a/src/main/java/com/epam/aidial/cfg/domain/mapper/ToolSetCoreMapper.java
+++ b/src/main/java/com/epam/aidial/cfg/domain/mapper/ToolSetCoreMapper.java
@@ -37,14 +37,12 @@ public abstract class ToolSetCoreMapper {
@Mapping(target = "interfaces", ignore = true)
@Mapping(target = "authSettings", source = "deployment.authSettings")
@Mapping(target = "forwardPerRequestKey", source = "deployment.forwardPerRequestKey")
- @Mapping(target = "description", expression = "java(toolSet.getDescription())")
public abstract CoreToolSet mapToolSet(ToolSet toolSet);
@Mapping(target = "deployment", source = "coreToolSet", qualifiedByName = "toSecuredResource")
@Mapping(target = "source", ignore = true)
@Mapping(target = "createdAt", ignore = true)
@Mapping(target = "updatedAt", ignore = true)
- @Mapping(target = "description", expression = "java(coreToolSet.getDescription())")
@BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
public abstract ToolSet mapToolSet(CoreToolSet coreToolSet, @Context List roleLimits, @MappingTarget ToolSet toolSet);
diff --git a/src/main/java/com/epam/aidial/cfg/dto/ApplicationEximDto.java b/src/main/java/com/epam/aidial/cfg/dto/ApplicationEximDto.java
index 327661452..3ef3125c6 100644
--- a/src/main/java/com/epam/aidial/cfg/dto/ApplicationEximDto.java
+++ b/src/main/java/com/epam/aidial/cfg/dto/ApplicationEximDto.java
@@ -1,5 +1,6 @@
package com.epam.aidial.cfg.dto;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@@ -19,11 +20,11 @@ public class ApplicationEximDto {
private String endpoint;
private String responsesEndpoint;
private Map interfaces;
- private String displayName;
+ private LocalizedValue displayName;
private String displayVersion;
private String iconUrl;
- private String description;
- private String intro;
+ private LocalizedValue description;
+ private LocalizedValue intro;
private String reference;
private Boolean forwardAuthToken;
private List inputAttachmentTypes;
diff --git a/src/main/java/com/epam/aidial/cfg/dto/ApplicationResourceDto.java b/src/main/java/com/epam/aidial/cfg/dto/ApplicationResourceDto.java
index 9ca5b9431..0cb8dd2d5 100644
--- a/src/main/java/com/epam/aidial/cfg/dto/ApplicationResourceDto.java
+++ b/src/main/java/com/epam/aidial/cfg/dto/ApplicationResourceDto.java
@@ -1,5 +1,6 @@
package com.epam.aidial.cfg.dto;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
import com.epam.aidial.cfg.dto.source.ApplicationResourceSourceDto;
import jakarta.validation.Valid;
import lombok.Data;
@@ -19,11 +20,11 @@ public class ApplicationResourceDto {
private String endpoint;
private String responsesEndpoint;
private Map interfaces;
- private String displayName;
+ private LocalizedValue displayName;
private String displayVersion;
private String iconUrl;
- private String description;
- private String intro;
+ private LocalizedValue description;
+ private LocalizedValue intro;
private String reference;
private Boolean forwardAuthToken;
private List inputAttachmentTypes;
diff --git a/src/main/java/com/epam/aidial/cfg/dto/CreateApplicationResourceDto.java b/src/main/java/com/epam/aidial/cfg/dto/CreateApplicationResourceDto.java
index d187ecbbf..d23765a85 100644
--- a/src/main/java/com/epam/aidial/cfg/dto/CreateApplicationResourceDto.java
+++ b/src/main/java/com/epam/aidial/cfg/dto/CreateApplicationResourceDto.java
@@ -1,5 +1,6 @@
package com.epam.aidial.cfg.dto;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
import com.epam.aidial.cfg.dto.source.ApplicationResourceSourceDto;
import com.epam.aidial.cfg.dto.validation.annotation.Endpoint;
import jakarta.validation.Valid;
@@ -28,11 +29,11 @@ public class CreateApplicationResourceDto {
private String responsesEndpoint;
@Valid
private Map interfaces;
- private String displayName;
+ private LocalizedValue displayName;
private String displayVersion;
private String iconUrl;
- private String description;
- private String intro;
+ private LocalizedValue description;
+ private LocalizedValue intro;
private String reference;
private Boolean forwardAuthToken;
private List inputAttachmentTypes;
diff --git a/src/main/java/com/epam/aidial/cfg/dto/CreateToolSetResourceDto.java b/src/main/java/com/epam/aidial/cfg/dto/CreateToolSetResourceDto.java
index bf2905d72..033b4c644 100644
--- a/src/main/java/com/epam/aidial/cfg/dto/CreateToolSetResourceDto.java
+++ b/src/main/java/com/epam/aidial/cfg/dto/CreateToolSetResourceDto.java
@@ -1,5 +1,6 @@
package com.epam.aidial.cfg.dto;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
import com.epam.aidial.cfg.dto.validation.annotation.Endpoint;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
@@ -20,12 +21,12 @@ public class CreateToolSetResourceDto {
@NotBlank(message = "Completion endpoint is required")
@Endpoint
private String endpoint;
- @NotBlank(message = "Display name is required")
- private String displayName;
+ @NotNull(message = "Display name is required")
+ private LocalizedValue displayName;
private String displayVersion;
private String iconUrl;
- private String description;
- private String intro;
+ private LocalizedValue description;
+ private LocalizedValue intro;
private String vendorWebsite;
private List descriptionKeywords;
private Integer maxRetryAttempts;
diff --git a/src/main/java/com/epam/aidial/cfg/dto/ToolSetEximDto.java b/src/main/java/com/epam/aidial/cfg/dto/ToolSetEximDto.java
index 7c5c07093..4b3373d8e 100644
--- a/src/main/java/com/epam/aidial/cfg/dto/ToolSetEximDto.java
+++ b/src/main/java/com/epam/aidial/cfg/dto/ToolSetEximDto.java
@@ -1,5 +1,6 @@
package com.epam.aidial.cfg.dto;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@@ -17,11 +18,11 @@ public class ToolSetEximDto {
private String folderId;
private String version;
private String endpoint;
- private String displayName;
+ private LocalizedValue displayName;
private String displayVersion;
private String iconUrl;
- private String description;
- private String intro;
+ private LocalizedValue description;
+ private LocalizedValue intro;
private String vendorWebsite;
private List descriptionKeywords;
private Integer maxRetryAttempts;
diff --git a/src/main/java/com/epam/aidial/cfg/dto/ToolSetResourceDto.java b/src/main/java/com/epam/aidial/cfg/dto/ToolSetResourceDto.java
index c8a50d254..a42f5f9e9 100644
--- a/src/main/java/com/epam/aidial/cfg/dto/ToolSetResourceDto.java
+++ b/src/main/java/com/epam/aidial/cfg/dto/ToolSetResourceDto.java
@@ -1,5 +1,6 @@
package com.epam.aidial.cfg.dto;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
import lombok.Data;
import java.util.List;
@@ -14,11 +15,11 @@ public class ToolSetResourceDto {
private String author;
private String name;
private String endpoint;
- private String displayName;
+ private LocalizedValue displayName;
private String displayVersion;
private String iconUrl;
- private String description;
- private String intro;
+ private LocalizedValue description;
+ private LocalizedValue intro;
private String vendorWebsite;
private List descriptionKeywords;
private Integer maxRetryAttempts;
diff --git a/src/main/java/com/epam/aidial/cfg/mapper/ApplicationResourceMapper.java b/src/main/java/com/epam/aidial/cfg/mapper/ApplicationResourceMapper.java
index 73fca80f0..94dce7f74 100644
--- a/src/main/java/com/epam/aidial/cfg/mapper/ApplicationResourceMapper.java
+++ b/src/main/java/com/epam/aidial/cfg/mapper/ApplicationResourceMapper.java
@@ -1,5 +1,6 @@
package com.epam.aidial.cfg.mapper;
+import com.epam.aidial.cfg.domain.mapper.LocalizedValueMapper;
import com.epam.aidial.cfg.dto.ApplicationResourceDto;
import com.epam.aidial.cfg.dto.ApplicationResourceNodeInfoDto;
import com.epam.aidial.cfg.dto.ApplicationsEximDto;
@@ -15,7 +16,7 @@
import org.mapstruct.Mapping;
import org.mapstruct.Named;
-@Mapper(componentModel = "spring")
+@Mapper(componentModel = "spring", uses = {LocalizedValueMapper.class})
public interface ApplicationResourceMapper {
@Mapping(target = "applicationTypeSchemaId", source = "source", qualifiedByName = "toSchemaIdString")
diff --git a/src/main/java/com/epam/aidial/cfg/mapper/ToolSetResourceMapper.java b/src/main/java/com/epam/aidial/cfg/mapper/ToolSetResourceMapper.java
index 3a2faf30a..ef89b237b 100644
--- a/src/main/java/com/epam/aidial/cfg/mapper/ToolSetResourceMapper.java
+++ b/src/main/java/com/epam/aidial/cfg/mapper/ToolSetResourceMapper.java
@@ -1,5 +1,6 @@
package com.epam.aidial.cfg.mapper;
+import com.epam.aidial.cfg.domain.mapper.LocalizedValueMapper;
import com.epam.aidial.cfg.dto.CreateToolSetResourceDto;
import com.epam.aidial.cfg.dto.ToolSetResourceDto;
import com.epam.aidial.cfg.dto.ToolSetResourceNodeInfoDto;
@@ -10,7 +11,7 @@
import com.epam.aidial.cfg.model.ToolSetsExim;
import org.mapstruct.Mapper;
-@Mapper(componentModel = "spring")
+@Mapper(componentModel = "spring", uses = {LocalizedValueMapper.class})
public interface ToolSetResourceMapper {
CreateToolSetResource toCreateToolSetResourceDto(CreateToolSetResourceDto createToolSetResourceDto);
diff --git a/src/main/java/com/epam/aidial/cfg/model/ApplicationExim.java b/src/main/java/com/epam/aidial/cfg/model/ApplicationExim.java
index de152d97b..f4b24a0e0 100644
--- a/src/main/java/com/epam/aidial/cfg/model/ApplicationExim.java
+++ b/src/main/java/com/epam/aidial/cfg/model/ApplicationExim.java
@@ -1,5 +1,6 @@
package com.epam.aidial.cfg.model;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@@ -19,10 +20,10 @@ public class ApplicationExim {
private String endpoint;
private String responsesEndpoint;
private Map interfaces;
- private String displayName;
+ private LocalizedValue displayName;
private String displayVersion;
private String iconUrl;
- private String description;
+ private LocalizedValue description;
private String reference;
private Boolean forwardAuthToken;
private List inputAttachmentTypes;
@@ -43,7 +44,7 @@ public class ApplicationExim {
private String applicationTypeSchemaId;
private Map applicationProperties;
private McpResource mcp;
- private String intro;
+ private LocalizedValue intro;
private String appIdentity;
private boolean allowUserExternalServices;
private Map externalServices;
diff --git a/src/main/java/com/epam/aidial/cfg/model/ApplicationResource.java b/src/main/java/com/epam/aidial/cfg/model/ApplicationResource.java
index ab3b92f85..53a2c88ef 100644
--- a/src/main/java/com/epam/aidial/cfg/model/ApplicationResource.java
+++ b/src/main/java/com/epam/aidial/cfg/model/ApplicationResource.java
@@ -1,5 +1,6 @@
package com.epam.aidial.cfg.model;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@@ -23,10 +24,10 @@ public class ApplicationResource {
private String endpoint;
private String responsesEndpoint;
private Map interfaces;
- private String displayName;
+ private LocalizedValue displayName;
private String displayVersion;
private String iconUrl;
- private String description;
+ private LocalizedValue description;
private String reference;
private Boolean forwardAuthToken;
private List inputAttachmentTypes;
@@ -50,7 +51,7 @@ public class ApplicationResource {
private McpResource mcp;
private Map externalServices;
private String url;
- private String intro;
+ private LocalizedValue intro;
private String appIdentity;
private boolean allowUserExternalServices;
private String catalogSchemaId;
diff --git a/src/main/java/com/epam/aidial/cfg/model/CreateApplicationResource.java b/src/main/java/com/epam/aidial/cfg/model/CreateApplicationResource.java
index fdf0129e7..bc1bf29dc 100644
--- a/src/main/java/com/epam/aidial/cfg/model/CreateApplicationResource.java
+++ b/src/main/java/com/epam/aidial/cfg/model/CreateApplicationResource.java
@@ -1,5 +1,6 @@
package com.epam.aidial.cfg.model;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@@ -19,10 +20,10 @@ public class CreateApplicationResource {
private String endpoint;
private String responsesEndpoint;
private Map interfaces;
- private String displayName;
+ private LocalizedValue displayName;
private String displayVersion;
private String iconUrl;
- private String description;
+ private LocalizedValue description;
private String reference;
private Boolean forwardAuthToken;
private List inputAttachmentTypes;
@@ -42,7 +43,7 @@ public class CreateApplicationResource {
private String applicationTypeSchemaId;
private Map applicationProperties;
private McpResource mcp;
- private String intro;
+ private LocalizedValue intro;
private String appIdentity;
private boolean allowUserExternalServices;
private Map externalServices;
diff --git a/src/main/java/com/epam/aidial/cfg/model/CreateToolSetResource.java b/src/main/java/com/epam/aidial/cfg/model/CreateToolSetResource.java
index c53c7d9dc..40e668e4a 100644
--- a/src/main/java/com/epam/aidial/cfg/model/CreateToolSetResource.java
+++ b/src/main/java/com/epam/aidial/cfg/model/CreateToolSetResource.java
@@ -1,5 +1,6 @@
package com.epam.aidial.cfg.model;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@@ -17,10 +18,10 @@ public class CreateToolSetResource {
private String version;
private String folderId;
private String endpoint;
- private String displayName;
+ private LocalizedValue displayName;
private String displayVersion;
private String iconUrl;
- private String description;
+ private LocalizedValue description;
private String vendorWebsite;
private List descriptionKeywords;
private Integer maxRetryAttempts;
@@ -30,7 +31,7 @@ public class CreateToolSetResource {
private ResourceAuthSettings authSettings;
private boolean forwardPerRequestKey;
private boolean forwardAuthToken;
- private String intro;
+ private LocalizedValue intro;
private String catalogSchemaId;
private Map catalogProperties;
diff --git a/src/main/java/com/epam/aidial/cfg/model/ToolSetExim.java b/src/main/java/com/epam/aidial/cfg/model/ToolSetExim.java
index e28287126..32303ef09 100644
--- a/src/main/java/com/epam/aidial/cfg/model/ToolSetExim.java
+++ b/src/main/java/com/epam/aidial/cfg/model/ToolSetExim.java
@@ -1,5 +1,6 @@
package com.epam.aidial.cfg.model;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@@ -18,10 +19,10 @@ public class ToolSetExim {
private String folderId;
private String version;
private String endpoint;
- private String displayName;
+ private LocalizedValue displayName;
private String displayVersion;
private String iconUrl;
- private String description;
+ private LocalizedValue description;
private String vendorWebsite;
private List descriptionKeywords;
private Integer maxRetryAttempts;
@@ -32,7 +33,7 @@ public class ToolSetExim {
private ResourceAuthSettings authSettings;
private boolean forwardPerRequestKey;
private boolean forwardAuthToken;
- private String intro;
+ private LocalizedValue intro;
private String catalogSchemaId;
private Map catalogProperties;
diff --git a/src/main/java/com/epam/aidial/cfg/model/ToolSetResource.java b/src/main/java/com/epam/aidial/cfg/model/ToolSetResource.java
index 4f8590fd8..360686a08 100644
--- a/src/main/java/com/epam/aidial/cfg/model/ToolSetResource.java
+++ b/src/main/java/com/epam/aidial/cfg/model/ToolSetResource.java
@@ -1,5 +1,6 @@
package com.epam.aidial.cfg.model;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@@ -21,10 +22,10 @@ public class ToolSetResource {
private String author;
private String name;
private String endpoint;
- private String displayName;
+ private LocalizedValue displayName;
private String displayVersion;
private String iconUrl;
- private String description;
+ private LocalizedValue description;
private String vendorWebsite;
private List descriptionKeywords;
private Integer maxRetryAttempts;
@@ -36,7 +37,7 @@ public class ToolSetResource {
private boolean forwardPerRequestKey;
private boolean forwardAuthToken;
private String url;
- private String intro;
+ private LocalizedValue intro;
private String catalogSchemaId;
private Map catalogProperties;
diff --git a/src/test/java/com/epam/aidial/cfg/service/ApplicationEximServiceTest.java b/src/test/java/com/epam/aidial/cfg/service/ApplicationEximServiceTest.java
index d724c181f..b2095e682 100644
--- a/src/test/java/com/epam/aidial/cfg/service/ApplicationEximServiceTest.java
+++ b/src/test/java/com/epam/aidial/cfg/service/ApplicationEximServiceTest.java
@@ -3,6 +3,9 @@
import com.epam.aidial.cfg.client.mapper.ApplicationClientMapperImpl;
import com.epam.aidial.cfg.client.mapper.RouteMapperImpl;
import com.epam.aidial.cfg.configuration.JsonMapperConfiguration;
+import com.epam.aidial.cfg.configuration.LocalizationProperties;
+import com.epam.aidial.cfg.domain.mapper.LocalizedValueMapper;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
import com.epam.aidial.cfg.dto.ApplicationEximDto;
import com.epam.aidial.cfg.dto.ApplicationsEximDto;
import com.epam.aidial.cfg.dto.McpResourceDto;
@@ -30,6 +33,7 @@
import java.util.Collections;
import java.util.List;
+import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -48,7 +52,9 @@
ApplicationClientMapperImpl.class,
ApplicationEximService.class,
ResourceImportValidator.class,
- RouteMapperImpl.class
+ RouteMapperImpl.class,
+ LocalizedValueMapper.class,
+ LocalizationProperties.class
})
@TestPropertySource(properties = {
"applications.import.consecutiveErrorsThreshold=2"
@@ -81,9 +87,9 @@ void exportApplications_SinglePath() {
var applicationExim = result.getApplications().get(0);
assertThat(applicationExim.getApplicationTypeSchemaId()).isEqualTo("https://test1.epam.com");
- assertThat(applicationExim.getDisplayName()).isEqualTo("application1");
+ assertThat(applicationExim.getDisplayName()).isEqualTo(LocalizedValue.of("application1"));
assertThat(applicationExim.getFolderId()).isEqualTo("public/");
- assertThat(applicationExim.getDescription()).isEqualTo("application description 1");
+ assertThat(applicationExim.getDescription()).isEqualTo(LocalizedValue.of("application description 1"));
assertThat(applicationExim.getMcp().getEndpoint()).isEqualTo("http://localhost:9876/1/mcp");
}
@@ -92,7 +98,10 @@ void exportApplications_SinglePath() {
void exportApplications_MultiplePaths() {
// given
var application1 = getApplicationResource("1");
+ application1.setDisplayName(LocalizedValue.of(Map.of("en", "application description en", "de",
+ "application description de")));
var application2 = getApplicationResource("2");
+ application2.setDisplayName(LocalizedValue.of(Map.of("en", "application description en")));
var path1 = application1.getPath();
var path2 = application2.getPath();
@@ -112,7 +121,9 @@ void exportApplications_MultiplePaths() {
assertThat(application1Exim1.getApplicationTypeSchemaId()).isEqualTo("https://test1.epam.com");
assertThat(application1Exim1.getName()).isEqualTo("application1");
assertThat(application1Exim1.getFolderId()).isEqualTo("public/");
- assertThat(application1Exim1.getDescription()).isEqualTo("application description 1");
+ assertThat(application1Exim1.getDescription()).isEqualTo(LocalizedValue.of("application description 1"));
+ assertThat(application1Exim1.getDisplayName()).isEqualTo(LocalizedValue.of(Map.of("en", "application description en", "de",
+ "application description de")));
assertThat(application1Exim1.getMcp().getEndpoint()).isEqualTo("http://localhost:9876/1/mcp");
// Verify second application
@@ -120,7 +131,8 @@ void exportApplications_MultiplePaths() {
assertThat(application1Exim2.getApplicationTypeSchemaId()).isEqualTo("https://test2.epam.com");
assertThat(application1Exim2.getName()).isEqualTo("application2");
assertThat(application1Exim2.getFolderId()).isEqualTo("public/");
- assertThat(application1Exim2.getDescription()).isEqualTo("application description 2");
+ assertThat(application1Exim2.getDescription()).isEqualTo(LocalizedValue.of("application description 2"));
+ assertThat(application1Exim2.getDisplayName()).isEqualTo(LocalizedValue.of(Map.of("en", "application description en")));
assertThat(application1Exim2.getMcp().getEndpoint()).isEqualTo("http://localhost:9876/2/mcp");
}
@@ -193,7 +205,7 @@ void exportApplications_FolderPath_excludesTechnicalFile() {
application.setName("test");
application.setVersion("0.0.1");
application.setFolderId("public/folder1/folder2/");
- application.setDescription("d");
+ application.setDescription(LocalizedValue.of("d"));
application.setMcp(mcp);
application.setApplicationTypeSchemaId("https://test.epam.com");
@@ -284,7 +296,7 @@ void importApplications_NotFlatImport() {
assertThat(applicationResource.getName()).isEqualTo("application1");
assertThat(applicationResource.getVersion()).isEqualTo("0.0.1");
assertThat(applicationResource.getFolderId()).isEqualTo("public/to/folder1/");
- assertThat(applicationResource.getDescription()).isEqualTo("application description 1");
+ assertThat(applicationResource.getDescription()).isEqualTo(LocalizedValue.of("application description 1"));
assertThat(applicationResource.getMcp().getEndpoint()).isEqualTo("http://localhost:9876/1/mcp");
}
@@ -335,7 +347,7 @@ void importApplications_FlatImport() {
assertThat(application.getName()).isEqualTo("application1");
assertThat(application.getVersion()).isEqualTo("0.0.1");
assertThat(application.getFolderId()).isEqualTo("public/to/");
- assertThat(application.getDescription()).isEqualTo("application description 1");
+ assertThat(application.getDescription()).isEqualTo(LocalizedValue.of("application description 1"));
assertThat(application.getMcp().getEndpoint()).isEqualTo("http://localhost:9876/1/mcp");
}
@@ -388,11 +400,11 @@ private ApplicationResource getApplicationResource(String suffix) {
var application = new ApplicationResource();
application.setApplicationTypeSchemaId(String.format("https://test%s.epam.com", suffix));
application.setName("application" + suffix);
- application.setDisplayName("application" + suffix);
+ application.setDisplayName(LocalizedValue.of("application" + suffix));
application.setVersion(String.format("0.0.%s", suffix));
application.setFolderId(String.format("public/folder%s/", suffix));
application.setPath(String.format("%s%s__%s", application.getFolderId(), application.getName(), application.getVersion()));
- application.setDescription(String.format("application description %s", suffix));
+ application.setDescription(LocalizedValue.of(String.format("application description %s", suffix)));
application.setMcp(mcp);
return application;
}
@@ -405,9 +417,9 @@ private ApplicationEximDto getApplicationEximDto(String suffix) {
.applicationTypeSchemaId(String.format("https://test%s.epam.com", suffix))
.name("application" + suffix)
.version(String.format("0.0.%s", suffix))
- .displayName("application" + suffix)
+ .displayName(LocalizedValue.of("application" + suffix))
.folderId(String.format("public/folder%s/", suffix))
- .description(String.format("application description %s", suffix))
+ .description(LocalizedValue.of(String.format("application description %s", suffix)))
.mcp(mcp)
.build();
}
diff --git a/src/test/java/com/epam/aidial/cfg/service/ToolSetEximServiceTest.java b/src/test/java/com/epam/aidial/cfg/service/ToolSetEximServiceTest.java
index d30919c0e..190a7d085 100644
--- a/src/test/java/com/epam/aidial/cfg/service/ToolSetEximServiceTest.java
+++ b/src/test/java/com/epam/aidial/cfg/service/ToolSetEximServiceTest.java
@@ -3,6 +3,9 @@
import com.epam.aidial.cfg.client.mapper.RouteMapperImpl;
import com.epam.aidial.cfg.client.mapper.ToolSetClientMapperImpl;
import com.epam.aidial.cfg.configuration.JsonMapperConfiguration;
+import com.epam.aidial.cfg.configuration.LocalizationProperties;
+import com.epam.aidial.cfg.domain.mapper.LocalizedValueMapper;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
import com.epam.aidial.cfg.dto.ToolSetEximDto;
import com.epam.aidial.cfg.dto.ToolSetsEximDto;
import com.epam.aidial.cfg.model.CreateToolSetResource;
@@ -28,6 +31,7 @@
import java.util.Collections;
import java.util.List;
+import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -46,7 +50,9 @@
ToolSetClientMapperImpl.class,
ToolSetEximService.class,
ResourceImportValidator.class,
- RouteMapperImpl.class
+ RouteMapperImpl.class,
+ LocalizedValueMapper.class,
+ LocalizationProperties.class
})
@TestPropertySource(properties = {
"toolsets.import.consecutiveErrorsThreshold=2"
@@ -78,9 +84,9 @@ void exportToolSets_SinglePath() {
assertThat(result.getToolSets()).hasSize(1);
var toolSetExim = result.getToolSets().get(0);
- assertThat(toolSetExim.getDisplayName()).isEqualTo("toolSet1");
+ assertThat(toolSetExim.getDisplayName()).isEqualTo(LocalizedValue.of("toolSet1"));
assertThat(toolSetExim.getFolderId()).isEqualTo("public/");
- assertThat(toolSetExim.getDescription()).isEqualTo("toolSet description 1");
+ assertThat(toolSetExim.getDescription()).isEqualTo(LocalizedValue.of("toolSet description 1"));
assertThat(toolSetExim.isForwardPerRequestKey()).isTrue();
}
@@ -108,14 +114,14 @@ void exportToolSets_MultiplePaths() {
var toolSetExim1 = result.getToolSets().get(0);
assertThat(toolSetExim1.getName()).isEqualTo("toolSet1");
assertThat(toolSetExim1.getFolderId()).isEqualTo("public/");
- assertThat(toolSetExim1.getDescription()).isEqualTo("toolSet description 1");
+ assertThat(toolSetExim1.getDescription()).isEqualTo(LocalizedValue.of("toolSet description 1"));
assertThat(toolSetExim1.isForwardPerRequestKey()).isTrue();
// Verify second toolSet1Exim1
var toolSetExim2 = result.getToolSets().get(1);
assertThat(toolSetExim2.getName()).isEqualTo("toolSet2");
assertThat(toolSetExim2.getFolderId()).isEqualTo("public/");
- assertThat(toolSetExim2.getDescription()).isEqualTo("toolSet description 2");
+ assertThat(toolSetExim2.getDescription()).isEqualTo(LocalizedValue.of("toolSet description 2"));
assertThat(toolSetExim2.isForwardPerRequestKey()).isTrue();
}
@@ -244,6 +250,7 @@ void importToolSets_NotFlatImport() {
.build();
var toolSetExim = getToolSetEximDto("1");
+ toolSetExim.setIntro(LocalizedValue.of(Map.of("en", "intro en", "de", "intro en")));
var toolSetsExim = new ToolSetsEximDto();
toolSetsExim.setToolSets(List.of(toolSetExim));
@@ -272,7 +279,8 @@ void importToolSets_NotFlatImport() {
assertThat(toolSetEximResource.getName()).isEqualTo("toolSet1");
assertThat(toolSetEximResource.getVersion()).isEqualTo("0.0.1");
assertThat(toolSetEximResource.getFolderId()).isEqualTo("public/to/folder1/");
- assertThat(toolSetEximResource.getDescription()).isEqualTo("toolSet description 1");
+ assertThat(toolSetEximResource.getDescription()).isEqualTo(LocalizedValue.of("toolSet description 1"));
+ assertThat(toolSetEximResource.getIntro()).isEqualTo(LocalizedValue.of(Map.of("en", "intro en", "de", "intro en")));
}
@Test
@@ -322,7 +330,7 @@ void importToolSets_FlatImport() {
assertThat(toolSet.getName()).isEqualTo("toolSet1");
assertThat(toolSet.getVersion()).isEqualTo("0.0.1");
assertThat(toolSet.getFolderId()).isEqualTo("public/to/");
- assertThat(toolSet.getDescription()).isEqualTo("toolSet description 1");
+ assertThat(toolSet.getDescription()).isEqualTo(LocalizedValue.of("toolSet description 1"));
assertThat(toolSet.isForwardPerRequestKey()).isFalse();
}
@@ -372,11 +380,11 @@ void importToolSets_UniquenessConflict_ImportsNonDuplicateItems() {
private ToolSetResource getToolSetResource(String suffix) {
var toolSet = new ToolSetResource();
toolSet.setName("toolSet" + suffix);
- toolSet.setDisplayName("toolSet" + suffix);
+ toolSet.setDisplayName(LocalizedValue.of("toolSet" + suffix));
toolSet.setVersion(String.format("0.0.%s", suffix));
toolSet.setFolderId(String.format("public/folder%s/", suffix));
toolSet.setPath(String.format("%s%s__%s", toolSet.getFolderId(), toolSet.getName(), toolSet.getVersion()));
- toolSet.setDescription(String.format("toolSet description %s", suffix));
+ toolSet.setDescription(LocalizedValue.of(String.format("toolSet description %s", suffix)));
toolSet.setForwardPerRequestKey(true);
return toolSet;
}
@@ -385,9 +393,9 @@ private ToolSetEximDto getToolSetEximDto(String suffix) {
return ToolSetEximDto.builder()
.name("toolSet" + suffix)
.version(String.format("0.0.%s", suffix))
- .displayName("toolSet" + suffix)
+ .displayName(LocalizedValue.of("toolSet" + suffix))
.folderId(String.format("public/folder%s/", suffix))
- .description(String.format("toolSet description %s", suffix))
+ .description(LocalizedValue.of(String.format("toolSet description %s", suffix)))
.forwardPerRequestKey(false)
.build();
}
diff --git a/src/test/java/com/epam/aidial/cfg/service/ZipApplicationEximServiceTest.java b/src/test/java/com/epam/aidial/cfg/service/ZipApplicationEximServiceTest.java
index 9fbcc09a3..6e483fd52 100644
--- a/src/test/java/com/epam/aidial/cfg/service/ZipApplicationEximServiceTest.java
+++ b/src/test/java/com/epam/aidial/cfg/service/ZipApplicationEximServiceTest.java
@@ -3,6 +3,9 @@
import com.epam.aidial.cfg.client.mapper.ApplicationClientMapperImpl;
import com.epam.aidial.cfg.client.mapper.RouteMapperImpl;
import com.epam.aidial.cfg.configuration.JsonMapperConfiguration;
+import com.epam.aidial.cfg.configuration.LocalizationProperties;
+import com.epam.aidial.cfg.domain.mapper.LocalizedValueMapper;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
import com.epam.aidial.cfg.dto.ApplicationEximDto;
import com.epam.aidial.cfg.dto.ApplicationsEximDto;
import com.epam.aidial.cfg.model.ApplicationResource;
@@ -43,7 +46,9 @@
ApplicationClientMapperImpl.class,
ZipApplicationEximService.class,
ResourceImportValidator.class,
- RouteMapperImpl.class
+ RouteMapperImpl.class,
+ LocalizedValueMapper.class,
+ LocalizationProperties.class
})
class ZipApplicationEximServiceTest {
@@ -174,7 +179,7 @@ void importZipApplications_ZipContainConflictingApplicationsAcrossFiles_ThrowErr
var application1 = getApplicationEximDto("1");
var application2 = getApplicationEximDto("1");
- application2.setDescription("changed description");
+ application2.setDescription(LocalizedValue.of("changed description"));
var inputStream = getZipInputStream(List.of(
Pair.of("applications/application1.json", ApplicationsEximDto.builder()
@@ -213,8 +218,8 @@ void importZipApplications_ZipContainConflictingApplicationsWithinAndAcrossFiles
var application1 = getApplicationEximDto("1");
var application2 = getApplicationEximDto("1");
var application3 = getApplicationEximDto("1");
- application2.setDescription("changed description 2");
- application3.setDescription("changed description 3");
+ application2.setDescription(LocalizedValue.of("changed description 2"));
+ application3.setDescription(LocalizedValue.of("changed description 3"));
var inputStream = getZipInputStream(List.of(
Pair.of("applications/application1.json", ApplicationsEximDto.builder()
@@ -384,11 +389,11 @@ private ApplicationResource getApplicationResource(String suffix) {
var application = new ApplicationResource();
application.setApplicationTypeSchemaId(String.format("https://test%s.epam.com", suffix));
application.setName("application" + suffix);
- application.setDisplayName("application" + suffix);
+ application.setDisplayName(LocalizedValue.of("application" + suffix));
application.setVersion(String.format("0.0.%s", suffix));
application.setFolderId(String.format("public/folder%s/", suffix));
application.setPath(String.format("%s/%s__%s", application.getFolderId(), application.getName(), application.getVersion()));
- application.setDescription(String.format("application description %s", suffix));
+ application.setDescription(LocalizedValue.of(String.format("application description %s", suffix)));
return application;
}
@@ -397,9 +402,9 @@ private ApplicationEximDto getApplicationEximDto(String suffix) {
.applicationTypeSchemaId(String.format("https://test%s.epam.com", suffix))
.name("application" + suffix)
.version(String.format("0.0.%s", suffix))
- .displayName("application" + suffix)
+ .displayName(LocalizedValue.of("application" + suffix))
.folderId(String.format("public/folder%s/", suffix))
- .description(String.format("application description %s", suffix))
+ .description(LocalizedValue.of(String.format("application description %s", suffix)))
.build();
}
diff --git a/src/test/java/com/epam/aidial/cfg/service/ZipToolSetEximServiceTest.java b/src/test/java/com/epam/aidial/cfg/service/ZipToolSetEximServiceTest.java
index c5961803d..b43e0f3c9 100644
--- a/src/test/java/com/epam/aidial/cfg/service/ZipToolSetEximServiceTest.java
+++ b/src/test/java/com/epam/aidial/cfg/service/ZipToolSetEximServiceTest.java
@@ -3,6 +3,9 @@
import com.epam.aidial.cfg.client.mapper.RouteMapperImpl;
import com.epam.aidial.cfg.client.mapper.ToolSetClientMapperImpl;
import com.epam.aidial.cfg.configuration.JsonMapperConfiguration;
+import com.epam.aidial.cfg.configuration.LocalizationProperties;
+import com.epam.aidial.cfg.domain.mapper.LocalizedValueMapper;
+import com.epam.aidial.cfg.domain.value.LocalizedValue;
import com.epam.aidial.cfg.dto.ToolSetEximDto;
import com.epam.aidial.cfg.dto.ToolSetsEximDto;
import com.epam.aidial.cfg.model.ImportConflictResolutionStrategy;
@@ -43,7 +46,9 @@
ToolSetClientMapperImpl.class,
ZipToolSetEximService.class,
ResourceImportValidator.class,
- RouteMapperImpl.class
+ RouteMapperImpl.class,
+ LocalizedValueMapper.class,
+ LocalizationProperties.class
})
class ZipToolSetEximServiceTest {
@@ -174,7 +179,7 @@ void importZipToolSets_ZipContainConflictingToolSetsAcrossFiles_ThrowError() {
var toolSet1 = getToolSetEximDto("1");
var toolSet2 = getToolSetEximDto("1");
- toolSet2.setDescription("changed description");
+ toolSet2.setDescription(LocalizedValue.of("changed description"));
var inputStream = getZipInputStream(List.of(
Pair.of("toolSets/toolSet1.json", ToolSetsEximDto.builder()
@@ -213,8 +218,8 @@ void importZipToolSets_ZipContainConflictingToolSetsWithinAndAcrossFiles_ThrowEr
var toolSet1 = getToolSetEximDto("1");
var toolSet2 = getToolSetEximDto("1");
var toolSet3 = getToolSetEximDto("1");
- toolSet2.setDescription("changed description 2");
- toolSet3.setDescription("changed description 3");
+ toolSet2.setDescription(LocalizedValue.of("changed description 2"));
+ toolSet3.setDescription(LocalizedValue.of("changed description 3"));
var inputStream = getZipInputStream(List.of(
Pair.of("toolSets/toolSet1.json", ToolSetsEximDto.builder()
@@ -383,11 +388,11 @@ private InputStream getZipWithRawEntry(String entryPath, byte[] rawContent) {
private ToolSetResource getToolSetResource(String suffix) {
var toolSet = new ToolSetResource();
toolSet.setName("toolSet" + suffix);
- toolSet.setDisplayName("toolSet" + suffix);
+ toolSet.setDisplayName(LocalizedValue.of("toolSet" + suffix));
toolSet.setVersion(String.format("0.0.%s", suffix));
toolSet.setFolderId(String.format("public/folder%s/", suffix));
toolSet.setPath(String.format("%s/%s__%s", toolSet.getFolderId(), toolSet.getName(), toolSet.getVersion()));
- toolSet.setDescription(String.format("toolSet description %s", suffix));
+ toolSet.setDescription(LocalizedValue.of(String.format("toolSet description %s", suffix)));
return toolSet;
}
@@ -395,9 +400,9 @@ private ToolSetEximDto getToolSetEximDto(String suffix) {
return ToolSetEximDto.builder()
.name("toolSet" + suffix)
.version(String.format("0.0.%s", suffix))
- .displayName("toolSet" + suffix)
+ .displayName(LocalizedValue.of("toolSet" + suffix))
.folderId(String.format("public/folder%s/", suffix))
- .description(String.format("toolSet description %s", suffix))
+ .description(LocalizedValue.of(String.format("toolSet description %s", suffix)))
.build();
}
diff --git a/src/test/java/com/epam/aidial/cfg/web/controller/none/ToolSetResourceControllerTest.java b/src/test/java/com/epam/aidial/cfg/web/controller/none/ToolSetResourceControllerTest.java
index 05919d1a1..6ec0a2ca7 100644
--- a/src/test/java/com/epam/aidial/cfg/web/controller/none/ToolSetResourceControllerTest.java
+++ b/src/test/java/com/epam/aidial/cfg/web/controller/none/ToolSetResourceControllerTest.java
@@ -257,24 +257,6 @@ void testCreateToolSetResourceWithUndefinedDisplayName() throws Exception {
verifyNoInteractions(toolSetResourceService);
}
- @Test
- void testCreateToolSetResourceWithEmptyDisplayName() throws Exception {
- var createToolSetDtoJson = ResourceUtils.readResource(DTO_JSON_BASE_PATH + JSON_TOOLSET_CREATE_DTO);
-
- // Test with empty displayName
- var dtoEmptyDisplayName = objectMapper.readValue(createToolSetDtoJson, CreateToolSetResourceDto.class);
- dtoEmptyDisplayName.setDisplayName("");
- var jsonEmptyDisplayName = objectMapper.writeValueAsString(dtoEmptyDisplayName);
-
- mockMvc.perform(post(CREATE_API_PATH)
- .contentType(MediaType.APPLICATION_JSON)
- .content(jsonEmptyDisplayName))
- .andExpect(status().isBadRequest())
- .andExpect(jsonPath("$.message").value("displayName: Display name is required"));
-
- verifyNoInteractions(toolSetResourceService);
- }
-
@Test
void testCreateToolSetResourceWithEmptyEndpoint() throws Exception {
var createToolSetDtoJson = ResourceUtils.readResource(DTO_JSON_BASE_PATH + JSON_TOOLSET_CREATE_DTO);
From fe9ef9016ae623a95ced1660e527afbd53fb652b Mon Sep 17 00:00:00 2001
From: Katerina_Charakhovich
Date: Fri, 7 Aug 2026 11:29:32 +0200
Subject: [PATCH 5/9] checkstyle
---
.../cfg/dao/model/ApplicationEntity.java | 18 ++++++------------
.../cfg/dao/model/InterceptorEntity.java | 12 ++++--------
.../epam/aidial/cfg/dao/model/ModelEntity.java | 18 ++++++------------
.../aidial/cfg/dao/model/ToolSetEntity.java | 18 ++++++------------
4 files changed, 22 insertions(+), 44 deletions(-)
diff --git a/src/main/java/com/epam/aidial/cfg/dao/model/ApplicationEntity.java b/src/main/java/com/epam/aidial/cfg/dao/model/ApplicationEntity.java
index 66694b33b..2526e83ab 100644
--- a/src/main/java/com/epam/aidial/cfg/dao/model/ApplicationEntity.java
+++ b/src/main/java/com/epam/aidial/cfg/dao/model/ApplicationEntity.java
@@ -50,36 +50,30 @@ public class ApplicationEntity extends ValidityStateAwareEntity {
@AttributeOverrides({
@AttributeOverride(
name = "plainValue",
- column = @Column(name = "description")
- ),
+ column = @Column(name = "description")),
@AttributeOverride(
name = "localeMap",
- column = @Column(name = "description_i18n")
- )
+ column = @Column(name = "description_i18n"))
})
private LocalizedValueEntity description;
@Embedded
@AttributeOverrides({
@AttributeOverride(
name = "plainValue",
- column = @Column(name = "intro")
- ),
+ column = @Column(name = "intro")),
@AttributeOverride(
name = "localeMap",
- column = @Column(name = "intro_i18n")
- )
+ column = @Column(name = "intro_i18n"))
})
private LocalizedValueEntity intro;
@Embedded
@AttributeOverrides({
@AttributeOverride(
name = "plainValue",
- column = @Column(name = "display_name")
- ),
+ column = @Column(name = "display_name")),
@AttributeOverride(
name = "localeMap",
- column = @Column(name = "display_name_i18n")
- )
+ column = @Column(name = "display_name_i18n"))
})
private LocalizedValueEntity displayName;
private String displayVersion;
diff --git a/src/main/java/com/epam/aidial/cfg/dao/model/InterceptorEntity.java b/src/main/java/com/epam/aidial/cfg/dao/model/InterceptorEntity.java
index 248f86e21..a3e73c7d1 100644
--- a/src/main/java/com/epam/aidial/cfg/dao/model/InterceptorEntity.java
+++ b/src/main/java/com/epam/aidial/cfg/dao/model/InterceptorEntity.java
@@ -37,24 +37,20 @@ public class InterceptorEntity extends TimeTrackableEntity {
@AttributeOverrides({
@AttributeOverride(
name = "plainValue",
- column = @Column(name = "description")
- ),
+ column = @Column(name = "description")),
@AttributeOverride(
name = "localeMap",
- column = @Column(name = "description_i18n")
- )
+ column = @Column(name = "description_i18n"))
})
private LocalizedValueEntity description;
@Embedded
@AttributeOverrides({
@AttributeOverride(
name = "plainValue",
- column = @Column(name = "display_name")
- ),
+ column = @Column(name = "display_name")),
@AttributeOverride(
name = "localeMap",
- column = @Column(name = "display_name_i18n")
- )
+ column = @Column(name = "display_name_i18n"))
})
private LocalizedValueEntity displayName;
private Boolean forwardAuthToken;
diff --git a/src/main/java/com/epam/aidial/cfg/dao/model/ModelEntity.java b/src/main/java/com/epam/aidial/cfg/dao/model/ModelEntity.java
index ee6db1a8b..9e450f074 100644
--- a/src/main/java/com/epam/aidial/cfg/dao/model/ModelEntity.java
+++ b/src/main/java/com/epam/aidial/cfg/dao/model/ModelEntity.java
@@ -43,36 +43,30 @@ public class ModelEntity extends TimeTrackableEntity {
@AttributeOverrides({
@AttributeOverride(
name = "plainValue",
- column = @Column(name = "description")
- ),
+ column = @Column(name = "description")),
@AttributeOverride(
name = "localeMap",
- column = @Column(name = "description_i18n")
- )
+ column = @Column(name = "description_i18n"))
})
private LocalizedValueEntity description;
@Embedded
@AttributeOverrides({
@AttributeOverride(
name = "plainValue",
- column = @Column(name = "intro")
- ),
+ column = @Column(name = "intro")),
@AttributeOverride(
name = "localeMap",
- column = @Column(name = "intro_i18n")
- )
+ column = @Column(name = "intro_i18n"))
})
private LocalizedValueEntity intro;
@Embedded
@AttributeOverrides({
@AttributeOverride(
name = "plainValue",
- column = @Column(name = "display_name")
- ),
+ column = @Column(name = "display_name")),
@AttributeOverride(
name = "localeMap",
- column = @Column(name = "display_name_i18n")
- )
+ column = @Column(name = "display_name_i18n"))
})
private LocalizedValueEntity displayName;
private String displayVersion;
diff --git a/src/main/java/com/epam/aidial/cfg/dao/model/ToolSetEntity.java b/src/main/java/com/epam/aidial/cfg/dao/model/ToolSetEntity.java
index 0356f03c0..65fa5c4c2 100644
--- a/src/main/java/com/epam/aidial/cfg/dao/model/ToolSetEntity.java
+++ b/src/main/java/com/epam/aidial/cfg/dao/model/ToolSetEntity.java
@@ -42,36 +42,30 @@ public class ToolSetEntity extends TimeTrackableEntity {
@AttributeOverrides({
@AttributeOverride(
name = "plainValue",
- column = @Column(name = "description")
- ),
+ column = @Column(name = "description")),
@AttributeOverride(
name = "localeMap",
- column = @Column(name = "description_i18n")
- )
+ column = @Column(name = "description_i18n"))
})
private LocalizedValueEntity description;
@Embedded
@AttributeOverrides({
@AttributeOverride(
name = "plainValue",
- column = @Column(name = "intro")
- ),
+ column = @Column(name = "intro")),
@AttributeOverride(
name = "localeMap",
- column = @Column(name = "intro_i18n")
- )
+ column = @Column(name = "intro_i18n"))
})
private LocalizedValueEntity intro;
@Embedded
@AttributeOverrides({
@AttributeOverride(
name = "plainValue",
- column = @Column(name = "display_name")
- ),
+ column = @Column(name = "display_name")),
@AttributeOverride(
name = "localeMap",
- column = @Column(name = "display_name_i18n")
- )
+ column = @Column(name = "display_name_i18n"))
})
private LocalizedValueEntity displayName;
private String vendorWebsite;
From da46afec7bf3fd1ead2530f9568f6ff6415a6b3b Mon Sep 17 00:00:00 2001
From: Katerina_Charakhovich
Date: Sun, 9 Aug 2026 22:04:16 +0200
Subject: [PATCH 6/9] revert localized entity fields to String values
---
.../mapper/LocalizedValueEntityMapper.java | 37 ++++-----
.../cfg/dao/model/ApplicationEntity.java | 35 +-------
.../cfg/dao/model/InterceptorEntity.java | 24 +-----
.../cfg/dao/model/LocalizedValueEntity.java | 17 ----
.../aidial/cfg/dao/model/ModelEntity.java | 35 +-------
.../aidial/cfg/dao/model/ToolSetEntity.java | 35 +-------
.../domain/service/ApplicationService.java | 5 +-
.../cfg/domain/service/ModelService.java | 5 +-
...18__AddI18nColumnsToDeploymentEntities.sql | 74 -----------------
...18__AddI18nColumnsToDeploymentEntities.sql | 83 -------------------
...18__AddI18nColumnsToDeploymentEntities.sql | 74 -----------------
.../ApplicationValidityStateResolverTest.java | 5 +-
12 files changed, 29 insertions(+), 400 deletions(-)
delete mode 100644 src/main/java/com/epam/aidial/cfg/dao/model/LocalizedValueEntity.java
delete mode 100644 src/main/resources/db/migration/H2/V1.118__AddI18nColumnsToDeploymentEntities.sql
delete mode 100644 src/main/resources/db/migration/MS_SQL_SERVER/V1.118__AddI18nColumnsToDeploymentEntities.sql
delete mode 100644 src/main/resources/db/migration/POSTGRES/V1.118__AddI18nColumnsToDeploymentEntities.sql
diff --git a/src/main/java/com/epam/aidial/cfg/dao/mapper/LocalizedValueEntityMapper.java b/src/main/java/com/epam/aidial/cfg/dao/mapper/LocalizedValueEntityMapper.java
index c06992216..27aefb5a2 100644
--- a/src/main/java/com/epam/aidial/cfg/dao/mapper/LocalizedValueEntityMapper.java
+++ b/src/main/java/com/epam/aidial/cfg/dao/mapper/LocalizedValueEntityMapper.java
@@ -1,6 +1,5 @@
package com.epam.aidial.cfg.dao.mapper;
-import com.epam.aidial.cfg.dao.model.LocalizedValueEntity;
import com.epam.aidial.cfg.domain.value.LocalizedValue;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -10,41 +9,35 @@
import java.util.Map;
-@Mapper(componentModel = "spring", uses = {MapPropertiesMapper.class})
+@Mapper(componentModel = "spring")
public class LocalizedValueEntityMapper {
+
@Autowired
protected ObjectMapper objectMapper;
@SneakyThrows
- public LocalizedValue toDomain(LocalizedValueEntity entity) {
- if (entity == null) {
+ public String convertToString(LocalizedValue value) {
+ if (value == null) {
return null;
}
-
- if (entity.getPlainValue() != null) {
- return LocalizedValue.of(entity.getPlainValue());
- }
-
- if (entity.getLocaleMap() == null) {
- return null;
+ if (!value.isMap()) {
+ return value.getPlainValue();
}
-
- Map map = objectMapper.readValue(entity.getLocaleMap(), new TypeReference<>() {
- });
-
- return LocalizedValue.of(map);
+ return objectMapper.writeValueAsString(value.getLocaleMap());
}
@SneakyThrows
- public LocalizedValueEntity toEntity(LocalizedValue value) {
+ public LocalizedValue convertToLocalizedValue(String value) {
if (value == null) {
return null;
}
-
- if (!value.isMap()) {
- return new LocalizedValueEntity(value.getPlainValue(), null);
+ if (value.startsWith("{")) {
+ return LocalizedValue.of(
+ objectMapper.readValue(value, new TypeReference