plugin_compatibility.go 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. package service
  2. import (
  3. "fmt"
  4. "strings"
  5. pluginv1 "github.com/Wei-Shaw/sub2api/pkg/pluginapi/v1"
  6. "golang.org/x/mod/semver"
  7. )
  8. type PluginHostInfo struct {
  9. Version string
  10. BuildType string
  11. }
  12. func EvaluatePluginCompatibility(manifest PluginManifest, host PluginHostInfo) PluginCompatibility {
  13. result := PluginCompatibility{
  14. CurrentSub2API: host.Version,
  15. RequiredSub2API: manifest.Requires.Sub2API,
  16. RecommendedSub2API: manifest.Requires.RecommendedSub2APIVersion,
  17. PluginProtocol: manifest.Requires.PluginProtocol,
  18. TransportAPI: manifest.Requires.TransportAPI,
  19. UIBridge: manifest.Requires.UIBridge,
  20. }
  21. if manifest.Requires.PluginProtocol != pluginv1.ProtocolVersion ||
  22. manifest.Requires.TransportAPI != pluginv1.TransportAPIVersion ||
  23. manifest.Requires.UIBridge != pluginv1.UIBridgeVersion {
  24. result.Status = "incompatible"
  25. result.Message = "插件协议版本与当前 Sub2API 不兼容"
  26. return result
  27. }
  28. if !matchesSemverRange(host.Version, manifest.Requires.Sub2API) {
  29. result.Status = "incompatible"
  30. result.Message = fmt.Sprintf("当前 Sub2API %s 不满足插件要求 %s", host.Version, manifest.Requires.Sub2API)
  31. return result
  32. }
  33. result.Compatible = true
  34. for _, tested := range manifest.Requires.TestedSub2APIVersions {
  35. if normalizeSemver(tested) == normalizeSemver(host.Version) {
  36. result.Tested = true
  37. break
  38. }
  39. }
  40. if result.Tested {
  41. result.Status = "compatible"
  42. result.Message = "当前 Sub2API 版本已由插件声明测试"
  43. } else {
  44. result.Status = "untested"
  45. result.Message = "版本范围兼容,但插件未声明已测试当前 Sub2API 版本"
  46. }
  47. return result
  48. }
  49. func normalizeSemver(version string) string {
  50. v := strings.TrimSpace(version)
  51. if v == "" {
  52. return ""
  53. }
  54. if !strings.HasPrefix(v, "v") {
  55. v = "v" + v
  56. }
  57. if !semver.IsValid(v) {
  58. return ""
  59. }
  60. return v
  61. }
  62. func matchesSemverRange(version, expression string) bool {
  63. v := normalizeSemver(version)
  64. if v == "" {
  65. return false
  66. }
  67. tokens := strings.Fields(strings.ReplaceAll(expression, ",", " "))
  68. if len(tokens) == 0 {
  69. return false
  70. }
  71. for _, token := range tokens {
  72. op := "="
  73. raw := token
  74. for _, candidate := range []string{">=", "<=", ">", "<", "="} {
  75. if strings.HasPrefix(token, candidate) {
  76. op = candidate
  77. raw = strings.TrimSpace(strings.TrimPrefix(token, candidate))
  78. break
  79. }
  80. }
  81. bound := normalizeSemver(raw)
  82. if bound == "" {
  83. return false
  84. }
  85. comparison := semver.Compare(v, bound)
  86. matched := map[string]bool{
  87. ">=": comparison >= 0,
  88. "<=": comparison <= 0,
  89. ">": comparison > 0,
  90. "<": comparison < 0,
  91. "=": comparison == 0,
  92. }[op]
  93. if !matched {
  94. return false
  95. }
  96. }
  97. return true
  98. }