Files
sub2api/backend/internal/service/plugin_compatibility.go
T

105 lines
2.6 KiB
Go

package service
import (
"fmt"
"strings"
pluginv1 "github.com/Wei-Shaw/sub2api/pkg/pluginapi/v1"
"golang.org/x/mod/semver"
)
type PluginHostInfo struct {
Version string
BuildType string
}
func EvaluatePluginCompatibility(manifest PluginManifest, host PluginHostInfo) PluginCompatibility {
result := PluginCompatibility{
CurrentSub2API: host.Version,
RequiredSub2API: manifest.Requires.Sub2API,
RecommendedSub2API: manifest.Requires.RecommendedSub2APIVersion,
PluginProtocol: manifest.Requires.PluginProtocol,
TransportAPI: manifest.Requires.TransportAPI,
UIBridge: manifest.Requires.UIBridge,
}
if manifest.Requires.PluginProtocol != pluginv1.ProtocolVersion ||
manifest.Requires.TransportAPI != pluginv1.TransportAPIVersion ||
manifest.Requires.UIBridge != pluginv1.UIBridgeVersion {
result.Status = "incompatible"
result.Message = "插件协议版本与当前 Sub2API 不兼容"
return result
}
if !matchesSemverRange(host.Version, manifest.Requires.Sub2API) {
result.Status = "incompatible"
result.Message = fmt.Sprintf("当前 Sub2API %s 不满足插件要求 %s", host.Version, manifest.Requires.Sub2API)
return result
}
result.Compatible = true
for _, tested := range manifest.Requires.TestedSub2APIVersions {
if normalizeSemver(tested) == normalizeSemver(host.Version) {
result.Tested = true
break
}
}
if result.Tested {
result.Status = "compatible"
result.Message = "当前 Sub2API 版本已由插件声明测试"
} else {
result.Status = "untested"
result.Message = "版本范围兼容,但插件未声明已测试当前 Sub2API 版本"
}
return result
}
func normalizeSemver(version string) string {
v := strings.TrimSpace(version)
if v == "" {
return ""
}
if !strings.HasPrefix(v, "v") {
v = "v" + v
}
if !semver.IsValid(v) {
return ""
}
return v
}
func matchesSemverRange(version, expression string) bool {
v := normalizeSemver(version)
if v == "" {
return false
}
tokens := strings.Fields(strings.ReplaceAll(expression, ",", " "))
if len(tokens) == 0 {
return false
}
for _, token := range tokens {
op := "="
raw := token
for _, candidate := range []string{">=", "<=", ">", "<", "="} {
if strings.HasPrefix(token, candidate) {
op = candidate
raw = strings.TrimSpace(strings.TrimPrefix(token, candidate))
break
}
}
bound := normalizeSemver(raw)
if bound == "" {
return false
}
comparison := semver.Compare(v, bound)
matched := map[string]bool{
">=": comparison >= 0,
"<=": comparison <= 0,
">": comparison > 0,
"<": comparison < 0,
"=": comparison == 0,
}[op]
if !matched {
return false
}
}
return true
}