ParsePersonalSkillMarkdown parses raw personal skill Markdown and enforces the personal skill contract. The raw size must not exceed MaxPersonalSkillSizeBytes, frontmatter must contain a valid kebab-case name, the skill name must not exceed MaxPersonalSkillNameBytes, the description must not exceed
(raw []byte)
| 79 | // not exceed MaxPersonalSkillDescriptionBytes, and the body after frontmatter |
| 80 | // must be non-empty. |
| 81 | func ParsePersonalSkillMarkdown(raw []byte) (ParsedSkill, error) { |
| 82 | if len(raw) > MaxPersonalSkillSizeBytes { |
| 83 | return ParsedSkill{}, xerrors.Errorf( |
| 84 | "%w: got %d bytes, maximum is %d bytes", |
| 85 | ErrSkillTooLarge, |
| 86 | len(raw), |
| 87 | MaxPersonalSkillSizeBytes, |
| 88 | ) |
| 89 | } |
| 90 | |
| 91 | name, description, body, err := workspacesdk.ParseSkillFrontmatter(string(raw)) |
| 92 | if err != nil { |
| 93 | if xerrors.Is(err, workspacesdk.ErrFrontmatterNameRequired) { |
| 94 | return ParsedSkill{}, xerrors.Errorf("%w: frontmatter must contain a 'name' field", ErrInvalidSkillName) |
| 95 | } |
| 96 | return ParsedSkill{}, xerrors.Errorf("parse skill frontmatter: %w", err) |
| 97 | } |
| 98 | if !workspacesdk.SkillNamePattern.MatchString(name) { |
| 99 | return ParsedSkill{}, xerrors.Errorf( |
| 100 | "%w: %q must match %s", |
| 101 | ErrInvalidSkillName, |
| 102 | name, |
| 103 | workspacesdk.SkillNameRegex, |
| 104 | ) |
| 105 | } |
| 106 | nameBytes := len(name) |
| 107 | if nameBytes > MaxPersonalSkillNameBytes { |
| 108 | return ParsedSkill{}, xerrors.Errorf( |
| 109 | "%w: %q is %d bytes, maximum is %d bytes", |
| 110 | ErrInvalidSkillName, |
| 111 | name, |
| 112 | nameBytes, |
| 113 | MaxPersonalSkillNameBytes, |
| 114 | ) |
| 115 | } |
| 116 | descriptionBytes := len(description) |
| 117 | if descriptionBytes > MaxPersonalSkillDescriptionBytes { |
| 118 | return ParsedSkill{}, xerrors.Errorf( |
| 119 | "%w: got %d bytes, maximum is %d bytes", |
| 120 | ErrSkillDescriptionTooLarge, |
| 121 | descriptionBytes, |
| 122 | MaxPersonalSkillDescriptionBytes, |
| 123 | ) |
| 124 | } |
| 125 | if strings.TrimSpace(body) == "" { |
| 126 | return ParsedSkill{}, xerrors.Errorf( |
| 127 | "%w: skill %q has no content after frontmatter", |
| 128 | ErrSkillBodyRequired, |
| 129 | name, |
| 130 | ) |
| 131 | } |
| 132 | |
| 133 | return ParsedSkill{ |
| 134 | Skill: Skill{ |
| 135 | Name: name, |
| 136 | Description: description, |
| 137 | Source: SourcePersonal, |
| 138 | }, |