urltable.go 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. package urltable
  2. import (
  3. "net/http"
  4. "strings"
  5. "git.bvbej.com/bvbej/base-golang/pkg/errors"
  6. )
  7. const (
  8. empty = ""
  9. fuzzy = "*"
  10. omitted = "**"
  11. delimiter = "/"
  12. methodView = "VIEW"
  13. )
  14. // parse and validate pattern
  15. func parse(pattern string) ([]string, error) {
  16. const format = "[get, post, put, patch, delete, view]/{a-Z}+/{*}+/{**}"
  17. if pattern = strings.TrimLeft(strings.TrimSpace(pattern), delimiter); pattern == "" {
  18. return nil, errors.Errorf("pattern illegal, should in format of %s", format)
  19. }
  20. paths := strings.Split(pattern, delimiter)
  21. if len(paths) < 2 {
  22. return nil, errors.Errorf("pattern illegal, should in format of %s", format)
  23. }
  24. for i := range paths {
  25. paths[i] = strings.TrimSpace(paths[i])
  26. }
  27. // likes get/ get/* get/**
  28. if len(paths) == 2 && (paths[1] == empty || paths[1] == fuzzy || paths[1] == omitted) {
  29. return nil, errors.New("illegal wildcard")
  30. }
  31. switch paths[0] = strings.ToUpper(paths[0]); paths[0] {
  32. case http.MethodGet,
  33. http.MethodPost,
  34. http.MethodPut,
  35. http.MethodPatch,
  36. http.MethodDelete,
  37. methodView:
  38. default:
  39. return nil, errors.Errorf("only supports [%s %s %s %s %s %s]",
  40. http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete, methodView)
  41. }
  42. for k := 1; k < len(paths); k++ {
  43. if paths[k] == empty && k+1 != len(paths) {
  44. return nil, errors.New("pattern contains illegal empty path")
  45. }
  46. if paths[k] == omitted && k+1 != len(paths) {
  47. return nil, errors.New("pattern contains illegal omitted path")
  48. }
  49. }
  50. return paths, nil
  51. }
  52. // Format pattern
  53. func Format(pattern string) (string, error) {
  54. paths, err := parse(pattern)
  55. if err != nil {
  56. return "", err
  57. }
  58. return strings.Join(paths, delimiter), nil
  59. }
  60. type section struct {
  61. leaf bool
  62. mapping map[string]*section
  63. }
  64. func newSection() *section {
  65. return &section{mapping: make(map[string]*section)}
  66. }
  67. // Table a (thread unsafe) table to store urls
  68. type Table struct {
  69. size int
  70. root *section
  71. }
  72. // NewTable create a table
  73. func NewTable() *Table {
  74. return &Table{root: newSection()}
  75. }
  76. // Size contains how many urls
  77. func (t *Table) Size() int {
  78. return t.size
  79. }
  80. // Append pattern
  81. func (t *Table) Append(pattern string) error {
  82. paths, err := parse(pattern)
  83. if err != nil {
  84. return err
  85. }
  86. insert := false
  87. root := t.root
  88. for i, path := range paths {
  89. if (path == fuzzy && root.mapping[omitted] != nil) ||
  90. (path == omitted && root.mapping[fuzzy] != nil) ||
  91. (path != omitted && root.mapping[omitted] != nil) {
  92. return errors.Errorf("conflict at %s", strings.Join(paths[:i], delimiter))
  93. }
  94. next := root.mapping[path]
  95. if next == nil {
  96. next = newSection()
  97. root.mapping[path] = next
  98. insert = true
  99. }
  100. root = next
  101. }
  102. if insert {
  103. t.size++
  104. }
  105. root.leaf = true
  106. return nil
  107. }
  108. // Mapping url to pattern
  109. func (t *Table) Mapping(url string) (string, error) {
  110. paths, err := parse(url)
  111. if err != nil {
  112. return "", err
  113. }
  114. pattern := make([]string, 0, len(paths))
  115. root := t.root
  116. for _, path := range paths {
  117. next := root.mapping[path]
  118. if next == nil {
  119. nextFuzzy := root.mapping[fuzzy]
  120. nextOmitted := root.mapping[omitted]
  121. if nextFuzzy == nil && nextOmitted == nil {
  122. return "", nil
  123. }
  124. if nextOmitted != nil {
  125. pattern = append(pattern, omitted)
  126. return strings.Join(pattern, delimiter), nil
  127. }
  128. next = nextFuzzy
  129. path = fuzzy
  130. }
  131. root = next
  132. pattern = append(pattern, path)
  133. }
  134. if root.leaf {
  135. return strings.Join(pattern, delimiter), nil
  136. }
  137. return "", nil
  138. }