Files
GSP/toolkit/internal/gsp/message.go

100 lines
3.5 KiB
Go

package gsp
import (
"fmt"
"os"
"path/filepath"
"gopkg.in/yaml.v3"
)
const GSPMessageVersion = "0.1"
var allowedMessageIntents = map[string]bool{
"design": true,
"implement": true,
"review": true,
"test": true,
"acceptance": true,
"handoff": true,
"inspect": true,
}
type Message struct {
MessageVersion string `json:"gspMessageVersion,omitempty" yaml:"gspMessageVersion"`
ID string `json:"id,omitempty" yaml:"id"`
From string `json:"from,omitempty" yaml:"from"`
To string `json:"to,omitempty" yaml:"to"`
Intent string `json:"intent,omitempty" yaml:"intent"`
Entry string `json:"entry,omitempty" yaml:"entry"`
Stage string `json:"stage,omitempty" yaml:"stage"`
Requires []string `json:"requires,omitempty" yaml:"requires"`
ContextPack ContextPack `json:"contextPack,omitempty" yaml:"contextPack"`
File string `json:"file,omitempty" yaml:"-"`
}
type ContextPack struct {
Mode string `json:"mode,omitempty" yaml:"mode"`
Depth int `json:"depth,omitempty" yaml:"depth"`
Budget int `json:"budget,omitempty" yaml:"budget"`
}
func ReadMessage(root, file string) (Message, error) {
path := file
if !filepath.IsAbs(path) {
path = filepath.Join(root, file)
}
data, err := os.ReadFile(path)
if err != nil {
return Message{}, err
}
var message Message
if err := yaml.Unmarshal(data, &message); err != nil {
return Message{}, err
}
message.File = relPath(root, path)
return message, nil
}
func (p *Project) ValidateMessage(message Message) Report {
report := Report{OK: true}
if message.MessageVersion == "" {
report.addError("missing_message_version", message.ID, message.File, "message requires gspMessageVersion")
} else if message.MessageVersion != GSPMessageVersion {
report.addError("unsupported_message_version", message.ID, message.File, fmt.Sprintf("GSP message version %q is not supported", message.MessageVersion))
}
if message.ID == "" {
report.addError("missing_id", "", message.File, "message requires id")
}
if message.From == "" {
report.addError("missing_from", message.ID, message.File, "message requires from")
}
if message.To == "" {
report.addError("missing_to", message.ID, message.File, "message requires to")
}
if message.Intent == "" {
report.addError("missing_intent", message.ID, message.File, "message requires intent")
} else if !allowedMessageIntents[message.Intent] {
report.addError("invalid_intent", message.ID, message.File, fmt.Sprintf("intent %q is not allowed", message.Intent))
}
if message.Entry == "" {
report.addError("missing_entry", message.ID, message.File, "message requires entry")
} else if _, ok := p.ByID[message.Entry]; !ok {
report.addError("missing_entry_gsp", message.Entry, message.File, fmt.Sprintf("entry references missing GSP %q", message.Entry))
}
if message.Stage != "" {
if _, ok := p.Manifest.minResolution(message.Stage); !ok {
report.addError("unknown_stage", message.ID, message.File, fmt.Sprintf("unknown stage %q", message.Stage))
}
}
for _, id := range message.Requires {
if _, ok := p.ByID[id]; !ok {
report.addError("missing_required_gsp", id, message.File, fmt.Sprintf("requires references missing GSP %q", id))
}
}
if message.ContextPack.Mode != "" && !allowedMessageIntents[message.ContextPack.Mode] {
report.addError("invalid_context_pack_mode", message.ID, message.File, fmt.Sprintf("contextPack mode %q is not allowed", message.ContextPack.Mode))
}
return report
}