From f3dbedb031c105119e2021ab55a30f40884253fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=A7=E5=8F=AF?= Date: Mon, 19 Feb 2024 22:18:10 +0800 Subject: [PATCH] chore: add shorturl example --- examples/go.mod | 1 + examples/go.sum | 2 + examples/shorturl/kod.toml | 2 + examples/shorturl/kod_gen.go | 94 ++++++++++++++++++++++++++ examples/shorturl/kod_gen_interface.go | 15 ++++ examples/shorturl/kod_gen_mock.go | 69 +++++++++++++++++++ examples/shorturl/short_url.go | 79 ++++++++++++++++++++++ examples/shorturl/short_url_test.go | 51 ++++++++++++++ 8 files changed, 313 insertions(+) create mode 100644 examples/shorturl/kod.toml create mode 100644 examples/shorturl/kod_gen.go create mode 100644 examples/shorturl/kod_gen_interface.go create mode 100644 examples/shorturl/kod_gen_mock.go create mode 100644 examples/shorturl/short_url.go create mode 100644 examples/shorturl/short_url_test.go diff --git a/examples/go.mod b/examples/go.mod index 2436abd..7fda98d 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -14,6 +14,7 @@ require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dominikbraun/graph v0.23.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/google/uuid v1.4.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect diff --git a/examples/go.sum b/examples/go.sum index 552d4d4..0f77920 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -14,6 +14,8 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= diff --git a/examples/shorturl/kod.toml b/examples/shorturl/kod.toml new file mode 100644 index 0000000..1715805 --- /dev/null +++ b/examples/shorturl/kod.toml @@ -0,0 +1,2 @@ +["github.com/go-kod/kod/examples/shorturl/Component"] +redisconfig = { addr = "localhost:6379" } diff --git a/examples/shorturl/kod_gen.go b/examples/shorturl/kod_gen.go new file mode 100644 index 0000000..560625d --- /dev/null +++ b/examples/shorturl/kod_gen.go @@ -0,0 +1,94 @@ +// Code generated by "kod generate". DO NOT EDIT. +//go:build !ignoreKodGen + +package shorturl + +import ( + "context" + "github.com/go-kod/kod" + "github.com/go-kod/kod/interceptor" + "reflect" +) + +func init() { + kod.Register(&kod.Registration{ + Name: "github.com/go-kod/kod/examples/shorturl/Component", + Interface: reflect.TypeOf((*Component)(nil)).Elem(), + Impl: reflect.TypeOf(impl{}), + Refs: ``, + LocalStubFn: func(ctx context.Context, info *kod.LocalStubFnInfo) any { + var interceptors []kod.Interceptor + if h, ok := info.Impl.(interface{ Interceptors() []kod.Interceptor }); ok { + interceptors = h.Interceptors() + } + + return component_local_stub{ + impl: info.Impl.(Component), + interceptor: interceptor.Chain(interceptors), + name: info.Name, + } + }, + }) +} + +// kod.InstanceOf checks. +var _ kod.InstanceOf[Component] = (*impl)(nil) + +// Local stub implementations. + +type component_local_stub struct { + impl Component + name string + interceptor kod.Interceptor +} + +// Check that component_local_stub implements the Component interface. +var _ Component = (*component_local_stub)(nil) + +func (s component_local_stub) Generate(ctx context.Context, a1 *GenerateRequest) (r0 *GenerateResponse, err error) { + + if s.interceptor == nil { + r0, err = s.impl.Generate(ctx, a1) + return + } + + call := func(ctx context.Context, info kod.CallInfo, req, res []any) (err error) { + r0, err = s.impl.Generate(ctx, a1) + res[0] = r0 + return + } + + info := kod.CallInfo{ + Impl: s.impl, + Component: s.name, + FullMethod: "github.com/go-kod/kod/examples/shorturl/Component.Generate", + Method: "Generate", + } + + err = s.interceptor(ctx, info, []any{a1}, []any{r0}, call) + return +} + +func (s component_local_stub) Get(ctx context.Context, a1 *GetRequest) (r0 *GetResponse, err error) { + + if s.interceptor == nil { + r0, err = s.impl.Get(ctx, a1) + return + } + + call := func(ctx context.Context, info kod.CallInfo, req, res []any) (err error) { + r0, err = s.impl.Get(ctx, a1) + res[0] = r0 + return + } + + info := kod.CallInfo{ + Impl: s.impl, + Component: s.name, + FullMethod: "github.com/go-kod/kod/examples/shorturl/Component.Get", + Method: "Get", + } + + err = s.interceptor(ctx, info, []any{a1}, []any{r0}, call) + return +} diff --git a/examples/shorturl/kod_gen_interface.go b/examples/shorturl/kod_gen_interface.go new file mode 100644 index 0000000..36ca549 --- /dev/null +++ b/examples/shorturl/kod_gen_interface.go @@ -0,0 +1,15 @@ +// Code generated by kod struct2interface; DO NOT EDIT. + +package shorturl + +import ( + "context" +) + +// impl is a component that implements Component. +type Component interface { + // Generate generates a short url. + Generate(ctx context.Context, req *GenerateRequest) (*GenerateResponse, error) + // Get gets the original url from short url. + Get(ctx context.Context, req *GetRequest) (*GetResponse, error) +} diff --git a/examples/shorturl/kod_gen_mock.go b/examples/shorturl/kod_gen_mock.go new file mode 100644 index 0000000..aa24b67 --- /dev/null +++ b/examples/shorturl/kod_gen_mock.go @@ -0,0 +1,69 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: examples/shorturl/kod_gen_interface.go +// +// Generated by this command: +// +// mockgen -source examples/shorturl/kod_gen_interface.go -destination examples/shorturl/kod_gen_mock.go -package shorturl +// +// Package shorturl is a generated GoMock package. +package shorturl + +import ( + context "context" + reflect "reflect" + + gomock "go.uber.org/mock/gomock" +) + +// MockComponent is a mock of Component interface. +type MockComponent struct { + ctrl *gomock.Controller + recorder *MockComponentMockRecorder +} + +// MockComponentMockRecorder is the mock recorder for MockComponent. +type MockComponentMockRecorder struct { + mock *MockComponent +} + +// NewMockComponent creates a new mock instance. +func NewMockComponent(ctrl *gomock.Controller) *MockComponent { + mock := &MockComponent{ctrl: ctrl} + mock.recorder = &MockComponentMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockComponent) EXPECT() *MockComponentMockRecorder { + return m.recorder +} + +// Generate mocks base method. +func (m *MockComponent) Generate(ctx context.Context, req *GenerateRequest) (*GenerateResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Generate", ctx, req) + ret0, _ := ret[0].(*GenerateResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Generate indicates an expected call of Generate. +func (mr *MockComponentMockRecorder) Generate(ctx, req any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Generate", reflect.TypeOf((*MockComponent)(nil).Generate), ctx, req) +} + +// Get mocks base method. +func (m *MockComponent) Get(ctx context.Context, req *GetRequest) (*GetResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Get", ctx, req) + ret0, _ := ret[0].(*GetResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Get indicates an expected call of Get. +func (mr *MockComponentMockRecorder) Get(ctx, req any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockComponent)(nil).Get), ctx, req) +} diff --git a/examples/shorturl/short_url.go b/examples/shorturl/short_url.go new file mode 100644 index 0000000..a9a1794 --- /dev/null +++ b/examples/shorturl/short_url.go @@ -0,0 +1,79 @@ +package shorturl + +import ( + "context" + "time" + + "github.com/go-kod/kod" + "github.com/go-kod/kod/ext/client/kredis" + "github.com/go-kod/kod/interceptor/kvalidate" + "github.com/google/uuid" +) + +type config struct { + RedisConfig kredis.Config +} + +type impl struct { + kod.Implements[Component] + kod.WithConfig[config] + + redis *kredis.Client + uuid uuid.UUID +} + +func (i *impl) Init(ctx context.Context) error { + i.redis = i.Config().RedisConfig.Build() + i.uuid = uuid.New() + + return nil +} + +type GenerateRequest struct { + URL string `validate:"required"` + Duration time.Duration +} + +type GenerateResponse struct { + Short string +} + +// Generate generates a short url. +func (i *impl) Generate(ctx context.Context, req *GenerateRequest) (*GenerateResponse, error) { + + key := i.uuid.String() + _, err := i.redis.SetEx(ctx, key, req.URL, req.Duration).Result() + if err != nil { + i.L(ctx).Error("failed to set key", "key", key, "req", req, "error", err) + return nil, err + } + return &GenerateResponse{ + Short: key, + }, nil +} + +type GetRequest struct { + Short string `validate:"required"` +} + +type GetResponse struct { + URL string +} + +// Get gets the original url from short url. +func (i *impl) Get(ctx context.Context, req *GetRequest) (*GetResponse, error) { + result, err := i.redis.Get(ctx, req.Short).Result() + if err != nil { + i.L(ctx).Error("failed to get key", "req", req, "error", err) + return nil, err + } + return &GetResponse{ + URL: result, + }, nil +} + +func (i *impl) Interceptors() []kod.Interceptor { + return []kod.Interceptor{ + kvalidate.Interceptor(), + } +} diff --git a/examples/shorturl/short_url_test.go b/examples/shorturl/short_url_test.go new file mode 100644 index 0000000..0a0ccdb --- /dev/null +++ b/examples/shorturl/short_url_test.go @@ -0,0 +1,51 @@ +package shorturl + +import ( + "context" + "testing" + "time" + + "github.com/go-kod/kod" + "github.com/stretchr/testify/assert" +) + +func TestShortURL(t *testing.T) { + kod.RunTest(t, func(ctx context.Context, impl Component) { + result, err := impl.Generate(ctx, &GenerateRequest{ + URL: "https://github.com/go-kod/kod", + Duration: time.Second, + }) + assert.Nil(t, err) + assert.NotEmpty(t, result.Short) + + getRes, err := impl.Get(ctx, &GetRequest{ + Short: result.Short, + }) + assert.Nil(t, err) + assert.Equal(t, "https://github.com/go-kod/kod", getRes.URL) + }, kod.WithConfigFile("./kod.toml")) +} + +func TestShortURLInvalid(t *testing.T) { + t.Run("Generate", func(t *testing.T) { + t.Parallel() + + kod.RunTest(t, func(ctx context.Context, impl Component) { + result, err := impl.Generate(ctx, &GenerateRequest{ + Duration: time.Second, + }) + assert.NotNil(t, err) + assert.Nil(t, result) + }) + }) + + t.Run("Get", func(t *testing.T) { + t.Parallel() + + kod.RunTest(t, func(ctx context.Context, impl Component) { + result, err := impl.Get(ctx, &GetRequest{}) + assert.NotNil(t, err) + assert.Nil(t, result) + }) + }) +}