mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-11-04 07:12:25 -06:00 
			
		
		
		
	
		
			
				
	
	
		
			467 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			467 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Package denco provides fast URL router.
 | 
						|
package denco
 | 
						|
 | 
						|
import (
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"sort"
 | 
						|
	"strings"
 | 
						|
)
 | 
						|
 | 
						|
const (
 | 
						|
	// ParamCharacter is a special character for path parameter.
 | 
						|
	ParamCharacter = ':'
 | 
						|
 | 
						|
	// WildcardCharacter is a special character for wildcard path parameter.
 | 
						|
	WildcardCharacter = '*'
 | 
						|
 | 
						|
	// TerminationCharacter is a special character for end of path.
 | 
						|
	TerminationCharacter = '#'
 | 
						|
 | 
						|
	// SeparatorCharacter separates path segments.
 | 
						|
	SeparatorCharacter = '/'
 | 
						|
 | 
						|
	// PathParamCharacter indicates a RESTCONF path param
 | 
						|
	PathParamCharacter = '='
 | 
						|
 | 
						|
	// MaxSize is max size of records and internal slice.
 | 
						|
	MaxSize = (1 << 22) - 1
 | 
						|
)
 | 
						|
 | 
						|
// Router represents a URL router.
 | 
						|
type Router struct {
 | 
						|
	param *doubleArray
 | 
						|
	// SizeHint expects the maximum number of path parameters in records to Build.
 | 
						|
	// SizeHint will be used to determine the capacity of the memory to allocate.
 | 
						|
	// By default, SizeHint will be determined from given records to Build.
 | 
						|
	SizeHint int
 | 
						|
 | 
						|
	static map[string]interface{}
 | 
						|
}
 | 
						|
 | 
						|
// New returns a new Router.
 | 
						|
func New() *Router {
 | 
						|
	return &Router{
 | 
						|
		SizeHint: -1,
 | 
						|
		static:   make(map[string]interface{}),
 | 
						|
		param:    newDoubleArray(),
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// Lookup returns data and path parameters that associated with path.
 | 
						|
// params is a slice of the Param that arranged in the order in which parameters appeared.
 | 
						|
// e.g. when built routing path is "/path/to/:id/:name" and given path is "/path/to/1/alice". params order is [{"id": "1"}, {"name": "alice"}], not [{"name": "alice"}, {"id": "1"}].
 | 
						|
func (rt *Router) Lookup(path string) (data interface{}, params Params, found bool) {
 | 
						|
	if data, found = rt.static[path]; found {
 | 
						|
		return data, nil, true
 | 
						|
	}
 | 
						|
	if len(rt.param.node) == 1 {
 | 
						|
		return nil, nil, false
 | 
						|
	}
 | 
						|
	nd, params, found := rt.param.lookup(path, make([]Param, 0, rt.SizeHint), 1)
 | 
						|
	if !found {
 | 
						|
		return nil, nil, false
 | 
						|
	}
 | 
						|
	for i := 0; i < len(params); i++ {
 | 
						|
		params[i].Name = nd.paramNames[i]
 | 
						|
	}
 | 
						|
	return nd.data, params, true
 | 
						|
}
 | 
						|
 | 
						|
// Build builds URL router from records.
 | 
						|
func (rt *Router) Build(records []Record) error {
 | 
						|
	statics, params := makeRecords(records)
 | 
						|
	if len(params) > MaxSize {
 | 
						|
		return errors.New("denco: too many records")
 | 
						|
	}
 | 
						|
	if rt.SizeHint < 0 {
 | 
						|
		rt.SizeHint = 0
 | 
						|
		for _, p := range params {
 | 
						|
			size := 0
 | 
						|
			for _, k := range p.Key {
 | 
						|
				if k == ParamCharacter || k == WildcardCharacter {
 | 
						|
					size++
 | 
						|
				}
 | 
						|
			}
 | 
						|
			if size > rt.SizeHint {
 | 
						|
				rt.SizeHint = size
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	for _, r := range statics {
 | 
						|
		rt.static[r.Key] = r.Value
 | 
						|
	}
 | 
						|
	if err := rt.param.build(params, 1, 0, make(map[int]struct{})); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// Param represents name and value of path parameter.
 | 
						|
type Param struct {
 | 
						|
	Name  string
 | 
						|
	Value string
 | 
						|
}
 | 
						|
 | 
						|
// Params represents the name and value of path parameters.
 | 
						|
type Params []Param
 | 
						|
 | 
						|
// Get gets the first value associated with the given name.
 | 
						|
// If there are no values associated with the key, Get returns "".
 | 
						|
func (ps Params) Get(name string) string {
 | 
						|
	for _, p := range ps {
 | 
						|
		if p.Name == name {
 | 
						|
			return p.Value
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return ""
 | 
						|
}
 | 
						|
 | 
						|
type doubleArray struct {
 | 
						|
	bc   []baseCheck
 | 
						|
	node []*node
 | 
						|
}
 | 
						|
 | 
						|
func newDoubleArray() *doubleArray {
 | 
						|
	return &doubleArray{
 | 
						|
		bc:   []baseCheck{0},
 | 
						|
		node: []*node{nil}, // A start index is adjusting to 1 because 0 will be used as a mark of non-existent node.
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// baseCheck contains BASE, CHECK and Extra flags.
 | 
						|
// From the top, 22bits of BASE, 2bits of Extra flags and 8bits of CHECK.
 | 
						|
//
 | 
						|
//	BASE (22bit) | Extra flags (2bit) | CHECK (8bit)
 | 
						|
//
 | 
						|
// |----------------------|--|--------|
 | 
						|
// 32                    10  8         0
 | 
						|
type baseCheck uint32
 | 
						|
 | 
						|
func (bc baseCheck) Base() int {
 | 
						|
	return int(bc >> 10)
 | 
						|
}
 | 
						|
 | 
						|
func (bc *baseCheck) SetBase(base int) {
 | 
						|
	*bc |= baseCheck(base) << 10
 | 
						|
}
 | 
						|
 | 
						|
func (bc baseCheck) Check() byte {
 | 
						|
	return byte(bc)
 | 
						|
}
 | 
						|
 | 
						|
func (bc *baseCheck) SetCheck(check byte) {
 | 
						|
	*bc |= baseCheck(check)
 | 
						|
}
 | 
						|
 | 
						|
func (bc baseCheck) IsEmpty() bool {
 | 
						|
	return bc&0xfffffcff == 0
 | 
						|
}
 | 
						|
 | 
						|
func (bc baseCheck) IsSingleParam() bool {
 | 
						|
	return bc¶mTypeSingle == paramTypeSingle
 | 
						|
}
 | 
						|
 | 
						|
func (bc baseCheck) IsWildcardParam() bool {
 | 
						|
	return bc¶mTypeWildcard == paramTypeWildcard
 | 
						|
}
 | 
						|
 | 
						|
func (bc baseCheck) IsAnyParam() bool {
 | 
						|
	return bc¶mTypeAny != 0
 | 
						|
}
 | 
						|
 | 
						|
func (bc *baseCheck) SetSingleParam() {
 | 
						|
	*bc |= (1 << 8)
 | 
						|
}
 | 
						|
 | 
						|
func (bc *baseCheck) SetWildcardParam() {
 | 
						|
	*bc |= (1 << 9)
 | 
						|
}
 | 
						|
 | 
						|
const (
 | 
						|
	paramTypeSingle   = 0x0100
 | 
						|
	paramTypeWildcard = 0x0200
 | 
						|
	paramTypeAny      = 0x0300
 | 
						|
)
 | 
						|
 | 
						|
func (da *doubleArray) lookup(path string, params []Param, idx int) (*node, []Param, bool) {
 | 
						|
	indices := make([]uint64, 0, 1)
 | 
						|
	for i := 0; i < len(path); i++ {
 | 
						|
		if da.bc[idx].IsAnyParam() {
 | 
						|
			indices = append(indices, (uint64(i)<<32)|(uint64(idx)&0xffffffff))
 | 
						|
		}
 | 
						|
		c := path[i]
 | 
						|
		if idx = nextIndex(da.bc[idx].Base(), c); idx >= len(da.bc) || da.bc[idx].Check() != c {
 | 
						|
			goto BACKTRACKING
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if next := nextIndex(da.bc[idx].Base(), TerminationCharacter); next < len(da.bc) && da.bc[next].Check() == TerminationCharacter {
 | 
						|
		return da.node[da.bc[next].Base()], params, true
 | 
						|
	}
 | 
						|
 | 
						|
BACKTRACKING:
 | 
						|
	for j := len(indices) - 1; j >= 0; j-- {
 | 
						|
		i, idx := int(indices[j]>>32), int(indices[j]&0xffffffff)
 | 
						|
		if da.bc[idx].IsSingleParam() {
 | 
						|
			nextIdx := nextIndex(da.bc[idx].Base(), ParamCharacter)
 | 
						|
			if nextIdx >= len(da.bc) {
 | 
						|
				break
 | 
						|
			}
 | 
						|
 | 
						|
			next := NextSeparator(path, i)
 | 
						|
			nextParams := params
 | 
						|
			nextParams = append(nextParams, Param{Value: path[i:next]})
 | 
						|
			if nd, nextNextParams, found := da.lookup(path[next:], nextParams, nextIdx); found {
 | 
						|
				return nd, nextNextParams, true
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		if da.bc[idx].IsWildcardParam() {
 | 
						|
			nextIdx := nextIndex(da.bc[idx].Base(), WildcardCharacter)
 | 
						|
			nextParams := params
 | 
						|
			nextParams = append(nextParams, Param{Value: path[i:]})
 | 
						|
			return da.node[da.bc[nextIdx].Base()], nextParams, true
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return nil, nil, false
 | 
						|
}
 | 
						|
 | 
						|
// build builds double-array from records.
 | 
						|
func (da *doubleArray) build(srcs []*record, idx, depth int, usedBase map[int]struct{}) error {
 | 
						|
	sort.Stable(recordSlice(srcs))
 | 
						|
	base, siblings, leaf, err := da.arrange(srcs, idx, depth, usedBase)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	if leaf != nil {
 | 
						|
		nd, err := makeNode(leaf)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		da.bc[idx].SetBase(len(da.node))
 | 
						|
		da.node = append(da.node, nd)
 | 
						|
	}
 | 
						|
	for _, sib := range siblings {
 | 
						|
		da.setCheck(nextIndex(base, sib.c), sib.c)
 | 
						|
	}
 | 
						|
	for _, sib := range siblings {
 | 
						|
		records := srcs[sib.start:sib.end]
 | 
						|
		switch sib.c {
 | 
						|
		case ParamCharacter:
 | 
						|
			for _, r := range records {
 | 
						|
				next := NextSeparator(r.Key, depth+1)
 | 
						|
				name := r.Key[depth+1 : next]
 | 
						|
				r.paramNames = append(r.paramNames, name)
 | 
						|
				r.Key = r.Key[next:]
 | 
						|
			}
 | 
						|
			da.bc[idx].SetSingleParam()
 | 
						|
			if err := da.build(records, nextIndex(base, sib.c), 0, usedBase); err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
		case WildcardCharacter:
 | 
						|
			r := records[0]
 | 
						|
			name := r.Key[depth+1 : len(r.Key)-1]
 | 
						|
			r.paramNames = append(r.paramNames, name)
 | 
						|
			r.Key = ""
 | 
						|
			da.bc[idx].SetWildcardParam()
 | 
						|
			if err := da.build(records, nextIndex(base, sib.c), 0, usedBase); err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
		default:
 | 
						|
			if err := da.build(records, nextIndex(base, sib.c), depth+1, usedBase); err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// setBase sets BASE.
 | 
						|
func (da *doubleArray) setBase(i, base int) {
 | 
						|
	da.bc[i].SetBase(base)
 | 
						|
}
 | 
						|
 | 
						|
// setCheck sets CHECK.
 | 
						|
func (da *doubleArray) setCheck(i int, check byte) {
 | 
						|
	da.bc[i].SetCheck(check)
 | 
						|
}
 | 
						|
 | 
						|
// findEmptyIndex returns an index of unused BASE/CHECK node.
 | 
						|
func (da *doubleArray) findEmptyIndex(start int) int {
 | 
						|
	i := start
 | 
						|
	for ; i < len(da.bc); i++ {
 | 
						|
		if da.bc[i].IsEmpty() {
 | 
						|
			break
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return i
 | 
						|
}
 | 
						|
 | 
						|
// findBase returns good BASE.
 | 
						|
func (da *doubleArray) findBase(siblings []sibling, start int, usedBase map[int]struct{}) (base int) {
 | 
						|
	for idx, firstChar := start+1, siblings[0].c; ; idx = da.findEmptyIndex(idx + 1) {
 | 
						|
		base = nextIndex(idx, firstChar)
 | 
						|
		if _, used := usedBase[base]; used {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		i := 0
 | 
						|
		for ; i < len(siblings); i++ {
 | 
						|
			next := nextIndex(base, siblings[i].c)
 | 
						|
			if len(da.bc) <= next {
 | 
						|
				da.bc = append(da.bc, make([]baseCheck, next-len(da.bc)+1)...)
 | 
						|
			}
 | 
						|
			if !da.bc[next].IsEmpty() {
 | 
						|
				break
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if i == len(siblings) {
 | 
						|
			break
 | 
						|
		}
 | 
						|
	}
 | 
						|
	usedBase[base] = struct{}{}
 | 
						|
	return base
 | 
						|
}
 | 
						|
 | 
						|
func (da *doubleArray) arrange(records []*record, idx, depth int, usedBase map[int]struct{}) (base int, siblings []sibling, leaf *record, err error) {
 | 
						|
	siblings, leaf, err = makeSiblings(records, depth)
 | 
						|
	if err != nil {
 | 
						|
		return -1, nil, nil, err
 | 
						|
	}
 | 
						|
	if len(siblings) < 1 {
 | 
						|
		return -1, nil, leaf, nil
 | 
						|
	}
 | 
						|
	base = da.findBase(siblings, idx, usedBase)
 | 
						|
	if base > MaxSize {
 | 
						|
		return -1, nil, nil, errors.New("denco: too many elements of internal slice")
 | 
						|
	}
 | 
						|
	da.setBase(idx, base)
 | 
						|
	return base, siblings, leaf, err
 | 
						|
}
 | 
						|
 | 
						|
// node represents a node of Double-Array.
 | 
						|
type node struct {
 | 
						|
	data interface{}
 | 
						|
 | 
						|
	// Names of path parameters.
 | 
						|
	paramNames []string
 | 
						|
}
 | 
						|
 | 
						|
// makeNode returns a new node from record.
 | 
						|
func makeNode(r *record) (*node, error) {
 | 
						|
	dups := make(map[string]bool)
 | 
						|
	for _, name := range r.paramNames {
 | 
						|
		if dups[name] {
 | 
						|
			return nil, fmt.Errorf("denco: path parameter `%v' is duplicated in the key `%v'", name, r.Key)
 | 
						|
		}
 | 
						|
		dups[name] = true
 | 
						|
	}
 | 
						|
	return &node{data: r.Value, paramNames: r.paramNames}, nil
 | 
						|
}
 | 
						|
 | 
						|
// sibling represents an intermediate data of build for Double-Array.
 | 
						|
type sibling struct {
 | 
						|
	// An index of start of duplicated characters.
 | 
						|
	start int
 | 
						|
 | 
						|
	// An index of end of duplicated characters.
 | 
						|
	end int
 | 
						|
 | 
						|
	// A character of sibling.
 | 
						|
	c byte
 | 
						|
}
 | 
						|
 | 
						|
// nextIndex returns a next index of array of BASE/CHECK.
 | 
						|
func nextIndex(base int, c byte) int {
 | 
						|
	return base ^ int(c)
 | 
						|
}
 | 
						|
 | 
						|
// makeSiblings returns slice of sibling.
 | 
						|
func makeSiblings(records []*record, depth int) (sib []sibling, leaf *record, err error) {
 | 
						|
	var (
 | 
						|
		pc byte
 | 
						|
		n  int
 | 
						|
	)
 | 
						|
	for i, r := range records {
 | 
						|
		if len(r.Key) <= depth {
 | 
						|
			leaf = r
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		c := r.Key[depth]
 | 
						|
		switch {
 | 
						|
		case pc < c:
 | 
						|
			sib = append(sib, sibling{start: i, c: c})
 | 
						|
		case pc == c:
 | 
						|
			continue
 | 
						|
		default:
 | 
						|
			return nil, nil, errors.New("denco: BUG: routing table hasn't been sorted")
 | 
						|
		}
 | 
						|
		if n > 0 {
 | 
						|
			sib[n-1].end = i
 | 
						|
		}
 | 
						|
		pc = c
 | 
						|
		n++
 | 
						|
	}
 | 
						|
	if n == 0 {
 | 
						|
		return nil, leaf, nil
 | 
						|
	}
 | 
						|
	sib[n-1].end = len(records)
 | 
						|
	return sib, leaf, nil
 | 
						|
}
 | 
						|
 | 
						|
// Record represents a record data for router construction.
 | 
						|
type Record struct {
 | 
						|
	// Key for router construction.
 | 
						|
	Key string
 | 
						|
 | 
						|
	// Result value for Key.
 | 
						|
	Value interface{}
 | 
						|
}
 | 
						|
 | 
						|
// NewRecord returns a new Record.
 | 
						|
func NewRecord(key string, value interface{}) Record {
 | 
						|
	return Record{
 | 
						|
		Key:   key,
 | 
						|
		Value: value,
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// record represents a record that use to build the Double-Array.
 | 
						|
type record struct {
 | 
						|
	Record
 | 
						|
	paramNames []string
 | 
						|
}
 | 
						|
 | 
						|
// makeRecords returns the records that use to build Double-Arrays.
 | 
						|
func makeRecords(srcs []Record) (statics, params []*record) {
 | 
						|
	termChar := string(TerminationCharacter)
 | 
						|
	paramPrefix := string(SeparatorCharacter) + string(ParamCharacter)
 | 
						|
	wildcardPrefix := string(SeparatorCharacter) + string(WildcardCharacter)
 | 
						|
	restconfPrefix := string(PathParamCharacter) + string(ParamCharacter)
 | 
						|
	for _, r := range srcs {
 | 
						|
		if strings.Contains(r.Key, paramPrefix) || strings.Contains(r.Key, wildcardPrefix) || strings.Contains(r.Key, restconfPrefix) {
 | 
						|
			r.Key += termChar
 | 
						|
			params = append(params, &record{Record: r})
 | 
						|
		} else {
 | 
						|
			statics = append(statics, &record{Record: r})
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return statics, params
 | 
						|
}
 | 
						|
 | 
						|
// recordSlice represents a slice of Record for sort and implements the sort.Interface.
 | 
						|
type recordSlice []*record
 | 
						|
 | 
						|
// Len implements the sort.Interface.Len.
 | 
						|
func (rs recordSlice) Len() int {
 | 
						|
	return len(rs)
 | 
						|
}
 | 
						|
 | 
						|
// Less implements the sort.Interface.Less.
 | 
						|
func (rs recordSlice) Less(i, j int) bool {
 | 
						|
	return rs[i].Key < rs[j].Key
 | 
						|
}
 | 
						|
 | 
						|
// Swap implements the sort.Interface.Swap.
 | 
						|
func (rs recordSlice) Swap(i, j int) {
 | 
						|
	rs[i], rs[j] = rs[j], rs[i]
 | 
						|
}
 |