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 }