Enhance ToolScaffolder with Pattern and Examples validation, support AI prompt generation for Pattern
All checks were successful
Deploy to OCIWP / deploy (push) Successful in 6m22s
All checks were successful
Deploy to OCIWP / deploy (push) Successful in 6m22s
This commit is contained in:
@@ -39,7 +39,7 @@ import org.springframework.web.bind.annotation.*;
|
|||||||
public class ScaffoldingController {
|
public class ScaffoldingController {
|
||||||
|
|
||||||
private static final Set<String> SUPPORTED_FIELD_TYPES = Set.of(
|
private static final Set<String> SUPPORTED_FIELD_TYPES = Set.of(
|
||||||
"String", "Integer", "Long", "Double", "Boolean", "BigDecimal", "Enum", "List");
|
"String", "Integer", "Long", "Double", "Boolean", "BigDecimal", "List");
|
||||||
private static final Set<String> SUPPORTED_AI_MODELS = Set.of(
|
private static final Set<String> SUPPORTED_AI_MODELS = Set.of(
|
||||||
"inclusionai/ling-3.0-flash:free",
|
"inclusionai/ling-3.0-flash:free",
|
||||||
"openai/gpt-oss-20b:free",
|
"openai/gpt-oss-20b:free",
|
||||||
@@ -96,7 +96,7 @@ public class ScaffoldingController {
|
|||||||
List<ToolScaffolder.FieldDefinition> inputFields = parseFields(req.get("inputFields"));
|
List<ToolScaffolder.FieldDefinition> inputFields = parseFields(req.get("inputFields"));
|
||||||
List<ToolScaffolder.FieldDefinition> outputFields = parseFields(req.get("outputFields"));
|
List<ToolScaffolder.FieldDefinition> outputFields = parseFields(req.get("outputFields"));
|
||||||
if (inputFields.isEmpty()) {
|
if (inputFields.isEmpty()) {
|
||||||
inputFields = List.of(new ToolScaffolder.FieldDefinition("query", "String", "Search query", "example", false));
|
inputFields = List.of(new ToolScaffolder.FieldDefinition("query", "String", "Search query", List.of("example"), "", false));
|
||||||
}
|
}
|
||||||
ToolScaffolder.ToolDefinitionOptions definitionOptions = new ToolScaffolder.ToolDefinitionOptions(
|
ToolScaffolder.ToolDefinitionOptions definitionOptions = new ToolScaffolder.ToolDefinitionOptions(
|
||||||
req.get("functionDescription"),
|
req.get("functionDescription"),
|
||||||
@@ -187,9 +187,10 @@ public class ScaffoldingController {
|
|||||||
Generate Java DTO fields for an MCP tool.
|
Generate Java DTO fields for an MCP tool.
|
||||||
Return JSON only. Do not add Markdown, explanations, or code fences.
|
Return JSON only. Do not add Markdown, explanations, or code fences.
|
||||||
The response must have this exact shape:
|
The response must have this exact shape:
|
||||||
{"fields":[{"name":"camelCaseName","type":"String","description":"short description","example":"example","required":true,"enumValues":[],"itemType":null,"itemFields":[]}]}
|
{"fields":[{"name":"camelCaseName","type":"String","description":"short description","examples":["example1","example2"],"pattern":"^regex$","required":true,"enumValues":[],"itemType":null,"itemFields":[]}]}
|
||||||
Allowed type values: String, Integer, Long, Double, Boolean, BigDecimal, Enum, List.
|
Allowed type values: String, Integer, Long, Double, Boolean, BigDecimal, List.
|
||||||
Enum fields must include enumValues. List fields must include itemType; use Object plus itemFields for object lists.
|
Finite values should be enforced by populating enumValues on standard types. List fields must include itemType; use Object plus itemFields for object lists.
|
||||||
|
If applicable, provide a regex for pattern.
|
||||||
Generate fields only for the requested target: %s.
|
Generate fields only for the requested target: %s.
|
||||||
For OUTPUT fields, include resultCode and resultMessage when appropriate.
|
For OUTPUT fields, include resultCode and resultMessage when appropriate.
|
||||||
Keep field names valid Java camelCase identifiers. Generate at most 10 fields.
|
Keep field names valid Java camelCase identifiers. Generate at most 10 fields.
|
||||||
@@ -217,12 +218,13 @@ public class ScaffoldingController {
|
|||||||
Generate an MCP Tool scaffold from the user request.
|
Generate an MCP Tool scaffold from the user request.
|
||||||
Return JSON only. Do not add Markdown, explanations, or code fences.
|
Return JSON only. Do not add Markdown, explanations, or code fences.
|
||||||
The response must have this exact shape:
|
The response must have this exact shape:
|
||||||
{"baseName":"PascalCaseName","title":"short Korean title","description":"clear Korean LLM tool guidance","categoryKey":"cmm","routingType":"HTTP","httpApiName":"simple-api-name","functionDescription":"core business function","displayDescription":"short portal description","whenToUse":"specific user requests that should select this tool","whenNotToUse":"requests or conditions that must not select this tool","ioLimits":"allowed input and output scope and limits","exampleQueries":["query 1","query 2","query 3"],"tags":["domain","action"],"ownerOrg":"MCP_TOOL","inputFields":[{"name":"camelCaseName","type":"String","description":"short description","example":"example","required":true,"enumValues":[],"itemType":null,"itemFields":[]}],"outputFields":[{"name":"resultCode","type":"String","description":"result code","example":"SUCCESS","required":true,"enumValues":[],"itemType":null,"itemFields":[]}]}
|
{"baseName":"PascalCaseName","title":"short Korean title","description":"clear Korean LLM tool guidance","categoryKey":"cmm","routingType":"HTTP","httpApiName":"simple-api-name","functionDescription":"core business function","displayDescription":"short portal description","whenToUse":"specific user requests that should select this tool","whenNotToUse":"requests or conditions that must not select this tool","ioLimits":"allowed input and output scope and limits","exampleQueries":["query 1","query 2","query 3"],"tags":["domain","action"],"ownerOrg":"MCP_TOOL","inputFields":[{"name":"camelCaseName","type":"String","description":"short description","examples":["example1","example2"],"pattern":"^regex$","required":true,"enumValues":[],"itemType":null,"itemFields":[]}],"outputFields":[{"name":"resultCode","type":"String","description":"result code","examples":["SUCCESS"],"pattern":"","required":true,"enumValues":[],"itemType":null,"itemFields":[]}]}
|
||||||
categoryKey must be exactly three lowercase letters or digits.
|
categoryKey must be exactly three lowercase letters or digits.
|
||||||
routingType must be either HTTP or MCI. httpApiName can contain only letters, digits, hyphens, and underscores.
|
routingType must be either HTTP or MCI. httpApiName can contain only letters, digits, hyphens, and underscores.
|
||||||
Write every V17 metadata field for its distinct purpose; do not copy the same sentence into all fields.
|
Write every V17 metadata field for its distinct purpose; do not copy the same sentence into all fields.
|
||||||
Generate 3 to 10 realistic exampleQueries and concise search tags. Use MCP_TOOL for ownerOrg unless the user names an owner.
|
Generate 3 to 10 realistic exampleQueries and concise search tags. Use MCP_TOOL for ownerOrg unless the user names an owner.
|
||||||
Allowed field type values: String, Integer, Long, Double, Boolean, BigDecimal, Enum, List. Enum must include enumValues; List must include itemType and object lists include itemFields.
|
Allowed field type values: String, Integer, Long, Double, Boolean, BigDecimal, List. Finite values should be enforced by populating enumValues. List must include itemType and object lists include itemFields.
|
||||||
|
If applicable, provide a regex for pattern.
|
||||||
Keep all field names valid Java camelCase identifiers. Generate at most 10 fields per list.
|
Keep all field names valid Java camelCase identifiers. Generate at most 10 fields per list.
|
||||||
Do not generate interfaceId or clientSystemCode; those must come from a real integration contract.
|
Do not generate interfaceId or clientSystemCode; those must come from a real integration contract.
|
||||||
User request: %s
|
User request: %s
|
||||||
@@ -302,7 +304,8 @@ public class ScaffoldingController {
|
|||||||
field.name().trim(),
|
field.name().trim(),
|
||||||
field.type() == null ? "String" : field.type().trim(),
|
field.type() == null ? "String" : field.type().trim(),
|
||||||
field.description() == null ? "" : field.description().trim(),
|
field.description() == null ? "" : field.description().trim(),
|
||||||
field.example() == null ? "" : field.example().trim(),
|
field.examples() == null ? List.of() : field.examples().stream().map(String::trim).toList(),
|
||||||
|
field.pattern() == null ? "" : field.pattern().trim(),
|
||||||
field.required(),
|
field.required(),
|
||||||
field.enumValues() == null ? List.of() : field.enumValues(),
|
field.enumValues() == null ? List.of() : field.enumValues(),
|
||||||
field.itemType(),
|
field.itemType(),
|
||||||
@@ -323,9 +326,6 @@ public class ScaffoldingController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void validateStructuredField(ToolScaffolder.FieldDefinition field) {
|
private void validateStructuredField(ToolScaffolder.FieldDefinition field) {
|
||||||
if ("Enum".equals(field.type()) && field.enumValues().isEmpty()) {
|
|
||||||
throw new IllegalArgumentException("Enum field needs enumValues: " + field.name());
|
|
||||||
}
|
|
||||||
if ("List".equals(field.type()) && (field.itemType() == null || field.itemType().isBlank())) {
|
if ("List".equals(field.type()) && (field.itemType() == null || field.itemType().isBlank())) {
|
||||||
throw new IllegalArgumentException("List field needs itemType: " + field.name());
|
throw new IllegalArgumentException("List field needs itemType: " + field.name());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1181,7 +1181,8 @@
|
|||||||
<th style="min-width: 170px;">Enum values / List item type</th>
|
<th style="min-width: 170px;">Enum values / List item type</th>
|
||||||
<th style="min-width: 260px;">Object list item fields (JSON)</th>
|
<th style="min-width: 260px;">Object list item fields (JSON)</th>
|
||||||
<th style="min-width: 210px;">Description</th>
|
<th style="min-width: 210px;">Description</th>
|
||||||
<th style="min-width: 180px;">Example</th>
|
<th style="min-width: 150px;">Pattern (Regex)</th>
|
||||||
|
<th style="min-width: 180px;">Examples (Enter 단위 구분)</th>
|
||||||
<th style="min-width: 125px;">Required</th>
|
<th style="min-width: 125px;">Required</th>
|
||||||
<th style="width: 1%;">삭제</th>
|
<th style="width: 1%;">삭제</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -1205,7 +1206,7 @@
|
|||||||
|
|
||||||
<!-- Sub Field Editor Modal -->
|
<!-- Sub Field Editor Modal -->
|
||||||
<div class="modal fade" id="subFieldEditorModal" tabindex="-1" aria-hidden="true" style="z-index: 1060;">
|
<div class="modal fade" id="subFieldEditorModal" tabindex="-1" aria-hidden="true" style="z-index: 1060;">
|
||||||
<div class="modal-dialog modal-lg modal-dialog-scrollable">
|
<div class="modal-dialog modal-xl modal-dialog-scrollable">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h5 class="modal-title">하위 필드(itemFields) 편집</h5>
|
<h5 class="modal-title">하위 필드(itemFields) 편집</h5>
|
||||||
@@ -1218,9 +1219,10 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th style="width:15%">Name</th>
|
<th style="width:15%">Name</th>
|
||||||
<th style="width:15%">Type</th>
|
<th style="width:15%">Type</th>
|
||||||
<th style="width:20%">Details</th>
|
<th style="width:15%">Details</th>
|
||||||
<th style="width:20%">Description</th>
|
<th style="width:15%">Description</th>
|
||||||
<th style="width:15%">Example</th>
|
<th style="width:10%">Pattern</th>
|
||||||
|
<th style="width:15%">Examples</th>
|
||||||
<th style="width:10%">Required</th>
|
<th style="width:10%">Required</th>
|
||||||
<th style="width:5%"></th>
|
<th style="width:5%"></th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -1728,7 +1730,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const fieldTypes = ['String', 'Integer', 'Long', 'Double', 'Boolean', 'BigDecimal', 'Enum', 'List'];
|
const fieldTypes = ['String', 'Integer', 'Long', 'Double', 'Boolean', 'BigDecimal', 'List'];
|
||||||
const typeExamples = {
|
const typeExamples = {
|
||||||
String: 'example',
|
String: 'example',
|
||||||
Integer: '1',
|
Integer: '1',
|
||||||
@@ -1736,7 +1738,6 @@
|
|||||||
Double: '1.0',
|
Double: '1.0',
|
||||||
Boolean: 'true',
|
Boolean: 'true',
|
||||||
BigDecimal: '1000.00',
|
BigDecimal: '1000.00',
|
||||||
Enum: 'OPEN',
|
|
||||||
List: 'C001'
|
List: 'C001'
|
||||||
};
|
};
|
||||||
const fieldTemplates = {
|
const fieldTemplates = {
|
||||||
@@ -1805,24 +1806,27 @@
|
|||||||
};
|
};
|
||||||
const n = makeInput(field.name, 'name'); n.dataset.field = 'name';
|
const n = makeInput(field.name, 'name'); n.dataset.field = 'name';
|
||||||
const d = makeInput(field.description, 'desc'); d.dataset.field = 'description';
|
const d = makeInput(field.description, 'desc'); d.dataset.field = 'description';
|
||||||
const e = makeInput(field.example, 'ex'); e.dataset.field = 'example';
|
const p = makeInput(field.pattern, 'pattern'); p.dataset.field = 'pattern';
|
||||||
|
const e = document.createElement('textarea');
|
||||||
|
e.className = 'form-control form-control-sm';
|
||||||
|
e.rows = 2; e.dataset.field = 'example';
|
||||||
|
e.value = (field.examples || []).join('\n') || '';
|
||||||
const t = document.createElement('select');
|
const t = document.createElement('select');
|
||||||
t.className = 'form-select form-select-sm'; t.dataset.field = 'type';
|
t.className = 'form-select form-select-sm'; t.dataset.field = 'type';
|
||||||
['String', 'Integer', 'Long', 'Double', 'Boolean', 'BigDecimal', 'Enum'].forEach(type => {
|
['String', 'Integer', 'Long', 'Double', 'Boolean', 'BigDecimal'].forEach(type => {
|
||||||
t.add(new Option(type, type, false, (field.type || 'String') === type));
|
t.add(new Option(type, type, false, (field.type || 'String') === type));
|
||||||
});
|
});
|
||||||
const det = makeInput(field.type === 'Enum' ? (field.enumValues||[]).join(',') : '', 'Enum csv');
|
const det = makeInput(field.type !== 'List' ? (field.enumValues||[]).join(',') : '', 'Enum csv (콤마 구분)');
|
||||||
det.dataset.field = 'details';
|
det.dataset.field = 'details';
|
||||||
t.addEventListener('change', () => {
|
t.addEventListener('change', () => {
|
||||||
if (t.value === 'Enum') det.placeholder = 'Enum csv';
|
det.placeholder = 'Enum csv (콤마 구분)';
|
||||||
else det.value = '';
|
|
||||||
});
|
});
|
||||||
const req = document.createElement('select');
|
const req = document.createElement('select');
|
||||||
req.className = 'form-select form-select-sm'; req.dataset.field = 'required';
|
req.className = 'form-select form-select-sm'; req.dataset.field = 'required';
|
||||||
req.add(new Option('Required', 'true', false, field.required === true || field.required === 'true'));
|
req.add(new Option('Required', 'true', false, field.required === true || field.required === 'true'));
|
||||||
req.add(new Option('Optional', 'false', false, !(field.required === true || field.required === 'true')));
|
req.add(new Option('Optional', 'false', false, !(field.required === true || field.required === 'true')));
|
||||||
|
|
||||||
[n, t, det, d, e, req].forEach(ctrl => {
|
[n, t, det, d, p, e, req].forEach(ctrl => {
|
||||||
const td = document.createElement('td'); td.appendChild(ctrl); row.appendChild(td);
|
const td = document.createElement('td'); td.appendChild(ctrl); row.appendChild(td);
|
||||||
});
|
});
|
||||||
const del = document.createElement('button');
|
const del = document.createElement('button');
|
||||||
@@ -1841,9 +1845,10 @@
|
|||||||
name: row.querySelector('[data-field="name"]').value.trim(),
|
name: row.querySelector('[data-field="name"]').value.trim(),
|
||||||
type,
|
type,
|
||||||
description: row.querySelector('[data-field="description"]').value.trim(),
|
description: row.querySelector('[data-field="description"]').value.trim(),
|
||||||
example: row.querySelector('[data-field="example"]').value.trim(),
|
pattern: row.querySelector('[data-field="pattern"]').value.trim(),
|
||||||
|
examples: row.querySelector('[data-field="example"]').value.split('\n').map(s=>s.trim()).filter(Boolean),
|
||||||
required: row.querySelector('[data-field="required"]').value === 'true',
|
required: row.querySelector('[data-field="required"]').value === 'true',
|
||||||
enumValues: type === 'Enum' ? details.split(',').map(s=>s.trim()).filter(Boolean) : [],
|
enumValues: type !== 'List' ? details.split(',').map(s=>s.trim()).filter(Boolean) : [],
|
||||||
itemType: null, itemFields: []
|
itemType: null, itemFields: []
|
||||||
};
|
};
|
||||||
}).filter(f => f.name);
|
}).filter(f => f.name);
|
||||||
@@ -1898,8 +1903,16 @@
|
|||||||
nameInput.dataset.field = 'name';
|
nameInput.dataset.field = 'name';
|
||||||
const descriptionInput = makeInput(field.description, 'e.g. Employee ID');
|
const descriptionInput = makeInput(field.description, 'e.g. Employee ID');
|
||||||
descriptionInput.dataset.field = 'description';
|
descriptionInput.dataset.field = 'description';
|
||||||
const exampleInput = makeInput(field.example, 'e.g. EMP10001');
|
|
||||||
|
const patternInput = makeInput(field.pattern, 'e.g. ^[0-9]{4}$');
|
||||||
|
patternInput.dataset.field = 'pattern';
|
||||||
|
|
||||||
|
const exampleInput = document.createElement('textarea');
|
||||||
|
exampleInput.className = 'form-control form-control-sm';
|
||||||
|
exampleInput.placeholder = 'e.g. EMP10001\nEMP10002';
|
||||||
|
exampleInput.rows = 2;
|
||||||
exampleInput.dataset.field = 'example';
|
exampleInput.dataset.field = 'example';
|
||||||
|
exampleInput.value = (field.examples || []).join('\n') || '';
|
||||||
|
|
||||||
const typeSelect = document.createElement('select');
|
const typeSelect = document.createElement('select');
|
||||||
typeSelect.className = 'form-select form-select-sm';
|
typeSelect.className = 'form-select form-select-sm';
|
||||||
@@ -1917,13 +1930,13 @@
|
|||||||
input.add(new Option(itemType, itemType, false, (value || 'String') === itemType));
|
input.add(new Option(itemType, itemType, false, (value || 'String') === itemType));
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
input = makeInput(type === 'Enum' ? value : '', 'Enum: OPEN, CLOSED');
|
input = makeInput(value, 'Enum csv (콤마 구분)');
|
||||||
}
|
}
|
||||||
input.dataset.field = 'details';
|
input.dataset.field = 'details';
|
||||||
return input;
|
return input;
|
||||||
};
|
};
|
||||||
let detailsInput = createDetailsInput(field.type || 'String',
|
let detailsInput = createDetailsInput(field.type || 'String',
|
||||||
field.type === 'Enum' ? (field.enumValues || []).join(', ') : field.itemType);
|
field.type !== 'List' ? (field.enumValues || []).join(', ') : field.itemType);
|
||||||
|
|
||||||
const requiredSelect = document.createElement('select');
|
const requiredSelect = document.createElement('select');
|
||||||
requiredSelect.className = 'form-select form-select-sm';
|
requiredSelect.className = 'form-select form-select-sm';
|
||||||
@@ -1978,12 +1991,12 @@
|
|||||||
});
|
});
|
||||||
bindDetailsInput();
|
bindDetailsInput();
|
||||||
updateObjectListFieldsState();
|
updateObjectListFieldsState();
|
||||||
[nameInput, descriptionInput, exampleInput, requiredSelect, itemFieldsInput].forEach(control => {
|
[nameInput, descriptionInput, patternInput, exampleInput, requiredSelect, itemFieldsInput].forEach(control => {
|
||||||
control.addEventListener('input', updateFieldEditorPreview);
|
control.addEventListener('input', updateFieldEditorPreview);
|
||||||
control.addEventListener('change', updateFieldEditorPreview);
|
control.addEventListener('change', updateFieldEditorPreview);
|
||||||
});
|
});
|
||||||
|
|
||||||
[nameInput, typeSelect, detailsInput, itemFieldsContainer, descriptionInput, exampleInput, requiredSelect].forEach(control => {
|
[nameInput, typeSelect, detailsInput, itemFieldsContainer, descriptionInput, patternInput, exampleInput, requiredSelect].forEach(control => {
|
||||||
const cell = document.createElement('td');
|
const cell = document.createElement('td');
|
||||||
cell.appendChild(control);
|
cell.appendChild(control);
|
||||||
row.appendChild(cell);
|
row.appendChild(cell);
|
||||||
@@ -2021,9 +2034,10 @@
|
|||||||
return {
|
return {
|
||||||
name: row.querySelector('[data-field="name"]').value.trim(), type,
|
name: row.querySelector('[data-field="name"]').value.trim(), type,
|
||||||
description: row.querySelector('[data-field="description"]').value.trim(),
|
description: row.querySelector('[data-field="description"]').value.trim(),
|
||||||
example: row.querySelector('[data-field="example"]').value.trim(),
|
pattern: row.querySelector('[data-field="pattern"]').value.trim(),
|
||||||
|
examples: row.querySelector('[data-field="example"]').value.split('\n').map(e => e.trim()).filter(Boolean),
|
||||||
required: row.querySelector('[data-field="required"]').value === 'true',
|
required: row.querySelector('[data-field="required"]').value === 'true',
|
||||||
enumValues: type === 'Enum' ? details.split(',').map(value => value.trim()).filter(Boolean) : [],
|
enumValues: type !== 'List' ? details.split(',').map(value => value.trim()).filter(Boolean) : [],
|
||||||
itemType: type === 'List' ? (details || 'String') : null,
|
itemType: type === 'List' ? (details || 'String') : null,
|
||||||
itemFields
|
itemFields
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -45,10 +45,10 @@ public class ToolScaffolder {
|
|||||||
private static final String BASE_PACKAGE = "io.shinhanlife.dap.mcc";
|
private static final String BASE_PACKAGE = "io.shinhanlife.dap.mcc";
|
||||||
private static final String BASE_PACKAGE_PATH = "src/main/java/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, List<String> examples, String pattern, boolean required,
|
||||||
List<String> enumValues, String itemType, List<FieldDefinition> itemFields) {
|
List<String> enumValues, String itemType, List<FieldDefinition> itemFields) {
|
||||||
public FieldDefinition(String name, String type, String description, String example, boolean required) {
|
public FieldDefinition(String name, String type, String description, List<String> examples, String pattern, boolean required) {
|
||||||
this(name, type, description, example, required, List.of(), null, List.of());
|
this(name, type, description, examples, pattern, required, List.of(), null, List.of());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -457,7 +457,7 @@ public class ToolScaffolder {
|
|||||||
String inputSchemaResource = useSchemaResource ? schemaResourceDirectory + toKebabCase(baseName) + "-resource-input-schema.json" : null;
|
String inputSchemaResource = useSchemaResource ? schemaResourceDirectory + toKebabCase(baseName) + "-resource-input-schema.json" : null;
|
||||||
String outputSchemaResource = useSchemaResource ? schemaResourceDirectory + toKebabCase(baseName) + "-resource-output-schema.json" : null;
|
String outputSchemaResource = useSchemaResource ? schemaResourceDirectory + toKebabCase(baseName) + "-resource-output-schema.json" : null;
|
||||||
|
|
||||||
String result = scaffold(baseName, interfaceId, title, description, group, routingType, moduleName, author, createDate, false, null, inputSchemaResource, outputSchemaResource, List.of(new FieldDefinition("query", "String", "Search query", "example", false)), List.of());
|
String result = scaffold(baseName, interfaceId, title, description, group, routingType, moduleName, author, createDate, false, null, inputSchemaResource, outputSchemaResource, List.of(new FieldDefinition("query", "String", "Search query", List.of("example"), "", false)), List.of());
|
||||||
System.out.println(result);
|
System.out.println(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -476,7 +476,7 @@ public class ToolScaffolder {
|
|||||||
public static String scaffold(String baseName, String interfaceId, String description, String group, String routingType, String moduleName, String author, String createDate, boolean register, String clientSystemCode, String inputSchemaResource, String outputSchemaResource) throws IOException {
|
public static String scaffold(String baseName, String interfaceId, String description, String group, String routingType, String moduleName, String author, String createDate, boolean register, String clientSystemCode, String inputSchemaResource, String outputSchemaResource) throws IOException {
|
||||||
return scaffold(baseName, interfaceId, description, group, routingType, moduleName, author, createDate,
|
return scaffold(baseName, interfaceId, description, group, routingType, moduleName, author, createDate,
|
||||||
register, clientSystemCode, inputSchemaResource, outputSchemaResource,
|
register, clientSystemCode, inputSchemaResource, outputSchemaResource,
|
||||||
List.of(new FieldDefinition("query", "String", "Search query", "example", false)), List.of());
|
List.of(new FieldDefinition("query", "String", "Search query", List.of("example"), "", false)), List.of());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String scaffold(String baseName, String interfaceId, String description, String group, String routingType, String moduleName, String author, String createDate, boolean register, String clientSystemCode, String inputSchemaResource, String outputSchemaResource, List<FieldDefinition> inputFields, List<FieldDefinition> outputFields) throws IOException {
|
public static String scaffold(String baseName, String interfaceId, String description, String group, String routingType, String moduleName, String author, String createDate, boolean register, String clientSystemCode, String inputSchemaResource, String outputSchemaResource, List<FieldDefinition> inputFields, List<FieldDefinition> outputFields) throws IOException {
|
||||||
@@ -1441,7 +1441,7 @@ public class ToolScaffolder {
|
|||||||
private static void appendSchemaProperty(StringBuilder properties, FieldDefinition field) {
|
private static void appendSchemaProperty(StringBuilder properties, FieldDefinition field) {
|
||||||
properties.append(" ").append(field.name().trim()).append(":\n")
|
properties.append(" ").append(field.name().trim()).append(":\n")
|
||||||
.append(" type: ").append(jsonSchemaType(field.type())).append("\n")
|
.append(" type: ").append(jsonSchemaType(field.type())).append("\n")
|
||||||
.append(" description: ").append(yamlText(field.description())).append("\n");
|
.append(" description: ").append(yamlText(richDescription(field))).append("\n");
|
||||||
if ("Enum".equals(field.type()) && field.enumValues() != null && !field.enumValues().isEmpty()) {
|
if ("Enum".equals(field.type()) && field.enumValues() != null && !field.enumValues().isEmpty()) {
|
||||||
properties.append(" enum: [").append(field.enumValues().stream()
|
properties.append(" enum: [").append(field.enumValues().stream()
|
||||||
.filter(value -> value != null && !value.isBlank()).map(String::trim)
|
.filter(value -> value != null && !value.isBlank()).map(String::trim)
|
||||||
@@ -1455,7 +1455,7 @@ public class ToolScaffolder {
|
|||||||
for (FieldDefinition itemField : field.itemFields()) {
|
for (FieldDefinition itemField : field.itemFields()) {
|
||||||
properties.append(" ").append(itemField.name()).append(":\n")
|
properties.append(" ").append(itemField.name()).append(":\n")
|
||||||
.append(" type: ").append(jsonSchemaType(itemField.type())).append("\n")
|
.append(" type: ").append(jsonSchemaType(itemField.type())).append("\n")
|
||||||
.append(" description: ").append(yamlText(itemField.description())).append("\n");
|
.append(" description: ").append(yamlText(richDescription(itemField))).append("\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1535,35 +1535,37 @@ public class ToolScaffolder {
|
|||||||
body = " private String resultCode;\n\n private String resultMessage;\n" + body;
|
body = " private String resultCode;\n\n private String resultMessage;\n" + body;
|
||||||
}
|
}
|
||||||
String listImport = hasListField(fields) ? "import java.util.List;\n" : "";
|
String listImport = hasListField(fields) ? "import java.util.List;\n" : "";
|
||||||
|
String patternImport = hasPatternField(fields) ? "import jakarta.validation.constraints.Pattern;\n" : "";
|
||||||
return """
|
return """
|
||||||
package %s;
|
package %s;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
%s
|
%s%s
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
public class %s {
|
public class %s {
|
||||||
%s%s}
|
%s%s}
|
||||||
""".formatted(packageName, listImport, className, body, innerObjectListClasses(fields));
|
""".formatted(packageName, listImport, patternImport, className, body, innerObjectListClasses(fields));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String mciIoContent(String packageSuffix, String className, List<FieldDefinition> fields,
|
private static String mciIoContent(String packageSuffix, String className, List<FieldDefinition> fields,
|
||||||
String author, String createDate) {
|
String author, String createDate) {
|
||||||
String listImport = hasListField(fields) ? "import java.util.List;\n" : "";
|
String listImport = hasListField(fields) ? "import java.util.List;\n" : "";
|
||||||
|
String patternImport = hasPatternField(fields) ? "import jakarta.validation.constraints.Pattern;\n" : "";
|
||||||
return """
|
return """
|
||||||
package %s.%s.io;
|
package %s.%s.io;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
%s
|
%s%s
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public class %s {
|
public class %s {
|
||||||
%s%s}
|
%s%s}
|
||||||
""".formatted(BASE_PACKAGE, packageSuffix, listImport, className, fieldLines(fields, Set.of(), className),
|
""".formatted(BASE_PACKAGE, packageSuffix, listImport, patternImport, className, fieldLines(fields, Set.of(), className),
|
||||||
innerObjectListClasses(fields));
|
innerObjectListClasses(fields));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1571,6 +1573,18 @@ public class ToolScaffolder {
|
|||||||
return fields != null && fields.stream().anyMatch(field -> field != null && "List".equals(field.type()));
|
return fields != null && fields.stream().anyMatch(field -> field != null && "List".equals(field.type()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean hasPatternField(List<FieldDefinition> fields) {
|
||||||
|
if (fields == null) return false;
|
||||||
|
return fields.stream().anyMatch(field -> {
|
||||||
|
if (field == null) return false;
|
||||||
|
if (field.pattern() != null && !field.pattern().isBlank()) return true;
|
||||||
|
if ("List".equals(field.type()) && "Object".equals(field.itemType())) {
|
||||||
|
return hasPatternField(field.itemFields());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private static void writeStructuredFieldTypes(Path directory, String packageName, String ownerClass,
|
private static void writeStructuredFieldTypes(Path directory, String packageName, String ownerClass,
|
||||||
List<FieldDefinition> fields) throws IOException {
|
List<FieldDefinition> fields) throws IOException {
|
||||||
for (FieldDefinition field : fields == null ? List.<FieldDefinition>of() : fields) {
|
for (FieldDefinition field : fields == null ? List.<FieldDefinition>of() : fields) {
|
||||||
@@ -1832,18 +1846,38 @@ public class ToolScaffolder {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
String type = javaFieldType(field, ownerClass);
|
String type = javaFieldType(field, ownerClass);
|
||||||
String description = field.description() == null ? "" : field.description().replace("\"", "\\\"");
|
String description = richDescription(field).replace("\"", "\\\"");
|
||||||
String example = field.example() == null ? "" : field.example().replace("\"", "\\\"");
|
String example = "";
|
||||||
source.append(" @Schema(description = \"").append(description).append("\", example = \"")
|
if (field.examples() != null && !field.examples().isEmpty()) {
|
||||||
.append(example).append("\"");
|
example = field.examples().get(0).replace("\"", "\\\"");
|
||||||
|
}
|
||||||
|
source.append(" @Schema(description = \"").append(description).append("\"");
|
||||||
|
if (!example.isEmpty()) {
|
||||||
|
source.append(", example = \"").append(example).append("\"");
|
||||||
|
}
|
||||||
if (field.required()) {
|
if (field.required()) {
|
||||||
source.append(", requiredMode = Schema.RequiredMode.REQUIRED");
|
source.append(", requiredMode = Schema.RequiredMode.REQUIRED");
|
||||||
}
|
}
|
||||||
source.append(")\n private ").append(type).append(' ').append(fieldName).append(";\n\n");
|
source.append(")\n");
|
||||||
|
if (field.pattern() != null && !field.pattern().isBlank()) {
|
||||||
|
source.append(" @Pattern(regexp = \"").append(field.pattern().replace("\"", "\\\"")).append("\")\n");
|
||||||
|
}
|
||||||
|
source.append(" private ").append(type).append(' ').append(fieldName).append(";\n\n");
|
||||||
}
|
}
|
||||||
return source.toString();
|
return source.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String richDescription(FieldDefinition field) {
|
||||||
|
String desc = field.description() == null ? "" : field.description().trim();
|
||||||
|
if (field.pattern() != null && !field.pattern().isBlank()) {
|
||||||
|
desc += " (형식: " + field.pattern() + ")";
|
||||||
|
}
|
||||||
|
if (field.examples() != null && !field.examples().isEmpty()) {
|
||||||
|
desc += " (예시: " + String.join(", ", field.examples()) + ")";
|
||||||
|
}
|
||||||
|
return desc.trim();
|
||||||
|
}
|
||||||
|
|
||||||
private static String javaFieldType(FieldDefinition field, String ownerClass) {
|
private static String javaFieldType(FieldDefinition field, String ownerClass) {
|
||||||
return switch (field.type() == null ? "String" : field.type()) {
|
return switch (field.type() == null ? "String" : field.type()) {
|
||||||
case "Enum" -> toPascalCase(field.name());
|
case "Enum" -> toPascalCase(field.name());
|
||||||
@@ -1914,19 +1948,21 @@ public class ToolScaffolder {
|
|||||||
}
|
}
|
||||||
return "[" + object + "]";
|
return "[" + object + "]";
|
||||||
}
|
}
|
||||||
FieldDefinition item = new FieldDefinition("item", field.itemType(), "", field.example(), false);
|
FieldDefinition item = new FieldDefinition("item", field.itemType(), "", field.examples(), field.pattern(), false);
|
||||||
return "[" + mockValue(item) + "]";
|
return "[" + mockValue(item) + "]";
|
||||||
}
|
}
|
||||||
if (field.example() == null || field.example().isBlank()) {
|
|
||||||
|
String exampleStr = (field.examples() != null && !field.examples().isEmpty()) ? field.examples().get(0) : null;
|
||||||
|
if (exampleStr == null || exampleStr.isBlank()) {
|
||||||
return "null";
|
return "null";
|
||||||
}
|
}
|
||||||
if ("Enum".equals(field.type())) {
|
if ("Enum".equals(field.type())) {
|
||||||
return "\"" + jsonEscape(field.example()) + "\"";
|
return "\"" + jsonEscape(exampleStr) + "\"";
|
||||||
}
|
}
|
||||||
return switch (supportedType(field.type())) {
|
return switch (supportedType(field.type())) {
|
||||||
case "Integer", "Long", "Double", "BigDecimal" -> field.example();
|
case "Integer", "Long", "Double", "BigDecimal" -> exampleStr;
|
||||||
case "Boolean" -> Boolean.parseBoolean(field.example()) ? "true" : "false";
|
case "Boolean" -> Boolean.parseBoolean(exampleStr) ? "true" : "false";
|
||||||
default -> "\"" + jsonEscape(field.example()) + "\"";
|
default -> "\"" + jsonEscape(exampleStr) + "\"";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user