Compare commits
82 Commits
inprogress
...
main
Author | SHA1 | Date | |
---|---|---|---|
6a510bf258 | |||
1f909bfe06 | |||
b0999c998a | |||
3aa52ef05c | |||
1ebb92237c | |||
ba29b823ba | |||
a0b3952424 | |||
15ca8a9683 | |||
a1c569c3ee | |||
d2d6348545 | |||
012c2373a8 | |||
d726380dbd | |||
bdda63edac | |||
ee45a0fd49 | |||
813c2e898d | |||
0e3c76ee3a | |||
70c6996f39 | |||
0f665d3484 | |||
d67d768c1e | |||
5c06b824e9 | |||
aef282e19e | |||
84f95c1dcd | |||
a8c7b51518 | |||
42b10fab54 | |||
dc0752153b | |||
7d1b8bcc4c | |||
7139fd9e42 | |||
ee55e6b7ed | |||
9f44b4e6ab | |||
ed6ffdcb9f | |||
452de79614 | |||
be08f900a1 | |||
|
111b24435d | ||
|
5b9b436125 | ||
7c4c1521fd | |||
|
12a3e7b1ff | ||
|
b8c2f9f510 | ||
|
9168f5c7bc | ||
|
6ee995298b | ||
|
fcc5d08aa7 | ||
|
d27eb21ffb | ||
|
8ba2846e62 | ||
|
f5e4c4f972 | ||
ebd46555ac | |||
0621e2e815 | |||
|
3f3ea4439c | ||
|
0f504a98e9 | ||
|
f9a088269c | ||
|
2b9f0ffa12 | ||
|
c7f56789de | ||
|
7d78cfb10a | ||
|
2fb3fae61f | ||
|
5b09ea3e96 | ||
|
352443f172 | ||
|
17569cfb2b | ||
|
0810ec73a0 | ||
|
4fc9c78dae | ||
|
250395d6b3 | ||
|
7315759b20 | ||
|
bbb45c8854 | ||
|
5e31d27c54 | ||
|
4b4d3b743d | ||
|
92b813c9ed | ||
|
165492e3ec | ||
|
cb6abfc4ba | ||
|
5bebbc473a | ||
|
4baeb07cb7 | ||
|
03df7902ad | ||
|
ab9561f8b3 | ||
|
12716ae972 | ||
|
6569c58e37 | ||
|
5bf052580f | ||
|
9dd9443bc9 | ||
|
7ae2a8ad25 | ||
|
e118d06985 | ||
|
33bba9d8f7 | ||
|
e7df4cc479 | ||
|
129968d179 | ||
|
9878ed4a79 | ||
|
9103de501b | ||
|
6425fcadcd | ||
|
4d3c5df454 |
37
.air.toml
37
.air.toml
|
@ -1,37 +0,0 @@
|
||||||
root = "."
|
|
||||||
testdata_dir = "data"
|
|
||||||
tmp_dir = "tmp"
|
|
||||||
|
|
||||||
[build]
|
|
||||||
args_bin = []
|
|
||||||
bin = "./tmp/main"
|
|
||||||
cmd = "go build -o ./tmp/main ."
|
|
||||||
delay = 1000
|
|
||||||
exclude_dir = ["assets", "tmp", "vendor", "testdata"]
|
|
||||||
exclude_file = []
|
|
||||||
exclude_regex = ["_test.go"]
|
|
||||||
exclude_unchanged = false
|
|
||||||
follow_symlink = false
|
|
||||||
full_bin = ""
|
|
||||||
include_dir = []
|
|
||||||
include_ext = ["go", "tpl", "tmpl", "html"]
|
|
||||||
kill_delay = "1s"
|
|
||||||
log = "build-errors.log"
|
|
||||||
send_interrupt = true
|
|
||||||
stop_on_error = true
|
|
||||||
|
|
||||||
[color]
|
|
||||||
app = ""
|
|
||||||
build = "yellow"
|
|
||||||
main = "magenta"
|
|
||||||
runner = "green"
|
|
||||||
watcher = "cyan"
|
|
||||||
|
|
||||||
[log]
|
|
||||||
time = false
|
|
||||||
|
|
||||||
[misc]
|
|
||||||
clean_on_exit = false
|
|
||||||
|
|
||||||
[screen]
|
|
||||||
clear_on_rebuild = false
|
|
14
.drone.yml
Normal file
14
.drone.yml
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
kind: pipeline
|
||||||
|
type: docker
|
||||||
|
name: build
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: test
|
||||||
|
image: golang:1.21.1
|
||||||
|
commands:
|
||||||
|
- go test -v -race -skip '^TestE2E|TestMain' ./...
|
||||||
|
|
||||||
|
trigger:
|
||||||
|
event:
|
||||||
|
- push
|
||||||
|
- pull_request
|
25
.gitea/workflows/bump-push.yml
Normal file
25
.gitea/workflows/bump-push.yml
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
name: Go Bump
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "main" ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
bump:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
fetch-tags: true
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v3
|
||||||
|
with:
|
||||||
|
go-version: 1.21.1
|
||||||
|
|
||||||
|
- run: go install github.com/psanetra/git-semver/cli@master
|
||||||
|
|
||||||
|
- run: git tag v$(cli next --stable=false) && git push --tags || echo no change
|
||||||
|
|
||||||
|
- run: echo "🍏 This job's status is ${{ job.status }}."
|
29
.gitea/workflows/github-mirror.yml
Normal file
29
.gitea/workflows/github-mirror.yml
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
name: Go Bump
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "main" ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
bump:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
fetch-tags: true
|
||||||
|
|
||||||
|
- name: Deploy to external repository
|
||||||
|
uses: https://git.sour.is/actions/github-action-push-to-another-repository@main
|
||||||
|
env:
|
||||||
|
API_TOKEN_GITHUB: ${{ secrets.GH_TOKEN }}
|
||||||
|
with:
|
||||||
|
# GitHub Action output files
|
||||||
|
source-directory: .
|
||||||
|
destination-github-username: sour-is
|
||||||
|
destination-repository-name: ev
|
||||||
|
user-email: jon@xuu.cc
|
||||||
|
# It defaults to `main`
|
||||||
|
target-branch: "main"
|
||||||
|
|
||||||
|
- run: echo "🍏 This job's status is ${{ job.status }}."
|
33
.gitea/workflows/test.yml
Normal file
33
.gitea/workflows/test.yml
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
name: Go Test
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "main" ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ "main" ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- run: echo "🎉 The job was automatically triggered by a ${{ gitea.event_name }} event."
|
||||||
|
- run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by Gitea!"
|
||||||
|
- run: echo "🔎 The name of your branch is ${{ gitea.ref }} and your repository is ${{ gitea.repository }}."
|
||||||
|
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- run: echo "💡 The ${{ gitea.repository }} repository has been cloned to the runner."
|
||||||
|
- run: echo "🖥️ The workflow is now ready to test your code on the runner."
|
||||||
|
|
||||||
|
- name: List files in the repository
|
||||||
|
run: |
|
||||||
|
ls ${{ gitea.workspace }}
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v3
|
||||||
|
with:
|
||||||
|
go-version: 1.21.1
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: go test --race -v ./...
|
||||||
|
|
||||||
|
- run: echo "🍏 This job's status is ${{ job.status }}."
|
25
.github/workflows/go.yml
vendored
Normal file
25
.github/workflows/go.yml
vendored
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
name: Go
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "main" ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ "main" ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v3
|
||||||
|
with:
|
||||||
|
go-version: 1.21.1
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: go build -v ./...
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: go test --race -v ./...
|
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -5,3 +5,8 @@ data/
|
||||||
local.mk
|
local.mk
|
||||||
logzio.yml
|
logzio.yml
|
||||||
tmp/
|
tmp/
|
||||||
|
/build
|
||||||
|
/ev
|
||||||
|
acct.yml
|
||||||
|
.DS_Store
|
||||||
|
/*.yaml
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1 +0,0 @@
|
||||||
local.mk:d632b22a2291637331e5613d35536c69e696447ce407d7320b4c5ab0922b47a9
|
|
11
LICENSE
Normal file
11
LICENSE
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
Copyright 2023 Jon Lundy <me@sour.is>
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
44
Makefile
44
Makefile
|
@ -1,44 +0,0 @@
|
||||||
export PATH:=$(shell go env GOPATH)/bin:$(PATH)
|
|
||||||
export EV_DATA=mem:
|
|
||||||
export EV_HTTP=:8080
|
|
||||||
export EV_TRACE_SAMPLE=always
|
|
||||||
export EV_TRACE_ENDPOINT=localhost:4318
|
|
||||||
-include local.mk
|
|
||||||
|
|
||||||
air: gen
|
|
||||||
ifeq (, $(shell which air))
|
|
||||||
go install github.com/cosmtrek/air@latest
|
|
||||||
endif
|
|
||||||
air
|
|
||||||
|
|
||||||
run:
|
|
||||||
go run .
|
|
||||||
|
|
||||||
test:
|
|
||||||
go test -cover -race ./...
|
|
||||||
|
|
||||||
|
|
||||||
GQLS=gqlgen.yml
|
|
||||||
GQLS:=$(GQLS) $(wildcard api/gql_ev/*.go)
|
|
||||||
GQLS:=$(GQLS) $(wildcard pkg/*/*.graphqls)
|
|
||||||
GQLS:=$(GQLS) $(wildcard app/*/*.graphqls)
|
|
||||||
GQLS:=$(GQLS) $(wildcard app/*/*.go)
|
|
||||||
GQLSRC=internal/graph/generated/generated.go
|
|
||||||
|
|
||||||
gen: gql
|
|
||||||
gql: $(GQLSRC)
|
|
||||||
$(GQLSRC): $(GQLS)
|
|
||||||
ifeq (, $(shell which gqlgen))
|
|
||||||
go install github.com/99designs/gqlgen@latest
|
|
||||||
endif
|
|
||||||
gqlgen
|
|
||||||
|
|
||||||
|
|
||||||
EV_HOST?=localhost:8080
|
|
||||||
load:
|
|
||||||
watch -n .1 "http POST $(EV_HOST)/inbox/asdf/test a=b one=1 two:='{\"v\":2}' | jq"
|
|
||||||
|
|
||||||
bi:
|
|
||||||
go build .
|
|
||||||
sudo mv ev /usr/local/bin/
|
|
||||||
sudo systemctl restart ev
|
|
|
@ -2,4 +2,6 @@
|
||||||
|
|
||||||
This project is learnings in building an eventstore and applications around it.
|
This project is learnings in building an eventstore and applications around it.
|
||||||
|
|
||||||
Feel free to explore.
|
Feel free to explore.
|
||||||
|
|
||||||
|
For examples of use of this see <go.sour.is/tools>
|
|
@ -1,62 +0,0 @@
|
||||||
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.26",
|
|
||||||
"cssSRI": "sha256-dKnNLEFwKSVFpkpjRWe+o/jQDM6n/JsvQ0J3l5Dk3fc=",
|
|
||||||
"faviconSRI": "sha256-GhTyE+McTU79R4+pRO6ih+4TfsTOrpPwD8ReKFzb3PM=",
|
|
||||||
"jsSRI": "sha256-SG9YAy4eywTcLckwij7V4oSCG3hOdV1m+2e1XuNxIgk=",
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,132 +0,0 @@
|
||||||
package gql
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"reflect"
|
|
||||||
"runtime/debug"
|
|
||||||
|
|
||||||
"github.com/99designs/gqlgen/graphql/handler"
|
|
||||||
"github.com/ravilushqa/otelgqlgen"
|
|
||||||
"github.com/sour-is/ev/app/gql/playground"
|
|
||||||
"github.com/sour-is/ev/app/msgbus"
|
|
||||||
"github.com/sour-is/ev/app/salty"
|
|
||||||
"github.com/sour-is/ev/internal/graph/generated"
|
|
||||||
"github.com/sour-is/ev/internal/lg"
|
|
||||||
"github.com/sour-is/ev/pkg/es"
|
|
||||||
"github.com/sour-is/ev/pkg/gql"
|
|
||||||
"github.com/vektah/gqlparser/v2/gqlerror"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Resolver struct {
|
|
||||||
msgbus.MsgbusResolver
|
|
||||||
salty.SaltyResolver
|
|
||||||
es.EventResolver
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(ctx context.Context, resolvers ...interface{ RegisterHTTP(*http.ServeMux) }) (*Resolver, error) {
|
|
||||||
_, span := lg.Span(ctx)
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
r := &Resolver{}
|
|
||||||
v := reflect.ValueOf(r)
|
|
||||||
v = reflect.Indirect(v)
|
|
||||||
noop := reflect.ValueOf(&noop{})
|
|
||||||
|
|
||||||
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()) {
|
|
||||||
span.AddEvent(fmt.Sprint("found ", field.Type().Name()))
|
|
||||||
field.Set(rs)
|
|
||||||
continue outer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
span.AddEvent(fmt.Sprint("default ", field.Type().Name()))
|
|
||||||
field.Set(noop)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Query returns generated.QueryResolver implementation.
|
|
||||||
func (r *Resolver) Query() generated.QueryResolver { return r }
|
|
||||||
|
|
||||||
// Query returns generated.QueryResolver implementation.
|
|
||||||
func (r *Resolver) Mutation() generated.MutationResolver { return r }
|
|
||||||
|
|
||||||
// Subscription returns generated.SubscriptionResolver implementation.
|
|
||||||
func (r *Resolver) Subscription() generated.SubscriptionResolver { return r }
|
|
||||||
|
|
||||||
// ChainMiddlewares will check all embeded resolvers for a GetMiddleware func and add to handler.
|
|
||||||
func (r *Resolver) ChainMiddlewares(h http.Handler) http.Handler {
|
|
||||||
v := reflect.ValueOf(r) // Get reflected value of *Resolver
|
|
||||||
v = reflect.Indirect(v) // Get the pointed value (returns a zero value on nil)
|
|
||||||
n := v.NumField() // Get number of fields to iterate over.
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
f := v.Field(i)
|
|
||||||
if !f.CanInterface() { // Skip non-interface types.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if iface, ok := f.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) RegisterHTTP(mux *http.ServeMux) {
|
|
||||||
gql := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: r}))
|
|
||||||
gql.SetRecoverFunc(NoopRecover)
|
|
||||||
gql.Use(otelgqlgen.Middleware())
|
|
||||||
mux.Handle("/", playground.Handler("GraphQL playground", "/gql"))
|
|
||||||
mux.Handle("/gql", lg.Htrace(r.ChainMiddlewares(gql), "gql"))
|
|
||||||
}
|
|
||||||
|
|
||||||
type noop struct{}
|
|
||||||
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ msgbus.MsgbusResolver = (*noop)(nil)
|
|
||||||
var _ salty.SaltyResolver = (*noop)(nil)
|
|
||||||
var _ es.EventResolver = (*noop)(nil)
|
|
||||||
|
|
||||||
func (*noop) CreateSaltyUser(ctx context.Context, nick string, pubkey string) (*salty.SaltyUser, error) {
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
||||||
func (*noop) Posts(ctx context.Context, streamID string, paging *gql.PageInput) (*gql.Connection, error) {
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
||||||
func (*noop) SaltyUser(ctx context.Context, nick string) (*salty.SaltyUser, error) {
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
||||||
func (*noop) PostAdded(ctx context.Context, streamID string, after int64) (<-chan *msgbus.PostEvent, error) {
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
||||||
func (*noop) Events(ctx context.Context, streamID string, paging *gql.PageInput) (*gql.Connection, error) {
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
||||||
func (*noop) EventAdded(ctx context.Context, streamID string, after int64) (<-chan *es.GQLEvent, error) {
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*noop) RegisterHTTP(*http.ServeMux) {}
|
|
|
@ -1,16 +0,0 @@
|
||||||
extend type Query {
|
|
||||||
posts(streamID: String! paging: PageInput): Connection!
|
|
||||||
}
|
|
||||||
extend type Subscription {
|
|
||||||
"""after == 0 start from begining, after == -1 start from end"""
|
|
||||||
postAdded(streamID: String! after: Int! = -1): PostEvent
|
|
||||||
}
|
|
||||||
type PostEvent implements Edge @goModel(model: "github.com/sour-is/ev/app/msgbus.PostEvent") {
|
|
||||||
id: ID!
|
|
||||||
|
|
||||||
payload: String!
|
|
||||||
payloadJSON: Map!
|
|
||||||
tags: [String!]!
|
|
||||||
|
|
||||||
meta: Meta!
|
|
||||||
}
|
|
|
@ -1,573 +0,0 @@
|
||||||
package msgbus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gorilla/websocket"
|
|
||||||
"github.com/sour-is/ev/internal/lg"
|
|
||||||
"github.com/sour-is/ev/pkg/es"
|
|
||||||
"github.com/sour-is/ev/pkg/es/event"
|
|
||||||
"github.com/sour-is/ev/pkg/gql"
|
|
||||||
"go.opentelemetry.io/otel/metric/instrument"
|
|
||||||
"go.opentelemetry.io/otel/metric/instrument/syncint64"
|
|
||||||
"go.opentelemetry.io/otel/metric/unit"
|
|
||||||
"go.uber.org/multierr"
|
|
||||||
)
|
|
||||||
|
|
||||||
type service struct {
|
|
||||||
es *es.EventStore
|
|
||||||
|
|
||||||
m_gql_posts syncint64.Counter
|
|
||||||
m_gql_post_added syncint64.Counter
|
|
||||||
m_gql_post_added_event syncint64.Counter
|
|
||||||
m_req_time syncint64.Histogram
|
|
||||||
}
|
|
||||||
|
|
||||||
type MsgbusResolver interface {
|
|
||||||
Posts(ctx context.Context, streamID string, paging *gql.PageInput) (*gql.Connection, error)
|
|
||||||
PostAdded(ctx context.Context, streamID string, after int64) (<-chan *PostEvent, error)
|
|
||||||
RegisterHTTP(mux *http.ServeMux)
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(ctx context.Context, es *es.EventStore) (*service, error) {
|
|
||||||
ctx, span := lg.Span(ctx)
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
if err := event.Register(ctx, &PostEvent{}); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := event.RegisterName(ctx, "domain.PostEvent", &PostEvent{}); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
m := lg.Meter(ctx)
|
|
||||||
|
|
||||||
svc := &service{es: es}
|
|
||||||
|
|
||||||
var err, errs error
|
|
||||||
svc.m_gql_posts, err = m.SyncInt64().Counter("msgbus_posts",
|
|
||||||
instrument.WithDescription("msgbus graphql posts requests"),
|
|
||||||
)
|
|
||||||
errs = multierr.Append(errs, err)
|
|
||||||
|
|
||||||
svc.m_gql_post_added, err = m.SyncInt64().Counter("msgbus_post_added",
|
|
||||||
instrument.WithDescription("msgbus graphql post added subcription requests"),
|
|
||||||
)
|
|
||||||
errs = multierr.Append(errs, err)
|
|
||||||
|
|
||||||
svc.m_gql_post_added_event, err = m.SyncInt64().Counter("msgbus_post_event",
|
|
||||||
instrument.WithDescription("msgbus graphql post added subscription events"),
|
|
||||||
)
|
|
||||||
errs = multierr.Append(errs, err)
|
|
||||||
|
|
||||||
svc.m_req_time, err = m.SyncInt64().Histogram("msgbus_request_time",
|
|
||||||
instrument.WithDescription("msgbus graphql post added subscription events"),
|
|
||||||
instrument.WithUnit(unit.Unit("ns")),
|
|
||||||
)
|
|
||||||
errs = multierr.Append(errs, err)
|
|
||||||
|
|
||||||
span.RecordError(err)
|
|
||||||
|
|
||||||
return svc, errs
|
|
||||||
}
|
|
||||||
|
|
||||||
var upgrader = websocket.Upgrader{
|
|
||||||
WriteBufferSize: 4096,
|
|
||||||
CheckOrigin: func(r *http.Request) bool {
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *service) RegisterHTTP(mux *http.ServeMux) {
|
|
||||||
mux.Handle("/inbox/", lg.Htrace(http.StripPrefix("/inbox/", s), "inbox"))
|
|
||||||
}
|
|
||||||
func (s *service) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
||||||
ctx := r.Context()
|
|
||||||
|
|
||||||
ctx, span := lg.Span(ctx)
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
r = r.WithContext(ctx)
|
|
||||||
|
|
||||||
switch r.Method {
|
|
||||||
case http.MethodGet:
|
|
||||||
if r.Header.Get("Upgrade") == "websocket" {
|
|
||||||
s.websocket(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
s.get(w, r)
|
|
||||||
case http.MethodPost, http.MethodPut:
|
|
||||||
s.post(w, r)
|
|
||||||
default:
|
|
||||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Posts is the resolver for the events field.
|
|
||||||
func (s *service) Posts(ctx context.Context, streamID string, paging *gql.PageInput) (*gql.Connection, error) {
|
|
||||||
ctx, span := lg.Span(ctx)
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
s.m_gql_posts.Add(ctx, 1)
|
|
||||||
|
|
||||||
start := time.Now()
|
|
||||||
defer s.m_req_time.Record(ctx, time.Since(start).Milliseconds())
|
|
||||||
|
|
||||||
lis, err := s.es.Read(ctx, streamID, paging.GetIdx(0), paging.GetCount(30))
|
|
||||||
if err != nil {
|
|
||||||
span.RecordError(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
edges := make([]gql.Edge, 0, len(lis))
|
|
||||||
for i := range lis {
|
|
||||||
span.AddEvent(fmt.Sprint("post ", i, " of ", len(lis)))
|
|
||||||
e := lis[i]
|
|
||||||
|
|
||||||
post, ok := e.(*PostEvent)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
edges = append(edges, post)
|
|
||||||
}
|
|
||||||
|
|
||||||
var first, last uint64
|
|
||||||
if first, err = s.es.FirstIndex(ctx, streamID); err != nil {
|
|
||||||
span.RecordError(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if last, err = s.es.LastIndex(ctx, streamID); err != nil {
|
|
||||||
span.RecordError(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &gql.Connection{
|
|
||||||
Paging: &gql.PageInfo{
|
|
||||||
Next: lis.Last().EventMeta().Position < last,
|
|
||||||
Prev: lis.First().EventMeta().Position > first,
|
|
||||||
Begin: lis.First().EventMeta().Position,
|
|
||||||
End: lis.Last().EventMeta().Position,
|
|
||||||
},
|
|
||||||
Edges: edges,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *service) PostAdded(ctx context.Context, streamID string, after int64) (<-chan *PostEvent, error) {
|
|
||||||
ctx, span := lg.Span(ctx)
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
r.m_gql_post_added.Add(ctx, 1)
|
|
||||||
|
|
||||||
es := r.es.EventStream()
|
|
||||||
if es == nil {
|
|
||||||
return nil, fmt.Errorf("EventStore does not implement streaming")
|
|
||||||
}
|
|
||||||
|
|
||||||
sub, err := es.Subscribe(ctx, streamID, after)
|
|
||||||
if err != nil {
|
|
||||||
span.RecordError(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ch := make(chan *PostEvent)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
ctx, span := lg.Span(ctx)
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
{
|
|
||||||
ctx, span := lg.Fork(ctx)
|
|
||||||
defer func() {
|
|
||||||
defer span.End()
|
|
||||||
ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
err := sub.Close(ctx)
|
|
||||||
span.RecordError(err)
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
for sub.Recv(ctx) {
|
|
||||||
events, err := sub.Events(ctx)
|
|
||||||
if err != nil {
|
|
||||||
span.RecordError(err)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
span.AddEvent(fmt.Sprintf("received %d events", len(events)))
|
|
||||||
r.m_gql_post_added_event.Add(ctx, int64(len(events)))
|
|
||||||
|
|
||||||
for _, e := range events {
|
|
||||||
if p, ok := e.(*PostEvent); ok {
|
|
||||||
select {
|
|
||||||
case ch <- p:
|
|
||||||
continue
|
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return ch, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *service) get(w http.ResponseWriter, r *http.Request) {
|
|
||||||
ctx := r.Context()
|
|
||||||
|
|
||||||
ctx, span := lg.Span(ctx)
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
start := time.Now()
|
|
||||||
defer s.m_req_time.Record(ctx, time.Since(start).Milliseconds())
|
|
||||||
|
|
||||||
name, _, _ := strings.Cut(r.URL.Path, "/")
|
|
||||||
if name == "" {
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var first event.Event = event.NilEvent
|
|
||||||
if lis, err := s.es.Read(ctx, "post-"+name, 0, 1); err == nil && len(lis) > 0 {
|
|
||||||
first = lis[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
var pos, count int64 = 0, es.AllEvents
|
|
||||||
qry := r.URL.Query()
|
|
||||||
|
|
||||||
if i, err := strconv.ParseInt(qry.Get("index"), 10, 64); err == nil && i > 1 {
|
|
||||||
pos = i - 1
|
|
||||||
}
|
|
||||||
if i, err := strconv.ParseInt(qry.Get("pos"), 10, 64); err == nil {
|
|
||||||
pos = i
|
|
||||||
}
|
|
||||||
if i, err := strconv.ParseInt(qry.Get("n"), 10, 64); err == nil {
|
|
||||||
count = i
|
|
||||||
}
|
|
||||||
|
|
||||||
span.AddEvent(fmt.Sprint("GET topic=", name, " idx=", pos, " n=", count))
|
|
||||||
events, err := s.es.Read(ctx, "post-"+name, pos, count)
|
|
||||||
if err != nil {
|
|
||||||
span.RecordError(err)
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.Contains(r.Header.Get("Accept"), "application/json") {
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
|
||||||
|
|
||||||
if err = encodeJSON(w, first, events...); err != nil {
|
|
||||||
span.RecordError(err)
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range events {
|
|
||||||
fmt.Fprintln(w, events[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func (s *service) post(w http.ResponseWriter, r *http.Request) {
|
|
||||||
ctx := r.Context()
|
|
||||||
|
|
||||||
ctx, span := lg.Span(ctx)
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
start := time.Now()
|
|
||||||
defer s.m_req_time.Record(ctx, time.Since(start).Milliseconds())
|
|
||||||
|
|
||||||
name, tags, _ := strings.Cut(r.URL.Path, "/")
|
|
||||||
if name == "" {
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var first event.Event = event.NilEvent
|
|
||||||
if lis, err := s.es.Read(ctx, "post-"+name, 0, 1); err == nil && len(lis) > 0 {
|
|
||||||
first = lis[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
b, err := io.ReadAll(io.LimitReader(r.Body, 64*1024))
|
|
||||||
if err != nil {
|
|
||||||
span.RecordError(err)
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
r.Body.Close()
|
|
||||||
|
|
||||||
if name == "" {
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
events := event.NewEvents(&PostEvent{
|
|
||||||
payload: b,
|
|
||||||
tags: fields(tags),
|
|
||||||
})
|
|
||||||
|
|
||||||
_, err = s.es.Append(ctx, "post-"+name, events)
|
|
||||||
if err != nil {
|
|
||||||
span.RecordError(err)
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if first == event.NilEvent {
|
|
||||||
first = events.First()
|
|
||||||
}
|
|
||||||
|
|
||||||
m := events.First().EventMeta()
|
|
||||||
span.AddEvent(fmt.Sprint("POST topic=", name, " tags=", tags, " idx=", m.Position, " id=", m.EventID))
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusAccepted)
|
|
||||||
if strings.Contains(r.Header.Get("Accept"), "application/json") {
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
|
||||||
if err = encodeJSON(w, first, events...); err != nil {
|
|
||||||
span.RecordError(err)
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
span.AddEvent("finish response")
|
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "text/plain")
|
|
||||||
fmt.Fprintf(w, "OK %d %s", m.Position, m.EventID)
|
|
||||||
}
|
|
||||||
func (s *service) websocket(w http.ResponseWriter, r *http.Request) {
|
|
||||||
ctx := r.Context()
|
|
||||||
ctx, span := lg.Span(ctx)
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
name, _, _ := strings.Cut(r.URL.Path, "/")
|
|
||||||
if name == "" {
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var first event.Event = event.NilEvent
|
|
||||||
if lis, err := s.es.Read(ctx, "post-"+name, 0, 1); err == nil && len(lis) > 0 {
|
|
||||||
first = lis[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
var pos int64 = 0
|
|
||||||
qry := r.URL.Query()
|
|
||||||
|
|
||||||
if i, err := strconv.ParseInt(qry.Get("index"), 10, 64); err == nil && i > 0 {
|
|
||||||
pos = i - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
span.AddEvent(fmt.Sprint("WS topic=", name, " idx=", pos))
|
|
||||||
|
|
||||||
c, err := upgrader.Upgrade(w, r, nil)
|
|
||||||
if err != nil {
|
|
||||||
span.RecordError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer c.Close()
|
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
|
||||||
c.SetCloseHandler(func(code int, text string) error {
|
|
||||||
cancel()
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
if err := ctx.Err(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
mt, message, err := c.ReadMessage()
|
|
||||||
if err != nil {
|
|
||||||
span.RecordError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
span.AddEvent(fmt.Sprintf("recv: %d %s", mt, message))
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
es := s.es.EventStream()
|
|
||||||
if es == nil {
|
|
||||||
span.AddEvent("EventStore does not implement streaming")
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
sub, err := es.Subscribe(ctx, "post-"+name, pos)
|
|
||||||
if err != nil {
|
|
||||||
span.RecordError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
{
|
|
||||||
ctx, span := lg.Fork(ctx)
|
|
||||||
defer func() {
|
|
||||||
defer span.End()
|
|
||||||
ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
err := sub.Close(ctx)
|
|
||||||
span.RecordError(err)
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
span.AddEvent("start ws")
|
|
||||||
for sub.Recv(ctx) {
|
|
||||||
events, err := sub.Events(ctx)
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
span.AddEvent(fmt.Sprint("got events ", len(events)))
|
|
||||||
for i := range events {
|
|
||||||
e, ok := events[i].(*PostEvent)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
span.AddEvent(fmt.Sprint("send", i, e.String()))
|
|
||||||
|
|
||||||
var b bytes.Buffer
|
|
||||||
if err = encodeJSON(&b, first, e); err != nil {
|
|
||||||
span.RecordError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.WriteMessage(websocket.TextMessage, b.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
span.RecordError(err)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type PostEvent struct {
|
|
||||||
payload []byte
|
|
||||||
tags []string
|
|
||||||
|
|
||||||
eventMeta event.Meta
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *PostEvent) EventMeta() event.Meta {
|
|
||||||
if e == nil {
|
|
||||||
return event.Meta{}
|
|
||||||
}
|
|
||||||
return e.eventMeta
|
|
||||||
}
|
|
||||||
func (e *PostEvent) SetEventMeta(eventMeta event.Meta) {
|
|
||||||
if e == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
e.eventMeta = eventMeta
|
|
||||||
}
|
|
||||||
func (e *PostEvent) Values() any {
|
|
||||||
if e == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return struct {
|
|
||||||
Payload []byte
|
|
||||||
Tags []string
|
|
||||||
}{
|
|
||||||
Payload: e.payload,
|
|
||||||
Tags: e.tags,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func (e *PostEvent) MarshalBinary() ([]byte, error) {
|
|
||||||
j := e.Values()
|
|
||||||
return json.Marshal(&j)
|
|
||||||
}
|
|
||||||
func (e *PostEvent) UnmarshalBinary(b []byte) error {
|
|
||||||
j := struct {
|
|
||||||
Payload []byte
|
|
||||||
Tags []string
|
|
||||||
}{}
|
|
||||||
err := json.Unmarshal(b, &j)
|
|
||||||
e.payload = j.Payload
|
|
||||||
e.tags = j.Tags
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
func (e *PostEvent) MarshalJSON() ([]byte, error) { return e.MarshalBinary() }
|
|
||||||
func (e *PostEvent) UnmarshalJSON(b []byte) error { return e.UnmarshalBinary(b) }
|
|
||||||
|
|
||||||
func (e *PostEvent) ID() string { return e.eventMeta.GetEventID() }
|
|
||||||
func (e *PostEvent) Tags() []string { return e.tags }
|
|
||||||
func (e *PostEvent) Payload() string { return string(e.payload) }
|
|
||||||
func (e *PostEvent) PayloadJSON(ctx context.Context) (m map[string]interface{}, err error) {
|
|
||||||
err = json.Unmarshal([]byte(e.payload), &m)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
func (e *PostEvent) Meta() *event.Meta { return &e.eventMeta }
|
|
||||||
func (e *PostEvent) IsEdge() {}
|
|
||||||
|
|
||||||
func (e *PostEvent) String() string {
|
|
||||||
var b bytes.Buffer
|
|
||||||
|
|
||||||
// b.WriteString(e.eventMeta.StreamID)
|
|
||||||
// b.WriteRune('@')
|
|
||||||
b.WriteString(strconv.FormatUint(e.eventMeta.Position, 10))
|
|
||||||
b.WriteRune('\t')
|
|
||||||
|
|
||||||
b.WriteString(e.eventMeta.EventID.String())
|
|
||||||
b.WriteRune('\t')
|
|
||||||
b.WriteString(string(e.payload))
|
|
||||||
if len(e.tags) > 0 {
|
|
||||||
b.WriteRune('\t')
|
|
||||||
b.WriteString(strings.Join(e.tags, ","))
|
|
||||||
}
|
|
||||||
|
|
||||||
return b.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func fields(s string) []string {
|
|
||||||
if s == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return strings.Split(s, "/")
|
|
||||||
}
|
|
||||||
|
|
||||||
func encodeJSON(w io.Writer, first event.Event, events ...event.Event) error {
|
|
||||||
out := make([]struct {
|
|
||||||
ID uint64 `json:"id"`
|
|
||||||
Payload []byte `json:"payload"`
|
|
||||||
Created string `json:"created"`
|
|
||||||
Tags []string `json:"tags"`
|
|
||||||
Topic struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
TTL uint64 `json:"ttl"`
|
|
||||||
Seq uint64 `json:"seq"`
|
|
||||||
Created string `json:"created"`
|
|
||||||
} `json:"topic"`
|
|
||||||
}, len(events))
|
|
||||||
|
|
||||||
for i := range events {
|
|
||||||
e, ok := events[i].(*PostEvent)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
out[i].ID = e.EventMeta().Position
|
|
||||||
out[i].Created = e.EventMeta().Created().Format(time.RFC3339Nano)
|
|
||||||
out[i].Payload = e.payload
|
|
||||||
out[i].Tags = e.tags
|
|
||||||
out[i].Topic.Name = strings.TrimPrefix(e.EventMeta().StreamID, "post-")
|
|
||||||
out[i].Topic.Created = first.EventMeta().Created().Format(time.RFC3339Nano)
|
|
||||||
out[i].Topic.Seq = e.EventMeta().Position
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(out) == 1 {
|
|
||||||
return json.NewEncoder(w).Encode(out[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
return json.NewEncoder(w).Encode(out)
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
package msgbus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestUnmarshal(t *testing.T) {
|
|
||||||
m := &PostEvent{}
|
|
||||||
s := `{"Payload":"QkVHSU4gU0FMVFBBQ0sgRU5DUllQVEVEIE1FU1NBR0UuIGtlRElETVFXWXZWUjU4QiBGVGZUZURRTkhzT2ZuZWMgWUV1dkNYSTNoVDBYZzZKIDJxeXBJcmdsT3ZlZjlqciA0dHVQaWpJRVgxdlpoTEkgUTVKTzNvYVY5cnNna01BIEFOeTJwYjg2Qkd6N0JGMCA4MXJuZk9OV2RRM0VldFAgSmU0ZFlHeUI4NkRydkVrIGNqcFpoajNmcEJUcDdiZiBpMktwRDJQM1kzNVJBQU8gWmIyZGZtOVpneHZNSVJ2IDJsVVRCWTQxVEtZNkJhTyB2NGVIeXF1MENjQkR4dW8gSEZIekxJd3BBb3ZoRGt1IGFJdXRZYzdhZ3puMUxvNCBZQWFyUDZxVVVtTVlrQXAgYkdSYTZLZWVOa3ZzTDdMIHFoMWd6WUlnS2l6cW51eCB1SVQ0QTdaU1BscWxlR1IgbTk3M2ZoNUduWEZTM3MwIDJzQ2FvclpmN2c1RUo5TiBlS1hkZkFSMWF6TVRBek8gSmNEM1hDNDBwVTRpaG9mIE8wYnB2RU1UOVlUb3ZOWCBobVUxZWZ6enpyMUFDdXcgWExwcUhlVXNXdEtGcXRnIHdyWEZleExBYU50T21jRSBOeFFDUi4gRU5EIFNBTFRQQUNLIEVOQ1JZUFRFRCBNRVNTQUdFLgo=","Tags":null}`
|
|
||||||
|
|
||||||
err := json.Unmarshal([]byte(s), m)
|
|
||||||
t.Log(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMarshal(t *testing.T) {
|
|
||||||
m := &PostEvent{
|
|
||||||
payload: []byte("QkVHSU4gU0FMVFBBQ0sgRU5DUllQVEVEIE1FU1NBR0UuIGtlRElETVFXWXZWUjU4QiBGVGZUZURRTkhzT2ZuZWMgWUV1dkNYSTNoVDBYZzZKIDJxeXBJcmdsT3ZlZjlqciA0dHVQaWpJRVgxdlpoTEkgUTVKTzNvYVY5cnNna01BIEFOeTJwYjg2Qkd6N0JGMCA4MXJuZk9OV2RRM0VldFAgSmU0ZFlHeUI4NkRydkVrIGNqcFpoajNmcEJUcDdiZiBpMktwRDJQM1kzNVJBQU8gWmIyZGZtOVpneHZNSVJ2IDJsVVRCWTQxVEtZNkJhTyB2NGVIeXF1MENjQkR4dW8gSEZIekxJd3BBb3ZoRGt1IGFJdXRZYzdhZ3puMUxvNCBZQWFyUDZxVVVtTVlrQXAgYkdSYTZLZWVOa3ZzTDdMIHFoMWd6WUlnS2l6cW51eCB1SVQ0QTdaU1BscWxlR1IgbTk3M2ZoNUduWEZTM3MwIDJzQ2FvclpmN2c1RUo5TiBlS1hkZkFSMWF6TVRBek8gSmNEM1hDNDBwVTRpaG9mIE8wYnB2RU1UOVlUb3ZOWCBobVUxZWZ6enpyMUFDdXcgWExwcUhlVXNXdEtGcXRnIHdyWEZleExBYU50T21jRSBOeFFDUi4gRU5EIFNBTFRQQUNLIEVOQ1JZUFRFRCBNRVNTQUdFLgo="),
|
|
||||||
}
|
|
||||||
b, err := json.Marshal(m)
|
|
||||||
t.Log(err)
|
|
||||||
|
|
||||||
err = json.Unmarshal(b, m)
|
|
||||||
t.Log(err)
|
|
||||||
}
|
|
|
@ -1,183 +0,0 @@
|
||||||
package peerfinder
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"net/netip"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sour-is/ev/pkg/es/event"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Request struct {
|
|
||||||
eventMeta event.Meta
|
|
||||||
|
|
||||||
RequestIP string `json:"req_ip"`
|
|
||||||
Hidden bool `json:"hide,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Request) StreamID() string {
|
|
||||||
return r.EventMeta().GetEventID()
|
|
||||||
}
|
|
||||||
func (r *Request) RequestID() string {
|
|
||||||
return r.EventMeta().GetEventID()
|
|
||||||
}
|
|
||||||
func (r *Request) Created() time.Time {
|
|
||||||
return r.EventMeta().Created()
|
|
||||||
}
|
|
||||||
func (r *Request) CreatedString() string {
|
|
||||||
return r.Created().Format("2006-01-02 15:04:05")
|
|
||||||
}
|
|
||||||
func (r *Request) Family() int {
|
|
||||||
if r == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
ip, err := netip.ParseAddr(r.RequestIP)
|
|
||||||
switch {
|
|
||||||
case err != nil:
|
|
||||||
return 0
|
|
||||||
case ip.Is4():
|
|
||||||
return 1
|
|
||||||
default:
|
|
||||||
return 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ event.Event = (*Request)(nil)
|
|
||||||
|
|
||||||
func (e *Request) EventMeta() event.Meta {
|
|
||||||
if e == nil {
|
|
||||||
return event.Meta{}
|
|
||||||
}
|
|
||||||
return e.eventMeta
|
|
||||||
}
|
|
||||||
func (e *Request) SetEventMeta(m event.Meta) {
|
|
||||||
if e != nil {
|
|
||||||
e.eventMeta = m
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func (e *Request) MarshalBinary() (text []byte, err error) {
|
|
||||||
return json.Marshal(e)
|
|
||||||
}
|
|
||||||
func (e *Request) UnmarshalBinary(b []byte) error {
|
|
||||||
return json.Unmarshal(b, e)
|
|
||||||
}
|
|
||||||
func (e *Request) MarshalEnviron() ([]byte, error) {
|
|
||||||
if e == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var b bytes.Buffer
|
|
||||||
b.WriteString("REQ_ID=")
|
|
||||||
b.WriteString(e.RequestID())
|
|
||||||
b.WriteRune('\n')
|
|
||||||
|
|
||||||
b.WriteString("REQ_IP=")
|
|
||||||
b.WriteString(e.RequestIP)
|
|
||||||
b.WriteRune('\n')
|
|
||||||
|
|
||||||
b.WriteString("REQ_FAMILY=")
|
|
||||||
if family := e.Family(); family > 0 {
|
|
||||||
b.WriteString(strconv.Itoa(family))
|
|
||||||
}
|
|
||||||
b.WriteRune('\n')
|
|
||||||
|
|
||||||
b.WriteString("REQ_CREATED=")
|
|
||||||
b.WriteString(e.CreatedString())
|
|
||||||
b.WriteRune('\n')
|
|
||||||
|
|
||||||
return b.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type Result struct {
|
|
||||||
eventMeta event.Meta
|
|
||||||
|
|
||||||
RequestID string `json:"req_id"`
|
|
||||||
PeerID string `json:"peer_id"`
|
|
||||||
PeerVersion string `json:"peer_version"`
|
|
||||||
Latency float64 `json:"latency,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Result) Created() time.Time {
|
|
||||||
return r.eventMeta.Created()
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ event.Event = (*Result)(nil)
|
|
||||||
|
|
||||||
func (e *Result) EventMeta() event.Meta {
|
|
||||||
if e == nil {
|
|
||||||
return event.Meta{}
|
|
||||||
}
|
|
||||||
return e.eventMeta
|
|
||||||
}
|
|
||||||
func (e *Result) SetEventMeta(m event.Meta) {
|
|
||||||
if e != nil {
|
|
||||||
e.eventMeta = m
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func (e *Result) MarshalBinary() (text []byte, err error) {
|
|
||||||
return json.Marshal(e)
|
|
||||||
}
|
|
||||||
func (e *Result) UnmarshalBinary(b []byte) error {
|
|
||||||
return json.Unmarshal(b, e)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Info struct {
|
|
||||||
ScriptVersion string `json:"script_version"`
|
|
||||||
|
|
||||||
event.AggregateRoot
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ event.Aggregate = (*Info)(nil)
|
|
||||||
|
|
||||||
func (a *Info) ApplyEvent(lis ...event.Event) {
|
|
||||||
for _, e := range lis {
|
|
||||||
switch e := e.(type) {
|
|
||||||
case *VersionChanged:
|
|
||||||
a.ScriptVersion = e.ScriptVersion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func (a *Info) MarshalEnviron() ([]byte, error) {
|
|
||||||
var b bytes.Buffer
|
|
||||||
|
|
||||||
b.WriteString("SCRIPT_VERSION=")
|
|
||||||
b.WriteString(a.ScriptVersion)
|
|
||||||
b.WriteRune('\n')
|
|
||||||
|
|
||||||
return b.Bytes(), nil
|
|
||||||
}
|
|
||||||
func (a *Info) OnCreate() error {
|
|
||||||
if a.StreamVersion() == 0 {
|
|
||||||
event.Raise(a, &VersionChanged{ScriptVersion: initVersion})
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type VersionChanged struct {
|
|
||||||
ScriptVersion string `json:"script_version"`
|
|
||||||
|
|
||||||
eventMeta event.Meta
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ event.Event = (*VersionChanged)(nil)
|
|
||||||
|
|
||||||
func (e *VersionChanged) EventMeta() event.Meta {
|
|
||||||
if e == nil {
|
|
||||||
return event.Meta{}
|
|
||||||
}
|
|
||||||
return e.eventMeta
|
|
||||||
}
|
|
||||||
func (e *VersionChanged) SetEventMeta(m event.Meta) {
|
|
||||||
if e != nil {
|
|
||||||
e.eventMeta = m
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func (e *VersionChanged) MarshalBinary() (text []byte, err error) {
|
|
||||||
return json.Marshal(e)
|
|
||||||
}
|
|
||||||
func (e *VersionChanged) UnmarshalBinary(b []byte) error {
|
|
||||||
return json.Unmarshal(b, e)
|
|
||||||
}
|
|
|
@ -1,268 +0,0 @@
|
||||||
package peerfinder
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
ulid "github.com/oklog/ulid/v2"
|
|
||||||
contentnegotiation "gitlab.com/jamietanna/content-negotiation-go"
|
|
||||||
|
|
||||||
"github.com/sour-is/ev/internal/lg"
|
|
||||||
"github.com/sour-is/ev/pkg/es"
|
|
||||||
"github.com/sour-is/ev/pkg/es/event"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
queueRequests = "pf-requests"
|
|
||||||
queueResponses = "pf-response-"
|
|
||||||
aggInfo = "pf-info"
|
|
||||||
initVersion = "1.1.0"
|
|
||||||
)
|
|
||||||
|
|
||||||
type service struct {
|
|
||||||
es *es.EventStore
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(ctx context.Context, es *es.EventStore) (*service, error) {
|
|
||||||
ctx, span := lg.Span(ctx)
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
if err := event.Register(ctx, &Request{}, &Result{}, &VersionChanged{}); err != nil {
|
|
||||||
span.RecordError(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
svc := &service{es: es}
|
|
||||||
|
|
||||||
return svc, nil
|
|
||||||
}
|
|
||||||
func (s *service) RegisterHTTP(mux *http.ServeMux) {
|
|
||||||
mux.Handle("/peers/", lg.Htrace(s, "peers"))
|
|
||||||
|
|
||||||
}
|
|
||||||
func (s *service) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
||||||
ctx := r.Context()
|
|
||||||
|
|
||||||
_, span := lg.Span(ctx)
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
r = r.WithContext(ctx)
|
|
||||||
|
|
||||||
switch r.Method {
|
|
||||||
case http.MethodGet:
|
|
||||||
switch {
|
|
||||||
case strings.HasPrefix(r.URL.Path, "/peers/pending/"):
|
|
||||||
s.getPending(w, r, strings.TrimPrefix(r.URL.Path, "/peers/pending/"))
|
|
||||||
return
|
|
||||||
|
|
||||||
case strings.HasPrefix(r.URL.Path, "/peers/req/"):
|
|
||||||
s.getResults(w, r, strings.TrimPrefix(r.URL.Path, "/peers/req/"))
|
|
||||||
return
|
|
||||||
|
|
||||||
default:
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case http.MethodPost:
|
|
||||||
switch {
|
|
||||||
case strings.HasPrefix(r.URL.Path, "/peers/req/"):
|
|
||||||
s.postResult(w, r, strings.TrimPrefix(r.URL.Path, "/peers/req/"))
|
|
||||||
return
|
|
||||||
|
|
||||||
case strings.HasPrefix(r.URL.Path, "/peers/req"):
|
|
||||||
s.postRequest(w, r)
|
|
||||||
return
|
|
||||||
|
|
||||||
default:
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *service) getPending(w http.ResponseWriter, r *http.Request, uuid string) {
|
|
||||||
ctx := r.Context()
|
|
||||||
|
|
||||||
_, span := lg.Span(ctx)
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
info, err := es.Upsert(ctx, s.es, "pf-info", func(ctx context.Context, agg *Info) error {
|
|
||||||
return agg.OnCreate() // initialize if not exists
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
span.RecordError(err)
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
requests, err := s.es.Read(ctx, queueRequests, -1, -30)
|
|
||||||
if err != nil {
|
|
||||||
span.RecordError(err)
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
responses, err := s.es.Read(ctx, queueResponses+uuid, -1, -30)
|
|
||||||
if err != nil {
|
|
||||||
span.RecordError(err)
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
req := filter(requests, responses)
|
|
||||||
|
|
||||||
negotiator := contentnegotiation.NewNegotiator("application/json", "text/environment", "text/plain", "text/html")
|
|
||||||
negotiated, _, err := negotiator.Negotiate(r.Header.Get("Accept"))
|
|
||||||
if err != nil {
|
|
||||||
span.RecordError(err)
|
|
||||||
w.WriteHeader(http.StatusNotAcceptable)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
span.AddEvent(negotiated.String())
|
|
||||||
switch negotiated.String() {
|
|
||||||
case "text/environment":
|
|
||||||
_, err = encodeTo(w, info.MarshalEnviron, req.MarshalEnviron)
|
|
||||||
case "application/json":
|
|
||||||
var out interface{} = info
|
|
||||||
if req != nil {
|
|
||||||
out = struct {
|
|
||||||
ScriptVersion string `json:"script_version"`
|
|
||||||
RequestID string `json:"req_id"`
|
|
||||||
RequestIP string `json:"req_ip"`
|
|
||||||
Family string `json:"req_family"`
|
|
||||||
Created string `json:"req_created"`
|
|
||||||
}{
|
|
||||||
info.ScriptVersion,
|
|
||||||
req.RequestID(),
|
|
||||||
req.RequestIP,
|
|
||||||
strconv.Itoa(req.Family()),
|
|
||||||
req.CreatedString(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err = json.NewEncoder(w).Encode(out)
|
|
||||||
}
|
|
||||||
span.RecordError(err)
|
|
||||||
}
|
|
||||||
func (s *service) getResults(w http.ResponseWriter, r *http.Request, uuid string) {
|
|
||||||
ctx := r.Context()
|
|
||||||
|
|
||||||
responses, err := s.es.Read(ctx, queueResponses+uuid, -1, es.AllEvents)
|
|
||||||
if err != nil {
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
negotiator := contentnegotiation.NewNegotiator("application/json", "text/environment", "text/csv", "text/plain", "text/html")
|
|
||||||
negotiated, _, err := negotiator.Negotiate("application/json")
|
|
||||||
if err != nil {
|
|
||||||
w.WriteHeader(http.StatusNotAcceptable)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch negotiated.String() {
|
|
||||||
// case "text/environment":
|
|
||||||
// encodeTo(w, responses.MarshalBinary)
|
|
||||||
case "application/json":
|
|
||||||
json.NewEncoder(w).Encode(responses)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func (s *service) postRequest(w http.ResponseWriter, r *http.Request) {
|
|
||||||
ctx := r.Context()
|
|
||||||
|
|
||||||
if err := r.ParseForm(); err != nil {
|
|
||||||
w.WriteHeader(http.StatusUnprocessableEntity)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
req := &Request{
|
|
||||||
RequestIP: r.Form.Get("req_ip"),
|
|
||||||
}
|
|
||||||
|
|
||||||
if hidden, err := strconv.ParseBool(r.Form.Get("req_hidden")); err != nil {
|
|
||||||
req.Hidden = hidden
|
|
||||||
}
|
|
||||||
|
|
||||||
s.es.Append(ctx, queueRequests, event.NewEvents(req))
|
|
||||||
}
|
|
||||||
func (s *service) postResult(w http.ResponseWriter, r *http.Request, id string) {
|
|
||||||
ctx := r.Context()
|
|
||||||
|
|
||||||
if _, err := ulid.ParseStrict(id); err != nil {
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := r.ParseForm(); err != nil {
|
|
||||||
w.WriteHeader(http.StatusUnprocessableEntity)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
latency, err := strconv.ParseFloat(r.Form.Get("res_latency"), 64)
|
|
||||||
if err != nil {
|
|
||||||
w.WriteHeader(http.StatusUnprocessableEntity)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
req := &Result{
|
|
||||||
RequestID: id,
|
|
||||||
PeerID: r.Form.Get("peer_id"),
|
|
||||||
PeerVersion: r.Form.Get("peer_version"),
|
|
||||||
Latency: latency,
|
|
||||||
}
|
|
||||||
|
|
||||||
s.es.Append(ctx, queueResponses+id, event.NewEvents(req))
|
|
||||||
}
|
|
||||||
|
|
||||||
func filter(requests, responses event.Events) *Request {
|
|
||||||
have := make(map[string]struct{}, len(responses))
|
|
||||||
for _, res := range toList[Result](responses...) {
|
|
||||||
have[res.RequestID] = struct{}{}
|
|
||||||
}
|
|
||||||
for _, req := range reverse(toList[Request](requests...)...) {
|
|
||||||
if _, ok := have[req.RequestID()]; !ok {
|
|
||||||
return req
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
func toList[E any, T es.PE[E]](lis ...event.Event) []T {
|
|
||||||
newLis := make([]T, 0, len(lis))
|
|
||||||
for i := range lis {
|
|
||||||
if e, ok := lis[i].(T); ok {
|
|
||||||
newLis = append(newLis, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return newLis
|
|
||||||
}
|
|
||||||
func reverse[T any](s ...T) []T {
|
|
||||||
first, last := 0, len(s)-1
|
|
||||||
for first < last {
|
|
||||||
s[first], s[last] = s[last], s[first]
|
|
||||||
first++
|
|
||||||
last--
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
func encodeTo(w io.Writer, fns ...func() ([]byte, error)) (int, error) {
|
|
||||||
i := 0
|
|
||||||
for _, fn := range fns {
|
|
||||||
b, err := fn()
|
|
||||||
if err != nil {
|
|
||||||
return i, err
|
|
||||||
}
|
|
||||||
|
|
||||||
j, err := w.Write(b)
|
|
||||||
i += j
|
|
||||||
if err != nil {
|
|
||||||
return i, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return i, nil
|
|
||||||
}
|
|
|
@ -1,155 +0,0 @@
|
||||||
package salty
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/keys-pub/keys"
|
|
||||||
"github.com/sour-is/ev/internal/lg"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Config represents a Salty Config for a User which at a minimum is required
|
|
||||||
// to have an Endpoint and Key (Public Key)
|
|
||||||
type Config struct {
|
|
||||||
Endpoint string `json:"endpoint"`
|
|
||||||
Key string `json:"key"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Capabilities struct {
|
|
||||||
AcceptEncoding string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c Capabilities) String() string {
|
|
||||||
if c.AcceptEncoding == "" {
|
|
||||||
return "<nil>"
|
|
||||||
}
|
|
||||||
return fmt.Sprint("accept-encoding: ", c.AcceptEncoding)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Addr struct {
|
|
||||||
User string
|
|
||||||
Domain string
|
|
||||||
|
|
||||||
capabilities Capabilities
|
|
||||||
discoveredDomain string
|
|
||||||
dns DNSResolver
|
|
||||||
endpoint *url.URL
|
|
||||||
key *keys.EdX25519PublicKey
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseAddr parsers a Salty Address for a user into it's user and domain
|
|
||||||
// parts and returns an Addr object with the User and Domain and a method
|
|
||||||
// for returning the expected User's Well-Known URI
|
|
||||||
func (s *service) ParseAddr(addr string) (*Addr, error) {
|
|
||||||
parts := strings.Split(strings.ToLower(addr), "@")
|
|
||||||
if len(parts) != 2 {
|
|
||||||
return nil, fmt.Errorf("expected nick@domain found %q", addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Addr{User: parts[0], Domain: parts[1], dns: s.dns}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Addr) String() string {
|
|
||||||
return fmt.Sprintf("%s@%s", a.User, a.Domain)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hash returns the Hex(SHA256Sum()) of the Address
|
|
||||||
func (a *Addr) Hash() string {
|
|
||||||
return fmt.Sprintf("%x", sha256.Sum256([]byte(strings.ToLower(a.String()))))
|
|
||||||
}
|
|
||||||
|
|
||||||
// URI returns the Well-Known URI for this Addr
|
|
||||||
func (a *Addr) URI() string {
|
|
||||||
return fmt.Sprintf("https://%s/.well-known/salty/%s.json", a.DiscoveredDomain(), a.User)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HashURI returns the Well-Known HashURI for this Addr
|
|
||||||
func (a *Addr) HashURI() string {
|
|
||||||
return fmt.Sprintf("https://%s/.well-known/salty/%s.json", a.DiscoveredDomain(), a.Hash())
|
|
||||||
}
|
|
||||||
|
|
||||||
// DiscoveredDomain returns the discovered domain (if any) of fallbacks to the Domain
|
|
||||||
func (a *Addr) DiscoveredDomain() string {
|
|
||||||
if a.discoveredDomain != "" {
|
|
||||||
return a.discoveredDomain
|
|
||||||
}
|
|
||||||
return a.Domain
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Addr) Refresh(ctx context.Context) error {
|
|
||||||
ctx, span := lg.Span(ctx)
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
span.AddEvent(fmt.Sprintf("Looking up SRV record for _salty._tcp.%s", a.Domain))
|
|
||||||
if _, srv, err := a.dns.LookupSRV(ctx, "salty", "tcp", a.Domain); err == nil {
|
|
||||||
if len(srv) > 0 {
|
|
||||||
a.discoveredDomain = strings.TrimSuffix(srv[0].Target, ".")
|
|
||||||
}
|
|
||||||
span.AddEvent(fmt.Sprintf("Discovered salty services %s", a.discoveredDomain))
|
|
||||||
} else if err != nil {
|
|
||||||
span.RecordError(fmt.Errorf("error looking up SRV record for _salty._tcp.%s : %s", a.Domain, err))
|
|
||||||
}
|
|
||||||
|
|
||||||
config, cap, err := fetchConfig(ctx, a.HashURI())
|
|
||||||
if err != nil {
|
|
||||||
// Fallback to plain user nick
|
|
||||||
span.RecordError(err)
|
|
||||||
|
|
||||||
config, cap, err = fetchConfig(ctx, a.URI())
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("error looking up user %s: %w", a, err)
|
|
||||||
span.RecordError(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
key, err := keys.NewEdX25519PublicKeyFromID(keys.ID(config.Key))
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("error parsing public key %s: %w", config.Key, err)
|
|
||||||
span.RecordError(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.key = key
|
|
||||||
|
|
||||||
u, err := url.Parse(config.Endpoint)
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("error parsing endpoint %s: %w", config.Endpoint, err)
|
|
||||||
span.RecordError(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.endpoint = u
|
|
||||||
a.capabilities = cap
|
|
||||||
|
|
||||||
span.AddEvent(fmt.Sprintf("Discovered endpoint: %v", a.endpoint))
|
|
||||||
span.AddEvent(fmt.Sprintf("Discovered capability: %v", a.capabilities))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func fetchConfig(ctx context.Context, addr string) (config Config, cap Capabilities, err error) {
|
|
||||||
ctx, span := lg.Span(ctx)
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
var req *http.Request
|
|
||||||
req, err = http.NewRequestWithContext(ctx, http.MethodGet, addr, nil)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := http.DefaultClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = json.NewDecoder(res.Body).Decode(&config); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
cap.AcceptEncoding = res.Header.Get("Accept-Encoding")
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
|
@ -1,100 +0,0 @@
|
||||||
package salty
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"crypto/sha256"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
|
|
||||||
"github.com/keys-pub/keys"
|
|
||||||
"github.com/oklog/ulid/v2"
|
|
||||||
"github.com/sour-is/ev/pkg/es/event"
|
|
||||||
"github.com/sour-is/ev/pkg/gql"
|
|
||||||
)
|
|
||||||
|
|
||||||
type SaltyUser struct {
|
|
||||||
name string
|
|
||||||
pubkey *keys.EdX25519PublicKey
|
|
||||||
inbox ulid.ULID
|
|
||||||
|
|
||||||
event.AggregateRoot
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ event.Aggregate = (*SaltyUser)(nil)
|
|
||||||
|
|
||||||
// ApplyEvent applies the event to the aggrigate state
|
|
||||||
func (a *SaltyUser) ApplyEvent(lis ...event.Event) {
|
|
||||||
for _, e := range lis {
|
|
||||||
switch e := e.(type) {
|
|
||||||
case *UserRegistered:
|
|
||||||
a.name = e.Name
|
|
||||||
a.pubkey = e.Pubkey
|
|
||||||
a.inbox = e.EventMeta().EventID
|
|
||||||
a.SetStreamID(a.streamID())
|
|
||||||
default:
|
|
||||||
log.Printf("unknown event %T", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *SaltyUser) streamID() string {
|
|
||||||
return fmt.Sprintf("saltyuser-%x", sha256.Sum256([]byte(strings.ToLower(a.name))))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *SaltyUser) OnUserRegister(name string, pubkey *keys.EdX25519PublicKey) error {
|
|
||||||
event.Raise(a, &UserRegistered{Name: name, Pubkey: pubkey})
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *SaltyUser) Nick() string { return a.name }
|
|
||||||
func (a *SaltyUser) Inbox() string { return a.inbox.String() }
|
|
||||||
func (a *SaltyUser) Pubkey() string { return a.pubkey.String() }
|
|
||||||
func (s *SaltyUser) Endpoint(ctx context.Context) (string, error) {
|
|
||||||
svc := gql.FromContext[contextKey, *service](ctx, saltyKey)
|
|
||||||
return url.JoinPath(svc.BaseURL(), s.inbox.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserRegistered struct {
|
|
||||||
Name string
|
|
||||||
Pubkey *keys.EdX25519PublicKey
|
|
||||||
|
|
||||||
eventMeta event.Meta
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ event.Event = (*UserRegistered)(nil)
|
|
||||||
|
|
||||||
func (e *UserRegistered) EventMeta() event.Meta {
|
|
||||||
if e == nil {
|
|
||||||
return event.Meta{}
|
|
||||||
}
|
|
||||||
return e.eventMeta
|
|
||||||
}
|
|
||||||
func (e *UserRegistered) SetEventMeta(m event.Meta) {
|
|
||||||
if e != nil {
|
|
||||||
e.eventMeta = m
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func (e *UserRegistered) MarshalBinary() (text []byte, err error) {
|
|
||||||
var b bytes.Buffer
|
|
||||||
b.WriteString(e.Name)
|
|
||||||
b.WriteRune('\t')
|
|
||||||
b.WriteString(e.Pubkey.String())
|
|
||||||
|
|
||||||
return b.Bytes(), nil
|
|
||||||
}
|
|
||||||
func (e *UserRegistered) UnmarshalBinary(b []byte) error {
|
|
||||||
name, pub, ok := bytes.Cut(b, []byte{'\t'})
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("parse error")
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
e.Name = string(name)
|
|
||||||
e.Pubkey, err = keys.NewEdX25519PublicKeyFromID(keys.ID(pub))
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
extend type Query {
|
|
||||||
saltyUser(nick: String!): SaltyUser
|
|
||||||
}
|
|
||||||
|
|
||||||
extend type Mutation {
|
|
||||||
createSaltyUser(nick: String! pubkey: String!): SaltyUser
|
|
||||||
}
|
|
||||||
|
|
||||||
type SaltyUser @goModel(model: "github.com/sour-is/ev/app/salty.SaltyUser"){
|
|
||||||
nick: String!
|
|
||||||
pubkey: String!
|
|
||||||
inbox: String!
|
|
||||||
endpoint: String!
|
|
||||||
}
|
|
|
@ -1,299 +0,0 @@
|
||||||
package salty
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/keys-pub/keys"
|
|
||||||
"github.com/sour-is/ev/internal/lg"
|
|
||||||
"github.com/sour-is/ev/pkg/es"
|
|
||||||
"github.com/sour-is/ev/pkg/es/event"
|
|
||||||
"github.com/sour-is/ev/pkg/gql"
|
|
||||||
"go.opentelemetry.io/otel/metric/instrument"
|
|
||||||
"go.opentelemetry.io/otel/metric/instrument/syncint64"
|
|
||||||
"go.opentelemetry.io/otel/metric/unit"
|
|
||||||
"go.uber.org/multierr"
|
|
||||||
)
|
|
||||||
|
|
||||||
type DNSResolver interface {
|
|
||||||
LookupSRV(ctx context.Context, service, proto, name string) (string, []*net.SRV, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type service struct {
|
|
||||||
baseURL string
|
|
||||||
es *es.EventStore
|
|
||||||
dns DNSResolver
|
|
||||||
|
|
||||||
m_create_user syncint64.Counter
|
|
||||||
m_get_user syncint64.Counter
|
|
||||||
m_api_ping syncint64.Counter
|
|
||||||
m_api_register syncint64.Counter
|
|
||||||
m_api_lookup syncint64.Counter
|
|
||||||
m_api_send syncint64.Counter
|
|
||||||
m_req_time syncint64.Histogram
|
|
||||||
}
|
|
||||||
type contextKey struct {
|
|
||||||
name string
|
|
||||||
}
|
|
||||||
|
|
||||||
var saltyKey = contextKey{"salty"}
|
|
||||||
|
|
||||||
type SaltyResolver interface {
|
|
||||||
CreateSaltyUser(ctx context.Context, nick string, pub string) (*SaltyUser, error)
|
|
||||||
SaltyUser(ctx context.Context, nick string) (*SaltyUser, error)
|
|
||||||
RegisterHTTP(mux *http.ServeMux)
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(ctx context.Context, es *es.EventStore, baseURL string) (*service, error) {
|
|
||||||
ctx, span := lg.Span(ctx)
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
if err := event.Register(ctx, &UserRegistered{}); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := event.RegisterName(ctx, "domain.UserRegistered", &UserRegistered{}); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
m := lg.Meter(ctx)
|
|
||||||
|
|
||||||
svc := &service{baseURL: baseURL, es: es, dns: net.DefaultResolver}
|
|
||||||
|
|
||||||
var err, errs error
|
|
||||||
svc.m_create_user, err = m.SyncInt64().Counter("salty_create_user",
|
|
||||||
instrument.WithDescription("salty create user graphql called"),
|
|
||||||
)
|
|
||||||
errs = multierr.Append(errs, err)
|
|
||||||
|
|
||||||
svc.m_get_user, err = m.SyncInt64().Counter("salty_get_user",
|
|
||||||
instrument.WithDescription("salty get user graphql called"),
|
|
||||||
)
|
|
||||||
errs = multierr.Append(errs, err)
|
|
||||||
|
|
||||||
svc.m_api_ping, err = m.SyncInt64().Counter("salty_api_ping",
|
|
||||||
instrument.WithDescription("salty api ping called"),
|
|
||||||
)
|
|
||||||
errs = multierr.Append(errs, err)
|
|
||||||
|
|
||||||
svc.m_api_register, err = m.SyncInt64().Counter("salty_api_register",
|
|
||||||
instrument.WithDescription("salty api register"),
|
|
||||||
)
|
|
||||||
errs = multierr.Append(errs, err)
|
|
||||||
|
|
||||||
svc.m_api_lookup, err = m.SyncInt64().Counter("salty_api_lookup",
|
|
||||||
instrument.WithDescription("salty api ping lookup"),
|
|
||||||
)
|
|
||||||
errs = multierr.Append(errs, err)
|
|
||||||
|
|
||||||
svc.m_api_send, err = m.SyncInt64().Counter("salty_api_send",
|
|
||||||
instrument.WithDescription("salty api ping send"),
|
|
||||||
)
|
|
||||||
errs = multierr.Append(errs, err)
|
|
||||||
|
|
||||||
svc.m_req_time, err = m.SyncInt64().Histogram("salty_request_time",
|
|
||||||
instrument.WithDescription("histogram of requests"),
|
|
||||||
instrument.WithUnit(unit.Unit("ns")),
|
|
||||||
)
|
|
||||||
errs = multierr.Append(errs, err)
|
|
||||||
|
|
||||||
span.RecordError(err)
|
|
||||||
|
|
||||||
return svc, errs
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *service) BaseURL() string {
|
|
||||||
if s == nil {
|
|
||||||
return "http://missing.context/"
|
|
||||||
}
|
|
||||||
return s.baseURL
|
|
||||||
}
|
|
||||||
func (s *service) RegisterHTTP(mux *http.ServeMux) {
|
|
||||||
mux.Handle("/.well-known/salty/", lg.Htrace(s, "lookup"))
|
|
||||||
}
|
|
||||||
func (s *service) RegisterAPIv1(mux *http.ServeMux) {
|
|
||||||
mux.HandleFunc("/ping", s.apiv1)
|
|
||||||
mux.HandleFunc("/register", s.apiv1)
|
|
||||||
mux.HandleFunc("/lookup/", s.apiv1)
|
|
||||||
mux.HandleFunc("/send", s.apiv1)
|
|
||||||
}
|
|
||||||
func (s *service) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
||||||
ctx := r.Context()
|
|
||||||
ctx, span := lg.Span(ctx)
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
start := time.Now()
|
|
||||||
defer s.m_req_time.Record(ctx, time.Since(start).Milliseconds())
|
|
||||||
|
|
||||||
addr := "saltyuser-" + strings.TrimPrefix(r.URL.Path, "/.well-known/salty/")
|
|
||||||
addr = strings.TrimSuffix(addr, ".json")
|
|
||||||
|
|
||||||
span.AddEvent(fmt.Sprint("find ", addr))
|
|
||||||
a, err := es.Update(ctx, s.es, addr, func(ctx context.Context, agg *SaltyUser) error { return nil })
|
|
||||||
switch {
|
|
||||||
case errors.Is(err, event.ErrShouldExist):
|
|
||||||
span.RecordError(err)
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
return
|
|
||||||
case err != nil:
|
|
||||||
span.RecordError(err)
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
basePath, _ := url.JoinPath(s.baseURL, a.inbox.String())
|
|
||||||
|
|
||||||
err = json.NewEncoder(w).Encode(
|
|
||||||
struct {
|
|
||||||
Endpoint string `json:"endpoint"`
|
|
||||||
Key string `json:"key"`
|
|
||||||
}{
|
|
||||||
Endpoint: basePath,
|
|
||||||
Key: a.pubkey.ID().String(),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
span.RecordError(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func (s *service) CreateSaltyUser(ctx context.Context, nick string, pub string) (*SaltyUser, error) {
|
|
||||||
ctx, span := lg.Span(ctx)
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
s.m_create_user.Add(ctx, 1)
|
|
||||||
start := time.Now()
|
|
||||||
defer s.m_req_time.Record(ctx, time.Since(start).Milliseconds())
|
|
||||||
|
|
||||||
streamID := fmt.Sprintf("saltyuser-%x", sha256.Sum256([]byte(strings.ToLower(nick))))
|
|
||||||
span.AddEvent(streamID)
|
|
||||||
|
|
||||||
key, err := keys.NewEdX25519PublicKeyFromID(keys.ID(pub))
|
|
||||||
if err != nil {
|
|
||||||
span.RecordError(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
a, err := es.Create(ctx, s.es, streamID, func(ctx context.Context, agg *SaltyUser) error {
|
|
||||||
return agg.OnUserRegister(nick, key)
|
|
||||||
})
|
|
||||||
switch {
|
|
||||||
case errors.Is(err, es.ErrShouldNotExist):
|
|
||||||
span.RecordError(err)
|
|
||||||
return nil, fmt.Errorf("user exists")
|
|
||||||
|
|
||||||
case err != nil:
|
|
||||||
span.RecordError(err)
|
|
||||||
return nil, fmt.Errorf("internal error")
|
|
||||||
}
|
|
||||||
|
|
||||||
return a, nil
|
|
||||||
}
|
|
||||||
func (s *service) SaltyUser(ctx context.Context, nick string) (*SaltyUser, error) {
|
|
||||||
ctx, span := lg.Span(ctx)
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
s.m_get_user.Add(ctx, 1)
|
|
||||||
start := time.Now()
|
|
||||||
defer s.m_req_time.Record(ctx, time.Since(start).Milliseconds())
|
|
||||||
|
|
||||||
streamID := fmt.Sprintf("saltyuser-%x", sha256.Sum256([]byte(strings.ToLower(nick))))
|
|
||||||
span.AddEvent(streamID)
|
|
||||||
|
|
||||||
a, err := es.Update(ctx, s.es, streamID, func(ctx context.Context, agg *SaltyUser) error { return nil })
|
|
||||||
switch {
|
|
||||||
case errors.Is(err, es.ErrShouldExist):
|
|
||||||
span.RecordError(err)
|
|
||||||
return nil, fmt.Errorf("user not found")
|
|
||||||
|
|
||||||
case err != nil:
|
|
||||||
span.RecordError(err)
|
|
||||||
return nil, fmt.Errorf("%w internal error", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return a, err
|
|
||||||
}
|
|
||||||
func (s *service) GetMiddleware() func(http.Handler) http.Handler {
|
|
||||||
return func(next http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
r = r.WithContext(gql.ToContext(r.Context(), saltyKey, s))
|
|
||||||
next.ServeHTTP(w, r)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *service) apiv1(w http.ResponseWriter, r *http.Request) {
|
|
||||||
ctx := r.Context()
|
|
||||||
|
|
||||||
ctx, span := lg.Span(ctx)
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
start := time.Now()
|
|
||||||
defer s.m_req_time.Record(ctx, time.Since(start).Nanoseconds())
|
|
||||||
|
|
||||||
switch r.Method {
|
|
||||||
case http.MethodGet:
|
|
||||||
switch {
|
|
||||||
case r.URL.Path == "/ping":
|
|
||||||
s.m_api_ping.Add(ctx, 1)
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
_, _ = w.Write([]byte(`{}`))
|
|
||||||
|
|
||||||
case strings.HasPrefix(r.URL.Path, "/lookup/"):
|
|
||||||
s.m_api_lookup.Add(ctx, 1)
|
|
||||||
|
|
||||||
addr, err := s.ParseAddr(strings.TrimPrefix(r.URL.Path, "/lookup/"))
|
|
||||||
if err != nil {
|
|
||||||
span.RecordError(err)
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = addr.Refresh(ctx)
|
|
||||||
if err != nil {
|
|
||||||
span.RecordError(err)
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = json.NewEncoder(w).Encode(addr)
|
|
||||||
span.RecordError(err)
|
|
||||||
return
|
|
||||||
|
|
||||||
default:
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
case http.MethodPost:
|
|
||||||
switch r.URL.Path {
|
|
||||||
case "/register":
|
|
||||||
s.m_api_register.Add(ctx, 1)
|
|
||||||
notImplemented(w)
|
|
||||||
return
|
|
||||||
|
|
||||||
case "/send":
|
|
||||||
s.m_api_send.Add(ctx, 1)
|
|
||||||
notImplemented(w)
|
|
||||||
return
|
|
||||||
|
|
||||||
default:
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func notImplemented(w http.ResponseWriter) {
|
|
||||||
w.WriteHeader(http.StatusNotImplemented)
|
|
||||||
}
|
|
402
driver/disk-store/disk-store.go
Normal file
402
driver/disk-store/disk-store.go
Normal file
|
@ -0,0 +1,402 @@
|
||||||
|
// package diskstore provides a driver that reads and writes events to disk.
|
||||||
|
|
||||||
|
package diskstore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"hash/fnv"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/tidwall/wal"
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.opentelemetry.io/otel/metric"
|
||||||
|
"go.uber.org/multierr"
|
||||||
|
|
||||||
|
"go.sour.is/pkg/cache"
|
||||||
|
"go.sour.is/pkg/lg"
|
||||||
|
"go.sour.is/pkg/locker"
|
||||||
|
|
||||||
|
"go.sour.is/ev"
|
||||||
|
"go.sour.is/ev/driver"
|
||||||
|
"go.sour.is/ev/event"
|
||||||
|
)
|
||||||
|
|
||||||
|
const CachSize = 1000
|
||||||
|
|
||||||
|
const AppendOnly = ev.AppendOnly
|
||||||
|
const AllEvents = ev.AllEvents
|
||||||
|
|
||||||
|
type lockedWal = locker.Locked[*wal.Log]
|
||||||
|
type openlogs struct {
|
||||||
|
logs *cache.Cache[string, *lockedWal]
|
||||||
|
}
|
||||||
|
type diskStore struct {
|
||||||
|
path string
|
||||||
|
openlogs *locker.Locked[*openlogs]
|
||||||
|
|
||||||
|
m_disk_open metric.Int64Counter
|
||||||
|
m_disk_evict metric.Int64Counter
|
||||||
|
m_disk_read metric.Int64Counter
|
||||||
|
m_disk_write metric.Int64Counter
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ driver.Driver = (*diskStore)(nil)
|
||||||
|
|
||||||
|
func Init(ctx context.Context) error {
|
||||||
|
ctx, span := lg.Span(ctx)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
d := &diskStore{}
|
||||||
|
|
||||||
|
m := lg.Meter(ctx)
|
||||||
|
var err, errs error
|
||||||
|
|
||||||
|
d.m_disk_open, err = m.Int64Counter("disk_open")
|
||||||
|
errs = multierr.Append(errs, err)
|
||||||
|
|
||||||
|
d.m_disk_evict, err = m.Int64Counter("disk_evict")
|
||||||
|
errs = multierr.Append(errs, err)
|
||||||
|
|
||||||
|
d.m_disk_read, err = m.Int64Counter("disk_read")
|
||||||
|
errs = multierr.Append(errs, err)
|
||||||
|
|
||||||
|
d.m_disk_write, err = m.Int64Counter("disk_write")
|
||||||
|
errs = multierr.Append(errs, err)
|
||||||
|
|
||||||
|
ev.Register(ctx, "file", d)
|
||||||
|
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *diskStore) Open(ctx context.Context, dsn string) (driver.Driver, error) {
|
||||||
|
_, span := lg.Span(ctx)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
span.SetAttributes(
|
||||||
|
attribute.String("args.dsn", dsn),
|
||||||
|
)
|
||||||
|
|
||||||
|
scheme, path, ok := strings.Cut(dsn, ":")
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("expected scheme")
|
||||||
|
}
|
||||||
|
|
||||||
|
if scheme != "file" {
|
||||||
|
return nil, fmt.Errorf("expeted scheme=file, got=%s", scheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||||
|
err = os.MkdirAll(path, 0700)
|
||||||
|
if err != nil {
|
||||||
|
span.RecordError(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c, err := cache.NewWithEvict(CachSize, func(ctx context.Context, s string, l *lockedWal) {
|
||||||
|
ctx, span := lg.Span(ctx)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
l.Use(ctx, func(ctx context.Context, w *wal.Log) error {
|
||||||
|
ctx, span := lg.Span(ctx)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
d.m_disk_evict.Add(ctx, 1)
|
||||||
|
|
||||||
|
err := w.Close()
|
||||||
|
if err != nil {
|
||||||
|
span.RecordError(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
span.RecordError(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
logs := &openlogs{logs: c}
|
||||||
|
return &diskStore{
|
||||||
|
path: path,
|
||||||
|
openlogs: locker.New(logs),
|
||||||
|
m_disk_open: d.m_disk_open,
|
||||||
|
m_disk_evict: d.m_disk_evict,
|
||||||
|
m_disk_read: d.m_disk_read,
|
||||||
|
m_disk_write: d.m_disk_write,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
func (d *diskStore) EventLog(ctx context.Context, streamID string) (driver.EventLog, error) {
|
||||||
|
ctx, span := lg.Span(ctx)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
span.SetAttributes(
|
||||||
|
attribute.String("args.streamID", streamID),
|
||||||
|
attribute.String("path", d.path),
|
||||||
|
)
|
||||||
|
|
||||||
|
el := &eventLog{streamID: streamID, diskStore: d}
|
||||||
|
|
||||||
|
return el, d.openlogs.Use(ctx, func(ctx context.Context, openlogs *openlogs) error {
|
||||||
|
ctx, span := lg.Span(ctx)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
if events, ok := openlogs.logs.Get(streamID); ok {
|
||||||
|
el.events = *events
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
d.m_disk_open.Add(ctx, 1)
|
||||||
|
|
||||||
|
// migrate streams into dir friendly subdirs
|
||||||
|
hashPart := mkDirName(streamID)
|
||||||
|
oldPath := filepath.Join(d.path, streamID)
|
||||||
|
newPath := filepath.Join(d.path, hashPart, streamID)
|
||||||
|
if _, err := os.Stat(oldPath); !os.IsNotExist(err) {
|
||||||
|
os.MkdirAll(filepath.Join(d.path, hashPart), 0700)
|
||||||
|
os.Rename(oldPath, newPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
l, err := wal.Open(newPath, wal.DefaultOptions)
|
||||||
|
if err != nil {
|
||||||
|
span.RecordError(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
el.events = locker.New(l)
|
||||||
|
openlogs.logs.Add(ctx, streamID, el.events)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type eventLog struct {
|
||||||
|
streamID string
|
||||||
|
events *locker.Locked[*wal.Log]
|
||||||
|
diskStore *diskStore
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ driver.EventLog = (*eventLog)(nil)
|
||||||
|
|
||||||
|
func (e *eventLog) Append(ctx context.Context, events event.Events, version uint64) (uint64, error) {
|
||||||
|
ctx, span := lg.Span(ctx)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
span.SetAttributes(
|
||||||
|
attribute.Int("args.events", len(events)),
|
||||||
|
attribute.Int64("args.version", int64(version)),
|
||||||
|
attribute.String("streamID", e.streamID),
|
||||||
|
attribute.String("path", e.diskStore.path),
|
||||||
|
)
|
||||||
|
|
||||||
|
event.SetStreamID(e.streamID, events...)
|
||||||
|
|
||||||
|
var count uint64
|
||||||
|
err := e.events.Use(ctx, func(ctx context.Context, l *wal.Log) error {
|
||||||
|
ctx, span := lg.Span(ctx)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
last, err := l.LastIndex()
|
||||||
|
if err != nil {
|
||||||
|
span.RecordError(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if version != AppendOnly && version != last {
|
||||||
|
err = fmt.Errorf("%w: current version wrong %d != %d", ev.ErrWrongVersion, version, last)
|
||||||
|
span.RecordError(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var b []byte
|
||||||
|
|
||||||
|
batch := &wal.Batch{}
|
||||||
|
for i, e := range events {
|
||||||
|
span.AddEvent(fmt.Sprintf("append event %d of %d", i, len(events)))
|
||||||
|
|
||||||
|
b, err = event.MarshalBinary(e)
|
||||||
|
if err != nil {
|
||||||
|
span.RecordError(err)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pos := last + uint64(i) + 1
|
||||||
|
event.SetPosition(e, pos)
|
||||||
|
|
||||||
|
batch.Write(pos, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
count = uint64(len(events))
|
||||||
|
e.diskStore.m_disk_write.Add(ctx, int64(len(events)))
|
||||||
|
|
||||||
|
return l.WriteBatch(batch)
|
||||||
|
})
|
||||||
|
span.RecordError(err)
|
||||||
|
|
||||||
|
return count, err
|
||||||
|
}
|
||||||
|
func (e *eventLog) ReadN(ctx context.Context, index ...uint64) (event.Events, error) {
|
||||||
|
ctx, span := lg.Span(ctx)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
lis := make([]int64, len(index))
|
||||||
|
for i := range index {
|
||||||
|
lis[i] = int64(index[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
span.SetAttributes(
|
||||||
|
attribute.Int64Slice("args.index", lis),
|
||||||
|
attribute.String("streamID", e.streamID),
|
||||||
|
attribute.String("path", e.diskStore.path),
|
||||||
|
)
|
||||||
|
|
||||||
|
var events event.Events
|
||||||
|
err := e.events.Use(ctx, func(ctx context.Context, stream *wal.Log) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
events, err = readStreamN(ctx, stream, index...)
|
||||||
|
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
|
||||||
|
return events, err
|
||||||
|
}
|
||||||
|
func (e *eventLog) Read(ctx context.Context, after, count int64) (event.Events, error) {
|
||||||
|
ctx, span := lg.Span(ctx)
|
||||||
|
defer span.End()
|
||||||
|
span.SetAttributes(
|
||||||
|
attribute.Int64("args.after", after),
|
||||||
|
attribute.Int64("args.count", count),
|
||||||
|
attribute.String("streamID", e.streamID),
|
||||||
|
attribute.String("path", e.diskStore.path),
|
||||||
|
)
|
||||||
|
|
||||||
|
var events event.Events
|
||||||
|
err := e.events.Use(ctx, func(ctx context.Context, stream *wal.Log) error {
|
||||||
|
ctx, span := lg.Span(ctx)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
first, err := stream.FirstIndex()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
last, err := stream.LastIndex()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
streamIDs, err := driver.GenerateStreamIDs(first, last, after, count)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
events, err = readStreamN(ctx, stream, streamIDs...)
|
||||||
|
event.SetStreamID(e.streamID, events...)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
span.RecordError(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
e.diskStore.m_disk_read.Add(ctx, int64(len(events)))
|
||||||
|
|
||||||
|
return events, nil
|
||||||
|
}
|
||||||
|
func (e *eventLog) FirstIndex(ctx context.Context) (uint64, error) {
|
||||||
|
ctx, span := lg.Span(ctx)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
span.SetAttributes(
|
||||||
|
attribute.String("streamID", e.streamID),
|
||||||
|
attribute.String("path", e.diskStore.path),
|
||||||
|
)
|
||||||
|
|
||||||
|
var idx uint64
|
||||||
|
var err error
|
||||||
|
|
||||||
|
err = e.events.Use(ctx, func(ctx context.Context, events *wal.Log) error {
|
||||||
|
idx, err = events.FirstIndex()
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
|
||||||
|
return idx, err
|
||||||
|
}
|
||||||
|
func (e *eventLog) LastIndex(ctx context.Context) (uint64, error) {
|
||||||
|
ctx, span := lg.Span(ctx)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
span.SetAttributes(
|
||||||
|
attribute.String("streamID", e.streamID),
|
||||||
|
attribute.String("path", e.diskStore.path),
|
||||||
|
)
|
||||||
|
|
||||||
|
var idx uint64
|
||||||
|
var err error
|
||||||
|
|
||||||
|
err = e.events.Use(ctx, func(ctx context.Context, events *wal.Log) error {
|
||||||
|
idx, err = events.LastIndex()
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
|
||||||
|
return idx, err
|
||||||
|
}
|
||||||
|
func (e *eventLog) Truncate(ctx context.Context, index int64) error {
|
||||||
|
ctx, span := lg.Span(ctx)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
span.SetAttributes(
|
||||||
|
attribute.Int64("args.index", index),
|
||||||
|
attribute.String("streamID", e.streamID),
|
||||||
|
attribute.String("path", e.diskStore.path),
|
||||||
|
)
|
||||||
|
|
||||||
|
if index == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return e.events.Use(ctx, func(ctx context.Context, events *wal.Log) error {
|
||||||
|
if index < 0 {
|
||||||
|
return events.TruncateBack(uint64(-index))
|
||||||
|
}
|
||||||
|
return events.TruncateFront(uint64(index))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func readStreamN(ctx context.Context, stream *wal.Log, index ...uint64) (event.Events, error) {
|
||||||
|
ctx, span := lg.Span(ctx)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
lis := make([]int64, len(index))
|
||||||
|
for i := range index {
|
||||||
|
lis[i] = int64(index[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
span.SetAttributes(
|
||||||
|
attribute.Int64Slice("args.index", lis),
|
||||||
|
)
|
||||||
|
|
||||||
|
var b []byte
|
||||||
|
var err error
|
||||||
|
events := make(event.Events, len(index))
|
||||||
|
for i, idx := range index {
|
||||||
|
b, err = stream.Read(idx)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, wal.ErrNotFound) || errors.Is(err, wal.ErrOutOfRange) {
|
||||||
|
err = fmt.Errorf("%w: empty", ev.ErrNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
span.RecordError(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
events[i], err = event.UnmarshalBinary(ctx, b, idx)
|
||||||
|
if err != nil {
|
||||||
|
span.RecordError(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
span.AddEvent(fmt.Sprintf("read event %d of %d - %d", i, len(events), events[i].EventMeta().ActualPosition))
|
||||||
|
}
|
||||||
|
return events, err
|
||||||
|
}
|
||||||
|
func mkDirName(name string) string {
|
||||||
|
h := fnv.New32a()
|
||||||
|
fmt.Fprint(h, name)
|
||||||
|
return fmt.Sprintf("%x/%x/%x", h.Sum32()>>24&0xff, h.Sum32()>>16&0xff, h.Sum32()&0xffff)
|
||||||
|
}
|
69
driver/driver.go
Normal file
69
driver/driver.go
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
// package driver defines interfaces to be used by driver implementations.
|
||||||
|
package driver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"go.sour.is/ev/event"
|
||||||
|
"go.sour.is/pkg/math"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Driver interface {
|
||||||
|
Open(ctx context.Context, dsn string) (Driver, error)
|
||||||
|
EventLog(ctx context.Context, streamID string) (EventLog, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type EventLog interface {
|
||||||
|
Read(ctx context.Context, after, count int64) (event.Events, error)
|
||||||
|
ReadN(ctx context.Context, index ...uint64) (event.Events, error)
|
||||||
|
Append(ctx context.Context, events event.Events, version uint64) (uint64, error)
|
||||||
|
FirstIndex(context.Context) (uint64, error)
|
||||||
|
LastIndex(context.Context) (uint64, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type EventLogWithTruncate interface {
|
||||||
|
Truncate(context.Context, int64) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type EventLogWithUpdate interface {
|
||||||
|
LoadForUpdate(context.Context, event.Aggregate, func(context.Context, event.Aggregate) error) (uint64, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Subscription interface {
|
||||||
|
Recv(context.Context) <-chan bool
|
||||||
|
Events(context.Context) (event.Events, error)
|
||||||
|
Close(context.Context) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type EventStream interface {
|
||||||
|
Subscribe(ctx context.Context, streamID string, start int64) (Subscription, error)
|
||||||
|
Send(ctx context.Context, streamID string, events event.Events) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenerateStreamIDs(first, last uint64, after, count int64) ([]uint64, error) {
|
||||||
|
// ---
|
||||||
|
if first == 0 || last == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
start, count := math.PagerBox(first, last, after, count)
|
||||||
|
if count == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
streamIDs := make([]uint64, math.Abs(count))
|
||||||
|
for i := range streamIDs {
|
||||||
|
streamIDs[i] = start
|
||||||
|
|
||||||
|
if count > 0 {
|
||||||
|
start += 1
|
||||||
|
} else {
|
||||||
|
start -= 1
|
||||||
|
}
|
||||||
|
if start < first || start > last {
|
||||||
|
streamIDs = streamIDs[:i+1]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return streamIDs, nil
|
||||||
|
}
|
239
driver/mem-store/mem-store.go
Normal file
239
driver/mem-store/mem-store.go
Normal file
|
@ -0,0 +1,239 @@
|
||||||
|
// package memstore provides a driver that reads and writes events to memory.
|
||||||
|
package memstore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.sour.is/pkg/lg"
|
||||||
|
"go.sour.is/pkg/locker"
|
||||||
|
|
||||||
|
"go.sour.is/ev"
|
||||||
|
"go.sour.is/ev/driver"
|
||||||
|
"go.sour.is/ev/event"
|
||||||
|
)
|
||||||
|
|
||||||
|
const AppendOnly = ev.AppendOnly
|
||||||
|
const AllEvents = ev.AllEvents
|
||||||
|
|
||||||
|
type state struct {
|
||||||
|
streams map[string]*locker.Locked[*event.Events]
|
||||||
|
}
|
||||||
|
type memstore struct {
|
||||||
|
state *locker.Locked[*state]
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ driver.Driver = (*memstore)(nil)
|
||||||
|
|
||||||
|
func Init(ctx context.Context) error {
|
||||||
|
ctx, span := lg.Span(ctx)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
return ev.Register(ctx, "mem", &memstore{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (memstore) Open(ctx context.Context, name string) (driver.Driver, error) {
|
||||||
|
_, span := lg.Span(ctx)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
s := &state{streams: make(map[string]*locker.Locked[*event.Events])}
|
||||||
|
return &memstore{locker.New(s)}, nil
|
||||||
|
}
|
||||||
|
func (m *memstore) EventLog(ctx context.Context, streamID string) (driver.EventLog, error) {
|
||||||
|
ctx, span := lg.Span(ctx)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
el := &eventLog{streamID: streamID}
|
||||||
|
|
||||||
|
err := m.state.Use(ctx, func(ctx context.Context, state *state) error {
|
||||||
|
_, span := lg.Span(ctx)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
l, ok := state.streams[streamID]
|
||||||
|
if !ok {
|
||||||
|
l = locker.New(&event.Events{})
|
||||||
|
state.streams[streamID] = l
|
||||||
|
}
|
||||||
|
el.events = l
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return el, err
|
||||||
|
}
|
||||||
|
|
||||||
|
type eventLog struct {
|
||||||
|
streamID string
|
||||||
|
events *locker.Locked[*event.Events]
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ driver.EventLog = (*eventLog)(nil)
|
||||||
|
|
||||||
|
// Append implements driver.EventStore
|
||||||
|
func (m *eventLog) Append(ctx context.Context, events event.Events, version uint64) (uint64, error) {
|
||||||
|
ctx, span := lg.Span(ctx)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
event.SetStreamID(m.streamID, events...)
|
||||||
|
|
||||||
|
return uint64(len(events)), m.events.Use(ctx, func(ctx context.Context, stream *event.Events) error {
|
||||||
|
ctx, span := lg.Span(ctx)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
span.AddEvent(fmt.Sprintf(" %s %d", m.streamID, len(*stream)))
|
||||||
|
|
||||||
|
last := uint64(len(*stream))
|
||||||
|
if version != AppendOnly && version != last {
|
||||||
|
return fmt.Errorf("%w: current version wrong %d != %d", ev.ErrWrongVersion, version, last)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range events {
|
||||||
|
span.AddEvent(fmt.Sprintf("read event %d of %d", i, len(events)))
|
||||||
|
|
||||||
|
// --- clone event
|
||||||
|
e := events[i]
|
||||||
|
b, err := event.MarshalBinary(e)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
e, err = event.UnmarshalBinary(ctx, b, e.EventMeta().Position)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// ---
|
||||||
|
|
||||||
|
pos := last + uint64(i) + 1
|
||||||
|
event.SetPosition(e, pos)
|
||||||
|
*stream = append(*stream, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadOne implements readone
|
||||||
|
func (m *eventLog) ReadN(ctx context.Context, index ...uint64) (event.Events, error) {
|
||||||
|
ctx, span := lg.Span(ctx)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
lis := make([]int64, len(index))
|
||||||
|
for i := range index {
|
||||||
|
lis[i] = int64(index[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
span.SetAttributes(
|
||||||
|
attribute.Int64Slice("args.index", lis),
|
||||||
|
attribute.String("streamID", m.streamID),
|
||||||
|
)
|
||||||
|
|
||||||
|
var events event.Events
|
||||||
|
err := m.events.Use(ctx, func(ctx context.Context, stream *event.Events) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
events, err = readStreamN(ctx, stream, index...)
|
||||||
|
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
|
||||||
|
return events, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read implements driver.EventStore
|
||||||
|
func (m *eventLog) Read(ctx context.Context, after int64, count int64) (event.Events, error) {
|
||||||
|
ctx, span := lg.Span(ctx)
|
||||||
|
defer span.End()
|
||||||
|
span.SetAttributes(
|
||||||
|
attribute.Int64("args.after", after),
|
||||||
|
attribute.Int64("args.count", count),
|
||||||
|
attribute.String("streamID", m.streamID),
|
||||||
|
)
|
||||||
|
|
||||||
|
var events event.Events
|
||||||
|
err := m.events.Use(ctx, func(ctx context.Context, stream *event.Events) error {
|
||||||
|
ctx, span := lg.Span(ctx)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
first := stream.First().EventMeta().Position
|
||||||
|
last := stream.Last().EventMeta().Position
|
||||||
|
|
||||||
|
streamIDs, err := driver.GenerateStreamIDs(first, last, after, count)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
events, err = readStreamN(ctx, stream, streamIDs...)
|
||||||
|
event.SetStreamID(m.streamID, events...)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
span.RecordError(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return events, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FirstIndex for the streamID
|
||||||
|
func (m *eventLog) FirstIndex(ctx context.Context) (uint64, error) {
|
||||||
|
ctx, span := lg.Span(ctx)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
events, err := m.events.Copy(ctx)
|
||||||
|
return events.First().EventMeta().Position, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// LastIndex for the streamID
|
||||||
|
func (m *eventLog) LastIndex(ctx context.Context) (uint64, error) {
|
||||||
|
ctx, span := lg.Span(ctx)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
events, err := m.events.Copy(ctx)
|
||||||
|
return events.Last().EventMeta().Position, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *eventLog) Truncate(ctx context.Context, index int64) error {
|
||||||
|
ctx, span := lg.Span(ctx)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
span.SetAttributes(
|
||||||
|
attribute.Int64("args.index", index),
|
||||||
|
attribute.String("streamID", e.streamID),
|
||||||
|
)
|
||||||
|
|
||||||
|
if index == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return e.events.Use(ctx, func(ctx context.Context, events *event.Events) error {
|
||||||
|
if index < 0 {
|
||||||
|
*events = (*events)[:index]
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
*events = (*events)[index:]
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func readStreamN(ctx context.Context, stream *event.Events, index ...uint64) (event.Events, error) {
|
||||||
|
ctx, span := lg.Span(ctx)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
var b []byte
|
||||||
|
var err error
|
||||||
|
|
||||||
|
count := len(index)
|
||||||
|
events := make(event.Events, count)
|
||||||
|
for i, index := range index {
|
||||||
|
span.AddEvent(fmt.Sprintf("read event %d of %d", i, count))
|
||||||
|
|
||||||
|
e := (*stream)[index-1]
|
||||||
|
b, err = event.MarshalBinary(e)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
events[i], err = event.UnmarshalBinary(ctx, b, e.EventMeta().Position)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return events, err
|
||||||
|
}
|
|
@ -1,13 +1,15 @@
|
||||||
|
// package projecter provides a driver middleware to derive new events from other events.
|
||||||
package projecter
|
package projecter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sour-is/ev/internal/lg"
|
"go.sour.is/pkg/lg"
|
||||||
"github.com/sour-is/ev/pkg/es"
|
|
||||||
"github.com/sour-is/ev/pkg/es/driver"
|
"go.sour.is/ev"
|
||||||
"github.com/sour-is/ev/pkg/es/event"
|
"go.sour.is/ev/driver"
|
||||||
|
"go.sour.is/ev/event"
|
||||||
)
|
)
|
||||||
|
|
||||||
type projector struct {
|
type projector struct {
|
||||||
|
@ -15,10 +17,21 @@ type projector struct {
|
||||||
fns []func(event.Event) []event.Event
|
fns []func(event.Event) []event.Event
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(ctx context.Context, fns ...func(event.Event) []event.Event) *projector {
|
func New(_ context.Context, fns ...func(event.Event) []event.Event) *projector {
|
||||||
return &projector{fns: fns}
|
return &projector{fns: fns}
|
||||||
}
|
}
|
||||||
func (p *projector) Apply(e *es.EventStore) {
|
func (p *projector) Apply(e *ev.EventStore) {
|
||||||
|
up := e.Driver
|
||||||
|
for up != nil {
|
||||||
|
if op, ok := up.(*projector); ok {
|
||||||
|
op.AddProjections(p.fns...)
|
||||||
|
p.up = op.up
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
up = ev.Unwrap(up)
|
||||||
|
}
|
||||||
|
|
||||||
p.up = e.Driver
|
p.up = e.Driver
|
||||||
e.Driver = p
|
e.Driver = p
|
||||||
}
|
}
|
||||||
|
@ -38,6 +51,9 @@ func (s *projector) EventLog(ctx context.Context, streamID string) (driver.Event
|
||||||
l, err := s.up.EventLog(ctx, streamID)
|
l, err := s.up.EventLog(ctx, streamID)
|
||||||
return &wrapper{l, s}, err
|
return &wrapper{l, s}, err
|
||||||
}
|
}
|
||||||
|
func (s *projector) AddProjections(fns ...func(event.Event) []event.Event) {
|
||||||
|
s.fns = append(s.fns, fns...)
|
||||||
|
}
|
||||||
|
|
||||||
type wrapper struct {
|
type wrapper struct {
|
||||||
up driver.EventLog
|
up driver.EventLog
|
||||||
|
@ -46,11 +62,20 @@ type wrapper struct {
|
||||||
|
|
||||||
var _ driver.EventLog = (*wrapper)(nil)
|
var _ driver.EventLog = (*wrapper)(nil)
|
||||||
|
|
||||||
func (w *wrapper) Read(ctx context.Context, pos int64, count int64) (event.Events, error) {
|
func (r *wrapper) Unwrap() driver.EventLog {
|
||||||
|
return r.up
|
||||||
|
}
|
||||||
|
func (w *wrapper) Read(ctx context.Context, after int64, count int64) (event.Events, error) {
|
||||||
ctx, span := lg.Span(ctx)
|
ctx, span := lg.Span(ctx)
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
return w.up.Read(ctx, pos, count)
|
return w.up.Read(ctx, after, count)
|
||||||
|
}
|
||||||
|
func (w *wrapper) ReadN(ctx context.Context, index ...uint64) (event.Events, error) {
|
||||||
|
ctx, span := lg.Span(ctx)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
return w.up.ReadN(ctx, index...)
|
||||||
}
|
}
|
||||||
func (w *wrapper) Append(ctx context.Context, events event.Events, version uint64) (uint64, error) {
|
func (w *wrapper) Append(ctx context.Context, events event.Events, version uint64) (uint64, error) {
|
||||||
ctx, span := lg.Span(ctx)
|
ctx, span := lg.Span(ctx)
|
||||||
|
@ -87,7 +112,7 @@ func (w *wrapper) Append(ctx context.Context, events event.Events, version uint6
|
||||||
span.RecordError(err)
|
span.RecordError(err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
_, err = l.Append(ctx, event.NewEvents(e), es.AppendOnly)
|
_, err = l.Append(ctx, event.NewEvents(e), ev.AppendOnly)
|
||||||
span.RecordError(err)
|
span.RecordError(err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
@ -107,18 +132,13 @@ func (w *wrapper) LastIndex(ctx context.Context) (uint64, error) {
|
||||||
|
|
||||||
return w.up.LastIndex(ctx)
|
return w.up.LastIndex(ctx)
|
||||||
}
|
}
|
||||||
func (w *wrapper) LoadForUpdate(ctx context.Context, a event.Aggregate, fn func(context.Context, event.Aggregate) error) (uint64, error) {
|
|
||||||
ctx, span := lg.Span(ctx)
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
return w.up.LoadForUpdate(ctx, a, fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func DefaultProjection(e event.Event) []event.Event {
|
func DefaultProjection(e event.Event) []event.Event {
|
||||||
eventType := event.TypeOf(e)
|
|
||||||
m := e.EventMeta()
|
m := e.EventMeta()
|
||||||
streamID := m.StreamID
|
streamID := m.StreamID
|
||||||
streamPos := m.Position
|
streamPos := m.Position
|
||||||
|
eventType := event.TypeOf(e)
|
||||||
|
pkg, _, _ := strings.Cut(eventType, ".")
|
||||||
|
|
||||||
e1 := event.NewPtr(streamID, streamPos)
|
e1 := event.NewPtr(streamID, streamPos)
|
||||||
event.SetStreamID("$all", e1)
|
event.SetStreamID("$all", e1)
|
||||||
|
@ -127,7 +147,6 @@ func DefaultProjection(e event.Event) []event.Event {
|
||||||
event.SetStreamID("$type-"+eventType, e2)
|
event.SetStreamID("$type-"+eventType, e2)
|
||||||
|
|
||||||
e3 := event.NewPtr(streamID, streamPos)
|
e3 := event.NewPtr(streamID, streamPos)
|
||||||
pkg, _, _ := strings.Cut(eventType, ".")
|
|
||||||
event.SetStreamID("$pkg-"+pkg, e3)
|
event.SetStreamID("$pkg-"+pkg, e3)
|
||||||
|
|
||||||
return []event.Event{e1, e2, e3}
|
return []event.Event{e1, e2, e3}
|
137
driver/projecter/projector_test.go
Normal file
137
driver/projecter/projector_test.go
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
package projecter_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/matryer/is"
|
||||||
|
|
||||||
|
"go.sour.is/ev"
|
||||||
|
"go.sour.is/ev/driver"
|
||||||
|
"go.sour.is/ev/driver/projecter"
|
||||||
|
"go.sour.is/ev/event"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mockDriver struct {
|
||||||
|
onOpen func(context.Context, string) (driver.Driver, error)
|
||||||
|
onEventLog func(context.Context, string) (driver.EventLog, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open implements driver.Driver
|
||||||
|
func (m *mockDriver) Open(ctx context.Context, dsn string) (driver.Driver, error) {
|
||||||
|
if m.onOpen != nil {
|
||||||
|
return m.onOpen(ctx, dsn)
|
||||||
|
}
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// EventLog implements driver.Driver
|
||||||
|
func (m *mockDriver) EventLog(ctx context.Context, streamID string) (driver.EventLog, error) {
|
||||||
|
if m.onEventLog != nil {
|
||||||
|
return m.onEventLog(ctx, streamID)
|
||||||
|
}
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ driver.Driver = (*mockDriver)(nil)
|
||||||
|
|
||||||
|
type mockEventLog struct {
|
||||||
|
onAppend func(context.Context, event.Events, uint64) (uint64, error)
|
||||||
|
onFirstIndex func(context.Context) (uint64, error)
|
||||||
|
onLastIndex func(context.Context) (uint64, error)
|
||||||
|
onRead func(context.Context, int64, int64) (event.Events, error)
|
||||||
|
onReadN func(context.Context, ...uint64) (event.Events, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append implements driver.EventLog
|
||||||
|
func (m *mockEventLog) Append(ctx context.Context, events event.Events, version uint64) (uint64, error) {
|
||||||
|
if m.onAppend != nil {
|
||||||
|
return m.onAppend(ctx, events, version)
|
||||||
|
}
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// FirstIndex implements driver.EventLog
|
||||||
|
func (m *mockEventLog) FirstIndex(ctx context.Context) (uint64, error) {
|
||||||
|
if m.onFirstIndex != nil {
|
||||||
|
return m.onFirstIndex(ctx)
|
||||||
|
}
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// LastIndex implements driver.EventLog
|
||||||
|
func (m *mockEventLog) LastIndex(ctx context.Context) (uint64, error) {
|
||||||
|
if m.onLastIndex != nil {
|
||||||
|
return m.onLastIndex(ctx)
|
||||||
|
}
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read implements driver.EventLog
|
||||||
|
func (m *mockEventLog) Read(ctx context.Context, pos int64, count int64) (event.Events, error) {
|
||||||
|
if m.onRead != nil {
|
||||||
|
return m.onRead(ctx, pos, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
||||||
|
func (m *mockEventLog) ReadN(ctx context.Context, index ...uint64) (event.Events, error) {
|
||||||
|
if m.onReadN != nil {
|
||||||
|
return m.onReadN(ctx, index...)
|
||||||
|
}
|
||||||
|
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ driver.EventLog = (*mockEventLog)(nil)
|
||||||
|
|
||||||
|
func TestProjecter(t *testing.T) {
|
||||||
|
is := is.New(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
var events []event.Event
|
||||||
|
|
||||||
|
wait := make(chan struct{})
|
||||||
|
|
||||||
|
mockEL := &mockEventLog{}
|
||||||
|
mockEL.onRead = func(ctx context.Context, i1, i2 int64) (event.Events, error) {
|
||||||
|
return event.NewEvents(), nil
|
||||||
|
}
|
||||||
|
mockEL.onAppend = func(ctx context.Context, e event.Events, u uint64) (uint64, error) {
|
||||||
|
events = append(events, e...)
|
||||||
|
if wait != nil && len(events) > 3 {
|
||||||
|
close(wait)
|
||||||
|
}
|
||||||
|
return uint64(len(e)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
mock := &mockDriver{}
|
||||||
|
mock.onOpen = func(ctx context.Context, s string) (driver.Driver, error) {
|
||||||
|
return mock, nil
|
||||||
|
}
|
||||||
|
mock.onEventLog = func(ctx context.Context, s string) (driver.EventLog, error) {
|
||||||
|
return mockEL, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ev.Init(ctx)
|
||||||
|
ev.Register(ctx, "mock", mock)
|
||||||
|
|
||||||
|
es, err := ev.Open(
|
||||||
|
ctx,
|
||||||
|
"mock:",
|
||||||
|
projecter.New(ctx, projecter.DefaultProjection),
|
||||||
|
)
|
||||||
|
|
||||||
|
is.NoErr(err)
|
||||||
|
|
||||||
|
_, err = es.Read(ctx, "test", 0, 1)
|
||||||
|
|
||||||
|
is.NoErr(err)
|
||||||
|
|
||||||
|
_, err = es.Append(ctx, "test", event.NewEvents(event.NilEvent))
|
||||||
|
is.NoErr(err)
|
||||||
|
|
||||||
|
<-wait
|
||||||
|
|
||||||
|
is.Equal(len(events), 4)
|
||||||
|
}
|
133
driver/resolve-links/resolve-links.go
Normal file
133
driver/resolve-links/resolve-links.go
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
package resolvelinks
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"go.sour.is/ev"
|
||||||
|
"go.sour.is/ev/driver"
|
||||||
|
"go.sour.is/ev/event"
|
||||||
|
"go.sour.is/pkg/lg"
|
||||||
|
)
|
||||||
|
|
||||||
|
type resolvelinks struct {
|
||||||
|
up driver.Driver
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() *resolvelinks {
|
||||||
|
return &resolvelinks{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *resolvelinks) Apply(es *ev.EventStore) {
|
||||||
|
r.up = es.Driver
|
||||||
|
es.Driver = r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *resolvelinks) Unwrap() driver.Driver {
|
||||||
|
return r.up
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *resolvelinks) Open(ctx context.Context, dsn string) (driver.Driver, error) {
|
||||||
|
ctx, span := lg.Span(ctx)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
return r.up.Open(ctx, dsn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *resolvelinks) EventLog(ctx context.Context, streamID string) (driver.EventLog, error) {
|
||||||
|
ctx, span := lg.Span(ctx)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
l, err := r.up.EventLog(ctx, streamID)
|
||||||
|
return &wrapper{l, r}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
type wrapper struct {
|
||||||
|
up driver.EventLog
|
||||||
|
resolver *resolvelinks
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *wrapper) Unwrap() driver.EventLog {
|
||||||
|
return r.up
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wrapper) Read(ctx context.Context, after int64, count int64) (event.Events, error) {
|
||||||
|
ctx, span := lg.Span(ctx)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
events, err := w.up.Read(ctx, after, count)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = w.resolvelinks(ctx, events)
|
||||||
|
return events, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wrapper) ReadN(ctx context.Context, index ...uint64) (event.Events, error) {
|
||||||
|
ctx, span := lg.Span(ctx)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
events, err := w.up.ReadN(ctx, index...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = w.resolvelinks(ctx, events)
|
||||||
|
return events, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wrapper) Append(ctx context.Context, events event.Events, version uint64) (uint64, error) {
|
||||||
|
ctx, span := lg.Span(ctx)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
return w.up.Append(ctx, events, version)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wrapper) FirstIndex(ctx context.Context) (uint64, error) {
|
||||||
|
ctx, span := lg.Span(ctx)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
return w.up.FirstIndex(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wrapper) LastIndex(ctx context.Context) (uint64, error) {
|
||||||
|
ctx, span := lg.Span(ctx)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
return w.up.LastIndex(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wrapper) resolvelinks(ctx context.Context, events event.Events) error {
|
||||||
|
idx := make(map[string][]uint64)
|
||||||
|
ptrs := make(map[string][]int)
|
||||||
|
for i := range events {
|
||||||
|
e := events[i]
|
||||||
|
if e, ok := e.(*event.EventPtr); ok {
|
||||||
|
idx[e.StreamID] = append(idx[e.StreamID], e.Pos)
|
||||||
|
ptrs[e.StreamID] = append(ptrs[e.StreamID], i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for streamID, ids := range idx {
|
||||||
|
d, err := w.resolver.EventLog(ctx, streamID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ptr := ptrs[streamID]
|
||||||
|
lis, err := d.ReadN(ctx, ids...)
|
||||||
|
if err != nil && !errors.Is(err, ev.ErrNotFound) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range lis {
|
||||||
|
meta := lis[i].EventMeta()
|
||||||
|
actual := events[ptr[i]].EventMeta()
|
||||||
|
meta.ActualPosition = actual.Position
|
||||||
|
meta.ActualStreamID = actual.ActualStreamID
|
||||||
|
lis[i].SetEventMeta(meta)
|
||||||
|
events[i] = lis[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -1,16 +1,20 @@
|
||||||
|
// package streamer provides a driver to allow awaiting for new events to be added to a stream.
|
||||||
package streamer
|
package streamer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/sour-is/ev/internal/lg"
|
|
||||||
"github.com/sour-is/ev/pkg/es"
|
|
||||||
"github.com/sour-is/ev/pkg/es/driver"
|
|
||||||
"github.com/sour-is/ev/pkg/es/event"
|
|
||||||
"github.com/sour-is/ev/pkg/locker"
|
|
||||||
"go.opentelemetry.io/otel/attribute"
|
"go.opentelemetry.io/otel/attribute"
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
|
||||||
|
"go.sour.is/pkg/lg"
|
||||||
|
"go.sour.is/pkg/locker"
|
||||||
|
|
||||||
|
"go.sour.is/ev"
|
||||||
|
"go.sour.is/ev/driver"
|
||||||
|
"go.sour.is/ev/event"
|
||||||
)
|
)
|
||||||
|
|
||||||
type state struct {
|
type state struct {
|
||||||
|
@ -18,7 +22,7 @@ type state struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type streamer struct {
|
type streamer struct {
|
||||||
state *locker.Locked[state]
|
state *locker.Locked[*state]
|
||||||
up driver.Driver
|
up driver.Driver
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,9 +33,9 @@ func New(ctx context.Context) *streamer {
|
||||||
return &streamer{state: locker.New(&state{subscribers: map[string][]*subscription{}})}
|
return &streamer{state: locker.New(&state{subscribers: map[string][]*subscription{}})}
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ es.Option = (*streamer)(nil)
|
var _ ev.Option = (*streamer)(nil)
|
||||||
|
|
||||||
func (s *streamer) Apply(e *es.EventStore) {
|
func (s *streamer) Apply(e *ev.EventStore) {
|
||||||
s.up = e.Driver
|
s.up = e.Driver
|
||||||
e.Driver = s
|
e.Driver = s
|
||||||
}
|
}
|
||||||
|
@ -57,6 +61,8 @@ func (s *streamer) EventLog(ctx context.Context, streamID string) (driver.EventL
|
||||||
|
|
||||||
var _ driver.EventStream = (*streamer)(nil)
|
var _ driver.EventStream = (*streamer)(nil)
|
||||||
|
|
||||||
|
// Subscribe sets up a subscription that tracks the last HWM. when new events arrive they are notified via Send.
|
||||||
|
// Call Close when done to clean up allocation.
|
||||||
func (s *streamer) Subscribe(ctx context.Context, streamID string, start int64) (driver.Subscription, error) {
|
func (s *streamer) Subscribe(ctx context.Context, streamID string, start int64) (driver.Subscription, error) {
|
||||||
ctx, span := lg.Span(ctx)
|
ctx, span := lg.Span(ctx)
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
@ -65,39 +71,51 @@ func (s *streamer) Subscribe(ctx context.Context, streamID string, start int64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
sub := &subscription{topic: streamID, events: events}
|
sub := &subscription{topic: streamID, events: events}
|
||||||
sub.position = locker.New(&position{
|
sub.position = locker.New(&position{
|
||||||
idx: start,
|
idx: start,
|
||||||
size: es.AllEvents,
|
size: ev.AllEvents,
|
||||||
})
|
})
|
||||||
sub.unsub = s.delete(streamID, sub)
|
sub.unsub = s.delete(streamID, sub)
|
||||||
|
|
||||||
return sub, s.state.Modify(ctx, func(state *state) error {
|
return sub, s.state.Use(ctx, func(ctx context.Context, state *state) error {
|
||||||
state.subscribers[streamID] = append(state.subscribers[streamID], sub)
|
state.subscribers[streamID] = append(state.subscribers[streamID], sub)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Send will notify all Recv-ing subscribers that new events are ready.
|
||||||
func (s *streamer) Send(ctx context.Context, streamID string, events event.Events) error {
|
func (s *streamer) Send(ctx context.Context, streamID string, events event.Events) error {
|
||||||
ctx, span := lg.Span(ctx)
|
ctx, span := lg.Span(ctx)
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
return s.state.Modify(ctx, func(state *state) error {
|
return s.state.Use(ctx, func(ctx context.Context, state *state) error {
|
||||||
ctx, span := lg.Span(ctx)
|
ctx, span := lg.Span(ctx)
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
span.AddEvent(fmt.Sprint("subscribers=", len(state.subscribers[streamID])))
|
span.AddEvent(fmt.Sprint("subscribers=", len(state.subscribers[streamID])))
|
||||||
|
|
||||||
for _, sub := range state.subscribers[streamID] {
|
for _, sub := range state.subscribers[streamID] {
|
||||||
err := sub.position.Modify(ctx, func(position *position) error {
|
err := sub.position.Use(ctx, func(ctx context.Context, position *position) error {
|
||||||
_, span := lg.Span(ctx)
|
ctx, span := lg.Span(ctx)
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
position.size = int64(events.Last().EventMeta().Position - uint64(position.idx))
|
span.SetAttributes(
|
||||||
|
attribute.String("streamID", streamID),
|
||||||
|
attribute.Int64("actualPosition", int64(events.Last().EventMeta().ActualPosition)),
|
||||||
|
attribute.String("actualStreamID", events.Last().EventMeta().ActualStreamID),
|
||||||
|
attribute.Int64("position", int64(events.Last().EventMeta().Position)),
|
||||||
|
)
|
||||||
|
|
||||||
|
position.size = int64(events.Last().EventMeta().ActualPosition - uint64(position.idx))
|
||||||
|
|
||||||
if position.wait != nil {
|
if position.wait != nil {
|
||||||
close(position.wait)
|
close(position.wait)
|
||||||
position.link = trace.LinkFromContext(ctx, attribute.String("src", "event"))
|
position.link = trace.LinkFromContext(ctx, attribute.String("src", "event"))
|
||||||
position.wait = nil
|
position.wait = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -115,7 +133,7 @@ func (s *streamer) delete(streamID string, sub *subscription) func(context.Conte
|
||||||
if err := ctx.Err(); err != nil {
|
if err := ctx.Err(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return s.state.Modify(ctx, func(state *state) error {
|
return s.state.Use(ctx, func(ctx context.Context, state *state) error {
|
||||||
_, span := lg.Span(ctx)
|
_, span := lg.Span(ctx)
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
|
@ -141,11 +159,20 @@ type wrapper struct {
|
||||||
|
|
||||||
var _ driver.EventLog = (*wrapper)(nil)
|
var _ driver.EventLog = (*wrapper)(nil)
|
||||||
|
|
||||||
func (w *wrapper) Read(ctx context.Context, pos int64, count int64) (event.Events, error) {
|
func (r *wrapper) Unwrap() driver.EventLog {
|
||||||
|
return r.up
|
||||||
|
}
|
||||||
|
func (w *wrapper) Read(ctx context.Context, after int64, count int64) (event.Events, error) {
|
||||||
ctx, span := lg.Span(ctx)
|
ctx, span := lg.Span(ctx)
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
return w.up.Read(ctx, pos, count)
|
return w.up.Read(ctx, after, count)
|
||||||
|
}
|
||||||
|
func (w *wrapper) ReadN(ctx context.Context, index ...uint64) (event.Events, error) {
|
||||||
|
ctx, span := lg.Span(ctx)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
return w.up.ReadN(ctx, index...)
|
||||||
}
|
}
|
||||||
func (w *wrapper) Append(ctx context.Context, events event.Events, version uint64) (uint64, error) {
|
func (w *wrapper) Append(ctx context.Context, events event.Events, version uint64) (uint64, error) {
|
||||||
ctx, span := lg.Span(ctx)
|
ctx, span := lg.Span(ctx)
|
||||||
|
@ -178,12 +205,6 @@ func (w *wrapper) LastIndex(ctx context.Context) (uint64, error) {
|
||||||
|
|
||||||
return w.up.LastIndex(ctx)
|
return w.up.LastIndex(ctx)
|
||||||
}
|
}
|
||||||
func (w *wrapper) LoadForUpdate(ctx context.Context, a event.Aggregate, fn func(context.Context, event.Aggregate) error) (uint64, error) {
|
|
||||||
ctx, span := lg.Span(ctx)
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
return w.up.LoadForUpdate(ctx, a, fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
type position struct {
|
type position struct {
|
||||||
size int64
|
size int64
|
||||||
|
@ -195,67 +216,79 @@ type position struct {
|
||||||
type subscription struct {
|
type subscription struct {
|
||||||
topic string
|
topic string
|
||||||
|
|
||||||
position *locker.Locked[position]
|
position *locker.Locked[*position]
|
||||||
|
|
||||||
events driver.EventLog
|
events driver.EventLog
|
||||||
unsub func(context.Context) error
|
unsub func(context.Context) error
|
||||||
|
once sync.Once
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *subscription) Recv(ctx context.Context) bool {
|
// Recv checks for new events. Returned chan will block until an event is ready or cancelled.
|
||||||
|
func (s *subscription) Recv(ctx context.Context) <-chan bool {
|
||||||
ctx, span := lg.Span(ctx)
|
ctx, span := lg.Span(ctx)
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
var wait func(context.Context) bool
|
done := make(chan bool)
|
||||||
|
|
||||||
err := s.position.Modify(ctx, func(position *position) error {
|
go func() {
|
||||||
_, span := lg.Span(ctx)
|
var wait func(context.Context) bool
|
||||||
defer span.End()
|
defer close(done)
|
||||||
|
|
||||||
if position.size == es.AllEvents {
|
err := s.position.Use(ctx, func(ctx context.Context, position *position) error {
|
||||||
return nil
|
_, span := lg.Span(ctx)
|
||||||
}
|
defer span.End()
|
||||||
if position.size == 0 {
|
|
||||||
position.wait = make(chan struct{})
|
|
||||||
wait = func(ctx context.Context) bool {
|
|
||||||
ctx, span := lg.Span(ctx)
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
select {
|
if position.size == ev.AllEvents {
|
||||||
case <-position.wait:
|
return nil
|
||||||
if position.link.SpanContext.IsValid() {
|
}
|
||||||
_, span := lg.Span(ctx, trace.WithLinks(position.link))
|
if position.size == 0 {
|
||||||
span.AddEvent("recv event")
|
position.wait = make(chan struct{})
|
||||||
span.End()
|
wait = func(ctx context.Context) bool {
|
||||||
position.link = trace.Link{}
|
ctx, span := lg.Span(ctx)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-position.wait:
|
||||||
|
if position.link.SpanContext.IsValid() {
|
||||||
|
_, span := lg.Span(ctx, trace.WithLinks(position.link))
|
||||||
|
span.AddEvent("recv event")
|
||||||
|
span.End()
|
||||||
|
position.link = trace.Link{}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
|
||||||
|
case <-ctx.Done():
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
|
||||||
case <-ctx.Done():
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
position.idx += position.size
|
||||||
|
position.size = 0
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
done <- false
|
||||||
|
return
|
||||||
}
|
}
|
||||||
position.idx += position.size
|
|
||||||
position.size = 0
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if wait != nil {
|
if wait != nil {
|
||||||
return wait(ctx)
|
done <- wait(ctx)
|
||||||
}
|
return
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
done <- true
|
||||||
|
}()
|
||||||
|
|
||||||
|
return done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Events returns a batch of events since last call. Updates position of HWM to end of stream. Call Recv to be notified when more are ready.
|
||||||
func (s *subscription) Events(ctx context.Context) (event.Events, error) {
|
func (s *subscription) Events(ctx context.Context) (event.Events, error) {
|
||||||
ctx, span := lg.Span(ctx)
|
ctx, span := lg.Span(ctx)
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
var events event.Events
|
var events event.Events
|
||||||
return events, s.position.Modify(ctx, func(position *position) error {
|
return events, s.position.Use(ctx, func(ctx context.Context, position *position) error {
|
||||||
ctx, span := lg.Span(ctx)
|
ctx, span := lg.Span(ctx)
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
|
@ -266,14 +299,30 @@ func (s *subscription) Events(ctx context.Context) (event.Events, error) {
|
||||||
}
|
}
|
||||||
position.size = int64(len(events))
|
position.size = int64(len(events))
|
||||||
if len(events) > 0 {
|
if len(events) > 0 {
|
||||||
position.idx = int64(events.First().EventMeta().Position - 1)
|
position.idx = int64(events.First().EventMeta().ActualPosition - 1)
|
||||||
}
|
}
|
||||||
|
span.SetAttributes(
|
||||||
|
attribute.Int64("position.idx", position.idx),
|
||||||
|
attribute.Int64("position.size", position.size),
|
||||||
|
attribute.Int64("meta.ActualPosition", int64(events.First().EventMeta().ActualPosition)),
|
||||||
|
attribute.Int64("meta.Position", int64(events.First().EventMeta().Position)),
|
||||||
|
)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close unsubscribes from a stream
|
||||||
func (s *subscription) Close(ctx context.Context) error {
|
func (s *subscription) Close(ctx context.Context) error {
|
||||||
ctx, span := lg.Span(ctx)
|
ctx, span := lg.Span(ctx)
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
return s.unsub(ctx)
|
if s == nil || s.unsub == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
s.once.Do(func() { err = s.unsub(ctx) })
|
||||||
|
|
||||||
|
return err
|
||||||
}
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
package es
|
// package es implements an event store and drivers for extending its functionality.
|
||||||
|
package ev
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
@ -6,12 +7,14 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sour-is/ev/internal/lg"
|
"go.opentelemetry.io/otel/attribute"
|
||||||
"github.com/sour-is/ev/pkg/es/driver"
|
"go.opentelemetry.io/otel/metric"
|
||||||
"github.com/sour-is/ev/pkg/es/event"
|
"go.sour.is/pkg/lg"
|
||||||
"github.com/sour-is/ev/pkg/locker"
|
"go.sour.is/pkg/locker"
|
||||||
"go.opentelemetry.io/otel/metric/instrument/syncint64"
|
|
||||||
"go.uber.org/multierr"
|
"go.uber.org/multierr"
|
||||||
|
|
||||||
|
"go.sour.is/ev/driver"
|
||||||
|
"go.sour.is/ev/event"
|
||||||
)
|
)
|
||||||
|
|
||||||
type config struct {
|
type config struct {
|
||||||
|
@ -29,19 +32,19 @@ func Init(ctx context.Context) error {
|
||||||
m := lg.Meter(ctx)
|
m := lg.Meter(ctx)
|
||||||
|
|
||||||
var err, errs error
|
var err, errs error
|
||||||
Mes_open, err = m.SyncInt64().Counter("es_open")
|
Mes_open, err = m.Int64Counter("es_open")
|
||||||
errs = multierr.Append(errs, err)
|
errs = multierr.Append(errs, err)
|
||||||
|
|
||||||
Mes_read, err = m.SyncInt64().Counter("es_read")
|
Mes_read, err = m.Int64Counter("es_read")
|
||||||
errs = multierr.Append(errs, err)
|
errs = multierr.Append(errs, err)
|
||||||
|
|
||||||
Mes_load, err = m.SyncInt64().Counter("es_load")
|
Mes_load, err = m.Int64Counter("es_load")
|
||||||
errs = multierr.Append(errs, err)
|
errs = multierr.Append(errs, err)
|
||||||
|
|
||||||
Mes_save, err = m.SyncInt64().Counter("es_save")
|
Mes_save, err = m.Int64Counter("es_save")
|
||||||
errs = multierr.Append(errs, err)
|
errs = multierr.Append(errs, err)
|
||||||
|
|
||||||
Mes_append, err = m.SyncInt64().Counter("es_append")
|
Mes_append, err = m.Int64Counter("es_append")
|
||||||
errs = multierr.Append(errs, err)
|
errs = multierr.Append(errs, err)
|
||||||
|
|
||||||
return errs
|
return errs
|
||||||
|
@ -51,7 +54,7 @@ func Register(ctx context.Context, name string, d driver.Driver) error {
|
||||||
ctx, span := lg.Span(ctx)
|
ctx, span := lg.Span(ctx)
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
return drivers.Modify(ctx, func(c *config) error {
|
return drivers.Use(ctx, func(ctx context.Context, c *config) error {
|
||||||
if _, set := c.drivers[name]; set {
|
if _, set := c.drivers[name]; set {
|
||||||
return fmt.Errorf("driver %s already set", name)
|
return fmt.Errorf("driver %s already set", name)
|
||||||
}
|
}
|
||||||
|
@ -65,17 +68,19 @@ type EventStore struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
Mes_open syncint64.Counter
|
Mes_open metric.Int64Counter
|
||||||
Mes_read syncint64.Counter
|
Mes_read metric.Int64Counter
|
||||||
Mes_load syncint64.Counter
|
Mes_load metric.Int64Counter
|
||||||
Mes_save syncint64.Counter
|
Mes_save metric.Int64Counter
|
||||||
Mes_append syncint64.Counter
|
Mes_append metric.Int64Counter
|
||||||
)
|
)
|
||||||
|
|
||||||
func Open(ctx context.Context, dsn string, options ...Option) (*EventStore, error) {
|
func Open(ctx context.Context, dsn string, options ...Option) (*EventStore, error) {
|
||||||
ctx, span := lg.Span(ctx)
|
ctx, span := lg.Span(ctx)
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
|
span.SetAttributes(attribute.String("dsn", dsn))
|
||||||
|
|
||||||
name, _, ok := strings.Cut(dsn, ":")
|
name, _, ok := strings.Cut(dsn, ":")
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("%w: no scheme", ErrNoDriver)
|
return nil, fmt.Errorf("%w: no scheme", ErrNoDriver)
|
||||||
|
@ -95,15 +100,19 @@ func Open(ctx context.Context, dsn string, options ...Option) (*EventStore, erro
|
||||||
conn, err := d.Open(ctx, dsn)
|
conn, err := d.Open(ctx, dsn)
|
||||||
|
|
||||||
es := &EventStore{Driver: conn}
|
es := &EventStore{Driver: conn}
|
||||||
for _, o := range options {
|
es.Option(options...)
|
||||||
o.Apply(es)
|
|
||||||
}
|
|
||||||
|
|
||||||
Mes_open.Add(ctx, 1)
|
Mes_open.Add(ctx, 1)
|
||||||
|
|
||||||
return es, err
|
return es, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (es *EventStore) Option(options ...Option) {
|
||||||
|
for _, o := range options {
|
||||||
|
o.Apply(es)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type Option interface {
|
type Option interface {
|
||||||
Apply(*EventStore)
|
Apply(*EventStore)
|
||||||
}
|
}
|
||||||
|
@ -117,7 +126,11 @@ func (es *EventStore) Save(ctx context.Context, agg event.Aggregate) (uint64, er
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
Mes_save.Add(ctx, 1)
|
span.SetAttributes(
|
||||||
|
attribute.String("agg.type", event.TypeOf(agg)),
|
||||||
|
attribute.String("agg.streamID", agg.StreamID()),
|
||||||
|
attribute.Int64("agg.version", int64(agg.StreamVersion())),
|
||||||
|
)
|
||||||
|
|
||||||
l, err := es.EventLog(ctx, agg.StreamID())
|
l, err := es.EventLog(ctx, agg.StreamID())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -128,6 +141,7 @@ func (es *EventStore) Save(ctx context.Context, agg event.Aggregate) (uint64, er
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
Mes_save.Add(ctx, int64(count))
|
||||||
|
|
||||||
agg.Commit()
|
agg.Commit()
|
||||||
return count, err
|
return count, err
|
||||||
|
@ -136,39 +150,90 @@ func (es *EventStore) Load(ctx context.Context, agg event.Aggregate) error {
|
||||||
ctx, span := lg.Span(ctx)
|
ctx, span := lg.Span(ctx)
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
Mes_load.Add(ctx, 1)
|
span.SetAttributes(
|
||||||
|
attribute.String("agg.type", event.TypeOf(agg)),
|
||||||
|
attribute.String("agg.streamID", agg.StreamID()),
|
||||||
|
)
|
||||||
l, err := es.Driver.EventLog(ctx, agg.StreamID())
|
l, err := es.Driver.EventLog(ctx, agg.StreamID())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
first, err := l.FirstIndex(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
events, err := l.Read(ctx, 0, AllEvents)
|
events, err := l.Read(ctx, 0, AllEvents)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if len(events) == 0 {
|
||||||
|
return ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
Mes_load.Add(ctx, events.Count())
|
||||||
|
event.Start(agg, first-1)
|
||||||
event.Append(agg, events...)
|
event.Append(agg, events...)
|
||||||
|
|
||||||
|
span.SetAttributes(
|
||||||
|
attribute.Int64("agg.version", int64(agg.StreamVersion())),
|
||||||
|
)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func (es *EventStore) Read(ctx context.Context, streamID string, pos, count int64) (event.Events, error) {
|
func (es *EventStore) Read(ctx context.Context, streamID string, after, count int64) (event.Events, error) {
|
||||||
ctx, span := lg.Span(ctx)
|
ctx, span := lg.Span(ctx)
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
Mes_read.Add(ctx, 1)
|
span.SetAttributes(
|
||||||
|
attribute.String("streamID", streamID),
|
||||||
|
attribute.Int64("after", after),
|
||||||
|
attribute.Int64("count", count),
|
||||||
|
)
|
||||||
|
|
||||||
l, err := es.Driver.EventLog(ctx, streamID)
|
l, err := es.Driver.EventLog(ctx, streamID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return l.Read(ctx, pos, count)
|
|
||||||
|
events, err := l.Read(ctx, after, count)
|
||||||
|
Mes_read.Add(ctx, events.Count())
|
||||||
|
|
||||||
|
return events, err
|
||||||
|
}
|
||||||
|
func (es *EventStore) ReadN(ctx context.Context, streamID string, index ...uint64) (event.Events, error) {
|
||||||
|
ctx, span := lg.Span(ctx)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
lis := make([]int64, len(index))
|
||||||
|
for i, j := range index {
|
||||||
|
lis[i] = int64(j)
|
||||||
|
}
|
||||||
|
|
||||||
|
span.SetAttributes(
|
||||||
|
attribute.String("streamID", streamID),
|
||||||
|
attribute.Int64Slice("index", lis),
|
||||||
|
)
|
||||||
|
|
||||||
|
l, err := es.Driver.EventLog(ctx, streamID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
events, err := l.ReadN(ctx, index...)
|
||||||
|
Mes_read.Add(ctx, events.Count())
|
||||||
|
|
||||||
|
return events, err
|
||||||
}
|
}
|
||||||
func (es *EventStore) Append(ctx context.Context, streamID string, events event.Events) (uint64, error) {
|
func (es *EventStore) Append(ctx context.Context, streamID string, events event.Events) (uint64, error) {
|
||||||
ctx, span := lg.Span(ctx)
|
ctx, span := lg.Span(ctx)
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
Mes_append.Add(ctx, 1)
|
Mes_append.Add(ctx, 1)
|
||||||
|
span.SetAttributes(
|
||||||
|
attribute.String("ev.streamID", streamID),
|
||||||
|
)
|
||||||
|
|
||||||
l, err := es.Driver.EventLog(ctx, streamID)
|
l, err := es.Driver.EventLog(ctx, streamID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -180,6 +245,10 @@ func (es *EventStore) FirstIndex(ctx context.Context, streamID string) (uint64,
|
||||||
ctx, span := lg.Span(ctx)
|
ctx, span := lg.Span(ctx)
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
|
span.SetAttributes(
|
||||||
|
attribute.String("ev.streamID", streamID),
|
||||||
|
)
|
||||||
|
|
||||||
l, err := es.Driver.EventLog(ctx, streamID)
|
l, err := es.Driver.EventLog(ctx, streamID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
|
@ -190,6 +259,10 @@ func (es *EventStore) LastIndex(ctx context.Context, streamID string) (uint64, e
|
||||||
ctx, span := lg.Span(ctx)
|
ctx, span := lg.Span(ctx)
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
|
span.SetAttributes(
|
||||||
|
attribute.String("ev.streamID", streamID),
|
||||||
|
)
|
||||||
|
|
||||||
l, err := es.Driver.EventLog(ctx, streamID)
|
l, err := es.Driver.EventLog(ctx, streamID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
|
@ -210,6 +283,27 @@ func (es *EventStore) EventStream() driver.EventStream {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
func (es *EventStore) Truncate(ctx context.Context, streamID string, index int64) error {
|
||||||
|
ctx, span := lg.Span(ctx)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
up, err := es.Driver.EventLog(ctx, streamID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for up != nil {
|
||||||
|
if up, ok := up.(driver.EventLogWithTruncate); ok {
|
||||||
|
err = up.Truncate(ctx, index)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
up = Unwrap(up)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ErrNoDriver
|
||||||
|
}
|
||||||
|
|
||||||
func Unwrap[T any](t T) T {
|
func Unwrap[T any](t T) T {
|
||||||
if unwrap, ok := any(t).(interface{ Unwrap() T }); ok {
|
if unwrap, ok := any(t).(interface{ Unwrap() T }); ok {
|
||||||
|
@ -224,6 +318,7 @@ var ErrNoDriver = errors.New("no driver")
|
||||||
var ErrWrongVersion = errors.New("wrong version")
|
var ErrWrongVersion = errors.New("wrong version")
|
||||||
var ErrShouldExist = event.ErrShouldExist
|
var ErrShouldExist = event.ErrShouldExist
|
||||||
var ErrShouldNotExist = event.ErrShouldNotExist
|
var ErrShouldNotExist = event.ErrShouldNotExist
|
||||||
|
var ErrNotFound = errors.New("not found")
|
||||||
|
|
||||||
type PA[T any] interface {
|
type PA[T any] interface {
|
||||||
event.Aggregate
|
event.Aggregate
|
||||||
|
@ -241,8 +336,11 @@ func Create[A any, T PA[A]](ctx context.Context, es *EventStore, streamID string
|
||||||
|
|
||||||
agg = new(A)
|
agg = new(A)
|
||||||
agg.SetStreamID(streamID)
|
agg.SetStreamID(streamID)
|
||||||
|
span.SetAttributes(
|
||||||
|
attribute.String("agg.streamID", streamID),
|
||||||
|
)
|
||||||
|
|
||||||
if err = es.Load(ctx, agg); err != nil {
|
if err = es.Load(ctx, agg); err != nil && !errors.Is(err, ErrNotFound) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -271,6 +369,9 @@ func Update[A any, T PA[A]](ctx context.Context, es *EventStore, streamID string
|
||||||
|
|
||||||
agg = new(A)
|
agg = new(A)
|
||||||
agg.SetStreamID(streamID)
|
agg.SetStreamID(streamID)
|
||||||
|
span.SetAttributes(
|
||||||
|
attribute.String("agg.streamID", streamID),
|
||||||
|
)
|
||||||
|
|
||||||
if err = es.Load(ctx, agg); err != nil {
|
if err = es.Load(ctx, agg); err != nil {
|
||||||
return
|
return
|
||||||
|
@ -298,8 +399,11 @@ func Upsert[A any, T PA[A]](ctx context.Context, es *EventStore, streamID string
|
||||||
|
|
||||||
agg = new(A)
|
agg = new(A)
|
||||||
agg.SetStreamID(streamID)
|
agg.SetStreamID(streamID)
|
||||||
|
span.SetAttributes(
|
||||||
|
attribute.String("agg.streamID", streamID),
|
||||||
|
)
|
||||||
|
|
||||||
if err = es.Load(ctx, agg); err != nil {
|
if err = es.Load(ctx, agg); err != nil && !errors.Is(err, ErrNotFound) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,21 @@
|
||||||
package es_test
|
package ev_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/matryer/is"
|
"github.com/matryer/is"
|
||||||
|
"go.uber.org/multierr"
|
||||||
|
|
||||||
"github.com/sour-is/ev/pkg/es"
|
"go.sour.is/ev"
|
||||||
memstore "github.com/sour-is/ev/pkg/es/driver/mem-store"
|
memstore "go.sour.is/ev/driver/mem-store"
|
||||||
"github.com/sour-is/ev/pkg/es/driver/projecter"
|
"go.sour.is/ev/driver/projecter"
|
||||||
"github.com/sour-is/ev/pkg/es/driver/streamer"
|
resolvelinks "go.sour.is/ev/driver/resolve-links"
|
||||||
"github.com/sour-is/ev/pkg/es/event"
|
"go.sour.is/ev/driver/streamer"
|
||||||
|
"go.sour.is/ev/event"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -26,12 +27,9 @@ type Thing struct {
|
||||||
Name string
|
Name string
|
||||||
Value string
|
Value string
|
||||||
|
|
||||||
event.AggregateRoot
|
event.IsAggregate
|
||||||
}
|
}
|
||||||
|
|
||||||
// func (a *Thing) StreamID() string {
|
|
||||||
// return fmt.Sprintf("thing-%s", a.Name)
|
|
||||||
// }
|
|
||||||
func (a *Thing) ApplyEvent(lis ...event.Event) {
|
func (a *Thing) ApplyEvent(lis ...event.Event) {
|
||||||
for _, e := range lis {
|
for _, e := range lis {
|
||||||
switch e := e.(type) {
|
switch e := e.(type) {
|
||||||
|
@ -48,26 +46,7 @@ func (a *Thing) OnSetValue(value string) error {
|
||||||
type ValueSet struct {
|
type ValueSet struct {
|
||||||
Value string
|
Value string
|
||||||
|
|
||||||
eventMeta event.Meta
|
event.IsEvent
|
||||||
}
|
|
||||||
|
|
||||||
func (e *ValueSet) EventMeta() event.Meta {
|
|
||||||
if e == nil {
|
|
||||||
return event.Meta{}
|
|
||||||
}
|
|
||||||
return e.eventMeta
|
|
||||||
}
|
|
||||||
func (e *ValueSet) SetEventMeta(eventMeta event.Meta) {
|
|
||||||
if e == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
e.eventMeta = eventMeta
|
|
||||||
}
|
|
||||||
func (e *ValueSet) MarshalBinary() ([]byte, error) {
|
|
||||||
return json.Marshal(e)
|
|
||||||
}
|
|
||||||
func (e *ValueSet) UnmarshalBinary(b []byte) error {
|
|
||||||
return json.Unmarshal(b, e)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestES(t *testing.T) {
|
func TestES(t *testing.T) {
|
||||||
|
@ -78,26 +57,23 @@ func TestES(t *testing.T) {
|
||||||
err := event.Register(ctx, &ValueSet{})
|
err := event.Register(ctx, &ValueSet{})
|
||||||
is.NoErr(err)
|
is.NoErr(err)
|
||||||
|
|
||||||
es.Init(ctx)
|
|
||||||
memstore.Init(ctx)
|
|
||||||
|
|
||||||
{
|
{
|
||||||
store, err := es.Open(ctx, "mem")
|
store, err := ev.Open(ctx, "mem")
|
||||||
is.True(errors.Is(err, es.ErrNoDriver))
|
is.True(errors.Is(err, ev.ErrNoDriver))
|
||||||
is.True(store.EventStream() == nil)
|
is.True(store.EventStream() == nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
_, err := es.Open(ctx, "bogo:")
|
_, err := ev.Open(ctx, "bogo:")
|
||||||
is.True(errors.Is(err, es.ErrNoDriver))
|
is.True(errors.Is(err, ev.ErrNoDriver))
|
||||||
}
|
}
|
||||||
|
|
||||||
store, err := es.Open(ctx, "mem:", streamer.New(ctx), projecter.New(ctx))
|
store, err := ev.Open(ctx, "mem:", streamer.New(ctx), projecter.New(ctx))
|
||||||
is.NoErr(err)
|
is.NoErr(err)
|
||||||
|
|
||||||
thing := &Thing{Name: "time"}
|
thing := &Thing{Name: "time"}
|
||||||
err = store.Load(ctx, thing)
|
err = store.Load(ctx, thing)
|
||||||
is.NoErr(err)
|
is.True(errors.Is(err, ev.ErrNotFound))
|
||||||
|
|
||||||
t.Log(thing.StreamVersion(), thing.Name, thing.Value)
|
t.Log(thing.StreamVersion(), thing.Name, thing.Value)
|
||||||
|
|
||||||
|
@ -138,13 +114,10 @@ func TestESOperations(t *testing.T) {
|
||||||
is := is.New(t)
|
is := is.New(t)
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
es.Init(ctx)
|
store, err := ev.Open(ctx, "mem:", streamer.New(ctx), projecter.New(ctx))
|
||||||
memstore.Init(ctx)
|
|
||||||
|
|
||||||
store, err := es.Open(ctx, "mem:", streamer.New(ctx), projecter.New(ctx))
|
|
||||||
is.NoErr(err)
|
is.NoErr(err)
|
||||||
|
|
||||||
thing, err := es.Create(ctx, store, "thing-1", func(ctx context.Context, agg *Thing) error {
|
thing, err := ev.Create(ctx, store, "thing-1", func(ctx context.Context, agg *Thing) error {
|
||||||
return agg.OnSetValue("foo")
|
return agg.OnSetValue("foo")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -152,7 +125,7 @@ func TestESOperations(t *testing.T) {
|
||||||
is.Equal(thing.Version(), uint64(1))
|
is.Equal(thing.Version(), uint64(1))
|
||||||
is.Equal(thing.Value, "foo")
|
is.Equal(thing.Value, "foo")
|
||||||
|
|
||||||
thing, err = es.Update(ctx, store, "thing-1", func(ctx context.Context, agg *Thing) error {
|
thing, err = ev.Update(ctx, store, "thing-1", func(ctx context.Context, agg *Thing) error {
|
||||||
return agg.OnSetValue("bar")
|
return agg.OnSetValue("bar")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -160,7 +133,7 @@ func TestESOperations(t *testing.T) {
|
||||||
is.Equal(thing.Version(), uint64(2))
|
is.Equal(thing.Version(), uint64(2))
|
||||||
is.Equal(thing.Value, "bar")
|
is.Equal(thing.Value, "bar")
|
||||||
|
|
||||||
thing, err = es.Upsert(ctx, store, "thing-2", func(ctx context.Context, agg *Thing) error {
|
thing, err = ev.Upsert(ctx, store, "thing-2", func(ctx context.Context, agg *Thing) error {
|
||||||
return agg.OnSetValue("bin")
|
return agg.OnSetValue("bin")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -168,7 +141,7 @@ func TestESOperations(t *testing.T) {
|
||||||
is.Equal(thing.Version(), uint64(1))
|
is.Equal(thing.Version(), uint64(1))
|
||||||
is.Equal(thing.Value, "bin")
|
is.Equal(thing.Value, "bin")
|
||||||
|
|
||||||
thing, err = es.Upsert(ctx, store, "thing-2", func(ctx context.Context, agg *Thing) error {
|
thing, err = ev.Upsert(ctx, store, "thing-2", func(ctx context.Context, agg *Thing) error {
|
||||||
return agg.OnSetValue("baz")
|
return agg.OnSetValue("baz")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -184,6 +157,46 @@ func TestUnwrap(t *testing.T) {
|
||||||
err := errors.New("foo")
|
err := errors.New("foo")
|
||||||
werr := fmt.Errorf("wrap: %w", err)
|
werr := fmt.Errorf("wrap: %w", err)
|
||||||
|
|
||||||
is.Equal(es.Unwrap(werr), err)
|
is.Equal(ev.Unwrap(werr), err)
|
||||||
is.Equal(es.Unwrap("test"), "")
|
is.Equal(ev.Unwrap("test"), "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnwrapProjector(t *testing.T) {
|
||||||
|
is := is.New(t)
|
||||||
|
|
||||||
|
ctx, stop := context.WithCancel(context.Background())
|
||||||
|
defer stop()
|
||||||
|
|
||||||
|
es, err := ev.Open(
|
||||||
|
ctx,
|
||||||
|
"mem:",
|
||||||
|
resolvelinks.New(),
|
||||||
|
streamer.New(ctx),
|
||||||
|
projecter.New(
|
||||||
|
ctx,
|
||||||
|
projecter.DefaultProjection,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
is.NoErr(err)
|
||||||
|
|
||||||
|
stream := es.EventStream()
|
||||||
|
is.True(stream != nil)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
ctx, stop := context.WithCancel(context.Background())
|
||||||
|
defer stop()
|
||||||
|
|
||||||
|
err := multierr.Combine(
|
||||||
|
ev.Init(ctx),
|
||||||
|
event.Init(ctx),
|
||||||
|
memstore.Init(ctx),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
m.Run()
|
||||||
}
|
}
|
|
@ -6,11 +6,17 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Aggregate implements functionality for working with event store streams as an aggregate.
|
||||||
|
// When creating a new Aggregate the struct should have an ApplyEvent method and embed the AggregateRoot.
|
||||||
type Aggregate interface {
|
type Aggregate interface {
|
||||||
// ApplyEvent applies the event to the aggrigate state
|
// ApplyEvent applies the event to the aggrigate state
|
||||||
ApplyEvent(...Event)
|
ApplyEvent(...Event)
|
||||||
|
|
||||||
AggregateRootInterface
|
AggregateRoot
|
||||||
|
}
|
||||||
|
|
||||||
|
func Start(a Aggregate, i uint64) {
|
||||||
|
a.start(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Raise adds new uncommitted events
|
// Raise adds new uncommitted events
|
||||||
|
@ -43,7 +49,7 @@ func ShouldExist(a Aggregate) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type AggregateRootInterface interface {
|
type AggregateRoot interface {
|
||||||
// Events returns the aggregate events
|
// Events returns the aggregate events
|
||||||
// pass true for only uncommitted events
|
// pass true for only uncommitted events
|
||||||
Events(bool) Events
|
Events(bool) Events
|
||||||
|
@ -56,33 +62,35 @@ type AggregateRootInterface interface {
|
||||||
// Version returns the current aggrigate version. (committed + uncommitted)
|
// Version returns the current aggrigate version. (committed + uncommitted)
|
||||||
Version() uint64
|
Version() uint64
|
||||||
|
|
||||||
|
start(uint64)
|
||||||
raise(lis ...Event)
|
raise(lis ...Event)
|
||||||
append(lis ...Event)
|
append(lis ...Event)
|
||||||
Commit()
|
Commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ AggregateRootInterface = &AggregateRoot{}
|
var _ AggregateRoot = &IsAggregate{}
|
||||||
|
|
||||||
type AggregateRoot struct {
|
type IsAggregate struct {
|
||||||
events Events
|
events Events
|
||||||
streamID string
|
streamID string
|
||||||
streamVersion uint64
|
firstIndex uint64
|
||||||
|
lastIndex uint64
|
||||||
|
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AggregateRoot) Commit() { a.streamVersion = uint64(len(a.events)) }
|
func (a *IsAggregate) Commit() { a.lastIndex = uint64(len(a.events)) }
|
||||||
func (a *AggregateRoot) StreamID() string { return a.streamID }
|
func (a *IsAggregate) StreamID() string { return a.streamID }
|
||||||
func (a *AggregateRoot) SetStreamID(streamID string) { a.streamID = streamID }
|
func (a *IsAggregate) SetStreamID(streamID string) { a.streamID = streamID }
|
||||||
func (a *AggregateRoot) StreamVersion() uint64 { return a.streamVersion }
|
func (a *IsAggregate) StreamVersion() uint64 { return a.lastIndex }
|
||||||
func (a *AggregateRoot) Version() uint64 { return uint64(len(a.events)) }
|
func (a *IsAggregate) Version() uint64 { return a.firstIndex + uint64(len(a.events)) }
|
||||||
func (a *AggregateRoot) Events(new bool) Events {
|
func (a *IsAggregate) Events(new bool) Events {
|
||||||
a.mu.RLock()
|
a.mu.RLock()
|
||||||
defer a.mu.RUnlock()
|
defer a.mu.RUnlock()
|
||||||
|
|
||||||
events := a.events
|
events := a.events
|
||||||
if new {
|
if new {
|
||||||
events = events[a.streamVersion:]
|
events = events[a.lastIndex-a.firstIndex:]
|
||||||
}
|
}
|
||||||
|
|
||||||
lis := make(Events, len(events))
|
lis := make(Events, len(events))
|
||||||
|
@ -91,8 +99,13 @@ func (a *AggregateRoot) Events(new bool) Events {
|
||||||
return lis
|
return lis
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *IsAggregate) start(i uint64) {
|
||||||
|
a.firstIndex = i
|
||||||
|
a.lastIndex = i
|
||||||
|
}
|
||||||
|
|
||||||
//lint:ignore U1000 is called by embeded interface
|
//lint:ignore U1000 is called by embeded interface
|
||||||
func (a *AggregateRoot) raise(lis ...Event) { //nolint
|
func (a *IsAggregate) raise(lis ...Event) { //nolint
|
||||||
a.mu.Lock()
|
a.mu.Lock()
|
||||||
defer a.mu.Unlock()
|
defer a.mu.Unlock()
|
||||||
|
|
||||||
|
@ -102,20 +115,20 @@ func (a *AggregateRoot) raise(lis ...Event) { //nolint
|
||||||
}
|
}
|
||||||
|
|
||||||
//lint:ignore U1000 is called by embeded interface
|
//lint:ignore U1000 is called by embeded interface
|
||||||
func (a *AggregateRoot) append(lis ...Event) {
|
func (a *IsAggregate) append(lis ...Event) {
|
||||||
a.mu.Lock()
|
a.mu.Lock()
|
||||||
defer a.mu.Unlock()
|
defer a.mu.Unlock()
|
||||||
|
|
||||||
a.posStartAt(lis...)
|
a.posStartAt(lis...)
|
||||||
|
|
||||||
a.events = append(a.events, lis...)
|
a.events = append(a.events, lis...)
|
||||||
a.streamVersion += uint64(len(lis))
|
a.lastIndex += uint64(len(lis))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AggregateRoot) posStartAt(lis ...Event) {
|
func (a *IsAggregate) posStartAt(lis ...Event) {
|
||||||
for i, e := range lis {
|
for i, e := range lis {
|
||||||
m := e.EventMeta()
|
m := e.EventMeta()
|
||||||
m.Position = a.streamVersion + uint64(i) + 1
|
m.Position = a.lastIndex + uint64(i) + 1
|
||||||
e.SetEventMeta(m)
|
e.SetEventMeta(m)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,16 +1,15 @@
|
||||||
package event_test
|
package event_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/sour-is/ev/pkg/es/event"
|
"go.sour.is/ev/event"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Agg struct {
|
type Agg struct {
|
||||||
Value string
|
Value string
|
||||||
|
|
||||||
event.AggregateRoot
|
event.IsAggregate
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ event.Aggregate = (*Agg)(nil)
|
var _ event.Aggregate = (*Agg)(nil)
|
||||||
|
@ -33,31 +32,11 @@ func (a *Agg) ApplyEvent(lis ...event.Event) {
|
||||||
type ValueApplied struct {
|
type ValueApplied struct {
|
||||||
Value string
|
Value string
|
||||||
|
|
||||||
eventMeta event.Meta
|
event.IsEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ event.Event = (*ValueApplied)(nil)
|
var _ event.Event = (*ValueApplied)(nil)
|
||||||
|
|
||||||
func (e *ValueApplied) EventMeta() event.Meta {
|
|
||||||
if e == nil {
|
|
||||||
return event.Meta{}
|
|
||||||
}
|
|
||||||
return e.eventMeta
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *ValueApplied) SetEventMeta(m event.Meta) {
|
|
||||||
if e != nil {
|
|
||||||
e.eventMeta = m
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *ValueApplied) MarshalBinary() ([]byte, error) {
|
|
||||||
return json.Marshal(e)
|
|
||||||
}
|
|
||||||
func (e *ValueApplied) UnmarshalBinary(b []byte) error {
|
|
||||||
return json.Unmarshal(b, e)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAggregate(t *testing.T) {
|
func TestAggregate(t *testing.T) {
|
||||||
agg := &Agg{}
|
agg := &Agg{}
|
||||||
event.Append(agg, &ValueApplied{Value: "one"})
|
event.Append(agg, &ValueApplied{Value: "one"})
|
|
@ -1,10 +1,9 @@
|
||||||
|
// package event implements functionality for working with an eventstore.
|
||||||
package event
|
package event
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -28,12 +27,10 @@ func getULID() ulid.ULID {
|
||||||
return ulid.MustNew(ulid.Now(), entropy)
|
return ulid.MustNew(ulid.Now(), entropy)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Event implements functionality of an individual event used with the event store. It should implement the getter/setter for EventMeta and BinaryMarshaler/BinaryUnmarshaler.
|
||||||
type Event interface {
|
type Event interface {
|
||||||
EventMeta() Meta
|
EventMeta() Meta
|
||||||
SetEventMeta(Meta)
|
SetEventMeta(Meta)
|
||||||
|
|
||||||
encoding.BinaryMarshaler
|
|
||||||
encoding.BinaryUnmarshaler
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Events is a list of events
|
// Events is a list of events
|
||||||
|
@ -43,6 +40,9 @@ func NewEvents(lis ...Event) Events {
|
||||||
for i, e := range lis {
|
for i, e := range lis {
|
||||||
meta := e.EventMeta()
|
meta := e.EventMeta()
|
||||||
meta.Position = uint64(i)
|
meta.Position = uint64(i)
|
||||||
|
if meta.ActualPosition == 0 {
|
||||||
|
meta.ActualPosition = uint64(i)
|
||||||
|
}
|
||||||
meta.EventID = getULID()
|
meta.EventID = getULID()
|
||||||
e.SetEventMeta(meta)
|
e.SetEventMeta(meta)
|
||||||
}
|
}
|
||||||
|
@ -58,6 +58,9 @@ func (lis Events) StreamID() string {
|
||||||
func (lis Events) SetStreamID(streamID string) {
|
func (lis Events) SetStreamID(streamID string) {
|
||||||
SetStreamID(streamID, lis...)
|
SetStreamID(streamID, lis...)
|
||||||
}
|
}
|
||||||
|
func (lis Events) Count() int64 {
|
||||||
|
return int64(len(lis))
|
||||||
|
}
|
||||||
func (lis Events) First() Event {
|
func (lis Events) First() Event {
|
||||||
if len(lis) == 0 {
|
if len(lis) == 0 {
|
||||||
return NilEvent
|
return NilEvent
|
||||||
|
@ -77,7 +80,7 @@ func (lis Events) Last() Event {
|
||||||
return lis[len(lis)-1]
|
return lis[len(lis)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
func TypeOf(e Event) string {
|
func TypeOf(e any) string {
|
||||||
if ie, ok := e.(interface{ UnwrapEvent() Event }); ok {
|
if ie, ok := e.(interface{ UnwrapEvent() Event }); ok {
|
||||||
e = ie.UnwrapEvent()
|
e = ie.UnwrapEvent()
|
||||||
}
|
}
|
||||||
|
@ -102,6 +105,9 @@ func SetStreamID(id string, lis ...Event) {
|
||||||
for _, e := range lis {
|
for _, e := range lis {
|
||||||
meta := e.EventMeta()
|
meta := e.EventMeta()
|
||||||
meta.StreamID = id
|
meta.StreamID = id
|
||||||
|
if meta.ActualStreamID == "" {
|
||||||
|
meta.ActualStreamID = id
|
||||||
|
}
|
||||||
e.SetEventMeta(meta)
|
e.SetEventMeta(meta)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -117,13 +123,16 @@ func SetEventID(e Event, id ulid.ULID) {
|
||||||
func SetPosition(e Event, i uint64) {
|
func SetPosition(e Event, i uint64) {
|
||||||
meta := e.EventMeta()
|
meta := e.EventMeta()
|
||||||
meta.Position = i
|
meta.Position = i
|
||||||
|
meta.ActualPosition = i
|
||||||
e.SetEventMeta(meta)
|
e.SetEventMeta(meta)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Meta struct {
|
type Meta struct {
|
||||||
EventID ulid.ULID
|
EventID ulid.ULID
|
||||||
StreamID string
|
StreamID string
|
||||||
Position uint64
|
Position uint64
|
||||||
|
ActualStreamID string
|
||||||
|
ActualPosition uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Meta) Created() time.Time {
|
func (m Meta) Created() time.Time {
|
||||||
|
@ -132,79 +141,126 @@ func (m Meta) Created() time.Time {
|
||||||
func (m Meta) GetEventID() string { return m.EventID.String() }
|
func (m Meta) GetEventID() string { return m.EventID.String() }
|
||||||
|
|
||||||
func Init(ctx context.Context) error {
|
func Init(ctx context.Context) error {
|
||||||
return Register(ctx, NilEvent, &eventPtr{})
|
if err := Register(ctx, NilEvent, &EventPtr{}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := RegisterName(ctx, "event.eventPtr", &EventPtr{}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type nilEvent struct{}
|
type nilEvent struct {
|
||||||
|
IsEvent
|
||||||
func (*nilEvent) EventMeta() Meta {
|
|
||||||
return Meta{}
|
|
||||||
}
|
}
|
||||||
func (*nilEvent) SetEventMeta(eventMeta Meta) {}
|
|
||||||
|
|
||||||
var NilEvent = &nilEvent{}
|
var NilEvent = &nilEvent{}
|
||||||
|
|
||||||
func (e *nilEvent) MarshalBinary() ([]byte, error) {
|
func (e *nilEvent) MarshalBinary() ([]byte, error) {
|
||||||
return json.Marshal(e)
|
return nil, nil
|
||||||
}
|
}
|
||||||
func (e *nilEvent) UnmarshalBinary(b []byte) error {
|
func (e *nilEvent) UnmarshalBinary(b []byte) error {
|
||||||
return json.Unmarshal(b, e)
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type eventPtr struct {
|
type EventPtr struct {
|
||||||
streamID string
|
StreamID string `json:"stream_id"`
|
||||||
pos uint64
|
Pos uint64 `json:"pos"`
|
||||||
|
|
||||||
eventMeta Meta
|
IsEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Event = (*eventPtr)(nil)
|
var _ Event = (*EventPtr)(nil)
|
||||||
|
|
||||||
func NewPtr(streamID string, pos uint64) *eventPtr {
|
func NewPtr(streamID string, pos uint64) *EventPtr {
|
||||||
return &eventPtr{streamID: streamID, pos: pos}
|
return &EventPtr{StreamID: streamID, Pos: pos}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalBinary implements Event
|
// MarshalBinary implements Event
|
||||||
func (e *eventPtr) MarshalBinary() (data []byte, err error) {
|
func (e *EventPtr) MarshalBinary() (data []byte, err error) {
|
||||||
return []byte(fmt.Sprintf("%s@%d", e.streamID, e.pos)), nil
|
return []byte(fmt.Sprintf("%s@%d", e.StreamID, e.Pos)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalBinary implements Event
|
// UnmarshalBinary implements Event
|
||||||
func (e *eventPtr) UnmarshalBinary(data []byte) error {
|
func (e *EventPtr) UnmarshalBinary(data []byte) error {
|
||||||
s := string(data)
|
s := string(data)
|
||||||
idx := strings.LastIndex(s, "@")
|
idx := strings.LastIndex(s, "@")
|
||||||
if idx == -1 {
|
if idx == -1 {
|
||||||
return fmt.Errorf("missing @ in: %s", s)
|
return fmt.Errorf("missing @ in: %s", s)
|
||||||
}
|
}
|
||||||
e.streamID = s[:idx]
|
e.StreamID = s[:idx]
|
||||||
var err error
|
var err error
|
||||||
e.pos, err = strconv.ParseUint(s[idx+1:], 10, 64)
|
e.Pos, err = strconv.ParseUint(s[idx+1:], 10, 64)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// EventMeta implements Event
|
func (e *EventPtr) Values() any {
|
||||||
func (e *eventPtr) EventMeta() Meta {
|
|
||||||
if e == nil {
|
|
||||||
return Meta{}
|
|
||||||
}
|
|
||||||
return e.eventMeta
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetEventMeta implements Event
|
|
||||||
func (e *eventPtr) SetEventMeta(m Meta) {
|
|
||||||
if e == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
e.eventMeta = m
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *eventPtr) Values() any {
|
|
||||||
return struct {
|
return struct {
|
||||||
StreamID string `json:"stream_id"`
|
StreamID string `json:"stream_id"`
|
||||||
Pos uint64 `json:"pos"`
|
Pos uint64 `json:"pos"`
|
||||||
}{
|
}{
|
||||||
e.streamID,
|
e.StreamID,
|
||||||
e.pos,
|
e.Pos,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type FeedTruncated struct {
|
||||||
|
IsEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *FeedTruncated) Values() any {
|
||||||
|
return struct {
|
||||||
|
}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type property[T any] struct {
|
||||||
|
v T
|
||||||
|
}
|
||||||
|
|
||||||
|
type IsEvent = property[Meta]
|
||||||
|
|
||||||
|
func (p *property[T]) EventMeta() T {
|
||||||
|
if p == nil {
|
||||||
|
var t T
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
return p.v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *property[T]) SetEventMeta(x T) {
|
||||||
|
if p != nil {
|
||||||
|
p.v = x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AsEvent[T any](e T) Event {
|
||||||
|
return &asEvent[T]{payload: e}
|
||||||
|
}
|
||||||
|
|
||||||
|
type asEvent[T any] struct {
|
||||||
|
payload T
|
||||||
|
IsEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e asEvent[T]) Payload() T {
|
||||||
|
return e.payload
|
||||||
|
}
|
||||||
|
|
||||||
|
type AGG interface{ ApplyEvent(...Event) }
|
||||||
|
|
||||||
|
func AsAggregate[T AGG](e T) Aggregate {
|
||||||
|
return &asAggregate[T]{payload: e}
|
||||||
|
}
|
||||||
|
|
||||||
|
type asAggregate[T AGG] struct {
|
||||||
|
payload T
|
||||||
|
IsAggregate
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *asAggregate[T]) Payload() T {
|
||||||
|
return e.payload
|
||||||
|
}
|
||||||
|
func (e *asAggregate[T]) ApplyEvent(lis ...Event) {
|
||||||
|
e.payload.ApplyEvent(lis...)
|
||||||
|
}
|
|
@ -8,27 +8,15 @@ import (
|
||||||
|
|
||||||
"github.com/matryer/is"
|
"github.com/matryer/is"
|
||||||
|
|
||||||
"github.com/sour-is/ev/pkg/es/event"
|
"go.sour.is/ev/event"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DummyEvent struct {
|
type DummyEvent struct {
|
||||||
Value string
|
Value string
|
||||||
|
|
||||||
eventMeta event.Meta
|
event.IsEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *DummyEvent) EventMeta() event.Meta {
|
|
||||||
if e == nil {
|
|
||||||
return event.Meta{}
|
|
||||||
}
|
|
||||||
return e.eventMeta
|
|
||||||
}
|
|
||||||
func (e *DummyEvent) SetEventMeta(eventMeta event.Meta) {
|
|
||||||
if e == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
e.eventMeta = eventMeta
|
|
||||||
}
|
|
||||||
func (e *DummyEvent) MarshalBinary() ([]byte, error) {
|
func (e *DummyEvent) MarshalBinary() ([]byte, error) {
|
||||||
return json.Marshal(e)
|
return json.Marshal(e)
|
||||||
}
|
}
|
||||||
|
@ -67,3 +55,28 @@ func TestEventEncode(t *testing.T) {
|
||||||
is.Equal(lis[i], chk[i])
|
is.Equal(lis[i], chk[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type exampleAgg struct{ value string }
|
||||||
|
|
||||||
|
func (a *exampleAgg) ApplyEvent(lis ...event.Event) {
|
||||||
|
for _, e := range lis {
|
||||||
|
switch e := e.(type) {
|
||||||
|
case interface{ Payload() exampleEvSetValue }:
|
||||||
|
a.value = e.Payload().value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type exampleEvSetValue struct{ value string }
|
||||||
|
|
||||||
|
func TestApplyEventGeneric(t *testing.T) {
|
||||||
|
payload := &exampleAgg{}
|
||||||
|
var agg = event.AsAggregate(payload)
|
||||||
|
|
||||||
|
agg.ApplyEvent(event.NewEvents(
|
||||||
|
event.AsEvent(exampleEvSetValue{"hello"}),
|
||||||
|
)...)
|
||||||
|
|
||||||
|
is := is.New(t)
|
||||||
|
is.Equal(payload.value, "hello")
|
||||||
|
}
|
|
@ -3,14 +3,15 @@ package event
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sour-is/ev/internal/lg"
|
"go.sour.is/pkg/lg"
|
||||||
"github.com/sour-is/ev/pkg/locker"
|
"go.sour.is/pkg/locker"
|
||||||
)
|
)
|
||||||
|
|
||||||
type config struct {
|
type config struct {
|
||||||
|
@ -25,7 +26,7 @@ type UnknownEvent struct {
|
||||||
eventType string
|
eventType string
|
||||||
values map[string]json.RawMessage
|
values map[string]json.RawMessage
|
||||||
|
|
||||||
eventMeta Meta
|
IsEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Event = (*UnknownEvent)(nil)
|
var _ Event = (*UnknownEvent)(nil)
|
||||||
|
@ -48,16 +49,16 @@ func NewUnknownEventFromValues(eventType string, meta Meta, values url.Values) *
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &UnknownEvent{eventType: eventType, eventMeta: meta, values: jsonValues}
|
e := &UnknownEvent{eventType: eventType, values: jsonValues}
|
||||||
|
e.SetEventMeta(meta)
|
||||||
|
return e
|
||||||
}
|
}
|
||||||
func NewUnknownEventFromRaw(eventType string, meta Meta, values map[string]json.RawMessage) *UnknownEvent {
|
func NewUnknownEventFromRaw(eventType string, meta Meta, values map[string]json.RawMessage) *UnknownEvent {
|
||||||
return &UnknownEvent{eventType: eventType, eventMeta: meta, values: values}
|
e := &UnknownEvent{eventType: eventType, values: values}
|
||||||
|
e.SetEventMeta(meta)
|
||||||
|
return e
|
||||||
}
|
}
|
||||||
func (u UnknownEvent) EventMeta() Meta { return u.eventMeta }
|
|
||||||
func (u UnknownEvent) EventType() string { return u.eventType }
|
func (u UnknownEvent) EventType() string { return u.eventType }
|
||||||
func (u *UnknownEvent) SetEventMeta(em Meta) {
|
|
||||||
u.eventMeta = em
|
|
||||||
}
|
|
||||||
func (u *UnknownEvent) UnmarshalBinary(b []byte) error {
|
func (u *UnknownEvent) UnmarshalBinary(b []byte) error {
|
||||||
return json.Unmarshal(b, &u.values)
|
return json.Unmarshal(b, &u.values)
|
||||||
}
|
}
|
||||||
|
@ -67,7 +68,7 @@ func (u *UnknownEvent) MarshalBinary() ([]byte, error) {
|
||||||
|
|
||||||
// Register a type container for Unmarshalling values into. The type must implement Event and not be a nil value.
|
// Register a type container for Unmarshalling values into. The type must implement Event and not be a nil value.
|
||||||
func Register(ctx context.Context, lis ...Event) error {
|
func Register(ctx context.Context, lis ...Event) error {
|
||||||
_, span := lg.Span(ctx)
|
ctx, span := lg.Span(ctx)
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
for _, e := range lis {
|
for _, e := range lis {
|
||||||
|
@ -84,7 +85,7 @@ func Register(ctx context.Context, lis ...Event) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func RegisterName(ctx context.Context, name string, e Event) error {
|
func RegisterName(ctx context.Context, name string, e Event) error {
|
||||||
_, span := lg.Span(ctx)
|
ctx, span := lg.Span(ctx)
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
if e == nil {
|
if e == nil {
|
||||||
|
@ -106,7 +107,7 @@ func RegisterName(ctx context.Context, name string, e Event) error {
|
||||||
|
|
||||||
span.AddEvent("register: " + name)
|
span.AddEvent("register: " + name)
|
||||||
|
|
||||||
if err := eventTypes.Modify(ctx, func(c *config) error {
|
if err := eventTypes.Use(ctx, func(ctx context.Context, c *config) error {
|
||||||
_, span := lg.Span(ctx)
|
_, span := lg.Span(ctx)
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
|
@ -119,12 +120,12 @@ func RegisterName(ctx context.Context, name string, e Event) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func GetContainer(ctx context.Context, s string) Event {
|
func GetContainer(ctx context.Context, s string) Event {
|
||||||
_, span := lg.Span(ctx)
|
ctx, span := lg.Span(ctx)
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
var e Event
|
var e Event
|
||||||
|
|
||||||
eventTypes.Modify(ctx, func(c *config) error {
|
eventTypes.Use(ctx, func(ctx context.Context, c *config) error {
|
||||||
_, span := lg.Span(ctx)
|
_, span := lg.Span(ctx)
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
|
@ -151,7 +152,8 @@ func GetContainer(ctx context.Context, s string) Event {
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
func MarshalBinary(e Event) (txt []byte, err error) {
|
func MarshalBinary(e Event) ([]byte, error) {
|
||||||
|
var err error
|
||||||
b := &bytes.Buffer{}
|
b := &bytes.Buffer{}
|
||||||
|
|
||||||
m := e.EventMeta()
|
m := e.EventMeta()
|
||||||
|
@ -167,16 +169,28 @@ func MarshalBinary(e Event) (txt []byte, err error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
b.WriteRune('\t')
|
b.WriteRune('\t')
|
||||||
if txt, err = e.MarshalBinary(); err != nil {
|
switch e := e.(type) {
|
||||||
return nil, err
|
case encoding.BinaryMarshaler:
|
||||||
|
var txt []byte
|
||||||
|
if txt, err = e.MarshalBinary(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = b.Write(txt)
|
||||||
|
case encoding.TextMarshaler:
|
||||||
|
var txt []byte
|
||||||
|
if txt, err = e.MarshalText(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = b.Write(txt)
|
||||||
|
default:
|
||||||
|
err = json.NewEncoder(b).Encode(e)
|
||||||
}
|
}
|
||||||
_, err = b.Write(txt)
|
|
||||||
|
|
||||||
return b.Bytes(), err
|
return b.Bytes(), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func UnmarshalBinary(ctx context.Context, txt []byte, pos uint64) (e Event, err error) {
|
func UnmarshalBinary(ctx context.Context, txt []byte, pos uint64) (e Event, err error) {
|
||||||
_, span := lg.Span(ctx)
|
ctx, span := lg.Span(ctx)
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
sp := bytes.SplitN(txt, []byte{'\t'}, 4)
|
sp := bytes.SplitN(txt, []byte{'\t'}, 4)
|
||||||
|
@ -194,16 +208,29 @@ func UnmarshalBinary(ctx context.Context, txt []byte, pos uint64) (e Event, err
|
||||||
|
|
||||||
m.StreamID = string(sp[1])
|
m.StreamID = string(sp[1])
|
||||||
m.Position = pos
|
m.Position = pos
|
||||||
|
m.ActualStreamID = string(sp[1])
|
||||||
|
m.ActualPosition = pos
|
||||||
|
|
||||||
eventType := string(sp[2])
|
eventType := string(sp[2])
|
||||||
e = GetContainer(ctx, eventType)
|
e = GetContainer(ctx, eventType)
|
||||||
span.AddEvent(fmt.Sprintf("%s == %T", eventType, e))
|
span.AddEvent(fmt.Sprintf("%s == %T", eventType, e))
|
||||||
|
switch e := e.(type) {
|
||||||
if err = e.UnmarshalBinary(sp[3]); err != nil {
|
case encoding.BinaryUnmarshaler:
|
||||||
span.RecordError(err)
|
if err = e.UnmarshalBinary(sp[3]); err != nil {
|
||||||
return nil, err
|
span.RecordError(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case encoding.TextUnmarshaler:
|
||||||
|
if err = e.UnmarshalText(sp[3]); err != nil {
|
||||||
|
span.RecordError(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if err = json.Unmarshal(sp[3], e); err != nil {
|
||||||
|
span.RecordError(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
e.SetEventMeta(m)
|
e.SetEventMeta(m)
|
||||||
|
|
||||||
return e, nil
|
return e, nil
|
||||||
|
@ -261,11 +288,30 @@ func Values(e Event) map[string]any {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
omitempty := false
|
||||||
field := v.FieldByIndex(idx.Index)
|
field := v.FieldByIndex(idx.Index)
|
||||||
|
|
||||||
name := idx.Name
|
name := idx.Name
|
||||||
if n, ok := idx.Tag.Lookup("json"); ok {
|
if n, ok := idx.Tag.Lookup("json"); ok {
|
||||||
name = n
|
var (
|
||||||
|
opt string
|
||||||
|
found bool
|
||||||
|
)
|
||||||
|
|
||||||
|
name, opt, found = strings.Cut(n, ",")
|
||||||
|
if name == "-" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if found {
|
||||||
|
if strings.Contains(opt, "omitempty") {
|
||||||
|
omitempty = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if omitempty && field.IsZero() {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
m[name] = field.Interface()
|
m[name] = field.Interface()
|
105
go.mod
105
go.mod
|
@ -1,86 +1,53 @@
|
||||||
module github.com/sour-is/ev
|
module go.sour.is/ev
|
||||||
|
|
||||||
go 1.19
|
go 1.19
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/99designs/gqlgen v0.17.13
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2
|
|
||||||
github.com/gorilla/websocket v1.5.0
|
|
||||||
github.com/logzio/logzio-go v1.0.6
|
|
||||||
github.com/ravilushqa/otelgqlgen v0.9.0
|
|
||||||
github.com/rs/cors v1.8.2
|
|
||||||
github.com/tidwall/wal v1.1.7
|
github.com/tidwall/wal v1.1.7
|
||||||
github.com/vektah/gqlparser/v2 v2.4.7
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.44.0 // indirect
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.34.0
|
go.opentelemetry.io/contrib/instrumentation/runtime v0.42.0 // indirect
|
||||||
go.opentelemetry.io/contrib/instrumentation/runtime v0.34.0
|
go.opentelemetry.io/otel v1.19.0
|
||||||
go.opentelemetry.io/otel v1.9.0
|
go.opentelemetry.io/otel/exporters/prometheus v0.41.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/prometheus v0.31.0
|
go.opentelemetry.io/otel/metric v1.19.0
|
||||||
go.opentelemetry.io/otel/metric v0.31.0
|
go.opentelemetry.io/otel/sdk v1.18.0 // indirect
|
||||||
go.opentelemetry.io/otel/sdk v1.9.0
|
go.opentelemetry.io/otel/sdk/metric v0.41.0 // indirect
|
||||||
go.opentelemetry.io/otel/sdk/metric v0.31.0
|
go.opentelemetry.io/otel/trace v1.19.0
|
||||||
go.opentelemetry.io/otel/trace v1.9.0
|
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
require go.sour.is/pkg v0.0.5
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/ScaleFT/sshkeys v0.0.0-20200327173127-6142f742bca5 // indirect
|
|
||||||
github.com/agnivade/levenshtein v1.1.1 // indirect
|
|
||||||
github.com/beeker1121/goque v2.1.0+incompatible // indirect
|
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
|
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
|
||||||
github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a // indirect
|
|
||||||
github.com/dchest/blake2b v1.0.0 // indirect
|
|
||||||
github.com/felixge/httpsnoop v1.0.3 // indirect
|
github.com/felixge/httpsnoop v1.0.3 // indirect
|
||||||
github.com/go-logr/logr v1.2.3 // indirect
|
github.com/go-logr/logr v1.2.4 // indirect
|
||||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
github.com/golang/protobuf v1.5.2 // indirect
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect
|
||||||
github.com/golang/snappy v0.0.4 // indirect
|
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||||
github.com/google/go-cmp v0.5.8 // indirect
|
github.com/prometheus/client_golang v1.17.0 // indirect
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect
|
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect
|
||||||
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
github.com/prometheus/common v0.44.0 // indirect
|
||||||
github.com/keybase/saltpack v0.0.0-20200430135328-e19b1910c0c5 // indirect
|
github.com/prometheus/procfs v0.13.0 // indirect
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.18.0 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
go.opentelemetry.io/proto/otlp v1.0.0 // indirect
|
||||||
github.com/onsi/ginkgo v1.14.0 // indirect
|
golang.org/x/net v0.24.0 // indirect
|
||||||
github.com/onsi/gomega v1.10.3 // indirect
|
golang.org/x/sys v0.19.0 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
golang.org/x/text v0.14.0 // indirect
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 // indirect
|
||||||
github.com/prometheus/client_golang v1.12.2 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect
|
||||||
github.com/prometheus/client_model v0.2.0 // indirect
|
google.golang.org/grpc v1.58.3 // indirect
|
||||||
github.com/prometheus/common v0.32.1 // indirect
|
google.golang.org/protobuf v1.33.0 // indirect
|
||||||
github.com/prometheus/procfs v0.7.3 // indirect
|
|
||||||
github.com/shirou/gopsutil/v3 v3.22.3 // indirect
|
|
||||||
github.com/syndtr/goleveldb v1.0.0 // indirect
|
|
||||||
github.com/tyler-smith/go-bip39 v1.1.0 // indirect
|
|
||||||
github.com/vmihailenco/msgpack/v4 v4.3.12 // indirect
|
|
||||||
github.com/vmihailenco/tagparser v0.1.2 // indirect
|
|
||||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
|
||||||
go.opentelemetry.io/contrib v1.9.0 // indirect
|
|
||||||
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.9.0 // indirect
|
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.9.0 // indirect
|
|
||||||
go.opentelemetry.io/proto/otlp v0.18.0 // indirect
|
|
||||||
go.uber.org/atomic v1.9.0 // indirect
|
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect
|
|
||||||
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f // indirect
|
|
||||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 // indirect
|
|
||||||
golang.org/x/text v0.3.7 // indirect
|
|
||||||
google.golang.org/appengine v1.6.7 // indirect
|
|
||||||
google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1 // indirect
|
|
||||||
google.golang.org/grpc v1.46.2 // indirect
|
|
||||||
google.golang.org/protobuf v1.28.0 // indirect
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/keys-pub/keys v0.1.22
|
github.com/matryer/is v1.4.1
|
||||||
github.com/matryer/is v1.4.0
|
|
||||||
github.com/oklog/ulid/v2 v2.1.0
|
github.com/oklog/ulid/v2 v2.1.0
|
||||||
github.com/tidwall/gjson v1.10.2 // indirect
|
github.com/tidwall/gjson v1.14.4 // indirect
|
||||||
github.com/tidwall/match v1.1.1 // indirect
|
github.com/tidwall/match v1.1.1 // indirect
|
||||||
github.com/tidwall/pretty v1.2.0 // indirect
|
github.com/tidwall/pretty v1.2.1 // indirect
|
||||||
github.com/tidwall/tinylru v1.1.0 // indirect
|
github.com/tidwall/tinylru v1.1.0 // indirect
|
||||||
gitlab.com/jamietanna/content-negotiation-go v0.2.0
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.18.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.9.0
|
go.uber.org/multierr v1.11.0
|
||||||
go.uber.org/multierr v1.8.0
|
|
||||||
)
|
)
|
||||||
|
|
796
go.sum
796
go.sum
|
@ -1,731 +1,121 @@
|
||||||
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/99designs/gqlgen v0.17.13 h1:ETUEqvRg5Zvr1lXtpoRdj026fzVay0ZlJPwI33qXLIw=
|
|
||||||
github.com/99designs/gqlgen v0.17.13/go.mod h1:w1brbeOdqVyNJI553BGwtwdVcYu1LKeYE1opLWN9RgQ=
|
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
|
||||||
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
|
||||||
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/ScaleFT/sshkeys v0.0.0-20200327173127-6142f742bca5 h1:VauE2GcJNZFun2Och6tIT2zJZK1v6jxALQDA9BIji/E=
|
|
||||||
github.com/ScaleFT/sshkeys v0.0.0-20200327173127-6142f742bca5/go.mod h1:gxOHeajFfvGQh/fxlC8oOKBe23xnnJTif00IFFbiT+o=
|
|
||||||
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
|
|
||||||
github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8=
|
|
||||||
github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo=
|
|
||||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
|
||||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
|
||||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
|
||||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
|
||||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
|
||||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
|
|
||||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
|
||||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
|
||||||
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
|
|
||||||
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
|
|
||||||
github.com/beeker1121/goque v2.1.0+incompatible h1:m5pZ5b8nqzojS2DF2ioZphFYQUqGYsDORq6uefUItPM=
|
|
||||||
github.com/beeker1121/goque v2.1.0+incompatible/go.mod h1:L6dOWBhDOnxUVQsb0wkLve0VCnt2xJW/MI8pdRX4ANw=
|
|
||||||
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
|
|
||||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
|
||||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
|
||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
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/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4=
|
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
|
||||||
github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
|
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/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
|
||||||
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
|
||||||
github.com/cespare/xxhash/v2 v2.1.2/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-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
|
||||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
|
||||||
github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a h1:saTgr5tMLFnmy/yg3qDTft4rE5DY2uJ/cCxCe3q0XTU=
|
|
||||||
github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a/go.mod h1:Bw9BbhOJVNR+t0jCqx2GC6zv0TGBsShs56Y3gfSCvl0=
|
|
||||||
github.com/dchest/blake2b v1.0.0 h1:KK9LimVmE0MjRl9095XJmKqZ+iLxWATvlcpVFRtaw6s=
|
|
||||||
github.com/dchest/blake2b v1.0.0/go.mod h1:U034kXgbJpCle2wSk5ybGIVhOSHCVLMDqOzcPEA0F7s=
|
|
||||||
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g=
|
|
||||||
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
|
|
||||||
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/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
|
|
||||||
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 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
|
||||||
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
github.com/flynn/noise v1.0.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag=
|
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
|
||||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
|
||||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
|
||||||
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-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
|
||||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
|
||||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
|
||||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
|
||||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
|
||||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
|
||||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
|
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||||
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
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 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE=
|
||||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
|
||||||
github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
|
||||||
github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ=
|
|
||||||
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.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.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
|
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
|
||||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
|
||||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
|
||||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
|
||||||
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.4/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.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/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=
|
||||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
|
||||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
||||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
||||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||||
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/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
|
||||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
|
||||||
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/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
|
|
||||||
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
|
||||||
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.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
|
||||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
|
||||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
|
||||||
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.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
|
||||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
|
||||||
github.com/kevinmbeaulieu/eq-go v1.0.0/go.mod h1:G3S8ajA56gKBZm4UB9AOyoOS37JO3roToPzKNM8dtdM=
|
|
||||||
github.com/keybase/go-codec v0.0.0-20180928230036-164397562123/go.mod h1:r/eVVWCngg6TsFV/3HuS9sWhDkAzGG8mXhiuYA+Z/20=
|
|
||||||
github.com/keybase/go-keychain v0.0.0-20201121013009-976c83ec27a6/go.mod h1:N83iQ9rnnzi2KZuTu+0xBcD1JNWn1jSN140ggAF7HeE=
|
|
||||||
github.com/keybase/go.dbus v0.0.0-20200324223359-a94be52c0b03/go.mod h1:a8clEhrrGV/d76/f9r2I41BwANMihfZYV9C223vaxqE=
|
|
||||||
github.com/keybase/saltpack v0.0.0-20200430135328-e19b1910c0c5 h1:X6nYzCVURqxDv0GuyptaCcRFTXPM0rSGNUrTeQ2NKUQ=
|
|
||||||
github.com/keybase/saltpack v0.0.0-20200430135328-e19b1910c0c5/go.mod h1:FNSq71OhXv/Z1W9M37nnHxJVhXitc03z6qshCbAten8=
|
|
||||||
github.com/keys-pub/keys v0.1.22 h1:bO0nx7c3HuC8dqjmjZ8njC8DzpuKWnOZQ5njaO5+A+o=
|
|
||||||
github.com/keys-pub/keys v0.1.22/go.mod h1:+41yREqLkYyGfGf4OkhUn/ljwe/+kwhrlTq1/46Jj8c=
|
|
||||||
github.com/keys-pub/secretservice v0.0.0-20200519003656-26e44b8df47f/go.mod h1:YRHMiVbZqh7u8xRm77CvwJNAZdDlNXwWvQ4DK0N9mYg=
|
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
|
||||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
|
||||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
|
||||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
|
||||||
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/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
|
||||||
github.com/logrusorgru/aurora/v3 v3.0.0/go.mod h1:vsR12bk5grlLvLXAYrBsb5Oc/N+LxAlxggSjiwMnCUc=
|
|
||||||
github.com/logzio/logzio-go v1.0.6 h1:BIVu5TWDZc0vlEkwSDjoxPlV/aMJV2LdM3k+CjdzFDg=
|
|
||||||
github.com/logzio/logzio-go v1.0.6/go.mod h1:ljlI3Zfi3hntJiHqCqWSUPT9cZP6yvDHUzDl5ZLGYRE=
|
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
|
||||||
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
|
|
||||||
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
|
||||||
github.com/matryer/moq v0.2.7/go.mod h1:kITsx543GOENm48TUAQyJ9+SAvFSr7iGQXPoth/VUBk=
|
|
||||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
|
||||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI=
|
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
|
||||||
github.com/mitchellh/mapstructure v1.3.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
|
||||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
|
||||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
|
||||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
|
||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
|
||||||
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
|
|
||||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
|
||||||
github.com/oklog/ulid/v2 v2.1.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU=
|
github.com/oklog/ulid/v2 v2.1.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU=
|
||||||
github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ=
|
github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ=
|
||||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
|
||||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
|
||||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
|
||||||
github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA=
|
|
||||||
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
|
||||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
|
||||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
|
||||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
|
||||||
github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA=
|
|
||||||
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
|
|
||||||
github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
|
github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q=
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY=
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 h1:v7DLqVdK4VrYkVD5diGdl4sxJurKJEMnODWRJlxV9oM=
|
||||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
|
||||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY=
|
||||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY=
|
||||||
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
|
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
|
||||||
github.com/prometheus/client_golang v1.12.2 h1:51L9cDoUHVrXx4zWYlcLQIZ+d+VXHgqnYKkIuq4g/34=
|
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
||||||
github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
|
github.com/prometheus/procfs v0.13.0 h1:GqzLlQyfsPbaEHaQkO7tbDlriv/4o5Hudv6OXHGKX7o=
|
||||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
github.com/prometheus/procfs v0.13.0/go.mod h1:cd4PFCR54QLnGKPaKGA6l+cfuNXtht43ZKY6tow0Y1g=
|
||||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
|
||||||
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
|
|
||||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
|
||||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
|
||||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
|
||||||
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
|
|
||||||
github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4=
|
|
||||||
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
|
|
||||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
|
||||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
|
||||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
|
||||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
|
||||||
github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
|
|
||||||
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
|
||||||
github.com/ravilushqa/otelgqlgen v0.9.0 h1:NgbrU9OHPKB/X6Ja618+SqPNls05M3T8aV1fkUiR9ow=
|
|
||||||
github.com/ravilushqa/otelgqlgen v0.9.0/go.mod h1:TqSvbt/7E23CHOOgL6G+42kCbhvxUpT/21tMsarq4Hk=
|
|
||||||
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/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U=
|
|
||||||
github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
|
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
|
||||||
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
|
||||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
|
||||||
github.com/shirou/gopsutil/v3 v3.22.3 h1:UebRzEomgMpv61e3hgD1tGooqX5trFbdU/ehphbHd00=
|
|
||||||
github.com/shirou/gopsutil/v3 v3.22.3/go.mod h1:D01hZJ4pVHPpCTZ3m3T2+wDF2YAGfd+H4ifUguaQzHM=
|
|
||||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
|
||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
|
||||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
|
||||||
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/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
||||||
github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
|
||||||
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/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
|
||||||
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
|
|
||||||
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
|
|
||||||
github.com/tidwall/gjson v1.10.2 h1:APbLGOM0rrEkd8WBw9C24nllro4ajFuJu0Sc9hRz8Bo=
|
|
||||||
github.com/tidwall/gjson v1.10.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
github.com/tidwall/gjson v1.10.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
|
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
|
||||||
|
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||||
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
|
||||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
|
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||||
|
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
github.com/tidwall/tinylru v1.1.0 h1:XY6IUfzVTU9rpwdhKUF6nQdChgCdGjkMfLzbWyiau6I=
|
github.com/tidwall/tinylru v1.1.0 h1:XY6IUfzVTU9rpwdhKUF6nQdChgCdGjkMfLzbWyiau6I=
|
||||||
github.com/tidwall/tinylru v1.1.0/go.mod h1:3+bX+TJ2baOLMWTnlyNWHh4QMnFyARg2TLTQ6OFbzw8=
|
github.com/tidwall/tinylru v1.1.0/go.mod h1:3+bX+TJ2baOLMWTnlyNWHh4QMnFyARg2TLTQ6OFbzw8=
|
||||||
github.com/tidwall/wal v1.1.7 h1:emc1TRjIVsdKKSnpwGBAcsAGg0767SvUk8+ygx7Bb+4=
|
github.com/tidwall/wal v1.1.7 h1:emc1TRjIVsdKKSnpwGBAcsAGg0767SvUk8+ygx7Bb+4=
|
||||||
github.com/tidwall/wal v1.1.7/go.mod h1:r6lR1j27W9EPalgHiB7zLJDYu3mzW5BQP5KrzBpYY/E=
|
github.com/tidwall/wal v1.1.7/go.mod h1:r6lR1j27W9EPalgHiB7zLJDYu3mzW5BQP5KrzBpYY/E=
|
||||||
github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.42.0 h1:pginetY7+onl4qN1vl0xW/V/v6OBZ0vVdH+esuJgvmM=
|
||||||
github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.42.0/go.mod h1:XiYsayHc36K3EByOO6nbAXnAWbrUxdjUROCEeeROOH8=
|
||||||
github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.44.0 h1:KfYpVmrjI7JuToy5k8XV3nkapjWx48k4E4JOtVstzQI=
|
||||||
github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.44.0/go.mod h1:SeQhzAEccGVZVEy7aH87Nh0km+utSpo1pTv6eMMop48=
|
||||||
github.com/urfave/cli/v2 v2.8.1/go.mod h1:Z41J9TPoffeoqP0Iza0YbAhGvymRdZAd2uPmZ5JxRdY=
|
go.opentelemetry.io/contrib/instrumentation/runtime v0.42.0 h1:EbmAUG9hEAMXyfWEasIt2kmh/WmXUznUksChApTgBGc=
|
||||||
github.com/vektah/gqlparser/v2 v2.4.6/go.mod h1:flJWIR04IMQPGz+BXLrORkrARBxv/rtyIAFvd/MceW0=
|
go.opentelemetry.io/contrib/instrumentation/runtime v0.42.0/go.mod h1:rD9feqRYP24P14t5kmhNMqsqm1jvKmpx2H2rKVw52V8=
|
||||||
github.com/vektah/gqlparser/v2 v2.4.7 h1:yub2WLoSIr+chP1zMv6bjrsgTasfubxGZJeC8ISEpgE=
|
go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs=
|
||||||
github.com/vektah/gqlparser/v2 v2.4.7/go.mod h1:flJWIR04IMQPGz+BXLrORkrARBxv/rtyIAFvd/MceW0=
|
go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY=
|
||||||
github.com/vmihailenco/msgpack/v4 v4.3.12 h1:07s4sz9IReOgdikxLTKNbBdqDMLsjPKXwvCazn8G65U=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.18.0 h1:IAtl+7gua134xcV3NieDhJHjjOVeJhXAnYf/0hswjUY=
|
||||||
github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.18.0/go.mod h1:w+pXobnBzh95MNIkeIuAKcHe/Uu/CX2PKIvBP6ipKRA=
|
||||||
github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.18.0 h1:6pu8ttx76BxHf+xz/H77AUZkPF3cwWzXqAUsXhVKI18=
|
||||||
github.com/vmihailenco/tagparser v0.1.2 h1:gnjoVuB/kljJ5wICEEOpx98oXMWPLj22G67Vbd1qPqc=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.18.0/go.mod h1:IOmXxPrxoxFMXdNy7lfDmE8MzE61YPcurbUm0SMjerI=
|
||||||
github.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
|
go.opentelemetry.io/otel/exporters/prometheus v0.41.0 h1:A3/bhjP5SmELy8dcpK+uttHeh9Qrh+YnS16/VzrztRQ=
|
||||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
go.opentelemetry.io/otel/exporters/prometheus v0.41.0/go.mod h1:mKuXEMi9suyyNJQ99SZCO0mpWGFe0MIALtjd3r6uo7Q=
|
||||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE=
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8=
|
||||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
go.opentelemetry.io/otel/sdk v1.18.0 h1:e3bAB0wB3MljH38sHzpV/qWrOTCFrdZF2ct9F8rBkcY=
|
||||||
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
go.opentelemetry.io/otel/sdk v1.18.0/go.mod h1:1RCygWV7plY2KmdskZEDDBs4tJeHG92MdHZIluiYs/M=
|
||||||
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
|
go.opentelemetry.io/otel/sdk/metric v0.41.0 h1:c3sAt9/pQ5fSIUfl0gPtClV3HhE18DCVzByD33R/zsk=
|
||||||
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
go.opentelemetry.io/otel/sdk/metric v0.41.0/go.mod h1:PmOmSt+iOklKtIg5O4Vz9H/ttcRFSNTgii+E1KGyn1w=
|
||||||
gitlab.com/jamietanna/content-negotiation-go v0.2.0 h1:vT0OLEPQ6DYRG3/1F7joXSNjVQHGivJ6+JzODlJfjWw=
|
go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg=
|
||||||
gitlab.com/jamietanna/content-negotiation-go v0.2.0/go.mod h1:n4ZZ8/X5TstnjYRnjEtR/fC7MCTe+aRKM7PQlLBH3PQ=
|
go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo=
|
||||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
|
||||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
|
||||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.sour.is/pkg v0.0.5 h1:ngRYyFl+Ks1S63hnAxcIpaKdoqXGQIKvQnz4QIrYm94=
|
||||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.sour.is/pkg v0.0.5/go.mod h1:kb8ERLsUC3DqHNwm7gra2tE6x+2C9g+zxzVEC36R884=
|
||||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
go.opentelemetry.io/contrib v1.9.0 h1:2KAoCVu4OMI9TYoSWvcV7+UbbIPOi4623S77nV+M/Ks=
|
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
go.opentelemetry.io/contrib v1.9.0/go.mod h1:yp0N4+hnpWCpnMzs6T6WbD9Amfg7reEZsS0jAd/5M2Q=
|
golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.34.0 h1:9NkMW03wwEzPtP/KciZ4Ozu/Uz5ZA7kfqXJIObnrjGU=
|
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.34.0/go.mod h1:548ZsYzmT4PL4zWKRd8q/N4z0Wxzn/ZxUE+lkEpwWQA=
|
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||||
go.opentelemetry.io/contrib/instrumentation/runtime v0.34.0 h1:zt4RDodWkgiHk8tyUmFOjFoOOfyGH7vwIbUzKP6CCh8=
|
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||||
go.opentelemetry.io/contrib/instrumentation/runtime v0.34.0/go.mod h1:5wIoZE96WbcQVU3D6UF/ukRfFQXbB6OYgeWi9CjHa90=
|
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
|
||||||
go.opentelemetry.io/otel v1.9.0 h1:8WZNQFIB2a71LnANS9JeyidJKKGOOremcUtb/OtHISw=
|
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||||
go.opentelemetry.io/otel v1.9.0/go.mod h1:np4EoPGzoPs3O67xUVNoPPcmSvsfOxNlNA4F4AC+0Eo=
|
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.9.0 h1:ggqApEjDKczicksfvZUCxuvoyDmR6Sbm56LwiK8DVR0=
|
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.9.0/go.mod h1:78XhIg8Ht9vR4tbLNUhXsiOnE2HOuSeKAiAcoVQEpOY=
|
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.9.0 h1:NN90Cuna0CnBg8YNu1Q0V35i2E8LDByFOwHRCq/ZP9I=
|
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.9.0/go.mod h1:0EsCXjZAiiZGnLdEUXM9YjCKuuLZMYyglh2QDXcYKVA=
|
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.9.0 h1:FAF9l8Wjxi9Ad2k/vLTfHZyzXYX72C62wBGpV3G6AIo=
|
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.9.0/go.mod h1:smUdtylgc0YQiUr2PuifS4hBXhAS5xtR6WQhxP1wiNA=
|
|
||||||
go.opentelemetry.io/otel/exporters/prometheus v0.31.0 h1:jwtnOGBM8dIty5AVZ+9ZCzZexCea3aVKmUfZAQcHqxs=
|
|
||||||
go.opentelemetry.io/otel/exporters/prometheus v0.31.0/go.mod h1:QarXIB8L79IwIPoNgG3A6zNvBgVmcppeFogV1d8612s=
|
|
||||||
go.opentelemetry.io/otel/metric v0.31.0 h1:6SiklT+gfWAwWUR0meEMxQBtihpiEs4c+vL9spDTqUs=
|
|
||||||
go.opentelemetry.io/otel/metric v0.31.0/go.mod h1:ohmwj9KTSIeBnDBm/ZwH2PSZxZzoOaG2xZeekTRzL5A=
|
|
||||||
go.opentelemetry.io/otel/sdk v1.9.0 h1:LNXp1vrr83fNXTHgU8eO89mhzxb/bbWAsHG6fNf3qWo=
|
|
||||||
go.opentelemetry.io/otel/sdk v1.9.0/go.mod h1:AEZc8nt5bd2F7BC24J5R0mrjYnpEgYHyTcM/vrSple4=
|
|
||||||
go.opentelemetry.io/otel/sdk/metric v0.31.0 h1:2sZx4R43ZMhJdteKAlKoHvRgrMp53V1aRxvEf5lCq8Q=
|
|
||||||
go.opentelemetry.io/otel/sdk/metric v0.31.0/go.mod h1:fl0SmNnX9mN9xgU6OLYLMBMrNAsaZQi7qBwprwO3abk=
|
|
||||||
go.opentelemetry.io/otel/trace v1.9.0 h1:oZaCNJUjWcg60VXWee8lJKlqhPbXAPB51URuR47pQYc=
|
|
||||||
go.opentelemetry.io/otel/trace v1.9.0/go.mod h1:2737Q0MuG8q1uILYm2YYVkAyLtOofiTNGg6VODnOiPo=
|
|
||||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
|
||||||
go.opentelemetry.io/proto/otlp v0.18.0 h1:W5hyXNComRa23tGpKwG+FRAc4rfF6ZUg1JReK+QHS80=
|
|
||||||
go.opentelemetry.io/proto/otlp v0.18.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=
|
|
||||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
|
||||||
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
|
||||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
|
||||||
go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
|
|
||||||
go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
|
|
||||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
|
||||||
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-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
|
||||||
golang.org/x/crypto v0.0.0-20200427165652-729f1e841bcc/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
|
||||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
|
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
|
||||||
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/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
|
||||||
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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/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-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
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-20200520004742-59133d7f0dd7/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-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
|
||||||
golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
|
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
|
||||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
|
||||||
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f h1:OfiFi4JbukWwe3lzw+xunroH1mnC1e2Gy5cxNJApiSY=
|
|
||||||
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
|
||||||
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-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
|
||||||
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-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/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
|
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
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-20190422165155-953cdadca894/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-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/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-20200106162015-b016eb3dc98e/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-20200219091948-cb0a6d8edb6c/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-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs=
|
|
||||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
|
||||||
golang.org/x/term v0.0.0-20210317153231-de623e64d2a6 h1:EC6+IGYTjPpRfv9a2b/6Puw0W+hLtAhkV1tPsXhutqs=
|
|
||||||
golang.org/x/term v0.0.0-20210317153231-de623e64d2a6/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.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
|
||||||
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/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
|
||||||
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
|
||||||
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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw=
|
||||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ=
|
||||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U=
|
||||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM=
|
||||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
google.golang.org/grpc v1.58.0 h1:32JY8YpPMSR45K+c3o6b8VL73V+rR8k+DeMIr4vRH8o=
|
||||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
google.golang.org/grpc v1.58.0/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0=
|
||||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ=
|
||||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0=
|
||||||
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/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
|
||||||
google.golang.org/appengine v1.6.7/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 h1:b9mVrqYfq3P4bCdaLg1qtBnPzUYgglsIdjZkL/fQVOE=
|
|
||||||
google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
|
||||||
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.46.2 h1:u+MLGgVf7vRdjEYZ8wDFhAVNmhkbJ5hmrA1LMWK1CAQ=
|
|
||||||
google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
|
||||||
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-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.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||||
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
|
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||||
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/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
|
||||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
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.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
|
||||||
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=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
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=
|
|
||||||
|
|
|
@ -1,24 +1,34 @@
|
||||||
|
|
||||||
type Meta @goModel(model: "github.com/sour-is/ev/pkg/es/event.Meta") {
|
type Meta @goModel(model: "go.sour.is/ev/pkg/event.Meta") {
|
||||||
eventID: String! @goField(name: "getEventID")
|
eventID: String! @goField(name: "getEventID")
|
||||||
streamID: String!
|
streamID: String! @goField(name: "ActualStreamID")
|
||||||
|
position: Int! @goField(name: "ActualPosition")
|
||||||
created: Time!
|
created: Time!
|
||||||
position: Int!
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extend type Query {
|
extend type Query {
|
||||||
events(streamID: String! paging: PageInput): Connection!
|
events(streamID: String! paging: PageInput): Connection!
|
||||||
}
|
}
|
||||||
|
extend type Mutation {
|
||||||
|
truncateStream(streamID: String! index:Int!): Boolean!
|
||||||
|
}
|
||||||
extend type Subscription {
|
extend type Subscription {
|
||||||
"""after == 0 start from begining, after == -1 start from end"""
|
"""after == 0 start from begining, after == -1 start from end"""
|
||||||
eventAdded(streamID: String! after: Int! = -1): Event
|
eventAdded(streamID: String! after: Int! = -1): Event
|
||||||
}
|
}
|
||||||
|
|
||||||
type Event implements Edge @goModel(model: "github.com/sour-is/ev/pkg/es.GQLEvent") {
|
type Event implements Edge @goModel(model: "go.sour.is/ev/pkg/gql.Event") {
|
||||||
id: ID!
|
id: ID!
|
||||||
|
|
||||||
eventID: String!
|
eventID: String!
|
||||||
|
streamID: String!
|
||||||
|
position: Int!
|
||||||
|
|
||||||
values: Map!
|
values: Map!
|
||||||
bytes: String!
|
bytes: String!
|
||||||
|
type: String!
|
||||||
|
created: Time!
|
||||||
meta: Meta!
|
meta: Meta!
|
||||||
|
|
||||||
|
linked: Event
|
||||||
}
|
}
|
203
gql/graph.go
Normal file
203
gql/graph.go
Normal file
|
@ -0,0 +1,203 @@
|
||||||
|
package gql_ev
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.sour.is/pkg/gql"
|
||||||
|
"go.sour.is/pkg/lg"
|
||||||
|
|
||||||
|
"go.sour.is/ev"
|
||||||
|
"go.sour.is/ev/event"
|
||||||
|
)
|
||||||
|
|
||||||
|
type EventResolver interface {
|
||||||
|
Events(ctx context.Context, streamID string, paging *gql.PageInput) (*gql.Connection, error)
|
||||||
|
EventAdded(ctx context.Context, streamID string, after int64) (<-chan *Event, error)
|
||||||
|
TruncateStream(ctx context.Context, streamID string, index int64) (bool, error)
|
||||||
|
}
|
||||||
|
type contextKey struct {
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
var esKey = contextKey{"event-store"}
|
||||||
|
|
||||||
|
type EventStore struct {
|
||||||
|
*ev.EventStore
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *EventStore) IsResolver() {}
|
||||||
|
func (es *EventStore) Events(ctx context.Context, streamID string, paging *gql.PageInput) (*gql.Connection, error) {
|
||||||
|
ctx, span := lg.Span(ctx)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
lis, err := es.Read(ctx, streamID, paging.GetIdx(0), paging.GetCount(30))
|
||||||
|
if err != nil && !errors.Is(err, ev.ErrNotFound) {
|
||||||
|
span.RecordError(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
edges := make([]gql.Edge, 0, len(lis))
|
||||||
|
for i := range lis {
|
||||||
|
span.AddEvent(fmt.Sprint("event ", i, " of ", len(lis)))
|
||||||
|
edges = append(edges, &Event{lis[i]})
|
||||||
|
}
|
||||||
|
|
||||||
|
var first, last uint64
|
||||||
|
if first, err = es.FirstIndex(ctx, streamID); err != nil {
|
||||||
|
span.RecordError(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if last, err = es.LastIndex(ctx, streamID); err != nil {
|
||||||
|
span.RecordError(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &gql.Connection{
|
||||||
|
Paging: &gql.PageInfo{
|
||||||
|
Next: lis.Last().EventMeta().ActualPosition < last,
|
||||||
|
Prev: lis.First().EventMeta().ActualPosition > first,
|
||||||
|
Begin: lis.First().EventMeta().ActualPosition,
|
||||||
|
End: lis.Last().EventMeta().ActualPosition,
|
||||||
|
},
|
||||||
|
Edges: edges,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
func (e *EventStore) EventAdded(ctx context.Context, streamID string, after int64) (<-chan *Event, error) {
|
||||||
|
ctx, span := lg.Span(ctx)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
es := e.EventStream()
|
||||||
|
if es == nil {
|
||||||
|
return nil, fmt.Errorf("EventStore does not implement streaming")
|
||||||
|
}
|
||||||
|
|
||||||
|
sub, err := es.Subscribe(ctx, streamID, after)
|
||||||
|
if err != nil {
|
||||||
|
span.RecordError(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ch := make(chan *Event)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
ctx, span := lg.Span(ctx)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
{
|
||||||
|
ctx, span := lg.Fork(ctx)
|
||||||
|
defer func() {
|
||||||
|
defer span.End()
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
err := sub.Close(ctx)
|
||||||
|
span.RecordError(err)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
for <-sub.Recv(ctx) {
|
||||||
|
events, err := sub.Events(ctx)
|
||||||
|
if err != nil {
|
||||||
|
span.RecordError(err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
span.AddEvent(fmt.Sprintf("received %d events", len(events)))
|
||||||
|
|
||||||
|
for i := range events {
|
||||||
|
select {
|
||||||
|
case ch <- &Event{events[i]}:
|
||||||
|
continue
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return ch, nil
|
||||||
|
}
|
||||||
|
func (es *EventStore) TruncateStream(ctx context.Context, streamID string, index int64) (bool, error) {
|
||||||
|
ctx, span := lg.Span(ctx)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
err := es.Truncate(ctx, streamID, index)
|
||||||
|
return err == nil, err
|
||||||
|
}
|
||||||
|
func (e *EventStore) GetMiddleware() func(http.Handler) http.Handler {
|
||||||
|
return func(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx, span := lg.Span(r.Context())
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
r = r.WithContext(gql.ToContext(ctx, esKey, e))
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Event struct {
|
||||||
|
e event.Event
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Event) ID() string {
|
||||||
|
return fmt.Sprint(e.e.EventMeta().StreamID, "@", e.e.EventMeta().Position)
|
||||||
|
}
|
||||||
|
func (e *Event) EventID() string {
|
||||||
|
return e.e.EventMeta().GetEventID()
|
||||||
|
}
|
||||||
|
func (e *Event) StreamID() string {
|
||||||
|
return e.e.EventMeta().StreamID
|
||||||
|
}
|
||||||
|
func (e *Event) Position() uint64 {
|
||||||
|
return e.e.EventMeta().Position
|
||||||
|
}
|
||||||
|
func (e *Event) Type() string {
|
||||||
|
return event.TypeOf(e.e)
|
||||||
|
}
|
||||||
|
func (e *Event) Created() time.Time {
|
||||||
|
return e.e.EventMeta().Created()
|
||||||
|
}
|
||||||
|
func (e *Event) Values() map[string]interface{} {
|
||||||
|
return event.Values(e.e)
|
||||||
|
}
|
||||||
|
func (e *Event) Bytes() (string, error) {
|
||||||
|
switch e := e.e.(type) {
|
||||||
|
case encoding.BinaryMarshaler:
|
||||||
|
b, err := e.MarshalBinary()
|
||||||
|
return string(b), err
|
||||||
|
case encoding.TextMarshaler:
|
||||||
|
b, err := e.MarshalText()
|
||||||
|
return string(b), err
|
||||||
|
default:
|
||||||
|
b, err := json.Marshal(e)
|
||||||
|
return string(b), err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (e *Event) Meta() *event.Meta {
|
||||||
|
meta := e.e.EventMeta()
|
||||||
|
return &meta
|
||||||
|
}
|
||||||
|
func (e *Event) Linked(ctx context.Context) (*Event, error) {
|
||||||
|
ctx, span := lg.Span(ctx)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
values := event.Values(e.e)
|
||||||
|
streamID, ok := values["stream_id"].(string)
|
||||||
|
if !ok {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
pos, ok := values["pos"].(uint64)
|
||||||
|
if !ok {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
events, err := gql.FromContext[contextKey, *EventStore](ctx, esKey).ReadN(ctx, streamID, pos)
|
||||||
|
return &Event{e: events.First()}, err
|
||||||
|
}
|
||||||
|
func (e *Event) IsEdge() {}
|
64
gqlgen.yml
64
gqlgen.yml
|
@ -1,64 +0,0 @@
|
||||||
# Where are all the schema files located? globs are supported eg src/**/*.graphqls
|
|
||||||
schema:
|
|
||||||
- pkg/*/*.graphqls
|
|
||||||
- app/*/*.graphqls
|
|
||||||
|
|
||||||
# Where should the generated server code go?
|
|
||||||
exec:
|
|
||||||
filename: internal/graph/generated/generated.go
|
|
||||||
package: generated
|
|
||||||
|
|
||||||
# Uncomment to enable federation
|
|
||||||
federation:
|
|
||||||
filename: internal/graph/generated/federation.go
|
|
||||||
package: generated
|
|
||||||
|
|
||||||
# Where should any generated models go?
|
|
||||||
model:
|
|
||||||
filename: internal/graph/model/models_gen.go
|
|
||||||
package: model
|
|
||||||
|
|
||||||
# Where should the resolver implementations go?
|
|
||||||
# resolver:
|
|
||||||
# layout: follow-schema
|
|
||||||
# dir: internal/graph
|
|
||||||
# package: graph
|
|
||||||
|
|
||||||
# Optional: turn on use `gqlgen:"fieldName"` tags in your models
|
|
||||||
# struct_tag: json
|
|
||||||
|
|
||||||
# Optional: turn on to use []Thing instead of []*Thing
|
|
||||||
# omit_slice_element_pointers: false
|
|
||||||
|
|
||||||
# Optional: set to speed up generation time by not performing a final validation pass.
|
|
||||||
# skip_validation: true
|
|
||||||
|
|
||||||
# gqlgen will search for any type names in the schema in these go packages
|
|
||||||
# if they match it will use them, otherwise it will generate them.
|
|
||||||
# autobind:
|
|
||||||
# - "github.com/sour-is/ev/pkg/gql"
|
|
||||||
|
|
||||||
# This section declares type mapping between the GraphQL and go type systems
|
|
||||||
#
|
|
||||||
# The first line in each type will be used as defaults for resolver arguments and
|
|
||||||
# modelgen, the others will be allowed when binding to fields. Configure them to
|
|
||||||
# your liking
|
|
||||||
models:
|
|
||||||
ID:
|
|
||||||
model:
|
|
||||||
- github.com/99designs/gqlgen/graphql.ID
|
|
||||||
- github.com/99designs/gqlgen/graphql.Int
|
|
||||||
- github.com/99designs/gqlgen/graphql.Int64
|
|
||||||
- github.com/99designs/gqlgen/graphql.Int32
|
|
||||||
- github.com/99designs/gqlgen/graphql.Uint
|
|
||||||
- github.com/99designs/gqlgen/graphql.Uint64
|
|
||||||
- github.com/99designs/gqlgen/graphql.Uint32
|
|
||||||
Int:
|
|
||||||
model:
|
|
||||||
- github.com/99designs/gqlgen/graphql.Int64
|
|
||||||
- github.com/99designs/gqlgen/graphql.Int32
|
|
||||||
- github.com/99designs/gqlgen/graphql.Int
|
|
||||||
- github.com/99designs/gqlgen/graphql.Uint64
|
|
||||||
- github.com/99designs/gqlgen/graphql.Uint32
|
|
||||||
- github.com/99designs/gqlgen/graphql.Uint
|
|
||||||
|
|
36
httpmux.go
36
httpmux.go
|
@ -1,36 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/rs/cors"
|
|
||||||
)
|
|
||||||
|
|
||||||
type mux struct {
|
|
||||||
*http.ServeMux
|
|
||||||
api *http.ServeMux
|
|
||||||
}
|
|
||||||
|
|
||||||
func httpMux(fns ...interface{ RegisterHTTP(*http.ServeMux) }) http.Handler {
|
|
||||||
mux := newMux()
|
|
||||||
for _, fn := range fns {
|
|
||||||
fn.RegisterHTTP(mux.ServeMux)
|
|
||||||
|
|
||||||
if fn, ok := fn.(interface{ RegisterAPIv1(*http.ServeMux) }); ok {
|
|
||||||
log.Printf("register api %T", fn)
|
|
||||||
fn.RegisterAPIv1(mux.api)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return cors.AllowAll().Handler(mux)
|
|
||||||
}
|
|
||||||
func newMux() *mux {
|
|
||||||
mux := &mux{
|
|
||||||
api: http.NewServeMux(),
|
|
||||||
ServeMux: http.NewServeMux(),
|
|
||||||
}
|
|
||||||
mux.Handle("/api/v1/", http.StripPrefix("/api/v1", mux.api))
|
|
||||||
|
|
||||||
return mux
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/matryer/is"
|
|
||||||
)
|
|
||||||
|
|
||||||
type mockHTTP struct {
|
|
||||||
onServeHTTP func()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mockHTTP) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
||||||
m.onServeHTTP()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *mockHTTP) RegisterHTTP(mux *http.ServeMux) {
|
|
||||||
mux.Handle("/", h)
|
|
||||||
}
|
|
||||||
func (h *mockHTTP) RegisterAPIv1(mux *http.ServeMux) {
|
|
||||||
mux.Handle("/ping", h)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHttpMux(t *testing.T) {
|
|
||||||
is := is.New(t)
|
|
||||||
|
|
||||||
called := false
|
|
||||||
|
|
||||||
mux := httpMux(&mockHTTP{func() { called = true }})
|
|
||||||
|
|
||||||
is.True(mux != nil)
|
|
||||||
|
|
||||||
w := httptest.NewRecorder()
|
|
||||||
r := httptest.NewRequest(http.MethodGet, "/api/v1/ping", nil)
|
|
||||||
mux.ServeHTTP(w, r)
|
|
||||||
|
|
||||||
is.True(called)
|
|
||||||
}
|
|
60
internal/clean/eventstore.go
Normal file
60
internal/clean/eventstore.go
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
package clean
|
||||||
|
|
||||||
|
import "encoding"
|
||||||
|
|
||||||
|
type EventLog[T, K, C comparable, E any] interface {
|
||||||
|
EventLog(T) List[K, C, E]
|
||||||
|
}
|
||||||
|
|
||||||
|
type EventStore[T, K, C comparable, E, A any] interface {
|
||||||
|
Bus[T, K, E]
|
||||||
|
EventLog[T, K, C, E]
|
||||||
|
|
||||||
|
Load(T, A) error
|
||||||
|
Store(A) error
|
||||||
|
Truncate(T) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type Event[T, C comparable, V any] struct {
|
||||||
|
Topic T
|
||||||
|
Position C
|
||||||
|
Payload V
|
||||||
|
}
|
||||||
|
|
||||||
|
type codec interface {
|
||||||
|
encoding.BinaryMarshaler
|
||||||
|
encoding.BinaryUnmarshaler
|
||||||
|
}
|
||||||
|
|
||||||
|
type aggr = struct{}
|
||||||
|
|
||||||
|
type evvent = Event[string, uint64, codec]
|
||||||
|
type evvee = EventStore[string, string, uint64, evvent, aggr]
|
||||||
|
type evvesub = Subscription[Event[string, uint64, codec]]
|
||||||
|
|
||||||
|
type PAGE = Page[string, string]
|
||||||
|
type LOG struct{}
|
||||||
|
|
||||||
|
var _ List[string, string, evvee] = (*LOG)(nil)
|
||||||
|
|
||||||
|
func (*LOG) First(n uint64, after string) ([]PAGE, error) { panic("N/A") }
|
||||||
|
func (*LOG) Last(n uint64, before string) ([]PAGE, error) { panic("N/A") }
|
||||||
|
|
||||||
|
type SUB struct{}
|
||||||
|
|
||||||
|
var _ evvesub = (*SUB)(nil)
|
||||||
|
|
||||||
|
func (*SUB) Recv() error { return nil }
|
||||||
|
func (*SUB) Events() []evvent { return nil }
|
||||||
|
func (*SUB) Close() {}
|
||||||
|
|
||||||
|
type EV struct{}
|
||||||
|
|
||||||
|
var _ evvee = (*EV)(nil)
|
||||||
|
|
||||||
|
func (*EV) Emit(topic string, event evvent) error { panic("N/A") }
|
||||||
|
func (*EV) EventLog(topic string) List[string, uint64, evvent] { panic("N/A") }
|
||||||
|
func (*EV) Subscribe(topic string, after uint64) evvesub { panic("N/A") }
|
||||||
|
func (*EV) Load(topic string, a aggr) error { panic("N/A") }
|
||||||
|
func (*EV) Store(a aggr) error { panic("N/A") }
|
||||||
|
func (*EV) Truncate(topic string) error { panic("N/A") }
|
38
internal/clean/interfaces.go
Normal file
38
internal/clean/interfaces.go
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
package clean
|
||||||
|
|
||||||
|
type GPD[K comparable, V any] interface {
|
||||||
|
Get(...K) ([]V, error)
|
||||||
|
Put(K, V) error
|
||||||
|
Delete(K) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type Edge[C, K comparable] struct {
|
||||||
|
Key K
|
||||||
|
Kursor C
|
||||||
|
}
|
||||||
|
type Page[C, K comparable] struct {
|
||||||
|
Edges Edge[C, K]
|
||||||
|
Start C
|
||||||
|
End C
|
||||||
|
Next bool
|
||||||
|
Prev bool
|
||||||
|
}
|
||||||
|
type List[K, C comparable, V any] interface {
|
||||||
|
First(n uint64, after C) ([]Page[C, K], error)
|
||||||
|
Last(n uint64, before C) ([]Page[C, K], error)
|
||||||
|
}
|
||||||
|
type Emitter[T comparable, E any] interface {
|
||||||
|
Emit(T, E) error
|
||||||
|
}
|
||||||
|
type Subscription[E any] interface {
|
||||||
|
Recv() error
|
||||||
|
Events() []E
|
||||||
|
Close()
|
||||||
|
}
|
||||||
|
type Subscriber[T comparable, E any] interface {
|
||||||
|
Subscribe(T, uint64) Subscription[E]
|
||||||
|
}
|
||||||
|
type Bus[T, K comparable, E any] interface {
|
||||||
|
Emitter[T, E]
|
||||||
|
Subscriber[T, E]
|
||||||
|
}
|
|
@ -1,35 +0,0 @@
|
||||||
// Code generated by github.com/99designs/gqlgen, DO NOT EDIT.
|
|
||||||
|
|
||||||
package generated
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/99designs/gqlgen/plugin/federation/fedruntime"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrUnknownType = errors.New("unknown type")
|
|
||||||
ErrTypeNotFound = errors.New("type not found")
|
|
||||||
)
|
|
||||||
|
|
||||||
func (ec *executionContext) __resolve__service(ctx context.Context) (fedruntime.Service, error) {
|
|
||||||
if ec.DisableIntrospection {
|
|
||||||
return fedruntime.Service{}, errors.New("federated introspection disabled")
|
|
||||||
}
|
|
||||||
|
|
||||||
var sdl []string
|
|
||||||
|
|
||||||
for _, src := range sources {
|
|
||||||
if src.BuiltIn {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
sdl = append(sdl, src.Input)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fedruntime.Service{
|
|
||||||
SDL: strings.Join(sdl, "\n"),
|
|
||||||
}, nil
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,3 +0,0 @@
|
||||||
// Code generated by github.com/99designs/gqlgen, DO NOT EDIT.
|
|
||||||
|
|
||||||
package model
|
|
|
@ -1,68 +0,0 @@
|
||||||
package lg
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"go.uber.org/multierr"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Init(ctx context.Context, name string) (context.Context, func() error) {
|
|
||||||
ctx, span := Span(ctx)
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
stop := [3]func() error{
|
|
||||||
initLogger(name),
|
|
||||||
}
|
|
||||||
ctx, stop[1] = initMetrics(ctx, name)
|
|
||||||
ctx, stop[2] = initTracing(ctx, name)
|
|
||||||
|
|
||||||
reverse(stop[:])
|
|
||||||
|
|
||||||
return ctx, func() 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...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func env(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 envSecret(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)
|
|
||||||
}
|
|
|
@ -1,111 +0,0 @@
|
||||||
package lg
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"runtime/debug"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/go-logr/stdr"
|
|
||||||
"github.com/logzio/logzio-go"
|
|
||||||
"go.opentelemetry.io/otel"
|
|
||||||
)
|
|
||||||
|
|
||||||
type logzwriter struct {
|
|
||||||
name string
|
|
||||||
pkg string
|
|
||||||
goversion string
|
|
||||||
hostname string
|
|
||||||
|
|
||||||
w io.Writer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *logzwriter) Write(b []byte) (int, error) {
|
|
||||||
i := 0
|
|
||||||
for _, sp := range bytes.Split(b, []byte("\n")) {
|
|
||||||
msg := struct {
|
|
||||||
Message string `json:"message"`
|
|
||||||
Host string `json:"host"`
|
|
||||||
GoVersion string `json:"go_version"`
|
|
||||||
Package string `json:"pkg"`
|
|
||||||
App string `json:"app"`
|
|
||||||
}{
|
|
||||||
Message: strings.TrimSpace(string(sp)),
|
|
||||||
Host: l.hostname,
|
|
||||||
GoVersion: l.goversion,
|
|
||||||
Package: l.pkg,
|
|
||||||
App: l.name,
|
|
||||||
}
|
|
||||||
|
|
||||||
if msg.Message == "" || strings.HasPrefix(msg.Message, "#") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
b, err := json.Marshal(msg)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
j, err := l.w.Write(b)
|
|
||||||
|
|
||||||
i += j
|
|
||||||
if err != nil {
|
|
||||||
return i, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return i, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func initLogger(name string) func() error {
|
|
||||||
log.SetPrefix("[" + name + "] ")
|
|
||||||
log.SetFlags(log.LstdFlags&^(log.Ldate|log.Ltime) | log.Lshortfile)
|
|
||||||
|
|
||||||
token := envSecret("LOGZIO_LOG_TOKEN", "")
|
|
||||||
if token == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
l, err := logzio.New(
|
|
||||||
token.Secret(),
|
|
||||||
// logzio.SetDebug(os.Stderr),
|
|
||||||
logzio.SetUrl(env("LOGZIO_LOG_URL", "https://listener.lg.io:8071")),
|
|
||||||
logzio.SetDrainDuration(time.Second*5),
|
|
||||||
logzio.SetTempDirectory(env("LOGZIO_DIR", os.TempDir())),
|
|
||||||
logzio.SetCheckDiskSpace(true),
|
|
||||||
logzio.SetDrainDiskThreshold(70),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
w := io.MultiWriter(os.Stderr, lzw(l, name))
|
|
||||||
log.SetOutput(w)
|
|
||||||
otel.SetLogger(stdr.New(log.Default()))
|
|
||||||
|
|
||||||
return func() error {
|
|
||||||
defer log.Println("logger stopped")
|
|
||||||
log.SetOutput(os.Stderr)
|
|
||||||
l.Stop()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func lzw(l io.Writer, name string) io.Writer {
|
|
||||||
lz := &logzwriter{
|
|
||||||
name: name,
|
|
||||||
w: l,
|
|
||||||
}
|
|
||||||
|
|
||||||
if info, ok := debug.ReadBuildInfo(); ok {
|
|
||||||
lz.goversion = info.GoVersion
|
|
||||||
lz.pkg = info.Path
|
|
||||||
}
|
|
||||||
if hostname, err := os.Hostname(); err == nil {
|
|
||||||
lz.hostname = hostname
|
|
||||||
}
|
|
||||||
|
|
||||||
return lz
|
|
||||||
}
|
|
|
@ -1,103 +0,0 @@
|
||||||
package lg
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"runtime/debug"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"go.opentelemetry.io/contrib/instrumentation/runtime"
|
|
||||||
"go.opentelemetry.io/otel/attribute"
|
|
||||||
"go.opentelemetry.io/otel/exporters/prometheus"
|
|
||||||
"go.opentelemetry.io/otel/metric"
|
|
||||||
"go.opentelemetry.io/otel/metric/global"
|
|
||||||
"go.opentelemetry.io/otel/sdk/metric/aggregator/histogram"
|
|
||||||
controller "go.opentelemetry.io/otel/sdk/metric/controller/basic"
|
|
||||||
"go.opentelemetry.io/otel/sdk/metric/export/aggregation"
|
|
||||||
processor "go.opentelemetry.io/otel/sdk/metric/processor/basic"
|
|
||||||
selector "go.opentelemetry.io/otel/sdk/metric/selector/simple"
|
|
||||||
"go.opentelemetry.io/otel/sdk/resource"
|
|
||||||
semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
|
|
||||||
)
|
|
||||||
|
|
||||||
var meterKey = contextKey{"meter"}
|
|
||||||
var promHTTPKey = contextKey{"promHTTP"}
|
|
||||||
|
|
||||||
func Meter(ctx context.Context) metric.Meter {
|
|
||||||
if t := fromContext[contextKey, metric.Meter](ctx, tracerKey); t != nil {
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
return global.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(config, cont)
|
|
||||||
if err != nil {
|
|
||||||
return ctx, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx = toContext(ctx, promHTTPKey, ex)
|
|
||||||
|
|
||||||
global.SetMeterProvider(cont)
|
|
||||||
m := cont.Meter(name)
|
|
||||||
ctx = toContext(ctx, meterKey, m)
|
|
||||||
runtime.Start()
|
|
||||||
|
|
||||||
return ctx, func() error {
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
defer log.Println("metrics stopped")
|
|
||||||
return cont.Stop(ctx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type httpHandle struct {
|
|
||||||
exp *prometheus.Exporter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *httpHandle) RegisterHTTP(mux *http.ServeMux) {
|
|
||||||
if h.exp == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
mux.Handle("/metrics", h.exp)
|
|
||||||
}
|
|
|
@ -1,166 +0,0 @@
|
||||||
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"
|
|
||||||
)
|
|
||||||
|
|
||||||
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),
|
|
||||||
attribute.String("name", name),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return name, attrs
|
|
||||||
}
|
|
||||||
|
|
||||||
func Span(ctx context.Context, opts ...trace.SpanStartOption) (context.Context, trace.Span) {
|
|
||||||
name, attrs := attrs(ctx)
|
|
||||||
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("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("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)
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
171
main.go
171
main.go
|
@ -1,171 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"go.uber.org/multierr"
|
|
||||||
"golang.org/x/sync/errgroup"
|
|
||||||
|
|
||||||
"github.com/sour-is/ev/app/gql"
|
|
||||||
"github.com/sour-is/ev/app/msgbus"
|
|
||||||
"github.com/sour-is/ev/app/peerfinder"
|
|
||||||
"github.com/sour-is/ev/app/salty"
|
|
||||||
"github.com/sour-is/ev/internal/lg"
|
|
||||||
"github.com/sour-is/ev/pkg/es"
|
|
||||||
diskstore "github.com/sour-is/ev/pkg/es/driver/disk-store"
|
|
||||||
memstore "github.com/sour-is/ev/pkg/es/driver/mem-store"
|
|
||||||
"github.com/sour-is/ev/pkg/es/driver/projecter"
|
|
||||||
"github.com/sour-is/ev/pkg/es/driver/streamer"
|
|
||||||
"github.com/sour-is/ev/pkg/es/event"
|
|
||||||
"github.com/sour-is/ev/pkg/set"
|
|
||||||
)
|
|
||||||
|
|
||||||
const AppName string = "sour.is-ev"
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill)
|
|
||||||
go func() {
|
|
||||||
<-ctx.Done()
|
|
||||||
defer cancel()
|
|
||||||
}()
|
|
||||||
|
|
||||||
ctx, stop := lg.Init(ctx, AppName)
|
|
||||||
defer stop()
|
|
||||||
|
|
||||||
if err := run(ctx); err != nil && err != http.ErrServerClosed {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func run(ctx context.Context) error {
|
|
||||||
g, ctx := errgroup.WithContext(ctx)
|
|
||||||
|
|
||||||
{
|
|
||||||
ctx, span := lg.Span(ctx)
|
|
||||||
|
|
||||||
err := multierr.Combine(
|
|
||||||
es.Init(ctx),
|
|
||||||
event.Init(ctx),
|
|
||||||
diskstore.Init(ctx),
|
|
||||||
memstore.Init(ctx),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
span.RecordError(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
es, err := es.Open(
|
|
||||||
ctx,
|
|
||||||
env("EV_DATA", "mem:"),
|
|
||||||
streamer.New(ctx),
|
|
||||||
projecter.New(ctx, projecter.DefaultProjection),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
span.RecordError(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
s := http.Server{
|
|
||||||
Addr: env("EV_HTTP", ":8080"),
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.HasPrefix(s.Addr, ":") {
|
|
||||||
s.Addr = "[::]" + s.Addr
|
|
||||||
}
|
|
||||||
|
|
||||||
enable := set.New(strings.Fields(env("EV_ENABLE", "salty msgbus gql peers"))...)
|
|
||||||
var svcs []interface{ RegisterHTTP(*http.ServeMux) }
|
|
||||||
|
|
||||||
svcs = append(svcs, es)
|
|
||||||
|
|
||||||
if enable.Has("salty") {
|
|
||||||
span.AddEvent("Enable Salty")
|
|
||||||
base, err := url.JoinPath(env("EV_BASE_URL", "http://"+s.Addr), "inbox")
|
|
||||||
if err != nil {
|
|
||||||
span.RecordError(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
salty, err := salty.New(ctx, es, base)
|
|
||||||
if err != nil {
|
|
||||||
span.RecordError(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
svcs = append(svcs, salty)
|
|
||||||
}
|
|
||||||
|
|
||||||
if enable.Has("msgbus") {
|
|
||||||
span.AddEvent("Enable Msgbus")
|
|
||||||
msgbus, err := msgbus.New(ctx, es)
|
|
||||||
if err != nil {
|
|
||||||
span.RecordError(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
svcs = append(svcs, msgbus)
|
|
||||||
}
|
|
||||||
|
|
||||||
if enable.Has("peers") {
|
|
||||||
span.AddEvent("Enable Peers")
|
|
||||||
peers, err := peerfinder.New(ctx, es)
|
|
||||||
if err != nil {
|
|
||||||
span.RecordError(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
svcs = append(svcs, peers)
|
|
||||||
}
|
|
||||||
|
|
||||||
if enable.Has("gql") {
|
|
||||||
span.AddEvent("Enable GraphQL")
|
|
||||||
gql, err := gql.New(ctx, svcs...)
|
|
||||||
if err != nil {
|
|
||||||
span.RecordError(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
svcs = append(svcs, gql)
|
|
||||||
}
|
|
||||||
svcs = append(svcs, lg.NewHTTP(ctx))
|
|
||||||
|
|
||||||
s.Handler = httpMux(svcs...)
|
|
||||||
|
|
||||||
log.Print("Listen on ", s.Addr)
|
|
||||||
span.AddEvent("begin listen and serve on " + s.Addr)
|
|
||||||
|
|
||||||
Mup, err := lg.Meter(ctx).SyncInt64().UpDownCounter("up")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
Mup.Add(ctx, 1)
|
|
||||||
|
|
||||||
g.Go(s.ListenAndServe)
|
|
||||||
|
|
||||||
g.Go(func() error {
|
|
||||||
<-ctx.Done()
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
return s.Shutdown(ctx)
|
|
||||||
})
|
|
||||||
|
|
||||||
span.End()
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := g.Wait(); err != nil && err != http.ErrServerClosed {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
func env(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
|
|
||||||
}
|
|
238
pkg/cache/cache.go
vendored
238
pkg/cache/cache.go
vendored
|
@ -1,238 +0,0 @@
|
||||||
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
pkg/cache/cache_test.go
vendored
131
pkg/cache/cache_test.go
vendored
|
@ -1,131 +0,0 @@
|
||||||
package cache_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/matryer/is"
|
|
||||||
"github.com/sour-is/ev/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
pkg/cache/list.go
vendored
235
pkg/cache/list.go
vendored
|
@ -1,235 +0,0 @@
|
||||||
// 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
pkg/cache/lru.go
vendored
175
pkg/cache/lru.go
vendored
|
@ -1,175 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,306 +0,0 @@
|
||||||
package diskstore
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/tidwall/wal"
|
|
||||||
"go.opentelemetry.io/otel/metric/instrument/syncint64"
|
|
||||||
"go.uber.org/multierr"
|
|
||||||
|
|
||||||
"github.com/sour-is/ev/internal/lg"
|
|
||||||
"github.com/sour-is/ev/pkg/cache"
|
|
||||||
"github.com/sour-is/ev/pkg/es"
|
|
||||||
"github.com/sour-is/ev/pkg/es/driver"
|
|
||||||
"github.com/sour-is/ev/pkg/es/event"
|
|
||||||
"github.com/sour-is/ev/pkg/locker"
|
|
||||||
"github.com/sour-is/ev/pkg/math"
|
|
||||||
)
|
|
||||||
|
|
||||||
const CachSize = 1000
|
|
||||||
|
|
||||||
type lockedWal = locker.Locked[wal.Log]
|
|
||||||
type openlogs struct {
|
|
||||||
logs *cache.Cache[string, *lockedWal]
|
|
||||||
}
|
|
||||||
type diskStore struct {
|
|
||||||
path string
|
|
||||||
openlogs *locker.Locked[openlogs]
|
|
||||||
|
|
||||||
m_disk_open syncint64.Counter
|
|
||||||
m_disk_evict syncint64.Counter
|
|
||||||
m_disk_read syncint64.Counter
|
|
||||||
m_disk_write syncint64.Counter
|
|
||||||
}
|
|
||||||
|
|
||||||
const AppendOnly = es.AppendOnly
|
|
||||||
const AllEvents = es.AllEvents
|
|
||||||
|
|
||||||
func Init(ctx context.Context) error {
|
|
||||||
_, span := lg.Span(ctx)
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
d := &diskStore{}
|
|
||||||
|
|
||||||
m := lg.Meter(ctx)
|
|
||||||
var err, errs error
|
|
||||||
|
|
||||||
d.m_disk_open, err = m.SyncInt64().Counter("disk_open")
|
|
||||||
errs = multierr.Append(errs, err)
|
|
||||||
|
|
||||||
d.m_disk_evict, err = m.SyncInt64().Counter("disk_evict")
|
|
||||||
errs = multierr.Append(errs, err)
|
|
||||||
|
|
||||||
d.m_disk_read, err = m.SyncInt64().Counter("disk_read")
|
|
||||||
errs = multierr.Append(errs, err)
|
|
||||||
|
|
||||||
d.m_disk_write, err = m.SyncInt64().Counter("disk_write")
|
|
||||||
errs = multierr.Append(errs, err)
|
|
||||||
|
|
||||||
es.Register(ctx, "file", d)
|
|
||||||
|
|
||||||
return errs
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ driver.Driver = (*diskStore)(nil)
|
|
||||||
|
|
||||||
func (d *diskStore) Open(ctx context.Context, dsn string) (driver.Driver, error) {
|
|
||||||
_, span := lg.Span(ctx)
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
scheme, path, ok := strings.Cut(dsn, ":")
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("expected scheme")
|
|
||||||
}
|
|
||||||
|
|
||||||
if scheme != "file" {
|
|
||||||
return nil, fmt.Errorf("expeted scheme=file, got=%s", scheme)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
|
||||||
err = os.MkdirAll(path, 0700)
|
|
||||||
if err != nil {
|
|
||||||
span.RecordError(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c, err := cache.NewWithEvict(CachSize, func(ctx context.Context, s string, l *lockedWal) {
|
|
||||||
_, span := lg.Span(ctx)
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
l.Modify(ctx, func(w *wal.Log) error {
|
|
||||||
_, span := lg.Span(ctx)
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
d.m_disk_evict.Add(ctx, 1)
|
|
||||||
|
|
||||||
err := w.Close()
|
|
||||||
if err != nil {
|
|
||||||
span.RecordError(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
span.RecordError(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
logs := &openlogs{logs: c}
|
|
||||||
return &diskStore{
|
|
||||||
path: path,
|
|
||||||
openlogs: locker.New(logs),
|
|
||||||
m_disk_open: d.m_disk_open,
|
|
||||||
m_disk_evict: d.m_disk_evict,
|
|
||||||
m_disk_read: d.m_disk_read,
|
|
||||||
m_disk_write: d.m_disk_write,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
func (d *diskStore) EventLog(ctx context.Context, streamID string) (driver.EventLog, error) {
|
|
||||||
_, span := lg.Span(ctx)
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
el := &eventLog{streamID: streamID, diskStore: d}
|
|
||||||
|
|
||||||
return el, d.openlogs.Modify(ctx, func(openlogs *openlogs) error {
|
|
||||||
_, span := lg.Span(ctx)
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
if events, ok := openlogs.logs.Get(streamID); ok {
|
|
||||||
el.events = *events
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
d.m_disk_open.Add(ctx, 1)
|
|
||||||
|
|
||||||
l, err := wal.Open(filepath.Join(d.path, streamID), wal.DefaultOptions)
|
|
||||||
if err != nil {
|
|
||||||
span.RecordError(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
el.events = locker.New(l)
|
|
||||||
openlogs.logs.Add(ctx, streamID, el.events)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
type eventLog struct {
|
|
||||||
streamID string
|
|
||||||
events *locker.Locked[wal.Log]
|
|
||||||
diskStore *diskStore
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ driver.EventLog = (*eventLog)(nil)
|
|
||||||
|
|
||||||
func (e *eventLog) Append(ctx context.Context, events event.Events, version uint64) (uint64, error) {
|
|
||||||
_, span := lg.Span(ctx)
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
event.SetStreamID(e.streamID, events...)
|
|
||||||
|
|
||||||
var count uint64
|
|
||||||
err := e.events.Modify(ctx, func(l *wal.Log) error {
|
|
||||||
_, span := lg.Span(ctx)
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
last, err := l.LastIndex()
|
|
||||||
if err != nil {
|
|
||||||
span.RecordError(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if version != AppendOnly && version != last {
|
|
||||||
return fmt.Errorf("%w: current version wrong %d != %d", es.ErrWrongVersion, version, last)
|
|
||||||
}
|
|
||||||
|
|
||||||
var b []byte
|
|
||||||
|
|
||||||
batch := &wal.Batch{}
|
|
||||||
for i, e := range events {
|
|
||||||
span.AddEvent(fmt.Sprintf("append event %d of %d", i, len(events)))
|
|
||||||
|
|
||||||
b, err = event.MarshalBinary(e)
|
|
||||||
if err != nil {
|
|
||||||
span.RecordError(err)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
pos := last + uint64(i) + 1
|
|
||||||
event.SetPosition(e, pos)
|
|
||||||
|
|
||||||
batch.Write(pos, b)
|
|
||||||
}
|
|
||||||
|
|
||||||
count = uint64(len(events))
|
|
||||||
e.diskStore.m_disk_write.Add(ctx, int64(len(events)))
|
|
||||||
|
|
||||||
return l.WriteBatch(batch)
|
|
||||||
})
|
|
||||||
|
|
||||||
return count, err
|
|
||||||
}
|
|
||||||
func (e *eventLog) Read(ctx context.Context, pos, count int64) (event.Events, error) {
|
|
||||||
_, span := lg.Span(ctx)
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
var events event.Events
|
|
||||||
|
|
||||||
err := e.events.Modify(ctx, func(stream *wal.Log) error {
|
|
||||||
_, span := lg.Span(ctx)
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
first, err := stream.FirstIndex()
|
|
||||||
if err != nil {
|
|
||||||
span.RecordError(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
last, err := stream.LastIndex()
|
|
||||||
if err != nil {
|
|
||||||
span.RecordError(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// ---
|
|
||||||
if first == 0 || last == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
start, count := math.PagerBox(first, last, pos, count)
|
|
||||||
if count == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
events = make([]event.Event, math.Abs(count))
|
|
||||||
for i := range events {
|
|
||||||
span.AddEvent(fmt.Sprintf("read event %d of %d", i, len(events)))
|
|
||||||
|
|
||||||
// ---
|
|
||||||
var b []byte
|
|
||||||
b, err = stream.Read(start)
|
|
||||||
if err != nil {
|
|
||||||
span.RecordError(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
events[i], err = event.UnmarshalBinary(ctx, b, start)
|
|
||||||
if err != nil {
|
|
||||||
span.RecordError(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// ---
|
|
||||||
|
|
||||||
if count > 0 {
|
|
||||||
start += 1
|
|
||||||
} else {
|
|
||||||
start -= 1
|
|
||||||
}
|
|
||||||
if start < first || start > last {
|
|
||||||
events = events[:i+1]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
span.RecordError(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
event.SetStreamID(e.streamID, events...)
|
|
||||||
e.diskStore.m_disk_read.Add(ctx, int64(len(events)))
|
|
||||||
|
|
||||||
return events, nil
|
|
||||||
}
|
|
||||||
func (e *eventLog) FirstIndex(ctx context.Context) (uint64, error) {
|
|
||||||
_, span := lg.Span(ctx)
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
var idx uint64
|
|
||||||
var err error
|
|
||||||
|
|
||||||
err = e.events.Modify(ctx, func(events *wal.Log) error {
|
|
||||||
idx, err = events.FirstIndex()
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
|
|
||||||
return idx, err
|
|
||||||
}
|
|
||||||
func (e *eventLog) LastIndex(ctx context.Context) (uint64, error) {
|
|
||||||
_, span := lg.Span(ctx)
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
var idx uint64
|
|
||||||
var err error
|
|
||||||
|
|
||||||
err = e.events.Modify(ctx, func(events *wal.Log) error {
|
|
||||||
idx, err = events.LastIndex()
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
|
|
||||||
return idx, err
|
|
||||||
}
|
|
||||||
func (e *eventLog) LoadForUpdate(ctx context.Context, a event.Aggregate, fn func(context.Context, event.Aggregate) error) (uint64, error) {
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
|
@ -1,32 +0,0 @@
|
||||||
package driver
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/sour-is/ev/pkg/es/event"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Driver interface {
|
|
||||||
Open(ctx context.Context, dsn string) (Driver, error)
|
|
||||||
EventLog(ctx context.Context, streamID string) (EventLog, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type EventLog interface {
|
|
||||||
Read(ctx context.Context, pos, count int64) (event.Events, error)
|
|
||||||
Append(ctx context.Context, events event.Events, version uint64) (uint64, error)
|
|
||||||
FirstIndex(context.Context) (uint64, error)
|
|
||||||
LastIndex(context.Context) (uint64, error)
|
|
||||||
|
|
||||||
LoadForUpdate(context.Context, event.Aggregate, func(context.Context, event.Aggregate) error) (uint64, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Subscription interface {
|
|
||||||
Recv(context.Context) bool
|
|
||||||
Events(context.Context) (event.Events, error)
|
|
||||||
Close(context.Context) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type EventStream interface {
|
|
||||||
Subscribe(ctx context.Context, streamID string, start int64) (Subscription, error)
|
|
||||||
Send(ctx context.Context, streamID string, events event.Events) error
|
|
||||||
}
|
|
|
@ -1,195 +0,0 @@
|
||||||
package memstore
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/sour-is/ev/internal/lg"
|
|
||||||
"github.com/sour-is/ev/pkg/es"
|
|
||||||
"github.com/sour-is/ev/pkg/es/driver"
|
|
||||||
"github.com/sour-is/ev/pkg/es/event"
|
|
||||||
"github.com/sour-is/ev/pkg/locker"
|
|
||||||
"github.com/sour-is/ev/pkg/math"
|
|
||||||
)
|
|
||||||
|
|
||||||
type state struct {
|
|
||||||
streams map[string]*locker.Locked[event.Events]
|
|
||||||
}
|
|
||||||
type eventLog struct {
|
|
||||||
streamID string
|
|
||||||
events *locker.Locked[event.Events]
|
|
||||||
}
|
|
||||||
type memstore struct {
|
|
||||||
state *locker.Locked[state]
|
|
||||||
}
|
|
||||||
|
|
||||||
const AppendOnly = es.AppendOnly
|
|
||||||
const AllEvents = es.AllEvents
|
|
||||||
|
|
||||||
func Init(ctx context.Context) error {
|
|
||||||
ctx, span := lg.Span(ctx)
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
return es.Register(ctx, "mem", &memstore{})
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ driver.Driver = (*memstore)(nil)
|
|
||||||
|
|
||||||
func (memstore) Open(ctx context.Context, name string) (driver.Driver, error) {
|
|
||||||
_, span := lg.Span(ctx)
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
s := &state{streams: make(map[string]*locker.Locked[event.Events])}
|
|
||||||
return &memstore{locker.New(s)}, nil
|
|
||||||
}
|
|
||||||
func (m *memstore) EventLog(ctx context.Context, streamID string) (driver.EventLog, error) {
|
|
||||||
ctx, span := lg.Span(ctx)
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
el := &eventLog{streamID: streamID}
|
|
||||||
|
|
||||||
err := m.state.Modify(ctx, func(state *state) error {
|
|
||||||
_, span := lg.Span(ctx)
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
l, ok := state.streams[streamID]
|
|
||||||
if !ok {
|
|
||||||
l = locker.New(&event.Events{})
|
|
||||||
state.streams[streamID] = l
|
|
||||||
}
|
|
||||||
el.events = l
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return el, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ driver.EventLog = (*eventLog)(nil)
|
|
||||||
|
|
||||||
// Append implements driver.EventStore
|
|
||||||
func (m *eventLog) Append(ctx context.Context, events event.Events, version uint64) (uint64, error) {
|
|
||||||
ctx, span := lg.Span(ctx)
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
event.SetStreamID(m.streamID, events...)
|
|
||||||
|
|
||||||
return uint64(len(events)), m.events.Modify(ctx, func(stream *event.Events) error {
|
|
||||||
_, span := lg.Span(ctx)
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
span.AddEvent(fmt.Sprintf(" %s %d", m.streamID, len(*stream)))
|
|
||||||
|
|
||||||
last := uint64(len(*stream))
|
|
||||||
if version != AppendOnly && version != last {
|
|
||||||
return fmt.Errorf("%w: current version wrong %d != %d", es.ErrWrongVersion, version, last)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range events {
|
|
||||||
span.AddEvent(fmt.Sprintf("read event %d of %d", i, len(events)))
|
|
||||||
|
|
||||||
// --- clone event
|
|
||||||
e := events[i]
|
|
||||||
b, err := event.MarshalBinary(e)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
e, err = event.UnmarshalBinary(ctx, b, e.EventMeta().Position)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// ---
|
|
||||||
|
|
||||||
pos := last + uint64(i) + 1
|
|
||||||
event.SetPosition(e, pos)
|
|
||||||
*stream = append(*stream, e)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read implements driver.EventStore
|
|
||||||
func (m *eventLog) Read(ctx context.Context, pos int64, count int64) (event.Events, error) {
|
|
||||||
ctx, span := lg.Span(ctx)
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
var events event.Events
|
|
||||||
|
|
||||||
err := m.events.Modify(ctx, func(stream *event.Events) error {
|
|
||||||
_, span := lg.Span(ctx)
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
span.AddEvent(fmt.Sprintf("%s %d", m.streamID, len(*stream)))
|
|
||||||
|
|
||||||
first := stream.First().EventMeta().Position
|
|
||||||
last := stream.Last().EventMeta().Position
|
|
||||||
// ---
|
|
||||||
if first == 0 || last == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
start, count := math.PagerBox(first, last, pos, count)
|
|
||||||
if count == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
span.AddEvent(fmt.Sprint("box", first, last, pos, count))
|
|
||||||
events = make([]event.Event, math.Abs(count))
|
|
||||||
for i := range events {
|
|
||||||
span.AddEvent(fmt.Sprintf("read event %d of %d", i, math.Abs(count)))
|
|
||||||
|
|
||||||
// --- clone event
|
|
||||||
e := (*stream)[start-1]
|
|
||||||
b, err := event.MarshalBinary(e)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
events[i], err = event.UnmarshalBinary(ctx, b, e.EventMeta().Position)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// ---
|
|
||||||
|
|
||||||
if count > 0 {
|
|
||||||
start += 1
|
|
||||||
} else {
|
|
||||||
start -= 1
|
|
||||||
}
|
|
||||||
if start < first || start > last {
|
|
||||||
events = events[:i+1]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
event.SetStreamID(m.streamID, events...)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return events, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FirstIndex for the streamID
|
|
||||||
func (m *eventLog) FirstIndex(ctx context.Context) (uint64, error) {
|
|
||||||
_, span := lg.Span(ctx)
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
events, err := m.events.Copy(ctx)
|
|
||||||
return events.First().EventMeta().Position, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// LastIndex for the streamID
|
|
||||||
func (m *eventLog) LastIndex(ctx context.Context) (uint64, error) {
|
|
||||||
_, span := lg.Span(ctx)
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
events, err := m.events.Copy(ctx)
|
|
||||||
return events.Last().EventMeta().Position, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *eventLog) LoadForUpdate(ctx context.Context, a event.Aggregate, fn func(context.Context, event.Aggregate) error) (uint64, error) {
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
package projecter_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/sour-is/ev/pkg/es/driver"
|
|
||||||
)
|
|
||||||
|
|
||||||
type mockDriver struct {
|
|
||||||
onOpen func()
|
|
||||||
onEventLog func()
|
|
||||||
}
|
|
||||||
|
|
||||||
// EventLog implements driver.Driver
|
|
||||||
func (*mockDriver) EventLog(ctx context.Context, streamID string) (driver.EventLog, error) {
|
|
||||||
panic("unimplemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open implements driver.Driver
|
|
||||||
func (*mockDriver) Open(ctx context.Context, dsn string) (driver.Driver, error) {
|
|
||||||
panic("unimplemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ driver.Driver = (*mockDriver)(nil)
|
|
||||||
|
|
||||||
func TestProjecter(t *testing.T) {
|
|
||||||
|
|
||||||
}
|
|
132
pkg/es/graph.go
132
pkg/es/graph.go
|
@ -1,132 +0,0 @@
|
||||||
package es
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sour-is/ev/internal/lg"
|
|
||||||
"github.com/sour-is/ev/pkg/es/event"
|
|
||||||
"github.com/sour-is/ev/pkg/gql"
|
|
||||||
)
|
|
||||||
|
|
||||||
type EventResolver interface {
|
|
||||||
Events(ctx context.Context, streamID string, paging *gql.PageInput) (*gql.Connection, error)
|
|
||||||
EventAdded(ctx context.Context, streamID string, after int64) (<-chan *GQLEvent, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (es *EventStore) Events(ctx context.Context, streamID string, paging *gql.PageInput) (*gql.Connection, error) {
|
|
||||||
ctx, span := lg.Span(ctx)
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
lis, err := es.Read(ctx, streamID, paging.GetIdx(0), paging.GetCount(30))
|
|
||||||
if err != nil {
|
|
||||||
span.RecordError(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
edges := make([]gql.Edge, 0, len(lis))
|
|
||||||
for i := range lis {
|
|
||||||
span.AddEvent(fmt.Sprint("event ", i, " of ", len(lis)))
|
|
||||||
edges = append(edges, &GQLEvent{lis[i]})
|
|
||||||
}
|
|
||||||
|
|
||||||
var first, last uint64
|
|
||||||
if first, err = es.FirstIndex(ctx, streamID); err != nil {
|
|
||||||
span.RecordError(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if last, err = es.LastIndex(ctx, streamID); err != nil {
|
|
||||||
span.RecordError(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &gql.Connection{
|
|
||||||
Paging: &gql.PageInfo{
|
|
||||||
Next: lis.Last().EventMeta().Position < last,
|
|
||||||
Prev: lis.First().EventMeta().Position > first,
|
|
||||||
Begin: lis.First().EventMeta().Position,
|
|
||||||
End: lis.Last().EventMeta().Position,
|
|
||||||
},
|
|
||||||
Edges: edges,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
func (e *EventStore) EventAdded(ctx context.Context, streamID string, after int64) (<-chan *GQLEvent, error) {
|
|
||||||
ctx, span := lg.Span(ctx)
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
es := e.EventStream()
|
|
||||||
if es == nil {
|
|
||||||
return nil, fmt.Errorf("EventStore does not implement streaming")
|
|
||||||
}
|
|
||||||
|
|
||||||
sub, err := es.Subscribe(ctx, streamID, after)
|
|
||||||
if err != nil {
|
|
||||||
span.RecordError(err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ch := make(chan *GQLEvent)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
ctx, span := lg.Span(ctx)
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
{
|
|
||||||
ctx, span := lg.Fork(ctx)
|
|
||||||
defer func() {
|
|
||||||
defer span.End()
|
|
||||||
ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
err := sub.Close(ctx)
|
|
||||||
span.RecordError(err)
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
for sub.Recv(ctx) {
|
|
||||||
events, err := sub.Events(ctx)
|
|
||||||
if err != nil {
|
|
||||||
span.RecordError(err)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
span.AddEvent(fmt.Sprintf("received %d events", len(events)))
|
|
||||||
|
|
||||||
for i := range events {
|
|
||||||
select {
|
|
||||||
case ch <- &GQLEvent{events[i]}:
|
|
||||||
continue
|
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return ch, nil
|
|
||||||
}
|
|
||||||
func (*EventStore) RegisterHTTP(*http.ServeMux) {}
|
|
||||||
|
|
||||||
type GQLEvent struct {
|
|
||||||
e event.Event
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *GQLEvent) ID() string {
|
|
||||||
return fmt.Sprint(e.e.EventMeta().StreamID, "@", e.e.EventMeta().Position)
|
|
||||||
}
|
|
||||||
func (e *GQLEvent) EventID() string {
|
|
||||||
return e.e.EventMeta().GetEventID()
|
|
||||||
}
|
|
||||||
func (e *GQLEvent) Values() map[string]interface{} {
|
|
||||||
return event.Values(e.e)
|
|
||||||
}
|
|
||||||
func (e *GQLEvent) Bytes() (string, error) {
|
|
||||||
b, err := e.e.MarshalBinary()
|
|
||||||
return string(b), err
|
|
||||||
}
|
|
||||||
func (e *GQLEvent) Meta() *event.Meta {
|
|
||||||
meta := e.e.EventMeta()
|
|
||||||
return &meta
|
|
||||||
}
|
|
||||||
func (e *GQLEvent) IsEdge() {}
|
|
|
@ -1,36 +0,0 @@
|
||||||
scalar Time
|
|
||||||
scalar Map
|
|
||||||
|
|
||||||
type Connection @goModel(model: "github.com/sour-is/ev/pkg/gql.Connection") {
|
|
||||||
paging: PageInfo!
|
|
||||||
edges: [Edge!]!
|
|
||||||
}
|
|
||||||
input PageInput @goModel(model: "github.com/sour-is/ev/pkg/gql.PageInput") {
|
|
||||||
idx: Int = 0
|
|
||||||
count: Int = 30
|
|
||||||
}
|
|
||||||
type PageInfo @goModel(model: "github.com/sour-is/ev/pkg/gql.PageInfo") {
|
|
||||||
next: Boolean!
|
|
||||||
prev: Boolean!
|
|
||||||
|
|
||||||
begin: Int!
|
|
||||||
end: Int!
|
|
||||||
}
|
|
||||||
interface Edge @goModel(model: "github.com/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
|
|
|
@ -1,56 +0,0 @@
|
||||||
package gql
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"github.com/sour-is/ev/pkg/es/event"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Edge interface {
|
|
||||||
IsEdge()
|
|
||||||
}
|
|
||||||
|
|
||||||
type Connection struct {
|
|
||||||
Paging *PageInfo `json:"paging"`
|
|
||||||
Edges []Edge `json:"edges"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type PostEvent struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
Payload string `json:"payload"`
|
|
||||||
Tags []string `json:"tags"`
|
|
||||||
Meta *event.Meta `json:"meta"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (PostEvent) IsEdge() {}
|
|
||||||
|
|
||||||
func (e *PostEvent) PayloadJSON(ctx context.Context) (m map[string]interface{}, err error) {
|
|
||||||
err = json.Unmarshal([]byte(e.Payload), &m)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type PageInfo struct {
|
|
||||||
Next bool `json:"next"`
|
|
||||||
Prev bool `json:"prev"`
|
|
||||||
Begin uint64 `json:"begin"`
|
|
||||||
End uint64 `json:"end"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type PageInput struct {
|
|
||||||
Idx *int64 `json:"idx"`
|
|
||||||
Count *int64 `json:"count"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PageInput) GetIdx(v int64) int64 {
|
|
||||||
if p == nil || p.Idx == nil {
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
return *p.Idx
|
|
||||||
}
|
|
||||||
func (p *PageInput) GetCount(v int64) int64 {
|
|
||||||
if p == nil || p.Count == nil {
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
return *p.Count
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
|
@ -1,51 +0,0 @@
|
||||||
package locker
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/sour-is/ev/internal/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
|
|
||||||
}
|
|
||||||
|
|
||||||
// Modify will call the function with the locked value
|
|
||||||
func (s *Locked[T]) Modify(ctx context.Context, fn func(*T) error) error {
|
|
||||||
_, span := lg.Span(ctx)
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
if ctx.Err() != nil {
|
|
||||||
return ctx.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case state := <-s.state:
|
|
||||||
defer func() { s.state <- state }()
|
|
||||||
return fn(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.Modify(ctx, func(c *T) error {
|
|
||||||
if c != nil {
|
|
||||||
t = *c
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
return t, err
|
|
||||||
}
|
|
|
@ -1,62 +0,0 @@
|
||||||
package locker_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/matryer/is"
|
|
||||||
|
|
||||||
"github.com/sour-is/ev/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.Modify(ctx, func(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.Modify(ctx, func(c *config) error {
|
|
||||||
c.Value = "two"
|
|
||||||
c.Counter++
|
|
||||||
close(wait)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
<-wait
|
|
||||||
cancel()
|
|
||||||
|
|
||||||
err = value.Modify(ctx, func(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)
|
|
||||||
}
|
|
|
@ -1,67 +0,0 @@
|
||||||
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 {
|
|
||||||
start = first + uint64(pos)
|
|
||||||
} 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
|
|
||||||
}
|
|
|
@ -1,92 +0,0 @@
|
||||||
package math_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/matryer/is"
|
|
||||||
"github.com/sour-is/ev/pkg/es"
|
|
||||||
"github.com/sour-is/ev/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, es.AllEvents, 1, 10},
|
|
||||||
{1, 10, -1, -es.AllEvents, 10, -10},
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
package set
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
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]) 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()
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
package set_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/matryer/is"
|
|
||||||
"github.com/sour-is/ev/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>)")
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user