mirror of
https://github.com/iflytek/skillhub.git
synced 2026-10-08 03:07:51 +00:00
fix: relax skill frontmatter parsing
This commit is contained in:
parent
4e71c93e86
commit
43493fa63d
3 changed files with 85 additions and 21 deletions
|
|
@ -3,6 +3,7 @@ package com.iflytek.skillhub.domain.skill.metadata;
|
|||
import com.iflytek.skillhub.domain.shared.exception.DomainBadRequestException;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class SkillMetadataParser {
|
||||
|
|
@ -35,14 +36,7 @@ public class SkillMetadataParser {
|
|||
|
||||
Map<String, Object> frontmatter;
|
||||
try {
|
||||
Yaml yaml = new Yaml();
|
||||
Object parsed = yaml.load(yamlContent);
|
||||
if (!(parsed instanceof Map)) {
|
||||
throw new DomainBadRequestException("error.skill.metadata.yaml.notMap");
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> map = (Map<String, Object>) parsed;
|
||||
frontmatter = map;
|
||||
frontmatter = parseFrontmatter(yamlContent);
|
||||
} catch (DomainBadRequestException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
|
|
@ -56,6 +50,62 @@ public class SkillMetadataParser {
|
|||
return new SkillMetadata(name, description, version, body, frontmatter);
|
||||
}
|
||||
|
||||
private Map<String, Object> parseFrontmatter(String yamlContent) {
|
||||
try {
|
||||
Yaml yaml = new Yaml();
|
||||
Object parsed = yaml.load(yamlContent);
|
||||
if (!(parsed instanceof Map)) {
|
||||
throw new DomainBadRequestException("error.skill.metadata.yaml.notMap");
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> map = (Map<String, Object>) parsed;
|
||||
return map;
|
||||
} catch (DomainBadRequestException exception) {
|
||||
throw exception;
|
||||
} catch (Exception exception) {
|
||||
Map<String, Object> fallback = parseLooseFrontmatter(yamlContent);
|
||||
if (!fallback.isEmpty()) {
|
||||
return fallback;
|
||||
}
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Object> parseLooseFrontmatter(String yamlContent) {
|
||||
Map<String, Object> values = new LinkedHashMap<>();
|
||||
for (String rawLine : yamlContent.split("\\R")) {
|
||||
String line = rawLine.trim();
|
||||
if (line.isEmpty() || line.startsWith("#")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int separatorIndex = line.indexOf(':');
|
||||
if (separatorIndex <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String key = line.substring(0, separatorIndex).trim();
|
||||
String value = line.substring(separatorIndex + 1).trim();
|
||||
if (key.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
values.put(key, stripWrappingQuotes(value));
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
private String stripWrappingQuotes(String value) {
|
||||
if (value.length() >= 2) {
|
||||
boolean wrappedInDoubleQuotes = value.startsWith("\"") && value.endsWith("\"");
|
||||
boolean wrappedInSingleQuotes = value.startsWith("'") && value.endsWith("'");
|
||||
if (wrappedInDoubleQuotes || wrappedInSingleQuotes) {
|
||||
return value.substring(1, value.length() - 1);
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private String extractRequiredField(Map<String, Object> frontmatter, String fieldName) {
|
||||
Object value = frontmatter.get(fieldName);
|
||||
if (value == null) {
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ class SkillMetadataParserTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testThrowsWhenInvalidYaml() {
|
||||
void testFallsBackToLooseFrontmatterParsingWhenYamlSyntaxIsNotStrict() {
|
||||
String content = """
|
||||
---
|
||||
name: test-skill
|
||||
|
|
@ -134,11 +134,29 @@ class SkillMetadataParserTest {
|
|||
Body
|
||||
""";
|
||||
|
||||
DomainBadRequestException exception = assertThrows(
|
||||
DomainBadRequestException.class,
|
||||
() -> parser.parse(content)
|
||||
);
|
||||
assertEquals("error.skill.metadata.yaml.invalid", exception.messageCode());
|
||||
SkillMetadata metadata = parser.parse(content);
|
||||
|
||||
assertEquals("test-skill", metadata.name());
|
||||
assertEquals("[unclosed bracket", metadata.description());
|
||||
assertEquals("1.0.0", metadata.version());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAllowsColonInDescriptionWithoutStrictYamlQuoting() {
|
||||
String content = """
|
||||
---
|
||||
name: clawdbot
|
||||
description: Send messages from Clawdbot via the discord tool: send messages, react, post or edit
|
||||
version: 1.0.0
|
||||
---
|
||||
Body
|
||||
""";
|
||||
|
||||
SkillMetadata metadata = parser.parse(content);
|
||||
|
||||
assertEquals("clawdbot", metadata.name());
|
||||
assertEquals("Send messages from Clawdbot via the discord tool: send messages, react, post or edit", metadata.description());
|
||||
assertEquals("1.0.0", metadata.version());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ class SkillPackageValidatorTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testInvalidYamlFrontmatterUsesFriendlyMessage() {
|
||||
void testInvalidYamlFrontmatterWithColonInValueShouldStillPass() {
|
||||
String skillMdContent = """
|
||||
---
|
||||
name: clawdbot
|
||||
|
|
@ -159,12 +159,8 @@ class SkillPackageValidatorTest {
|
|||
|
||||
ValidationResult result = validator.validate(entries);
|
||||
|
||||
assertFalse(result.passed());
|
||||
assertTrue(result.errors().stream().anyMatch(e ->
|
||||
e.contains("Invalid SKILL.md frontmatter")
|
||||
&& e.contains("line")
|
||||
&& e.contains("column")));
|
||||
assertFalse(result.errors().stream().anyMatch(e -> e.contains("mapping values are not allowed here")));
|
||||
assertTrue(result.passed());
|
||||
assertTrue(result.errors().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue