fix: tolerate AI manifest YAML colons
Some checks failed
Deploy to OCIWP / deploy (push) Failing after 37s
Some checks failed
Deploy to OCIWP / deploy (push) Failing after 37s
This commit is contained in:
@@ -16,6 +16,7 @@ import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Scanner;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Stream;
|
||||
@@ -475,7 +476,7 @@ public class PodScaffolder {
|
||||
private static String normalizeToolServiceManifest(String source, Path rootDir, String moduleName, String categoryKey,
|
||||
List<String> targetModules)
|
||||
throws IOException {
|
||||
Map<String, Object> root = YAML_MAPPER.readValue(source, Map.class);
|
||||
Map<String, Object> root = parseManifestYaml(source);
|
||||
if (!(root.get("mcp") instanceof Map<?, ?> mcp)
|
||||
|| !(mcp.get("manifest") instanceof Map<?, ?> manifest)) {
|
||||
throw new IOException("tool-service-manifest.yml의 mcp.manifest.routing-functions 형식이 올바르지 않습니다.");
|
||||
@@ -526,6 +527,56 @@ public class PodScaffolder {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static Map<String, Object> parseManifestYaml(String source) throws IOException {
|
||||
try {
|
||||
return YAML_MAPPER.readValue(source, Map.class);
|
||||
} catch (IOException original) {
|
||||
String normalized = quoteUnquotedManifestScalars(source);
|
||||
if (normalized.equals(source)) {
|
||||
throw original;
|
||||
}
|
||||
return YAML_MAPPER.readValue(normalized, Map.class);
|
||||
}
|
||||
}
|
||||
|
||||
private static String quoteUnquotedManifestScalars(String source) {
|
||||
Set<String> stringKeys = Set.of(
|
||||
"name", "server-id", "category-key", "product-boundary", "business-domain",
|
||||
"business-outcome", "select-if", "reject-if", "decision-policy", "description-serialization");
|
||||
Pattern property = Pattern.compile("^(\\s*)([a-z-]+):(\\s*)(.*)$");
|
||||
Pattern listItem = Pattern.compile("^(\\s*-\\s+)(.*)$");
|
||||
StringBuilder normalized = new StringBuilder();
|
||||
for (String line : source.split("\\r?\\n", -1)) {
|
||||
Matcher propertyMatcher = property.matcher(line);
|
||||
if (propertyMatcher.matches() && stringKeys.contains(propertyMatcher.group(2))) {
|
||||
String value = propertyMatcher.group(4).trim();
|
||||
if (!value.isEmpty() && !isQuoted(value)) {
|
||||
line = propertyMatcher.group(1) + propertyMatcher.group(2) + ": " + yamlQuoted(value);
|
||||
}
|
||||
} else {
|
||||
Matcher listItemMatcher = listItem.matcher(line);
|
||||
if (listItemMatcher.matches()) {
|
||||
String value = listItemMatcher.group(2).trim();
|
||||
if (value.contains(":") && !isQuoted(value) && !value.matches("^[a-z-]+:.*$")) {
|
||||
line = listItemMatcher.group(1) + yamlQuoted(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
normalized.append(line).append(System.lineSeparator());
|
||||
}
|
||||
return normalized.toString();
|
||||
}
|
||||
|
||||
private static boolean isQuoted(String value) {
|
||||
return (value.startsWith("\"") && value.endsWith("\""))
|
||||
|| (value.startsWith("'") && value.endsWith("'"));
|
||||
}
|
||||
|
||||
private static String yamlQuoted(String value) {
|
||||
return "\"" + value.replace("\\", "\\\\").replace("\"", "\\\"") + "\"";
|
||||
}
|
||||
|
||||
private static List<String> allowedTargetModules(Path rootDir, String moduleName, List<String> targetModules)
|
||||
throws IOException {
|
||||
List<String> candidates = targetModules == null || targetModules.isEmpty()
|
||||
|
||||
@@ -114,6 +114,29 @@ class PodScaffolderTest {
|
||||
assertTrue(compose.contains("SPRING_PROFILES_ACTIVE=${ACTIVE_PROFILE:-local}"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void normalizesAiManifestWhenAnUnquotedKoreanDescriptionContainsColon() throws Exception {
|
||||
String aiManifest = """
|
||||
mcp:
|
||||
manifest:
|
||||
routing-functions:
|
||||
- name: route_to_dat-was-ehr
|
||||
server-id: dat-was-ehr
|
||||
category-key: ehr
|
||||
business-outcome: 인사 정보와 휴가 정보를 제공합니다.
|
||||
reject-if: 인사 운영의 핵심 기능(예: 급여, 승진, 보상)과 관련된 경우 다른 서버로 전달합니다.
|
||||
confusable-servers: [dat-was-hrd, dat-was-pay]
|
||||
""";
|
||||
|
||||
String normalized = PodScaffolder.normalizeToolServiceManifest(aiManifest, "dat-was-ehr",
|
||||
List.of("dat-was-cus", "dat-was-ehr", "dat-was-sal"));
|
||||
Map<String, Object> routingFunction = routingFunction(normalized);
|
||||
|
||||
assertEquals("인사 운영의 핵심 기능(예: 급여, 승진, 보상)과 관련된 경우 다른 서버로 전달합니다.",
|
||||
routingFunction.get("reject-if"));
|
||||
assertEquals(List.of("dat-was-cus", "dat-was-sal"), routingFunction.get("confusable-servers"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void generatesRedisOptionalPodComposeConfiguration() throws Exception {
|
||||
Files.writeString(root.resolve("settings.gradle"), "rootProject.name = 'test'\n");
|
||||
|
||||
Reference in New Issue
Block a user