├── .github
├── .editorconfig
├── .hound.yml
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── ISSUE_TEMPLATE
│ ├── ---bug.md
│ ├── ---feature.md
│ └── ---question.md
├── README_zh-CN.md
├── config.yml
├── dependabot.yml
├── pull_request_template.md
├── testdata
│ ├── ssl.key
│ └── ssl.pem
└── workflows
│ ├── linter.yml
│ ├── security.yml
│ └── test.yml
├── .gitignore
├── LICENSE
├── README.md
├── _examples
├── application
│ ├── config.toml
│ └── main.go
├── config
│ ├── config.toml
│ └── main.go
├── custom_app
│ └── main.go
├── daemon
│ ├── config.toml
│ └── main.go
├── log
│ └── main.go
└── quickstart
│ └── main.go
├── config
├── config.go
├── config_test.go
├── foo.toml
└── testdata
│ ├── all
│ ├── app.toml
│ ├── http.toml
│ ├── others
│ │ └── 1.toml
│ └── sql.toml
│ └── error
│ └── error.toml
├── daemon
├── daemon.go
├── daemon_test.go
├── sys_proc_attr.go
└── sys_proc_attr_windows.go
├── db
├── redis
│ ├── redis.go
│ ├── redis.toml
│ └── redis_test.go
└── sql
│ ├── sql.go
│ ├── sql.toml
│ └── sql_test.go
├── fiberx
├── fiberx.go
├── fiberx_test.go
├── jsontime.go
├── jsontime_test.go
├── validators.go
└── validators_test.go
├── go.mod
├── go.sum
├── gormx
├── gormx.go
└── gormx_test.go
├── log
├── log.go
└── log_test.go
├── moduler.go
├── moduler_test.go
├── sloop.go
└── sloop_test.go
/.github/.editorconfig:
--------------------------------------------------------------------------------
1 | ; https://editorconfig.org/
2 |
3 | root = true
4 |
5 | [*]
6 | insert_final_newline = true
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | indent_style = space
10 | indent_size = 2
11 |
12 | [{Makefile,go.mod,go.sum,*.go,.gitmodules}]
13 | indent_style = tab
14 | indent_size = 8
15 |
16 | [*.md]
17 | indent_size = 4
18 | trim_trailing_whitespace = false
19 |
20 | eclint_indent_style = unset
21 |
22 | [Dockerfile]
23 | indent_size = 4
--------------------------------------------------------------------------------
/.github/.hound.yml:
--------------------------------------------------------------------------------
1 | golint:
2 | enabled: false
--------------------------------------------------------------------------------
/.github/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
6 |
7 | We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
8 |
9 | ## Our Standards
10 |
11 | Examples of behavior that contributes to a positive environment for our community include:
12 |
13 | - Demonstrating empathy and kindness toward other people
14 | - Being respectful of differing opinions, viewpoints, and experiences
15 | - Giving and gracefully accepting constructive feedback
16 | - Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
17 | - Focusing on what is best not just for us as individuals, but for the overall community
18 |
19 | Examples of unacceptable behavior include:
20 |
21 | - The use of sexualized language or imagery, and sexual attention or advances of any kind
22 | - Trolling, insulting or derogatory comments, and personal or political attacks
23 | - Public or private harassment
24 | - Publishing others' private information, such as a physical or email address, without their explicit permission
25 | - Other conduct which could reasonably be considered inappropriate in a professional setting
26 |
27 | ## Enforcement Responsibilities
28 |
29 | Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
30 |
31 | Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
32 |
33 | ## Scope
34 |
35 | This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
36 |
37 | ## Enforcement
38 |
39 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [Issue](https://github.com/go-dawn/dawn/issues). All complaints will be reviewed and investigated promptly and fairly.
40 |
41 | All community leaders are obligated to respect the privacy and security of the reporter of any incident.
42 |
43 | ## Enforcement Guidelines
44 |
45 | Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
46 |
47 | ### 1. Correction
48 |
49 | **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
50 |
51 | **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
52 |
53 | ### 2. Warning
54 |
55 | **Community Impact**: A violation through a single incident or series of actions.
56 |
57 | **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
58 |
59 | ### 3. Temporary Ban
60 |
61 | **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
62 |
63 | **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
64 |
65 | ### 4. Permanent Ban
66 |
67 | **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
68 |
69 | **Consequence**: A permanent ban from any sort of public interaction within the community.
70 |
71 | ## Attribution
72 |
73 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0,
74 | available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
75 |
76 | Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
77 |
78 | [homepage]: https://www.contributor-covenant.org
79 |
80 | For answers to common questions about this code of conduct, see the FAQ at
81 | https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
82 |
--------------------------------------------------------------------------------
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | When contributing to this repository, please first discuss the change you wish to make by creating an [issue](https://github.com/go-dawn/dawn/issues) or any other method with the owners of this repository before making a change.
4 |
5 | Please note: we have a [code of conduct](https://github.com/go-dawn/dawn/blob/master/.github/CODE_OF_CONDUCT.md), please follow it in all your interactions with the `Dawn` project.
6 |
7 | ## Pull Requests or Commits
8 | Titles always we Should use prefix according to below:
9 |
10 | > 🔥 Feature, ♻️ Refactor, 🩹 Fix, 🚨 Test, 📚 Doc, 🎨 Style
11 | - 🔥 Feature: Add flow to add person
12 | - ♻️ Refactor: Rename file X to Y
13 | - 🩹 Fix: Improve flow
14 | - 🚨 Test: Validate to add a new person
15 | - 📚 Doc: Translate to Portuguese middleware redirect
16 | - 🎨 Style: Respected pattern Golint
17 |
18 | All pull request that contains a feature or fix is mandatory to have unit tests. Your PR is only to be merged if you respect this flow.
19 |
20 | # 👍 Contribute
21 |
22 | If you want to say **thank you** and/or support the active development of `Dawn`:
23 |
24 | 1. Add a [GitHub Star](https://github.com/go-dawn/dawn/stargazers) to the project.
25 | 2. Write a review or tutorial on [Medium](https://medium.com/), [Dev.to](https://dev.to/) or personal blog.
26 | 3. Support the project by donating a [cup of coffee](https://buymeacoff.ee/kiyon).
27 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/---bug.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "\U0001F41B Bug"
3 | about: Create a report to help us improve
4 | title: "\U0001F41B "
5 | labels: 'bug'
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Dawn version**
11 |
12 | **Issue description**
13 |
14 | **Code snippet**
15 |
16 | ```go
17 |
18 | ```
19 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/---feature.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "\U0001F680 Feature"
3 | about: Suggest an idea for this project
4 | title: "\U0001F680 "
5 | labels: 'feature'
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem?**
11 |
12 | **Describe the solution you'd like**
13 |
14 | **Describe alternatives you've considered**
15 |
16 | **Additional context**
17 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/---question.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "\U0001F917 Question"
3 | about: Ask a question so we can help
4 | title: "\U0001F917 "
5 | labels: 'question'
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Question description**
11 |
12 | **Code snippet** _Optional_
13 |
14 | ```go
15 |
16 | ```
17 |
--------------------------------------------------------------------------------
/.github/README_zh-CN.md:
--------------------------------------------------------------------------------
1 | # Dawn
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | `Dawn`是一个基于[fiber](https://github.com/gofiber/fiber)的,个性化的,轻量的,提供了快速开发能力的`web`框架。它提供了配置、日志、`fiber`扩展、`gorm`扩展、事件系统等基础服务。
26 |
27 | `Dawn`的核心理念是模块化。高层的业务模块可以调用低层的基础模块,例如数据库等等。遵循`DDD`的思想,每一个模块对应一个领域,且都可以轻易地转换为微服务。
28 |
29 | 每个模块都需要实现自己的`Init`,`Boot`这两个核心方法,然后注册到`Sloop`中。一般业务模块需要实现其`RegisterRoutes`方法,用于注册路由,提供`http`服务。
30 |
31 | 模块的封装本着不重复造轮子的原则,直接提供依赖库其原本的结构和方法。
32 |
33 | 目前用到的库有
34 | - [klog](https://github.com/kubernetes/klog)
35 | - [viper](https://github.com/spf13/viper)
36 | - [godotenv](https://github.com/joho/godotenv)
37 | - [fiber](https://github.com/gofiber/fiber)
38 | - [gorm](https://github.com/go-gorm/gorm)
39 | - [go-redis](https://github.com/go-redis/redis)
40 | - [validator](https://github.com/go-playground/validator)
41 |
42 | # 注意
43 | **本项目还在开发中,请勿在生产环境中使用。**
44 |
45 | # 为什么是dawn
46 | 这是为了致敬海贼王第一集——`Romance Dawn`。让我们向着浪漫扬帆起航。
47 |
--------------------------------------------------------------------------------
/.github/config.yml:
--------------------------------------------------------------------------------
1 | # Configuration for new-issue-welcome - https://github.com/behaviorbot/new-issue-welcome
2 | # Comment to be posted to on first time issues
3 | newIssueWelcomeComment: >
4 | Thanks for opening your first issue here! 🎉 Be sure to follow the issue template!
5 |
6 | # Configuration for new-pr-welcome - https://github.com/behaviorbot/new-pr-welcome
7 | # Comment to be posted to on PRs from first time contributors in your repository
8 | newPRWelcomeComment: >
9 | Thanks for opening this pull request! 🎉 Please check out our contributing guidelines.
10 |
11 | # Configuration for first-pr-merge - https://github.com/behaviorbot/first-pr-merge
12 | # Comment to be posted to on pull requests merged by a first time user
13 | firstPRMergeComment: >
14 | Congrats on merging your first pull request! 🎉 We here at Dawn are proud of you!
15 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
2 |
3 | version: 2
4 | updates:
5 | - package-ecosystem: "gomod"
6 | directory: "/" # Location of package manifests
7 | schedule:
8 | interval: "daily"
9 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | **Please provide enough information so that others can review your pull request:**
2 |
3 |
4 |
5 | **Explain the *details* for making this change. What existing problem does the pull request solve?**
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.github/testdata/ssl.key:
--------------------------------------------------------------------------------
1 | -----BEGIN PRIVATE KEY-----
2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQD4IQusAs8PJdnG
3 | 3mURt/AXtgC+ceqLOatJ49JJE1VPTkMAy+oE1f1XvkMrYsHqmDf6GWVzgVXryL4U
4 | wq2/nJSm56ddhN55nI8oSN3dtywUB8/ShelEN73nlN77PeD9tl6NksPwWaKrqxq0
5 | FlabRPZSQCfmgZbhDV8Sa8mfCkFU0G0lit6kLGceCKMvmW+9Bz7ebsYmVdmVMxmf
6 | IJStFD44lWFTdUc65WISKEdW2ELcUefb0zOLw+0PCbXFGJH5x5ktksW8+BBk2Hkg
7 | GeQRL/qPCccthbScO0VgNj3zJ3ZZL0ObSDAbvNDG85joeNjDNq5DT/BAZ0bOSbEF
8 | sh+f9BAzAgMBAAECggEBAJWv2cq7Jw6MVwSRxYca38xuD6TUNBopgBvjREixURW2
9 | sNUaLuMb9Omp7fuOaE2N5rcJ+xnjPGIxh/oeN5MQctz9gwn3zf6vY+15h97pUb4D
10 | uGvYPRDaT8YVGS+X9NMZ4ZCmqW2lpWzKnCFoGHcy8yZLbcaxBsRdvKzwOYGoPiFb
11 | K2QuhXZ/1UPmqK9i2DFKtj40X6vBszTNboFxOVpXrPu0FJwLVSDf2hSZ4fMM0DH3
12 | YqwKcYf5te+hxGKgrqRA3tn0NCWii0in6QIwXMC+kMw1ebg/tZKqyDLMNptAK8J+
13 | DVw9m5X1seUHS5ehU/g2jrQrtK5WYn7MrFK4lBzlRwECgYEA/d1TeANYECDWRRDk
14 | B0aaRZs87Rwl/J9PsvbsKvtU/bX+OfSOUjOa9iQBqn0LmU8GqusEET/QVUfocVwV
15 | Bggf/5qDLxz100Rj0ags/yE/kNr0Bb31kkkKHFMnCT06YasR7qKllwrAlPJvQv9x
16 | IzBKq+T/Dx08Wep9bCRSFhzRCnsCgYEA+jdeZXTDr/Vz+D2B3nAw1frqYFfGnEVY
17 | wqmoK3VXMDkGuxsloO2rN+SyiUo3JNiQNPDub/t7175GH5pmKtZOlftePANsUjBj
18 | wZ1D0rI5Bxu/71ibIUYIRVmXsTEQkh/ozoh3jXCZ9+bLgYiYx7789IUZZSokFQ3D
19 | FICUT9KJ36kCgYAGoq9Y1rWJjmIrYfqj2guUQC+CfxbbGIrrwZqAsRsSmpwvhZ3m
20 | tiSZxG0quKQB+NfSxdvQW5ulbwC7Xc3K35F+i9pb8+TVBdeaFkw+yu6vaZmxQLrX
21 | fQM/pEjD7A7HmMIaO7QaU5SfEAsqdCTP56Y8AftMuNXn/8IRfo2KuGwaWwKBgFpU
22 | ILzJoVdlad9E/Rw7LjYhZfkv1uBVXIyxyKcfrkEXZSmozDXDdxsvcZCEfVHM6Ipk
23 | K/+7LuMcqp4AFEAEq8wTOdq6daFaHLkpt/FZK6M4TlruhtpFOPkoNc3e45eM83OT
24 | 6mziKINJC1CQ6m65sQHpBtjxlKMRG8rL/D6wx9s5AoGBAMRlqNPMwglT3hvDmsAt
25 | 9Lf9pdmhERUlHhD8bj8mDaBj2Aqv7f6VRJaYZqP403pKKQexuqcn80mtjkSAPFkN
26 | Cj7BVt/RXm5uoxDTnfi26RF9F6yNDEJ7UU9+peBr99aazF/fTgW/1GcMkQnum8uV
27 | c257YgaWmjK9uB0Y2r2VxS0G
28 | -----END PRIVATE KEY-----
--------------------------------------------------------------------------------
/.github/testdata/ssl.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIICujCCAaKgAwIBAgIJAMbXnKZ/cikUMA0GCSqGSIb3DQEBCwUAMBUxEzARBgNV
3 | BAMTCnVidW50dS5uYW4wHhcNMTUwMjA0MDgwMTM5WhcNMjUwMjAxMDgwMTM5WjAV
4 | MRMwEQYDVQQDEwp1YnVudHUubmFuMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
5 | CgKCAQEA+CELrALPDyXZxt5lEbfwF7YAvnHqizmrSePSSRNVT05DAMvqBNX9V75D
6 | K2LB6pg3+hllc4FV68i+FMKtv5yUpuenXYTeeZyPKEjd3bcsFAfP0oXpRDe955Te
7 | +z3g/bZejZLD8Fmiq6satBZWm0T2UkAn5oGW4Q1fEmvJnwpBVNBtJYrepCxnHgij
8 | L5lvvQc+3m7GJlXZlTMZnyCUrRQ+OJVhU3VHOuViEihHVthC3FHn29Mzi8PtDwm1
9 | xRiR+ceZLZLFvPgQZNh5IBnkES/6jwnHLYW0nDtFYDY98yd2WS9Dm0gwG7zQxvOY
10 | 6HjYwzauQ0/wQGdGzkmxBbIfn/QQMwIDAQABow0wCzAJBgNVHRMEAjAAMA0GCSqG
11 | SIb3DQEBCwUAA4IBAQBQjKm/4KN/iTgXbLTL3i7zaxYXFLXsnT1tF+ay4VA8aj98
12 | L3JwRTciZ3A5iy/W4VSCt3eASwOaPWHKqDBB5RTtL73LoAqsWmO3APOGQAbixcQ2
13 | 45GXi05OKeyiYRi1Nvq7Unv9jUkRDHUYVPZVSAjCpsXzPhFkmZoTRxmx5l0ZF7Li
14 | K91lI5h+eFq0dwZwrmlPambyh1vQUi70VHv8DNToVU29kel7YLbxGbuqETfhrcy6
15 | X+Mha6RYITkAn5FqsZcKMsc9eYGEF4l3XV+oS7q6xfTxktYJMFTI18J0lQ2Lv/CI
16 | whdMnYGntDQBE/iFCrJEGNsKGc38796GBOb5j+zd
17 | -----END CERTIFICATE-----
--------------------------------------------------------------------------------
/.github/workflows/linter.yml:
--------------------------------------------------------------------------------
1 | on: [push, pull_request]
2 | name: Linter
3 | jobs:
4 | Golint:
5 | runs-on: ubuntu-latest
6 | steps:
7 | - name: Fetch Repository
8 | uses: actions/checkout@v2
9 | - name: Run Golint
10 | uses: reviewdog/action-golangci-lint@v1
11 | with:
12 | golangci_lint_flags: "--tests=false"
13 |
--------------------------------------------------------------------------------
/.github/workflows/security.yml:
--------------------------------------------------------------------------------
1 | on: [push, pull_request]
2 | name: Security
3 | jobs:
4 | Gosec:
5 | runs-on: ubuntu-latest
6 | steps:
7 | - name: Fetch Repository
8 | uses: actions/checkout@v2
9 | - name: Run Gosec
10 | uses: securego/gosec@master
11 | with:
12 | args: -exclude=G204 ./...
13 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | on: [push, pull_request]
2 | name: Test
3 | jobs:
4 | Build-Windows:
5 | strategy:
6 | matrix:
7 | go-version: [1.14.x, 1.15.x]
8 | platform: [windows-latest]
9 | runs-on: ${{ matrix.platform }}
10 | steps:
11 | - name: Install Go
12 | uses: actions/setup-go@v1
13 | with:
14 | go-version: ${{ matrix.go-version }}
15 | - name: Fetch Repository
16 | uses: actions/checkout@v2
17 | - name: Run Test
18 | run: go test ./... -v -race
19 |
20 | Build-Macos:
21 | strategy:
22 | matrix:
23 | go-version: [ 1.14.x, 1.15.x ]
24 | platform: [macos-latest]
25 | runs-on: ${{ matrix.platform }}
26 | steps:
27 | - name: Install Go
28 | uses: actions/setup-go@v1
29 | with:
30 | go-version: ${{ matrix.go-version }}
31 | - name: Fetch Repository
32 | uses: actions/checkout@v2
33 | - name: Run Test
34 | run: go test ./... -v -race
35 |
36 | Build-Linux:
37 | strategy:
38 | matrix:
39 | go-version: [1.14.x, 1.15.x]
40 | platform: [ubuntu-latest]
41 | runs-on: ${{ matrix.platform }}
42 | steps:
43 | - name: Install Go
44 | uses: actions/setup-go@v1
45 | with:
46 | go-version: ${{ matrix.go-version }}
47 | - name: Fetch Repository
48 | uses: actions/checkout@v2
49 | - name: Run Test
50 | run: go test ./... -v -race -coverprofile=coverage.txt -covermode=atomic
51 | - name: Upload Coverage report to CodeCov
52 | uses: codecov/codecov-action@v1.0.0
53 | with:
54 | token: ${{secrets.CODECOV_TOKEN}}
55 | file: ./coverage.txt
56 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | ### Go template
3 | # Binaries for programs and plugins
4 | *.exe
5 | *.exe~
6 | *.dll
7 | *.so
8 | *.dylib
9 |
10 | # Test binary, built with `go test -c`
11 | *.test
12 |
13 | # Output of the go coverage tool, specifically when used with LiteIDE
14 | *.out
15 |
16 | # Dependency directories (remove the comment below to include it)
17 | # vendor/
18 |
19 | .idea
20 | /logs/
21 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020-present Kiyon Lin and Contributors
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Dawn
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | `Dawn` is an opinionated `web` framework that provides rapid development capabilities which on top of [fiber](https://github.com/gofiber/fiber). It provides basic services such as configuration, logging, `fiber` extension, `gorm` extension, and event system.
27 |
28 | The core idea of Dawn is modularity. High-level business modules can invoke low-level modules, such as databases, cache and so on. Following the idea of `DDD`, each module corresponds to a domain and can be easily converted into microservices.
29 |
30 | Each module needs to implement its own two core methods of `Init` and `Boot`, and then register it in `Sloop`. General business modules need to implement its `RegisterRoutes` method to register routes and provide `http` services.
31 |
32 | The modules should be based on the principle of not recreating the wheel, and directly provides the original structure and method of the dependent library.
33 |
34 | The libraries currently used are
35 | - [klog](https://github.com/kubernetes/klog)
36 | - [viper](https://github.com/spf13/viper)
37 | - [godotenv](https://github.com/joho/godotenv)
38 | - [fiber](https://github.com/gofiber/fiber)
39 | - [gorm](https://github.com/go-gorm/gorm)
40 | - [go-redis](https://github.com/go-redis/redis)
41 | - [validator](https://github.com/go-playground/validator)
42 |
43 | # Notice
44 | **This project is still under development, please do not use it in a production environment.**
45 |
46 | # Why dawn?
47 | Tribute to the first episode of one piece romance dawn. Let us set sail towards romance with the sloop.
48 |
--------------------------------------------------------------------------------
/_examples/application/config.toml:
--------------------------------------------------------------------------------
1 | Debug = true
2 |
3 | [Sql]
4 | Default = "testing"
5 |
6 | [Sql.Connections]
7 | [Sql.Connections.Testing]
8 | Driver = "sqlite"
9 | Database = "file:dawn?mode=memory&cache=shared&_fk=1"
10 | Prefix = "dawn_"
11 | Log = true
12 | # Uncomment to use other sql connections
13 | #[Sql.Connections.Mysql]
14 | #Driver = "mysql"
15 | #Username = "username"
16 | #Password = "password"
17 | #Host = "127.0.0.1"
18 | #Port = "3306"
19 | #Database = "database"
20 | #Location = "Asia/Shanghai"
21 | #Charset = "utf8mb4"
22 | #ParseTime = true
23 | #Prefix = "dawn_"
24 | #Log = false
25 | #MaxIdleConns = 10
26 | #MaxOpenConns = 100
27 | #ConnMaxLifetime = "5m"
28 | #
29 | #[Sql.Connections.Postgres]
30 | #Driver = "postgres"
31 | #Host = "127.0.0.1"
32 | #Port = "5432"
33 | #Database = "database"
34 | #Username = "username"
35 | #Password = "password"
36 | #Sslmode = "disable"
37 | #TimeZone = "Asia/Shanghai"
38 | #Prefix = "dawn_"
39 | #Log = false
40 | #MaxIdleConns = 10
41 | #MaxOpenConns = 100
42 | #ConnMaxLifetime = "5m"
43 |
44 | [Redis]
45 | Default = "default"
46 |
47 | [Redis.Connections]
48 | [Redis.Connections.default]
49 | Network = "tcp"
50 | Addr = "127.0.0.1:6379"
51 | Username = ""
52 | Password = ""
53 | DB = 0
54 | MaxRetries = 5
55 | DialTimeout = "5s"
56 | ReadTimeout = "5s"
57 | WriteTimeout = "5s"
58 | PoolSize = 1024
59 | MinIdleConns = 10
60 | MaxConnAge = "1m"
61 | PoolTimeout = "1m"
62 | IdleTimeout = "1m"
63 | IdleCheckFrequency = "1m"
64 |
--------------------------------------------------------------------------------
/_examples/application/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/go-dawn/dawn"
5 | "github.com/go-dawn/dawn/config"
6 | "github.com/go-dawn/dawn/db/redis"
7 | "github.com/go-dawn/dawn/db/sql"
8 | "github.com/go-dawn/dawn/log"
9 | )
10 |
11 | func main() {
12 | config.Load("./_examples/application")
13 |
14 | sloop := dawn.New().
15 | AddModulers(
16 | log.New(nil),
17 | sql.New(),
18 | redis.New(),
19 | // add custom module
20 | )
21 |
22 | defer sloop.Cleanup()
23 |
24 | sloop.Setup().Watch()
25 | }
26 |
--------------------------------------------------------------------------------
/_examples/config/config.toml:
--------------------------------------------------------------------------------
1 | foo = "bar"
2 |
--------------------------------------------------------------------------------
/_examples/config/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 |
6 | "github.com/go-dawn/dawn/config"
7 | )
8 |
9 | func main() {
10 | config.Load("./_examples/config")
11 | config.LoadEnv("dawn")
12 |
13 | // output: bar
14 | log.Println(config.GetString("foo"))
15 |
16 | // output: baz
17 | log.Println(config.GetString("bar", "baz"))
18 |
19 | // DAWN_FROM_ENV=hello go run ./examples/config
20 | // output: hello
21 | log.Println(config.GetString("from.env"))
22 | }
23 |
--------------------------------------------------------------------------------
/_examples/custom_app/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 |
6 | "github.com/go-dawn/dawn"
7 | "github.com/go-dawn/dawn/fiberx"
8 | "github.com/gofiber/fiber/v2"
9 | )
10 |
11 | func main() {
12 | app := fiber.New(fiber.Config{
13 | Prefork: true,
14 | })
15 |
16 | // GET / => I'm in prefork mode 🚀
17 | app.Get("/", func(c *fiber.Ctx) error {
18 | return fiberx.Message(c, "I'm in prefork mode 🚀")
19 | })
20 |
21 | sloop := dawn.New(dawn.Config{App: app})
22 |
23 | log.Println(sloop.Run(":3000"))
24 | }
25 |
--------------------------------------------------------------------------------
/_examples/daemon/config.toml:
--------------------------------------------------------------------------------
1 | Debug = true
2 |
3 | [Daemon]
4 | Enable = true
5 | Tries = 10
6 | StdoutLogFile = "./daemon.log"
7 | StderrLogFile = "./daemon.err"
8 |
--------------------------------------------------------------------------------
/_examples/daemon/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/go-dawn/dawn"
5 | "github.com/go-dawn/dawn/config"
6 | "github.com/go-dawn/dawn/daemon"
7 | "github.com/go-dawn/dawn/fiberx"
8 | "github.com/go-dawn/dawn/log"
9 | "github.com/gofiber/fiber/v2"
10 | )
11 |
12 | func main() {
13 | // 🌶️ Notice that go run won't work in daemon mode
14 | // 🌶️ Please at dawn root dir and run go build -o play ./_examples/daemon
15 | // 🌶️ And run ./play
16 | daemon.Run()
17 |
18 | config.Load("./_examples/daemon")
19 |
20 | sloop := dawn.Default().
21 | AddModulers(log.New(nil))
22 |
23 | router := sloop.Router()
24 | router.Get("/", func(c *fiber.Ctx) error {
25 | return fiberx.Message(c, "I'm running in daemon 🍀")
26 | })
27 |
28 | log.Infoln(0, sloop.Run(":3000"))
29 | }
30 |
--------------------------------------------------------------------------------
/_examples/log/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 |
6 | "github.com/go-dawn/dawn/log"
7 | )
8 |
9 | func main() {
10 | log.InitFlags(nil)
11 | flag.Parse()
12 |
13 | defer log.Flush()
14 |
15 | log.Errorln("error")
16 | log.Infoln(0, "info 0")
17 | log.Infoln(1, "info 1")
18 | // Won't log if set -v=1
19 | log.Infoln(2, "info 2")
20 | }
21 |
--------------------------------------------------------------------------------
/_examples/quickstart/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 |
6 | "github.com/go-dawn/dawn"
7 | "github.com/go-dawn/dawn/fiberx"
8 | "github.com/gofiber/fiber/v2"
9 | )
10 |
11 | func main() {
12 | sloop := dawn.Default()
13 |
14 | router := sloop.Router()
15 | // GET / => Welcome to dawn 👋
16 | router.Get("/", func(c *fiber.Ctx) error {
17 | return fiberx.Message(c, "Welcome to dawn 👋")
18 | })
19 |
20 | log.Println(sloop.Run(":3000"))
21 | }
22 |
--------------------------------------------------------------------------------
/config/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "os"
7 | "path/filepath"
8 | "strings"
9 | "sync"
10 | "time"
11 |
12 | _ "github.com/joho/godotenv/autoload"
13 | "github.com/spf13/viper"
14 | )
15 |
16 | // Config is based on spf13/viper
17 | // and extends by supporting default value
18 | type Config struct {
19 | v *viper.Viper
20 | mut sync.RWMutex
21 | }
22 |
23 | var (
24 | global *Config
25 | )
26 |
27 | func init() {
28 | global = New()
29 | }
30 |
31 | // New returns a new Config instance. If a specified filePath(with or without
32 | // extension are both fine) is given, then read config from that file.
33 | func New(filePath ...string) (c *Config) {
34 | c = &Config{v: viper.New()}
35 |
36 | if len(filePath) == 0 {
37 | return
38 | }
39 |
40 | fp := filepath.Clean(filePath[0])
41 | dir, name, ext := filepath.Dir(fp), filepath.Base(fp), filepath.Ext(fp)
42 |
43 | c.v.SetConfigName(strings.TrimRight(name, ext))
44 | c.v.AddConfigPath(dir)
45 | if err := c.v.ReadInConfig(); err != nil {
46 | panic(fmt.Errorf("config: failed to read in %s: %w", fp, err))
47 | }
48 |
49 | return
50 | }
51 |
52 | // Load config into global environment.
53 | // Default config name is "config".
54 | func Load(configPath string, configName ...string) {
55 | v := viper.New()
56 |
57 | name := "config"
58 | if len(configName) > 0 {
59 | name = configName[0]
60 | }
61 |
62 | v.SetConfigName(name)
63 | v.AddConfigPath(configPath)
64 | if err := v.ReadInConfig(); err != nil {
65 | panic(fmt.Errorf("config: failed to read in %s: %w", name, err))
66 | }
67 |
68 | v.WatchConfig()
69 |
70 | global = &Config{v: v}
71 | }
72 |
73 | // LoadAll loads all config contents in the dir path
74 | func LoadAll(configPath string) error {
75 | return filepath.Walk(configPath, func(path string, info os.FileInfo, err error) error {
76 | if !info.IsDir() {
77 | dir, filename := filepath.Split(path)
78 | name := strings.TrimSuffix(filename, filepath.Ext(filename))
79 |
80 | v := viper.New()
81 |
82 | v.SetConfigName(name)
83 | v.AddConfigPath(dir)
84 | if err := v.ReadInConfig(); err != nil {
85 | return fmt.Errorf("config: failed to read in %s: %w", path, err)
86 | }
87 |
88 | rel, _ := filepath.Rel(configPath, path)
89 |
90 | global.MergeConfigMap(configMap(getKeys(rel), v.AllSettings()))
91 | }
92 | return nil
93 | })
94 | }
95 |
96 | // configMap combines configuration recursively
97 | func configMap(keys []string, value interface{}) map[string]interface{} {
98 | if len(keys) == 1 {
99 | return map[string]interface{}{keys[0]: value}
100 | }
101 | return map[string]interface{}{keys[0]: configMap(keys[1:], value)}
102 | }
103 |
104 | func getKeys(path string) (keys []string) {
105 | path = strings.TrimSuffix(path, filepath.Ext(path))
106 |
107 | b := new(bytes.Buffer)
108 | for i := 0; i < len(path); i++ {
109 | if path[i] == os.PathSeparator {
110 | //b.WriteByte('.')
111 | keys = append(keys, b.String())
112 | b.Reset()
113 | } else {
114 | b.WriteByte(path[i])
115 | }
116 | }
117 |
118 | keys = append(keys, b.String())
119 |
120 | return
121 | }
122 |
123 | // Get is shorthand for GetValue.
124 | func Get(key string, defaultValue ...interface{}) interface{} {
125 | return global.Get(key, defaultValue...)
126 | }
127 | func (c *Config) Get(key string, defaultValue ...interface{}) interface{} {
128 | c.mut.RLock()
129 | defer c.mut.RUnlock()
130 |
131 | return c.GetValue(key, defaultValue...)
132 | }
133 |
134 | // GetValue gets value of the key or fallback to the default value.
135 | func GetValue(key string, defaultValue ...interface{}) interface{} {
136 | return global.GetValue(key, defaultValue...)
137 | }
138 | func (c *Config) GetValue(key string, defaultValue ...interface{}) interface{} {
139 | c.mut.RLock()
140 | defer c.mut.RUnlock()
141 |
142 | if len(defaultValue) > 0 {
143 | c.v.SetDefault(key, defaultValue[0])
144 | }
145 |
146 | return c.v.Get(key)
147 | }
148 |
149 | // GetBool gets bool value of the key or fallback to the default value.
150 | func GetBool(key string, defaultValue ...bool) bool {
151 | return global.GetBool(key, defaultValue...)
152 | }
153 | func (c *Config) GetBool(key string, defaultValue ...bool) bool {
154 | c.mut.RLock()
155 | defer c.mut.RUnlock()
156 |
157 | if len(defaultValue) > 0 {
158 | c.v.SetDefault(key, defaultValue[0])
159 | }
160 |
161 | return c.v.GetBool(key)
162 | }
163 |
164 | // GetFloat64 gets float64 value of the key or fallback to the default value.
165 | func GetFloat64(key string, defaultValue ...float64) float64 {
166 | return global.GetFloat64(key, defaultValue...)
167 | }
168 | func (c *Config) GetFloat64(key string, defaultValue ...float64) float64 {
169 | c.mut.RLock()
170 | defer c.mut.RUnlock()
171 |
172 | if len(defaultValue) > 0 {
173 | c.v.SetDefault(key, defaultValue[0])
174 | }
175 |
176 | return c.v.GetFloat64(key)
177 | }
178 |
179 | // GetInt gets int value of the key or fallback to the default value.
180 | func GetInt(key string, defaultValue ...int) int {
181 | return global.GetInt(key, defaultValue...)
182 | }
183 | func (c *Config) GetInt(key string, defaultValue ...int) int {
184 | c.mut.RLock()
185 | defer c.mut.RUnlock()
186 |
187 | if len(defaultValue) > 0 {
188 | c.v.SetDefault(key, defaultValue[0])
189 | }
190 |
191 | return c.v.GetInt(key)
192 | }
193 |
194 | // GetInt64 gets int64 value of the key or fallback to the default value.
195 | func GetInt64(key string, defaultValue ...int64) int64 {
196 | return global.GetInt64(key, defaultValue...)
197 | }
198 | func (c *Config) GetInt64(key string, defaultValue ...int64) int64 {
199 | c.mut.RLock()
200 | defer c.mut.RUnlock()
201 |
202 | if len(defaultValue) > 0 {
203 | c.v.SetDefault(key, defaultValue[0])
204 | }
205 |
206 | return c.v.GetInt64(key)
207 | }
208 |
209 | // GetString gets string value of the key or fallback to the default value.
210 | func GetString(key string, defaultValue ...string) string {
211 | return global.GetString(key, defaultValue...)
212 | }
213 | func (c *Config) GetString(key string, defaultValue ...string) string {
214 | c.mut.RLock()
215 | defer c.mut.RUnlock()
216 |
217 | if len(defaultValue) > 0 {
218 | c.v.SetDefault(key, defaultValue[0])
219 | }
220 |
221 | return c.v.GetString(key)
222 | }
223 |
224 | // GetStringMap gets map[string]interface{} value of the key or fallback to the default value.
225 | func GetStringMap(key string, defaultValue ...map[string]interface{}) map[string]interface{} {
226 | return global.GetStringMap(key, defaultValue...)
227 | }
228 | func (c *Config) GetStringMap(key string, defaultValue ...map[string]interface{}) map[string]interface{} {
229 | c.mut.RLock()
230 | defer c.mut.RUnlock()
231 |
232 | if len(defaultValue) > 0 {
233 | c.v.SetDefault(key, defaultValue[0])
234 | }
235 |
236 | return c.v.GetStringMap(key)
237 | }
238 |
239 | // GetStringMapString gets map[string]string value of the key or fallback to the default value.
240 | func GetStringMapString(key string, defaultValue ...map[string]string) map[string]string {
241 | return global.GetStringMapString(key, defaultValue...)
242 | }
243 | func (c *Config) GetStringMapString(key string, defaultValue ...map[string]string) map[string]string {
244 | c.mut.RLock()
245 | defer c.mut.RUnlock()
246 |
247 | if len(defaultValue) > 0 {
248 | c.v.SetDefault(key, defaultValue[0])
249 | }
250 |
251 | return c.v.GetStringMapString(key)
252 | }
253 |
254 | // GetStringSlice gets string slice value of the key or fallback to the default value.
255 | func GetStringSlice(key string, defaultValue ...[]string) []string {
256 | return global.GetStringSlice(key, defaultValue...)
257 | }
258 | func (c *Config) GetStringSlice(key string, defaultValue ...[]string) []string {
259 | c.mut.RLock()
260 | defer c.mut.RUnlock()
261 |
262 | if len(defaultValue) > 0 {
263 | c.v.SetDefault(key, defaultValue[0])
264 | }
265 |
266 | return c.v.GetStringSlice(key)
267 | }
268 |
269 | // GetTime gets time value of the key or fallback to the default value.
270 | func GetTime(key string, defaultValue ...time.Time) time.Time {
271 | return global.GetTime(key, defaultValue...)
272 | }
273 | func (c *Config) GetTime(key string, defaultValue ...time.Time) time.Time {
274 | c.mut.RLock()
275 | defer c.mut.RUnlock()
276 |
277 | if len(defaultValue) > 0 {
278 | c.v.SetDefault(key, defaultValue[0])
279 | }
280 |
281 | return c.v.GetTime(key)
282 | }
283 |
284 | // GetDuration gets duration value of the key or fallback to the default value.
285 | func GetDuration(key string, defaultValue ...time.Duration) time.Duration {
286 | return global.GetDuration(key, defaultValue...)
287 | }
288 | func (c *Config) GetDuration(key string, defaultValue ...time.Duration) time.Duration {
289 | c.mut.RLock()
290 | defer c.mut.RUnlock()
291 |
292 | if len(defaultValue) > 0 {
293 | c.v.SetDefault(key, defaultValue[0])
294 | }
295 |
296 | return c.v.GetDuration(key)
297 | }
298 |
299 | // AllSettings gets all settings in config.
300 | func AllSettings() map[string]interface{} {
301 | return global.AllSettings()
302 | }
303 | func (c *Config) AllSettings() map[string]interface{} {
304 | c.mut.RLock()
305 | defer c.mut.RUnlock()
306 |
307 | return c.v.AllSettings()
308 | }
309 |
310 | // Unmarshal unmarshals the config into a Struct. Make sure that the tags
311 | // on the fields of the structure are properly set.
312 | func Unmarshal(rawVal interface{}) error {
313 | return global.Unmarshal(rawVal)
314 | }
315 | func (c *Config) Unmarshal(rawVal interface{}) error {
316 | c.mut.RLock()
317 | defer c.mut.RUnlock()
318 |
319 | return c.v.Unmarshal(rawVal)
320 | }
321 |
322 | // UnmarshalKey takes a single key and unmarshals it into a Struct.
323 | func UnmarshalKey(key string, rawVal interface{}) error {
324 | return global.UnmarshalKey(key, rawVal)
325 | }
326 | func (c *Config) UnmarshalKey(key string, rawVal interface{}) error {
327 | c.mut.RLock()
328 | defer c.mut.RUnlock()
329 |
330 | return c.v.UnmarshalKey(key, rawVal)
331 | }
332 |
333 | // MergeConfigMap merges the configuration from the map given with an existing config.
334 | // Note that the map given may be modified.
335 | func MergeConfigMap(cfg map[string]interface{}) {
336 | global.MergeConfigMap(cfg)
337 | }
338 | func (c *Config) MergeConfigMap(cfg map[string]interface{}) {
339 | c.mut.Lock()
340 | defer c.mut.Unlock()
341 |
342 | _ = c.v.MergeConfigMap(cfg)
343 | }
344 |
345 | // Sub returns a new Config instance representing a sub tree of this instance.
346 | // Sub is case-insensitive for a key.
347 | func Sub(key string) *Config {
348 | return global.Sub(key)
349 | }
350 | func (c *Config) Sub(key string) *Config {
351 | c.mut.RLock()
352 | defer c.mut.RUnlock()
353 |
354 | var newConf *Config
355 | if v := c.v.Sub(key); v != nil {
356 | newConf = &Config{v: v}
357 | } else {
358 | newConf = New()
359 | }
360 |
361 | return newConf
362 | }
363 |
364 | // Set sets the value for the key in the override register.
365 | // Set is case-insensitive for a key.
366 | // Will be used instead of values obtained via
367 | // flags, config file, ENV, default, or key/value store.
368 | func Set(key string, value interface{}) {
369 | global.Set(key, value)
370 | }
371 | func (c *Config) Set(key string, value interface{}) {
372 | c.mut.Lock()
373 | defer c.mut.Unlock()
374 |
375 | c.v.Set(key, value)
376 | }
377 |
378 | // Has checks to see if the key has been set in any of the data locations.
379 | // Has is case-insensitive for a key.
380 | func Has(key string) bool {
381 | return global.Has(key)
382 | }
383 | func (c *Config) Has(key string) bool {
384 | c.mut.RLock()
385 | defer c.mut.RUnlock()
386 |
387 | return c.v.IsSet(key)
388 | }
389 |
390 | // LoadEnv loads env from .env or command line
391 | // Use prefix to avoid conflicts with other env variables
392 | // Same config key in env will override that in config file
393 | func LoadEnv(prefix ...string) {
394 | global.LoadEnv(prefix...)
395 | }
396 | func (c *Config) LoadEnv(prefix ...string) {
397 | c.mut.Lock()
398 | defer c.mut.Unlock()
399 |
400 | c.v.AutomaticEnv()
401 | if len(prefix) > 0 && prefix[0] != "" {
402 | c.v.SetEnvPrefix(prefix[0])
403 | }
404 | replacer := strings.NewReplacer(".", "_")
405 | c.v.SetEnvKeyReplacer(replacer)
406 | }
407 |
--------------------------------------------------------------------------------
/config/config_test.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "os"
5 | "testing"
6 | "time"
7 |
8 | "github.com/stretchr/testify/assert"
9 | "github.com/stretchr/testify/require"
10 | )
11 |
12 | var (
13 | configPath = "./"
14 | configName = "foo"
15 | key = "foo"
16 | value = "bar"
17 |
18 | nonExistKey = "non"
19 |
20 | mergeCfg = map[string]interface{}{
21 | "merge": "cfg",
22 | }
23 |
24 | fu forUnmarshal
25 | sub SubConfig
26 | )
27 |
28 | type (
29 | forUnmarshal struct {
30 | S string
31 | SubConfig
32 | }
33 | SubConfig struct {
34 | B bool
35 | }
36 | )
37 |
38 | func Test_Config_New(t *testing.T) {
39 | t.Run("panic", func(t *testing.T) {
40 | assert.Panics(t, func() {
41 | New("./")
42 | })
43 | })
44 |
45 | t.Run("success", func(t *testing.T) {
46 | c := New("./testdata/all/app.toml")
47 | assert.Equal(t, "dev", c.GetString("env"))
48 | })
49 | }
50 |
51 | func Test_Config_Load_Panic(t *testing.T) {
52 | t.Parallel()
53 |
54 | assert.Panics(t, func() {
55 | nonConfigName := "non config name"
56 | Load(configPath, nonConfigName)
57 | })
58 | }
59 |
60 | func Test_Config_Load(t *testing.T) {
61 | reset()
62 |
63 | Load(configPath, configName)
64 | assert.Equal(t, value, GetString(key))
65 |
66 | defaultFileStorePath := "./data/dawn_store.db"
67 | assert.Equal(t, defaultFileStorePath, GetString("cache.file.path", defaultFileStorePath))
68 | }
69 |
70 | func Test_Config_AllGetFunctions(t *testing.T) {
71 | Load(configPath, configName)
72 |
73 | assert.Equal(t, "iface", Get("iface"))
74 | assert.Equal(t, "di", Get(nonExistKey, "di"))
75 |
76 | assert.Equal(t, "iface", GetValue("iface"))
77 | assert.Equal(t, "di", GetValue(nonExistKey, "di"))
78 |
79 | assert.Equal(t, "s", GetString("string"))
80 | assert.Equal(t, "ds", GetString(nonExistKey, "ds"))
81 |
82 | assert.Equal(t, true, GetBool("Bool"))
83 | assert.Equal(t, true, GetBool(nonExistKey, true))
84 |
85 | assert.Equal(t, time.Second, GetDuration("Duration"))
86 | assert.Equal(t, time.Minute, GetDuration(nonExistKey, time.Minute))
87 |
88 | Time, _ := time.Parse("2006-01-02 15:04:05", "2020-03-07 12:31:19")
89 | assert.Equal(t, Time, GetTime("Time"))
90 | now := time.Now()
91 | assert.Equal(t, now, GetTime(nonExistKey, now))
92 |
93 | assert.Equal(t, 1, GetInt("Int"))
94 | assert.Equal(t, 2, GetInt(nonExistKey, 2))
95 |
96 | assert.Equal(t, int64(1), GetInt64("Int"))
97 | assert.Equal(t, int64(2), GetInt64(nonExistKey, 2))
98 |
99 | assert.Equal(t, 1.1, GetFloat64("Float64"))
100 | assert.Equal(t, 2.2, GetFloat64(nonExistKey, 2.2))
101 |
102 | assert.Equal(t, map[string]interface{}{"string": "Map"}, GetStringMap("StringMap"))
103 | assert.Equal(t, map[string]interface{}{"k1": "v1"},
104 | GetStringMap(nonExistKey, map[string]interface{}{"K1": "v1"}))
105 |
106 | assert.Equal(t, map[string]string{"string": "String"},
107 | GetStringMapString("StringMapString"))
108 | assert.Equal(t, map[string]string{"K1": "v1"},
109 | GetStringMapString(nonExistKey, map[string]string{"K1": "v1"}))
110 |
111 | assert.Equal(t, []string{"s1", "s2"}, GetStringSlice("StringSlice"))
112 | assert.Equal(t, []string{"s3", "s4"}, GetStringSlice(nonExistKey, []string{"s3", "s4"}))
113 | }
114 |
115 | func Test_Config_AllSettings(t *testing.T) {
116 | reset()
117 | assert.Len(t, AllSettings(), 0)
118 | }
119 |
120 | func Test_Config_Unmarshal(t *testing.T) {
121 | reset()
122 |
123 | Set("S", value)
124 | err := Unmarshal(&fu)
125 | assert.Nil(t, err)
126 | assert.Equal(t, value, fu.S)
127 | }
128 |
129 | func Test_Config_UnmarshalKey(t *testing.T) {
130 | reset()
131 |
132 | Set("SubConfig.B", true)
133 | err := UnmarshalKey("SubConfig", &sub)
134 | assert.Nil(t, err)
135 | assert.True(t, sub.B)
136 | }
137 |
138 | func Test_Config_MergeConfigMap(t *testing.T) {
139 | reset()
140 |
141 | MergeConfigMap(mergeCfg)
142 | assert.Equal(t, mergeCfg["merge"], GetString("merge"))
143 | }
144 |
145 | func Test_Config_Sub(t *testing.T) {
146 | reset()
147 |
148 | Set("SubConfig.B", true)
149 | c := Sub("SubConfig")
150 | assert.True(t, c.GetBool("B"))
151 |
152 | c = Sub("non-exist")
153 | assert.False(t, c.GetBool("B"))
154 | }
155 |
156 | func Test_Config_Has(t *testing.T) {
157 | reset()
158 |
159 | Set("SubConfig.B", true)
160 |
161 | assert.True(t, Has("SubConfig"))
162 | assert.True(t, Has("SubConfig.B"))
163 | assert.False(t, Has("B"))
164 | }
165 |
166 | func Test_Config_LoadAll(t *testing.T) {
167 | t.Run("error", func(t *testing.T) {
168 | assert.NotNil(t, LoadAll("./testdata/error"))
169 | })
170 |
171 | t.Run("success", func(t *testing.T) {
172 | assert.Nil(t, LoadAll("./testdata/all"))
173 | assert.True(t, global.Has("http"))
174 | assert.True(t, global.Has("others.1"))
175 | })
176 |
177 | t.Run("env", func(t *testing.T) {
178 | assert.Nil(t, LoadAll("./testdata/all"))
179 | assert.Equal(t, false, global.GetBool("app.debug"))
180 |
181 | LoadEnv("DAWN")
182 |
183 | require.NoError(t, os.Setenv("DAWN_APP_DEBUG", "true"))
184 |
185 | assert.Equal(t, true, global.GetBool("app.debug"))
186 | })
187 | }
188 |
189 | func reset() {
190 | global = New()
191 | }
192 |
--------------------------------------------------------------------------------
/config/foo.toml:
--------------------------------------------------------------------------------
1 | foo = "bar"
2 | iface = "iface"
3 | String = "s"
4 | Bool = true
5 | Int = 1
6 | Float64 = 1.1
7 | Duration = 1e9
8 | Time = "2020-03-07 12:31:19"
9 | StringSlice = ["s1", "s2"]
10 | [StringMap]
11 | String = "Map"
12 | [StringMapString]
13 | String = "String"
14 |
--------------------------------------------------------------------------------
/config/testdata/all/app.toml:
--------------------------------------------------------------------------------
1 | # "dev" "production"
2 | Env = "dev"
3 | Debug = false
4 |
--------------------------------------------------------------------------------
/config/testdata/all/http.toml:
--------------------------------------------------------------------------------
1 | # http addr
2 | Host = "127.0.0.1"
3 | # http port
4 | Port = 8888
5 |
--------------------------------------------------------------------------------
/config/testdata/all/others/1.toml:
--------------------------------------------------------------------------------
1 | a = "b"
2 |
--------------------------------------------------------------------------------
/config/testdata/all/sql.toml:
--------------------------------------------------------------------------------
1 | Default = "testing"
2 |
3 | [Connections]
4 | [Connections.testing]
5 | Driver = "sqlite3"
6 | Database = ":memory:"
7 | Prefix = "dawn_"
8 | Log = true
9 |
10 | [Connections.sqlite3]
11 | Driver = "sqlite3"
12 | Preload = false
13 | Database = "./data/database.sqlite"
14 | Prefix = "dawn_"
15 |
16 | [Connections.mysql]
17 | Preload = false
18 | Driver = "mysql"
19 | Host = "127.0.0.1"
20 | Port = "3306"
21 | Database = "forge"
22 | Username = "root"
23 | Password = ""
24 | Location = "Asia/Shanghai"
25 | Charset = "utf8mb4"
26 | ParseTime = true
27 | Prefix = "dawn_"
28 |
29 | [Connections.postgres]
30 | Preload = false
31 | Driver = "postgres"
32 | Host = "127.0.0.1"
33 | Port = "5432"
34 | Database = "forge"
35 | Username = "root"
36 | Password = ""
37 | Prefix = "dawn_"
38 |
39 | [Connections.mssql]
40 | Preload = false
41 | Driver = "mssql"
42 | Host = "127.0.0.1"
43 | Port = "1433"
44 | Database = "forge"
45 | Username = "root"
46 | Password = ""
47 | Prefix = "dawn_"
48 |
--------------------------------------------------------------------------------
/config/testdata/error/error.toml:
--------------------------------------------------------------------------------
1 | xxxxx
2 |
--------------------------------------------------------------------------------
/daemon/daemon.go:
--------------------------------------------------------------------------------
1 | package daemon
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "os"
7 | "os/exec"
8 | "path/filepath"
9 | "time"
10 |
11 | "github.com/go-dawn/dawn/config"
12 | "github.com/go-dawn/pkg/deck"
13 | )
14 |
15 | const envDaemon = "DAWN_DAEMON"
16 | const envDaemonWorker = "DAWN_DAEMON_WORKER"
17 |
18 | var stdoutLogFile *os.File
19 | var stderrLogFile *os.File
20 | var normalRunningTime = time.Second * 10
21 | var osExit = deck.OsExit
22 | var execCommand = deck.ExecCommand
23 |
24 | func Run() {
25 | if isWorker() {
26 | return
27 | }
28 |
29 | // Panic if the initial spawned daemon process has error
30 | if _, err := spawn(true); err != nil {
31 | panic(fmt.Sprintf("dawn: failed to run in daemon mode: %s", err))
32 | }
33 |
34 | setupLogFiles()
35 | defer teardownLogFiles()
36 |
37 | run()
38 | }
39 |
40 | func run() {
41 | var (
42 | cmd *exec.Cmd
43 | err error
44 | count int
45 | start time.Time
46 | max = config.GetInt("daemon.tries", 10)
47 | logger = log.New(stderrLogFile, "", log.LstdFlags)
48 | )
49 |
50 | for {
51 | if count++; count > max {
52 | break
53 | }
54 |
55 | start = time.Now()
56 | if cmd, err = spawn(false); err != nil {
57 | continue
58 | }
59 |
60 | err = cmd.Wait()
61 |
62 | logger.Printf("dawn: (pid:%d)%v exist with err: %v", cmd.Process.Pid, cmd.Args, err)
63 |
64 | if time.Since(start) > normalRunningTime {
65 | // reset count
66 | count = 0
67 | }
68 | }
69 |
70 | logger.Printf("dawn: already attempted %d times", max)
71 |
72 | osExit(1)
73 | }
74 |
75 | func spawn(skip bool) (cmd *exec.Cmd, err error) {
76 | if isDaemon() && skip {
77 | return
78 | }
79 |
80 | args, env := setupArgsAndEnv()
81 |
82 | cmd = execCommand(args[0], args[1:]...)
83 | cmd.Env = append(cmd.Env, env...)
84 | cmd.SysProcAttr = newSysProcAttr()
85 |
86 | if isDaemon() {
87 | if stdoutLogFile != nil {
88 | cmd.Stdout = stdoutLogFile
89 | }
90 |
91 | if stderrLogFile != nil {
92 | cmd.Stderr = stderrLogFile
93 | }
94 | }
95 |
96 | if err = cmd.Start(); err != nil {
97 | return
98 | }
99 |
100 | // Exit main process
101 | if !isDaemon() {
102 | osExit(0)
103 | }
104 |
105 | return
106 | }
107 |
108 | func setupArgsAndEnv() ([]string, []string) {
109 | args, env := os.Args, os.Environ()
110 | if !isDaemon() {
111 | args = append(args, "master process dawn")
112 | env = append(env, envDaemon+"=")
113 | } else if !isWorker() {
114 | args[len(args)-1] = "worker process"
115 | env = append(env, envDaemonWorker+"=")
116 | }
117 |
118 | return args, env
119 | }
120 |
121 | func isDaemon() bool {
122 | _, ok := os.LookupEnv(envDaemon)
123 | return ok
124 | }
125 |
126 | func isWorker() bool {
127 | _, ok := os.LookupEnv(envDaemonWorker)
128 | return ok
129 | }
130 |
131 | func setupLogFiles() {
132 | var err error
133 | if f := config.GetString("daemon.stdoutLogFile"); f != "" {
134 | if stdoutLogFile, err = os.OpenFile(filepath.Clean(f), os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600); err != nil {
135 | panic(fmt.Sprintf("dawn: failed to open stdout log file %s: %s", f, err))
136 | }
137 | }
138 |
139 | if f := config.GetString("daemon.stderrLogFile"); f != "" {
140 | if stderrLogFile, err = os.OpenFile(filepath.Clean(f), os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600); err != nil {
141 | panic(fmt.Sprintf("dawn: failed to open stderr log file %s: %s", f, err))
142 | }
143 | }
144 | }
145 |
146 | func teardownLogFiles() {
147 | if stdoutLogFile != nil {
148 | _ = stdoutLogFile.Close()
149 | }
150 |
151 | if stderrLogFile != nil {
152 | _ = stderrLogFile.Close()
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/daemon/daemon_test.go:
--------------------------------------------------------------------------------
1 | package daemon
2 |
3 | import (
4 | "io/ioutil"
5 | "os"
6 | "testing"
7 |
8 | "github.com/go-dawn/dawn/config"
9 | "github.com/go-dawn/pkg/deck"
10 | "github.com/stretchr/testify/assert"
11 | )
12 |
13 | func TestRun(t *testing.T) {
14 | at := assert.New(t)
15 |
16 | t.Run("worker", func(t *testing.T) {
17 | deck.SetupEnvs(deck.Envs{envDaemonWorker: ""})
18 | defer deck.TeardownEnvs()
19 |
20 | Run()
21 | })
22 |
23 | t.Run("main success", func(t *testing.T) {
24 | config.Set("daemon.tries", 1)
25 |
26 | deck.SetupCmd()
27 | defer deck.TeardownCmd()
28 | deck.SetupOsExit(func(code int) {
29 | if code == 0 {
30 | deck.SetupEnvs(deck.Envs{envDaemon: ""})
31 | } else {
32 | at.Equal(1, code)
33 | }
34 | })
35 | defer deck.TeardownOsExit()
36 | defer deck.TeardownEnvs()
37 |
38 | Run()
39 | })
40 |
41 | t.Run("main error", func(t *testing.T) {
42 | deck.SetupCmdError()
43 | defer deck.TeardownCmd()
44 |
45 | at.Panics(Run)
46 | })
47 |
48 | t.Run("break master", func(t *testing.T) {
49 | config.Set("daemon.tries", 1)
50 |
51 | deck.SetupCmdError()
52 | defer deck.TeardownCmd()
53 | deck.SetupOsExit()
54 | defer deck.TeardownOsExit()
55 |
56 | run()
57 | })
58 | }
59 |
60 | func TestSpawn(t *testing.T) {
61 | at := assert.New(t)
62 |
63 | t.Run("skip", func(t *testing.T) {
64 | deck.SetupEnvs(deck.Envs{envDaemon: ""})
65 | defer deck.TeardownEnvs()
66 |
67 | cmd, err := spawn(true)
68 | at.Nil(err)
69 | at.Nil(cmd)
70 | })
71 |
72 | t.Run("redirect output", func(t *testing.T) {
73 | deck.SetupEnvs(deck.Envs{envDaemon: ""})
74 | defer deck.TeardownEnvs()
75 |
76 | deck.SetupCmdError()
77 | defer deck.TeardownCmd()
78 |
79 | stdoutLogFile, stderrLogFile = os.Stdout, os.Stderr
80 |
81 | cmd, err := spawn(false)
82 |
83 | at.NotNil(err)
84 | at.NotNil(cmd)
85 | })
86 | }
87 |
88 | func TestSetupLogFiles(t *testing.T) {
89 | at := assert.New(t)
90 |
91 | f, err := ioutil.TempFile("", "")
92 | at.Nil(err)
93 | defer func() {
94 | _ = os.Remove(f.Name())
95 | }()
96 |
97 | t.Run("success", func(t *testing.T) {
98 | config.Set("daemon.stdoutLogFile", f.Name())
99 | config.Set("daemon.stderrLogFile", f.Name())
100 |
101 | setupLogFiles()
102 |
103 | at.NotNil(stdoutLogFile)
104 | at.NotNil(stderrLogFile)
105 | })
106 |
107 | t.Run("stdout panic", func(t *testing.T) {
108 | config.Set("daemon.stdoutLogFile", ".")
109 |
110 | at.Panics(setupLogFiles)
111 | })
112 |
113 | t.Run("stderr panic", func(t *testing.T) {
114 | config.Set("daemon.stdoutLogFile", f.Name())
115 | config.Set("daemon.stderrLogFile", ".")
116 |
117 | at.Panics(setupLogFiles)
118 | })
119 | }
120 |
121 | func TestTeardownLogFiles(t *testing.T) {
122 | f := os.NewFile(1, "")
123 | stdoutLogFile, stderrLogFile = f, f
124 | defer func() { stdoutLogFile, stderrLogFile = nil, nil }()
125 |
126 | teardownLogFiles()
127 | }
128 |
129 | func TestHelperCommand(t *testing.T) {
130 | deck.HandleCommand(func(args []string, expectStderr bool) {})
131 | }
132 |
--------------------------------------------------------------------------------
/daemon/sys_proc_attr.go:
--------------------------------------------------------------------------------
1 | // +build !windows,!plan9
2 |
3 | package daemon
4 |
5 | import "syscall"
6 |
7 | func newSysProcAttr() *syscall.SysProcAttr {
8 | return &syscall.SysProcAttr{
9 | Setsid: true,
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/daemon/sys_proc_attr_windows.go:
--------------------------------------------------------------------------------
1 | // +build windows
2 |
3 | package daemon
4 |
5 | import "syscall"
6 |
7 | func newSysProcAttr() *syscall.SysProcAttr {
8 | return &syscall.SysProcAttr{
9 | HideWindow: true,
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/db/redis/redis.go:
--------------------------------------------------------------------------------
1 | package redis
2 |
3 | import (
4 | "context"
5 | "fmt"
6 |
7 | "github.com/go-dawn/dawn"
8 | "github.com/go-dawn/dawn/config"
9 | "github.com/go-redis/redis/v8"
10 | )
11 |
12 | var (
13 | m = &Module{}
14 | fallback = "default"
15 | )
16 |
17 | type Module struct {
18 | dawn.Module
19 | conns map[string]*redis.Client
20 | fallback string
21 | }
22 |
23 | // New gets the moduler
24 | func New() *Module {
25 | return m
26 | }
27 |
28 | // String is module name
29 | func (*Module) String() string {
30 | return "dawn:redis"
31 | }
32 |
33 | // Init does connection work to each database by config:
34 | // [Redis]
35 | // Default = "default"
36 | // [Redis.Connections]
37 | // [Redis.Connections.default]
38 | // Network = "tcp"
39 | // Addr = "127.0.0.1:6379"
40 | // Username = "username"
41 | // Password = "password"
42 | // DB = 0
43 | // MaxRetries = 5
44 | // DialTimeout = "5s"
45 | // ReadTimeout = "5s"
46 | // WriteTimeout = "5s"
47 | // PoolSize = 1024
48 | // MinIdleConns = 10
49 | // MaxConnAge = "1m"
50 | // PoolTimeout = "1m"
51 | // IdleTimeout = "1m"
52 | // IdleCheckFrequency = "1m"
53 | func (m *Module) Init() dawn.Cleanup {
54 | m.conns = make(map[string]*redis.Client)
55 |
56 | // extract redis config
57 | c := config.Sub("redis")
58 |
59 | m.fallback = c.GetString("default", fallback)
60 |
61 | connsConfig := c.GetStringMap("connections")
62 |
63 | // connect each db in config
64 | for name := range connsConfig {
65 | cfg := c.Sub("connections." + name)
66 | m.conns[name] = connect(name, cfg)
67 | }
68 |
69 | return m.cleanup
70 | }
71 |
72 | func connect(name string, c *config.Config) (client *redis.Client) {
73 | addr := c.GetString("Addr", "127.0.0.1:6379")
74 | client = redis.NewClient(&redis.Options{
75 | Network: c.GetString("Network"),
76 | Addr: addr,
77 | Username: c.GetString("Username"),
78 | Password: c.GetString("Password"),
79 | DB: c.GetInt("DB"),
80 | MaxRetries: c.GetInt("MaxRetries"),
81 | DialTimeout: c.GetDuration("DialTimeout"),
82 | ReadTimeout: c.GetDuration("ReadTimeout"),
83 | WriteTimeout: c.GetDuration("WriteTimeout"),
84 | PoolSize: c.GetInt("PoolSize"),
85 | MinIdleConns: c.GetInt("MinIdleConns"),
86 | MaxConnAge: c.GetDuration("MaxConnAge"),
87 | PoolTimeout: c.GetDuration("PoolTimeout"),
88 | IdleTimeout: c.GetDuration("IdleTimeout"),
89 | IdleCheckFrequency: c.GetDuration("IdleCheckFrequency"),
90 | })
91 |
92 | return
93 | }
94 |
95 | func (m *Module) Boot() {
96 | for name, client := range m.conns {
97 | if _, err := client.Ping(context.Background()).Result(); err != nil {
98 | panic(fmt.Sprintf("dawn:redis failed to ping %s(%s): %v", name, client.Options().Addr, err))
99 | }
100 | }
101 | }
102 |
103 | func (m *Module) cleanup() {
104 | // close every connections
105 | for _, client := range m.conns {
106 | _ = client.Close()
107 | }
108 | }
109 |
110 | // Conn gets redis connection by specific name or fallback
111 | func Conn(name ...string) redis.Cmdable {
112 | n := m.fallback
113 |
114 | if len(name) > 0 && name[0] != "" {
115 | n = name[0]
116 | }
117 |
118 | return m.conns[n]
119 | }
120 |
--------------------------------------------------------------------------------
/db/redis/redis.toml:
--------------------------------------------------------------------------------
1 | [Redis]
2 | Default = "default"
3 |
4 | [Redis.Connections]
5 | [Redis.Connections.default]
6 | Network = "tcp"
7 | Addr = "127.0.0.1:6379"
8 | Username = ""
9 | Password = ""
10 | DB = 0
11 | MaxRetries = 5
12 | DialTimeout = "5s"
13 | ReadTimeout = "5s"
14 | WriteTimeout = "5s"
15 | PoolSize = 1024
16 | MinIdleConns = 10
17 | MaxConnAge = "1m"
18 | PoolTimeout = "1m"
19 | IdleTimeout = "1m"
20 | IdleCheckFrequency = "1m"
21 |
--------------------------------------------------------------------------------
/db/redis/redis_test.go:
--------------------------------------------------------------------------------
1 | package redis
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/go-dawn/dawn/config"
7 |
8 | "github.com/go-redis/redis/v8"
9 |
10 | "github.com/stretchr/testify/assert"
11 | )
12 |
13 | func Test_Redis_Module_Name(t *testing.T) {
14 | t.Parallel()
15 |
16 | assert.Equal(t, "dawn:redis", New().String())
17 | }
18 |
19 | func Test_Redis_Module_Init(t *testing.T) {
20 | t.Parallel()
21 |
22 | config.Load("./", "redis")
23 | config.Set("Redis.Connections.Default.Addr", "127.0.0.1:99999")
24 | m := &Module{}
25 |
26 | m.Init()()
27 | }
28 |
29 | func Test_Redis_Module_Boot(t *testing.T) {
30 | m := &Module{
31 | conns: map[string]*redis.Client{
32 | fallback: redis.NewClient(&redis.Options{
33 | Addr: "127.0.0.1:99999",
34 | }),
35 | },
36 | }
37 |
38 | assert.Panics(t, m.Boot)
39 | }
40 |
41 | func Test_Redis_Cleanup(t *testing.T) {
42 | m := &Module{
43 | conns: map[string]*redis.Client{
44 | fallback: redis.NewClient(&redis.Options{}),
45 | },
46 | }
47 |
48 | m.cleanup()
49 | }
50 |
51 | func Test_Redis_Conn(t *testing.T) {
52 | assert.Nil(t, Conn("non"))
53 | }
54 |
--------------------------------------------------------------------------------
/db/sql/sql.go:
--------------------------------------------------------------------------------
1 | package sql
2 |
3 | import (
4 | "fmt"
5 | "net/url"
6 | "strings"
7 |
8 | "github.com/go-dawn/dawn"
9 | "github.com/go-dawn/dawn/config"
10 | "github.com/go-dawn/pkg/deck"
11 | "gorm.io/driver/mysql"
12 | "gorm.io/driver/postgres"
13 | "gorm.io/driver/sqlite"
14 | "gorm.io/gorm"
15 | "gorm.io/gorm/schema"
16 | )
17 |
18 | var (
19 | m = &Module{}
20 | fallback = "testing"
21 | )
22 |
23 | type Module struct {
24 | dawn.Module
25 | conns map[string]*gorm.DB
26 | fallback string
27 | }
28 |
29 | // New gets the moduler
30 | func New() *Module {
31 | return m
32 | }
33 |
34 | // String is module name
35 | func (*Module) String() string {
36 | return "dawn:sql"
37 | }
38 |
39 | // Init does connection work to each database by config:
40 | // [Sql]
41 | // Default = "testing"
42 | // [Sql.Connections]
43 | // [Sql.Connections.testing]
44 | // Driver = "sqlite"
45 | // [Sql.Connections.mysql]
46 | // Driver = "mysql"
47 | func (m *Module) Init() dawn.Cleanup {
48 | m.conns = make(map[string]*gorm.DB)
49 |
50 | // extract sql config
51 | c := config.Sub("sql")
52 |
53 | m.fallback = c.GetString("default", fallback)
54 |
55 | connsConfig := c.GetStringMap("connections")
56 |
57 | if len(connsConfig) == 0 {
58 | m.conns[m.fallback] = connect(m.fallback, config.New())
59 | return m.cleanup
60 | }
61 |
62 | // connect each db in config
63 | for name := range connsConfig {
64 | cfg := c.Sub("connections." + name)
65 | m.conns[name] = connect(name, cfg)
66 | }
67 |
68 | return m.cleanup
69 | }
70 |
71 | // cleanup close every connections
72 | func (m *Module) cleanup() {
73 | for _, gdb := range m.conns {
74 | if db, err := gdb.DB(); err == nil {
75 | _ = db.Close()
76 | }
77 | }
78 | }
79 |
80 | func connect(name string, c *config.Config) (db *gorm.DB) {
81 | driver := c.GetString("driver", "sqlite")
82 |
83 | var err error
84 | switch strings.ToLower(driver) {
85 | case "sqlite":
86 | db, err = resolveSqlite(c)
87 | case "mysql":
88 | db, err = resolveMysql(c)
89 | case "postgres":
90 | db, err = resolvePostgres(c)
91 | default:
92 | panic(fmt.Sprintf("dawn:sql unknown driver %s of %s", driver, name))
93 | }
94 |
95 | if err != nil || db == nil {
96 | panic(fmt.Sprintf("dawn:sql failed to connect %s(%s): %v", name, driver, err))
97 | }
98 |
99 | return
100 | }
101 |
102 | // Conn gets sql connection by specific name or fallback
103 | func Conn(name ...string) *gorm.DB {
104 | n := m.fallback
105 |
106 | if len(name) > 0 && name[0] != "" {
107 | n = name[0]
108 | }
109 |
110 | return m.conns[n]
111 | }
112 |
113 | var l = deck.DisabledGormLogger{}
114 |
115 | // resolveSqlite resolves sqlite connection with config:
116 | // Driver = "sqlite"
117 | // Database = "file:dawn?mode=memory&cache=shared&_fk=1"
118 | // Prefix = "dawn_"
119 | // Log = false
120 | func resolveSqlite(c *config.Config) (*gorm.DB, error) {
121 | gormConfig := &gorm.Config{
122 | NamingStrategy: schema.NamingStrategy{
123 | TablePrefix: c.GetString("Prefix"),
124 | SingularTable: false,
125 | },
126 | }
127 |
128 | // disable logger
129 | if !c.GetBool("Log") {
130 | gormConfig.Logger = l
131 | }
132 |
133 | dbname := c.GetString("Database", "file:dawn?mode=memory&cache=shared&_fk=1")
134 |
135 | return gorm.Open(sqlite.Open(dbname), gormConfig)
136 | }
137 |
138 | // resolveMysql resolves mysql connection with config:
139 | //Driver = "mysql"
140 | //Username = "username"
141 | //Password = "password"
142 | //Host = "127.0.0.1"
143 | //Port = "3306"
144 | //Database = "database"
145 | //Location = "Asia/Shanghai"
146 | //Charset = "utf8mb4"
147 | //ParseTime = true
148 | //Prefix = "dawn_"
149 | //Log = false
150 | //MaxIdleConns = 10
151 | //MaxOpenConns = 100
152 | //ConnMaxLifetime = "5m"
153 | func resolveMysql(c *config.Config) (*gorm.DB, error) {
154 | parseTime := "True"
155 | if !c.GetBool("ParseTime", true) {
156 | parseTime = "False"
157 | }
158 | dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=%s&parseTime=%s&loc=%s",
159 | c.GetString("Username"),
160 | c.GetString("Password"),
161 | c.GetString("Host"),
162 | c.GetString("Port"),
163 | c.GetString("Database"),
164 | c.GetString("Charset", "utf8mb4"),
165 | parseTime,
166 | url.QueryEscape(c.GetString("Location", "Asia/Shanghai")),
167 | )
168 |
169 | gormConfig := &gorm.Config{
170 | NamingStrategy: schema.NamingStrategy{
171 | TablePrefix: c.GetString("Prefix"),
172 | SingularTable: false,
173 | },
174 | }
175 |
176 | // disable logger
177 | if !c.GetBool("Log") {
178 | gormConfig.Logger = l
179 | }
180 |
181 | gdb, err := gorm.Open(mysql.Open(dsn), gormConfig)
182 | if err == nil || c.GetBool("Testing") {
183 | db, err := gdb.DB()
184 | if err != nil {
185 | return gdb, err
186 | }
187 | db.SetMaxIdleConns(c.GetInt("MaxIdleConns"))
188 | db.SetMaxOpenConns(c.GetInt("MaxOpenConns"))
189 | db.SetConnMaxLifetime(c.GetDuration("ConnMaxLifetime"))
190 | }
191 |
192 | return gdb, err
193 | }
194 |
195 | // resolvePostgres resolves postgres connection with config:
196 | //Driver = "postgres"
197 | //Username = "username"
198 | //Password = "password"
199 | //Host = "127.0.0.1"
200 | //Port = "5432"
201 | //Database = "database"
202 | //Sslmode = "disable"
203 | //TimeZone = "Asia/Shanghai"
204 | //Prefix = "dawn_"
205 | //Log = false
206 | //MaxIdleConns = 10
207 | //MaxOpenConns = 100
208 | //ConnMaxLifetime = "5m"
209 | func resolvePostgres(c *config.Config) (*gorm.DB, error) {
210 | dsn := fmt.Sprintf("user=%s password=%s host=%s port=%s dbname=%s sslmode=%s TimeZone=%s",
211 | c.GetString("Username"),
212 | c.GetString("Password"),
213 | c.GetString("Host"),
214 | c.GetString("Port"),
215 | c.GetString("Database"),
216 | c.GetString("Sslmode", "disable"),
217 | url.QueryEscape(c.GetString("TimeZone", "Asia/Shanghai")),
218 | )
219 |
220 | gormConfig := &gorm.Config{
221 | NamingStrategy: schema.NamingStrategy{
222 | TablePrefix: c.GetString("Prefix"),
223 | SingularTable: false,
224 | },
225 | }
226 |
227 | // disable logger
228 | if !c.GetBool("Log") {
229 | gormConfig.Logger = l
230 | }
231 |
232 | gdb, err := gorm.Open(postgres.Open(dsn), gormConfig)
233 | if err == nil || c.GetBool("Testing") {
234 | db, err := gdb.DB()
235 | if err != nil {
236 | return gdb, err
237 | }
238 | db.SetMaxIdleConns(c.GetInt("MaxIdleConns"))
239 | db.SetMaxOpenConns(c.GetInt("MaxOpenConns"))
240 | db.SetConnMaxLifetime(c.GetDuration("ConnMaxLifetime"))
241 | }
242 |
243 | return gdb, err
244 | }
245 |
--------------------------------------------------------------------------------
/db/sql/sql.toml:
--------------------------------------------------------------------------------
1 | [Sql]
2 | Default = "testing"
3 |
4 | [Sql.Connections]
5 | [Sql.Connections.Testing]
6 | Driver = "sqlite"
7 | Database = "file:dawn?mode=memory&cache=shared&_fk=1"
8 | Prefix = "dawn_"
9 | Log = true
10 |
11 | [Sql.Connections.Mysql]
12 | Driver = "mysql"
13 | Username = "username"
14 | Password = "password"
15 | Host = "127.0.0.1"
16 | Port = "3306"
17 | Database = "database"
18 | Location = "Asia/Shanghai"
19 | Charset = "utf8mb4"
20 | ParseTime = true
21 | Prefix = "dawn_"
22 | Log = false
23 | MaxIdleConns = 10
24 | MaxOpenConns = 100
25 | ConnMaxLifetime = "5m"
26 |
27 | [Sql.Connections.Postgres]
28 | Driver = "postgres"
29 | Host = "127.0.0.1"
30 | Port = "5432"
31 | Database = "database"
32 | Username = "username"
33 | Password = "password"
34 | Sslmode = "disable"
35 | TimeZone = "Asia/Shanghai"
36 | Prefix = "dawn_"
37 | Log = false
38 | MaxIdleConns = 10
39 | MaxOpenConns = 100
40 | ConnMaxLifetime = "5m"
41 |
--------------------------------------------------------------------------------
/db/sql/sql_test.go:
--------------------------------------------------------------------------------
1 | package sql
2 |
3 | import (
4 | "context"
5 | "testing"
6 | "time"
7 |
8 | "github.com/go-dawn/dawn/config"
9 | "github.com/stretchr/testify/assert"
10 | "gorm.io/gorm"
11 | "gorm.io/gorm/logger"
12 | )
13 |
14 | func Test_Sql_Module_Name(t *testing.T) {
15 | t.Parallel()
16 |
17 | assert.Equal(t, "dawn:sql", New().String())
18 | }
19 |
20 | func Test_Sql_Module_Init(t *testing.T) {
21 | t.Parallel()
22 |
23 | t.Run("empty config", func(t *testing.T) {
24 | m := &Module{conns: map[string]*gorm.DB{}}
25 |
26 | at := assert.New(t)
27 |
28 | m.Init()()
29 |
30 | at.Equal(fallback, m.fallback)
31 | at.Len(m.conns, 1)
32 | })
33 |
34 | t.Run("with config", func(t *testing.T) {
35 | m := &Module{conns: map[string]*gorm.DB{}}
36 |
37 | config.Set("sql.default", "sqlite")
38 | config.Set("sql.connections.sqlite", map[string]string{})
39 |
40 | at := assert.New(t)
41 |
42 | m.Init()()
43 |
44 | at.Equal("sqlite", m.fallback)
45 | at.Len(m.conns, 1)
46 | })
47 | }
48 |
49 | func Test_Sql_Conn(t *testing.T) {
50 | assert.Nil(t, Conn("non"))
51 | }
52 |
53 | func Test_Sql_connect(t *testing.T) {
54 | t.Parallel()
55 |
56 | t.Run("unknown driver", func(t *testing.T) {
57 | defer func() {
58 | assert.Equal(t, "dawn:sql unknown driver test of name", recover())
59 | }()
60 | c := config.New()
61 | c.Set("driver", "test")
62 | connect("name", c)
63 | })
64 |
65 | t.Run("sqlite", func(t *testing.T) {
66 | c := config.New()
67 | gdb := connect("name", c)
68 |
69 | gdb.Logger.LogMode(logger.Info)
70 | ctx := context.Background()
71 | gdb.Logger.Info(ctx, "info")
72 | gdb.Logger.Warn(ctx, "warn")
73 | gdb.Logger.Trace(ctx, time.Now(), func() (string, int64) { return "", 0 }, nil)
74 | })
75 |
76 | t.Run("mysql", func(t *testing.T) {
77 | defer func() {
78 | assert.Contains(t, recover(),
79 | "dawn:sql failed to connect name(mysql):")
80 | }()
81 |
82 | c := config.New()
83 | c.Set("Driver", "mysql")
84 | c.Set("ParseTime", false)
85 | c.Set("Testing", true)
86 | connect("name", c)
87 | })
88 |
89 | t.Run("postgres", func(t *testing.T) {
90 | defer func() {
91 | assert.Contains(t, recover(),
92 | "dawn:sql failed to connect name(postgres):")
93 | }()
94 |
95 | c := config.New()
96 | c.Set("Driver", "postgres")
97 | c.Set("Testing", true)
98 | connect("name", c)
99 | })
100 | }
101 |
--------------------------------------------------------------------------------
/fiberx/fiberx.go:
--------------------------------------------------------------------------------
1 | package fiberx
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "strings"
7 | "time"
8 |
9 | "github.com/go-playground/validator/v10"
10 | "github.com/gofiber/fiber/v2"
11 | "github.com/gofiber/fiber/v2/utils"
12 | "github.com/valyala/bytebufferpool"
13 | "github.com/valyala/fasthttp"
14 | )
15 |
16 | // ErrHandler is Dawn's error handler
17 | var ErrHandler = func(c *fiber.Ctx, err error) error {
18 | code := fiber.StatusInternalServerError
19 | res := Response{
20 | Code: code,
21 | Message: utils.StatusMessage(code),
22 | }
23 |
24 | if errs, ok := err.(validator.ValidationErrors); ok {
25 | res.Code = fiber.StatusUnprocessableEntity
26 | res.Message = ""
27 | res.Data = removeTopStruct(errs.Translate(trans))
28 | } else if e, ok := err.(*fiber.Error); ok {
29 | res.Code = e.Code
30 | res.Message = e.Message
31 | } else if e, ok := err.(*Error); ok {
32 | res.Code = e.code
33 | res.Message = e.message
34 | if res.Message == "" {
35 | res.Message = e.Error()
36 | }
37 | }
38 |
39 | return Resp(c, res.Code, res)
40 | }
41 |
42 | func removeTopStruct(fields map[string]string) map[string]string {
43 | res := map[string]string{}
44 | for field, msg := range fields {
45 | stripStruct := field[strings.Index(field, ".")+1:]
46 | res[stripStruct] = strings.TrimLeft(msg, stripStruct)
47 | }
48 | return res
49 | }
50 |
51 | // ValidateBody accepts a obj holds results from BodyParser
52 | // and then do the validation by a validator
53 | func ValidateBody(c *fiber.Ctx, obj interface{}) (err error) {
54 | if err := c.BodyParser(obj); err != nil {
55 | return CodeErr(fiber.StatusBadRequest, err)
56 | }
57 |
58 | return V.Struct(obj)
59 | }
60 |
61 | // ValidateQuery accepts a obj holds results from QueryParser
62 | // and then do the validation by a validator
63 | func ValidateQuery(c *fiber.Ctx, obj interface{}) (err error) {
64 | if err := c.QueryParser(obj); err != nil {
65 | return err
66 | }
67 |
68 | return V.Struct(obj)
69 | }
70 |
71 | // Response is a formatted struct for api results
72 | type Response struct {
73 | // Code is the status code by default, but also can be
74 | // a custom code
75 | Code int `json:"code,omitempty"`
76 | // Message shows detail thing back to caller
77 | Message string `json:"message,omitempty"`
78 | // RequestID needs to be used with middleware
79 | RequestID string `json:"request_id,omitempty"`
80 | // Data accepts any thing as the response data
81 | Data interface{} `json:"data,omitempty"`
82 | }
83 |
84 | // Error represents an error that occurred while handling a request.
85 | type Error struct {
86 | code int
87 | err error
88 | message string
89 | }
90 |
91 | // Error makes it compatible with the `error` interface.
92 | func (e *Error) Error() string {
93 | return e.err.Error()
94 | }
95 |
96 | // Resp returns the custom response
97 | func Resp(c *fiber.Ctx, statusCode int, res Response) error {
98 | if res.Code == 0 {
99 | res.Code = statusCode
100 | }
101 |
102 | if id := c.Response().Header.Peek(fiber.HeaderXRequestID); len(id) > 0 && res.RequestID == "" {
103 | res.RequestID = utils.GetString(id)
104 | }
105 |
106 | return c.Status(statusCode).JSON(res)
107 | }
108 |
109 | // Data returns data with status code OK by default
110 | func Data(c *fiber.Ctx, data interface{}) error {
111 | return Resp(c, fiber.StatusOK, Response{Data: data})
112 | }
113 |
114 | // Message responses with 200 and specific message
115 | func Message(c *fiber.Ctx, msg string) error {
116 | return CodeMessage(c, fiber.StatusOK, msg)
117 | }
118 |
119 | // Messagef responses with 200 and specific formatted message
120 | func Messagef(c *fiber.Ctx, format string, args ...interface{}) error {
121 | return CodeMessagef(c, fiber.StatusOK, fmt.Sprintf(format, args...))
122 | }
123 |
124 | // CodeMessage responses with specific status code and message
125 | func CodeMessage(c *fiber.Ctx, code int, msg string) error {
126 | return respCommon(c, code, msg)
127 | }
128 |
129 | // CodeMessagef responses with specific status code and formatted message
130 | func CodeMessagef(c *fiber.Ctx, code int, format string, args ...interface{}) error {
131 | return CodeMessage(c, code, fmt.Sprintf(format, args...))
132 | }
133 |
134 | // Err creates a new internal server Error instance with an optional message
135 | func Err(err error, message ...string) *Error {
136 | return CodeErr(fiber.StatusInternalServerError, err, message...)
137 | }
138 |
139 | // Errf creates a new internal server Error instance with formatted message
140 | func Errf(err error, format string, args ...interface{}) *Error {
141 | return Err(err, fmt.Sprintf(format, args...))
142 | }
143 |
144 | // CodeErr creates a new Error instance with specific status code and
145 | // an optional message
146 | func CodeErr(code int, err error, message ...string) *Error {
147 | e := &Error{
148 | code: code,
149 | err: err,
150 | }
151 |
152 | if len(message) > 0 {
153 | e.message = message[0]
154 | }
155 |
156 | return e
157 | }
158 |
159 | // CodeErrf creates a new Error instance with specific status code and
160 | // a formatted message
161 | func CodeErrf(code int, err error, format string, args ...interface{}) *Error {
162 | return CodeErr(code, err, fmt.Sprintf(format, args...))
163 | }
164 |
165 | // respCommon
166 | func respCommon(c *fiber.Ctx, code int, msg ...string) error {
167 | res := Response{
168 | Message: utils.StatusMessage(code),
169 | }
170 |
171 | if len(msg) > 0 {
172 | res.Message = msg[0]
173 | }
174 | return Resp(c, code, res)
175 | }
176 |
177 | // RespOK responses with status code 200 RFC 7231, 6.3.1
178 | func RespOK(c *fiber.Ctx, msg ...string) error {
179 | return respCommon(c, fiber.StatusOK, msg...)
180 | }
181 |
182 | // RespCreated responses with status code 201 RFC 7231, 6.3.2
183 | func RespCreated(c *fiber.Ctx, msg ...string) error {
184 | return respCommon(c, fiber.StatusCreated, msg...)
185 | }
186 |
187 | // RespAccepted responses with status code 202 RFC 7231, 6.3.3
188 | func RespAccepted(c *fiber.Ctx, msg ...string) error {
189 | return respCommon(c, fiber.StatusAccepted, msg...)
190 | }
191 |
192 | // RespNonAuthoritativeInformation responses with status code 203 RFC 7231, 6.3.4
193 | func RespNonAuthoritativeInformation(c *fiber.Ctx, msg ...string) error {
194 | return respCommon(c, fiber.StatusNonAuthoritativeInformation, msg...)
195 | }
196 |
197 | // RespNoContent responses with status code 204 RFC 7231, 6.3.5
198 | func RespNoContent(c *fiber.Ctx, msg ...string) error {
199 | return respCommon(c, fiber.StatusNoContent, msg...)
200 | }
201 |
202 | // RespResetContent responses with status code 205 RFC 7231, 6.3.6
203 | func RespResetContent(c *fiber.Ctx, msg ...string) error {
204 | return respCommon(c, fiber.StatusResetContent, msg...)
205 | }
206 |
207 | // RespPartialContent responses with status code 206 RFC 7233, 4.1
208 | func RespPartialContent(c *fiber.Ctx, msg ...string) error {
209 | return respCommon(c, fiber.StatusPartialContent, msg...)
210 | }
211 |
212 | // RespMultiStatus responses with status code 207 RFC 4918, 11.1
213 | func RespMultiStatus(c *fiber.Ctx, msg ...string) error {
214 | return respCommon(c, fiber.StatusMultiStatus, msg...)
215 | }
216 |
217 | // RespAlreadyReported responses with status code 208 RFC 5842, 7.1
218 | func RespAlreadyReported(c *fiber.Ctx, msg ...string) error {
219 | return respCommon(c, fiber.StatusAlreadyReported, msg...)
220 | }
221 |
222 | var pid = os.Getpid()
223 |
224 | // Logger logs request and response info to os.Stdout
225 | // or os.Stderr. The format is:
226 | // time #pid[ request-id]: latency status clientIP method protocol://host_path[ error]
227 | func Logger() fiber.Handler {
228 | return func(ctx *fiber.Ctx) (err error) {
229 | start := time.Now()
230 |
231 | err = ctx.Next()
232 |
233 | end := time.Now()
234 | latency := end.Sub(start).Truncate(time.Microsecond)
235 |
236 | bb := bytebufferpool.Get()
237 | defer bytebufferpool.Put(bb)
238 |
239 | // append time
240 | bb.B = end.AppendFormat(bb.B, "2006/01/02 15:04:05.000")
241 |
242 | // append pid
243 | _, _ = bb.WriteString(" #")
244 | bb.B = fasthttp.AppendUint(bb.B, pid)
245 |
246 | // append request id
247 | if requestId := ctx.Response().Header.Peek(fiber.HeaderXRequestID); len(requestId) > 0 {
248 | _ = bb.WriteByte(' ')
249 | _, _ = bb.Write(requestId)
250 | }
251 | _, _ = bb.WriteString(": ")
252 |
253 | // append latency
254 | _, _ = bb.WriteString(latency.String())
255 | _ = bb.WriteByte(' ')
256 |
257 | // append status code
258 | statusCode := ctx.Response().StatusCode()
259 | bb.B = fasthttp.AppendUint(bb.B, statusCode)
260 | _ = bb.WriteByte(' ')
261 |
262 | // append client ip
263 | _, _ = bb.WriteString(ctx.IP())
264 | _ = bb.WriteByte(' ')
265 |
266 | // append http method
267 | _, _ = bb.WriteString(ctx.Method())
268 | _ = bb.WriteByte(' ')
269 |
270 | // append http protocol://host/uri
271 | _, _ = bb.WriteString(ctx.Protocol())
272 | _, _ = bb.WriteString("://")
273 | _, _ = bb.Write(ctx.Request().URI().Host())
274 | _, _ = bb.Write(ctx.Request().RequestURI())
275 |
276 | w := os.Stdout
277 | // append error
278 | if err != nil {
279 | _ = bb.WriteByte(' ')
280 | _, _ = bb.WriteString(err.Error())
281 | w = os.Stderr
282 | }
283 |
284 | // append newline
285 | _ = bb.WriteByte('\n')
286 |
287 | // ignore error on purpose
288 | _, _ = bb.WriteTo(w)
289 |
290 | return
291 | }
292 | }
293 |
--------------------------------------------------------------------------------
/fiberx/fiberx_test.go:
--------------------------------------------------------------------------------
1 | package fiberx
2 |
3 | import (
4 | "bytes"
5 | "errors"
6 | "fmt"
7 | "io"
8 | "io/ioutil"
9 | "net/http/httptest"
10 | "strconv"
11 | "strings"
12 | "testing"
13 |
14 | "github.com/gofiber/fiber/v2"
15 | "github.com/gofiber/fiber/v2/utils"
16 | "github.com/stretchr/testify/assert"
17 | "github.com/valyala/fasthttp"
18 | )
19 |
20 | type respCase struct {
21 | app *fiber.App
22 | method string
23 | target string
24 | reqBody io.Reader
25 | statusCode int
26 | respBody string
27 | }
28 |
29 | func Test_Fiberx_ErrorHandler(t *testing.T) {
30 | t.Run("StatusUnprocessableEntity", func(t *testing.T) {
31 | t.Parallel()
32 |
33 | app := fiber.New(fiber.Config{
34 | ErrorHandler: ErrHandler,
35 | })
36 | app.Get("/422", func(c *fiber.Ctx) error {
37 | type User struct {
38 | Username string `validate:"required"`
39 | Field1 string `validate:"required,lt=10"`
40 | Field2 string `validate:"required,gt=1"`
41 | }
42 |
43 | user := User{
44 | Username: "kiyon",
45 | Field1: "This field is always too long.",
46 | Field2: "1",
47 | }
48 |
49 | return V.Struct(user)
50 | })
51 |
52 | assertRespCase(t, respCase{
53 | app: app,
54 | method: fiber.MethodGet,
55 | target: "/422",
56 | statusCode: fiber.StatusUnprocessableEntity,
57 | respBody: `{"code":422, "data":{"Field1":" must be less than 10 characters in length", "Field2":" must be greater than 1 character in length"}}`,
58 | })
59 | })
60 |
61 | t.Run("normal error", func(t *testing.T) {
62 | t.Parallel()
63 |
64 | app := fiber.New(fiber.Config{
65 | ErrorHandler: ErrHandler,
66 | })
67 | app.Get("/", func(c *fiber.Ctx) error {
68 | return errors.New("hi, i'm an error")
69 | })
70 |
71 | assertRespCase(t, respCase{
72 | app: app,
73 | method: fiber.MethodGet,
74 | target: "/",
75 | statusCode: fiber.StatusInternalServerError,
76 | respBody: `{"code":500, "message":"Internal Server Error"}`,
77 | })
78 | })
79 |
80 | t.Run("fiber error", func(t *testing.T) {
81 | t.Parallel()
82 |
83 | app := fiber.New(fiber.Config{
84 | ErrorHandler: ErrHandler,
85 | })
86 | app.Get("/400", func(c *fiber.Ctx) error {
87 | return fiber.ErrBadRequest
88 | })
89 |
90 | assertRespCase(t, respCase{
91 | app: app,
92 | method: fiber.MethodGet,
93 | target: "/400",
94 | statusCode: fiber.StatusBadRequest,
95 | respBody: `{"code":400, "message":"Bad Request"}`,
96 | })
97 | })
98 |
99 | t.Run("dawn error", func(t *testing.T) {
100 | t.Parallel()
101 |
102 | app := fiber.New(fiber.Config{
103 | ErrorHandler: ErrHandler,
104 | })
105 | app.Get("/500", func(c *fiber.Ctx) error {
106 | return Err(errors.New("error"))
107 | })
108 |
109 | assertRespCase(t, respCase{
110 | app: app,
111 | method: fiber.MethodGet,
112 | target: "/500",
113 | statusCode: fiber.StatusInternalServerError,
114 | respBody: `{"code":500, "message":"error"}`,
115 | })
116 | })
117 | }
118 |
119 | func Test_Fiberx_ValidateBody(t *testing.T) {
120 | at := assert.New(t)
121 | t.Run("success", func(t *testing.T) {
122 | t.Parallel()
123 |
124 | app := fiber.New()
125 |
126 | app.Post("/", func(c *fiber.Ctx) error {
127 | type User struct {
128 | Username string `validate:"required" json:"username"`
129 | }
130 |
131 | var u User
132 | if err := ValidateBody(c, &u); err != nil {
133 | return err
134 | }
135 |
136 | return c.SendString(u.Username)
137 | })
138 |
139 | assertRespCase(t, respCase{
140 | app: app,
141 | method: fiber.MethodPost,
142 | target: "/?username=kiyon",
143 | reqBody: bytes.NewReader([]byte("username=kiyon")),
144 | statusCode: fiber.StatusOK,
145 | respBody: `kiyon`,
146 | })
147 | })
148 |
149 | t.Run("error", func(t *testing.T) {
150 | t.Parallel()
151 |
152 | c := fiber.New().AcquireCtx(&fasthttp.RequestCtx{})
153 | at.NotNil(ValidateBody(c, nil))
154 | })
155 | }
156 |
157 | func Test_Fiberx_ValidateQuery(t *testing.T) {
158 | t.Run("success", func(t *testing.T) {
159 | t.Parallel()
160 |
161 | app := fiber.New()
162 |
163 | app.Get("/", func(c *fiber.Ctx) error {
164 | type User struct {
165 | Username string `validate:"required" json:"username"`
166 | }
167 |
168 | var u User
169 | if err := ValidateQuery(c, &u); err != nil {
170 | return err
171 | }
172 |
173 | return c.SendString(u.Username)
174 | })
175 |
176 | assertRespCase(t, respCase{
177 | app: app,
178 | method: fiber.MethodGet,
179 | target: "/?username=kiyon",
180 | statusCode: fiber.StatusOK,
181 | respBody: `kiyon`,
182 | })
183 | })
184 |
185 | t.Run("error", func(t *testing.T) {
186 | t.Parallel()
187 |
188 | at := assert.New(t)
189 |
190 | fctx := &fasthttp.RequestCtx{}
191 | fctx.Request.URI().SetQueryString("a=b")
192 | c := fiber.New().AcquireCtx(fctx)
193 | at.NotNil(ValidateQuery(c, nil))
194 | })
195 | }
196 |
197 | func Test_Fiberx_2xx(t *testing.T) {
198 | t.Parallel()
199 |
200 | tt := []struct {
201 | code int
202 | fn func(c *fiber.Ctx, msg ...string) error
203 | }{
204 | {fiber.StatusOK, RespOK},
205 | {fiber.StatusCreated, RespCreated},
206 | {fiber.StatusAccepted, RespAccepted},
207 | {fiber.StatusNonAuthoritativeInformation, RespNonAuthoritativeInformation},
208 | {fiber.StatusNoContent, RespNoContent},
209 | {fiber.StatusResetContent, RespResetContent},
210 | {fiber.StatusPartialContent, RespPartialContent},
211 | {fiber.StatusMultiStatus, RespMultiStatus},
212 | {fiber.StatusAlreadyReported, RespAlreadyReported},
213 | }
214 |
215 | for _, tc := range tt {
216 | t.Run(strconv.Itoa(tc.code), func(t *testing.T) {
217 | fn := tc.fn
218 | app := fiber.New()
219 | app.Get("/", func(c *fiber.Ctx) error {
220 | if tc.code == fiber.StatusNoContent {
221 | return fn(c, "I will be removed")
222 | }
223 | return fn(c)
224 | })
225 |
226 | c := respCase{
227 | app: app,
228 | method: fiber.MethodGet,
229 | target: "/",
230 | statusCode: tc.code,
231 | respBody: fmt.Sprintf("{\"code\":%d,\"message\":\"%s\"}", tc.code, utils.StatusMessage(tc.code)),
232 | }
233 |
234 | if tc.code == fiber.StatusNoContent {
235 | c.respBody = ""
236 | }
237 |
238 | assertRespCase(t, c)
239 | })
240 | }
241 | }
242 |
243 | func Test_Fiberx_RequestID(t *testing.T) {
244 | t.Parallel()
245 |
246 | app := fiber.New()
247 | app.Get("/", func(c *fiber.Ctx) error {
248 | c.Set(fiber.HeaderXRequestID, "id")
249 | return RespOK(c)
250 | })
251 |
252 | assertRespCase(t, respCase{
253 | app: app,
254 | method: fiber.MethodGet,
255 | target: "/",
256 | statusCode: fiber.StatusOK,
257 | respBody: `{"code":200,"message":"OK","request_id":"id"}`,
258 | })
259 | }
260 |
261 | func Test_Fiberx_Logger(t *testing.T) {
262 | t.Parallel()
263 |
264 | app := fiber.New(fiber.Config{ErrorHandler: ErrHandler})
265 | app.Use(Logger())
266 |
267 | app.Get("/", func(c *fiber.Ctx) error {
268 | c.Set(fiber.HeaderXRequestID, "id")
269 | return RespOK(c)
270 | })
271 |
272 | assertRespCase(t, respCase{
273 | app: app,
274 | method: fiber.MethodGet,
275 | target: "/",
276 | statusCode: fiber.StatusOK,
277 | respBody: `{"code":200,"message":"OK","request_id":"id"}`,
278 | })
279 |
280 | app.Get("/error", func(c *fiber.Ctx) error {
281 | return fiber.ErrForbidden
282 | })
283 |
284 | assertRespCase(t, respCase{
285 | app: app,
286 | method: fiber.MethodGet,
287 | target: "/error",
288 | statusCode: fiber.StatusForbidden,
289 | respBody: `{"code":403,"message":"Forbidden"}`,
290 | })
291 | }
292 |
293 | func Test_Fiberx_Message(t *testing.T) {
294 | t.Parallel()
295 |
296 | app := fiber.New()
297 | app.Get("/", func(c *fiber.Ctx) error {
298 | return Message(c, "message")
299 | })
300 |
301 | assertRespCase(t, respCase{
302 | app: app,
303 | method: fiber.MethodGet,
304 | target: "/",
305 | statusCode: fiber.StatusOK,
306 | respBody: `{"code":200,"message":"message"}`,
307 | })
308 | }
309 |
310 | func Test_Fiberx_Messagef(t *testing.T) {
311 | t.Parallel()
312 |
313 | app := fiber.New()
314 | app.Get("/", func(c *fiber.Ctx) error {
315 | return Messagef(c, "%s", "message")
316 | })
317 |
318 | assertRespCase(t, respCase{
319 | app: app,
320 | method: fiber.MethodGet,
321 | target: "/",
322 | statusCode: fiber.StatusOK,
323 | respBody: `{"code":200,"message":"message"}`,
324 | })
325 | }
326 |
327 | func Test_Fiberx_Errf(t *testing.T) {
328 | t.Parallel()
329 |
330 | app := fiber.New(fiber.Config{
331 | ErrorHandler: ErrHandler,
332 | })
333 | app.Get("/", func(c *fiber.Ctx) error {
334 | return Errf(errors.New("err"), "%s", "message")
335 | })
336 |
337 | assertRespCase(t, respCase{
338 | app: app,
339 | method: fiber.MethodGet,
340 | target: "/",
341 | statusCode: fiber.StatusInternalServerError,
342 | respBody: `{"code":500,"message":"message"}`,
343 | })
344 | }
345 |
346 | func Test_Fiberx_CodeErrf(t *testing.T) {
347 | t.Parallel()
348 |
349 | app := fiber.New(fiber.Config{
350 | ErrorHandler: ErrHandler,
351 | })
352 | app.Get("/", func(c *fiber.Ctx) error {
353 | return CodeErrf(fiber.StatusUnauthorized, errors.New("err"), "%s", "message")
354 | })
355 |
356 | assertRespCase(t, respCase{
357 | app: app,
358 | method: fiber.MethodGet,
359 | target: "/",
360 | statusCode: fiber.StatusUnauthorized,
361 | respBody: `{"code":401,"message":"message"}`,
362 | })
363 | }
364 |
365 | func Test_Fiberx_Data(t *testing.T) {
366 | t.Parallel()
367 |
368 | app := fiber.New()
369 | app.Get("/", func(c *fiber.Ctx) error {
370 | return Data(c, []string{"data1", "data2"})
371 | })
372 |
373 | assertRespCase(t, respCase{
374 | app: app,
375 | method: fiber.MethodGet,
376 | target: "/",
377 | statusCode: fiber.StatusOK,
378 | respBody: `{"code":200,"data":["data1","data2"]}`,
379 | })
380 | }
381 |
382 | func assertRespCase(t *testing.T, c respCase) {
383 | t.Helper()
384 |
385 | at := assert.New(t)
386 |
387 | isJson := strings.HasPrefix(c.respBody, "{") && strings.HasSuffix(c.respBody, "}")
388 |
389 | res := httptest.NewRequest(c.method, c.target, c.reqBody)
390 | if c.method == fiber.MethodPost {
391 | res.Header.Set(fiber.HeaderContentType, fiber.MIMEApplicationForm)
392 | }
393 |
394 | resp, err := c.app.Test(res)
395 | at.Nil(err)
396 | at.Equal(c.statusCode, resp.StatusCode)
397 | if isJson {
398 | at.Equal(fiber.MIMEApplicationJSON, resp.Header.Get(fiber.HeaderContentType))
399 | }
400 |
401 | var body []byte
402 | body, err = ioutil.ReadAll(resp.Body)
403 | at.Nil(err)
404 | if isJson {
405 | at.JSONEq(c.respBody, string(body))
406 | } else {
407 | at.Equal(c.respBody, string(body))
408 | }
409 | }
410 |
--------------------------------------------------------------------------------
/fiberx/jsontime.go:
--------------------------------------------------------------------------------
1 | package fiberx
2 |
3 | import (
4 | "errors"
5 | "time"
6 | )
7 |
8 | const (
9 | JsonTimeFormat = "2006-01-02 15:04:05"
10 | )
11 |
12 | // JsonTime uses custom format to marshal
13 | // time to json
14 | type JsonTime time.Time
15 |
16 | // MarshalJSON marshals JsonTime into json
17 | func (jt JsonTime) MarshalJSON() ([]byte, error) {
18 | t := time.Time(jt)
19 |
20 | if y := t.Year(); y < 0 || y >= 10000 {
21 | // RFC 3339 is clear that years are 4 digits exactly.
22 | // See golang.org/issue/4556#c15 for more discussion.
23 | return nil, errors.New("JsonTime.MarshalJSON: year outside of range [0,9999]")
24 | }
25 |
26 | b := make([]byte, 0, len(JsonTimeFormat)+2)
27 | b = append(b, '"')
28 | b = t.AppendFormat(b, JsonTimeFormat)
29 | b = append(b, '"')
30 | return b, nil
31 | }
32 |
33 | // UnmarshalJSON unmarshal data to JsonTime
34 | func (jt *JsonTime) UnmarshalJSON(data []byte) error {
35 | // Ignore null, like in the main JSON package.
36 | if string(data) == "null" {
37 | return nil
38 | }
39 |
40 | t, err := time.Parse(`"`+JsonTimeFormat+`"`, string(data))
41 |
42 | *jt = JsonTime(t)
43 |
44 | return err
45 | }
46 |
47 | // String returns custom formatted time string
48 | func (jt JsonTime) String() string {
49 | return time.Time(jt).Format(JsonTimeFormat)
50 | }
51 |
--------------------------------------------------------------------------------
/fiberx/jsontime_test.go:
--------------------------------------------------------------------------------
1 | package fiberx
2 |
3 | import (
4 | "testing"
5 | "time"
6 |
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func TestJsonTime_MarshalJSON(t *testing.T) {
11 | t.Run("out of range", func(t *testing.T) {
12 | target := time.Unix(253402300800, 0)
13 | jt := JsonTime(target)
14 |
15 | _, err := jt.MarshalJSON()
16 | assert.NotNil(t, err)
17 | })
18 |
19 | t.Run("success", func(t *testing.T) {
20 | v := "2020-01-02 03:04:05"
21 |
22 | target, err := time.Parse(JsonTimeFormat, v)
23 | assert.Nil(t, err)
24 |
25 | jt := JsonTime(target)
26 |
27 | b, err := jt.MarshalJSON()
28 | assert.Nil(t, err)
29 |
30 | expect := `"` + v + `"`
31 | assert.Equal(t, expect, string(b))
32 | })
33 | }
34 |
35 | func TestJsonTime_UnmarshalJSON(t *testing.T) {
36 | t.Run("null", func(t *testing.T) {
37 | var (
38 | v = "null"
39 | jt JsonTime
40 | )
41 |
42 | err := jt.UnmarshalJSON([]byte(v))
43 |
44 | assert.Nil(t, err)
45 | assert.Equal(t, JsonTime(time.Time{}), jt)
46 | })
47 |
48 | t.Run("illegal format", func(t *testing.T) {
49 | var (
50 | v string
51 | jt JsonTime
52 | )
53 |
54 | err := jt.UnmarshalJSON([]byte(v))
55 |
56 | assert.NotNil(t, err)
57 | })
58 |
59 | t.Run("success", func(t *testing.T) {
60 | var (
61 | v = `"2020-01-02 03:04:05"`
62 | jt JsonTime
63 | )
64 |
65 | err := jt.UnmarshalJSON([]byte(v))
66 | assert.Nil(t, err)
67 |
68 | assert.Contains(t, v, time.Time(jt).Format(JsonTimeFormat))
69 | })
70 | }
71 |
72 | func TestJsonTime_String(t *testing.T) {
73 | v := "2020-01-02 03:04:05"
74 |
75 | target, err := time.Parse(JsonTimeFormat, v)
76 | assert.Nil(t, err)
77 |
78 | jt := JsonTime(target)
79 |
80 | assert.Equal(t, v, jt.String())
81 | }
82 |
--------------------------------------------------------------------------------
/fiberx/validators.go:
--------------------------------------------------------------------------------
1 | package fiberx
2 |
3 | import (
4 | "regexp"
5 |
6 | "github.com/go-playground/locales/en"
7 | ut "github.com/go-playground/universal-translator"
8 | "github.com/go-playground/validator/v10"
9 | ent "github.com/go-playground/validator/v10/translations/en"
10 | "github.com/gofiber/fiber/v2/utils"
11 | )
12 |
13 | // V is an instance of *validator.Validate
14 | // used for custom registrations.
15 | var V *validator.Validate
16 |
17 | var (
18 | uni *ut.UniversalTranslator
19 | trans ut.Translator
20 |
21 | mobileRegexp *regexp.Regexp
22 | )
23 |
24 | func init() {
25 | V = validator.New()
26 |
27 | uni = ut.New(en.New())
28 | trans, _ = uni.GetTranslator("en")
29 |
30 | if err := ent.RegisterDefaultTranslations(V, trans); err != nil {
31 | panic(err)
32 | }
33 |
34 | registerValidations()
35 | }
36 |
37 | func registerValidations() {
38 | mobileRegexp = regexp.MustCompile(`^1[3456789]\d{9}$`)
39 |
40 | _ = V.RegisterValidation("mobile", MobileRule)
41 | }
42 |
43 | // MobileRule validates mobile phone number
44 | func MobileRule(fl validator.FieldLevel) bool {
45 | return mobileRegexp.Match(utils.GetBytes(fl.Field().String()))
46 | }
47 |
--------------------------------------------------------------------------------
/fiberx/validators_test.go:
--------------------------------------------------------------------------------
1 | package fiberx
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func TestMobile(t *testing.T) {
10 | t.Parallel()
11 |
12 | at := assert.New(t)
13 |
14 | at.Nil(V.Var("13888888888", "mobile"))
15 | at.NotNil(V.Var("23888888888", "mobile"))
16 | at.NotNil(V.Var("1388888888", "mobile"))
17 | at.NotNil(V.Var(13888888888, "mobile"))
18 | }
19 |
20 | func BenchmarkName(b *testing.B) {
21 | b.ReportAllocs()
22 | b.ResetTimer()
23 | b.RunParallel(func(pb *testing.PB) {
24 | for pb.Next() {
25 | _ = V.Var("13888888888", "mobile")
26 | }
27 | })
28 | }
29 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/go-dawn/dawn
2 |
3 | go 1.13
4 |
5 | require (
6 | github.com/go-dawn/pkg v0.0.4-0.20201104085859-62b37379c717
7 | github.com/go-playground/locales v0.13.0
8 | github.com/go-playground/universal-translator v0.17.0
9 | github.com/go-playground/validator/v10 v10.4.1
10 | github.com/go-redis/redis/v8 v8.7.1
11 | github.com/gofiber/fiber/v2 v2.5.0
12 | github.com/jackc/pgproto3/v2 v2.0.7 // indirect
13 | github.com/jinzhu/copier v0.0.0-20201025035756-632e723a6687
14 | github.com/joho/godotenv v1.3.0
15 | github.com/json-iterator/go v1.1.10
16 | github.com/kiyonlin/klog v1.1.1
17 | github.com/klauspost/compress v1.11.12 // indirect
18 | github.com/mattn/go-sqlite3 v1.14.6 // indirect
19 | github.com/spf13/viper v1.7.1
20 | github.com/stretchr/testify v1.7.0
21 | github.com/valyala/bytebufferpool v1.0.0
22 | github.com/valyala/fasthttp v1.22.0
23 | golang.org/x/sys v0.0.0-20210309040221-94ec62e08169 // indirect
24 | gorm.io/driver/mysql v1.0.4
25 | gorm.io/driver/postgres v1.0.8
26 | gorm.io/driver/sqlite v1.1.4
27 | gorm.io/gorm v1.21.3
28 | )
29 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
6 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
7 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
8 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
9 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
10 | cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
11 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
12 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
13 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
14 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
15 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
16 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
17 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
18 | github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
19 | github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
20 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
21 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
22 | github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
23 | github.com/andybalholm/brotli v1.0.1 h1:KqhlKozYbRtJvsPrrEeXcO+N2l6NYT5A2QAFmSULpEc=
24 | github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
25 | github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
26 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
27 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
28 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
29 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
30 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
31 | github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
32 | github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
33 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
34 | github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
35 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
36 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
37 | github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
38 | github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
39 | github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
40 | github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
41 | github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
42 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
43 | github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
44 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
45 | github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
46 | github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
47 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
48 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
49 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
50 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
51 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
52 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
53 | github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
54 | github.com/fasthttp/websocket v1.4.2 h1:AU/zSiIIAuJjBMf5o+vO0syGOnEfvZRu40xIhW/3RuM=
55 | github.com/fasthttp/websocket v1.4.2/go.mod h1:smsv/h4PBEBaU0XDTY5UwJTpZv69fQ0FfcLJr21mA6Y=
56 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
57 | github.com/fatih/structs v1.0.0 h1:BrX964Rv5uQ3wwS+KRUAJCBBw5PQmgJfJ6v4yly5QwU=
58 | github.com/fatih/structs v1.0.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
59 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
60 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
61 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
62 | github.com/gavv/httpexpect/v2 v2.1.0 h1:Q7xnFuKqBY2si4DsqxdbWBt9rfrbVTT2/9YSomc9tEw=
63 | github.com/gavv/httpexpect/v2 v2.1.0/go.mod h1:lnd0TqJLrP+wkJk3SFwtrpSlOAZQ7HaaIFuOYbgqgUM=
64 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
65 | github.com/go-dawn/dawn v0.4.2/go.mod h1:Saosp0k/kTMA8jevdaSAr0ofHWJZXqr92NvvWGsbuCE=
66 | github.com/go-dawn/pkg v0.0.2/go.mod h1:YPEelJmDoZX5xYrHPiYPFyyk7X7BHzjHlXr99IK7sb4=
67 | github.com/go-dawn/pkg v0.0.4-0.20201104085859-62b37379c717 h1:tXcigGAzG0ky6WTmua7IVAFTxOV8QeYWqBJ+kTxgO/I=
68 | github.com/go-dawn/pkg v0.0.4-0.20201104085859-62b37379c717/go.mod h1:isaSuALyoPVmiLYwP5+JXiCSbOsqdaxo0NbPEP1vEaQ=
69 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
70 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
71 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
72 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
73 | github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
74 | github.com/go-logr/logr v0.2.1 h1:fV3MLmabKIZ383XifUjFSwcoGee0v9qgPp8wy5svibE=
75 | github.com/go-logr/logr v0.2.1/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
76 | github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
77 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
78 | github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
79 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
80 | github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
81 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
82 | github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
83 | github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
84 | github.com/go-redis/redis/v8 v8.3.3/go.mod h1:jszGxBCez8QA1HWSmQxJO9Y82kNibbUmeYhKWrBejTU=
85 | github.com/go-redis/redis/v8 v8.7.1 h1:8IYi6RO83fNcG5amcUUYTN/qH2h4OjZHlim3KWGFSsA=
86 | github.com/go-redis/redis/v8 v8.7.1/go.mod h1:BRxHBWn3pO3CfjyX6vAoyeRmCquvxr6QG+2onGV2gYs=
87 | github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
88 | github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
89 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
90 | github.com/gofiber/fiber/v2 v2.1.2/go.mod h1:jMNH7iuOJ1AGdoJrx1OwaZIX7SOrQUtJi9R35QWhi4s=
91 | github.com/gofiber/fiber/v2 v2.1.3/go.mod h1:MMiSv1HrDkN8Pv7NeVDYK+T/lwXOEKAvPBbLvJPCEfA=
92 | github.com/gofiber/fiber/v2 v2.5.0 h1:yml405Um7b98EeMjx63OjSFTATLmX985HPWFfNUPV0w=
93 | github.com/gofiber/fiber/v2 v2.5.0/go.mod h1:f8BRRIMjMdRyt2qmJ/0Sea3j3rwwfufPrh9WNBRiVZ0=
94 | github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
95 | github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
96 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
97 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
98 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
99 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
100 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
101 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
102 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
103 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
104 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
105 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
106 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
107 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
108 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
109 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
110 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
111 | github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
112 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
113 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
114 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
115 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
116 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
117 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
118 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
119 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
120 | github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
121 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
122 | github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
123 | github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
124 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
125 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
126 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
127 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
128 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
129 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
130 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
131 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
132 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
133 | github.com/gorilla/websocket v1.0.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
134 | github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
135 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
136 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
137 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
138 | github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
139 | github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
140 | github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
141 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
142 | github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
143 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
144 | github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
145 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
146 | github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
147 | github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
148 | github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
149 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
150 | github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
151 | github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
152 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
153 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
154 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
155 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
156 | github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
157 | github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
158 | github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
159 | github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
160 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
161 | github.com/imkira/go-interpol v1.0.0 h1:HrmLyvOLJyjR0YofMw8QGdCIuYOs4TJUBDNU5sJC09E=
162 | github.com/imkira/go-interpol v1.0.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
163 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
164 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
165 | github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=
166 | github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
167 | github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
168 | github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
169 | github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
170 | github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
171 | github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
172 | github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
173 | github.com/jackc/pgconn v1.4.0/go.mod h1:Y2O3ZDF0q4mMacyWV3AstPJpeHXWGEetiFttmq5lahk=
174 | github.com/jackc/pgconn v1.5.0/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI=
175 | github.com/jackc/pgconn v1.5.1-0.20200601181101-fa742c524853/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI=
176 | github.com/jackc/pgconn v1.7.0 h1:pwjzcYyfmz/HQOQlENvG1OcDqauTGaqlVahq934F0/U=
177 | github.com/jackc/pgconn v1.7.0/go.mod h1:sF/lPpNEMEOp+IYhyQGdAvrG20gWf6A1tKlr0v7JMeA=
178 | github.com/jackc/pgconn v1.8.0 h1:FmjZ0rOyXTr1wfWs45i4a9vjnjWUAGpMuQLD9OSs+lw=
179 | github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
180 | github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
181 | github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
182 | github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2 h1:JVX6jT/XfzNqIjye4717ITLaNwV9mWbJx0dLCpcRzdA=
183 | github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
184 | github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
185 | github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
186 | github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A=
187 | github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
188 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
189 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
190 | github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
191 | github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
192 | github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
193 | github.com/jackc/pgproto3/v2 v2.0.5 h1:NUbEWPmCQZbMmYlTjVoNPhc0CfnYyz2bfUAh6A5ZVJM=
194 | github.com/jackc/pgproto3/v2 v2.0.5/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
195 | github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
196 | github.com/jackc/pgproto3/v2 v2.0.7 h1:6Pwi1b3QdY65cuv6SyVO0FgPd5J3Bl7wf/nQQjinHMA=
197 | github.com/jackc/pgproto3/v2 v2.0.7/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
198 | github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
199 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
200 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
201 | github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
202 | github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
203 | github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
204 | github.com/jackc/pgtype v1.2.0/go.mod h1:5m2OfMh1wTK7x+Fk952IDmI4nw3nPrvtQdM0ZT4WpC0=
205 | github.com/jackc/pgtype v1.3.1-0.20200510190516-8cd94a14c75a/go.mod h1:vaogEUkALtxZMCH411K+tKzNpwzCKU+AnPzBKZ+I+Po=
206 | github.com/jackc/pgtype v1.3.1-0.20200606141011-f6355165a91c/go.mod h1:cvk9Bgu/VzJ9/lxTO5R5sf80p0DiucVtN7ZxvaC4GmQ=
207 | github.com/jackc/pgtype v1.5.0 h1:jzBqRk2HFG2CV4AIwgCI2PwTgm6UUoCAK2ofHHRirtc=
208 | github.com/jackc/pgtype v1.5.0/go.mod h1:JCULISAZBFGrHaOXIIFiyfzW5VY0GRitRr8NeJsrdig=
209 | github.com/jackc/pgtype v1.6.2 h1:b3pDeuhbbzBYcg5kwNmNDun4pFUD/0AAr1kLXZLeNt8=
210 | github.com/jackc/pgtype v1.6.2/go.mod h1:JCULISAZBFGrHaOXIIFiyfzW5VY0GRitRr8NeJsrdig=
211 | github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
212 | github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
213 | github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
214 | github.com/jackc/pgx/v4 v4.5.0/go.mod h1:EpAKPLdnTorwmPUUsqrPxy5fphV18j9q3wrfRXgo+kA=
215 | github.com/jackc/pgx/v4 v4.6.1-0.20200510190926-94ba730bb1e9/go.mod h1:t3/cdRQl6fOLDxqtlyhe9UWgfIi9R8+8v8GKV5TRA/o=
216 | github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904/go.mod h1:ZDaNWkt9sW1JMiNn0kdYBaLelIhw7Pg4qd+Vk6tw7Hg=
217 | github.com/jackc/pgx/v4 v4.9.0 h1:6STjDqppM2ROy5p1wNDcsC7zJTjSHeuCsguZmXyzx7c=
218 | github.com/jackc/pgx/v4 v4.9.0/go.mod h1:MNGWmViCgqbZck9ujOOBN63gK9XVGILXWCvKLGKmnms=
219 | github.com/jackc/pgx/v4 v4.10.1 h1:/6Q3ye4myIj6AaplUm+eRcz4OhK9HAvFf4ePsG40LJY=
220 | github.com/jackc/pgx/v4 v4.10.1/go.mod h1:QlrWebbs3kqEZPHCTGyxecvzG6tvIsYu+A5b1raylkA=
221 | github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
222 | github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
223 | github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
224 | github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
225 | github.com/jackc/puddle v1.1.2/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
226 | github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
227 | github.com/jinzhu/copier v0.0.0-20201025035756-632e723a6687 h1:bWXum+xWafUxxJpcXnystwg5m3iVpPYtrGJFc1rjfLc=
228 | github.com/jinzhu/copier v0.0.0-20201025035756-632e723a6687/go.mod h1:24xnZezI2Yqac9J61UC6/dG/k76ttpq0DdJI3QmUvro=
229 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
230 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
231 | github.com/jinzhu/now v1.1.1 h1:g39TucaRWyV3dwDO++eEc6qf8TVIQ/Da48WmqjZ3i7E=
232 | github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
233 | github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
234 | github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
235 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
236 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
237 | github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
238 | github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
239 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
240 | github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
241 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
242 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
243 | github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 h1:uC1QfSlInpQF+M0ao65imhwqKnz3Q2z/d8PWZRMQvDM=
244 | github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
245 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
246 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
247 | github.com/kiyonlin/klog v1.1.1 h1:NDNHiUpThMivg/yk4RNPRnSNlX4dPVQcrUMchUqDSYg=
248 | github.com/kiyonlin/klog v1.1.1/go.mod h1:duJmM85PjfJKqZPDrX+4rRnqzQ/wmZqiIAYmc44R1y4=
249 | github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
250 | github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
251 | github.com/klauspost/compress v1.11.1/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
252 | github.com/klauspost/compress v1.11.8/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
253 | github.com/klauspost/compress v1.11.12 h1:famVnQVu7QwryBN4jNseQdUKES71ZAOnB6UQQJPZvqk=
254 | github.com/klauspost/compress v1.11.12/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
255 | github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
256 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
257 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
258 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
259 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
260 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
261 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
262 | github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
263 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
264 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
265 | github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
266 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
267 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
268 | github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
269 | github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
270 | github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU=
271 | github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
272 | github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
273 | github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
274 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
275 | github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
276 | github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
277 | github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE=
278 | github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
279 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
280 | github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
281 | github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
282 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
283 | github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
284 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
285 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
286 | github.com/mattn/go-sqlite3 v1.14.3/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
287 | github.com/mattn/go-sqlite3 v1.14.5 h1:1IdxlwTNazvbKJQSxoJ5/9ECbEeaTTyeU7sEAZ5KKTQ=
288 | github.com/mattn/go-sqlite3 v1.14.5/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
289 | github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg=
290 | github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
291 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
292 | github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
293 | github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
294 | github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
295 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
296 | github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
297 | github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
298 | github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
299 | github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
300 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
301 | github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8h+eeNA8=
302 | github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
303 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
304 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
305 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
306 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
307 | github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
308 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
309 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
310 | github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
311 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
312 | github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
313 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
314 | github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
315 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
316 | github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
317 | github.com/onsi/ginkgo v1.15.0 h1:1V1NfVQR87RtWAgp1lv9JZJ5Jap+XFGKPi00andXGi4=
318 | github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg=
319 | github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
320 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
321 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
322 | github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
323 | github.com/onsi/gomega v1.10.5 h1:7n6FEkpFmfCoo2t+YYqXH0evK+a9ICQz0xcAy9dYcaQ=
324 | github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48=
325 | github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
326 | github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
327 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
328 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
329 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
330 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
331 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
332 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
333 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
334 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
335 | github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
336 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
337 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
338 | github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
339 | github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
340 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
341 | github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
342 | github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
343 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
344 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
345 | github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
346 | github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
347 | github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
348 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
349 | github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
350 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
351 | github.com/savsgio/gotils v0.0.0-20200117113501-90175b0fbe3f h1:PgA+Olipyj258EIEYnpFFONrrCcAIWNUNoFhUfMqAGY=
352 | github.com/savsgio/gotils v0.0.0-20200117113501-90175b0fbe3f/go.mod h1:lHhJedqxCoHN+zMtwGNTXWmF0u9Jt363FYRhV6g0CdY=
353 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
354 | github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
355 | github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
356 | github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
357 | github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc h1:jUIKcSPO9MoMJBbEoyE/RJoE8vz7Mb8AjvifMMwSyvY=
358 | github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
359 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
360 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
361 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
362 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
363 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
364 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
365 | github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
366 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
367 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
368 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
369 | github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
370 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
371 | github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
372 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
373 | github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4=
374 | github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
375 | github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
376 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
377 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
378 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
379 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
380 | github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
381 | github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk=
382 | github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
383 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
384 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
385 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
386 | github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
387 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
388 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
389 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
390 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
391 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
392 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
393 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
394 | github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
395 | github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
396 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
397 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
398 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
399 | github.com/valyala/fasthttp v1.9.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w=
400 | github.com/valyala/fasthttp v1.16.0/go.mod h1:YOKImeEosDdBPnxc0gy7INqi3m1zK6A+xl6TwOBhHCA=
401 | github.com/valyala/fasthttp v1.18.0/go.mod h1:jjraHZVbKOXftJfsOYoAjaeygpj5hr8ermTRJNroD7A=
402 | github.com/valyala/fasthttp v1.22.0 h1:OpwH5KDOJ9cS2bq8fD+KfT4IrksK0llvkHf4MZx42jQ=
403 | github.com/valyala/fasthttp v1.22.0/go.mod h1:0mw2RjXGOzxf4NL2jni3gUQ7LfjjUSiG5sskOUUSEpU=
404 | github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a h1:0R4NLDRDZX6JcmhJgXi5E4b8Wg84ihbmUKp/GvSPEzc=
405 | github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
406 | github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
407 | github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
408 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
409 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
410 | github.com/xeipuuv/gojsonschema v1.1.0 h1:ngVtJC9TY/lg0AA/1k48FYhBrhRoFlEmWzsehpNAaZg=
411 | github.com/xeipuuv/gojsonschema v1.1.0/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
412 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
413 | github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 h1:6fRhSjgLCkTD3JnJxvaJ4Sj+TYblw757bqYgZaOq5ZY=
414 | github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI=
415 | github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA=
416 | github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg=
417 | github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M=
418 | github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=
419 | github.com/yudai/pp v2.0.1+incompatible h1:Q4//iY4pNF6yPLZIigmvcl7k/bPgrcTPIFIcmawg5bI=
420 | github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc=
421 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
422 | github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
423 | go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
424 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
425 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
426 | go.opentelemetry.io/otel v0.13.0/go.mod h1:dlSNewoRYikTkotEnxdmuBHgzT+k/idJSfDv/FxEnOY=
427 | go.opentelemetry.io/otel v0.18.0 h1:d5Of7+Zw4ANFOJB+TIn2K3QWsgS2Ht7OU9DqZHI6qu8=
428 | go.opentelemetry.io/otel v0.18.0/go.mod h1:PT5zQj4lTsR1YeARt8YNKcFb88/c2IKoSABK9mX0r78=
429 | go.opentelemetry.io/otel/metric v0.18.0 h1:yuZCmY9e1ZTaMlZXLrrbAPmYW6tW1A5ozOZeOYGaTaY=
430 | go.opentelemetry.io/otel/metric v0.18.0/go.mod h1:kEH2QtzAyBy3xDVQfGZKIcok4ZZFvd5xyKPfPcuK6pE=
431 | go.opentelemetry.io/otel/oteltest v0.18.0 h1:FbKDFm/LnQDOHuGjED+fy3s5YMVg0z019GJ9Er66hYo=
432 | go.opentelemetry.io/otel/oteltest v0.18.0/go.mod h1:NyierCU3/G8DLTva7KRzGii2fdxdR89zXKH1bNWY7Bo=
433 | go.opentelemetry.io/otel/trace v0.18.0 h1:ilCfc/fptVKaDMK1vWk0elxpolurJbEgey9J6g6s+wk=
434 | go.opentelemetry.io/otel/trace v0.18.0/go.mod h1:FzdUu3BPwZSZebfQ1vl5/tAa8LyMLXSJN57AXIt/iDk=
435 | go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
436 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
437 | go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
438 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
439 | go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
440 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
441 | go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
442 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
443 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
444 | golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
445 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
446 | golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
447 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
448 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
449 | golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
450 | golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
451 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
452 | golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
453 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
454 | golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g=
455 | golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
456 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
457 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
458 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
459 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
460 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
461 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
462 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
463 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
464 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
465 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
466 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
467 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
468 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
469 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
470 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
471 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
472 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
473 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
474 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
475 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
476 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
477 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
478 | golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
479 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
480 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
481 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
482 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
483 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
484 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
485 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
486 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
487 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
488 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
489 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
490 | golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
491 | golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
492 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
493 | golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
494 | golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
495 | golang.org/x/net v0.0.0-20201016165138-7b1cca2348c0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
496 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
497 | golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
498 | golang.org/x/net v0.0.0-20210226101413-39120d07d75e h1:jIQURUJ9mlLvYwTBtRHm9h58rYhSonLvRvgAnP8Nr7I=
499 | golang.org/x/net v0.0.0-20210226101413-39120d07d75e/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
500 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
501 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
502 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
503 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
504 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
505 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
506 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
507 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
508 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
509 | golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
510 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
511 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
512 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
513 | golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
514 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
515 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
516 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
517 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
518 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
519 | golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
520 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
521 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
522 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
523 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
524 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
525 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
526 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
527 | golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
528 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
529 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
530 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
531 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
532 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
533 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
534 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
535 | golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
536 | golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
537 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
538 | golang.org/x/sys v0.0.0-20201026173827-119d4633e4d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
539 | golang.org/x/sys v0.0.0-20201027140754-0fcbb8f4928c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
540 | golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
541 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
542 | golang.org/x/sys v0.0.0-20201210223839-7e3030f88018/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
543 | golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
544 | golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
545 | golang.org/x/sys v0.0.0-20210309040221-94ec62e08169 h1:fpeMGRM6A+XFcw4RPCO8s8hH7ppgrGR22pSIjwM7YUI=
546 | golang.org/x/sys v0.0.0-20210309040221-94ec62e08169/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
547 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
548 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
549 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
550 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
551 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
552 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
553 | golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
554 | golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
555 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
556 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
557 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
558 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
559 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
560 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
561 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
562 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
563 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
564 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
565 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
566 | golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
567 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
568 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
569 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
570 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
571 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
572 | golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
573 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
574 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
575 | golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
576 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
577 | golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
578 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
579 | golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
580 | golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
581 | golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
582 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
583 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
584 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
585 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
586 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
587 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
588 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
589 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
590 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
591 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
592 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
593 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
594 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
595 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
596 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
597 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
598 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
599 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
600 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
601 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
602 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
603 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
604 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
605 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
606 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
607 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
608 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
609 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
610 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
611 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
612 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
613 | google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
614 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
615 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
616 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
617 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
618 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
619 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
620 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
621 | gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
622 | gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
623 | gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
624 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
625 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
626 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
627 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
628 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
629 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
630 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
631 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
632 | gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
633 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
634 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
635 | gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
636 | gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
637 | gorm.io/driver/mysql v1.0.3 h1:+JKBYPfn1tygR1/of/Fh2T8iwuVwzt+PEJmKaXzMQXg=
638 | gorm.io/driver/mysql v1.0.3/go.mod h1:twGxftLBlFgNVNakL7F+P/x9oYqoymG3YYT8cAfI9oI=
639 | gorm.io/driver/mysql v1.0.4 h1:TATTzt+kR+IV0+h3iUB3dHUe8omCvQ0rOkmfCsUBohk=
640 | gorm.io/driver/mysql v1.0.4/go.mod h1:MEgp8tk2n60cSBCq5iTcPDw3ns8Gs+zOva9EUhkknTs=
641 | gorm.io/driver/postgres v1.0.5 h1:raX6ezL/ciUmaYTvOq48jq1GE95aMC0CmxQYbxQ4Ufw=
642 | gorm.io/driver/postgres v1.0.5/go.mod h1:qrD92UurYzNctBMVCJ8C3VQEjffEuphycXtxOudXNCA=
643 | gorm.io/driver/postgres v1.0.8 h1:PAgM+PaHOSAeroTjHkCHCBIHHoBIf9RgPWGo8dF2DA8=
644 | gorm.io/driver/postgres v1.0.8/go.mod h1:4eOzrI1MUfm6ObJU/UcmbXyiHSs8jSwH95G5P5dxcAg=
645 | gorm.io/driver/sqlite v1.1.3/go.mod h1:AKDgRWk8lcSQSw+9kxCJnX/yySj8G3rdwYlU57cB45c=
646 | gorm.io/driver/sqlite v1.1.4 h1:PDzwYE+sI6De2+mxAneV9Xs11+ZyKV6oxD3wDGkaNvM=
647 | gorm.io/driver/sqlite v1.1.4/go.mod h1:mJCeTFr7+crvS+TRnWc5Z3UvwxUN1BGBLMrf5LA9DYw=
648 | gorm.io/gorm v1.20.1/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
649 | gorm.io/gorm v1.20.4/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
650 | gorm.io/gorm v1.20.5/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
651 | gorm.io/gorm v1.20.7/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
652 | gorm.io/gorm v1.20.12/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
653 | gorm.io/gorm v1.21.3 h1:qDFi55ZOsjZTwk5eN+uhAmHi8GysJ/qCTichM/yO7ME=
654 | gorm.io/gorm v1.21.3/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
655 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
656 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
657 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
658 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
659 | moul.io/http2curl v1.0.1-0.20190925090545-5cd742060b0e h1:C7q+e9M5nggAvWfVg9Nl66kebKeuJlP3FD58V4RR5wo=
660 | moul.io/http2curl v1.0.1-0.20190925090545-5cd742060b0e/go.mod h1:nejbQVfXh96n9dSF6cH3Jsk/QI1Z2oEL7sSI2ifXFNA=
661 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
662 |
--------------------------------------------------------------------------------
/gormx/gormx.go:
--------------------------------------------------------------------------------
1 | package gormx
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "reflect"
7 | "strings"
8 | "time"
9 |
10 | "github.com/go-dawn/dawn/config"
11 | "github.com/jinzhu/copier"
12 | jsoniter "github.com/json-iterator/go"
13 | "github.com/valyala/fasthttp"
14 | "gorm.io/gorm"
15 | )
16 |
17 | type Dao struct {
18 | ID uint32 `gorm:"primary_key"`
19 | CreatedAt time.Time
20 | UpdatedAt time.Time
21 | DeletedAt *gorm.DeletedAt `gorm:"index"`
22 | }
23 |
24 | // Pagination contains page, page size, total count
25 | // and list data
26 | type Pagination struct {
27 | Page int `json:"page"`
28 | PageSize int `json:"page_size"`
29 | Total int `json:"total"`
30 | Data interface{} `json:"data"`
31 | }
32 |
33 | // Paginate gets pagination based on index query and transfer
34 | // dao to domain object
35 | func Paginate(scope *gorm.DB, args *fasthttp.Args, daos, data interface{}) (*Pagination, error) {
36 | q := indexQuery{args}
37 |
38 | page, pageSize := q.pageInfo()
39 |
40 | scope = scope.Scopes(
41 | scopeSearch(q.search()),
42 | scopeSort(q.sort()),
43 | scopePaginate(page, pageSize),
44 | )
45 |
46 | var (
47 | total int64
48 | errChan = make(chan error)
49 | )
50 |
51 | go func() {
52 | errChan <- scope.Find(daos).Error
53 | }()
54 |
55 | // get total count
56 | err1, err2 := <-errChan, scope.Model(daos).Count(&total).Error
57 |
58 | if err1 != nil && err1 != gorm.ErrRecordNotFound {
59 | return nil, err1
60 | }
61 |
62 | if err2 != nil {
63 | return nil, err2
64 | }
65 |
66 | if err := copier.Copy(data, daos); err != nil {
67 | return nil, err
68 | }
69 |
70 | return &Pagination{
71 | Page: page,
72 | PageSize: pageSize,
73 | Total: int(total),
74 | // transfer pointer to slice
75 | Data: reflect.ValueOf(data).Elem().Interface(),
76 | }, nil
77 | }
78 |
79 | // indexQuery wraps helper functions of query string
80 | type indexQuery struct {
81 | *fasthttp.Args
82 | }
83 |
84 | // search gets search data in json format
85 | func (q indexQuery) search() []byte {
86 | return q.Peek("search")
87 | }
88 |
89 | // sort gets sort params
90 | func (q indexQuery) sort() []byte {
91 | return q.Peek("sort")
92 | }
93 |
94 | // page gets index page
95 | func (q indexQuery) page() int {
96 | n := q.GetUintOrZero("page")
97 | if n <= 0 {
98 | return 1
99 | }
100 | return n
101 | }
102 |
103 | // pageSize gets page size of index
104 | func (q indexQuery) pageSize() int {
105 | n := q.GetUintOrZero("pageSize")
106 | if n <= 0 {
107 | return config.GetInt("http.pageSize", 15)
108 | }
109 | return n
110 | }
111 |
112 | // pageInfo gets page and page size
113 | func (q indexQuery) pageInfo() (int, int) {
114 | return q.page(), q.pageSize()
115 | }
116 |
117 | func scopePaginate(page, pageSize int) func(db *gorm.DB) *gorm.DB {
118 | return func(scope *gorm.DB) *gorm.DB {
119 | offset := (page - 1) * pageSize
120 |
121 | return scope.Offset(offset).Limit(pageSize)
122 | }
123 | }
124 |
125 | func scopeSearch(search []byte) func(db *gorm.DB) *gorm.DB {
126 | return func(scope *gorm.DB) *gorm.DB {
127 | params := map[string]interface{}{}
128 | if err := jsoniter.Unmarshal(search, ¶ms); err == nil {
129 | //"name":"jone" => name like ?, %jone%
130 | //"name":["jone","kj"] => name in (?), ["jone","kj"]
131 | //"free": true | false => free = ?, true
132 | //"name$<>": "jone" name <> ? , "jone"
133 | //"date$<>":["2020-12-12",""] date not in (?), "", ""
134 | //"date$><":["2020-12-12",""] date between ? and ?, "", ""
135 | //"date$<":"" => date < ?
136 | //"date$<=":"" => date <= ?
137 | //"date$>=":"" => date >= ?
138 | //"date$>":"" => date > ?
139 | for key, val := range params {
140 | // 使用$分割列名和操作符
141 | strs := strings.Split(key, "$")
142 | // 不带操作符
143 | if len(strs) == 1 {
144 | column := strs[0]
145 | // 根据值的类型附加过滤条件
146 | switch reflect.TypeOf(val).Kind() {
147 | case reflect.String:
148 | // 字符串全部用like过滤
149 | scope = scope.Where(column+" LIKE ?", fmt.Sprintf("%%%v%%", val))
150 | case reflect.Bool, reflect.Float64:
151 | scope = scope.Where(column+" = ?", val)
152 | case reflect.Slice, reflect.Array:
153 | // 数组使用 IN
154 | if arr, ok := val.([]interface{}); ok {
155 | scope = scope.Where(column+" IN (?)", arr)
156 | }
157 | }
158 | }
159 | // 带操作符
160 | if len(strs) == 2 {
161 | column, opt := strs[0], strs[1]
162 | switch opt {
163 | // 比较操作符
164 | case ">", ">=", "<", "<=":
165 | switch val.(type) {
166 | // 只支持字符串和数字类型
167 | case string, float64:
168 | scope = scope.Where(fmt.Sprintf("%s %s ?", column, opt), val)
169 | }
170 | case "><": // between 操作符
171 | switch reflect.TypeOf(val).Kind() {
172 | case reflect.Slice, reflect.Array:
173 | // 只支持长度为2的数组
174 | if arr, ok := val.([]interface{}); ok && len(arr) == 2 {
175 | scope = scope.Where(strs[0]+" BETWEEN ? AND ?", arr[0], arr[1])
176 | }
177 | }
178 | }
179 | }
180 |
181 | // 忽略其他情况
182 | }
183 | }
184 |
185 | return scope
186 | }
187 | }
188 |
189 | var sortSep = []byte(",")
190 |
191 | func scopeSort(sort []byte) func(db *gorm.DB) *gorm.DB {
192 | return func(scope *gorm.DB) *gorm.DB {
193 | if len(sort) == 0 {
194 | return scope
195 | }
196 | buf := new(bytes.Buffer)
197 | for _, key := range bytes.Split(sort, sortSep) {
198 | if key[0] == '-' {
199 | buf.Write(key[1:])
200 | buf.WriteString(" desc,")
201 | } else {
202 | buf.Write(key)
203 | buf.WriteByte(',')
204 | }
205 | }
206 | if buf.Len() != 0 {
207 | scope = scope.Order(buf.String()[:buf.Len()-1])
208 | }
209 | return scope
210 | }
211 | }
212 |
--------------------------------------------------------------------------------
/gormx/gormx_test.go:
--------------------------------------------------------------------------------
1 | package gormx
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "testing"
7 | "time"
8 |
9 | "github.com/stretchr/testify/assert"
10 | "github.com/valyala/fasthttp"
11 | "gorm.io/driver/sqlite"
12 | "gorm.io/gorm"
13 | "gorm.io/gorm/logger"
14 | )
15 |
16 | func Test_Gormx_Paginate(t *testing.T) {
17 | at := assert.New(t)
18 |
19 | gdb := mockGdb(t)
20 |
21 | args := fasthttp.AcquireArgs()
22 |
23 | type Data struct {
24 | ID uint32 `json:"id"`
25 | CreatedAt time.Time `json:"created_at"`
26 | UpdatedAt time.Time `json:"updated_at"`
27 | F string `json:"f"`
28 | }
29 |
30 | args.Set("page", "1")
31 | p, err := Paginate(gdb, args, &[]Fake{}, &[]Data{})
32 | at.NotNil(err)
33 |
34 | at.Nil(gdb.AutoMigrate(&Fake{}))
35 | fakers := []Fake{{F: "f0"}, {F: "f1"}, {F: "f2"}}
36 | at.Nil(gdb.Create(&fakers).Error)
37 |
38 | args.Reset()
39 | args.Set("pageSize", "2")
40 | p, err = Paginate(gdb, args, &[]Fake{}, &[]Data{})
41 |
42 | at.Nil(err)
43 | at.Equal(1, p.Page)
44 | at.Equal(2, p.PageSize)
45 | at.Equal(3, p.Total)
46 |
47 | list, ok := p.Data.([]Data)
48 | at.Len(list, 2)
49 | at.True(ok)
50 | at.Equal("f0", list[0].F)
51 | at.Equal("f1", list[1].F)
52 | }
53 |
54 | func Test_Scope_Paginate(t *testing.T) {
55 | gdb := dryRunSession(t)
56 |
57 | t.Run("int", func(t *testing.T) {
58 | stat := gdb.Scopes(scopePaginate(1, 10)).Find(&Fake{}).Statement
59 |
60 | assert.Equal(t, "SELECT * FROM `fakes` WHERE `fakes`.`deleted_at` IS NULL LIMIT 10", stat.SQL.String())
61 | })
62 |
63 | t.Run("offset", func(t *testing.T) {
64 | stat := gdb.Scopes(scopePaginate(2, 10)).Find(&Fake{}).Statement
65 |
66 | assert.Equal(t, "SELECT * FROM `fakes` WHERE `fakes`.`deleted_at` IS NULL LIMIT 10 OFFSET 10", stat.SQL.String())
67 | })
68 | }
69 |
70 | func Test_Scope_Search(t *testing.T) {
71 | gdb := dryRunSession(t)
72 |
73 | t.Run("empty object", func(t *testing.T) {
74 | stat := gdb.Scopes(scopeSearch([]byte(`{}`))).Find(&Fake{}).Statement
75 |
76 | assert.Equal(t, "SELECT * FROM `fakes` WHERE `fakes`.`deleted_at` IS NULL", stat.SQL.String())
77 | })
78 |
79 | t.Run("number", func(t *testing.T) {
80 | stat := gdb.Scopes(scopeSearch([]byte(`{"id":1}`))).Find(&Fake{}).Statement
81 |
82 | assert.Equal(t, "SELECT * FROM `fakes` WHERE id = ? AND `fakes`.`deleted_at` IS NULL", stat.SQL.String())
83 | })
84 |
85 | t.Run("like", func(t *testing.T) {
86 | stat := gdb.Scopes(scopeSearch([]byte(`{"name":"k"}`))).Find(&Fake{}).Statement
87 |
88 | assert.Equal(t, "SELECT * FROM `fakes` WHERE name LIKE ? AND `fakes`.`deleted_at` IS NULL", stat.SQL.String())
89 | assert.Equal(t, []interface{}{"%k%"}, stat.Vars)
90 | })
91 |
92 | t.Run("in", func(t *testing.T) {
93 | t.Run("number", func(t *testing.T) {
94 | stat := gdb.Scopes(scopeSearch([]byte(`{"name":[1.1,2.2,3.3]}`))).
95 | Find(&Fake{}).Statement
96 |
97 | assert.Equal(t, "SELECT * FROM `fakes` WHERE name IN (?,?,?) AND `fakes`.`deleted_at` IS NULL", stat.SQL.String())
98 | assert.Equal(t, []interface{}{1.1, 2.2, 3.3}, stat.Vars)
99 | })
100 | t.Run("string", func(t *testing.T) {
101 | stat := gdb.Scopes(scopeSearch([]byte(`{"name":["1","2","3"]}`))).
102 | Find(&Fake{}).Statement
103 | assert.Equal(t, "SELECT * FROM `fakes` WHERE name IN (?,?,?) AND `fakes`.`deleted_at` IS NULL", stat.SQL.String())
104 | assert.Equal(t, []interface{}{"1", "2", "3"}, stat.Vars)
105 | })
106 | })
107 |
108 | t.Run("operator", func(t *testing.T) {
109 | for _, opt := range []string{"<", "<=", ">", ">="} {
110 | for _, val := range []interface{}{1.1, "3"} {
111 | name := fmt.Sprintf("%s %v", opt, val)
112 | t.Run(name, func(t *testing.T) {
113 | var search string
114 | search = fmt.Sprintf(`{"c$%s":%v}`, opt, val)
115 | if val == "3" {
116 | search = fmt.Sprintf(`{"c$%s":"%s"}`, opt, val)
117 | }
118 | stat := gdb.Scopes(scopeSearch([]byte(search))).Find(&Fake{}).Statement
119 |
120 | exp := fmt.Sprintf("SELECT * FROM `fakes` WHERE c %s ? AND `fakes`.`deleted_at` IS NULL", opt)
121 | assert.Equal(t, exp, stat.SQL.String())
122 | assert.Equal(t, []interface{}{val}, stat.Vars)
123 | })
124 | }
125 | }
126 |
127 | t.Run("><", func(t *testing.T) {
128 | stat := gdb.Scopes(scopeSearch([]byte(`{"c$><":["2020-01-01", "2020-03-01"]}`))).Find(&Fake{}).Statement
129 |
130 | assert.Equal(t, "SELECT * FROM `fakes` WHERE (c BETWEEN ? AND ?) AND `fakes`.`deleted_at` IS NULL", stat.SQL.String())
131 | assert.Equal(t, []interface{}{"2020-01-01", "2020-03-01"}, stat.Vars)
132 | })
133 |
134 | t.Run("bool", func(t *testing.T) {
135 | stat := gdb.Scopes(scopeSearch([]byte(`{"ok":true}`))).Find(&Fake{}).Statement
136 |
137 | assert.Equal(t, "SELECT * FROM `fakes` WHERE ok = ? AND `fakes`.`deleted_at` IS NULL", stat.SQL.String())
138 | assert.Equal(t, []interface{}{true}, stat.Vars)
139 | })
140 | })
141 | }
142 |
143 | func Benchmark_Scope_Search(b *testing.B) {
144 | gdb := dryRunSession(b)
145 |
146 | b.ReportAllocs()
147 | b.ResetTimer()
148 |
149 | for i := 0; i < b.N; i++ {
150 | gdb.Session(&gorm.Session{DryRun: false}).
151 | Scopes(scopeSearch([]byte(`{"ID":1,"created_at$><":["2020-01-01", "2020-03-01"]}`))).
152 | Find(&Fake{})
153 | }
154 | }
155 |
156 | func Test_Scope_Sort(t *testing.T) {
157 | gdb := dryRunSession(t)
158 |
159 | t.Run("no sort", func(t *testing.T) {
160 | stat := gdb.Scopes(scopeSort([]byte(""))).Find(&Fake{}).Statement
161 |
162 | assert.Equal(t, "SELECT * FROM `fakes` WHERE `fakes`.`deleted_at` IS NULL", stat.SQL.String())
163 | })
164 |
165 | t.Run("asc sort", func(t *testing.T) {
166 | stat := gdb.Scopes(scopeSort([]byte("name"))).Find(&Fake{}).Statement
167 |
168 | assert.Equal(t, "SELECT * FROM `fakes` WHERE `fakes`.`deleted_at` IS NULL ORDER BY name", stat.SQL.String())
169 | })
170 |
171 | t.Run("desc sort", func(t *testing.T) {
172 | stat := gdb.Scopes(scopeSort([]byte("-name"))).Find(&Fake{}).Statement
173 |
174 | assert.Equal(t, "SELECT * FROM `fakes` WHERE `fakes`.`deleted_at` IS NULL ORDER BY name desc", stat.SQL.String())
175 | })
176 |
177 | t.Run("two column sort", func(t *testing.T) {
178 | stat := gdb.Scopes(scopeSort([]byte("-name,key"))).Find(&Fake{}).Statement
179 |
180 | assert.Equal(t, "SELECT * FROM `fakes` WHERE `fakes`.`deleted_at` IS NULL ORDER BY name desc,key", stat.SQL.String())
181 | })
182 | }
183 |
184 | type Fake struct {
185 | Dao
186 | F string
187 | }
188 |
189 | func mockGdb(t assert.TestingT, dst ...interface{}) *gorm.DB {
190 | db, err := gorm.Open(
191 | sqlite.Open(fmt.Sprintf("file:%d?mode=memory&cache=shared&_fk=1", time.Now().UnixNano())),
192 | &gorm.Config{Logger: disabledLogger{}})
193 |
194 | assert.Nil(t, err)
195 |
196 | if len(dst) > 0 {
197 | assert.Nil(t, db.AutoMigrate(dst...))
198 | }
199 |
200 | return db
201 | }
202 |
203 | func dryRunSession(t assert.TestingT) *gorm.DB {
204 | return mockGdb(t).Session(&gorm.Session{DryRun: true, Logger: disabledLogger{}})
205 | }
206 |
207 | type disabledLogger struct{}
208 |
209 | func (disabledLogger) LogMode(logger.LogLevel) logger.Interface {
210 | return disabledLogger{}
211 | }
212 | func (disabledLogger) Info(context.Context, string, ...interface{}) {}
213 | func (disabledLogger) Warn(context.Context, string, ...interface{}) {}
214 | func (disabledLogger) Error(context.Context, string, ...interface{}) {}
215 | func (disabledLogger) Trace(context.Context, time.Time, func() (string, int64), error) {}
216 |
--------------------------------------------------------------------------------
/log/log.go:
--------------------------------------------------------------------------------
1 | package log
2 |
3 | import (
4 | "flag"
5 | "io"
6 |
7 | "github.com/go-dawn/dawn"
8 | "github.com/kiyonlin/klog"
9 | )
10 |
11 | type Module struct {
12 | dawn.Module
13 | flagset *flag.FlagSet
14 | }
15 |
16 | func New(flagset *flag.FlagSet) *Module {
17 | return &Module{flagset: flagset}
18 | }
19 |
20 | func (m *Module) String() string {
21 | return "dawn:log"
22 | }
23 |
24 | func (m *Module) Init() dawn.Cleanup {
25 | InitFlags(m.flagset)
26 |
27 | flag.Parse()
28 |
29 | return func() {
30 | Flush()
31 | }
32 | }
33 |
34 | // InitFlags is for explicitly initializing the flags.
35 | // Default to use logs as log dir.
36 | func InitFlags(flagset *flag.FlagSet) {
37 | klog.InitFlags(flagset)
38 | }
39 |
40 | // SetOutput sets the output destination for all severities
41 | func SetOutput(w io.Writer) {
42 | klog.SetOutput(w)
43 | }
44 |
45 | // Flush flushes all pending log I/O.
46 | func Flush() {
47 | klog.Flush()
48 | }
49 |
50 | // Warningln logs to the WARNING and INFO logs.
51 | // Arguments are handled in the manner of fmt.Println; a newline is always appended.
52 | func Warningln(args ...interface{}) {
53 | klog.WarningDepth(1, args...)
54 | }
55 |
56 | // Warningf logs to the WARNING and INFO logs.
57 | // Arguments are handled in the manner of fmt.Printf; a newline is appended if missing.
58 | func Warningf(format string, args ...interface{}) {
59 | klog.WarningDepthf(1, format, args...)
60 | }
61 |
62 | // Errorln logs to the ERROR, WARNING, and INFO logs.
63 | // Arguments are handled in the manner of fmt.Println; a newline is always appended.
64 | func Errorln(args ...interface{}) {
65 | klog.ErrorDepth(1, args...)
66 | }
67 |
68 | // Errorf logs to the ERROR, WARNING, and INFO logs.
69 | // Arguments are handled in the manner of fmt.Printf; a newline is appended if missing.
70 | func Errorf(format string, args ...interface{}) {
71 | klog.ErrorDepthf(1, format, args...)
72 | }
73 |
74 | // Infoln is equivalent to the global Infoln function, guarded by the value of v.
75 | func Infoln(level int, args ...interface{}) {
76 | l := klog.Level(level)
77 | if klog.V(l).Enabled() {
78 | klog.V(l).InfoDepth(1, args...)
79 | }
80 | }
81 |
82 | // Infof is equivalent to the global Infof function, guarded by the value of v.
83 | func Infof(level int, format string, args ...interface{}) {
84 | l := klog.Level(level)
85 | if klog.V(l).Enabled() {
86 | klog.V(l).InfoDepthf(1, format, args...)
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/log/log_test.go:
--------------------------------------------------------------------------------
1 | package log
2 |
3 | import (
4 | "bytes"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func Test_All(t *testing.T) {
11 | SetOutput(new(bytes.Buffer))
12 | Errorln("errorln")
13 | Errorf("%s", "errorf")
14 | Warningln("warningln")
15 | Warningf("%s", "warningf")
16 | Infoln(0, "infoln level 0")
17 | Infof(0, "%s", "infof level 0")
18 | Infof(1, "%s", "infof level 1")
19 | }
20 |
21 | func Test_Moduler(t *testing.T) {
22 | m := New(nil)
23 |
24 | assert.Equal(t, "dawn:log", m.String())
25 |
26 | m.Init()()
27 | }
28 |
--------------------------------------------------------------------------------
/moduler.go:
--------------------------------------------------------------------------------
1 | package dawn
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/gofiber/fiber/v2"
7 | )
8 |
9 | // Cleanup is a function does cleanup works
10 | type Cleanup func()
11 |
12 | // Moduler is the interface that wraps the module's method.
13 | type Moduler interface {
14 | // Stringer indicates module's name
15 | fmt.Stringer
16 |
17 | // Init does initialization works and should return
18 | // a cleanup function.
19 | Init() Cleanup
20 |
21 | // Boot boots the module.
22 | Boot()
23 |
24 | // RegisterRoutes add routes to fiber router
25 | RegisterRoutes(fiber.Router)
26 | }
27 |
28 | // Module is an empty struct implements Moduler interface
29 | // and can be embedded into custom struct as a Moduler
30 | type Module struct{}
31 |
32 | // String indicates module's name
33 | func (Module) String() string { return "anonymous" }
34 |
35 | // Init does initialization works and should return a cleanup function.
36 | func (Module) Init() Cleanup { return func() {} }
37 |
38 | // Boot boots the module.
39 | func (Module) Boot() {}
40 |
41 | // RegisterRoutes add routes to fiber router
42 | func (Module) RegisterRoutes(fiber.Router) {}
43 |
--------------------------------------------------------------------------------
/moduler_test.go:
--------------------------------------------------------------------------------
1 | package dawn
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | type mockModule struct {
10 | Module
11 | }
12 |
13 | // go test -run Test_Moduler_Embed_Empty_Module -race
14 | func Test_Moduler_Embed_Empty_Module(t *testing.T) {
15 | t.Parallel()
16 |
17 | module := mockModule{Module{}}
18 |
19 | assert.Implements(t, (*Moduler)(nil), module)
20 |
21 | assert.Equal(t, "anonymous", module.String())
22 |
23 | assert.NotNil(t, module.Init())
24 |
25 | module.Boot()
26 |
27 | module.RegisterRoutes(nil)
28 | }
29 |
--------------------------------------------------------------------------------
/sloop.go:
--------------------------------------------------------------------------------
1 | package dawn
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "os"
7 | "os/signal"
8 | "syscall"
9 |
10 | "github.com/go-dawn/dawn/config"
11 | "github.com/go-dawn/dawn/fiberx"
12 | "github.com/gofiber/fiber/v2"
13 | "github.com/gofiber/fiber/v2/middleware/pprof"
14 | "github.com/gofiber/fiber/v2/middleware/recover"
15 | "github.com/gofiber/fiber/v2/middleware/requestid"
16 | )
17 |
18 | // Version of current dawn package
19 | const Version = "0.4.0"
20 |
21 | // Config is a struct holding the sloop settings.
22 | type Config struct {
23 | // App indicates to fiber app instance
24 | App *fiber.App
25 | }
26 |
27 | // Sloop denotes Dawn application
28 | type Sloop struct {
29 | // Config is the embedded config
30 | Config
31 |
32 | app *fiber.App
33 | mods []Moduler
34 | cleanups []Cleanup
35 | sigCh chan os.Signal
36 | }
37 |
38 | // New returns a new Sloop with options.
39 | func New(config ...Config) *Sloop {
40 | s := &Sloop{
41 | sigCh: make(chan os.Signal, 1),
42 | }
43 |
44 | if len(config) > 0 {
45 | s.Config = config[0]
46 | }
47 |
48 | s.app = s.Config.App
49 |
50 | return s
51 | }
52 |
53 | // Default returns an Sloop instance with the
54 | // `RequestID`, `Logger`, `Recovery`, `Pprof`
55 | // middleware already attached in default fiber app.
56 | func Default(cfg ...fiber.Config) *Sloop {
57 | c := fiber.Config{}
58 | if len(cfg) > 0 {
59 | c = cfg[0]
60 | }
61 | if c.ErrorHandler == nil {
62 | c.ErrorHandler = fiberx.ErrHandler
63 | }
64 | app := fiber.New(c)
65 | app.Use(
66 | requestid.New(),
67 | fiberx.Logger(),
68 | recover.New(),
69 | )
70 |
71 | if config.GetBool("debug") {
72 | app.Use(pprof.New())
73 | }
74 |
75 | return &Sloop{
76 | app: app,
77 | sigCh: make(chan os.Signal, 1),
78 | }
79 | }
80 |
81 | // AddModulers appends more Modulers
82 | func (s *Sloop) AddModulers(m ...Moduler) *Sloop {
83 | s.mods = append(s.mods, m...)
84 |
85 | return s
86 | }
87 |
88 | // Run runs a web server
89 | func (s *Sloop) Run(addr string) error {
90 | defer s.Cleanup()
91 | if s.app == nil {
92 | return errors.New("dawn: app is nil")
93 | }
94 |
95 | s.Setup().registerRoutes()
96 |
97 | return s.app.Listen(addr)
98 | }
99 |
100 | // RunTls runs a tls web server
101 | func (s *Sloop) RunTls(addr, certFile, keyFile string) error {
102 | defer s.Cleanup()
103 |
104 | if s.app == nil {
105 | return errors.New("dawn: app is nil")
106 | }
107 |
108 | s.Setup().registerRoutes()
109 |
110 | return s.app.ListenTLS(addr, certFile, keyFile)
111 | }
112 |
113 | // Shutdown gracefully shuts down the server without interrupting any active connections.
114 | func (s *Sloop) Shutdown() error {
115 | if s.app == nil {
116 | return fmt.Errorf("shutdown: fiber app is not found")
117 | }
118 | return s.app.Shutdown()
119 | }
120 |
121 | // Router returns the server router
122 | func (s *Sloop) Router() fiber.Router {
123 | return s.app
124 | }
125 |
126 | // Setup initializes all modules and then boots them
127 | func (s *Sloop) Setup() *Sloop {
128 | return s.init().boot()
129 | }
130 |
131 | func (s *Sloop) init() *Sloop {
132 | for _, mod := range s.mods {
133 | if cleanup := mod.Init(); cleanup != nil {
134 | s.cleanups = append(s.cleanups, cleanup)
135 | }
136 | }
137 | return s
138 | }
139 |
140 | func (s *Sloop) boot() *Sloop {
141 | for _, mod := range s.mods {
142 | mod.Boot()
143 | }
144 |
145 | return s
146 | }
147 |
148 | func (s *Sloop) registerRoutes() *Sloop {
149 | for _, mod := range s.mods {
150 | mod.RegisterRoutes(s.app)
151 | }
152 | return s
153 | }
154 |
155 | // Cleanup releases resources
156 | func (s *Sloop) Cleanup() {
157 | for _, fn := range s.cleanups {
158 | fn()
159 | }
160 | }
161 |
162 | // Watch listens to signals and waits to exit
163 | func (s *Sloop) Watch() {
164 | signal.Notify(s.sigCh,
165 | syscall.SIGTERM, syscall.SIGINT,
166 | syscall.SIGHUP, syscall.SIGQUIT)
167 |
168 | <-s.sigCh
169 | }
170 |
--------------------------------------------------------------------------------
/sloop_test.go:
--------------------------------------------------------------------------------
1 | package dawn
2 |
3 | import (
4 | "os"
5 | "testing"
6 | "time"
7 |
8 | "github.com/go-dawn/dawn/config"
9 | "github.com/gofiber/fiber/v2"
10 | "github.com/stretchr/testify/assert"
11 | "github.com/stretchr/testify/require"
12 | )
13 |
14 | var (
15 | m = mockModule{}
16 | )
17 |
18 | func Test_Sloop_New(t *testing.T) {
19 | t.Parallel()
20 |
21 | app := fiber.New()
22 |
23 | s := New(Config{App: app}).AddModulers(m)
24 |
25 | assert.Equal(t, app, s.app)
26 | assert.Len(t, s.mods, 1)
27 | assert.Equal(t, "anonymous", s.mods[0].String())
28 | }
29 |
30 | func Test_Sloop_Default(t *testing.T) {
31 | t.Parallel()
32 |
33 | config.Set("debug", true)
34 | s := Default(fiber.Config{})
35 |
36 | require.NotNil(t, s.app)
37 | assert.Len(t, s.app.Stack()[0], 1)
38 | }
39 |
40 | func Test_Sloop_AddModulers(t *testing.T) {
41 | t.Parallel()
42 |
43 | s := New().AddModulers(m)
44 |
45 | assert.Len(t, s.mods, 1)
46 | assert.Equal(t, "anonymous", s.mods[0].String())
47 | }
48 |
49 | func Test_Sloop_Run(t *testing.T) {
50 | t.Parallel()
51 |
52 | assert.NotNil(t, New().Run(""))
53 |
54 | s := New(Config{App: fiber.New()}).AddModulers(m)
55 |
56 | go func() {
57 | time.Sleep(time.Millisecond * 100)
58 | assert.NoError(t, s.app.Shutdown())
59 | }()
60 |
61 | assert.NoError(t, s.Run(""))
62 | }
63 |
64 | func Test_Sloop_RunTls(t *testing.T) {
65 | assert.NotNil(t, New().RunTls("", "", ""))
66 |
67 | s := New(Config{App: fiber.New()})
68 |
69 | t.Run("invalid addr", func(t *testing.T) {
70 | t.Parallel()
71 |
72 | assert.NotNil(t, s.RunTls(":99999", "./.github/testdata/ssl.pem", "./.github/testdata/ssl.key"))
73 | })
74 |
75 | t.Run("invalid ssl info", func(t *testing.T) {
76 | t.Parallel()
77 |
78 | assert.NotNil(t, s.RunTls("", "./.github/README.md", "./.github/README.md"))
79 | })
80 |
81 | t.Run("with ssl", func(t *testing.T) {
82 | t.Parallel()
83 |
84 | go func() {
85 | time.Sleep(time.Millisecond * 100)
86 | assert.NoError(t, s.app.Shutdown())
87 | }()
88 |
89 | assert.NoError(t, s.RunTls("", "./.github/testdata/ssl.pem", "./.github/testdata/ssl.key"))
90 | })
91 | }
92 |
93 | func Test_Sloop_Shutdown(t *testing.T) {
94 | t.Parallel()
95 |
96 | require.NotNil(t, (&Sloop{}).Shutdown())
97 | require.Nil(t, New(Config{App: fiber.New()}).Shutdown())
98 | }
99 |
100 | func Test_Sloop_Router(t *testing.T) {
101 | t.Parallel()
102 |
103 | require.Nil(t, (&Sloop{}).Router())
104 | require.NotNil(t, New(Config{App: fiber.New()}).Router())
105 | }
106 |
107 | func Test_Sloop_Watch(t *testing.T) {
108 | t.Parallel()
109 |
110 | s := &Sloop{
111 | sigCh: make(chan os.Signal, 1),
112 | }
113 |
114 | go s.Watch()
115 |
116 | select {
117 | case s.sigCh <- os.Interrupt:
118 | case <-time.NewTimer(time.Second).C:
119 | assert.Fail(t, "should receive signal")
120 | }
121 | }
122 |
--------------------------------------------------------------------------------