Merge pull request #18 from unistack-org/flow
flow: initial tests
This commit was merged in pull request #18.
	This commit is contained in:
		
							
								
								
									
										2
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							| @@ -11,7 +11,7 @@ jobs: | |||||||
|     - name: setup |     - name: setup | ||||||
|       uses: actions/setup-go@v2 |       uses: actions/setup-go@v2 | ||||||
|       with: |       with: | ||||||
|         go-version: 1.15 |         go-version: 1.16 | ||||||
|     - name: cache |     - name: cache | ||||||
|       uses: actions/cache@v2 |       uses: actions/cache@v2 | ||||||
|       with: |       with: | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								.github/workflows/pr.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/pr.yml
									
									
									
									
										vendored
									
									
								
							| @@ -11,7 +11,7 @@ jobs: | |||||||
|     - name: setup |     - name: setup | ||||||
|       uses: actions/setup-go@v2 |       uses: actions/setup-go@v2 | ||||||
|       with: |       with: | ||||||
|         go-version: 1.15 |         go-version: 1.16 | ||||||
|     - name: cache |     - name: cache | ||||||
|       uses: actions/cache@v2 |       uses: actions/cache@v2 | ||||||
|       with: |       with: | ||||||
|   | |||||||
| @@ -3,14 +3,13 @@ package broker | |||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"math/rand" |  | ||||||
| 	"sync" | 	"sync" | ||||||
| 	"time" |  | ||||||
|  |  | ||||||
| 	"github.com/google/uuid" | 	"github.com/google/uuid" | ||||||
| 	"github.com/unistack-org/micro/v3/logger" | 	"github.com/unistack-org/micro/v3/logger" | ||||||
| 	maddr "github.com/unistack-org/micro/v3/util/addr" | 	maddr "github.com/unistack-org/micro/v3/util/addr" | ||||||
| 	mnet "github.com/unistack-org/micro/v3/util/net" | 	mnet "github.com/unistack-org/micro/v3/util/net" | ||||||
|  | 	"github.com/unistack-org/micro/v3/util/rand" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type memoryBroker struct { | type memoryBroker struct { | ||||||
| @@ -59,7 +58,8 @@ func (m *memoryBroker) Connect(ctx context.Context) error { | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	i := rand.Intn(20000) | 	var rng rand.Rand | ||||||
|  | 	i := rng.Intn(20000) | ||||||
| 	// set addr with port | 	// set addr with port | ||||||
| 	addr = mnet.HostPort(addr, 10000+i) | 	addr = mnet.HostPort(addr, 10000+i) | ||||||
|  |  | ||||||
| @@ -237,8 +237,6 @@ func (m *memorySubscriber) Unsubscribe(ctx context.Context) error { | |||||||
|  |  | ||||||
| // NewBroker return new memory broker | // NewBroker return new memory broker | ||||||
| func NewBroker(opts ...Option) Broker { | func NewBroker(opts ...Option) Broker { | ||||||
| 	rand.Seed(time.Now().UnixNano()) |  | ||||||
|  |  | ||||||
| 	return &memoryBroker{ | 	return &memoryBroker{ | ||||||
| 		opts:        NewOptions(opts...), | 		opts:        NewOptions(opts...), | ||||||
| 		Subscribers: make(map[string][]*memorySubscriber), | 		Subscribers: make(map[string][]*memorySubscriber), | ||||||
|   | |||||||
| @@ -3,7 +3,6 @@ package codec | |||||||
| import ( | import ( | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| 	"io" | 	"io" | ||||||
| 	"io/ioutil" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type noopCodec struct { | type noopCodec struct { | ||||||
| @@ -20,7 +19,7 @@ func (c *noopCodec) ReadHeader(conn io.Reader, m *Message, t MessageType) error | |||||||
|  |  | ||||||
| func (c *noopCodec) ReadBody(conn io.Reader, b interface{}) error { | func (c *noopCodec) ReadBody(conn io.Reader, b interface{}) error { | ||||||
| 	// read bytes | 	// read bytes | ||||||
| 	buf, err := ioutil.ReadAll(conn) | 	buf, err := io.ReadAll(conn) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|   | |||||||
							
								
								
									
										21
									
								
								flow/dag.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								flow/dag.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | |||||||
|  | package flow | ||||||
|  |  | ||||||
|  | type node struct { | ||||||
|  | 	name string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (n *node) ID() string { | ||||||
|  | 	return n.name | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (n *node) Name() string { | ||||||
|  | 	return n.name | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (n *node) String() string { | ||||||
|  | 	return n.name | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (n *node) Hashcode() interface{} { | ||||||
|  | 	return n.name | ||||||
|  | } | ||||||
							
								
								
									
										68
									
								
								flow/dag_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								flow/dag_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,68 @@ | |||||||
|  | package flow | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"github.com/silas/dag" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func checkErr(t *testing.T, err error) { | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestDag(t *testing.T) { | ||||||
|  | 	d1 := &dag.AcyclicGraph{} | ||||||
|  | 	d2 := &dag.AcyclicGraph{} | ||||||
|  | 	d2v1 := d2.Add(&node{"Substep.Create"}) | ||||||
|  | 	v1 := d1.Add(&node{"AccountService.Create"}) | ||||||
|  | 	v2 := d1.Add(&node{"AuthzService.Create"}) | ||||||
|  | 	v3 := d1.Add(&node{"AuthnService.Create"}) | ||||||
|  | 	v4 := d1.Add(&node{"ProjectService.Create"}) | ||||||
|  | 	v5 := d1.Add(&node{"ContactService.Create"}) | ||||||
|  | 	v6 := d1.Add(&node{"NetworkService.Create"}) | ||||||
|  | 	v7 := d1.Add(&node{"MailerService.Create"}) | ||||||
|  | 	v8 := d1.Add(&node{"NestedService.Create"}) | ||||||
|  | 	v9 := d1.Add(d2v1) | ||||||
|  | 	d1.Connect(dag.BasicEdge(v1, v2)) | ||||||
|  | 	d1.Connect(dag.BasicEdge(v1, v3)) | ||||||
|  | 	d1.Connect(dag.BasicEdge(v1, v4)) | ||||||
|  | 	d1.Connect(dag.BasicEdge(v1, v5)) | ||||||
|  | 	d1.Connect(dag.BasicEdge(v1, v6)) | ||||||
|  | 	d1.Connect(dag.BasicEdge(v1, v7)) | ||||||
|  | 	d1.Connect(dag.BasicEdge(v7, v8)) | ||||||
|  | 	d1.Connect(dag.BasicEdge(v8, v9)) | ||||||
|  |  | ||||||
|  | 	if err := d1.Validate(); err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	d1.TransitiveReduction() | ||||||
|  |  | ||||||
|  | 	var steps [][]string | ||||||
|  | 	fn := func(n dag.Vertex, idx int) error { | ||||||
|  | 		if idx == 0 { | ||||||
|  | 			steps = make([][]string, 1, 1) | ||||||
|  | 			steps[0] = make([]string, 0, 1) | ||||||
|  | 		} else if idx >= len(steps) { | ||||||
|  | 			tsteps := make([][]string, idx+1, idx+1) | ||||||
|  | 			copy(tsteps, steps) | ||||||
|  | 			steps = tsteps | ||||||
|  | 			steps[idx] = make([]string, 0, 1) | ||||||
|  | 		} | ||||||
|  | 		steps[idx] = append(steps[idx], fmt.Sprintf("%s", n)) | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	start := &node{"AccountService.Create"} | ||||||
|  | 	err := d1.SortedDepthFirstWalk([]dag.Vertex{start}, fn) | ||||||
|  | 	checkErr(t, err) | ||||||
|  | 	if len(steps) != 4 { | ||||||
|  | 		t.Fatalf("invalid steps: %#+v", steps) | ||||||
|  | 	} | ||||||
|  | 	if steps[3][0] != "Substep.Create" { | ||||||
|  | 		t.Fatalf("invalid last step: %#+v", steps) | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										7
									
								
								flow/flow.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								flow/flow.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | |||||||
|  | // Package flow is an interface used for saga pattern messaging | ||||||
|  | package flow | ||||||
|  |  | ||||||
|  | type Step interface { | ||||||
|  | 	// Endpoint returns service_name.service_method | ||||||
|  | 	Endpoint() string | ||||||
|  | } | ||||||
							
								
								
									
										4
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								go.mod
									
									
									
									
									
								
							| @@ -1,15 +1,17 @@ | |||||||
| module github.com/unistack-org/micro/v3 | module github.com/unistack-org/micro/v3 | ||||||
|  |  | ||||||
| go 1.14 | go 1.16 | ||||||
|  |  | ||||||
| require ( | require ( | ||||||
| 	github.com/dgrijalva/jwt-go v3.2.0+incompatible | 	github.com/dgrijalva/jwt-go v3.2.0+incompatible | ||||||
| 	github.com/ef-ds/deque v1.0.4 | 	github.com/ef-ds/deque v1.0.4 | ||||||
| 	github.com/google/uuid v1.2.0 | 	github.com/google/uuid v1.2.0 | ||||||
|  | 	github.com/heimdalr/dag v1.0.1 // indirect | ||||||
| 	github.com/imdario/mergo v0.3.11 | 	github.com/imdario/mergo v0.3.11 | ||||||
| 	github.com/kr/text v0.2.0 // indirect | 	github.com/kr/text v0.2.0 // indirect | ||||||
| 	github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect | 	github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect | ||||||
| 	github.com/patrickmn/go-cache v2.1.0+incompatible | 	github.com/patrickmn/go-cache v2.1.0+incompatible | ||||||
|  | 	github.com/silas/dag v0.0.0-20210121180416-41cf55125c34 // indirect | ||||||
| 	golang.org/x/net v0.0.0-20210119194325-5f4716e94777 | 	golang.org/x/net v0.0.0-20210119194325-5f4716e94777 | ||||||
| 	gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect | 	gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect | ||||||
| ) | ) | ||||||
|   | |||||||
							
								
								
									
										10
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								go.sum
									
									
									
									
									
								
							| @@ -3,8 +3,13 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumC | |||||||
| github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= | ||||||
| github.com/ef-ds/deque v1.0.4 h1:iFAZNmveMT9WERAkqLJ+oaABF9AcVQ5AjXem/hroniI= | github.com/ef-ds/deque v1.0.4 h1:iFAZNmveMT9WERAkqLJ+oaABF9AcVQ5AjXem/hroniI= | ||||||
| github.com/ef-ds/deque v1.0.4/go.mod h1:gXDnTC3yqvBcHbq2lcExjtAcVrOnJCbMcZXmuj8Z4tg= | github.com/ef-ds/deque v1.0.4/go.mod h1:gXDnTC3yqvBcHbq2lcExjtAcVrOnJCbMcZXmuj8Z4tg= | ||||||
|  | github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M= | ||||||
|  | github.com/go-test/deep v1.0.7/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8= | ||||||
|  | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | ||||||
| github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= | github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= | ||||||
| github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | ||||||
|  | github.com/heimdalr/dag v1.0.1 h1:iR2K3DSUFDYx0GeV7iXBnZkedWS1xePSGrylQ197uxg= | ||||||
|  | github.com/heimdalr/dag v1.0.1/go.mod h1:t+ZkR+sjKL4xhlE1B9rwpvwfo+x+2R0363efS+Oghns= | ||||||
| github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= | github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= | ||||||
| github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= | github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= | ||||||
| github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= | ||||||
| @@ -15,13 +20,12 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWb | |||||||
| github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= | ||||||
| github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= | github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= | ||||||
| github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= | github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= | ||||||
| github.com/unistack-org/micro v1.18.0 h1:EbFiII0bKV0Xcua7o6J30MFmm4/g0Hv3ECOKzsUBihU= | github.com/silas/dag v0.0.0-20210121180416-41cf55125c34 h1:vBfVmA5mZhsQa2jr1FOL9nfA37N/jnbBmi5XUfviVTI= | ||||||
|  | github.com/silas/dag v0.0.0-20210121180416-41cf55125c34/go.mod h1:7RTUFBdIRC9nZ7/3RyRNH1bdqIShrDejd1YbLwgPS+I= | ||||||
| golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew= | golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew= | ||||||
| golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= | golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= | ||||||
| golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= |  | ||||||
| golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
| golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | ||||||
| golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= |  | ||||||
| golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||||||
| golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||||||
| gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||||
|   | |||||||
| @@ -4,13 +4,13 @@ import ( | |||||||
| 	"context" | 	"context" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"math/rand" |  | ||||||
| 	"net" | 	"net" | ||||||
| 	"sync" | 	"sync" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| 	maddr "github.com/unistack-org/micro/v3/util/addr" | 	maddr "github.com/unistack-org/micro/v3/util/addr" | ||||||
| 	mnet "github.com/unistack-org/micro/v3/util/net" | 	mnet "github.com/unistack-org/micro/v3/util/net" | ||||||
|  | 	"github.com/unistack-org/micro/v3/util/rand" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type memorySocket struct { | type memorySocket struct { | ||||||
| @@ -207,7 +207,8 @@ func (m *memoryTransport) Listen(ctx context.Context, addr string, opts ...Liste | |||||||
|  |  | ||||||
| 	// if zero port then randomly assign one | 	// if zero port then randomly assign one | ||||||
| 	if len(port) > 0 && port == "0" { | 	if len(port) > 0 && port == "0" { | ||||||
| 		i := rand.Intn(20000) | 		var rng rand.Rand | ||||||
|  | 		i := rng.Intn(20000) | ||||||
| 		port = fmt.Sprintf("%d", 10000+i) | 		port = fmt.Sprintf("%d", 10000+i) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -255,8 +256,6 @@ func (m *memoryTransport) Name() string { | |||||||
| func NewTransport(opts ...Option) Transport { | func NewTransport(opts ...Option) Transport { | ||||||
| 	options := NewOptions(opts...) | 	options := NewOptions(opts...) | ||||||
|  |  | ||||||
| 	rand.Seed(time.Now().UnixNano()) |  | ||||||
|  |  | ||||||
| 	return &memoryTransport{ | 	return &memoryTransport{ | ||||||
| 		opts:      options, | 		opts:      options, | ||||||
| 		listeners: make(map[string]*memoryListener), | 		listeners: make(map[string]*memoryListener), | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ package http | |||||||
| import ( | import ( | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"io/ioutil" | 	"io" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"net/url" | 	"net/url" | ||||||
|  |  | ||||||
| @@ -61,7 +61,7 @@ func (r *HTTPResolver) Resolve(name string) ([]*resolver.Record, error) { | |||||||
| 	if rsp.StatusCode != 200 { | 	if rsp.StatusCode != 200 { | ||||||
| 		return nil, errors.New("non 200 response") | 		return nil, errors.New("non 200 response") | ||||||
| 	} | 	} | ||||||
| 	b, err := ioutil.ReadAll(rsp.Body) | 	b, err := io.ReadAll(rsp.Body) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -1,9 +1,8 @@ | |||||||
| package random | package random | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"math/rand" |  | ||||||
|  |  | ||||||
| 	"github.com/unistack-org/micro/v3/selector" | 	"github.com/unistack-org/micro/v3/selector" | ||||||
|  | 	"github.com/unistack-org/micro/v3/util/rand" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type random struct{} | type random struct{} | ||||||
| @@ -20,10 +19,9 @@ func (r *random) Select(routes []string, opts ...selector.SelectOption) (selecto | |||||||
| 		if len(routes) == 1 { | 		if len(routes) == 1 { | ||||||
| 			return routes[0] | 			return routes[0] | ||||||
| 		} | 		} | ||||||
|  | 		var rng rand.Rand | ||||||
| 		// select a random route from the slice | 		// select a random route from the slice | ||||||
| 		//nolint:gosec | 		return routes[rng.Intn(len(routes))] | ||||||
| 		return routes[rand.Intn(len(routes))] |  | ||||||
| 	}, nil | 	}, nil | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,9 +1,8 @@ | |||||||
| package roundrobin | package roundrobin | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"math/rand" |  | ||||||
|  |  | ||||||
| 	"github.com/unistack-org/micro/v3/selector" | 	"github.com/unistack-org/micro/v3/selector" | ||||||
|  | 	"github.com/unistack-org/micro/v3/util/rand" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // NewSelector returns an initialised round robin selector | // NewSelector returns an initialised round robin selector | ||||||
| @@ -18,8 +17,8 @@ func (r *roundrobin) Select(routes []string, opts ...selector.SelectOption) (sel | |||||||
| 	if len(routes) == 0 { | 	if len(routes) == 0 { | ||||||
| 		return nil, selector.ErrNoneAvailable | 		return nil, selector.ErrNoneAvailable | ||||||
| 	} | 	} | ||||||
|  | 	var rng rand.Rand | ||||||
| 	i := rand.Intn(len(routes)) | 	i := rng.Intn(len(routes)) | ||||||
|  |  | ||||||
| 	return func() string { | 	return func() string { | ||||||
| 		route := routes[i%len(routes)] | 		route := routes[i%len(routes)] | ||||||
|   | |||||||
| @@ -3,7 +3,6 @@ | |||||||
| package http | package http | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"io/ioutil" |  | ||||||
| 	"net" | 	"net" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"testing" | 	"testing" | ||||||
| @@ -52,7 +51,7 @@ func TestRoundTripper(t *testing.T) { | |||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	b, err := ioutil.ReadAll(w.Body) | 	b, err := io.ReadAll(w.Body) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
| @@ -72,7 +71,7 @@ func TestRoundTripper(t *testing.T) { | |||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	b, err = ioutil.ReadAll(rsp.Body) | 	b, err = io.ReadAll(rsp.Body) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -2,16 +2,14 @@ | |||||||
| package jitter | package jitter | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"math/rand" |  | ||||||
| 	"time" | 	"time" | ||||||
| ) |  | ||||||
|  |  | ||||||
| var ( | 	"github.com/unistack-org/micro/v3/util/rand" | ||||||
| 	r = rand.New(rand.NewSource(time.Now().UnixNano())) |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // Do returns a random time to jitter with max cap specified | // Do returns a random time to jitter with max cap specified | ||||||
| func Do(d time.Duration) time.Duration { | func Do(d time.Duration) time.Duration { | ||||||
| 	v := r.Float64() * float64(d.Nanoseconds()) | 	var rng rand.Rand | ||||||
|  | 	v := rng.Float64() * float64(d.Nanoseconds()) | ||||||
| 	return time.Duration(v) | 	return time.Duration(v) | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										84
									
								
								util/rand/rand.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								util/rand/rand.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,84 @@ | |||||||
|  | package rand | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"crypto/rand" | ||||||
|  | 	"encoding/binary" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Rand is a wrapper around crypto/rand that adds some convenience functions known from math/rand. | ||||||
|  | type Rand struct { | ||||||
|  | 	buf [8]byte | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (r *Rand) Int31() int32 { | ||||||
|  | 	rand.Read(r.buf[:4]) | ||||||
|  | 	return int32(binary.BigEndian.Uint32(r.buf[:4]) & ^uint32(1<<31)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (r *Rand) Int() int { | ||||||
|  | 	u := uint(r.Int63()) | ||||||
|  | 	return int(u << 1 >> 1) // clear sign bit if int == int32 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (r *Rand) Float64() float64 { | ||||||
|  | again: | ||||||
|  | 	f := float64(r.Int63()) / (1 << 63) | ||||||
|  | 	if f == 1 { | ||||||
|  | 		goto again // resample; this branch is taken O(never) | ||||||
|  | 	} | ||||||
|  | 	return f | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (r *Rand) Float32() float32 { | ||||||
|  | again: | ||||||
|  | 	f := float32(r.Float64()) | ||||||
|  | 	if f == 1 { | ||||||
|  | 		goto again // resample; this branch is taken O(very rarely) | ||||||
|  | 	} | ||||||
|  | 	return f | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (r *Rand) Uint32() uint32 { | ||||||
|  | 	return uint32(r.Int63() >> 31) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (r *Rand) Uint64() uint64 { | ||||||
|  | 	return uint64(r.Int63())>>31 | uint64(r.Int63())<<32 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (r *Rand) Intn(n int) int { | ||||||
|  | 	if n <= 1<<31-1 { | ||||||
|  | 		return int(r.Int31n(int32(n))) | ||||||
|  | 	} | ||||||
|  | 	return int(r.Int63n(int64(n))) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (r *Rand) Int63() int64 { | ||||||
|  | 	rand.Read(r.buf[:]) | ||||||
|  | 	return int64(binary.BigEndian.Uint64(r.buf[:]) & ^uint64(1<<63)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // copied from the standard library math/rand implementation of Int63n | ||||||
|  | func (r *Rand) Int31n(n int32) int32 { | ||||||
|  | 	if n&(n-1) == 0 { // n is power of two, can mask | ||||||
|  | 		return r.Int31() & (n - 1) | ||||||
|  | 	} | ||||||
|  | 	max := int32((1 << 31) - 1 - (1<<31)%uint32(n)) | ||||||
|  | 	v := r.Int31() | ||||||
|  | 	for v > max { | ||||||
|  | 		v = r.Int31() | ||||||
|  | 	} | ||||||
|  | 	return v % n | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (r *Rand) Int63n(n int64) int64 { | ||||||
|  | 	if n&(n-1) == 0 { // n is power of two, can mask | ||||||
|  | 		return r.Int63() & (n - 1) | ||||||
|  | 	} | ||||||
|  | 	max := int64((1 << 63) - 1 - (1<<63)%uint64(n)) | ||||||
|  | 	v := r.Int63() | ||||||
|  | 	for v > max { | ||||||
|  | 		v = r.Int63() | ||||||
|  | 	} | ||||||
|  | 	return v % n | ||||||
|  | } | ||||||
							
								
								
									
										257
									
								
								util/reflect/path.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										257
									
								
								util/reflect/path.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,257 @@ | |||||||
|  | package reflect | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"reflect" | ||||||
|  | 	"strconv" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	SplitToken     = "." | ||||||
|  | 	IndexCloseChar = "]" | ||||||
|  | 	IndexOpenChar  = "[" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var ( | ||||||
|  | 	ErrMalformedIndex    = errors.New("Malformed index key") | ||||||
|  | 	ErrInvalidIndexUsage = errors.New("Invalid index key usage") | ||||||
|  | 	ErrKeyNotFound       = errors.New("Unable to find the key") | ||||||
|  | 	ErrBadJSONPath       = errors.New("Bad path: must start with $ and have more then 2 chars") | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func Lookup(i interface{}, path string) (reflect.Value, error) { | ||||||
|  | 	if path == "" || path[0:1] != "$" { | ||||||
|  | 		return reflect.Value{}, ErrBadJSONPath | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if path == "$" { | ||||||
|  | 		return reflect.ValueOf(i), nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(path) < 2 { | ||||||
|  | 		return reflect.Value{}, ErrBadJSONPath | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return lookup(i, strings.Split(path[2:], SplitToken)...) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Lookup performs a lookup into a value, using a path of keys. The key should | ||||||
|  | // match with a Field or a MapIndex. For slice you can use the syntax key[index] | ||||||
|  | // to access a specific index. If one key owns to a slice and an index is not | ||||||
|  | // specificied the rest of the path will be apllied to evaley value of the | ||||||
|  | // slice, and the value will be merged into a slice. | ||||||
|  | func lookup(i interface{}, path ...string) (reflect.Value, error) { | ||||||
|  | 	value := reflect.ValueOf(i) | ||||||
|  | 	var parent reflect.Value | ||||||
|  | 	var err error | ||||||
|  |  | ||||||
|  | 	for i, part := range path { | ||||||
|  | 		parent = value | ||||||
|  |  | ||||||
|  | 		value, err = getValueByName(value, part) | ||||||
|  | 		if err == nil { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if !isAggregable(parent) { | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		value, err = aggreateAggregableValue(parent, path[i:]) | ||||||
|  |  | ||||||
|  | 		break | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return value, err | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func getValueByName(v reflect.Value, key string) (reflect.Value, error) { | ||||||
|  | 	var value reflect.Value | ||||||
|  | 	var index int | ||||||
|  | 	var err error | ||||||
|  |  | ||||||
|  | 	key, index, err = parseIndex(key) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return value, err | ||||||
|  | 	} | ||||||
|  | 	switch v.Kind() { | ||||||
|  | 	case reflect.Ptr, reflect.Interface: | ||||||
|  | 		return getValueByName(v.Elem(), key) | ||||||
|  | 	case reflect.Struct: | ||||||
|  | 		value = v.FieldByName(key) | ||||||
|  | 	case reflect.Map: | ||||||
|  | 		kValue := reflect.Indirect(reflect.New(v.Type().Key())) | ||||||
|  | 		kValue.SetString(key) | ||||||
|  | 		value = v.MapIndex(kValue) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if !value.IsValid() { | ||||||
|  | 		return reflect.Value{}, ErrKeyNotFound | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if index != -1 { | ||||||
|  | 		if value.Type().Kind() != reflect.Slice { | ||||||
|  | 			return reflect.Value{}, ErrInvalidIndexUsage | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		value = value.Index(index) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if value.Kind() == reflect.Ptr || value.Kind() == reflect.Interface { | ||||||
|  | 		value = value.Elem() | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return value, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func aggreateAggregableValue(v reflect.Value, path []string) (reflect.Value, error) { | ||||||
|  | 	values := make([]reflect.Value, 0) | ||||||
|  |  | ||||||
|  | 	l := v.Len() | ||||||
|  | 	if l == 0 { | ||||||
|  | 		ty, ok := lookupType(v.Type(), path...) | ||||||
|  | 		if !ok { | ||||||
|  | 			return reflect.Value{}, ErrKeyNotFound | ||||||
|  | 		} | ||||||
|  | 		return reflect.MakeSlice(reflect.SliceOf(ty), 0, 0), nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	switch v.Kind() { | ||||||
|  | 	case reflect.Slice, reflect.Map: | ||||||
|  | 		break | ||||||
|  | 	default: | ||||||
|  | 		return reflect.Value{}, fmt.Errorf("unsuported kind for index") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	index := indexFunction(v) | ||||||
|  | 	for i := 0; i < l; i++ { | ||||||
|  | 		value, err := lookup(index(i).Interface(), path...) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return reflect.Value{}, err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		values = append(values, value) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return mergeValue(values), nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func indexFunction(v reflect.Value) func(i int) reflect.Value { | ||||||
|  | 	switch v.Kind() { | ||||||
|  | 	case reflect.Slice: | ||||||
|  | 		return v.Index | ||||||
|  | 	case reflect.Map: | ||||||
|  | 		keys := v.MapKeys() | ||||||
|  | 		return func(i int) reflect.Value { | ||||||
|  | 			return v.MapIndex(keys[i]) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return func(i int) reflect.Value { return reflect.Value{} } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func mergeValue(values []reflect.Value) reflect.Value { | ||||||
|  | 	values = removeZeroValues(values) | ||||||
|  | 	l := len(values) | ||||||
|  | 	if l == 0 { | ||||||
|  | 		return reflect.Value{} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	sample := values[0] | ||||||
|  | 	mergeable := isMergeable(sample) | ||||||
|  |  | ||||||
|  | 	t := sample.Type() | ||||||
|  | 	if mergeable { | ||||||
|  | 		t = t.Elem() | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	value := reflect.MakeSlice(reflect.SliceOf(t), 0, 0) | ||||||
|  | 	for i := 0; i < l; i++ { | ||||||
|  | 		if !values[i].IsValid() { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if mergeable { | ||||||
|  | 			value = reflect.AppendSlice(value, values[i]) | ||||||
|  | 		} else { | ||||||
|  | 			value = reflect.Append(value, values[i]) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return value | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func removeZeroValues(values []reflect.Value) []reflect.Value { | ||||||
|  | 	l := len(values) | ||||||
|  |  | ||||||
|  | 	var v []reflect.Value | ||||||
|  | 	for i := 0; i < l; i++ { | ||||||
|  | 		if values[i].IsValid() { | ||||||
|  | 			v = append(v, values[i]) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return v | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func isAggregable(v reflect.Value) bool { | ||||||
|  | 	k := v.Kind() | ||||||
|  |  | ||||||
|  | 	return k == reflect.Map || k == reflect.Slice | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func isMergeable(v reflect.Value) bool { | ||||||
|  | 	k := v.Kind() | ||||||
|  | 	return k == reflect.Map || k == reflect.Slice | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func hasIndex(s string) bool { | ||||||
|  | 	return strings.Contains(s, IndexOpenChar) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func parseIndex(s string) (string, int, error) { | ||||||
|  | 	start := strings.Index(s, IndexOpenChar) | ||||||
|  | 	end := strings.Index(s, IndexCloseChar) | ||||||
|  |  | ||||||
|  | 	if start == -1 && end == -1 { | ||||||
|  | 		return s, -1, nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if (start != -1 && end == -1) || (start == -1 && end != -1) { | ||||||
|  | 		return "", -1, ErrMalformedIndex | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	index, err := strconv.Atoi(s[start+1 : end]) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", -1, ErrMalformedIndex | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return s[:start], index, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func lookupType(ty reflect.Type, path ...string) (reflect.Type, bool) { | ||||||
|  | 	if len(path) == 0 { | ||||||
|  | 		return ty, true | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	switch ty.Kind() { | ||||||
|  | 	case reflect.Slice, reflect.Array, reflect.Map: | ||||||
|  | 		if hasIndex(path[0]) { | ||||||
|  | 			return lookupType(ty.Elem(), path[1:]...) | ||||||
|  | 		} | ||||||
|  | 		// Aggregate. | ||||||
|  | 		return lookupType(ty.Elem(), path...) | ||||||
|  | 	case reflect.Ptr: | ||||||
|  | 		return lookupType(ty.Elem(), path...) | ||||||
|  | 	case reflect.Interface: | ||||||
|  | 		// We can't know from here without a value. Let's just return this type. | ||||||
|  | 		return ty, true | ||||||
|  | 	case reflect.Struct: | ||||||
|  | 		f, ok := ty.FieldByName(path[0]) | ||||||
|  | 		if ok { | ||||||
|  | 			return lookupType(f.Type, path[1:]...) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return nil, false | ||||||
|  | } | ||||||
							
								
								
									
										34
									
								
								util/reflect/path_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								util/reflect/path_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | |||||||
|  | package reflect | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestPath(t *testing.T) { | ||||||
|  |  | ||||||
|  | 	type Nested2 struct { | ||||||
|  | 		Name string | ||||||
|  | 	} | ||||||
|  | 	type Nested1 struct { | ||||||
|  | 		Nested2 Nested2 | ||||||
|  | 	} | ||||||
|  | 	type Config struct { | ||||||
|  | 		Nested1 Nested1 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	cfg := &Config{ | ||||||
|  | 		Nested1: Nested1{ | ||||||
|  | 			Nested2: Nested2{ | ||||||
|  | 				Name: "NAME", | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	v, err := Lookup(cfg, "$.Nested1.Nested2.Name") | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 	if v.String() != "NAME" { | ||||||
|  | 		t.Fatalf("lookup returns invalid value: %v", v) | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -1,7 +1,7 @@ | |||||||
| package jwt | package jwt | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"io/ioutil" | 	"os" | ||||||
| 	"testing" | 	"testing" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| @@ -10,7 +10,7 @@ import ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestGenerate(t *testing.T) { | func TestGenerate(t *testing.T) { | ||||||
| 	privKey, err := ioutil.ReadFile("test/sample_key") | 	privKey, err := os.ReadFile("test/sample_key") | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatalf("Unable to read private key: %v", err) | 		t.Fatalf("Unable to read private key: %v", err) | ||||||
| 	} | 	} | ||||||
| @@ -26,11 +26,11 @@ func TestGenerate(t *testing.T) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func TestInspect(t *testing.T) { | func TestInspect(t *testing.T) { | ||||||
| 	pubKey, err := ioutil.ReadFile("test/sample_key.pub") | 	pubKey, err := os.ReadFile("test/sample_key.pub") | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatalf("Unable to read public key: %v", err) | 		t.Fatalf("Unable to read public key: %v", err) | ||||||
| 	} | 	} | ||||||
| 	privKey, err := ioutil.ReadFile("test/sample_key") | 	privKey, err := os.ReadFile("test/sample_key") | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatalf("Unable to read private key: %v", err) | 		t.Fatalf("Unable to read private key: %v", err) | ||||||
| 	} | 	} | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user