Skip to content
Open
86 changes: 77 additions & 9 deletions config/src/main/java/com/epam/aidial/core/config/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,43 +56,111 @@ public class Config {

private List<String> globalInterceptors = List.of();

/**
* $id → canonical-id index for {@link #applicationTypeSchemas}, built at rebuild time from
* blob bodies: each key is a schema's own {@code $id} (as declared in its body), each value
* is the canonical id of the blob entry storing that schema. Bridges $id-keyed file entries
* and canonical-id-keyed blob entries, since a schema's $id is not derivable from its path.
*
* <p>For example, given a blob entry stored under canonical id
* {@code schemas/platform/my-schema} whose body declares
* {@code "$id": "https://example.com/schemas/my-schema.json"}, this map holds
* {@code "https://example.com/schemas/my-schema.json" → "schemas/platform/my-schema"}.
*/
@JsonIgnore
private Map<String, String> applicationSchemaAliasesById = Map.of();

@JsonIgnore
private Map<String, String> catalogSchemaAliasesById = Map.of();

@JsonIgnore
public Deployment selectDeployment(String deploymentId) {
Application application = applications.get(deploymentId);
Application application = resolve(applications, "applications", deploymentId);
if (application != null) {
return application;
}

Model model = models.get(deploymentId);
Model model = resolve(models, "models", deploymentId);
if (model != null) {
return model;
}

ToolSet toolSet = toolsets.get(deploymentId);
ToolSet toolSet = resolve(toolsets, "toolsets", deploymentId);
if (toolSet != null) {
return toolSet;
}

return interceptors.get(deploymentId);
return resolve(interceptors, "interceptors", deploymentId);
}

public boolean isDeploymentExists(String deploymentId) {
return selectDeployment(deploymentId) != null;
}

@JsonIgnore
public Model getModel(String id) {
return resolve(models, "models", id);
}

@JsonIgnore
public Role getRole(String id) {
return resolve(roles, "roles", id);
}

@JsonIgnore
public Interceptor getInterceptor(String id) {
return resolve(interceptors, "interceptors", id);
}

/**
* @return the schema body, or {@code null} if {@code schemaId} is null or unresolved
*/
@JsonIgnore
public String getCustomApplicationSchema(URI schemaId) {
if (schemaId == null) {
return null;
}
return applicationTypeSchemas.get(schemaId.toString());
return resolveSchema(applicationTypeSchemas, applicationSchemaAliasesById, schemaId);
}

/**
* @return the schema body, or {@code null} if {@code schemaId} is null or unresolved
*/
@JsonIgnore
public String getCatalogSchema(URI schemaId) {
return resolveSchema(catalogSchemas, catalogSchemaAliasesById, schemaId);
}

/**
* Resolves a schema by its $id: verbatim lookup first (canonical-id callers, and file entries
* already keyed by $id), then falls back through the $id → canonical-id alias index for a
* migrated blob entry. A schema's $id is not derivable from its path, so unlike {@link
* #resolve}, the alias index must be maintained explicitly (see {@code MergedConfigStore}).
*
* @return the schema body, or {@code null} if {@code schemaId} is null or unresolved
*/
private static String resolveSchema(Map<String, String> schemas, Map<String, String> aliasesById, URI schemaId) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

javax.annotation.Nullable isn't on the config module's classpath (only pulled in transitively by server), and adding a direct dependency just for this annotation felt disproportionate. Documented nullability via @return ... or {@code null} if ... javadoc on getCustomApplicationSchema/getCatalogSchema/resolveSchema instead.

if (schemaId == null) {
return null;
}
return catalogSchemas.get(schemaId.toString());
String id = schemaId.toString();
String body = schemas.get(id);
if (body != null) {
return body;
}
String canonicalId = aliasesById.get(id);
return canonicalId == null ? null : schemas.get(canonicalId);
}

/**
* Resolves a deployment-map lookup by id. Tries {@code id} verbatim first (canonical-id
* callers, and not-yet-migrated file entries keyed by short name), then falls back to the
* derived canonical id {@code typeSegment/platform/id} for a short-name lookup against a
* migrated blob entry. {@code typeSegment} is a string literal because this module has no
* dependency on storage/ResourceTypes.
*/
private static <V> V resolve(Map<String, V> entities, String typeSegment, String id) {
V direct = entities.get(id);
if (direct != null) {
return direct;
}
return entities.get(typeSegment + "/platform/" + id);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

import org.junit.jupiter.api.Test;

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

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;

public class ConfigTest {

Expand All @@ -27,4 +29,78 @@ public void testSelectDeployment() {
assertEquals(interceptor, config.selectDeployment("interceptor"));
assertNull(config.selectDeployment("unknown"));
}

@Test
public void testSelectDeploymentResolvesShortNameAgainstCanonicalId() {
Config config = new Config();
Model model = new Model();
config.setModels(Map.of("models/platform/gpt-4", model));

assertSame(model, config.selectDeployment("models/platform/gpt-4"), "verbatim (canonical) hit");
assertSame(model, config.selectDeployment("gpt-4"), "derived (short-name) hit");
assertNull(config.selectDeployment("unknown"));
}

@Test
public void testGetModelResolvesVerbatimAndDerived() {
Config config = new Config();
Model model = new Model();
config.setModels(Map.of("models/platform/gpt-4", model));

assertSame(model, config.getModel("models/platform/gpt-4"));
assertSame(model, config.getModel("gpt-4"));
assertNull(config.getModel("unknown"));
}

@Test
public void testGetRoleResolvesVerbatimAndDerived() {
Config config = new Config();
Role role = new Role();
config.setRoles(Map.of("roles/platform/admin", role));

assertSame(role, config.getRole("roles/platform/admin"));
assertSame(role, config.getRole("admin"));
assertNull(config.getRole("unknown"));
}

@Test
public void testGetInterceptorResolvesVerbatimAndDerived() {
Config config = new Config();
Interceptor interceptor = new Interceptor();
config.setInterceptors(Map.of("interceptors/platform/my-interceptor", interceptor));

assertSame(interceptor, config.getInterceptor("interceptors/platform/my-interceptor"));
assertSame(interceptor, config.getInterceptor("my-interceptor"));
assertNull(config.getInterceptor("unknown"));
}

@Test
public void testGetCustomApplicationSchemaFallsBackThroughAliasIndex() {
Config config = new Config();
String canonicalId = "schemas/platform/my-schema";
String schemaId = "https://mydial.epam.com/custom_application_schemas/specific_application_type";
String body = "{\"$id\":\"" + schemaId + "\"}";
config.setApplicationTypeSchemas(Map.of(canonicalId, body));
config.setApplicationSchemaAliasesById(Map.of(schemaId, canonicalId));

assertEquals(body, config.getCustomApplicationSchema(URI.create(schemaId)), "$id lookup via alias index");
assertEquals(body, config.getCustomApplicationSchema(URI.create(canonicalId)), "verbatim canonical-id lookup");
assertNull(config.getCustomApplicationSchema(URI.create("https://mydial.epam.com/custom_application_schemas/unknown")));
assertNull(config.getCustomApplicationSchema(null));
}

@Test
public void testGetCatalogSchemaFallsBackThroughAliasIndex() {
Config config = new Config();
String canonicalId = "catalog_schemas/platform/my-schema";
String schemaId = "https://dial.epam.com/catalog-schemas/model";
String body = "{\"$id\":\"" + schemaId + "\"}";
config.setCatalogSchemas(Map.of(canonicalId, body));
config.setCatalogSchemaAliasesById(Map.of(schemaId, canonicalId));

assertEquals(body, config.getCatalogSchema(URI.create(schemaId)), "$id lookup via alias index");
assertEquals(body, config.getCatalogSchema(URI.create(canonicalId)), "verbatim canonical-id lookup");
assertNull(config.getCatalogSchema(URI.create("https://dial.epam.com/catalog-schemas/unknown")));
assertNull(config.getCatalogSchema(null));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ private static void appendInterceptorWarnings(List<String> refs, Config config,
}
for (int i = 0; i < refs.size(); i++) {
String ref = refs.get(i);
if (ref == null || !config.getInterceptors().containsKey(ref)) {
if (ref == null || config.getInterceptor(ref) == null) {
warnings.add(new ValidationWarning("interceptors[" + i + "]",
"Interceptor '" + ref + "' not found"));
}
Expand All @@ -48,7 +48,7 @@ private static void appendSchemaWarning(URI schemaId, Config config, List<Valida
if (schemaId == null) {
return;
}
if (!config.getApplicationTypeSchemas().containsKey(schemaId.toString())) {
if (config.getCustomApplicationSchema(schemaId) == null) {
warnings.add(new ValidationWarning("applicationTypeSchemaId",
"Schema '" + schemaId + "' not found"));
}
Expand Down
Loading
Loading