text:
package buildmerge

import (
	"strings"

	"go.chromium.org/luci/common/errors"
	"go.chromium.org/luci/logdog/common/types"
)

// absolutizeURLs absolutizes the provided `logURL` and `viewURL`.
//
// No-op if both `logURL` and `viewURL` are absolute urls. Absolute url here
// means url that has valid scheme. A valid scheme must be
// `[a-zA-Z][a-zA-Z0-9+-.]*`` according to
// https://datatracker.ietf.org/doc/html/rfc3986#section-3.1
//
// If `logURL` is relative, calculate the return urls using `calcFn`. The
// provided `viewURL` will be omitted.
//
// Returns error if any of the following conditions are true:
//  * `logURL` is an absolute url but `viewURL` is empty.
//  * `logURL` is an absolute url but `viewURL` is not an absolute url.
//  * `logURL` is a relative url but not a valid logdog stream.
// The input `logURL` and `viewURL` will be returned verbatim on errors.
func absolutizeURLs(logURL, viewURL string, ns types.StreamName, calcFn CalcURLFn) (retLogURL, retViewURL string, err error) {
	retLogURL, retViewURL = logURL, viewURL
	if isAbs(logURL) {
		switch {
		case viewURL == "":
			return retLogURL, retViewURL, errors.Reason("absolute log url is provided %q but view url is empty", logURL).Err()
		case !isAbs(viewURL):
			return retLogURL, retViewURL, errors.Reason("expected absolute view url, got %q", viewURL).Err()
		}
		// both urls are absolute.
		return retLogURL, retViewURL, nil
	}

	stream := types.StreamName(logURL)
	if serr := stream.Validate(); serr != nil {
		return retLogURL, retViewURL, errors.Annotate(serr, "bad log url %q", logURL).Err()
	}
	retLogURL, retViewURL = calcFn(ns, stream)
	return retLogURL, retViewURL, nil
}

func isAbs(url string) bool {
	idx := strings.Index(url, "://")
	if idx <= 0 {
		return false
	}
	scheme := url[:idx]
	for i := 0; i < len(scheme); i++ {
		c := scheme[i]
		switch {
		case 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z':
		// always valid
		case '0' <= c && c <= '9' || c == '+' || c == '-' || c == '.':
			if i != 0 {
				return false
			}
		}
	}
	return true
}