101 lines
2.8 KiB
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
|
|
}
|