versioning.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. package versioning
  2. import (
  3. "github.com/kataras/iris/context"
  4. "github.com/hashicorp/go-version"
  5. )
  6. // If reports whether the "version" is matching to the "is".
  7. // the "is" can be a constraint like ">= 1, < 3".
  8. func If(v string, is string) bool {
  9. _, ok := check(v, is)
  10. return ok
  11. }
  12. func check(v string, is string) (string, bool) {
  13. ver, err := version.NewVersion(v)
  14. if err != nil {
  15. return "", false
  16. }
  17. constraints, err := version.NewConstraint(is)
  18. if err != nil {
  19. return "", false
  20. }
  21. // return the extracted version from request, even if not matched.
  22. return ver.String(), constraints.Check(ver)
  23. }
  24. // Match acts exactly the same as `If` does but instead it accepts
  25. // a Context, so it can be called by a handler to determinate the requested version.
  26. //
  27. // If matched then it sets the "X-API-Version" response header and
  28. // stores the matched version into Context (see `GetVersion` too).
  29. func Match(ctx *context.Context, expectedVersion string) bool {
  30. versionString, matched := check(GetVersion(ctx), expectedVersion)
  31. if !matched {
  32. return false
  33. }
  34. SetVersion(ctx, versionString)
  35. ctx.Header("X-API-Version", versionString)
  36. return true
  37. }
  38. // Handler returns a handler which stop the execution
  39. // when the given "version" does not match with the requested one.
  40. func Handler(version string) context.Handler {
  41. return func(ctx *context.Context) {
  42. if !Match(ctx, version) {
  43. // Any overlapped handler
  44. // can just clear the status code
  45. // and the error to ignore this (see `NewGroup`).
  46. NotFoundHandler(ctx)
  47. return
  48. }
  49. ctx.Next()
  50. }
  51. }
  52. // Map is a map of versions targets to a handlers,
  53. // a handler per version or constraint, the key can be something like ">1, <=2" or just "1".
  54. type Map map[string]context.Handler
  55. // NewMatcher creates a single handler which decides what handler
  56. // should be executed based on the requested version.
  57. //
  58. // Use the `NewGroup` if you want to add many routes under a specific version.
  59. //
  60. // See `Map` and `NewGroup` too.
  61. func NewMatcher(versions Map) context.Handler {
  62. constraintsHandlers, notFoundHandler := buildConstraints(versions)
  63. return func(ctx *context.Context) {
  64. versionString := GetVersion(ctx)
  65. if versionString == "" || versionString == NotFound {
  66. notFoundHandler(ctx)
  67. return
  68. }
  69. ver, err := version.NewVersion(versionString)
  70. if err != nil {
  71. notFoundHandler(ctx)
  72. return
  73. }
  74. for _, ch := range constraintsHandlers {
  75. if ch.constraints.Check(ver) {
  76. ctx.Header("X-API-Version", ver.String())
  77. ch.handler(ctx)
  78. return
  79. }
  80. }
  81. // pass the not matched version so the not found handler can have knowedge about it.
  82. // SetVersion(ctx, versionString)
  83. // or let a manual cal of GetVersion(ctx) do that instead.
  84. notFoundHandler(ctx)
  85. }
  86. }
  87. type constraintsHandler struct {
  88. constraints version.Constraints
  89. handler context.Handler
  90. }
  91. func buildConstraints(versionsHandler Map) (constraintsHandlers []*constraintsHandler, notfoundHandler context.Handler) {
  92. for v, h := range versionsHandler {
  93. if v == NotFound {
  94. notfoundHandler = h
  95. continue
  96. }
  97. constraints, err := version.NewConstraint(v)
  98. if err != nil {
  99. panic(err)
  100. }
  101. constraintsHandlers = append(constraintsHandlers, &constraintsHandler{
  102. constraints: constraints,
  103. handler: h,
  104. })
  105. }
  106. if notfoundHandler == nil {
  107. notfoundHandler = NotFoundHandler
  108. }
  109. // no sort, the end-dev should declare
  110. // all version constraint, i.e < 4.0 may be catch 1.0 if not something like
  111. // >= 3.0, < 4.0.
  112. // I can make it ordered but I do NOT like the final API of it:
  113. /*
  114. app.Get("/api/user", NewMatcher( // accepts an array, ordered, see last elem.
  115. V("1.0", vHandler("v1 here")),
  116. V("2.0", vHandler("v2 here")),
  117. V("< 4.0", vHandler("v3.x here")),
  118. ))
  119. instead we have:
  120. app.Get("/api/user", NewMatcher(Map{ // accepts a map, unordered, see last elem.
  121. "1.0": Deprecated(vHandler("v1 here")),
  122. "2.0": vHandler("v2 here"),
  123. ">= 3.0, < 4.0": vHandler("v3.x here"),
  124. VersionUnknown: customHandlerForNotMatchingVersion,
  125. }))
  126. */
  127. return
  128. }