code/go/internal/validator/semantic/validate_prerelease.go (69 lines of code) (raw):
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License;
// you may not use this file except in compliance with the Elastic License.
package semantic
import (
"fmt"
"regexp"
"strings"
"github.com/Masterminds/semver/v3"
"github.com/elastic/package-spec/v3/code/go/internal/fspath"
"github.com/elastic/package-spec/v3/code/go/pkg/specerrors"
)
var (
// Prereleases allowed as literals.
literalPrereleases = []string{
// For convenience with previous recommendations
"next",
"SNAPSHOT",
}
// Prereleases allowed, potentially with additional numbering.
numberedPrereleases = []string{
"beta",
"rc",
"preview",
}
)
// ValidatePrerelease validates additional restrictions on the prerelease tags.
func ValidatePrerelease(fsys fspath.FS) specerrors.ValidationErrors {
manifestVersion, err := readManifestVersion(fsys)
if err != nil {
return specerrors.ValidationErrors{specerrors.NewStructuredError(err, specerrors.UnassignedCode)}
}
err = validatePrerelease(manifestVersion)
if err != nil {
return specerrors.ValidationErrors{specerrors.NewStructuredError(err, specerrors.UnassignedCode)}
}
return nil
}
func validatePrerelease(manifestVersion string) error {
version, err := semver.NewVersion(manifestVersion)
if err != nil {
return err
}
if version.Major() == 0 && version.Prerelease() != "" {
return fmt.Errorf("versions below 1.0.0 are considered technical previews, please remove prerelease tag (version: %s)", manifestVersion)
}
if err := validatePrereleaseTag(version.Prerelease()); err != nil {
return err
}
return nil
}
// prereleaseNumberPattern is the pattern that the number after the prerelease tag must match.
// It has to start with a number, hyphen or dot, and end with number or letter.
const prereleaseNumberPattern = "(([0-9]|[.-][0-9A-Za-z])([0-9A-Za-z-.]*[0-9A-Za-z])?)?"
func validatePrereleaseTag(tag string) error {
if tag == "" {
return nil
}
for _, literal := range literalPrereleases {
if tag == literal {
return nil
}
}
for _, numbered := range numberedPrereleases {
if tag == numbered {
return nil
}
pattern := regexp.MustCompile(fmt.Sprintf("^%s%s$", numbered, prereleaseNumberPattern))
if pattern.MatchString(tag) {
return nil
}
}
return fmt.Errorf("prerelease tag (%s) should be one of [%s], or one of [%s] followed by numbers",
tag,
strings.Join(literalPrereleases, ", "),
strings.Join(numberedPrereleases, ", "),
)
}