├── statics └── simple-clean-arch.png ├── README.md ├── .gitignore ├── modules └── task │ ├── entity │ ├── task.go │ ├── error.go │ └── task_vars.go │ ├── task.go │ ├── business │ └── business.go │ ├── transport │ └── rest │ │ └── rest_api.go │ └── repository │ └── inmem │ └── store.go ├── common └── paging.go ├── main.go ├── go.mod └── go.sum /statics/simple-clean-arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viettranx/simple-clean-architecture-demo/HEAD/statics/simple-clean-arch.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simple Clean Architecture Demo 2 | 3 | This repo is a very simple example for Clean Architecture. It's CRUD REST API Service (TaskService). 4 | 5 | To keep it simple, I implemented in-memory repository instead of DB Connections. 6 | 7 | ![Clean Architecture Demo](./statics/simple-clean-arch.png) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | app 14 | 15 | # Dependency directories (remove the comment below to include it) 16 | # vendor/ 17 | /.idea/ 18 | 19 | # IDEs 20 | .DS_STORE 21 | .idea 22 | .vscode 23 | 24 | -------------------------------------------------------------------------------- /modules/task/entity/task.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | const ( 4 | StatusDoing = "doing" 5 | StatusDone = "done" 6 | ) 7 | 8 | // Task is main entity model, full itself data 9 | // and it's associations (such as belongsTo relationship) 10 | type Task struct { 11 | Id string `json:"id"` 12 | Title string `json:"title"` 13 | Description string `json:"description"` 14 | Status string `json:"status"` 15 | } 16 | -------------------------------------------------------------------------------- /common/paging.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | type Paging struct { 4 | Page int `json:"page" form:"page"` 5 | Limit int `json:"limit" form:"limit"` 6 | Total int64 `json:"total" form:"-"` 7 | FakeCursor string `json:"cursor" form:"cursor"` 8 | NextCursor string `json:"next_cursor"` 9 | } 10 | 11 | func (p *Paging) Process() { 12 | if p.Page < 1 { 13 | p.Page = 1 14 | } 15 | 16 | if p.Limit <= 0 { 17 | p.Limit = 10 18 | } 19 | 20 | if p.Limit >= 200 { 21 | p.Limit = 200 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /modules/task/entity/error.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import "errors" 4 | 5 | var ( 6 | ErrTaskNotFound = errors.New("task not found") 7 | ErrCannotCreateTask = errors.New("cannot create task") 8 | ErrCannotUpdateTask = errors.New("cannot update task") 9 | ErrCannotDeleteTask = errors.New("cannot delete task") 10 | ErrTitleCannotBeBlank = errors.New("title cannot be blank") 11 | ErrStatusNotValid = errors.New("status must be 'doing' or 'done'") 12 | ) 13 | 14 | func validateTitle(title string) error { 15 | if title == "" { 16 | return ErrTitleCannotBeBlank 17 | } 18 | 19 | return nil 20 | } 21 | 22 | func validateStatus(status string) error { 23 | if status != StatusDoing && status != StatusDone { 24 | return ErrStatusNotValid 25 | } 26 | 27 | return nil 28 | } 29 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "log" 6 | "simple-clean-architecture-demo/modules/task/business" 7 | "simple-clean-architecture-demo/modules/task/repository/inmem" 8 | "simple-clean-architecture-demo/modules/task/transport/rest" 9 | ) 10 | 11 | func main() { 12 | engine := gin.Default() 13 | 14 | // Setup full service dependencies 15 | apiService := rest.NewAPI(business.NewBusiness(inmem.NewInMemStorage())) 16 | 17 | v1 := engine.Group("v1") 18 | { 19 | tasks := v1.Group("/tasks") 20 | { 21 | tasks.POST("", apiService.CreateTaskHdl()) 22 | tasks.GET("", apiService.ListTaskHdl()) 23 | tasks.GET("/:id", apiService.GetTaskHdl()) 24 | tasks.PATCH("/:id", apiService.UpdateTaskHdl()) 25 | tasks.DELETE("/:id", apiService.DeleteTaskHdl()) 26 | } 27 | } 28 | 29 | if err := engine.Run(":3000"); err != nil { 30 | log.Fatal(err) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /modules/task/task.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | import ( 4 | "context" 5 | "github.com/gin-gonic/gin" 6 | "simple-clean-architecture-demo/common" 7 | "simple-clean-architecture-demo/modules/task/entity" 8 | ) 9 | 10 | type Repository interface { 11 | InsertNewTask(ctx context.Context, data *entity.TaskCreationData) error 12 | GetTaskById(ctx context.Context, id string) (*entity.Task, error) 13 | FindTasks(ctx context.Context, filter *entity.Filter, paging *common.Paging) ([]entity.Task, error) 14 | UpdateTask(ctx context.Context, id string, data *entity.TaskPatchData) error 15 | DeleteTask(ctx context.Context, id string) error 16 | } 17 | 18 | type Business interface { 19 | CreateNewTask(ctx context.Context, data *entity.TaskCreationData) error 20 | ListTasks(ctx context.Context, filter *entity.Filter, paging *common.Paging) ([]entity.Task, error) 21 | GetTaskDetails(ctx context.Context, id string) (*entity.Task, error) 22 | UpdateTask(ctx context.Context, id string, data *entity.TaskPatchData) error 23 | DeleteTask(ctx context.Context, id string) error 24 | } 25 | 26 | type API interface { 27 | CreateTaskHdl() gin.HandlerFunc 28 | ListTaskHdl() gin.HandlerFunc 29 | GetTaskHdl() gin.HandlerFunc 30 | UpdateTaskHdl() gin.HandlerFunc 31 | DeleteTaskHdl() gin.HandlerFunc 32 | } 33 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module simple-clean-architecture-demo 2 | 3 | go 1.19 4 | 5 | require github.com/google/uuid v1.3.0 6 | 7 | require ( 8 | github.com/bytedance/sonic v1.8.0 // indirect 9 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect 10 | github.com/gin-contrib/sse v0.1.0 // indirect 11 | github.com/gin-gonic/gin v1.9.0 // indirect 12 | github.com/go-playground/locales v0.14.1 // indirect 13 | github.com/go-playground/universal-translator v0.18.1 // indirect 14 | github.com/go-playground/validator/v10 v10.11.2 // indirect 15 | github.com/goccy/go-json v0.10.0 // indirect 16 | github.com/json-iterator/go v1.1.12 // indirect 17 | github.com/klauspost/cpuid/v2 v2.0.9 // indirect 18 | github.com/leodido/go-urn v1.2.1 // indirect 19 | github.com/mattn/go-isatty v0.0.17 // indirect 20 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect 21 | github.com/modern-go/reflect2 v1.0.2 // indirect 22 | github.com/pelletier/go-toml/v2 v2.0.6 // indirect 23 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 24 | github.com/ugorji/go/codec v1.2.9 // indirect 25 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect 26 | golang.org/x/crypto v0.5.0 // indirect 27 | golang.org/x/net v0.7.0 // indirect 28 | golang.org/x/sys v0.5.0 // indirect 29 | golang.org/x/text v0.7.0 // indirect 30 | google.golang.org/protobuf v1.28.1 // indirect 31 | gopkg.in/yaml.v3 v3.0.1 // indirect 32 | ) 33 | -------------------------------------------------------------------------------- /modules/task/entity/task_vars.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import "strings" 4 | 5 | // TaskCreationData should be used for parsing data from HTTP Request body 6 | // In many cases, main entity model contains data fields that clients 7 | // cannot send, especially data from other modules (or services). 8 | type TaskCreationData struct { 9 | Id string `json:"-"` // just carry inserted id, so json tag is omitted 10 | Title string `json:"title"` 11 | Description string `json:"description"` 12 | Status string `json:"status"` 13 | } 14 | 15 | // Validate is entity logic, ensure data is validated 16 | func (t *TaskCreationData) Validate() error { 17 | t.Title = strings.TrimSpace(t.Title) 18 | 19 | if err := validateTitle(t.Title); err != nil { 20 | return err 21 | } 22 | 23 | t.Status = strings.ToLower(strings.TrimSpace(t.Status)) 24 | 25 | if err := validateStatus(t.Status); err != nil { 26 | return err 27 | } 28 | 29 | return nil 30 | } 31 | 32 | // TaskPatchData should be used for parsing data from HTTP Request body. 33 | // The data fields are limited and pointers at all. The pointers help 34 | // distinguish if they have carry value. 35 | // Of course, we just do update for pointers are not nil. 36 | type TaskPatchData struct { 37 | Title *string `json:"title"` 38 | Description *string `json:"description"` 39 | Status *string `json:"status"` 40 | } 41 | 42 | func (t *TaskPatchData) Validate() error { 43 | if s := t.Title; s != nil { 44 | title := strings.TrimSpace(*s) 45 | 46 | if err := validateTitle(title); err != nil { 47 | return err 48 | 49 | } 50 | t.Title = &title 51 | } 52 | 53 | if s := t.Description; s != nil { 54 | des := strings.TrimSpace(*s) 55 | 56 | if err := validateTitle(des); err != nil { 57 | return err 58 | 59 | } 60 | t.Description = &des 61 | } 62 | 63 | return nil 64 | } 65 | 66 | type Filter struct { 67 | Status *string `json:"status,omitempty" form:"status"` 68 | } 69 | -------------------------------------------------------------------------------- /modules/task/business/business.go: -------------------------------------------------------------------------------- 1 | package business 2 | 3 | import ( 4 | "context" 5 | "simple-clean-architecture-demo/common" 6 | "simple-clean-architecture-demo/modules/task" 7 | "simple-clean-architecture-demo/modules/task/entity" 8 | ) 9 | 10 | type business struct { 11 | repository task.Repository 12 | } 13 | 14 | func NewBusiness(repository task.Repository) task.Business { 15 | return &business{repository: repository} 16 | } 17 | 18 | func (biz *business) CreateNewTask(ctx context.Context, data *entity.TaskCreationData) error { 19 | // Validate creation data 20 | if err := data.Validate(); err != nil { 21 | return err 22 | } 23 | 24 | if err := biz.repository.InsertNewTask(ctx, data); err != nil { 25 | return err 26 | } 27 | 28 | return nil 29 | } 30 | 31 | func (biz *business) ListTasks(ctx context.Context, filter *entity.Filter, paging *common.Paging) ([]entity.Task, error) { 32 | return biz.repository.FindTasks(ctx, filter, paging) 33 | } 34 | 35 | func (biz *business) GetTaskDetails(ctx context.Context, id string) (*entity.Task, error) { 36 | return biz.repository.GetTaskById(ctx, id) 37 | } 38 | 39 | func (biz *business) UpdateTask(ctx context.Context, id string, data *entity.TaskPatchData) error { 40 | // Validate creation data 41 | if err := data.Validate(); err != nil { 42 | return err 43 | } 44 | 45 | foundTask, err := biz.repository.GetTaskById(ctx, id) 46 | 47 | if err != nil { 48 | return err 49 | } 50 | 51 | if foundTask.Status == entity.StatusDone { 52 | return entity.ErrCannotUpdateTask 53 | } 54 | 55 | if err := biz.repository.UpdateTask(ctx, id, data); err != nil { 56 | return entity.ErrCannotUpdateTask 57 | } 58 | 59 | return nil 60 | } 61 | 62 | func (biz *business) DeleteTask(ctx context.Context, id string) error { 63 | _, err := biz.repository.GetTaskById(ctx, id) 64 | 65 | if err != nil { 66 | return err 67 | } 68 | 69 | if err := biz.repository.DeleteTask(ctx, id); err != nil { 70 | return entity.ErrCannotDeleteTask 71 | } 72 | 73 | return nil 74 | } 75 | -------------------------------------------------------------------------------- /modules/task/transport/rest/rest_api.go: -------------------------------------------------------------------------------- 1 | package rest 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "net/http" 6 | "simple-clean-architecture-demo/common" 7 | "simple-clean-architecture-demo/modules/task" 8 | "simple-clean-architecture-demo/modules/task/entity" 9 | ) 10 | 11 | type api struct { 12 | biz task.Business 13 | } 14 | 15 | func NewAPI(biz task.Business) *api { 16 | return &api{biz: biz} 17 | } 18 | 19 | func (api *api) CreateTaskHdl() gin.HandlerFunc { 20 | return func(c *gin.Context) { 21 | var data entity.TaskCreationData 22 | 23 | if err := c.ShouldBind(&data); err != nil { 24 | c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 25 | return 26 | } 27 | 28 | if err := api.biz.CreateNewTask(c.Request.Context(), &data); err != nil { 29 | c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 30 | return 31 | } 32 | 33 | c.JSON(http.StatusOK, gin.H{"data": data.Id}) 34 | } 35 | } 36 | 37 | func (api *api) ListTaskHdl() gin.HandlerFunc { 38 | return func(c *gin.Context) { 39 | var requestData struct { 40 | entity.Filter 41 | common.Paging 42 | } 43 | 44 | if err := c.ShouldBind(&requestData); err != nil { 45 | c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 46 | return 47 | } 48 | 49 | data, err := api.biz.ListTasks(c.Request.Context(), &requestData.Filter, &requestData.Paging) 50 | 51 | if err != nil { 52 | c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 53 | return 54 | } 55 | 56 | c.JSON(http.StatusOK, gin.H{ 57 | "data": data, 58 | //"paging": requestData.Paging, 59 | //"extra": requestData.Filter, 60 | }) 61 | } 62 | } 63 | 64 | func (api *api) GetTaskHdl() gin.HandlerFunc { 65 | return func(c *gin.Context) { 66 | id := c.Param("id") 67 | 68 | data, err := api.biz.GetTaskDetails(c.Request.Context(), id) 69 | 70 | if err != nil { 71 | c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 72 | return 73 | } 74 | 75 | c.JSON(http.StatusOK, gin.H{"data": data}) 76 | } 77 | } 78 | 79 | func (api *api) UpdateTaskHdl() gin.HandlerFunc { 80 | return func(c *gin.Context) { 81 | id := c.Param("id") 82 | 83 | var data entity.TaskPatchData 84 | 85 | if err := c.ShouldBind(&data); err != nil { 86 | c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 87 | return 88 | } 89 | 90 | err := api.biz.UpdateTask(c.Request.Context(), id, &data) 91 | 92 | if err != nil { 93 | c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 94 | return 95 | } 96 | 97 | c.JSON(http.StatusOK, gin.H{"data": true}) 98 | } 99 | } 100 | 101 | func (api *api) DeleteTaskHdl() gin.HandlerFunc { 102 | return func(c *gin.Context) { 103 | id := c.Param("id") 104 | 105 | err := api.biz.DeleteTask(c.Request.Context(), id) 106 | 107 | if err != nil { 108 | c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 109 | return 110 | } 111 | 112 | c.JSON(http.StatusOK, gin.H{"data": true}) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /modules/task/repository/inmem/store.go: -------------------------------------------------------------------------------- 1 | package inmem 2 | 3 | import ( 4 | "context" 5 | "github.com/google/uuid" 6 | "simple-clean-architecture-demo/common" 7 | "simple-clean-architecture-demo/modules/task" 8 | "simple-clean-architecture-demo/modules/task/entity" 9 | "sync" 10 | ) 11 | 12 | // This storage has only task data, we don't have anything about User data 13 | var initData = []entity.Task{ 14 | { 15 | Id: "57606a8d-9348-4cc2-ac0b-c7886108e65e", 16 | Title: "This a task 1", 17 | Description: "Description of task 1", 18 | Status: entity.StatusDoing, 19 | }, 20 | { 21 | Id: "46dacc98-4084-4901-b90e-267d4219f374", 22 | Title: "This a task 2", 23 | Description: "Description of task 2", 24 | Status: entity.StatusDoing, 25 | }, 26 | { 27 | Id: "9fb2f6a6-dcca-4d17-bd58-4bda471d914d", 28 | Title: "This a task 3", 29 | Description: "Description of task 3", 30 | Status: entity.StatusDone, 31 | }, 32 | { 33 | Id: "767f3519-44e0-4a25-a60c-a769a8bdbfc1", 34 | Title: "This a task 4", 35 | Description: "Description of task 3", 36 | Status: entity.StatusDoing, 37 | }, 38 | } 39 | 40 | type inMemStorage struct { 41 | db []entity.Task 42 | l *sync.RWMutex 43 | } 44 | 45 | func NewInMemStorage() task.Repository { 46 | return &inMemStorage{ 47 | db: initData, 48 | l: new(sync.RWMutex), 49 | } 50 | } 51 | 52 | func (s *inMemStorage) InsertNewTask(ctx context.Context, data *entity.TaskCreationData) error { 53 | s.l.Lock() 54 | defer s.l.Unlock() 55 | 56 | newTaskId := uuid.New().String() 57 | newTask := entity.Task{ 58 | Id: newTaskId, 59 | Title: data.Title, 60 | Description: data.Description, 61 | Status: data.Status, 62 | } 63 | 64 | s.db = append(s.db, newTask) 65 | data.Id = newTaskId // carry inserted id 66 | 67 | return nil 68 | } 69 | 70 | func (s *inMemStorage) GetTaskById(ctx context.Context, id string) (*entity.Task, error) { 71 | s.l.RLock() 72 | defer s.l.RUnlock() 73 | 74 | for i, t := range s.db { 75 | if t.Id == id { 76 | foundTask := s.db[i] // copy task 77 | return &foundTask, nil 78 | } 79 | } 80 | 81 | return nil, entity.ErrTaskNotFound 82 | } 83 | 84 | func (s *inMemStorage) FindTasks(ctx context.Context, f *entity.Filter, p *common.Paging) ([]entity.Task, error) { 85 | s.l.RLock() 86 | defer s.l.RUnlock() 87 | 88 | tasks := make([]entity.Task, len(s.db)) 89 | 90 | // we should copy it to avoid data racing when return s.db directly 91 | // in this example, we just return all tasks 92 | copy(tasks, s.db) 93 | p.Total = int64(len(tasks)) 94 | 95 | return tasks, nil 96 | } 97 | 98 | func (s *inMemStorage) UpdateTask(ctx context.Context, id string, data *entity.TaskPatchData) error { 99 | s.l.Lock() 100 | defer s.l.Unlock() 101 | 102 | var foundTask *entity.Task 103 | 104 | // In memory storage, we have to loop find task. 105 | // In DBMS, we should not do this 106 | for i, t := range s.db { 107 | if t.Id == id { 108 | foundTask = &s.db[i] 109 | break 110 | } 111 | } 112 | 113 | if foundTask != nil { 114 | if data.Title != nil { 115 | foundTask.Title = *data.Title 116 | } 117 | 118 | if data.Description != nil { 119 | foundTask.Description = *data.Description 120 | } 121 | 122 | if data.Status != nil { 123 | foundTask.Status = *data.Status 124 | } 125 | } 126 | 127 | return nil 128 | } 129 | 130 | func (s *inMemStorage) DeleteTask(ctx context.Context, id string) error { 131 | s.l.Lock() 132 | defer s.l.Unlock() 133 | 134 | for i := range s.db { 135 | if s.db[i].Id == id { 136 | s.db = append(s.db[:i], s.db[i+1:]...) 137 | break 138 | } 139 | } 140 | 141 | return nil 142 | } 143 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= 2 | github.com/bytedance/sonic v1.8.0 h1:ea0Xadu+sHlu7x5O3gKhRpQ1IKiMrSiHttPF0ybECuA= 3 | github.com/bytedance/sonic v1.8.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= 4 | github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= 5 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= 6 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= 7 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 10 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 11 | github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8= 12 | github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k= 13 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 14 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 15 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 16 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 17 | github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU= 18 | github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s= 19 | github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA= 20 | github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 21 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 22 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 23 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 24 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 25 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 26 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 27 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 28 | github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= 29 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 30 | github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= 31 | github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= 32 | github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= 33 | github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 34 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= 35 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 36 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 37 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 38 | github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= 39 | github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= 40 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 41 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 42 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 43 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 44 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 45 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 46 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 47 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 48 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 49 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 50 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 51 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 52 | github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU= 53 | github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 54 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= 55 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 56 | golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= 57 | golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= 58 | golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= 59 | golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 60 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 61 | golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= 62 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 63 | golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= 64 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 65 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 66 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 67 | google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= 68 | google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 69 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 70 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 71 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 72 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 73 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 74 | --------------------------------------------------------------------------------