1package namespace 2 3import ( 4 "context" 5 "errors" 6 "strings" 7) 8 9type contextValues struct{} 10 11type Namespace struct { 12 ID string `json:"id"` 13 Path string `json:"path"` 14} 15 16const ( 17 RootNamespaceID = "root" 18) 19 20var ( 21 contextNamespace contextValues = struct{}{} 22 ErrNoNamespace error = errors.New("no namespace") 23 RootNamespace *Namespace = &Namespace{ 24 ID: RootNamespaceID, 25 Path: "", 26 } 27) 28 29func (n *Namespace) HasParent(possibleParent *Namespace) bool { 30 switch { 31 case n.Path == "": 32 return false 33 case possibleParent.Path == "": 34 return true 35 default: 36 return strings.HasPrefix(n.Path, possibleParent.Path) 37 } 38} 39 40func (n *Namespace) TrimmedPath(path string) string { 41 return strings.TrimPrefix(path, n.Path) 42} 43 44func ContextWithNamespace(ctx context.Context, ns *Namespace) context.Context { 45 return context.WithValue(ctx, contextNamespace, ns) 46} 47 48func RootContext(ctx context.Context) context.Context { 49 if ctx == nil { 50 return ContextWithNamespace(context.Background(), RootNamespace) 51 } 52 return ContextWithNamespace(ctx, RootNamespace) 53} 54 55// This function caches the ns to avoid doing a .Value lookup over and over, 56// because it's called a *lot* in the request critical path. .Value is 57// concurrency-safe so uses some kind of locking/atomicity, but it should never 58// be read before first write, plus we don't believe this will be called from 59// different goroutines, so it should be safe. 60func FromContext(ctx context.Context) (*Namespace, error) { 61 if ctx == nil { 62 return nil, errors.New("context was nil") 63 } 64 65 nsRaw := ctx.Value(contextNamespace) 66 if nsRaw == nil { 67 return nil, ErrNoNamespace 68 } 69 70 ns := nsRaw.(*Namespace) 71 if ns == nil { 72 return nil, ErrNoNamespace 73 } 74 75 return ns, nil 76} 77 78// Canonicalize trims any prefix '/' and adds a trailing '/' to the 79// provided string 80func Canonicalize(nsPath string) string { 81 if nsPath == "" { 82 return "" 83 } 84 85 // Canonicalize the path to not have a '/' prefix 86 nsPath = strings.TrimPrefix(nsPath, "/") 87 88 // Canonicalize the path to always having a '/' suffix 89 if !strings.HasSuffix(nsPath, "/") { 90 nsPath += "/" 91 } 92 93 return nsPath 94} 95 96func SplitIDFromString(input string) (string, string) { 97 prefix := "" 98 slashIdx := strings.LastIndex(input, "/") 99 100 switch { 101 case strings.HasPrefix(input, "b."): 102 prefix = "b." 103 input = input[2:] 104 105 case strings.HasPrefix(input, "s."): 106 prefix = "s." 107 input = input[2:] 108 109 case slashIdx > 0: 110 // Leases will never have a b./s. to start 111 if slashIdx == len(input)-1 { 112 return input, "" 113 } 114 prefix = input[:slashIdx+1] 115 input = input[slashIdx+1:] 116 } 117 118 idx := strings.LastIndex(input, ".") 119 if idx == -1 { 120 return prefix + input, "" 121 } 122 if idx == len(input)-1 { 123 return prefix + input, "" 124 } 125 126 return prefix + input[:idx], input[idx+1:] 127} 128