initial commit
This commit is contained in:
commit
80dbf39736
131
authreq/authreq.go
Normal file
131
authreq/authreq.go
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
package authreq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"crypto/ed25519"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"hash/fnv"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang-jwt/jwt/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
var SignatureLifetime = 30 * time.Minute
|
||||||
|
var AuthHeader = "Authorization"
|
||||||
|
|
||||||
|
func Sign(req *http.Request, key ed25519.PrivateKey) (*http.Request, error) {
|
||||||
|
pub := enc([]byte(key.Public().(ed25519.PublicKey)))
|
||||||
|
|
||||||
|
h := fnv.New128a()
|
||||||
|
fmt.Fprint(h, req.Method, req.URL.String())
|
||||||
|
|
||||||
|
if req.Body != nil {
|
||||||
|
b := &bytes.Buffer{}
|
||||||
|
w := io.MultiWriter(h, b)
|
||||||
|
_, err := io.Copy(w, req.Body)
|
||||||
|
if err != nil {
|
||||||
|
return req, err
|
||||||
|
}
|
||||||
|
req.Body = io.NopCloser(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
token := jwt.NewWithClaims(jwt.SigningMethodEdDSA, jwt.RegisteredClaims{
|
||||||
|
Subject: enc(h.Sum(nil)),
|
||||||
|
ExpiresAt: jwt.NewNumericDate(time.Now().Add(SignatureLifetime)),
|
||||||
|
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||||
|
Issuer: pub,
|
||||||
|
})
|
||||||
|
|
||||||
|
sig, err := token.SignedString(key)
|
||||||
|
if err != nil {
|
||||||
|
return req, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set(AuthHeader, sig)
|
||||||
|
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Authorization(hdlr http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
auth := req.Header.Get(AuthHeader)
|
||||||
|
if auth == "" {
|
||||||
|
rw.WriteHeader(http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h := fnv.New128a()
|
||||||
|
fmt.Fprint(h, req.Method, req.URL.String())
|
||||||
|
|
||||||
|
if req.Body != nil {
|
||||||
|
b := &bytes.Buffer{}
|
||||||
|
w := io.MultiWriter(h, b)
|
||||||
|
_, err := io.Copy(w, req.Body)
|
||||||
|
if err != nil {
|
||||||
|
rw.WriteHeader(http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
subject := enc(h.Sum(nil))
|
||||||
|
token, err := jwt.ParseWithClaims(
|
||||||
|
string(auth),
|
||||||
|
&jwt.RegisteredClaims{},
|
||||||
|
func(tok *jwt.Token) (any, error) {
|
||||||
|
c, ok := tok.Claims.(*jwt.RegisteredClaims)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("wrong type of claim")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub, err := dec(c.Issuer)
|
||||||
|
return ed25519.PublicKey(pub), err
|
||||||
|
},
|
||||||
|
jwt.WithValidMethods([]string{jwt.SigningMethodEdDSA.Alg()}),
|
||||||
|
jwt.WithJSONNumber(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
rw.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c, ok := token.Claims.(*jwt.RegisteredClaims)
|
||||||
|
if !ok {
|
||||||
|
rw.WriteHeader(http.StatusUnprocessableEntity)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req = req.WithContext(context.WithValue(req.Context(), contextKey, c))
|
||||||
|
|
||||||
|
if c.Subject != subject {
|
||||||
|
rw.WriteHeader(http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
hdlr.ServeHTTP(rw, req)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func enc(b []byte) string {
|
||||||
|
return base64.RawURLEncoding.EncodeToString(b)
|
||||||
|
}
|
||||||
|
func dec(s string) ([]byte, error) {
|
||||||
|
s = strings.TrimSpace(s)
|
||||||
|
return base64.RawURLEncoding.DecodeString(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
var contextKey = struct{ name string }{"jwtClaim"}
|
||||||
|
|
||||||
|
func FromContext(ctx context.Context) *jwt.RegisteredClaims {
|
||||||
|
if v := ctx.Value(contextKey); v != nil {
|
||||||
|
if c, ok := v.(*jwt.RegisteredClaims); ok {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
104
authreq/authreq_test.go
Normal file
104
authreq/authreq_test.go
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
package authreq_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ed25519"
|
||||||
|
"encoding/base64"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/matryer/is"
|
||||||
|
"go.sour.is/pkg/authreq"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGETRequest(t *testing.T) {
|
||||||
|
is := is.New(t)
|
||||||
|
|
||||||
|
pub, priv, err := ed25519.GenerateKey(nil)
|
||||||
|
is.NoErr(err)
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodGet, "http://example.com/"+enc(pub)+"/test?q=test", nil)
|
||||||
|
is.NoErr(err)
|
||||||
|
|
||||||
|
req, err = authreq.Sign(req, priv)
|
||||||
|
is.NoErr(err)
|
||||||
|
|
||||||
|
t.Log(enc(pub))
|
||||||
|
t.Log(req.Header.Get(authreq.AuthHeader))
|
||||||
|
|
||||||
|
var hdlr http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
c := authreq.FromContext(r.Context())
|
||||||
|
if c == nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(req.URL.Path, c.Issuer) {
|
||||||
|
w.WriteHeader(http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
hdlr = authreq.Authorization(hdlr)
|
||||||
|
|
||||||
|
rw := httptest.NewRecorder()
|
||||||
|
|
||||||
|
hdlr.ServeHTTP(rw, req)
|
||||||
|
|
||||||
|
is.Equal(rw.Code, http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPOSTRequest(t *testing.T) {
|
||||||
|
is := is.New(t)
|
||||||
|
|
||||||
|
content := "this is post!"
|
||||||
|
|
||||||
|
pub, priv, err := ed25519.GenerateKey(nil)
|
||||||
|
is.NoErr(err)
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodPost, "http://example.com/"+enc(pub)+"/test?q=test", strings.NewReader(content))
|
||||||
|
is.NoErr(err)
|
||||||
|
|
||||||
|
req, err = authreq.Sign(req, priv)
|
||||||
|
is.NoErr(err)
|
||||||
|
|
||||||
|
t.Log(enc(pub))
|
||||||
|
t.Log(req.Header.Get(authreq.AuthHeader))
|
||||||
|
|
||||||
|
var hdlr http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
c := authreq.FromContext(r.Context())
|
||||||
|
if c == nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
contentCheck, err := io.ReadAll(r.Body)
|
||||||
|
r.Body.Close()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Log(string(contentCheck))
|
||||||
|
if !strings.Contains(req.URL.Path, c.Issuer) {
|
||||||
|
w.WriteHeader(http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
hdlr = authreq.Authorization(hdlr)
|
||||||
|
|
||||||
|
rw := httptest.NewRecorder()
|
||||||
|
|
||||||
|
hdlr.ServeHTTP(rw, req)
|
||||||
|
|
||||||
|
is.Equal(rw.Code, http.StatusOK)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func enc(b []byte) string {
|
||||||
|
return base64.RawURLEncoding.EncodeToString(b)
|
||||||
|
}
|
238
cache/cache.go
vendored
Normal file
238
cache/cache.go
vendored
Normal file
|
@ -0,0 +1,238 @@
|
||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DefaultEvictedBufferSize defines the default buffer size to store evicted key/val
|
||||||
|
DefaultEvictedBufferSize = 16
|
||||||
|
)
|
||||||
|
|
||||||
|
// Cache is a thread-safe fixed size LRU cache.
|
||||||
|
type Cache[K comparable, V any] struct {
|
||||||
|
lru *LRU[K, V]
|
||||||
|
evictedKeys []K
|
||||||
|
evictedVals []V
|
||||||
|
onEvictedCB func(ctx context.Context, k K, v V)
|
||||||
|
lock sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates an LRU of the given size.
|
||||||
|
func NewCache[K comparable, V any](size int) (*Cache[K, V], error) {
|
||||||
|
return NewWithEvict[K, V](size, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWithEvict constructs a fixed size cache with the given eviction
|
||||||
|
// callback.
|
||||||
|
func NewWithEvict[K comparable, V any](size int, onEvicted func(context.Context, K, V)) (c *Cache[K, V], err error) {
|
||||||
|
// create a cache with default settings
|
||||||
|
c = &Cache[K, V]{
|
||||||
|
onEvictedCB: onEvicted,
|
||||||
|
}
|
||||||
|
if onEvicted != nil {
|
||||||
|
c.initEvictBuffers()
|
||||||
|
onEvicted = c.onEvicted
|
||||||
|
}
|
||||||
|
c.lru, err = NewLRU(size, onEvicted)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cache[K, V]) initEvictBuffers() {
|
||||||
|
c.evictedKeys = make([]K, 0, DefaultEvictedBufferSize)
|
||||||
|
c.evictedVals = make([]V, 0, DefaultEvictedBufferSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// onEvicted save evicted key/val and sent in externally registered callback
|
||||||
|
// outside of critical section
|
||||||
|
func (c *Cache[K, V]) onEvicted(ctx context.Context, k K, v V) {
|
||||||
|
c.evictedKeys = append(c.evictedKeys, k)
|
||||||
|
c.evictedVals = append(c.evictedVals, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Purge is used to completely clear the cache.
|
||||||
|
func (c *Cache[K, V]) Purge(ctx context.Context) {
|
||||||
|
var ks []K
|
||||||
|
var vs []V
|
||||||
|
c.lock.Lock()
|
||||||
|
c.lru.Purge(ctx)
|
||||||
|
if c.onEvictedCB != nil && len(c.evictedKeys) > 0 {
|
||||||
|
ks, vs = c.evictedKeys, c.evictedVals
|
||||||
|
c.initEvictBuffers()
|
||||||
|
}
|
||||||
|
c.lock.Unlock()
|
||||||
|
// invoke callback outside of critical section
|
||||||
|
if c.onEvictedCB != nil {
|
||||||
|
for i := 0; i < len(ks); i++ {
|
||||||
|
c.onEvictedCB(ctx, ks[i], vs[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add adds a value to the cache. Returns true if an eviction occurred.
|
||||||
|
func (c *Cache[K, V]) Add(ctx context.Context, key K, value V) (evicted bool) {
|
||||||
|
var k K
|
||||||
|
var v V
|
||||||
|
c.lock.Lock()
|
||||||
|
evicted = c.lru.Add(ctx, key, value)
|
||||||
|
if c.onEvictedCB != nil && evicted {
|
||||||
|
k, v = c.evictedKeys[0], c.evictedVals[0]
|
||||||
|
c.evictedKeys, c.evictedVals = c.evictedKeys[:0], c.evictedVals[:0]
|
||||||
|
}
|
||||||
|
c.lock.Unlock()
|
||||||
|
if c.onEvictedCB != nil && evicted {
|
||||||
|
c.onEvictedCB(ctx, k, v)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get looks up a key's value from the cache.
|
||||||
|
func (c *Cache[K, V]) Get(key K) (value *V, ok bool) {
|
||||||
|
c.lock.Lock()
|
||||||
|
value, ok = c.lru.Get(key)
|
||||||
|
c.lock.Unlock()
|
||||||
|
return value, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contains checks if a key is in the cache, without updating the
|
||||||
|
// recent-ness or deleting it for being stale.
|
||||||
|
func (c *Cache[K, V]) Contains(key K) bool {
|
||||||
|
c.lock.RLock()
|
||||||
|
containKey := c.lru.Contains(key)
|
||||||
|
c.lock.RUnlock()
|
||||||
|
return containKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// Peek returns the key value (or undefined if not found) without updating
|
||||||
|
// the "recently used"-ness of the key.
|
||||||
|
func (c *Cache[K, V]) Peek(key K) (value *V, ok bool) {
|
||||||
|
c.lock.RLock()
|
||||||
|
value, ok = c.lru.Peek(key)
|
||||||
|
c.lock.RUnlock()
|
||||||
|
return value, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContainsOrAdd checks if a key is in the cache without updating the
|
||||||
|
// recent-ness or deleting it for being stale, and if not, adds the value.
|
||||||
|
// Returns whether found and whether an eviction occurred.
|
||||||
|
func (c *Cache[K, V]) ContainsOrAdd(ctx context.Context, key K, value V) (ok, evicted bool) {
|
||||||
|
var k K
|
||||||
|
var v V
|
||||||
|
c.lock.Lock()
|
||||||
|
if c.lru.Contains(key) {
|
||||||
|
c.lock.Unlock()
|
||||||
|
return true, false
|
||||||
|
}
|
||||||
|
evicted = c.lru.Add(ctx, key, value)
|
||||||
|
if c.onEvictedCB != nil && evicted {
|
||||||
|
k, v = c.evictedKeys[0], c.evictedVals[0]
|
||||||
|
c.evictedKeys, c.evictedVals = c.evictedKeys[:0], c.evictedVals[:0]
|
||||||
|
}
|
||||||
|
c.lock.Unlock()
|
||||||
|
if c.onEvictedCB != nil && evicted {
|
||||||
|
c.onEvictedCB(ctx, k, v)
|
||||||
|
}
|
||||||
|
return false, evicted
|
||||||
|
}
|
||||||
|
|
||||||
|
// PeekOrAdd checks if a key is in the cache without updating the
|
||||||
|
// recent-ness or deleting it for being stale, and if not, adds the value.
|
||||||
|
// Returns whether found and whether an eviction occurred.
|
||||||
|
func (c *Cache[K, V]) PeekOrAdd(ctx context.Context, key K, value V) (previous *V, ok, evicted bool) {
|
||||||
|
var k K
|
||||||
|
var v V
|
||||||
|
c.lock.Lock()
|
||||||
|
previous, ok = c.lru.Peek(key)
|
||||||
|
if ok {
|
||||||
|
c.lock.Unlock()
|
||||||
|
return previous, true, false
|
||||||
|
}
|
||||||
|
evicted = c.lru.Add(ctx, key, value)
|
||||||
|
if c.onEvictedCB != nil && evicted {
|
||||||
|
k, v = c.evictedKeys[0], c.evictedVals[0]
|
||||||
|
c.evictedKeys, c.evictedVals = c.evictedKeys[:0], c.evictedVals[:0]
|
||||||
|
}
|
||||||
|
c.lock.Unlock()
|
||||||
|
if c.onEvictedCB != nil && evicted {
|
||||||
|
c.onEvictedCB(ctx, k, v)
|
||||||
|
}
|
||||||
|
return nil, false, evicted
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove removes the provided key from the cache.
|
||||||
|
func (c *Cache[K, V]) Remove(ctx context.Context, key K) (present bool) {
|
||||||
|
var k K
|
||||||
|
var v V
|
||||||
|
c.lock.Lock()
|
||||||
|
present = c.lru.Remove(ctx, key)
|
||||||
|
if c.onEvictedCB != nil && present {
|
||||||
|
k, v = c.evictedKeys[0], c.evictedVals[0]
|
||||||
|
c.evictedKeys, c.evictedVals = c.evictedKeys[:0], c.evictedVals[:0]
|
||||||
|
}
|
||||||
|
c.lock.Unlock()
|
||||||
|
if c.onEvictedCB != nil && present {
|
||||||
|
c.onEvicted(ctx, k, v)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resize changes the cache size.
|
||||||
|
func (c *Cache[K, V]) Resize(ctx context.Context, size int) (evicted int) {
|
||||||
|
var ks []K
|
||||||
|
var vs []V
|
||||||
|
c.lock.Lock()
|
||||||
|
evicted = c.lru.Resize(ctx, size)
|
||||||
|
if c.onEvictedCB != nil && evicted > 0 {
|
||||||
|
ks, vs = c.evictedKeys, c.evictedVals
|
||||||
|
c.initEvictBuffers()
|
||||||
|
}
|
||||||
|
c.lock.Unlock()
|
||||||
|
if c.onEvictedCB != nil && evicted > 0 {
|
||||||
|
for i := 0; i < len(ks); i++ {
|
||||||
|
c.onEvictedCB(ctx, ks[i], vs[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return evicted
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveOldest removes the oldest item from the cache.
|
||||||
|
func (c *Cache[K, V]) RemoveOldest(ctx context.Context) (key *K, value *V, ok bool) {
|
||||||
|
var k K
|
||||||
|
var v V
|
||||||
|
c.lock.Lock()
|
||||||
|
key, value, ok = c.lru.RemoveOldest(ctx)
|
||||||
|
if c.onEvictedCB != nil && ok {
|
||||||
|
k, v = c.evictedKeys[0], c.evictedVals[0]
|
||||||
|
c.evictedKeys, c.evictedVals = c.evictedKeys[:0], c.evictedVals[:0]
|
||||||
|
}
|
||||||
|
c.lock.Unlock()
|
||||||
|
if c.onEvictedCB != nil && ok {
|
||||||
|
c.onEvictedCB(ctx, k, v)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOldest returns the oldest entry
|
||||||
|
func (c *Cache[K, V]) GetOldest() (key *K, value *V, ok bool) {
|
||||||
|
c.lock.RLock()
|
||||||
|
key, value, ok = c.lru.GetOldest()
|
||||||
|
c.lock.RUnlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keys returns a slice of the keys in the cache, from oldest to newest.
|
||||||
|
func (c *Cache[K, V]) Keys() []K {
|
||||||
|
c.lock.RLock()
|
||||||
|
keys := c.lru.Keys()
|
||||||
|
c.lock.RUnlock()
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the number of items in the cache.
|
||||||
|
func (c *Cache[K, V]) Len() int {
|
||||||
|
c.lock.RLock()
|
||||||
|
length := c.lru.Len()
|
||||||
|
c.lock.RUnlock()
|
||||||
|
return length
|
||||||
|
}
|
131
cache/cache_test.go
vendored
Normal file
131
cache/cache_test.go
vendored
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
package cache_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/matryer/is"
|
||||||
|
"go.sour.is/pkg/cache"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCache(t *testing.T) {
|
||||||
|
is := is.New(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
c, err := cache.NewCache[string, int](1)
|
||||||
|
is.NoErr(err)
|
||||||
|
|
||||||
|
evicted := c.Add(ctx, "one", 1)
|
||||||
|
is.True(!evicted)
|
||||||
|
|
||||||
|
is.True(c.Contains("one"))
|
||||||
|
_, ok := c.Peek("one")
|
||||||
|
is.True(ok)
|
||||||
|
|
||||||
|
ok, evicted = c.ContainsOrAdd(ctx, "two", 2)
|
||||||
|
is.True(!ok)
|
||||||
|
is.True(evicted)
|
||||||
|
|
||||||
|
is.True(!c.Contains("one"))
|
||||||
|
is.True(c.Contains("two"))
|
||||||
|
|
||||||
|
is.Equal(c.Len(), 1)
|
||||||
|
is.Equal(c.Keys(), []string{"two"})
|
||||||
|
|
||||||
|
v, ok := c.Get("two")
|
||||||
|
is.True(ok)
|
||||||
|
is.Equal(*v, 2)
|
||||||
|
|
||||||
|
evictCount := c.Resize(ctx, 100)
|
||||||
|
is.True(evictCount == 0)
|
||||||
|
|
||||||
|
c.Add(ctx, "one", 1)
|
||||||
|
|
||||||
|
prev, ok, evicted := c.PeekOrAdd(ctx, "three", 3)
|
||||||
|
is.True(!ok)
|
||||||
|
is.True(!evicted)
|
||||||
|
is.Equal(prev, nil)
|
||||||
|
|
||||||
|
key, value, ok := c.GetOldest()
|
||||||
|
is.True(ok)
|
||||||
|
is.Equal(*key, "two")
|
||||||
|
is.Equal(*value, 2)
|
||||||
|
|
||||||
|
key, value, ok = c.RemoveOldest(ctx)
|
||||||
|
is.True(ok)
|
||||||
|
is.Equal(*key, "two")
|
||||||
|
is.Equal(*value, 2)
|
||||||
|
|
||||||
|
c.Remove(ctx, "one")
|
||||||
|
|
||||||
|
c.Purge(ctx)
|
||||||
|
is.True(!c.Contains("three"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCacheWithEvict(t *testing.T) {
|
||||||
|
is := is.New(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
evictions := 0
|
||||||
|
|
||||||
|
c, err := cache.NewWithEvict(1, func(ctx context.Context, s string, i int) { evictions++ })
|
||||||
|
is.NoErr(err)
|
||||||
|
|
||||||
|
key, value, ok := c.GetOldest()
|
||||||
|
is.True(!ok)
|
||||||
|
is.Equal(key, nil)
|
||||||
|
is.Equal(value, nil)
|
||||||
|
|
||||||
|
key, value, ok = c.RemoveOldest(ctx)
|
||||||
|
is.True(!ok)
|
||||||
|
is.Equal(key, nil)
|
||||||
|
is.Equal(value, nil)
|
||||||
|
|
||||||
|
evicted := c.Add(ctx, "one", 1)
|
||||||
|
is.True(!evicted)
|
||||||
|
|
||||||
|
is.True(c.Contains("one"))
|
||||||
|
_, ok = c.Peek("one")
|
||||||
|
is.True(ok)
|
||||||
|
|
||||||
|
ok, evicted = c.ContainsOrAdd(ctx, "two", 2)
|
||||||
|
is.True(!ok)
|
||||||
|
is.True(evicted)
|
||||||
|
|
||||||
|
is.True(!c.Contains("one"))
|
||||||
|
is.True(c.Contains("two"))
|
||||||
|
|
||||||
|
is.Equal(c.Len(), 1)
|
||||||
|
is.Equal(c.Keys(), []string{"two"})
|
||||||
|
|
||||||
|
v, ok := c.Get("two")
|
||||||
|
is.True(ok)
|
||||||
|
is.Equal(*v, 2)
|
||||||
|
|
||||||
|
evictCount := c.Resize(ctx, 100)
|
||||||
|
is.True(evictCount == 0)
|
||||||
|
|
||||||
|
c.Add(ctx, "one", 1)
|
||||||
|
|
||||||
|
prev, ok, evicted := c.PeekOrAdd(ctx, "three", 3)
|
||||||
|
is.True(!ok)
|
||||||
|
is.True(!evicted)
|
||||||
|
is.Equal(prev, nil)
|
||||||
|
|
||||||
|
key, value, ok = c.GetOldest()
|
||||||
|
is.True(ok)
|
||||||
|
is.Equal(*key, "two")
|
||||||
|
is.Equal(*value, 2)
|
||||||
|
|
||||||
|
key, value, ok = c.RemoveOldest(ctx)
|
||||||
|
is.True(ok)
|
||||||
|
is.Equal(*key, "two")
|
||||||
|
is.Equal(*value, 2)
|
||||||
|
|
||||||
|
c.Resize(ctx, 1)
|
||||||
|
|
||||||
|
c.Purge(ctx)
|
||||||
|
is.True(!c.Contains("three"))
|
||||||
|
|
||||||
|
is.Equal(evictions, 4)
|
||||||
|
}
|
235
cache/list.go
vendored
Normal file
235
cache/list.go
vendored
Normal file
|
@ -0,0 +1,235 @@
|
||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package list implements a doubly linked list.
|
||||||
|
//
|
||||||
|
// To iterate over a list (where l is a *List):
|
||||||
|
//
|
||||||
|
// for e := l.Front(); e != nil; e = e.Next() {
|
||||||
|
// // do something with e.Value
|
||||||
|
// }
|
||||||
|
package cache
|
||||||
|
|
||||||
|
// Element is an element of a linked list.
|
||||||
|
type Element[V any] struct {
|
||||||
|
// Next and previous pointers in the doubly-linked list of elements.
|
||||||
|
// To simplify the implementation, internally a list l is implemented
|
||||||
|
// as a ring, such that &l.root is both the next element of the last
|
||||||
|
// list element (l.Back()) and the previous element of the first list
|
||||||
|
// element (l.Front()).
|
||||||
|
next, prev *Element[V]
|
||||||
|
|
||||||
|
// The list to which this element belongs.
|
||||||
|
list *List[V]
|
||||||
|
|
||||||
|
// The value stored with this element.
|
||||||
|
Value V
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next returns the next list element or nil.
|
||||||
|
func (e *Element[V]) Next() *Element[V] {
|
||||||
|
if p := e.next; e.list != nil && p != &e.list.root {
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prev returns the previous list element or nil.
|
||||||
|
func (e *Element[V]) Prev() *Element[V] {
|
||||||
|
if p := e.prev; e.list != nil && p != &e.list.root {
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// List represents a doubly linked list.
|
||||||
|
// The zero value for List is an empty list ready to use.
|
||||||
|
type List[V any] struct {
|
||||||
|
root Element[V] // sentinel list element, only &root, root.prev, and root.next are used
|
||||||
|
len int // current list length excluding (this) sentinel element
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init initializes or clears list l.
|
||||||
|
func (l *List[V]) Init() *List[V] {
|
||||||
|
l.root.next = &l.root
|
||||||
|
l.root.prev = &l.root
|
||||||
|
l.len = 0
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewList returns an initialized list.
|
||||||
|
func NewList[V any]() *List[V] { return new(List[V]).Init() }
|
||||||
|
|
||||||
|
// Len returns the number of elements of list l.
|
||||||
|
// The complexity is O(1).
|
||||||
|
func (l *List[V]) Len() int { return l.len }
|
||||||
|
|
||||||
|
// Front returns the first element of list l or nil if the list is empty.
|
||||||
|
func (l *List[V]) Front() *Element[V] {
|
||||||
|
if l.len == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return l.root.next
|
||||||
|
}
|
||||||
|
|
||||||
|
// Back returns the last element of list l or nil if the list is empty.
|
||||||
|
func (l *List[V]) Back() *Element[V] {
|
||||||
|
if l.len == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return l.root.prev
|
||||||
|
}
|
||||||
|
|
||||||
|
// lazyInit lazily initializes a zero List value.
|
||||||
|
func (l *List[V]) lazyInit() {
|
||||||
|
if l.root.next == nil {
|
||||||
|
l.Init()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert inserts e after at, increments l.len, and returns e.
|
||||||
|
func (l *List[V]) insert(e, at *Element[V]) *Element[V] {
|
||||||
|
e.prev = at
|
||||||
|
e.next = at.next
|
||||||
|
e.prev.next = e
|
||||||
|
e.next.prev = e
|
||||||
|
e.list = l
|
||||||
|
l.len++
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// insertValue is a convenience wrapper for insert(&Element{Value: v}, at).
|
||||||
|
func (l *List[V]) insertValue(v V, at *Element[V]) *Element[V] {
|
||||||
|
return l.insert(&Element[V]{Value: v}, at)
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove removes e from its list, decrements l.len
|
||||||
|
func (l *List[V]) remove(e *Element[V]) {
|
||||||
|
e.prev.next = e.next
|
||||||
|
e.next.prev = e.prev
|
||||||
|
e.next = nil // avoid memory leaks
|
||||||
|
e.prev = nil // avoid memory leaks
|
||||||
|
e.list = nil
|
||||||
|
l.len--
|
||||||
|
}
|
||||||
|
|
||||||
|
// move moves e to next to at.
|
||||||
|
func (l *List[V]) move(e, at *Element[V]) {
|
||||||
|
if e == at {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
e.prev.next = e.next
|
||||||
|
e.next.prev = e.prev
|
||||||
|
|
||||||
|
e.prev = at
|
||||||
|
e.next = at.next
|
||||||
|
e.prev.next = e
|
||||||
|
e.next.prev = e
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove removes e from l if e is an element of list l.
|
||||||
|
// It returns the element value e.Value.
|
||||||
|
// The element must not be nil.
|
||||||
|
func (l *List[V]) Remove(e *Element[V]) any {
|
||||||
|
if e.list == l {
|
||||||
|
// if e.list == l, l must have been initialized when e was inserted
|
||||||
|
// in l or l == nil (e is a zero Element) and l.remove will crash
|
||||||
|
l.remove(e)
|
||||||
|
}
|
||||||
|
return e.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// PushFront inserts a new element e with value v at the front of list l and returns e.
|
||||||
|
func (l *List[V]) PushFront(v V) *Element[V] {
|
||||||
|
l.lazyInit()
|
||||||
|
return l.insertValue(v, &l.root)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PushBack inserts a new element e with value v at the back of list l and returns e.
|
||||||
|
func (l *List[V]) PushBack(v V) *Element[V] {
|
||||||
|
l.lazyInit()
|
||||||
|
return l.insertValue(v, l.root.prev)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InsertBefore inserts a new element e with value v immediately before mark and returns e.
|
||||||
|
// If mark is not an element of l, the list is not modified.
|
||||||
|
// The mark must not be nil.
|
||||||
|
func (l *List[V]) InsertBefore(v V, mark *Element[V]) *Element[V] {
|
||||||
|
if mark.list != l {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// see comment in List.Remove about initialization of l
|
||||||
|
return l.insertValue(v, mark.prev)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InsertAfter inserts a new element e with value v immediately after mark and returns e.
|
||||||
|
// If mark is not an element of l, the list is not modified.
|
||||||
|
// The mark must not be nil.
|
||||||
|
func (l *List[V]) InsertAfter(v V, mark *Element[V]) *Element[V] {
|
||||||
|
if mark.list != l {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// see comment in List.Remove about initialization of l
|
||||||
|
return l.insertValue(v, mark)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MoveToFront moves element e to the front of list l.
|
||||||
|
// If e is not an element of l, the list is not modified.
|
||||||
|
// The element must not be nil.
|
||||||
|
func (l *List[V]) MoveToFront(e *Element[V]) {
|
||||||
|
if e.list != l || l.root.next == e {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// see comment in List.Remove about initialization of l
|
||||||
|
l.move(e, &l.root)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MoveToBack moves element e to the back of list l.
|
||||||
|
// If e is not an element of l, the list is not modified.
|
||||||
|
// The element must not be nil.
|
||||||
|
func (l *List[V]) MoveToBack(e *Element[V]) {
|
||||||
|
if e.list != l || l.root.prev == e {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// see comment in List.Remove about initialization of l
|
||||||
|
l.move(e, l.root.prev)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MoveBefore moves element e to its new position before mark.
|
||||||
|
// If e or mark is not an element of l, or e == mark, the list is not modified.
|
||||||
|
// The element and mark must not be nil.
|
||||||
|
func (l *List[V]) MoveBefore(e, mark *Element[V]) {
|
||||||
|
if e.list != l || e == mark || mark.list != l {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l.move(e, mark.prev)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MoveAfter moves element e to its new position after mark.
|
||||||
|
// If e or mark is not an element of l, or e == mark, the list is not modified.
|
||||||
|
// The element and mark must not be nil.
|
||||||
|
func (l *List[V]) MoveAfter(e, mark *Element[V]) {
|
||||||
|
if e.list != l || e == mark || mark.list != l {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l.move(e, mark)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PushBackList inserts a copy of another list at the back of list l.
|
||||||
|
// The lists l and other may be the same. They must not be nil.
|
||||||
|
func (l *List[V]) PushBackList(other *List[V]) {
|
||||||
|
l.lazyInit()
|
||||||
|
for i, e := other.Len(), other.Front(); i > 0; i, e = i-1, e.Next() {
|
||||||
|
l.insertValue(e.Value, l.root.prev)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PushFrontList inserts a copy of another list at the front of list l.
|
||||||
|
// The lists l and other may be the same. They must not be nil.
|
||||||
|
func (l *List[V]) PushFrontList(other *List[V]) {
|
||||||
|
l.lazyInit()
|
||||||
|
for i, e := other.Len(), other.Back(); i > 0; i, e = i-1, e.Prev() {
|
||||||
|
l.insertValue(e.Value, &l.root)
|
||||||
|
}
|
||||||
|
}
|
175
cache/lru.go
vendored
Normal file
175
cache/lru.go
vendored
Normal file
|
@ -0,0 +1,175 @@
|
||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EvictCallback is used to get a callback when a cache entry is evicted
|
||||||
|
type EvictCallback[K comparable, V any] func(context.Context, K, V)
|
||||||
|
|
||||||
|
// LRU implements a non-thread safe fixed size LRU cache
|
||||||
|
type LRU[K comparable, V any] struct {
|
||||||
|
size int
|
||||||
|
evictList *List[entry[K, V]]
|
||||||
|
items map[K]*Element[entry[K, V]]
|
||||||
|
onEvict EvictCallback[K, V]
|
||||||
|
}
|
||||||
|
|
||||||
|
// entry is used to hold a value in the evictList
|
||||||
|
type entry[K comparable, V any] struct {
|
||||||
|
key K
|
||||||
|
value V
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLRU constructs an LRU of the given size
|
||||||
|
func NewLRU[K comparable, V any](size int, onEvict EvictCallback[K, V]) (*LRU[K, V], error) {
|
||||||
|
if size <= 0 {
|
||||||
|
return nil, errors.New("must provide a positive size")
|
||||||
|
}
|
||||||
|
c := &LRU[K, V]{
|
||||||
|
size: size,
|
||||||
|
evictList: NewList[entry[K, V]](),
|
||||||
|
items: make(map[K]*Element[entry[K, V]]),
|
||||||
|
onEvict: onEvict,
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Purge is used to completely clear the cache.
|
||||||
|
func (c *LRU[K, V]) Purge(ctx context.Context) {
|
||||||
|
for k, v := range c.items {
|
||||||
|
if c.onEvict != nil {
|
||||||
|
c.onEvict(ctx, k, v.Value.value)
|
||||||
|
}
|
||||||
|
delete(c.items, k)
|
||||||
|
}
|
||||||
|
c.evictList.Init()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add adds a value to the cache. Returns true if an eviction occurred.
|
||||||
|
func (c *LRU[K, V]) Add(ctx context.Context, key K, value V) (evicted bool) {
|
||||||
|
// Check for existing item
|
||||||
|
if ent, ok := c.items[key]; ok {
|
||||||
|
c.evictList.MoveToFront(ent)
|
||||||
|
ent.Value.value = value
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add new item
|
||||||
|
entry := c.evictList.PushFront(entry[K, V]{key, value})
|
||||||
|
c.items[key] = entry
|
||||||
|
|
||||||
|
evict := c.evictList.Len() > c.size
|
||||||
|
// Verify size not exceeded
|
||||||
|
if evict {
|
||||||
|
c.removeOldest(ctx)
|
||||||
|
}
|
||||||
|
return evict
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get looks up a key's value from the cache.
|
||||||
|
func (c *LRU[K, V]) Get(key K) (value *V, ok bool) {
|
||||||
|
if ent, ok := c.items[key]; ok {
|
||||||
|
c.evictList.MoveToFront(ent)
|
||||||
|
if ent == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
return &ent.Value.value, true
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contains checks if a key is in the cache, without updating the recent-ness
|
||||||
|
// or deleting it for being stale.
|
||||||
|
func (c *LRU[K, V]) Contains(key K) (ok bool) {
|
||||||
|
_, ok = c.items[key]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// Peek returns the key value (or undefined if not found) without updating
|
||||||
|
// the "recently used"-ness of the key.
|
||||||
|
func (c *LRU[K, V]) Peek(key K) (value *V, ok bool) {
|
||||||
|
if ent, ok := c.items[key]; ok {
|
||||||
|
return &ent.Value.value, true
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove removes the provided key from the cache, returning if the
|
||||||
|
// key was contained.
|
||||||
|
func (c *LRU[K, V]) Remove(ctx context.Context, key K) (present bool) {
|
||||||
|
if ent, ok := c.items[key]; ok {
|
||||||
|
c.removeElement(ctx, ent)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveOldest removes the oldest item from the cache.
|
||||||
|
func (c *LRU[K, V]) RemoveOldest(ctx context.Context) (key *K, value *V, ok bool) {
|
||||||
|
ent := c.evictList.Back()
|
||||||
|
if ent != nil {
|
||||||
|
c.removeElement(ctx, ent)
|
||||||
|
kv := ent.Value
|
||||||
|
return &kv.key, &kv.value, true
|
||||||
|
}
|
||||||
|
return nil, nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOldest returns the oldest entry
|
||||||
|
func (c *LRU[K, V]) GetOldest() (key *K, value *V, ok bool) {
|
||||||
|
ent := c.evictList.Back()
|
||||||
|
if ent != nil {
|
||||||
|
kv := ent.Value
|
||||||
|
return &kv.key, &kv.value, true
|
||||||
|
}
|
||||||
|
return nil, nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keys returns a slice of the keys in the cache, from oldest to newest.
|
||||||
|
func (c *LRU[K, V]) Keys() []K {
|
||||||
|
keys := make([]K, len(c.items))
|
||||||
|
i := 0
|
||||||
|
for ent := c.evictList.Back(); ent != nil; ent = ent.Prev() {
|
||||||
|
keys[i] = ent.Value.key
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the number of items in the cache.
|
||||||
|
func (c *LRU[K, V]) Len() int {
|
||||||
|
return c.evictList.Len()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resize changes the cache size.
|
||||||
|
func (c *LRU[K, V]) Resize(ctx context.Context, size int) (evicted int) {
|
||||||
|
diff := c.Len() - size
|
||||||
|
if diff < 0 {
|
||||||
|
diff = 0
|
||||||
|
}
|
||||||
|
for i := 0; i < diff; i++ {
|
||||||
|
c.removeOldest(ctx)
|
||||||
|
}
|
||||||
|
c.size = size
|
||||||
|
return diff
|
||||||
|
}
|
||||||
|
|
||||||
|
// removeOldest removes the oldest item from the cache.
|
||||||
|
func (c *LRU[K, V]) removeOldest(ctx context.Context) {
|
||||||
|
ent := c.evictList.Back()
|
||||||
|
if ent != nil {
|
||||||
|
c.removeElement(ctx, ent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// removeElement is used to remove a given list element from the cache
|
||||||
|
func (c *LRU[K, V]) removeElement(ctx context.Context, e *Element[entry[K, V]]) {
|
||||||
|
c.evictList.Remove(e)
|
||||||
|
kv := e.Value
|
||||||
|
delete(c.items, kv.key)
|
||||||
|
if c.onEvict != nil {
|
||||||
|
c.onEvict(ctx, kv.key, kv.value)
|
||||||
|
}
|
||||||
|
}
|
161
cron/cron.go
Normal file
161
cron/cron.go
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
package cron
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
|
||||||
|
"go.sour.is/pkg/lg"
|
||||||
|
"go.sour.is/pkg/locker"
|
||||||
|
"go.sour.is/pkg/set"
|
||||||
|
)
|
||||||
|
|
||||||
|
type task func(context.Context, time.Time) error
|
||||||
|
type job struct {
|
||||||
|
Month, Weekday, Day,
|
||||||
|
Hour, Minute, Second *set.BoundSet[int8]
|
||||||
|
Task task
|
||||||
|
}
|
||||||
|
|
||||||
|
var DefaultGranularity = time.Minute
|
||||||
|
|
||||||
|
type state struct {
|
||||||
|
queue []task
|
||||||
|
}
|
||||||
|
type cron struct {
|
||||||
|
jobs []job
|
||||||
|
state *locker.Locked[state]
|
||||||
|
granularity time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(granularity time.Duration) *cron {
|
||||||
|
return &cron{granularity: granularity, state: locker.New(&state{})}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseInto(c string, s *set.BoundSet[int8]) *set.BoundSet[int8] {
|
||||||
|
if c == "*" || c == "" {
|
||||||
|
s.AddRange(0, 100)
|
||||||
|
}
|
||||||
|
for _, split := range strings.Split(c, ",") {
|
||||||
|
minmax := strings.SplitN(split, "-", 2)
|
||||||
|
switch len(minmax) {
|
||||||
|
case 2:
|
||||||
|
min, _ := strconv.ParseInt(minmax[0], 10, 8)
|
||||||
|
max, _ := strconv.ParseInt(minmax[1], 10, 8)
|
||||||
|
s.AddRange(int8(min), int8(max))
|
||||||
|
default:
|
||||||
|
min, _ := strconv.ParseInt(minmax[0], 10, 8)
|
||||||
|
s.Add(int8(min))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function creates a new job that occurs at the given day and the given
|
||||||
|
// 24hour time. Any of the values may be -1 as an "any" match, so passing in
|
||||||
|
// a day of -1, the event occurs every day; passing in a second value of -1, the
|
||||||
|
// event will fire every second that the other parameters match.
|
||||||
|
func (c *cron) NewCron(expr string, task func(context.Context, time.Time) error) {
|
||||||
|
sp := append(strings.Fields(expr), make([]string, 5)...)[:5]
|
||||||
|
|
||||||
|
job := job{
|
||||||
|
Month: parseInto(sp[4], set.NewBoundSet[int8](1, 12)),
|
||||||
|
Weekday: parseInto(sp[3], set.NewBoundSet[int8](0, 6)),
|
||||||
|
Day: parseInto(sp[2], set.NewBoundSet[int8](1, 31)),
|
||||||
|
Hour: parseInto(sp[1], set.NewBoundSet[int8](0, 23)),
|
||||||
|
Minute: parseInto(sp[0], set.NewBoundSet[int8](0, 59)),
|
||||||
|
Task: task,
|
||||||
|
}
|
||||||
|
c.jobs = append(c.jobs, job)
|
||||||
|
}
|
||||||
|
func (c *cron) RunOnce(ctx context.Context, once func(context.Context, time.Time) error) {
|
||||||
|
c.state.Use(ctx, func(ctx context.Context, state *state) error {
|
||||||
|
state.queue = append(state.queue, once)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cj job) Matches(t time.Time) (ok bool) {
|
||||||
|
return cj.Month.Has(int8(t.Month())) &&
|
||||||
|
cj.Day.Has(int8(t.Day())) &&
|
||||||
|
cj.Weekday.Has(int8(t.Weekday()%7)) &&
|
||||||
|
cj.Hour.Has(int8(t.Hour())) &&
|
||||||
|
cj.Minute.Has(int8(t.Minute()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cj job) String() string {
|
||||||
|
return fmt.Sprintf("job[\n m:%s\n h:%s\n d:%s\n w:%s\n M:%s\n]", cj.Minute, cj.Hour, cj.Day, cj.Weekday, cj.Month)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cron) Run(ctx context.Context) error {
|
||||||
|
tick := time.NewTicker(c.granularity)
|
||||||
|
defer tick.Stop()
|
||||||
|
|
||||||
|
go c.run(ctx, time.Now())
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil
|
||||||
|
case now := <-tick.C:
|
||||||
|
// fmt.Println(now.Second(), now.Hour(), now.Day(), int8(now.Weekday()), uint8(now.Month()))
|
||||||
|
go c.run(ctx, now)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cron) run(ctx context.Context, now time.Time) {
|
||||||
|
var run []task
|
||||||
|
ctx, span := lg.Span(ctx)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
// Add Jitter
|
||||||
|
timer := time.NewTimer(time.Duration(rand.Intn(300)) * time.Millisecond)
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
timer.Stop()
|
||||||
|
return
|
||||||
|
case <-timer.C:
|
||||||
|
}
|
||||||
|
|
||||||
|
span.AddEvent("Cron Run: " + now.Format(time.RFC822))
|
||||||
|
// fmt.Println("Cron Run: ", now.Format(time.RFC822))
|
||||||
|
|
||||||
|
c.state.Use(ctx, func(ctx context.Context, state *state) error {
|
||||||
|
run = append(run, state.queue...)
|
||||||
|
state.queue = state.queue[:0]
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, j := range c.jobs {
|
||||||
|
if j.Matches(now) {
|
||||||
|
span.AddEvent(j.String())
|
||||||
|
run = append(run, j.Task)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(run) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
wg, _ := errgroup.WithContext(ctx)
|
||||||
|
|
||||||
|
for i := range run {
|
||||||
|
fn := run[i]
|
||||||
|
wg.Go(func() error { return fn(ctx, now) })
|
||||||
|
}
|
||||||
|
span.SetAttributes(
|
||||||
|
attribute.String("tick", now.String()),
|
||||||
|
attribute.Int("count", len(run)),
|
||||||
|
)
|
||||||
|
|
||||||
|
err := wg.Wait()
|
||||||
|
span.RecordError(err)
|
||||||
|
}
|
40
env/env.go
vendored
Normal file
40
env/env.go
vendored
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
package env
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Default(name, defaultValue string) string {
|
||||||
|
name = strings.TrimSpace(name)
|
||||||
|
defaultValue = strings.TrimSpace(defaultValue)
|
||||||
|
if v := strings.TrimSpace(os.Getenv(name)); v != "" {
|
||||||
|
log.Println("# ", name, "=", v)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
log.Println("# ", name, "=", defaultValue, "(default)")
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
type secret string
|
||||||
|
|
||||||
|
func (s secret) String() string {
|
||||||
|
if s == "" {
|
||||||
|
return "(nil)"
|
||||||
|
}
|
||||||
|
return "***"
|
||||||
|
}
|
||||||
|
func (s secret) Secret() string {
|
||||||
|
return string(s)
|
||||||
|
}
|
||||||
|
func Secret(name, defaultValue string) secret {
|
||||||
|
name = strings.TrimSpace(name)
|
||||||
|
defaultValue = strings.TrimSpace(defaultValue)
|
||||||
|
if v := strings.TrimSpace(os.Getenv(name)); v != "" {
|
||||||
|
log.Println("# ", name, "=", secret(v))
|
||||||
|
return secret(v)
|
||||||
|
}
|
||||||
|
log.Println("# ", name, "=", secret(defaultValue), "(default)")
|
||||||
|
return secret(defaultValue)
|
||||||
|
}
|
46
go.mod
Normal file
46
go.mod
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
module go.sour.is/pkg
|
||||||
|
|
||||||
|
go 1.20
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/matryer/is v1.4.1
|
||||||
|
go.opentelemetry.io/otel v1.16.0
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.16.0
|
||||||
|
go.uber.org/multierr v1.11.0
|
||||||
|
golang.org/x/sync v0.3.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||||
|
github.com/felixge/httpsnoop v1.0.3 // indirect
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||||
|
github.com/prometheus/client_model v0.4.0 // indirect
|
||||||
|
github.com/prometheus/common v0.42.0 // indirect
|
||||||
|
github.com/prometheus/procfs v0.10.1 // indirect
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v0.39.0 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
|
||||||
|
github.com/go-logr/logr v1.2.4 // indirect
|
||||||
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect
|
||||||
|
github.com/prometheus/client_golang v1.16.0
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.42.0
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/runtime v0.42.0
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.16.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.16.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/exporters/prometheus v0.39.0
|
||||||
|
go.opentelemetry.io/otel/metric v1.16.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/sdk v1.16.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/trace v1.16.0 // indirect
|
||||||
|
go.opentelemetry.io/proto/otlp v0.19.0 // indirect
|
||||||
|
golang.org/x/net v0.8.0 // indirect
|
||||||
|
golang.org/x/sys v0.8.0 // indirect
|
||||||
|
golang.org/x/text v0.8.0 // indirect
|
||||||
|
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 // indirect
|
||||||
|
google.golang.org/grpc v1.55.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.30.0 // indirect
|
||||||
|
)
|
480
go.sum
Normal file
480
go.sum
Normal file
|
@ -0,0 +1,480 @@
|
||||||
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||||
|
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||||
|
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||||
|
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||||
|
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||||
|
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||||
|
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
||||||
|
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||||
|
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||||
|
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
||||||
|
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||||
|
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||||
|
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||||
|
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||||
|
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||||
|
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||||
|
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||||
|
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||||
|
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||||
|
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||||
|
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||||
|
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||||
|
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||||
|
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||||
|
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
||||||
|
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||||
|
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||||
|
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||||
|
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||||
|
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||||
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
|
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||||
|
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||||
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
|
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
|
||||||
|
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||||
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
|
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||||
|
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||||
|
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
|
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||||
|
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||||
|
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
|
||||||
|
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||||
|
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||||
|
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||||
|
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
|
||||||
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
|
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
|
||||||
|
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
|
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||||
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
|
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||||
|
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
|
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
|
||||||
|
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||||
|
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||||
|
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||||
|
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||||
|
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||||
|
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||||
|
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||||
|
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||||
|
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||||
|
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
|
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
|
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
|
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||||
|
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
|
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||||
|
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||||
|
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
|
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
|
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
|
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
|
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
|
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
|
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
|
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 h1:BZHcxBETFHIdVyhyEfOvn/RdU/QGdLI4y34qQGjGWO0=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
|
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
|
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||||
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
|
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||||
|
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||||
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ=
|
||||||
|
github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
|
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
|
||||||
|
github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
|
||||||
|
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
|
||||||
|
github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
|
||||||
|
github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
|
||||||
|
github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
|
||||||
|
github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
|
||||||
|
github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
|
||||||
|
github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
|
||||||
|
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||||
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
|
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||||
|
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||||
|
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
|
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
|
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.42.0 h1:pginetY7+onl4qN1vl0xW/V/v6OBZ0vVdH+esuJgvmM=
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.42.0/go.mod h1:XiYsayHc36K3EByOO6nbAXnAWbrUxdjUROCEeeROOH8=
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/runtime v0.42.0 h1:EbmAUG9hEAMXyfWEasIt2kmh/WmXUznUksChApTgBGc=
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/runtime v0.42.0/go.mod h1:rD9feqRYP24P14t5kmhNMqsqm1jvKmpx2H2rKVw52V8=
|
||||||
|
go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
|
||||||
|
go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.16.0 h1:t4ZwRPU+emrcvM2e9DHd0Fsf0JTPVcbfa/BhTDF03d0=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.16.0/go.mod h1:vLarbg68dH2Wa77g71zmKQqlQ8+8Rq3GRG31uc0WcWI=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.16.0 h1:cbsD4cUcviQGXdw8+bo5x2wazq10SKz8hEbtCRPcU78=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.16.0/go.mod h1:JgXSGah17croqhJfhByOLVY719k1emAXC8MVhCIJlRs=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.16.0 h1:iqjq9LAB8aK++sKVcELezzn655JnBNdsDhghU4G/So8=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.16.0/go.mod h1:hGXzO5bhhSHZnKvrDaXB82Y9DRFour0Nz/KrBh7reWw=
|
||||||
|
go.opentelemetry.io/otel/exporters/prometheus v0.39.0 h1:whAaiHxOatgtKd+w0dOi//1KUxj3KoPINZdtDaDj3IA=
|
||||||
|
go.opentelemetry.io/otel/exporters/prometheus v0.39.0/go.mod h1:4jo5Q4CROlCpSPsXLhymi+LYrDXd2ObU5wbKayfZs7Y=
|
||||||
|
go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo=
|
||||||
|
go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4=
|
||||||
|
go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE=
|
||||||
|
go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4=
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI=
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI=
|
||||||
|
go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
|
||||||
|
go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
|
||||||
|
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||||
|
go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw=
|
||||||
|
go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=
|
||||||
|
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
|
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
|
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||||
|
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||||
|
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||||
|
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||||
|
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||||
|
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||||
|
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||||
|
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||||
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
|
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||||
|
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
|
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
|
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||||
|
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||||
|
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||||
|
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||||
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
|
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
|
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
|
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
|
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
|
||||||
|
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||||
|
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||||
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||||
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
|
||||||
|
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||||
|
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||||
|
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||||
|
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
|
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
|
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
|
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
|
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
|
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
|
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||||
|
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||||
|
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||||
|
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||||
|
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||||
|
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||||
|
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||||
|
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||||
|
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||||
|
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||||
|
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||||
|
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||||
|
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||||
|
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||||
|
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||||
|
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||||
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||||
|
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||||
|
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||||
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
|
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
|
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||||
|
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
||||||
|
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
||||||
|
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||||
|
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||||
|
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||||
|
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA=
|
||||||
|
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s=
|
||||||
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
|
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||||
|
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||||
|
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
|
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||||
|
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
|
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
|
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
|
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
||||||
|
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||||
|
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||||
|
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||||
|
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
||||||
|
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||||
|
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||||
|
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
||||||
|
google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
|
||||||
|
google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
|
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||||
|
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||||
|
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||||
|
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||||
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
|
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
|
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||||
|
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||||
|
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||||
|
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||||
|
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||||
|
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||||
|
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
37
gql/common.graphqls
Normal file
37
gql/common.graphqls
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
scalar Time
|
||||||
|
scalar Map
|
||||||
|
|
||||||
|
type Connection @goModel(model: "go.sour.is/ev/pkg/gql.Connection") {
|
||||||
|
paging: PageInfo!
|
||||||
|
edges: [Edge!]!
|
||||||
|
}
|
||||||
|
input PageInput @goModel(model: "go.sour.is/ev/pkg/gql.PageInput") {
|
||||||
|
after: Int = 0
|
||||||
|
before: Int
|
||||||
|
count: Int = 30
|
||||||
|
}
|
||||||
|
type PageInfo @goModel(model: "go.sour.is/ev/pkg/gql.PageInfo") {
|
||||||
|
next: Boolean!
|
||||||
|
prev: Boolean!
|
||||||
|
|
||||||
|
begin: Int!
|
||||||
|
end: Int!
|
||||||
|
}
|
||||||
|
interface Edge @goModel(model: "go.sour.is/ev/pkg/gql.Edge"){
|
||||||
|
id: ID!
|
||||||
|
}
|
||||||
|
|
||||||
|
directive @goModel(
|
||||||
|
model: String
|
||||||
|
models: [String!]
|
||||||
|
) on OBJECT | INPUT_OBJECT | SCALAR | ENUM | INTERFACE | UNION
|
||||||
|
|
||||||
|
directive @goField(
|
||||||
|
forceResolver: Boolean
|
||||||
|
name: String
|
||||||
|
) on INPUT_FIELD_DEFINITION | FIELD_DEFINITION
|
||||||
|
|
||||||
|
directive @goTag(
|
||||||
|
key: String!
|
||||||
|
value: String
|
||||||
|
) on INPUT_FIELD_DEFINITION | FIELD_DEFINITION
|
46
gql/connection.go
Normal file
46
gql/connection.go
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
package gql
|
||||||
|
|
||||||
|
type Edge interface {
|
||||||
|
IsEdge()
|
||||||
|
}
|
||||||
|
|
||||||
|
type Connection struct {
|
||||||
|
Paging *PageInfo `json:"paging"`
|
||||||
|
Edges []Edge `json:"edges"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PageInfo struct {
|
||||||
|
Next bool `json:"next"`
|
||||||
|
Prev bool `json:"prev"`
|
||||||
|
Begin uint64 `json:"begin"`
|
||||||
|
End uint64 `json:"end"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PageInput struct {
|
||||||
|
After *int64 `json:"after"`
|
||||||
|
Before *int64 `json:"before"`
|
||||||
|
Count *int64 `json:"count"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PageInput) GetIdx(v int64) int64 {
|
||||||
|
if p == nil {
|
||||||
|
// pass
|
||||||
|
} else if p.Before != nil {
|
||||||
|
return (*p.Before)
|
||||||
|
} else if p.After != nil {
|
||||||
|
return *p.After
|
||||||
|
}
|
||||||
|
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
func (p *PageInput) GetCount(v int64) int64 {
|
||||||
|
if p == nil || p.Count == nil {
|
||||||
|
return v
|
||||||
|
} else if p.Before != nil {
|
||||||
|
return -(*p.Count)
|
||||||
|
} else if p.After != nil {
|
||||||
|
return *p.Count
|
||||||
|
}
|
||||||
|
|
||||||
|
return *p.Count
|
||||||
|
}
|
14
gql/context.go
Normal file
14
gql/context.go
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
package gql
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
|
func ToContext[K comparable, V any](ctx context.Context, key K, value V) context.Context {
|
||||||
|
return context.WithValue(ctx, key, value)
|
||||||
|
}
|
||||||
|
func FromContext[K comparable, V any](ctx context.Context, key K) V {
|
||||||
|
var empty V
|
||||||
|
if v, ok := ctx.Value(key).(V); ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return empty
|
||||||
|
}
|
120
gql/graphiql/playground.go
Normal file
120
gql/graphiql/playground.go
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
package graphiql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html/template"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
var page = template.Must(template.New("graphiql").Parse(`<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>{{.title}}</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#graphiql {
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script
|
||||||
|
src="https://cdn.jsdelivr.net/npm/react@{{.reactVersion}}/umd/react.production.min.js"
|
||||||
|
integrity="{{.reactSRI}}"
|
||||||
|
crossorigin="anonymous"
|
||||||
|
></script>
|
||||||
|
<script
|
||||||
|
src="https://cdn.jsdelivr.net/npm/react-dom@{{.reactVersion}}/umd/react-dom.production.min.js"
|
||||||
|
integrity="{{.reactDOMSRI}}"
|
||||||
|
crossorigin="anonymous"
|
||||||
|
></script>
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://cdn.jsdelivr.net/npm/graphiql@{{.version}}/graphiql.min.css"
|
||||||
|
x-integrity="{{.cssSRI}}"
|
||||||
|
crossorigin="anonymous"
|
||||||
|
/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="graphiql">Loading...</div>
|
||||||
|
|
||||||
|
<script
|
||||||
|
src="https://cdn.jsdelivr.net/npm/graphiql@{{.version}}/graphiql.min.js"
|
||||||
|
x-integrity="{{.jsSRI}}"
|
||||||
|
crossorigin="anonymous"
|
||||||
|
></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
{{- if .endpointIsAbsolute}}
|
||||||
|
const url = {{.endpoint}};
|
||||||
|
const subscriptionUrl = {{.subscriptionEndpoint}};
|
||||||
|
{{- else}}
|
||||||
|
const url = location.protocol + '//' + location.host + {{.endpoint}};
|
||||||
|
const wsProto = location.protocol == 'https:' ? 'wss:' : 'ws:';
|
||||||
|
const subscriptionUrl = wsProto + '//' + location.host + {{.endpoint}};
|
||||||
|
{{- end}}
|
||||||
|
|
||||||
|
const fetcher = GraphiQL.createFetcher({ url, subscriptionUrl });
|
||||||
|
ReactDOM.render(
|
||||||
|
React.createElement(GraphiQL, {
|
||||||
|
fetcher: fetcher,
|
||||||
|
isHeadersEditorEnabled: true,
|
||||||
|
shouldPersistHeaders: true
|
||||||
|
}),
|
||||||
|
document.getElementById('graphiql'),
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`))
|
||||||
|
|
||||||
|
// Handler responsible for setting up the playground
|
||||||
|
func Handler(title string, endpoint string) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Add("Content-Type", "text/html; charset=UTF-8")
|
||||||
|
err := page.Execute(w, map[string]interface{}{
|
||||||
|
"title": title,
|
||||||
|
"endpoint": endpoint,
|
||||||
|
"endpointIsAbsolute": endpointHasScheme(endpoint),
|
||||||
|
"subscriptionEndpoint": getSubscriptionEndpoint(endpoint),
|
||||||
|
"version": "2.4.1",
|
||||||
|
"reactVersion": "17.0.2",
|
||||||
|
"cssSRI": "sha256-bGeEsMhcAqeXBjh2w0eQzBTFAxwlxhM0PKIKqMshlnk=",
|
||||||
|
"jsSRI": "sha256-s+f7CFAPSUIygFnRC2nfoiEKd3liCUy+snSdYFAoLUc=",
|
||||||
|
"reactSRI": "sha256-Ipu/TQ50iCCVZBUsZyNJfxrDk0E2yhaEIz0vqI+kFG8=",
|
||||||
|
"reactDOMSRI": "sha256-nbMykgB6tsOFJ7OdVmPpdqMFVk4ZsqWocT6issAPUF0=",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// endpointHasScheme checks if the endpoint has a scheme.
|
||||||
|
func endpointHasScheme(endpoint string) bool {
|
||||||
|
u, err := url.Parse(endpoint)
|
||||||
|
return err == nil && u.Scheme != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSubscriptionEndpoint returns the subscription endpoint for the given
|
||||||
|
// endpoint if it is parsable as a URL, or an empty string.
|
||||||
|
func getSubscriptionEndpoint(endpoint string) string {
|
||||||
|
u, err := url.Parse(endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
switch u.Scheme {
|
||||||
|
case "https":
|
||||||
|
u.Scheme = "wss"
|
||||||
|
default:
|
||||||
|
u.Scheme = "ws"
|
||||||
|
}
|
||||||
|
|
||||||
|
return u.String()
|
||||||
|
}
|
62
gql/playground/playground.go
Normal file
62
gql/playground/playground.go
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
package playground
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html/template"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
var page = template.Must(template.New("graphiql").Parse(`<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset=utf-8/>
|
||||||
|
<meta name="viewport" content="user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, minimal-ui">
|
||||||
|
<link rel="shortcut icon" href="https://graphcool-playground.netlify.com/favicon.png">
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/graphql-playground-react@{{ .version }}/build/static/css/index.css"
|
||||||
|
integrity="{{ .cssSRI }}" crossorigin="anonymous"/>
|
||||||
|
<link rel="shortcut icon" href="https://cdn.jsdelivr.net/npm/graphql-playground-react@{{ .version }}/build/favicon.png"
|
||||||
|
integrity="{{ .faviconSRI }}" crossorigin="anonymous"/>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/graphql-playground-react@{{ .version }}/build/static/js/middleware.js"
|
||||||
|
integrity="{{ .jsSRI }}" crossorigin="anonymous"></script>
|
||||||
|
<title>{{.title}}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<style type="text/css">
|
||||||
|
html { font-family: "Open Sans", sans-serif; overflow: hidden; }
|
||||||
|
body { margin: 0; background: #172a3a; }
|
||||||
|
</style>
|
||||||
|
<div id="root"/>
|
||||||
|
<script type="text/javascript">
|
||||||
|
window.addEventListener('load', function (event) {
|
||||||
|
const root = document.getElementById('root');
|
||||||
|
root.classList.add('playgroundIn');
|
||||||
|
const wsProto = location.protocol == 'https:' ? 'wss:' : 'ws:'
|
||||||
|
GraphQLPlayground.init(root, {
|
||||||
|
endpoint: location.protocol + '//' + location.host + '{{.endpoint}}',
|
||||||
|
subscriptionsEndpoint: wsProto + '//' + location.host + '{{.endpoint }}',
|
||||||
|
shareEnabled: true,
|
||||||
|
settings: {
|
||||||
|
'request.credentials': 'same-origin'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`))
|
||||||
|
|
||||||
|
func Handler(title string, endpoint string) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Add("Content-Type", "text/html")
|
||||||
|
err := page.Execute(w, map[string]string{
|
||||||
|
"title": title,
|
||||||
|
"endpoint": endpoint,
|
||||||
|
"version": "1.7.28",
|
||||||
|
"cssSRI": "sha256-dKnNLEFwKSVFpkpjRWe+o/jQDM6n/JsvQ0J3l5Dk3fc=",
|
||||||
|
"faviconSRI": "sha256-GhTyE+McTU79R4+pRO6ih+4TfsTOrpPwD8ReKFzb3PM=",
|
||||||
|
"jsSRI": "sha256-VVwEZwxs4qS5W7E+/9nXINYgr/BJRWKOi/rTMUdmmWg=",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
143
gql/resolver/resolver.go
Normal file
143
gql/resolver/resolver.go
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
package resolver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"runtime/debug"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/99designs/gqlgen/graphql"
|
||||||
|
"github.com/99designs/gqlgen/graphql/handler"
|
||||||
|
"github.com/99designs/gqlgen/graphql/handler/extension"
|
||||||
|
"github.com/99designs/gqlgen/graphql/handler/lru"
|
||||||
|
"github.com/99designs/gqlgen/graphql/handler/transport"
|
||||||
|
"github.com/vektah/gqlparser/v2/gqlerror"
|
||||||
|
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
"github.com/ravilushqa/otelgqlgen"
|
||||||
|
|
||||||
|
"go.sour.is/pkg/gql/graphiql"
|
||||||
|
"go.sour.is/pkg/gql/playground"
|
||||||
|
"go.sour.is/pkg/lg"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BaseResolver interface {
|
||||||
|
ExecutableSchema() graphql.ExecutableSchema
|
||||||
|
BaseResolver() IsResolver
|
||||||
|
}
|
||||||
|
|
||||||
|
type Resolver[T BaseResolver] struct {
|
||||||
|
res T
|
||||||
|
CheckOrigin func(r *http.Request) bool
|
||||||
|
}
|
||||||
|
type IsResolver interface {
|
||||||
|
IsResolver()
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultCheckOrign = func(r *http.Request) bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func New[T BaseResolver](ctx context.Context, base T, resolvers ...IsResolver) (*Resolver[T], error) {
|
||||||
|
_, span := lg.Span(ctx)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
noop := reflect.ValueOf(base.BaseResolver())
|
||||||
|
|
||||||
|
v := reflect.ValueOf(base)
|
||||||
|
v = reflect.Indirect(v)
|
||||||
|
|
||||||
|
outer:
|
||||||
|
for _, idx := range reflect.VisibleFields(v.Type()) {
|
||||||
|
field := v.FieldByIndex(idx.Index)
|
||||||
|
|
||||||
|
for i := range resolvers {
|
||||||
|
rs := reflect.ValueOf(resolvers[i])
|
||||||
|
|
||||||
|
if field.IsNil() && rs.Type().Implements(field.Type()) {
|
||||||
|
// log.Print("found ", field.Type().Name())
|
||||||
|
span.AddEvent(fmt.Sprint("found ", field.Type().Name()))
|
||||||
|
field.Set(rs)
|
||||||
|
continue outer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// log.Print(fmt.Sprint("default ", field.Type().Name()))
|
||||||
|
span.AddEvent(fmt.Sprint("default ", field.Type().Name()))
|
||||||
|
field.Set(noop)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Resolver[T]{res: base, CheckOrigin: defaultCheckOrign}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Resolver[T]) Resolver() T {
|
||||||
|
return r.res
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChainMiddlewares will check all embeded resolvers for a GetMiddleware func and add to handler.
|
||||||
|
func (r *Resolver[T]) ChainMiddlewares(h http.Handler) http.Handler {
|
||||||
|
v := reflect.ValueOf(r.Resolver()) // Get reflected value of *Resolver
|
||||||
|
v = reflect.Indirect(v) // Get the pointed value (returns a zero value on nil)
|
||||||
|
for _, idx := range reflect.VisibleFields(v.Type()) {
|
||||||
|
field := v.FieldByIndex(idx.Index)
|
||||||
|
// log.Print("middleware ", field.Type().Name())
|
||||||
|
|
||||||
|
if !field.CanInterface() { // Skip non-interface types.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if iface, ok := field.Interface().(interface {
|
||||||
|
GetMiddleware() func(http.Handler) http.Handler
|
||||||
|
}); ok {
|
||||||
|
h = iface.GetMiddleware()(h) // Append only items that fulfill the interface.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Resolver[T]) RegisterHTTP(mux *http.ServeMux) {
|
||||||
|
gql := NewServer(r.Resolver().ExecutableSchema(), r.CheckOrigin)
|
||||||
|
gql.SetRecoverFunc(NoopRecover)
|
||||||
|
gql.Use(otelgqlgen.Middleware())
|
||||||
|
mux.Handle("/gql", lg.Htrace(r.ChainMiddlewares(gql), "gql"))
|
||||||
|
mux.Handle("/graphiql", graphiql.Handler("GraphiQL playground", "/gql"))
|
||||||
|
mux.Handle("/playground", playground.Handler("GraphQL playground", "/gql"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func NoopRecover(ctx context.Context, err interface{}) error {
|
||||||
|
if err, ok := err.(string); ok && err == "not implemented" {
|
||||||
|
return gqlerror.Errorf("not implemented")
|
||||||
|
}
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
fmt.Fprintln(os.Stderr)
|
||||||
|
debug.PrintStack()
|
||||||
|
|
||||||
|
return gqlerror.Errorf("internal system error")
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServer(es graphql.ExecutableSchema, checkOrigin func(*http.Request) bool) *handler.Server {
|
||||||
|
srv := handler.New(es)
|
||||||
|
|
||||||
|
srv.AddTransport(transport.Websocket{
|
||||||
|
Upgrader: websocket.Upgrader{
|
||||||
|
CheckOrigin: checkOrigin,
|
||||||
|
},
|
||||||
|
KeepAlivePingInterval: 10 * time.Second,
|
||||||
|
})
|
||||||
|
srv.AddTransport(transport.Options{})
|
||||||
|
srv.AddTransport(transport.GET{})
|
||||||
|
srv.AddTransport(transport.POST{})
|
||||||
|
srv.AddTransport(transport.MultipartForm{})
|
||||||
|
|
||||||
|
srv.SetQueryCache(lru.New(1000))
|
||||||
|
|
||||||
|
srv.Use(extension.Introspection{})
|
||||||
|
srv.Use(extension.AutomaticPersistedQuery{
|
||||||
|
Cache: lru.New(100),
|
||||||
|
})
|
||||||
|
|
||||||
|
return srv
|
||||||
|
}
|
31
lg/init.go
Normal file
31
lg/init.go
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
package lg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"go.uber.org/multierr"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Init(ctx context.Context, name string) (context.Context, func(context.Context) error) {
|
||||||
|
ctx, span := Span(ctx)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
stop := [2]func() error{}
|
||||||
|
ctx, stop[0] = initMetrics(ctx, name)
|
||||||
|
ctx, stop[1] = initTracing(ctx, name)
|
||||||
|
|
||||||
|
reverse(stop[:])
|
||||||
|
|
||||||
|
return ctx, func(context.Context) error {
|
||||||
|
log.Println("flushing logs...")
|
||||||
|
errs := make([]error, len(stop))
|
||||||
|
for i, fn := range stop {
|
||||||
|
if fn != nil {
|
||||||
|
errs[i] = fn()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Println("all stopped.")
|
||||||
|
return multierr.Combine(errs...)
|
||||||
|
}
|
||||||
|
}
|
97
lg/metric.go
Normal file
97
lg/metric.go
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
package lg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/contrib/instrumentation/runtime"
|
||||||
|
"go.opentelemetry.io/otel"
|
||||||
|
"go.opentelemetry.io/otel/exporters/prometheus"
|
||||||
|
api "go.opentelemetry.io/otel/metric"
|
||||||
|
sdk "go.opentelemetry.io/otel/sdk/metric"
|
||||||
|
)
|
||||||
|
|
||||||
|
var meterKey = contextKey{"meter"}
|
||||||
|
var promHTTPKey = contextKey{"promHTTP"}
|
||||||
|
|
||||||
|
func Meter(ctx context.Context) api.Meter {
|
||||||
|
if t := fromContext[contextKey, api.Meter](ctx, tracerKey); t != nil {
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
return otel.Meter("")
|
||||||
|
}
|
||||||
|
func NewHTTP(ctx context.Context) *httpHandle {
|
||||||
|
t := fromContext[contextKey, *prometheus.Exporter](ctx, promHTTPKey)
|
||||||
|
return &httpHandle{t}
|
||||||
|
}
|
||||||
|
|
||||||
|
func initMetrics(ctx context.Context, name string) (context.Context, func() error) {
|
||||||
|
// goversion := ""
|
||||||
|
// pkg := ""
|
||||||
|
// host := ""
|
||||||
|
// if info, ok := debug.ReadBuildInfo(); ok {
|
||||||
|
// goversion = info.GoVersion
|
||||||
|
// pkg = info.Path
|
||||||
|
// }
|
||||||
|
// if h, err := os.Hostname(); err == nil {
|
||||||
|
// host = h
|
||||||
|
// }
|
||||||
|
|
||||||
|
// config := prometheus.Config{
|
||||||
|
// DefaultHistogramBoundaries: []float64{
|
||||||
|
// 2 << 6, 2 << 8, 2 << 10, 2 << 12, 2 << 14, 2 << 16, 2 << 18, 2 << 20, 2 << 22, 2 << 24, 2 << 26, 2 << 28,
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
// cont := controller.New(
|
||||||
|
// processor.NewFactory(
|
||||||
|
// selector.NewWithHistogramDistribution(
|
||||||
|
// histogram.WithExplicitBoundaries(config.DefaultHistogramBoundaries),
|
||||||
|
// ),
|
||||||
|
// aggregation.CumulativeTemporalitySelector(),
|
||||||
|
// processor.WithMemory(true),
|
||||||
|
// ),
|
||||||
|
// controller.WithResource(
|
||||||
|
// resource.NewWithAttributes(
|
||||||
|
// semconv.SchemaURL,
|
||||||
|
// attribute.String("app", name),
|
||||||
|
// attribute.String("host", host),
|
||||||
|
// attribute.String("go_version", goversion),
|
||||||
|
// attribute.String("pkg", pkg),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// )
|
||||||
|
ex, err := prometheus.New()
|
||||||
|
if err != nil {
|
||||||
|
return ctx, nil
|
||||||
|
}
|
||||||
|
provider := sdk.NewMeterProvider(sdk.WithReader(ex))
|
||||||
|
meter := provider.Meter(name)
|
||||||
|
|
||||||
|
|
||||||
|
ctx = toContext(ctx, promHTTPKey, ex)
|
||||||
|
ctx = toContext(ctx, meterKey, meter)
|
||||||
|
runtime.Start()
|
||||||
|
|
||||||
|
return ctx, func() error {
|
||||||
|
_, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
defer log.Println("metrics stopped")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type httpHandle struct {
|
||||||
|
exp *prometheus.Exporter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *httpHandle) RegisterHTTP(mux *http.ServeMux) {
|
||||||
|
if h.exp == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mux.Handle("/metrics", promhttp.Handler())
|
||||||
|
}
|
177
lg/tracer.go
Normal file
177
lg/tracer.go
Normal file
|
@ -0,0 +1,177 @@
|
||||||
|
package lg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
||||||
|
"go.opentelemetry.io/otel"
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
|
||||||
|
"go.opentelemetry.io/otel/propagation"
|
||||||
|
"go.opentelemetry.io/otel/sdk/resource"
|
||||||
|
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||||
|
semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
"go.sour.is/pkg/env"
|
||||||
|
)
|
||||||
|
|
||||||
|
type contextKey struct {
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
var tracerKey = contextKey{"tracer"}
|
||||||
|
|
||||||
|
func Tracer(ctx context.Context) trace.Tracer {
|
||||||
|
if t := fromContext[contextKey, trace.Tracer](ctx, tracerKey); t != nil {
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
return otel.Tracer("")
|
||||||
|
}
|
||||||
|
|
||||||
|
func attrs(ctx context.Context) (string, []attribute.KeyValue) {
|
||||||
|
var attrs []attribute.KeyValue
|
||||||
|
var name string
|
||||||
|
if pc, file, line, ok := runtime.Caller(2); ok {
|
||||||
|
if fn := runtime.FuncForPC(pc); fn != nil {
|
||||||
|
name = fn.Name()
|
||||||
|
}
|
||||||
|
attrs = append(attrs,
|
||||||
|
attribute.String("pc", fmt.Sprintf("%v", pc)),
|
||||||
|
attribute.String("file", file),
|
||||||
|
attribute.Int("line", line),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return name, attrs
|
||||||
|
}
|
||||||
|
|
||||||
|
func Span(ctx context.Context, opts ...trace.SpanStartOption) (context.Context, trace.Span) {
|
||||||
|
name, attrs := attrs(ctx)
|
||||||
|
attrs = append(attrs, attribute.String("name", name))
|
||||||
|
ctx, span := Tracer(ctx).Start(ctx, name, opts...)
|
||||||
|
span.SetAttributes(attrs...)
|
||||||
|
|
||||||
|
return ctx, span
|
||||||
|
}
|
||||||
|
func NamedSpan(ctx context.Context, name string, opts ...trace.SpanStartOption) (context.Context, trace.Span) {
|
||||||
|
_, attrs := attrs(ctx)
|
||||||
|
attrs = append(attrs, attribute.String("name", name))
|
||||||
|
ctx, span := Tracer(ctx).Start(ctx, name, opts...)
|
||||||
|
span.SetAttributes(attrs...)
|
||||||
|
|
||||||
|
return ctx, span
|
||||||
|
}
|
||||||
|
|
||||||
|
func Fork(ctx context.Context, opts ...trace.SpanStartOption) (context.Context, trace.Span) {
|
||||||
|
name, attrs := attrs(ctx)
|
||||||
|
childCTX, childSpan := Tracer(ctx).Start(context.Background(), name, append(opts, trace.WithLinks(trace.LinkFromContext(ctx)))...)
|
||||||
|
childSpan.SetAttributes(attrs...)
|
||||||
|
|
||||||
|
_, span := Tracer(ctx).Start(ctx, name, append(opts, trace.WithLinks(trace.LinkFromContext(childCTX)))...)
|
||||||
|
span.SetAttributes(attrs...)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
return childCTX, childSpan
|
||||||
|
}
|
||||||
|
|
||||||
|
type SampleRate string
|
||||||
|
|
||||||
|
const (
|
||||||
|
SampleAlways SampleRate = "always"
|
||||||
|
SampleNever SampleRate = "never"
|
||||||
|
)
|
||||||
|
|
||||||
|
func initTracing(ctx context.Context, name string) (context.Context, func() error) {
|
||||||
|
res, err := resource.New(ctx,
|
||||||
|
resource.WithAttributes(
|
||||||
|
semconv.ServiceNameKey.String(name),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(wrap(err, "failed to create trace resource"))
|
||||||
|
return ctx, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
exporterAddr := env.Default("EV_TRACE_ENDPOINT", "")
|
||||||
|
if exporterAddr == "" {
|
||||||
|
return ctx, nil
|
||||||
|
}
|
||||||
|
traceExporter, err := otlptracehttp.New(ctx,
|
||||||
|
otlptracehttp.WithInsecure(),
|
||||||
|
otlptracehttp.WithEndpoint(exporterAddr),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(wrap(err, "failed to create trace exporter"))
|
||||||
|
return ctx, nil
|
||||||
|
}
|
||||||
|
bsp := sdktrace.NewBatchSpanProcessor(traceExporter)
|
||||||
|
|
||||||
|
var sample sdktrace.TracerProviderOption
|
||||||
|
sampleRate := SampleRate(env.Default("EV_TRACE_SAMPLE", string(SampleNever)))
|
||||||
|
switch sampleRate {
|
||||||
|
case "always":
|
||||||
|
sample = sdktrace.WithSampler(sdktrace.AlwaysSample())
|
||||||
|
case "never":
|
||||||
|
sample = sdktrace.WithSampler(sdktrace.NeverSample())
|
||||||
|
default:
|
||||||
|
if v, err := strconv.Atoi(string(sampleRate)); err != nil {
|
||||||
|
sample = sdktrace.WithSampler(sdktrace.NeverSample())
|
||||||
|
} else {
|
||||||
|
sample = sdktrace.WithSampler(sdktrace.TraceIDRatioBased(float64(v) * 0.01))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tracerProvider := sdktrace.NewTracerProvider(
|
||||||
|
sample,
|
||||||
|
sdktrace.WithResource(res),
|
||||||
|
sdktrace.WithSpanProcessor(bsp),
|
||||||
|
)
|
||||||
|
otel.SetTracerProvider(tracerProvider)
|
||||||
|
otel.SetTextMapPropagator(propagation.TraceContext{})
|
||||||
|
|
||||||
|
ctx = toContext(ctx, tracerKey, otel.Tracer(name))
|
||||||
|
|
||||||
|
return ctx, func() error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
defer log.Println("tracer stopped")
|
||||||
|
return wrap(tracerProvider.Shutdown(ctx), "failed to shutdown TracerProvider")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrap(err error, s string) error {
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(s, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func reverse[T any](s []T) {
|
||||||
|
first, last := 0, len(s)-1
|
||||||
|
for first < last {
|
||||||
|
s[first], s[last] = s[last], s[first]
|
||||||
|
first++
|
||||||
|
last--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Htrace(h http.Handler, name string) http.Handler {
|
||||||
|
return otelhttp.NewHandler(h, name, otelhttp.WithSpanNameFormatter(func(operation string, r *http.Request) string {
|
||||||
|
return fmt.Sprintf("%s: %s", operation, r.RequestURI)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func toContext[K comparable, V any](ctx context.Context, key K, value V) context.Context {
|
||||||
|
return context.WithValue(ctx, key, value)
|
||||||
|
}
|
||||||
|
func fromContext[K comparable, V any](ctx context.Context, key K) V {
|
||||||
|
var empty V
|
||||||
|
if v, ok := ctx.Value(key).(V); ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return empty
|
||||||
|
}
|
74
locker/locker.go
Normal file
74
locker/locker.go
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
package locker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.sour.is/pkg/lg"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Locked[T any] struct {
|
||||||
|
state chan *T
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new locker for the given value.
|
||||||
|
func New[T any](initial *T) *Locked[T] {
|
||||||
|
s := &Locked[T]{}
|
||||||
|
s.state = make(chan *T, 1)
|
||||||
|
s.state <- initial
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
type ctxKey struct{ name string }
|
||||||
|
|
||||||
|
// Use will call the function with the locked value
|
||||||
|
func (s *Locked[T]) Use(ctx context.Context, fn func(context.Context, *T) error) error {
|
||||||
|
if s == nil {
|
||||||
|
return fmt.Errorf("locker not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
key := ctxKey{fmt.Sprintf("%p", s)}
|
||||||
|
|
||||||
|
if value := ctx.Value(key); value != nil {
|
||||||
|
return fmt.Errorf("%w: %T", ErrNested, s)
|
||||||
|
}
|
||||||
|
ctx = context.WithValue(ctx, key, key)
|
||||||
|
|
||||||
|
ctx, span := lg.Span(ctx)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
var t T
|
||||||
|
span.SetAttributes(
|
||||||
|
attribute.String("typeOf", fmt.Sprintf("%T", t)),
|
||||||
|
)
|
||||||
|
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case state := <-s.state:
|
||||||
|
defer func() { s.state <- state }()
|
||||||
|
return fn(ctx, state)
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy will return a shallow copy of the locked object.
|
||||||
|
func (s *Locked[T]) Copy(ctx context.Context) (T, error) {
|
||||||
|
var t T
|
||||||
|
|
||||||
|
err := s.Use(ctx, func(ctx context.Context, c *T) error {
|
||||||
|
if c != nil {
|
||||||
|
t = *c
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return t, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var ErrNested = errors.New("nested locker call")
|
96
locker/locker_test.go
Normal file
96
locker/locker_test.go
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
package locker_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/matryer/is"
|
||||||
|
|
||||||
|
"go.sour.is/pkg/locker"
|
||||||
|
)
|
||||||
|
|
||||||
|
type config struct {
|
||||||
|
Value string
|
||||||
|
Counter int
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLocker(t *testing.T) {
|
||||||
|
is := is.New(t)
|
||||||
|
|
||||||
|
value := locker.New(&config{})
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
err := value.Use(ctx, func(ctx context.Context, c *config) error {
|
||||||
|
c.Value = "one"
|
||||||
|
c.Counter++
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
is.NoErr(err)
|
||||||
|
|
||||||
|
c, err := value.Copy(context.Background())
|
||||||
|
|
||||||
|
is.NoErr(err)
|
||||||
|
is.Equal(c.Value, "one")
|
||||||
|
is.Equal(c.Counter, 1)
|
||||||
|
|
||||||
|
wait := make(chan struct{})
|
||||||
|
|
||||||
|
go value.Use(ctx, func(ctx context.Context, c *config) error {
|
||||||
|
c.Value = "two"
|
||||||
|
c.Counter++
|
||||||
|
close(wait)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
<-wait
|
||||||
|
cancel()
|
||||||
|
|
||||||
|
err = value.Use(ctx, func(ctx context.Context, c *config) error {
|
||||||
|
c.Value = "three"
|
||||||
|
c.Counter++
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
is.True(err != nil)
|
||||||
|
|
||||||
|
c, err = value.Copy(context.Background())
|
||||||
|
|
||||||
|
is.NoErr(err)
|
||||||
|
is.Equal(c.Value, "two")
|
||||||
|
is.Equal(c.Counter, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNestedLocker(t *testing.T) {
|
||||||
|
is := is.New(t)
|
||||||
|
|
||||||
|
value := locker.New(&config{})
|
||||||
|
other := locker.New(&config{})
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
err := value.Use(ctx, func(ctx context.Context, c *config) error {
|
||||||
|
return value.Use(ctx, func(ctx context.Context, t *config) error {
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
})
|
||||||
|
is.True(errors.Is(err, locker.ErrNested))
|
||||||
|
|
||||||
|
err = value.Use(ctx, func(ctx context.Context, c *config) error {
|
||||||
|
return other.Use(ctx, func(ctx context.Context, t *config) error {
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
})
|
||||||
|
is.NoErr(err)
|
||||||
|
|
||||||
|
err = value.Use(ctx, func(ctx context.Context, c *config) error {
|
||||||
|
return other.Use(ctx, func(ctx context.Context, t *config) error {
|
||||||
|
return value.Use(ctx, func(ctx context.Context, x *config) error {
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
is.True(errors.Is(err, locker.ErrNested))
|
||||||
|
}
|
71
math/math.go
Normal file
71
math/math.go
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
package math
|
||||||
|
|
||||||
|
type signed interface {
|
||||||
|
~int | ~int8 | ~int16 | ~int32 | ~int64
|
||||||
|
}
|
||||||
|
type unsigned interface {
|
||||||
|
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
|
||||||
|
}
|
||||||
|
type integer interface {
|
||||||
|
signed | unsigned
|
||||||
|
}
|
||||||
|
type float interface {
|
||||||
|
~float32 | ~float64
|
||||||
|
}
|
||||||
|
type ordered interface {
|
||||||
|
integer | float | ~string
|
||||||
|
}
|
||||||
|
|
||||||
|
func Abs[T signed](i T) T {
|
||||||
|
if i > 0 {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
return -i
|
||||||
|
}
|
||||||
|
func Max[T ordered](i T, candidates ...T) T {
|
||||||
|
for _, j := range candidates {
|
||||||
|
if i < j {
|
||||||
|
i = j
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
func Min[T ordered](i T, candidates ...T) T {
|
||||||
|
for _, j := range candidates {
|
||||||
|
if i > j {
|
||||||
|
i = j
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
func PagerBox(first, last uint64, pos, count int64) (uint64, int64) {
|
||||||
|
var start uint64
|
||||||
|
|
||||||
|
if pos >= 0 {
|
||||||
|
if int64(first) > pos {
|
||||||
|
start = first
|
||||||
|
} else {
|
||||||
|
start = uint64(pos) + 1
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
start = uint64(int64(last) + pos + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case count > 0:
|
||||||
|
count = Min(count, int64(last-start)+1)
|
||||||
|
|
||||||
|
case pos >= 0 && count < 0:
|
||||||
|
count = Max(count, int64(first-start))
|
||||||
|
|
||||||
|
case pos < 0 && count < 0:
|
||||||
|
count = Max(count, int64(first-start)-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if count == 0 || (start < first && count <= 0) || (start > last && count >= 0) {
|
||||||
|
return 0, 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return start, count
|
||||||
|
}
|
96
math/math_test.go
Normal file
96
math/math_test.go
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
package math_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/matryer/is"
|
||||||
|
"go.sour.is/pkg/math"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMath(t *testing.T) {
|
||||||
|
is := is.New(t)
|
||||||
|
|
||||||
|
is.Equal(5, math.Abs(-5))
|
||||||
|
is.Equal(math.Abs(5), math.Abs(-5))
|
||||||
|
|
||||||
|
is.Equal(10, math.Max(1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
|
||||||
|
is.Equal(1, math.Min(1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
|
||||||
|
|
||||||
|
is.Equal(1, math.Min(89, 71, 54, 48, 49, 1, 72, 88, 25, 69))
|
||||||
|
is.Equal(89, math.Max(89, 71, 54, 48, 49, 1, 72, 88, 25, 69))
|
||||||
|
|
||||||
|
is.Equal(0.9348207729, math.Max(
|
||||||
|
0.3943310720,
|
||||||
|
0.1090868377,
|
||||||
|
0.9348207729,
|
||||||
|
0.3525527584,
|
||||||
|
0.4359833682,
|
||||||
|
0.7958538081,
|
||||||
|
0.1439352569,
|
||||||
|
0.1547311967,
|
||||||
|
0.6403818871,
|
||||||
|
0.8618832818,
|
||||||
|
))
|
||||||
|
|
||||||
|
is.Equal(0.1090868377, math.Min(
|
||||||
|
0.3943310720,
|
||||||
|
0.1090868377,
|
||||||
|
0.9348207729,
|
||||||
|
0.3525527584,
|
||||||
|
0.4359833682,
|
||||||
|
0.7958538081,
|
||||||
|
0.1439352569,
|
||||||
|
0.1547311967,
|
||||||
|
0.6403818871,
|
||||||
|
0.8618832818,
|
||||||
|
))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPagerBox(t *testing.T) {
|
||||||
|
is := is.New(t)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
first uint64
|
||||||
|
last uint64
|
||||||
|
pos int64
|
||||||
|
n int64
|
||||||
|
|
||||||
|
start uint64
|
||||||
|
count int64
|
||||||
|
}{
|
||||||
|
{1, 10, 0, 10, 1, 10},
|
||||||
|
{1, 10, 0, 11, 1, 10},
|
||||||
|
{1, 5, 0, 10, 1, 5},
|
||||||
|
{1, 10, 4, 10, 5, 6},
|
||||||
|
{1, 10, 5, 10, 6, 5},
|
||||||
|
{1, 10, 0, -10, 0, 0},
|
||||||
|
{1, 10, 1, -1, 2, -1},
|
||||||
|
{1, 10, 1, -10, 2, -1},
|
||||||
|
{1, 10, -1, 1, 10, 1},
|
||||||
|
{1, 10, -2, 10, 9, 2},
|
||||||
|
{1, 10, -1, -1, 10, -1},
|
||||||
|
{1, 10, -2, -10, 9, -9},
|
||||||
|
{1, 10, 0, -10, 0, 0},
|
||||||
|
{1, 10, 10, 10, 0, 0},
|
||||||
|
{1, 10, 0, AllEvents, 1, 10},
|
||||||
|
{1, 10, -1, -AllEvents, 10, -10},
|
||||||
|
|
||||||
|
{5, 10, 0, 1, 5, 1},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
start, count := math.PagerBox(tt.first, tt.last, tt.pos, tt.n)
|
||||||
|
if count > 0 {
|
||||||
|
t.Log(tt, "|", start, count, int64(start)+count-1)
|
||||||
|
} else {
|
||||||
|
t.Log(tt, "|", start, count, int64(start)+count+1)
|
||||||
|
}
|
||||||
|
|
||||||
|
is.Equal(start, tt.start)
|
||||||
|
is.Equal(count, tt.count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const AppendOnly = ^uint64(0)
|
||||||
|
const AllEvents = int64(AppendOnly >> 1)
|
43
mux/httpmux.go
Normal file
43
mux/httpmux.go
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
package mux
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mux struct {
|
||||||
|
*http.ServeMux
|
||||||
|
api *http.ServeMux
|
||||||
|
wellknown *http.ServeMux
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mux *mux) Add(fns ...interface{ RegisterHTTP(*http.ServeMux) }) {
|
||||||
|
for _, fn := range fns {
|
||||||
|
// log.Printf("HTTP: %T", fn)
|
||||||
|
fn.RegisterHTTP(mux.ServeMux)
|
||||||
|
|
||||||
|
if fn, ok := fn.(interface{ RegisterAPIv1(*http.ServeMux) }); ok {
|
||||||
|
// log.Printf("APIv1: %T", fn)
|
||||||
|
fn.RegisterAPIv1(mux.api)
|
||||||
|
}
|
||||||
|
|
||||||
|
if fn, ok := fn.(interface{ RegisterWellKnown(*http.ServeMux) }); ok {
|
||||||
|
// log.Printf("WellKnown: %T", fn)
|
||||||
|
fn.RegisterWellKnown(mux.wellknown)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func New() *mux {
|
||||||
|
mux := &mux{
|
||||||
|
api: http.NewServeMux(),
|
||||||
|
wellknown: http.NewServeMux(),
|
||||||
|
ServeMux: http.NewServeMux(),
|
||||||
|
}
|
||||||
|
mux.Handle("/api/v1/", http.StripPrefix("/api/v1", mux.api))
|
||||||
|
mux.Handle("/.well-known/", http.StripPrefix("/.well-known", mux.wellknown))
|
||||||
|
|
||||||
|
return mux
|
||||||
|
}
|
||||||
|
|
||||||
|
type RegisterHTTP func(*http.ServeMux)
|
||||||
|
|
||||||
|
func (fn RegisterHTTP) RegisterHTTP(mux *http.ServeMux) { fn(mux) }
|
104
mux/httpmux_test.go
Normal file
104
mux/httpmux_test.go
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
package mux_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/matryer/is"
|
||||||
|
"go.sour.is/pkg/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mockHTTP struct {
|
||||||
|
onServeHTTP func()
|
||||||
|
onServeAPIv1 func()
|
||||||
|
onServeWellKnown func()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*mockHTTP) ServeFn(fn func()) func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) { fn() }
|
||||||
|
}
|
||||||
|
func (h *mockHTTP) RegisterHTTP(mux *http.ServeMux) {
|
||||||
|
mux.HandleFunc("/", h.ServeFn(h.onServeHTTP))
|
||||||
|
}
|
||||||
|
func (h *mockHTTP) RegisterAPIv1(mux *http.ServeMux) {
|
||||||
|
mux.HandleFunc("/ping", h.ServeFn(h.onServeAPIv1))
|
||||||
|
}
|
||||||
|
func (h *mockHTTP) RegisterWellKnown(mux *http.ServeMux) {
|
||||||
|
mux.HandleFunc("/echo", h.ServeFn(h.onServeWellKnown))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHttp(t *testing.T) {
|
||||||
|
is := is.New(t)
|
||||||
|
|
||||||
|
called := false
|
||||||
|
calledAPIv1 := false
|
||||||
|
calledWellKnown := false
|
||||||
|
|
||||||
|
mux := mux.New()
|
||||||
|
mux.Add(&mockHTTP{
|
||||||
|
func() { called = true },
|
||||||
|
func() { calledAPIv1 = true },
|
||||||
|
func() { calledWellKnown = true },
|
||||||
|
})
|
||||||
|
|
||||||
|
is.True(mux != nil)
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
r := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||||
|
mux.ServeHTTP(w, r)
|
||||||
|
|
||||||
|
is.True(called)
|
||||||
|
is.True(!calledAPIv1)
|
||||||
|
is.True(!calledWellKnown)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHttpAPIv1(t *testing.T) {
|
||||||
|
is := is.New(t)
|
||||||
|
|
||||||
|
called := false
|
||||||
|
calledAPIv1 := false
|
||||||
|
calledWellKnown := false
|
||||||
|
|
||||||
|
mux := mux.New()
|
||||||
|
mux.Add(&mockHTTP{
|
||||||
|
func() { called = true },
|
||||||
|
func() { calledAPIv1 = true },
|
||||||
|
func() { calledWellKnown = true },
|
||||||
|
})
|
||||||
|
|
||||||
|
is.True(mux != nil)
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
r := httptest.NewRequest(http.MethodGet, "/api/v1/ping", nil)
|
||||||
|
mux.ServeHTTP(w, r)
|
||||||
|
|
||||||
|
is.True(!called)
|
||||||
|
is.True(calledAPIv1)
|
||||||
|
is.True(!calledWellKnown)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHttpWellKnown(t *testing.T) {
|
||||||
|
is := is.New(t)
|
||||||
|
|
||||||
|
called := false
|
||||||
|
calledAPIv1 := false
|
||||||
|
calledWellKnown := false
|
||||||
|
|
||||||
|
mux := mux.New()
|
||||||
|
mux.Add(&mockHTTP{
|
||||||
|
func() { called = true },
|
||||||
|
func() { calledAPIv1 = true },
|
||||||
|
func() { calledWellKnown = true },
|
||||||
|
})
|
||||||
|
|
||||||
|
is.True(mux != nil)
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
r := httptest.NewRequest(http.MethodGet, "/.well-known/echo", nil)
|
||||||
|
mux.ServeHTTP(w, r)
|
||||||
|
|
||||||
|
is.True(!called)
|
||||||
|
is.True(!calledAPIv1)
|
||||||
|
is.True(calledWellKnown)
|
||||||
|
}
|
179
service/service.go
Normal file
179
service/service.go
Normal file
|
@ -0,0 +1,179 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"runtime/debug"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.uber.org/multierr"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
|
||||||
|
"go.sour.is/pkg/cron"
|
||||||
|
"go.sour.is/pkg/lg"
|
||||||
|
)
|
||||||
|
|
||||||
|
type crontab interface {
|
||||||
|
NewCron(expr string, task func(context.Context, time.Time) error)
|
||||||
|
RunOnce(ctx context.Context, once func(context.Context, time.Time) error)
|
||||||
|
}
|
||||||
|
type Harness struct {
|
||||||
|
crontab
|
||||||
|
|
||||||
|
Services []any
|
||||||
|
|
||||||
|
onStart []func(context.Context) error
|
||||||
|
onRunning chan struct{}
|
||||||
|
onStop []func(context.Context) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Harness) Setup(ctx context.Context, apps ...application) error {
|
||||||
|
ctx, span := lg.Span(ctx)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
// setup crontab
|
||||||
|
c := cron.New(cron.DefaultGranularity)
|
||||||
|
s.OnStart(c.Run)
|
||||||
|
s.onRunning = make(chan struct{})
|
||||||
|
s.crontab = c
|
||||||
|
|
||||||
|
var err error
|
||||||
|
for _, app := range apps {
|
||||||
|
err = multierr.Append(err, app(ctx, s))
|
||||||
|
}
|
||||||
|
|
||||||
|
span.RecordError(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
func (s *Harness) OnStart(fn func(context.Context) error) {
|
||||||
|
s.onStart = append(s.onStart, fn)
|
||||||
|
}
|
||||||
|
func (s *Harness) OnRunning() <-chan struct{} {
|
||||||
|
return s.onRunning
|
||||||
|
}
|
||||||
|
func (s *Harness) OnStop(fn func(context.Context) error) {
|
||||||
|
s.onStop = append(s.onStop, fn)
|
||||||
|
}
|
||||||
|
func (s *Harness) Add(svcs ...any) {
|
||||||
|
s.Services = append(s.Services, svcs...)
|
||||||
|
}
|
||||||
|
func (s *Harness) stop(ctx context.Context) error {
|
||||||
|
g, _ := errgroup.WithContext(ctx)
|
||||||
|
for i := range s.onStop {
|
||||||
|
fn := s.onStop[i]
|
||||||
|
g.Go(func() error {
|
||||||
|
if err := fn(ctx); err != nil && err != http.ErrServerClosed {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return g.Wait()
|
||||||
|
}
|
||||||
|
func (s *Harness) Run(ctx context.Context, appName, version string) error {
|
||||||
|
{
|
||||||
|
ctx, span := lg.Span(ctx)
|
||||||
|
|
||||||
|
log.Println(appName, version)
|
||||||
|
span.SetAttributes(
|
||||||
|
attribute.String("app", appName),
|
||||||
|
attribute.String("version", version),
|
||||||
|
)
|
||||||
|
|
||||||
|
Mup, err := lg.Meter(ctx).Int64UpDownCounter("up")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
Mup.Add(ctx, 1)
|
||||||
|
|
||||||
|
span.End()
|
||||||
|
}
|
||||||
|
|
||||||
|
g, _ := errgroup.WithContext(ctx)
|
||||||
|
g.Go(func() error {
|
||||||
|
<-ctx.Done()
|
||||||
|
// shutdown jobs
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
return s.stop(ctx)
|
||||||
|
})
|
||||||
|
|
||||||
|
for i := range s.onStart {
|
||||||
|
fn := s.onStart[i]
|
||||||
|
g.Go(func() error { return fn(ctx) })
|
||||||
|
}
|
||||||
|
|
||||||
|
close(s.onRunning)
|
||||||
|
|
||||||
|
err := g.Wait()
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
type application func(context.Context, *Harness) error // Len is the number of elements in the collection.
|
||||||
|
|
||||||
|
type appscore struct {
|
||||||
|
score int
|
||||||
|
application
|
||||||
|
}
|
||||||
|
type Apps []appscore
|
||||||
|
|
||||||
|
func (a *Apps) Apps() []application {
|
||||||
|
sort.Sort(a)
|
||||||
|
lis := make([]application, len(*a))
|
||||||
|
for i, app := range *a {
|
||||||
|
lis[i] = app.application
|
||||||
|
}
|
||||||
|
return lis
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len is the number of elements in the collection.
|
||||||
|
func (a *Apps) Len() int {
|
||||||
|
if a == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return len(*a)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Less reports whether the element with index i
|
||||||
|
func (a *Apps) Less(i int, j int) bool {
|
||||||
|
if a == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return (*a)[i].score < (*a)[j].score
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap swaps the elements with indexes i and j.
|
||||||
|
func (a *Apps) Swap(i int, j int) {
|
||||||
|
if a == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
(*a)[i], (*a)[j] = (*a)[j], (*a)[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Apps) Register(score int, app application) (none struct{}) {
|
||||||
|
if a == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
*a = append(*a, appscore{score, app})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func AppName() (string, string) {
|
||||||
|
if info, ok := debug.ReadBuildInfo(); ok {
|
||||||
|
_, name, _ := strings.Cut(info.Main.Path, "/")
|
||||||
|
name = strings.Replace(name, "-", ".", -1)
|
||||||
|
name = strings.Replace(name, "/", "-", -1)
|
||||||
|
return name, info.Main.Version
|
||||||
|
}
|
||||||
|
|
||||||
|
return "sour.is-app", "(devel)"
|
||||||
|
}
|
137
set/set.go
Normal file
137
set/set.go
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
package set
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"go.sour.is/pkg/math"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Set[T comparable] map[T]struct{}
|
||||||
|
|
||||||
|
func New[T comparable](items ...T) Set[T] {
|
||||||
|
s := make(map[T]struct{}, len(items))
|
||||||
|
for i := range items {
|
||||||
|
s[items[i]] = struct{}{}
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
func (s Set[T]) Has(v T) bool {
|
||||||
|
_, ok := (s)[v]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
func (s Set[T]) Add(items ...T) Set[T] {
|
||||||
|
for _, i := range items {
|
||||||
|
s[i] = struct{}{}
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
func (s Set[T]) Delete(items ...T) Set[T] {
|
||||||
|
for _, i := range items {
|
||||||
|
delete(s, i)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Set[T]) Equal(e Set[T]) bool {
|
||||||
|
for k := range s {
|
||||||
|
if _, ok := e[k]; !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for k := range e {
|
||||||
|
if _, ok := s[k]; !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Set[T]) String() string {
|
||||||
|
if s == nil {
|
||||||
|
return "set(<nil>)"
|
||||||
|
}
|
||||||
|
lis := make([]string, 0, len(s))
|
||||||
|
for k := range s {
|
||||||
|
lis = append(lis, fmt.Sprint(k))
|
||||||
|
}
|
||||||
|
|
||||||
|
var b strings.Builder
|
||||||
|
b.WriteString("set(")
|
||||||
|
b.WriteString(strings.Join(lis, ","))
|
||||||
|
b.WriteString(")")
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
type ordered interface {
|
||||||
|
~int | ~int8 | ~int16 | ~int32 | ~int64 |
|
||||||
|
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |
|
||||||
|
~float32 | ~float64
|
||||||
|
}
|
||||||
|
|
||||||
|
type BoundSet[T ordered] struct {
|
||||||
|
min, max T
|
||||||
|
s Set[T]
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBoundSet[T ordered](min, max T, items ...T) *BoundSet[T] {
|
||||||
|
b := &BoundSet[T]{
|
||||||
|
min: min,
|
||||||
|
max: max,
|
||||||
|
s: New[T](),
|
||||||
|
}
|
||||||
|
b.Add(items...)
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
func (l *BoundSet[T]) Add(items ...T) *BoundSet[T] {
|
||||||
|
n := 0
|
||||||
|
for i := range items {
|
||||||
|
if items[i] >= l.min && items[i] <= l.max {
|
||||||
|
items[n] = items[i]
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
l.s.Add(items[:n]...)
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (l *BoundSet[T]) AddRange(min, max T) {
|
||||||
|
min = math.Max(min, l.min)
|
||||||
|
max = math.Min(max, l.max)
|
||||||
|
var lis []T
|
||||||
|
for ; min <= max; min++ {
|
||||||
|
lis = append(lis, min)
|
||||||
|
}
|
||||||
|
l.s.Add(lis...)
|
||||||
|
}
|
||||||
|
func (l *BoundSet[T]) Delete(items ...T) *BoundSet[T] {
|
||||||
|
n := 0
|
||||||
|
for i := range items {
|
||||||
|
if items[i] >= l.min && items[i] <= l.max {
|
||||||
|
items[n] = items[i]
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
l.s.Delete(items[:n]...)
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (l *BoundSet[T]) Has(v T) bool {
|
||||||
|
return l.s.Has(v)
|
||||||
|
}
|
||||||
|
func (l *BoundSet[T]) String() string {
|
||||||
|
lis := make([]string, len(l.s))
|
||||||
|
n := 0
|
||||||
|
for k := range l.s {
|
||||||
|
lis[n] = fmt.Sprint(k)
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
sort.Strings(lis)
|
||||||
|
|
||||||
|
var b strings.Builder
|
||||||
|
b.WriteString("set(")
|
||||||
|
b.WriteString(strings.Join(lis, ","))
|
||||||
|
b.WriteString(")")
|
||||||
|
return b.String()
|
||||||
|
}
|
39
set/set_test.go
Normal file
39
set/set_test.go
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
package set_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/matryer/is"
|
||||||
|
"go.sour.is/pkg/set"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStringSet(t *testing.T) {
|
||||||
|
is := is.New(t)
|
||||||
|
|
||||||
|
s := set.New(strings.Fields("one two three")...)
|
||||||
|
|
||||||
|
is.True(s.Has("one"))
|
||||||
|
is.True(s.Has("two"))
|
||||||
|
is.True(s.Has("three"))
|
||||||
|
is.True(!s.Has("four"))
|
||||||
|
|
||||||
|
is.Equal(set.New("one").String(), "set(one)")
|
||||||
|
|
||||||
|
var n set.Set[string]
|
||||||
|
is.Equal(n.String(), "set(<nil>)")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBoundSet(t *testing.T) {
|
||||||
|
is := is.New(t)
|
||||||
|
|
||||||
|
s := set.NewBoundSet(1, 100, 1, 2, 3, 100, 1001)
|
||||||
|
|
||||||
|
is.True(s.Has(1))
|
||||||
|
is.True(s.Has(2))
|
||||||
|
is.True(s.Has(3))
|
||||||
|
is.True(!s.Has(1001))
|
||||||
|
|
||||||
|
is.Equal(set.NewBoundSet(1, 100, 1).String(), "set(1)")
|
||||||
|
|
||||||
|
}
|
162
slice/slice.go
Normal file
162
slice/slice.go
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
package slice
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go.sour.is/pkg/math"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FilterType returns a subset that matches the type.
|
||||||
|
func FilterType[T any](in ...any) []T {
|
||||||
|
lis := make([]T, 0, len(in))
|
||||||
|
for _, u := range in {
|
||||||
|
if t, ok := u.(T); ok {
|
||||||
|
lis = append(lis, t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lis
|
||||||
|
}
|
||||||
|
|
||||||
|
func FilterFn[T any](fn func(T) bool, in ...T) []T {
|
||||||
|
lis := make([]T, 0, len(in))
|
||||||
|
for _, t := range in {
|
||||||
|
if fn(t) {
|
||||||
|
lis = append(lis, t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lis
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find returns the first of type found. or false if not found.
|
||||||
|
func Find[T any](in ...any) (T, bool) {
|
||||||
|
return First(FilterType[T](in...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func FindFn[T any](fn func(T) bool, in ...T) (T, bool) {
|
||||||
|
return First(FilterFn(fn, in...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// First returns the first element in a slice.
|
||||||
|
func First[T any](in ...T) (T, bool) {
|
||||||
|
if len(in) == 0 {
|
||||||
|
var zero T
|
||||||
|
return zero, false
|
||||||
|
}
|
||||||
|
return in[0], true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map applys func to each element s and returns results as slice.
|
||||||
|
func Map[T, U any](f func(int, T) U) func(...T) []U {
|
||||||
|
return func(lis ...T) []U {
|
||||||
|
r := make([]U, len(lis))
|
||||||
|
for i, v := range lis {
|
||||||
|
r[i] = f(i, v)
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Reduce[T, R any](r R, fn func(T, R, int) R) func(...T) R {
|
||||||
|
return func(lis ...T) R {
|
||||||
|
for i, t := range lis {
|
||||||
|
r = fn(t, r, i)
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Pair[K comparable, V any] struct {
|
||||||
|
Key K
|
||||||
|
Value V
|
||||||
|
}
|
||||||
|
|
||||||
|
func FromMap[K comparable, V any](m map[K]V) (keys []K, values []V) {
|
||||||
|
if m == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
keys = FromMapKeys(m)
|
||||||
|
return keys, FromMapValues(m, keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
func FromMapKeys[K comparable, V any](m map[K]V) (keys []K) {
|
||||||
|
if m == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
keys = make([]K, 0, len(m))
|
||||||
|
|
||||||
|
for k := range m {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
func FromMapValues[K comparable, V any](m map[K]V, keys []K) (values []V) {
|
||||||
|
if m == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
values = make([]V, 0, len(keys))
|
||||||
|
for _, k := range keys {
|
||||||
|
values = append(values, m[k])
|
||||||
|
}
|
||||||
|
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToMap[K comparable, V any](keys []K, values []V) (m map[K]V) {
|
||||||
|
m = make(map[K]V, len(keys))
|
||||||
|
|
||||||
|
for i := range keys {
|
||||||
|
if len(values) < i {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
m[keys[i]] = values[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func Zip[K comparable, V any](k []K, v []V) []Pair[K, V] {
|
||||||
|
lis := make([]Pair[K, V], math.Max(len(k), len(v)))
|
||||||
|
for i := range lis {
|
||||||
|
if k != nil && len(k) > i {
|
||||||
|
lis[i].Key = k[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
if v != nil && len(v) > i {
|
||||||
|
lis[i].Value = v[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lis
|
||||||
|
}
|
||||||
|
|
||||||
|
func Align[T any](k []T, v []T, less func(T, T) bool) []Pair[*T, *T] {
|
||||||
|
lis := make([]Pair[*T, *T], 0, math.Max(len(k), len(v)))
|
||||||
|
|
||||||
|
var j int
|
||||||
|
|
||||||
|
for i := 0; i < len(k); {
|
||||||
|
if j >= len(v) || less(k[i], v[j]) {
|
||||||
|
lis = append(lis, Pair[*T, *T]{&k[i], nil})
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if less(v[j], k[i]) {
|
||||||
|
lis = append(lis, Pair[*T, *T]{nil, &v[j]})
|
||||||
|
j++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
lis = append(lis, Pair[*T, *T]{&k[i], &v[j]})
|
||||||
|
i++
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
for ; j < len(v); j++ {
|
||||||
|
lis = append(lis, Pair[*T, *T]{nil, &v[j]})
|
||||||
|
}
|
||||||
|
|
||||||
|
return lis
|
||||||
|
|
||||||
|
}
|
53
slice/slice_test.go
Normal file
53
slice/slice_test.go
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
package slice_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/matryer/is"
|
||||||
|
"go.sour.is/pkg/slice"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAlign(t *testing.T) {
|
||||||
|
type testCase struct {
|
||||||
|
left, right []string
|
||||||
|
combined []slice.Pair[*string, *string]
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []testCase{
|
||||||
|
{
|
||||||
|
left: []string{"1", "3", "5"},
|
||||||
|
right: []string{"2", "3", "4"},
|
||||||
|
combined: []slice.Pair[*string, *string]{
|
||||||
|
{ptr("1"), nil},
|
||||||
|
{nil, ptr("2")},
|
||||||
|
{ptr("3"), ptr("3")},
|
||||||
|
{nil, ptr("4")},
|
||||||
|
{ptr("5"), nil},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
left: []string{"2", "3", "4"},
|
||||||
|
right: []string{"1", "3", "5"},
|
||||||
|
combined: []slice.Pair[*string, *string]{
|
||||||
|
{nil, ptr("1")},
|
||||||
|
{ptr("2"), nil},
|
||||||
|
{ptr("3"), ptr("3")},
|
||||||
|
{ptr("4"), nil},
|
||||||
|
{nil, ptr("5")},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
is := is.New(t)
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
combined := slice.Align(tt.left, tt.right, func(l, r string) bool { return l < r })
|
||||||
|
is.Equal(len(combined), len(tt.combined))
|
||||||
|
for i := range combined {
|
||||||
|
is.Equal(combined[i], tt.combined[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ptr[T any](v T) *T { return &v }
|
30
xdg/path_darwin.go
Normal file
30
xdg/path_darwin.go
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
//go:build darwin
|
||||||
|
// +build darwin
|
||||||
|
|
||||||
|
package xdg
|
||||||
|
|
||||||
|
func literal(name string) string {
|
||||||
|
return "$" + name
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultDataHome = "~/Library/Application Support"
|
||||||
|
defaultDataDirs = "/Library/Application Support"
|
||||||
|
defaultConfigHome = "~/Library/Preferences"
|
||||||
|
defaultConfigDirs = "/Library/Preferences"
|
||||||
|
defaultCacheHome = "~/Library/Caches"
|
||||||
|
defaultStateHome = "~/Library/Caches"
|
||||||
|
defaultRuntime = "~/Library/Application Support"
|
||||||
|
|
||||||
|
defaultDesktop = "~/Desktop"
|
||||||
|
defaultDownload = "~/Downloads"
|
||||||
|
defaultDocuments = "~/Documents"
|
||||||
|
defaultMusic = "~/Music"
|
||||||
|
defaultPictures = "~/Pictures"
|
||||||
|
defaultVideos = "~/Videos"
|
||||||
|
defaultTemplates = "~/Templates"
|
||||||
|
defaultPublic = "~/Public"
|
||||||
|
|
||||||
|
defaultApplicationDirs = "~/Applications:/Applications"
|
||||||
|
defaultFontDirs = "~/Library/Fonts:/Library/Fonts:/System/Library/Fonts:/Network/Library/Fonts"
|
||||||
|
)
|
30
xdg/path_linux.go
Normal file
30
xdg/path_linux.go
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
//go:build linux
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package xdg
|
||||||
|
|
||||||
|
func literal(name string) string {
|
||||||
|
return "$" + name
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultDataHome = "~/.local/share"
|
||||||
|
defaultDataDirs = "/usr/local/share:/usr/share"
|
||||||
|
defaultConfigHome = "~/.config"
|
||||||
|
defaultConfigDirs = "/etc/xdg"
|
||||||
|
defaultCacheHome = "~/.local/cache"
|
||||||
|
defaultStateHome = "~/.local/state"
|
||||||
|
defaultRuntime = "/run/user/$UID"
|
||||||
|
|
||||||
|
defaultDesktop = "~/Desktop"
|
||||||
|
defaultDownload = "~/Downloads"
|
||||||
|
defaultDocuments = "~/Documents"
|
||||||
|
defaultMusic = "~/Music"
|
||||||
|
defaultPictures = "~/Pictures"
|
||||||
|
defaultVideos = "~/Videos"
|
||||||
|
defaultTemplates = "~/Templates"
|
||||||
|
defaultPublic = "~/Public"
|
||||||
|
|
||||||
|
defaultApplicationDirs = "~/Applications:/Applications"
|
||||||
|
defaultFontDirs = "~/.local/share/fonts:/usr/local/share/fonts:/usr/share/fonts:~/.fonts"
|
||||||
|
)
|
30
xdg/path_windows.go
Normal file
30
xdg/path_windows.go
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
//go:build windows
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package xdg
|
||||||
|
|
||||||
|
func literal(name string) string {
|
||||||
|
return "%" + name + "%"
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultDataHome = `%LOCALAPPDATA%`
|
||||||
|
defaultDataDirs = `%APPDATA%\Roaming;%ProgramData%`
|
||||||
|
defaultConfigHome = `%LOCALAPPDATA%`
|
||||||
|
defaultConfigDirs = `%ProgramData%`
|
||||||
|
defaultCacheHome = `%LOCALAPPDATA%\cache`
|
||||||
|
defaultStateHome = `%LOCALAPPDATA%\state`
|
||||||
|
defaultRuntime = `%LOCALAPPDATA%`
|
||||||
|
|
||||||
|
defaultDesktop = `%USERPROFILE%\Desktop`
|
||||||
|
defaultDownload = `%USERPROFILE%\Downloads`
|
||||||
|
defaultDocuments = `%USERPROFILE%\Documents`
|
||||||
|
defaultMusic = `%USERPROFILE%\Music`
|
||||||
|
defaultPictures = `%USERPROFILE%\Pictures`
|
||||||
|
defaultVideos = `%USERPROFILE%\Videos`
|
||||||
|
defaultTemplates = `%USERPROFILE%\Templates`
|
||||||
|
defaultPublic = `%USERPROFILE%\Public`
|
||||||
|
|
||||||
|
defaultApplicationDirs = `%APPDATA%\Roaming\Microsoft\Windows\Start Menu\Programs`
|
||||||
|
defaultFontDirs = `%windir%\Fonts;%LOCALAPPDATA%\Microsoft\Windows\Fonts`
|
||||||
|
)
|
52
xdg/xdg.go
Normal file
52
xdg/xdg.go
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
package xdg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
EnvDataHome = setENV("XDG_DATA_HOME", defaultDataHome)
|
||||||
|
EnvDataDirs = setENV("XDG_DATA_DIRS", defaultDataDirs)
|
||||||
|
EnvConfigHome = setENV("XDG_CONFIG_HOME", defaultConfigHome)
|
||||||
|
EnvConfigDirs = setENV("XDG_CONFIG_DIRS", defaultConfigDirs)
|
||||||
|
EnvCacheHome = setENV("XDG_CACHE_HOME", defaultCacheHome)
|
||||||
|
EnvStateHome = setENV("XDG_STATE_HOME", defaultStateHome)
|
||||||
|
EnvRuntime = setENV("XDG_RUNTIME_DIR", defaultRuntime)
|
||||||
|
EnvDesktopDir = setENV("XDG_DESKTOP_DIR", defaultDesktop)
|
||||||
|
EnvDownloadDir = setENV("XDG_DOWNLOAD_DIR", defaultDownload)
|
||||||
|
EnvDocumentsDir = setENV("XDG_DOCUMENTS_DIR", defaultDocuments)
|
||||||
|
EnvMusicDir = setENV("XDG_MUSIC_DIR", defaultMusic)
|
||||||
|
EnvPicturesDir = setENV("XDG_PICTURES_DIR", defaultPictures)
|
||||||
|
EnvVideosDir = setENV("XDG_VIDEOS_DIR", defaultVideos)
|
||||||
|
EnvTemplatesDir = setENV("XDG_TEMPLATES_DIR", defaultTemplates)
|
||||||
|
EnvPublicShareDir = setENV("XDG_PUBLICSHARE_DIR", defaultPublic)
|
||||||
|
EnvApplicationsDir = setENV("XDG_APPLICATIONS_DIR", defaultApplicationDirs)
|
||||||
|
EnvFontsDir = setENV("XDG_FONTS_DIR", defaultFontDirs)
|
||||||
|
)
|
||||||
|
|
||||||
|
func setENV(name, value string) string {
|
||||||
|
if _, ok := os.LookupEnv(name); !ok {
|
||||||
|
os.Setenv(name, value)
|
||||||
|
}
|
||||||
|
return literal(name)
|
||||||
|
}
|
||||||
|
func Get(base, suffix string) string {
|
||||||
|
paths := strings.Split(os.ExpandEnv(base), string(os.PathListSeparator))
|
||||||
|
for i, path := range paths {
|
||||||
|
if strings.HasPrefix(path, "~") {
|
||||||
|
path = strings.Replace(path, "~", getHome(), 1)
|
||||||
|
}
|
||||||
|
paths[i] = os.ExpandEnv(filepath.Join(path, suffix))
|
||||||
|
}
|
||||||
|
return strings.Join(paths, string(os.PathListSeparator))
|
||||||
|
}
|
||||||
|
|
||||||
|
func getHome() string {
|
||||||
|
home, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
return "."
|
||||||
|
}
|
||||||
|
return home
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user