go/trains: init

This commit is contained in:
Luke Granger-Brown 2021-11-18 22:24:20 +00:00
parent 6d21c17a2a
commit 66875b327e
40 changed files with 4628 additions and 0 deletions

View file

@ -15,6 +15,7 @@ let
pkgs = builtins.removeAttrs depot.nix.pkgs [ "grafana-plugins" "windows" "unifiHacked" "nixpkgs-mozilla" "nightlyRust" "nightlyRustPlatform" ]; pkgs = builtins.removeAttrs depot.nix.pkgs [ "grafana-plugins" "windows" "unifiHacked" "nixpkgs-mozilla" "nightlyRust" "nightlyRustPlatform" ];
pkg-grafana-plugins = depot.nix.pkgs.grafana-plugins; pkg-grafana-plugins = depot.nix.pkgs.grafana-plugins;
web = lib.filterAttrs (n: v: !lib.isFunction v) depot.web; web = lib.filterAttrs (n: v: !lib.isFunction v) depot.web;
trains = depot.go.trains.cmd.bins;
other = { other = {
twitterchiver-archiver = depot.go.twitterchiver.archiver; twitterchiver-archiver = depot.go.twitterchiver.archiver;
twitterchiver-archiver-docker = depot.go.twitterchiver.archiver.dockerImage; twitterchiver-archiver-docker = depot.go.twitterchiver.archiver.dockerImage;

View file

@ -7,4 +7,5 @@ args: {
twitternuke = import ./twitternuke args; twitternuke = import ./twitternuke args;
minotarproxy = import ./minotarproxy args; minotarproxy = import ./minotarproxy args;
streetworks = import ./streetworks args; streetworks = import ./streetworks args;
trains = import ./trains args;
} }

View file

@ -0,0 +1,221 @@
package main
import (
"context"
"flag"
"fmt"
"log"
"github.com/aws/aws-sdk-go/aws"
awscreds "github.com/aws/aws-sdk-go/aws/credentials"
awssession "github.com/aws/aws-sdk-go/aws/session"
"github.com/go-stomp/stomp/v3"
pgx "github.com/jackc/pgx/v4"
"github.com/jlaffaye/ftp"
"gocloud.dev/blob"
"gocloud.dev/blob/s3blob"
"hg.lukegb.com/lukegb/depot/go/trains/darwin"
"hg.lukegb.com/lukegb/depot/go/trains/darwin/darwindb"
"hg.lukegb.com/lukegb/depot/go/trains/darwin/darwingest"
"hg.lukegb.com/lukegb/depot/go/trains/darwin/darwingest/darwingeststomp"
)
var (
cleanStart = flag.Bool("clean_start", false, "Start by downloading all timetabling and reference data and restart from snapshot")
awsAccessKey = requiredString("s3_access_key", "Darwin XML timetable S3 access key")
awsAccessSecret = requiredString("s3_access_secret", "Darwin XML timetable S3 access secret")
awsBucket = flag.String("s3_bucket", "darwin.xmltimetable", "Darwin XML timetable S3 bucket")
awsPrefix = flag.String("s3_prefix", "PPTimetable/", "Darwin XML timetable S3 bucket key prefix")
postgresString = flag.String("pg_string", "host=/var/run/postgresql database=trains search_path=darwindb", "String to connect to PG")
stompHostport = requiredString("stomp_address", "Darwin STOMP address (host:port)")
stompUsername = requiredString("stomp_username", "Darwin STOMP username")
stompPassword = requiredString("stomp_password", "Darwin STOMP password")
stompClientID = requiredString("stomp_client_id", "Darwin STOMP client ID")
stompTopic = flag.String("stomp_topic", "/topic/darwin.pushport-v16", "Darwin STOMP topic")
ftpHostport = requiredString("ftp_address", "Darwin FTP address (host:port)")
ftpUsername = requiredString("ftp_username", "Darwin FTP username")
ftpPassword = requiredString("ftp_password", "Darwin FTP password")
requiredFlags []string
)
func requiredString(name, descr string) *string {
requireFlag(name)
return flag.String(name, "", descr)
}
func requireFlag(names ...string) {
requiredFlags = append(requiredFlags, names...)
}
func checkRequiredFlags() {
for _, fname := range requiredFlags {
f := flag.CommandLine.Lookup(fname)
if f == nil {
log.Fatalf("required flag %q doesn't actually exist", fname)
}
v := f.Value.(flag.Getter).Get()
if v == nil {
log.Fatalf("required flag %q not set", fname)
}
if v, ok := v.(string); ok && v == "" {
log.Fatalf("required flag %q not set or empty", fname)
}
}
}
func main() {
flag.Parse()
checkRequiredFlags()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
awsSess, err := awssession.NewSession(&aws.Config{
Region: aws.String("eu-west-1"),
Credentials: awscreds.NewStaticCredentials(*awsAccessKey, *awsAccessSecret, ""),
})
if err != nil {
log.Fatalf("awssession.NewSession: %w", err)
}
s3Bucket, err := s3blob.OpenBucket(ctx, awsSess, *awsBucket, nil)
if err != nil {
log.Fatalf("s3blob.OpenBucket: %w", err)
}
defer s3Bucket.Close()
bucket := blob.PrefixedBucket(s3Bucket, *awsPrefix)
defer bucket.Close()
pc, err := pgx.Connect(ctx, *postgresString)
if err != nil {
log.Fatalf("pgx.Connect: %v", err)
}
defer pc.Close(context.Background())
catchupTx, err := pc.Begin(ctx)
if err != nil {
log.Fatalf("beginning catchup transaction: %v", err)
}
catchupAffected := darwindb.NewAffected()
gimmeTransaction := func(ctx context.Context, cb func(ctx context.Context, tx pgx.Tx) error) error {
if catchupTx != nil {
return cb(ctx, catchupTx)
}
tx, err := pc.Begin(ctx)
if err != nil {
return fmt.Errorf("starting transaction: %w", err)
}
defer tx.Rollback(ctx)
if err := cb(ctx, tx); err != nil {
return err
}
return tx.Commit(ctx)
}
sendAffectedNotifications := func(ctx context.Context, tx pgx.Tx, affected *darwindb.Affected) error {
for tsid := range affected.TSIDs {
if _, err := tx.Exec(ctx, fmt.Sprintf(`NOTIFY "tsid-%d"`, tsid)); err != nil {
return fmt.Errorf("notifying channel for tsid %d: %w", tsid, err)
}
}
return nil
}
processMessage := func(ctx context.Context, pp *darwin.PushPort) error {
return gimmeTransaction(ctx, func(ctx context.Context, tx pgx.Tx) error {
affected := catchupAffected
sendAffected := false
if affected == nil {
affected = darwindb.NewAffected()
sendAffected = true
}
if err := darwindb.Process(ctx, tx, pp, affected); err != nil {
return fmt.Errorf("darwindb.Process: %w", err)
}
if sendAffected {
return sendAffectedNotifications(ctx, tx, affected)
}
return nil
})
}
cfg := darwingest.Config{
Verbose: true,
DialStomp: func(ctx context.Context) (*stomp.Conn, error) {
return darwingeststomp.Dial(ctx, darwingeststomp.Config{
Hostname: *stompHostport,
Username: *stompUsername,
Password: *stompPassword,
ClientID: *stompClientID,
})
},
StompSubscribe: func(ctx context.Context, c *stomp.Conn) (*stomp.Subscription, error) {
return c.Subscribe(*stompTopic, stomp.AckClientIndividual)
},
DialFTP: func(ctx context.Context) (*ftp.ServerConn, error) {
sc, err := ftp.Dial(*ftpHostport, ftp.DialWithContext(ctx))
if err != nil {
return nil, fmt.Errorf("ftp.Dial: %w", err)
}
if err := sc.Login(*ftpUsername, *ftpPassword); err != nil {
sc.Quit()
return nil, fmt.Errorf("ftp.ServerConn.Login: %w", err)
}
return sc, nil
},
Timetable: func(ppt *darwin.PushPortTimetable) error {
return gimmeTransaction(ctx, func(ctx context.Context, tx pgx.Tx) error {
if err := darwindb.ProcessTimetable(ctx, tx, ppt, catchupAffected); err != nil {
return fmt.Errorf("processing timetable data: %w", err)
}
return nil
})
},
ReferenceData: func(pprd *darwin.PushPortReferenceData) error {
return gimmeTransaction(ctx, func(ctx context.Context, tx pgx.Tx) error {
if err := darwindb.ProcessReferenceData(ctx, tx, pprd, catchupAffected); err != nil {
return fmt.Errorf("processing reference data: %w", err)
}
return nil
})
},
MessageCatchUp: func(pp *darwin.PushPort) error {
return processMessage(ctx, pp)
},
CatchUpComplete: func(ctx context.Context) error {
log.Printf("catchup affected: %v", catchupAffected.Summary())
if err := sendAffectedNotifications(ctx, catchupTx, catchupAffected); err != nil {
return fmt.Errorf("sending affected notifications: %v", err)
}
if err := catchupTx.Commit(ctx); err != nil {
return fmt.Errorf("committing catchup transaction: %v", err)
}
catchupAffected = nil
catchupTx = nil
return nil
},
Message: func(pp *darwin.PushPort) error {
return processMessage(ctx, pp)
},
}
if *cleanStart {
if err := darwingest.CatchUpAndStart(ctx, bucket, cfg); err != nil {
log.Printf("CatchUpAndStart: %v", err)
}
} else {
if err := darwingest.Start(ctx, bucket, cfg); err != nil {
log.Printf("Start: %v", err)
}
}
log.Println("terminating...")
}

View file

@ -0,0 +1,24 @@
# SPDX-FileCopyrightText: 2020 Luke Granger-Brown <depot@lukegb.com>
#
# SPDX-License-Identifier: Apache-2.0
{ depot, ... }:
depot.third_party.buildGo.program {
name = "darwiningestd";
srcs = [ ./darwiningestd.go ];
deps = with depot.third_party; [
gopkgs."github.com".aws.aws-sdk-go.aws
gopkgs."github.com".aws.aws-sdk-go.aws.credentials
gopkgs."github.com".aws.aws-sdk-go.aws.session
gopkgs."github.com".go-stomp.stomp.v3
gopkgs."github.com".jackc.pgx.v4
gopkgs."github.com".jlaffaye.ftp
gopkgs."gocloud.dev".blob
gopkgs."gocloud.dev".blob.s3blob
depot.go.trains.darwin
depot.go.trains.darwin.darwindb
depot.go.trains.darwin.darwingest
depot.go.trains.darwin.darwingest.darwingeststomp
];
}

View file

@ -0,0 +1,463 @@
package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"log"
"net/http"
"net/url"
"regexp"
"strconv"
"time"
"github.com/jackc/pgx/v4"
"github.com/jackc/pgx/v4/pgxpool"
"hg.lukegb.com/lukegb/depot/go/trains/webapi"
)
type querier interface {
Query(ctx context.Context, sql string, args ...interface{}) (pgx.Rows, error)
QueryRow(ctx context.Context, sql string, args ...interface{}) pgx.Row
}
func summarizeService(ctx context.Context, pc querier, id int) (*webapi.ServiceData, error) {
row := pc.QueryRow(ctx, `
SELECT
ts.id, ts.rid, ts.uid, ts.rsid, ts.headcode, ts.scheduled_start_date::varchar,
ts.train_operator, rop.name, rop.url,
COALESCE(ts.delay_reason_code::int, 0), COALESCE(rlr.text, ''), COALESCE(ts.delay_reason_tiploc, ''), COALESCE(rlrloc.name, ''), rlrloc.crs, COALESCE(rlrloc.toc, ''), rlrop.name, rlrop.url, COALESCE(ts.delay_reason_tiploc_near, false),
COALESCE(ts.cancellation_reason_code::int, 0), COALESCE(rlr.text, ''), COALESCE(ts.cancellation_reason_tiploc, ''), COALESCE(rlrloc.name, ''), rlrloc.crs, COALESCE(rlrloc.toc, ''), rlrop.name, rlrop.url, COALESCE(ts.cancellation_reason_tiploc_near, false),
ts.active, ts.deleted, ts.cancelled
FROM
train_services ts
LEFT JOIN ref_tocs rop ON rop.toc=ts.train_operator
LEFT JOIN ref_late_running_reasons rlr ON rlr.code=ts.delay_reason_code::int
LEFT JOIN ref_locations rlrloc ON rlrloc.tiploc=ts.delay_reason_tiploc
LEFT JOIN ref_tocs rlrop ON rlrop.toc=rlrloc.toc
LEFT JOIN ref_cancel_reasons rc ON rc.code=ts.cancellation_reason_code::int
LEFT JOIN ref_locations rcloc ON rcloc.tiploc=ts.cancellation_reason_tiploc
LEFT JOIN ref_tocs rcop ON rcop.toc=rcloc.toc
WHERE
ts.id=$1
`, id)
sd := webapi.ServiceData{
DelayReason: &webapi.DisruptionReason{Location: &webapi.Location{TOC: &webapi.TrainOperator{}}},
CancelReason: &webapi.DisruptionReason{Location: &webapi.Location{TOC: &webapi.TrainOperator{}}},
}
if err := row.Scan(
&sd.ID, &sd.RID, &sd.UID, &sd.RSID, &sd.Headcode, &sd.StartDate,
&sd.TrainOperator.Code, &sd.TrainOperator.Name, &sd.TrainOperator.URL,
&sd.DelayReason.Code, &sd.DelayReason.Text, &sd.DelayReason.Location.TIPLOC, &sd.DelayReason.Location.Name, &sd.DelayReason.Location.CRS, &sd.DelayReason.Location.TOC.Code, &sd.DelayReason.Location.TOC.Name, &sd.DelayReason.Location.TOC.URL, &sd.DelayReason.NearLocation,
&sd.CancelReason.Code, &sd.CancelReason.Text, &sd.CancelReason.Location.TIPLOC, &sd.CancelReason.Location.Name, &sd.CancelReason.Location.CRS, &sd.CancelReason.Location.TOC.Code, &sd.CancelReason.Location.TOC.Name, &sd.CancelReason.Location.TOC.URL, &sd.CancelReason.NearLocation,
&sd.Active, &sd.Deleted, &sd.Cancelled,
); err != nil {
return nil, fmt.Errorf("reading from train_services for %d: %w", id, err)
}
sd.Canonicalize()
// Now for the locations.
rows, err := pc.Query(ctx, `
SELECT
tl.id,
tl.tiploc, COALESCE(rloc.name, ''), COALESCE(rloc.crs, ''), COALESCE(rloc.toc, ''), rloctoc.name, rloctoc.url,
tl.calling_point::varchar, tl.train_length, tl.service_suppressed,
tl.schedule_cancelled,
COALESCE(tl.schedule_platform, ''), COALESCE(tl.platform, ''), COALESCE(tl.platform_confirmed, false), COALESCE(tl.platform_suppressed, false),
tl.schedule_false_destination_tiploc, COALESCE(rfdloc.name, ''), COALESCE(rfdloc.crs, ''), COALESCE(rfdloc.toc, ''), rfdloctoc.name, rfdloctoc.url,
tl.schedule_public_arrival, tl.schedule_working_arrival, tl.estimated_arrival, tl.working_estimated_arrival, tl.actual_arrival,
tl.schedule_public_departure, tl.schedule_working_departure, tl.estimated_departure, tl.working_estimated_departure, tl.actual_departure,
/* no public_pass */ tl.schedule_working_pass, tl.estimated_pass, tl.working_estimated_pass, tl.actual_pass
FROM
train_locations tl
LEFT JOIN ref_locations rloc ON rloc.tiploc=tl.tiploc
LEFT JOIN ref_tocs rloctoc ON rloctoc.toc=rloc.toc
LEFT JOIN ref_locations rfdloc ON rfdloc.tiploc=tl.schedule_false_destination_tiploc
LEFT JOIN ref_tocs rfdloctoc ON rfdloctoc.toc=rfdloc.toc
WHERE
tl.tsid=$1 AND tl.active_in_schedule
ORDER BY
COALESCE(tl.schedule_working_arrival, tl.schedule_working_pass, tl.schedule_working_departure)
`, id)
if err != nil {
return nil, fmt.Errorf("querying locations for %d: %w", id, err)
}
defer rows.Close()
for rows.Next() {
loc := webapi.ServiceLocation{
Location: &webapi.Location{TOC: &webapi.TrainOperator{}},
Platform: &webapi.PlatformData{},
FalseDestination: &webapi.Location{TOC: &webapi.TrainOperator{}},
ArrivalTiming: &webapi.TimingData{},
DepartureTiming: &webapi.TimingData{},
PassTiming: &webapi.TimingData{},
}
if err := rows.Scan(
&loc.ID,
&loc.Location.TIPLOC, &loc.Location.Name, &loc.Location.CRS, &loc.Location.TOC.Code, &loc.Location.TOC.Name, &loc.Location.TOC.URL,
&loc.CallingPointType, &loc.Length, &loc.Suppressed,
&loc.Cancelled,
&loc.Platform.Scheduled, &loc.Platform.Live, &loc.Platform.Confirmed, &loc.Platform.Suppressed,
&loc.FalseDestination.TIPLOC, &loc.FalseDestination.Name, &loc.FalseDestination.CRS, &loc.FalseDestination.TOC.Code, &loc.FalseDestination.TOC.Name, &loc.FalseDestination.TOC.URL,
&loc.ArrivalTiming.PublicScheduled, &loc.ArrivalTiming.WorkingScheduled, &loc.ArrivalTiming.PublicEstimated, &loc.ArrivalTiming.WorkingEstimated, &loc.ArrivalTiming.Actual,
&loc.DepartureTiming.PublicScheduled, &loc.DepartureTiming.WorkingScheduled, &loc.DepartureTiming.PublicEstimated, &loc.DepartureTiming.WorkingEstimated, &loc.DepartureTiming.Actual,
/*&loc.PassTiming.PublicScheduled,*/ &loc.PassTiming.WorkingScheduled, &loc.PassTiming.PublicEstimated, &loc.PassTiming.WorkingEstimated, &loc.PassTiming.Actual,
); err != nil {
return nil, fmt.Errorf("scanning locations for %d: %w", id, err)
}
loc.Canonicalize()
sd.Locations = append(sd.Locations, loc)
}
if rows.Err() != nil {
return nil, fmt.Errorf("iterating over locations for %d: %w", id, err)
}
return &sd, nil
}
type watchingResponseWriter struct {
http.ResponseWriter
http.Flusher
WrittenTo bool
WroteStatus int
WroteContentBytes int
}
func (rw *watchingResponseWriter) Flush() {
if rw.Flusher != nil {
rw.Flusher.Flush()
}
}
func (rw *watchingResponseWriter) Header() http.Header { return rw.ResponseWriter.Header() }
func (rw *watchingResponseWriter) Write(bs []byte) (int, error) {
if !rw.WrittenTo {
rw.WroteStatus = http.StatusOK
}
rw.WrittenTo = true
rw.WroteContentBytes += len(bs)
return rw.ResponseWriter.Write(bs)
}
func (rw *watchingResponseWriter) WriteHeader(statusCode int) {
if rw.WrittenTo {
return
}
rw.WrittenTo = true
rw.WroteStatus = statusCode
rw.ResponseWriter.WriteHeader(statusCode)
}
type httpError struct {
err error
httpStatusCode int
publicError string
}
func (he httpError) Error() string {
if he.err == nil {
return fmt.Sprintf("HTTP %d: %v", he.httpStatusCode, he.publicError)
}
return he.err.Error()
}
type server struct {
dbPool *pgxpool.Pool
}
var jsonPathRegexp = regexp.MustCompile(`/([1-9][0-9]*)$`)
func (s *server) handleJSON(ctx context.Context, rw http.ResponseWriter, r *http.Request) error {
// /<id>
ms := jsonPathRegexp.FindStringSubmatch(r.URL.Path)
if len(ms) != 2 {
return httpError{
httpStatusCode: http.StatusNotFound,
publicError: "not found",
}
}
id, err := strconv.Atoi(ms[1])
if err != nil {
return httpError{
err: err,
httpStatusCode: http.StatusBadRequest,
publicError: "bad id",
}
}
conn, err := s.dbPool.Acquire(ctx)
if err != nil {
return fmt.Errorf("acquiring database connection: %w", err)
}
defer conn.Release()
svc, err := summarizeService(ctx, conn, id)
if errors.Is(err, pgx.ErrNoRows) {
return httpError{
err: err,
httpStatusCode: http.StatusNotFound,
publicError: "no train with that ID",
}
} else if err != nil {
return err
}
rw.Header().Set("Content-Type", "application/json; charset=utf-8")
if err := json.NewEncoder(rw).Encode(svc); err != nil {
return fmt.Errorf("encoding JSON: %w", err)
}
return nil
}
var channelTIDRegex = regexp.MustCompile(`tsid-([0-9]+)`)
func (s *server) handleEventStream(ctx context.Context, rw http.ResponseWriter, r *http.Request) error {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
if r.URL.Path != "/" {
return httpError{
httpStatusCode: http.StatusNotFound,
publicError: "not found",
}
}
qs, err := url.ParseQuery(r.URL.RawQuery)
if err != nil {
return httpError{
err: err,
httpStatusCode: http.StatusBadRequest,
publicError: "can't parse query string",
}
}
var ids []int
for _, idStr := range qs["id"] {
id, err := strconv.Atoi(idStr)
if err != nil {
return httpError{
err: err,
httpStatusCode: http.StatusBadRequest,
publicError: "id parameter bad",
}
}
ids = append(ids, id)
}
if len(ids) == 0 {
return httpError{
httpStatusCode: http.StatusBadRequest,
publicError: "need at least one ?id= to watch",
}
}
listenConn, err := s.dbPool.Acquire(ctx)
if err != nil {
return fmt.Errorf("acquiring database connection: %w", err)
}
defer listenConn.Release()
for _, id := range ids {
_, err := listenConn.Exec(ctx, fmt.Sprintf(`LISTEN "tsid-%d"`, id))
if err != nil {
return fmt.Errorf("watching for %d: %w", id, err)
}
}
rw.Header().Set("Content-Type", "text/event-stream")
rw.Header().Set("Cache-Control", "no-cache")
je := json.NewEncoder(rw)
encodeSvc := func(conn querier, id int) error {
svc, err := summarizeService(ctx, conn, id)
if errors.Is(err, pgx.ErrNoRows) {
if _, err := fmt.Fprintf(rw, "event: notyet\ndata: %d\n\n", id); err != nil {
return err
}
} else if err != nil {
return fmt.Errorf("summarizeService(%d): %w", id, err)
}
if _, err := fmt.Fprint(rw, "data: "); err != nil {
return fmt.Errorf("writing data: prefix for service %d: %w", id, err)
}
if err := je.Encode(svc); err != nil {
return fmt.Errorf("je.Encode service %d: %w", id, err)
}
if _, err := fmt.Fprint(rw, "\n\n"); err != nil {
return fmt.Errorf("printing separator after service %d: %w", id, err)
}
if f, ok := rw.(http.Flusher); ok {
f.Flush()
}
return nil
}
for _, id := range ids {
if err := encodeSvc(listenConn, id); err != nil {
return fmt.Errorf("initial data for service %d: %w", err)
}
}
type updatedMsg struct {
TID int
Err error
}
updatedCh := make(chan updatedMsg)
go func() {
defer close(updatedCh)
for {
n, err := listenConn.Conn().WaitForNotification(ctx)
if err != nil {
select {
case <-ctx.Done():
return
case updatedCh <- updatedMsg{Err: err}:
}
}
ms := channelTIDRegex.FindStringSubmatch(n.Channel)
if len(ms) != 2 {
log.Printf("unrecognised notification channel %q", n.Channel)
continue
}
id, err := strconv.Atoi(ms[1])
if err != nil {
log.Printf("failed to parse TID from notification channel %q: %v", n.Channel, err)
continue
}
select {
case <-ctx.Done():
return
case updatedCh <- updatedMsg{TID: id}:
}
}
}()
pingCh := time.NewTicker(1 * time.Minute)
defer pingCh.Stop()
for {
select {
case <-ctx.Done():
return ctx.Err()
case <-pingCh.C:
if _, err := fmt.Fprint(rw, "event: ping\n\n"); err != nil {
return err
}
if f, ok := rw.(http.Flusher); ok {
f.Flush()
}
case umsg, ok := <-updatedCh:
if !ok {
log.Printf("updatedCh closed, shutting down conn")
return nil
}
if umsg.Err != nil {
return umsg.Err
}
sconn, err := s.dbPool.Acquire(ctx)
if err != nil {
return fmt.Errorf("acquiring database connection: %w", err)
}
err = encodeSvc(sconn, umsg.TID)
sconn.Release()
if err != nil {
return err
}
}
}
}
func (s *server) handleHTTP(ctx context.Context, rw http.ResponseWriter, r *http.Request) error {
accept := r.Header.Get("Accept")
switch accept {
case "application/json":
return s.handleJSON(ctx, rw, r)
case "text/event-stream":
return s.handleEventStream(ctx, rw, r)
default:
return httpError{
httpStatusCode: http.StatusNotAcceptable,
publicError: "Accept should be text/event-stream or application/json",
}
}
}
func (s *server) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
wrw := &watchingResponseWriter{ResponseWriter: rw, Flusher: rw.(http.Flusher)}
if err := s.handleHTTP(r.Context(), wrw, r); err != nil {
statusCode := http.StatusInternalServerError
errText := fmt.Sprintf("internal server error: %v", err.Error())
var he httpError
if errors.As(err, &he) {
if he.httpStatusCode != 0 {
statusCode = he.httpStatusCode
}
if he.publicError != "" {
errText = he.publicError
}
}
log.Printf("[%v] %v %v -- %v (%d %v)", r.RemoteAddr, r.Method, r.RequestURI, err, statusCode, errText)
if !wrw.WrittenTo {
for k := range wrw.Header() {
wrw.Header().Del(k)
}
wrw.Header().Set("Content-Type", "text/plain")
wrw.WriteHeader(statusCode)
fmt.Fprintf(wrw, "%s\n", errText)
}
} else {
log.Printf("[%v] %v %v -- (%d) %d bytes", r.RemoteAddr, r.Method, r.RequestURI, wrw.WroteStatus, wrw.WroteContentBytes)
}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
cfg, err := pgxpool.ParseConfig("host=/var/run/postgresql database=trains search_path=darwindb")
if err != nil {
log.Fatalf("pgxpool.ParseConfig: %v", err)
}
cfg.AfterRelease = func(pc *pgx.Conn) bool {
// true to return to pool, false to destroy
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
_, err := pc.Exec(ctx, "UNLISTEN *")
if err != nil {
log.Printf("pg connection cleanup: UNLISTEN failed: %v", err)
return false
}
return true
}
pcpool, err := pgxpool.ConnectConfig(ctx, cfg)
if err != nil {
log.Fatalf("pgxpool.ConnectConfig: %v", err)
}
defer pcpool.Close()
s := &server{
dbPool: pcpool,
}
listen := ":13974"
log.Printf("listening on %s", listen)
log.Fatal(http.ListenAndServe(listen, s))
}

View file

@ -0,0 +1,15 @@
# SPDX-FileCopyrightText: 2020 Luke Granger-Brown <depot@lukegb.com>
#
# SPDX-License-Identifier: Apache-2.0
{ depot, ... }:
depot.third_party.buildGo.program {
name = "db2web";
srcs = [ ./db2web.go ];
deps = with depot.third_party; [
gopkgs."github.com".jackc.pgx.v4
gopkgs."github.com".jackc.pgx.v4.pgxpool
depot.go.trains.webapi
];
}

17
go/trains/cmd/default.nix Normal file
View file

@ -0,0 +1,17 @@
# SPDX-FileCopyrightText: 2020 Luke Granger-Brown <depot@lukegb.com>
#
# SPDX-License-Identifier: Apache-2.0
{ depot, ... }@args:
rec {
darwiningestd = import ./darwiningestd args;
db2web = import ./db2web args;
rttingest = import ./rttingest args;
train2livesplit = import ./train2livesplit args;
bins = {
inherit darwiningestd db2web train2livesplit;
inherit (rttingest) tiploc;
};
}

View file

@ -0,0 +1,15 @@
# SPDX-FileCopyrightText: 2020 Luke Granger-Brown <depot@lukegb.com>
#
# SPDX-License-Identifier: Apache-2.0
{ depot, ... }:
{
tiploc = depot.third_party.buildGo.program {
name = "tiploc";
srcs = [ ./tiploc.go ];
deps = with depot.third_party; [
gopkgs."github.com".jackc.pgx.v4
];
};
}

View file

@ -0,0 +1,131 @@
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
pgx "github.com/jackc/pgx/v4"
)
func fetchWork(ctx context.Context, pc *pgx.Conn) ([]string, error) {
rows, err := pc.Query(ctx, `SELECT tiploc FROM ref_locations WHERE name=tiploc AND override_name IS NULL`)
if err != nil {
return nil, fmt.Errorf("querying for work: %w", err)
}
defer rows.Close()
var tiplocs []string
for rows.Next() {
var tiploc string
if err := rows.Scan(&tiploc); err != nil {
return nil, fmt.Errorf("scanning row: %w", err)
}
tiplocs = append(tiplocs, tiploc)
}
if rows.Err() != nil {
return nil, fmt.Errorf("retrieving rows for work: %w", err)
}
return tiplocs, nil
}
type RTT struct {
Username, Password string
}
type RTTLocationDetail struct {
TIPLOC string `json:"tiploc"`
CRS string `json:"crs"`
Name string `json:"name"`
Description string `json:"description"`
}
type RTTLocationContainer struct {
Location *RTTLocationDetail `json:"location"`
}
func (r RTT) LocationDetail(ctx context.Context, inp string) (*RTTLocationDetail, error) {
req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://api.rtt.io/api/v1/json/search/%v", inp), nil)
if err != nil {
return nil, fmt.Errorf("formulating query for %q: %w", inp, err)
}
req.SetBasicAuth(r.Username, r.Password)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, fmt.Errorf("querying API for %q: %w", inp, err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("status for %q was %d %v; wanted 200 OK", inp, resp.StatusCode, resp.Status)
}
var cont RTTLocationContainer
if err := json.NewDecoder(resp.Body).Decode(&cont); err != nil {
return nil, fmt.Errorf("unmarshalling API response for %q: %w", inp, err)
}
return cont.Location, nil
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
pc, err := pgx.Connect(ctx, "host=/var/run/postgresql database=trains search_path=darwindb")
if err != nil {
log.Fatalf("pgx.Connect: %v", err)
}
defer pc.Close(context.Background())
tiplocs, err := fetchWork(ctx, pc)
if err != nil {
log.Fatalf("fetchWork: %v", err)
}
log.Printf("%d tiplocs to process", len(tiplocs))
r := RTT{Username: "rttapi_lukegb", Password: "5c1e5ae8a88882b090cd2e55225a3f0f581ec57c"}
var rttApiErrCount int
for n, t := range tiplocs {
if rttApiErrCount == 20 {
log.Println("Too many errors from RTT, stopping")
break
}
if n%20 == 0 {
log.Printf("%d/%d (%d%%)", n, len(tiplocs), (n*100)/len(tiplocs))
}
d, err := r.LocationDetail(ctx, t)
if err != nil {
log.Printf("LocationDetail(ctx, %q): %v", t, err)
rttApiErrCount++
continue
}
if d == nil {
log.Printf("got a nil location for %q!", t)
rttApiErrCount++
continue
}
rttApiErrCount = 0 // reset the breaker
if d.Name == "" {
log.Printf("No name for %q", t)
continue
}
ct, err := pc.Exec(ctx, `UPDATE ref_locations SET name=$2, override_name=$2, override_name_src='RTT' WHERE tiploc=$1 AND override_name IS NULL AND name=tiploc`, t, d.Name)
if err != nil {
log.Printf("Updating database for %q failed: %w", t, err)
break
}
if ra := ct.RowsAffected(); ra != 1 {
log.Printf("Updated %d rows for %q; wanted 1???", ra, t)
}
}
log.Println("terminating...")
}

View file

@ -0,0 +1,12 @@
# SPDX-FileCopyrightText: 2020 Luke Granger-Brown <depot@lukegb.com>
#
# SPDX-License-Identifier: Apache-2.0
{ depot, ... }:
depot.third_party.buildGo.program {
name = "train2livesplit";
srcs = [ ./train2livesplit.go ];
deps = with depot.third_party; [
depot.go.trains.webapi
];
}

View file

@ -0,0 +1,247 @@
package main
import (
"bufio"
"context"
"encoding/json"
"flag"
"fmt"
"log"
"net/http"
"os"
"strings"
"text/template"
"time"
"hg.lukegb.com/lukegb/depot/go/trains/webapi"
)
var (
header = template.Must(template.New("script").Parse(`
startscript C#
this["model"].Reset();
var run = this["state"].Run;
run.CategoryName = "{{if .Service.Headcode}}{{.Service.Headcode}} {{end}}{{.FirstLocation.DepartureTiming.PublicScheduled.Format "1504"}} {{.FirstLocation.Location.Name}} to {{.LastLocation.Location.Name}}";
run.GameName = "{{.Service.StartDate}}";
run.AttemptCount = 0;
run.AttemptHistory.Clear();
run.AutoSplitter = null;
run.HasChanged = true;
run.Clear();
var ass = run.GetType().Assembly;
var timingMethodRealTime = Enum.GetValues(ass.GetType("LiveSplit.Model.TimingMethod"))[0];
this["state"].CurrentTimingMethod = timingMethodRealTime;
var timeStampType = ass.GetType("LiveSplit.Model.TimeStamp");
var atomicDateTime = ass.GetType("LiveSplit.Model.AtomicDateTime"); // (System.DateTime, synced)
var runExtensionsType = ass.GetType("LiveSplit.Model.RunExtensions");
var runExtensionsAddSegment = runExtensionsType.GetMethod("AddSegment");
var timeType = ass.GetType("LiveSplit.Model.Time");
var defaultTime = Activator.CreateInstance(timeType);
var lastSplitTime = defaultTime;
Func<int, object> timeFromSeconds = delegate (int seconds) {
if (seconds < 0) {
return defaultTime;
}
var time = Activator.CreateInstance(timeType);
time.RealTime = TimeSpan.FromSeconds(seconds);
return time;
};
Action<string, int, int> addSplit = delegate (string name, int timetableSeconds, int realSeconds) {
var timetableTime = Convert.ChangeType(timeFromSeconds(timetableSeconds), timeType);
var lastTimetableTime = Convert.ChangeType(timeFromSeconds(0), timeType);
if (this["state"].Run.Count > 0) {
lastTimetableTime = this["state"].Run[this["state"].Run.Count-1].PersonalBestSplitTime;
}
var timetableSegmentTime = timetableTime - lastTimetableTime;
var realTime = timeFromSeconds(realSeconds);
runExtensionsAddSegment.Invoke(null, new object[] { run, name, timetableTime, timetableSegmentTime, null, realTime, null });
lastSplitTime = this["state"].Run[this["state"].Run.Count-1].SplitTime;
};
// run, name, pbSplitTime, bestSegmentTime, icon, splitTime, segmentHistory
{{.AddSplitFunctions}}
endscript
`))
trainId = flag.Int("train_id", -1, "Train ID")
endpoint = flag.String("endpoint", "http://localhost:13974", "db2web endpoint")
)
type templateVars struct {
Service *webapi.ServiceData
FirstLocation *webapi.ServiceLocation
LastLocation *webapi.ServiceLocation
AddSplitFunctions string
}
func fetchTrainOnce(ctx context.Context, id int) (*webapi.ServiceData, error) {
req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/%d", *endpoint, id), nil)
if err != nil {
return nil, fmt.Errorf("constructing new request for %d: %w", id, err)
}
req.Header.Set("Accept", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, fmt.Errorf("doing request for %d: %w", id, err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("http status for %d was %d %v", id, resp.StatusCode, resp.Status)
}
var sd webapi.ServiceData
if err := json.NewDecoder(resp.Body).Decode(&sd); err != nil {
return nil, fmt.Errorf("decoding train %d: %w", id, err)
}
return &sd, nil
}
func generateLiveSplitScriptData(sd *webapi.ServiceData) (*templateVars, error) {
asf := new(strings.Builder)
currentSplit := -1
usingTime := func(td *webapi.TimingData) time.Time {
if td.PublicScheduled != nil {
return *td.PublicScheduled
}
return *td.WorkingScheduled
}
timetableDepartureTime := usingTime(sd.Locations[0].DepartureTiming)
var lastHadActual bool
for n, l := range sd.Locations {
var usingData *webapi.TimingData
switch {
case l.DepartureTiming != nil:
usingData = l.DepartureTiming
case l.PassTiming != nil:
usingData = l.PassTiming
case l.ArrivalTiming != nil:
usingData = l.ArrivalTiming
}
usingTimetableTime := usingTime(usingData)
var secondsOnTimetable, secondsActual int
secondsOnTimetable = int((usingTimetableTime.Sub(timetableDepartureTime)) / time.Second)
secondsActual = -1
if lastHadActual {
currentSplit = n
lastHadActual = false
}
if usingData.Actual != nil {
secondsActual = int((usingData.Actual.Sub(timetableDepartureTime)) / time.Second)
lastHadActual = true
}
fmt.Fprintf(asf, "addSplit(%q, %d, %d);\n", l.Location.Name, secondsOnTimetable, secondsActual)
}
if lastHadActual {
fmt.Fprintf(asf, `
this["state"].Run.Offset = TimeSpan.FromSeconds(%d);
this["model"].Start();
this["state"].CurrentSplitIndex = %d-1;
this["model"].Split();
this["state"].Run[%d-1].SplitTime = lastSplitTime;
`, (time.Now().Sub(timetableDepartureTime))/time.Second, len(sd.Locations), len(sd.Locations))
} else {
fmt.Fprintf(asf, `
this["state"].Run.Offset = TimeSpan.FromSeconds(%d);
this["model"].Start();
this["state"].CurrentSplitIndex = %d;
`, (time.Now().Sub(timetableDepartureTime))/time.Second, currentSplit)
}
return &templateVars{
Service: sd,
FirstLocation: &sd.Locations[0],
LastLocation: &sd.Locations[len(sd.Locations)-1],
AddSplitFunctions: asf.String(),
}, nil
}
func fetchTrainAndOutputLiveSplit(ctx context.Context, id int) error {
req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/?id=%d", *endpoint, id), nil)
if err != nil {
return fmt.Errorf("constructing new request for %d: %w", id, err)
}
req.Header.Set("Accept", "text/event-stream")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return fmt.Errorf("doing request for %d: %w", id, err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("http status for %d was %d %v", id, resp.StatusCode, resp.Status)
}
sc := bufio.NewScanner(resp.Body)
var buffer []string
for sc.Scan() {
buffer = append(buffer, sc.Text())
if buffer[0] == "" {
buffer = buffer[1:]
}
fmt.Fprintf(os.Stderr, "%#v\n\n", buffer)
if len(buffer) < 2 || buffer[len(buffer)-1] != "" {
continue
}
if strings.HasPrefix(buffer[0], "event:") {
buffer = nil
continue
}
var xs []string
for _, s := range buffer {
xs = append(xs, strings.TrimPrefix(s, "data: "))
}
var sd webapi.ServiceData
if err := json.Unmarshal([]byte(strings.Join(xs, "")), &sd); err != nil {
return err
}
tv, err := generateLiveSplitScriptData(&sd)
if err != nil {
return err
}
if err := header.Execute(os.Stdout, tv); err != nil {
return err
}
buffer = nil
}
return nil
}
func main() {
flag.Parse()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
log.Fatal(fetchTrainAndOutputLiveSplit(ctx, *trainId))
sd, err := fetchTrainOnce(ctx, 33634)
if err != nil {
log.Fatal(err)
}
tv, err := generateLiveSplitScriptData(sd)
if err != nil {
log.Fatal(err)
}
if err := header.Execute(os.Stdout, tv); err != nil {
log.Fatal(err)
}
}

View file

@ -0,0 +1,309 @@
DROP SCHEMA darwindb CASCADE;
CREATE SCHEMA darwindb;
SET search_path TO darwindb;
CREATE TABLE train_services (
id BIGSERIAL NOT NULL PRIMARY KEY,
rid VARCHAR(16) NOT NULL UNIQUE,
uid VARCHAR(6) NOT NULL,
rsid VARCHAR(8) NOT NULL,
headcode VARCHAR(4) NOT NULL,
scheduled_start_date DATE NOT NULL,
effective_start_time TIMESTAMPTZ NULL, /* computed for ease of use */
train_operator VARCHAR(2) NOT NULL,
status VARCHAR(2) NOT NULL DEFAULT 'P',
train_category VARCHAR(2) NOT NULL DEFAULT 'OO',
is_passenger_svc BOOLEAN NOT NULL DEFAULT TRUE,
is_charter BOOLEAN NOT NULL DEFAULT FALSE,
is_q_train BOOLEAN NOT NULL DEFAULT FALSE,
delay_reason_code VARCHAR NULL,
delay_reason_tiploc VARCHAR NULL,
delay_reason_tiploc_near BOOLEAN NULL,
cancellation_reason_code VARCHAR NULL,
cancellation_reason_tiploc VARCHAR NULL,
cancellation_reason_tiploc_near BOOLEAN NULL,
active BOOLEAN NOT NULL DEFAULT TRUE,
deleted BOOLEAN NOT NULL DEFAULT FALSE,
cancelled BOOLEAN NOT NULL DEFAULT FALSE
);
CREATE INDEX train_services_by_ssd_uid ON train_services (scheduled_start_date, uid);
CREATE INDEX train_services_by_ssd_rsid ON train_services (scheduled_start_date, rsid);
CREATE INDEX train_services_by_ssd_headcode ON train_services (scheduled_start_date, headcode);
CREATE TYPE timestamp_type AS ENUM ('est', 'act');
CREATE TYPE platform_source AS ENUM ('planned', 'automatic', 'manual');
CREATE TYPE calling_point_type AS ENUM ('OR', 'OPOR', 'IP', 'OPIP', 'PP', 'DT', 'OPDT');
CREATE TABLE train_locations (
id BIGSERIAL NOT NULL PRIMARY KEY,
tsid BIGINT NOT NULL REFERENCES train_services (id) ON DELETE CASCADE,
rid VARCHAR(16) NOT NULL REFERENCES train_services (rid) ON DELETE CASCADE,
tiploc VARCHAR(7) NOT NULL,
-- From Darwin Schedule
calling_point calling_point_type NULL,
schedule_public_arrival TIMESTAMPTZ NULL,
schedule_working_arrival TIMESTAMPTZ NULL,
schedule_public_departure TIMESTAMPTZ NULL,
schedule_working_departure TIMESTAMPTZ NULL,
-- No concept of a "public pass"
schedule_working_pass TIMESTAMPTZ NULL,
schedule_route_delay SMALLINT NULL,
schedule_activity VARCHAR(12) NULL,
schedule_planned_activity VARCHAR(12) NULL,
schedule_cancelled BOOLEAN NOT NULL DEFAULT false,
schedule_false_destination_tiploc VARCHAR(7) NULL,
schedule_platform VARCHAR(5) NULL,
-- Indicates whether this appeared in a schedule we've seen previously, but not in the latest version.
active_in_schedule BOOLEAN NOT NULL DEFAULT true,
-- From Darwin TrainStatus
service_suppressed BOOLEAN NOT NULL DEFAULT false,
platform VARCHAR(5) NULL,
platform_suppressed BOOLEAN NOT NULL DEFAULT false,
cis_platform_suppression BOOLEAN NOT NULL DEFAULT false,
platform_source platform_source NOT NULL DEFAULT 'planned',
platform_confirmed BOOLEAN NOT NULL DEFAULT false,
train_length SMALLINT NULL,
-- estimated, working, actual
-- arrival, departure, pass
estimated_arrival TIMESTAMPTZ NULL,
working_estimated_arrival TIMESTAMPTZ NULL,
actual_arrival TIMESTAMPTZ NULL,
arrival_data JSONB NULL,
estimated_departure TIMESTAMPTZ NULL,
working_estimated_departure TIMESTAMPTZ NULL,
actual_departure TIMESTAMPTZ NULL,
departure_data JSONB NULL,
estimated_pass TIMESTAMPTZ NULL,
working_estimated_pass TIMESTAMPTZ NULL,
actual_pass TIMESTAMPTZ NULL,
pass_data JSONB NULL,
CONSTRAINT train_locations_tiploc_wtd UNIQUE (tsid, tiploc, schedule_working_departure),
CONSTRAINT train_locations_tiploc_ptd UNIQUE (tsid, tiploc, schedule_public_departure),
CONSTRAINT train_locations_tiploc_wta UNIQUE (tsid, tiploc, schedule_working_arrival),
CONSTRAINT train_locations_tiploc_pta UNIQUE (tsid, tiploc, schedule_public_arrival),
CONSTRAINT train_locations_tiploc_wtp UNIQUE (tsid, tiploc, schedule_working_pass)
);
CREATE INDEX train_locations_by_tsid ON train_locations (tsid, tiploc);
CREATE INDEX train_locations_by_rid ON train_locations (rid, tiploc);
CREATE FUNCTION train_locations_upsert_from_schedule (
p_tsid BIGINT,
p_rid VARCHAR(16),
p_tiploc VARCHAR(7),
p_calling_point calling_point_type,
p_schedule_public_arrival TIMESTAMPTZ,
p_schedule_working_arrival TIMESTAMPTZ,
p_schedule_public_departure TIMESTAMPTZ,
p_schedule_working_departure TIMESTAMPTZ,
p_schedule_working_pass TIMESTAMPTZ,
p_schedule_route_delay SMALLINT,
p_schedule_activity VARCHAR(12),
p_schedule_planned_activity VARCHAR(12),
p_schedule_cancelled BOOLEAN,
p_schedule_false_destination_tiploc VARCHAR(7),
p_schedule_platform VARCHAR(3)) RETURNS BIGINT AS
$$
-- There is one problem with this function, which is why it's not generic and using ON CONFLICT DO UPDATE:
-- if we notice that one particular constraint has caused a problem, so we do an update, and _that_ update causes a problem
-- then we're stuck; in this instance we'll just explode.
--
-- Imagine the following (unlikely) case:
-- Old schedule: (tsid, tiploc, wtd, ptd):
-- (1, "AAAAAA", 01:00, 01:00)
-- (1, "AAAAAA", 01:10, 01:10)
-- If we then try to update with a new schedule location (1, "AAAAAA", 01:00, 01:10), we cause a conflict.
BEGIN
LOOP
DECLARE v_locid BIGINT;
DECLARE v_constraint_name TEXT;
BEGIN
INSERT INTO train_locations
(tsid, rid, tiploc, calling_point,
schedule_public_arrival, schedule_working_arrival,
schedule_public_departure, schedule_working_departure,
schedule_working_pass,
schedule_route_delay, schedule_activity, schedule_planned_activity, schedule_cancelled,
schedule_false_destination_tiploc, schedule_platform)
VALUES
(p_tsid, p_rid, p_tiploc, p_calling_point,
p_schedule_public_arrival, p_schedule_working_arrival,
p_schedule_public_departure, p_schedule_working_departure,
p_schedule_working_pass,
p_schedule_route_delay, p_schedule_activity, p_schedule_planned_activity, p_schedule_cancelled,
p_schedule_false_destination_tiploc, p_schedule_platform)
RETURNING id INTO v_locid;
RETURN v_locid;
EXCEPTION WHEN unique_violation THEN
-- Work out which constraint caused this.
GET STACKED DIAGNOSTICS v_constraint_name = CONSTRAINT_NAME;
CASE v_constraint_name
WHEN 'train_locations_tiploc_wtd' THEN
UPDATE train_locations SET
calling_point=p_calling_point,
schedule_public_arrival=p_schedule_public_arrival, schedule_working_arrival=p_schedule_working_arrival,
schedule_public_departure=p_schedule_public_departure, /* wtd */
schedule_working_pass=p_schedule_working_pass,
schedule_route_delay=p_schedule_route_delay, schedule_activity=p_schedule_activity, schedule_planned_activity=p_schedule_planned_activity, schedule_cancelled=p_schedule_cancelled,
schedule_false_destination_tiploc=p_schedule_false_destination_tiploc, schedule_platform=p_schedule_platform,
active_in_schedule=true
WHERE
tsid=p_tsid AND tiploc=p_tiploc AND schedule_working_departure=p_schedule_working_departure
RETURNING id INTO v_locid;
IF found THEN RETURN v_locid; END IF;
-- We didn't find it? Try again.
WHEN 'train_locations_tiploc_ptd' THEN
UPDATE train_locations SET
calling_point=p_calling_point,
schedule_public_arrival=p_schedule_public_arrival, schedule_working_arrival=p_schedule_working_arrival,
/* ptd */ schedule_working_departure=p_schedule_working_departure,
schedule_working_pass=p_schedule_working_pass,
schedule_route_delay=p_schedule_route_delay, schedule_activity=p_schedule_activity, schedule_planned_activity=p_schedule_planned_activity, schedule_cancelled=p_schedule_cancelled,
schedule_false_destination_tiploc=p_schedule_false_destination_tiploc, schedule_platform=p_schedule_platform,
active_in_schedule=true
WHERE
tsid=p_tsid AND tiploc=p_tiploc AND schedule_public_departure=p_schedule_public_departure
RETURNING id INTO v_locid;
IF found THEN RETURN v_locid; END IF;
-- We didn't find it? Try again.
WHEN 'train_locations_tiploc_wta' THEN
UPDATE train_locations SET
calling_point=p_calling_point,
schedule_public_arrival=p_schedule_public_arrival, /* wta */
schedule_public_departure=p_schedule_public_departure, schedule_working_departure=p_schedule_working_departure,
schedule_working_pass=p_schedule_working_pass,
schedule_route_delay=p_schedule_route_delay, schedule_activity=p_schedule_activity, schedule_planned_activity=p_schedule_planned_activity, schedule_cancelled=p_schedule_cancelled,
schedule_false_destination_tiploc=p_schedule_false_destination_tiploc, schedule_platform=p_schedule_platform,
active_in_schedule=true
WHERE
tsid=p_tsid AND tiploc=p_tiploc AND schedule_working_arrival=p_schedule_working_arrival
RETURNING id INTO v_locid;
IF found THEN RETURN v_locid; END IF;
-- We didn't find it? Try again.
WHEN 'train_locations_tiploc_pta' THEN
UPDATE train_locations SET
calling_point=p_calling_point,
/* pta */ schedule_working_arrival=p_schedule_working_arrival,
schedule_public_departure=p_schedule_public_departure, schedule_working_departure=p_schedule_working_departure,
schedule_working_pass=p_schedule_working_pass,
schedule_route_delay=p_schedule_route_delay, schedule_activity=p_schedule_activity, schedule_planned_activity=p_schedule_planned_activity, schedule_cancelled=p_schedule_cancelled,
schedule_false_destination_tiploc=p_schedule_false_destination_tiploc, schedule_platform=p_schedule_platform,
active_in_schedule=true
WHERE
tsid=p_tsid AND tiploc=p_tiploc AND schedule_public_arrival=p_schedule_public_arrival
RETURNING id INTO v_locid;
IF found THEN RETURN v_locid; END IF;
-- We didn't find it? Try again.
WHEN 'train_locations_tiploc_wtp' THEN
UPDATE train_locations SET
calling_point=p_calling_point,
schedule_public_arrival=p_schedule_public_arrival, schedule_working_arrival=p_schedule_working_arrival,
schedule_public_departure=p_schedule_public_departure, schedule_working_departure=p_schedule_working_departure,
/* wtp */
schedule_route_delay=p_schedule_route_delay, schedule_activity=p_schedule_activity, schedule_planned_activity=p_schedule_planned_activity, schedule_cancelled=p_schedule_cancelled,
schedule_false_destination_tiploc=p_schedule_false_destination_tiploc, schedule_platform=p_schedule_platform,
active_in_schedule=true
WHERE
tsid=p_tsid AND tiploc=p_tiploc AND schedule_working_pass=p_schedule_working_pass
RETURNING id INTO v_locid;
IF found THEN RETURN v_locid; END IF;
-- We didn't find it? Try again.
WHEN 'train_locations_tiploc_wtd' THEN
UPDATE train_locations SET
calling_point=p_calling_point,
schedule_public_arrival=p_schedule_public_arrival, schedule_working_arrival=p_schedule_working_arrival,
schedule_public_departure=p_schedule_public_departure, schedule_working_departure=p_schedule_working_departure,
schedule_working_pass=p_schedule_working_pass,
schedule_route_delay=p_schedule_route_delay, schedule_activity=p_schedule_activity, schedule_planned_activity=p_schedule_planned_activity, schedule_cancelled=p_schedule_cancelled,
schedule_false_destination_tiploc=p_schedule_false_destination_tiploc, schedule_platform=p_schedule_platform,
active_in_schedule=true
WHERE
tsid=p_tsid AND tiploc=p_tiploc AND schedule_working_departure=p_schedule_working_departure
RETURNING id INTO v_locid;
IF found THEN RETURN v_locid; END IF;
-- We didn't find it? Try again.
ELSE
RAISE; -- don't know how to deal with this particular constraint.
END CASE;
END;
END LOOP;
END;
$$
LANGUAGE plpgsql;
CREATE TABLE train_alerts (
id BIGSERIAL NOT NULL PRIMARY KEY,
alert_id BIGINT NOT NULL,
source VARCHAR(10) NOT NULL,
alert_text TEXT NOT NULL,
audience VARCHAR(15) NOT NULL,
alert_type VARCHAR(15) NOT NULL,
send_alert_by_sms BOOLEAN NOT NULL,
send_alert_by_email BOOLEAN NOT NULL,
send_alert_by_twitter BOOLEAN NOT NULL
);
CREATE TABLE train_alert_services (
id BIGSERIAL NOT NULL PRIMARY KEY,
ta_id BIGINT NOT NULL REFERENCES train_alerts (id) ON DELETE CASCADE,
service_id BIGINT NOT NULL REFERENCES train_services (id) ON DELETE CASCADE,
crs VARCHAR(3) NULL,
CONSTRAINT train_alert_services_by_service_id_crs UNIQUE (service_id, crs, ta_id)
);
CREATE TABLE ref_tocs (
toc VARCHAR(2) PRIMARY KEY NOT NULL,
name VARCHAR(256) NOT NULL,
url VARCHAR(512) NULL
);
CREATE TABLE ref_locations (
tiploc VARCHAR(7) PRIMARY KEY NOT NULL,
name VARCHAR(30) NULL,
override_name VARCHAR(30) NULL,
crs VARCHAR(3) NULL,
toc VARCHAR(2) NULL
);
CREATE INDEX ref_locations_by_crs ON ref_locations (crs);
CREATE TABLE ref_cancel_reasons (
code INT NOT NULL PRIMARY KEY,
text VARCHAR(256) NOT NULL
);
CREATE TABLE ref_late_running_reasons (
code INT NOT NULL PRIMARY KEY,
text VARCHAR(256) NOT NULL
);
CREATE TABLE ref_via (
id BIGSERIAL NOT NULL PRIMARY KEY,
at_crs VARCHAR(3) NOT NULL,
dest_tiploc VARCHAR(7) NOT NULL,
loc1_tiploc VARCHAR(7) NOT NULL,
loc2_tiploc VARCHAR(7) NOT NULL DEFAULT '',
text VARCHAR(256) NOT NULL,
CONSTRAINT ref_via_by_at_dest_loc1_loc2 UNIQUE (at_crs, dest_tiploc, loc1_tiploc, loc2_tiploc)
);
CREATE TABLE ref_cis (
code VARCHAR(4) NOT NULL PRIMARY KEY,
name VARCHAR(60) NOT NULL
);

View file

@ -0,0 +1,65 @@
package darwindb
import (
"fmt"
"strings"
)
// Affected tracks the TIPLOCs, RIDs and TSIDs which have been updated.
type Affected struct {
TIPLOCs map[string]bool
RIDs map[string]bool
TSIDs map[int]bool
}
// NewAffected creates a new Affected instance.
func NewAffected() *Affected {
return &Affected{
TIPLOCs: make(map[string]bool),
RIDs: make(map[string]bool),
TSIDs: make(map[int]bool),
}
}
// TIPLOC marks the given TIPLOC code as affected.
func (a *Affected) TIPLOC(t string) {
if a != nil {
a.TIPLOCs[t] = true
}
}
// RID marks the given train RID identifier as affected.
func (a *Affected) RID(r string) {
if a != nil {
a.RIDs[r] = true
}
}
// TSID marks the given train service ID (internal ID) as affected.
func (a *Affected) TSID(t int) {
if a != nil {
a.TSIDs[t] = true
}
}
// Summary returns a string summary of affected assets.
func (a *Affected) Summary() string {
if a == nil {
return "change tracking disabled"
}
var s []string
if len(a.TIPLOCs) > 0 {
s = append(s, fmt.Sprintf("%d TIPLOCs", len(a.TIPLOCs)))
}
if len(a.RIDs) > 0 {
s = append(s, fmt.Sprintf("%d RIDs", len(a.RIDs)))
}
if len(a.TSIDs) > 0 {
s = append(s, fmt.Sprintf("%d TSIDs", len(a.TSIDs)))
}
if len(s) == 0 {
return "no changes"
}
return fmt.Sprintf("%v", strings.Join(s, ", "))
}

View file

@ -0,0 +1,71 @@
package darwindb
import (
"context"
"fmt"
"log"
pgx "github.com/jackc/pgx/v4"
"hg.lukegb.com/lukegb/depot/go/trains/darwin"
)
// handleDataResponse handles a Darwin DataResponse message (i.e. a SnapshotResponse or a UpdateResponse).
func handleDataResponse(ctx context.Context, tx pgx.Tx, dr *darwin.DataResponse, a *Affected) error {
for _, s := range dr.Schedule {
if err := handleSchedule(ctx, tx, &s, a, scheduleModeOverwrite); err != nil {
return fmt.Errorf("handling schedule for RID %v: %w", s.RID, err)
}
}
for _, ts := range dr.TrainStatus {
if err := handleTrainStatus(ctx, tx, &ts, a); err != nil {
return fmt.Errorf("handling trainstatus for RID %v: %w", ts.RID, err)
}
}
for _, ds := range dr.Deactivated {
var tid int
row := tx.QueryRow(ctx, "UPDATE train_services SET active=FALSE WHERE rid=$1 RETURNING id", ds.RID)
if err := row.Scan(&tid); err == pgx.ErrNoRows {
log.Printf("deactivated schedule for unknown RID %v", ds.RID)
} else if err != nil {
return fmt.Errorf("handling deactivateschedule for RID %v: %w", ds.RID, err)
}
a.RID(ds.RID)
a.TSID(tid)
}
return nil
}
// ProcessTimetable updates the database by processing a PportTimetable message in the given transaction.
func ProcessTimetable(ctx context.Context, tx pgx.Tx, ppt *darwin.PushPortTimetable, a *Affected) error {
for _, s := range ppt.Journey {
if err := handleSchedule(ctx, tx, &s, a, scheduleModeIgnoreExisting); err != nil {
return fmt.Errorf("handling timetable journey for RID %v: %w", s.RID, err)
}
}
return nil
}
// Process updates the database by processing a single PushPort message in the given transaction.
func Process(ctx context.Context, tx pgx.Tx, pp *darwin.PushPort, a *Affected) error {
drs := make([]*darwin.DataResponse, 0, len(pp.SnapshotResp)+len(pp.UpdateResp))
for _, r := range pp.SnapshotResp {
drs = append(drs, &r.DataResponse)
}
for _, r := range pp.UpdateResp {
drs = append(drs, &r.DataResponse)
}
if len(drs) == 0 {
// Short-circuit if we don't have anything we're interested in - which is very unlikely.
return nil
}
for _, dr := range drs {
if err := handleDataResponse(ctx, tx, dr, a); err != nil {
return fmt.Errorf("handling data response: %w", err)
}
}
return nil
}

View file

@ -0,0 +1,103 @@
package darwindb
import (
"context"
"fmt"
pgx "github.com/jackc/pgx/v4"
"hg.lukegb.com/lukegb/depot/go/trains/darwin"
)
// ProcessReferenceData updates the database by processing a PportReferenceData message in the given transaction.
func ProcessReferenceData(ctx context.Context, tx pgx.Tx, pprd *darwin.PushPortReferenceData, a *Affected) error {
crsToTIPLOC := make(map[string]string)
for _, loc := range pprd.Locations {
if loc.CRS != nil {
crsToTIPLOC[*loc.CRS] = loc.TIPLOC
}
_, err := tx.Exec(ctx, `
INSERT INTO ref_locations
(tiploc, name, crs, toc)
VALUES
($1, $2, $3, $4)
ON CONFLICT (tiploc) DO UPDATE
SET name=COALESCE(ref_locations.override_name, $2), crs=$3, toc=$4
`, loc.TIPLOC, loc.Name, loc.CRS, loc.TOC)
if err != nil {
return fmt.Errorf("updating location %q: %w", loc.TIPLOC, err)
}
a.TIPLOC(loc.TIPLOC)
}
for _, toc := range pprd.TOCs {
_, err := tx.Exec(ctx, `
INSERT INTO ref_tocs
(toc, name, url)
VALUES
($1, $2, $3)
ON CONFLICT (toc) DO UPDATE
SET name=$2, url=$3
`, toc.TOC, toc.Name, toc.URL)
if err != nil {
return fmt.Errorf("updating TOC %q: %w", toc.TOC, err)
}
}
for _, r := range pprd.LateRunningReasons {
_, err := tx.Exec(ctx, `
INSERT INTO ref_late_running_reasons
(code, text)
VALUES
($1, $2)
ON CONFLICT (code) DO UPDATE
SET text=$2
`, r.Code, r.Text)
if err != nil {
return fmt.Errorf("updating LateRunningReason %q: %w", r.Code, err)
}
}
for _, r := range pprd.CancellationReasons {
_, err := tx.Exec(ctx, `
INSERT INTO ref_cancel_reasons
(code, text)
VALUES
($1, $2)
ON CONFLICT (code) DO UPDATE
SET text=$2
`, r.Code, r.Text)
if err != nil {
return fmt.Errorf("updating CancellationReason %q: %w", r.Code, err)
}
}
for _, v := range pprd.Via {
_, err := tx.Exec(ctx, `
INSERT INTO ref_via
(at_crs, dest_tiploc, loc1_tiploc, loc2_tiploc, text)
VALUES
($1, $2, $3, $4, $5)
ON CONFLICT (at_crs, dest_tiploc, loc1_tiploc, loc2_tiploc) DO UPDATE
SET text=$5
RETURNING at_crs
`, v.AtCRS, v.DestTIPLOC, v.Loc1TIPLOC, v.Loc2TIPLOC, v.Text)
if err != nil {
return fmt.Errorf("updating Via %v/%v/%v/%v: %w", v.AtCRS, v.DestTIPLOC, v.Loc1TIPLOC, v.Loc2TIPLOC, err)
}
tiploc, ok := crsToTIPLOC[v.AtCRS]
if ok {
a.TIPLOC(tiploc)
}
}
for _, cis := range pprd.CISSource {
_, err := tx.Exec(ctx, `
INSERT INTO ref_cis
(code, name)
VALUES
($1, $2)
ON CONFLICT (code) DO UPDATE
SET name=$2
`, cis.CISCode, cis.Name)
if err != nil {
return fmt.Errorf("updating CISSource %v: %w", cis.CISCode, err)
}
}
return nil
}

View file

@ -0,0 +1,112 @@
package darwindb
import (
"context"
"encoding/json"
"fmt"
"log"
pgx "github.com/jackc/pgx/v4"
"hg.lukegb.com/lukegb/depot/go/trains/darwin"
)
type scheduleMode int
const (
scheduleModeOverwrite scheduleMode = iota
scheduleModeIgnoreExisting
)
// handleSchedule processes a Darwin Schedule.
func handleSchedule(ctx context.Context, tx pgx.Tx, s *darwin.Schedule, a *Affected, sm scheduleMode) error {
startTS, err := startTime(s)
if err != nil {
return fmt.Errorf("finding start time for RID %v: %w", s.RID, err)
}
var cancelCode, cancelTIPLOC *string
var cancelTIPLOCNear *bool
if s.CancellationReason != nil {
cancelCode = &s.CancellationReason.Code
cancelTIPLOC = &s.CancellationReason.TIPLOC
cancelTIPLOCNear = &s.CancellationReason.Near
}
sql := `
INSERT INTO train_services
(rid, uid, rsid, headcode, scheduled_start_date, effective_start_time,
train_operator, status, train_category, is_passenger_svc, is_charter, is_q_train,
cancellation_reason_code, cancellation_reason_tiploc, cancellation_reason_tiploc_near,
active, deleted, cancelled)
VALUES
($1, $2, $3, $4, $5, $6,
$7, COALESCE($8, 'P'), COALESCE($9, 'OO'), COALESCE($10, true), COALESCE($11, false), COALESCE($12, false),
$13, $14, $15,
COALESCE($16, true), $17, COALESCE($18, false))
ON CONFLICT (rid)`
switch sm {
case scheduleModeOverwrite:
sql += ` DO UPDATE SET
uid=$2, rsid=$3, headcode=$4, scheduled_start_date=$5, effective_start_time=$6,
train_operator=$7, status=COALESCE($8, 'P'), train_category=COALESCE($9, 'OO'), is_passenger_svc=COALESCE($10, true), is_charter=COALESCE($11, false), is_q_train=COALESCE($12, train_services.is_q_train),
cancellation_reason_code=$13, cancellation_reason_tiploc=$14, cancellation_reason_tiploc_near=$15,
active=COALESCE($16, 'true'), deleted=$17, cancelled=COALESCE($18, 'false')
`
case scheduleModeIgnoreExisting:
sql += ` DO NOTHING
`
}
sql += `RETURNING id`
row := tx.QueryRow(ctx, sql,
s.RID, s.UID, s.RSID, s.TrainID, s.SSD, startTS,
s.TOC, s.Status, s.TrainCat, s.IsPassengerSvc, s.IsCharter, s.IsQTrain,
cancelCode, cancelTIPLOC, cancelTIPLOCNear,
s.IsActive, s.Deleted, s.Cancelled)
var serviceID int
if err := row.Scan(&serviceID); err == pgx.ErrNoRows && sm == scheduleModeIgnoreExisting {
// The row existed already.
return nil
} else if err != nil {
log.Printf("inserting schedule for RID %v: %v", s.RID, err)
return fmt.Errorf("inserting schedule for RID %v: %w", s.RID, err)
}
a.RID(s.RID)
a.TSID(serviceID)
// Insert/update all the locations.
var activeLocations []int
for _, cp := range s.CallingPoints {
var locid int
row := tx.QueryRow(ctx, `
SELECT train_locations_upsert_from_schedule(
p_tsid => $1, p_rid => $2, p_tiploc => $3, p_calling_point => $4,
p_schedule_public_arrival => $5, p_schedule_working_arrival => $6,
p_schedule_public_departure => $7, p_schedule_working_departure => $8,
p_schedule_working_pass => $9,
p_schedule_route_delay => $10, p_schedule_activity => $11, p_schedule_planned_activity => $12, p_schedule_cancelled => $13,
p_schedule_false_destination_tiploc => $14, p_schedule_platform => $15)
`,
serviceID, s.RID, cp.TIPLOC, cp.XMLName.Local,
timeToTimestamp(cp.PTA, startTS), timeToTimestamp(cp.WTA, startTS),
timeToTimestamp(cp.PTD, startTS), timeToTimestamp(cp.WTD, startTS),
timeToTimestamp(cp.WTP, startTS),
cp.RDelay, cp.Act, cp.PlanAct, cp.Cancelled,
cp.FD, cp.Platform,
)
if err := row.Scan(&locid); err != nil {
log.Printf("updating location %v from RID %v/my ID %v: %v", cp.TIPLOC, s.RID, serviceID, err)
zz, _ := json.MarshalIndent(s, "", " ")
log.Println(string(zz))
zz, _ = json.MarshalIndent(cp, "", " ")
log.Println(string(zz))
return fmt.Errorf("updating location %v from RID %v/my ID %v: %w", cp.TIPLOC, s.RID, serviceID, err)
} else {
activeLocations = append(activeLocations, locid)
}
a.TIPLOC(cp.TIPLOC)
}
if _, err := tx.Exec(ctx, "UPDATE train_locations SET active_in_schedule=false WHERE tsid=$1 AND NOT (id = ANY ($2))", serviceID, activeLocations); err != nil {
return fmt.Errorf("marking old locations as removed from schedule for RID %v/my ID %v (active locations: %v): %w", s.RID, serviceID, activeLocations, err)
}
fmt.Printf("s")
return nil
}

View file

@ -0,0 +1,130 @@
package darwindb
import (
"context"
"fmt"
"log"
"strings"
"time"
pgx "github.com/jackc/pgx/v4"
"hg.lukegb.com/lukegb/depot/go/trains/darwin"
)
// handleTrainStatus handles a Darwin "TS" (train status) message.
func handleTrainStatus(ctx context.Context, tx pgx.Tx, ts *darwin.TrainStatus, a *Affected) error {
var tsid int
var tsEST time.Time
if ts.LateReason != nil {
row := tx.QueryRow(ctx, `
UPDATE train_services
SET delay_reason_code=$2, delay_reason_tiploc=$3, delay_reason_tiploc_near=$4
WHERE rid=$1
RETURNING id, effective_start_time
`, ts.RID, ts.LateReason.Code, ts.LateReason.TIPLOC, ts.LateReason.Near)
if err := row.Scan(&tsid, &tsEST); err == pgx.ErrNoRows {
log.Printf("got TrainStatus (delayed) for RID %v which isn't known", ts.RID)
return nil
} else if err != nil {
return fmt.Errorf("updating delay reason: %w", err)
}
} else {
row := tx.QueryRow(ctx, "SELECT id, effective_start_time FROM train_services WHERE rid=$1", ts.RID)
if err := row.Scan(&tsid, &tsEST); err == pgx.ErrNoRows {
log.Printf("got TrainStatus for RID %v which isn't known", ts.RID)
return nil
} else if err != nil {
return fmt.Errorf("fetching train_services ID: %w", err)
}
}
a.RID(ts.RID)
a.TSID(tsid)
for _, l := range ts.Location {
// OK, this is a bit hairy. We only get updates for things which have _changed_.
var els []interface{}
addEl := func(x interface{}) int {
els = append(els, x)
return len(els)
}
addSet := func(s string, x interface{}) string {
return fmt.Sprintf("%s=$%d", s, addEl(x))
}
addTSTimeData := func(prefix string, td *darwin.TSTimeData) []string {
if td == nil {
return nil
}
x := []interface{}{
"estimated_%s", timeToTimestamp(td.ET, tsEST),
"working_estimated_%s", timeToTimestamp(td.WET, tsEST),
"actual_%s", timeToTimestamp(td.AT, tsEST),
"%s_data", td,
}
out := make([]string, len(x)/2)
for n := 0; n < len(out); n++ {
out[n] = fmt.Sprintf(x[n*2].(string)+"=$%d", prefix, addEl(x[n*2+1]))
}
return out
}
var setClause []string
setClause = append(setClause, addTSTimeData("arrival", l.Arr)...)
setClause = append(setClause, addTSTimeData("departure", l.Dep)...)
setClause = append(setClause, addTSTimeData("pass", l.Pass)...)
if l.Plat != nil {
setClause = append(setClause, addSet("platform", l.Plat.Platform))
setClause = append(setClause, addSet("platform_suppressed", l.Plat.PlatSup))
setClause = append(setClause, addSet("cis_platform_suppression", l.Plat.CisPlatSup))
platsrc := map[string]string{
"P": "planned",
"A": "automatic",
"M": "manual",
"": "planned",
}[l.Plat.PlatSrc]
setClause = append(setClause, addSet("platform_source", platsrc))
setClause = append(setClause, addSet("platform_confirmed", l.Plat.Conf))
}
if l.Suppr != nil {
setClause = append(setClause, addSet("service_suppressed", *l.Suppr))
}
if l.Length != nil {
setClause = append(setClause, addSet("train_length", *l.Length))
}
if len(setClause) == 0 {
// Nothing to set for this location.
continue
}
var whereClause []string
for columnName, tsValue := range map[string]string{
"schedule_public_arrival": l.PTA,
"schedule_working_arrival": l.WTA,
"schedule_public_departure": l.PTD,
"schedule_working_departure": l.WTD,
"schedule_working_pass": l.WTP,
} {
if tsValue == "" {
continue
}
whereClause = append(whereClause, addSet(columnName, timeToTimestamp(tsValue, tsEST)))
}
if len(whereClause) == 0 {
return fmt.Errorf("RID %v/my ID %v at TIPLOC %v: ended up with no CircularTimes-based WHERE", ts.RID, tsid, l.TIPLOC)
}
whereClause = append(whereClause, addSet("tiploc", l.TIPLOC))
whereClause = append(whereClause, addSet("tsid", tsid))
query := fmt.Sprintf("UPDATE train_locations SET %s WHERE active_in_schedule AND %s", strings.Join(setClause, ", "), strings.Join(whereClause, " AND "))
cmd, err := tx.Exec(ctx, query, els...)
if err != nil {
log.Printf("RID %v/my ID %v at TIPLOC %v: failed to update with query %q: %v", ts.RID, tsid, l.TIPLOC, query, err)
return fmt.Errorf("RID %v/my ID %v at TIPLOC %v: failed to update with query %q: %w", ts.RID, tsid, l.TIPLOC, query, err)
}
a.TIPLOC(l.TIPLOC)
if cmd.RowsAffected() != 1 {
return fmt.Errorf("RID %v/my ID %v at TIPLOC %v: query %q: wanted to update 1 row, updated %d", ts.RID, tsid, l.TIPLOC, query, cmd.RowsAffected())
}
}
fmt.Printf("t")
return nil
}

View file

@ -0,0 +1,21 @@
# SPDX-FileCopyrightText: 2020 Luke Granger-Brown <depot@lukegb.com>
#
# SPDX-License-Identifier: Apache-2.0
{ depot, ... }@args:
depot.third_party.buildGo.package {
name = "darwindb";
srcs = [
./affected.go
./darwindb.go
./ddbref.go
./ddbschedule.go
./ddbtrainstatus.go
./tsutil.go
];
path = "hg.lukegb.com/lukegb/depot/go/trains/darwin/darwindb";
deps = with depot.third_party; [
gopkgs."github.com".jackc.pgx.v4
depot.go.trains.darwin
];
}

View file

@ -0,0 +1,74 @@
package darwindb
import (
"fmt"
"time"
"hg.lukegb.com/lukegb/depot/go/trains/darwin"
)
// secondsify adds :00 onto the end of a string if it's HH:MM formatted, otherwise does nothing.
func secondsify(ts string) string {
if len(ts) == 5 { // xx:xx (no seconds)
return ts + ":00"
}
return ts
}
// startTime takes a train and ascertains its "start time".
func startTime(s *darwin.Schedule) (time.Time, error) {
// We assume these are sensibly sorted by Darwin.
if len(s.CallingPoints) == 0 {
return time.Time{}, fmt.Errorf("no calling points")
}
cp := s.CallingPoints[0]
var ts string
tsCandidates := []string{cp.WTD, cp.PTD, cp.WTP, cp.WTA, cp.PTA}
for _, tsc := range tsCandidates {
if tsc != "" {
ts = tsc
}
}
if ts == "" {
return time.Time{}, fmt.Errorf("failed to find a suitable timestamp in first calling point")
}
ts = fmt.Sprintf("%v %v", s.SSD, secondsify(ts))
t, err := time.ParseInLocation("2006-01-02 15:04:05", ts, darwin.London)
if err != nil {
return time.Time{}, fmt.Errorf("parsing start time %q: %w", ts, err)
}
return t, nil
}
// timeToTimestamp takes a string HH:MM and places it appropriately given the approximate 'start time' of the train.
func timeToTimestamp(ts string, startTime time.Time) *time.Time {
if ts == "" {
return nil
}
ts = secondsify(ts)
t, err := time.ParseInLocation("2006-01-02 15:04:05", startTime.Format("2006-01-02 ")+ts, darwin.London)
if err != nil {
return nil
}
// Follow Darwin rules for determining time skips
// https://wiki.openraildata.com/index.php/Darwin:Schedule_Element#Ordering
difference := t.Sub(startTime)
switch {
case difference > -6*time.Hour && difference < 18*time.Hour:
return &t
case difference <= -6*time.Hour:
// e.g. 08:00 start time, 01:00 time "now" (-7 hrs) -- add a day
t = t.AddDate(0, 0, 1)
return &t
case difference >= 18*time.Hour:
// e.g. 01:00 start time, 22:00 time "now" (+18 hrs) -- sub a day
t = t.AddDate(0, 0, -1)
return &t
}
panic(fmt.Sprintf("unhandled difference %v", difference))
}

View file

@ -0,0 +1,310 @@
package darwingest
import (
"bytes"
"compress/gzip"
"context"
"encoding/xml"
"fmt"
"io"
"log"
"strings"
"time"
"github.com/cenkalti/backoff/v4"
"github.com/go-stomp/stomp/v3"
"github.com/jlaffaye/ftp"
"gocloud.dev/blob"
"hg.lukegb.com/lukegb/depot/go/trains/darwin"
"hg.lukegb.com/lukegb/depot/go/trains/darwin/darwingest/darwingestftp"
"hg.lukegb.com/lukegb/depot/go/trains/darwin/darwingest/darwingests3"
)
func degzip(b []byte) ([]byte, error) {
gzr, err := gzip.NewReader(bytes.NewBuffer(b))
if err != nil {
return nil, fmt.Errorf("gzip.NewReader: %w", err)
}
d, err := io.ReadAll(gzr)
if err != nil {
return nil, fmt.Errorf("io.ReadAll: %w", err)
}
return d, nil
}
func unmarshalGzipPushPort(m []byte) (*darwin.PushPort, error) {
d, err := degzip(m)
if err != nil {
return nil, fmt.Errorf("degzip: %w", err)
}
var pp darwin.PushPort
if err := xml.Unmarshal(d, &pp); err != nil {
return nil, fmt.Errorf("xml.Unmarshal(%q): %w", string(d), err)
}
return &pp, nil
}
func ridToDate(rid string) (time.Time, error) {
t, err := time.ParseInLocation("20060102", rid[0:8], darwin.London)
if err != nil {
return time.Time{}, err
}
if t.Hour() < 2 || (t.Hour() == 2 && t.Minute() < 45) {
t = t.AddDate(0, 0, -1)
}
return t, nil
}
type Config struct {
Verbose bool
DialStomp func(ctx context.Context) (*stomp.Conn, error)
StompSubscribe func(ctx context.Context, sc *stomp.Conn) (*stomp.Subscription, error)
DialFTP func(ctx context.Context) (*ftp.ServerConn, error)
Message func(pp *darwin.PushPort) error
MessageCatchUp func(pp *darwin.PushPort) error
Timetable func(ppt *darwin.PushPortTimetable) error
ReferenceData func(pprd *darwin.PushPortReferenceData) error
CatchUpComplete func(ctx context.Context) error
}
func Start(ctx context.Context, bucket *blob.Bucket, cfg Config) error {
return run(ctx, bucket, cfg, false)
}
func CatchUpAndStart(ctx context.Context, bucket *blob.Bucket, cfg Config) error {
return run(ctx, bucket, cfg, true)
}
func run(ctx context.Context, bucket *blob.Bucket, cfg Config, runCatchup bool) error {
handleMsg := func(cb func(*darwin.PushPort) error) func(*darwin.PushPort) error {
return func(pp *darwin.PushPort) error {
for _, ttid := range pp.TimeTableID {
fn := strings.TrimSpace(ttid.Filename)
if fn != "" {
log.Printf("informed that there's a new timetable at %q", fn)
ppt, err := darwingests3.LoadTimetable(ctx, bucket, fn)
if err == darwingests3.ErrBadVersion {
continue
}
if err != nil {
return fmt.Errorf("LoadTimetable(%q): %w", fn, err)
}
if err := cfg.Timetable(ppt); err != nil {
return fmt.Errorf("Timetable(%q): %w", fn, err)
}
}
fn = strings.TrimSpace(ttid.ReferenceFilename)
if fn != "" {
log.Printf("informed that there's a new refsheet at %q", fn)
rd, err := darwingests3.LoadReferenceData(ctx, bucket, fn)
if err == darwingests3.ErrBadVersion {
continue
}
if err != nil {
return fmt.Errorf("LoadReferenceData(%q): %w", fn, err)
}
if err := cfg.ReferenceData(rd); err != nil {
return fmt.Errorf("cfg.ReferenceData(%q): %w", fn, err)
}
}
}
return cb(pp)
}
}
if runCatchup {
// Load the latest reference data.
rd, err := darwingests3.LoadLatestReferenceData(ctx, bucket)
if err != nil {
return fmt.Errorf("LoadLatestReferenceData: %w", err)
}
if err := cfg.ReferenceData(rd); err != nil {
return fmt.Errorf("cfg.ReferenceData: %w", err)
}
}
stompBackoff := backoff.NewExponentialBackOff()
stompBackoff.MaxElapsedTime = 24 * time.Hour
stompConn, err := cfg.DialStomp(ctx)
if err != nil {
return fmt.Errorf("DialStomp: %w", err)
}
defer stompConn.Disconnect()
sub, err := cfg.StompSubscribe(ctx, stompConn)
if err != nil {
return fmt.Errorf("StompSubscribe: %w", err)
}
defer sub.Unsubscribe()
drainConnection := func() {
if sub == nil {
return
}
go sub.Unsubscribe()
for m := range sub.C {
stompConn.Nack(m)
}
}
if runCatchup {
if err := func() error {
log.Println("awaiting first message to find replay timestamp")
// Find the start timestamp we need to replay up to.
m := <-sub.C
defer stompConn.Nack(m)
d, err := unmarshalGzipPushPort(m.Body)
if err != nil {
return fmt.Errorf("unmarshalGzipPushPort: %w", err)
}
log.Printf("first message is at %v", d.Time)
// Don't handle the message just yet.
rids := d.AffectedRIDs()
var timetableDate time.Time
if len(rids) == 0 {
log.Printf("no affected RIDs in first message! falling back to time.Now...")
timetableDate = time.Now()
} else {
timetableDate, err = ridToDate(rids[0])
if err != nil {
log.Printf("couldn't parse RID %q to date, falling back to time.Now: %v", rids[0], err)
timetableDate = time.Now()
}
}
// Load the timetable first.
log.Println("loading initial timetable")
ppt, err := darwingests3.LoadTimetableForDate(ctx, bucket, timetableDate)
if err != nil {
return fmt.Errorf("LoadTimetableForDate: %w", err)
}
log.Println("feeding timetable to database")
if err := cfg.Timetable(ppt); err != nil {
return fmt.Errorf("Timetable: %w", err)
}
log.Println("connecting to FTP")
sc, err := cfg.DialFTP(ctx)
if err != nil {
return fmt.Errorf("DialFTP: %w", err)
}
defer sc.Quit()
// Grab the full snapshot dump
log.Println("loading snapshot")
lw, _, err := darwingestftp.LoadSnapshot(ctx, sc, handleMsg(cfg.MessageCatchUp))
if err != nil {
return fmt.Errorf("LoadSnapshot: %w", err)
}
// Now try to "catch it up" using the push-port logs to the start timestamp of the message...
log.Println("loading logs")
if err := darwingestftp.LoadLogs(ctx, sc, lw, d.Time, handleMsg(cfg.MessageCatchUp)); err != nil {
return fmt.Errorf("LoadLogs: %w", err)
}
log.Println("marking catchup complete")
if err := cfg.CatchUpComplete(ctx); err != nil {
return fmt.Errorf("CatchUpComplete callback: %w", err)
}
log.Println("we think we've caught up! let's go...")
if err := handleMsg(cfg.Message)(d); err != nil {
return fmt.Errorf("replaying first pushport message: %w", err)
}
if err := stompConn.Ack(m); err != nil {
return fmt.Errorf("acking first pushport message: %w", err)
}
return nil
}(); err != nil {
drainConnection()
return err
}
} else {
if err := cfg.CatchUpComplete(ctx); err != nil {
return fmt.Errorf("CatchUpComplete callback: %w", err)
}
}
mustReconnect := false
for {
if sub == nil {
mustReconnect = true
}
if mustReconnect {
duration := stompBackoff.NextBackOff()
if duration == backoff.Stop {
drainConnection()
return fmt.Errorf("exceeded stomp reconnection retries")
}
log.Printf("subscription appears to have closed, retrying after %v...", duration)
if err := darwin.Sleep(ctx, duration); err != nil {
drainConnection()
return err
}
var err error
log.Printf("resubscribing...")
sub, err = cfg.StompSubscribe(ctx, stompConn)
if err != nil {
log.Printf("StompSubscribe while resubscribing: %v", err)
// Force close the server connection and dial again.
stompConn.MustDisconnect()
stompConn, err = cfg.DialStomp(ctx)
if err != nil {
log.Printf("DialStomp failed as well :(")
// Try again later.
continue
}
defer stompConn.Disconnect()
sub, err = cfg.StompSubscribe(ctx, stompConn)
if err != nil {
log.Printf("StompSubscribe with new connection failed: %v", err)
// Try again later.
continue
}
}
defer sub.Unsubscribe()
mustReconnect = false
}
select {
case <-ctx.Done():
return ctx.Err()
case m, ok := <-sub.C:
if !ok {
mustReconnect = true
continue
}
d, err := unmarshalGzipPushPort(m.Body)
if err != nil {
log.Printf("unmarshalGzipPushPort: %v", err)
if err := stompConn.Nack(m); err != nil {
log.Printf("attempting to nack unparsable message: %v", err)
}
continue
}
if err := handleMsg(cfg.Message)(d); err != nil {
log.Printf("Message: %v", err)
if err := stompConn.Nack(m); err != nil {
log.Printf("attempting to nack unhandled message: %v", err)
}
continue
}
if err := stompConn.Ack(m); err != nil {
log.Printf("attempting to ack handled message: %v", err)
continue
}
stompBackoff.Reset()
}
}
}

View file

@ -0,0 +1,169 @@
package darwingestftp
import (
"compress/gzip"
"context"
"encoding/xml"
"errors"
"fmt"
"io"
"log"
"sort"
"time"
"github.com/jlaffaye/ftp"
"hg.lukegb.com/lukegb/depot/go/trains/darwin"
)
func loadGzippedFile(ctx context.Context, sc *ftp.ServerConn, fn string, cb func(pp *darwin.PushPort) error) error {
log.Printf("fetching %v", fn)
resp, err := sc.Retr(fn)
if err != nil {
return fmt.Errorf("Retr(%q): %w", fn, err)
}
defer resp.Close()
respGz, err := gzip.NewReader(resp)
if err != nil {
return fmt.Errorf("gzip.NewReader from %q: %w", fn, err)
}
defer respGz.Close()
xmlDec := xml.NewDecoder(respGz)
for {
if ctx.Err() != nil {
return ctx.Err()
}
var pp darwin.PushPort
if err := xmlDec.Decode(&pp); errors.Is(err, io.EOF) {
break
} else if err != nil {
return fmt.Errorf("xmlDec.Decode from %q: %w", fn, err)
}
if err := cb(&pp); err != nil {
return fmt.Errorf("processing %q: %w", fn, err)
}
}
return nil
}
func LoadSnapshot(ctx context.Context, sc *ftp.ServerConn, cb func(*darwin.PushPort) error) (lowWater, highWater time.Time, err error) {
log.Println("fetching snapshot...")
err = loadGzippedFile(ctx, sc, "/snapshot/snapshot.gz", func(pp *darwin.PushPort) error {
if lowWater.IsZero() || lowWater.After(pp.Time) {
lowWater = pp.Time
}
if highWater.IsZero() || highWater.Before(pp.Time) {
highWater = pp.Time
}
return cb(pp)
})
if err != nil {
return time.Time{}, time.Time{}, err
}
return lowWater, highWater, nil
}
type logFTPFile struct {
Filename string
EndAt time.Time
}
func LoadLogs(ctx context.Context, sc *ftp.ServerConn, startAt, endAt time.Time, cb func(*darwin.PushPort) error) error {
firstIt := true
for {
if ctx.Err() != nil {
return ctx.Err()
}
log.Println("fetching logs starting at", startAt)
entries, err := sc.List("/pushport")
if err != nil {
return fmt.Errorf("listing /pushport: %w", err)
}
var fs []logFTPFile
for _, e := range entries {
if e.Type != ftp.EntryTypeFile {
continue
}
t, err := time.ParseInLocation("pPortData.log.2006-01-02_1504.gz", e.Name, darwin.London)
if err != nil {
log.Println("failed to parse filename %q: %v", err)
continue
}
fs = append(fs, logFTPFile{
Filename: e.Name,
EndAt: t,
})
}
sort.Slice(fs, func(i, j int) bool {
return fs[i].EndAt.Before(fs[j].EndAt)
})
startReadingFrom := -1
endReadingAt := len(fs) - 1
for n, f := range fs {
if f.EndAt.After(startAt) && startReadingFrom == -1 {
startReadingFrom = n
}
if f.EndAt.After(endAt) && !endAt.IsZero() {
endReadingAt = n
break
}
}
var lowWater, highWater time.Time
if startReadingFrom != -1 {
for n := startReadingFrom; n <= endReadingAt; n++ {
fmt.Printf("%v: ", fs[n].Filename)
err := loadGzippedFile(ctx, sc, fmt.Sprintf("/pushport/%v", fs[n].Filename), func(pp *darwin.PushPort) error {
if lowWater.IsZero() || lowWater.After(pp.Time) {
lowWater = pp.Time
}
if highWater.IsZero() || highWater.Before(pp.Time) {
highWater = pp.Time
}
if !pp.Time.After(startAt) {
fmt.Printf("x")
return nil
} else if !endAt.IsZero() && (endAt.Before(pp.Time) || endAt.Equal(pp.Time)) {
fmt.Printf("_")
return nil
}
return cb(pp)
})
if err != nil {
return err
}
fmt.Print("\n")
}
if !lowWater.Before(startAt) {
// Don't believe that we saw everything :/
log.Printf("low water was %v; start at was %v; we might not have seen everything!", lowWater, startAt)
}
}
if !highWater.After(endAt) {
if !firstIt {
log.Printf("not yet caught up: high water %v; end at %v; waiting 1 minute...", highWater, endAt)
if err := darwin.Sleep(ctx, 1*time.Minute); err != nil {
return err
}
}
firstIt = false
if !highWater.IsZero() {
startAt = highWater
}
continue
}
return nil
}
}

View file

@ -0,0 +1,17 @@
# SPDX-FileCopyrightText: 2020 Luke Granger-Brown <depot@lukegb.com>
#
# SPDX-License-Identifier: Apache-2.0
{ depot, ... }@args:
depot.third_party.buildGo.package {
name = "darwingestftp";
srcs = [
./darwingestftp.go
];
path = "hg.lukegb.com/lukegb/depot/go/trains/darwin/darwingest/darwingestftp";
deps = with depot.third_party; [
gopkgs."github.com".jlaffaye.ftp
depot.go.trains.darwin
];
}

View file

@ -0,0 +1,214 @@
package darwingests3
import (
"compress/gzip"
"context"
"encoding/xml"
"errors"
"fmt"
"io"
"log"
"regexp"
"sort"
"strconv"
"time"
"gocloud.dev/blob"
"hg.lukegb.com/lukegb/depot/go/trains/darwin"
)
var (
blobRegex = regexp.MustCompile(`^(?P<datetime>[0-9]{14})(_ref)?_v(?P<version>[0-9]+)\.xml\.gz$`)
)
const (
understoodVersionTimetable = 8
understoodVersionReference = 3
)
type fileType int
const (
fileTypeWantAny fileType = iota
fileTypeTimetable
fileTypeReference
)
func (ft fileType) IsA(wantFileType fileType) bool {
switch wantFileType {
case fileTypeWantAny:
return true
case ft:
return true
default:
return false
}
}
type fileInfo struct {
filename string
fileType fileType
version int
filenameDate time.Time
}
func parseFilename(fn string) (fileInfo, error) {
m := blobRegex.FindStringSubmatch(fn)
if m == nil {
return fileInfo{}, fmt.Errorf("couldn't parse filename %q: regex didn't match", fn)
}
filenameDateStr := m[1]
filenameDate, err := time.ParseInLocation("20060102150405", filenameDateStr, darwin.London)
if err != nil {
return fileInfo{}, fmt.Errorf("couldn't parse blob filename %q: datetime part %q unparsable: %v", fn, filenameDateStr, err)
}
versionStr := m[3]
version, err := strconv.Atoi(versionStr)
if err != nil {
return fileInfo{}, fmt.Errorf("couldn't parse blob filename %q: version part %q unparsable: %v", fn, versionStr, err)
}
fileType := fileTypeTimetable
if m[2] != "" {
fileType = fileTypeReference
}
return fileInfo{
filename: fn,
fileType: fileType,
version: version,
filenameDate: filenameDate,
}, nil
}
func filesInBucket(ctx context.Context, bucket *blob.Bucket, wantFileType fileType) ([]fileInfo, error) {
var fis []fileInfo
iter := bucket.List(nil)
for {
obj, err := iter.Next(ctx)
if err == io.EOF {
break
}
if err != nil {
return nil, fmt.Errorf("listing objects in bucket: %w", err)
}
fi, err := parseFilename(obj.Key)
if err != nil {
log.Print(err)
continue
}
if !fi.fileType.IsA(wantFileType) {
continue
}
fis = append(fis, fi)
}
return fis, nil
}
func LoadTimetableForDate(ctx context.Context, bucket *blob.Bucket, ts time.Time) (*darwin.PushPortTimetable, error) {
// Find the latest filename that covers "today".
// This is a little tricky, and probably wrong, but we'll try and be good enough.
fis, err := filesInBucket(ctx, bucket, fileTypeTimetable)
if err != nil {
return nil, fmt.Errorf("filesInBucket: %w", err)
}
// OK, find one where the date matches...
wanty, wantm, wantd := ts.Date()
var got *fileInfo
for _, fi := range fis {
if fi.version != understoodVersionTimetable {
continue
}
fiy, fim, fid := fi.filenameDate.Date()
if wanty == fiy && wantm == fim && wantd == fid {
fi := fi
got = &fi
break
}
}
if got == nil {
return nil, fmt.Errorf("unable to find matching timetable for %v")
}
return LoadTimetable(ctx, bucket, got.filename)
}
func decodeFromBucket(ctx context.Context, bucket *blob.Bucket, filename string, out interface{}) error {
r, err := bucket.NewReader(ctx, filename, nil)
if err != nil {
return fmt.Errorf("NewReader(%q): %w", filename, err)
}
defer r.Close()
gzr, err := gzip.NewReader(r)
if err != nil {
return fmt.Errorf("gzip.NewReader(fn=%q): %w", filename, err)
}
defer gzr.Close()
if err := xml.NewDecoder(gzr).Decode(out); err != nil {
return fmt.Errorf("decoding XML from %q: %w", filename, err)
}
return nil
}
var ErrBadVersion = errors.New("darwingests3: bad version, ignoring request")
func LoadTimetable(ctx context.Context, bucket *blob.Bucket, filename string) (*darwin.PushPortTimetable, error) {
log.Printf("loading timetable from S3 file %q", filename)
fi, err := parseFilename(filename)
if err != nil {
return nil, err
}
if fi.version != understoodVersionTimetable {
return nil, ErrBadVersion
}
var ppt darwin.PushPortTimetable
if err := decodeFromBucket(ctx, bucket, filename, &ppt); err != nil {
return nil, err
}
return &ppt, nil
}
func LoadLatestReferenceData(ctx context.Context, bucket *blob.Bucket) (*darwin.PushPortReferenceData, error) {
fis, err := filesInBucket(ctx, bucket, fileTypeReference)
if err != nil {
return nil, fmt.Errorf("filesInBucket: %w", err)
}
sort.Slice(fis, func(i, j int) bool {
if fis[i].version == understoodVersionReference && fis[j].version != understoodVersionReference {
return true
} else if fis[i].version != understoodVersionReference && fis[j].version == understoodVersionReference {
return false
}
return fis[i].filenameDate.Before(fis[j].filenameDate)
})
return LoadReferenceData(ctx, bucket, fis[0].filename)
}
func LoadReferenceData(ctx context.Context, bucket *blob.Bucket, filename string) (*darwin.PushPortReferenceData, error) {
log.Printf("loading reference data from S3 file %q", filename)
fi, err := parseFilename(filename)
if err != nil {
return nil, err
}
if fi.version != understoodVersionReference {
return nil, ErrBadVersion
}
var pprd darwin.PushPortReferenceData
if err := decodeFromBucket(ctx, bucket, filename, &pprd); err != nil {
return nil, err
}
return &pprd, nil
}

View file

@ -0,0 +1,17 @@
# SPDX-FileCopyrightText: 2020 Luke Granger-Brown <depot@lukegb.com>
#
# SPDX-License-Identifier: Apache-2.0
{ depot, ... }@args:
depot.third_party.buildGo.package {
name = "darwingests3";
srcs = [
./darwingests3.go
];
path = "hg.lukegb.com/lukegb/depot/go/trains/darwin/darwingest/darwingests3";
deps = with depot.third_party; [
gopkgs."gocloud.dev".blob
depot.go.trains.darwin
];
}

View file

@ -0,0 +1,34 @@
package darwingeststomp
import (
"context"
"time"
stomp "github.com/go-stomp/stomp/v3"
)
type Config struct {
Hostname string
Username, Password string
ClientID string
}
func Dial(ctx context.Context, cfg Config) (*stomp.Conn, error) {
const maxExpectedQPS = 60
var maxExpectedDowntime = int((10 * time.Minute).Seconds())
return stomp.Dial(
"tcp", cfg.Hostname,
stomp.ConnOpt.Login(cfg.Username, cfg.Password),
stomp.ConnOpt.Header("client-id", cfg.ClientID),
stomp.ConnOpt.ReadChannelCapacity(maxExpectedQPS*maxExpectedDowntime),
stomp.ConnOpt.HeartBeat(7*time.Minute, 1*time.Minute),
stomp.ConnOpt.HeartBeatGracePeriodMultiplier(3),
)
}
func Subscribe(c *stomp.Conn, subName string) (*stomp.Subscription, error) {
return c.Subscribe(
"/topic/darwin.pushport-v16", stomp.AckClientIndividual,
stomp.SubscribeOpt.Header("durable-subscription-name", subName),
)
}

View file

@ -0,0 +1,17 @@
# SPDX-FileCopyrightText: 2020 Luke Granger-Brown <depot@lukegb.com>
#
# SPDX-License-Identifier: Apache-2.0
{ depot, ... }@args:
depot.third_party.buildGo.package {
name = "darwingeststomp";
srcs = [
./darwingeststomp.go
];
path = "hg.lukegb.com/lukegb/depot/go/trains/darwin/darwingest/darwingeststomp";
deps = with depot.third_party; [
gopkgs."github.com".go-stomp.stomp.v3
depot.go.trains.darwin
];
}

View file

@ -0,0 +1,27 @@
# SPDX-FileCopyrightText: 2020 Luke Granger-Brown <depot@lukegb.com>
#
# SPDX-License-Identifier: Apache-2.0
{ depot, ... }@args:
depot.third_party.buildGo.package {
name = "darwingest";
srcs = [
./darwingest.go
];
path = "hg.lukegb.com/lukegb/depot/go/trains/darwin/darwingest";
deps = with depot.third_party; [
gopkgs."github.com".cenkalti.backoff.v4
gopkgs."github.com".go-stomp.stomp.v3
gopkgs."github.com".jlaffaye.ftp
gopkgs."gocloud.dev".blob
depot.go.trains.darwin
depot.go.trains.darwin.darwingest.darwingestftp
depot.go.trains.darwin.darwingest.darwingests3
depot.go.trains.darwin.darwingest.darwingeststomp
];
} // {
darwingestftp = import ./darwingestftp args;
darwingests3 = import ./darwingests3 args;
darwingeststomp = import ./darwingeststomp args;
}

View file

@ -0,0 +1,18 @@
# SPDX-FileCopyrightText: 2020 Luke Granger-Brown <depot@lukegb.com>
#
# SPDX-License-Identifier: Apache-2.0
{ depot, ... }@args:
depot.third_party.buildGo.package {
name = "darwin";
srcs = [
./msg.go
./refdata.go
./timezone.go
./util.go
];
path = "hg.lukegb.com/lukegb/depot/go/trains/darwin";
} // {
darwindb = import ./darwindb args;
darwingest = import ./darwingest args;
}

436
go/trains/darwin/msg.go Normal file
View file

@ -0,0 +1,436 @@
package darwin
import (
"encoding/xml"
"time"
)
type QueryTimetable struct {
//XMLName xml.Name `xml:"QueryTimetable"`
}
type TimetableID struct {
//XMLName xml.Name `xml:"TimeTableId"`
ID string `xml:",chardata"`
Filename string `xml:"ttfile,attr"`
ReferenceFilename string `xml:"ttreffile,attr"`
}
type GetSnapshotReq struct {
//XMLName xml.Name `xml:"GetSnapshotReq"`
ViaFTP bool `xml:"viaftp,attr"`
}
type GetFullSnapshotReq struct {
//XMLName xml.Name `xml:"GetFullSnapshotReq"`
ViaFTP bool `xml:"viaftp,attr"`
}
type SnapshotID struct {
//XMLName xml.Name `xml:"SnapshotId"`
ID string `xml:",chardata"`
}
type StartUpdateReq struct {
//XMLName xml.Name `xml:"StartUpdateReq"`
}
type StopUpdateReq struct {
//XMLName xml.Name `xml:"StopUpdateReq"`
}
type FailureResp struct {
//XMLName xml.Name `xml:"FailureResp"`
ErrorMessage string `xml:",chardata"`
Code string `xml:"code,attr"`
RequestSource string `xml:"requestSource,attr"`
RequestID string `xml:"requestID,attr"`
}
type ScheduleCallingPoint struct {
XMLName xml.Name
TIPLOC string `xml:"tpl,attr"`
Act string `xml:"act,attr"`
PlanAct string `xml:"planAct,attr"`
Platform string `xml:"plat,attr"`
Cancelled bool `xml:"can,attr"`
FID string `xml:"fid,attr"`
PTA string `xml:"pta,attr"`
PTD string `xml:"ptd,attr"`
AvgLoading int `xml:"avgLoading,attr"`
WTA string `xml:"wta,attr"`
WTD string `xml:"wtd,attr"`
WTP string `xml:"wtp,attr"`
FD string `xml:"fd,attr"`
RDelay int `xml:"rdelay,attr"`
}
type Schedule struct {
//XMLName xml.Name `xml:"schedule"`
CallingPoints []ScheduleCallingPoint `xml:",any"`
CancellationReason *DisruptionReason `xml:"cancelReason" json:",omitempty"`
RID string `xml:"rid,attr"`
UID string `xml:"uid,attr"`
TrainID string `xml:"trainId,attr"`
RSID string `xml:"rsid,attr"`
SSD string `xml:"ssd,attr"` // RTTIDateType
TOC string `xml:"toc,attr"`
Status *string `xml:"status,attr"`
TrainCat *string `xml:"trainCat,attr"`
IsPassengerSvc *bool `xml:"isPassengerSvc,attr"`
IsActive *bool `xml:"isActive,attr"`
Deleted bool `xml:"deleted,attr"`
IsCharter *bool `xml:"isCharter,attr"`
IsQTrain *bool `xml:"qtrain,attr"`
Cancelled *bool `xml:"can,attr"`
}
func (s Schedule) AffectedRIDs() []string {
return []string{s.RID}
}
type DeactivatedSchedule struct {
//XMLName xml.Name `xml:"deactivated"`
RID string `xml:"rid,attr"`
}
func (ds DeactivatedSchedule) AffectedRIDs() []string {
return []string{ds.RID}
}
type CircularTimes struct {
WTA string `xml:"wta,attr" json:",omitempty"`
WTD string `xml:"wtd,attr" json:",omitempty"`
WTP string `xml:"wtp,attr" json:",omitempty"`
PTA string `xml:"pta,attr" json:",omitempty"`
PTD string `xml:"ptd,attr" json:",omitempty"`
}
type AssocService struct {
RID string `xml:"rid,attr"`
CircularTimes
}
type Association struct {
//XMLName xml.Name `xml:"association"`
Main AssocService `xml:"main"`
Assoc AssocService `xml:"assoc"`
TIPLOC string `xml:"tiploc,attr"`
Category string `xml:"category,attr"`
IsCancelled bool `xml:"isCancelled,attr"`
IsDeleted bool `xml:"isDeleted,attr"`
}
func (a Association) AffectedRIDs() []string {
return []string{a.Main.RID, a.Assoc.RID}
}
type ToiletAvailability struct {
Type string `xml:",chardata"`
Status string `xml:"status,attr"`
}
type Coach struct {
Toilet []ToiletAvailability `xml:"toilet"`
CoachNumber string `xml:"coachNumber,attr"`
CoachClass string `xml:"coachClass,attr"`
}
type Formation struct {
//XMLName xml.Name `xml:"formation"`
Coaches []Coach `xml:"coaches>coach"`
FID string `xml:"fid,attr"`
Src string `xml:"src,attr"`
SrcInst string `xml:"srcInst,attr"`
}
type ScheduleFormation struct {
//XMLName xml.Name `xml:"scheduleFormations"`
Formation []Formation `xml:"formation"`
RID string `xml:"rid,attr"`
}
func (sf ScheduleFormation) AffectedRIDs() []string {
return []string{sf.RID}
}
type DisruptionReason struct {
Code string `xml:",chardata"`
TIPLOC string `xml:"tiploc,attr"`
Near bool `xml:"near,attr"`
}
type TSTimeData struct {
ET string `xml:"et,attr" json:",omitempty"`
WET string `xml:"wet,attr" json:",omitempty"`
AT string `xml:"at,attr" json:",omitempty"`
ATRemoved bool `xml:"atRemoved,attr" json:",omitempty"`
ETMin string `xml:"etmin,attr" json:",omitempty"`
ETUnknown bool `xml:"etUnknown,attr" json:",omitempty"`
Delayed bool `xml:"delayed,attr" json:",omitempty"`
Src string `xml:"src,attr" json:",omitempty"`
SrcInst string `xml:"srcInst,attr" json:",omitempty"`
}
type PlatformData struct {
Platform string `xml:",chardata"`
PlatSup bool `xml:"platsup,attr"`
CisPlatSup bool `xml:"cisPlatsup,attr"`
PlatSrc string `xml:"platsrc,attr"`
Conf bool `xml:"conf,attr"`
}
type TSLocation struct {
TIPLOC string `xml:"tpl,attr"`
CircularTimes
Arr *TSTimeData `xml:"arr" json:",omitempty"`
Dep *TSTimeData `xml:"dep" json:",omitempty"`
Pass *TSTimeData `xml:"pass" json:",omitempty"`
Plat *PlatformData `xml:"plat" json:",omitempty"`
Suppr *bool `xml:"suppr" json:",omitempty"`
Length *int `xml:"length" json:",omitempty"`
DetachFront *bool `xml:"detachFront" json:",omitempty"`
}
type TrainStatus struct {
//XMLName xml.Name `xml:"TS"`
LateReason *DisruptionReason `xml:"LateReason" json:",omitempty"`
Location []TSLocation `xml:"Location"`
RID string `xml:"rid,attr"`
UID string `xml:"uid,attr"`
SSD string `xml:"ssd,attr"`
IsReverseFormation bool `xml:"isReverseFormation,attr"`
}
func (ts TrainStatus) AffectedRIDs() []string {
return []string{ts.RID}
}
type CoachLoadingData struct {
Value string `xml:",chardata"`
CoachNumber string `xml:"coachNumber,attr"`
Src string `xml:"src,attr"`
SrcInst string `xml:"srcInst,attr"`
}
type FormationLoading struct {
//XMLName xml.Name `xml:"formationLoading"`
Loading []CoachLoadingData `xml:"loading"`
FID string `xml:"fid,attr"`
RID string `xml:"rid,attr"`
TIPLOC string `xml:"tpl,attr"`
CircularTimes
}
func (fl FormationLoading) AffectedRIDs() []string {
return []string{fl.RID}
}
type StationMessageStation struct {
CRS string `xml:"crs,attr"`
}
type StationMessageMessage struct {
InnerXML string `xml:",innerxml"`
}
type StationMessage struct {
Station []StationMessageStation `xml:"Station"`
Msg StationMessageMessage `xml:"Msg"`
ID int `xml:"id,attr"`
Cat string `xml:"cat,attr"`
Sev string `xml:"sev,attr"`
Suppress bool `xml:"bool,attr"`
}
type AlertService struct {
RID string `xml:"RID,attr"`
UID string `xml:"UID,attr"`
SSD string `xml:"SSD,attr"`
Location []string `xml:"Location"`
}
type TrainAlert struct {
AlertID int `xml:"AlertID"`
AlertServices []AlertService `xml:"AlertServices"`
SendAlertBySMS bool `xml:"SendAlertBySMS"`
SendAlertByEmail bool `xml:"SendAlertByEmail"`
SendAlertByTwitter bool `xml:"SendAlertByTwitter"`
Source string `xml:"Source"`
AlertText string `xml:"AlertText"`
Audience string `xml:"Audience"`
AlertType string `xml:"AlertType"`
}
func (ta TrainAlert) AffectedRIDs() []string {
rids := make([]string, len(ta.AlertServices))
for n, as := range ta.AlertServices {
rids[n] = as.RID
}
return rids
}
type TrainOrderRID struct {
RID string `xml:",chardata"`
CircularTimes
}
type TrainOrderItem struct {
RID *TrainOrderRID `xml:"rid"`
TrainID *string `xml:"trainID"`
}
type TrainOrderData struct {
First TrainOrderItem `xml:"first"`
Second *TrainOrderItem `xml:"second"`
Third *TrainOrderItem `xml:"third"`
}
type TrainOrder struct {
Set *TrainOrderData `xml:"set"`
Clear struct{} `xml:"clear"`
TIPLOC string `xml:"tiploc,attr"`
CRS string `xml:"crs,attr"`
Platform string `xml:"platform,attr"`
}
type FullTDBerthID struct {
Berth string `xml:",chardata"`
Area string `xml:"area,attr"`
}
type TrackingID struct {
Berth FullTDBerthID `xml:"berth"`
IncorrectTrainID string `xml:"incorrectTrainID"`
CorrectTrainID string `xml:"correctTrainID"`
}
type RTTIAlarmData struct {
TDAreaFail *string `xml:"tdAreaFail"`
TDFeedFail *string `xml:"tdFeedFail"`
TyrellFeedFail *string `xml:"tyrellFeedFail"`
ID string `xml:"id,attr"`
}
type RTTIAlarm struct {
Set *RTTIAlarmData `xml:"set"`
Clear *string `xml:"clear"`
}
type DataResponse struct {
Schedule []Schedule `xml:"schedule" json:",omitempty"`
Deactivated []DeactivatedSchedule `xml:"deactivated" json:",omitempty"`
Association []Association `xml:"association" json:",omitempty"`
ScheduleFormations []ScheduleFormation `xml:"scheduleFormations" json:",omitempty"`
TrainStatus []TrainStatus `xml:"TS" json:",omitempty"`
FormationLoading []FormationLoading `xml:"formationLoading" json:",omitempty"`
OW []StationMessage `xml:"OW" json:",omitempty"`
TrainAlert []TrainAlert `xml:"trainAlert" json:",omitempty"`
TrainOrder []TrainOrder `xml:"trainOrder" json:",omitempty"`
TrackingID []TrackingID `xml:"trackingID" json:",omitempty"`
Alarm []RTTIAlarm `xml:"alarm" json:",omitempty"`
}
func (r DataResponse) AffectedRIDs() []string {
var rids []string
for _, s := range r.Schedule {
rids = append(rids, s.AffectedRIDs()...)
}
for _, ds := range r.Deactivated {
rids = append(rids, ds.AffectedRIDs()...)
}
for _, a := range r.Association {
rids = append(rids, a.AffectedRIDs()...)
}
for _, sf := range r.ScheduleFormations {
rids = append(rids, sf.AffectedRIDs()...)
}
for _, ts := range r.TrainStatus {
rids = append(rids, ts.AffectedRIDs()...)
}
for _, fl := range r.FormationLoading {
rids = append(rids, fl.AffectedRIDs()...)
}
// OW doesn't affect RIDs directly.
for _, ta := range r.TrainAlert {
rids = append(rids, ta.AffectedRIDs()...)
}
// TrainOrder needs extra processing (clear doesn't specify anything so we have to know ahead of time...)
// TrackingID doesn't contain RIDs.
// Alarm doesn't affect RIDs directly.
return rids
}
type UpdateResp struct {
UpdateOrigin string `xml:"updateOrigin,attr"`
RequestSource string `xml:"requestSource,attr" json:",omitempty"`
RequestID string `xml:"requestID,attr" json:",omitempty"`
DataResponse
}
type SnapshotResp struct {
DataResponse
}
type PushPort struct {
//XMLName xml.Name `xml:"Pport"`
// Subelements
QueryTimetable []QueryTimetable `xml:"QueryTimetable" json:",omitempty"`
TimeTableID []TimetableID `xml:"TimeTableId" json:",omitempty"`
GetSnapshotReq []GetSnapshotReq `xml:"GetSnapshotReq" json:",omitempty"`
GetFullSnapshotReq []GetFullSnapshotReq `xml:"GetFullSnapshotReq" json:",omitempty"`
SnapshotID []SnapshotID `xml:"SnapshotId" json:",omitempty"`
StartUpdateReq []StartUpdateReq `xml:"StartUpdateReq" json:",omitempty"`
StopUpdateReq []StopUpdateReq `xml:"StopUpdateReq" json:",omitempty"`
FailureResp []FailureResp `xml:"FailureResp" json:",omitempty"`
UpdateResp []UpdateResp `xml:"uR" json:",omitempty"`
SnapshotResp []SnapshotResp `xml:"sR" json:",omitempty"`
// Attrs
Time time.Time `xml:"ts,attr"`
}
func (pp PushPort) AffectedRIDs() []string {
var rids []string
for _, r := range pp.UpdateResp {
rids = append(rids, r.AffectedRIDs()...)
}
for _, r := range pp.SnapshotResp {
rids = append(rids, r.AffectedRIDs()...)
}
return rids
}
type PushPortTimetable struct {
Journey []Schedule `xml:"Journey"`
Association []Association `xml:"Association"`
// Attrs
TimetableID string `xml:"timetableID,attr"`
}

View file

@ -0,0 +1,45 @@
package darwin
import "encoding/xml"
type RefLocationRef struct {
TIPLOC string `xml:"tpl,attr"`
Name string `xml:"locname,attr"`
CRS *string `xml:"crs,attr"`
TOC *string `xml:"toc,attr"`
}
type RefTOCRef struct {
TOC string `xml:"toc,attr"`
Name string `xml:"tocname,attr"`
URL *string `xml:"url,attr"`
}
type RefReason struct {
Code string `xml:"code,attr"`
Text string `xml:"reasontext,attr"`
}
type RefVia struct {
AtCRS string `xml:"at,attr"`
DestTIPLOC string `xml:"dest,attr"`
Loc1TIPLOC string `xml:"loc1,attr"`
Loc2TIPLOC string `xml:"loc2,attr"`
Text string `xml:"viatext,attr"`
}
type RefCISSource struct {
CISCode string `xml:"code,attr"`
Name string `xml:"name,attr"`
}
type PushPortReferenceData struct {
XMLName xml.Name `xml:"PportTimetableRef"`
Locations []RefLocationRef `xml:"LocationRef"`
TOCs []RefTOCRef `xml:"TocRef"`
LateRunningReasons []RefReason `xml:"LateRunningReasons>Reason"`
CancellationReasons []RefReason `xml:"CancellationReasons>Reason"`
Via []RefVia `xml:"Via"`
CISSource []RefCISSource `xml:"CISSource"`
}

View file

@ -0,0 +1,15 @@
package darwin
import (
"time"
)
var (
London = func() *time.Location {
tz, err := time.LoadLocation("Europe/London")
if err != nil {
panic(err)
}
return tz
}()
)

18
go/trains/darwin/util.go Normal file
View file

@ -0,0 +1,18 @@
package darwin
import (
"context"
"time"
)
func Sleep(ctx context.Context, d time.Duration) error {
t := time.NewTicker(d)
defer t.Stop()
select {
case <-t.C:
return nil
case <-ctx.Done():
return ctx.Err()
}
}

11
go/trains/default.nix Normal file
View file

@ -0,0 +1,11 @@
# SPDX-FileCopyrightText: 2020 Luke Granger-Brown <depot@lukegb.com>
#
# SPDX-License-Identifier: Apache-2.0
{ depot, ... }@args:
{
cmd = import ./cmd args;
webapi = import ./webapi args;
darwin = import ./darwin args;
}

38
go/trains/go.mod Normal file
View file

@ -0,0 +1,38 @@
module hg.lukegb.com/lukegb/depot/go/trains
go 1.16
require (
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999 // indirect
github.com/cenkalti/backoff/v4 v4.1.1
github.com/coreos/bbolt v1.3.1-coreos.6 // indirect
github.com/coreos/etcd v3.3.10+incompatible // indirect
github.com/coreos/go-semver v0.2.0 // indirect
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect
github.com/dnaeon/go-vcr v1.0.1 // indirect
github.com/go-ini/ini v1.39.0 // indirect
github.com/go-stomp/stomp/v3 v3.0.3
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7 // indirect
github.com/googleapis/gax-go v2.0.0+incompatible // indirect
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 // indirect
github.com/gorilla/context v1.1.1 // indirect
github.com/gorilla/mux v1.6.2 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 // indirect
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
github.com/hooklift/gowsdl v0.5.0 // indirect
github.com/jackc/pgx/v4 v4.13.0
github.com/jlaffaye/ftp v0.0.0-20211029032751-b1140299f4df
github.com/jonboulle/clockwork v0.1.0 // indirect
github.com/jtolds/gls v4.2.1+incompatible // indirect
github.com/openzipkin/zipkin-go v0.1.1 // indirect
github.com/prometheus/client_golang v1.11.0
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d // indirect
github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a // indirect
github.com/soheilhy/cmux v0.1.4 // indirect
github.com/streadway/amqp v0.0.0-20181107104731-27835f1a64e9 // indirect
github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6 // indirect
github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18 // indirect
gocloud.dev v0.24.0 // indirect
gopkg.in/ini.v1 v1.39.0 // indirect
gopkg.in/pipe.v2 v2.0.0-20140414041502-3c2ca4d52544 // indirect
)

959
go/trains/go.sum Normal file
View file

@ -0,0 +1,959 @@
bazil.org/fuse v0.0.0-20180421153158-65cc252bf669/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8=
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 v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
cloud.google.com/go v0.82.0/go.mod h1:vlKccHJGuFBFufnAnuB08dfEH9Y3H7dzDzRECFdC2TA=
cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=
cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=
cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
cloud.google.com/go v0.88.0/go.mod h1:dnKwfYbP9hQhefiUvpbcAyoGSHUrOxR20JVElLiUvEY=
cloud.google.com/go v0.89.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
cloud.google.com/go v0.92.2/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
cloud.google.com/go v0.92.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
cloud.google.com/go v0.94.0/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
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/firestore v1.5.0/go.mod h1:c4nNYR1qdq7eaZ+jSc5fonrQN2k3M7sWATcYTiakjEo=
cloud.google.com/go/kms v0.1.0/go.mod h1:8Qp8PCAypHg4FdmlyW1QRAv09BGQ9Uzh7JnmIZxPk+c=
cloud.google.com/go/monitoring v0.1.0/go.mod h1:Hpm3XfzJv+UTiXzCG5Ffp0wijzHTC7Cv4eR7o3x/fEE=
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/pubsub v1.16.0/go.mod h1:6A8EfoWZ/lUvCWStKGwAWauJZSiuV0Mkmu6WilK/TxQ=
cloud.google.com/go/secretmanager v0.1.0/go.mod h1:3nGKHvnzDUVit7U0S9KAKJ4aOsO1xtwRG+7ey5LK1bM=
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=
cloud.google.com/go/storage v1.16.1/go.mod h1:LaNorbty3ehnU3rEjXSNV/NRgQA0O8Y+uh6bPe5UOk4=
cloud.google.com/go/trace v0.1.0/go.mod h1:wxEwsoeRVPbeSkt7ZC9nWCgmoKQRAoySN7XHW2AmI7g=
contrib.go.opencensus.io/exporter/aws v0.0.0-20200617204711-c478e41e60e9/go.mod h1:uu1P0UCM/6RbsMrgPa98ll8ZcHM858i/AD06a9aLRCA=
contrib.go.opencensus.io/exporter/stackdriver v0.13.8/go.mod h1:huNtlWx75MwO7qMs0KrMxPZXzNNWebav1Sq/pm02JdQ=
contrib.go.opencensus.io/integrations/ocsql v0.1.7/go.mod h1:8DsSdjz3F+APR+0z0WkU1aRorQCFfRxvqjUUPMbF3fE=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
github.com/Azure/azure-amqp-common-go/v3 v3.1.0/go.mod h1:PBIGdzcO1teYoufTKMcGibdKaYZv4avS+O6LNIp8bq0=
github.com/Azure/azure-amqp-common-go/v3 v3.1.1/go.mod h1:YsDaPfaO9Ub2XeSKdIy2DfwuiQlHQCauHJwSqtrkECI=
github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k=
github.com/Azure/azure-sdk-for-go v51.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-sdk-for-go v57.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-service-bus-go v0.10.16/go.mod h1:MlkLwGGf1ewcx5jZadn0gUEty+tTg0RaElr6bPf+QhI=
github.com/Azure/azure-storage-blob-go v0.14.0/go.mod h1:SMqIBi+SuiQH32bvyjngEewEeXoPfKMgWlBDaYf6fck=
github.com/Azure/go-amqp v0.13.0/go.mod h1:qj+o8xPCz9tMSbQ83Vp8boHahuRDl5mkNHyt1xlxUTs=
github.com/Azure/go-amqp v0.13.11/go.mod h1:D5ZrjQqB1dyp1A+G73xeL/kNn7D5qHJIIsNNps7YNmk=
github.com/Azure/go-amqp v0.13.12/go.mod h1:D5ZrjQqB1dyp1A+G73xeL/kNn7D5qHJIIsNNps7YNmk=
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest/autorest v0.11.3/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw=
github.com/Azure/go-autorest/autorest v0.11.17/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw=
github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA=
github.com/Azure/go-autorest/autorest v0.11.20/go.mod h1:o3tqFY+QR40VOlk+pV4d77mORO64jOXSgEnPQgLK6JY=
github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg=
github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A=
github.com/Azure/go-autorest/autorest/adal v0.9.11/go.mod h1:nBKAnTomx8gDtl+3ZCJv2v0KACFHWTB2drffI1B68Pk=
github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
github.com/Azure/go-autorest/autorest/adal v0.9.14/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
github.com/Azure/go-autorest/autorest/adal v0.9.15/go.mod h1:tGMin8I49Yij6AQ+rvV+Xa/zwxYQB5hmsd6DkfAx2+A=
github.com/Azure/go-autorest/autorest/azure/auth v0.5.8/go.mod h1:kxyKZTSfKh8OVFWPAgOgQ/frrJgeYQJPyR5fLFmXko4=
github.com/Azure/go-autorest/autorest/azure/cli v0.4.2/go.mod h1:7qkJkT+j6b+hIpzMOwPChJhTqS8VbsqqgULzMNRugoM=
github.com/Azure/go-autorest/autorest/azure/cli v0.4.3/go.mod h1:yAQ2b6eP/CmLPnmLvxtT1ALIY3OR1oFcCqVBi8vHiTc=
github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE=
github.com/Azure/go-autorest/autorest/validation v0.3.1/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E=
github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/GoogleCloudPlatform/cloudsql-proxy v1.24.0/go.mod h1:3tx938GhY4FC+E1KT/jNjDw7Z5qxAEtIiERJ2sXjnII=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
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/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
github.com/aws/aws-sdk-go v1.40.34 h1:SBYmodndE2d4AYucuuJnOXk4MD1SFbucoIdpwKVKeSA=
github.com/aws/aws-sdk-go v1.40.34/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
github.com/aws/aws-sdk-go-v2 v1.9.0 h1:+S+dSqQCN3MSU5vJRu1HqHrq00cJn6heIMU7X9hcsoo=
github.com/aws/aws-sdk-go-v2 v1.9.0/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4=
github.com/aws/aws-sdk-go-v2/config v1.7.0 h1:J2cZ7qe+3IpqBEXnHUrFrOjoB9BlsXg7j53vxcl5IVg=
github.com/aws/aws-sdk-go-v2/config v1.7.0/go.mod h1:w9+nMZ7soXCe5nT46Ri354SNhXDQ6v+V5wqDjnZE+GY=
github.com/aws/aws-sdk-go-v2/credentials v1.4.0 h1:kmvesfjY861FzlCU9mvAfe01D9aeXcG2ZuC+k9F2YLM=
github.com/aws/aws-sdk-go-v2/credentials v1.4.0/go.mod h1:dgGR+Qq7Wjcd4AOAW5Rf5Tnv3+x7ed6kETXyS9WCuAY=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.5.0 h1:OxTAgH8Y4BXHD6PGCJ8DHx2kaZPCQfSTqmDsdRZFezE=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.5.0/go.mod h1:CpNzHK9VEFUCknu50kkB8z58AH2B5DvPP7ea1LHve/Y=
github.com/aws/aws-sdk-go-v2/internal/ini v1.2.2 h1:d95cddM3yTm4qffj3P6EnP+TzX1SSkWaQypXSgT/hpA=
github.com/aws/aws-sdk-go-v2/internal/ini v1.2.2/go.mod h1:BQV0agm+JEhqR+2RT5e1XTFIDcAAV0eW6z2trp+iduw=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.3.0 h1:VNJ5NLBteVXEwE2F1zEXVmyIH58mZ6kIQGJoC7C+vkg=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.3.0/go.mod h1:R1KK+vY8AfalhG1AOu5e35pOD2SdoPKQCFLTvnxiohk=
github.com/aws/aws-sdk-go-v2/service/kms v1.5.0/go.mod h1:w7JuP9Oq1IKMFQPkNe3V6s9rOssXzOVEMNEqK1L1bao=
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.6.0/go.mod h1:B+7C5UKdVq1ylkI/A6O8wcurFtaux0R1njePNPtKwoA=
github.com/aws/aws-sdk-go-v2/service/ssm v1.10.0/go.mod h1:4dXS5YNqI3SNbetQ7X7vfsMlX6ZnboJA2dulBwJx7+g=
github.com/aws/aws-sdk-go-v2/service/sso v1.4.0 h1:sHXMIKYS6YiLPzmKSvDpPmOpJDHxmAUgbiF49YNVztg=
github.com/aws/aws-sdk-go-v2/service/sso v1.4.0/go.mod h1:+1fpWnL96DL23aXPpMGbsmKe8jLTEfbjuQoA4WS1VaA=
github.com/aws/aws-sdk-go-v2/service/sts v1.7.0 h1:1at4e5P+lvHNl2nUktdM2/v+rpICg/QSEr9TO/uW9vU=
github.com/aws/aws-sdk-go-v2/service/sts v1.7.0/go.mod h1:0qcSMCyASQPN2sk/1KQLQ2Fh6yq8wm0HSDAimPhzCoM=
github.com/aws/smithy-go v1.8.0 h1:AEwwwXQZtUwP5Mz506FeXXrKBe0jA8gVM+1gEcSRooc=
github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
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/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cenkalti/backoff/v4 v4.1.1 h1:G2HAfAmvm/GcKan2oOQpBXOd2tT2G57ZnZGWa1PxPBQ=
github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/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-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/coreos/bbolt v1.3.1-coreos.6/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/devigned/tab v0.1.1/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mzjeJY=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
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.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
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.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
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-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-ini/ini v1.39.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
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-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-stomp/stomp/v3 v3.0.3 h1:7YQGJCDMkbA05Rw8dS00LxwU1mhzEHS69gMlPjMZGDk=
github.com/go-stomp/stomp/v3 v3.0.3/go.mod h1:jTrybHBK20jPdM9iyh65m6GusX6aMf7atfEFZ1nIcgc=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
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/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
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/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
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 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
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.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.3/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.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/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 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
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-replayers/grpcreplay v1.1.0/go.mod h1:qzAvJ8/wi57zq7gWqaE6AwLM6miiXUQwP1S+I9icmhk=
github.com/google/go-replayers/httpreplay v1.0.0/go.mod h1:LJhKoTwS5Wy5Ld/peq8dFFG5OfJyHEz7ft+DsTUv25M=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian v2.1.1-0.20190517191504-25dcb96d9e51+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
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/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210506205249-923b5ab0fc1a/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210715191844-86eeefc3e471/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/wire v0.5.0 h1:I7ELFeVBr3yfPIcc8+MWvrjk+3VjbcSzoXm3JVa+jD8=
github.com/google/wire v0.5.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU=
github.com/googleapis/gax-go v2.0.0+incompatible h1:j0GKcs05QVmm7yesiZq2+9cxHkNK9YM6zKx4D2qucQU=
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
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/googleapis/gax-go/v2 v2.1.0 h1:6DWmvNpomjL1+3liNSZbVns3zsYzzCjm6pRBO1tLeso=
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
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/hooklift/gowsdl v0.5.0 h1:DE8RevqhGPLchumV/V7OwbCzfJ8lcozFg1uWC/ESCBQ=
github.com/hooklift/gowsdl v0.5.0/go.mod h1:9kRc402w9Ci/Mek5a1DNgTmU14yPY8fMumxNVvxhis4=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
github.com/jackc/pgconn v1.10.0 h1:4EYhlDVEMsJ30nNj0mmgwIUXoq7e9sMJrVC2ED6QlCU=
github.com/jackc/pgconn v1.10.0/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc=
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A=
github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.1.1 h1:7PQ/4gLoqnl87ZxL7xjO0DR5gYuviDCZxQJsUlFW1eI=
github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
github.com/jackc/pgtype v1.8.1 h1:9k0IXtdJXHJbyAWQgbWr1lU+MEhPXZz6RIXxfR5oxXs=
github.com/jackc/pgtype v1.8.1/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
github.com/jackc/pgx/v4 v4.13.0 h1:JCjhT5vmhMAf/YwBHLvrBn4OGdIQBiFG6ym8Zmdx570=
github.com/jackc/pgx/v4 v4.13.0/go.mod h1:9P4X524sErlaxj0XSGZk7s+LD0eOyu1ZDUrrpznYDF0=
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.3 h1:JnPg/5Q9xVJGfjsO5CPUOjnJps1JaRUm8I9FXVCFK94=
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jlaffaye/ftp v0.0.0-20211029032751-b1140299f4df h1:nsRFf9ZkcalB12ZJZYAD35TE2r+g/i088w9ytnaUvUo=
github.com/jlaffaye/ftp v0.0.0-20211029032751-b1140299f4df/go.mod h1:2lmrmq866uF2tnje75wQHzmPXhmSWUt7Gyx2vgK1RCU=
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
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.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
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/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/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
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/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
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.2/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/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.4.1/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/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/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ=
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
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 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
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 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
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/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
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/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/streadway/amqp v0.0.0-20181107104731-27835f1a64e9/go.mod h1:1WNBiOZtZQLpVAyu0iTduoJL9hEsMloAK5XWrtW0xdY=
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.2.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.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opencensus.io v0.22.6/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
gocloud.dev v0.24.0 h1:cNtHD07zQQiv02OiwwDyVMuHmR7iQt2RLkzoAgz7wBs=
gocloud.dev v0.24.0/go.mod h1:uA+als++iBX5ShuG4upQo/3Zoz49iIPlYUWHV5mM8w8=
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-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
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-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/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/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/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.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
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-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-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210825183410-e898025ed96a h1:bRuuGXV8wwSdGTB+CtJf+FjgO1APK1CoO39T4BN/XBw=
golang.org/x/net v0.0.0-20210825183410-e898025ed96a/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-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210126194326-f9ce19ea3013/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/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-20190403152447-81d4e9dc473e/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-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/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-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-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-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/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-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/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-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210223095934-7937bea0104d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/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-20210503080704-8803ae5d1324/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-20210514084401-e8d321eab015/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-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/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-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e h1:XMgFehsDnnLGtjvjOfqWSUzt0alpTR1RSEuznObga2c=
golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
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/time v0.0.0-20210723032227-1f47c861a9ac/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-20190422233926-fe54fb35175b/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/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-20190823170909-c4a336ef6a2f/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-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/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-20200103221440-774c71fcf114/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.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
google.golang.org/api v0.37.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
google.golang.org/api v0.46.0/go.mod h1:ceL4oozhkAiTID8XMmJBsIxID/9wMXJVVFXPg4ylg3I=
google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=
google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
google.golang.org/api v0.52.0/go.mod h1:Him/adpjt0sxtkWViy0b6xyKW/SD71CwdJ7HqJo7SrU=
google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
google.golang.org/api v0.56.0 h1:08F9XVYTLOGeSQb3xI9C0gXMuQanhdGed0cWFhDozbI=
google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
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/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-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210126160654-44e461bb6506/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
google.golang.org/genproto v0.0.0-20210429181445-86c259c2b4ab/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
google.golang.org/genproto v0.0.0-20210517163617-5e0236093d7a/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
google.golang.org/genproto v0.0.0-20210721163202-f1cecdd8b78a/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
google.golang.org/genproto v0.0.0-20210722135532-667f2b7c528f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210825212027-de86158e7fda/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2 h1:NHN4wOCScVzKhPenJ2dt+BTs3X/XkBVI/Rh4iDt55T8=
google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
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.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
google.golang.org/grpc v1.40.0 h1:AGJ0Ih4mHjSeibYkFGh1dD9KJ/eOtZ93I6hoHhukQ5Q=
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
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 h1:7QnIQpGRHE5RnLKnESfDoxm2dTapTZua5a0kS0A+VXQ=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
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/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/ini.v1 v1.39.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/pipe.v2 v2.0.0-20140414041502-3c2ca4d52544/go.mod h1:UhTeH/yXCK/KY7TX24mqPkaQ7gZeqmWd/8SSS8B3aHw=
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.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
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=
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=
nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
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=

View file

@ -0,0 +1,12 @@
# SPDX-FileCopyrightText: 2020 Luke Granger-Brown <depot@lukegb.com>
#
# SPDX-License-Identifier: Apache-2.0
{ depot, ... }:
depot.third_party.buildGo.package {
name = "webapi";
srcs = [
./structs.go
];
path = "hg.lukegb.com/lukegb/depot/go/trains/webapi";
}

197
go/trains/webapi/structs.go Normal file
View file

@ -0,0 +1,197 @@
package webapi
import (
"time"
)
type TrainOperator struct {
Code string `json:"code"`
Name *string `json:"name"`
URL *string `json:"url"`
}
func (toc *TrainOperator) IsEmpty() bool {
return toc == nil || toc.Code == ""
}
func (toc *TrainOperator) Canonicalize() {
if toc == nil {
return
}
if *toc.Name == "" {
toc.Name = nil
}
if *toc.URL == "" {
toc.URL = nil
}
}
type Location struct {
TIPLOC string `json:"tiploc"`
Name string `json:"name"`
CRS *string `json:"crs"`
TOC *TrainOperator `json:"operator"`
}
func (loc *Location) IsEmpty() bool {
return loc == nil || loc.TIPLOC == ""
}
func (loc *Location) Canonicalize() {
if loc == nil {
return
}
if loc.CRS == nil || *loc.CRS == "" {
loc.CRS = nil
}
if loc.TOC.IsEmpty() {
loc.TOC = nil
}
loc.TOC.Canonicalize()
}
type DisruptionReason struct {
Code int `json:"code"`
Text string `json:"text"`
Location *Location `json:"location"`
NearLocation bool `json:"near_location"`
}
func (dr *DisruptionReason) IsEmpty() bool {
return dr == nil || dr.Code == 0
}
func (dr *DisruptionReason) Canonicalize() {
if dr == nil {
return
}
if dr.Location.IsEmpty() {
dr.Location = nil
}
dr.Location.Canonicalize()
}
type ServiceData struct {
ID int `json:"id"`
RID string `json:"rid"`
UID string `json:"uid"`
RSID string `json:"rsid"`
Headcode string `json:"headcode"`
StartDate string `json:"scheduled_start_date"`
TrainOperator TrainOperator `json:"operator"`
DelayReason *DisruptionReason `json:"delay_reason"`
CancelReason *DisruptionReason `json:"cancel_reason"`
Active bool `json:"is_active"`
Deleted bool `json:"is_deleted"`
Cancelled bool `json:"is_cancelled"`
Locations []ServiceLocation `json:"locations"`
}
func (sd *ServiceData) Canonicalize() {
if sd.DelayReason.IsEmpty() {
sd.DelayReason = nil
}
sd.DelayReason.Canonicalize()
if sd.CancelReason.IsEmpty() {
sd.CancelReason = nil
}
sd.CancelReason.Canonicalize()
}
type TimingData struct {
PublicScheduled *time.Time `json:"public_scheduled"`
WorkingScheduled *time.Time `json:"working_scheduled"`
PublicEstimated *time.Time `json:"public_estimated"`
WorkingEstimated *time.Time `json:"working_estimated"`
Actual *time.Time `json:"actual"`
}
func (td *TimingData) IsEmpty() bool {
if td == nil {
return true
}
return td.PublicScheduled == nil && td.WorkingScheduled == nil && td.PublicEstimated == nil && td.WorkingEstimated == nil && td.Actual == nil
}
func (td *TimingData) Canonicalize() {
if td == nil {
return
}
ts := []**time.Time{&td.PublicScheduled, &td.WorkingScheduled, &td.PublicEstimated, &td.WorkingEstimated, &td.Actual}
for _, t := range ts {
if *t == nil || (*t).IsZero() {
*t = nil
}
}
}
type PlatformData struct {
Scheduled string `json:"scheduled"`
Live string `json:"live"`
Confirmed bool `json:"confirmed"`
Suppressed bool `json:"platform_suppressed"`
}
func (pd *PlatformData) IsEmpty() bool {
return pd.Scheduled == "" && pd.Live == ""
}
func (pd *PlatformData) Canonicalize() {}
type ServiceLocation struct {
ID int `json:"id"`
Location *Location `json:"location"`
CallingPointType string `json:"calling_point_type"`
Length *int `json:"train_length"`
Suppressed bool `json:"service_suppressed"`
Platform *PlatformData `json:"platform"`
Cancelled bool `json:"cancelled"`
FalseDestination *Location `json:"false_destination"`
ArrivalTiming *TimingData `json:"arrival_timing"`
DepartureTiming *TimingData `json:"departure_timing"`
PassTiming *TimingData `json:"pass_timing"`
}
func (sl *ServiceLocation) Canonicalize() {
if sl == nil {
return
}
sl.Location.Canonicalize()
if sl.Length == nil || *sl.Length == 0 {
sl.Length = nil
}
if sl.Platform.IsEmpty() {
sl.Platform = nil
}
sl.Platform.Canonicalize()
if sl.FalseDestination.IsEmpty() {
sl.FalseDestination = nil
}
sl.FalseDestination.Canonicalize()
if sl.ArrivalTiming.IsEmpty() {
sl.ArrivalTiming = nil
}
sl.ArrivalTiming.Canonicalize()
if sl.DepartureTiming.IsEmpty() {
sl.DepartureTiming = nil
}
sl.DepartureTiming.Canonicalize()
if sl.PassTiming.IsEmpty() {
sl.PassTiming = nil
}
sl.PassTiming.Canonicalize()
}

View file

@ -0,0 +1,14 @@
# SPDX-FileCopyrightText: 2020 Luke Granger-Brown <depot@lukegb.com>
#
# SPDX-License-Identifier: Apache-2.0
{ depot, ... }:
depot.third_party.buildGo.external {
path = "github.com/cenkalti/backoff/v4";
src = depot.third_party.nixpkgs.fetchFromGitHub {
owner = "cenkalti";
repo = "backoff";
rev = "v4.1.2";
hash = "sha256:08c28226q612i1pv83161y57qh16631vpc51ai9f76qfrzsy946z";
};
}

View file

@ -0,0 +1,14 @@
# SPDX-FileCopyrightText: 2020 Luke Granger-Brown <depot@lukegb.com>
#
# SPDX-License-Identifier: Apache-2.0
{ depot, ... }:
depot.third_party.buildGo.external {
path = "github.com/go-stomp/stomp/v3";
src = depot.third_party.nixpkgs.fetchFromGitHub {
owner = "go-stomp";
repo = "stomp";
rev = "v3.0.3";
hash = "sha256:0plm1p5nibqaaqbs4qxaxc3vr9h5xwx5jp3z2fv3nip07slg7rb6";
};
}

View file

@ -0,0 +1,14 @@
# SPDX-FileCopyrightText: 2020 Luke Granger-Brown <depot@lukegb.com>
#
# SPDX-License-Identifier: Apache-2.0
{ depot, ... }:
depot.third_party.buildGo.external {
path = "github.com/jlaffaye/ftp";
src = depot.third_party.nixpkgs.fetchFromGitHub {
owner = "jlaffaye";
repo = "ftp";
rev = "11820403398b118877718a169d6368edc6e3ef9a";
hash = "sha256:18v142q292wp5y8flnq15iyfb33801xz1sg4c9pn2aibi546j011";
};
}