urltable.go 3.4 KB

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