123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169 |
- package urltable
- import (
- "errors"
- "fmt"
- "net/http"
- "strings"
- )
- const (
- empty = ""
- fuzzy = "*"
- omitted = "**"
- delimiter = "/"
- methodView = "VIEW"
- )
- // parse and validate pattern
- func parse(pattern string) ([]string, error) {
- const format = "[get, post, put, patch, delete, view]/{a-Z}+/{*}+/{**}"
- if pattern = strings.TrimLeft(strings.TrimSpace(pattern), delimiter); pattern == "" {
- return nil, fmt.Errorf("pattern illegal, should in format of %s", format)
- }
- paths := strings.Split(pattern, delimiter)
- if len(paths) < 2 {
- return nil, fmt.Errorf("pattern illegal, should in format of %s", format)
- }
- for i := range paths {
- paths[i] = strings.TrimSpace(paths[i])
- }
- // likes get/ get/* get/**
- if len(paths) == 2 && (paths[1] == empty || paths[1] == fuzzy || paths[1] == omitted) {
- return nil, errors.New("illegal wildcard")
- }
- switch paths[0] = strings.ToUpper(paths[0]); paths[0] {
- case http.MethodGet,
- http.MethodPost,
- http.MethodPut,
- http.MethodPatch,
- http.MethodDelete,
- methodView:
- default:
- return nil, fmt.Errorf("only supports [%s %s %s %s %s %s]",
- http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete, methodView)
- }
- for k := 1; k < len(paths); k++ {
- if paths[k] == empty && k+1 != len(paths) {
- return nil, errors.New("pattern contains illegal empty path")
- }
- if paths[k] == omitted && k+1 != len(paths) {
- return nil, errors.New("pattern contains illegal omitted path")
- }
- }
- return paths, nil
- }
- // Format pattern
- func Format(pattern string) (string, error) {
- paths, err := parse(pattern)
- if err != nil {
- return "", err
- }
- return strings.Join(paths, delimiter), nil
- }
- type section struct {
- leaf bool
- mapping map[string]*section
- }
- func newSection() *section {
- return §ion{mapping: make(map[string]*section)}
- }
- // Table a (thread unsafe) table to store urls
- type Table struct {
- size int
- root *section
- }
- // NewTable create a table
- func NewTable() *Table {
- return &Table{root: newSection()}
- }
- // Size contains how many urls
- func (t *Table) Size() int {
- return t.size
- }
- // Append pattern
- func (t *Table) Append(pattern string) error {
- paths, err := parse(pattern)
- if err != nil {
- return err
- }
- insert := false
- root := t.root
- for i, path := range paths {
- if (path == fuzzy && root.mapping[omitted] != nil) ||
- (path == omitted && root.mapping[fuzzy] != nil) ||
- (path != omitted && root.mapping[omitted] != nil) {
- return fmt.Errorf("conflict at %s", strings.Join(paths[:i], delimiter))
- }
- next := root.mapping[path]
- if next == nil {
- next = newSection()
- root.mapping[path] = next
- insert = true
- }
- root = next
- }
- if insert {
- t.size++
- }
- root.leaf = true
- return nil
- }
- // Mapping url to pattern
- func (t *Table) Mapping(url string) (string, error) {
- paths, err := parse(url)
- if err != nil {
- return "", err
- }
- pattern := make([]string, 0, len(paths))
- root := t.root
- for _, path := range paths {
- next := root.mapping[path]
- if next == nil {
- nextFuzzy := root.mapping[fuzzy]
- nextOmitted := root.mapping[omitted]
- if nextFuzzy == nil && nextOmitted == nil {
- return "", nil
- }
- if nextOmitted != nil {
- pattern = append(pattern, omitted)
- return strings.Join(pattern, delimiter), nil
- }
- next = nextFuzzy
- path = fuzzy
- }
- root = next
- pattern = append(pattern, path)
- }
- if root.leaf {
- return strings.Join(pattern, delimiter), nil
- }
- return "", nil
- }
|