using easyjson.

This commit is contained in:
fiatjaf 2023-04-16 15:56:50 -03:00
parent af52f8d490
commit 0a3e898c2f
No known key found for this signature in database
GPG Key ID: BAD43C4BE5C1A3A1
8 changed files with 517 additions and 327 deletions

View File

@ -3,27 +3,33 @@ package nostr
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/mailru/easyjson"
)
type Event struct {
ID string
PubKey string
CreatedAt time.Time
Kind int
Tags Tags
Content string
Sig string
ID string `json:"id"`
PubKey string `json:"pubkey"`
CreatedAt Timestamp `json:"created_at"`
Kind int `json:"kind"`
Tags Tags `json:"tags"`
Content string `json:"content"`
Sig string `json:"sig"`
// anything here will be mashed together with the main event object when serializing
extra map[string]any
}
type Timestamp int64
func (t Timestamp) Time() time.Time {
return time.Unix(int64(t), 0)
}
const (
KindSetMetadata int = 0
KindTextNote int = 1
@ -44,7 +50,7 @@ const (
// Event Stringer interface, just returns the raw JSON as a string
func (evt Event) String() string {
j, _ := json.Marshal(evt)
j, _ := easyjson.Marshal(evt)
return string(j)
}
@ -67,7 +73,7 @@ func (evt *Event) Serialize() []byte {
fmt.Sprintf(
"[0,\"%s\",%d,%d,",
evt.PubKey,
evt.CreatedAt.Unix(),
evt.CreatedAt,
evt.Kind,
))...)

View File

@ -1,142 +0,0 @@
package nostr
import (
"bytes"
"encoding/json"
"fmt"
"time"
"github.com/valyala/fastjson"
)
func (evt *Event) UnmarshalJSON(payload []byte) error {
var fastjsonParser fastjson.Parser
parsed, err := fastjsonParser.ParseBytes(payload)
if err != nil {
return fmt.Errorf("failed to parse event: %w", err)
}
obj, err := parsed.Object()
if err != nil {
return fmt.Errorf("event is not an object: %w", err)
}
// prepare this to receive any extra property that may serialized along with the event
evt.extra = make(map[string]any)
var visiterr error
obj.Visit(func(k []byte, v *fastjson.Value) {
key := string(k)
switch key {
case "id":
id, err := v.StringBytes()
if err != nil {
visiterr = fmt.Errorf("invalid 'id' field: %w", err)
}
evt.ID = string(id)
case "pubkey":
id, err := v.StringBytes()
if err != nil {
visiterr = fmt.Errorf("invalid 'pubkey' field: %w", err)
}
evt.PubKey = string(id)
case "created_at":
val, err := v.Int64()
if err != nil {
visiterr = fmt.Errorf("invalid 'created_at' field: %w", err)
}
evt.CreatedAt = time.Unix(val, 0)
case "kind":
kind, err := v.Int64()
if err != nil {
visiterr = fmt.Errorf("invalid 'kind' field: %w", err)
}
evt.Kind = int(kind)
case "tags":
evt.Tags, err = fastjsonArrayToTags(v)
if err != nil {
visiterr = fmt.Errorf("invalid '%s' field: %w", key, err)
}
case "content":
id, err := v.StringBytes()
if err != nil {
visiterr = fmt.Errorf("invalid 'content' field: %w", err)
}
evt.Content = string(id)
case "sig":
id, err := v.StringBytes()
if err != nil {
visiterr = fmt.Errorf("invalid 'sig' field: %w", err)
}
evt.Sig = string(id)
default:
var anyValue any
json.Unmarshal(v.MarshalTo([]byte{}), &anyValue)
evt.extra[key] = anyValue
}
})
return visiterr
}
// unmarshaling helper
func fastjsonArrayToTags(v *fastjson.Value) (Tags, error) {
arr, err := v.Array()
if err != nil {
return nil, err
}
sll := make(Tags, len(arr))
for i, v := range arr {
subarr, err := v.Array()
if err != nil {
return nil, err
}
sl := make(Tag, len(subarr))
for j, subv := range subarr {
sb, err := subv.StringBytes()
if err != nil {
return nil, err
}
sl[j] = string(sb)
}
sll[i] = sl
}
return sll, nil
}
// MarshalJSON() returns the JSON byte encoding of the event, as in NIP-01.
func (evt Event) MarshalJSON() ([]byte, error) {
dst := make([]byte, 0)
dst = append(dst, '{')
dst = append(dst, []byte(fmt.Sprintf("\"id\":\"%s\",\"pubkey\":\"%s\",\"created_at\":%d,\"kind\":%d,\"tags\":",
evt.ID,
evt.PubKey,
evt.CreatedAt.Unix(),
evt.Kind,
))...)
dst = evt.Tags.marshalTo(dst)
dst = append(dst, []byte(",\"content\":")...)
dst = escapeString(dst, evt.Content)
dst = append(dst, []byte(fmt.Sprintf(",\"sig\":\"%s\"",
evt.Sig,
))...)
// slower marshaling of "any" interface type
if evt.extra != nil {
buf := bytes.NewBuffer(nil)
enc := json.NewEncoder(buf)
enc.SetEscapeHTML(false)
for k, v := range evt.extra {
if e := enc.Encode(v); e == nil {
dst = append(dst, ',')
dst = escapeString(dst, k)
dst = append(dst, ':')
dst = append(dst, buf.Bytes()[:buf.Len()-1]...)
}
buf.Reset()
}
}
dst = append(dst, '}')
return dst, nil
}

199
event_easyjson.go Normal file
View File

@ -0,0 +1,199 @@
// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT.
package nostr
import (
json "encoding/json"
easyjson "github.com/mailru/easyjson"
jlexer "github.com/mailru/easyjson/jlexer"
jwriter "github.com/mailru/easyjson/jwriter"
)
// suppress unused package warning
var (
_ *json.RawMessage
_ *jlexer.Lexer
_ *jwriter.Writer
_ easyjson.Marshaler
)
func easyjsonF642ad3eDecodeGithubComNbdWtfGoNostr(in *jlexer.Lexer, out *Event) {
isTopLevel := in.IsStart()
if in.IsNull() {
if isTopLevel {
in.Consumed()
}
in.Skip()
return
}
in.Delim('{')
for !in.IsDelim('}') {
key := in.UnsafeFieldName(true)
in.WantColon()
if in.IsNull() {
in.Skip()
in.WantComma()
continue
}
switch key {
case "id":
out.ID = in.UnsafeString()
case "pubkey":
out.PubKey = in.UnsafeString()
case "created_at":
out.CreatedAt = Timestamp(in.Int64())
case "kind":
out.Kind = in.Int()
case "tags":
if in.IsNull() {
in.Skip()
out.Tags = nil
} else {
in.Delim('[')
if out.Tags == nil {
if !in.IsDelim(']') {
out.Tags = make(Tags, 0, 7)
} else {
out.Tags = Tags{}
}
} else {
out.Tags = (out.Tags)[:0]
}
for !in.IsDelim(']') {
var v1 Tag
if in.IsNull() {
in.Skip()
v1 = nil
} else {
in.Delim('[')
if v1 == nil {
if !in.IsDelim(']') {
v1 = make(Tag, 0, 5)
} else {
v1 = Tag{}
}
} else {
v1 = (v1)[:0]
}
for !in.IsDelim(']') {
var v2 string
v2 = string(in.String())
v1 = append(v1, v2)
in.WantComma()
}
in.Delim(']')
}
out.Tags = append(out.Tags, v1)
in.WantComma()
}
in.Delim(']')
}
case "content":
out.Content = in.UnsafeString()
case "sig":
out.Sig = in.UnsafeString()
default:
out.extra[key] = in.Interface()
}
in.WantComma()
}
in.Delim('}')
if isTopLevel {
in.Consumed()
}
}
func easyjsonF642ad3eEncodeGithubComNbdWtfGoNostr(out *jwriter.Writer, in Event) {
out.RawByte('{')
first := true
_ = first
{
const prefix string = ",\"id\":"
out.RawString(prefix[1:])
out.String(in.ID)
}
{
const prefix string = ",\"pubkey\":"
out.RawString(prefix)
out.String(in.PubKey)
}
{
const prefix string = ",\"created_at\":"
out.RawString(prefix)
out.Int64(int64(in.CreatedAt))
}
{
const prefix string = ",\"kind\":"
out.RawString(prefix)
out.Int(in.Kind)
}
{
const prefix string = ",\"tags\":"
out.RawString(prefix)
if in.Tags == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 {
out.RawString("null")
} else {
out.RawByte('[')
for v3, v4 := range in.Tags {
if v3 > 0 {
out.RawByte(',')
}
if v4 == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 {
out.RawString("null")
} else {
out.RawByte('[')
for v5, v6 := range v4 {
if v5 > 0 {
out.RawByte(',')
}
out.String(string(v6))
}
out.RawByte(']')
}
}
out.RawByte(']')
}
}
{
const prefix string = ",\"content\":"
out.RawString(prefix)
out.String(in.Content)
}
{
const prefix string = ",\"sig\":"
out.RawString(prefix)
out.String(in.Sig)
}
{
for key, value := range in.extra {
out.RawString(",\"" + key + "\":")
out.Raw(json.Marshal(value))
}
}
out.RawByte('}')
}
// MarshalJSON supports json.Marshaler interface
func (v Event) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{}
easyjsonF642ad3eEncodeGithubComNbdWtfGoNostr(&w, v)
return w.Buffer.BuildBytes(), w.Error
}
// MarshalEasyJSON supports easyjson.Marshaler interface
func (v Event) MarshalEasyJSON(w *jwriter.Writer) {
easyjsonF642ad3eEncodeGithubComNbdWtfGoNostr(w, v)
}
// UnmarshalJSON supports json.Unmarshaler interface
func (v *Event) UnmarshalJSON(data []byte) error {
r := jlexer.Lexer{Data: data}
easyjsonF642ad3eDecodeGithubComNbdWtfGoNostr(&r, v)
return r.Error()
}
// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
func (v *Event) UnmarshalEasyJSON(l *jlexer.Lexer) {
easyjsonF642ad3eDecodeGithubComNbdWtfGoNostr(l, v)
}

View File

@ -2,7 +2,6 @@ package nostr
import (
"encoding/json"
"time"
"golang.org/x/exp/slices"
)
@ -14,8 +13,8 @@ type Filter struct {
Kinds []int
Authors []string
Tags TagMap
Since *time.Time
Until *time.Time
Since Timestamp
Until Timestamp
Limit int
Search string
}
@ -64,11 +63,11 @@ func (ef Filter) Matches(event *Event) bool {
}
}
if ef.Since != nil && time.Time(event.CreatedAt).Before(*ef.Since) {
if event.CreatedAt < ef.Since {
return false
}
if ef.Until != nil && time.Time(event.CreatedAt).After(*ef.Until) {
if ef.Until != 0 && event.CreatedAt > ef.Until {
return false
}

View File

@ -1,170 +0,0 @@
package nostr
import (
"fmt"
"strings"
"time"
"github.com/valyala/fastjson"
)
func (f *Filter) UnmarshalJSON(payload []byte) error {
var fastjsonParser fastjson.Parser
parsed, err := fastjsonParser.ParseBytes(payload)
if err != nil {
return fmt.Errorf("failed to parse filter: %w", err)
}
obj, err := parsed.Object()
if err != nil {
return fmt.Errorf("filter is not an object")
}
f.Tags = make(TagMap)
var visiterr error
obj.Visit(func(k []byte, v *fastjson.Value) {
key := string(k)
switch key {
case "ids":
f.IDs, err = fastjsonArrayToStringList(v)
if err != nil {
visiterr = fmt.Errorf("invalid 'ids' field: %w", err)
}
case "kinds":
f.Kinds, err = fastjsonArrayToIntList(v)
if err != nil {
visiterr = fmt.Errorf("invalid 'kinds' field: %w", err)
}
case "authors":
f.Authors, err = fastjsonArrayToStringList(v)
if err != nil {
visiterr = fmt.Errorf("invalid 'authors' field: %w", err)
}
case "since":
val, err := v.Int64()
if err != nil {
visiterr = fmt.Errorf("invalid 'since' field: %w", err)
}
tm := time.Unix(val, 0)
f.Since = &tm
case "until":
val, err := v.Int64()
if err != nil {
visiterr = fmt.Errorf("invalid 'until' field: %w", err)
}
tm := time.Unix(val, 0)
f.Until = &tm
case "limit":
val, err := v.Int()
if err != nil {
visiterr = fmt.Errorf("invalid 'limit' field: %w", err)
}
f.Limit = val
case "search":
val, err := v.StringBytes()
if err != nil {
visiterr = fmt.Errorf("invalid 'search' field: %w", err)
}
f.Search = string(val)
default:
if strings.HasPrefix(key, "#") {
f.Tags[key[1:]], err = fastjsonArrayToStringList(v)
if err != nil {
visiterr = fmt.Errorf("invalid '%s' field: %w", key, err)
}
}
}
})
if visiterr != nil {
return visiterr
}
return nil
}
func (f Filter) MarshalJSON() ([]byte, error) {
var arena fastjson.Arena
o := arena.NewObject()
if f.IDs != nil {
o.Set("ids", stringListToFastjsonArray(&arena, f.IDs))
}
if f.Kinds != nil {
o.Set("kinds", intListToFastjsonArray(&arena, f.Kinds))
}
if f.Authors != nil {
o.Set("authors", stringListToFastjsonArray(&arena, f.Authors))
}
if f.Since != nil {
o.Set("since", arena.NewNumberInt(int(f.Since.Unix())))
}
if f.Until != nil {
o.Set("until", arena.NewNumberInt(int(f.Until.Unix())))
}
if f.Tags != nil {
for k, v := range f.Tags {
o.Set("#"+k, stringListToFastjsonArray(&arena, v))
}
}
if f.Limit != 0 {
o.Set("limit", arena.NewNumberInt(f.Limit))
}
if f.Search != "" {
o.Set("search", arena.NewString(f.Search))
}
return o.MarshalTo(nil), nil
}
func stringListToFastjsonArray(arena *fastjson.Arena, sl []string) *fastjson.Value {
arr := arena.NewArray()
for i, v := range sl {
arr.SetArrayItem(i, arena.NewString(v))
}
return arr
}
func intListToFastjsonArray(arena *fastjson.Arena, il []int) *fastjson.Value {
arr := arena.NewArray()
for i, v := range il {
arr.SetArrayItem(i, arena.NewNumberInt(v))
}
return arr
}
func fastjsonArrayToStringList(v *fastjson.Value) ([]string, error) {
arr, err := v.Array()
if err != nil {
return nil, err
}
sl := make([]string, len(arr))
for i, v := range arr {
sb, err := v.StringBytes()
if err != nil {
return nil, err
}
sl[i] = string(sb)
}
return sl, nil
}
func fastjsonArrayToIntList(v *fastjson.Value) ([]int, error) {
arr, err := v.Array()
if err != nil {
return nil, err
}
il := make([]int, len(arr))
for i, v := range arr {
il[i], err = v.Int()
if err != nil {
return nil, err
}
}
return il, nil
}

292
filter_easyjson.go Normal file
View File

@ -0,0 +1,292 @@
// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT.
package nostr
import (
json "encoding/json"
easyjson "github.com/mailru/easyjson"
jlexer "github.com/mailru/easyjson/jlexer"
jwriter "github.com/mailru/easyjson/jwriter"
)
// suppress unused package warning
var (
_ *json.RawMessage
_ *jlexer.Lexer
_ *jwriter.Writer
_ easyjson.Marshaler
)
func easyjson4d398eaaDecodeGithubComNbdWtfGoNostr(in *jlexer.Lexer, out *Filter) {
isTopLevel := in.IsStart()
if in.IsNull() {
if isTopLevel {
in.Consumed()
}
in.Skip()
return
}
in.Delim('{')
for !in.IsDelim('}') {
key := in.UnsafeFieldName(false)
in.WantColon()
if in.IsNull() {
in.Skip()
in.WantComma()
continue
}
switch key {
case "IDs":
if in.IsNull() {
in.Skip()
out.IDs = nil
} else {
in.Delim('[')
if out.IDs == nil {
if !in.IsDelim(']') {
out.IDs = make([]string, 0, 4)
} else {
out.IDs = []string{}
}
} else {
out.IDs = (out.IDs)[:0]
}
for !in.IsDelim(']') {
var v1 string
v1 = string(in.String())
out.IDs = append(out.IDs, v1)
in.WantComma()
}
in.Delim(']')
}
case "Kinds":
if in.IsNull() {
in.Skip()
out.Kinds = nil
} else {
in.Delim('[')
if out.Kinds == nil {
if !in.IsDelim(']') {
out.Kinds = make([]int, 0, 8)
} else {
out.Kinds = []int{}
}
} else {
out.Kinds = (out.Kinds)[:0]
}
for !in.IsDelim(']') {
var v2 int
v2 = int(in.Int())
out.Kinds = append(out.Kinds, v2)
in.WantComma()
}
in.Delim(']')
}
case "Authors":
if in.IsNull() {
in.Skip()
out.Authors = nil
} else {
in.Delim('[')
if out.Authors == nil {
if !in.IsDelim(']') {
out.Authors = make([]string, 0, 4)
} else {
out.Authors = []string{}
}
} else {
out.Authors = (out.Authors)[:0]
}
for !in.IsDelim(']') {
var v3 string
v3 = string(in.String())
out.Authors = append(out.Authors, v3)
in.WantComma()
}
in.Delim(']')
}
case "Tags":
if in.IsNull() {
in.Skip()
} else {
in.Delim('{')
out.Tags = make(TagMap)
for !in.IsDelim('}') {
key := string(in.String())
in.WantColon()
var v4 []string
if in.IsNull() {
in.Skip()
v4 = nil
} else {
in.Delim('[')
if v4 == nil {
if !in.IsDelim(']') {
v4 = make([]string, 0, 4)
} else {
v4 = []string{}
}
} else {
v4 = (v4)[:0]
}
for !in.IsDelim(']') {
var v5 string
v5 = string(in.String())
v4 = append(v4, v5)
in.WantComma()
}
in.Delim(']')
}
(out.Tags)[key] = v4
in.WantComma()
}
in.Delim('}')
}
case "Since":
out.Since = Timestamp(in.Int64())
case "Until":
out.Until = Timestamp(in.Int64())
case "Limit":
out.Limit = int(in.Int())
case "Search":
out.Search = string(in.String())
default:
in.SkipRecursive()
}
in.WantComma()
}
in.Delim('}')
if isTopLevel {
in.Consumed()
}
}
func easyjson4d398eaaEncodeGithubComNbdWtfGoNostr(out *jwriter.Writer, in Filter) {
out.RawByte('{')
first := true
_ = first
{
const prefix string = ",\"IDs\":"
out.RawString(prefix[1:])
if in.IDs == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 {
out.RawString("null")
} else {
out.RawByte('[')
for v6, v7 := range in.IDs {
if v6 > 0 {
out.RawByte(',')
}
out.String(string(v7))
}
out.RawByte(']')
}
}
{
const prefix string = ",\"Kinds\":"
out.RawString(prefix)
if in.Kinds == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 {
out.RawString("null")
} else {
out.RawByte('[')
for v8, v9 := range in.Kinds {
if v8 > 0 {
out.RawByte(',')
}
out.Int(int(v9))
}
out.RawByte(']')
}
}
{
const prefix string = ",\"Authors\":"
out.RawString(prefix)
if in.Authors == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 {
out.RawString("null")
} else {
out.RawByte('[')
for v10, v11 := range in.Authors {
if v10 > 0 {
out.RawByte(',')
}
out.String(string(v11))
}
out.RawByte(']')
}
}
{
const prefix string = ",\"Tags\":"
out.RawString(prefix)
if in.Tags == nil && (out.Flags&jwriter.NilMapAsEmpty) == 0 {
out.RawString(`null`)
} else {
out.RawByte('{')
v12First := true
for v12Name, v12Value := range in.Tags {
if v12First {
v12First = false
} else {
out.RawByte(',')
}
out.String(string(v12Name))
out.RawByte(':')
if v12Value == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 {
out.RawString("null")
} else {
out.RawByte('[')
for v13, v14 := range v12Value {
if v13 > 0 {
out.RawByte(',')
}
out.String(string(v14))
}
out.RawByte(']')
}
}
out.RawByte('}')
}
}
{
const prefix string = ",\"Since\":"
out.RawString(prefix)
out.Int64(int64(in.Since))
}
{
const prefix string = ",\"Until\":"
out.RawString(prefix)
out.Int64(int64(in.Until))
}
{
const prefix string = ",\"Limit\":"
out.RawString(prefix)
out.Int(int(in.Limit))
}
{
const prefix string = ",\"Search\":"
out.RawString(prefix)
out.String(string(in.Search))
}
out.RawByte('}')
}
// MarshalJSON supports json.Marshaler interface
func (v Filter) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{}
easyjson4d398eaaEncodeGithubComNbdWtfGoNostr(&w, v)
return w.Buffer.BuildBytes(), w.Error
}
// MarshalEasyJSON supports easyjson.Marshaler interface
func (v Filter) MarshalEasyJSON(w *jwriter.Writer) {
easyjson4d398eaaEncodeGithubComNbdWtfGoNostr(w, v)
}
// UnmarshalJSON supports json.Unmarshaler interface
func (v *Filter) UnmarshalJSON(data []byte) error {
r := jlexer.Lexer{Data: data}
easyjson4d398eaaDecodeGithubComNbdWtfGoNostr(&r, v)
return r.Error()
}
// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
func (v *Filter) UnmarshalEasyJSON(l *jlexer.Lexer) {
easyjson4d398eaaDecodeGithubComNbdWtfGoNostr(l, v)
}

2
go.mod
View File

@ -7,6 +7,7 @@ require (
github.com/btcsuite/btcd/btcec/v2 v2.2.0
github.com/btcsuite/btcd/btcutil v1.1.3
github.com/gorilla/websocket v1.4.2
github.com/mailru/easyjson v0.7.7
github.com/tyler-smith/go-bip32 v1.0.0
github.com/tyler-smith/go-bip39 v1.1.0
github.com/valyala/fastjson v1.6.3
@ -20,5 +21,6 @@ require (
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 // indirect
github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
github.com/josharian/intern v1.0.0 // indirect
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect
)

4
go.sum
View File

@ -57,8 +57,12 @@ github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=