Move item occurrences to canonical namespace

This commit is contained in:
2026-08-05 20:00:20 +00:00
parent 3dfefd0e14
commit a6e176e160
46 changed files with 342 additions and 337 deletions

View File

@@ -1,5 +1,5 @@
// Package itemevents encodes durable D&D item-event artifacts.
package itemevents
// Package itemoccurrences encodes durable D&D item-occurrence artifacts.
package itemoccurrences
import (
"embed"
@@ -10,7 +10,7 @@ import (
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/candidatejson"
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/itemevents"
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/itemoccurrences"
)
const (
@@ -86,35 +86,35 @@ func (c *Codec) DecodeCandidate(content []byte) (dnd.ItemOccurrenceList, error)
func validate(value dnd.ItemOccurrenceList) error {
if value.Occurrences == nil {
return fmt.Errorf("events must be present")
return fmt.Errorf("occurrences must be present")
}
for index, event := range value.Occurrences {
for index, occurrence := range value.Occurrences {
prefix := fmt.Sprintf("occurrences[%d]", index)
if strings.TrimSpace(event.ItemID) == "" {
if strings.TrimSpace(occurrence.ItemID) == "" {
return fmt.Errorf("%s.item_id must not be empty", prefix)
}
if strings.TrimSpace(event.Name) == "" {
if strings.TrimSpace(occurrence.Name) == "" {
return fmt.Errorf("%s.name must not be empty", prefix)
}
if !itemevents.SupportedKind(event.Kind) {
if !itemoccurrences.SupportedKind(occurrence.Kind) {
return fmt.Errorf("%s.kind must be supported", prefix)
}
if event.From != "" && strings.TrimSpace(event.From) == "" {
if occurrence.From != "" && strings.TrimSpace(occurrence.From) == "" {
return fmt.Errorf("%s.from must not be empty when present", prefix)
}
if event.To != "" && strings.TrimSpace(event.To) == "" {
if occurrence.To != "" && strings.TrimSpace(occurrence.To) == "" {
return fmt.Errorf("%s.to must not be empty when present", prefix)
}
if !itemevents.ValidHolderCombination(event.Kind, event.From, event.To) {
return fmt.Errorf("%s holders are incompatible with %q", prefix, event.Kind)
if !itemoccurrences.ValidHolderCombination(occurrence.Kind, occurrence.From, occurrence.To) {
return fmt.Errorf("%s holders are incompatible with %q", prefix, occurrence.Kind)
}
if event.Quantity != nil && *event.Quantity < 1 {
if occurrence.Quantity != nil && *occurrence.Quantity < 1 {
return fmt.Errorf("%s.quantity must be positive when present", prefix)
}
if len(event.SourceRefs) == 0 {
if len(occurrence.SourceRefs) == 0 {
return fmt.Errorf("%s.source_refs must contain at least one reference", prefix)
}
for refIndex, ref := range event.SourceRefs {
for refIndex, ref := range occurrence.SourceRefs {
refPrefix := fmt.Sprintf("%s.source_refs[%d]", prefix, refIndex)
if strings.TrimSpace(ref.SourceID) == "" {
return fmt.Errorf("%s.source_id must not be empty", refPrefix)
@@ -135,14 +135,14 @@ func cloneList(value dnd.ItemOccurrenceList) dnd.ItemOccurrenceList {
return dnd.ItemOccurrenceList{}
}
cloned := dnd.ItemOccurrenceList{Occurrences: make([]dnd.ItemOccurrence, len(value.Occurrences))}
for index, event := range value.Occurrences {
cloned.Occurrences[index] = event
if event.Quantity != nil {
quantity := *event.Quantity
for index, occurrence := range value.Occurrences {
cloned.Occurrences[index] = occurrence
if occurrence.Quantity != nil {
quantity := *occurrence.Quantity
cloned.Occurrences[index].Quantity = &quantity
}
if event.SourceRefs != nil {
cloned.Occurrences[index].SourceRefs = append([]source.SourceRef(nil), event.SourceRefs...)
if occurrence.SourceRefs != nil {
cloned.Occurrences[index].SourceRefs = append([]source.SourceRef(nil), occurrence.SourceRefs...)
}
}
return cloned

View File

@@ -1,4 +1,4 @@
package itemevents
package itemoccurrences
import (
"bytes"
@@ -93,10 +93,10 @@ func TestCodecRejectsStrictJSONAndApprovedBoundaries(t *testing.T) {
}{
{"malformed", `{`, "decode dnd item occurrence list"},
{"unknown top level", `{"occurrences":[],"unexpected":true}`, "unknown field"},
{"unknown event field", strings.Replace(validJSON, `"to":"party"`, `"to":"party","unexpected":true`, 1), "unknown field"},
{"unknown occurrence field", strings.Replace(validJSON, `"to":"party"`, `"to":"party","unexpected":true`, 1), "unknown field"},
{"unknown reference field", strings.Replace(validJSON, `"end_unit_id":1`, `"end_unit_id":1,"unexpected":true`, 1), "unknown field"},
{"trailing", `{"occurrences":[]} {}`, "multiple JSON values"},
{"missing list", `{}`, "events must be present"},
{"missing list", `{}`, "occurrences must be present"},
{"zero quantity", strings.Replace(validJSON, `"to":"party"`, `"quantity":0,"to":"party"`, 1), "quantity must be positive"},
{"negative quantity", strings.Replace(validJSON, `"to":"party"`, `"quantity":-1,"to":"party"`, 1), "quantity must be positive"},
{"party transfer", strings.Replace(validJSON, `"kind":"acquired","to":"party"`, `"kind":"transferred","from":"party","to":"Borin"`, 1), "holders are incompatible"},
@@ -125,7 +125,7 @@ func TestCodecDeepCopiesBoundaryValuesAndMetadata(t *testing.T) {
t.Fatal(err)
}
if decoded.Occurrences[1].Quantity == value.Occurrences[1].Quantity || &decoded.Occurrences[1].SourceRefs[0] == &value.Occurrences[1].SourceRefs[0] {
t.Fatal("DecodeCandidate() retained caller-owned event fields")
t.Fatal("DecodeCandidate() retained caller-owned occurrence fields")
}
*decoded.Occurrences[1].Quantity = 99
decoded.Occurrences[1].SourceRefs[0].SourceID = "changed"