Files
notarius/internal/framework/evidencecontext/build.go

101 lines
2.8 KiB
Go

package evidencecontext
import (
"fmt"
"sort"
"gitea.maximumdirect.net/eric/notarius/internal/core/source"
)
type expandedRange struct {
startPos int
endPos int
}
// Build validates projected source references, expands them by source-document
// position, and returns their ordered union as an owned source-unit excerpt.
func Build(request BuildRequest) (Document, error) {
if request.WindowUnits < 0 {
return nil, fmt.Errorf("window_units must not be negative")
}
if err := source.ValidateDocument(request.Source); err != nil {
return nil, fmt.Errorf("validate source document: %w", err)
}
digest, err := source.DigestDocument(request.Source)
if err != nil {
return nil, fmt.Errorf("digest source document: %w", err)
}
if digest != request.Source.Digest {
return nil, fmt.Errorf("source digest does not match source document digest")
}
index := source.NewDocumentIndex(request.Source)
ranges := make([]expandedRange, 0, len(request.SourceRefs))
for refIndex, ref := range request.SourceRefs {
if err := index.ValidateRef(ref); err != nil {
return nil, fmt.Errorf("source reference[%d]: %w", refIndex, err)
}
startPos, _ := index.Position(ref.StartUnitID)
endPos, _ := index.Position(ref.EndUnitID)
ranges = append(ranges, expandedRange{
startPos: expandStart(startPos, request.WindowUnits),
endPos: expandEnd(endPos, len(request.Source.Units), request.WindowUnits),
})
}
merged := mergeRanges(ranges)
unitCount := 0
for _, value := range merged {
unitCount += value.endPos - value.startPos + 1
}
document := make(Document, 0, unitCount)
for _, value := range merged {
for position := value.startPos; position <= value.endPos; position++ {
unit, err := cloneSourceUnit(request.Source.Units[position])
if err != nil {
return nil, fmt.Errorf("clone source unit at position %d: %w", position, err)
}
document = append(document, unit)
}
}
return document, nil
}
func expandStart(position, window int) int {
if window > position {
return 0
}
return position - window
}
func expandEnd(position, length, window int) int {
last := length - 1
if window > last-position {
return last
}
return position + window
}
func mergeRanges(values []expandedRange) []expandedRange {
if len(values) == 0 {
return nil
}
sort.Slice(values, func(i, j int) bool {
if values[i].startPos != values[j].startPos {
return values[i].startPos < values[j].startPos
}
return values[i].endPos < values[j].endPos
})
merged := make([]expandedRange, 0, len(values))
for _, value := range values {
if len(merged) == 0 || value.startPos > merged[len(merged)-1].endPos+1 {
merged = append(merged, value)
continue
}
if value.endPos > merged[len(merged)-1].endPos {
merged[len(merged)-1].endPos = value.endPos
}
}
return merged
}