Files
notarius/internal/modules/sharedassets/dnd/unit_refs.go

134 lines
2.9 KiB
Go

package dnd
import (
"bytes"
"encoding/json"
"fmt"
"strconv"
"strings"
"gitea.maximumdirect.net/eric/notarius/internal/core/source"
)
type UnitRef struct {
value string
fromNumber bool
number int
}
type SourceRefResponse struct {
SourceID string `json:"source_id"`
StartUnitID UnitRef `json:"start_unit_id"`
EndUnitID UnitRef `json:"end_unit_id"`
}
func UnitRefFromString(value string) UnitRef {
return UnitRef{value: value}
}
func UnitRefFromInt(value int) UnitRef {
return UnitRef{
value: strconv.Itoa(value),
fromNumber: true,
number: value,
}
}
func (ref UnitRef) String() string {
return ref.value
}
func (ref *UnitRef) UnmarshalJSON(raw []byte) error {
raw = bytes.TrimSpace(raw)
if len(raw) == 0 {
return fmt.Errorf("unit ref must be a string or integer")
}
if raw[0] == '"' {
var value string
if err := json.Unmarshal(raw, &value); err != nil {
return err
}
*ref = UnitRefFromString(value)
return nil
}
number, err := strconv.Atoi(string(raw))
if err != nil {
return fmt.Errorf("unit ref must be a string or integer")
}
*ref = UnitRefFromInt(number)
return nil
}
func (ref UnitRef) MarshalJSON() ([]byte, error) {
if ref.fromNumber {
return []byte(strconv.Itoa(ref.number)), nil
}
return json.Marshal(ref.value)
}
func ResolveUnitID(doc *source.SourceDocument, field string, ref UnitRef) (string, error) {
value := strings.TrimSpace(ref.value)
if value == "" {
return "", fmt.Errorf("%s must not be empty", field)
}
if id, ok := canonicalUnitID(doc, value); ok {
return id, nil
}
if number, ok := unitNumber(value); ok {
if id, ok := unitIDByNumber(doc, number); ok {
return id, nil
}
return "", fmt.Errorf("%s %d was not found as a source-unit ID or 1-based unit number", field, number)
}
return "", fmt.Errorf("%s %q was not found", field, value)
}
func SourceRefCandidate(doc *source.SourceDocument, ref SourceRefResponse) source.SourceRef {
return source.SourceRef{
SourceID: strings.TrimSpace(ref.SourceID),
StartUnitID: unitIDCandidate(doc, ref.StartUnitID),
EndUnitID: unitIDCandidate(doc, ref.EndUnitID),
}
}
func unitIDCandidate(doc *source.SourceDocument, ref UnitRef) string {
value := strings.TrimSpace(ref.value)
if id, ok := canonicalUnitID(doc, value); ok {
return id
}
if number, ok := unitNumber(value); ok {
if id, ok := unitIDByNumber(doc, number); ok {
return id
}
}
return value
}
func canonicalUnitID(doc *source.SourceDocument, value string) (string, bool) {
if doc == nil {
return "", false
}
for _, unit := range doc.Units {
if unit.ID == value {
return unit.ID, true
}
}
return "", false
}
func unitIDByNumber(doc *source.SourceDocument, number int) (string, bool) {
if doc == nil || number < 1 || number > len(doc.Units) {
return "", false
}
return doc.Units[number-1].ID, true
}
func unitNumber(value string) (int, bool) {
number, err := strconv.Atoi(value)
if err != nil {
return 0, false
}
return number, true
}