feat: enhance tool scaffold and usecase naming
Some checks failed
Deploy to OCIWP / deploy (push) Failing after 0s

This commit is contained in:
jade
2026-08-12 23:42:14 +09:00
parent bd4d2a7624
commit 97d380afe0
35 changed files with 657 additions and 60 deletions

View File

@@ -45,7 +45,18 @@ public class ToolScaffolder {
private static final String BASE_PACKAGE = "io.shinhanlife.dap.mcc";
private static final String BASE_PACKAGE_PATH = "src/main/java/io/shinhanlife/dap/mcc";
public record FieldDefinition(String name, String type, String description, String example, boolean required) {
public record FieldDefinition(String name, String type, String description, String example, boolean required,
List<String> enumValues, String itemType, List<FieldDefinition> itemFields) {
public FieldDefinition(String name, String type, String description, String example, boolean required) {
this(name, type, description, example, required, List.of(), null, List.of());
}
}
public record ToolMethodDefinition(String baseName, String methodName, String interfaceId,
String title, String description, String group, String routingType,
boolean register, String clientSystemCode, String httpApiName,
List<FieldDefinition> inputFields, List<FieldDefinition> outputFields,
ToolDefinitionOptions definitionOptions) {
}
public record ToolDefinitionOptions(
@@ -59,6 +70,206 @@ public class ToolScaffolder {
String ownerOrg) {
}
public static String scaffoldUseCase(String useCaseName, String moduleName, String author,
String createDate, List<ToolMethodDefinition> tools) throws IOException {
if (tools == null || tools.isEmpty()) {
throw new IllegalArgumentException("At least one Tool method is required.");
}
String useCaseBaseName = toPascalCase(useCaseName);
String group = tools.getFirst().group().toLowerCase(Locale.ROOT);
validateToolMethods(tools, group);
String sourceDir = System.getenv("AXHUB_SOURCE_DIR");
Path rootDir = sourceDir == null ? Paths.get(".") : Paths.get(sourceDir);
Path configuredModule = Paths.get(moduleName);
Path moduleRoot = configuredModule.isAbsolute() ? configuredModule : rootDir.resolve(configuredModule);
Path sourceRoot = moduleRoot.resolve(BASE_PACKAGE_PATH);
Path useCaseDir = sourceRoot.resolve(Paths.get("biz", group, "usecase"));
Path implDir = useCaseDir.resolve("impl");
Path dtoDir = sourceRoot.resolve(Paths.get("biz", group, "dto"));
Path converterDir = sourceRoot.resolve(Paths.get("biz", group, "converter"));
Path definitionDir = moduleRoot.resolve(Paths.get("src", "main", "resources", "tool-definitions", group));
Path mockDir = moduleRoot.resolve(Paths.get("src", "main", "resources", "mock-responses"));
Files.createDirectories(useCaseDir);
Files.createDirectories(implDir);
Files.createDirectories(dtoDir);
Files.createDirectories(converterDir);
Files.createDirectories(definitionDir);
Files.createDirectories(mockDir);
String bizPackage = BASE_PACKAGE + ".biz." + group;
writeUtf8(useCaseDir.resolve(useCaseBaseName + "UseCase.java"),
groupedUseCaseContent(bizPackage, useCaseBaseName, moduleName, tools));
writeUtf8(implDir.resolve(useCaseBaseName + "UseCaseImpl.java"),
groupedUseCaseImplContent(bizPackage, useCaseBaseName, tools));
writeUtf8(converterDir.resolve(useCaseBaseName + "Converter.java"),
groupedConverterContent(bizPackage, useCaseBaseName, tools));
StringBuilder log = new StringBuilder("\n=========================================\n")
.append(" Multi Tool Scaffolding Complete\n")
.append("=========================================\n")
.append("[Usecase Interface] ").append(useCaseDir.resolve(useCaseBaseName + "UseCase.java")).append("\n")
.append("[Usecase Impl] ").append(implDir.resolve(useCaseBaseName + "UseCaseImpl.java")).append("\n");
for (ToolMethodDefinition tool : tools) {
writeGroupedToolFiles(moduleRoot, sourceRoot, dtoDir, definitionDir, mockDir, bizPackage, tool, moduleName, log);
}
log.append("[Converter] ").append(converterDir.resolve(useCaseBaseName + "Converter.java")).append("\n");
return log.toString();
}
private static void validateToolMethods(List<ToolMethodDefinition> tools, String expectedGroup) {
Set<String> methods = new LinkedHashSet<>();
Set<String> toolNames = new LinkedHashSet<>();
for (ToolMethodDefinition tool : tools) {
if (tool == null || tool.baseName() == null || tool.baseName().isBlank()
|| tool.methodName() == null || !tool.methodName().matches("^[a-zA-Z_$][a-zA-Z0-9_$]*$")) {
throw new IllegalArgumentException("Every Tool needs a valid base name and Java method name.");
}
if (!expectedGroup.equalsIgnoreCase(tool.group())) {
throw new IllegalArgumentException("All Tool methods in one UseCase must use the same category.");
}
if (!"MCI".equalsIgnoreCase(tool.routingType())) {
throw new IllegalArgumentException("Grouped Tool scaffolding currently supports MCI Tools only.");
}
if (tool.interfaceId() == null || tool.interfaceId().isBlank()
|| tool.clientSystemCode() == null || tool.clientSystemCode().isBlank()) {
throw new IllegalArgumentException("MCI Tool needs an interface ID and Client system code.");
}
String toolName = toToolName("", tool.group(), toPascalCase(tool.baseName()));
if (!methods.add(tool.methodName()) || !toolNames.add(toolName)) {
throw new IllegalArgumentException("Tool method names and MCP Tool names must be unique.");
}
}
}
private static void writeGroupedToolFiles(Path moduleRoot, Path sourceRoot, Path dtoDir, Path definitionDir,
Path mockDir, String bizPackage, ToolMethodDefinition tool,
String moduleName, StringBuilder log) throws IOException {
String baseName = toPascalCase(tool.baseName());
String code = tool.clientSystemCode().toLowerCase(Locale.ROOT);
String ioPackage = BASE_PACKAGE + ".infra.itrf.mci." + code;
Path clientDir = sourceRoot.resolve(Paths.get("infra", "itrf", "mci", code));
Path ioDir = clientDir.resolve("io");
Files.createDirectories(ioDir);
writeUtf8(dtoDir.resolve(baseName + "Request.java"),
dtoContent(bizPackage + ".dto", baseName + "Request", tool.inputFields(), "", "", true));
writeUtf8(dtoDir.resolve(baseName + "Response.java"),
dtoContent(bizPackage + ".dto", baseName + "Response", tool.outputFields(), "", "", false));
writeStructuredFieldTypes(dtoDir, bizPackage + ".dto", baseName + "Request", tool.inputFields());
writeStructuredFieldTypes(dtoDir, bizPackage + ".dto", baseName + "Response", tool.outputFields());
writeUtf8(ioDir.resolve(baseName + "_I.java"),
mciIoContent("infra.itrf.mci." + code, baseName + "_I", tool.inputFields(), "", ""));
writeUtf8(ioDir.resolve(baseName + "_O.java"),
mciIoContent("infra.itrf.mci." + code, baseName + "_O", tool.outputFields(), "", ""));
writeStructuredFieldTypes(ioDir, ioPackage + ".io", baseName + "_I", tool.inputFields());
writeStructuredFieldTypes(ioDir, ioPackage + ".io", baseName + "_O", tool.outputFields());
writeUtf8(clientDir.resolve(baseName + "Client.java"),
groupedMciClientContent(ioPackage, baseName, tool.interfaceId()));
String toolName = toToolName(moduleName, tool.group(), baseName);
writeUtf8(definitionDir.resolve(toolName + ".yml"), toolDefinitionContentV17(toolName,
option(tool.title(), baseName), tool.description(), tool.group(), tool.interfaceId(),
tool.inputFields(), isMutationTool(baseName), tool.definitionOptions()));
writeUtf8(mockDir.resolve(toolName + ".json"), mockResponseContent(tool.outputFields()));
log.append("[Tool] ").append(toolName).append(" -> ").append(clientDir.resolve(baseName + "Client.java")).append("\n");
}
private static String groupedUseCaseContent(String bizPackage, String useCaseBaseName, String moduleName,
List<ToolMethodDefinition> tools) {
StringBuilder imports = new StringBuilder();
StringBuilder methods = new StringBuilder();
for (ToolMethodDefinition tool : tools) {
String baseName = toPascalCase(tool.baseName());
imports.append("import ").append(bizPackage).append(".dto.").append(baseName).append("Request;\n")
.append("import ").append(bizPackage).append(".dto.").append(baseName).append("Response;\n");
methods.append(" @McpTool(name = \"").append(toToolName(moduleName, tool.group(), baseName))
.append("\", title = \"").append(javaText(option(tool.title(), baseName)))
.append("\", description = \"").append(javaText(option(tool.description(), ""))).append("\")\n")
.append(" @ToolHint(register = ").append(tool.register()).append(", categoryKey = \"")
.append(tool.group().toLowerCase(Locale.ROOT)).append("\", mappingId = \"")
.append(javaText(tool.interfaceId())).append("\")\n")
.append(" ").append(baseName).append("Response ").append(tool.methodName()).append("(")
.append(baseName).append("Request req);\n\n");
}
return "package " + bizPackage + ".usecase;\n\n"
+ "import org.springaicommunity.mcp.annotation.McpTool;\n"
+ "import io.shinhanlife.dap.lib.annotation.ToolHint;\n"
+ imports + "\npublic interface " + useCaseBaseName + "UseCase {\n\n" + methods + "}\n";
}
private static String groupedUseCaseImplContent(String bizPackage, String useCaseBaseName,
List<ToolMethodDefinition> tools) {
StringBuilder imports = new StringBuilder();
StringBuilder fields = new StringBuilder();
StringBuilder methods = new StringBuilder();
for (ToolMethodDefinition tool : tools) {
String baseName = toPascalCase(tool.baseName());
String code = tool.clientSystemCode().toLowerCase(Locale.ROOT);
String clientVariable = Character.toLowerCase(baseName.charAt(0)) + baseName.substring(1) + "Client";
imports.append("import ").append(bizPackage).append(".dto.").append(baseName).append("Request;\n")
.append("import ").append(bizPackage).append(".dto.").append(baseName).append("Response;\n")
.append("import ").append(BASE_PACKAGE).append(".infra.itrf.mci.").append(code).append(".").append(baseName).append("Client;\n")
.append("import ").append(BASE_PACKAGE).append(".infra.itrf.mci.").append(code).append(".io.").append(baseName).append("_I;\n")
.append("import ").append(BASE_PACKAGE).append(".infra.itrf.mci.").append(code).append(".io.").append(baseName).append("_O;\n");
fields.append(" private final ").append(baseName).append("Client ").append(clientVariable).append(";\n");
methods.append(" @Override\n public ").append(baseName).append("Response ").append(tool.methodName())
.append("(").append(baseName).append("Request req) {\n")
.append(" ").append(baseName).append("_I request = converter.to").append(baseName).append("Request(req);\n")
.append(" ").append(baseName).append("_O response = ").append(clientVariable).append(".call").append(baseName).append("(request);\n")
.append(" ").append(baseName).append("Response toolResponse = converter.to").append(baseName).append("Response(response);\n")
.append(" if (toolResponse == null) toolResponse = new ").append(baseName).append("Response();\n")
.append(" toolResponse.setResultCode(\"SUCCESS\");\n")
.append(" return toolResponse;\n }\n\n");
}
return "package " + bizPackage + ".usecase.impl;\n\n"
+ "import " + bizPackage + ".converter." + useCaseBaseName + "Converter;\n"
+ "import " + bizPackage + ".usecase." + useCaseBaseName + "UseCase;\n"
+ "import lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Service;\n" + imports
+ "\n@Service\n@RequiredArgsConstructor\npublic class " + useCaseBaseName + "UseCaseImpl implements " + useCaseBaseName + "UseCase {\n\n"
+ " private final " + useCaseBaseName + "Converter converter;\n" + fields + "\n" + methods + "}\n";
}
private static String groupedConverterContent(String bizPackage, String useCaseBaseName,
List<ToolMethodDefinition> tools) {
StringBuilder imports = new StringBuilder();
StringBuilder methods = new StringBuilder();
for (ToolMethodDefinition tool : tools) {
String baseName = toPascalCase(tool.baseName());
String code = tool.clientSystemCode().toLowerCase(Locale.ROOT);
imports.append("import ").append(bizPackage).append(".dto.").append(baseName).append("Request;\n")
.append("import ").append(bizPackage).append(".dto.").append(baseName).append("Response;\n")
.append("import ").append(BASE_PACKAGE).append(".infra.itrf.mci.").append(code).append(".io.").append(baseName).append("_I;\n")
.append("import ").append(BASE_PACKAGE).append(".infra.itrf.mci.").append(code).append(".io.").append(baseName).append("_O;\n");
methods.append(" ").append(baseName).append("_I to").append(baseName).append("Request(").append(baseName).append("Request request);\n")
.append(" ").append(baseName).append("Response to").append(baseName).append("Response(").append(baseName).append("_O response);\n\n");
}
return "package " + bizPackage + ".converter;\n\nimport org.mapstruct.Mapper;\nimport org.mapstruct.ReportingPolicy;\n"
+ imports + "\n@Mapper(componentModel = \"spring\", unmappedTargetPolicy = ReportingPolicy.IGNORE)\n"
+ "public interface " + useCaseBaseName + "Converter {\n\n" + methods + "}\n";
}
private static String groupedMciClientContent(String ioPackage, String baseName, String interfaceId) {
return "package " + ioPackage + ";\n\n"
+ "import io.shinhanlife.dap.lib.integration.mci.component.AxhubMciComponent;\n"
+ "import io.shinhanlife.glow.communication.dto.Transfer;\n"
+ "import lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Component;\n"
+ "import " + ioPackage + ".io." + baseName + "_I;\n"
+ "import " + ioPackage + ".io." + baseName + "_O;\n\n"
+ "@Component\n@RequiredArgsConstructor\npublic class " + baseName + "Client {\n"
+ " private final AxhubMciComponent mci;\n\n"
+ " public " + baseName + "_O call" + baseName + "(" + baseName + "_I request) {\n"
+ " try {\n"
+ " Transfer<" + baseName + "_O> transfer = mci.callTo(\"" + javaText(interfaceId) + "\", null, request, " + baseName + "_O.class);\n"
+ " return transfer.getBody();\n"
+ " } catch (Exception e) {\n"
+ " throw new IllegalStateException(\"MCI call failed: " + javaText(interfaceId) + "\", e);\n"
+ " }\n }\n}\n";
}
private static String javaText(String value) {
return value == null ? "" : value.replace("\\", "\\\\").replace("\"", "\\\"").replace("\r", " ").replace("\n", " ");
}
public static void main(String[] args) throws IOException {
Scanner scanner = new Scanner(System.in);
@@ -254,6 +465,7 @@ public class ToolScaffolder {
.replace("private String message;", "@Schema(example = \"테스트 메시지입니다.\")\n private String message;");
reqContent = dtoContent(bizPackage + ".dto", baseName + "Request", inputFields, author, createDate, true);
writeUtf8(dtoDir.resolve(baseName + "Request.java"), reqContent);
writeStructuredFieldTypes(dtoDir, bizPackage + ".dto", baseName + "Request", inputFields);
// Generate Response DTO
String resContent = """
@@ -288,6 +500,7 @@ public class ToolScaffolder {
""".formatted(bizPackage, bizPackage, baseName, author, createDate, createDate, author, baseName);
resContent = dtoContent(bizPackage + ".dto", baseName + "Response", outputFields, author, createDate, false);
writeUtf8(dtoDir.resolve(baseName + "Response.java"), resContent);
writeStructuredFieldTypes(dtoDir, bizPackage + ".dto", baseName + "Response", outputFields);
String toolName = toToolName(moduleName, group, baseName);
@@ -551,6 +764,7 @@ public class ToolScaffolder {
""".formatted(BASE_PACKAGE, mciGroupPath.replace("/", "."), BASE_PACKAGE, mciGroupPath.replace("/", "."), interfaceId, author, createDate, createDate, author, interfaceId);
mciReqContent = mciIoContent(mciGroupPath.replace("/", "."), interfaceId + "_I", inputFields, author, createDate);
writeUtf8(mciIoDir.resolve(interfaceId + "_I.java"), mciReqContent);
writeStructuredFieldTypes(mciIoDir, BASE_PACKAGE + "." + mciGroupPath.replace("/", ".") + ".io", interfaceId + "_I", inputFields);
String mciResContent = """
package %s.%s.io;
@@ -578,6 +792,7 @@ public class ToolScaffolder {
""".formatted(BASE_PACKAGE, mciGroupPath.replace("/", "."), BASE_PACKAGE, mciGroupPath.replace("/", "."), interfaceId, author, createDate, createDate, author, interfaceId);
mciResContent = mciIoContent(mciGroupPath.replace("/", "."), interfaceId + "_O", outputFields, author, createDate);
writeUtf8(mciIoDir.resolve(interfaceId + "_O.java"), mciResContent);
writeStructuredFieldTypes(mciIoDir, BASE_PACKAGE + "." + mciGroupPath.replace("/", ".") + ".io", interfaceId + "_O", outputFields);
String converterContent = """
package %s.converter;
@@ -692,6 +907,8 @@ public class ToolScaffolder {
dtoContent(httpPackage + ".io", httpRequestClass, inputFields, author, createDate, true));
writeUtf8(httpIoDir.resolve(httpResponseClass + ".java"),
dtoContent(httpPackage + ".io", httpResponseClass, outputFields, author, createDate, false));
writeStructuredFieldTypes(httpIoDir, httpPackage + ".io", httpRequestClass, inputFields);
writeStructuredFieldTypes(httpIoDir, httpPackage + ".io", httpResponseClass, outputFields);
writeUtf8(httpClientDir.resolve(httpClientClass + ".java"),
httpClientContent(httpPackage, httpClientClass, httpApiName));
writeUtf8(converterDir.resolve(baseName + "Converter.java"),
@@ -925,9 +1142,7 @@ public class ToolScaffolder {
|| !generatedNames.add(field.name().trim())) {
continue;
}
properties.append(" ").append(field.name()).append(":\n")
.append(" type: ").append(jsonSchemaType(field.type())).append("\n")
.append(" description: ").append(yamlText(field.description())).append("\n");
appendSchemaProperty(properties, field);
if (field.required()) {
required.append(" - ").append(field.name()).append("\n");
}
@@ -996,9 +1211,7 @@ public class ToolScaffolder {
|| !generatedNames.add(field.name().trim())) {
continue;
}
properties.append(" ").append(field.name().trim()).append(":\n")
.append(" type: ").append(jsonSchemaType(field.type())).append("\n")
.append(" description: ").append(yamlText(field.description())).append("\n");
appendSchemaProperty(properties, field);
if (field.required()) {
required.append(" - ").append(field.name().trim()).append("\n");
}
@@ -1070,10 +1283,34 @@ public class ToolScaffolder {
case "Integer", "Long" -> "integer";
case "Double", "BigDecimal" -> "number";
case "Boolean" -> "boolean";
case "List" -> "array";
default -> "string";
};
}
private static void appendSchemaProperty(StringBuilder properties, FieldDefinition field) {
properties.append(" ").append(field.name().trim()).append(":\n")
.append(" type: ").append(jsonSchemaType(field.type())).append("\n")
.append(" description: ").append(yamlText(field.description())).append("\n");
if ("Enum".equals(field.type()) && field.enumValues() != null && !field.enumValues().isEmpty()) {
properties.append(" enum: [").append(field.enumValues().stream()
.filter(value -> value != null && !value.isBlank()).map(String::trim)
.collect(java.util.stream.Collectors.joining(", "))).append("]\n");
}
if ("List".equals(field.type())) {
properties.append(" items:\n")
.append(" type: ").append("Object".equals(field.itemType()) ? "object" : jsonSchemaType(field.itemType())).append("\n");
if ("Object".equals(field.itemType()) && field.itemFields() != null && !field.itemFields().isEmpty()) {
properties.append(" properties:\n");
for (FieldDefinition itemField : field.itemFields()) {
properties.append(" ").append(itemField.name()).append(":\n")
.append(" type: ").append(jsonSchemaType(itemField.type())).append("\n")
.append(" description: ").append(yamlText(itemField.description())).append("\n");
}
}
}
}
private static String yamlText(String value) {
String safe = value == null ? "" : value.replace("\\", "\\\\").replace("\"", "\\\"")
.replace("\r", " ").replace("\n", " ");
@@ -1143,36 +1380,110 @@ public class ToolScaffolder {
private static String dtoContent(String packageName, String className, List<FieldDefinition> fields,
String author, String createDate, boolean request) {
String body = fieldLines(fields, request ? Set.of() : Set.of("resultCode", "resultMessage"));
String body = fieldLines(fields, request ? Set.of() : Set.of("resultCode", "resultMessage"), className);
if (!request) {
body = " private String resultCode;\n\n private String resultMessage;\n" + body;
}
String listImport = hasListField(fields) ? "import java.util.List;\n" : "";
return """
package %s;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
%s
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class %s {
%s}
""".formatted(packageName, className, body);
""".formatted(packageName, listImport, className, body);
}
private static String mciIoContent(String packageSuffix, String className, List<FieldDefinition> fields,
String author, String createDate) {
String listImport = hasListField(fields) ? "import java.util.List;\n" : "";
return """
package %s.%s.io;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
%s
@Data
public class %s {
%s}
""".formatted(BASE_PACKAGE, packageSuffix, className, fieldLines(fields));
""".formatted(BASE_PACKAGE, packageSuffix, listImport, className, fieldLines(fields, Set.of(), className));
}
private static boolean hasListField(List<FieldDefinition> fields) {
return fields != null && fields.stream().anyMatch(field -> field != null && "List".equals(field.type()));
}
private static void writeStructuredFieldTypes(Path directory, String packageName, String ownerClass,
List<FieldDefinition> fields) throws IOException {
for (FieldDefinition field : fields == null ? List.<FieldDefinition>of() : fields) {
if (field == null || field.name() == null || field.name().isBlank()) {
continue;
}
if ("Enum".equals(field.type())) {
String enumName = toPascalCase(field.name());
List<String> values = field.enumValues() == null ? List.of() : field.enumValues().stream()
.filter(value -> value != null && !value.isBlank()).map(String::trim).distinct().toList();
if (values.isEmpty()) {
throw new IllegalArgumentException("Enum field needs at least one allowed value: " + field.name());
}
String constants = values.stream().map(value -> " " + enumConstant(value) + "(\"" + javaText(value) + "\")")
.collect(java.util.stream.Collectors.joining(",\n"));
writeUtf8(directory.resolve(enumName + ".java"), """
package %s;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
public enum %s {
%s;
private final String value;
%s(String value) {
this.value = value;
}
@JsonValue
public String getValue() {
return value;
}
@JsonCreator
public static %s fromValue(String value) {
for (%s candidate : values()) {
if (candidate.value.equals(value)) return candidate;
}
throw new IllegalArgumentException("Unsupported value: " + value);
}
}
""".formatted(packageName, enumName, constants, enumName, enumName, enumName));
}
if ("List".equals(field.type()) && "Object".equals(field.itemType())) {
List<FieldDefinition> itemFields = field.itemFields() == null ? List.of() : field.itemFields();
if (itemFields.isEmpty()) {
throw new IllegalArgumentException("Object List field needs item fields: " + field.name());
}
String itemName = listItemClassName(ownerClass, field);
writeUtf8(directory.resolve(itemName + ".java"), dtoContent(packageName, itemName, itemFields, "", "", true));
writeStructuredFieldTypes(directory, packageName, itemName, itemFields);
}
}
}
private static String enumConstant(String value) {
String constant = value.toUpperCase(Locale.ROOT).replaceAll("[^A-Z0-9]+", "_").replaceAll("^_+|_+$", "");
return constant.isBlank() ? "VALUE" : (Character.isDigit(constant.charAt(0)) ? "VALUE_" + constant : constant);
}
private static String listItemClassName(String ownerClass, FieldDefinition field) {
return ownerClass + toPascalCase(field.name()) + "Item";
}
private static String httpUseCaseImplContent(String bizPackage, String baseName, String httpPackage,
@@ -1334,10 +1645,14 @@ public class ToolScaffolder {
baseName, baseName, baseName, baseName, baseName, baseName, baseName);
}
private static String fieldLines(List<FieldDefinition> fields) {
return fieldLines(fields, Set.of());
return fieldLines(fields, Set.of(), "");
}
private static String fieldLines(List<FieldDefinition> fields, Set<String> excludedNames) {
return fieldLines(fields, excludedNames, "");
}
private static String fieldLines(List<FieldDefinition> fields, Set<String> excludedNames, String ownerClass) {
StringBuilder source = new StringBuilder();
Set<String> generatedNames = new LinkedHashSet<>();
for (FieldDefinition field : fields == null ? List.<FieldDefinition>of() : fields) {
@@ -1348,7 +1663,7 @@ public class ToolScaffolder {
if (excludedNames.contains(fieldName) || !generatedNames.add(fieldName)) {
continue;
}
String type = supportedType(field.type());
String type = javaFieldType(field, ownerClass);
String description = field.description() == null ? "" : field.description().replace("\"", "\\\"");
String example = field.example() == null ? "" : field.example().replace("\"", "\\\"");
source.append(" @Schema(description = \"").append(description).append("\", example = \"")
@@ -1360,6 +1675,22 @@ public class ToolScaffolder {
}
return source.toString();
}
private static String javaFieldType(FieldDefinition field, String ownerClass) {
return switch (field.type() == null ? "String" : field.type()) {
case "Enum" -> toPascalCase(field.name());
case "List" -> "List<" + listItemJavaType(field, ownerClass) + ">";
default -> supportedType(field.type());
};
}
private static String listItemJavaType(FieldDefinition field, String ownerClass) {
String itemType = field.itemType() == null ? "" : field.itemType();
if ("Object".equals(itemType)) {
return listItemClassName(ownerClass, field);
}
return supportedType(itemType);
}
private static String supportedType(String type) {
return switch (type == null ? "String" : type) {
case "String", "Integer", "Long", "Double", "Boolean", "BigDecimal" -> type;
@@ -1404,9 +1735,26 @@ public class ToolScaffolder {
}
private static String mockValue(FieldDefinition field) {
if ("List".equals(field.type())) {
if ("Object".equals(field.itemType())) {
StringBuilder object = new StringBuilder("{");
boolean first = true;
for (FieldDefinition itemField : field.itemFields() == null ? List.<FieldDefinition>of() : field.itemFields()) {
if (!first) object.append(", ");
object.append("\"").append(jsonEscape(itemField.name())).append("\" : ").append(mockValue(itemField));
first = false;
}
return "[" + object + "]";
}
FieldDefinition item = new FieldDefinition("item", field.itemType(), "", field.example(), false);
return "[" + mockValue(item) + "]";
}
if (field.example() == null || field.example().isBlank()) {
return "null";
}
if ("Enum".equals(field.type())) {
return "\"" + jsonEscape(field.example()) + "\"";
}
return switch (supportedType(field.type())) {
case "Integer", "Long", "Double", "BigDecimal" -> field.example();
case "Boolean" -> Boolean.parseBoolean(field.example()) ? "true" : "false";