package shared import ( "bytes" "encoding/json" "fmt" "strconv" "strings" "gitea.maximumdirect.net/eric/notarius/internal/core/source" ) type UnitRef struct { value int fromNumber bool } 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 { parsed, _ := parseUnitRefNumber(value) return UnitRef{value: parsed} } func UnitRefFromInt(value int) UnitRef { return UnitRef{ value: value, fromNumber: true, } } func (ref UnitRef) String() string { if ref.value == 0 { return "" } return strconv.Itoa(ref.value) } func (ref UnitRef) Int() int { 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 } number, err := parseUnitRefNumber(value) if err != nil { return err } *ref = UnitRef{value: number} return nil } number, err := parseUnitRefNumber(string(raw)) if err != nil { return err } *ref = UnitRefFromInt(number) return nil } func (ref UnitRef) MarshalJSON() ([]byte, error) { if ref.fromNumber { return []byte(strconv.Itoa(ref.value)), nil } return json.Marshal(ref.String()) } func ResolveUnitID(doc *source.SourceDocument, field string, ref UnitRef) (int, error) { if ref.value <= 0 { return 0, fmt.Errorf("%s must be positive", field) } if _, ok := source.UnitIndex(doc, ref.value); !ok { return 0, fmt.Errorf("%s %d was not found", field, ref.value) } return ref.value, nil } func SourceRefCandidate(doc *source.SourceDocument, ref SourceRefResponse) source.SourceRef { return source.SourceRef{ SourceID: strings.TrimSpace(ref.SourceID), StartUnitID: unitIDCandidate(ref.StartUnitID), EndUnitID: unitIDCandidate(ref.EndUnitID), } } func unitIDCandidate(ref UnitRef) int { return ref.value } func parseUnitRefNumber(value string) (int, error) { trimmed := strings.TrimSpace(value) if trimmed == "" { return 0, fmt.Errorf("unit ref must not be empty") } if trimmed != value { return 0, fmt.Errorf("unit ref must not contain leading or trailing whitespace") } number, err := strconv.Atoi(value) if err != nil { return 0, fmt.Errorf("unit ref must be an integer") } if number <= 0 { return 0, fmt.Errorf("unit ref must be positive") } return number, nil }