blob: df464592fad5697f791cfde0c58b0b605fef9399 [file] [edit]
// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rsmsg
import (
"fmt"
"time"
"google.golang.org/protobuf/proto"
rspb "google.golang.org/genproto/googleapis/devtools/resultstore/v2"
)
// ValidateTimingConsistency checks that a child resource's timing is logically consistent
// with its parent resource's timing.
func ValidateTimingConsistency(parent, child *rspb.Timing) error {
if parent == nil || child == nil || parent.StartTime == nil || child.StartTime == nil {
return nil
}
return ValidateTimeOrder(parent.StartTime.AsTime(), child.StartTime.AsTime(), "child starts before parent")
}
// ValidateTimeOrder ensures that 'earlier' is indeed before or equal to 'later'.
func ValidateTimeOrder(earlier, later time.Time, msg string) error {
if earlier.IsZero() || later.IsZero() {
return nil
}
if later.Before(earlier) {
return fmt.Errorf("timing violation: %s (%v is before %v) (diff: %v)",
msg, later, earlier, earlier.Sub(later))
}
return nil
}
// ClampTiming ensures that a child resource's start time is no earlier than the parent's.
// It returns true if the timing was modified.
func ClampTiming(parent, child *rspb.Timing) bool {
if parent == nil || child == nil {
return false
}
if parent.StartTime == nil || child.StartTime == nil {
return false
}
pStart := parent.StartTime.AsTime()
cStart := child.StartTime.AsTime()
if cStart.Before(pStart) {
child.StartTime = parent.StartTime
return true
}
return false
}
// ClampUploadRequestTimingWithClone checks if the request's timing violates the parent's
// constraints.
//
// To safely avoid modifying a potentially shared request (Clone-on-Write),
// this function returns a CLONED request if a repair is actually performed.
// If no repair is needed, it returns the original request pointer.
func ClampUploadRequestTimingWithClone(req *rspb.UploadRequest, parentTiming *rspb.Timing) (*rspb.UploadRequest, bool) {
action := req.GetAction()
if action == nil || action.Timing == nil || parentTiming == nil || parentTiming.StartTime == nil || action.Timing.StartTime == nil {
return req, false
}
if !action.Timing.StartTime.AsTime().Before(parentTiming.StartTime.AsTime()) {
return req, false
}
// Mutation needed, so clone before modifying to ensure data isolation.
req = proto.Clone(req).(*rspb.UploadRequest)
ClampTiming(parentTiming, req.GetAction().Timing)
return req, true
}