package spellpayload import ( "bytes" "encoding/json" "fmt" "io" "strings" "gitea.maximumdirect.net/eric/notarius/internal/core/source" "gitea.maximumdirect.net/eric/notarius/internal/framework/contracts" "gitea.maximumdirect.net/eric/notarius/internal/modules/sharedassets/dnd" ) type Payload struct { SpellCasts []SpellCast `json:"spell_casts"` } type SpellCast struct { Caster string `json:"caster"` Spell string `json:"spell"` Effect string `json:"effect"` NarrativeDescription string `json:"narrative_description"` SourceRefs []dnd.SourceRefResponse `json:"source_refs"` } func Parse(raw []byte) (Payload, error) { decoder := json.NewDecoder(bytes.NewReader(raw)) decoder.DisallowUnknownFields() var payload Payload if err := decoder.Decode(&payload); err != nil { return Payload{}, fmt.Errorf("parse spell payload: %w", err) } var extra any if err := decoder.Decode(&extra); err != io.EOF { return Payload{}, fmt.Errorf("parse spell payload: multiple JSON values") } return payload, nil } func ValidateShape(payload Payload) error { if payload.SpellCasts == nil { return fmt.Errorf("spell_casts must be present") } for index, spell := range payload.SpellCasts { if strings.TrimSpace(spell.Caster) == "" { return fmt.Errorf("spell_casts[%d].caster must not be empty", index) } if strings.TrimSpace(spell.Spell) == "" { return fmt.Errorf("spell_casts[%d].spell must not be empty", index) } if strings.TrimSpace(spell.Effect) == "" { return fmt.Errorf("spell_casts[%d].effect must not be empty", index) } if strings.TrimSpace(spell.NarrativeDescription) == "" { return fmt.Errorf("spell_casts[%d].narrative_description must not be empty", index) } if len(spell.SourceRefs) == 0 { return fmt.Errorf("spell_casts[%d].source_refs must not be empty", index) } } return nil } func SourceRefCandidates(doc *source.SourceDocument, spell SpellCast) []source.SourceRef { refs := make([]source.SourceRef, 0, len(spell.SourceRefs)) for _, ref := range spell.SourceRefs { refs = append(refs, dnd.SourceRefCandidate(doc, ref)) } return refs } func CitedText(doc *source.SourceDocument, ref source.SourceRef) (string, bool) { if doc == nil { return "", false } startIndex, ok := source.UnitIndex(doc, ref.StartUnitID) if !ok { return "", false } endIndex, ok := source.UnitIndex(doc, ref.EndUnitID) if !ok || startIndex > endIndex { return "", false } var b strings.Builder for i := startIndex; i <= endIndex; i++ { if b.Len() > 0 { b.WriteByte('\n') } b.WriteString(doc.Units[i].Text) } return b.String(), true } func ValidationRequestPayload(req contracts.ValidationRequest) (Payload, error) { return Parse(req.Payload.Content) }