Resolve "Deprecate Providers package in favor of Worker"

This commit is contained in:
2025-10-09 15:53:02 +00:00
parent a1993c6c36
commit c512f51a57
26 changed files with 1695 additions and 2239 deletions

View File

@@ -1,4 +1,3 @@
// Package domain defines external message identifiers.
package domain
import (
@@ -9,56 +8,96 @@ import (
var ErrBadIdentifier = errors.New("identifier: invalid format")
// Identifier is a lightweight wrapper around the canonical key.
// Identifier is an immutable canonical key.
// Canonical form: "namespace::tag1[] . tag2[k=v;foo=bar] . tag3[]"
type Identifier struct{ key string }
// NewIdentifier builds a canonical key: "namespace::l1.l2[param=v;...] .l3".
// Labels and params are sorted for determinism.
func NewIdentifier(namespace string, labels map[string]map[string]string) Identifier {
// NewIdentifier builds a canonical key with strict validation.
// Tags and param keys are sorted. Tags with no params emit "name[]".
// Rejects on: empty namespace, bad tag names, bad keys/values.
func NewIdentifier(namespace string, tags map[string]map[string]string) (Identifier, error) {
ns := strings.TrimSpace(namespace)
if !validNamespace(ns) {
return Identifier{}, ErrBadIdentifier
}
// Validate and copy to protect immutability and reject dup keys early.
clean := make(map[string]map[string]string, len(tags))
for name, params := range tags {
n := strings.TrimSpace(name)
if !validIDTagName(n) {
return Identifier{}, ErrBadIdentifier
}
if _, exists := clean[n]; exists {
// impossible via map input, but keep the intent explicit
return Identifier{}, ErrBadIdentifier
}
if len(params) == 0 {
clean[n] = map[string]string{}
continue
}
dst := make(map[string]string, len(params))
for k, v := range params {
kk := strings.TrimSpace(k)
vv := strings.TrimSpace(v)
if !validParamKey(kk) || !validIDParamValue(vv) {
return Identifier{}, ErrBadIdentifier
}
if _, dup := dst[kk]; dup {
return Identifier{}, ErrBadIdentifier
}
dst[kk] = vv
}
clean[n] = dst
}
var b strings.Builder
// rough prealloc: ns + "::" + avg label + some params
b.Grow(len(namespace) + 2 + 10*len(labels) + 20)
// Rough capacity hint.
b.Grow(len(ns) + 2 + 16*len(clean) + 32)
// namespace
b.WriteString(namespace)
b.WriteString(ns)
b.WriteString("::")
// sort label names for stable output
labelNames := make([]string, 0, len(labels))
for name := range labels {
labelNames = append(labelNames, name)
// stable tag order
names := make([]string, 0, len(clean))
for n := range clean {
names = append(names, n)
}
sort.Strings(labelNames)
sort.Strings(names)
for i, name := range labelNames {
for i, name := range names {
if i > 0 {
b.WriteByte('.')
}
b.WriteString(name)
// params (sorted)
params := labels[name]
if len(params) > 0 {
keys := make([]string, 0, len(params))
for k := range params {
keys = append(keys, k)
}
sort.Strings(keys)
b.WriteByte('[')
for j, k := range keys {
if j > 0 {
b.WriteByte(';')
}
b.WriteString(k)
b.WriteByte('=')
b.WriteString(params[k])
}
b.WriteByte(']')
params := clean[name]
if len(params) == 0 {
b.WriteString("[]")
continue
}
// stable param order
keys := make([]string, 0, len(params))
for k := range params {
keys = append(keys, k)
}
sort.Strings(keys)
b.WriteByte('[')
for j, k := range keys {
if j > 0 {
b.WriteByte(';')
}
b.WriteString(k)
b.WriteByte('=')
b.WriteString(params[k])
}
b.WriteByte(']')
}
return Identifier{key: b.String()}
return Identifier{key: b.String()}, nil
}
// NewIdentifierFromRaw wraps a raw key without validation.
@@ -67,65 +106,156 @@ func NewIdentifierFromRaw(raw string) Identifier { return Identifier{key: raw} }
// Key returns the canonical key string.
func (id Identifier) Key() string { return id.key }
// Parse returns namespace and labels from Key.
// Format: "namespace::label1.label2[param=a;foo=bar].label3"
// Parse returns namespace and tags from Key.
// Accepts "tag" (bare) as "tag[]". Emits "name[]"/"[k=v;...]". First token wins on duplicates.
func (id Identifier) Parse() (string, map[string]map[string]string, error) {
k := id.key
// namespace
i := strings.Index(k, "::")
if i <= 0 {
return "", nil, ErrBadIdentifier
}
ns := k[:i]
if ns == "" {
ns := strings.TrimSpace(k[:i])
if !validNamespace(ns) {
return "", nil, ErrBadIdentifier
}
raw := k[i+2:]
lbls := make(map[string]map[string]string, 8)
tags := make(map[string]map[string]string, 8)
if raw == "" {
return ns, lbls, nil
return ns, tags, nil
}
for tok := range strings.SplitSeq(raw, ".") {
tok = strings.TrimSpace(tok)
if tok == "" {
continue
}
name, params, err := parseLabel(tok)
if err != nil || name == "" {
return "", nil, ErrBadIdentifier
}
lbls[name] = params
}
return ns, lbls, nil
}
// parseLabel parses "name" or "name[k=v;...]" into (name, params).
func parseLabel(tok string) (string, map[string]string, error) {
lb := strings.IndexByte(tok, '[')
if lb == -1 {
return tok, map[string]string{}, nil
}
rb := strings.LastIndexByte(tok, ']')
if rb == -1 || rb < lb {
return "", nil, ErrBadIdentifier
}
name := tok[:lb]
paramStr := tok[lb+1 : rb]
params := map[string]string{}
if paramStr == "" {
return name, params, nil
}
for pair := range strings.SplitSeq(paramStr, ";") {
if pair == "" {
lb := strings.IndexByte(tok, '[')
if lb == -1 {
// bare tag => empty params
name := strings.TrimSpace(tok)
if !validIDTagName(name) {
return "", nil, ErrBadIdentifier
}
if _, exists := tags[name]; !exists {
tags[name] = map[string]string{}
}
continue
}
kv := strings.SplitN(pair, "=", 2)
if len(kv) != 2 || kv[0] == "" {
rb := strings.LastIndexByte(tok, ']')
if rb == -1 || rb < lb || rb != len(tok)-1 {
return "", nil, ErrBadIdentifier
}
params[kv[0]] = kv[1]
name := strings.TrimSpace(tok[:lb])
if !validIDTagName(name) {
return "", nil, ErrBadIdentifier
}
// first tag wins
if _, exists := tags[name]; exists {
continue
}
body := tok[lb+1 : rb]
// forbid outer whitespace like "[ x=1 ]"
if body != strings.TrimSpace(body) {
return "", nil, ErrBadIdentifier
}
if body == "" {
tags[name] = map[string]string{}
continue
}
// parse "k=v;foo=bar"
params := make(map[string]string, 4)
for pair := range strings.SplitSeq(body, ";") {
pair = strings.TrimSpace(pair)
if pair == "" {
continue
}
kv := strings.SplitN(pair, "=", 2)
if len(kv) != 2 {
return "", nil, ErrBadIdentifier
}
key := strings.TrimSpace(kv[0])
val := strings.TrimSpace(kv[1])
if !validParamKey(key) || !validIDParamValue(val) || val == "" {
return "", nil, ErrBadIdentifier
}
// first key wins
if _, dup := params[key]; !dup {
params[key] = val
}
}
tags[name] = params
}
return name, params, nil
return ns, tags, nil
}
// --- validation helpers ---
func validNamespace(s string) bool {
if s == "" {
return false
}
for _, r := range s {
switch r {
case '[', ']', ':':
return false
}
if isSpace(r) {
return false
}
}
return true
}
func validIDTagName(s string) bool {
if s == "" {
return false
}
for _, r := range s {
switch r {
case '[', ']', '.', ':': // added ':'
return false
}
if isSpace(r) {
return false
}
}
return true
}
func validParamKey(s string) bool {
if s == "" {
return false
}
for _, r := range s {
switch r {
case '[', ']', ';', '=':
return false
}
if isSpace(r) {
return false
}
}
return true
}
func validIDParamValue(s string) bool {
// allow spaces; forbid only bracket, pair, and kv delimiters
for _, r := range s {
switch r {
case '[', ']', ';', '=':
return false
}
}
return true
}
func isSpace(r rune) bool { return r == ' ' || r == '\t' || r == '\n' || r == '\r' }