diff --git a/.dockerignore b/.dockerignore index 65959251..e562725f 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,21 +1,26 @@ .editorconfig .git +.gitignore .github sonar-project.properties -AUTHORS.md -CONTRIBUTING.md +*.md LICENSE Makefile NOTICE -README.md arm/ powerpc/ mips/ .golangci.yml -_temp .vscode -node1 -node2 -node3 -.gitignore -changelog.md +go.work +go.work.sum +tools/ +test_integration/ +test_e2e/ +!test_e2e/test_api.go +!test_e2e/go.mod +!test_e2e/go.sum +mocks/ +docker/ +**/*_test.go +docs/ \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 31914924..29cbd6fd 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -2,33 +2,38 @@ name: Bug report about: Create a report to help us improve title: '' -labels: Bug +labels: bug --- -**Describe the bug** +## Describe the bug A clear and concise description of what the bug is. -**To Reproduce** +## To Reproduce Steps to reproduce the behavior: + 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error -**Expected behavior** +## Expected behavior A clear and concise description of what you expected to happen. -**Screenshots** +## Screenshots If applicable, add screenshots to help explain your problem. -**Application (please complete the following information):** +## Application + +Please complete the following information: - badaas version [X.X.X] or commit hash +- go version +- database vendor and version (in case of bugs related with BaDORM) -**Additional context** +## Additional context Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/discussion.md b/.github/ISSUE_TEMPLATE/discussion.md new file mode 100644 index 00000000..9835b3a0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/discussion.md @@ -0,0 +1,7 @@ +--- +name: Discussion +about: Start a discussion for BaDaaS +title: '' +labels: question +--- + \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/user_story.md b/.github/ISSUE_TEMPLATE/user_story.md index ec61755b..f9a6617a 100644 --- a/.github/ISSUE_TEMPLATE/user_story.md +++ b/.github/ISSUE_TEMPLATE/user_story.md @@ -2,7 +2,7 @@ name: Feature request about: Suggest an idea for this project title: '' -labels: User Story, To be verify +labels: enhancement --- ## Description @@ -26,7 +26,7 @@ labels: User Story, To be verify `[Put all others constraints here, like list of acceptances values or other]` -## Resources: +## Resources `[Put all your resources here, like mockups, diagrams or other here]` @@ -37,4 +37,3 @@ labels: User Story, To be verify ## Links `[Only use by the team, to link this feature with epic, technical tasks or bugs]` - diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index c2d7558b..2c29b492 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,5 +1,6 @@ :information_source: Don't forget to modify the changelog.md before merging this branch. :information_source: Don't forget to modify config files: -- `badaas.example.yml`: the example file. -- `/scripts/e2e/api/ci-conf.yml`: otherwise you will probably break the CI. (*For local testing please use [act](https://github.com/nektos/act)*). \ No newline at end of file + +- `badaas.example.yml`: the example file. +- `tools/badctl/cmd/config/badaas.yml`: the default config file generated by badctl. diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index e382df70..04b8c9d4 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -5,6 +5,7 @@ on: - main pull_request: types: [opened, synchronize, reopened] + jobs: branch-naming-rules: name: Check branch name @@ -18,6 +19,41 @@ jobs: min_length: 5 max_length: 50 + check-style: + name: Code style + needs: [branch-naming-rules] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - uses: actions/setup-go@v3 + with: + go-version: '^1.18' + cache: true + - name: badaas lint + uses: golangci/golangci-lint-action@v3 + with: + skip-cache: true + skip-pkg-cache: true + skip-build-cache: true + - name: teste2e lint + uses: golangci/golangci-lint-action@v3 + with: + working-directory: test_e2e + args: --config=../.golangci.yml + skip-cache: true + skip-pkg-cache: true + skip-build-cache: true + - name: badctl lint + uses: golangci/golangci-lint-action@v3 + with: + working-directory: tools/badctl + args: --config=../../.golangci.yml + skip-cache: true + skip-pkg-cache: true + skip-build-cache: true + unit-tests: name: Unit tests needs: [branch-naming-rules] @@ -30,17 +66,35 @@ jobs: with: go-version: '^1.18' cache: true - - name: Run test - run: go test $(go list ./... | sed 1d) -coverprofile=coverage.out -v + - name: Install gotestsum + run: go install gotest.tools/gotestsum@latest + - name: Run BaDaaS tests + run: gotestsum --junitfile unit-tests-badaas.xml $(go list ./... | tail -n +2 | grep -v testintegration) -coverpkg=./... -coverprofile=coverage_unit.out - uses: actions/upload-artifact@v3 with: name: coverage - path: coverage.out + path: coverage_unit.out + - name: Run BaDctl tests + run: gotestsum --junitfile unit-tests-badctl.xml ./tools/badctl/... -coverprofile=coverage_badctl.out + - uses: actions/upload-artifact@v3 + with: + name: coverage + path: coverage_badctl.out + - name: Test Report + uses: dorny/test-reporter@v1 + if: always() # run this step even if previous steps failed + with: + name: Unit Tests Report # Name of the check run which will be created + path: unit-tests-*.xml # Path to test results + reporter: java-junit # Format of test results - check-style: - name: Code style - needs: [branch-naming-rules] + integration-tests: + name: Integration tests + needs: [unit-tests] runs-on: ubuntu-latest + strategy: + matrix: + db: [postgresql, cockroachdb, mysql, sqlite, sqlserver] steps: - uses: actions/checkout@v3 with: @@ -49,18 +103,54 @@ jobs: with: go-version: '^1.18' cache: true - - - name: golangci-lint - uses: golangci/golangci-lint-action@v3 + - name: Install gotestsum + run: go install gotest.tools/gotestsum@latest + - name: Start containers + run: docker compose -f "docker/${{ matrix.db }}/docker-compose.yml" up -d + if: ${{ matrix.db != 'sqlite' }} + - uses: kanga333/variable-mapper@master + id: export with: - version: latest - skip-cache: true - skip-pkg-cache: true - skip-build-cache: true + key: ${{ matrix.db }} + map: | + { + "postgresql": { + "dialector": "postgresql" + }, + "cockroachdb": { + "dialector": "postgresql" + }, + "mysql": { + "dialector": "mysql" + }, + "sqlite": { + "dialector": "sqlite" + }, + "sqlserver": { + "dialector": "sqlserver" + } + } + export_to: env + - name: Run test + run: DB=${{ env.dialector }} gotestsum --junitfile integration-tests-${{ matrix.db }}.xml ./testintegration -tags=${{ matrix.db }} -coverpkg=./... -coverprofile=coverage_int_${{ matrix.db }}.out + - name: Test Report + uses: dorny/test-reporter@v1 + if: always() # run this step even if previous steps failed + with: + name: ${{ matrix.db }} Integration Tests Report # Name of the check run which will be created + path: integration-tests-${{ matrix.db }}.xml # Path to test results + reporter: java-junit # Format of test results + - uses: actions/upload-artifact@v3 + with: + name: coverage + path: coverage_int_${{ matrix.db }}.out + - name: Stop containers + if: ${{ matrix.db != 'sqlite' }} + run: docker stop badaas-test-db e2e-tests: name: E2E Tests - needs: [unit-tests, check-style] + needs: [integration-tests] runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -71,41 +161,39 @@ jobs: go-version: '^1.18' cache: true - name: Start containers - run: docker compose -f "scripts/e2e/docker-compose.yml" up -d --build + run: docker compose -f "docker/cockroachdb/docker-compose.yml" -f "docker/test_api/docker-compose.yml" up -d --build - name: Wait for API server to be up uses: mydea/action-wait-for-api@v1 with: url: "http://localhost:8000/info" - timeout: 20 + timeout: 60 - name: Run test - run: go test -v + run: go test ./test_e2e -v - name: Get logs if: always() - run: docker compose -f "scripts/e2e/docker-compose.yml" logs --no-color 2>&1 | tee app.log & + run: docker compose -f "docker/cockroachdb/docker-compose.yml" -f "docker/test_api/docker-compose.yml" logs --no-color 2>&1 | tee app.log & - name: Stop containers if: always() - run: docker compose -f "scripts/e2e/docker-compose.yml" down + run: docker compose -f "docker/cockroachdb/docker-compose.yml" -f "docker/test_api/docker-compose.yml" down - uses: actions/upload-artifact@v3 with: name: docker-compose-e2e-logs path: app.log - + sonarcloud: name: SonarCloud - needs: [unit-tests, check-style] + needs: [check-style, integration-tests] runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 with: fetch-depth: 0 - - name: Download line coverage report + - name: Download line coverage reports uses: actions/download-artifact@v3 with: name: coverage - path: coverage.out - name: SonarCloud Scan uses: sonarsource/sonarcloud-github-action@master env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index d477eb47..7452fac1 100644 --- a/.gitignore +++ b/.gitignore @@ -13,21 +13,33 @@ # Output of the go coverage tool, specifically when used with LiteIDE *.out +*-tests-*.xml # Dependency directories (remove the comment below to include it) -# vendor/ +vendor/ # Go workspace file -go.work +# go.work # cockroach files node* -#Vscode conf +# sqlite files +testintegration/sqlite:* + +# vscode conf .vscode # binary output badaas +!docs/badaas/ +badctl +!tools/badctl/ +!docs/badctl/ + +# test results +tools/badctl/cmd/gen/conditions/*_conditions.go +tools/badctl/cmd/gen/conditions/tests/**/badorm.go -# temporary directories -_temp \ No newline at end of file +# Sphinx documentation +docs/_build/ \ No newline at end of file diff --git a/.golangci.yml b/.golangci.yml index d4705090..7ab996a9 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,242 +1,210 @@ -# WARNING: DO NOT EDIT, THIS FILE IS PROBABLY A COPY -# -# The original version of this file is located in the https://github.com/istio/common-files repo. -# If you're looking at this file in a different repo and want to make a change, please go to the -# common-files repo, make the change there and check it in. Then come back to this repo and run -# "make update-common". - -output: - # Format: colored-line-number|line-number|json|tab|checkstyle|code-climate|junit-xml|github-actions - # - # Multiple can be specified by separating them by comma, output can be provided - # for each of them by separating format name and path by colon symbol. - # Output path can be either `stdout`, `stderr` or path to the file to write to. - # Example: "checkstyle:report.json,colored-line-number" - # - # Default: colored-line-number - format: github-actions - # Print lines of code with issue. - # Default: true - print-issued-lines: false - # Print linter name in the end of issue text. - # Default: true - print-linter-name: false - # Make issues output unique by line. - # Default: true - uniq-by-line: false - # Add a prefix to the output file references. - # Default is no prefix. - path-prefix: "" - # Sort results by: filepath, line and column. - sort-results: false +# based on +# - https://github.com/istio/common-files/blob/master/files/common/config/.golangci.yml +# - https://gist.github.com/maratori/47a4d00457a92aa426dbd48a18776322 run: - tests: false # timeout for analysis, e.g. 30s, 5m, default is 1m - deadline: 20m - build-tags: - - integ - - integfuzz + deadline: 3m # which dirs to skip: they won't be analyzed; # can use regexp here: generated.*, regexp is applied on full path; # default value is empty list, but next dirs are always skipped independently # from this option's value: - #vendor$, third_party$, testdata$, examples$, Godeps$, builtin$ + # vendor$, third_party$, testdata$, examples$, Godeps$, builtin$ skip-dirs: - - genfiles$ - - vendor$ - + - mocks$ # which files to skip: they will be analyzed, but issues from them # won't be reported. Default value is empty list, but there is # no need to include all autogenerated files, we confidently recognize - # autogenerated files. If it's not please let us know. - skip-files: - - ".*\\.pb\\.go" - - ".*\\.gen\\.go" + # autogenerated files. + # skip-files: + # - ".*\\.pb\\.go" + # - ".*\\.gen\\.go" linters: - disable-all: true - enable: - - deadcode - - exportloopref - - gocritic - - revive - - gosimple - - govet - - ineffassign - - lll - - misspell - - staticcheck - - structcheck - - stylecheck - - typecheck - - unconvert - - unparam - - varcheck + enable-all: true + disable: + - dogsled # [sometimes necessary] checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) + - exhaustruct # [make it easier to construct structs] checks if all structure fields are initialized + - gochecknoglobals # [we use them] checks that no global variables exist + - gochecknoinits # [we use them] checks that no init functions are present in Go code + - godot # [not necessary] checks if comments end in a period + - godox # [we use them] detects FIXME, TODO and other comment keywords + - ireturn # [useful for mocks generation] accept interfaces, return concrete types + - nlreturn # [too strict and mostly code is not more readable] checks for a new line before return and branch statements to increase code clarity + - nonamedreturns # [are util sometimes] reports all named returns + - paralleltest # [too many false positives] detects missing usage of t.Parallel() method in your Go test + - testpackage # [doesn't allow white box tests] makes you use a separate _test package + - thelper # [not the expected result by us] detects golang test helpers without t.Helper() call and checks the consistency of test helpers + - varnamelen # [great idea, but too many false positives] checks that the length of a variable's name matches its scope + # deprecated + - deadcode # [deprecated, replaced by unused] finds unused code + - exhaustivestruct # [deprecated, replaced by exhaustruct] checks if all struct's fields are initialized + - golint # [deprecated, replaced by revive] golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes + - ifshort # [deprecated] checks that your code uses short syntax for if-statements whenever possible + - interfacer # [deprecated] suggests narrower interface types + - maligned # [deprecated, replaced by govet fieldalignment] detects Go structs that would take less memory if their fields were sorted + - nosnakecase # [deprecated, replaced by revive var-naming] detects snake case of variable naming and function name + - scopelint # [deprecated, replaced by exportloopref] checks for unpinned variables in go programs + - structcheck # [deprecated, replaced by unused] finds unused struct fields + - varcheck # [deprecated, replaced by unused] finds unused global variables and constants + # can be util in the future for better errors + - goerr113 # [too strict] checks the errors handling expressions + - wrapcheck # [too strict] checks that errors returned from external packages are wrapped + # wish activated but not time by now to solve it + - errname # checks that sentinel errors are prefixed with the Err and error types are suffixed with the Error + - errchkjson # checks types passed to the json encoding functions. Reports unsupported types and optionally reports occasions, where the check for the returned error can be omitted fast: false +# All possible options can be found here https://github.com/golangci/golangci-lint/blob/master/.golangci.reference.yml linters-settings: + cyclop: + # The maximal code complexity to report. + # Default: 10 + max-complexity: 20 + # The maximal average package complexity. + # If it's higher than 0.0 (float) the check is enabled + # Default: 0.0 + package-average: 0.0 + depguard: + rules: + main: + deny: + - pkg: "github.com/gogo/protobuf" + desc: gogo/protobuf is deprecated, use golang/protobuf + errcheck: + # report about not checking of errors in type assetions: `a := b.(MyStruct)`; + # default is false: such cases aren't reported by default. + check-type-assertions: true + # report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`; + # default is false: such cases aren't reported by default. + check-blank: false + exhaustive: + # Program elements to check for exhaustiveness. + # Default: [ switch ] + check: + - switch + - map + funlen: + # Checks the number of lines in a function. + # If lower than 0, disable the check. + # Default: 60 + lines: 80 + # Checks the number of statements in a function. + # If lower than 0, disable the check. + # Default: 40 + statements: 40 + gci: + sections: + - standard # Captures all standard packages if they do not match another section. + - default # Contains all imports that could not be matched to another section type. + - prefix(github.com/ditrit/) # Groups all imports with the specified Prefix. + gocritic: + # Settings passed to gocritic. + # The settings key is the name of a supported gocritic checker. + # The list of supported checkers can be find in https://go-critic.github.io/overview. + settings: + captLocal: + # Whether to restrict checker to params only. + # Default: true + paramsOnly: false + underef: + # Whether to skip (*x).method() calls where x is a pointer receiver. + # Default: true + skipRecvDeref: false + goimports: + # put imports beginning with prefix after 3rd-party packages; + # it's a comma-separated list of prefixes + local-prefixes: github.com/ditrit + gomodguard: + blocked: + # List of blocked modules. + # Default: [] + modules: + - github.com/golang/protobuf: + recommendations: + - google.golang.org/protobuf + reason: "see https://developers.google.com/protocol-buffers/docs/reference/go/faq#modules" + - github.com/satori/go.uuid: + recommendations: + - github.com/google/uuid + reason: "satori's package is not maintained" + - github.com/gofrs/uuid: + recommendations: + - github.com/google/uuid + reason: "gofrs' package is not go module" govet: - # report about shadowed variables - check-shadowing: false - maligned: - # print struct with more effective memory layout or not, false by default - suggest-new: true - misspell: - # Correct spellings using locale preferences for US or UK. - # Default is to use a neutral variety of English. - # Setting locale to US will correct the British spelling of 'colour' to 'color'. - locale: US - ignore-words: - - cancelled + # Enable all analyzers. + # Default: false + enable-all: true + # Disable analyzers by name. + # Run `go tool vet help` to see all analyzers. + # Default: [] + disable: + - fieldalignment # too strict + # Settings per analyzer. + settings: + shadow: + # Whether to be strict about shadowing; can be noisy. + # Default: false + strict: false lll: # max line length, lines longer will be reported. Default is 120. # '\t' is counted as 1 character by default, and can be changed with the tab-width option line-length: 160 # tab width in spaces. Default to 1. tab-width: 1 - revive: - ignore-generated-header: false - severity: "warning" - confidence: 0.0 - error-code: 2 - warning-code: 1 - rules: - - name: blank-imports - - name: context-keys-type - - name: time-naming - - name: var-declaration - - name: unexported-return - - name: errorf - - name: context-as-argument - - name: dot-imports - - name: error-return - - name: error-strings - - name: error-naming - - name: increment-decrement - - name: var-naming - - name: package-comments - - name: range - - name: receiver-naming - - name: indent-error-flow - - name: superfluous-else - - name: modifies-parameter - - name: unreachable-code - - name: struct-tag - - name: constant-logical-expr - - name: bool-literal-in-expr - - name: redefines-builtin-id - - name: imports-blacklist - - name: range-val-in-closure - - name: range-val-address - - name: waitgroup-by-value - - name: atomic - - name: call-to-gc - - name: duplicated-imports - - name: string-of-int - - name: defer - arguments: [["call-chain"]] - - name: unconditional-recursion - - name: identical-branches - # the following rules can be enabled in the future - # - name: empty-lines - # - name: confusing-results - # - name: empty-block - # - name: get-return - # - name: confusing-naming - # - name: unexported-naming - - name: early-return - # - name: unused-parameter - # - name: unnecessary-stmt - # - name: deep-exit - # - name: import-shadowing - # - name: modifies-value-receiver - # - name: unused-receiver - # - name: bare-return - # - name: flag-parameter - # - name: unhandled-error - # - name: if-return - unused: - # treat code as a program (not a library) and report unused exported identifiers; default is false. - # XXX: if you enable this setting, unused will report a lot of false-positives in text editors: - # if it's called for subdir of a project it can't find funcs usages. All text editor integrations - # with golangci-lint call it on a directory with the changed file. - check-exported: false + misspell: + # Correct spellings using locale preferences for US or UK. + # Default is to use a neutral variety of English. + # Setting locale to US will correct the British spelling of 'colour' to 'color'. + locale: US + ignore-words: + - cancelled + nolintlint: + # Exclude following linters from requiring an explanation. + # Default: [] + allow-no-explanation: [ funlen, gocognit, lll ] + # Enable to require an explanation of nonzero length after each nolint directive. + # Default: false + require-explanation: true + # Enable to require nolint directives to mention the specific linter being suppressed. + # Default: false + require-specific: true + rowserrcheck: + # database/sql is always checked + # Default: [] + packages: + - github.com/jmoiron/sqlx unparam: # call graph construction algorithm (cha, rta). In general, use cha for libraries, # and rta for programs with main packages. Default is cha. - algo: rta - + algo: cha # Inspect exported functions, default is false. Set to true if no external program/library imports your code. # XXX: if you enable this setting, unparam will report a lot of false-positives in text editors: # if it's called for subdir of a project it can't find external interfaces. All text editor integrations # with golangci-lint call it on a directory with the changed file. check-exported: false - gocritic: - enabled-checks: - - appendCombine - - argOrder - - assignOp - - badCond - - boolExprSimplify - - builtinShadow - - captLocal - - caseOrder - - codegenComment - - commentedOutCode - - commentedOutImport - - defaultCaseOrder - - deprecatedComment - - docStub - - dupArg - - dupBranchBody - - dupCase - - dupSubExpr - - elseif - - emptyFallthrough - - equalFold - - flagDeref - - flagName - - hexLiteral - - indexAlloc - - initClause - - methodExprCall - - nilValReturn - - octalLiteral - - offBy1 - - rangeExprCopy - - regexpMust - - sloppyLen - - stringXbytes - - switchTrue - - typeAssertChain - - typeSwitchVar - - typeUnparen - - underef - - unlambda - - unnecessaryBlock - - unslice - - valSwap - - weakCond - - # Unused - # - yodaStyleExpr - # - appendAssign - # - commentFormatting - # - emptyStringTest - # - exitAfterDefer - # - ifElseChain - # - hugeParam - # - importShadow - # - nestingReduce - # - paramTypeCombine - # - ptrToRefParam - # - rangeValCopy - # - singleCaseSwitch - # - sloppyReassign - # - unlabelStmt - # - unnamedResult - # - wrapperFunc + unused: + # treat code as a program (not a library) and report unused exported identifiers; default is false. + # XXX: if you enable this setting, unused will report a lot of false-positives in text editors: + # if it's called for subdir of a project it can't find funcs usages. All text editor integrations + # with golangci-lint call it on a directory with the changed file. + check-exported: false + tenv: + # The option `all` will run against whole test files (`_test.go`) regardless of method/function signatures. + # Otherwise, only methods that take `*testing.T`, `*testing.B`, and `testing.TB` as arguments are checked. + # Default: false + all: true + gosec: + config: + # Globals are applicable to all rules. + global: + # If true, ignore #nosec in comments (and an alternative as well). + # Default: false + nosec: true + # Audit mode enables addition checks that for normal code analysis might be too nosy. + # Default: false + audit: true issues: # List of regexps of issue texts to exclude, empty list by default. @@ -245,27 +213,30 @@ issues: # excluded by default patterns execute `golangci-lint run --help` exclude: - composite literal uses unkeyed fields - exclude-rules: # Exclude some linters from running on test files. - - path: _test\.go$|^tests/|^samples/ + - path: _test\.go$|^testintegration/|^test_e2e/ linters: - errcheck - - maligned - - # TODO(https://github.com/dominikh/go-tools/issues/732) remove this once we update + - forcetypeassert + - funlen + - goconst + - noctx + - gomnd + - unparam + # We need to use the deprecated module since the jsonpb replacement is not backwards compatible. - linters: - staticcheck - text: "SA1019: package github.com/golang/protobuf" - + text: "SA1019: package github.com/golang/protobuf/jsonpb" + - linters: + - staticcheck + text: 'SA1019: "github.com/golang/protobuf/jsonpb"' # Independently from option `exclude` we use default exclude patterns, # it can be disabled by this option. To list all # excluded by default patterns execute `golangci-lint run --help`. # Default value for this option is true. exclude-use-default: true - # Maximum issues count per one linter. Set to 0 to disable. Default is 50. max-per-linter: 0 - # Maximum count of issues with the same text. Set to 0 to disable. Default is 3. max-same-issues: 0 \ No newline at end of file diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 00000000..7dcd5ee7 --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,25 @@ +# .readthedocs.yaml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +version: 2 + +# Set the version of Python and other tools you might need +build: + os: ubuntu-22.04 + tools: + python: "3.10" + +# Build documentation in the docs/ directory with Sphinx +sphinx: + configuration: docs/conf.py + +# If using Sphinx, optionally build your docs in additional formats such as PDF +formats: + - pdf + - epub + +# Optionally declare the Python requirements required to build your docs +python: + install: + - requirements: docs/requirements.txt \ No newline at end of file diff --git a/.vscode.settings.json.template b/.vscode.settings.json.template new file mode 100644 index 00000000..39be3908 --- /dev/null +++ b/.vscode.settings.json.template @@ -0,0 +1,14 @@ +{ + "json.schemaDownload.enable": true, + "go.lintTool": "golangci-lint", + "go.lintFlags": [ + "--fast" + ], + "gopls": { + "formatting.gofumpt": true, + "formatting.local": "github.com/ditrit" + }, + "go.formatFlags": [ + "-s" + ] +} \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..41fe403e --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,132 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +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, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, + without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +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. + +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. + +## Scope + +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. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**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. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**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. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**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. + +### 4. Permanent Ban + +**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. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7d252da0..3621cc97 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,121 +1,3 @@ -# Contribute to the development of badaas +# Contributing -- [Tests](#tests) - - [Unit tests](#unit-tests) - - [Feature tests (of end to end tests)](#feature-tests-of-end-to-end-tests) -- [Logger](#logger) -- [Directory structure](#directory-structure) -- [Git](#git) - - [Branch naming policy](#branch-naming-policy) - - [Default branch](#default-branch) - - [How to release](#how-to-release) - -## Tests - -### Unit tests - -We use the standard test suite in combination with [github.com/stretchr/testify](https://github.com/stretchr/testify) to do our unit testing. Mocks are generated using [mockery](https://github.com/vektra/mockery) a mock generator using this command `mockery --all --keeptree`. - -To run them, please run: - -```sh -go test $(go list ./... | sed 1d) -v -``` - -### Feature tests (of end to end tests) - -We use docker to run a Badaas instance in combination with one node of CockroachDB. - -Run: - -```sh -docker compose -f "scripts/e2e/docker-compose.yml" up -d --build -``` - -Then in an another shell: - -```sh -go test -v -``` - -The feature files can be found in the `feature` folder. - -## Logger - -We use ubber's [zap](https://pkg.go.dev/go.uber.org/zap) to log stuff, please take `zap.Logger` as an argument for your services constructors. [fx](https://github.com/uber-go/fx) will provide your service with an instance. - -## Directory structure - -This is the directory structure we use for the project: - -- `commands/` *(Go code)*: Contains all the CLI commands. This package relies heavily on github.com/ditrit/verdeter. -- `configuration/` *(Go code)*: Contains all the configuration holders. Please only use the interfaces, they are all mocked for easy testing -- `controllers/` *(Go code)*: Contains all the http controllers, they handle http requests and consume services. -- `docs/`: Contains the documentation. -- `features/`: Contains all the feature tests (or end to end tests). -- `logger/` *(Go code)*: Contains the logger creation logic. Please don't call it from your own services and code, use the dependency injection system. -- `persistance/` *(Go code)*: - - `/gormdatabase/` *(Go code)*: Contains the logic to create a database. Also contains a go package named `gormzap`: it is a compatibility layer between *gorm.io/gorm* and *github.com/uber-go/zap*. - - `/models/` *(Go code)*: Contains the models. (For a structure to me considered a valid model, it has to embed `models.BaseModel` and satisfy the `models.Tabler` interface. This interface returns the name of the sql table.) - - `/dto/` *(Go code)*: Contains the Data Transfert Objects. They are used mainly to decode json payloads. - - `/pagination/` *(Go code)*: Contains the pagination logic. - - `/repository/` *(Go code)*: Contains the repository interface and implementation. Use uint as ID when using gorm models. -- `resources/` *(Go code)*: Contains the resources shared with the rest of the codebase (ex: API version). -- `router/` *(Go code)*: Contains http router of badaas. - - `/middlewares/` *(Go code)*: Contains the various http middlewares that we use. -- `scripts/e2e/` : Contains the docker-compose file for end-to-end test. - - `/api/` : Contains the Dockerfile to build badaas with a dedicated config file. - - `/db/` : Contains the Dockerfile to build a developpement version of CockroachDB. -- `services/` *(Go code)*: Contains the Dockerfile to build a developpement version of CockroachDB. - - `/auth/protocols/`: Contains the implementations of authentication clients for differents protocols. - - `/basicauth/` *(Go code)*: Handle the authentification using email/password. - - `/oidc/` *(Go code)*: Handle the authentication via Open-ID Connect. - - `/sessionservice/` *(Go code)*: Handle sessions and their lifecycle. - - `/userservice/` *(Go code)*: Handle users. - - `validators/` *(Go code)*: Contains validators such as an email validator. - -At the root of the project, you will find: - -- The README. -- The changelog. -- The files for the E2E test http support. -- The LICENCE file. - -## Git - -### Branch naming policy - -`[BRANCH_TYPE]/[BRANCH_NAME]` - -- `BRANCH_TYPE` is a prefix to describe the purpose of the branch. - - Accepted prefixes are: - - `feature`, used for feature development - - `bugfix`, used for bug fix - - `improvement`, used for refacto - - `library`, used for updating library - - `prerelease`, used for preparing the branch for the release - - `release`, used for releasing project - - `hotfix`, used for applying a hotfix on main - - `poc`, used for proof of concept -- `BRANCH_NAME` is managed by this regex: `[a-z0-9._-]` (`_` is used as space character). - -### Default branch - -The default branch is `main`. Direct commit on it is forbidden. The only way to update the application is through pull request. - -Release tag are only done on the `main` branch. - -### How to release - -We use [Semantic Versioning](https://semver.org/spec/v2.0.0.html) as guideline for the version management. - -Steps to release: - -- Create a new branch labeled `release/vX.Y.Z` from the latest `main`. -- Improve the version number in `changelog.md` and `resources/api.go`. -- Verify the content of the `changelog.md`. -- Commit the modifications with the label `Release version X.Y.Z`. -- Create a pull request on github for this branch into `main`. -- Once the pull request validated and merged, tag the `main` branch with `vX.Y.Z` -- After the tag is pushed, make the release on the tag in GitHub +See [this section](./docs/contributing/contributing.md). diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 96dc2afe..00000000 --- a/Dockerfile +++ /dev/null @@ -1,13 +0,0 @@ -# builder image -FROM golang:1.19-alpine as builder -WORKDIR /app -COPY . . -RUN apk add build-base -RUN CGO_ENABLED=1 go build -a -o badaas . - - -# final image for end users -FROM alpine:3.16.2 -COPY --from=builder /app/badaas . -EXPOSE 8000 -ENTRYPOINT [ "./badaas" ] \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..2a56596b --- /dev/null +++ b/Makefile @@ -0,0 +1,67 @@ +PATHS = $(shell go list ./... | tail -n +2 | grep -v testintegration) + +install_dependencies: + go install gotest.tools/gotestsum@latest + go install github.com/vektra/mockery/v2@latest + go install github.com/ditrit/badaas/tools/badctl@latest + go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest + +lint: + golangci-lint run + cd test_e2e && golangci-lint run --config ../.golangci.yml + cd tools/badctl && golangci-lint run --config ../../.golangci.yml + +test_unit_badaas: + gotestsum --format pkgname $(PATHS) + +test_unit_badctl: + gotestsum --format pkgname ./tools/badctl/... + +test_unit: test_unit_badaas test_unit_badctl + +rmdb: + docker stop badaas-test-db && docker rm badaas-test-db + +postgresql: + docker compose -f "docker/postgresql/docker-compose.yml" up -d + ./docker/wait_for_db.sh + +cockroachdb: + docker compose -f "docker/cockroachdb/docker-compose.yml" up -d + ./docker/wait_for_db.sh + +mysql: + docker compose -f "docker/mysql/docker-compose.yml" up -d + ./docker/wait_for_db.sh + +sqlserver: + docker compose -f "docker/sqlserver/docker-compose.yml" up -d --build + ./docker/wait_for_db.sh + +test_integration_postgresql: postgresql + DB=postgresql gotestsum --format testname ./testintegration + +test_integration_cockroachdb: cockroachdb + DB=postgresql gotestsum --format testname ./testintegration + +test_integration_mysql: mysql + DB=mysql gotestsum --format testname ./testintegration -tags=mysql + +test_integration_sqlite: + DB=sqlite gotestsum --format testname ./testintegration + +test_integration_sqlserver: sqlserver + DB=sqlserver gotestsum --format testname ./testintegration + +test_integration: test_integration_postgresql + +test_e2e: + docker compose -f "docker/cockroachdb/docker-compose.yml" -f "docker/test_api/docker-compose.yml" up -d + ./docker/wait_for_api.sh 8000/info + go test ./test_e2e -v + +test_generate_mocks: + go generate + +.PHONY: test_unit test_integration test_e2e + diff --git a/README.md b/README.md index 15ade58d..a569d499 100644 --- a/README.md +++ b/README.md @@ -1,99 +1,44 @@ # BADAAS: Backend And Distribution As A Service -Badaas enables the effortless construction of ***distributed, resilient, highly available and secure applications by design***, while ensuring very simple deployment and management (NoOps). +[![Build Status](https://github.com/ditrit/badaas/actions/workflows/CI.yml/badge.svg?branch=main)](https://github.com/ditrit/badaas/actions) +[![Go Report Card](https://goreportcard.com/badge/github.com/ditrit/badaas)](https://goreportcard.com/report/github.com/ditrit/badaas) +[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=ditrit_badaas&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=ditrit_badaas) +[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=ditrit_badaas&metric=coverage)](https://sonarcloud.io/summary/new_code?id=ditrit_badaas) +[![OpenSSF Best Practices](https://bestpractices.coreinfrastructure.org/projects/7624/badge)](https://bestpractices.coreinfrastructure.org/projects/7624) -Badaas provides several key features: +[![Go.Dev reference](https://img.shields.io/badge/go.dev-reference-blue?logo=go&logoColor=white)](https://pkg.go.dev/github.com/ditrit/badaas) -- **Authentification**: Badaas can authentify users using its internal authentification scheme or externally by using protocols such as OIDC, SAML, Oauth2... -- **Habilitation**: On a resource access, Badaas will check if the user is authorized using a RBAC model. -- **Distribution**: Badaas is built to run in clusters by default. Communications between nodes are TLS encrypted using [shoset](https://github.com/ditrit/shoset). -- **Persistence**: Applicative objects are persisted as well as user files. Those resources are shared accross the clusters to increase resiliency. -- **Querying Resources**: Resources are accessible via a REST API. -- **Posix complient**: Badaas strives towards being a good unix citizen and respecting commonly accepted norms. (see [Configuration](#configuration)) -- **Advanced logs management**: Badaas provides an interface to interact with the logs produced by the clusters. Logs are formated in json by default. +[![Discord DitRit](https://dcbadge.vercel.app/api/server/zkKfj9gj2C?style=flat&theme=default-inverted)](https://discord.gg/zkKfj9gj2C) -To quickly get badaas up and running, please head to the [miniblog tutorial]() +BaDaaS enables the effortless construction of ***distributed, resilient, highly available and secure applications by design***, while ensuring very simple deployment and management (NoOps). -- [Quickstart](#quickstart) -- [Docker install](#docker-install) -- [Install from sources](#install-from-sources) - - [Prerequisites](#prerequisites) - - [Configuration](#configuration) -- [Contributing](#contributing) -- [Licence](#licence) +> **Warning** +> BaDaaS is still under development and each of its components can have a different state of evolution -## Quickstart +## Features and components -You can either use the [Docker Install](#docker-install) or build it from source . +Badaas provides several key features, each provided by a component that can be used independently and has a different state of evolution: -## Docker install +- **Authentication**(unstable): Badaas can authenticate users using its internal authentication scheme or externally by using protocols such as OIDC, SAML, Oauth2... +- **Authorization**(wip_unstable): On resource access, Badaas will check if the user is authorized using a RBAC model. +- **Distribution**(todo): Badaas is built to run in clusters by default. Communications between nodes are TLS encrypted using [shoset](https://github.com/ditrit/shoset). +- **Persistence**(wip_unstable): Applicative objects are persisted as well as user files. Those resources are shared across the clusters to increase resiliency. To achieve this, BaDaaS uses the [BaDorm](https://github.com/ditrit/badaas/badorm) component. +- **Querying Resources**(unstable): Resources are accessible via a REST API. +- **Posix compliant**(stable): Badaas strives towards being a good unix citizen and respecting commonly accepted norms. +- **Advanced logs management**(todo): Badaas provides an interface to interact with the logs produced by the clusters. Logs are formatted in json by default. -You can build the image using `docker build -t badaas .` since we don't have an official docker image yet. +## Documentation -## Install from sources + -### Prerequisites - -Get the sources of the project, either by visiting the [releases](https://github.com/ditrit/badaas/releases) page and downloading an archive or clone the main branch (please be aware that is it not a stable version). - -To build the project: - -- [Install go](https://go.dev/dl/#go1.18.4) v1.18 -- Install project dependencies - -```bash -go get -``` - -- Run build command - -```bash -go build . -``` - -Well done, you have a binary `badaas` at the root of the project. - -Then you can launch Badaas directly with: - -```bash -export BADAAS_DATABASE_PORT= -export BADAAS_DATABASE_HOST= -export BADAAS_DATABASE_DBNAME= -export BADAAS_DATABASE_SSLMODE= -export BADAAS_DATABASE_USERNAME= -export BADAAS_DATABASE_PASSWORD= -./badaas -``` - -### Configuration - -Badaas use [verdeter](https://github.com/ditrit/verdeter) to manage it's configuration. So Badaas is POSIX complient by default. - -Badaas can be configured using environment variables, configuration files or CLI flags. -CLI flags take priority on the environment variables and the environment variables take priority on the content of the configuration file. - -As an exemple we will define the `database.port` configuration key using the 3 methods: - -- Using a CLI flag: `--database.port=1222` -- Using an environment variable: `export BADAAS_DATABASE_PORT=1222` (*dots are replaced by underscores*) -- Using a config file (in YAML here): - - ```yml - # /etc/badaas/badaas.yml - database: - port: 1222 - ``` - -The config file can be placed at `/etc/badaas/badaas.yml` or `$HOME/.config/badaas/badaas.yml` or in the same folder as the badaas binary `./badaas.yml`. - -If needed, the location can be overridden using the config key `config_path`. +## Contributing -***For a full overview of the configuration keys: please head to the [configuration documentation](./configuration.md).*** +See [this section](./docs/contributing/contributing.md). -## Contributing +## Code of Conduct -See [this section](./CONTRIBUTING.md). +This project has adopted the [Contributor Covenant Code of Conduct](CODE_OF_CONDUCT.md) -## Licence +## License -Badaas is Licenced under the [Mozilla Public License Version 2.0](./LICENSE). +Badaas is Licensed under the [Mozilla Public License Version 2.0](./LICENSE). diff --git a/badaas.example.yml b/badaas.example.yml index 44313081..166127a6 100644 --- a/badaas.example.yml +++ b/badaas.example.yml @@ -1,18 +1,18 @@ # The settings for the database. database: - # The host of the database server. + # The host of the database server (or path for sqlite). # (mandatory) host: e2e-db-1 - # The port of the database server. + # The port of the database server. # (mandatory) port: 26257 - # The sslmode of the connection to the database server. + # The sslmode of the connection to the database server. # (mandatory) sslmode: disable - # The username of the account on the database server. + # The username of the account on the database server. # (mandatory) username: root @@ -20,7 +20,7 @@ database: # (mandatory) password: postgres - # The settings for the initialization of the database server. + # The settings for the initialization of the database server. init: # Number of time badaas will try to establish a connection to the database server. # default (10) @@ -30,11 +30,15 @@ database: # default (5) retryTime: 5 + # dialector to use in communication with database (postgresql | mysql | sqlite | sqlserver) + # (mandatory) + dialector: postgresql + # The settings for the http server. server: # The address to bind badaas to. # default ("0.0.0.0") - host: "" + host: "" # The port badaas should use. # default (8000) @@ -42,12 +46,12 @@ server: # The maximum timeout for the http server in seconds. # default (15) - timeout: 15 + timeout: 15 # The settings for the pagination. pagination: page: - # The maximum number of record per page + # The maximum number of record per page. # default (100) max: 100 @@ -61,7 +65,7 @@ logger: template: "Receive {{method}} request on {{url}}" # The settings for session service -# This section contains some good defaults, don't change thoses value unless you need to. +# This section contains some good defaults, don't change those value unless you need to. session: # The duration of a user session, in seconds # Default (14400) equal to 4 hours @@ -78,5 +82,4 @@ default: # The admin settings for the first run admin: # The admin password for the first run. Won't change is the admin user already exists. - password: admin - \ No newline at end of file + password: admin \ No newline at end of file diff --git a/badaas.go b/badaas.go index 05513376..13691219 100644 --- a/badaas.go +++ b/badaas.go @@ -1,11 +1,20 @@ -// Package main : -package main +package badaas + +//go:generate mockery --all --keeptree import ( - "github.com/ditrit/badaas/commands" + "go.uber.org/fx" + + "github.com/ditrit/badaas/configuration" + "github.com/ditrit/badaas/logger" + "github.com/ditrit/badaas/persistence" + "github.com/ditrit/badaas/router" ) -// Badaas application, run a http-server on 8000. -func main() { - commands.Execute() -} +var BadaasModule = fx.Module( + "badaas", + configuration.ConfigurationModule, + router.RouterModule, + logger.LoggerModule, + persistence.PersistanceModule, +) diff --git a/badaas_e2e_test.go b/badaas_e2e_test.go deleted file mode 100644 index 8b5e2072..00000000 --- a/badaas_e2e_test.go +++ /dev/null @@ -1,56 +0,0 @@ -package main - -import ( - "net/http" - "net/http/cookiejar" - "os" - "testing" - "time" - - "github.com/cucumber/godog" - "github.com/cucumber/godog/colors" - "github.com/spf13/pflag" -) - -type TestContext struct { - statusCode int - json map[string]interface{} - httpClient *http.Client -} - -var opts = godog.Options{Output: colors.Colored(os.Stdout)} - -func init() { - godog.BindCommandLineFlags("godog.", &opts) -} - -func TestMain(m *testing.M) { - pflag.Parse() - opts.Paths = pflag.Args() - - status := godog.TestSuite{ - Name: "godogs", - ScenarioInitializer: InitializeScenario, - Options: &opts, - }.Run() - - os.Exit(status) -} - -func InitializeScenario(ctx *godog.ScenarioContext) { - t := &TestContext{} - jar, err := cookiejar.New(nil) - if err != nil { - panic(err) - } - t.httpClient = &http.Client{ - Transport: http.DefaultTransport, - Timeout: time.Duration(5 * time.Second), - Jar: jar, - } - - ctx.Step(`^I request "(.+)"$`, t.requestGET) - ctx.Step(`^I expect status code is "(\d+)"$`, t.assertStatusCode) - ctx.Step(`^I expect response field "(.+)" is "(.+)"$`, t.assertResponseFieldIsEquals) - ctx.Step(`^I request "(.+)" with method "(.+)" with json$`, t.requestWithJson) -} diff --git a/badorm/ModuleFx.go b/badorm/ModuleFx.go new file mode 100644 index 00000000..fac0b24e --- /dev/null +++ b/badorm/ModuleFx.go @@ -0,0 +1,99 @@ +package badorm + +import ( + "fmt" + "log" + "reflect" + + "go.uber.org/fx" +) + +type GetModelsResult struct { + fx.Out + + Models []any `group:"modelsTables"` +} + +var BaDORMModule = fx.Module( + "BaDORM", + fx.Invoke( + fx.Annotate( + autoMigrate, + fx.ParamTags(`group:"modelsTables"`), + ), + ), +) + +func GetCRUDServiceModule[T Model]() fx.Option { + entity := *new(T) + + moduleName := fmt.Sprintf( + "%TCRUDServiceModule", + entity, + ) + + kind := GetBaDORMModelKind(entity) + switch kind { + case KindUUIDModel: + return fx.Module( + moduleName, + // repository + fx.Provide(NewCRUDRepository[T, UUID]), + // service + fx.Provide(NewCRUDService[T, UUID]), + ) + case KindUIntModel: + return fx.Module( + moduleName, + // repository + fx.Provide(NewCRUDRepository[T, UIntID]), + // service + fx.Provide(NewCRUDService[T, UIntID]), + ) + case KindNotBaDORMModel: + log.Printf("type %T is not a BaDORM Module\n", entity) + return fx.Invoke(FailNotBadORMModule()) + } + + return fx.Invoke(FailNotBadORMModule()) +} + +func FailNotBadORMModule() error { + return fmt.Errorf("type is not a BaDORM Module") +} + +type ModelKind uint + +const ( + KindUUIDModel ModelKind = iota + KindUIntModel + KindNotBaDORMModel +) + +func GetBaDORMModelKind(entity Model) ModelKind { + entityType := GetEntityType(entity) + + _, isUUIDModel := entityType.FieldByName("UUIDModel") + if isUUIDModel { + return KindUUIDModel + } + + _, isUIntModel := entityType.FieldByName("UIntModel") + if isUIntModel { + return KindUIntModel + } + + return KindNotBaDORMModel +} + +// Get the reflect.Type of any entity or pointer to entity +func GetEntityType(entity any) reflect.Type { + entityType := reflect.TypeOf(entity) + + // entityType will be a pointer if the relation can be nullable + if entityType.Kind() == reflect.Pointer { + entityType = entityType.Elem() + } + + return entityType +} diff --git a/badorm/README.md b/badorm/README.md new file mode 100644 index 00000000..eee9208e --- /dev/null +++ b/badorm/README.md @@ -0,0 +1,23 @@ +# BaDORM + +BaDORM stands for Backend and Distribution ORM (Object Relational Mapping). It's the BaDaaS' component that allows for easy and safe persistence and querying of objects but it can be used both within a BaDaaS application and independently. + +BaDORM is built on top of `gorm `_, a library that actually provides the functionality of an ORM: mapping objects to tables in the SQL database. While gorm does this job well with its automatic migration then performing queries on these objects is somewhat limited, forcing us to write SQL queries directly when they are complex. BaDORM seeks to address these limitations with a query system that: + +- Is compile-time safe: the BaDORM query system is validated at compile time to avoid errors such as comparing attributes that are of different types, trying to use attributes or navigate relationships that do not exist, using information from tables that are not included in the query, etc. +- Is easy to use: the use of this system does not require knowledge of databases, SQL languages or complex concepts. Writing queries only requires programming in go and the result is easy to read. +- Is designed for real applications: the query system is designed to work well in real-world cases where queries are complex, require navigating multiple relationships, performing multiple comparisons, etc. +- Is designed so that developers can focus on the business model: its queries allow easy retrieval of model relationships to apply business logic to the model and it provides mechanisms to avoid errors in the business logic due to mistakes in loading information from the database. +- It is designed for high performance: the query system avoids as much as possible the use of reflection and aims that all the necessary model data can be retrieved in a single query to the database. + +## Documentation + + + +## Contributing + +See [this section](../docs/contributing/contributing.md) to view the badaas contribution guidelines. + +## License + +Badaas is Licensed under the [Mozilla Public License Version 2.0](../LICENSE). diff --git a/badorm/badorm.go b/badorm/badorm.go new file mode 100644 index 00000000..88d1ebc7 --- /dev/null +++ b/badorm/badorm.go @@ -0,0 +1,16 @@ +package badorm + +import ( + "github.com/elliotchance/pie/v2" + "gorm.io/gorm" +) + +func GetCRUD[T Model, ID ModelID](db *gorm.DB) (CRUDService[T, ID], CRUDRepository[T, ID]) { + repository := NewCRUDRepository[T, ID]() + return NewCRUDService(db, repository), repository +} + +func autoMigrate(modelsLists [][]any, db *gorm.DB) error { + allModels := pie.Flat(modelsLists) + return db.AutoMigrate(allModels...) +} diff --git a/badorm/baseModels.go b/badorm/baseModels.go new file mode 100644 index 00000000..89832803 --- /dev/null +++ b/badorm/baseModels.go @@ -0,0 +1,60 @@ +package badorm + +import ( + "time" + + "gorm.io/gorm" +) + +// supported types for model identifier +type ModelID interface { + UIntID | UUID + + IsNil() bool +} + +type Model interface { + IsLoaded() bool +} + +// Base Model for gorm +// +// Every model intended to be saved in the database must embed this badorm.UUIDModel +// reference: https://gorm.io/docs/models.html#gorm-Model +type UUIDModel struct { + ID UUID `gorm:"primarykey;not null"` + CreatedAt time.Time + UpdatedAt time.Time + DeletedAt gorm.DeletedAt `gorm:"index"` +} + +func (model UUIDModel) IsLoaded() bool { + return !model.ID.IsNil() +} + +func (model *UUIDModel) BeforeCreate(_ *gorm.DB) (err error) { + if model.ID == NilUUID { + model.ID = NewUUID() + } + + return nil +} + +type UIntID uint + +const NilUIntID = 0 + +func (id UIntID) IsNil() bool { + return id == NilUIntID +} + +type UIntModel struct { + ID UIntID `gorm:"primarykey;not null"` + CreatedAt time.Time + UpdatedAt time.Time + DeletedAt gorm.DeletedAt `gorm:"index"` +} + +func (model UIntModel) IsLoaded() bool { + return !model.ID.IsNil() +} diff --git a/badorm/condition.go b/badorm/condition.go new file mode 100644 index 00000000..ab14d197 --- /dev/null +++ b/badorm/condition.go @@ -0,0 +1,492 @@ +package badorm + +import ( + "fmt" + "strings" + + "github.com/elliotchance/pie/v2" + "gorm.io/gorm" + + "github.com/ditrit/badaas/badorm/sql" +) + +const deletedAtField = "DeletedAt" + +type Condition[T Model] interface { + // Applies the condition to the "query" + // using the "tableName" as name for the table holding + // the data for object of type T + ApplyTo(query *Query, table Table) error + + // This method is necessary to get the compiler to verify + // that an object is of type Condition[T], + // since if no method receives by parameter a type T, + // any other Condition[T2] would also be considered a Condition[T]. + InterfaceVerificationMethod(T) +} + +// Conditions that can be used in a where clause +// (or in a on of a join) +type WhereCondition[T Model] interface { + Condition[T] + + // Get the sql string and values to use in the query + GetSQL(query *Query, table Table) (string, []any, error) + + // Returns true if the DeletedAt column if affected by the condition + // If no condition affects the DeletedAt, the verification that it's null will be added automatically + AffectsDeletedAt() bool +} + +// Condition that contains a internal condition. +// Example: NOT (internal condition) +type ContainerCondition[T Model] struct { + ConnectionCondition WhereCondition[T] + Prefix sql.Connector +} + +func (condition ContainerCondition[T]) InterfaceVerificationMethod(_ T) { + // This method is necessary to get the compiler to verify + // that an object is of type Condition[T] +} + +func (condition ContainerCondition[T]) ApplyTo(query *Query, table Table) error { + return ApplyWhereCondition[T](condition, query, table) +} + +func (condition ContainerCondition[T]) GetSQL(query *Query, table Table) (string, []any, error) { + sqlString, values, err := condition.ConnectionCondition.GetSQL(query, table) + if err != nil { + return "", nil, err + } + + sqlString = condition.Prefix.String() + " (" + sqlString + ")" + + return sqlString, values, nil +} + +func (condition ContainerCondition[T]) AffectsDeletedAt() bool { + return condition.ConnectionCondition.AffectsDeletedAt() +} + +// Condition that contains a internal condition. +// Example: NOT (internal condition) +func NewContainerCondition[T Model](prefix sql.Connector, conditions ...WhereCondition[T]) WhereCondition[T] { + if len(conditions) == 0 { + return NewInvalidCondition[T](emptyConditionsError[T](prefix)) + } + + return ContainerCondition[T]{ + Prefix: prefix, + ConnectionCondition: And(conditions...), + } +} + +// Condition that connects multiple conditions. +// Example: condition1 AND condition2 +type ConnectionCondition[T Model] struct { + Connector sql.Connector + Conditions []WhereCondition[T] +} + +func (condition ConnectionCondition[T]) InterfaceVerificationMethod(_ T) { + // This method is necessary to get the compiler to verify + // that an object is of type Condition[T] +} + +func (condition ConnectionCondition[T]) ApplyTo(query *Query, table Table) error { + return ApplyWhereCondition[T](condition, query, table) +} + +func (condition ConnectionCondition[T]) GetSQL(query *Query, table Table) (string, []any, error) { + sqlStrings := []string{} + values := []any{} + + for _, internalCondition := range condition.Conditions { + internalSQLString, exprValues, err := internalCondition.GetSQL(query, table) + if err != nil { + return "", nil, err + } + + sqlStrings = append(sqlStrings, internalSQLString) + + values = append(values, exprValues...) + } + + return strings.Join( + sqlStrings, + " "+condition.Connector.String()+" ", + ), values, nil +} + +func (condition ConnectionCondition[T]) AffectsDeletedAt() bool { + return pie.Any(condition.Conditions, func(internalCondition WhereCondition[T]) bool { + return internalCondition.AffectsDeletedAt() + }) +} + +// Condition that connects multiple conditions. +// Example: condition1 AND condition2 +func NewConnectionCondition[T Model](connector sql.Connector, conditions ...WhereCondition[T]) WhereCondition[T] { + return ConnectionCondition[T]{ + Connector: connector, + Conditions: conditions, + } +} + +// Condition used to the preload the attributes of a model +type PreloadCondition[T Model] struct { + Fields []iFieldIdentifier +} + +func (condition PreloadCondition[T]) InterfaceVerificationMethod(_ T) { + // This method is necessary to get the compiler to verify + // that an object is of type Condition[T] +} + +func (condition PreloadCondition[T]) ApplyTo(query *Query, table Table) error { + for _, fieldID := range condition.Fields { + query.AddSelect(table, fieldID) + } + + return nil +} + +// Condition used to the preload the attributes of a model +func NewPreloadCondition[T Model](fields ...iFieldIdentifier) PreloadCondition[T] { + return PreloadCondition[T]{ + Fields: fields, + } +} + +// Condition used to the preload a collection of models of a model +type CollectionPreloadCondition[T1, T2 Model] struct { + CollectionField string + // For each model in the collection we can preload its relations + NestedPreloads []IJoinCondition[T2] +} + +func (condition CollectionPreloadCondition[T1, T2]) InterfaceVerificationMethod(_ T1) { + // This method is necessary to get the compiler to verify + // that an object is of type Condition[T] +} + +func (condition CollectionPreloadCondition[T1, T2]) ApplyTo(query *Query, _ Table) error { + if len(condition.NestedPreloads) == 0 { + query.Preload(condition.CollectionField) + return nil + } + + query.Preload( + condition.CollectionField, + func(db *gorm.DB) *gorm.DB { + // apply NestedPreloads + preloadsAsCondition := pie.Map( + condition.NestedPreloads, + func(joinCondition IJoinCondition[T2]) Condition[T2] { + return joinCondition + }, + ) + + preloadInternalQuery, err := NewQuery(db, preloadsAsCondition) + if err != nil { + _ = db.AddError(err) + return db + } + + return preloadInternalQuery.gormDB + }, + ) + + return nil +} + +// Condition used to the preload a collection of models of a model +func NewCollectionPreloadCondition[T1 Model, T2 Model](collectionField string, nestedPreloads []IJoinCondition[T2]) Condition[T1] { + if pie.Any(nestedPreloads, func(nestedPreload IJoinCondition[T2]) bool { + return !nestedPreload.makesPreload() || nestedPreload.makesFilter() + }) { + return NewInvalidCondition[T1](onlyPreloadsAllowedError[T1](collectionField)) + } + + return CollectionPreloadCondition[T1, T2]{ + CollectionField: collectionField, + NestedPreloads: nestedPreloads, + } +} + +// Condition that verifies the value of a field, +// using the Operator +type FieldCondition[TObject Model, TAtribute any] struct { + FieldIdentifier FieldIdentifier[TAtribute] + Operator Operator[TAtribute] +} + +func (condition FieldCondition[TObject, TAtribute]) InterfaceVerificationMethod(_ TObject) { + // This method is necessary to get the compiler to verify + // that an object is of type Condition[T] +} + +// Returns a gorm Where condition that can be used +// to filter that the Field as a value of Value +func (condition FieldCondition[TObject, TAtribute]) ApplyTo(query *Query, table Table) error { + return ApplyWhereCondition[TObject](condition, query, table) +} + +func ApplyWhereCondition[T Model](condition WhereCondition[T], query *Query, table Table) error { + sql, values, err := condition.GetSQL(query, table) + if err != nil { + return err + } + + if condition.AffectsDeletedAt() { + query.Unscoped() + } + + query.Where( + sql, + values..., + ) + + return nil +} + +func (condition FieldCondition[TObject, TAtribute]) AffectsDeletedAt() bool { + return condition.FieldIdentifier.Field == deletedAtField +} + +func (condition FieldCondition[TObject, TAtribute]) GetSQL(query *Query, table Table) (string, []any, error) { + sqlString, values, err := condition.Operator.ToSQL( + query, + condition.FieldIdentifier.ColumnSQL(query, table), + ) + if err != nil { + return "", nil, conditionOperatorError[TObject](err, condition) + } + + return sqlString, values, nil +} + +// Interface of a join condition that joins T with any other model +type IJoinCondition[T Model] interface { + Condition[T] + + // Returns true if this condition or any nested condition makes a preload + makesPreload() bool + + // Returns true if the condition of nay nested condition applies a filter (has where conditions) + makesFilter() bool +} + +// Condition that joins with other table +type JoinCondition[T1 Model, T2 Model] struct { + T1Field string + T2Field string + RelationField string + Conditions []Condition[T2] + // condition to preload T1 in case T2 any nested object is preloaded by user + T1PreloadCondition PreloadCondition[T1] +} + +func (condition JoinCondition[T1, T2]) InterfaceVerificationMethod(_ T1) { + // This method is necessary to get the compiler to verify + // that an object is of type Condition[T] +} + +// Returns true if this condition or any nested condition makes a preload +func (condition JoinCondition[T1, T2]) makesPreload() bool { + _, joinConditions, t2PreloadCondition := divideConditionsByType(condition.Conditions) + + return t2PreloadCondition != nil || pie.Any(joinConditions, func(cond IJoinCondition[T2]) bool { + return cond.makesPreload() + }) +} + +// Returns true if the condition of nay nested condition applies a filter (has where conditions) +// +//nolint:unused // is used +func (condition JoinCondition[T1, T2]) makesFilter() bool { + whereConditions, joinConditions, _ := divideConditionsByType(condition.Conditions) + + return len(whereConditions) != 0 || pie.Any(joinConditions, func(cond IJoinCondition[T2]) bool { + return cond.makesFilter() + }) +} + +// Applies a join between the tables of T1 and T2 +// previousTableName is the name of the table of T1 +// It also applies the nested conditions +func (condition JoinCondition[T1, T2]) ApplyTo(query *Query, t1Table Table) error { + whereConditions, joinConditions, t2PreloadCondition := divideConditionsByType(condition.Conditions) + + t2Model := *new(T2) + + // get the sql to do the join with T2 + // if it's only a preload use a left join + t2Table, err := t1Table.DeliverTable(query, t2Model, condition.RelationField) + if err != nil { + return err + } + + makesPreload := condition.makesPreload() + joinQuery := condition.getSQLJoin( + query, + t1Table, + t2Table, + len(whereConditions) == 0 && makesPreload, + ) + + query.AddConcernedModel( + t2Model, + t2Table, + ) + + // apply WhereConditions to the join in the "on" clause + connectionCondition := And(whereConditions...) + + onQuery, onValues, err := connectionCondition.GetSQL(query, t2Table) + if err != nil { + return err + } + + if onQuery != "" { + joinQuery += " AND " + onQuery + } + + if !connectionCondition.AffectsDeletedAt() { + joinQuery += fmt.Sprintf( + " AND %s.deleted_at IS NULL", + t2Table.Alias, + ) + } + + // add the join to the query + query.Joins(joinQuery, onValues...) + + // apply T1 preload condition + // if this condition has a T2 preload condition + // or any nested join condition has a preload condition + // and this is not first level (T1 is the type of the repository) + // because T1 is always loaded in that case + if makesPreload && !t1Table.IsInitial() { + err = condition.T1PreloadCondition.ApplyTo(query, t1Table) + if err != nil { + return err + } + } + + // apply T2 preload condition + if t2PreloadCondition != nil { + err = t2PreloadCondition.ApplyTo(query, t2Table) + if err != nil { + return err + } + } + + // apply nested joins + for _, joinCondition := range joinConditions { + err = joinCondition.ApplyTo(query, t2Table) + if err != nil { + return err + } + } + + return nil +} + +// Returns the SQL string to do a join between T1 and T2 +// taking into account that the ID attribute necessary to do it +// can be either in T1's or T2's table. +func (condition JoinCondition[T1, T2]) getSQLJoin( + query *Query, + t1Table Table, + t2Table Table, + isLeftJoin bool, +) string { + joinString := "INNER JOIN" + if isLeftJoin { + joinString = "LEFT JOIN" + } + + return fmt.Sprintf( + `%[6]s %[1]s %[2]s ON %[2]s.%[3]s = %[4]s.%[5]s + `, + t2Table.Name, + t2Table.Alias, + query.ColumnName(t2Table, condition.T2Field), + t1Table.Alias, + query.ColumnName(t1Table, condition.T1Field), + joinString, + ) +} + +// Divides a list of conditions by its type: WhereConditions and JoinConditions +func divideConditionsByType[T Model]( + conditions []Condition[T], +) (whereConditions []WhereCondition[T], joinConditions []IJoinCondition[T], preloadCondition Condition[T]) { + for _, condition := range conditions { + possibleWhereCondition, ok := condition.(WhereCondition[T]) + if ok { + whereConditions = append(whereConditions, possibleWhereCondition) + continue + } + + possibleJoinCondition, ok := condition.(IJoinCondition[T]) + if ok { + joinConditions = append(joinConditions, possibleJoinCondition) + continue + } + + preloadCondition = condition + } + + return +} + +// Condition used to returns an error when the query is executed +type InvalidCondition[T any] struct { + Err error +} + +func (condition InvalidCondition[T]) InterfaceVerificationMethod(_ T) { + // This method is necessary to get the compiler to verify + // that an object is of type Condition[T] +} + +func (condition InvalidCondition[T]) ApplyTo(_ *Query, _ Table) error { + return condition.Err +} + +func (condition InvalidCondition[T]) GetSQL(_ *Query, _ Table) (string, []any, error) { + return "", nil, condition.Err +} + +func (condition InvalidCondition[T]) AffectsDeletedAt() bool { + return false +} + +// Condition used to returns an error when the query is executed +func NewInvalidCondition[T any](err error) InvalidCondition[T] { + return InvalidCondition[T]{ + Err: err, + } +} + +// Logical Operators +// ref: +// - PostgreSQL: https://www.postgresql.org/docs/current/functions-logical.html +// - MySQL: https://dev.mysql.com/doc/refman/8.0/en/logical-operators.html +// - SQLServer: https://learn.microsoft.com/en-us/sql/t-sql/language-elements/logical-operators-transact-sql?view=sql-server-ver16 +// - SQLite: https://www.sqlite.org/lang_expr.html + +func And[T Model](conditions ...WhereCondition[T]) WhereCondition[T] { + return NewConnectionCondition(sql.And, conditions...) +} + +func Or[T Model](conditions ...WhereCondition[T]) WhereCondition[T] { + return NewConnectionCondition(sql.Or, conditions...) +} + +func Not[T Model](conditions ...WhereCondition[T]) WhereCondition[T] { + return NewContainerCondition(sql.Not, conditions...) +} diff --git a/badorm/crudRepository.go b/badorm/crudRepository.go new file mode 100644 index 00000000..9438d70a --- /dev/null +++ b/badorm/crudRepository.go @@ -0,0 +1,106 @@ +package badorm + +import ( + "errors" + + "gorm.io/gorm" +) + +// Generic CRUD Repository +// T can be any model whose identifier attribute is of type ID +type CRUDRepository[T Model, ID ModelID] interface { + // Create model "model" inside transaction "tx" + Create(tx *gorm.DB, entity *T) error + + // ----- read ----- + + // Get a model by its ID + GetByID(tx *gorm.DB, id ID) (*T, error) + + // Get only one model that match "conditions" inside transaction "tx" + // or returns error if 0 or more than 1 are found. + QueryOne(tx *gorm.DB, conditions ...Condition[T]) (*T, error) + + // Get the list of models that match "conditions" inside transaction "tx" + Query(tx *gorm.DB, conditions ...Condition[T]) ([]*T, error) + + // Save model "model" inside transaction "tx" + Save(tx *gorm.DB, entity *T) error + + // Delete model "model" inside transaction "tx" + Delete(tx *gorm.DB, entity *T) error +} + +var ( + ErrMoreThanOneObjectFound = errors.New("found more that one object that meet the requested conditions") + ErrObjectNotFound = errors.New("no object exists that meets the requested conditions") +) + +// Implementation of the Generic CRUD Repository +type crudRepositoryImpl[T Model, ID ModelID] struct { + CRUDRepository[T, ID] +} + +// Constructor of the Generic CRUD Repository +func NewCRUDRepository[T Model, ID ModelID]() CRUDRepository[T, ID] { + return &crudRepositoryImpl[T, ID]{} +} + +// Create model "model" inside transaction "tx" +func (repository *crudRepositoryImpl[T, ID]) Create(tx *gorm.DB, model *T) error { + return tx.Create(model).Error +} + +// Delete model "model" inside transaction "tx" +func (repository *crudRepositoryImpl[T, ID]) Delete(tx *gorm.DB, model *T) error { + return tx.Delete(model).Error +} + +// Save model "model" inside transaction "tx" +func (repository *crudRepositoryImpl[T, ID]) Save(tx *gorm.DB, model *T) error { + return tx.Save(model).Error +} + +// Get a model by its ID +func (repository *crudRepositoryImpl[T, ID]) GetByID(tx *gorm.DB, id ID) (*T, error) { + var model T + + err := tx.First(&model, "id = ?", id).Error + if err != nil { + return nil, err + } + + return &model, nil +} + +// Get only one model that match "conditions" inside transaction "tx" +// or returns error if 0 or more than 1 are found. +func (repository *crudRepositoryImpl[T, ID]) QueryOne(tx *gorm.DB, conditions ...Condition[T]) (*T, error) { + entities, err := repository.Query(tx, conditions...) + if err != nil { + return nil, err + } + + switch { + case len(entities) == 1: + return entities[0], nil + case len(entities) == 0: + return nil, ErrObjectNotFound + default: + return nil, ErrMoreThanOneObjectFound + } +} + +// Get the list of models that match "conditions" inside transaction "tx" +func (repository *crudRepositoryImpl[T, ID]) Query(tx *gorm.DB, conditions ...Condition[T]) ([]*T, error) { + query, err := NewQuery(tx, conditions) + if err != nil { + return nil, err + } + + // execute query + var entities []*T + err = query.Find(&entities) + + return entities, err +} diff --git a/badorm/crudService.go b/badorm/crudService.go new file mode 100644 index 00000000..6b17c6de --- /dev/null +++ b/badorm/crudService.go @@ -0,0 +1,54 @@ +package badorm + +import ( + "gorm.io/gorm" +) + +// T can be any model whose identifier attribute is of type ID +type CRUDService[T Model, ID ModelID] interface { + // Get the model of type T that has the "id" + GetByID(id ID) (*T, error) + + // Get only one model that match "conditions" + // or returns error if 0 or more than 1 are found. + QueryOne(conditions ...Condition[T]) (*T, error) + + // Get the list of models that match "conditions" + Query(conditions ...Condition[T]) ([]*T, error) +} + +// check interface compliance +var _ CRUDService[UUIDModel, UUID] = (*crudServiceImpl[UUIDModel, UUID])(nil) + +// Implementation of the CRUD Service +type crudServiceImpl[T Model, ID ModelID] struct { + CRUDService[T, ID] + db *gorm.DB + repository CRUDRepository[T, ID] +} + +func NewCRUDService[T Model, ID ModelID]( + db *gorm.DB, + repository CRUDRepository[T, ID], +) CRUDService[T, ID] { + return &crudServiceImpl[T, ID]{ + db: db, + repository: repository, + } +} + +// Get the model of type T that has the "id" +func (service *crudServiceImpl[T, ID]) GetByID(id ID) (*T, error) { + return service.repository.GetByID(service.db, id) +} + +// Get only one model that match "conditions" +// or returns error if 0 or more than 1 are found. +func (service *crudServiceImpl[T, ID]) QueryOne(conditions ...Condition[T]) (*T, error) { + return service.repository.QueryOne(service.db, conditions...) +} + +// Get the list of models that match "conditions" +func (service *crudServiceImpl[T, ID]) Query(conditions ...Condition[T]) ([]*T, error) { + return service.repository.Query(service.db, conditions...) +} diff --git a/badorm/db.go b/badorm/db.go new file mode 100644 index 00000000..c0a3d54f --- /dev/null +++ b/badorm/db.go @@ -0,0 +1,92 @@ +package badorm + +import ( + "fmt" + "net" + "strconv" + "time" + + "go.uber.org/zap" + "gorm.io/driver/mysql" + "gorm.io/driver/postgres" + "gorm.io/driver/sqlite" + "gorm.io/driver/sqlserver" + "gorm.io/gorm" + + "github.com/ditrit/badaas/persistence/gormdatabase/gormzap" +) + +func CreatePostgreSQLDialector(host, username, password, sslmode, dbname string, port int) gorm.Dialector { + return postgres.Open(CreatePostgreSQLDSN( + host, username, password, sslmode, dbname, port, + )) +} + +func CreatePostgreSQLDSN(host, username, password, sslmode, dbname string, port int) string { + return fmt.Sprintf( + "user=%s password=%s host=%s port=%d sslmode=%s dbname=%s", + username, password, host, port, sslmode, dbname, + ) +} + +func CreateMySQLDialector(host, username, password, dbname string, port int) gorm.Dialector { + return mysql.Open(CreateMySQLDSN( + host, username, password, dbname, port, + )) +} + +func CreateMySQLDSN(host, username, password, dbname string, port int) string { + return fmt.Sprintf( + "%s:%s@tcp(%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", + username, password, net.JoinHostPort(host, strconv.Itoa(port)), dbname, + ) +} + +func CreateSQLiteDialector(path string) gorm.Dialector { + return sqlite.Open(CreateSQLiteDSN(path)) +} + +func CreateSQLiteDSN(path string) string { + return fmt.Sprintf("sqlite:%s", path) +} + +func CreateSQLServerDialector(host, username, password, dbname string, port int) gorm.Dialector { + return sqlserver.Open(CreateSQLServerDSN(host, username, password, dbname, port)) +} + +func CreateSQLServerDSN(host, username, password, dbname string, port int) string { + return fmt.Sprintf( + "sqlserver://%s:%s@%s?database=%s", + username, + password, + net.JoinHostPort(host, strconv.Itoa(port)), + dbname, + ) +} + +func ConnectToDialector( + logger *zap.Logger, + dialector gorm.Dialector, + retryAmount uint, + retryTime time.Duration, +) (database *gorm.DB, err error) { + for numberRetry := uint(0); numberRetry < retryAmount; numberRetry++ { + database, err = gorm.Open(dialector, &gorm.Config{ + Logger: gormzap.New(logger), + }) + + if err == nil { + logger.Sugar().Debugf("Database connection is active") + return database, nil + } + + logger.Sugar().Debugf("Database connection failed with error %q", err.Error()) + logger.Sugar().Debugf( + "Retrying database connection %d/%d in %s", + numberRetry+1, retryAmount, retryTime.String(), + ) + time.Sleep(retryTime) + } + + return nil, err +} diff --git a/badorm/db_test.go b/badorm/db_test.go new file mode 100644 index 00000000..0249d8f3 --- /dev/null +++ b/badorm/db_test.go @@ -0,0 +1,36 @@ +package badorm + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCreatePostgreDSN(t *testing.T) { + assert.Equal( + t, + "user=username password=password host=192.168.2.5 port=1225 sslmode=disable dbname=badaas_db", + CreatePostgreSQLDSN( + "192.168.2.5", + "username", + "password", + "disable", + "badaas_db", + 1225, + ), + ) +} + +func TestCreateMySQLDSN(t *testing.T) { + assert.Equal( + t, + "username:password@tcp(192.168.2.5:1225)/badaas_db?charset=utf8mb4&parseTime=True&loc=Local", + CreateMySQLDSN( + "192.168.2.5", + "username", + "password", + "badaas_db", + 1225, + ), + ) +} diff --git a/badorm/dynamic/multivalue.go b/badorm/dynamic/multivalue.go new file mode 100644 index 00000000..e52eda2c --- /dev/null +++ b/badorm/dynamic/multivalue.go @@ -0,0 +1,27 @@ +package dynamic + +import ( + "github.com/elliotchance/pie/v2" + + "github.com/ditrit/badaas/badorm" + "github.com/ditrit/badaas/badorm/sql" +) + +func NewMultivalueOperator[T any]( + sqlOperator sql.Operator, + sqlConnector sql.Connector, + sqlPrefix, sqlSuffix string, + fields ...badorm.FieldIdentifier[T], +) badorm.DynamicOperator[T] { + values := pie.Map(fields, func(field badorm.FieldIdentifier[T]) any { + return field + }) + + return &badorm.MultivalueOperator[T]{ + Values: values, + SQLOperator: sqlOperator, + SQLConnector: sqlConnector, + SQLPrefix: sqlPrefix, + SQLSuffix: sqlSuffix, + } +} diff --git a/badorm/dynamic/mysqldynamic/comparison.go b/badorm/dynamic/mysqldynamic/comparison.go new file mode 100644 index 00000000..1970025e --- /dev/null +++ b/badorm/dynamic/mysqldynamic/comparison.go @@ -0,0 +1,15 @@ +package mysqldynamic + +import ( + "github.com/ditrit/badaas/badorm" + "github.com/ditrit/badaas/badorm/dynamic" + "github.com/ditrit/badaas/badorm/sql" +) + +// Comparison Predicates + +// preferred over eq +// https://dev.mysql.com/doc/refman/8.0/en/comparison-operators.html#operator_equal-to +func IsEqual[T any](field badorm.FieldIdentifier[T]) badorm.DynamicOperator[T] { + return dynamic.NewValueOperator[T](sql.MySQLIsEqual, field) +} diff --git a/badorm/dynamic/operators.go b/badorm/dynamic/operators.go new file mode 100644 index 00000000..1c389269 --- /dev/null +++ b/badorm/dynamic/operators.go @@ -0,0 +1,80 @@ +package dynamic + +import ( + "github.com/ditrit/badaas/badorm" + "github.com/ditrit/badaas/badorm/sql" +) + +// Comparison Operators +// refs: +// - MySQL: https://dev.mysql.com/doc/refman/8.0/en/comparison-operators.html +// - PostgreSQL: https://www.postgresql.org/docs/current/functions-comparison.html +// - SQLServer: https://learn.microsoft.com/en-us/sql/t-sql/language-elements/comparison-operators-transact-sql?view=sql-server-ver16 +// - SQLite: https://www.sqlite.org/lang_expr.html + +// EqualTo +func Eq[T any](field badorm.FieldIdentifier[T]) badorm.DynamicOperator[T] { + return NewValueOperator(sql.Eq, field) +} + +// NotEqualTo +func NotEq[T any](field badorm.FieldIdentifier[T]) badorm.DynamicOperator[T] { + return NewValueOperator(sql.NotEq, field) +} + +// LessThan +func Lt[T any](field badorm.FieldIdentifier[T]) badorm.DynamicOperator[T] { + return NewValueOperator(sql.Lt, field) +} + +// LessThanOrEqualTo +func LtOrEq[T any](field badorm.FieldIdentifier[T]) badorm.DynamicOperator[T] { + return NewValueOperator(sql.LtOrEq, field) +} + +// GreaterThan +func Gt[T any](field badorm.FieldIdentifier[T]) badorm.DynamicOperator[T] { + return NewValueOperator(sql.Gt, field) +} + +// GreaterThanOrEqualTo +func GtOrEq[T any](field badorm.FieldIdentifier[T]) badorm.DynamicOperator[T] { + return NewValueOperator(sql.GtOrEq, field) +} + +// Comparison Predicates +// refs: +// https://dev.mysql.com/doc/refman/8.0/en/comparison-operators.html +// https://www.postgresql.org/docs/current/functions-comparison.html#FUNCTIONS-COMPARISON-PRED-TABLE + +// Equivalent to field1 < value < field2 +func Between[T any](field1, field2 badorm.FieldIdentifier[T]) badorm.DynamicOperator[T] { + return NewMultivalueOperator(sql.Between, sql.And, "", "", field1, field2) +} + +// Equivalent to NOT (field1 < value < field2) +func NotBetween[T any](field1, field2 badorm.FieldIdentifier[T]) badorm.DynamicOperator[T] { + return NewMultivalueOperator(sql.NotBetween, sql.And, "", "", field1, field2) +} + +// Boolean Comparison Predicates + +// Not supported by: mysql +func IsDistinct[T any](field badorm.FieldIdentifier[T]) badorm.DynamicOperator[T] { + return NewValueOperator(sql.IsDistinct, field) +} + +// Not supported by: mysql +func IsNotDistinct[T any](field badorm.FieldIdentifier[T]) badorm.DynamicOperator[T] { + return NewValueOperator(sql.IsNotDistinct, field) +} + +// Row and Array Comparisons + +func ArrayIn[T any](fields ...badorm.FieldIdentifier[T]) badorm.DynamicOperator[T] { + return NewMultivalueOperator(sql.ArrayIn, sql.Comma, "(", ")", fields...) +} + +func ArrayNotIn[T any](fields ...badorm.FieldIdentifier[T]) badorm.DynamicOperator[T] { + return NewMultivalueOperator(sql.ArrayNotIn, sql.Comma, "(", ")", fields...) +} diff --git a/badorm/dynamic/sqlserverdynamic/comparison.go b/badorm/dynamic/sqlserverdynamic/comparison.go new file mode 100644 index 00000000..b94c0d5e --- /dev/null +++ b/badorm/dynamic/sqlserverdynamic/comparison.go @@ -0,0 +1,18 @@ +package sqlserverdynamic + +import ( + "github.com/ditrit/badaas/badorm" + "github.com/ditrit/badaas/badorm/dynamic" + "github.com/ditrit/badaas/badorm/sql" +) + +// Comparison Operators +// ref: https://learn.microsoft.com/en-us/sql/t-sql/language-elements/comparison-operators-transact-sql?view=sql-server-ver16 + +func NotLt[T any](field badorm.FieldIdentifier[T]) badorm.DynamicOperator[T] { + return dynamic.NewValueOperator[T](sql.SQLServerNotLt, field) +} + +func NotGt[T any](field badorm.FieldIdentifier[T]) badorm.DynamicOperator[T] { + return dynamic.NewValueOperator[T](sql.SQLServerNotGt, field) +} diff --git a/badorm/dynamic/value.go b/badorm/dynamic/value.go new file mode 100644 index 00000000..364aff1c --- /dev/null +++ b/badorm/dynamic/value.go @@ -0,0 +1,14 @@ +package dynamic + +import ( + "github.com/ditrit/badaas/badorm" + "github.com/ditrit/badaas/badorm/sql" +) + +func NewValueOperator[T any]( + sqlOperator sql.Operator, + field badorm.FieldIdentifier[T], +) badorm.DynamicOperator[T] { + op := badorm.NewValueOperator[T](sqlOperator, field) + return &op +} diff --git a/badorm/errors.go b/badorm/errors.go new file mode 100644 index 00000000..7774ffb0 --- /dev/null +++ b/badorm/errors.go @@ -0,0 +1,81 @@ +package badorm + +import ( + "errors" + "fmt" + + "github.com/ditrit/badaas/badorm/sql" +) + +// operators + +var ( + ErrValueCantBeNull = errors.New("value to be compared can't be null") + ErrFieldModelNotConcerned = errors.New("field's model is not concerned by the query (not joined)") + ErrJoinMustBeSelected = errors.New("field's model is joined more than once, select which one you want to use with SelectJoin") + ErrNotRelated = errors.New("value type not related with T") +) + +func OperatorError(err error, sqlOperator sql.Operator) error { + return fmt.Errorf("%w; operator: %s", err, sqlOperator.Name()) +} + +func operatorNameError(err error, operatorName string) error { + return fmt.Errorf("%w; operator: %s", err, operatorName) +} + +func notRelatedError[T any](value any, operatorName string) error { + return operatorNameError(fmt.Errorf("%w; type: %T, T: %T", + ErrNotRelated, + value, + *new(T), + ), operatorName) +} + +func fieldModelNotConcernedError(field iFieldIdentifier, sqlOperator sql.Operator) error { + return OperatorError(fmt.Errorf("%w; not concerned model: %s", + ErrFieldModelNotConcerned, + field.GetModelType(), + ), sqlOperator) +} + +func joinMustBeSelectedError(field iFieldIdentifier, sqlOperator sql.Operator) error { + return OperatorError(fmt.Errorf("%w; joined multiple times model: %s", + ErrJoinMustBeSelected, + field.GetModelType(), + ), sqlOperator) +} + +// conditions + +var ( + ErrEmptyConditions = errors.New("condition must have at least one inner condition") + ErrOnlyPreloadsAllowed = errors.New("only conditions that do a preload are allowed") +) + +func conditionOperatorError[TObject Model, TAtribute any](operatorErr error, condition FieldCondition[TObject, TAtribute]) error { + return fmt.Errorf( + "%w; model: %s, field: %s", + operatorErr, + condition.FieldIdentifier.ModelType.String(), + condition.FieldIdentifier.Field, + ) +} + +func emptyConditionsError[T Model](connector sql.Connector) error { + return fmt.Errorf( + "%w; connector: %s; model: %T", + ErrEmptyConditions, + connector.Name(), + *new(T), + ) +} + +func onlyPreloadsAllowedError[T Model](fieldName string) error { + return fmt.Errorf( + "%w; model: %T, field: %s", + ErrOnlyPreloadsAllowed, + *new(T), + fieldName, + ) +} diff --git a/badorm/multitype/errors.go b/badorm/multitype/errors.go new file mode 100644 index 00000000..174c85fa --- /dev/null +++ b/badorm/multitype/errors.go @@ -0,0 +1,31 @@ +package multitype + +import ( + "errors" + "fmt" + "reflect" + + "github.com/ditrit/badaas/badorm" + "github.com/ditrit/badaas/badorm/sql" +) + +var ( + ErrFieldTypeDoesNotMatch = errors.New("field type does not match attribute type") + ErrParamNotValueOrField = errors.New("parameter is neither a value nor a field of the attribute type") +) + +func fieldTypeDoesNotMatchError(fieldType, attributeType reflect.Type, sqlOperator sql.Operator) error { + return badorm.OperatorError(fmt.Errorf("%w; field type: %s, attribute type: %s", + ErrFieldTypeDoesNotMatch, + fieldType, + attributeType, + ), sqlOperator) +} + +func paramNotValueOrField[T any](value any, sqlOperator sql.Operator) error { + return badorm.OperatorError(fmt.Errorf("%w; parameter type: %T, attribute type: %T", + ErrParamNotValueOrField, + value, + *new(T), + ), sqlOperator) +} diff --git a/badorm/multitype/multivalue.go b/badorm/multitype/multivalue.go new file mode 100644 index 00000000..ca149908 --- /dev/null +++ b/badorm/multitype/multivalue.go @@ -0,0 +1,42 @@ +package multitype + +import ( + "github.com/ditrit/badaas/badorm" + "github.com/ditrit/badaas/badorm/sql" +) + +func NewMultivalueOperator[TAttribute, TField any]( + sqlOperator sql.Operator, + sqlConnector sql.Connector, + sqlPrefix, sqlSuffix string, + values ...any, +) badorm.DynamicOperator[TAttribute] { + for _, value := range values { + _, isT1 := value.(TAttribute) + if isT1 { + continue + } + + _, isField := value.(badorm.FieldIdentifier[TField]) + if isField { + invalidOperator := verifyFieldType[TAttribute, TField](sqlOperator) + if invalidOperator != nil { + return invalidOperator + } + + continue + } + + return badorm.NewInvalidOperator[TAttribute]( + paramNotValueOrField[TAttribute](value, sqlOperator), + ) + } + + return &badorm.MultivalueOperator[TAttribute]{ + Values: values, + SQLOperator: sqlOperator, + SQLConnector: sqlConnector, + SQLPrefix: sqlPrefix, + SQLSuffix: sqlSuffix, + } +} diff --git a/badorm/multitype/mysqlmultitype/comparison.go b/badorm/multitype/mysqlmultitype/comparison.go new file mode 100644 index 00000000..7f4f7940 --- /dev/null +++ b/badorm/multitype/mysqlmultitype/comparison.go @@ -0,0 +1,15 @@ +package mysqlmultitype + +import ( + "github.com/ditrit/badaas/badorm" + "github.com/ditrit/badaas/badorm/multitype" + "github.com/ditrit/badaas/badorm/sql" +) + +// Comparison Predicates + +// preferred over eq +// https://dev.mysql.com/doc/refman/8.0/en/comparison-operators.html#operator_equal-to +func IsEqual[TAttribute, TField any](field badorm.FieldIdentifier[TField]) badorm.DynamicOperator[TAttribute] { + return multitype.NewValueOperator[TAttribute, TField](sql.MySQLIsEqual, field) +} diff --git a/badorm/multitype/operators.go b/badorm/multitype/operators.go new file mode 100644 index 00000000..562dc3b8 --- /dev/null +++ b/badorm/multitype/operators.go @@ -0,0 +1,81 @@ +package multitype + +import ( + "github.com/ditrit/badaas/badorm" + "github.com/ditrit/badaas/badorm/sql" +) + +// Comparison Operators +// refs: +// - MySQL: https://dev.mysql.com/doc/refman/8.0/en/comparison-operators.html +// - PostgreSQL: https://www.postgresql.org/docs/current/functions-comparison.html +// - SQLServer: https://learn.microsoft.com/en-us/sql/t-sql/language-elements/comparison-operators-transact-sql?view=sql-server-ver16 +// - SQLite: https://www.sqlite.org/lang_expr.html + +// EqualTo +func Eq[TAttribute, TField any](field badorm.FieldIdentifier[TField]) badorm.DynamicOperator[TAttribute] { + return NewValueOperator[TAttribute, TField](sql.Eq, field) +} + +// NotEqualTo +func NotEq[TAttribute, TField any](field badorm.FieldIdentifier[TField]) badorm.DynamicOperator[TAttribute] { + return NewValueOperator[TAttribute, TField](sql.NotEq, field) +} + +// LessThan +func Lt[TAttribute, TField any](field badorm.FieldIdentifier[TField]) badorm.DynamicOperator[TAttribute] { + return NewValueOperator[TAttribute, TField](sql.Lt, field) +} + +// LessThanOrEqualTo +func LtOrEq[TAttribute, TField any](field badorm.FieldIdentifier[TField]) badorm.DynamicOperator[TAttribute] { + return NewValueOperator[TAttribute, TField](sql.LtOrEq, field) +} + +// GreaterThan +func Gt[TAttribute, TField any](field badorm.FieldIdentifier[TField]) badorm.DynamicOperator[TAttribute] { + return NewValueOperator[TAttribute, TField](sql.Gt, field) +} + +// GreaterThanOrEqualTo +func GtOrEq[TAttribute, TField any](field badorm.FieldIdentifier[TField]) badorm.DynamicOperator[TAttribute] { + return NewValueOperator[TAttribute, TField](sql.GtOrEq, field) +} + +// Comparison Predicates +// refs: +// https://dev.mysql.com/doc/refman/8.0/en/comparison-operators.html +// https://www.postgresql.org/docs/current/functions-comparison.html#FUNCTIONS-COMPARISON-PRED-TABLE + +// Equivalent to v1 < value < v2 +func Between[TAttribute, TField any](v1, v2 any) badorm.DynamicOperator[TAttribute] { + return NewMultivalueOperator[TAttribute, TField](sql.Between, sql.And, "", "", v1, v2) +} + +// Equivalent to NOT (v1 < value < v2) +func NotBetween[TAttribute, TField any](v1, v2 any) badorm.DynamicOperator[TAttribute] { + return NewMultivalueOperator[TAttribute, TField](sql.NotBetween, sql.And, "", "", v1, v2) +} + +// Boolean Comparison Predicates + +// Not supported by: mysql +func IsDistinct[TAttribute, TField any](field badorm.FieldIdentifier[TField]) badorm.DynamicOperator[TAttribute] { + return NewValueOperator[TAttribute, TField](sql.IsDistinct, field) +} + +// Not supported by: mysql +func IsNotDistinct[TAttribute, TField any](field badorm.FieldIdentifier[TField]) badorm.DynamicOperator[TAttribute] { + return NewValueOperator[TAttribute, TField](sql.IsNotDistinct, field) +} + +// Row and Array Comparisons + +// https://dev.mysql.com/doc/refman/8.0/en/comparison-operators.html#operator_in +func ArrayIn[TAttribute, TField any](values ...any) badorm.DynamicOperator[TAttribute] { + return NewMultivalueOperator[TAttribute, TField](sql.ArrayIn, sql.Comma, "(", ")", values...) +} + +func ArrayNotIn[TAttribute, TField any](values ...any) badorm.DynamicOperator[TAttribute] { + return NewMultivalueOperator[TAttribute, TField](sql.ArrayNotIn, sql.Comma, "(", ")", values...) +} diff --git a/badorm/multitype/sqlservermultitype/comparison.go b/badorm/multitype/sqlservermultitype/comparison.go new file mode 100644 index 00000000..b361c9a3 --- /dev/null +++ b/badorm/multitype/sqlservermultitype/comparison.go @@ -0,0 +1,18 @@ +package sqlservermultitype + +import ( + "github.com/ditrit/badaas/badorm" + "github.com/ditrit/badaas/badorm/multitype" + "github.com/ditrit/badaas/badorm/sql" +) + +// Comparison Operators +// ref: https://learn.microsoft.com/en-us/sql/t-sql/language-elements/comparison-operators-transact-sql?view=sql-server-ver16 + +func NotLt[TAttribute, TField any](field badorm.FieldIdentifier[TField]) badorm.DynamicOperator[TAttribute] { + return multitype.NewValueOperator[TAttribute, TField](sql.SQLServerNotLt, field) +} + +func NotGt[TAttribute, TField any](field badorm.FieldIdentifier[TField]) badorm.DynamicOperator[TAttribute] { + return multitype.NewValueOperator[TAttribute, TField](sql.SQLServerNotGt, field) +} diff --git a/badorm/multitype/value.go b/badorm/multitype/value.go new file mode 100644 index 00000000..9a8622d2 --- /dev/null +++ b/badorm/multitype/value.go @@ -0,0 +1,57 @@ +package multitype + +import ( + "database/sql" + "reflect" + + "github.com/elliotchance/pie/v2" + "gorm.io/gorm" + + "github.com/ditrit/badaas/badorm" + badormSQL "github.com/ditrit/badaas/badorm/sql" +) + +var nullableTypes = []reflect.Type{ + reflect.TypeOf(sql.NullBool{}), + reflect.TypeOf(sql.NullByte{}), + reflect.TypeOf(sql.NullFloat64{}), + reflect.TypeOf(sql.NullInt16{}), + reflect.TypeOf(sql.NullInt32{}), + reflect.TypeOf(sql.NullInt64{}), + reflect.TypeOf(sql.NullString{}), + reflect.TypeOf(sql.NullTime{}), + reflect.TypeOf(gorm.DeletedAt{}), +} + +func isNullable(fieldType reflect.Type) bool { + return pie.Contains(nullableTypes, fieldType) +} + +func verifyFieldType[TAttribute, TField any](sqlOperator badormSQL.Operator) badorm.DynamicOperator[TAttribute] { + attributeType := reflect.TypeOf(*new(TAttribute)) + fieldType := reflect.TypeOf(*new(TField)) + + if fieldType != attributeType && + !((isNullable(fieldType) && fieldType.Field(0).Type == attributeType) || + (isNullable(attributeType) && attributeType.Field(0).Type == fieldType)) { + return badorm.NewInvalidOperator[TAttribute]( + fieldTypeDoesNotMatchError(fieldType, attributeType, sqlOperator), + ) + } + + return nil +} + +func NewValueOperator[TAttribute, TField any]( + sqlOperator badormSQL.Operator, + field badorm.FieldIdentifier[TField], +) badorm.DynamicOperator[TAttribute] { + invalidOperator := verifyFieldType[TAttribute, TField](sqlOperator) + if invalidOperator != nil { + return invalidOperator + } + + op := badorm.NewValueOperator[TAttribute](sqlOperator, field) + + return &op +} diff --git a/badorm/multivalueOperator.go b/badorm/multivalueOperator.go new file mode 100644 index 00000000..a7af4afd --- /dev/null +++ b/badorm/multivalueOperator.go @@ -0,0 +1,115 @@ +package badorm + +import ( + "fmt" + "strings" + + "github.com/elliotchance/pie/v2" + + "github.com/ditrit/badaas/badorm/sql" +) + +// Operator that compares the value of the column against multiple values +// Example: value IN (v1, v2, v3, ..., vN) +type MultivalueOperator[T any] struct { + Values []any // the values to compare with + SQLOperator sql.Operator // the operator used to compare, example: IN + SQLConnector sql.Connector // the connector between values, example: ', ' + SQLPrefix string // something to put before the values, example: ( + SQLSuffix string // something to put after the values, example: ) + JoinNumbers map[uint]int // join number to use in each value +} + +func (operator MultivalueOperator[T]) InterfaceVerificationMethod(_ T) { + // This method is necessary to get the compiler to verify + // that an object is of type Operator[T] +} + +// Allows to choose which number of join use +// for the value in position "valueNumber" +// when the value is a field and its model is joined more than once. +// Does nothing if the valueNumber is bigger than the amount of values. +func (operator *MultivalueOperator[T]) SelectJoin(valueNumber, joinNumber uint) DynamicOperator[T] { + joinNumbers := operator.JoinNumbers + if joinNumbers == nil { + joinNumbers = map[uint]int{} + } + + joinNumbers[valueNumber] = int(joinNumber) + operator.JoinNumbers = joinNumbers + + return operator +} + +func (operator MultivalueOperator[T]) ToSQL(query *Query, columnName string) (string, []any, error) { + placeholderList := []string{} + values := []any{} + + for i, value := range operator.Values { + field, isField := value.(iFieldIdentifier) + if isField { + joinNumber, isPresent := operator.JoinNumbers[uint(i)] + if !isPresent { + joinNumber = undefinedJoinNumber + } + + // if it is a field, add the field column to the query + modelTable, err := getModelTable(query, field, joinNumber, operator.SQLOperator) + if err != nil { + return "", nil, err + } + + placeholderList = append(placeholderList, field.ColumnSQL(query, modelTable)) + } else { + // if it is not a field, it a value, ass the placeholder ? and the value to the list + placeholderList = append(placeholderList, "?") + values = append(values, value) + } + } + + placeholders := strings.Join(placeholderList, " "+operator.SQLConnector.String()+" ") + + return fmt.Sprintf( + "%s %s %s"+placeholders+"%s", + columnName, + operator.SQLOperator, + operator.SQLPrefix, + operator.SQLSuffix, + ), values, nil +} + +func getModelTable(query *Query, field iFieldIdentifier, joinNumber int, sqlOperator sql.Operator) (Table, error) { + modelTables := query.GetTables(field.GetModelType()) + if modelTables == nil { + return Table{}, fieldModelNotConcernedError(field, sqlOperator) + } + + if len(modelTables) == 1 { + return modelTables[0], nil + } + + if joinNumber == undefinedJoinNumber { + return Table{}, joinMustBeSelectedError(field, sqlOperator) + } + + return modelTables[joinNumber], nil +} + +func NewMultivalueOperator[T any]( + sqlOperator sql.Operator, + sqlConnector sql.Connector, + sqlPrefix, sqlSuffix string, + values ...T, +) Operator[T] { + valuesAny := pie.Map(values, func(value T) any { + return value + }) + + return &MultivalueOperator[T]{ + Values: valuesAny, + SQLOperator: sqlOperator, + SQLConnector: sqlConnector, + SQLPrefix: sqlPrefix, + SQLSuffix: sqlSuffix, + } +} diff --git a/badorm/mysql/comparison.go b/badorm/mysql/comparison.go new file mode 100644 index 00000000..1f1f5bb6 --- /dev/null +++ b/badorm/mysql/comparison.go @@ -0,0 +1,32 @@ +package mysql + +import ( + "database/sql" + + "github.com/ditrit/badaas/badorm" + badormSQL "github.com/ditrit/badaas/badorm/sql" +) + +// Comparison Predicates + +// preferred over eq +// https://dev.mysql.com/doc/refman/8.0/en/comparison-operators.html#operator_equal-to +func IsEqual[T any](value T) badorm.ValueOperator[T] { + return badorm.NewValueOperator[T](badormSQL.MySQLIsEqual, value) +} + +// Pattern Matching + +// As an extension to standard SQL, MySQL permits LIKE on numeric expressions. +func Like[T string | sql.NullString | + int | int8 | int16 | int32 | int64 | + uint | uint8 | uint16 | uint32 | uint64 | + float32 | float64](pattern string, +) badorm.ValueOperator[T] { + return badorm.NewValueOperator[T](badormSQL.Like, pattern) +} + +// ref: https://dev.mysql.com/doc/refman/8.0/en/regexp.html#operator_regexp +func RegexP[T string | sql.NullString](pattern string) badorm.Operator[T] { + return badorm.NewMustBePOSIXValueOperator[T](badormSQL.MySQLRegexp, pattern) +} diff --git a/badorm/mysql/logical.go b/badorm/mysql/logical.go new file mode 100644 index 00000000..1ed78ce3 --- /dev/null +++ b/badorm/mysql/logical.go @@ -0,0 +1,10 @@ +package mysql + +import ( + "github.com/ditrit/badaas/badorm" + "github.com/ditrit/badaas/badorm/sql" +) + +func Xor[T badorm.Model](conditions ...badorm.WhereCondition[T]) badorm.WhereCondition[T] { + return badorm.NewConnectionCondition(sql.MySQLXor, conditions...) +} diff --git a/badorm/operator.go b/badorm/operator.go new file mode 100644 index 00000000..e0f378bd --- /dev/null +++ b/badorm/operator.go @@ -0,0 +1,73 @@ +package badorm + +import ( + "fmt" +) + +type Operator[T any] interface { + // Transform the Operator to a SQL string and a list of values to use in the query + // columnName is used by the operator to determine which is the objective column. + ToSQL(query *Query, columnName string) (string, []any, error) + + // This method is necessary to get the compiler to verify + // that an object is of type Operator[T], + // since if no method receives by parameter a type T, + // any other Operator[T2] would also be considered a Operator[T]. + InterfaceVerificationMethod(T) +} + +type DynamicOperator[T any] interface { + Operator[T] + + // Allows to choose which number of join use + // for the value in position "valueNumber" + // when the value is a field and its model is joined more than once. + // Does nothing if the valueNumber is bigger than the amount of values. + SelectJoin(valueNumber, joinNumber uint) DynamicOperator[T] +} + +// Operator that verifies a predicate +// Example: value IS TRUE +type PredicateOperator[T any] struct { + SQLOperator string +} + +func (expr PredicateOperator[T]) InterfaceVerificationMethod(_ T) { + // This method is necessary to get the compiler to verify + // that an object is of type Operator[T] +} + +func (expr PredicateOperator[T]) ToSQL(_ *Query, columnName string) (string, []any, error) { + return fmt.Sprintf("%s %s", columnName, expr.SQLOperator), []any{}, nil +} + +func NewPredicateOperator[T any](sqlOperator string) PredicateOperator[T] { + return PredicateOperator[T]{ + SQLOperator: sqlOperator, + } +} + +// Operator used to return an error +type InvalidOperator[T any] struct { + Err error +} + +func (expr InvalidOperator[T]) InterfaceVerificationMethod(_ T) { + // This method is necessary to get the compiler to verify + // that an object is of type Operator[T] +} + +// InvalidOperator has SelectJoin to implement DynamicOperator +func (expr InvalidOperator[T]) SelectJoin(_, _ uint) DynamicOperator[T] { + return expr +} + +func (expr InvalidOperator[T]) ToSQL(_ *Query, _ string) (string, []any, error) { + return "", nil, expr.Err +} + +func NewInvalidOperator[T any](err error) InvalidOperator[T] { + return InvalidOperator[T]{ + Err: err, + } +} diff --git a/badorm/operators.go b/badorm/operators.go new file mode 100644 index 00000000..e62119cc --- /dev/null +++ b/badorm/operators.go @@ -0,0 +1,247 @@ +package badorm + +import ( + "database/sql" + "database/sql/driver" + "reflect" + + "github.com/elliotchance/pie/v2" + + badormSQL "github.com/ditrit/badaas/badorm/sql" +) + +// Comparison Operators +// refs: +// - MySQL: https://dev.mysql.com/doc/refman/8.0/en/comparison-operators.html +// - PostgreSQL: https://www.postgresql.org/docs/current/functions-comparison.html +// - SQLServer: https://learn.microsoft.com/en-us/sql/t-sql/language-elements/comparison-operators-transact-sql?view=sql-server-ver16 +// - SQLite: https://www.sqlite.org/lang_expr.html + +// EqualTo +// EqOrIsNull must be used in cases where value can be NULL +func Eq[T any](value T) Operator[T] { + return NewCantBeNullValueOperator[T](badormSQL.Eq, value) +} + +// if value is not NULL returns a Eq operator +// but if value is NULL returns a IsNull operator +// this must be used because ANSI SQL-92 standard defines: +// NULL = NULL evaluates to unknown, which is later considered a false +// +// this behavior can be also avoided in other ways: +// - in SQLServer you can: +// ** set ansi_nulls setting to off and use sqlserver.EqNullable +// ** use the IS NOT DISTINCT operator (implemented in IsNotDistinct) +// - in MySQL you can use the equal_to operator (implemented in mysql.IsEqual) +// - in PostgreSQL you can use the IS NOT DISTINCT operator (implemented in IsNotDistinct) +// - in SQLite you can use the IS NOT DISTINCT operator (implemented in IsNotDistinct) +func EqOrIsNull[T any](value any) Operator[T] { + return operatorFromValueOrNil[T](value, "EqOrIsNull", Eq[T], IsNull[T]()) +} + +// NotEqualTo +// NotEqOrNotIsNull must be used in cases where value can be NULL +func NotEq[T any](value T) Operator[T] { + return NewCantBeNullValueOperator[T](badormSQL.NotEq, value) +} + +// if value is not NULL returns a NotEq operator +// but if value is NULL returns a IsNotNull operator +// this must be used because ANSI SQL-92 standard defines: +// NULL = NULL evaluates to unknown, which is later considered a false +// +// this behavior can be also avoided in other ways: +// - in SQLServer you can: +// ** set ansi_nulls setting to off and use sqlserver.NotEqNullable +// ** use the IS DISTINCT operator (implemented in IsDistinct) +// - in PostgreSQL you can use the IS DISTINCT operator (implemented in IsDistinct) +// - in SQLite you can use the IS DISTINCT operator (implemented in IsDistinct) +func NotEqOrIsNotNull[T any](value any) Operator[T] { + return operatorFromValueOrNil[T](value, "NotEqOrIsNotNull", NotEq[T], IsNotNull[T]()) +} + +func operatorFromValueOrNil[T any]( + value any, operatorName string, + notNilFunc func(T) Operator[T], nilOperator Operator[T], +) Operator[T] { + if value == nil { + return nilOperator + } + + valueTPointer, isTPointer := value.(*T) + if isTPointer { + if valueTPointer == nil { + return nilOperator + } + + return notNilFunc(*valueTPointer) + } + + valueT, isT := value.(T) + if isT { + if mapsToNull(value) { + return nilOperator + } + + return notNilFunc(valueT) + } + + return NewInvalidOperator[T]( + notRelatedError[T](value, operatorName), + ) +} + +func mapsToNull(value any) bool { + reflectVal := reflect.ValueOf(value) + isNullableKind := pie.Contains(nullableKinds, reflectVal.Kind()) + // avoid nil is not nil behavior of go + if isNullableKind && reflectVal.IsNil() { + return true + } + + valuer, isValuer := value.(driver.Valuer) + if isValuer { + valuerValue, err := valuer.Value() + if err == nil && valuerValue == nil { + return true + } + } + + return false +} + +// LessThan +func Lt[T any](value T) Operator[T] { + return NewCantBeNullValueOperator[T](badormSQL.Lt, value) +} + +// LessThanOrEqualTo +func LtOrEq[T any](value T) Operator[T] { + return NewCantBeNullValueOperator[T](badormSQL.LtOrEq, value) +} + +// GreaterThan +func Gt[T any](value T) Operator[T] { + return NewCantBeNullValueOperator[T](badormSQL.Gt, value) +} + +// GreaterThanOrEqualTo +func GtOrEq[T any](value T) Operator[T] { + return NewCantBeNullValueOperator[T](badormSQL.GtOrEq, value) +} + +// Comparison Predicates +// refs: +// https://dev.mysql.com/doc/refman/8.0/en/comparison-operators.html +// https://www.postgresql.org/docs/current/functions-comparison.html#FUNCTIONS-COMPARISON-PRED-TABLE + +func IsNull[T any]() PredicateOperator[T] { + return NewPredicateOperator[T]("IS NULL") +} + +func IsNotNull[T any]() PredicateOperator[T] { + return NewPredicateOperator[T]("IS NOT NULL") +} + +// Equivalent to v1 < attribute < v2 +func Between[T any](v1 T, v2 T) Operator[T] { + return NewMultivalueOperator(badormSQL.Between, badormSQL.And, "", "", v1, v2) +} + +// Equivalent to NOT (v1 < attribute < v2) +func NotBetween[T any](v1 T, v2 T) Operator[T] { + return NewMultivalueOperator(badormSQL.NotBetween, badormSQL.And, "", "", v1, v2) +} + +// Boolean Comparison Predicates + +// Not supported by: sqlserver +func IsTrue[T bool | sql.NullBool]() PredicateOperator[T] { + return NewPredicateOperator[T]("IS TRUE") +} + +// Not supported by: sqlserver +func IsNotTrue[T bool | sql.NullBool]() PredicateOperator[T] { + return NewPredicateOperator[T]("IS NOT TRUE") +} + +// Not supported by: sqlserver +func IsFalse[T bool | sql.NullBool]() PredicateOperator[T] { + return NewPredicateOperator[T]("IS FALSE") +} + +// Not supported by: sqlserver +func IsNotFalse[T bool | sql.NullBool]() PredicateOperator[T] { + return NewPredicateOperator[T]("IS NOT FALSE") +} + +// Not supported by: sqlserver, sqlite +func IsUnknown[T bool | sql.NullBool]() PredicateOperator[T] { + return NewPredicateOperator[T]("IS UNKNOWN") +} + +// Not supported by: sqlserver, sqlite +func IsNotUnknown[T bool | sql.NullBool]() PredicateOperator[T] { + return NewPredicateOperator[T]("IS NOT UNKNOWN") +} + +// Not supported by: mysql +func IsDistinct[T any](value T) ValueOperator[T] { + return NewValueOperator[T](badormSQL.IsDistinct, value) +} + +// Not supported by: mysql +func IsNotDistinct[T any](value T) ValueOperator[T] { + return NewValueOperator[T](badormSQL.IsNotDistinct, value) +} + +// Pattern Matching + +type LikeOperator[T string | sql.NullString] struct { + ValueOperator[T] +} + +func NewLikeOperator[T string | sql.NullString](pattern string, sqlOperator badormSQL.Operator) LikeOperator[T] { + return LikeOperator[T]{ + ValueOperator: NewValueOperator[T](sqlOperator, pattern), + } +} + +func (expr LikeOperator[T]) Escape(escape rune) ValueOperator[T] { + return *expr.AddOperation(badormSQL.Escape, string(escape)) +} + +// Pattern in all databases: +// - An underscore (_) in pattern stands for (matches) any single character. +// - A percent sign (%) matches any sequence of zero or more characters. +// +// Additionally in SQLServer: +// - Square brackets ([ ]) matches any single character within the specified range ([a-f]) or set ([abcdef]). +// - [^] matches any single character not within the specified range ([^a-f]) or set ([^abcdef]). +// +// WARNINGS: +// - SQLite: LIKE is case-insensitive unless case_sensitive_like pragma (https://www.sqlite.org/pragma.html#pragma_case_sensitive_like) is true. +// - SQLServer, MySQL: the case-sensitivity depends on the collation used in compared column. +// - PostgreSQL: LIKE is always case-sensitive, if you want case-insensitive use the ILIKE operator (implemented in psql.ILike) +// +// refs: +// - mysql: https://dev.mysql.com/doc/refman/8.0/en/string-comparison-functions.html#operator_like +// - postgresql: https://www.postgresql.org/docs/current/functions-matching.html#FUNCTIONS-LIKE +// - sqlserver: https://learn.microsoft.com/en-us/sql/t-sql/language-elements/like-transact-sql?view=sql-server-ver16 +// - sqlite: https://www.sqlite.org/lang_expr.html#like +func Like[T string | sql.NullString](pattern string) LikeOperator[T] { + return NewLikeOperator[T](pattern, badormSQL.Like) +} + +// Row and Array Comparisons + +// https://dev.mysql.com/doc/refman/8.0/en/comparison-operators.html#operator_in +func ArrayIn[T any](values ...T) Operator[T] { + return NewMultivalueOperator(badormSQL.ArrayIn, badormSQL.Comma, "(", ")", values...) +} + +func ArrayNotIn[T any](values ...T) Operator[T] { + return NewMultivalueOperator(badormSQL.ArrayNotIn, badormSQL.Comma, "(", ")", values...) +} + +// TODO Subquery Operators diff --git a/badorm/preload.go b/badorm/preload.go new file mode 100644 index 00000000..4dab56cf --- /dev/null +++ b/badorm/preload.go @@ -0,0 +1,41 @@ +package badorm + +import "errors" + +var ErrRelationNotLoaded = errors.New("relation not loaded") + +func VerifyStructLoaded[T Model](toVerify *T) (*T, error) { + if toVerify == nil || !(*toVerify).IsLoaded() { + return nil, ErrRelationNotLoaded + } + + return toVerify, nil +} + +func VerifyPointerLoaded[TModel Model, TID ModelID](id *TID, toVerify *TModel) (*TModel, error) { + // when the pointer to the object is nil + // but the id pointer indicates that the relation is not nil + if id != nil && toVerify == nil { + return nil, ErrRelationNotLoaded + } + + return toVerify, nil +} + +func VerifyPointerWithIDLoaded[TModel Model, TID ModelID](id TID, toVerify *TModel) (*TModel, error) { + // when the pointer to the object is nil + // but the id indicates that the relation is not nil + if !id.IsNil() && toVerify == nil { + return nil, ErrRelationNotLoaded + } + + return toVerify, nil +} + +func VerifyCollectionLoaded[T Model](collection *[]T) ([]T, error) { + if collection == nil { + return nil, ErrRelationNotLoaded + } + + return *collection, nil +} diff --git a/badorm/psql/comparison.go b/badorm/psql/comparison.go new file mode 100644 index 00000000..37bb5c24 --- /dev/null +++ b/badorm/psql/comparison.go @@ -0,0 +1,29 @@ +package psql + +import ( + "database/sql" + + "github.com/ditrit/badaas/badorm" + badormSQL "github.com/ditrit/badaas/badorm/sql" +) + +// Pattern Matching + +func ILike[T string | sql.NullString](pattern string) badorm.ValueOperator[T] { + return badorm.NewValueOperator[T](badormSQL.PostgreSQLILike, pattern) +} + +// ref: https://www.postgresql.org/docs/current/functions-matching.html#FUNCTIONS-SIMILARTO-REGEXP +func SimilarTo[T string | sql.NullString](pattern string) badorm.ValueOperator[T] { + return badorm.NewValueOperator[T](badormSQL.PostgreSQLSimilarTo, pattern) +} + +// ref: https://www.postgresql.org/docs/current/functions-matching.html#FUNCTIONS-POSIX-REGEXP +func POSIXMatch[T string | sql.NullString](pattern string) badorm.Operator[T] { + return badorm.NewMustBePOSIXValueOperator[T](badormSQL.PostgreSQLPosixMatch, pattern) +} + +// ref: https://www.postgresql.org/docs/current/functions-matching.html#FUNCTIONS-POSIX-REGEXP +func POSIXIMatch[T string | sql.NullString](pattern string) badorm.Operator[T] { + return badorm.NewMustBePOSIXValueOperator[T](badormSQL.PostgreSQLPosixIMatch, pattern) +} diff --git a/badorm/query.go b/badorm/query.go new file mode 100644 index 00000000..209c0c87 --- /dev/null +++ b/badorm/query.go @@ -0,0 +1,181 @@ +package badorm + +import ( + "fmt" + "reflect" + "sync" + + "gorm.io/gorm" + "gorm.io/gorm/schema" +) + +type Table struct { + Name string + Alias string + Initial bool +} + +// Returns true if the Table is the initial table in a query +func (t Table) IsInitial() bool { + return t.Initial +} + +// Returns the related Table corresponding to the model +func (t Table) DeliverTable(query *Query, model Model, relationName string) (Table, error) { + // get the name of the table for the model + tableName, err := GetTableName(query.gormDB, model) + if err != nil { + return Table{}, err + } + + // add a suffix to avoid tables with the same name when joining + // the same table more than once + tableAlias := relationName + if !t.IsInitial() { + tableAlias = t.Alias + "__" + relationName + } + + return Table{ + Name: tableName, + Alias: tableAlias, + Initial: false, + }, nil +} + +type iFieldIdentifier interface { + ColumnName(query *Query, table Table) string + ColumnSQL(query *Query, table Table) string + GetModelType() reflect.Type +} + +type FieldIdentifier[T any] struct { + Column string + Field string + ColumnPrefix string + ModelType reflect.Type +} + +func (fieldID FieldIdentifier[T]) GetModelType() reflect.Type { + return fieldID.ModelType +} + +// Returns the name of the column in which the field is saved in the table +func (fieldID FieldIdentifier[T]) ColumnName(query *Query, table Table) string { + columnName := fieldID.Column + if columnName == "" { + columnName = query.ColumnName(table, fieldID.Field) + } + + // add column prefix and table name once we know the column name + return fieldID.ColumnPrefix + columnName +} + +// Returns the SQL to get the value of the field in the table +func (fieldID FieldIdentifier[T]) ColumnSQL(query *Query, table Table) string { + return table.Alias + "." + fieldID.ColumnName(query, table) +} + +type Query struct { + gormDB *gorm.DB + concernedModels map[reflect.Type][]Table +} + +func (query *Query) AddSelect(table Table, fieldID iFieldIdentifier) { + columnName := fieldID.ColumnName(query, table) + + query.gormDB.Statement.Selects = append( + query.gormDB.Statement.Selects, + fmt.Sprintf( + "%[1]s.%[2]s AS \"%[1]s__%[2]s\"", // name used by gorm to load the fields inside the models + table.Alias, + columnName, + ), + ) +} + +func (query *Query) Preload(preloadQuery string, args ...interface{}) { + query.gormDB = query.gormDB.Preload(preloadQuery, args...) +} + +func (query *Query) Unscoped() { + query.gormDB = query.gormDB.Unscoped() +} + +func (query *Query) Where(whereQuery interface{}, args ...interface{}) { + query.gormDB = query.gormDB.Where(whereQuery, args...) +} + +func (query *Query) Joins(joinQuery string, args ...interface{}) { + query.gormDB = query.gormDB.Joins(joinQuery, args...) +} + +func (query *Query) Find(dest interface{}, conds ...interface{}) error { + query.gormDB = query.gormDB.Find(dest, conds...) + + return query.gormDB.Error +} + +func (query *Query) AddConcernedModel(model Model, table Table) { + tableList, isPresent := query.concernedModels[reflect.TypeOf(model)] + if !isPresent { + query.concernedModels[reflect.TypeOf(model)] = []Table{table} + } else { + tableList = append(tableList, table) + query.concernedModels[reflect.TypeOf(model)] = tableList + } +} + +func (query *Query) GetTables(modelType reflect.Type) []Table { + tableList, isPresent := query.concernedModels[modelType] + if !isPresent { + return nil + } + + return tableList +} + +func (query Query) ColumnName(table Table, fieldName string) string { + return query.gormDB.NamingStrategy.ColumnName(table.Name, fieldName) +} + +func NewQuery[T Model](db *gorm.DB, conditions []Condition[T]) (*Query, error) { + model := *new(T) + + initialTableName, err := GetTableName(db, model) + if err != nil { + return nil, err + } + + initialTable := Table{ + Name: initialTableName, + Alias: initialTableName, + Initial: true, + } + + query := &Query{ + gormDB: db.Select(initialTableName + ".*"), + concernedModels: map[reflect.Type][]Table{}, + } + query.AddConcernedModel(model, initialTable) + + for _, condition := range conditions { + err = condition.ApplyTo(query, initialTable) + if err != nil { + return nil, err + } + } + + return query, nil +} + +// Get the name of the table in "db" in which the data for "model" is saved +// returns error is table name can not be found by gorm, +// probably because the type of "model" is not registered using AddModel +func GetTableName(db *gorm.DB, model any) (string, error) { + schemaName, err := schema.Parse(model, &sync.Map{}, db.NamingStrategy) + if err != nil { + return "", err + } + + return schemaName.Table, nil +} diff --git a/badorm/sql/connectors.go b/badorm/sql/connectors.go new file mode 100644 index 00000000..67c04925 --- /dev/null +++ b/badorm/sql/connectors.go @@ -0,0 +1,36 @@ +package sql + +type Connector uint + +const ( + And Connector = iota + Or + Not + Comma + // mysql + MySQLXor +) + +func (con Connector) String() string { + return connectorToSQL[con] +} + +var connectorToSQL = map[Connector]string{ + And: "AND", + Or: "OR", + Not: "NOT", + Comma: ",", + MySQLXor: "XOR", +} + +func (con Connector) Name() string { + return connectorToName[con] +} + +var connectorToName = map[Connector]string{ + And: "And", + Or: "Or", + Not: "Not", + Comma: "Comma", + MySQLXor: "mysql.Xor", +} diff --git a/badorm/sql/operators.go b/badorm/sql/operators.go new file mode 100644 index 00000000..c90b71f5 --- /dev/null +++ b/badorm/sql/operators.go @@ -0,0 +1,100 @@ +package sql + +type Operator uint + +const ( + Eq Operator = iota + NotEq + Lt + LtOrEq + Gt + GtOrEq + Between + NotBetween + IsDistinct // Not supported by: mysql + IsNotDistinct // Not supported by: mysql + Like + Escape + // mysql + MySQLIsEqual + MySQLRegexp + // sqlserver + SQLServerNotLt + SQLServerNotGt + // postgresql + PostgreSQLILike + PostgreSQLSimilarTo + PostgreSQLPosixMatch + PostgreSQLPosixIMatch + // sqlite + SQLiteGlob + // shared + ArrayIn + ArrayNotIn +) + +// alias +const ( + SQLServerEqNullable = Eq + SQLServerNotEqNullable = NotEq +) + +func (op Operator) String() string { + return operatorToSQL[op] +} + +var operatorToSQL = map[Operator]string{ + Eq: "=", + NotEq: "<>", + Lt: "<", + LtOrEq: "<=", + Gt: ">", + GtOrEq: ">=", + Between: "BETWEEN", + NotBetween: "NOT BETWEEN", + IsDistinct: "IS DISTINCT FROM", + IsNotDistinct: "IS NOT DISTINCT FROM", + Like: "LIKE", + Escape: "ESCAPE", + MySQLIsEqual: "<=>", + MySQLRegexp: "REGEXP", + SQLServerNotLt: "!<", + SQLServerNotGt: "!>", + PostgreSQLILike: "ILIKE", + PostgreSQLSimilarTo: "SIMILAR TO", + PostgreSQLPosixMatch: "~", + PostgreSQLPosixIMatch: "~*", + SQLiteGlob: "GLOB", + ArrayIn: "IN", + ArrayNotIn: "NOT IN", +} + +func (op Operator) Name() string { + return operatorToName[op] +} + +var operatorToName = map[Operator]string{ + Eq: "Eq", + NotEq: "NotEq", + Lt: "Lt", + LtOrEq: "LtOrEq", + Gt: "Gt", + GtOrEq: "GtOrEq", + Between: "Between", + NotBetween: "NotBetween", + IsDistinct: "IsDistinct", + IsNotDistinct: "IsNotDistinct", + Like: "Like", + Escape: "Escape", + MySQLIsEqual: "mysql.IsEqual", + MySQLRegexp: "mysql.Regexp", + SQLServerNotLt: "sqlserver.NotLt", + SQLServerNotGt: "sqlserver.NotGt", + PostgreSQLILike: "psql.ILike", + PostgreSQLSimilarTo: "psql.SimilarTo", + PostgreSQLPosixMatch: "psql.PosixMatch", + PostgreSQLPosixIMatch: "psql.PosixIMatch", + SQLiteGlob: "sqlite.Glob", + ArrayIn: "ArrayIn", + ArrayNotIn: "ArrayNotIn", +} diff --git a/badorm/sqlite/comparison.go b/badorm/sqlite/comparison.go new file mode 100644 index 00000000..f8ffbbd8 --- /dev/null +++ b/badorm/sqlite/comparison.go @@ -0,0 +1,13 @@ +package sqlite + +import ( + "database/sql" + + "github.com/ditrit/badaas/badorm" + badormSQL "github.com/ditrit/badaas/badorm/sql" +) + +// ref: https://www.sqlite.org/lang_expr.html#like +func Glob[T string | sql.NullString](pattern string) badorm.Operator[T] { + return badorm.NewMustBePOSIXValueOperator[T](badormSQL.SQLiteGlob, pattern) +} diff --git a/badorm/sqlserver/comparison.go b/badorm/sqlserver/comparison.go new file mode 100644 index 00000000..198dbf2b --- /dev/null +++ b/badorm/sqlserver/comparison.go @@ -0,0 +1,29 @@ +package sqlserver + +import ( + "github.com/ditrit/badaas/badorm" + "github.com/ditrit/badaas/badorm/sql" +) + +// Comparison Operators +// ref: https://learn.microsoft.com/en-us/sql/t-sql/language-elements/comparison-operators-transact-sql?view=sql-server-ver16 + +// EqNullable is the same as badorm.Eq but it supports value to be NULL +// ansi_nulls must be set to off to avoid the NULL = NULL: unknown problem +func EqNullable[T any](value T) badorm.Operator[T] { + return badorm.NewValueOperator[T](sql.SQLServerEqNullable, value) +} + +// NotEqNullable is the same as badorm.NotEq but it supports value to be NULL +// ansi_nulls must be set to off to avoid the NULL = NULL: unknown problem +func NotEqNullable[T any](value T) badorm.Operator[T] { + return badorm.NewValueOperator[T](sql.SQLServerNotEqNullable, value) +} + +func NotLt[T any](value T) badorm.Operator[T] { + return badorm.NewCantBeNullValueOperator[T](sql.SQLServerNotLt, value) +} + +func NotGt[T any](value T) badorm.Operator[T] { + return badorm.NewCantBeNullValueOperator[T](sql.SQLServerNotGt, value) +} diff --git a/badorm/unsafe/ModuleFx.go b/badorm/unsafe/ModuleFx.go new file mode 100644 index 00000000..ab1a6476 --- /dev/null +++ b/badorm/unsafe/ModuleFx.go @@ -0,0 +1,57 @@ +package unsafe + +import ( + "fmt" + "log" + "reflect" + + "go.uber.org/fx" + + "github.com/ditrit/badaas/badorm" +) + +func GetCRUDServiceModule[T badorm.Model]() fx.Option { + entity := *new(T) + + moduleName := fmt.Sprintf( + "unsafe.%TCRUDServiceModule", + entity, + ) + + kind := badorm.GetBaDORMModelKind(entity) + switch kind { + case badorm.KindUUIDModel: + return fx.Module( + moduleName, + // models + fx.Invoke(addUnsafeModel[T]), + // repository + fx.Provide(NewCRUDRepository[T, badorm.UUID]), + // service + fx.Provide(NewCRUDUnsafeService[T, badorm.UUID]), + ) + case badorm.KindUIntModel: + return fx.Module( + moduleName, + // models + fx.Invoke(addUnsafeModel[T]), + // repository + fx.Provide(NewCRUDRepository[T, badorm.UIntID]), + // service + fx.Provide(NewCRUDUnsafeService[T, badorm.UIntID]), + ) + case badorm.KindNotBaDORMModel: + log.Printf("type %T is not a BaDORM Module\n", entity) + return fx.Invoke(badorm.FailNotBadORMModule()) + } + + return fx.Invoke(badorm.FailNotBadORMModule()) +} + +var modelsMapping = map[string]reflect.Type{} + +func addUnsafeModel[T badorm.Model]() { + entity := *new(T) + entityType := reflect.TypeOf(entity) + modelsMapping[entityType.Name()] = entityType +} diff --git a/badorm/unsafe/condition.go b/badorm/unsafe/condition.go new file mode 100644 index 00000000..61b22778 --- /dev/null +++ b/badorm/unsafe/condition.go @@ -0,0 +1,43 @@ +package unsafe + +import ( + "fmt" + + "github.com/ditrit/badaas/badorm" +) + +// Condition that can be used to express conditions that are not supported (yet?) by BaDORM +// Example: table1.columnX = table2.columnY +type Condition[T badorm.Model] struct { + SQLCondition string + Values []any +} + +func (condition Condition[T]) InterfaceVerificationMethod(_ T) { + // This method is necessary to get the compiler to verify + // that an object is of type Condition[T] +} + +func (condition Condition[T]) ApplyTo(query *badorm.Query, table badorm.Table) error { + return badorm.ApplyWhereCondition[T](condition, query, table) +} + +func (condition Condition[T]) GetSQL(_ *badorm.Query, table badorm.Table) (string, []any, error) { + return fmt.Sprintf( + condition.SQLCondition, + table.Alias, + ), condition.Values, nil +} + +func (condition Condition[T]) AffectsDeletedAt() bool { + return false +} + +// Condition that can be used to express conditions that are not supported (yet?) by BaDORM +// Example: table1.columnX = table2.columnY +func NewCondition[T badorm.Model](condition string, values ...any) Condition[T] { + return Condition[T]{ + SQLCondition: condition, + Values: values, + } +} diff --git a/badorm/unsafe/crudRepository.go b/badorm/unsafe/crudRepository.go new file mode 100644 index 00000000..28d7b6f5 --- /dev/null +++ b/badorm/unsafe/crudRepository.go @@ -0,0 +1,250 @@ +package unsafe + +import ( + "fmt" + "reflect" + + "gorm.io/gorm" + + "github.com/ditrit/badaas/badorm" +) + +// Generic CRUD Repository +// T can be any model whose identifier attribute is of type ID +type CRUDRepository[T badorm.Model, ID badorm.ModelID] interface { + Query(tx *gorm.DB, conditions map[string]any) ([]*T, error) +} + +var ( + ErrObjectsNotRelated = func(typeName, attributeName string) error { + return fmt.Errorf("%[1]s has not attribute named %[2]s or %[2]sID", typeName, attributeName) + } + ErrModelNotRegistered = func(typeName, attributeName string) error { + return fmt.Errorf( + "%[1]s has an attribute named %[2]s or %[2]sID but %[2]s is not registered as model (use badorm.AddUnsafeModel or badorm.GetCRUDUnsafeServiceModule)", + typeName, attributeName, + ) + } +) + +// Implementation of the Generic CRUD Repository +type CRUDRepositoryImpl[T badorm.Model, ID badorm.ModelID] struct { + CRUDRepository[T, ID] +} + +// Constructor of the Generic CRUD Repository +func NewCRUDRepository[T badorm.Model, ID badorm.ModelID]() CRUDRepository[T, ID] { + return &CRUDRepositoryImpl[T, ID]{} +} + +// Get the list of objects that match "conditions" inside transaction "tx" +// "conditions" is in {"attributeName": expectedValue} format +// in case of join "conditions" can have the format: +// {"relationAttributeName": {"attributeName": expectedValue}} +func (repository *CRUDRepositoryImpl[T, ID]) Query(tx *gorm.DB, conditions map[string]any) ([]*T, error) { + thisEntityConditions, joinConditions, err := divideConditionsByEntity(conditions) + if err != nil { + return nil, err + } + + // TODO on column definition, conditions need to have the column name in place of the attribute name + query := tx.Where(thisEntityConditions) + + entity := new(T) + // only entities that match the conditions + for joinAttributeName, joinConditions := range joinConditions { + var tableName string + + tableName, err = badorm.GetTableName(tx, entity) + if err != nil { + return nil, err + } + + err = repository.addJoinToQuery( + query, + entity, + tableName, + joinAttributeName, + joinConditions, + ) + if err != nil { + return nil, err + } + } + + // execute query + var entities []*T + err = query.Find(&entities).Error + + return entities, err +} + +// Adds a join to the "query" by the "joinAttributeName" +// then, adds the verification that the joined values match "conditions" + +// "conditions" is in {"attributeName": expectedValue} format +// "previousEntity" is a pointer to a object from where we navigate the relationship +// "previousTableName" is the name of the table where the previous object is saved and from we the join will we done +func (repository *CRUDRepositoryImpl[T, ID]) addJoinToQuery( + query *gorm.DB, previousEntity any, + previousTableName, joinAttributeName string, + conditions map[string]any, +) error { + thisEntityConditions, joinConditions, err := divideConditionsByEntity(conditions) + if err != nil { + return err + } + + relatedObject, relationIDIsInPreviousTable, err := getRelatedObject( + previousEntity, + joinAttributeName, + ) + if err != nil { + return err + } + + joinTableName, err := badorm.GetTableName(query, relatedObject) + if err != nil { + return err + } + + tableWithSuffix := joinTableName + "_" + previousTableName + + var stringQuery string + if relationIDIsInPreviousTable { + stringQuery = fmt.Sprintf( + `JOIN %[1]s %[2]s ON + %[2]s.id = %[3]s.%[4]s_id + AND %[2]s.deleted_at IS NULL + `, + joinTableName, + tableWithSuffix, + previousTableName, + joinAttributeName, + ) + } else { + // TODO foreignKey can be redefined (https://gorm.io/docs/has_one.html#Override-References) + previousAttribute := reflect.TypeOf(previousEntity).Elem().Name() + stringQuery = fmt.Sprintf( + `JOIN %[1]s %[2]s ON + %[2]s.%[4]s_id = %[3]s.id + AND %[2]s.deleted_at IS NULL + `, + joinTableName, + tableWithSuffix, + previousTableName, + previousAttribute, + ) + } + + conditionsValues := []any{} + + for attributeName, conditionValue := range thisEntityConditions { + stringQuery += fmt.Sprintf( + `AND %[1]s.%[2]s = ? + `, + tableWithSuffix, attributeName, + ) + + conditionsValues = append(conditionsValues, conditionValue) + } + + query.Joins(stringQuery, conditionsValues...) + + for joinAttributeName, joinConditions := range joinConditions { + err = repository.addJoinToQuery( + query, + relatedObject, + tableWithSuffix, + joinAttributeName, + joinConditions, + ) + if err != nil { + return err + } + } + + return nil +} + +// Given a map of "conditions" that is in {"attributeName": expectedValue} format +// and in case of join "conditions" can have the format: +// +// {"relationAttributeName": {"attributeName": expectedValue}} +// +// it divides the map in two: +// the conditions that will be applied to the current entity ({"attributeName": expectedValue} format) +// the conditions that will generate a join with another entity ({"relationAttributeName": {"attributeName": expectedValue}} format) +// +// Returns error if any expectedValue is not of a supported type +func divideConditionsByEntity( + conditions map[string]any, +) (map[string]any, map[string]map[string]any, error) { + thisEntityConditions := map[string]any{} + joinConditions := map[string]map[string]any{} + + for attributeName, expectedValue := range conditions { + switch typedExpectedValue := expectedValue.(type) { + case string: + uuid, err := badorm.ParseUUID(typedExpectedValue) + if err == nil { + thisEntityConditions[attributeName] = uuid + } else { + thisEntityConditions[attributeName] = expectedValue + } + case float64, bool, int: + thisEntityConditions[attributeName] = expectedValue + case map[string]any: + joinConditions[attributeName] = typedExpectedValue + default: + return nil, nil, fmt.Errorf("unsupported type") + } + } + + return thisEntityConditions, joinConditions, nil +} + +// Returns an object of the type of the "entity" attribute called "relationName" +// and a boolean value indicating whether the id attribute that relates them +// in the database is in the "entity"'s table. +// Returns error if "entity" not a relation called "relationName". +func getRelatedObject(entity any, relationName string) (any, bool, error) { + entityType := badorm.GetEntityType(entity) + + field, isPresent := entityType.FieldByName(relationName) + if !isPresent { + // some gorm relations dont have a direct relation in the model, only the id + relatedObject, err := getRelatedObjectByID(entityType, relationName) + if err != nil { + return nil, false, err + } + + return relatedObject, true, nil + } + + _, isIDPresent := entityType.FieldByName(relationName + "ID") + + return createObject(field.Type), isIDPresent, nil +} + +// Returns an object of the type of the "entity" attribute called "relationName" + "ID" +// Returns error if "entity" not a relation called "relationName" + "ID" +func getRelatedObjectByID(entityType reflect.Type, relationName string) (any, error) { + _, isPresent := entityType.FieldByName(relationName + "ID") + if !isPresent { + return nil, ErrObjectsNotRelated(entityType.Name(), relationName) + } + + // TODO foreignKey can be redefined (https://gorm.io/docs/has_one.html#Override-References) + fieldType, isPresent := modelsMapping[relationName] + if !isPresent { + return nil, ErrModelNotRegistered(entityType.Name(), relationName) + } + + return createObject(fieldType), nil +} + +// Creates an object of type reflect.Type using reflection +func createObject(entityType reflect.Type) any { + return reflect.New(entityType).Elem().Interface() +} diff --git a/badorm/unsafe/crudService.go b/badorm/unsafe/crudService.go new file mode 100644 index 00000000..cd70f64f --- /dev/null +++ b/badorm/unsafe/crudService.go @@ -0,0 +1,39 @@ +package unsafe + +import ( + "gorm.io/gorm" + + "github.com/ditrit/badaas/badorm" +) + +// T can be any model whose identifier attribute is of type ID +type CRUDService[T badorm.Model, ID badorm.ModelID] interface { + Query(conditions map[string]any) ([]*T, error) +} + +// check interface compliance +var _ CRUDService[badorm.UUIDModel, badorm.UUID] = (*crudServiceImpl[badorm.UUIDModel, badorm.UUID])(nil) + +// Implementation of the CRUD Service +type crudServiceImpl[T badorm.Model, ID badorm.ModelID] struct { + db *gorm.DB + repository CRUDRepository[T, ID] +} + +func NewCRUDUnsafeService[T badorm.Model, ID badorm.ModelID]( + db *gorm.DB, + repository CRUDRepository[T, ID], +) CRUDService[T, ID] { + return &crudServiceImpl[T, ID]{ + db: db, + repository: repository, + } +} + +// Query models of type T that match all "conditions" +// "params" is in {"attributeName": expectedValue} format +// in case of join "params" can have the format: +// {"relationAttributeName": {"attributeName": expectedValue}} +func (service *crudServiceImpl[T, ID]) Query(conditions map[string]any) ([]*T, error) { + return service.repository.Query(service.db, conditions) +} diff --git a/badorm/unsafe/multivalue.go b/badorm/unsafe/multivalue.go new file mode 100644 index 00000000..98112fae --- /dev/null +++ b/badorm/unsafe/multivalue.go @@ -0,0 +1,21 @@ +package unsafe + +import ( + "github.com/ditrit/badaas/badorm" + "github.com/ditrit/badaas/badorm/sql" +) + +func NewMultivalueOperator[T any]( + sqlOperator sql.Operator, + sqlConnector sql.Connector, + sqlPrefix, sqlSuffix string, + values ...any, +) badorm.DynamicOperator[T] { + return &badorm.MultivalueOperator[T]{ + Values: values, + SQLOperator: sqlOperator, + SQLConnector: sqlConnector, + SQLPrefix: sqlPrefix, + SQLSuffix: sqlSuffix, + } +} diff --git a/badorm/unsafe/mysqlunsafe/comparison.go b/badorm/unsafe/mysqlunsafe/comparison.go new file mode 100644 index 00000000..45e1f798 --- /dev/null +++ b/badorm/unsafe/mysqlunsafe/comparison.go @@ -0,0 +1,15 @@ +package mysqlunsafe + +import ( + "github.com/ditrit/badaas/badorm" + "github.com/ditrit/badaas/badorm/sql" + "github.com/ditrit/badaas/badorm/unsafe" +) + +// Comparison Predicates + +// preferred over eq +// https://dev.mysql.com/doc/refman/8.0/en/comparison-operators.html#operator_equal-to +func IsEqual[T any](value any) badorm.DynamicOperator[T] { + return unsafe.NewValueOperator[T](sql.MySQLIsEqual, value) +} diff --git a/badorm/unsafe/operators.go b/badorm/unsafe/operators.go new file mode 100644 index 00000000..b5ab4a99 --- /dev/null +++ b/badorm/unsafe/operators.go @@ -0,0 +1,81 @@ +package unsafe + +import ( + "github.com/ditrit/badaas/badorm" + "github.com/ditrit/badaas/badorm/sql" +) + +// Comparison Operators +// refs: +// - MySQL: https://dev.mysql.com/doc/refman/8.0/en/comparison-operators.html +// - PostgreSQL: https://www.postgresql.org/docs/current/functions-comparison.html +// - SQLServer: https://learn.microsoft.com/en-us/sql/t-sql/language-elements/comparison-operators-transact-sql?view=sql-server-ver16 +// - SQLite: https://www.sqlite.org/lang_expr.html + +// EqualTo +func Eq[T any](value any) badorm.DynamicOperator[T] { + return NewValueOperator[T](sql.Eq, value) +} + +// NotEqualTo +func NotEq[T any](value any) badorm.DynamicOperator[T] { + return NewValueOperator[T](sql.NotEq, value) +} + +// LessThan +func Lt[T any](value any) badorm.DynamicOperator[T] { + return NewValueOperator[T](sql.Lt, value) +} + +// LessThanOrEqualTo +func LtOrEq[T any](value any) badorm.DynamicOperator[T] { + return NewValueOperator[T](sql.LtOrEq, value) +} + +// GreaterThan +func Gt[T any](value any) badorm.DynamicOperator[T] { + return NewValueOperator[T](sql.Gt, value) +} + +// GreaterThanOrEqualTo +func GtOrEq[T any](value any) badorm.DynamicOperator[T] { + return NewValueOperator[T](sql.GtOrEq, value) +} + +// Comparison Predicates +// refs: +// https://dev.mysql.com/doc/refman/8.0/en/comparison-operators.html +// https://www.postgresql.org/docs/current/functions-comparison.html#FUNCTIONS-COMPARISON-PRED-TABLE + +// Equivalent to v1 < value < v2 +func Between[T any](v1, v2 any) badorm.DynamicOperator[T] { + return NewMultivalueOperator[T](sql.Between, sql.And, "", "", v1, v2) +} + +// Equivalent to NOT (v1 < value < v2) +func NotBetween[T any](v1, v2 any) badorm.DynamicOperator[T] { + return NewMultivalueOperator[T](sql.NotBetween, sql.And, "", "", v1, v2) +} + +// Boolean Comparison Predicates + +// Not supported by: mysql +func IsDistinct[T any](value any) badorm.DynamicOperator[T] { + return NewValueOperator[T](sql.IsDistinct, value) +} + +// Not supported by: mysql +func IsNotDistinct[T any](value any) badorm.DynamicOperator[T] { + return NewValueOperator[T](sql.IsNotDistinct, value) +} + +// Row and Array Comparisons + +// https://dev.mysql.com/doc/refman/8.0/en/comparison-operators.html#operator_in +func ArrayIn[T any](values ...any) badorm.DynamicOperator[T] { + return NewMultivalueOperator[T](sql.ArrayIn, sql.Comma, "(", ")", values...) +} + +func ArrayNotIn[T any](values ...any) badorm.DynamicOperator[T] { + return NewMultivalueOperator[T](sql.ArrayNotIn, sql.Comma, "(", ")", values...) +} diff --git a/badorm/unsafe/sqlserverunsafe/comparison.go b/badorm/unsafe/sqlserverunsafe/comparison.go new file mode 100644 index 00000000..08867999 --- /dev/null +++ b/badorm/unsafe/sqlserverunsafe/comparison.go @@ -0,0 +1,18 @@ +package sqlserverunsafe + +import ( + "github.com/ditrit/badaas/badorm" + "github.com/ditrit/badaas/badorm/sql" + "github.com/ditrit/badaas/badorm/unsafe" +) + +// Comparison Operators +// ref: https://learn.microsoft.com/en-us/sql/t-sql/language-elements/comparison-operators-transact-sql?view=sql-server-ver16 + +func NotLt[T any](value any) badorm.DynamicOperator[T] { + return unsafe.NewValueOperator[T](sql.SQLServerNotLt, value) +} + +func NotGt[T any](value any) badorm.DynamicOperator[T] { + return unsafe.NewValueOperator[T](sql.SQLServerNotGt, value) +} diff --git a/badorm/unsafe/value.go b/badorm/unsafe/value.go new file mode 100644 index 00000000..3acea704 --- /dev/null +++ b/badorm/unsafe/value.go @@ -0,0 +1,14 @@ +package unsafe + +import ( + "github.com/ditrit/badaas/badorm" + "github.com/ditrit/badaas/badorm/sql" +) + +func NewValueOperator[T any]( + sqlOperator sql.Operator, + value any, +) badorm.DynamicOperator[T] { + op := badorm.NewValueOperator[T](sqlOperator, value) + return &op +} diff --git a/badorm/uuid.go b/badorm/uuid.go new file mode 100644 index 00000000..4bf0f95d --- /dev/null +++ b/badorm/uuid.go @@ -0,0 +1,114 @@ +package badorm + +import ( + "context" + "database/sql/driver" + + "github.com/google/uuid" + "gorm.io/gorm" + "gorm.io/gorm/clause" + "gorm.io/gorm/schema" +) + +type UUID uuid.UUID + +var NilUUID = UUID(uuid.Nil) + +func (id UUID) GormDBDataType(db *gorm.DB, _ *schema.Field) string { + switch db.Dialector.Name() { + case "mysql": + return "binary(16)" + case "postgres": + return "uuid" + case "sqlite": + return "varchar(36)" + case "sqlserver": + return "uniqueidentifier" + } + + return "" +} + +func (id UUID) String() string { + return uuid.UUID(id).String() +} + +func (id UUID) URN() string { + return uuid.UUID(id).URN() +} + +func (id UUID) Variant() uuid.Variant { + return uuid.UUID(id).Variant() +} + +func (id UUID) Version() uuid.Version { + return uuid.UUID(id).Version() +} + +func (id UUID) MarshalText() ([]byte, error) { + return uuid.UUID(id).MarshalText() +} + +func (id *UUID) UnmarshalText(data []byte) error { + return (*uuid.UUID)(id).UnmarshalText(data) +} + +func (id UUID) MarshalBinary() ([]byte, error) { + return uuid.UUID(id).MarshalBinary() +} + +func (id *UUID) UnmarshalBinary(data []byte) error { + return (*uuid.UUID)(id).UnmarshalBinary(data) +} + +func (id *UUID) Scan(src interface{}) error { + return (*uuid.UUID)(id).Scan(src) +} + +func (id UUID) IsNil() bool { + return id == NilUUID +} + +func (id UUID) GormValue(_ context.Context, db *gorm.DB) clause.Expr { + if id == NilUUID { + return gorm.Expr("NULL") + } + + switch db.Dialector.Name() { + case "mysql", "sqlserver": + binary, err := id.MarshalBinary() + if err != nil { + _ = db.AddError(err) + return clause.Expr{} + } + + return gorm.Expr("?", binary) + default: + return gorm.Expr("?", id.String()) + } +} + +func (id UUID) Value() (driver.Value, error) { + return uuid.UUID(id).Value() +} + +func (id UUID) Time() uuid.Time { + return uuid.UUID(id).Time() +} + +func (id UUID) ClockSequence() int { + return uuid.UUID(id).ClockSequence() +} + +func NewUUID() UUID { + return UUID(uuid.New()) +} + +func ParseUUID(s string) (UUID, error) { + uid, err := uuid.Parse(s) + if err != nil { + return UUID(uuid.Nil), err + } + + return UUID(uid), nil +} diff --git a/badorm/uuid_test.go b/badorm/uuid_test.go new file mode 100644 index 00000000..bf384619 --- /dev/null +++ b/badorm/uuid_test.go @@ -0,0 +1,22 @@ +package badorm_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/ditrit/badaas/badorm" +) + +func TestParseCorrectUUID(t *testing.T) { + uuidString := badorm.NewUUID().String() + uuid, err := badorm.ParseUUID(uuidString) + assert.Nil(t, err) + assert.Equal(t, uuidString, uuid.String()) +} + +func TestParseIncorrectUUID(t *testing.T) { + uuid, err := badorm.ParseUUID("not uuid") + assert.Error(t, err) + assert.Equal(t, badorm.NilUUID, uuid) +} diff --git a/badorm/valueOperator.go b/badorm/valueOperator.go new file mode 100644 index 00000000..b9ddd1d1 --- /dev/null +++ b/badorm/valueOperator.go @@ -0,0 +1,121 @@ +package badorm + +import ( + "database/sql" + "fmt" + "reflect" + "regexp" + + badormSQL "github.com/ditrit/badaas/badorm/sql" +) + +const undefinedJoinNumber = -1 + +// Operator that compares the value of the column against a fixed value +// If SQLOperators has multiple entries, comparisons will be nested +// Example (single): value = v1 +// Example (multi): value LIKE v1 ESCAPE v2 +type ValueOperator[T any] struct { + operations []operation +} + +type operation struct { + SQLOperator badormSQL.Operator + Value any + JoinNumber int +} + +func (operator ValueOperator[T]) InterfaceVerificationMethod(_ T) { + // This method is necessary to get the compiler to verify + // that an object is of type Operator[T] +} + +// Allows to choose which number of join use +// for the operation in position "operationNumber" +// when the value is a field and its model is joined more than once. +// Does nothing if the operationNumber is bigger than the amount of operations. +func (operator *ValueOperator[T]) SelectJoin(operationNumber, joinNumber uint) DynamicOperator[T] { + if operationNumber >= uint(len(operator.operations)) { + return operator + } + + operationSaved := operator.operations[operationNumber] + operationSaved.JoinNumber = int(joinNumber) + operator.operations[operationNumber] = operationSaved + + return operator +} + +func (operator *ValueOperator[T]) AddOperation(sqlOperator badormSQL.Operator, value any) *ValueOperator[T] { + operator.operations = append( + operator.operations, + operation{ + Value: value, + SQLOperator: sqlOperator, + JoinNumber: undefinedJoinNumber, + }, + ) + + return operator +} + +func (operator ValueOperator[T]) ToSQL(query *Query, columnName string) (string, []any, error) { + operationString := columnName + values := []any{} + + // add each operation to the sql + for _, operation := range operator.operations { + field, isField := operation.Value.(iFieldIdentifier) + if isField { + // if the value of the operation is a field, + // verify that this field is concerned by the query + // (a join was performed with the model to which this field belongs) + // and get the alias of the table of this model. + modelTable, err := getModelTable(query, field, operation.JoinNumber, operation.SQLOperator) + if err != nil { + return "", nil, err + } + + operationString += fmt.Sprintf( + " %s %s", + operation.SQLOperator, + field.ColumnSQL(query, modelTable), + ) + } else { + operationString += " " + operation.SQLOperator.String() + " ?" + values = append(values, operation.Value) + } + } + + return operationString, values, nil +} + +func NewValueOperator[T any](sqlOperator badormSQL.Operator, value any) ValueOperator[T] { + return *new(ValueOperator[T]).AddOperation(sqlOperator, value) +} + +var nullableKinds = []reflect.Kind{ + reflect.Chan, reflect.Func, + reflect.Map, reflect.Pointer, + reflect.UnsafePointer, reflect.Interface, + reflect.Slice, +} + +func NewCantBeNullValueOperator[T any](sqlOperator badormSQL.Operator, value any) Operator[T] { + if value == nil || mapsToNull(value) { + return NewInvalidOperator[T]( + OperatorError(ErrValueCantBeNull, sqlOperator), + ) + } + + return NewValueOperator[T](sqlOperator, value) +} + +func NewMustBePOSIXValueOperator[T string | sql.NullString](sqlOperator badormSQL.Operator, pattern string) Operator[T] { + _, err := regexp.CompilePOSIX(pattern) + if err != nil { + return NewInvalidOperator[T](OperatorError(err, sqlOperator)) + } + + return NewValueOperator[T](sqlOperator, pattern) +} diff --git a/changelog.md b/changelog.md index 866b2573..968e3255 100644 --- a/changelog.md +++ b/changelog.md @@ -9,27 +9,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Setup project (ci and sonar) +- Setup project (ci and sonar). - Setup e2e test solution (cucumber + docker). - Setup Docker based build system. -- Add default api endpoint `info` -- Setup command based pattern using verdeter -- Add an http error handling mecanism -- Add a json controller -- Add a dto package +- Add default api endpoint `info`. +- Setup command based pattern using verdeter. +- Add an http error handling mechanism. +- Add a json controller. +- Add a dto package. - The tasks in the CI are ran in parallel. -- Add a Generic CRUD Repository +- Add a Generic CRUD Repository. - Add a configuration structure containing all the configuration holder. - Refactor codebase to use the DI framework uber-go/fx. Now all services and controllers relies on interfaces. -- Add an generic ID to the repository interface -- Add a retry mecanism for the database connection +- Add an generic ID to the repository interface. +- Add a retry mechanism for the database connection. - Add `init` flag to migrate database and create admin user. - Add a CONTRIBUTING.md and a documentation file for configuration (configuration.md) - Add a session services. -- Add a basic authentification controller. +- Add a basic authentication controller. - Now config keys are only declared once with constants in the `configuration/` package. - Add a dto that is returned on a successful login. -- Update verdeter to version v0.4.0 +- Update verdeter to version v0.4.0. +- Add integration tests. +- Transform BadAas into a library. +- Add BadCtl tool. +- Add BaDORM to facilitate queries on sql databases. +- Add gen conditions to BaDctl to generate a compilable query system. - -[unreleased]: https://github.com/ditrit/badaas/blob/main/changelog.md#unreleased \ No newline at end of file +[unreleased]: https://github.com/ditrit/badaas/blob/main/changelog.md#unreleased diff --git a/commands/init.go b/commands/init.go deleted file mode 100644 index 1d9b4f20..00000000 --- a/commands/init.go +++ /dev/null @@ -1,27 +0,0 @@ -package commands - -import ( - "strings" - - "github.com/ditrit/badaas/configuration" - "github.com/ditrit/badaas/services/userservice" - "go.uber.org/zap" -) - -// Create a super user -func createSuperUser( - config configuration.InitializationConfiguration, - logger *zap.Logger, - userService userservice.UserService, -) error { - // Create a super admin user and exit with code 1 on error - _, err := userService.NewUser("admin", "admin-no-reply@badaas.com", config.GetAdminPassword()) - if err != nil { - if !strings.Contains(err.Error(), "already exist in database") { - logger.Sugar().Errorf("failed to save the super admin %w", err) - return err - } - logger.Sugar().Infof("The superadmin user already exists in database") - } - return nil -} diff --git a/commands/initDatabaseCommands.go b/commands/initDatabaseCommands.go deleted file mode 100644 index 022cfb98..00000000 --- a/commands/initDatabaseCommands.go +++ /dev/null @@ -1,32 +0,0 @@ -package commands - -import ( - "github.com/ditrit/badaas/configuration" - "github.com/ditrit/verdeter" -) - -func initDatabaseCommands(cfg *verdeter.VerdeterCommand) { - cfg.GKey(configuration.DatabasePortKey, verdeter.IsInt, "", "The port of the database server") - cfg.SetRequired(configuration.DatabasePortKey) - - cfg.GKey(configuration.DatabaseHostKey, verdeter.IsStr, "", "The host of the database server") - cfg.SetRequired(configuration.DatabaseHostKey) - - cfg.GKey(configuration.DatabaseNameKey, verdeter.IsStr, "", "The name of the database to use") - cfg.SetRequired(configuration.DatabaseNameKey) - - cfg.GKey(configuration.DatabaseUsernameKey, verdeter.IsStr, "", "The username of the account on the database server") - cfg.SetRequired(configuration.DatabaseUsernameKey) - - cfg.GKey(configuration.DatabasePasswordKey, verdeter.IsStr, "", "The password of the account one the database server") - cfg.SetRequired(configuration.DatabasePasswordKey) - - cfg.GKey(configuration.DatabaseSslmodeKey, verdeter.IsStr, "", "The sslmode to use when connecting to the database server") - cfg.SetRequired(configuration.DatabaseSslmodeKey) - - cfg.GKey(configuration.DatabaseRetryKey, verdeter.IsUint, "", "The number of times badaas tries to establish a connection with the database") - cfg.SetDefault(configuration.DatabaseRetryKey, uint(10)) - - cfg.GKey(configuration.DatabaseRetryDurationKey, verdeter.IsUint, "", "The duration in seconds badaas wait between connection attempts") - cfg.SetDefault(configuration.DatabaseRetryDurationKey, uint(5)) -} diff --git a/commands/initInitialisationCommands.go b/commands/initInitialisationCommands.go deleted file mode 100644 index 130e9778..00000000 --- a/commands/initInitialisationCommands.go +++ /dev/null @@ -1,13 +0,0 @@ -package commands - -import ( - "github.com/ditrit/badaas/configuration" - "github.com/ditrit/verdeter" -) - -func initInitialisationCommands(cfg *verdeter.VerdeterCommand) { - - cfg.GKey(configuration.InitializationDefaultAdminPasswordKey, verdeter.IsStr, "", - "Set the default admin password is the admin user is not created yet.") - cfg.SetDefault(configuration.InitializationDefaultAdminPasswordKey, "admin") -} diff --git a/commands/initLoggerCommands.go b/commands/initLoggerCommands.go deleted file mode 100644 index 097ac1d3..00000000 --- a/commands/initLoggerCommands.go +++ /dev/null @@ -1,16 +0,0 @@ -package commands - -import ( - "github.com/ditrit/badaas/configuration" - "github.com/ditrit/verdeter" - "github.com/ditrit/verdeter/validators" -) - -func initLoggerCommands(cfg *verdeter.VerdeterCommand) { - cfg.GKey(configuration.LoggerModeKey, verdeter.IsStr, "", "The logger mode (default to \"prod\")") - cfg.SetDefault(configuration.LoggerModeKey, "prod") - cfg.AddValidator(configuration.LoggerModeKey, validators.AuthorizedValues("prod", "dev")) - - cfg.GKey(configuration.LoggerRequestTemplateKey, verdeter.IsStr, "", "Template message for all request logs") - cfg.SetDefault(configuration.LoggerRequestTemplateKey, "Receive {{method}} request on {{url}}") -} diff --git a/commands/initServerCommands.go b/commands/initServerCommands.go deleted file mode 100644 index 7323524b..00000000 --- a/commands/initServerCommands.go +++ /dev/null @@ -1,23 +0,0 @@ -package commands - -import ( - "github.com/ditrit/badaas/configuration" - "github.com/ditrit/verdeter" - "github.com/ditrit/verdeter/validators" -) - -func initServerCommands(cfg *verdeter.VerdeterCommand) { - cfg.GKey(configuration.ServerTimeoutKey, verdeter.IsInt, "", "Maximum timeout of the http server in second (default is 15s)") - cfg.SetDefault(configuration.ServerTimeoutKey, 15) - - cfg.GKey(configuration.ServerHostKey, verdeter.IsStr, "", "Address to bind (default is 0.0.0.0)") - cfg.SetDefault(configuration.ServerHostKey, "0.0.0.0") - - cfg.GKey(configuration.ServerPortKey, verdeter.IsInt, "p", "Port to bind (default is 8000)") - cfg.AddValidator(configuration.ServerPortKey, validators.CheckTCPHighPort) - cfg.SetDefault(configuration.ServerPortKey, 8000) - - cfg.GKey(configuration.ServerPaginationMaxElemPerPage, verdeter.IsUint, "", "The max number of records returned per page") - cfg.SetDefault(configuration.ServerPaginationMaxElemPerPage, 100) - -} diff --git a/commands/initSessionCommands.go b/commands/initSessionCommands.go deleted file mode 100644 index 13f4ad74..00000000 --- a/commands/initSessionCommands.go +++ /dev/null @@ -1,19 +0,0 @@ -package commands - -import ( - "github.com/ditrit/badaas/configuration" - "github.com/ditrit/verdeter" -) - -// initialize session related config keys -func initSessionCommands(cfg *verdeter.VerdeterCommand) { - cfg.LKey(configuration.SessionDurationKey, verdeter.IsUint, "", "The duration of a user session in seconds.") - cfg.SetDefault(configuration.SessionDurationKey, uint(3600*4)) // 4 hours by default - - cfg.LKey(configuration.SessionPullIntervalKey, - verdeter.IsUint, "", "The refresh interval in seconds. Badaas refresh it's internal session cache periodically.") - cfg.SetDefault(configuration.SessionPullIntervalKey, uint(30)) // 30 seconds by default - - cfg.LKey(configuration.SessionRollIntervalKey, verdeter.IsUint, "", "The interval in which the user can renew it's session by making a request.") - cfg.SetDefault(configuration.SessionRollIntervalKey, uint(3600)) // 1 hour by default -} diff --git a/commands/init_test.go b/commands/init_test.go deleted file mode 100644 index 1966506d..00000000 --- a/commands/init_test.go +++ /dev/null @@ -1,68 +0,0 @@ -package commands - -import ( - "errors" - "testing" - - mocks "github.com/ditrit/badaas/mocks/configuration" - mockUserServices "github.com/ditrit/badaas/mocks/services/userservice" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.uber.org/zap" - "go.uber.org/zap/zaptest/observer" -) - -func TestCreateSuperUser(t *testing.T) { - core, _ := observer.New(zap.DebugLevel) - logger := zap.New(core) - initializationConfig := mocks.NewInitializationConfiguration(t) - initializationConfig.On("GetAdminPassword").Return("adminpassword") - userService := mockUserServices.NewUserService(t) - userService. - On("NewUser", "admin", "admin-no-reply@badaas.com", "adminpassword"). - Return(nil, nil) - err := createSuperUser( - initializationConfig, - logger, - userService, - ) - assert.NoError(t, err) -} - -func TestCreateSuperUser_UserExists(t *testing.T) { - core, logs := observer.New(zap.DebugLevel) - logger := zap.New(core) - initializationConfig := mocks.NewInitializationConfiguration(t) - initializationConfig.On("GetAdminPassword").Return("adminpassword") - userService := mockUserServices.NewUserService(t) - userService. - On("NewUser", "admin", "admin-no-reply@badaas.com", "adminpassword"). - Return(nil, errors.New("user already exist in database")) - err := createSuperUser( - initializationConfig, - logger, - userService, - ) - assert.NoError(t, err) - - require.Equal(t, 1, logs.Len()) -} - -func TestCreateSuperUser_UserServiceError(t *testing.T) { - core, logs := observer.New(zap.DebugLevel) - logger := zap.New(core) - initializationConfig := mocks.NewInitializationConfiguration(t) - initializationConfig.On("GetAdminPassword").Return("adminpassword") - userService := mockUserServices.NewUserService(t) - userService. - On("NewUser", "admin", "admin-no-reply@badaas.com", "adminpassword"). - Return(nil, errors.New("email not valid")) - err := createSuperUser( - initializationConfig, - logger, - userService, - ) - assert.Error(t, err) - - require.Equal(t, 1, logs.Len()) -} diff --git a/commands/r.md b/commands/r.md deleted file mode 100644 index c32746b4..00000000 --- a/commands/r.md +++ /dev/null @@ -1,86 +0,0 @@ -package commands - -import ( - "log" - - "github.com/ditrit/badaas/logger" - "github.com/ditrit/badaas/persistence/registry" - "github.com/ditrit/badaas/persistence/repository" - "github.com/ditrit/badaas/router" - "github.com/ditrit/badaas/services/session" - "github.com/ditrit/badaas/services/userservice" - "github.com/ditrit/verdeter" - "go.uber.org/zap" -) - -// Create a super admin user and exit with code 1 on error -func createSuperAdminUser() { - logg := zap.L().Sugar() - _, err := userservice.NewUser("superadmin", "superadmin@badaas.test", "1234") - if err != nil { - if repository.ErrAlreadyExists == err { - logg.Debugf("The superadmin user already exists in database") - } else { - logg.Fatalf("failed to save the super admin %w", err) - } - } - -} - -// Run the http server for badaas -func runHTTPServer(cfg *verdeter.VerdeterCommand, args []string) error { - err := logger.InitLoggerFromConf() - if err != nil { - log.Fatalf("An error happened while initializing logger (ERROR=%s)", err.Error()) - } - - zap.L().Info("The logger is initialiazed") - - // create router - router := router.SetupRouter() - - registryInstance, err := registry.FactoryRegistry(registry.GormDataStore) - if err != nil { - zap.L().Sugar().Fatalf("An error happened while initializing datastorage layer (ERROR=%s)", err.Error()) - } - registry.ReplaceGlobals(registryInstance) - zap.L().Info("The datastorage layer is initialized") - - createSuperAdminUser() - - err = session.Init() - if err != nil { - zap.L().Sugar().Fatalf("An error happened while initializing the session service (ERROR=%s)", err.Error()) - } - zap.L().Info("The session service is initialized") - - // create server - srv := createServerFromConfiguration(router) - - zap.L().Sugar().Infof("Ready to serve at %s\n", srv.Addr) - return srv.ListenAndServe() -} - -var rootCfg = verdeter.NewVerdeterCommand( - "badaas", - "Backend and Distribution as a Service", - `Badaas stands for Backend and Distribution as a Service.`, - runHTTPServer, -) - -// Execute adds all child commands to the root command and sets flags appropriately. -// This is called by main.main(). It only needs to happen once to the rootCmd. -func Execute() { - rootCfg.Execute() -} - -func init() { - rootCfg.Initialize() - - rootCfg.GKey("config_path", verdeter.IsStr, "", "Path to the config file/directory") - rootCfg.SetDefault("config_path", ".") - - initServerCommands(rootCfg) - initLoggerCommands(rootCfg) - initDatabaseCommands(rootCfg) -} diff --git a/commands/rootCmd.go b/commands/rootCmd.go deleted file mode 100644 index 605b3bec..00000000 --- a/commands/rootCmd.go +++ /dev/null @@ -1,70 +0,0 @@ -package commands - -import ( - "net/http" - - "github.com/ditrit/badaas/configuration" - "github.com/ditrit/badaas/controllers" - "github.com/ditrit/badaas/logger" - "github.com/ditrit/badaas/persistence" - "github.com/ditrit/badaas/resources" - "github.com/ditrit/badaas/router" - "github.com/ditrit/badaas/services/sessionservice" - "github.com/ditrit/badaas/services/userservice" - "github.com/ditrit/verdeter" - "github.com/spf13/cobra" - "go.uber.org/fx" - "go.uber.org/fx/fxevent" - "go.uber.org/zap" -) - -// Run the http server for badaas -func runHTTPServer(cmd *cobra.Command, args []string) { - fx.New( - // Modules - configuration.ConfigurationModule, - router.RouterModule, - controllers.ControllerModule, - logger.LoggerModule, - persistence.PersistanceModule, - - fx.Provide(userservice.NewUserService), - fx.Provide(sessionservice.NewSessionService), - // logger for fx - fx.WithLogger(func(logger *zap.Logger) fxevent.Logger { - return &fxevent.ZapLogger{Logger: logger} - }), - - fx.Provide(NewHTTPServer), - - // Finally: we invoke the newly created server - fx.Invoke(func(*http.Server) { /* we need this function to be empty*/ }), - fx.Invoke(createSuperUser), - ).Run() -} - -// The command badaas -var rootCfg = verdeter.BuildVerdeterCommand(verdeter.VerdeterConfig{ - Use: "badaas", - Short: "Backend and Distribution as a Service", - Long: "Badaas stands for Backend and Distribution as a Service.", - Version: resources.Version, - Run: runHTTPServer, -}) - -// Execute adds all child commands to the root command and sets flags appropriately. -// This is called by main.main(). It only needs to happen once to the rootCmd. -func Execute() { - rootCfg.Execute() -} - -func init() { - rootCfg.GKey("config_path", verdeter.IsStr, "", "Path to the config file/directory") - rootCfg.SetDefault("config_path", ".") - - initServerCommands(rootCfg) - initLoggerCommands(rootCfg) - initDatabaseCommands(rootCfg) - initInitialisationCommands(rootCfg) - initSessionCommands(rootCfg) -} diff --git a/commands/server.go b/commands/server.go deleted file mode 100644 index 1d023b42..00000000 --- a/commands/server.go +++ /dev/null @@ -1,71 +0,0 @@ -package commands - -// This file holds functions needed by the badaas rootCommand, thoses functions help in creating the http.Server. - -import ( - "context" - "fmt" - "net" - "net/http" - "time" - - "go.uber.org/fx" - "go.uber.org/zap" - - "github.com/ditrit/badaas/configuration" -) - -// Create the server from the configuration holder and the http handler -func createServerFromConfigurationHolder(router http.Handler, httpServerConfig configuration.HTTPServerConfiguration) *http.Server { - address := addrFromConf(httpServerConfig.GetHost(), httpServerConfig.GetPort()) - timeout := httpServerConfig.GetMaxTimeout() - return createServer(router, address, timeout, timeout) -} - -// Create an http server -func createServer(router http.Handler, address string, writeTimeout, readTimeout time.Duration) *http.Server { - srv := &http.Server{ - Handler: router, - Addr: address, - - WriteTimeout: writeTimeout, - ReadTimeout: readTimeout, - } - return srv -} - -// Create the addr string for the http.Server -// returns ":" -func addrFromConf(host string, port int) string { - address := fmt.Sprintf("%s:%d", - host, - port, - ) - return address -} - -func NewHTTPServer( - lc fx.Lifecycle, - logger *zap.Logger, - router http.Handler, - httpServerConfig configuration.HTTPServerConfiguration, -) *http.Server { - srv := createServerFromConfigurationHolder(router, httpServerConfig) - lc.Append(fx.Hook{ - OnStart: func(ctx context.Context) error { - ln, err := net.Listen("tcp", srv.Addr) - if err != nil { - return err - } - logger.Sugar().Infof("Ready to serve at %s", srv.Addr) - go srv.Serve(ln) - return nil - }, - OnStop: func(ctx context.Context) error { - // Flush the logger - logger.Sync() - return srv.Shutdown(ctx) - }, - }) - return srv -} diff --git a/commands/server_test.go b/commands/server_test.go deleted file mode 100644 index aa949b71..00000000 --- a/commands/server_test.go +++ /dev/null @@ -1,36 +0,0 @@ -package commands - -// This files holds the tests for the commands/server.go file. - -import ( - "net/http" - "testing" - "time" - - "github.com/ditrit/badaas/configuration" - "github.com/stretchr/testify/assert" -) - -func Test_addrFromConf(t *testing.T) { - expected := "192.168.236.222:25100" - addr := addrFromConf("192.168.236.222", 25100) - assert.Equal(t, expected, addr) -} -func Test_createServer(t *testing.T) { - handl := http.NewServeMux() - timeout := time.Duration(time.Second) - srv := createServer( - handl, - "localhost:8000", - timeout, timeout, - ) - assert.NotNil(t, srv) -} - -func TestCreateServerFromConfigurationHolder(t *testing.T) { - handl := http.NewServeMux() - - srv := createServerFromConfigurationHolder(handl, configuration.NewHTTPServerConfiguration()) - assert.NotNil(t, srv) - -} diff --git a/configuration.md b/configuration.md deleted file mode 100644 index 06383628..00000000 --- a/configuration.md +++ /dev/null @@ -1,150 +0,0 @@ -# Configuration - -To see a complete example of a working config file: head to [`badaas.example.yml`](./badaas.example.yml) - -As said in the README: - -> Badaas can be configured using environment variables, CLI flags or a configuration file. -> CLI flags take priority on the environment variables and the environment variables take priority on the content of the configuration file. - -In this documentation file, we will mainly focus our attention on config files but we won't forget that we can use environement variables and CLI flags to change Badaas' config. - -The config file can be formated in any syntax that [github.com/spf13/viper](https://github.com/spf13/viper) supports but we will only use YAML syntax in our docs. - -- [Configuration](#configuration) - - [Database](#database) - - [Logger](#logger) - - [HTTP Server](#http-server) - - [Default values](#default-values) - - [Session management](#session-management) - -## Database - -We use CockroachDB as a database. It is Postgres compatible, so the information we need to provide will not be a surprise to Postgres users. - -```yml -# The settings for the database. -database: - # The host of the database server. - # (mandatory) - host: e2e-db-1 - - # The port of the database server. - # (mandatory) - port: 26257 - - # The sslmode of the connection to the database server. - # (mandatory) - sslmode: disable - - # The username of the account on the database server. - # (mandatory) - username: root - - # The password of the account on the database server. - # (mandatory) - password: postgres - - # The settings for the initialization of the database server. - init: - # Number of time badaas will try to establish a connection to the database server. - # default (10) - retry: 10 - - # Waiting time between connection, in seconds. - # default (5) - retryTime: 5 -``` - -Please note that the init section `init:` is not mandatory. Badaas is suited with a simple but effective retry mecanism that will retry `database.init.retry` time to establish a connection with the database. Badaas will wait `database.init.retryTime` seconds between each retry. - -## Logger - -Badaas use a structured logger that can output json logs in production and user adapted logs for debug using the `logger.mode` key. - -Badaas offers the possibility to change the log message of the Middleware Logger but provides a sane default. It is formated using the Jinja syntax. The values available are `method`, `url` and `protocol`. - -```yml -# The settings for the logger. -logger: - # Either `dev` or `prod` - # default (`prod`) - mode: prod - request: - # Change the log emitted when badaas receives a request on a valid endpoint. - template: "Receive {{method}} request on {{url}}" -``` - -## HTTP Server - -You can change the host Badaas will bind to, the port and the timeout in seconds. - -Additionaly you can change the number of elements returned by default for a paginated response. - -```yml -# The settings for the http server. -server: - # The address to bind badaas to. - # default ("0.0.0.0") - host: "" - - # The port badaas should use. - # default (8000) - port: 8000 - - # The maximum timeout for the http server in seconds. - # default (15) - timeout: 15 - - # The settings for the pagination. - pagination: - page: - # The maximum number of record per page - # default (100) - max: 100 -``` - -## Default values - -The section allow to change some settings for the first run. - -```yml -# The settings for the first run. -default: - # The admin settings for the first run - admin: - # The admin password for the first run. Won't change is the admin user already exists. - password: admin -``` - -## Session management - -You can change the way the session service handle user sessions. -Session are extended if the user made a request to badaas in the "roll duration". The session duration and the refresh interval of the cache can be changed. They contains some good defaults. - -Please see the diagram below to see what is the roll duration relative to the session duration. - - -```txt - | session duration | - |<----------------------------------------->| - ----|-------------------------|-----------------|----> time - | | | - |<--------------->| - roll duration -``` - -```yml -# The settings for session service -# This section contains some good defaults, don't change thoses value unless you need to. -session: - # The duration of a user session, in seconds - # Default (14400) equal to 4 hours - duration: 14400 - # The refresh interval in seconds. Badaas refresh it's internal session cache periodically. - # Default (30) - pullInterval: 30 - # The duration in which the user can renew it's session by making a request. - # Default (3600) equal to 1 hour - rollDuration: 3600 -``` diff --git a/configuration/CommandsInitializer.go b/configuration/CommandsInitializer.go new file mode 100644 index 00000000..b2f57716 --- /dev/null +++ b/configuration/CommandsInitializer.go @@ -0,0 +1,54 @@ +package configuration + +import ( + "github.com/ditrit/verdeter" +) + +type CommandsInitializer interface { + Init(config *verdeter.VerdeterCommand) error +} + +type CommandsInitializerImpl struct { + KeySetter KeySetter + Initializers []CommandsInitializer +} + +func NewCommandInitializer() CommandsInitializer { + return CommandsInitializerImpl{ + KeySetter: NewKeySetter(), + Initializers: []CommandsInitializer{ + NewConfigCommandsInitializer(), + NewServerCommandsInitializer(), + NewLoggerCommandsInitializer(), + NewDatabaseCommandsInitializer(), + NewInitializationCommandsInitializer(), + NewSessionCommandsInitializer(), + }, + } +} + +func (ci CommandsInitializerImpl) Init(config *verdeter.VerdeterCommand) error { + for _, initializer := range ci.Initializers { + if err := initializer.Init(config); err != nil { + return err + } + } + + return nil +} + +type CommandsKeyInitializer struct { + KeySetter KeySetter + Keys []Key +} + +func (ci CommandsKeyInitializer) Init(config *verdeter.VerdeterCommand) error { + for _, key := range ci.Keys { + err := ci.KeySetter.Set(config, key) + if err != nil { + return err + } + } + + return nil +} diff --git a/configuration/CommandsInitializer_test.go b/configuration/CommandsInitializer_test.go new file mode 100644 index 00000000..52643541 --- /dev/null +++ b/configuration/CommandsInitializer_test.go @@ -0,0 +1,24 @@ +package configuration_test + +import ( + "testing" + + "github.com/spf13/cobra" + "github.com/stretchr/testify/assert" + + "github.com/ditrit/badaas/configuration" + "github.com/ditrit/verdeter" +) + +var rootCfg = verdeter.BuildVerdeterCommand(verdeter.VerdeterConfig{ + Use: "badaas", + Short: "Backend and Distribution as a Service", + Run: doNothing, +}) + +func doNothing(_ *cobra.Command, _ []string) {} + +func TestInitCommandsInitializer(t *testing.T) { + err := configuration.NewCommandInitializer().Init(rootCfg) + assert.Nil(t, err) +} diff --git a/configuration/ConfigCommandsInitializer.go b/configuration/ConfigCommandsInitializer.go new file mode 100644 index 00000000..6398f87e --- /dev/null +++ b/configuration/ConfigCommandsInitializer.go @@ -0,0 +1,17 @@ +package configuration + +import "github.com/ditrit/verdeter" + +func NewConfigCommandsInitializer() CommandsInitializer { + return CommandsKeyInitializer{ + KeySetter: NewKeySetter(), + Keys: []Key{ + { + Name: "config_path", + ValType: verdeter.IsStr, + Usage: "Path to the config file/directory", + DefaultV: ".", + }, + }, + } +} diff --git a/configuration/DatabaseCommandsInitializer.go b/configuration/DatabaseCommandsInitializer.go new file mode 100644 index 00000000..a8f43100 --- /dev/null +++ b/configuration/DatabaseCommandsInitializer.go @@ -0,0 +1,73 @@ +package configuration + +import ( + "fmt" + + "github.com/ditrit/badaas/configuration/defaults" + "github.com/ditrit/verdeter" + "github.com/ditrit/verdeter/validators" +) + +func NewDatabaseCommandsInitializer() CommandsInitializer { + dialectorValidator := validators.AuthorizedValues(DBDialectors...) + + return CommandsKeyInitializer{ + KeySetter: NewKeySetter(), + Keys: []Key{ + { + Name: DatabasePortKey, + ValType: verdeter.IsInt, + Usage: "The port of the database server", + }, + { + Name: DatabaseHostKey, + ValType: verdeter.IsStr, + Usage: "The host of the database server", + }, + { + Name: DatabaseNameKey, + ValType: verdeter.IsStr, + Usage: "The name of the database to use", + }, + { + Name: DatabaseUsernameKey, + ValType: verdeter.IsStr, + Usage: "The username of the account on the database server", + }, + { + Name: DatabasePasswordKey, + ValType: verdeter.IsStr, + Usage: "The password of the account one the database server", + }, + { + Name: DatabaseSslmodeKey, + ValType: verdeter.IsStr, + Usage: "The sslmode to use when connecting to the database server", + }, + { + Name: DatabaseRetryKey, + ValType: verdeter.IsUint, + Usage: fmt.Sprintf( + "The number of times badaas tries to establish a connection with the database (default is %d)", + defaults.DatabaseRetryTimes, + ), + DefaultV: defaults.DatabaseRetryTimes, + }, + { + Name: DatabaseRetryDurationKey, + ValType: verdeter.IsUint, + Usage: fmt.Sprintf( + "The duration in seconds badaas wait between connection attempts (default is %ds)", + defaults.DatabaseRetryDuration, + ), + DefaultV: defaults.DatabaseRetryDuration, + }, + { + Name: DatabaseDialectorKey, + ValType: verdeter.IsStr, + Usage: "The dialector to use to connect with the database server", + Validator: &dialectorValidator, + }, + }, + } +} diff --git a/configuration/DatabaseConfiguration.go b/configuration/DatabaseConfiguration.go index 293874a1..b5d5feeb 100644 --- a/configuration/DatabaseConfiguration.go +++ b/configuration/DatabaseConfiguration.go @@ -5,6 +5,8 @@ import ( "github.com/spf13/viper" "go.uber.org/zap" + + "github.com/ditrit/badaas/utils" ) // The config keys regarding the database settings @@ -17,11 +19,28 @@ const ( DatabaseSslmodeKey string = "database.sslmode" DatabaseRetryKey string = "database.init.retry" DatabaseRetryDurationKey string = "database.init.retryTime" + DatabaseDialectorKey string = "database.dialector" +) + +type DBDialector string + +const ( + PostgreSQL DBDialector = "postgresql" + MySQL DBDialector = "mysql" + SQLite DBDialector = "sqlite" + SQLServer DBDialector = "sqlserver" ) +var DBDialectors = []string{ + string(PostgreSQL), + string(MySQL), + string(SQLite), + string(SQLServer), +} + // Hold the configuration values for the database connection type DatabaseConfiguration interface { - ConfigurationHolder + Holder GetPort() int GetHost() string GetDBName() string @@ -30,6 +49,7 @@ type DatabaseConfiguration interface { GetSSLMode() string GetRetry() uint GetRetryTime() time.Duration + GetDialector() DBDialector } // Concrete implementation of the DatabaseConfiguration interface @@ -42,12 +62,14 @@ type databaseConfigurationImpl struct { sslmode string retry uint retryTime uint + dialector DBDialector } // Instantiate a new configuration holder for the database connection func NewDatabaseConfiguration() DatabaseConfiguration { databaseConfiguration := new(databaseConfigurationImpl) databaseConfiguration.Reload() + return databaseConfiguration } @@ -61,6 +83,7 @@ func (databaseConfiguration *databaseConfigurationImpl) Reload() { databaseConfiguration.sslmode = viper.GetString(DatabaseSslmodeKey) databaseConfiguration.retry = viper.GetUint(DatabaseRetryKey) databaseConfiguration.retryTime = viper.GetUint(DatabaseRetryDurationKey) + databaseConfiguration.dialector = DBDialector(viper.GetString(DatabaseDialectorKey)) } // Return the port of the database server @@ -100,7 +123,12 @@ func (databaseConfiguration *databaseConfigurationImpl) GetRetry() uint { // Return the waiting time between the database connections in seconds func (databaseConfiguration *databaseConfigurationImpl) GetRetryTime() time.Duration { - return intToSecond(int(databaseConfiguration.retryTime)) + return utils.IntToSecond(int(databaseConfiguration.retryTime)) +} + +// Return the dialector to be used to connect to database +func (databaseConfiguration *databaseConfigurationImpl) GetDialector() DBDialector { + return databaseConfiguration.dialector } // Log the values provided by the configuration holder diff --git a/configuration/DatabaseConfiguration_test.go b/configuration/DatabaseConfiguration_test.go index 21d5447c..5a08a0f2 100644 --- a/configuration/DatabaseConfiguration_test.go +++ b/configuration/DatabaseConfiguration_test.go @@ -5,13 +5,14 @@ import ( "testing" "time" - "github.com/ditrit/badaas/configuration" "github.com/spf13/viper" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap" "go.uber.org/zap/zapcore" "go.uber.org/zap/zaptest/observer" + + "github.com/ditrit/badaas/configuration" ) var databaseConfigurationString = ` @@ -35,6 +36,7 @@ database: func setupViperEnvironment(configurationString string) { viper.Reset() viper.SetConfigType("yaml") + err := viper.ReadConfig(strings.NewReader(configurationString)) if err != nil { panic(err) @@ -43,51 +45,63 @@ func setupViperEnvironment(configurationString string) { func TestDatabaseConfigurationNewDBConfig(t *testing.T) { setupViperEnvironment(databaseConfigurationString) + databaseConfiguration := configuration.NewDatabaseConfiguration() assert.NotNil(t, databaseConfiguration, "the database configuration should not be nil") } func TestDatabaseConfigurationGetPort(t *testing.T) { setupViperEnvironment(databaseConfigurationString) + databaseConfiguration := configuration.NewDatabaseConfiguration() assert.Equal(t, 26257, databaseConfiguration.GetPort(), "should be equals") } func TestDatabaseConfigurationGetHost(t *testing.T) { setupViperEnvironment(databaseConfigurationString) + databaseConfiguration := configuration.NewDatabaseConfiguration() assert.Equal(t, "e2e-db-1", databaseConfiguration.GetHost()) } func TestDatabaseConfigurationGetUsername(t *testing.T) { setupViperEnvironment(databaseConfigurationString) + databaseConfiguration := configuration.NewDatabaseConfiguration() assert.Equal(t, "root", databaseConfiguration.GetUsername()) } + func TestDatabaseConfigurationGetPassword(t *testing.T) { setupViperEnvironment(databaseConfigurationString) + databaseConfiguration := configuration.NewDatabaseConfiguration() assert.Equal(t, "postgres", databaseConfiguration.GetPassword()) } + func TestDatabaseConfigurationGetSSLMode(t *testing.T) { setupViperEnvironment(databaseConfigurationString) + databaseConfiguration := configuration.NewDatabaseConfiguration() assert.Equal(t, "disable", databaseConfiguration.GetSSLMode()) } + func TestDatabaseConfigurationGetDBName(t *testing.T) { setupViperEnvironment(databaseConfigurationString) + databaseConfiguration := configuration.NewDatabaseConfiguration() assert.Equal(t, "badaas_db", databaseConfiguration.GetDBName()) } func TestDatabaseConfigurationGetRetryTime(t *testing.T) { setupViperEnvironment(databaseConfigurationString) + databaseConfiguration := configuration.NewDatabaseConfiguration() - assert.Equal(t, time.Duration(5*time.Second), databaseConfiguration.GetRetryTime()) + assert.Equal(t, 5*time.Second, databaseConfiguration.GetRetryTime()) } func TestDatabaseConfigurationGetRetry(t *testing.T) { setupViperEnvironment(databaseConfigurationString) + databaseConfiguration := configuration.NewDatabaseConfiguration() assert.Equal(t, uint(10), databaseConfiguration.GetRetry()) } diff --git a/configuration/ConfigurationHolder.go b/configuration/Holder.go similarity index 87% rename from configuration/ConfigurationHolder.go rename to configuration/Holder.go index 95f150e4..a749222b 100644 --- a/configuration/ConfigurationHolder.go +++ b/configuration/Holder.go @@ -3,7 +3,7 @@ package configuration import "go.uber.org/zap" // Every configuration holder must implement this interface -type ConfigurationHolder interface { +type Holder interface { // Reload the values provided by the configuration holder Reload() diff --git a/configuration/HttpServerConfiguration.go b/configuration/HttpServerConfiguration.go index 0a00f5f3..6adf5465 100644 --- a/configuration/HttpServerConfiguration.go +++ b/configuration/HttpServerConfiguration.go @@ -1,23 +1,27 @@ package configuration import ( + "fmt" "time" "github.com/spf13/viper" "go.uber.org/zap" + + "github.com/ditrit/badaas/utils" ) // The config keys regarding the http server settings const ( - ServerTimeoutKey string = "server.timeout" - ServerHostKey string = "server.host" - ServerPortKey string = "server.port" - ServerPaginationMaxElemPerPage string = "server.pagination.page.max" + ServerTimeoutKey string = "server.timeout" + ServerHostKey string = "server.host" + ServerPortKey string = "server.port" + ServerPaginationMaxElemPerPageKey string = "server.pagination.page.max" ) // Hold the configuration values for the http server type HTTPServerConfiguration interface { - ConfigurationHolder + Holder + GetAddr() string GetHost() string GetPort() int GetMaxTimeout() time.Duration @@ -34,6 +38,7 @@ type hTTPServerConfigurationImpl struct { func NewHTTPServerConfiguration() HTTPServerConfiguration { httpServerConfiguration := new(hTTPServerConfigurationImpl) httpServerConfiguration.Reload() + return httpServerConfiguration } @@ -41,7 +46,7 @@ func NewHTTPServerConfiguration() HTTPServerConfiguration { func (httpServerConfiguration *hTTPServerConfigurationImpl) Reload() { httpServerConfiguration.host = viper.GetString(ServerHostKey) httpServerConfiguration.port = viper.GetInt(ServerPortKey) - httpServerConfiguration.timeout = intToSecond(viper.GetInt(ServerTimeoutKey)) + httpServerConfiguration.timeout = utils.IntToSecond(viper.GetInt(ServerTimeoutKey)) } // Return the host addr @@ -54,7 +59,7 @@ func (httpServerConfiguration *hTTPServerConfigurationImpl) GetPort() int { return httpServerConfiguration.port } -// Return the maximum timout for read and write +// Return the maximum timeout for read and write func (httpServerConfiguration *hTTPServerConfigurationImpl) GetMaxTimeout() time.Duration { return httpServerConfiguration.timeout } @@ -67,3 +72,11 @@ func (httpServerConfiguration *hTTPServerConfigurationImpl) Log(logger *zap.Logg zap.Duration("timeout", httpServerConfiguration.timeout), ) } + +// Create the addr string in format: ":" +func (httpServerConfiguration *hTTPServerConfigurationImpl) GetAddr() string { + return fmt.Sprintf("%s:%d", + httpServerConfiguration.GetHost(), + httpServerConfiguration.GetPort(), + ) +} diff --git a/configuration/HttpServerConfiguration_test.go b/configuration/HttpServerConfiguration_test.go index d50119c5..fc150991 100644 --- a/configuration/HttpServerConfiguration_test.go +++ b/configuration/HttpServerConfiguration_test.go @@ -4,12 +4,13 @@ import ( "testing" "time" - "github.com/ditrit/badaas/configuration" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap" "go.uber.org/zap/zapcore" "go.uber.org/zap/zaptest/observer" + + "github.com/ditrit/badaas/configuration" ) var HTTPServerConfigurationString = `server: @@ -24,19 +25,30 @@ func TestHTTPServerConfigurationNewHttpServerConfiguration(t *testing.T) { func TestHTTPServerConfigurationGetPort(t *testing.T) { setupViperEnvironment(HTTPServerConfigurationString) - HTTPServerConfiguration := configuration.NewHTTPServerConfiguration() - assert.Equal(t, 8000, HTTPServerConfiguration.GetPort()) + + httpServerConfiguration := configuration.NewHTTPServerConfiguration() + assert.Equal(t, 8000, httpServerConfiguration.GetPort()) } + func TestHTTPServerConfigurationGetHost(t *testing.T) { setupViperEnvironment(HTTPServerConfigurationString) - HTTPServerConfiguration := configuration.NewHTTPServerConfiguration() - assert.Equal(t, "0.0.0.0", HTTPServerConfiguration.GetHost()) + + httpServerConfiguration := configuration.NewHTTPServerConfiguration() + assert.Equal(t, "0.0.0.0", httpServerConfiguration.GetHost()) +} + +func TestHTTPServerConfigurationGetAddr(t *testing.T) { + setupViperEnvironment(HTTPServerConfigurationString) + + httpServerConfiguration := configuration.NewHTTPServerConfiguration() + assert.Equal(t, "0.0.0.0:8000", httpServerConfiguration.GetAddr()) } func TestHTTPServerConfigurationGetMaxTimeout(t *testing.T) { setupViperEnvironment(HTTPServerConfigurationString) - HTTPServerConfiguration := configuration.NewHTTPServerConfiguration() - assert.Equal(t, time.Duration(15*time.Second), HTTPServerConfiguration.GetMaxTimeout()) + + httpServerConfiguration := configuration.NewHTTPServerConfiguration() + assert.Equal(t, 15*time.Second, httpServerConfiguration.GetMaxTimeout()) } func TestHTTPServerConfigurationLog(t *testing.T) { @@ -45,8 +57,8 @@ func TestHTTPServerConfigurationLog(t *testing.T) { observedZapCore, observedLogs := observer.New(zap.DebugLevel) observedLogger := zap.New(observedZapCore) - HTTPServerConfiguration := configuration.NewHTTPServerConfiguration() - HTTPServerConfiguration.Log(observedLogger) + httpServerConfiguration := configuration.NewHTTPServerConfiguration() + httpServerConfiguration.Log(observedLogger) require.Equal(t, 1, observedLogs.Len()) log := observedLogs.All()[0] @@ -55,6 +67,6 @@ func TestHTTPServerConfigurationLog(t *testing.T) { assert.ElementsMatch(t, []zap.Field{ {Key: "port", Type: zapcore.Int64Type, Integer: 8000}, {Key: "host", Type: zapcore.StringType, String: "0.0.0.0"}, - {Key: "timeout", Type: zapcore.DurationType, Integer: int64(time.Duration(time.Second * 15))}, + {Key: "timeout", Type: zapcore.DurationType, Integer: int64(time.Second * 15)}, }, log.Context) } diff --git a/configuration/InitializationCommandsInitializer.go b/configuration/InitializationCommandsInitializer.go new file mode 100644 index 00000000..7f6792a6 --- /dev/null +++ b/configuration/InitializationCommandsInitializer.go @@ -0,0 +1,19 @@ +package configuration + +import ( + "github.com/ditrit/verdeter" +) + +func NewInitializationCommandsInitializer() CommandsInitializer { + return CommandsKeyInitializer{ + KeySetter: NewKeySetter(), + Keys: []Key{ + { + Name: InitializationDefaultAdminPasswordKey, + ValType: verdeter.IsStr, + Usage: "Set the default admin password is the admin user is not created yet.", + DefaultV: "admin", + }, + }, + } +} diff --git a/configuration/InitializationConfiguration.go b/configuration/InitializationConfiguration.go index 8e365d23..ac70938c 100644 --- a/configuration/InitializationConfiguration.go +++ b/configuration/InitializationConfiguration.go @@ -12,7 +12,7 @@ const ( // Hold the configuration values for the initialization type InitializationConfiguration interface { - ConfigurationHolder + Holder GetAdminPassword() string } @@ -25,6 +25,7 @@ type initializationConfigurationIml struct { func NewInitializationConfiguration() InitializationConfiguration { initializationConfiguration := &initializationConfigurationIml{} initializationConfiguration.Reload() + return initializationConfiguration } diff --git a/configuration/InitializationConfiguration_test.go b/configuration/InitializationConfiguration_test.go index b80fbd9d..2d548576 100644 --- a/configuration/InitializationConfiguration_test.go +++ b/configuration/InitializationConfiguration_test.go @@ -3,12 +3,13 @@ package configuration_test import ( "testing" - "github.com/ditrit/badaas/configuration" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap" "go.uber.org/zap/zapcore" "go.uber.org/zap/zaptest/observer" + + "github.com/ditrit/badaas/configuration" ) var initializationConfigurationString = `default: @@ -21,6 +22,7 @@ func TestInitializationConfigurationInitializationConfiguration(t *testing.T) { func TestInitializationConfigurationGetInit(t *testing.T) { setupViperEnvironment(initializationConfigurationString) + initializationConfiguration := configuration.NewInitializationConfiguration() assert.Equal(t, "admin", initializationConfiguration.GetAdminPassword()) } diff --git a/configuration/KeySetter.go b/configuration/KeySetter.go new file mode 100644 index 00000000..1cf8b259 --- /dev/null +++ b/configuration/KeySetter.go @@ -0,0 +1,45 @@ +package configuration + +import ( + "github.com/ditrit/verdeter" + "github.com/ditrit/verdeter/models" +) + +type KeySetter interface { + Set(config *verdeter.VerdeterCommand, key Key) error +} + +type KeySetterImpl struct{} + +func NewKeySetter() KeySetter { + return KeySetterImpl{} +} + +func (ks KeySetterImpl) Set(config *verdeter.VerdeterCommand, key Key) error { + if err := config.GKey(key.Name, key.ValType, "", key.Usage); err != nil { + return err + } + + if key.Required { + config.SetRequired(key.Name) + } + + if key.DefaultV != nil { + config.SetDefault(key.Name, key.DefaultV) + } + + if key.Validator != nil { + config.AddValidator(key.Name, *key.Validator) + } + + return nil +} + +type Key struct { + Name string + ValType models.ConfigType + Usage string + Required bool + DefaultV any + Validator *models.Validator +} diff --git a/configuration/LoggerCommandsInitializer.go b/configuration/LoggerCommandsInitializer.go new file mode 100644 index 00000000..a4ca7414 --- /dev/null +++ b/configuration/LoggerCommandsInitializer.go @@ -0,0 +1,29 @@ +package configuration + +import ( + "github.com/ditrit/verdeter" + "github.com/ditrit/verdeter/validators" +) + +func NewLoggerCommandsInitializer() CommandsInitializer { + modeValidator := validators.AuthorizedValues("prod", "dev") + + return CommandsKeyInitializer{ + KeySetter: NewKeySetter(), + Keys: []Key{ + { + Name: LoggerRequestTemplateKey, + ValType: verdeter.IsStr, + Usage: "Template message for all request logs", + DefaultV: "Receive {{method}} request on {{url}}", + }, + { + Name: LoggerModeKey, + ValType: verdeter.IsStr, + Usage: "The logger mode (default to \"prod\")", + DefaultV: "prod", + Validator: &modeValidator, + }, + }, + } +} diff --git a/configuration/LoggerConfiguration.go b/configuration/LoggerConfiguration.go index 133da8c8..a92ce125 100644 --- a/configuration/LoggerConfiguration.go +++ b/configuration/LoggerConfiguration.go @@ -13,7 +13,7 @@ const ( // Hold the configuration values for the logger type LoggerConfiguration interface { - ConfigurationHolder + Holder GetMode() string GetRequestTemplate() string } @@ -27,6 +27,7 @@ type loggerConfigurationImpl struct { func NewLoggerConfiguration() LoggerConfiguration { loggerConfiguration := new(loggerConfigurationImpl) loggerConfiguration.Reload() + return loggerConfiguration } diff --git a/configuration/LoggerConfiguration_test.go b/configuration/LoggerConfiguration_test.go index 53b005bb..ca97bf78 100644 --- a/configuration/LoggerConfiguration_test.go +++ b/configuration/LoggerConfiguration_test.go @@ -3,12 +3,13 @@ package configuration_test import ( "testing" - "github.com/ditrit/badaas/configuration" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap" "go.uber.org/zap/zapcore" "go.uber.org/zap/zaptest/observer" + + "github.com/ditrit/badaas/configuration" ) var LoggerConfigurationString = `logger: @@ -18,19 +19,21 @@ var LoggerConfigurationString = `logger: ` func TestLoggerConfigurationNewLoggerConfiguration(t *testing.T) { - assert.NotNil(t, configuration.NewLoggerConfiguration(), "the contructor for LoggerConfiguration should not return a nil value") + assert.NotNil(t, configuration.NewLoggerConfiguration(), "the constructor for LoggerConfiguration should not return a nil value") } func TestLoggerConfigurationLoggerGetMode(t *testing.T) { setupViperEnvironment(LoggerConfigurationString) - LoggerConfiguration := configuration.NewLoggerConfiguration() - assert.Equal(t, "prod", LoggerConfiguration.GetMode()) + + loggerConfiguration := configuration.NewLoggerConfiguration() + assert.Equal(t, "prod", loggerConfiguration.GetMode()) } func TestLoggerConfigurationLoggerRequestTemplate(t *testing.T) { setupViperEnvironment(LoggerConfigurationString) - LoggerConfiguration := configuration.NewLoggerConfiguration() - assert.Equal(t, "{proto} {method} {url}", LoggerConfiguration.GetRequestTemplate()) + + loggerConfiguration := configuration.NewLoggerConfiguration() + assert.Equal(t, "{proto} {method} {url}", loggerConfiguration.GetRequestTemplate()) } func TestLoggerConfigurationLog(t *testing.T) { @@ -39,8 +42,8 @@ func TestLoggerConfigurationLog(t *testing.T) { observedZapCore, observedLogs := observer.New(zap.DebugLevel) observedLogger := zap.New(observedZapCore) - LoggerConfiguration := configuration.NewLoggerConfiguration() - LoggerConfiguration.Log(observedLogger) + loggerConfiguration := configuration.NewLoggerConfiguration() + loggerConfiguration.Log(observedLogger) require.Equal(t, 1, observedLogs.Len()) log := observedLogs.All()[0] diff --git a/configuration/PaginationConfiguration.go b/configuration/PaginationConfiguration.go index 264a8233..2bcd16a0 100644 --- a/configuration/PaginationConfiguration.go +++ b/configuration/PaginationConfiguration.go @@ -7,7 +7,7 @@ import ( // Hold the configuration values for the pagination type PaginationConfiguration interface { - ConfigurationHolder + Holder GetMaxElemPerPage() uint } @@ -20,6 +20,7 @@ type paginationConfigurationImpl struct { func NewPaginationConfiguration() PaginationConfiguration { paginationConfiguration := new(paginationConfigurationImpl) paginationConfiguration.Reload() + return paginationConfiguration } @@ -30,7 +31,7 @@ func (paginationConfiguration *paginationConfigurationImpl) GetMaxElemPerPage() // Reload pagination configuration func (paginationConfiguration *paginationConfigurationImpl) Reload() { - paginationConfiguration.pagesNb = viper.GetUint(ServerPaginationMaxElemPerPage) + paginationConfiguration.pagesNb = viper.GetUint(ServerPaginationMaxElemPerPageKey) } // Log the values provided by the configuration holder diff --git a/configuration/PaginationConfiguration_test.go b/configuration/PaginationConfiguration_test.go index aaca2116..341eba3c 100644 --- a/configuration/PaginationConfiguration_test.go +++ b/configuration/PaginationConfiguration_test.go @@ -3,12 +3,13 @@ package configuration_test import ( "testing" - "github.com/ditrit/badaas/configuration" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap" "go.uber.org/zap/zapcore" "go.uber.org/zap/zaptest/observer" + + "github.com/ditrit/badaas/configuration" ) var PaginationConfigurationString = `server.pagination.page.max: 12` @@ -19,8 +20,9 @@ func TestPaginationConfigurationNewPaginationConfiguration(t *testing.T) { func TestPaginationConfigurationGetMaxElemPerPage(t *testing.T) { setupViperEnvironment(PaginationConfigurationString) - PaginationConfiguration := configuration.NewPaginationConfiguration() - assert.Equal(t, uint(12), PaginationConfiguration.GetMaxElemPerPage()) + + paginationConfiguration := configuration.NewPaginationConfiguration() + assert.Equal(t, uint(12), paginationConfiguration.GetMaxElemPerPage()) } func TestPaginationConfigurationLog(t *testing.T) { @@ -29,8 +31,8 @@ func TestPaginationConfigurationLog(t *testing.T) { observedZapCore, observedLogs := observer.New(zap.DebugLevel) observedLogger := zap.New(observedZapCore) - PaginationConfiguration := configuration.NewPaginationConfiguration() - PaginationConfiguration.Log(observedLogger) + paginationConfiguration := configuration.NewPaginationConfiguration() + paginationConfiguration.Log(observedLogger) require.Equal(t, 1, observedLogs.Len()) log := observedLogs.All()[0] diff --git a/configuration/ServerCommandsInitializer.go b/configuration/ServerCommandsInitializer.go new file mode 100644 index 00000000..1c6ee03b --- /dev/null +++ b/configuration/ServerCommandsInitializer.go @@ -0,0 +1,42 @@ +package configuration + +import ( + "fmt" + + "github.com/ditrit/badaas/configuration/defaults" + "github.com/ditrit/verdeter" + "github.com/ditrit/verdeter/validators" +) + +func NewServerCommandsInitializer() CommandsInitializer { + return CommandsKeyInitializer{ + KeySetter: NewKeySetter(), + Keys: []Key{ + { + Name: ServerTimeoutKey, + ValType: verdeter.IsInt, + Usage: fmt.Sprintf("Maximum timeout of the http server in second (default is %ds)", defaults.ServerTimeout), + DefaultV: defaults.ServerTimeout, + }, + { + Name: ServerHostKey, + ValType: verdeter.IsStr, + Usage: fmt.Sprintf("Address to bind (default is %s)", defaults.ServerHost), + DefaultV: defaults.ServerHost, + }, + { + Name: ServerPortKey, + ValType: verdeter.IsInt, + Usage: fmt.Sprintf("Port to bind (default is %d)", defaults.ServerPort), + DefaultV: defaults.ServerPort, + Validator: &validators.CheckTCPHighPort, + }, + { + Name: ServerPaginationMaxElemPerPageKey, + ValType: verdeter.IsUint, + Usage: fmt.Sprintf("The max number of records returned per page (default is %d)", defaults.ServerPaginationMaxElemPerPage), + DefaultV: defaults.ServerPaginationMaxElemPerPage, + }, + }, + } +} diff --git a/configuration/SessionCommandsInitializer.go b/configuration/SessionCommandsInitializer.go new file mode 100644 index 00000000..70e56c60 --- /dev/null +++ b/configuration/SessionCommandsInitializer.go @@ -0,0 +1,32 @@ +package configuration + +import ( + "github.com/ditrit/badaas/configuration/defaults" + "github.com/ditrit/verdeter" +) + +func NewSessionCommandsInitializer() CommandsInitializer { + return CommandsKeyInitializer{ + KeySetter: NewKeySetter(), + Keys: []Key{ + { + Name: SessionDurationKey, + ValType: verdeter.IsUint, + Usage: "The duration of a user session in seconds", + DefaultV: defaults.SessionDuration, + }, + { + Name: SessionPullIntervalKey, + ValType: verdeter.IsUint, + Usage: "The refresh interval in seconds. Badaas refresh it's internal session cache periodically", + DefaultV: defaults.SessionPullInterval, + }, + { + Name: SessionRollIntervalKey, + ValType: verdeter.IsUint, + Usage: "The interval in which the user can renew it's session by making a request", + DefaultV: defaults.SessionRollInterval, + }, + }, + } +} diff --git a/configuration/SessionConfiguration.go b/configuration/SessionConfiguration.go index 3a76ef6f..242a7739 100644 --- a/configuration/SessionConfiguration.go +++ b/configuration/SessionConfiguration.go @@ -5,6 +5,8 @@ import ( "github.com/spf13/viper" "go.uber.org/zap" + + "github.com/ditrit/badaas/utils" ) // The config keys regarding the session handling settings @@ -16,7 +18,7 @@ const ( // Hold the configuration values to handle the sessions type SessionConfiguration interface { - ConfigurationHolder + Holder GetSessionDuration() time.Duration GetPullInterval() time.Duration GetRollDuration() time.Duration @@ -33,6 +35,7 @@ type sessionConfigurationImpl struct { func NewSessionConfiguration() SessionConfiguration { sessionConfiguration := new(sessionConfigurationImpl) sessionConfiguration.Reload() + return sessionConfiguration } @@ -53,9 +56,9 @@ func (sessionConfiguration *sessionConfigurationImpl) GetRollDuration() time.Dur // Reload session configuration func (sessionConfiguration *sessionConfigurationImpl) Reload() { - sessionConfiguration.sessionDuration = intToSecond(int(viper.GetUint(SessionDurationKey))) - sessionConfiguration.pullInterval = intToSecond(int(viper.GetUint(SessionPullIntervalKey))) - sessionConfiguration.rollDuration = intToSecond(int(viper.GetUint(SessionRollIntervalKey))) + sessionConfiguration.sessionDuration = utils.IntToSecond(int(viper.GetUint(SessionDurationKey))) + sessionConfiguration.pullInterval = utils.IntToSecond(int(viper.GetUint(SessionPullIntervalKey))) + sessionConfiguration.rollDuration = utils.IntToSecond(int(viper.GetUint(SessionRollIntervalKey))) } // Log the values provided by the configuration holder diff --git a/configuration/SessionConfiguration_test.go b/configuration/SessionConfiguration_test.go index 05a9e101..ae7239c2 100644 --- a/configuration/SessionConfiguration_test.go +++ b/configuration/SessionConfiguration_test.go @@ -4,12 +4,13 @@ import ( "testing" "time" - "github.com/ditrit/badaas/configuration" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap" "go.uber.org/zap/zapcore" "go.uber.org/zap/zaptest/observer" + + "github.com/ditrit/badaas/configuration" ) var SessionConfigurationString = `session: @@ -18,25 +19,28 @@ var SessionConfigurationString = `session: rollDuration: 10 # 10 seconds` func TestSessionConfigurationNewSessionConfiguration(t *testing.T) { - assert.NotNil(t, configuration.NewSessionConfiguration(), "the contructor for PaginationConfiguration should not return a nil value") + assert.NotNil(t, configuration.NewSessionConfiguration(), "the constructor for PaginationConfiguration should not return a nil value") } func TestSessionConfigurationGetSessionDuration(t *testing.T) { setupViperEnvironment(SessionConfigurationString) - SessionConfiguration := configuration.NewSessionConfiguration() - assert.Equal(t, time.Duration(time.Hour), SessionConfiguration.GetSessionDuration()) + + sessionConfiguration := configuration.NewSessionConfiguration() + assert.Equal(t, time.Hour, sessionConfiguration.GetSessionDuration()) } -func TestSessionConfigurationGetPullIntervall(t *testing.T) { +func TestSessionConfigurationGetPullInterval(t *testing.T) { setupViperEnvironment(SessionConfigurationString) - SessionConfiguration := configuration.NewSessionConfiguration() - assert.Equal(t, time.Duration(time.Second*30), SessionConfiguration.GetPullInterval()) + + sessionConfiguration := configuration.NewSessionConfiguration() + assert.Equal(t, time.Second*30, sessionConfiguration.GetPullInterval()) } -func TestSessionConfigurationGetRollIntervall(t *testing.T) { +func TestSessionConfigurationGetRollDuration(t *testing.T) { setupViperEnvironment(SessionConfigurationString) - SessionConfiguration := configuration.NewSessionConfiguration() - assert.Equal(t, time.Duration(time.Second*10), SessionConfiguration.GetRollDuration()) + + sessionConfiguration := configuration.NewSessionConfiguration() + assert.Equal(t, time.Second*10, sessionConfiguration.GetRollDuration()) } func TestSessionConfigurationLog(t *testing.T) { @@ -45,16 +49,16 @@ func TestSessionConfigurationLog(t *testing.T) { observedZapCore, observedLogs := observer.New(zap.DebugLevel) observedLogger := zap.New(observedZapCore) - PaginationConfiguration := configuration.NewSessionConfiguration() - PaginationConfiguration.Log(observedLogger) + sessionConfiguration := configuration.NewSessionConfiguration() + sessionConfiguration.Log(observedLogger) require.Equal(t, 1, observedLogs.Len()) log := observedLogs.All()[0] assert.Equal(t, "Session configuration", log.Message) require.Len(t, log.Context, 3) assert.ElementsMatch(t, []zap.Field{ - {Key: "sessionDuration", Type: zapcore.DurationType, Integer: int64(time.Duration(time.Hour))}, - {Key: "pullInterval", Type: zapcore.DurationType, Integer: int64(time.Duration(time.Second * 30))}, - {Key: "rollDuration", Type: zapcore.DurationType, Integer: int64(time.Duration(time.Second * 10))}, + {Key: "sessionDuration", Type: zapcore.DurationType, Integer: int64(time.Hour)}, + {Key: "pullInterval", Type: zapcore.DurationType, Integer: int64(time.Second * 30)}, + {Key: "rollDuration", Type: zapcore.DurationType, Integer: int64(time.Second * 10)}, }, log.Context) } diff --git a/configuration/defaults/defaults.go b/configuration/defaults/defaults.go new file mode 100644 index 00000000..922a9f30 --- /dev/null +++ b/configuration/defaults/defaults.go @@ -0,0 +1,14 @@ +package defaults + +// Database +const ( + DatabaseRetryTimes = uint(10) + DatabaseRetryDuration = uint(5) + ServerTimeout = 15 + ServerHost = "0.0.0.0" + ServerPort = 8000 + ServerPaginationMaxElemPerPage = uint(100) + SessionDuration = uint(3600 * 4) // 4 hours + SessionPullInterval = uint(30) // 30 seconds + SessionRollInterval = uint(3600) // 1 hour +) diff --git a/controllers/ModuleFx.go b/controllers/ModuleFx.go deleted file mode 100644 index ccab54eb..00000000 --- a/controllers/ModuleFx.go +++ /dev/null @@ -1,10 +0,0 @@ -package controllers - -import "go.uber.org/fx" - -// ControllerModule for fx -var ControllerModule = fx.Module( - "controllers", - fx.Provide(NewInfoController), - fx.Provide(NewBasicAuthentificationController), -) diff --git a/controllers/basicAuth.go b/controllers/basicAuth.go index d98c4de5..9061ffd8 100644 --- a/controllers/basicAuth.go +++ b/controllers/basicAuth.go @@ -1,49 +1,47 @@ package controllers import ( - "encoding/json" + "errors" + "fmt" "net/http" + "time" + "go.uber.org/zap" + + "github.com/ditrit/badaas/badorm" "github.com/ditrit/badaas/httperrors" "github.com/ditrit/badaas/persistence/models/dto" "github.com/ditrit/badaas/services/sessionservice" "github.com/ditrit/badaas/services/userservice" - "go.uber.org/zap" ) -var ( - // Sent when the request is malformed - HTTPErrRequestMalformed httperrors.HTTPError = httperrors.NewHTTPError( - http.StatusBadRequest, - "Request malformed", - "The schema of the received data is not correct", - nil, - false) -) +var HERRAccessToken = func(err error) httperrors.HTTPError { + return httperrors.NewInternalServerError("access token error", "unable to create access token", err) +} + +const SessionCookieDuration = 48 * time.Hour -// Basic Authentification Controller -type BasicAuthentificationController interface { +type BasicAuthenticationController interface { BasicLoginHandler(http.ResponseWriter, *http.Request) (any, httperrors.HTTPError) Logout(http.ResponseWriter, *http.Request) (any, httperrors.HTTPError) } // Check interface compliance -var _ BasicAuthentificationController = (*basicAuthentificationController)(nil) +var _ BasicAuthenticationController = (*basicAuthenticationController)(nil) -// BasicAuthentificationController implementation -type basicAuthentificationController struct { +type basicAuthenticationController struct { logger *zap.Logger userService userservice.UserService sessionService sessionservice.SessionService } -// BasicAuthentificationController contructor -func NewBasicAuthentificationController( +// BasicAuthenticationController constructor +func NewBasicAuthenticationController( logger *zap.Logger, userService userservice.UserService, sessionService sessionservice.SessionService, -) BasicAuthentificationController { - return &basicAuthentificationController{ +) BasicAuthenticationController { + return &basicAuthenticationController{ logger: logger, userService: userService, sessionService: sessionService, @@ -51,25 +49,42 @@ func NewBasicAuthentificationController( } // Log In with username and password -func (basicAuthController *basicAuthentificationController) BasicLoginHandler(w http.ResponseWriter, r *http.Request) (any, httperrors.HTTPError) { +func (basicAuthController *basicAuthenticationController) BasicLoginHandler(w http.ResponseWriter, r *http.Request) (any, httperrors.HTTPError) { var loginJSONStruct dto.UserLoginDTO - err := json.NewDecoder(r.Body).Decode(&loginJSONStruct) - if err != nil { - return nil, HTTPErrRequestMalformed - } - user, herr := basicAuthController.userService.GetUser(loginJSONStruct) + + herr := decodeJSON(r, &loginJSONStruct) if herr != nil { return nil, herr } + user, err := basicAuthController.userService.GetUser(loginJSONStruct) + if err != nil { + if errors.Is(err, badorm.ErrObjectNotFound) { + return nil, httperrors.NewErrorNotFound( + "user", + fmt.Sprintf("no user found with email %q", loginJSONStruct.Email), + ) + } else if errors.Is(err, userservice.ErrWrongPassword) { + return nil, httperrors.NewUnauthorizedError( + "wrong password", "the provided password is incorrect", + ) + } + + return nil, httperrors.NewDBError(err) + } + // On valid password, generate a session and return it's uuid to the client - herr = basicAuthController.sessionService.LogUserIn(user, w) - if herr != nil { - return nil, herr + session, err := basicAuthController.sessionService.LogUserIn(user) + if err != nil { + return nil, httperrors.NewDBError(err) + } + err = createAndSetAccessTokenCookie(w, session.ID.String()) + if err != nil { + return nil, HERRAccessToken(err) } - return dto.DTOLoginSuccess{ + return dto.LoginSuccess{ Email: user.Email, ID: user.ID.String(), Username: user.Username, @@ -77,7 +92,36 @@ func (basicAuthController *basicAuthentificationController) BasicLoginHandler(w } // Log Out the user -func (basicAuthController *basicAuthentificationController) Logout(w http.ResponseWriter, r *http.Request) (any, httperrors.HTTPError) { - basicAuthController.sessionService.LogUserOut(sessionservice.GetSessionClaimsFromContext(r.Context()), w) +func (basicAuthController *basicAuthenticationController) Logout(w http.ResponseWriter, r *http.Request) (any, httperrors.HTTPError) { + herr := basicAuthController.sessionService.LogUserOut(sessionservice.GetSessionClaimsFromContext(r.Context())) + if herr != nil { + return nil, herr + } + + err := createAndSetAccessTokenCookie(w, "") + if err != nil { + return nil, HERRAccessToken(err) + } + return nil, nil } + +// Create an access token and send it in a cookie +func createAndSetAccessTokenCookie(w http.ResponseWriter, sessionUUID string) error { + accessToken := &http.Cookie{ + Name: "access_token", + Path: "/", + Value: sessionUUID, + HttpOnly: true, + SameSite: http.SameSiteNoneMode, // TODO change to http.SameSiteStrictMode in prod + Secure: false, // TODO change to true in prod + Expires: time.Now().Add(SessionCookieDuration), + } + if err := accessToken.Valid(); err != nil { + return err + } + + http.SetCookie(w, accessToken) + + return nil +} diff --git a/controllers/basicAuth_test.go b/controllers/basicAuth_test.go index 2fecf795..25751d4a 100644 --- a/controllers/basicAuth_test.go +++ b/controllers/basicAuth_test.go @@ -1,22 +1,35 @@ package controllers_test import ( + "net/http" "net/http/httptest" "strings" "testing" + "time" + "github.com/stretchr/testify/assert" + "go.uber.org/zap" + "go.uber.org/zap/zaptest/observer" + + "github.com/ditrit/badaas/badorm" "github.com/ditrit/badaas/controllers" "github.com/ditrit/badaas/httperrors" mocksSessionService "github.com/ditrit/badaas/mocks/services/sessionservice" mocksUserService "github.com/ditrit/badaas/mocks/services/userservice" "github.com/ditrit/badaas/persistence/models" "github.com/ditrit/badaas/persistence/models/dto" - "github.com/google/uuid" - "github.com/stretchr/testify/assert" - "go.uber.org/zap" - "go.uber.org/zap/zaptest/observer" ) +// ExampleErr is an HTTPError instance useful for testing. If the code does not care +// about HTTPError specifics, and only needs to return the HTTPError for example, this +// HTTPError should be used to make the test code more readable. +var ExampleErr httperrors.HTTPError = &httperrors.HTTPErrorImpl{ + Status: -1, + Err: "TESTING ERROR", + Message: "USE ONLY FOR TESTING", + GolangError: nil, +} + func Test_BasicLoginHandler_MalformedRequest(t *testing.T) { core, _ := observer.New(zap.DebugLevel) logger := zap.New(core) @@ -24,22 +37,21 @@ func Test_BasicLoginHandler_MalformedRequest(t *testing.T) { userService := mocksUserService.NewUserService(t) sessionService := mocksSessionService.NewSessionService(t) - controller := controllers.NewBasicAuthentificationController( + controller := controllers.NewBasicAuthenticationController( logger, userService, sessionService, ) response := httptest.NewRecorder() request := httptest.NewRequest( - "POST", - "/v1/auth/basic/login", + http.MethodPost, + "/login", strings.NewReader("qsdqsdqsd"), ) payload, err := controller.BasicLoginHandler(response, request) assert.Equal(t, controllers.HTTPErrRequestMalformed, err) assert.Nil(t, payload) - } func Test_BasicLoginHandler_UserNotFound(t *testing.T) { @@ -52,18 +64,19 @@ func Test_BasicLoginHandler_UserNotFound(t *testing.T) { userService := mocksUserService.NewUserService(t) userService. On("GetUser", loginJSONStruct). - Return(nil, httperrors.AnError) + Return(nil, ExampleErr) + sessionService := mocksSessionService.NewSessionService(t) - controller := controllers.NewBasicAuthentificationController( + controller := controllers.NewBasicAuthenticationController( logger, userService, sessionService, ) response := httptest.NewRecorder() request := httptest.NewRequest( - "POST", - "/v1/auth/basic/login", + http.MethodPost, + "/login", strings.NewReader(`{ "email": "bob@email.com", "password":"1234" @@ -73,7 +86,6 @@ func Test_BasicLoginHandler_UserNotFound(t *testing.T) { payload, err := controller.BasicLoginHandler(response, request) assert.Error(t, err) assert.Nil(t, payload) - } func Test_BasicLoginHandler_LoginFailed(t *testing.T) { @@ -85,8 +97,8 @@ func Test_BasicLoginHandler_LoginFailed(t *testing.T) { } response := httptest.NewRecorder() request := httptest.NewRequest( - "POST", - "/v1/auth/basic/login", + http.MethodPost, + "/login", strings.NewReader(`{ "email": "bob@email.com", "password":"1234" @@ -94,7 +106,7 @@ func Test_BasicLoginHandler_LoginFailed(t *testing.T) { ) userService := mocksUserService.NewUserService(t) user := &models.User{ - BaseModel: models.BaseModel{}, + UUIDModel: badorm.UUIDModel{}, Username: "bob", Email: "bob@email.com", Password: []byte("hash of 1234"), @@ -102,12 +114,13 @@ func Test_BasicLoginHandler_LoginFailed(t *testing.T) { userService. On("GetUser", loginJSONStruct). Return(user, nil) + sessionService := mocksSessionService.NewSessionService(t) sessionService. - On("LogUserIn", user, response). - Return(httperrors.AnError) + On("LogUserIn", user). + Return(nil, ExampleErr) - controller := controllers.NewBasicAuthentificationController( + controller := controllers.NewBasicAuthenticationController( logger, userService, sessionService, @@ -116,7 +129,6 @@ func Test_BasicLoginHandler_LoginFailed(t *testing.T) { payload, err := controller.BasicLoginHandler(response, request) assert.Error(t, err) assert.Nil(t, payload) - } func Test_BasicLoginHandler_LoginSuccess(t *testing.T) { @@ -128,8 +140,8 @@ func Test_BasicLoginHandler_LoginSuccess(t *testing.T) { } response := httptest.NewRecorder() request := httptest.NewRequest( - "POST", - "/v1/auth/basic/login", + http.MethodPost, + "/login", strings.NewReader(`{ "email": "bob@email.com", "password":"1234" @@ -137,8 +149,8 @@ func Test_BasicLoginHandler_LoginSuccess(t *testing.T) { ) userService := mocksUserService.NewUserService(t) user := &models.User{ - BaseModel: models.BaseModel{ - ID: uuid.Nil, + UUIDModel: badorm.UUIDModel{ + ID: badorm.NilUUID, }, Username: "bob", Email: "bob@email.com", @@ -147,12 +159,13 @@ func Test_BasicLoginHandler_LoginSuccess(t *testing.T) { userService. On("GetUser", loginJSONStruct). Return(user, nil) + sessionService := mocksSessionService.NewSessionService(t) sessionService. - On("LogUserIn", user, response). - Return(nil) + On("LogUserIn", user). + Return(models.NewSession(user.ID, time.Duration(5)), nil) - controller := controllers.NewBasicAuthentificationController( + controller := controllers.NewBasicAuthenticationController( logger, userService, sessionService, @@ -160,7 +173,7 @@ func Test_BasicLoginHandler_LoginSuccess(t *testing.T) { payload, err := controller.BasicLoginHandler(response, request) assert.NoError(t, err) - assert.Equal(t, payload, dto.DTOLoginSuccess{ + assert.Equal(t, payload, dto.LoginSuccess{ Email: "bob@email.com", ID: user.ID.String(), Username: user.Username, diff --git a/controllers/crud.go b/controllers/crud.go new file mode 100644 index 00000000..50b66e39 --- /dev/null +++ b/controllers/crud.go @@ -0,0 +1,81 @@ +package controllers + +import ( + "fmt" + "net/http" + "strings" + + "github.com/elliotchance/pie/v2" + "go.uber.org/zap" + + "github.com/ditrit/badaas/badorm" + "github.com/ditrit/badaas/badorm/unsafe" + "github.com/ditrit/badaas/httperrors" + "github.com/ditrit/badaas/persistence/models" +) + +type CRUDController interface { + // The handler responsible of the retrieval of one model + GetModel(w http.ResponseWriter, r *http.Request) (any, httperrors.HTTPError) + + // The handler responsible of the retrieval of multiple models + GetModels(w http.ResponseWriter, r *http.Request) (any, httperrors.HTTPError) +} + +// check interface compliance +var _ CRUDController = (*crudControllerImpl[models.User])(nil) + +type CRUDRoute struct { + TypeName string + Controller CRUDController +} + +func NewCRUDController[T badorm.Model]( + logger *zap.Logger, + crudService badorm.CRUDService[T, badorm.UUID], + unsafeCRUDService unsafe.CRUDService[T, badorm.UUID], +) CRUDRoute { + fullTypeName := strings.ToLower(fmt.Sprintf("%T", *new(T))) + // remove the package name of the type + typeName := pie.Last(strings.Split(fullTypeName, ".")) + + return CRUDRoute{ + TypeName: typeName, + Controller: &crudControllerImpl[T]{ + logger: logger, + crudService: crudService, + unsafeCRUDService: unsafeCRUDService, + }, + } +} + +// The concrete implementation of the CRUDController +type crudControllerImpl[T badorm.Model] struct { + logger *zap.Logger + crudService badorm.CRUDService[T, badorm.UUID] + unsafeCRUDService unsafe.CRUDService[T, badorm.UUID] +} + +// The handler responsible of the retrieval of one model +func (controller *crudControllerImpl[T]) GetModel(_ http.ResponseWriter, r *http.Request) (any, httperrors.HTTPError) { + entityID, herr := getEntityIDFromRequest(r) + if herr != nil { + return nil, herr + } + + entity, err := controller.crudService.GetByID(entityID) + + return entity, mapServiceError(err) +} + +// The handler responsible of the retrieval of multiple models +func (controller *crudControllerImpl[T]) GetModels(_ http.ResponseWriter, r *http.Request) (any, httperrors.HTTPError) { + params, herr := decodeJSONOptional(r) + if herr != nil { + return nil, herr + } + + entities, err := controller.unsafeCRUDService.Query(params) + + return entities, mapServiceError(err) +} diff --git a/controllers/crud_test.go b/controllers/crud_test.go new file mode 100644 index 00000000..1436b9ef --- /dev/null +++ b/controllers/crud_test.go @@ -0,0 +1,257 @@ +package controllers_test + +import ( + "errors" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/gorilla/mux" + "github.com/stretchr/testify/assert" + "go.uber.org/zap" + "gorm.io/gorm" + + "github.com/ditrit/badaas/badorm" + "github.com/ditrit/badaas/controllers" + mockBadorm "github.com/ditrit/badaas/mocks/badorm" + mockUnsafe "github.com/ditrit/badaas/mocks/badorm/unsafe" +) + +var logger, _ = zap.NewDevelopment() + +// ----------------------- GetModel ----------------------- + +type Model struct { + badorm.UUIDModel +} + +func TestCRUDGetWithoutEntityIDReturnsError(t *testing.T) { + crudService := mockBadorm.NewCRUDService[Model, badorm.UUID](t) + crudUnsafeService := mockUnsafe.NewCRUDService[Model, badorm.UUID](t) + + route := controllers.NewCRUDController[Model]( + logger, + crudService, + crudUnsafeService, + ) + response := httptest.NewRecorder() + request := httptest.NewRequest( + http.MethodGet, + "/objects/exists/", + strings.NewReader(""), + ) + + _, err := route.Controller.GetModel(response, request) + assert.ErrorIs(t, err, controllers.ErrEntityNotFound) +} + +func TestCRUDGetWithEntityIDNotUUIDReturnsError(t *testing.T) { + crudService := mockBadorm.NewCRUDService[Model, badorm.UUID](t) + crudUnsafeService := mockUnsafe.NewCRUDService[Model, badorm.UUID](t) + + route := controllers.NewCRUDController[Model]( + logger, + crudService, + crudUnsafeService, + ) + response := httptest.NewRecorder() + request := httptest.NewRequest( + http.MethodGet, + "/objects/exists/", + strings.NewReader(""), + ) + request = mux.SetURLVars(request, map[string]string{"id": "not-uuid"}) + + _, err := route.Controller.GetModel(response, request) + assert.ErrorIs(t, err, controllers.ErrIDNotAnUUID) +} + +func TestCRUDGetWithEntityIDThatDoesNotExistReturnsError(t *testing.T) { + crudService := mockBadorm.NewCRUDService[Model, badorm.UUID](t) + crudUnsafeService := mockUnsafe.NewCRUDService[Model, badorm.UUID](t) + + uuid := badorm.NewUUID() + + crudService. + On("GetByID", uuid). + Return(nil, gorm.ErrRecordNotFound) + + route := controllers.NewCRUDController[Model]( + logger, + crudService, + crudUnsafeService, + ) + response := httptest.NewRecorder() + request := httptest.NewRequest( + http.MethodGet, + "/objects/exists/", + strings.NewReader(""), + ) + + request = mux.SetURLVars(request, map[string]string{"id": uuid.String()}) + + _, err := route.Controller.GetModel(response, request) + assert.ErrorIs(t, err, controllers.ErrEntityNotFound) +} + +func TestCRUDGetWithErrorInDBReturnsError(t *testing.T) { + crudService := mockBadorm.NewCRUDService[Model, badorm.UUID](t) + crudUnsafeService := mockUnsafe.NewCRUDService[Model, badorm.UUID](t) + + uuid := badorm.NewUUID() + + crudService. + On("GetByID", uuid). + Return(nil, errors.New("db error")) + + route := controllers.NewCRUDController[Model]( + logger, + crudService, + crudUnsafeService, + ) + response := httptest.NewRecorder() + request := httptest.NewRequest( + http.MethodGet, + "/objects/exists/", + strings.NewReader(""), + ) + + request = mux.SetURLVars(request, map[string]string{"id": uuid.String()}) + + _, err := route.Controller.GetModel(response, request) + assert.ErrorContains(t, err, "db error") +} + +func TestCRUDGetWithCorrectIDReturnsObject(t *testing.T) { + crudService := mockBadorm.NewCRUDService[Model, badorm.UUID](t) + crudUnsafeService := mockUnsafe.NewCRUDService[Model, badorm.UUID](t) + + uuid := badorm.NewUUID() + entity := Model{} + + crudService. + On("GetByID", uuid). + Return(&entity, nil) + + route := controllers.NewCRUDController[Model]( + logger, + crudService, + crudUnsafeService, + ) + response := httptest.NewRecorder() + request := httptest.NewRequest( + http.MethodGet, + "/objects/exists/", + strings.NewReader(""), + ) + + request = mux.SetURLVars(request, map[string]string{"id": uuid.String()}) + entityReturned, err := route.Controller.GetModel(response, request) + assert.Nil(t, err) + assert.Equal(t, &entity, entityReturned) +} + +// ----------------------- GetModels ----------------------- + +func TestGetModelsWithErrorInDBReturnsError(t *testing.T) { + crudService := mockBadorm.NewCRUDService[Model, badorm.UUID](t) + crudUnsafeService := mockUnsafe.NewCRUDService[Model, badorm.UUID](t) + + crudUnsafeService. + On("Query", map[string]any{}). + Return(nil, errors.New("db error")) + + route := controllers.NewCRUDController[Model]( + logger, + crudService, + crudUnsafeService, + ) + response := httptest.NewRecorder() + request := httptest.NewRequest( + http.MethodGet, + "/objects/exists/", + strings.NewReader(""), + ) + + _, err := route.Controller.GetModels(response, request) + assert.ErrorContains(t, err, "db error") +} + +func TestGetModelsWithoutParams(t *testing.T) { + crudService := mockBadorm.NewCRUDService[Model, badorm.UUID](t) + crudUnsafeService := mockUnsafe.NewCRUDService[Model, badorm.UUID](t) + + entity1 := &Model{} + entity2 := &Model{} + + crudUnsafeService. + On("Query", map[string]any{}). + Return([]*Model{entity1, entity2}, nil) + + route := controllers.NewCRUDController[Model]( + logger, + crudService, + crudUnsafeService, + ) + response := httptest.NewRecorder() + request := httptest.NewRequest( + http.MethodGet, + "/objects/exists/", + strings.NewReader(""), + ) + + entitiesReturned, err := route.Controller.GetModels(response, request) + assert.Nil(t, err) + assert.Len(t, entitiesReturned, 2) + assert.Contains(t, entitiesReturned, entity1) + assert.Contains(t, entitiesReturned, entity2) +} + +func TestGetModelsWithParams(t *testing.T) { + crudService := mockBadorm.NewCRUDService[Model, badorm.UUID](t) + crudUnsafeService := mockUnsafe.NewCRUDService[Model, badorm.UUID](t) + + entity1 := &Model{} + + crudUnsafeService. + On("Query", map[string]any{"param1": "something"}). + Return([]*Model{entity1}, nil) + + route := controllers.NewCRUDController[Model]( + logger, + crudService, + crudUnsafeService, + ) + response := httptest.NewRecorder() + request := httptest.NewRequest( + http.MethodGet, + "/objects/exists/", + strings.NewReader("{\"param1\": \"something\"}"), + ) + + entitiesReturned, err := route.Controller.GetModels(response, request) + assert.Nil(t, err) + assert.Len(t, entitiesReturned, 1) + assert.Contains(t, entitiesReturned, entity1) +} + +func TestGetModelsWithParamsNotJsonReturnsError(t *testing.T) { + crudService := mockBadorm.NewCRUDService[Model, badorm.UUID](t) + crudUnsafeService := mockUnsafe.NewCRUDService[Model, badorm.UUID](t) + + route := controllers.NewCRUDController[Model]( + logger, + crudService, + crudUnsafeService, + ) + response := httptest.NewRecorder() + request := httptest.NewRequest( + http.MethodGet, + "/objects/exists/", + strings.NewReader("bad json"), + ) + + _, err := route.Controller.GetModels(response, request) + assert.ErrorIs(t, err, controllers.HTTPErrRequestMalformed) +} diff --git a/controllers/info.go b/controllers/info.go index f39f30a2..1c4f95f6 100644 --- a/controllers/info.go +++ b/controllers/info.go @@ -3,34 +3,41 @@ package controllers import ( "net/http" + "github.com/Masterminds/semver/v3" + "github.com/ditrit/badaas/httperrors" - "github.com/ditrit/badaas/persistence/models/dto" - "github.com/ditrit/badaas/resources" ) // The information controller type InformationController interface { - // Return the badaas server informations + // Return the badaas server information Info(response http.ResponseWriter, r *http.Request) (any, httperrors.HTTPError) } // check interface compliance var _ InformationController = (*infoControllerImpl)(nil) -// The InformationController constructor -func NewInfoController() InformationController { - return &infoControllerImpl{} -} - // The concrete implementation of the InformationController -type infoControllerImpl struct{} +type infoControllerImpl struct { + Version *semver.Version +} -// Return the badaas server informations -func (*infoControllerImpl) Info(response http.ResponseWriter, r *http.Request) (any, httperrors.HTTPError) { +// The InformationController constructor +func NewInfoController(version *semver.Version) InformationController { + return &infoControllerImpl{ + Version: version, + } +} - infos := &dto.DTOBadaasServerInfo{ +// Return the badaas server information +func (c *infoControllerImpl) Info(_ http.ResponseWriter, _ *http.Request) (any, httperrors.HTTPError) { + return &BadaasServerInfo{ Status: "OK", - Version: resources.Version, - } - return infos, nil + Version: c.Version.String(), + }, nil +} + +type BadaasServerInfo struct { + Status string `json:"status"` + Version string `json:"version"` } diff --git a/controllers/utils.go b/controllers/utils.go new file mode 100644 index 00000000..b265d7da --- /dev/null +++ b/controllers/utils.go @@ -0,0 +1,81 @@ +package controllers + +import ( + "encoding/json" + "errors" + "io" + "net/http" + + "github.com/gorilla/mux" + "gorm.io/gorm" + + "github.com/ditrit/badaas/badorm" + "github.com/ditrit/badaas/httperrors" +) + +var ( + // HTTPErrRequestMalformed is sent when the request is malformed + HTTPErrRequestMalformed httperrors.HTTPError = httperrors.NewHTTPError( + http.StatusBadRequest, + "Request malformed", + "The schema of the received data is not correct", + nil, + false, + ) + ErrEntityNotFound = httperrors.NewErrorNotFound("entity", "please use a valid object id") + ErrEntityTypeNotFound = httperrors.NewErrorNotFound("entity type", "please use a type that exists") + ErrIDNotAnUUID = httperrors.NewBadRequestError("id is not an uuid", "please use an uuid for the id value") +) + +// Decode json present in request body +func decodeJSON(r *http.Request, to any) httperrors.HTTPError { + err := json.NewDecoder(r.Body).Decode(to) + if err != nil { + return HTTPErrRequestMalformed + } + + return nil +} + +// Decode json present in request body +func decodeJSONOptional(r *http.Request) (map[string]any, httperrors.HTTPError) { + to := map[string]any{} + err := json.NewDecoder(r.Body).Decode(&to) + + switch { + case errors.Is(err, io.EOF): + // empty body + return to, nil + case err != nil: + return nil, HTTPErrRequestMalformed + } + + return to, nil +} + +// Extract the "id" parameter from url +func getEntityIDFromRequest(r *http.Request) (badorm.UUID, httperrors.HTTPError) { + id, present := mux.Vars(r)["id"] + if !present { + return badorm.NilUUID, ErrEntityNotFound + } + + uuid, err := badorm.ParseUUID(id) + if err != nil { + return uuid, ErrIDNotAnUUID + } + + return uuid, nil +} + +func mapServiceError(err error) httperrors.HTTPError { + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return ErrEntityNotFound + } + + return httperrors.NewDBError(err) + } + + return nil +} diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index e616ca88..00000000 --- a/docker-compose.yml +++ /dev/null @@ -1,19 +0,0 @@ -version: '3.5' - -services: - db: - image: cockroachdb/cockroach:latest - ports: - - "26257:26257" - - "8080:8080" # Web based dashboard - command: start-single-node --insecure - volumes: - - "${PWD}/_temp/cockroach-data/crdb:/cockroach/cockroach-data" - - - api: - build: ditrit/badaas:latest # local image - ports: - - "8000:8000" - depends_on: - - db diff --git a/docker/cockroachdb/docker-compose.yml b/docker/cockroachdb/docker-compose.yml new file mode 100644 index 00000000..7b743183 --- /dev/null +++ b/docker/cockroachdb/docker-compose.yml @@ -0,0 +1,22 @@ +# DEVELOPMENT ONLY, DO NOT USE FOR PRODUCTION +version: '3.5' + +services: + db: + container_name: "badaas-test-db" + image: cockroachdb/cockroach:latest + volumes: + - .:/cockroach/files + working_dir: /cockroach + entrypoint: /cockroach/cockroach.sh start-single-node --insecure --log-config-file=files/logs.yaml + ports: + - "5000:26257" + - "8080:8080" # Web based dashboard + environment: + - COCKROACH_USER=badaas + - COCKROACH_DATABASE=badaas_db + healthcheck: + test: curl --fail http://localhost:8080 || exit 1 + interval: 10s + timeout: 5s + retries: 5 diff --git a/scripts/e2e/db/logs.yaml b/docker/cockroachdb/logs.yaml similarity index 96% rename from scripts/e2e/db/logs.yaml rename to docker/cockroachdb/logs.yaml index 4cdfbb24..87058924 100644 --- a/scripts/e2e/db/logs.yaml +++ b/docker/cockroachdb/logs.yaml @@ -48,6 +48,8 @@ sinks: auditable: true sql-exec: channels: [SQL_EXEC] + sql-schema: + channels: [SQL_SCHEMA] sql-slow: channels: [SQL_PERF] sql-slow-internal-only: diff --git a/docker/mysql/docker-compose.yml b/docker/mysql/docker-compose.yml new file mode 100644 index 00000000..bcc350ad --- /dev/null +++ b/docker/mysql/docker-compose.yml @@ -0,0 +1,19 @@ +# DEVELOPMENT ONLY, DO NOT USE FOR PRODUCTION +version: '3.5' + +services: + db: + container_name: "badaas-test-db" + image: mysql:latest + environment: + MYSQL_ALLOW_EMPTY_PASSWORD: "true" + MYSQL_DATABASE: 'badaas_db' + MYSQL_USER: 'badaas' + MYSQL_PASSWORD: 'badaas_password2023' + ports: + - '5000:3306' + healthcheck: + test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"] + interval: 10s + timeout: 5s + retries: 5 \ No newline at end of file diff --git a/docker/postgresql/docker-compose.yml b/docker/postgresql/docker-compose.yml new file mode 100644 index 00000000..f21a2b2a --- /dev/null +++ b/docker/postgresql/docker-compose.yml @@ -0,0 +1,21 @@ +# DEVELOPMENT ONLY, DO NOT USE FOR PRODUCTION +version: '3.5' + +services: + db: + container_name: "badaas-test-db" + image: postgres:latest + environment: + POSTGRES_USER: badaas + POSTGRES_PASSWORD: badaas_password2023 + POSTGRES_DB: badaas_db + PGDATA: /data/postgres + ports: + - "5000:5432" + volumes: + - .:/docker-entrypoint-initdb.d/ + healthcheck: + test: ["CMD-SHELL", "pg_isready"] + interval: 10s + timeout: 5s + retries: 5 \ No newline at end of file diff --git a/docker/postgresql/init.sql b/docker/postgresql/init.sql new file mode 100644 index 00000000..682131d3 --- /dev/null +++ b/docker/postgresql/init.sql @@ -0,0 +1 @@ +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; \ No newline at end of file diff --git a/docker/sqlserver/Dockerfile b/docker/sqlserver/Dockerfile new file mode 100644 index 00000000..d54ef6cc --- /dev/null +++ b/docker/sqlserver/Dockerfile @@ -0,0 +1,19 @@ +FROM mcr.microsoft.com/mssql/server:2022-latest + +USER mssql + +# Create a config directory +RUN mkdir -p /usr/config +WORKDIR /usr/config + +# Bundle config source +COPY entrypoint.sh /usr/config/ +COPY configure-db.sh /usr/config/ +COPY setup.sql /usr/config/ + +ENTRYPOINT ["./entrypoint.sh"] + +# Tail the setup logs to trap the process +CMD ["tail -f /dev/null"] + +HEALTHCHECK --interval=15s CMD /opt/mssql-tools/bin/sqlcmd -U sa -P $MSSQL_SA_PASSWORD -Q "select 1" && grep -q "MSSQL CONFIG COMPLETE" ./config.log \ No newline at end of file diff --git a/docker/sqlserver/configure-db.sh b/docker/sqlserver/configure-db.sh new file mode 100755 index 00000000..fc3c2f66 --- /dev/null +++ b/docker/sqlserver/configure-db.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +# wait for MSSQL server to start +export STATUS=1 +i=0 + +while [[ $STATUS -ne 0 ]] && [[ $i -lt 60 ]]; do + i=$i+1 + /opt/mssql-tools/bin/sqlcmd -t 1 -U sa -P $MSSQL_SA_PASSWORD -Q "select 1" >> /dev/null + STATUS=$? + sleep 1 +done + +if [ $STATUS -ne 0 ]; then + echo "---------------------------- Error: MSSQL SERVER took more than 60 seconds to start up. ----------------------------------------------" + exit 1 +fi + +echo "======= MSSQL SERVER STARTED ========" | tee -a ./config.log +# Run the setup script to create the DB and the schema in the DB +/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P $MSSQL_SA_PASSWORD -d master -i setup.sql + +echo "======= MSSQL CONFIG COMPLETE =======" | tee -a ./config.log \ No newline at end of file diff --git a/docker/sqlserver/docker-compose.yml b/docker/sqlserver/docker-compose.yml new file mode 100644 index 00000000..192f3ac4 --- /dev/null +++ b/docker/sqlserver/docker-compose.yml @@ -0,0 +1,16 @@ +# DEVELOPMENT ONLY, DO NOT USE FOR PRODUCTION +version: "3.5" + +services: + db: + container_name: "badaas-test-db" + build: . + image: badaas/mssqlserver:latest + ports: + - "5000:1433" + environment: + MSSQL_SA_PASSWORD: "badaas2023!" + ACCEPT_EULA: "Y" + MSSQL_DB: badaas_db + MSSQL_USER: badaas + MSSQL_PASSWORD: badaas_password2023 \ No newline at end of file diff --git a/docker/sqlserver/entrypoint.sh b/docker/sqlserver/entrypoint.sh new file mode 100755 index 00000000..98830030 --- /dev/null +++ b/docker/sqlserver/entrypoint.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +# Start SQL Server +/opt/mssql/bin/sqlservr & + +# Start the script to create the DB and user +/usr/config/configure-db.sh + +# Call extra command +eval $1 \ No newline at end of file diff --git a/docker/sqlserver/setup.sql b/docker/sqlserver/setup.sql new file mode 100644 index 00000000..8ff68fd1 --- /dev/null +++ b/docker/sqlserver/setup.sql @@ -0,0 +1,10 @@ +CREATE DATABASE $(MSSQL_DB); +GO +USE $(MSSQL_DB); +GO +CREATE LOGIN $(MSSQL_USER) WITH PASSWORD = '$(MSSQL_PASSWORD)'; +GO +CREATE USER $(MSSQL_USER) FOR LOGIN $(MSSQL_USER); +GO +ALTER SERVER ROLE sysadmin ADD MEMBER [$(MSSQL_USER)]; +GO \ No newline at end of file diff --git a/docker/test_api/Dockerfile b/docker/test_api/Dockerfile new file mode 100644 index 00000000..fbeba3a2 --- /dev/null +++ b/docker/test_api/Dockerfile @@ -0,0 +1,10 @@ +# DEVELOPMENT ONLY, DO NOT USE FOR PRODUCTION +FROM golang:1.19-alpine +RUN addgroup -S badaas \ + && adduser -S badaas -G badaas +USER badaas +WORKDIR /badaas +ENV CGO_ENABLED=0 +ENV GOOS=linux +ENV GOARCH=amd64 +ENV GOFLAGS=-buildvcs=false \ No newline at end of file diff --git a/scripts/e2e/api/badaas.yml b/docker/test_api/badaas.yml similarity index 74% rename from scripts/e2e/api/badaas.yml rename to docker/test_api/badaas.yml index 831b12d8..8e583e9f 100644 --- a/scripts/e2e/api/badaas.yml +++ b/docker/test_api/badaas.yml @@ -3,20 +3,20 @@ server: host: "0.0.0.0" # listening on all interfaces timeout: 15 # in seconds pagination: - page: + page: max: 10 - database: - host: e2e-db-1 + host: badaas-test-db port: 26257 sslmode: disable - username: root - password: postres + username: badaas + password: badaas_password2023 name: badaas_db init: retry: 10 retryTime: 5 + dialector: postgresql logger: mode: dev diff --git a/docker/test_api/docker-compose.yml b/docker/test_api/docker-compose.yml new file mode 100644 index 00000000..6796447c --- /dev/null +++ b/docker/test_api/docker-compose.yml @@ -0,0 +1,19 @@ +# DEVELOPMENT ONLY, DO NOT USE FOR PRODUCTION +version: '3.5' + +services: + api: + container_name: "badaas-test-api" + build: + context: ../.. + dockerfile: ./docker/test_api/Dockerfile + image: badaas-test-api + volumes: + - ../..:/badaas:ro + entrypoint: go run /badaas/test_e2e/test_api.go --config_path /badaas/docker/test_api/badaas.yml + ports: + - "8000:8000" + restart: always + depends_on: + db: + condition: service_healthy diff --git a/docker/wait_for_api.sh b/docker/wait_for_api.sh new file mode 100755 index 00000000..9c586dde --- /dev/null +++ b/docker/wait_for_api.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +until $(curl --output /dev/null --silent --fail http://localhost:$1); do + printf '.' + sleep 5 +done \ No newline at end of file diff --git a/docker/wait_for_db.sh b/docker/wait_for_db.sh new file mode 100755 index 00000000..04263a18 --- /dev/null +++ b/docker/wait_for_db.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +until [ "`docker inspect -f {{.State.Health.Status}} badaas-test-db`"=="healthy" ]; do + printf '.'; + sleep 1; +done; \ No newline at end of file diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 00000000..6283bf6c --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,29 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build +PYTHON = python3 + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +htmlview: html + @ls + $(PYTHON) -c "import webbrowser; from pathlib import Path; \ + webbrowser.open(Path('_build/html/index.html').resolve().as_uri())" + +watch: htmlview + watchmedo shell-command -p '*.rst' -c 'make html' -R -D + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/README.rst b/docs/README.rst new file mode 100644 index 00000000..75d490a5 --- /dev/null +++ b/docs/README.rst @@ -0,0 +1,59 @@ +:orphan: + +====================================== +BaDaaS documentation quick start guide +====================================== + +This file provides a quick guide on how to compile the BaDaaS documentation. + + +Setup the environment +--------------------- + +To compile the documentation you need Sphinx Python library. To install it +and all its dependencies run the following command from this dir + +:: + + pip install -r requirements.txt + + +Compile the documentation +------------------------- + +To compile the documentation (to classic HTML output) run the following command +from this dir:: + + make html + +Documentation will be generated (in HTML format) inside the ``_build/html`` dir. + + +View the documentation +---------------------- + +To view the documentation run the following command:: + + make htmlview + +This command will fire up your default browser and open the main page of your +(previously generated) HTML documentation. + + +Start over +---------- + +To clean up all generated documentation files and start from scratch run:: + + make clean + +Keep in mind that this command won't touch any documentation source files. + + +Recreating documentation on the fly +----------------------------------- + +There is a way to recreate the doc automatically when you make changes, you +need to install watchdog (``pip install watchdog``) and then use:: + + make watch diff --git a/docs/badaas/configuration.rst b/docs/badaas/configuration.rst new file mode 100644 index 00000000..b8b5dfa6 --- /dev/null +++ b/docs/badaas/configuration.rst @@ -0,0 +1,186 @@ +============================== +Configuration +============================== + +Methods +------------------------------- + +Badaas use `verdeter `_ to manage it's configuration, +so Badaas is POSIX compliant by default. + +Badctl automatically generates a default configuration in `badaas/config/badaas.yml`, +but you are free to modify it if you need to. + +This can be done using environment variables, configuration files or CLI flags. +CLI flags take priority on the environment variables and the environment variables take +priority on the content of the configuration file. + +As an example we will define the `database.port` configuration key using the 3 methods: + +- Using a CLI flag: :code:`--database.port=1222` +- Using an environment variable: :code:`export BADAAS_DATABASE_PORT=1222` (*dots are replaced by underscores*) +- Using a config file (in YAML here) + +:: + + # /etc/badaas/badaas.yml + database: + port: 1222 + +The config file can be placed at `/etc/badaas/badaas.yml` or `$HOME/.config/badaas/badaas.yml` +or in the same folder as the badaas binary `./badaas.yml`. + +If needed, the location can be overridden using the config key `config_path`. + +Config file +---------------------------- + +In this section, we will focus our attention on config files but +we won't forget that we can use environment variables and CLI flags to change Badaas' config. + +The config file can be formatted in any syntax that +`viper `_ supports but we will only use YAML syntax in our docs. + +To see a complete example of a working config file: head to +`badaas.example.yml `_. + +Database +^^^^^^^^^^^^^^^^^^^^^^^^ +:: + + # The settings for the database. + database: + # The host of the database server. + # (mandatory) + host: e2e-db-1 + + # The port of the database server. + # (mandatory) + port: 26257 + + # The sslmode of the connection to the database server. + # (mandatory) + sslmode: disable + + # The username of the account on the database server. + # (mandatory) + username: root + + # The password of the account on the database server. + # (mandatory) + password: postgres + + # The settings for the initialization of the database server. + init: + # Number of time badaas will try to establish a connection to the database server. + # default (10) + retry: 10 + + # Waiting time between connection, in seconds. + # default (5) + retryTime: 5 + + # dialector to use in communication with database (postgresql | mysql | sqlite | sqlserver) + # (mandatory) + dialector: postgresql + +Please note that the init section `init:` is not mandatory. +Badaas is suited with a simple but effective retry mechanism that will retry +`database.init.retry` time to establish a connection with the database. +Badaas will wait `database.init.retryTime` seconds between each retry. + +Logger +^^^^^^^^^^^^^^^^^^^^^^^^ + +Badaas use a structured logger that can output json logs in +production and user adapted logs for debug using the `logger.mode` key. + +Badaas offers the possibility to change the log message of the +Middleware Logger but provides a sane default. It is formatted using the Jinja syntax. +The values available are `method`, `url` and `protocol`. +:: + + # The settings for the logger. + logger: + # Either `dev` or `prod` + # default (`prod`) + mode: prod + request: + # Change the log emitted when badaas receives a request on a valid endpoint. + template: "Receive {{method}} request on {{url}}" + +HTTP Server +^^^^^^^^^^^^^^^^^^^^^^^^ + +You can change the host Badaas will bind to, the port and the timeout in seconds. + +Additionally you can change the number of elements returned by default for a paginated response. +:: + + # The settings for the http server. + server: + # The address to bind badaas to. + # default ("0.0.0.0") + host: "" + + # The port badaas should use. + # default (8000) + port: 8000 + + # The maximum timeout for the http server in seconds. + # default (15) + timeout: 15 + + # The settings for the pagination. + pagination: + page: + # The maximum number of record per page + # default (100) + max: 100 + + +Default values +^^^^^^^^^^^^^^^^^^^^^^^^ + +The section allow to change some settings for the first run. +:: + + # The settings for the first run. + default: + # The admin settings for the first run + admin: + # The admin password for the first run. Won't change is the admin user already exists. + password: admin + +Session management +^^^^^^^^^^^^^^^^^^^^^^^^ + +You can change the way the session service handle user sessions. +Session are extended if the user made a request to badaas in the "roll duration". +The session duration and the refresh interval of the cache can be changed. +They contains some good defaults. + +Please see the diagram below to see what is the roll duration relative to the session duration. +:: + + | session duration | + |<----------------------------------------->| + ----|-------------------------|-----------------|----> time + | | | + |<--------------->| + roll duration + +:: + + # The settings for session service + # This section contains some good defaults, don't change those value unless you need to. + session: + # The duration of a user session, in seconds + # Default (14400) equal to 4 hours + duration: 14400 + # The refresh interval in seconds. Badaas refresh it's internal session cache periodically. + # Default (30) + pullInterval: 30 + # The duration in which the user can renew it's session by making a request. + # Default (3600) equal to 1 hour + rollDuration: 3600 \ No newline at end of file diff --git a/docs/badaas/functionalities.rst b/docs/badaas/functionalities.rst new file mode 100644 index 00000000..7137e99c --- /dev/null +++ b/docs/badaas/functionalities.rst @@ -0,0 +1,60 @@ +============================== +Functionalities +============================== + +InfoControllerModule +------------------------------- + +`InfoControllerModule` adds the path `/info`, where the api version will be answered. +To set the version we want to be responded to we must provide the version using fx: + +.. code-block:: go + + func runCommandFunc(cmd *cobra.Command, args []string) { + fx.New( + badaas.BadaasModule, + + // provide api version + fx.Provide(NewAPIVersion), + // add /info route provided by badaas + badaasControllers.InfoControllerModule, + ).Run() + + func NewAPIVersion() *semver.Version { + return semver.MustParse("0.0.0-unreleased") + } + +AuthControllerModule +------------------------------- + +`AuthControllerModule` adds `/login` and `/logout`, +which allow us to add authentication to our application in a simple way: + +.. code-block:: go + + func runCommandFunc(cmd *cobra.Command, args []string) { + fx.New( + badaas.BadaasModule, + + // add /login and /logout routes provided by badaas + badaasControllers.AuthControllerModule, + ).Run() + } + +CRUDControllerModule +------------------------------- + +`CRUDControllerModule` adds `/objects/{type}` and `/objects/{type}/{id}`, +where `{type}` is any defined type and `{id}` is any uuid. +These routes allow us to read objects. For more information on how to use them, +see the `example `_. + +.. code-block:: go + + func runCommandFunc(cmd *cobra.Command, args []string) { + fx.New( + badaas.BadaasModule, + + router.GetCRUDRoutesModule[models.Company](), + ).Run() + } \ No newline at end of file diff --git a/docs/badaas/quickstart.rst b/docs/badaas/quickstart.rst new file mode 100644 index 00000000..bf2f3dbc --- /dev/null +++ b/docs/badaas/quickstart.rst @@ -0,0 +1,83 @@ +============================== +Quickstart +============================== + +To quickly get badaas up and running, you can head to the +`example `_. +By following its README.md, you will see how to use badaas and it will be util +as a template to start your own project. + +Step-by-step instructions +----------------------------------- + +Once you have started your project with :code:`go init`, you must add the dependency to badaas. +To use badaas, your project must also use `fx `_ and +`verdeter `_: + +.. code-block:: bash + + go get -u github.com/ditrit/badaas github.com/uber-go/fx github.com/ditrit/verdeter + +Then, your application must be defined as a `verdeter command` and you have to call +the configuration of this command: + +.. code-block:: go + + var rootCfg = verdeter.BuildVerdeterCommand(verdeter.VerdeterConfig{ + Use: "badaas-example", + Short: "Example of BadAss", + Long: "A HTTP server build over BadAas that uses its Login features", + Run: runHTTPServer, + }) + + func main() { + err := configuration.NewCommandInitializer().Init(rootCfg) + if err != nil { + panic(err) + } + + rootCfg.Execute() + } + +Then, in the Run function of your command, you must use `fx` and start the badaas functions: + +.. code-block:: go + + func runCommandFunc(cmd *cobra.Command, args []string) { + fx.New( + fx.Provide(GetModels), + badaas.BadaasModule, + + // Here you can add the functionalities provided by badaas + // Here you can start the rest of the modules that your project uses. + ).Run() + } + +You are free to choose which badaas functionalities you wish to use. +To add them, you must initialise the corresponding module: + +.. code-block:: go + + func runCommandFunc(cmd *cobra.Command, args []string) { + fx.New( + fx.Provide(GetModels), + badaas.BadaasModule, + + fx.Provide(NewAPIVersion), + // add routes provided by badaas + router.InfoRouteModule, + router.AuthRoutesModule, + + // Here you can start the rest of the modules that your project uses. + ).Run() + } + + func NewAPIVersion() *semver.Version { + return semver.MustParse("0.0.0-unreleased") + } + +For details visit :doc:`functionalities`. + +Once you have defined the functionalities of your project (an http api for example), +you can generate everything you need to run your application using `badctl`, +as described in the `README.md `_ of the example. \ No newline at end of file diff --git a/docs/badctl/commands.rst b/docs/badctl/commands.rst new file mode 100644 index 00000000..4abd3ba1 --- /dev/null +++ b/docs/badctl/commands.rst @@ -0,0 +1,52 @@ +============================== +Commands +============================== + +You can see the available commands by running:: + + badctl help + +For more information about the functionality provided and how to use each command use:: + + badctl help [command] + +gen docker +--------------------------- + +gen docker is the command you can use to generate the files and configurations +necessary for your project to use BadAss in a simple way. + +Depending of the `db_provider` chosen `gen` will generate the docker and +configuration files needed to run the application in the `badaas/docker/db` +and `badaas/config` folders respectively. It will also generate docker files +to run a http api in `badaas/docker/api`. + +The possible values for `db_provider` are `cockroachdb` and `postgres`. +CockroachDB is recommended since it's a distributed database from its +conception and postgres compatible. + +All these files can be modified in case you need different values than +those provided by default. For more information about the configuration +head to :doc:`/badaas/configuration` + +A Makefile will be generated for the execution of a badaas server, with the command:: + + make badaas_run + +gen conditions +--------------------------- + +gen conditions is the command you can use to generate +conditions to query your objects using BaDORM. +For each BaDORM Model found in the input packages a file +containing all possible Conditions on that object will be generated, +allowing you to use BaDORM in an easy way. + +Its use is recommended through `go generate`. +For that, you will only need to create a file with the following content:: + + package conditions + + //go:generate badctl gen conditions ../models + +An example can be found `here `_. \ No newline at end of file diff --git a/docs/badctl/index.rst b/docs/badctl/index.rst new file mode 100644 index 00000000..bbcdabe8 --- /dev/null +++ b/docs/badctl/index.rst @@ -0,0 +1,6 @@ +============================== +Introduction +============================== + +`badctl` is the command line tool that makes it possible to configure +and run your BaDaaS applications easily. diff --git a/docs/badctl/installation.rst b/docs/badctl/installation.rst new file mode 100644 index 00000000..f2993a7b --- /dev/null +++ b/docs/badctl/installation.rst @@ -0,0 +1,30 @@ +================ +Installation +================ + +Install with go install +----------------------------------- + +For simply installing it, use + +.. code-block:: bash + + go install github.com/ditrit/badaas/tools/badctl + +Or you can build it from sources. + +Build from sources +----------------------------------- + +Get the sources of the project, either by visiting the `releases `_ +page and downloading an archive or clone the main branch (please be aware that is it not a stable version). + +To build the project: + +- Install `go `_ +- :code:`cd tools/badctl` +- Install project dependencies: :code:`go get` +- Run build command: :code:`go build .` + +Well done, you have a binary `badctl` at the root of the project. + diff --git a/docs/badorm/advanced_query.rst b/docs/badorm/advanced_query.rst new file mode 100644 index 00000000..26bafc82 --- /dev/null +++ b/docs/badorm/advanced_query.rst @@ -0,0 +1,209 @@ +============================== +Advanced query +============================== + +Dynamic operators +-------------------------------- + +In :doc:`/badorm/query` we have seen how to use the operators +to make comparisons between the attributes of a model and static values such as a string, +a number, etc. But if we want to make comparisons between two or more attributes of +the same type we need to use the dynamic operators. +These, instead of a dynamic value, receive a FieldIdentifier, that is, +an object that identifies the attribute with which the operation is to be performed. + +These identifiers are also generated during the generation of conditions and +their name of these FieldIdentifiers will be Field where + is the model type and is the attribute name. + +For example we query all YourModels that has the same value in its String attribute that +its related Related's String attribute. + +.. code-block:: go + + type Related struct { + badorm.UUIDModel + + String string + } + + type YourModel struct { + badorm.UUIDModel + + String string + + Related Related + RelatedID badorm.UUID + } + + yourModels, err := ts.crudYourModelService.Query( + conditions.YourModelRelated( + conditions.RelatedString( + dynamic.Eq(conditions.YourModelStringField), + ), + ), + ) + +**Attention**, when using dynamic operators the verification that the FieldIdentifier +is concerned by the query is performed at run time, returning an error otherwise. +For example: + +.. code-block:: go + + type Related struct { + badorm.UUIDModel + + String string + } + + type YourModel struct { + badorm.UUIDModel + + String string + + Related Related + RelatedID badorm.UUID + } + + yourModels, err := ts.crudYourModelService.Query( + conditions.YourModelString( + dynamic.Eq(conditions.RelatedStringField), + ), + ) + +will respond badorm.ErrFieldModelNotConcerned in err. + +All operators supported by BaDORM that receive any value are available in their dynamic version at +. +In turn, there are dynamic versions of the operators specific to each database that can be found at + and +. + +Select join +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In case the attribute to be used by the dynamic operator is present more +than once in the query, it will be necessary to select the join to be used, +to avoid getting the error badorm.ErrJoinMustBeSelected. +To do this, you must use the SelectJoin method, as in the following example: + +.. code-block:: go + + type ParentParent struct { + badorm.UUIDModel + } + + type Parent1 struct { + badorm.UUIDModel + + ParentParent ParentParent + ParentParentID badorm.UUID + } + + type Parent2 struct { + badorm.UUIDModel + + ParentParent ParentParent + ParentParentID badorm.UUID + } + + type Child struct { + badorm.UUIDModel + + Parent1 Parent1 + Parent1ID badorm.UUID + + Parent2 Parent2 + Parent2ID badorm.UUID + } + + models, err := ts.crudChildService.Query( + conditions.ChildParent1( + conditions.Parent1ParentParent(), + ), + conditions.ChildParent2( + conditions.Parent2ParentParent(), + ), + conditions.ChildName( + // for the value 0 (conditions.ParentParentNameField), + // choose the first (0) join (made by conditions.ChildParent1()) + dynamic.Eq(conditions.ParentParentNameField).SelectJoin(0, 0), + ), + ) + +Multitype operators +---------------------------- + +To make as many checks as possible at compile time to avoid run-time errors, +the dynamic operators only accept FieldIdentifiers that are of the same type as the condition attribute. +But there are cases in which we want to make this limitation more flexible, +either because we want to compare attributes of :ref:`nullable ` +and non-nullable type or because the operator accepts multiple values and we want +to combine both static and dynamic values, as in the following example: + +.. code-block:: go + + type YourModel struct { + badorm.UUIDModel + + Int int + NullableInt sql.NullInt32 + } + + yourModels, err := ts.crudYourModelService.Query( + conditions.YourModelInt( + multitype.Between(1, conditions.YourModelNullableIntField), + ), + ) + +In case the type of any of the operator parameters is not related to the type of the condition's attribute, +err will be multitype.ErrFieldTypeDoesNotMatch. + +All operators supported by BaDORM that receive any value are available in their multitype version at +. +In turn, there are multitype versions of the operators specific to each database that can be found at + and +. + +Unsafe operators +-------------------------------- + +With multitype operators we add more flexibility to the operators at the cost of more validations +to be performed at runtime. +However, BaDORM will validate that the types of the values to be used inside the operator +are the same or related. + +In case you want to avoid this validation, unsafe operators should be used. +Although their use is not recommended, this can be useful when the database +used allows operations between different types or when attributes of different +types map at the same time in the database (see ). + +If it is neither of these two cases, the use of an unsafe operator will result in +an error in the execution of the query that depends on the database used. + +All operators supported by BaDORM that receive any value are available in their unsafe version at +. +In turn, there are unsafe versions of the operators specific to each database that can be found at + and +. + +Unsafe conditions (raw SQL) +-------------------------------- + +In case you need to use operators that are not supported by BaDORM +(please create an issue in our repository if you think we have forgotten any), +you can always run raw SQL with unsafe.NewCondition, as in the following example: + +.. code-block:: go + + yourModels, err := ts.crudYourModelService.Query( + conditions.YourModelString( + unsafe.NewCondition[models.YourModel]("%s.name = NULL"), + ), + ) + +As you can see in the example, "%s" can be used in the raw SQL to be replaced +by the table name of the model to which the condition belongs. + +Of course, its use is not recommended because it can generate errors in the execution +of the query that will depend on the database used. \ No newline at end of file diff --git a/docs/badorm/concepts.rst b/docs/badorm/concepts.rst new file mode 100644 index 00000000..5eefaf77 --- /dev/null +++ b/docs/badorm/concepts.rst @@ -0,0 +1,206 @@ +============================== +Concepts +============================== + +Model +------------------------------ + +A model is any object (struct) of go that you want to persist +in the database and on which you can perform queries. +For this, the struct must have an embedded BaDORM base model. + +For details visit :ref:`badorm/declaring_models:model declaration`. + +Base model +----------------------------- + +It is a struct that when embedded allows your structures to become BadORM models, +adding attributes ID, CreatedAt, UpdatedAt and DeletedAt attributes and the possibility to persist, +create conditions and perform queries on these structures. + +For details visit :ref:`badorm/declaring_models:base models`. + +Model ID +----------------------------- + +The id is a unique identifier needed to persist a model in the database. +It can be a badorm.UIntID or a badorm.UUID, depending on the base model used. + +For details visit :ref:`badorm/declaring_models:base models`. + +Auto Migration +---------------------------------------------------------- + +To persist the models it is necessary to migrate the database, +so that the structure of the tables corresponds to the definition of the model. +This migration is performed by gorm through the gormDB. + +For details visit :ref:`badorm/connecting_to_a_database:migration`. + +GormDB +----------------------------- + +GormDB is a gorm.DB object that allows communication with the database. + +For details visit :ref:`badorm/connecting_to_a_database:connection`. + +Condition +----------------------------- + +Conditions are the basis of the BaDORM query system, every query is composed of a set of conditions. +Conditions belong to a particular model and there are 4 different types: +WhereConditions, ConnectionConditions, JoinConditions and PreloadConditions. + +For details visit :doc:`/badorm/query`. + +WhereCondition +----------------------------- + +Type of condition that allows filters to be made on the model to which they belong +and an attribute of this model. These filters are performed through operators. + +For details visit :doc:`/badorm/query`. + +ConnectionCondition +----------------------------- + +Type of condition that allows the use of logical operators +(and, or, or, not) between WhereConditions. + +For details visit :doc:`/badorm/query`. + +JoinCondition +----------------------------- + +Condition type that allows to navigate relationships between models, +which will result in a join in the executed query +(don't worry, if you don't know what a join is, +you don't need to understand the queries that badorm executes). + +For details visit :doc:`/badorm/query`. + +PreloadCondition +----------------------------- + +Type of condition that allows retrieving information from a model as a result of the database (preload). +This information can be all its attributes and/or another model that is related to it. + +For details visit :doc:`/badorm/preloading`. + +Operator +----------------------------- + +Concept similar to database operators, +which allow different operations to be performed on an attribute of a model, +such as comparisons, predicates, pattern matching, etc. + +Operators can be classified as static, dynamic, multitype and unsafe. + +For details visit :doc:`/badorm/query`. + +Static operator +----------------------------- + +Static operators are those that perform operations on an attribute and static values, +such as a boolean value, an integer, etc. + +For details visit :doc:`/badorm/query`. + +Dynamic operator +----------------------------- + +Dynamic operators are those that perform operations between an attribute and other attributes, +either from the same model or from a different model, as long as the type of these attributes is the same. + +For details visit :doc:`/badorm/advanced_query`. + +Multitype operator +----------------------------- + +Multitype operators are those that can perform operations between an attribute +and static values or other attributes at the same time and, in addition, +these values and attributes can be of a type related to the type of the attribute +(nullable and not nullable types of the attribute). + +For details visit :doc:`/badorm/advanced_query`. + +Nullable types +----------------------------- + +Nullable types are the types provided by the sql library +that are a nullable version of the basic types: +sql.NullString, sql.NullTime, sql.NullInt64, sql.NullInt32, +sql.NullBool, sql.NullFloat64, etc.. + +For details visit . + +Unsafe operator +----------------------------- + +Unsafe operators are those that can perform operations between an attribute and +any type of value or attribute. + +For details visit :doc:`/badorm/advanced_query`. + +Database specific operator +----------------------------- + +Since not all SQL databases support the same set of operators, +there are operators that only work for a specific database. + +For details visit :doc:`/badorm/advanced_query`. + +CRUDService +----------------------------- + +A CrudService is a service that allows us to perform CRUD (create, read, update and delete) +operations on a specific model, executing all the necessary operations within a transaction. +Internally they use the CRUDRepository of that model. + +For details visit :ref:`badorm/crud:CRUDServices and CRUDRepositories`. + +CRUDRepository +----------------------------- + +A CRUDRepository is an object that allows us to perform CRUD operations (create, read, update, delete) +on a model but, unlike services, its internal operations are performed within a transaction received +by parameter. +This is useful to be able to define services that perform multiple CRUD +operations within the same transaction. + +For details visit :ref:`badorm/crud:CRUDServices and CRUDRepositories`. + +Compilable query system +----------------------------- + +The set of conditions that are received by the read operations of the CRUDService +and CRUDRepository form the BaDORM compilable query system. +It is so named because the conditions will verify at compile time that the query to be executed is correct. + +For details visit :ref:`badorm/query:compilable query system`. + +Conditions generation +---------------------------- + +Conditions are the basis of the compilable query system. +They are generated for each model and attribute and can then be used. +Their generation is done with badctl. + +For details visit :ref:`badorm/query:Conditions generation`. + +Dependency injection +----------------------------------- + +Dependency injection is a programming technique in which an object or function +receives other objects or functions that it depends on. BaDORM is compatible with +`uber fx `_ to inject the CRUDServices and +CRUDRepositories in your objects and functions. + +Relation getter +----------------------------------- + +Relationships between objects can be loaded from the database using PreloadConditions. +In order to safely navigate the relations in the loaded model BaDORM provides methods +called "relation getters". + +For details visit :doc:`/badorm/preloading`. \ No newline at end of file diff --git a/docs/badorm/connecting_to_a_database.rst b/docs/badorm/connecting_to_a_database.rst new file mode 100644 index 00000000..e4262484 --- /dev/null +++ b/docs/badorm/connecting_to_a_database.rst @@ -0,0 +1,36 @@ +============================== +Connecting to a database +============================== + +Connection +----------------------------- + +BaDORM supports the databases MySQL, PostgreSQL, SQLite, SQL Server using gorm's driver. +Some databases may be compatible with the mysql or postgres dialect, +in which case you could just use the dialect for those databases (from which CockroachDB is tested). + +To communicate with the database BaDORM need a :ref:`GormDB ` object, +that can be created by following `gorm documentation `_. + +BaDORM also offers the `badorm.ConnectToDialector` method that will allow you to connect to a database +using the specified dialector with retrying. +It also configures the `gorm's logger `_ to work with +`zap logger `_. + +When using BaDORM with `fx` as :ref:`dependency injector ` you +will need to provide (`fx.Provide`) a function that returns a `*gorm.DB`. + +Migration +---------------------------- + +Migration is done by gorm using the `gormDB.AutoMigrate` method. +For details visit `gorm docs `_. + +When using BaDORM with `fx` as :ref:`dependency injector ` +this method can't be called directly. In that case, BaDORM will execute the migration by providing +`badorm.BaDORMModule` to fx. For this to work, you will need to provide also a method that returns +`badorm.GetModelsResult` with the models you want to include in the migration. +Remember that the order in this list is important for gorm to be able to execute the migration. + + + diff --git a/docs/badorm/crud.rst b/docs/badorm/crud.rst new file mode 100644 index 00000000..ef04946f --- /dev/null +++ b/docs/badorm/crud.rst @@ -0,0 +1,70 @@ +============================== +CRUD Operations +============================== + +CRUDServices and CRUDRepositories +-------------------------------------- + +CRUD operations are made to your models via the CRUDServices and CRUDRepositories. +The difference between them is that a CRUDService will execute this operations within a transaction +while the CRUDRepository will be executed within a transaction received by parameter, +thus allowing defining services that perform multiple CRUD operations within the same transaction. + +Create, Save and Delete methods are just hooks to the gorm's corresponding methods. +For details visit +, and . +On the other hand, read (query) operations are provided by BaDORM via its +:ref:`compilable query system ` +(see how in :doc:`/badorm/query`). + +Each pair of CRUDService and CRUDRepository corresponds to a model. To create them you must use +the `badorm.GetCRUD[, ](gormDB)` where +`` is the type of your :ref:`model `, +`` is the type of your :ref:`model's id ` +and `gormDB` is the :ref:`GormDB ` object. + +When using BaDORM with `fx` as :ref:`dependency injector ` you +will need to provide to fx `badorm.GetCRUDServiceModule[]()` +where `` is the type of your :ref:`model `. +After that the following can be used by dependency injection: + +- `crudYourModelService badorm.CRUDService[, ]` +- `crudYourModelRepository badorm.CRUDRepository[, ]` + +For example: + +.. code-block:: go + + + type YourModel struct { + badorm.UUIDModel + } + + func main() { + fx.New( + // activate BaDORM + fx.Provide(NewGormDBConnection), + fx.Provide(GetModels), + badorm.BaDORMModule, + + badorm.GetCRUDServiceModule[YourModel](), + fx.Invoke(QueryCRUDObjects), + ).Run() + } + + func QueryCRUDObjects(crudYourModelService badorm.CRUDService[YourModel, badorm.UUID]) { + // use crudYourModelService + } + +unsafe.CRUDService and unsafe.CRUDRepository +---------------------------------------------------- + +There is another version of the CRUDService and CRUDRepository found in +unsafe.CRUDService and unsafe.CRUDRepository respectively. +This version is not part of the compilable query system but allows to perform +queries through maps built on runtime (hence the name unsafe). + +**Its direct use is not recommended**, since using the compilable query system we can make +sure that the query is correct at compile time, while here errors will happen at runtime in +case your condition map is not well structured. +This functionality is used internally by BaDaaS to provide an HTTP api for queries. \ No newline at end of file diff --git a/docs/badorm/declaring_models.rst b/docs/badorm/declaring_models.rst new file mode 100644 index 00000000..16f42a34 --- /dev/null +++ b/docs/badorm/declaring_models.rst @@ -0,0 +1,173 @@ +============================== +Declaring models +============================== + +Model declaration +----------------------- + +The BaDORM :ref:`model ` declaration is based on the GORM model declaration, +so its definition, conventions, tags and associations are compatible with BaDORM. +For details see `gorm documentation `_. +On the contrary, BaDORM presents some differences/extras that are explained in this section. + +Base models +----------------------- + +To be considered a model, your structures must have embedded one of the +:ref:`base models ` provided by BaDORM: + +- `badorm.UUIDModel`: Model identified by a badorm.UUID (Random (Version 4) UUID). +- `badorm.UIntModel`: Model identified by a badorm.UIntID (auto-incremental uint). + +Both base models provide date created, updated and `deleted `_. + +To use them, simply embed the desired model in any of your structs: + +.. code-block:: go + + type MyModel struct { + badorm.UUIDModel + + Name string + Email *string + Age uint8 + Birthday *time.Time + MemberNumber sql.NullString + ActivatedAt sql.NullTime + // ... + } + +Type of attributes +----------------------- + +As we can see in the example in the previous section, +the attributes of your models can be of multiple types, +such as basic go types, pointers, and :ref:`nullable types `. + +This difference can generate differences in the information that is stored in the database, +since saving a model created as follows: + +.. code-block:: go + + MyModel{} + +will save a empty string for Name but a null for the Email and the MemberNumber. + +The use of nullable types is strongly recommended and BaDORM takes into account +their use in each of its functionalities. + +Associations +----------------------- + +All associations provided by GORM are supported. +For more information see , +, and +. +However, in this section we will give some differences in BaDORM and +details that are not clear in this documentation. + +IDs +^^^^^^^^^^^^^^^^^^^^^ + +Since BaDORM base models use badorm.UUID or badorm.UIntID to identify the models, +the type of id used in a reference to another model is the corresponding one of these two, +for example: + +.. code-block:: go + + type ModelWithUUID struct { + badorm.UUIDModel + } + + type ModelWithUIntID struct { + badorm.UIntModel + } + + type ModelWithReferences struct { + badorm.UUIDModel + + ModelWithUUID *ModelWithUUID + ModelWithUUIDID *badorm.UUID + + ModelWithUIntID *ModelWithUIntID + ModelWithUIntIDID *badorm.UIntID + } + +References +^^^^^^^^^^^^^^^^^^^^^ + +References to other models can be made with or without pointers: + +.. code-block:: go + + type ReferencedModel struct { + badorm.UUIDModel + } + + type ModelWithPointer struct { + badorm.UUIDModel + + // reference with pointer + PointerReference *ReferencedModel + PointerReferenceID *badorm.UUID + } + + type ModelWithoutPointer struct { + badorm.UUIDModel + + // reference without pointer + Reference ReferencedModel + ReferenceID badorm.UUID + } + +As in the case of attributes, +this can make a difference when persisting, since one created as follows: + +.. code-block:: go + + ModelWithoutPointer{} + +will also create and save an empty ReferencedModel{}, what may be undesired behavior. +For this reason, although both options are still compatible with BaDORM, +we recommend the use of pointers for references. +In case the relation is not nullable, use the `not null` tag in the id of the reference, for example: + +.. code-block:: go + + type ReferencedModel struct { + badorm.UUIDModel + } + + type ModelWithPointer struct { + badorm.UUIDModel + + // reference with pointer not null + PointerReference *ReferencedModel + PointerReferenceID *badorm.UUID `gorm:"not null"` + } + +Reverse reference +------------------------------------ + +Although no example within the `gorm's documentation `_ shows it, +when defining relations, we can also put a reference in the reverse direction +to add navigability to our model. +In addition, adding this reverse reference will allow the corresponding conditions +to be generated during condition generation. + +For example: + +.. code-block:: go + + type Related struct { + badorm.UUIDModel + + YourModel *YourModel + } + + type YourModel struct { + badorm.UUIDModel + + Related *Related + RelatedID *badorm.UUID + } \ No newline at end of file diff --git a/docs/badorm/index.rst b/docs/badorm/index.rst new file mode 100644 index 00000000..3aa93dd8 --- /dev/null +++ b/docs/badorm/index.rst @@ -0,0 +1,36 @@ +============================== +Introduction +============================== + +BaDORM stands for Backend and Distribution ORM (Object Relational Mapping). +It's the BaDaaS' component that allows for easy and safe persistence and querying of objects but +it can be used both within a BaDaaS application and independently. + +BaDORM is built on top of `gorm `_, +a library that actually provides the functionality of an ORM: mapping objects to tables in the SQL database. +While gorm does this job well with its automatic migration +then performing queries on these objects is somewhat limited, +forcing us to write SQL queries directly when they are complex. +BaDORM seeks to address these limitations with a query system that: + +- Is compile-time safe: + the BaDORM query system is validated at compile time to avoid errors such as + comparing attributes that are of different types, + trying to use attributes or navigate relationships that do not exist, + using information from tables that are not included in the query, etc. +- Is easy to use: + the use of this system does not require knowledge of databases, + SQL languages or complex concepts. + Writing queries only requires programming in go and the result is easy to read. +- Is designed for real applications: + the query system is designed to work well in real-world cases where queries are complex, + require navigating multiple relationships, performing multiple comparisons, etc. +- Is designed so that developers can focus on the business model: + its queries allow easy retrieval of model relationships to apply business logic to the model + and it provides mechanisms to avoid errors in the business logic due to mistakes in loading + information from the database. +- It is designed for high performance: + the query system avoids as much as possible the use of reflection and aims + that all the necessary model data can be retrieved in a single query to the database. + +To quickly see how BaDORM can be used you can read the :doc:`quickstart`. \ No newline at end of file diff --git a/docs/badorm/preloading.rst b/docs/badorm/preloading.rst new file mode 100644 index 00000000..605c20fc --- /dev/null +++ b/docs/badorm/preloading.rst @@ -0,0 +1,171 @@ +============================== +Preloading +============================== + +PreloadConditions +--------------------------- + +During the :ref:`conditions generation ` the following +PreloadConditions are also generated which are useful for preloading: + +- One PreloadCondition for each of your models, that will allow to preload this model when doing a query. + The name of these conditions will be PreloadAttributes where + is the model type. +- One PreloadCondition for each of the relations of your model, + to preload that the related object when doing a query. + This is really just a facility that translates to using the JoinCondition of + that relation and then the PreloadAttributes of the related model. + The name of these conditions will be Preload where + is the model type and is the name of the attribute that creates the relation. +- One PreloadCondition to preload all the related models of your model. + The name of these conditions will be PreloadRelations where + is the model type. + +Examples +---------------------------------- + +**Preload a related model** + +In this example we query all YourModels and preload whose related Related. + +.. code-block:: go + + type Related struct { + badorm.UUIDModel + } + + type YourModel struct { + badorm.UUIDModel + + Related Related + RelatedID badorm.UUID + } + + yourModels, err := ts.crudYourModelService.Query( + conditions.YourModelRelated( + conditions.RelatedPreloadAttributes, + ), + ) + +Or using the PreloadCondition to avoid the JoinCondition +(only useful when you don't want to add other conditions to that Join): + +.. code-block:: go + + type Related struct { + badorm.UUIDModel + } + + type YourModel struct { + badorm.UUIDModel + + Related Related + RelatedID badorm.UUID + } + + yourModels, err := ts.crudYourModelService.Query( + conditions.YourModelPreloadRelated, + ) + +**Preload a list of models** + +.. code-block:: go + + type Related struct { + badorm.UUIDModel + + YourModel *YourModel + YourModelID *badorm.UUID + } + + type YourModel struct { + badorm.UUIDModel + + Related *[]Related + } + + yourModels, err := ts.crudYourModelService.Query( + conditions.YourModelPreloadRelated, + ) + +**Nested preloads** + +.. code-block:: go + + type Parent struct { + badorm.UUIDModel + } + + type Related struct { + badorm.UUIDModel + + Parent Parent + ParentID badorm.UUID + } + + type YourModel struct { + badorm.UUIDModel + + Related Related + RelatedID badorm.UUID + } + + yourModels, err := ts.crudYourModelService.Query( + conditions.YourModelRelated( + conditions.RelatedPreloadParent, + ), + ) + +As we can see, it is not necessary to add the preload to all joins, +it is enough to do it in the deepest one, +to recover, in this example, both Related and Parent. + +Relation getters +-------------------------------------- + +At the moment, with the PreloadConditions, we can choose whether or not to preload a relation. +The problem is that once we get the result of the query, we cannot determine if a null value +corresponds to the fact that the relation is really null or that the preload was not performed, +which means a big risk of making decisions in our business logic on incomplete information. + +For this reason, BaDORM provides the Relation getters. +These are methods that will be added to your models to safely navigate a relation, +responding `badorm.ErrRelationNotLoaded` in case you try to navigate a relation +that was not loaded from the database. +They are created in a file called badorm.go in your model package when +:ref:`generating conditions `. + +Here is an example of its use: + +.. code-block:: go + + type Related struct { + badorm.UUIDModel + } + + type YourModel struct { + badorm.UUIDModel + + Related Related + RelatedID badorm.UUID + } + + yourModels, err := ts.crudYourModelService.Query( + conditions.YourModelPreloadRelated, + ) + + if err == nil && len(yourModels) > 1 { + firstRelated, err := yourModels[0].GetRelated() + if err == nil { + // you can safely apply your business logic + } else { + // err is badorm.ErrRelationNotLoaded + } + } + +Unfortunately, these relation getters cannot be created in all cases but only in those in which: + +- The relation is made with an object directly instead of a pointer + (which is not recommended as described :ref:`here `). +- The relation is made with pointers and the foreign key (typically the ID) is in the same model. +- The relation is made with a pointer to a list. \ No newline at end of file diff --git a/docs/badorm/query.rst b/docs/badorm/query.rst new file mode 100644 index 00000000..6a377cb1 --- /dev/null +++ b/docs/badorm/query.rst @@ -0,0 +1,182 @@ +============================== +Query +============================== + +Query methods +------------------------ + +In CRUDRepository you will find different methods that will +allow you to perform queries on the model to which that repository belongs: + +- GetByID: will allow you to obtain a model by its id. +- QueryOne: will allow you to obtain the model that meets the conditions received by parameter. +- Query: will allow you to obtain the models that meet the conditions received by parameter. + +Compilable query system +------------------------ + +The set of conditions that are received by the read operations of the CRUDService +and CRUDRepository form the BaDORM compilable query system. +It is so named because the conditions will verify at compile time that the query to be executed is correct. + +These conditions are objects of type badorm.Condition that contain the +necessary information to perform the queries in a safe way. +They are generated from the definition of your models using badctl. + +Conditions generation +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The generation of conditions is done with badctl. For this, we need to install badctl: + +.. code-block:: bash + + go install github.com/ditrit/badaas/tools/badctl + +Then, inside our project we will have to create a package called conditions +(or another name if you wish) and inside it a file with the following content: + +.. code-block:: go + + package conditions + + //go:generate badctl gen conditions ../models_path_1 ../models_path_2 + +where ../models_path_1 ../models_path_2 are the relative paths between the package conditions +and the packages containing the definition of your models (can be only one). + +Now, from the root of your project you can execute: + +.. code-block:: bash + + go generate ./... + +and the conditions for each of your models will be created in the conditions package. + +Use of the conditions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +After generating the conditions you will have the following conditions: + +- One condition for each attribute of each of your models. + The name of these conditions will be where + is the model type and is the attribute name. + These conditions are of type WhereCondition. +- One condition for each relationship with another model that each of your models has. + The name of these conditions will be where + is the model type and is the name of the attribute that creates the relation. + These conditions are of type JoinCondition because using them will + mean performing a join within the executed query. + +Then, combining these conditions, the Connection Conditions (badorm.And, badorm.Or, badorm.Not) +and the Operators (badorm.Eq, badorm.Lt, etc.) you will be able to make all +the queries you need in a safe way. + +Examples +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**Filter by an attribute** + +In this example we query all YourModel that has "a_string" in the Attribute attribute. + +.. code-block:: go + + type YourModel struct { + badorm.UUIDModel + + Attribute string + } + + yourModels, err := ts.crudYourModelService.Query( + conditions.YourModelAttribute(badorm.Eq("a_string")), + ) + +**Filter by an attribute of a related model** + +In this example we query all YourModels whose related Related has "a_string" in its Attribute attribute. + +.. code-block:: go + + type Related struct { + badorm.UUIDModel + + Attribute string + } + + type YourModel struct { + badorm.UUIDModel + + Related Related + RelatedID badorm.UUID + } + + yourModels, err := ts.crudYourModelService.Query( + conditions.YourModelRelated( + conditions.RelatedAttribute(badorm.Eq("a_string")), + ), + ) + +**Multiple conditions** + +In this example we query all YourModels that has a 4 in the IntAttribute attribute and +whose related Related has "a_string" in its Attribute attribute. + +.. code-block:: go + + type Related struct { + badorm.UUIDModel + + Attribute string + } + + type YourModel struct { + badorm.UUIDModel + + IntAttribute int + + Related Related + RelatedID badorm.UUID + } + + yourModels, err := ts.crudYourModelService.Query( + conditions.YourModelIntAttribute(badorm.Eq(4)), + conditions.YourModelRelated( + conditions.RelatedAttribute(badorm.Eq("a_string")), + ), + ) + +Operators +------------------------ + +Below you will find the complete list of available operators: + +- badorm.Eq(value): EqualTo +- badorm.EqOrIsNull(value): if value is not NULL returns a Eq operator but if value is NULL returns a IsNull operator +- badorm.NotEq(value): NotEqualTo +- badorm.NotEqOrIsNotNull(value): if value is not NULL returns a NotEq operator but if value is NULL returns a IsNotNull operator +- badorm.Lt(value): LessThan +- badorm.LtOrEq(value): LessThanOrEqualTo +- badorm.Gt(value): GreaterThan +- badorm.GtOrEq(value): GreaterThanOrEqualTo +- badorm.IsNull() +- badorm.IsNotNull() +- badorm.Between(v1, v2): Equivalent to v1 < attribute < v2 +- badorm.NotBetween(v1, v2): Equivalent to NOT (v1 < attribute < v2) +- badorm.IsTrue() (Not supported by: sqlserver) +- badorm.IsNotTrue() (Not supported by: sqlserver) +- badorm.IsFalse() (Not supported by: sqlserver) +- badorm.IsNotFalse() (Not supported by: sqlserver) +- badorm.IsUnknown() (Not supported by: sqlserver, sqlite) +- badorm.IsNotUnknown() (Not supported by: sqlserver, sqlite) +- badorm.IsDistinct(value) (Not supported by: mysql) +- badorm.IsNotDistinct(value) (Not supported by: mysql) +- badorm.Like(pattern) +- badorm.Like(pattern).Escape(escape) +- badorm.ArrayIn(values) +- badorm.ArrayNotIn(values) + +In addition to these, BaDORM gives the possibility to use operators +that are only supported by a certain database (outside the standard). +These operators can be found in , +, + +and . \ No newline at end of file diff --git a/docs/badorm/quickstart.rst b/docs/badorm/quickstart.rst new file mode 100644 index 00000000..ca929d60 --- /dev/null +++ b/docs/badorm/quickstart.rst @@ -0,0 +1,173 @@ +============================== +Quickstart +============================== + +BaDORM example +--------------------------- + +To quickly understand how to use BaDORM, you can head to the +`example `_, where you will find two different variations: + +- `standalone/` where BaDORM is used in the simplest possible way. +- `fx/` where BaDORM is used within the :ref:`fx dependency injection system ` + +File structure +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Both variations follow the same file structure: + +- In `main.go` you will find the configuration required to use the BaDORM. +- In `example.go` you will find the actual example, where objects are created and then queried using BaDORM. +- In `models.go` you will find the :ref:`models ` definition. +- In `conditions/badorm.go` you will find the file that allows the :ref:`conditions generation `. + +Generate conditions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +After choosing the example you want to run (`cd standalone` or `cd fx`) +you will need to :ref:`generate the conditions ` for the models using `BaDctl`. + +Install `badctl`: + +.. code-block:: bash + + go install github.com/ditrit/badaas/tools/badctl + +Generate conditions: + +.. code-block:: bash + + go generate ./... + +Now you will find in `conditions/` the :ref:`conditions ` generated by badctl +that allow you query the models in `example.go` and in `models/badorm.go` the :ref:`relation getters `. + +Run it +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +First, we need a database to store the data, in this case we will use CockroachDB: + +.. code-block:: bash + + docker compose up -d + +After that, we can run the application: + +.. code-block:: bash + + go run . + +And you should see something like: + +.. code-block:: bash + + 2023/05/16 09:52:03 Setting up CRUD example + 2023/05/16 09:52:03 Finished creating CRUD example + 2023/05/16 09:52:03 Products with int = 1 are: + &{UUIDModel:{ID:1483487f-c585-4455-8d5b-2a58be27acbc CreatedAt:2023-05-16 09:50:12.025843 +0200 CEST UpdatedAt:2023-05-16 09:50:12.025843 +0200 CEST DeletedAt:{Time:0001-01-01 00:00:00 +0000 UTC Valid:false}} String: Int:1 Float:0 Bool:false} + +Understand it (optional) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In this section we will see the steps carried out to develop this example. + +**Standalone** + +Once you have started your project with `go init`, you must add the dependency to BaDaaS and others: + +.. code-block:: bash + + go get -u github.com/ditrit/badaas github.com/uber-go/zap gorm.io/gorm + +In models.go the :ref:`models ` are defined and +in conditions/badorm.go the file required to +:ref:`generate the conditions ` is created. + +In main.go a main function is created with the configuration required to use the BaDORM. +First, we need to create a :ref:`gormDB ` that allows connection with the database: + +.. code-block:: go + + gormDB, err := NewGormDBConnection() + +After that, we have to call the :ref:`AutoMigrate ` +method of the gormDB with the models you want to be persisted:: + + err = gormDB.AutoMigrate( + models.Product{}, + models.Company{}, + models.Seller{}, + models.Sale{}, + ) + +From here, we can start to use BaDORM, getting the :ref:`CRUDService ` +and :ref:`CRUDRepository ` of a model with the GetCRUD function: + +.. code-block:: go + + crudProductService, crudProductRepository := badorm.GetCRUD[models.Product, badorm.UUID](gormDB) + +As you can see, we need to specify the type of the model and the kind +of :ref:`id ` this model uses. + +Finally, you can use this service and repository to perform CRUD operations on your model: + +.. code-block:: go + + CreateCRUDObjects(gormDB, crudProductRepository) + QueryCRUDObjects(crudProductService) + +This two functions are defined in `example.go`. +In `QueryCRUDObjects` you can find a basic usage of the +:ref:`compilable query system `. + +**Fx** + +Once you have started your project with `go init`, you must add the dependency to BaDaaS and others: + +.. code-block:: bash + + go get -u github.com/ditrit/badaas github.com/uber-go/fx github.com/uber-go/zap gorm.io/gorm + +In models.go the :ref:`models ` are defined and +in conditions/badorm.go the file required to +:ref:`generate the conditions ` is created. + +In main.go a main function is created with the configuration required to use the BaDORM with fx. +First, we will need to start your application with `fx`: + +.. code-block:: go + + func main() { + fx.New( + // activate BaDORM + fx.Provide(NewGormDBConnection), + fx.Provide(GetModels), + badorm.BaDORMModule, + + // start example data + badorm.GetCRUDServiceModule[models.Company](), + badorm.GetCRUDServiceModule[models.Product](), + badorm.GetCRUDServiceModule[models.Seller](), + badorm.GetCRUDServiceModule[models.Sale](), + + fx.Provide(CreateCRUDObjects), + fx.Invoke(QueryCRUDObjects), + ).Run() + } + +There are some things you need to provide to the BaDORM module: + +- `NewGORMDBConnection` is the function that we need to create + a :ref:`gormDB ` that allows connection with the database. +- `GetModels` is a function that returns in a `badorm.GetModelsResult` the list of models + you want to be persisted by the :ref:`auto migration `. + +After that, we need to start the `badorm.BaDORMModule` and we are ready create +:ref:`CRUDServices ` to your models using `badorm.GetCRUDServiceModule`. + +Finally, we call the functions `CreateCRUDObjects` +and `QueryCRUDObjects` where the CRUDServices are injected to create, +read, update and delete the models easily. This two functions are defined in `example.go`. +In `QueryCRUDObjects` you can find a basic usage of the compiled query system.u can find a basic usage of the +:ref:`compilable query system `. \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 00000000..86279d51 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,40 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +project = 'BaDaaS' +copyright = '2023, DitRit' +author = 'DitRit' +release = '0.0.1' +version = '0.0.1' + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +extensions = [ + "myst_parser", + 'sphinx.ext.duration', + 'sphinx.ext.doctest', + 'sphinx.ext.autodoc', + 'sphinx.ext.autosummary', + "sphinx.ext.autosectionlabel", +] + +templates_path = ['_templates'] +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# Make sure the target is unique +autosectionlabel_prefix_document = True + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +html_theme = 'sphinx_rtd_theme' +html_static_path = ['_static'] + +# -- Options for EPUB output +epub_show_urls = 'footnote' diff --git a/docs/contributing/contributing.md b/docs/contributing/contributing.md new file mode 100644 index 00000000..abd81493 --- /dev/null +++ b/docs/contributing/contributing.md @@ -0,0 +1,66 @@ +# Contributing + +Thank you for your interest in BaDaaS! This document provides the guidelines for how to contribute to the project through issues and pull-requests. Contributions can also come in additional ways such as joining the [DitRit discord server](https://discord.gg/zkKfj9gj2C), commenting on issues or pull requests and more. + +## Issues + +### Issue types + +There are 3 types of issues: + +- Bug report: You've found a bug with the code, and want to report it, or create an issue to track the bug. +- Discussion: You have something on your mind, which requires input form others in a discussion, before it eventually manifests as a proposal. +- Feature request: Used for items that propose a new idea or functionality. This allows feedback from others before code is written. + +To ask questions and troubleshoot, please join the [DitRit discord server](https://discord.gg/zkKfj9gj2C) (use the BADAAS channel). + +### Before submitting + +Before you submit an issue, make sure you’ve checked the following: + +1. Check for existing issues + - Before you create a new issue, please do a search in [open issues](https://github.com/ditrit/badaas/issues) to see if the issue or feature request has already been filed. + - If you find your issue already exists, make relevant comments and add your reaction. +2. For bugs + - It’s not an environment issue. + - You have as much data as possible. This usually comes in the form of logs and/or stacktrace. +3. You are assigned to the issue, a branch is created from the issue and the `wip` tag is added if you are also planning to develop the solution. + +## Pull Requests + +All contributions come through pull requests. To submit a proposed change, follow this workflow: + +1. Make sure there's an issue (bug report or feature request) opened, which sets the expectations for the contribution you are about to make +2. Assign yourself to the issue and add the `wip` tag +3. Fork the [repo](https://github.com/ditrit/badaas) and create a new [branch](#branch-naming-policy) from the issue +4. Install the necessary [development environment](developing.md#environment) +5. Create your change and the corresponding [tests](developing.md#tests) +6. Update relevant documentation for the change in `docs/` +7. If changes are necessary in [BaDaaS example](https://github.com/ditrit/badaas-example) and [BaDORM example](https://github.com/ditrit/badorm-example), follow the same workflow there +8. Open a PR (and add links to the example repos' PR if they exist) +9. Wait for the CI process to finish and make sure all checks are green +10. A maintainer of the project will be assigned + +### Use work-in-progress PRs for early feedback + +A good way to communicate before investing too much time is to create a "Work-in-progress" PR and share it with your reviewers. The standard way of doing this is to add a "[WIP]" prefix in your PR’s title and assign the do-not-merge label. This will let people looking at your PR know that it is not well baked yet. + +### Branch naming policy + +`[BRANCH_TYPE]/[BRANCH_NAME]` + +- `BRANCH_TYPE` is a prefix to describe the purpose of the branch. + Accepted prefixes are: + - `feature`, used for feature development + - `bugfix`, used for bug fix + - `improvement`, used for refactor + - `library`, used for updating library + - `prerelease`, used for preparing the branch for the release + - `release`, used for releasing project + - `hotfix`, used for applying a hotfix on main + - `poc`, used for proof of concept +- `BRANCH_NAME` is managed by this regex: `[a-z0-9._-]` (`_` is used as space character). + +## Code of Conduct + +This project has adopted the [Contributor Covenant Code of Conduct](https://github.com/ditrit/badaas/blob/main/CODE_OF_CONDUCT.md) diff --git a/docs/contributing/developing.md b/docs/contributing/developing.md new file mode 100644 index 00000000..24a4df2a --- /dev/null +++ b/docs/contributing/developing.md @@ -0,0 +1,98 @@ +# Developing + +This document provides the information you need to know before developing code for a pull request. + +## Environment + +- Install [go](https://go.dev/doc/install) >= v1.20 +- Install project dependencies: `go get` +- Install [docker](https://docs.docker.com/engine/install/) and [compose plugin](https://docs.docker.com/compose/install/) + +## Directory structure + +This is the directory structure we use for the project: + +- `badorm/`: Contains the code of the BaDORM component. +- `configuration/`: Contains all the configuration holders. Please only use the interfaces, they are all mocked for easy testing. +- `controllers/`: Contains all the http controllers, they handle http requests and consume services. +- `docker/` : Contains the docker, docker-compose and configuration files for different environments. +- `docs/`: Contains the documentation showed for readthedocs.io. +- `httperrors/`: Contains the http errors that can be responded by the http api. Should be moved to `controller/` when services stop using them. +- `logger/`: Contains the logger creation logic. Please don't call it from your own services and code, use the dependency injection system. +- `mocks/`: Contains the mocks generated with `mockery`. +- `persistance/`: + - `gormdatabase/`: Contains the logic to create a database. Also contains a go package named `gormzap`: it is a compatibility layer between *gorm.io/gorm* and *github.com/uber-go/zap*. + - `models/`: Contains the models. + - `dto/`: Contains the Data Transfer Objects. They are used mainly to decode json payloads. + - `repository/`: Contains the repository interfaces and implementations to manage queries to the database. +- `router/`: Contains http router of badaas and the routes that can be added by the user. + - `middlewares/`: Contains the various http middlewares that we use. +- `services/`: Contains services. + - `auth/protocols/`: Contains the implementations of authentication clients for different protocols. + - `basicauth/`: Handle the authentication using email/password. + - `oidc/`: Handle the authentication via Open-ID Connect. +- `test_e2e/`: Contains all the feature and steps for e2e tests. +- `testintegration/`: Contains all the integration tests. +- `tools/`: Contains the go tools necessary to use BaDaaS. + - `badctl`: Contains the command line tool that makes it possible to configure and run BaDaaS applications easily. +- `utils/`: Contains functions that can be util all around the project, as managing data structures, time, etc. + +At the root of the project, you will find: + +- The README. +- The changelog. +- The LICENSE file. + +## Tests + +### Dependencies + +Running tests have some dependencies as: `mockery`, `gotestsum`, etc.. Install them with `make install_dependencies`. + +### Linting + +We use `golangci-lint` for linting our code. You can test it with `make lint`. The configuration file is in the default path (`.golangci.yml`). The file `.vscode.settings.json.template` is a template for your `.vscode/settings.json` that formats the code according to our configuration. + +### Unit tests + +We use the standard test suite in combination with [github.com/stretchr/testify](https://github.com/stretchr/testify) to do our unit testing. Mocks are generated using [mockery](https://github.com/vektra/mockery) using the command `make test_generate_mocks`. + +To run them, use: + +```sh +make -k test_unit +``` + +### Integration tests + +Integration tests have a database and the dependency injection system. BaDaaS and BaDORM are tested on multiple databases. By default, the database used will be postgresql: + +```sh +make test_integration +``` + +To run the tests on another database you can use: `make test_integration_postgresql`, `make test_integration_cockroachdb`, `make test_integration_mysql`, `make test_integration_sqlite`, `make test_integration_sqlserver`. All of them will be verified by our continuous integration system. + +### Feature tests (end to end tests) + +These are black box tests that test BaDaaS using its http api. We use docker to run a Badaas instance in combination with one node of CockroachDB. + +Run: + +```sh +make test_e2e +``` + +The feature files can be found in the `test_e2e/features` folder. + +## Requirements + +To be acceptable, contributions must: + +- Have a good quality of code, based on . +- Have at least 80 percent new code coverage (although a higher percentage may be required depending on the importance of the feature). The tests that contribute to coverage are unit tests and integration tests. +- The features defined in the PR base issue must be explicitly tested by an e2e test or by integration tests in case it is not possible (for BaDORM features for example). + +## Use of Third-party code + +Third-party code must include licenses. diff --git a/docs/contributing/maintaining.md b/docs/contributing/maintaining.md new file mode 100644 index 00000000..0a86e5c4 --- /dev/null +++ b/docs/contributing/maintaining.md @@ -0,0 +1,17 @@ +# Maintaining + +This document is intended for BaDaaS maintainers only. + +## How to release + +Release tag are only done on the `main` branch. We use [Semantic Versioning](https://semver.org/spec/v2.0.0.html) as guideline for the version management. + +Steps to release: + +- Create a new branch labeled `release/vX.Y.Z` from the latest `main`. +- Improve the version number in `changelog.md`, `tools/badctl/cmd/version/version.go` and `docs/conf.py`. +- Verify the content of the `changelog.md`. +- Commit the modifications with the label `Release version X.Y.Z`. +- Create a pull request on github for this branch into `main`. +- Once the pull request validated and merged, tag the `main` branch with `vX.Y.Z`. +- After the tag is pushed, make the release on the tag in GitHub. diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 00000000..55ce1e3f --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,69 @@ +============================== +Introduction +============================== + +Badaas enables the effortless construction of **distributed, resilient, +highly available and secure applications by design**, while ensuring very simple +deployment and management (NoOps). + +.. warning:: + BaDaaS is still under development and each of its components can have a different state of evolution + +Features and components +================================= + +Badaas provides several key features, +each provided by a component that can be used independently and has a different state of evolution: + +- **Authentication** (unstable): Badaas can authenticate users using its internal + authentication scheme or externally by using protocols such as OIDC, SAML, Oauth2... +- **Authorization** (wip_unstable): On resource access, Badaas will check if the user + is authorized using a RBAC model. +- **Distribution** (todo): Badaas is built to run in clusters by default. + Communications between nodes are TLS encrypted using `shoset `_. +- **Persistence** (wip_unstable): Applicative objects are persisted as well as user files. + Those resources are shared across the clusters to increase resiliency. + To achieve this, BaDaaS uses the :doc:`BaDORM ` component. +- **Querying Resources** (unstable): Resources are accessible via a REST API. +- **Posix compliant** (stable): Badaas strives towards being a good unix citizen and + respecting commonly accepted norms. (see :doc:`badaas/configuration`) +- **Advanced logs management** (todo): Badaas provides an interface to interact with + the logs produced by the clusters. Logs are formatted in json by default. + +Learn how to use BaDaaS following the :doc:`badaas/quickstart`. + +.. toctree:: + :caption: BaDaaS + + self + badaas/quickstart + badaas/functionalities + badaas/configuration + +.. toctree:: + :caption: BaDORM + + badorm/index + badorm/quickstart + badorm/concepts + badorm/declaring_models + badorm/connecting_to_a_database + badorm/crud + badorm/query + badorm/advanced_query + badorm/preloading + +.. toctree:: + :caption: BaDctl + + badctl/index + badctl/installation + badctl/commands + +.. toctree:: + :caption: Contributing + + contributing/contributing + contributing/developing + contributing/maintaining + Github diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 00000000..32bb2452 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/requirements.in b/docs/requirements.in new file mode 100644 index 00000000..4cb3091f --- /dev/null +++ b/docs/requirements.in @@ -0,0 +1,3 @@ +Sphinx +sphinx_rtd_theme +myst-parser \ No newline at end of file diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 00000000..4f36d62c --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,73 @@ +# +# This file is autogenerated by pip-compile with Python 3.10 +# by the following command: +# +# pip-compile docs/requirements.in +# +alabaster==0.7.13 + # via sphinx +babel==2.12.1 + # via sphinx +certifi==2023.5.7 + # via requests +charset-normalizer==3.2.0 + # via requests +docutils==0.18.1 + # via + # myst-parser + # sphinx + # sphinx-rtd-theme +idna==3.4 + # via requests +imagesize==1.4.1 + # via sphinx +jinja2==3.1.2 + # via + # myst-parser + # sphinx +markdown-it-py==3.0.0 + # via + # mdit-py-plugins + # myst-parser +markupsafe==2.1.3 + # via jinja2 +mdit-py-plugins==0.4.0 + # via myst-parser +mdurl==0.1.2 + # via markdown-it-py +myst-parser==2.0.0 + # via -r docs/requirements.in +packaging==23.1 + # via sphinx +pygments==2.15.1 + # via sphinx +pyyaml==6.0 + # via myst-parser +requests==2.31.0 + # via sphinx +snowballstemmer==2.2.0 + # via sphinx +sphinx==6.2.1 + # via + # -r docs/requirements.in + # myst-parser + # sphinx-rtd-theme + # sphinxcontrib-jquery +sphinx-rtd-theme==1.2.2 + # via -r docs/requirements.in +sphinxcontrib-applehelp==1.0.4 + # via sphinx +sphinxcontrib-devhelp==1.0.2 + # via sphinx +sphinxcontrib-htmlhelp==2.0.1 + # via sphinx +sphinxcontrib-jquery==4.1 + # via sphinx-rtd-theme +sphinxcontrib-jsmath==1.0.1 + # via sphinx +sphinxcontrib-qthelp==1.0.3 + # via sphinx +sphinxcontrib-serializinghtml==1.1.5 + # via sphinx +urllib3==2.0.3 + # via requests diff --git a/features/api_info.feature b/features/api_info.feature deleted file mode 100644 index f7b6790f..00000000 --- a/features/api_info.feature +++ /dev/null @@ -1,7 +0,0 @@ -Feature: Test info controller - -Scenario: Server should return ok and current project version - When I request "/info" - Then I expect status code is "200" - And I expect response field "status" is "OK" - And I expect response field "version" is "UNRELEASED" diff --git a/go.mod b/go.mod index 788ae80b..2cad02f8 100644 --- a/go.mod +++ b/go.mod @@ -1,68 +1,72 @@ module github.com/ditrit/badaas -go 1.18 +go 1.20 require ( - github.com/Masterminds/squirrel v1.5.3 - github.com/cucumber/godog v0.12.5 + github.com/DATA-DOG/go-sqlmock v1.5.0 + github.com/Masterminds/semver/v3 v3.2.1 + github.com/Masterminds/squirrel v1.5.4 github.com/ditrit/verdeter v0.4.0 + github.com/elliotchance/pie/v2 v2.5.2 github.com/google/uuid v1.3.0 github.com/gorilla/mux v1.8.0 - github.com/jackc/pgconn v1.13.0 - github.com/magiconair/properties v1.8.6 + github.com/jackc/pgconn v1.14.0 + github.com/magiconair/properties v1.8.7 github.com/noirbizarre/gonja v0.0.0-20200629003239-4d051fd0be61 - github.com/spf13/cobra v1.5.0 - github.com/spf13/pflag v1.0.5 - github.com/spf13/viper v1.13.0 - github.com/stretchr/testify v1.8.1 - go.uber.org/fx v1.18.2 - go.uber.org/zap v1.23.0 - golang.org/x/crypto v0.1.0 - gorm.io/driver/postgres v1.4.5 - gorm.io/gorm v1.24.1 + github.com/spf13/cobra v1.7.0 + github.com/spf13/viper v1.16.0 + github.com/stretchr/testify v1.8.4 + go.uber.org/fx v1.19.3 + go.uber.org/zap v1.24.0 + golang.org/x/crypto v0.9.0 + gorm.io/driver/mysql v1.5.1 + gorm.io/driver/postgres v1.5.2 + gorm.io/driver/sqlite v1.5.1 + gorm.io/driver/sqlserver v1.5.0 + gorm.io/gorm v1.25.1 + gotest.tools v2.2.0+incompatible ) require ( - github.com/cucumber/gherkin-go/v19 v19.0.3 // indirect - github.com/cucumber/messages-go/v16 v16.0.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/fsnotify/fsnotify v1.5.4 // indirect - github.com/gofrs/uuid v4.0.0+incompatible // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/go-sql-driver/mysql v1.7.0 // indirect + github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect + github.com/golang-sql/sqlexp v0.1.0 // indirect + github.com/google/go-cmp v0.5.9 // indirect github.com/goph/emperror v0.17.2 // indirect - github.com/hashicorp/go-immutable-radix v1.3.1 // indirect - github.com/hashicorp/go-memdb v1.3.3 // indirect - github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/inconshreveable/mousetrap v1.0.1 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect github.com/jackc/pgio v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgproto3/v2 v2.3.1 // indirect - github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect - github.com/jackc/pgtype v1.12.0 // indirect - github.com/jackc/pgx/v4 v4.17.2 // indirect + github.com/jackc/pgproto3/v2 v2.3.2 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgx/v5 v5.3.1 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect + github.com/mattn/go-sqlite3 v1.14.16 // indirect + github.com/microsoft/go-mssqldb v0.21.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/pelletier/go-toml v1.9.5 // indirect - github.com/pelletier/go-toml/v2 v2.0.5 // indirect + github.com/pelletier/go-toml/v2 v2.0.8 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/sirupsen/logrus v1.9.0 // indirect - github.com/spf13/afero v1.9.2 // indirect - github.com/spf13/cast v1.5.0 // indirect + github.com/sirupsen/logrus v1.9.2 // indirect + github.com/spf13/afero v1.9.5 // indirect + github.com/spf13/cast v1.5.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/objx v0.5.0 // indirect - github.com/subosito/gotenv v1.4.1 // indirect - go.uber.org/atomic v1.10.0 // indirect - go.uber.org/dig v1.15.0 // indirect - go.uber.org/multierr v1.8.0 // indirect - golang.org/x/sys v0.1.0 // indirect - golang.org/x/text v0.4.0 // indirect + github.com/subosito/gotenv v1.4.2 // indirect + go.uber.org/atomic v1.11.0 // indirect + go.uber.org/dig v1.17.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect + golang.org/x/sys v0.8.0 // indirect + golang.org/x/text v0.9.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index a7b58dea..b8872a44 100644 --- a/go.sum +++ b/go.sum @@ -25,7 +25,6 @@ cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4g cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= @@ -37,32 +36,28 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.0.0/go.mod h1:uGG2W01BaETf0Ozp+QxxKJdMBNRWPdstHG0Fmdwn1/U= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.2/go.mod h1:uGG2W01BaETf0Ozp+QxxKJdMBNRWPdstHG0Fmdwn1/U= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.1/go.mod h1:gLa1CL2RNE4s7M3yopJ/p0iq5DdY6Yv5ZUt9MTRZOQM= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w= +github.com/AzureAD/microsoft-authentication-library-for-go v0.8.1/go.mod h1:4qFor3D/HDsvBME35Xy9rwW9DecL+M2sNw1ybjPtwA0= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= -github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= -github.com/Masterminds/squirrel v1.5.3 h1:YPpoceAcxuzIljlr5iWpNKaql7hLeG1KLSrhvdHpkZc= -github.com/Masterminds/squirrel v1.5.3/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= +github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= +github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= +github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= github.com/airbrake/gobrake v3.6.1+incompatible/go.mod h1:wM4gu3Cn0W0K7GUuVWnlXZU11AGBXMILnrdOU8Kn00o= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= -github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bmuller/arrow v0.0.0-20180318014521-b14bfde8dff2/go.mod h1:+voQMVaya0tr8p3W33Qxj/dKOjZNCepW+k8JJvt91gk= github.com/bugsnag/bugsnag-go v1.4.0/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= github.com/bugsnag/panicwrap v1.2.0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/certifi/gocertifi v0.0.0-20190105021004-abcd57078448/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -70,64 +65,47 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= -github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= -github.com/cucumber/gherkin-go/v19 v19.0.3 h1:mMSKu1077ffLbTJULUfM5HPokgeBcIGboyeNUof1MdE= -github.com/cucumber/gherkin-go/v19 v19.0.3/go.mod h1:jY/NP6jUtRSArQQJ5h1FXOUgk5fZK24qtE7vKi776Vw= -github.com/cucumber/godog v0.12.5 h1:FZIy6VCfMbmGHts9qd6UjBMT9abctws/pQYO/ZcwOVs= -github.com/cucumber/godog v0.12.5/go.mod h1:u6SD7IXC49dLpPN35kal0oYEjsXZWee4pW6Tm9t5pIc= -github.com/cucumber/messages-go/v16 v16.0.0/go.mod h1:EJcyR5Mm5ZuDsKJnT2N9KRnBK30BGjtYotDKpwQ0v6g= -github.com/cucumber/messages-go/v16 v16.0.1 h1:fvkpwsLgnIm0qugftrw2YwNlio+ABe2Iu94Ap8GMYIY= -github.com/cucumber/messages-go/v16 v16.0.1/go.mod h1:EJcyR5Mm5ZuDsKJnT2N9KRnBK30BGjtYotDKpwQ0v6g= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/ditrit/verdeter v0.3.2-0.20230118160022-0caba70148cd h1:r2NABj0IHkzEtp4ZGaKpWNxsWAhjQU4ezxyELvXObrk= -github.com/ditrit/verdeter v0.3.2-0.20230118160022-0caba70148cd/go.mod h1:sKpWuOvYqNabLN4aNXqeBhcWpt7nf0frwqk0B5M6ax0= github.com/ditrit/verdeter v0.4.0 h1:DzEOFauuXEGNQYP6OgYtHwEyb3w9riem99u0xE/l7+o= github.com/ditrit/verdeter v0.4.0/go.mod h1:sKpWuOvYqNabLN4aNXqeBhcWpt7nf0frwqk0B5M6ax0= +github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko= +github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= +github.com/elliotchance/pie/v2 v2.5.2 h1:jRENMmysCljhUmyT8ITKV0Atp6Lukm3XpeqaI87POsM= +github.com/elliotchance/pie/v2 v2.5.2/go.mod h1:18t0dgGFH006g4eVdDtWfgFZPQEgl10IoEO8YWEq3Og= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= -github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-check/check v0.0.0-20180628173108-788fd7840127 h1:0gkP6mzaMqkmpcJYCFOLkIBwI7xFExG03bbkOkCvUPI= github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= -github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= +github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= +github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -163,7 +141,8 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -178,6 +157,7 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -187,51 +167,20 @@ github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8 github.com/goph/emperror v0.17.1/go.mod h1:+ZbQ+fUNO/6FNiUo0ujtMjhgad9Xa6fQL9KhH4LNHic= github.com/goph/emperror v0.17.2 h1:yLapQcmEsO0ipe9p5TaN22djm3OFV/TfM/fcYP0/J18= github.com/goph/emperror v0.17.2/go.mod h1:+ZbQ+fUNO/6FNiUo0ujtMjhgad9Xa6fQL9KhH4LNHic= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= -github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-memdb v1.3.0/go.mod h1:Mluclgwib3R93Hk5fxEfiRhB+6Dar64wWh71LpNSe3g= -github.com/hashicorp/go-memdb v1.3.3 h1:oGfEWrFuxtIUF3W2q/Jzt6G85TrMk9ey6XfYLvVe1Wo= -github.com/hashicorp/go-memdb v1.3.3/go.mod h1:uBTr1oQbtuMgd1SSGoR8YV27eT3sBHbYiNm53bMpgSg= -github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= -github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= -github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= -github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= @@ -241,9 +190,8 @@ github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= -github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= -github.com/jackc/pgconn v1.13.0 h1:3L1XMNV2Zvca/8BYhzcRFS70Lr0WlDg16Di6SFGAbys= -github.com/jackc/pgconn v1.13.0/go.mod h1:AnowpAqO4CMIIJNZl2VJp+KrkAZciAkhEl0W0JIobpI= +github.com/jackc/pgconn v1.14.0 h1:vrbA9Ud87g6JdFWkHTJXppVce58qPIdP7N8y0Ml/A7Q= +github.com/jackc/pgconn v1.14.0/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= @@ -259,52 +207,45 @@ github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvW github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.3.1 h1:nwj7qwf0S+Q7ISFfBndqeLwSwxs+4DPsbRFjECT1Y4Y= -github.com/jackc/pgproto3/v2 v2.3.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= +github.com/jackc/pgproto3/v2 v2.3.2 h1:7eY55bdBeCz1F2fTzSz69QC+pG46jYq9/jtSPiJ5nn0= +github.com/jackc/pgproto3/v2 v2.3.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= -github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= -github.com/jackc/pgtype v1.12.0 h1:Dlq8Qvcch7kiehm8wPGIW0W3KsCCHJnRacKW0UM8n5w= -github.com/jackc/pgtype v1.12.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= -github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= -github.com/jackc/pgx/v4 v4.17.2 h1:0Ut0rpeKwvIVbMQ1KbMBU4h6wxehBI535LK6Flheh8E= -github.com/jackc/pgx/v4 v4.17.2/go.mod h1:lcxIZN44yMIrWI78a5CpucdD14hX0SBDbNRvjDBItsw= +github.com/jackc/pgx/v5 v5.3.1 h1:Fcr8QJ1ZeLi5zsPZqQeUZhNhxfkkKBOgJuYkJHoBOtU= +github.com/jackc/pgx/v5 v5.3.1/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8= github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= +github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= +github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= +github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= +github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aWNYyvBVK62bc= +github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= -github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= -github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= @@ -312,52 +253,36 @@ github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6Fm github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= -github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= -github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= +github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= -github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/microsoft/go-mssqldb v0.21.0 h1:p2rpHIL7TlSv1QrbXJUAcbyRKnIT0C9rRkH2E4OjLn8= +github.com/microsoft/go-mssqldb v0.21.0/go.mod h1:+4wZTUnz/SV6nffv+RRRB/ss8jPng5Sho2SmM1l2ts4= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= -github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= -github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= +github.com/montanaflynn/stats v0.6.6/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/noirbizarre/gonja v0.0.0-20200629003239-4d051fd0be61 h1:8HaKr2WO2B5XKEFbJE9Z7W8mWC6+dL3jZCw53Dbl0oI= github.com/noirbizarre/gonja v0.0.0-20200629003239-4d051fd0be61/go.mod h1:WboHq+I9Ck8PwKsVFJNrpiRyngXhquRSTWBGwuSWOrg= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= -github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= -github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= +github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= +github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= +github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ= +github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -365,63 +290,34 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rollbar/rollbar-go v1.0.2/go.mod h1:AcFs5f0I+c71bpHlXNNDbOWJiKwjFDtISeXco0L5PKQ= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= -github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= -github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= -github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw= -github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= -github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= -github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= -github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= -github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y= +github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= +github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= +github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= +github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= -github.com/spf13/viper v1.13.0 h1:BWSJ/M+f+3nmdz9bxB+bWX28kkALN2ok11D0rSo8EJU= -github.com/spf13/viper v1.13.0/go.mod h1:Icm2xNL3/8uyh/wFuB1jI7TiTNKp8632Nwegu+zgdYw= +github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= +github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= @@ -432,24 +328,24 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= -github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= +github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg= github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= -go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -458,29 +354,21 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= -go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -go.uber.org/dig v1.15.0 h1:vq3YWr8zRj1eFGC7Gvf907hE0eRjPTZ1d3xHadD6liE= -go.uber.org/dig v1.15.0/go.mod h1:pKHs0wMynzL6brANhB2hLMro+zalv1osARTviTcqHLM= -go.uber.org/fx v1.18.2 h1:bUNI6oShr+OVFQeU8cDNbnN7VFsu+SsjHzUF51V/GAU= -go.uber.org/fx v1.18.2/go.mod h1:g0V1KMQ66zIRk8bLu3Ea5Jt2w/cHlOIp4wdRsgh0JaY= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/dig v1.17.0 h1:5Chju+tUvcC+N7N6EV08BJz41UZuO3BmHcN4A287ZLI= +go.uber.org/dig v1.17.0/go.mod h1:rTxpf7l5I0eBTlE6/9RL+lDybC7WFwY2QH55ZSjy1mU= +go.uber.org/fx v1.19.3 h1:YqMRE4+2IepTYCMOvXqQpRa+QAVdiSTnsHU4XNWBceA= +go.uber.org/fx v1.19.3/go.mod h1:w2HrQg26ql9fLK7hlBiZ6JsRUKV+Lj/atT1KCjT8YhM= go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= -go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= -go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= -go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= -go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY= -go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY= +go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= +go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -488,14 +376,19 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= -golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -506,6 +399,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc= +golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -529,13 +424,10 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -561,12 +453,16 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -586,13 +482,10 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -610,7 +503,6 @@ golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -629,17 +521,26 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220224120231-95c6836cb0e7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -648,19 +549,18 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= @@ -672,9 +572,6 @@ golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -682,7 +579,6 @@ golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= @@ -708,6 +604,7 @@ golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -802,36 +699,37 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= -gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= +gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gorm.io/driver/postgres v1.4.5 h1:mTeXTTtHAgnS9PgmhN2YeUbazYpLhUI1doLnw42XUZc= -gorm.io/driver/postgres v1.4.5/go.mod h1:GKNQYSJ14qvWkvPwXljMGehpKrhlDNsqYRr5HnYGncg= -gorm.io/gorm v1.24.1-0.20221019064659-5dd2bb482755/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= -gorm.io/gorm v1.24.1 h1:CgvzRniUdG67hBAzsxDGOAuq4Te1osVMYsa1eQbd4fs= -gorm.io/gorm v1.24.1/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= +gorm.io/driver/mysql v1.5.1 h1:WUEH5VF9obL/lTtzjmML/5e6VfFR/788coz2uaVCAZw= +gorm.io/driver/mysql v1.5.1/go.mod h1:Jo3Xu7mMhCyj8dlrb3WoCaRd1FhsVh+yMXb1jUInf5o= +gorm.io/driver/postgres v1.5.2 h1:ytTDxxEv+MplXOfFe3Lzm7SjG09fcdb3Z/c056DTBx0= +gorm.io/driver/postgres v1.5.2/go.mod h1:fmpX0m2I1PKuR7mKZiEluwrP3hbs+ps7JIGMUBpCgl8= +gorm.io/driver/sqlite v1.5.1 h1:hYyrLkAWE71bcarJDPdZNTLWtr8XrSjOWyjUYI6xdL4= +gorm.io/driver/sqlite v1.5.1/go.mod h1:7MZZ2Z8bqyfSQA1gYEV6MagQWj3cpUkJj9Z+d1HEMEQ= +gorm.io/driver/sqlserver v1.5.0 h1:zol7ePfY1XiPfjEvjjBh4VatIF3kycNcPE0EMCXznHY= +gorm.io/driver/sqlserver v1.5.0/go.mod h1:tBAqioK34BHl0Iiez+BFfG5/K9nDAlhLxRkgc2qy3+4= +gorm.io/gorm v1.25.1 h1:nsSALe5Pr+cM3V1qwwQ7rOkw+6UeLrX5O4v3llhHa64= +gorm.io/gorm v1.25.1/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/go.work b/go.work new file mode 100644 index 00000000..9a8f5a12 --- /dev/null +++ b/go.work @@ -0,0 +1,7 @@ +go 1.18 + +use ( + . + ./test_e2e + ./tools/badctl +) diff --git a/go.work.sum b/go.work.sum new file mode 100644 index 00000000..35e9278e --- /dev/null +++ b/go.work.sum @@ -0,0 +1,478 @@ +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= +cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= +cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= +cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= +cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= +cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= +cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= +cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= +cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= +cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= +cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA= +cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM= +cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I= +cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY= +cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw= +cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E= +cloud.google.com/go/accesscontextmanager v1.4.0/go.mod h1:/Kjh7BBu/Gh83sv+K60vN9QE5NJcd80sU33vIe2IFPE= +cloud.google.com/go/accesscontextmanager v1.7.0/go.mod h1:CEGLewx8dwa33aDAZQujl7Dx+uYhS0eay198wB/VumQ= +cloud.google.com/go/aiplatform v1.27.0/go.mod h1:Bvxqtl40l0WImSb04d0hXFU7gDOiq9jQmorivIiWcKg= +cloud.google.com/go/aiplatform v1.37.0/go.mod h1:IU2Cv29Lv9oCn/9LkFiiuKfwrRTq+QQMbW+hPCxJGZw= +cloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4= +cloud.google.com/go/analytics v0.19.0/go.mod h1:k8liqf5/HCnOUkbawNtrWWc+UAzyDlW89doe8TtoDsE= +cloud.google.com/go/apigateway v1.4.0/go.mod h1:pHVY9MKGaH9PQ3pJ4YLzoj6U5FUDeDFBllIz7WmzJoc= +cloud.google.com/go/apigateway v1.5.0/go.mod h1:GpnZR3Q4rR7LVu5951qfXPJCHquZt02jf7xQx7kpqN8= +cloud.google.com/go/apigeeconnect v1.4.0/go.mod h1:kV4NwOKqjvt2JYR0AoIWo2QGfoRtn/pkS3QlHp0Ni04= +cloud.google.com/go/apigeeconnect v1.5.0/go.mod h1:KFaCqvBRU6idyhSNyn3vlHXc8VMDJdRmwDF6JyFRqZ8= +cloud.google.com/go/apigeeregistry v0.6.0/go.mod h1:BFNzW7yQVLZ3yj0TKcwzb8n25CFBri51GVGOEUcgQsc= +cloud.google.com/go/apikeys v0.6.0/go.mod h1:kbpXu5upyiAlGkKrJgQl8A0rKNNJ7dQ377pdroRSSi8= +cloud.google.com/go/appengine v1.5.0/go.mod h1:TfasSozdkFI0zeoxW3PTBLiNqRmzraodCWatWI9Dmak= +cloud.google.com/go/appengine v1.7.1/go.mod h1:IHLToyb/3fKutRysUlFO0BPt5j7RiQ45nrzEJmKTo6E= +cloud.google.com/go/area120 v0.6.0/go.mod h1:39yFJqWVgm0UZqWTOdqkLhjoC7uFfgXRC8g/ZegeAh0= +cloud.google.com/go/area120 v0.7.1/go.mod h1:j84i4E1RboTWjKtZVWXPqvK5VHQFJRF2c1Nm69pWm9k= +cloud.google.com/go/artifactregistry v1.9.0/go.mod h1:2K2RqvA2CYvAeARHRkLDhMDJ3OXy26h3XW+3/Jh2uYc= +cloud.google.com/go/artifactregistry v1.13.0/go.mod h1:uy/LNfoOIivepGhooAUpL1i30Hgee3Cu0l4VTWHUC08= +cloud.google.com/go/asset v1.10.0/go.mod h1:pLz7uokL80qKhzKr4xXGvBQXnzHn5evJAEAtZiIb0wY= +cloud.google.com/go/asset v1.13.0/go.mod h1:WQAMyYek/b7NBpYq/K4KJWcRqzoalEsxz/t/dTk4THw= +cloud.google.com/go/assuredworkloads v1.9.0/go.mod h1:kFuI1P78bplYtT77Tb1hi0FMxM0vVpRC7VVoJC3ZoT0= +cloud.google.com/go/assuredworkloads v1.10.0/go.mod h1:kwdUQuXcedVdsIaKgKTp9t0UJkE5+PAVNhdQm4ZVq2E= +cloud.google.com/go/automl v1.8.0/go.mod h1:xWx7G/aPEe/NP+qzYXktoBSDfjO+vnKMGgsApGJJquM= +cloud.google.com/go/automl v1.12.0/go.mod h1:tWDcHDp86aMIuHmyvjuKeeHEGq76lD7ZqfGLN6B0NuU= +cloud.google.com/go/baremetalsolution v0.4.0/go.mod h1:BymplhAadOO/eBa7KewQ0Ppg4A4Wplbn+PsFKRLo0uI= +cloud.google.com/go/baremetalsolution v0.5.0/go.mod h1:dXGxEkmR9BMwxhzBhV0AioD0ULBmuLZI8CdwalUxuss= +cloud.google.com/go/batch v0.4.0/go.mod h1:WZkHnP43R/QCGQsZ+0JyG4i79ranE2u8xvjq/9+STPE= +cloud.google.com/go/batch v0.7.0/go.mod h1:vLZN95s6teRUqRQ4s3RLDsH8PvboqBK+rn1oevL159g= +cloud.google.com/go/beyondcorp v0.3.0/go.mod h1:E5U5lcrcXMsCuoDNyGrpyTm/hn7ne941Jz2vmksAxW8= +cloud.google.com/go/beyondcorp v0.5.0/go.mod h1:uFqj9X+dSfrheVp7ssLTaRHd2EHqSL4QZmH4e8WXGGU= +cloud.google.com/go/bigquery v1.44.0/go.mod h1:0Y33VqXTEsbamHJvJHdFmtqHvMIY28aK1+dFsvaChGc= +cloud.google.com/go/bigquery v1.50.0/go.mod h1:YrleYEh2pSEbgTBZYMJ5SuSr0ML3ypjRB1zgf7pvQLU= +cloud.google.com/go/billing v1.7.0/go.mod h1:q457N3Hbj9lYwwRbnlD7vUpyjq6u5U1RAOArInEiD5Y= +cloud.google.com/go/billing v1.13.0/go.mod h1:7kB2W9Xf98hP9Sr12KfECgfGclsH3CQR0R08tnRlRbc= +cloud.google.com/go/binaryauthorization v1.4.0/go.mod h1:tsSPQrBd77VLplV70GUhBf/Zm3FsKmgSqgm4UmiDItk= +cloud.google.com/go/binaryauthorization v1.5.0/go.mod h1:OSe4OU1nN/VswXKRBmciKpo9LulY41gch5c68htf3/Q= +cloud.google.com/go/certificatemanager v1.4.0/go.mod h1:vowpercVFyqs8ABSmrdV+GiFf2H/ch3KyudYQEMM590= +cloud.google.com/go/certificatemanager v1.6.0/go.mod h1:3Hh64rCKjRAX8dXgRAyOcY5vQ/fE1sh8o+Mdd6KPgY8= +cloud.google.com/go/channel v1.9.0/go.mod h1:jcu05W0my9Vx4mt3/rEHpfxc9eKi9XwsdDL8yBMbKUk= +cloud.google.com/go/channel v1.12.0/go.mod h1:VkxCGKASi4Cq7TbXxlaBezonAYpp1GCnKMY6tnMQnLU= +cloud.google.com/go/cloudbuild v1.4.0/go.mod h1:5Qwa40LHiOXmz3386FrjrYM93rM/hdRr7b53sySrTqA= +cloud.google.com/go/cloudbuild v1.9.0/go.mod h1:qK1d7s4QlO0VwfYn5YuClDGg2hfmLZEb4wQGAbIgL1s= +cloud.google.com/go/clouddms v1.4.0/go.mod h1:Eh7sUGCC+aKry14O1NRljhjyrr0NFC0G2cjwX0cByRk= +cloud.google.com/go/clouddms v1.5.0/go.mod h1:QSxQnhikCLUw13iAbffF2CZxAER3xDGNHjsTAkQJcQA= +cloud.google.com/go/cloudtasks v1.8.0/go.mod h1:gQXUIwCSOI4yPVK7DgTVFiiP0ZW/eQkydWzwVMdHxrI= +cloud.google.com/go/cloudtasks v1.10.0/go.mod h1:NDSoTLkZ3+vExFEWu2UJV1arUyzVDAiZtdWcsUyNwBs= +cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= +cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= +cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= +cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= +cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= +cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= +cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU= +cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= +cloud.google.com/go/compute v1.13.0/go.mod h1:5aPTS0cUNMIc1CE546K+Th6weJUNQErARyZtRXDJ8GE= +cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo= +cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs= +cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU= +cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/contactcenterinsights v1.4.0/go.mod h1:L2YzkGbPsv+vMQMCADxJoT9YiTTnSEd6fEvCeHTYVck= +cloud.google.com/go/contactcenterinsights v1.6.0/go.mod h1:IIDlT6CLcDoyv79kDv8iWxMSTZhLxSCofVV5W6YFM/w= +cloud.google.com/go/container v1.7.0/go.mod h1:Dp5AHtmothHGX3DwwIHPgq45Y8KmNsgN3amoYfxVkLo= +cloud.google.com/go/container v1.15.0/go.mod h1:ft+9S0WGjAyjDggg5S06DXj+fHJICWg8L7isCQe9pQA= +cloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4= +cloud.google.com/go/containeranalysis v0.9.0/go.mod h1:orbOANbwk5Ejoom+s+DUCTTJ7IBdBQJDcSylAx/on9s= +cloud.google.com/go/datacatalog v1.8.0/go.mod h1:KYuoVOv9BM8EYz/4eMFxrr4DUKhGIOXxZoKYF5wdISM= +cloud.google.com/go/datacatalog v1.13.0/go.mod h1:E4Rj9a5ZtAxcQJlEBTLgMTphfP11/lNaAshpoBgemX8= +cloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ= +cloud.google.com/go/dataflow v0.8.0/go.mod h1:Rcf5YgTKPtQyYz8bLYhFoIV/vP39eL7fWNcSOyFfLJE= +cloud.google.com/go/dataform v0.5.0/go.mod h1:GFUYRe8IBa2hcomWplodVmUx/iTL0FrsauObOM3Ipr0= +cloud.google.com/go/dataform v0.7.0/go.mod h1:7NulqnVozfHvWUBpMDfKMUESr+85aJsC/2O0o3jWPDE= +cloud.google.com/go/datafusion v1.5.0/go.mod h1:Kz+l1FGHB0J+4XF2fud96WMmRiq/wj8N9u007vyXZ2w= +cloud.google.com/go/datafusion v1.6.0/go.mod h1:WBsMF8F1RhSXvVM8rCV3AeyWVxcC2xY6vith3iw3S+8= +cloud.google.com/go/datalabeling v0.6.0/go.mod h1:WqdISuk/+WIGeMkpw/1q7bK/tFEZxsrFJOJdY2bXvTQ= +cloud.google.com/go/datalabeling v0.7.0/go.mod h1:WPQb1y08RJbmpM3ww0CSUAGweL0SxByuW2E+FU+wXcM= +cloud.google.com/go/dataplex v1.4.0/go.mod h1:X51GfLXEMVJ6UN47ESVqvlsRplbLhcsAt0kZCCKsU0A= +cloud.google.com/go/dataplex v1.6.0/go.mod h1:bMsomC/aEJOSpHXdFKFGQ1b0TDPIeL28nJObeO1ppRs= +cloud.google.com/go/dataproc v1.8.0/go.mod h1:5OW+zNAH0pMpw14JVrPONsxMQYMBqJuzORhIBfBn9uI= +cloud.google.com/go/dataproc v1.12.0/go.mod h1:zrF3aX0uV3ikkMz6z4uBbIKyhRITnxvr4i3IjKsKrw4= +cloud.google.com/go/dataqna v0.6.0/go.mod h1:1lqNpM7rqNLVgWBJyk5NF6Uen2PHym0jtVJonplVsDA= +cloud.google.com/go/dataqna v0.7.0/go.mod h1:Lx9OcIIeqCrw1a6KdO3/5KMP1wAmTc0slZWwP12Qq3c= +cloud.google.com/go/datastore v1.10.0/go.mod h1:PC5UzAmDEkAmkfaknstTYbNpgE49HAgW2J1gcgUfmdM= +cloud.google.com/go/datastore v1.11.0/go.mod h1:TvGxBIHCS50u8jzG+AW/ppf87v1of8nwzFNgEZU1D3c= +cloud.google.com/go/datastream v1.5.0/go.mod h1:6TZMMNPwjUqZHBKPQ1wwXpb0d5VDVPl2/XoS5yi88q4= +cloud.google.com/go/datastream v1.7.0/go.mod h1:uxVRMm2elUSPuh65IbZpzJNMbuzkcvu5CjMqVIUHrww= +cloud.google.com/go/deploy v1.5.0/go.mod h1:ffgdD0B89tToyW/U/D2eL0jN2+IEV/3EMuXHA0l4r+s= +cloud.google.com/go/deploy v1.8.0/go.mod h1:z3myEJnA/2wnB4sgjqdMfgxCA0EqC3RBTNcVPs93mtQ= +cloud.google.com/go/dialogflow v1.19.0/go.mod h1:JVmlG1TwykZDtxtTXujec4tQ+D8SBFMoosgy+6Gn0s0= +cloud.google.com/go/dialogflow v1.32.0/go.mod h1:jG9TRJl8CKrDhMEcvfcfFkkpp8ZhgPz3sBGmAUYJ2qE= +cloud.google.com/go/dlp v1.7.0/go.mod h1:68ak9vCiMBjbasxeVD17hVPxDEck+ExiHavX8kiHG+Q= +cloud.google.com/go/dlp v1.9.0/go.mod h1:qdgmqgTyReTz5/YNSSuueR8pl7hO0o9bQ39ZhtgkWp4= +cloud.google.com/go/documentai v1.10.0/go.mod h1:vod47hKQIPeCfN2QS/jULIvQTugbmdc0ZvxxfQY1bg4= +cloud.google.com/go/documentai v1.18.0/go.mod h1:F6CK6iUH8J81FehpskRmhLq/3VlwQvb7TvwOceQ2tbs= +cloud.google.com/go/domains v0.7.0/go.mod h1:PtZeqS1xjnXuRPKE/88Iru/LdfoRyEHYA9nFQf4UKpg= +cloud.google.com/go/domains v0.8.0/go.mod h1:M9i3MMDzGFXsydri9/vW+EWz9sWb4I6WyHqdlAk0idE= +cloud.google.com/go/edgecontainer v0.2.0/go.mod h1:RTmLijy+lGpQ7BXuTDa4C4ssxyXT34NIuHIgKuP4s5w= +cloud.google.com/go/edgecontainer v1.0.0/go.mod h1:cttArqZpBB2q58W/upSG++ooo6EsblxDIolxa3jSjbY= +cloud.google.com/go/errorreporting v0.3.0/go.mod h1:xsP2yaAp+OAW4OIm60An2bbLpqIhKXdWR/tawvl7QzU= +cloud.google.com/go/essentialcontacts v1.4.0/go.mod h1:8tRldvHYsmnBCHdFpvU+GL75oWiBKl80BiqlFh9tp+8= +cloud.google.com/go/essentialcontacts v1.5.0/go.mod h1:ay29Z4zODTuwliK7SnX8E86aUF2CTzdNtvv42niCX0M= +cloud.google.com/go/eventarc v1.8.0/go.mod h1:imbzxkyAU4ubfsaKYdQg04WS1NvncblHEup4kvF+4gw= +cloud.google.com/go/eventarc v1.11.0/go.mod h1:PyUjsUKPWoRBCHeOxZd/lbOOjahV41icXyUY5kSTvVY= +cloud.google.com/go/filestore v1.4.0/go.mod h1:PaG5oDfo9r224f8OYXURtAsY+Fbyq/bLYoINEK8XQAI= +cloud.google.com/go/filestore v1.6.0/go.mod h1:di5unNuss/qfZTw2U9nhFqo8/ZDSc466dre85Kydllg= +cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY= +cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE= +cloud.google.com/go/functions v1.9.0/go.mod h1:Y+Dz8yGguzO3PpIjhLTbnqV1CWmgQ5UwtlpzoyquQ08= +cloud.google.com/go/functions v1.13.0/go.mod h1:EU4O007sQm6Ef/PwRsI8N2umygGqPBS/IZQKBQBcJ3c= +cloud.google.com/go/gaming v1.8.0/go.mod h1:xAqjS8b7jAVW0KFYeRUxngo9My3f33kFmua++Pi+ggM= +cloud.google.com/go/gaming v1.9.0/go.mod h1:Fc7kEmCObylSWLO334NcO+O9QMDyz+TKC4v1D7X+Bc0= +cloud.google.com/go/gkebackup v0.3.0/go.mod h1:n/E671i1aOQvUxT541aTkCwExO/bTer2HDlj4TsBRAo= +cloud.google.com/go/gkebackup v0.4.0/go.mod h1:byAyBGUwYGEEww7xsbnUTBHIYcOPy/PgUWUtOeRm9Vg= +cloud.google.com/go/gkeconnect v0.6.0/go.mod h1:Mln67KyU/sHJEBY8kFZ0xTeyPtzbq9StAVvEULYK16A= +cloud.google.com/go/gkeconnect v0.7.0/go.mod h1:SNfmVqPkaEi3bF/B3CNZOAYPYdg7sU+obZ+QTky2Myw= +cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y977wO+hBH0= +cloud.google.com/go/gkehub v0.12.0/go.mod h1:djiIwwzTTBrF5NaXCGv3mf7klpEMcST17VBTVVDcuaw= +cloud.google.com/go/gkemulticloud v0.4.0/go.mod h1:E9gxVBnseLWCk24ch+P9+B2CoDFJZTyIgLKSalC7tuI= +cloud.google.com/go/gkemulticloud v0.5.0/go.mod h1:W0JDkiyi3Tqh0TJr//y19wyb1yf8llHVto2Htf2Ja3Y= +cloud.google.com/go/gsuiteaddons v1.4.0/go.mod h1:rZK5I8hht7u7HxFQcFei0+AtfS9uSushomRlg+3ua1o= +cloud.google.com/go/gsuiteaddons v1.5.0/go.mod h1:TFCClYLd64Eaa12sFVmUyG62tk4mdIsI7pAnSXRkcFo= +cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= +cloud.google.com/go/iam v0.7.0/go.mod h1:H5Br8wRaDGNc8XP3keLc4unfUUZeyH3Sfl9XpQEYOeg= +cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE= +cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0= +cloud.google.com/go/iap v1.5.0/go.mod h1:UH/CGgKd4KyohZL5Pt0jSKE4m3FR51qg6FKQ/z/Ix9A= +cloud.google.com/go/iap v1.7.1/go.mod h1:WapEwPc7ZxGt2jFGB/C/bm+hP0Y6NXzOYGjpPnmMS74= +cloud.google.com/go/ids v1.2.0/go.mod h1:5WXvp4n25S0rA/mQWAg1YEEBBq6/s+7ml1RDCW1IrcY= +cloud.google.com/go/ids v1.3.0/go.mod h1:JBdTYwANikFKaDP6LtW5JAi4gubs57SVNQjemdt6xV4= +cloud.google.com/go/iot v1.4.0/go.mod h1:dIDxPOn0UvNDUMD8Ger7FIaTuvMkj+aGk94RPP0iV+g= +cloud.google.com/go/iot v1.6.0/go.mod h1:IqdAsmE2cTYYNO1Fvjfzo9po179rAtJeVGUvkLN3rLE= +cloud.google.com/go/kms v1.6.0/go.mod h1:Jjy850yySiasBUDi6KFUwUv2n1+o7QZFyuUJg6OgjA0= +cloud.google.com/go/kms v1.10.1/go.mod h1:rIWk/TryCkR59GMC3YtHtXeLzd634lBbKenvyySAyYI= +cloud.google.com/go/language v1.8.0/go.mod h1:qYPVHf7SPoNNiCL2Dr0FfEFNil1qi3pQEyygwpgVKB8= +cloud.google.com/go/language v1.9.0/go.mod h1:Ns15WooPM5Ad/5no/0n81yUetis74g3zrbeJBE+ptUY= +cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08= +cloud.google.com/go/lifesciences v0.8.0/go.mod h1:lFxiEOMqII6XggGbOnKiyZ7IBwoIqA84ClvoezaA/bo= +cloud.google.com/go/logging v1.6.1/go.mod h1:5ZO0mHHbvm8gEmeEUHrmDlTDSu5imF6MUP9OfilNXBw= +cloud.google.com/go/logging v1.7.0/go.mod h1:3xjP2CjkM3ZkO73aj4ASA5wRPGGCRrPIAeNqVNkzY8M= +cloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc= +cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo= +cloud.google.com/go/managedidentities v1.4.0/go.mod h1:NWSBYbEMgqmbZsLIyKvxrYbtqOsxY1ZrGM+9RgDqInM= +cloud.google.com/go/managedidentities v1.5.0/go.mod h1:+dWcZ0JlUmpuxpIDfyP5pP5y0bLdRwOS4Lp7gMni/LA= +cloud.google.com/go/maps v0.1.0/go.mod h1:BQM97WGyfw9FWEmQMpZ5T6cpovXXSd1cGmFma94eubI= +cloud.google.com/go/maps v0.7.0/go.mod h1:3GnvVl3cqeSvgMcpRlQidXsPYuDGQ8naBis7MVzpXsY= +cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w= +cloud.google.com/go/mediatranslation v0.7.0/go.mod h1:LCnB/gZr90ONOIQLgSXagp8XUW1ODs2UmUMvcgMfI2I= +cloud.google.com/go/memcache v1.7.0/go.mod h1:ywMKfjWhNtkQTxrWxCkCFkoPjLHPW6A7WOTVI8xy3LY= +cloud.google.com/go/memcache v1.9.0/go.mod h1:8oEyzXCu+zo9RzlEaEjHl4KkgjlNDaXbCQeQWlzNFJM= +cloud.google.com/go/metastore v1.8.0/go.mod h1:zHiMc4ZUpBiM7twCIFQmJ9JMEkDSyZS9U12uf7wHqSI= +cloud.google.com/go/metastore v1.10.0/go.mod h1:fPEnH3g4JJAk+gMRnrAnoqyv2lpUCqJPWOodSaf45Eo= +cloud.google.com/go/monitoring v1.8.0/go.mod h1:E7PtoMJ1kQXWxPjB6mv2fhC5/15jInuulFdYYtlcvT4= +cloud.google.com/go/monitoring v1.13.0/go.mod h1:k2yMBAB1H9JT/QETjNkgdCGD9bPF712XiLTVr+cBrpw= +cloud.google.com/go/networkconnectivity v1.7.0/go.mod h1:RMuSbkdbPwNMQjB5HBWD5MpTBnNm39iAVpC3TmsExt8= +cloud.google.com/go/networkconnectivity v1.11.0/go.mod h1:iWmDD4QF16VCDLXUqvyspJjIEtBR/4zq5hwnY2X3scM= +cloud.google.com/go/networkmanagement v1.5.0/go.mod h1:ZnOeZ/evzUdUsnvRt792H0uYEnHQEMaz+REhhzJRcf4= +cloud.google.com/go/networkmanagement v1.6.0/go.mod h1:5pKPqyXjB/sgtvB5xqOemumoQNB7y95Q7S+4rjSOPYY= +cloud.google.com/go/networksecurity v0.6.0/go.mod h1:Q5fjhTr9WMI5mbpRYEbiexTzROf7ZbDzvzCrNl14nyU= +cloud.google.com/go/networksecurity v0.8.0/go.mod h1:B78DkqsxFG5zRSVuwYFRZ9Xz8IcQ5iECsNrPn74hKHU= +cloud.google.com/go/notebooks v1.5.0/go.mod h1:q8mwhnP9aR8Hpfnrc5iN5IBhrXUy8S2vuYs+kBJ/gu0= +cloud.google.com/go/notebooks v1.8.0/go.mod h1:Lq6dYKOYOWUCTvw5t2q1gp1lAp0zxAxRycayS0iJcqQ= +cloud.google.com/go/optimization v1.2.0/go.mod h1:Lr7SOHdRDENsh+WXVmQhQTrzdu9ybg0NecjHidBq6xs= +cloud.google.com/go/optimization v1.3.1/go.mod h1:IvUSefKiwd1a5p0RgHDbWCIbDFgKuEdB+fPPuP0IDLI= +cloud.google.com/go/orchestration v1.4.0/go.mod h1:6W5NLFWs2TlniBphAViZEVhrXRSMgUGDfW7vrWKvsBk= +cloud.google.com/go/orchestration v1.6.0/go.mod h1:M62Bevp7pkxStDfFfTuCOaXgaaqRAga1yKyoMtEoWPQ= +cloud.google.com/go/orgpolicy v1.5.0/go.mod h1:hZEc5q3wzwXJaKrsx5+Ewg0u1LxJ51nNFlext7Tanwc= +cloud.google.com/go/orgpolicy v1.10.0/go.mod h1:w1fo8b7rRqlXlIJbVhOMPrwVljyuW5mqssvBtU18ONc= +cloud.google.com/go/osconfig v1.10.0/go.mod h1:uMhCzqC5I8zfD9zDEAfvgVhDS8oIjySWh+l4WK6GnWw= +cloud.google.com/go/osconfig v1.11.0/go.mod h1:aDICxrur2ogRd9zY5ytBLV89KEgT2MKB2L/n6x1ooPw= +cloud.google.com/go/oslogin v1.7.0/go.mod h1:e04SN0xO1UNJ1M5GP0vzVBFicIe4O53FOfcixIqTyXo= +cloud.google.com/go/oslogin v1.9.0/go.mod h1:HNavntnH8nzrn8JCTT5fj18FuJLFJc4NaZJtBnQtKFs= +cloud.google.com/go/phishingprotection v0.6.0/go.mod h1:9Y3LBLgy0kDTcYET8ZH3bq/7qni15yVUoAxiFxnlSUA= +cloud.google.com/go/phishingprotection v0.7.0/go.mod h1:8qJI4QKHoda/sb/7/YmMQ2omRLSLYSu9bU0EKCNI+Lk= +cloud.google.com/go/policytroubleshooter v1.4.0/go.mod h1:DZT4BcRw3QoO8ota9xw/LKtPa8lKeCByYeKTIf/vxdE= +cloud.google.com/go/policytroubleshooter v1.6.0/go.mod h1:zYqaPTsmfvpjm5ULxAyD/lINQxJ0DDsnWOP/GZ7xzBc= +cloud.google.com/go/privatecatalog v0.6.0/go.mod h1:i/fbkZR0hLN29eEWiiwue8Pb+GforiEIBnV9yrRUOKI= +cloud.google.com/go/privatecatalog v0.8.0/go.mod h1:nQ6pfaegeDAq/Q5lrfCQzQLhubPiZhSaNhIgfJlnIXs= +cloud.google.com/go/pubsub v1.27.1/go.mod h1:hQN39ymbV9geqBnfQq6Xf63yNhUAhv9CZhzp5O6qsW0= +cloud.google.com/go/pubsub v1.30.0/go.mod h1:qWi1OPS0B+b5L+Sg6Gmc9zD1Y+HaM0MdUr7LsupY1P4= +cloud.google.com/go/pubsublite v1.5.0/go.mod h1:xapqNQ1CuLfGi23Yda/9l4bBCKz/wC3KIJ5gKcxveZg= +cloud.google.com/go/pubsublite v1.7.0/go.mod h1:8hVMwRXfDfvGm3fahVbtDbiLePT3gpoiJYJY+vxWxVM= +cloud.google.com/go/recaptchaenterprise/v2 v2.5.0/go.mod h1:O8LzcHXN3rz0j+LBC91jrwI3R+1ZSZEWrfL7XHgNo9U= +cloud.google.com/go/recaptchaenterprise/v2 v2.7.0/go.mod h1:19wVj/fs5RtYtynAPJdDTb69oW0vNHYDBTbB4NvMD9c= +cloud.google.com/go/recommendationengine v0.6.0/go.mod h1:08mq2umu9oIqc7tDy8sx+MNJdLG0fUi3vaSVbztHgJ4= +cloud.google.com/go/recommendationengine v0.7.0/go.mod h1:1reUcE3GIu6MeBz/h5xZJqNLuuVjNg1lmWMPyjatzac= +cloud.google.com/go/recommender v1.8.0/go.mod h1:PkjXrTT05BFKwxaUxQmtIlrtj0kph108r02ZZQ5FE70= +cloud.google.com/go/recommender v1.9.0/go.mod h1:PnSsnZY7q+VL1uax2JWkt/UegHssxjUVVCrX52CuEmQ= +cloud.google.com/go/redis v1.10.0/go.mod h1:ThJf3mMBQtW18JzGgh41/Wld6vnDDc/F/F35UolRZPM= +cloud.google.com/go/redis v1.11.0/go.mod h1:/X6eicana+BWcUda5PpwZC48o37SiFVTFSs0fWAJ7uQ= +cloud.google.com/go/resourcemanager v1.4.0/go.mod h1:MwxuzkumyTX7/a3n37gmsT3py7LIXwrShilPh3P1tR0= +cloud.google.com/go/resourcemanager v1.7.0/go.mod h1:HlD3m6+bwhzj9XCouqmeiGuni95NTrExfhoSrkC/3EI= +cloud.google.com/go/resourcesettings v1.4.0/go.mod h1:ldiH9IJpcrlC3VSuCGvjR5of/ezRrOxFtpJoJo5SmXg= +cloud.google.com/go/resourcesettings v1.5.0/go.mod h1:+xJF7QSG6undsQDfsCJyqWXyBwUoJLhetkRMDRnIoXA= +cloud.google.com/go/retail v1.11.0/go.mod h1:MBLk1NaWPmh6iVFSz9MeKG/Psyd7TAgm6y/9L2B4x9Y= +cloud.google.com/go/retail v1.12.0/go.mod h1:UMkelN/0Z8XvKymXFbD4EhFJlYKRx1FGhQkVPU5kF14= +cloud.google.com/go/run v0.3.0/go.mod h1:TuyY1+taHxTjrD0ZFk2iAR+xyOXEA0ztb7U3UNA0zBo= +cloud.google.com/go/run v0.9.0/go.mod h1:Wwu+/vvg8Y+JUApMwEDfVfhetv30hCG4ZwDR/IXl2Qg= +cloud.google.com/go/scheduler v1.7.0/go.mod h1:jyCiBqWW956uBjjPMMuX09n3x37mtyPJegEWKxRsn44= +cloud.google.com/go/scheduler v1.9.0/go.mod h1:yexg5t+KSmqu+njTIh3b7oYPheFtBWGcbVUYF1GGMIc= +cloud.google.com/go/secretmanager v1.9.0/go.mod h1:b71qH2l1yHmWQHt9LC80akm86mX8AL6X1MA01dW8ht4= +cloud.google.com/go/secretmanager v1.10.0/go.mod h1:MfnrdvKMPNra9aZtQFvBcvRU54hbPD8/HayQdlUgJpU= +cloud.google.com/go/security v1.10.0/go.mod h1:QtOMZByJVlibUT2h9afNDWRZ1G96gVywH8T5GUSb9IA= +cloud.google.com/go/security v1.13.0/go.mod h1:Q1Nvxl1PAgmeW0y3HTt54JYIvUdtcpYKVfIB8AOMZ+0= +cloud.google.com/go/securitycenter v1.16.0/go.mod h1:Q9GMaLQFUD+5ZTabrbujNWLtSLZIZF7SAR0wWECrjdk= +cloud.google.com/go/securitycenter v1.19.0/go.mod h1:LVLmSg8ZkkyaNy4u7HCIshAngSQ8EcIRREP3xBnyfag= +cloud.google.com/go/servicecontrol v1.5.0/go.mod h1:qM0CnXHhyqKVuiZnGKrIurvVImCs8gmqWsDoqe9sU1s= +cloud.google.com/go/servicecontrol v1.11.1/go.mod h1:aSnNNlwEFBY+PWGQ2DoM0JJ/QUXqV5/ZD9DOLB7SnUk= +cloud.google.com/go/servicedirectory v1.7.0/go.mod h1:5p/U5oyvgYGYejufvxhgwjL8UVXjkuw7q5XcG10wx1U= +cloud.google.com/go/servicedirectory v1.9.0/go.mod h1:29je5JjiygNYlmsGz8k6o+OZ8vd4f//bQLtvzkPPT/s= +cloud.google.com/go/servicemanagement v1.5.0/go.mod h1:XGaCRe57kfqu4+lRxaFEAuqmjzF0r+gWHjWqKqBvKFo= +cloud.google.com/go/servicemanagement v1.8.0/go.mod h1:MSS2TDlIEQD/fzsSGfCdJItQveu9NXnUniTrq/L8LK4= +cloud.google.com/go/serviceusage v1.4.0/go.mod h1:SB4yxXSaYVuUBYUml6qklyONXNLt83U0Rb+CXyhjEeU= +cloud.google.com/go/serviceusage v1.6.0/go.mod h1:R5wwQcbOWsyuOfbP9tGdAnCAc6B9DRwPG1xtWMDeuPA= +cloud.google.com/go/shell v1.4.0/go.mod h1:HDxPzZf3GkDdhExzD/gs8Grqk+dmYcEjGShZgYa9URw= +cloud.google.com/go/shell v1.6.0/go.mod h1:oHO8QACS90luWgxP3N9iZVuEiSF84zNyLytb+qE2f9A= +cloud.google.com/go/spanner v1.41.0/go.mod h1:MLYDBJR/dY4Wt7ZaMIQ7rXOTLjYrmxLE/5ve9vFfWos= +cloud.google.com/go/spanner v1.45.0/go.mod h1:FIws5LowYz8YAE1J8fOS7DJup8ff7xJeetWEo5REA2M= +cloud.google.com/go/speech v1.9.0/go.mod h1:xQ0jTcmnRFFM2RfX/U+rk6FQNUF6DQlydUSyoooSpco= +cloud.google.com/go/speech v1.15.0/go.mod h1:y6oH7GhqCaZANH7+Oe0BhgIogsNInLlz542tg3VqeYI= +cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= +cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= +cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= +cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4= +cloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I= +cloud.google.com/go/storagetransfer v1.8.0/go.mod h1:JpegsHHU1eXg7lMHkvf+KE5XDJ7EQu0GwNJbbVGanEw= +cloud.google.com/go/talent v1.4.0/go.mod h1:ezFtAgVuRf8jRsvyE6EwmbTK5LKciD4KVnHuDEFmOOA= +cloud.google.com/go/talent v1.5.0/go.mod h1:G+ODMj9bsasAEJkQSzO2uHQWXHHXUomArjWQQYkqK6c= +cloud.google.com/go/texttospeech v1.5.0/go.mod h1:oKPLhR4n4ZdQqWKURdwxMy0uiTS1xU161C8W57Wkea4= +cloud.google.com/go/texttospeech v1.6.0/go.mod h1:YmwmFT8pj1aBblQOI3TfKmwibnsfvhIBzPXcW4EBovc= +cloud.google.com/go/tpu v1.4.0/go.mod h1:mjZaX8p0VBgllCzF6wcU2ovUXN9TONFLd7iz227X2Xg= +cloud.google.com/go/tpu v1.5.0/go.mod h1:8zVo1rYDFuW2l4yZVY0R0fb/v44xLh3llq7RuV61fPM= +cloud.google.com/go/trace v1.4.0/go.mod h1:UG0v8UBqzusp+z63o7FK74SdFE+AXpCLdFb1rshXG+Y= +cloud.google.com/go/trace v1.9.0/go.mod h1:lOQqpE5IaWY0Ixg7/r2SjixMuc6lfTFeO4QGM4dQWOk= +cloud.google.com/go/translate v1.4.0/go.mod h1:06Dn/ppvLD6WvA5Rhdp029IX2Mi3Mn7fpMRLPvXT5Wg= +cloud.google.com/go/translate v1.7.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos= +cloud.google.com/go/video v1.9.0/go.mod h1:0RhNKFRF5v92f8dQt0yhaHrEuH95m068JYOvLZYnJSw= +cloud.google.com/go/video v1.15.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ= +cloud.google.com/go/videointelligence v1.9.0/go.mod h1:29lVRMPDYHikk3v8EdPSaL8Ku+eMzDljjuvRs105XoU= +cloud.google.com/go/videointelligence v1.10.0/go.mod h1:LHZngX1liVtUhZvi2uNS0VQuOzNi2TkY1OakiuoUOjU= +cloud.google.com/go/vision/v2 v2.5.0/go.mod h1:MmaezXOOE+IWa+cS7OhRRLK2cNv1ZL98zhqFFZaaH2E= +cloud.google.com/go/vision/v2 v2.7.0/go.mod h1:H89VysHy21avemp6xcf9b9JvZHVehWbET0uT/bcuY/0= +cloud.google.com/go/vmmigration v1.3.0/go.mod h1:oGJ6ZgGPQOFdjHuocGcLqX4lc98YQ7Ygq8YQwHh9A7g= +cloud.google.com/go/vmmigration v1.6.0/go.mod h1:bopQ/g4z+8qXzichC7GW1w2MjbErL54rk3/C843CjfY= +cloud.google.com/go/vmwareengine v0.1.0/go.mod h1:RsdNEf/8UDvKllXhMz5J40XxDrNJNN4sagiox+OI208= +cloud.google.com/go/vmwareengine v0.3.0/go.mod h1:wvoyMvNWdIzxMYSpH/R7y2h5h3WFkx6d+1TIsP39WGY= +cloud.google.com/go/vpcaccess v1.5.0/go.mod h1:drmg4HLk9NkZpGfCmZ3Tz0Bwnm2+DKqViEpeEpOq0m8= +cloud.google.com/go/vpcaccess v1.6.0/go.mod h1:wX2ILaNhe7TlVa4vC5xce1bCnqE3AeH27RV31lnmZes= +cloud.google.com/go/webrisk v1.7.0/go.mod h1:mVMHgEYH0r337nmt1JyLthzMr6YxwN1aAIEc2fTcq7A= +cloud.google.com/go/webrisk v1.8.0/go.mod h1:oJPDuamzHXgUc+b8SiHRcVInZQuybnvEW72PqTc7sSg= +cloud.google.com/go/websecurityscanner v1.4.0/go.mod h1:ebit/Fp0a+FWu5j4JOmJEV8S8CzdTkAS77oDsiSqYWQ= +cloud.google.com/go/websecurityscanner v1.5.0/go.mod h1:Y6xdCPy81yi0SQnDY1xdNTNpfY1oAgXUlcfN3B3eSng= +cloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT3ujaO/WwSA= +cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcPALq2CxzdePw= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= +github.com/armon/go-metrics v0.4.0/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20230310173818-32f1caf87195/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/ditrit/badaas v0.0.0-20230607151244-ded438ad025c/go.mod h1:Qaxactfr4iYYzfBmyW4DkG/5pkfkk/dVZ0Y8H1cYeTM= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/go-control-plane v0.11.0/go.mod h1:VnHyVMpzcLvCFt9yUz1UnCwHLhwx1WguiVDV7pTG/tI= +github.com/envoyproxy/protoc-gen-validate v0.10.0/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/s2a-go v0.1.3/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= +github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= +github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= +github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= +github.com/googleapis/gax-go/v2 v2.8.0/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/consul/api v1.20.0/go.mod h1:nR64eD44KQ59Of/ECwt2vUmIK2DKsDzAwTmwmLl8Wpo= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= +github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0= +github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= +github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= +github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= +github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/sagikazarmark/crypt v0.10.0/go.mod h1:gwTNHQVoOS3xp9Xvz5LLR+1AauC5M6880z5NWzdhOyQ= +github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.etcd.io/etcd/api/v3 v3.5.9/go.mod h1:uyAal843mC8uUVSLWz6eHa/d971iDGnCRpmKd2Z+X8k= +go.etcd.io/etcd/client/pkg/v3 v3.5.9/go.mod h1:y+CzeSmkMpWN2Jyu1npecjB9BBnABxGM4pN8cGuJeL4= +go.etcd.io/etcd/client/v2 v2.305.7/go.mod h1:GQGT5Z3TBuAQGvgPfhR7VPySu/SudxmEkRq9BgzFU6s= +go.etcd.io/etcd/client/v3 v3.5.9/go.mod h1:i/Eo5LrZ5IKqpbtpPDuaUnDOUv471oDg8cjQaUr2MbA= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/dig v1.16.1/go.mod h1:557JTAUZT5bUK0SvCwikmLPPtdQhfvLYtO5tJgQSbnk= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= +golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= +golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= +golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= +golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= +golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= +golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= +golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= +golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y= +golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +google.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0= +google.golang.org/api v0.106.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= +google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI= +google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0= +google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= +google.golang.org/api v0.122.0/go.mod h1:gcitW0lvnyWjSp9nKxAbdHKIZ6vF4aajGueeslZOyms= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230222225845-10f96fb3dbec/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= +google.golang.org/genproto v0.0.0-20230303212802-e74f57abe488/go.mod h1:TvhZT5f700eVlTNwND1xoEZQeWTB2RY/65kplwl/bFA= +google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= +google.golang.org/genproto v0.0.0-20230320184635-7606e756e683/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= +google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= +google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= +google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gorm.io/gorm v1.25.0/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= +mvdan.cc/gofumpt v0.5.0/go.mod h1:HBeVDtMKRZpXyxFciAirzdKklDlGu8aAy1wEbH5Y9js= diff --git a/http_support_test.go b/http_support_test.go deleted file mode 100644 index b1f5ec7c..00000000 --- a/http_support_test.go +++ /dev/null @@ -1,168 +0,0 @@ -package main - -import ( - "context" - "encoding/json" - "fmt" - "io" - "log" - "net/http" - "strconv" - "strings" - - "github.com/cucumber/godog" -) - -const BaseUrl = "http://localhost:8000" - -func (t *TestContext) requestGET(url string) error { - response, err := t.httpClient.Get(fmt.Sprintf("%s%s", BaseUrl, url)) - if err != nil { - return err - } - - t.storeResponseInContext(response) - return nil -} - -func (t *TestContext) storeResponseInContext(response *http.Response) { - t.statusCode = response.StatusCode - - buffer, err := io.ReadAll(response.Body) - if err != nil { - log.Panic(err) - } - response.Body.Close() - json.Unmarshal(buffer, &t.json) -} - -func (t *TestContext) assertStatusCode(_ context.Context, expectedStatusCode int) error { - if t.statusCode != expectedStatusCode { - return fmt.Errorf("expect status code %d but is %d", expectedStatusCode, t.statusCode) - } - return nil -} -func (t *TestContext) assertResponseFieldIsEquals(field string, expectedValue string) error { - value := t.json[field].(string) - if !assertValue(value, expectedValue) { - return fmt.Errorf("expect response field %s is %s but is %s", field, expectedValue, value) - } - return nil -} - -func assertValue(value string, expectedValue string) bool { - return expectedValue == value -} - -func (t *TestContext) requestWithJson(url, method string, jsonTable *godog.Table) error { - payload, err := buildJSONFromTable(jsonTable) - if err != nil { - return err - } - - method, err = checkMethod(method) - if err != nil { - return err - } - request, err := http.NewRequest(method, BaseUrl+url, payload) - if err != nil { - return fmt.Errorf("failed to build request ERROR=%s", err.Error()) - } - response, err := t.httpClient.Do(request) - if err != nil { - return fmt.Errorf("failed to run request ERROR=%s", err.Error()) - } - t.storeResponseInContext(response) - return nil -} - -// build a json payload in the form of a reader from a godog.Table -func buildJSONFromTable(table *godog.Table) (io.Reader, error) { - data := make(map[string]any, 0) - for indexRow, row := range table.Rows { - if indexRow == 0 { - for indexCell, cell := range row.Cells { - if cell.Value != []string{"key", "value", "type"}[indexCell] { - return nil, fmt.Errorf("should have %q as first line of the table", "| key | value | type |") - } - } - } else { - key := row.Cells[0].Value - valueAsString := row.Cells[1].Value - valueType := row.Cells[2].Value - - switch valueType { - case stringValueType: - data[key] = valueAsString - case booleanValueType: - boolean, err := strconv.ParseBool(valueAsString) - if err != nil { - return nil, fmt.Errorf("can't parse %q as boolean for key %q", valueAsString, key) - } - data[key] = boolean - case integerValueType: - integer, err := strconv.ParseInt(valueAsString, 10, 64) - if err != nil { - return nil, fmt.Errorf("can't parse %q as integer for key %q", valueAsString, key) - } - data[key] = integer - case floatValueType: - floatingNumber, err := strconv.ParseFloat(valueAsString, 64) - if err != nil { - return nil, fmt.Errorf("can't parse %q as float for key %q", valueAsString, key) - } - data[key] = floatingNumber - case nullValueType: - data[key] = nil - default: - return nil, fmt.Errorf("type %q does not exists, please use %v", valueType, []string{stringValueType, booleanValueType, integerValueType, floatValueType, nullValueType}) - } - - } - } - bytes, err := json.Marshal(data) - if err != nil { - panic("should not return an error") - } - return strings.NewReader(string(bytes)), nil -} - -const ( - stringValueType = "string" - booleanValueType = "boolean" - integerValueType = "integer" - floatValueType = "float" - nullValueType = "null" -) - -// check if the method is allowed and sanitize the string -func checkMethod(method string) (string, error) { - allowedMethods := []string{http.MethodGet, - http.MethodHead, - http.MethodPost, - http.MethodPut, - http.MethodPatch, - http.MethodDelete, - http.MethodConnect, - http.MethodOptions, - http.MethodTrace} - sanitizedMethod := strings.TrimSpace(strings.ToUpper(method)) - if !contains( - allowedMethods, - sanitizedMethod, - ) { - return "", fmt.Errorf("%q is not a valid HTTP method (please choose between %v)", method, allowedMethods) - } - return sanitizedMethod, nil - -} - -// return true if the set contains the target -func contains[T comparable](set []T, target T) bool { - for _, elem := range set { - if target == elem { - return true - } - } - return false -} diff --git a/httperrors/httperrors.go b/httperrors/httperrors.go index c5a02ecf..bb1338b6 100644 --- a/httperrors/httperrors.go +++ b/httperrors/httperrors.go @@ -5,21 +5,9 @@ import ( "fmt" "net/http" - "github.com/ditrit/badaas/persistence/models/dto" "go.uber.org/zap" -) -var ( - // AnError is an HTTPError instance useful for testing. If the code does not care - // about HTTPError specifics, and only needs to return the HTTPError for example, this - // HTTPError should be used to make the test code more readable. - AnError HTTPError = &HTTPErrorImpl{ - Status: -1, - Err: "TESTING ERROR", - Message: "USE ONLY FOR TESTING", - GolangError: nil, - toLog: true, - } + "github.com/ditrit/badaas/persistence/models/dto" ) type HTTPError interface { @@ -46,12 +34,13 @@ type HTTPErrorImpl struct { // Convert an HTTPError to a json string func (httpError *HTTPErrorImpl) ToJSON() string { - dto := &dto.DTOHTTPError{ + dtoHTTPError := &dto.HTTPError{ Error: httpError.Err, Message: httpError.Message, Status: http.StatusText(httpError.Status), } - payload, _ := json.Marshal(dto) + payload, _ := json.Marshal(dtoHTTPError) + return string(payload) } @@ -70,6 +59,7 @@ func (httpError *HTTPErrorImpl) Write(httpResponse http.ResponseWriter, logger * if httpError.toLog && logger != nil { logHTTPError(httpError, logger) } + http.Error(httpResponse, httpError.ToJSON(), httpError.Status) } @@ -83,7 +73,7 @@ func logHTTPError(httpError *HTTPErrorImpl, logger *zap.Logger) { } // HTTPError constructor -func NewHTTPError(status int, err string, message string, golangError error, toLog bool) HTTPError { +func NewHTTPError(status int, err, message string, golangError error, toLog bool) HTTPError { return &HTTPErrorImpl{ Status: status, Err: err, @@ -93,19 +83,19 @@ func NewHTTPError(status int, err string, message string, golangError error, toL } } -// A contructor for an HttpError "Not Found" -func NewErrorNotFound(ressourceName string, msg string) HTTPError { +// A constructor for an HttpError "Not Found" +func NewErrorNotFound(resourceName, msg string) HTTPError { return NewHTTPError( http.StatusNotFound, - fmt.Sprintf("%s not found", ressourceName), + fmt.Sprintf("%s not found", resourceName), msg, nil, false, ) } -// A contructor for an HttpError "Internal Server Error" -func NewInternalServerError(errorName string, msg string, err error) HTTPError { +// A constructor for an HttpError "Internal Server Error" +func NewInternalServerError(errorName, msg string, err error) HTTPError { return NewHTTPError( http.StatusInternalServerError, errorName, @@ -115,8 +105,13 @@ func NewInternalServerError(errorName string, msg string, err error) HTTPError { ) } -// A contructor for an HttpError "Unauthorized Error" -func NewUnauthorizedError(errorName string, msg string) HTTPError { +// Constructor for an HttpError "DB Error", a internal server error produced by a query +func NewDBError(err error) HTTPError { + return NewInternalServerError("db error", "database query failed", err) +} + +// A constructor for an HttpError "Unauthorized Error" +func NewUnauthorizedError(errorName, msg string) HTTPError { return NewHTTPError( http.StatusUnauthorized, errorName, @@ -125,3 +120,8 @@ func NewUnauthorizedError(errorName string, msg string) HTTPError { true, ) } + +// A constructor for an HTTPError "Bad Request" +func NewBadRequestError(err, msg string) HTTPError { + return NewHTTPError(http.StatusBadRequest, err, msg, nil, false) +} diff --git a/httperrors/httperrors_test.go b/httperrors/httperrors_test.go index b98b9c77..fbc7115e 100644 --- a/httperrors/httperrors_test.go +++ b/httperrors/httperrors_test.go @@ -9,25 +9,31 @@ import ( "net/http/httptest" "testing" - "github.com/ditrit/badaas/httperrors" - "github.com/ditrit/badaas/persistence/models/dto" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap" "go.uber.org/zap/zapcore" "go.uber.org/zap/zaptest/observer" + + "github.com/ditrit/badaas/httperrors" + "github.com/ditrit/badaas/persistence/models/dto" ) func TestTojson(t *testing.T) { - err := "Error while parsing json" + errString := "Error while parsing json" message := "The request body was malformed" - error := httperrors.NewHTTPError(http.StatusBadRequest, err, message, nil, true) - assert.NotEmpty(t, error.ToJSON()) - assert.True(t, json.Valid([]byte(error.ToJSON())), "output json is not valid") + herr := httperrors.NewHTTPError(http.StatusBadRequest, errString, message, nil, true) + assert.NotEmpty(t, herr.ToJSON()) + assert.True(t, json.Valid([]byte(herr.ToJSON())), "output json is not valid") - // check if is is correctly deserialized + // check if it is correctly deserialized var content map[string]any - json.Unmarshal([]byte(error.ToJSON()), &content) + + err := json.Unmarshal([]byte(herr.ToJSON()), &content) + if err != nil { + t.Error(err) + } + _, ok := content["err"] assert.True(t, ok, "\"err\" field should be in the json string") _, ok = content["msg"] @@ -35,35 +41,52 @@ func TestTojson(t *testing.T) { _, ok = content["status"] assert.True(t, ok, "\"status\" field should be in the json string") - assert.Equal(t, err, content["err"].(string)) - assert.Equal(t, message, content["msg"].(string)) - assert.Equal(t, http.StatusText(http.StatusBadRequest), content["status"].(string)) - assert.True(t, error.Log()) + contentErr, ok := content["err"].(string) + if !ok { + t.Error("content err is not a string") + } + + assert.Equal(t, errString, contentErr) + + contentMsg, ok := content["msg"].(string) + if !ok { + t.Error("content msg is not a string") + } + + assert.Equal(t, message, contentMsg) + + contentStatus, ok := content["status"].(string) + if !ok { + t.Error("content status is not a string") + } + + assert.Equal(t, http.StatusText(http.StatusBadRequest), contentStatus) + assert.True(t, herr.Log()) } func TestLog(t *testing.T) { - error := httperrors.NewHTTPError(http.StatusBadRequest, "err", "message", nil, true) - assert.True(t, error.Log()) - error = httperrors.NewHTTPError(http.StatusBadRequest, "err", "message", nil, false) - assert.False(t, error.Log()) + herr := httperrors.NewHTTPError(http.StatusBadRequest, "err", "message", nil, true) + assert.True(t, herr.Log()) + herr = httperrors.NewHTTPError(http.StatusBadRequest, "err", "message", nil, false) + assert.False(t, herr.Log()) } func TestError(t *testing.T) { - error := httperrors.NewHTTPError(http.StatusBadRequest, "Error while parsing json", "The request body was malformed", nil, true) - assert.Contains(t, error.Error(), error.ToJSON()) + herr := httperrors.NewHTTPError(http.StatusBadRequest, "Error while parsing json", "The request body was malformed", nil, true) + assert.Contains(t, herr.Error(), herr.ToJSON()) } func TestWrite(t *testing.T) { res := httptest.NewRecorder() - error := httperrors.NewHTTPError(http.StatusBadRequest, "Error while parsing json", "The request body was malformed", nil, true) - error.Write(res, zap.L()) + herr := httperrors.NewHTTPError(http.StatusBadRequest, "Error while parsing json", "The request body was malformed", nil, true) + herr.Write(res, zap.L()) bodyBytes, err := io.ReadAll(res.Body) assert.Nil(t, err) assert.NotEmpty(t, bodyBytes) - originalBytes := []byte(error.ToJSON()) + + originalBytes := []byte(herr.ToJSON()) // can't use assert.Contains because it only support strings - assert.True(t, - bytes.Contains(bodyBytes, originalBytes)) + assert.True(t, bytes.Contains(bodyBytes, originalBytes)) } func TestLogger(t *testing.T) { @@ -72,8 +95,8 @@ func TestLogger(t *testing.T) { observedLogger := zap.New(observedZapCore) res := httptest.NewRecorder() - error := httperrors.NewHTTPError(http.StatusBadRequest, "Error while parsing json", "The request body was malformed", nil, true) - error.Write(res, observedLogger) + herr := httperrors.NewHTTPError(http.StatusBadRequest, "Error while parsing json", "The request body was malformed", nil, true) + herr.Write(res, observedLogger) require.Equal(t, 1, observedLogs.Len()) log := observedLogs.All()[0] @@ -88,32 +111,48 @@ func TestLogger(t *testing.T) { func TestNewErrorNotFound(t *testing.T) { ressourceName := "file" - error := httperrors.NewErrorNotFound(ressourceName, "main.css is not accessible") - assert.NotNil(t, error) - assert.False(t, error.Log()) - dto := new(dto.DTOHTTPError) - err := json.Unmarshal([]byte(error.ToJSON()), &dto) + herr := httperrors.NewErrorNotFound(ressourceName, "main.css is not accessible") + assert.NotNil(t, herr) + assert.False(t, herr.Log()) + + dtoHTTPError := new(dto.HTTPError) + err := json.Unmarshal([]byte(herr.ToJSON()), &dtoHTTPError) assert.NoError(t, err) - assert.Equal(t, http.StatusText(http.StatusNotFound), dto.Status) - assert.Equal(t, fmt.Sprintf("%s not found", ressourceName), dto.Error) + assert.Equal(t, http.StatusText(http.StatusNotFound), dtoHTTPError.Status) + assert.Equal(t, fmt.Sprintf("%s not found", ressourceName), dtoHTTPError.Error) } func TestNewInternalServerError(t *testing.T) { - error := httperrors.NewInternalServerError("casbin error", "the ressource is not accessible", nil) - assert.NotNil(t, error) - assert.True(t, error.Log()) - dto := new(dto.DTOHTTPError) - err := json.Unmarshal([]byte(error.ToJSON()), &dto) + herr := httperrors.NewInternalServerError("casbin error", "the ressource is not accessible", nil) + assert.NotNil(t, herr) + assert.True(t, herr.Log()) + + dtoHTTPError := new(dto.HTTPError) + err := json.Unmarshal([]byte(herr.ToJSON()), &dtoHTTPError) assert.NoError(t, err) - assert.Equal(t, http.StatusText(http.StatusInternalServerError), dto.Status) + assert.Equal(t, http.StatusText(http.StatusInternalServerError), dtoHTTPError.Status) } func TestNewUnauthorizedError(t *testing.T) { - error := httperrors.NewUnauthorizedError("json unmarshalling", "nil value whatever") - assert.NotNil(t, error) - assert.True(t, error.Log()) - dto := new(dto.DTOHTTPError) - err := json.Unmarshal([]byte(error.ToJSON()), &dto) + herr := httperrors.NewUnauthorizedError("json unmarshalling", "nil value whatever") + assert.NotNil(t, herr) + assert.True(t, herr.Log()) + + dtoHTTPError := new(dto.HTTPError) + err := json.Unmarshal([]byte(herr.ToJSON()), &dtoHTTPError) + assert.NoError(t, err) + assert.Equal(t, http.StatusText(http.StatusUnauthorized), dtoHTTPError.Status) +} + +func TestNewBadRequestError(t *testing.T) { + herr := httperrors.NewBadRequestError("id must be an unsigned integer", "please use a valid id") + assert.NotNil(t, herr) + assert.False(t, herr.Log()) + + dtoHTTPError := new(dto.HTTPError) + err := json.Unmarshal([]byte(herr.ToJSON()), &dtoHTTPError) assert.NoError(t, err) - assert.Equal(t, http.StatusText(http.StatusUnauthorized), dto.Status) + assert.Equal(t, http.StatusText(http.StatusBadRequest), dtoHTTPError.Status) + assert.Equal(t, "id must be an unsigned integer", dtoHTTPError.Error) + assert.Equal(t, "please use a valid id", dtoHTTPError.Message) } diff --git a/logger/log.go b/logger/log.go index 9170f8e4..8527db43 100644 --- a/logger/log.go +++ b/logger/log.go @@ -3,8 +3,9 @@ package logger import ( "log" - "github.com/ditrit/badaas/configuration" "go.uber.org/zap" + + "github.com/ditrit/badaas/configuration" ) const ( @@ -17,18 +18,21 @@ func NewLogger(conf configuration.LoggerConfiguration) *zap.Logger { var config zap.Config if conf.GetMode() == ProductionLogger { config = zap.NewProductionConfig() - log.Printf("Log mode use: %s\n", ProductionLogger) + log.Printf("Log mode use: %s\n", ProductionLogger) } else { config = zap.NewDevelopmentConfig() log.Printf("Log mode use: %s\n", DevelopmentLogger) - } + config.DisableStacktrace = true + logger, err := config.Build() if err != nil { panic(err) } + logger.Info("The logger was successfully initialized") + return logger } diff --git a/logger/log_test.go b/logger/log_test.go index 873a698c..2271131b 100644 --- a/logger/log_test.go +++ b/logger/log_test.go @@ -3,9 +3,10 @@ package logger import ( "testing" - configurationmocks "github.com/ditrit/badaas/mocks/configuration" "github.com/stretchr/testify/assert" "go.uber.org/zap/zapcore" + + configurationmocks "github.com/ditrit/badaas/mocks/configuration" ) func TestInitializeDevelopmentLogger(t *testing.T) { diff --git a/mocks/badorm/BadaasID.go b/mocks/badorm/BadaasID.go new file mode 100644 index 00000000..cf2c0098 --- /dev/null +++ b/mocks/badorm/BadaasID.go @@ -0,0 +1,25 @@ +// Code generated by mockery v2.20.0. DO NOT EDIT. + +package mocks + +import mock "github.com/stretchr/testify/mock" + +// BadaasID is an autogenerated mock type for the BadaasID type +type BadaasID struct { + mock.Mock +} + +type mockConstructorTestingTNewBadaasID interface { + mock.TestingT + Cleanup(func()) +} + +// NewBadaasID creates a new instance of BadaasID. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewBadaasID(t mockConstructorTestingTNewBadaasID) *BadaasID { + mock := &BadaasID{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mocks/badorm/CRUDRepository.go b/mocks/badorm/CRUDRepository.go new file mode 100644 index 00000000..1659e91e --- /dev/null +++ b/mocks/badorm/CRUDRepository.go @@ -0,0 +1,164 @@ +// Code generated by mockery v2.20.0. DO NOT EDIT. + +package mocks + +import ( + badorm "github.com/ditrit/badaas/badorm" + gorm "gorm.io/gorm" + + mock "github.com/stretchr/testify/mock" +) + +// CRUDRepository is an autogenerated mock type for the CRUDRepository type +type CRUDRepository[T badorm.Model, ID badorm.ModelID] struct { + mock.Mock +} + +// Create provides a mock function with given fields: tx, entity +func (_m *CRUDRepository[T, ID]) Create(tx *gorm.DB, entity *T) error { + ret := _m.Called(tx, entity) + + var r0 error + if rf, ok := ret.Get(0).(func(*gorm.DB, *T) error); ok { + r0 = rf(tx, entity) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Delete provides a mock function with given fields: tx, entity +func (_m *CRUDRepository[T, ID]) Delete(tx *gorm.DB, entity *T) error { + ret := _m.Called(tx, entity) + + var r0 error + if rf, ok := ret.Get(0).(func(*gorm.DB, *T) error); ok { + r0 = rf(tx, entity) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// GetByID provides a mock function with given fields: tx, id +func (_m *CRUDRepository[T, ID]) GetByID(tx *gorm.DB, id ID) (*T, error) { + ret := _m.Called(tx, id) + + var r0 *T + var r1 error + if rf, ok := ret.Get(0).(func(*gorm.DB, ID) (*T, error)); ok { + return rf(tx, id) + } + if rf, ok := ret.Get(0).(func(*gorm.DB, ID) *T); ok { + r0 = rf(tx, id) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*T) + } + } + + if rf, ok := ret.Get(1).(func(*gorm.DB, ID) error); ok { + r1 = rf(tx, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Query provides a mock function with given fields: tx, conditions +func (_m *CRUDRepository[T, ID]) Query(tx *gorm.DB, conditions ...badorm.Condition[T]) ([]*T, error) { + _va := make([]interface{}, len(conditions)) + for _i := range conditions { + _va[_i] = conditions[_i] + } + var _ca []interface{} + _ca = append(_ca, tx) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 []*T + var r1 error + if rf, ok := ret.Get(0).(func(*gorm.DB, ...badorm.Condition[T]) ([]*T, error)); ok { + return rf(tx, conditions...) + } + if rf, ok := ret.Get(0).(func(*gorm.DB, ...badorm.Condition[T]) []*T); ok { + r0 = rf(tx, conditions...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*T) + } + } + + if rf, ok := ret.Get(1).(func(*gorm.DB, ...badorm.Condition[T]) error); ok { + r1 = rf(tx, conditions...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// QueryOne provides a mock function with given fields: tx, conditions +func (_m *CRUDRepository[T, ID]) QueryOne(tx *gorm.DB, conditions ...badorm.Condition[T]) (*T, error) { + _va := make([]interface{}, len(conditions)) + for _i := range conditions { + _va[_i] = conditions[_i] + } + var _ca []interface{} + _ca = append(_ca, tx) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *T + var r1 error + if rf, ok := ret.Get(0).(func(*gorm.DB, ...badorm.Condition[T]) (*T, error)); ok { + return rf(tx, conditions...) + } + if rf, ok := ret.Get(0).(func(*gorm.DB, ...badorm.Condition[T]) *T); ok { + r0 = rf(tx, conditions...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*T) + } + } + + if rf, ok := ret.Get(1).(func(*gorm.DB, ...badorm.Condition[T]) error); ok { + r1 = rf(tx, conditions...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Save provides a mock function with given fields: tx, entity +func (_m *CRUDRepository[T, ID]) Save(tx *gorm.DB, entity *T) error { + ret := _m.Called(tx, entity) + + var r0 error + if rf, ok := ret.Get(0).(func(*gorm.DB, *T) error); ok { + r0 = rf(tx, entity) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +type mockConstructorTestingTNewCRUDRepository interface { + mock.TestingT + Cleanup(func()) +} + +// NewCRUDRepository creates a new instance of CRUDRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewCRUDRepository[T badorm.Model, ID badorm.ModelID](t mockConstructorTestingTNewCRUDRepository) *CRUDRepository[T, ID] { + mock := &CRUDRepository[T, ID]{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mocks/badorm/CRUDService.go b/mocks/badorm/CRUDService.go new file mode 100644 index 00000000..90de4256 --- /dev/null +++ b/mocks/badorm/CRUDService.go @@ -0,0 +1,118 @@ +// Code generated by mockery v2.20.0. DO NOT EDIT. + +package mocks + +import ( + badorm "github.com/ditrit/badaas/badorm" + mock "github.com/stretchr/testify/mock" +) + +// CRUDService is an autogenerated mock type for the CRUDService type +type CRUDService[T badorm.Model, ID badorm.ModelID] struct { + mock.Mock +} + +// GetByID provides a mock function with given fields: id +func (_m *CRUDService[T, ID]) GetByID(id ID) (*T, error) { + ret := _m.Called(id) + + var r0 *T + var r1 error + if rf, ok := ret.Get(0).(func(ID) (*T, error)); ok { + return rf(id) + } + if rf, ok := ret.Get(0).(func(ID) *T); ok { + r0 = rf(id) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*T) + } + } + + if rf, ok := ret.Get(1).(func(ID) error); ok { + r1 = rf(id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Query provides a mock function with given fields: conditions +func (_m *CRUDService[T, ID]) Query(conditions ...badorm.Condition[T]) ([]*T, error) { + _va := make([]interface{}, len(conditions)) + for _i := range conditions { + _va[_i] = conditions[_i] + } + var _ca []interface{} + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 []*T + var r1 error + if rf, ok := ret.Get(0).(func(...badorm.Condition[T]) ([]*T, error)); ok { + return rf(conditions...) + } + if rf, ok := ret.Get(0).(func(...badorm.Condition[T]) []*T); ok { + r0 = rf(conditions...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*T) + } + } + + if rf, ok := ret.Get(1).(func(...badorm.Condition[T]) error); ok { + r1 = rf(conditions...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// QueryOne provides a mock function with given fields: conditions +func (_m *CRUDService[T, ID]) QueryOne(conditions ...badorm.Condition[T]) (*T, error) { + _va := make([]interface{}, len(conditions)) + for _i := range conditions { + _va[_i] = conditions[_i] + } + var _ca []interface{} + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *T + var r1 error + if rf, ok := ret.Get(0).(func(...badorm.Condition[T]) (*T, error)); ok { + return rf(conditions...) + } + if rf, ok := ret.Get(0).(func(...badorm.Condition[T]) *T); ok { + r0 = rf(conditions...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*T) + } + } + + if rf, ok := ret.Get(1).(func(...badorm.Condition[T]) error); ok { + r1 = rf(conditions...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +type mockConstructorTestingTNewCRUDService interface { + mock.TestingT + Cleanup(func()) +} + +// NewCRUDService creates a new instance of CRUDService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewCRUDService[T badorm.Model, ID badorm.ModelID](t mockConstructorTestingTNewCRUDService) *CRUDService[T, ID] { + mock := &CRUDService[T, ID]{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mocks/badorm/Condition.go b/mocks/badorm/Condition.go new file mode 100644 index 00000000..6910fafe --- /dev/null +++ b/mocks/badorm/Condition.go @@ -0,0 +1,47 @@ +// Code generated by mockery v2.20.0. DO NOT EDIT. + +package mocks + +import ( + badorm "github.com/ditrit/badaas/badorm" + mock "github.com/stretchr/testify/mock" +) + +// Condition is an autogenerated mock type for the Condition type +type Condition[T badorm.Model] struct { + mock.Mock +} + +// ApplyTo provides a mock function with given fields: query, table +func (_m *Condition[T]) ApplyTo(query *badorm.Query, table badorm.Table) error { + ret := _m.Called(query, table) + + var r0 error + if rf, ok := ret.Get(0).(func(*badorm.Query, badorm.Table) error); ok { + r0 = rf(query, table) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// InterfaceVerificationMethod provides a mock function with given fields: _a0 +func (_m *Condition[T]) InterfaceVerificationMethod(_a0 T) { + _m.Called(_a0) +} + +type mockConstructorTestingTNewCondition interface { + mock.TestingT + Cleanup(func()) +} + +// NewCondition creates a new instance of Condition. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewCondition[T badorm.Model](t mockConstructorTestingTNewCondition) *Condition[T] { + mock := &Condition[T]{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mocks/badorm/DynamicOperator.go b/mocks/badorm/DynamicOperator.go new file mode 100644 index 00000000..d89cfe4b --- /dev/null +++ b/mocks/badorm/DynamicOperator.go @@ -0,0 +1,82 @@ +// Code generated by mockery v2.20.0. DO NOT EDIT. + +package mocks + +import ( + badorm "github.com/ditrit/badaas/badorm" + mock "github.com/stretchr/testify/mock" +) + +// DynamicOperator is an autogenerated mock type for the DynamicOperator type +type DynamicOperator[T interface{}] struct { + mock.Mock +} + +// InterfaceVerificationMethod provides a mock function with given fields: _a0 +func (_m *DynamicOperator[T]) InterfaceVerificationMethod(_a0 T) { + _m.Called(_a0) +} + +// SelectJoin provides a mock function with given fields: valueNumber, joinNumber +func (_m *DynamicOperator[T]) SelectJoin(valueNumber uint, joinNumber uint) badorm.DynamicOperator[T] { + ret := _m.Called(valueNumber, joinNumber) + + var r0 badorm.DynamicOperator[T] + if rf, ok := ret.Get(0).(func(uint, uint) badorm.DynamicOperator[T]); ok { + r0 = rf(valueNumber, joinNumber) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(badorm.DynamicOperator[T]) + } + } + + return r0 +} + +// ToSQL provides a mock function with given fields: query, columnName +func (_m *DynamicOperator[T]) ToSQL(query *badorm.Query, columnName string) (string, []interface{}, error) { + ret := _m.Called(query, columnName) + + var r0 string + var r1 []interface{} + var r2 error + if rf, ok := ret.Get(0).(func(*badorm.Query, string) (string, []interface{}, error)); ok { + return rf(query, columnName) + } + if rf, ok := ret.Get(0).(func(*badorm.Query, string) string); ok { + r0 = rf(query, columnName) + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func(*badorm.Query, string) []interface{}); ok { + r1 = rf(query, columnName) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).([]interface{}) + } + } + + if rf, ok := ret.Get(2).(func(*badorm.Query, string) error); ok { + r2 = rf(query, columnName) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +type mockConstructorTestingTNewDynamicOperator interface { + mock.TestingT + Cleanup(func()) +} + +// NewDynamicOperator creates a new instance of DynamicOperator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewDynamicOperator[T interface{}](t mockConstructorTestingTNewDynamicOperator) *DynamicOperator[T] { + mock := &DynamicOperator[T]{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mocks/badorm/IJoinCondition.go b/mocks/badorm/IJoinCondition.go new file mode 100644 index 00000000..8b50a24a --- /dev/null +++ b/mocks/badorm/IJoinCondition.go @@ -0,0 +1,75 @@ +// Code generated by mockery v2.20.0. DO NOT EDIT. + +package mocks + +import ( + badorm "github.com/ditrit/badaas/badorm" + mock "github.com/stretchr/testify/mock" +) + +// IJoinCondition is an autogenerated mock type for the IJoinCondition type +type IJoinCondition[T badorm.Model] struct { + mock.Mock +} + +// ApplyTo provides a mock function with given fields: query, table +func (_m *IJoinCondition[T]) ApplyTo(query *badorm.Query, table badorm.Table) error { + ret := _m.Called(query, table) + + var r0 error + if rf, ok := ret.Get(0).(func(*badorm.Query, badorm.Table) error); ok { + r0 = rf(query, table) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// InterfaceVerificationMethod provides a mock function with given fields: _a0 +func (_m *IJoinCondition[T]) InterfaceVerificationMethod(_a0 T) { + _m.Called(_a0) +} + +// makesFilter provides a mock function with given fields: +func (_m *IJoinCondition[T]) makesFilter() bool { + ret := _m.Called() + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// makesPreload provides a mock function with given fields: +func (_m *IJoinCondition[T]) makesPreload() bool { + ret := _m.Called() + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +type mockConstructorTestingTNewIJoinCondition interface { + mock.TestingT + Cleanup(func()) +} + +// NewIJoinCondition creates a new instance of IJoinCondition. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewIJoinCondition[T badorm.Model](t mockConstructorTestingTNewIJoinCondition) *IJoinCondition[T] { + mock := &IJoinCondition[T]{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mocks/badorm/Model.go b/mocks/badorm/Model.go new file mode 100644 index 00000000..ca967b67 --- /dev/null +++ b/mocks/badorm/Model.go @@ -0,0 +1,39 @@ +// Code generated by mockery v2.20.0. DO NOT EDIT. + +package mocks + +import mock "github.com/stretchr/testify/mock" + +// Model is an autogenerated mock type for the Model type +type Model struct { + mock.Mock +} + +// IsLoaded provides a mock function with given fields: +func (_m *Model) IsLoaded() bool { + ret := _m.Called() + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +type mockConstructorTestingTNewModel interface { + mock.TestingT + Cleanup(func()) +} + +// NewModel creates a new instance of Model. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewModel(t mockConstructorTestingTNewModel) *Model { + mock := &Model{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mocks/badorm/ModelID.go b/mocks/badorm/ModelID.go new file mode 100644 index 00000000..dfaee03d --- /dev/null +++ b/mocks/badorm/ModelID.go @@ -0,0 +1,39 @@ +// Code generated by mockery v2.20.0. DO NOT EDIT. + +package mocks + +import mock "github.com/stretchr/testify/mock" + +// ModelID is an autogenerated mock type for the ModelID type +type ModelID struct { + mock.Mock +} + +// IsNil provides a mock function with given fields: +func (_m *ModelID) IsNil() bool { + ret := _m.Called() + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +type mockConstructorTestingTNewModelID interface { + mock.TestingT + Cleanup(func()) +} + +// NewModelID creates a new instance of ModelID. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewModelID(t mockConstructorTestingTNewModelID) *ModelID { + mock := &ModelID{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mocks/badorm/Operator.go b/mocks/badorm/Operator.go new file mode 100644 index 00000000..35bf5416 --- /dev/null +++ b/mocks/badorm/Operator.go @@ -0,0 +1,66 @@ +// Code generated by mockery v2.20.0. DO NOT EDIT. + +package mocks + +import ( + badorm "github.com/ditrit/badaas/badorm" + mock "github.com/stretchr/testify/mock" +) + +// Operator is an autogenerated mock type for the Operator type +type Operator[T interface{}] struct { + mock.Mock +} + +// InterfaceVerificationMethod provides a mock function with given fields: _a0 +func (_m *Operator[T]) InterfaceVerificationMethod(_a0 T) { + _m.Called(_a0) +} + +// ToSQL provides a mock function with given fields: query, columnName +func (_m *Operator[T]) ToSQL(query *badorm.Query, columnName string) (string, []interface{}, error) { + ret := _m.Called(query, columnName) + + var r0 string + var r1 []interface{} + var r2 error + if rf, ok := ret.Get(0).(func(*badorm.Query, string) (string, []interface{}, error)); ok { + return rf(query, columnName) + } + if rf, ok := ret.Get(0).(func(*badorm.Query, string) string); ok { + r0 = rf(query, columnName) + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func(*badorm.Query, string) []interface{}); ok { + r1 = rf(query, columnName) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).([]interface{}) + } + } + + if rf, ok := ret.Get(2).(func(*badorm.Query, string) error); ok { + r2 = rf(query, columnName) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +type mockConstructorTestingTNewOperator interface { + mock.TestingT + Cleanup(func()) +} + +// NewOperator creates a new instance of Operator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewOperator[T interface{}](t mockConstructorTestingTNewOperator) *Operator[T] { + mock := &Operator[T]{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mocks/badorm/WhereCondition.go b/mocks/badorm/WhereCondition.go new file mode 100644 index 00000000..55e7ad34 --- /dev/null +++ b/mocks/badorm/WhereCondition.go @@ -0,0 +1,94 @@ +// Code generated by mockery v2.20.0. DO NOT EDIT. + +package mocks + +import ( + badorm "github.com/ditrit/badaas/badorm" + mock "github.com/stretchr/testify/mock" +) + +// WhereCondition is an autogenerated mock type for the WhereCondition type +type WhereCondition[T badorm.Model] struct { + mock.Mock +} + +// AffectsDeletedAt provides a mock function with given fields: +func (_m *WhereCondition[T]) AffectsDeletedAt() bool { + ret := _m.Called() + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// ApplyTo provides a mock function with given fields: query, table +func (_m *WhereCondition[T]) ApplyTo(query *badorm.Query, table badorm.Table) error { + ret := _m.Called(query, table) + + var r0 error + if rf, ok := ret.Get(0).(func(*badorm.Query, badorm.Table) error); ok { + r0 = rf(query, table) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// GetSQL provides a mock function with given fields: query, table +func (_m *WhereCondition[T]) GetSQL(query *badorm.Query, table badorm.Table) (string, []interface{}, error) { + ret := _m.Called(query, table) + + var r0 string + var r1 []interface{} + var r2 error + if rf, ok := ret.Get(0).(func(*badorm.Query, badorm.Table) (string, []interface{}, error)); ok { + return rf(query, table) + } + if rf, ok := ret.Get(0).(func(*badorm.Query, badorm.Table) string); ok { + r0 = rf(query, table) + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func(*badorm.Query, badorm.Table) []interface{}); ok { + r1 = rf(query, table) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).([]interface{}) + } + } + + if rf, ok := ret.Get(2).(func(*badorm.Query, badorm.Table) error); ok { + r2 = rf(query, table) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// InterfaceVerificationMethod provides a mock function with given fields: _a0 +func (_m *WhereCondition[T]) InterfaceVerificationMethod(_a0 T) { + _m.Called(_a0) +} + +type mockConstructorTestingTNewWhereCondition interface { + mock.TestingT + Cleanup(func()) +} + +// NewWhereCondition creates a new instance of WhereCondition. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewWhereCondition[T badorm.Model](t mockConstructorTestingTNewWhereCondition) *WhereCondition[T] { + mock := &WhereCondition[T]{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mocks/badorm/iFieldIdentifier.go b/mocks/badorm/iFieldIdentifier.go new file mode 100644 index 00000000..c20c1408 --- /dev/null +++ b/mocks/badorm/iFieldIdentifier.go @@ -0,0 +1,74 @@ +// Code generated by mockery v2.20.0. DO NOT EDIT. + +package mocks + +import ( + badorm "github.com/ditrit/badaas/badorm" + mock "github.com/stretchr/testify/mock" + + reflect "reflect" +) + +// iFieldIdentifier is an autogenerated mock type for the iFieldIdentifier type +type iFieldIdentifier struct { + mock.Mock +} + +// ColumnName provides a mock function with given fields: query, table +func (_m *iFieldIdentifier) ColumnName(query *badorm.Query, table badorm.Table) string { + ret := _m.Called(query, table) + + var r0 string + if rf, ok := ret.Get(0).(func(*badorm.Query, badorm.Table) string); ok { + r0 = rf(query, table) + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// ColumnSQL provides a mock function with given fields: query, table +func (_m *iFieldIdentifier) ColumnSQL(query *badorm.Query, table badorm.Table) string { + ret := _m.Called(query, table) + + var r0 string + if rf, ok := ret.Get(0).(func(*badorm.Query, badorm.Table) string); ok { + r0 = rf(query, table) + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// GetModelType provides a mock function with given fields: +func (_m *iFieldIdentifier) GetModelType() reflect.Type { + ret := _m.Called() + + var r0 reflect.Type + if rf, ok := ret.Get(0).(func() reflect.Type); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(reflect.Type) + } + } + + return r0 +} + +type mockConstructorTestingTnewIFieldIdentifier interface { + mock.TestingT + Cleanup(func()) +} + +// newIFieldIdentifier creates a new instance of iFieldIdentifier. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func newIFieldIdentifier(t mockConstructorTestingTnewIFieldIdentifier) *iFieldIdentifier { + mock := &iFieldIdentifier{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mocks/badorm/unsafe/CRUDRepository.go b/mocks/badorm/unsafe/CRUDRepository.go new file mode 100644 index 00000000..50466455 --- /dev/null +++ b/mocks/badorm/unsafe/CRUDRepository.go @@ -0,0 +1,56 @@ +// Code generated by mockery v2.20.0. DO NOT EDIT. + +package mocks + +import ( + badorm "github.com/ditrit/badaas/badorm" + gorm "gorm.io/gorm" + + mock "github.com/stretchr/testify/mock" +) + +// CRUDRepository is an autogenerated mock type for the CRUDRepository type +type CRUDRepository[T badorm.Model, ID badorm.ModelID] struct { + mock.Mock +} + +// Query provides a mock function with given fields: tx, conditions +func (_m *CRUDRepository[T, ID]) Query(tx *gorm.DB, conditions map[string]interface{}) ([]*T, error) { + ret := _m.Called(tx, conditions) + + var r0 []*T + var r1 error + if rf, ok := ret.Get(0).(func(*gorm.DB, map[string]interface{}) ([]*T, error)); ok { + return rf(tx, conditions) + } + if rf, ok := ret.Get(0).(func(*gorm.DB, map[string]interface{}) []*T); ok { + r0 = rf(tx, conditions) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*T) + } + } + + if rf, ok := ret.Get(1).(func(*gorm.DB, map[string]interface{}) error); ok { + r1 = rf(tx, conditions) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +type mockConstructorTestingTNewCRUDRepository interface { + mock.TestingT + Cleanup(func()) +} + +// NewCRUDRepository creates a new instance of CRUDRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewCRUDRepository[T badorm.Model, ID badorm.ModelID](t mockConstructorTestingTNewCRUDRepository) *CRUDRepository[T, ID] { + mock := &CRUDRepository[T, ID]{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mocks/badorm/unsafe/CRUDService.go b/mocks/badorm/unsafe/CRUDService.go new file mode 100644 index 00000000..9a6be35f --- /dev/null +++ b/mocks/badorm/unsafe/CRUDService.go @@ -0,0 +1,54 @@ +// Code generated by mockery v2.20.0. DO NOT EDIT. + +package mocks + +import ( + badorm "github.com/ditrit/badaas/badorm" + mock "github.com/stretchr/testify/mock" +) + +// CRUDService is an autogenerated mock type for the CRUDService type +type CRUDService[T badorm.Model, ID badorm.ModelID] struct { + mock.Mock +} + +// Query provides a mock function with given fields: conditions +func (_m *CRUDService[T, ID]) Query(conditions map[string]interface{}) ([]*T, error) { + ret := _m.Called(conditions) + + var r0 []*T + var r1 error + if rf, ok := ret.Get(0).(func(map[string]interface{}) ([]*T, error)); ok { + return rf(conditions) + } + if rf, ok := ret.Get(0).(func(map[string]interface{}) []*T); ok { + r0 = rf(conditions) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*T) + } + } + + if rf, ok := ret.Get(1).(func(map[string]interface{}) error); ok { + r1 = rf(conditions) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +type mockConstructorTestingTNewCRUDService interface { + mock.TestingT + Cleanup(func()) +} + +// NewCRUDService creates a new instance of CRUDService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewCRUDService[T badorm.Model, ID badorm.ModelID](t mockConstructorTestingTNewCRUDService) *CRUDService[T, ID] { + mock := &CRUDService[T, ID]{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mocks/configuration/CommandsInitializer.go b/mocks/configuration/CommandsInitializer.go new file mode 100644 index 00000000..b1ba3863 --- /dev/null +++ b/mocks/configuration/CommandsInitializer.go @@ -0,0 +1,42 @@ +// Code generated by mockery v2.20.0. DO NOT EDIT. + +package mocks + +import ( + verdeter "github.com/ditrit/verdeter" + mock "github.com/stretchr/testify/mock" +) + +// CommandsInitializer is an autogenerated mock type for the CommandsInitializer type +type CommandsInitializer struct { + mock.Mock +} + +// Init provides a mock function with given fields: config +func (_m *CommandsInitializer) Init(config *verdeter.VerdeterCommand) error { + ret := _m.Called(config) + + var r0 error + if rf, ok := ret.Get(0).(func(*verdeter.VerdeterCommand) error); ok { + r0 = rf(config) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +type mockConstructorTestingTNewCommandsInitializer interface { + mock.TestingT + Cleanup(func()) +} + +// NewCommandsInitializer creates a new instance of CommandsInitializer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewCommandsInitializer(t mockConstructorTestingTNewCommandsInitializer) *CommandsInitializer { + mock := &CommandsInitializer{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mocks/configuration/ConfigurationHolder.go b/mocks/configuration/ConfigurationHolder.go deleted file mode 100644 index f6475bb1..00000000 --- a/mocks/configuration/ConfigurationHolder.go +++ /dev/null @@ -1,38 +0,0 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. - -package mocks - -import ( - mock "github.com/stretchr/testify/mock" - zap "go.uber.org/zap" -) - -// ConfigurationHolder is an autogenerated mock type for the ConfigurationHolder type -type ConfigurationHolder struct { - mock.Mock -} - -// Log provides a mock function with given fields: logger -func (_m *ConfigurationHolder) Log(logger *zap.Logger) { - _m.Called(logger) -} - -// Reload provides a mock function with given fields: -func (_m *ConfigurationHolder) Reload() { - _m.Called() -} - -type mockConstructorTestingTNewConfigurationHolder interface { - mock.TestingT - Cleanup(func()) -} - -// NewConfigurationHolder creates a new instance of ConfigurationHolder. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewConfigurationHolder(t mockConstructorTestingTNewConfigurationHolder) *ConfigurationHolder { - mock := &ConfigurationHolder{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/mocks/configuration/DatabaseConfiguration.go b/mocks/configuration/DatabaseConfiguration.go index a210f186..12eeb7f7 100644 --- a/mocks/configuration/DatabaseConfiguration.go +++ b/mocks/configuration/DatabaseConfiguration.go @@ -1,12 +1,13 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks import ( - time "time" - + configuration "github.com/ditrit/badaas/configuration" mock "github.com/stretchr/testify/mock" + time "time" + zap "go.uber.org/zap" ) @@ -29,6 +30,20 @@ func (_m *DatabaseConfiguration) GetDBName() string { return r0 } +// GetDialector provides a mock function with given fields: +func (_m *DatabaseConfiguration) GetDialector() configuration.DBDialector { + ret := _m.Called() + + var r0 configuration.DBDialector + if rf, ok := ret.Get(0).(func() configuration.DBDialector); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(configuration.DBDialector) + } + + return r0 +} + // GetHost provides a mock function with given fields: func (_m *DatabaseConfiguration) GetHost() string { ret := _m.Called() diff --git a/mocks/configuration/HTTPServerConfiguration.go b/mocks/configuration/HTTPServerConfiguration.go index 77cb41e5..ffb21116 100644 --- a/mocks/configuration/HTTPServerConfiguration.go +++ b/mocks/configuration/HTTPServerConfiguration.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks @@ -15,6 +15,20 @@ type HTTPServerConfiguration struct { mock.Mock } +// GetAddr provides a mock function with given fields: +func (_m *HTTPServerConfiguration) GetAddr() string { + ret := _m.Called() + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + // GetHost provides a mock function with given fields: func (_m *HTTPServerConfiguration) GetHost() string { ret := _m.Called() diff --git a/mocks/configuration/Holder.go b/mocks/configuration/Holder.go new file mode 100644 index 00000000..4dfc6188 --- /dev/null +++ b/mocks/configuration/Holder.go @@ -0,0 +1,38 @@ +// Code generated by mockery v2.20.0. DO NOT EDIT. + +package mocks + +import ( + mock "github.com/stretchr/testify/mock" + zap "go.uber.org/zap" +) + +// Holder is an autogenerated mock type for the Holder type +type Holder struct { + mock.Mock +} + +// Log provides a mock function with given fields: logger +func (_m *Holder) Log(logger *zap.Logger) { + _m.Called(logger) +} + +// Reload provides a mock function with given fields: +func (_m *Holder) Reload() { + _m.Called() +} + +type mockConstructorTestingTNewHolder interface { + mock.TestingT + Cleanup(func()) +} + +// NewHolder creates a new instance of Holder. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewHolder(t mockConstructorTestingTNewHolder) *Holder { + mock := &Holder{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mocks/configuration/InitializationConfiguration.go b/mocks/configuration/InitializationConfiguration.go index 58bdec3b..303c62b7 100644 --- a/mocks/configuration/InitializationConfiguration.go +++ b/mocks/configuration/InitializationConfiguration.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks diff --git a/mocks/configuration/KeySetter.go b/mocks/configuration/KeySetter.go new file mode 100644 index 00000000..e3db9efc --- /dev/null +++ b/mocks/configuration/KeySetter.go @@ -0,0 +1,44 @@ +// Code generated by mockery v2.20.0. DO NOT EDIT. + +package mocks + +import ( + configuration "github.com/ditrit/badaas/configuration" + mock "github.com/stretchr/testify/mock" + + verdeter "github.com/ditrit/verdeter" +) + +// KeySetter is an autogenerated mock type for the KeySetter type +type KeySetter struct { + mock.Mock +} + +// Set provides a mock function with given fields: config, key +func (_m *KeySetter) Set(config *verdeter.VerdeterCommand, key configuration.Key) error { + ret := _m.Called(config, key) + + var r0 error + if rf, ok := ret.Get(0).(func(*verdeter.VerdeterCommand, configuration.Key) error); ok { + r0 = rf(config, key) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +type mockConstructorTestingTNewKeySetter interface { + mock.TestingT + Cleanup(func()) +} + +// NewKeySetter creates a new instance of KeySetter. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewKeySetter(t mockConstructorTestingTNewKeySetter) *KeySetter { + mock := &KeySetter{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mocks/configuration/LoggerConfiguration.go b/mocks/configuration/LoggerConfiguration.go index 2115ec7e..88682c1c 100644 --- a/mocks/configuration/LoggerConfiguration.go +++ b/mocks/configuration/LoggerConfiguration.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks diff --git a/mocks/configuration/PaginationConfiguration.go b/mocks/configuration/PaginationConfiguration.go index 9146b875..7db54091 100644 --- a/mocks/configuration/PaginationConfiguration.go +++ b/mocks/configuration/PaginationConfiguration.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks diff --git a/mocks/configuration/SessionConfiguration.go b/mocks/configuration/SessionConfiguration.go index 2d5e9ee7..e107947d 100644 --- a/mocks/configuration/SessionConfiguration.go +++ b/mocks/configuration/SessionConfiguration.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks diff --git a/mocks/controllers/BasicAuthentificationController.go b/mocks/controllers/BasicAuthenticationController.go similarity index 53% rename from mocks/controllers/BasicAuthentificationController.go rename to mocks/controllers/BasicAuthenticationController.go index 19092bfa..5bf3c0b9 100644 --- a/mocks/controllers/BasicAuthentificationController.go +++ b/mocks/controllers/BasicAuthenticationController.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks @@ -9,16 +9,20 @@ import ( mock "github.com/stretchr/testify/mock" ) -// BasicAuthentificationController is an autogenerated mock type for the BasicAuthentificationController type -type BasicAuthentificationController struct { +// BasicAuthenticationController is an autogenerated mock type for the BasicAuthenticationController type +type BasicAuthenticationController struct { mock.Mock } // BasicLoginHandler provides a mock function with given fields: _a0, _a1 -func (_m *BasicAuthentificationController) BasicLoginHandler(_a0 http.ResponseWriter, _a1 *http.Request) (interface{}, httperrors.HTTPError) { +func (_m *BasicAuthenticationController) BasicLoginHandler(_a0 http.ResponseWriter, _a1 *http.Request) (interface{}, httperrors.HTTPError) { ret := _m.Called(_a0, _a1) var r0 interface{} + var r1 httperrors.HTTPError + if rf, ok := ret.Get(0).(func(http.ResponseWriter, *http.Request) (interface{}, httperrors.HTTPError)); ok { + return rf(_a0, _a1) + } if rf, ok := ret.Get(0).(func(http.ResponseWriter, *http.Request) interface{}); ok { r0 = rf(_a0, _a1) } else { @@ -27,7 +31,6 @@ func (_m *BasicAuthentificationController) BasicLoginHandler(_a0 http.ResponseWr } } - var r1 httperrors.HTTPError if rf, ok := ret.Get(1).(func(http.ResponseWriter, *http.Request) httperrors.HTTPError); ok { r1 = rf(_a0, _a1) } else { @@ -40,10 +43,14 @@ func (_m *BasicAuthentificationController) BasicLoginHandler(_a0 http.ResponseWr } // Logout provides a mock function with given fields: _a0, _a1 -func (_m *BasicAuthentificationController) Logout(_a0 http.ResponseWriter, _a1 *http.Request) (interface{}, httperrors.HTTPError) { +func (_m *BasicAuthenticationController) Logout(_a0 http.ResponseWriter, _a1 *http.Request) (interface{}, httperrors.HTTPError) { ret := _m.Called(_a0, _a1) var r0 interface{} + var r1 httperrors.HTTPError + if rf, ok := ret.Get(0).(func(http.ResponseWriter, *http.Request) (interface{}, httperrors.HTTPError)); ok { + return rf(_a0, _a1) + } if rf, ok := ret.Get(0).(func(http.ResponseWriter, *http.Request) interface{}); ok { r0 = rf(_a0, _a1) } else { @@ -52,7 +59,6 @@ func (_m *BasicAuthentificationController) Logout(_a0 http.ResponseWriter, _a1 * } } - var r1 httperrors.HTTPError if rf, ok := ret.Get(1).(func(http.ResponseWriter, *http.Request) httperrors.HTTPError); ok { r1 = rf(_a0, _a1) } else { @@ -64,14 +70,14 @@ func (_m *BasicAuthentificationController) Logout(_a0 http.ResponseWriter, _a1 * return r0, r1 } -type mockConstructorTestingTNewBasicAuthentificationController interface { +type mockConstructorTestingTNewBasicAuthenticationController interface { mock.TestingT Cleanup(func()) } -// NewBasicAuthentificationController creates a new instance of BasicAuthentificationController. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewBasicAuthentificationController(t mockConstructorTestingTNewBasicAuthentificationController) *BasicAuthentificationController { - mock := &BasicAuthentificationController{} +// NewBasicAuthenticationController creates a new instance of BasicAuthenticationController. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewBasicAuthenticationController(t mockConstructorTestingTNewBasicAuthenticationController) *BasicAuthenticationController { + mock := &BasicAuthenticationController{} mock.Mock.Test(t) t.Cleanup(func() { mock.AssertExpectations(t) }) diff --git a/mocks/controllers/CRUDController.go b/mocks/controllers/CRUDController.go new file mode 100644 index 00000000..22b41e2c --- /dev/null +++ b/mocks/controllers/CRUDController.go @@ -0,0 +1,86 @@ +// Code generated by mockery v2.20.0. DO NOT EDIT. + +package mocks + +import ( + http "net/http" + + httperrors "github.com/ditrit/badaas/httperrors" + mock "github.com/stretchr/testify/mock" +) + +// CRUDController is an autogenerated mock type for the CRUDController type +type CRUDController struct { + mock.Mock +} + +// GetModel provides a mock function with given fields: w, r +func (_m *CRUDController) GetModel(w http.ResponseWriter, r *http.Request) (interface{}, httperrors.HTTPError) { + ret := _m.Called(w, r) + + var r0 interface{} + var r1 httperrors.HTTPError + if rf, ok := ret.Get(0).(func(http.ResponseWriter, *http.Request) (interface{}, httperrors.HTTPError)); ok { + return rf(w, r) + } + if rf, ok := ret.Get(0).(func(http.ResponseWriter, *http.Request) interface{}); ok { + r0 = rf(w, r) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(interface{}) + } + } + + if rf, ok := ret.Get(1).(func(http.ResponseWriter, *http.Request) httperrors.HTTPError); ok { + r1 = rf(w, r) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(httperrors.HTTPError) + } + } + + return r0, r1 +} + +// GetModels provides a mock function with given fields: w, r +func (_m *CRUDController) GetModels(w http.ResponseWriter, r *http.Request) (interface{}, httperrors.HTTPError) { + ret := _m.Called(w, r) + + var r0 interface{} + var r1 httperrors.HTTPError + if rf, ok := ret.Get(0).(func(http.ResponseWriter, *http.Request) (interface{}, httperrors.HTTPError)); ok { + return rf(w, r) + } + if rf, ok := ret.Get(0).(func(http.ResponseWriter, *http.Request) interface{}); ok { + r0 = rf(w, r) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(interface{}) + } + } + + if rf, ok := ret.Get(1).(func(http.ResponseWriter, *http.Request) httperrors.HTTPError); ok { + r1 = rf(w, r) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(httperrors.HTTPError) + } + } + + return r0, r1 +} + +type mockConstructorTestingTNewCRUDController interface { + mock.TestingT + Cleanup(func()) +} + +// NewCRUDController creates a new instance of CRUDController. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewCRUDController(t mockConstructorTestingTNewCRUDController) *CRUDController { + mock := &CRUDController{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mocks/controllers/InformationController.go b/mocks/controllers/InformationController.go index 51eeffef..4a82f694 100644 --- a/mocks/controllers/InformationController.go +++ b/mocks/controllers/InformationController.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks @@ -19,6 +19,10 @@ func (_m *InformationController) Info(response http.ResponseWriter, r *http.Requ ret := _m.Called(response, r) var r0 interface{} + var r1 httperrors.HTTPError + if rf, ok := ret.Get(0).(func(http.ResponseWriter, *http.Request) (interface{}, httperrors.HTTPError)); ok { + return rf(response, r) + } if rf, ok := ret.Get(0).(func(http.ResponseWriter, *http.Request) interface{}); ok { r0 = rf(response, r) } else { @@ -27,7 +31,6 @@ func (_m *InformationController) Info(response http.ResponseWriter, r *http.Requ } } - var r1 httperrors.HTTPError if rf, ok := ret.Get(1).(func(http.ResponseWriter, *http.Request) httperrors.HTTPError); ok { r1 = rf(response, r) } else { diff --git a/mocks/httperrors/HTTPError.go b/mocks/httperrors/HTTPError.go index cbe71e52..3bae2ab3 100644 --- a/mocks/httperrors/HTTPError.go +++ b/mocks/httperrors/HTTPError.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks diff --git a/mocks/persistence/models/Tabler.go b/mocks/persistence/models/Tabler.go deleted file mode 100644 index cbc8e129..00000000 --- a/mocks/persistence/models/Tabler.go +++ /dev/null @@ -1,39 +0,0 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. - -package mocks - -import mock "github.com/stretchr/testify/mock" - -// Tabler is an autogenerated mock type for the Tabler type -type Tabler struct { - mock.Mock -} - -// TableName provides a mock function with given fields: -func (_m *Tabler) TableName() string { - ret := _m.Called() - - var r0 string - if rf, ok := ret.Get(0).(func() string); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(string) - } - - return r0 -} - -type mockConstructorTestingTNewTabler interface { - mock.TestingT - Cleanup(func()) -} - -// NewTabler creates a new instance of Tabler. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewTabler(t mockConstructorTestingTNewTabler) *Tabler { - mock := &Tabler{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/mocks/persistence/pagination/Paginator.go b/mocks/persistence/pagination/Paginator.go deleted file mode 100644 index dabbbf23..00000000 --- a/mocks/persistence/pagination/Paginator.go +++ /dev/null @@ -1,53 +0,0 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. - -package mocks - -import mock "github.com/stretchr/testify/mock" - -// Paginator is an autogenerated mock type for the Paginator type -type Paginator struct { - mock.Mock -} - -// Limit provides a mock function with given fields: -func (_m *Paginator) Limit() uint { - ret := _m.Called() - - var r0 uint - if rf, ok := ret.Get(0).(func() uint); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(uint) - } - - return r0 -} - -// Offset provides a mock function with given fields: -func (_m *Paginator) Offset() uint { - ret := _m.Called() - - var r0 uint - if rf, ok := ret.Get(0).(func() uint); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(uint) - } - - return r0 -} - -type mockConstructorTestingTNewPaginator interface { - mock.TestingT - Cleanup(func()) -} - -// NewPaginator creates a new instance of Paginator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewPaginator(t mockConstructorTestingTNewPaginator) *Paginator { - mock := &Paginator{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/mocks/persistence/repository/CRUDRepository.go b/mocks/persistence/repository/CRUDRepository.go deleted file mode 100644 index 31f127b1..00000000 --- a/mocks/persistence/repository/CRUDRepository.go +++ /dev/null @@ -1,207 +0,0 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. - -package mocks - -import ( - httperrors "github.com/ditrit/badaas/httperrors" - mock "github.com/stretchr/testify/mock" - - models "github.com/ditrit/badaas/persistence/models" - - pagination "github.com/ditrit/badaas/persistence/pagination" - - repository "github.com/ditrit/badaas/persistence/repository" - - squirrel "github.com/Masterminds/squirrel" -) - -// CRUDRepository is an autogenerated mock type for the CRUDRepository type -type CRUDRepository[T models.Tabler, ID interface{}] struct { - mock.Mock -} - -// Count provides a mock function with given fields: _a0 -func (_m *CRUDRepository[T, ID]) Count(_a0 squirrel.Sqlizer) (uint, httperrors.HTTPError) { - ret := _m.Called(_a0) - - var r0 uint - if rf, ok := ret.Get(0).(func(squirrel.Sqlizer) uint); ok { - r0 = rf(_a0) - } else { - r0 = ret.Get(0).(uint) - } - - var r1 httperrors.HTTPError - if rf, ok := ret.Get(1).(func(squirrel.Sqlizer) httperrors.HTTPError); ok { - r1 = rf(_a0) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(httperrors.HTTPError) - } - } - - return r0, r1 -} - -// Create provides a mock function with given fields: _a0 -func (_m *CRUDRepository[T, ID]) Create(_a0 *T) httperrors.HTTPError { - ret := _m.Called(_a0) - - var r0 httperrors.HTTPError - if rf, ok := ret.Get(0).(func(*T) httperrors.HTTPError); ok { - r0 = rf(_a0) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(httperrors.HTTPError) - } - } - - return r0 -} - -// Delete provides a mock function with given fields: _a0 -func (_m *CRUDRepository[T, ID]) Delete(_a0 *T) httperrors.HTTPError { - ret := _m.Called(_a0) - - var r0 httperrors.HTTPError - if rf, ok := ret.Get(0).(func(*T) httperrors.HTTPError); ok { - r0 = rf(_a0) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(httperrors.HTTPError) - } - } - - return r0 -} - -// Find provides a mock function with given fields: _a0, _a1, _a2 -func (_m *CRUDRepository[T, ID]) Find(_a0 squirrel.Sqlizer, _a1 pagination.Paginator, _a2 repository.SortOption) (*pagination.Page[T], httperrors.HTTPError) { - ret := _m.Called(_a0, _a1, _a2) - - var r0 *pagination.Page[T] - if rf, ok := ret.Get(0).(func(squirrel.Sqlizer, pagination.Paginator, repository.SortOption) *pagination.Page[T]); ok { - r0 = rf(_a0, _a1, _a2) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*pagination.Page[T]) - } - } - - var r1 httperrors.HTTPError - if rf, ok := ret.Get(1).(func(squirrel.Sqlizer, pagination.Paginator, repository.SortOption) httperrors.HTTPError); ok { - r1 = rf(_a0, _a1, _a2) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(httperrors.HTTPError) - } - } - - return r0, r1 -} - -// GetAll provides a mock function with given fields: _a0 -func (_m *CRUDRepository[T, ID]) GetAll(_a0 repository.SortOption) ([]*T, httperrors.HTTPError) { - ret := _m.Called(_a0) - - var r0 []*T - if rf, ok := ret.Get(0).(func(repository.SortOption) []*T); ok { - r0 = rf(_a0) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]*T) - } - } - - var r1 httperrors.HTTPError - if rf, ok := ret.Get(1).(func(repository.SortOption) httperrors.HTTPError); ok { - r1 = rf(_a0) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(httperrors.HTTPError) - } - } - - return r0, r1 -} - -// GetByID provides a mock function with given fields: _a0 -func (_m *CRUDRepository[T, ID]) GetByID(_a0 ID) (*T, httperrors.HTTPError) { - ret := _m.Called(_a0) - - var r0 *T - if rf, ok := ret.Get(0).(func(ID) *T); ok { - r0 = rf(_a0) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*T) - } - } - - var r1 httperrors.HTTPError - if rf, ok := ret.Get(1).(func(ID) httperrors.HTTPError); ok { - r1 = rf(_a0) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(httperrors.HTTPError) - } - } - - return r0, r1 -} - -// Save provides a mock function with given fields: _a0 -func (_m *CRUDRepository[T, ID]) Save(_a0 *T) httperrors.HTTPError { - ret := _m.Called(_a0) - - var r0 httperrors.HTTPError - if rf, ok := ret.Get(0).(func(*T) httperrors.HTTPError); ok { - r0 = rf(_a0) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(httperrors.HTTPError) - } - } - - return r0 -} - -// Transaction provides a mock function with given fields: fn -func (_m *CRUDRepository[T, ID]) Transaction(fn func(repository.CRUDRepository[T, ID]) (interface{}, error)) (interface{}, httperrors.HTTPError) { - ret := _m.Called(fn) - - var r0 interface{} - if rf, ok := ret.Get(0).(func(func(repository.CRUDRepository[T, ID]) (interface{}, error)) interface{}); ok { - r0 = rf(fn) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(interface{}) - } - } - - var r1 httperrors.HTTPError - if rf, ok := ret.Get(1).(func(func(repository.CRUDRepository[T, ID]) (interface{}, error)) httperrors.HTTPError); ok { - r1 = rf(fn) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(httperrors.HTTPError) - } - } - - return r0, r1 -} - -type mockConstructorTestingTNewCRUDRepository interface { - mock.TestingT - Cleanup(func()) -} - -// NewCRUDRepository creates a new instance of CRUDRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewCRUDRepository[T models.Tabler, ID interface{}](t mockConstructorTestingTNewCRUDRepository) *CRUDRepository[T, ID] { - mock := &CRUDRepository[T, ID]{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/mocks/persistence/repository/SortOption.go b/mocks/persistence/repository/SortOption.go deleted file mode 100644 index d2e10e83..00000000 --- a/mocks/persistence/repository/SortOption.go +++ /dev/null @@ -1,53 +0,0 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. - -package mocks - -import mock "github.com/stretchr/testify/mock" - -// SortOption is an autogenerated mock type for the SortOption type -type SortOption struct { - mock.Mock -} - -// Column provides a mock function with given fields: -func (_m *SortOption) Column() string { - ret := _m.Called() - - var r0 string - if rf, ok := ret.Get(0).(func() string); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(string) - } - - return r0 -} - -// Desc provides a mock function with given fields: -func (_m *SortOption) Desc() bool { - ret := _m.Called() - - var r0 bool - if rf, ok := ret.Get(0).(func() bool); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(bool) - } - - return r0 -} - -type mockConstructorTestingTNewSortOption interface { - mock.TestingT - Cleanup(func()) -} - -// NewSortOption creates a new instance of SortOption. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewSortOption(t mockConstructorTestingTNewSortOption) *SortOption { - mock := &SortOption{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/mocks/router/middlewares/AuthenticationMiddleware.go b/mocks/router/middlewares/AuthenticationMiddleware.go index a0a4053f..77b8fe2d 100644 --- a/mocks/router/middlewares/AuthenticationMiddleware.go +++ b/mocks/router/middlewares/AuthenticationMiddleware.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks diff --git a/mocks/router/middlewares/JSONController.go b/mocks/router/middlewares/JSONController.go index b1ad06cb..5fe7aa5a 100644 --- a/mocks/router/middlewares/JSONController.go +++ b/mocks/router/middlewares/JSONController.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks diff --git a/mocks/router/middlewares/JSONHandler.go b/mocks/router/middlewares/JSONHandler.go index 98dd567b..37a072cb 100644 --- a/mocks/router/middlewares/JSONHandler.go +++ b/mocks/router/middlewares/JSONHandler.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks @@ -20,6 +20,10 @@ func (_m *JSONHandler) Execute(w http.ResponseWriter, r *http.Request) (interfac ret := _m.Called(w, r) var r0 interface{} + var r1 httperrors.HTTPError + if rf, ok := ret.Get(0).(func(http.ResponseWriter, *http.Request) (interface{}, httperrors.HTTPError)); ok { + return rf(w, r) + } if rf, ok := ret.Get(0).(func(http.ResponseWriter, *http.Request) interface{}); ok { r0 = rf(w, r) } else { @@ -28,7 +32,6 @@ func (_m *JSONHandler) Execute(w http.ResponseWriter, r *http.Request) (interfac } } - var r1 httperrors.HTTPError if rf, ok := ret.Get(1).(func(http.ResponseWriter, *http.Request) httperrors.HTTPError); ok { r1 = rf(w, r) } else { diff --git a/mocks/router/middlewares/MiddlewareLogger.go b/mocks/router/middlewares/MiddlewareLogger.go index 671b8ca8..f18920a8 100644 --- a/mocks/router/middlewares/MiddlewareLogger.go +++ b/mocks/router/middlewares/MiddlewareLogger.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks diff --git a/mocks/services/sessionservice/SessionService.go b/mocks/services/sessionservice/SessionService.go index 22b49694..9b3ce411 100644 --- a/mocks/services/sessionservice/SessionService.go +++ b/mocks/services/sessionservice/SessionService.go @@ -1,18 +1,16 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks import ( - http "net/http" - + badorm "github.com/ditrit/badaas/badorm" httperrors "github.com/ditrit/badaas/httperrors" + mock "github.com/stretchr/testify/mock" models "github.com/ditrit/badaas/persistence/models" sessionservice "github.com/ditrit/badaas/services/sessionservice" - - uuid "github.com/google/uuid" ) // SessionService is an autogenerated mock type for the SessionService type @@ -21,18 +19,21 @@ type SessionService struct { } // IsValid provides a mock function with given fields: sessionUUID -func (_m *SessionService) IsValid(sessionUUID uuid.UUID) (bool, *sessionservice.SessionClaims) { +func (_m *SessionService) IsValid(sessionUUID badorm.UUID) (bool, *sessionservice.SessionClaims) { ret := _m.Called(sessionUUID) var r0 bool - if rf, ok := ret.Get(0).(func(uuid.UUID) bool); ok { + var r1 *sessionservice.SessionClaims + if rf, ok := ret.Get(0).(func(badorm.UUID) (bool, *sessionservice.SessionClaims)); ok { + return rf(sessionUUID) + } + if rf, ok := ret.Get(0).(func(badorm.UUID) bool); ok { r0 = rf(sessionUUID) } else { r0 = ret.Get(0).(bool) } - var r1 *sessionservice.SessionClaims - if rf, ok := ret.Get(1).(func(uuid.UUID) *sessionservice.SessionClaims); ok { + if rf, ok := ret.Get(1).(func(badorm.UUID) *sessionservice.SessionClaims); ok { r1 = rf(sessionUUID) } else { if ret.Get(1) != nil { @@ -43,29 +44,39 @@ func (_m *SessionService) IsValid(sessionUUID uuid.UUID) (bool, *sessionservice. return r0, r1 } -// LogUserIn provides a mock function with given fields: user, response -func (_m *SessionService) LogUserIn(user *models.User, response http.ResponseWriter) httperrors.HTTPError { - ret := _m.Called(user, response) +// LogUserIn provides a mock function with given fields: user +func (_m *SessionService) LogUserIn(user *models.User) (*models.Session, error) { + ret := _m.Called(user) - var r0 httperrors.HTTPError - if rf, ok := ret.Get(0).(func(*models.User, http.ResponseWriter) httperrors.HTTPError); ok { - r0 = rf(user, response) + var r0 *models.Session + var r1 error + if rf, ok := ret.Get(0).(func(*models.User) (*models.Session, error)); ok { + return rf(user) + } + if rf, ok := ret.Get(0).(func(*models.User) *models.Session); ok { + r0 = rf(user) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(httperrors.HTTPError) + r0 = ret.Get(0).(*models.Session) } } - return r0 + if rf, ok := ret.Get(1).(func(*models.User) error); ok { + r1 = rf(user) + } else { + r1 = ret.Error(1) + } + + return r0, r1 } -// LogUserOut provides a mock function with given fields: sessionClaims, response -func (_m *SessionService) LogUserOut(sessionClaims *sessionservice.SessionClaims, response http.ResponseWriter) httperrors.HTTPError { - ret := _m.Called(sessionClaims, response) +// LogUserOut provides a mock function with given fields: sessionClaims +func (_m *SessionService) LogUserOut(sessionClaims *sessionservice.SessionClaims) httperrors.HTTPError { + ret := _m.Called(sessionClaims) var r0 httperrors.HTTPError - if rf, ok := ret.Get(0).(func(*sessionservice.SessionClaims, http.ResponseWriter) httperrors.HTTPError); ok { - r0 = rf(sessionClaims, response) + if rf, ok := ret.Get(0).(func(*sessionservice.SessionClaims) httperrors.HTTPError); ok { + r0 = rf(sessionClaims) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(httperrors.HTTPError) @@ -76,11 +87,11 @@ func (_m *SessionService) LogUserOut(sessionClaims *sessionservice.SessionClaims } // RollSession provides a mock function with given fields: _a0 -func (_m *SessionService) RollSession(_a0 uuid.UUID) httperrors.HTTPError { +func (_m *SessionService) RollSession(_a0 badorm.UUID) httperrors.HTTPError { ret := _m.Called(_a0) var r0 httperrors.HTTPError - if rf, ok := ret.Get(0).(func(uuid.UUID) httperrors.HTTPError); ok { + if rf, ok := ret.Get(0).(func(badorm.UUID) httperrors.HTTPError); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { diff --git a/mocks/services/userservice/UserService.go b/mocks/services/userservice/UserService.go index 6ce9b7fa..6ecc1e6b 100644 --- a/mocks/services/userservice/UserService.go +++ b/mocks/services/userservice/UserService.go @@ -1,11 +1,9 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks import ( - httperrors "github.com/ditrit/badaas/httperrors" dto "github.com/ditrit/badaas/persistence/models/dto" - mock "github.com/stretchr/testify/mock" models "github.com/ditrit/badaas/persistence/models" @@ -17,10 +15,14 @@ type UserService struct { } // GetUser provides a mock function with given fields: _a0 -func (_m *UserService) GetUser(_a0 dto.UserLoginDTO) (*models.User, httperrors.HTTPError) { +func (_m *UserService) GetUser(_a0 dto.UserLoginDTO) (*models.User, error) { ret := _m.Called(_a0) var r0 *models.User + var r1 error + if rf, ok := ret.Get(0).(func(dto.UserLoginDTO) (*models.User, error)); ok { + return rf(_a0) + } if rf, ok := ret.Get(0).(func(dto.UserLoginDTO) *models.User); ok { r0 = rf(_a0) } else { @@ -29,13 +31,10 @@ func (_m *UserService) GetUser(_a0 dto.UserLoginDTO) (*models.User, httperrors.H } } - var r1 httperrors.HTTPError - if rf, ok := ret.Get(1).(func(dto.UserLoginDTO) httperrors.HTTPError); ok { + if rf, ok := ret.Get(1).(func(dto.UserLoginDTO) error); ok { r1 = rf(_a0) } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(httperrors.HTTPError) - } + r1 = ret.Error(1) } return r0, r1 @@ -46,6 +45,10 @@ func (_m *UserService) NewUser(username string, email string, password string) ( ret := _m.Called(username, email, password) var r0 *models.User + var r1 error + if rf, ok := ret.Get(0).(func(string, string, string) (*models.User, error)); ok { + return rf(username, email, password) + } if rf, ok := ret.Get(0).(func(string, string, string) *models.User); ok { r0 = rf(username, email, password) } else { @@ -54,7 +57,6 @@ func (_m *UserService) NewUser(username string, email string, password string) ( } } - var r1 error if rf, ok := ret.Get(1).(func(string, string, string) error); ok { r1 = rf(username, email, password) } else { diff --git a/persistence/ModuleFx.go b/persistence/ModuleFx.go index d99d1c02..a682f2cb 100644 --- a/persistence/ModuleFx.go +++ b/persistence/ModuleFx.go @@ -1,11 +1,10 @@ package persistence import ( - "github.com/ditrit/badaas/persistence/gormdatabase" - "github.com/ditrit/badaas/persistence/models" - "github.com/ditrit/badaas/persistence/repository" - "github.com/google/uuid" "go.uber.org/fx" + + "github.com/ditrit/badaas/badorm" + "github.com/ditrit/badaas/persistence/gormdatabase" ) // PersistanceModule for fx @@ -13,14 +12,11 @@ import ( // Provides: // // - The database connection -// -// - The repositories +// - BaDORM var PersistanceModule = fx.Module( "persistence", // Database connection - fx.Provide(gormdatabase.CreateDatabaseConnectionFromConfiguration), - - //repositories - fx.Provide(repository.NewCRUDRepository[models.Session, uuid.UUID]), - fx.Provide(repository.NewCRUDRepository[models.User, uuid.UUID]), + fx.Provide(gormdatabase.SetupDatabaseConnection), + // activate BaDORM + badorm.BaDORMModule, ) diff --git a/persistence/gormdatabase/db.go b/persistence/gormdatabase/db.go index ddd5a22b..2e08f275 100644 --- a/persistence/gormdatabase/db.go +++ b/persistence/gormdatabase/db.go @@ -2,97 +2,64 @@ package gormdatabase import ( "fmt" - "time" - "github.com/ditrit/badaas/configuration" - "github.com/ditrit/badaas/persistence/gormdatabase/gormzap" - "github.com/ditrit/badaas/persistence/models" "go.uber.org/zap" - "gorm.io/driver/postgres" "gorm.io/gorm" -) -// Create the dsn string from the configuration -func createDsnFromConf(databaseConfiguration configuration.DatabaseConfiguration) string { - dsn := createDsn( - databaseConfiguration.GetHost(), - databaseConfiguration.GetUsername(), - databaseConfiguration.GetPassword(), - databaseConfiguration.GetSSLMode(), - databaseConfiguration.GetDBName(), - databaseConfiguration.GetPort(), - ) - return dsn -} - -// Create the dsn strings with the provided args -func createDsn(host, username, password, sslmode, dbname string, port int) string { - return fmt.Sprintf("user=%s password=%s host=%s port=%d sslmode=%s dbname=%s", - username, password, host, port, sslmode, dbname, - ) -} + "github.com/ditrit/badaas/badorm" + "github.com/ditrit/badaas/configuration" +) -// Initialize the database with using the database configuration -func CreateDatabaseConnectionFromConfiguration(logger *zap.Logger, databaseConfiguration configuration.DatabaseConfiguration) (*gorm.DB, error) { - dsn := createDsnFromConf(databaseConfiguration) - var err error - var database *gorm.DB - for numberRetry := uint(0); numberRetry < databaseConfiguration.GetRetry(); numberRetry++ { - database, err = initializeDBFromDsn(dsn, logger) - if err == nil { - logger.Sugar().Debugf("Database connection is active") - err = AutoMigrate(logger, database) - if err != nil { - logger.Error("migration failed") - return nil, err - } - logger.Info("AutoMigration was executed successfully") - return database, err - } - logger.Sugar().Debugf("Database connection failed with error %q", err.Error()) - logger.Sugar().Debugf("Retrying database connection %d/%d in %s", - numberRetry+1, databaseConfiguration.GetRetry(), databaseConfiguration.GetRetryTime().String()) - time.Sleep(databaseConfiguration.GetRetryTime()) +func createDialectorFromConf(databaseConfiguration configuration.DatabaseConfiguration) (gorm.Dialector, error) { + switch databaseConfiguration.GetDialector() { + case configuration.PostgreSQL: + return badorm.CreatePostgreSQLDialector( + databaseConfiguration.GetHost(), + databaseConfiguration.GetUsername(), + databaseConfiguration.GetPassword(), + databaseConfiguration.GetSSLMode(), + databaseConfiguration.GetDBName(), + databaseConfiguration.GetPort(), + ), nil + case configuration.MySQL: + return badorm.CreateMySQLDialector( + databaseConfiguration.GetHost(), + databaseConfiguration.GetUsername(), + databaseConfiguration.GetPassword(), + databaseConfiguration.GetDBName(), + databaseConfiguration.GetPort(), + ), nil + case configuration.SQLite: + return badorm.CreateSQLiteDialector( + databaseConfiguration.GetHost(), + ), nil + case configuration.SQLServer: + return badorm.CreateSQLServerDialector( + databaseConfiguration.GetHost(), + databaseConfiguration.GetUsername(), + databaseConfiguration.GetPassword(), + databaseConfiguration.GetDBName(), + databaseConfiguration.GetPort(), + ), nil + default: + return nil, fmt.Errorf("unknown dialector: %s", databaseConfiguration.GetDialector()) } - return nil, err } -// Initialize the database with the dsn string -func initializeDBFromDsn(dsn string, logger *zap.Logger) (*gorm.DB, error) { - database, err := gorm.Open(postgres.Open(dsn), &gorm.Config{ - Logger: gormzap.New(logger), - }) - +// Creates the database object with using the database configuration and exec the setup +func SetupDatabaseConnection( + logger *zap.Logger, + databaseConfiguration configuration.DatabaseConfiguration, +) (*gorm.DB, error) { + dialector, err := createDialectorFromConf(databaseConfiguration) if err != nil { return nil, err } - rawDatabase, err := database.DB() - if err != nil { - return nil, err - } - // ping the underlying database - err = rawDatabase.Ping() - if err != nil { - return nil, err - } - return database, nil -} - -// Migrate the database using gorm [https://gorm.io/docs/migration.html#Auto-Migration] -func autoMigrate(database *gorm.DB, listOfDatabaseTables []any) error { - err := database.AutoMigrate(listOfDatabaseTables...) - if err != nil { - return err - } - return nil -} - -// Run the automigration -func AutoMigrate(logger *zap.Logger, database *gorm.DB) error { - err := autoMigrate(database, models.ListOfTables) - if err != nil { - return err - } - return nil + return badorm.ConnectToDialector( + logger, + dialector, + databaseConfiguration.GetRetry(), + databaseConfiguration.GetRetryTime(), + ) } diff --git a/persistence/gormdatabase/db_test.go b/persistence/gormdatabase/db_test.go deleted file mode 100644 index 05eef996..00000000 --- a/persistence/gormdatabase/db_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package gormdatabase - -import ( - "testing" - - configurationmocks "github.com/ditrit/badaas/mocks/configuration" - "github.com/stretchr/testify/assert" -) - -func TestCreateDsnFromconf(t *testing.T) { - conf := configurationmocks.NewDatabaseConfiguration(t) - conf.On("GetPort").Return(1225) - conf.On("GetHost").Return("192.168.2.5") - conf.On("GetDBName").Return("badaas_db") - conf.On("GetUsername").Return("username") - conf.On("GetPassword").Return("password") - conf.On("GetSSLMode").Return("disable") - assert.Equal(t, "user=username password=password host=192.168.2.5 port=1225 sslmode=disable dbname=badaas_db", - createDsnFromConf(conf)) -} - -func TestCreateDsn(t *testing.T) { - assert.Equal(t, - "user=username password=password host=192.168.2.5 port=1225 sslmode=disable dbname=badaas_db", - createDsn( - "192.168.2.5", - "username", - "password", - "disable", - "badaas_db", - 1225, - ), - "no dsn should be empty", - ) -} diff --git a/persistence/gormdatabase/gormzap/gormzap.go b/persistence/gormdatabase/gormzap/gormzap.go index 0f7b9563..f2c02103 100644 --- a/persistence/gormdatabase/gormzap/gormzap.go +++ b/persistence/gormdatabase/gormzap/gormzap.go @@ -13,6 +13,8 @@ import ( gormlogger "gorm.io/gorm/logger" ) +const SlowThreshold = 100 * time.Millisecond + // This type implement the [gorm.io/gorm/logger.Interface] interface. // It is to be used as a replacement for the original logger type Logger struct { @@ -28,7 +30,7 @@ func New(zapLogger *zap.Logger) gormlogger.Interface { return Logger{ ZapLogger: zapLogger, LogLevel: gormlogger.Info, - SlowThreshold: 100 * time.Millisecond, + SlowThreshold: SlowThreshold, SkipCallerLookup: true, IgnoreRecordNotFoundError: true, } @@ -55,6 +57,7 @@ func (l Logger) Info(_ context.Context, str string, args ...interface{}) { if l.LogLevel < gormlogger.Info { return } + l.logger().Sugar().Debugf(str, args...) } @@ -63,6 +66,7 @@ func (l Logger) Warn(_ context.Context, str string, args ...interface{}) { if l.LogLevel < gormlogger.Warn { return } + l.logger().Sugar().Warnf(str, args...) } @@ -71,6 +75,7 @@ func (l Logger) Error(_ context.Context, str string, args ...interface{}) { if l.LogLevel < gormlogger.Error { return } + l.logger().Sugar().Errorf(str, args...) } @@ -82,6 +87,7 @@ func (l Logger) Trace(_ context.Context, begin time.Time, fc func() (string, int elapsed := time.Since(begin) sql, rows := fc() + switch { case err != nil && l.LogLevel >= gormlogger.Error && (!l.IgnoreRecordNotFoundError || !errors.Is(err, gorm.ErrRecordNotFound)): l.logger().Error("trace", zap.Error(err), zap.Duration("elapsed", elapsed), zap.Int64("rows", rows), zap.String("sql", sql)) @@ -101,14 +107,11 @@ var ( func (l Logger) logger() *zap.Logger { for i := 2; i < 15; i++ { _, file, _, ok := runtime.Caller(i) - switch { - case !ok: - case strings.HasSuffix(file, "_test.go"): - case strings.Contains(file, gormPackage): - case strings.Contains(file, zapgormPackage): - default: + + if ok && !strings.HasSuffix(file, "_test.go") && !strings.Contains(file, gormPackage) && !strings.Contains(file, zapgormPackage) { return l.ZapLogger.WithOptions(zap.AddCallerSkip(i)) } } + return l.ZapLogger } diff --git a/persistence/gormdatabase/helpers.go b/persistence/gormdatabase/helpers.go deleted file mode 100644 index c71650a5..00000000 --- a/persistence/gormdatabase/helpers.go +++ /dev/null @@ -1,17 +0,0 @@ -package gormdatabase - -import "github.com/jackc/pgconn" - -func IsDuplicateKeyError(err error) bool { - // unique_violation code is equals to 23505 - return isPostgresError(err, "23505") -} - -func isPostgresError(err error, errCode string) bool { - postgresError, ok := err.(*pgconn.PgError) - if ok { - return postgresError.Code == errCode - - } - return false -} diff --git a/persistence/gormdatabase/helpers_test.go b/persistence/gormdatabase/helpers_test.go deleted file mode 100644 index 59cb2d6e..00000000 --- a/persistence/gormdatabase/helpers_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package gormdatabase - -import ( - "errors" - "testing" - - "github.com/jackc/pgconn" - "github.com/stretchr/testify/assert" -) - -func TestIsDuplicateError(t *testing.T) { - assert.False(t, IsDuplicateKeyError(errors.New("voila"))) - assert.False(t, IsDuplicateKeyError(&pgconn.PgError{ - Code: "235252551514", - })) - assert.True(t, IsDuplicateKeyError(&pgconn.PgError{ - Code: "23505", - })) -} - -func Test_isPostgresError(t *testing.T) { - var postgresErrorAsError error = &pgconn.PgError{Code: "1234"} - assert.True(t, isPostgresError( - postgresErrorAsError, - "1234", - )) - postgresErrorAsError = &pgconn.PgError{Code: ""} - assert.False(t, isPostgresError( - postgresErrorAsError, - "1234", - )) - assert.False(t, isPostgresError(errors.New("a classic error"), "1234")) -} diff --git a/persistence/models/BaseModel.go b/persistence/models/BaseModel.go deleted file mode 100644 index acf3c2e0..00000000 --- a/persistence/models/BaseModel.go +++ /dev/null @@ -1,19 +0,0 @@ -package models - -import ( - "time" - - "github.com/google/uuid" - "gorm.io/gorm" -) - -// Base Model for gorm -// -// Every model intended to be saved in the database must embed this BaseModel -// reference: https://gorm.io/docs/models.html#gorm-Model -type BaseModel struct { - ID uuid.UUID `gorm:"type:uuid;default:uuid_generate_v4()"` - CreatedAt time.Time - UpdatedAt time.Time - DeletedAt gorm.DeletedAt `gorm:"index"` -} diff --git a/persistence/models/ProductInfo.go b/persistence/models/ProductInfo.go deleted file mode 100644 index 42040ce5..00000000 --- a/persistence/models/ProductInfo.go +++ /dev/null @@ -1,7 +0,0 @@ -package models - -// Describe the current BADAAS instance -type BadaasServerInfo struct { - Status string `json:"status"` - Version string `json:"version"` -} diff --git a/persistence/models/Session.go b/persistence/models/Session.go index 89bdb1b7..2ca84797 100644 --- a/persistence/models/Session.go +++ b/persistence/models/Session.go @@ -3,14 +3,22 @@ package models import ( "time" - "github.com/google/uuid" + "github.com/ditrit/badaas/badorm" ) // Represent a user session type Session struct { - BaseModel - UserID uuid.UUID `gorm:"not null"` - ExpiresAt time.Time `gorm:"not null"` + badorm.UUIDModel + UserID badorm.UUID `gorm:"not null"` + ExpiresAt time.Time `gorm:"not null"` +} + +// Create a new session +func NewSession(userID badorm.UUID, sessionDuration time.Duration) *Session { + return &Session{ + UserID: userID, + ExpiresAt: time.Now().Add(sessionDuration), + } } // Return true is expired @@ -22,10 +30,3 @@ func (session *Session) IsExpired() bool { func (session *Session) CanBeRolled(rollInterval time.Duration) bool { return time.Now().After(session.ExpiresAt.Add(-rollInterval)) } - -// Return the pluralized table name -// -// Satisfie the Tabler interface -func (Session) TableName() string { - return "sessions" -} diff --git a/persistence/models/Session_test.go b/persistence/models/Session_test.go index a1fdd240..4494b47b 100644 --- a/persistence/models/Session_test.go +++ b/persistence/models/Session_test.go @@ -4,8 +4,9 @@ import ( "testing" "time" - "github.com/ditrit/badaas/persistence/models" "github.com/stretchr/testify/assert" + + "github.com/ditrit/badaas/persistence/models" ) func TestExpired(t *testing.T) { @@ -26,7 +27,3 @@ func TestCanBeRolled(t *testing.T) { time.Sleep(400 * time.Millisecond) assert.True(t, sessionInstance.CanBeRolled(sessionDuration)) } - -func TestTableName(t *testing.T) { - assert.Equal(t, "sessions", models.Session{}.TableName()) -} diff --git a/persistence/models/Tabler.go b/persistence/models/Tabler.go deleted file mode 100644 index eac89ab8..00000000 --- a/persistence/models/Tabler.go +++ /dev/null @@ -1,12 +0,0 @@ -package models - -var ListOfTables = []any{ - User{}, - Session{}, -} - -// The interface "type" need to implement to be considered models -type Tabler interface { - // pluralized name - TableName() string -} diff --git a/persistence/models/User.go b/persistence/models/User.go index d65aa425..3fae54f4 100644 --- a/persistence/models/User.go +++ b/persistence/models/User.go @@ -1,8 +1,10 @@ package models +import "github.com/ditrit/badaas/badorm" + // Represents a user type User struct { - BaseModel + badorm.UUIDModel Username string `gorm:"not null"` Email string `gorm:"unique;not null"` @@ -10,9 +12,11 @@ type User struct { Password []byte `gorm:"not null"` } -// Return the pluralized table name -// -// Satisfie the Tabler interface -func (User) TableName() string { - return "users" +func UserEmailCondition(op badorm.Operator[string]) badorm.WhereCondition[User] { + return badorm.FieldCondition[User, string]{ + Operator: op, + FieldIdentifier: badorm.FieldIdentifier[string]{ + Field: "Email", + }, + } } diff --git a/persistence/models/dto/HTTPError.go b/persistence/models/dto/HTTPError.go index 0e352145..e182eadd 100644 --- a/persistence/models/dto/HTTPError.go +++ b/persistence/models/dto/HTTPError.go @@ -1,9 +1,9 @@ package dto -// Data Transfert Object Package +// Data Transfer Object Package // Describe the HTTP Error payload -type DTOHTTPError struct { +type HTTPError struct { Error string `json:"err"` Message string `json:"msg"` Status string `json:"status"` diff --git a/persistence/models/dto/LoginSuccess.go b/persistence/models/dto/LoginSuccess.go index 3601e975..87492bad 100644 --- a/persistence/models/dto/LoginSuccess.go +++ b/persistence/models/dto/LoginSuccess.go @@ -1,8 +1,8 @@ package dto -// DTOLoginSuccess is a dto returned to the client when the authentication is successful. -type DTOLoginSuccess struct { +// LoginSuccess is a dto returned to the client when the authentication is successful. +type LoginSuccess struct { Email string `json:"email"` ID string `json:"id"` Username string `json:"username"` -} \ No newline at end of file +} diff --git a/persistence/models/dto/ProductInfo.go b/persistence/models/dto/ProductInfo.go deleted file mode 100644 index 988650ea..00000000 --- a/persistence/models/dto/ProductInfo.go +++ /dev/null @@ -1,9 +0,0 @@ -package dto - -// Data Transfert Object Package - -// Describe the Server Info payload -type DTOBadaasServerInfo struct { - Status string `json:"status"` - Version string `json:"version"` -} diff --git a/persistence/pagination/Page.go b/persistence/pagination/Page.go deleted file mode 100644 index 2d76a050..00000000 --- a/persistence/pagination/Page.go +++ /dev/null @@ -1,40 +0,0 @@ -package pagination - -import "github.com/ditrit/badaas/persistence/models" - -// A page hold ressources and data regarding the pagination -type Page[T models.Tabler] struct { - Ressources []*T `json:"ressources"` - // max d'element par page - Limit uint `json:"limit"` - // page courante - Offset uint `json:"offset"` - // total d'element en base - Total uint `json:"total"` - // total de pages - TotalPages uint `json:"totalPages"` - HasNextPage bool `json:"hasNextpage"` - HasPreviousPage bool `json:"hasPreviousPage"` - IsFirstPage bool `json:"isFirstPage"` - IsLastPage bool `json:"isLastPage"` - HasContent bool `json:"hasContent"` -} - -// Create a new page -func NewPage[T models.Tabler](records []*T, offset, size, nbElemTotal uint) *Page[T] { - nbPage := nbElemTotal / size - p := Page[T]{ - Ressources: records, - Limit: size, - Offset: offset, - Total: nbElemTotal, - TotalPages: nbPage, - - HasNextPage: nbElemTotal > (offset+1)*size, - HasPreviousPage: offset >= 1, - IsFirstPage: offset == 0, - IsLastPage: offset == (nbPage - 1), - HasContent: len(records) != 0, - } - return &p -} diff --git a/persistence/pagination/Page_test.go b/persistence/pagination/Page_test.go deleted file mode 100644 index 999e5201..00000000 --- a/persistence/pagination/Page_test.go +++ /dev/null @@ -1,145 +0,0 @@ -package pagination_test - -import ( - "testing" - - "github.com/ditrit/badaas/persistence/pagination" - "github.com/stretchr/testify/assert" -) - -type Whatever struct { - DumbData int -} - -func (Whatever) TableName() string { - return "whatevers" -} - -var ( - // test fixture - ressources = []*Whatever{ - {10}, - {11}, - {12}, - {13}, - {14}, - {15}, - {16}, - {17}, - {18}, - {19}, - } -) - -func TestNewPage(t *testing.T) { - p := pagination.NewPage( - ressources, - 1, // page 1 - 10, // 10 elems per page - 50, // 50 elem in total - ) - assert.ElementsMatch(t, ressources, p.Ressources) - assert.Equal(t, uint(10), p.Limit) - assert.Equal(t, uint(1), p.Offset) - assert.Equal(t, uint(5), p.TotalPages) -} - -func TestPageHasNextPageFalse(t *testing.T) { - p := pagination.NewPage( - ressources, - 4, // page 4: last page - 10, // 10 elems per page - 50, // 50 elem in total - ) - assert.False(t, p.HasNextPage) -} - -func TestPageHasNextPageTrue(t *testing.T) { - p := pagination.NewPage( - ressources, - 1, // page 1 - 10, // 10 elems per page - 50, // 50 elem in total - ) - assert.True(t, p.HasNextPage) -} - -func TestPageIsLastPageFalse(t *testing.T) { - p := pagination.NewPage( - ressources, - 1, // page 1 - 10, // 10 elems per page - 50, // 50 elem in total - ) - assert.False(t, p.IsLastPage) -} - -func TestPageIsLastPageTrue(t *testing.T) { - p := pagination.NewPage( - ressources, - 4, // page 4: last page - 10, // 10 elems per page - 50, // 50 elem in total - ) - assert.True(t, p.IsLastPage) -} - -func TestPageHasPreviousPageFalse(t *testing.T) { - p := pagination.NewPage( - ressources, - 0, // page 1 - 10, // 10 elems per page - 50, // 50 elem in total - ) - assert.False(t, p.HasPreviousPage) -} - -func TestPageHasPreviousPageTrue(t *testing.T) { - p := pagination.NewPage( - ressources, - 1, // page 1 - 10, // 10 elems per page - 50, // 50 elem in total - ) - assert.True(t, p.HasPreviousPage) -} - -func TestPageIsFirstPageFalse(t *testing.T) { - p := pagination.NewPage( - ressources, - 1, // page 1 - 10, // 10 elems per page - 50, // 50 elem in total - ) - assert.False(t, p.IsFirstPage) -} - -func TestPageIsFirstPageTrue(t *testing.T) { - p := pagination.NewPage( - ressources, - 0, // page 0: first page - 10, // 10 elems per page - 50, // 50 elem in total - ) - assert.True(t, p.IsFirstPage) -} - -func TestPageHasContentFalse(t *testing.T) { - p := pagination.NewPage( - []*Whatever{}, // no content - 0, // page 1 - 10, // 10 elems per page - 50, // 50 elem in total - ) - assert.False(t, p.HasPreviousPage) -} - -func TestPageHasContentTrue(t *testing.T) { - p := pagination.NewPage( - ressources, - 1, // page 1 - 10, // 10 elems per page - 50, // 50 elem in total - ) - assert.True(t, p.HasContent) -} diff --git a/persistence/pagination/Paginator.go b/persistence/pagination/Paginator.go deleted file mode 100644 index e2811a72..00000000 --- a/persistence/pagination/Paginator.go +++ /dev/null @@ -1,36 +0,0 @@ -package pagination - -// Handle pagination -type Paginator interface { - Offset() uint - Limit() uint -} - -type paginatorImpl struct { - offset uint - limit uint -} - -// Constructor of Paginator -func NewPaginator(page, limit uint) Paginator { - if page == 0 { - page = 1 - } - if limit == 0 { - limit = 1 - } - return &paginatorImpl{ - offset: page, - limit: limit, - } -} - -// Return the page number -func (p *paginatorImpl) Offset() uint { - return p.offset -} - -// Return the max number of records for one page -func (p *paginatorImpl) Limit() uint { - return p.limit -} diff --git a/persistence/pagination/Paginator_test.go b/persistence/pagination/Paginator_test.go deleted file mode 100644 index 8fc58329..00000000 --- a/persistence/pagination/Paginator_test.go +++ /dev/null @@ -1,22 +0,0 @@ -package pagination_test - -import ( - "testing" - - "github.com/ditrit/badaas/persistence/pagination" - "github.com/stretchr/testify/assert" -) - -func TestPaginator(t *testing.T) { - paginator := pagination.NewPaginator(uint(0), uint(12)) - assert.NotNil(t, paginator) - assert.Equal(t, uint(12), paginator.Limit()) - - paginator = pagination.NewPaginator(uint(2), uint(12)) - assert.NotNil(t, paginator) - assert.Equal(t, uint(12), paginator.Limit()) - - paginator = pagination.NewPaginator(uint(2), uint(0)) - assert.NotNil(t, paginator) - assert.Equal(t, uint(1), paginator.Limit()) -} diff --git a/persistence/repository/CRUDRepository.go b/persistence/repository/CRUDRepository.go deleted file mode 100644 index e3c45662..00000000 --- a/persistence/repository/CRUDRepository.go +++ /dev/null @@ -1,20 +0,0 @@ -package repository - -import ( - "github.com/Masterminds/squirrel" - "github.com/ditrit/badaas/httperrors" - "github.com/ditrit/badaas/persistence/models" - "github.com/ditrit/badaas/persistence/pagination" -) - -// Generic CRUD Repository -type CRUDRepository[T models.Tabler, ID any] interface { - Create(*T) httperrors.HTTPError - Delete(*T) httperrors.HTTPError - Save(*T) httperrors.HTTPError - GetByID(ID) (*T, httperrors.HTTPError) - GetAll(SortOption) ([]*T, httperrors.HTTPError) - Count(squirrel.Sqlizer) (uint, httperrors.HTTPError) - Find(squirrel.Sqlizer, pagination.Paginator, SortOption) (*pagination.Page[T], httperrors.HTTPError) - Transaction(fn func(CRUDRepository[T, ID]) (any, error)) (any, httperrors.HTTPError) -} diff --git a/persistence/repository/CRUDRepositoryImpl.go b/persistence/repository/CRUDRepositoryImpl.go deleted file mode 100644 index eca2365f..00000000 --- a/persistence/repository/CRUDRepositoryImpl.go +++ /dev/null @@ -1,240 +0,0 @@ -package repository - -import ( - "fmt" - "net/http" - - "github.com/Masterminds/squirrel" - "github.com/ditrit/badaas/configuration" - "github.com/ditrit/badaas/httperrors" - "github.com/ditrit/badaas/persistence/gormdatabase" - "github.com/ditrit/badaas/persistence/models" - "github.com/ditrit/badaas/persistence/pagination" - "go.uber.org/zap" - "gorm.io/gorm" - "gorm.io/gorm/clause" -) - -// Return a database error -func DatabaseError(message string, golangError error) httperrors.HTTPError { - return httperrors.NewInternalServerError( - "database error", - message, - golangError, - ) -} - -// Implementation of the Generic CRUD Repository -type CRUDRepositoryImpl[T models.Tabler, ID any] struct { - CRUDRepository[T, ID] - gormDatabase *gorm.DB - logger *zap.Logger - paginationConfiguration configuration.PaginationConfiguration -} - -// Contructor of the Generic CRUD Repository -func NewCRUDRepository[T models.Tabler, ID any]( - database *gorm.DB, - logger *zap.Logger, - paginationConfiguration configuration.PaginationConfiguration, -) CRUDRepository[T, ID] { - return &CRUDRepositoryImpl[T, ID]{ - gormDatabase: database, - logger: logger, - paginationConfiguration: paginationConfiguration, - } -} - -// Run the function passed as parameter, if it returns the error and rollback the transaction. -// If no error is returned, it commits the transaction and return the interface{} value. -func (repository *CRUDRepositoryImpl[T, ID]) Transaction(transactionFunction func(CRUDRepository[T, ID]) (any, error)) (any, httperrors.HTTPError) { - transaction := repository.gormDatabase.Begin() - defer func() { - if recoveredError := recover(); recoveredError != nil { - transaction.Rollback() - } - }() - returnValue, err := transactionFunction(&CRUDRepositoryImpl[T, ID]{gormDatabase: transaction}) - if err != nil { - transaction.Rollback() - return nil, DatabaseError("transaction failed", err) - } - err = transaction.Commit().Error - if err != nil { - return nil, DatabaseError("transaction failed to commit", err) - } - return returnValue, nil -} - -// Create an entity of a Model -func (repository *CRUDRepositoryImpl[T, ID]) Create(entity *T) httperrors.HTTPError { - err := repository.gormDatabase.Create(entity).Error - if err != nil { - if gormdatabase.IsDuplicateKeyError(err) { - return httperrors.NewHTTPError( - http.StatusConflict, - fmt.Sprintf("%T already exist in database", entity), - "", - nil, false) - } - return DatabaseError( - fmt.Sprintf("could not create %v in %s", entity, (*entity).TableName()), - err, - ) - - } - return nil -} - -// Delete an entity of a Model -func (repository *CRUDRepositoryImpl[T, ID]) Delete(entity *T) httperrors.HTTPError { - err := repository.gormDatabase.Delete(entity).Error - if err != nil { - return DatabaseError( - fmt.Sprintf("could not delete %v in %s", entity, (*entity).TableName()), - err, - ) - } - return nil -} - -// Save an entity of a Model -func (repository *CRUDRepositoryImpl[T, ID]) Save(entity *T) httperrors.HTTPError { - err := repository.gormDatabase.Save(entity).Error - if err != nil { - return DatabaseError( - fmt.Sprintf("could not save user %v in %s", entity, (*entity).TableName()), - err, - ) - } - return nil -} - -// Get an entity of a Model By ID -func (repository *CRUDRepositoryImpl[T, ID]) GetByID(id ID) (*T, httperrors.HTTPError) { - var entity T - transaction := repository.gormDatabase.First(&entity, "id = ?", id) - if transaction.Error != nil { - return nil, DatabaseError( - fmt.Sprintf("could not get %s by id %v", entity.TableName(), id), - transaction.Error, - ) - } - return &entity, nil -} - -// Get all entities of a Model -func (repository *CRUDRepositoryImpl[T, ID]) GetAll(sortOption SortOption) ([]*T, httperrors.HTTPError) { - var entities []*T - transaction := repository.gormDatabase - if sortOption != nil { - transaction = transaction.Order(buildClauseFromSortOption(sortOption)) - } - transaction.Find(&entities) - if transaction.Error != nil { - var emptyInstanceForError T - return nil, DatabaseError( - fmt.Sprintf("could not get %s", emptyInstanceForError.TableName()), - transaction.Error, - ) - } - return entities, nil -} - -// Build a gorm order clause from a SortOption -func buildClauseFromSortOption(sortOption SortOption) clause.OrderByColumn { - return clause.OrderByColumn{Column: clause.Column{Name: sortOption.Column()}, Desc: sortOption.Desc()} -} - -// Count entities of a models -func (repository *CRUDRepositoryImpl[T, ID]) Count(filters squirrel.Sqlizer) (uint, httperrors.HTTPError) { - whereClause, values, httpError := repository.compileSQL(filters) - if httpError != nil { - return 0, httpError - } - return repository.count(whereClause, values) -} - -// Count the number of record that match the where clause with the provided values on the db -func (repository *CRUDRepositoryImpl[T, ID]) count(whereClause string, values []interface{}) (uint, httperrors.HTTPError) { - var entity *T - var count int64 - transaction := repository.gormDatabase.Model(entity).Where(whereClause, values).Count(&count) - if transaction.Error != nil { - var emptyInstanceForError T - return 0, DatabaseError( - fmt.Sprintf("could not count data from %s with condition %q", emptyInstanceForError.TableName(), whereClause), - transaction.Error, - ) - } - return uint(count), nil -} - -// Find entities of a Model -func (repository *CRUDRepositoryImpl[T, ID]) Find( - filters squirrel.Sqlizer, - page pagination.Paginator, - sortOption SortOption, -) (*pagination.Page[T], httperrors.HTTPError) { - transaction := repository.gormDatabase.Begin() - defer func() { - if recoveredError := recover(); recoveredError != nil { - transaction.Rollback() - - } - }() - var instances []*T - whereClause, values, httpError := repository.compileSQL(filters) - - if httpError != nil { - return nil, httpError - } - if page != nil { - transaction = transaction. - Offset( - int((page.Offset() - 1) * page.Limit()), - ). - Limit( - int(page.Limit()), - ) - } else { - page = pagination.NewPaginator(0, repository.paginationConfiguration.GetMaxElemPerPage()) - } - if sortOption != nil { - transaction = transaction.Order(buildClauseFromSortOption(sortOption)) - } - transaction = transaction.Where(whereClause, values...).Find(&instances) - if transaction.Error != nil { - transaction.Rollback() - var emptyInstanceForError T - return nil, DatabaseError( - fmt.Sprintf("could not get data from %s with condition %q", emptyInstanceForError.TableName(), whereClause), - transaction.Error, - ) - } - // Get Count - nbElem, httpError := repository.count(whereClause, values) - if httpError != nil { - transaction.Rollback() - return nil, httpError - } - err := transaction.Commit().Error - if err != nil { - return nil, DatabaseError( - "transaction failed to commit", err) - } - return pagination.NewPage(instances, page.Offset(), page.Limit(), nbElem), nil -} - -// compile the sql where clause -func (repository *CRUDRepositoryImpl[T, ID]) compileSQL(filters squirrel.Sqlizer) (string, []interface{}, httperrors.HTTPError) { - compiledSQLString, values, err := filters.ToSql() - if err != nil { - return "", []interface{}{}, httperrors.NewInternalServerError( - "sql error", - fmt.Sprintf("Failed to build the sql request (condition=%v)", filters), - err, - ) - } - return compiledSQLString, values, nil -} diff --git a/persistence/repository/CRUDRepositoryImpl_test.go b/persistence/repository/CRUDRepositoryImpl_test.go deleted file mode 100644 index 1dac5dbd..00000000 --- a/persistence/repository/CRUDRepositoryImpl_test.go +++ /dev/null @@ -1,52 +0,0 @@ -package repository - -import ( - "testing" - - "github.com/Masterminds/squirrel" - mocks "github.com/ditrit/badaas/mocks/configuration" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.uber.org/zap" -) - -func TestDatabaseError(t *testing.T) { - err := DatabaseError("test err", assert.AnError) - require.NotNil(t, err) - assert.True(t, err.Log()) -} - -type dumbModel struct{} - -func (dumbModel) TableName() string { - return "dumb_models" -} - -func TestNewRepository(t *testing.T) { - paginationConfiguration := mocks.NewPaginationConfiguration(t) - dumbModelRepository := NewCRUDRepository[dumbModel, uint](nil, zap.L(), paginationConfiguration) - assert.NotNil(t, dumbModelRepository) -} - -func TestCompileSql_NoError(t *testing.T) { - paginationConfiguration := mocks.NewPaginationConfiguration(t) - dumbModelRepository := &CRUDRepositoryImpl[dumbModel, uint]{ - gormDatabase: nil, - logger: zap.L(), - paginationConfiguration: paginationConfiguration, - } - _, _, err := dumbModelRepository.compileSQL(squirrel.Eq{"name": "qsdqsd"}) - assert.Nil(t, err) -} - -func TestCompileSql_Err(t *testing.T) { - paginationConfiguration := mocks.NewPaginationConfiguration(t) - dumbModelRepository := &CRUDRepositoryImpl[dumbModel, uint]{ - gormDatabase: nil, - logger: zap.L(), - paginationConfiguration: paginationConfiguration, - } - _, _, err := dumbModelRepository.compileSQL(squirrel.GtOrEq{"name": nil}) - - assert.Error(t, err) -} diff --git a/persistence/repository/SortOption.go b/persistence/repository/SortOption.go deleted file mode 100644 index 0fda0c40..00000000 --- a/persistence/repository/SortOption.go +++ /dev/null @@ -1,27 +0,0 @@ -package repository - -type SortOption interface { - Column() string - Desc() bool -} - -// SortOption constructor -func NewSortOption(column string, desc bool) SortOption { - return &sortOption{column, desc} -} - -// Sorting option for the repository -type sortOption struct { - column string - desc bool -} - -// return the column name to sort on -func (sortOption *sortOption) Column() string { - return sortOption.column -} - -// return true for descending sort and false for ascending -func (sortOption *sortOption) Desc() bool { - return sortOption.desc -} diff --git a/persistence/repository/SortOption_test.go b/persistence/repository/SortOption_test.go deleted file mode 100644 index 89eba6c7..00000000 --- a/persistence/repository/SortOption_test.go +++ /dev/null @@ -1,14 +0,0 @@ -package repository_test - -import ( - "testing" - - "github.com/ditrit/badaas/persistence/repository" - "github.com/stretchr/testify/assert" -) - -func TestNewSortOption(t *testing.T) { - sortOption := repository.NewSortOption("a", true) - assert.Equal(t, "a", sortOption.Column()) - assert.True(t, sortOption.Desc()) -} diff --git a/resources/api.go b/resources/api.go deleted file mode 100644 index ddea375f..00000000 --- a/resources/api.go +++ /dev/null @@ -1,4 +0,0 @@ -package resources - -// Version of Badaas -const Version = "UNRELEASED" diff --git a/router/ModuleFx.go b/router/ModuleFx.go index a9f9d763..efc7dc7b 100644 --- a/router/ModuleFx.go +++ b/router/ModuleFx.go @@ -1,19 +1,69 @@ package router import ( - "github.com/ditrit/badaas/router/middlewares" + "fmt" + "go.uber.org/fx" + + "github.com/ditrit/badaas/badorm" + "github.com/ditrit/badaas/badorm/unsafe" + "github.com/ditrit/badaas/controllers" + "github.com/ditrit/badaas/router/middlewares" + "github.com/ditrit/badaas/services" ) // RouterModule for fx var RouterModule = fx.Module( "router", + fx.Provide(NewRouter), + fx.Invoke( + fx.Annotate( + AddCRUDRoutes, + fx.ParamTags(`group:"crudControllers"`), + ), + ), // middlewares fx.Provide(middlewares.NewJSONController), fx.Provide(middlewares.NewMiddlewareLogger), + fx.Invoke(middlewares.AddLoggerMiddleware), +) - fx.Provide(middlewares.NewAuthenticationMiddleware), +var InfoRouteModule = fx.Module( + "infoRoute", + // controller + fx.Provide(controllers.NewInfoController), + // routes + fx.Invoke(AddInfoRoutes), +) - // create router - fx.Provide(SetupRouter), +var AuthRoutesModule = fx.Module( + "authRoutes", + // service + services.AuthServiceModule, + + // controller + fx.Provide(controllers.NewBasicAuthenticationController), + + // routes + fx.Provide(middlewares.NewAuthenticationMiddleware), + fx.Invoke(AddAuthRoutes), ) + +func GetCRUDRoutesModule[T badorm.Model]() fx.Option { + typeName := fmt.Sprintf("%T", *new(T)) + + return fx.Module( + typeName+"CRUDRoutesModule", + // service + badorm.GetCRUDServiceModule[T](), + unsafe.GetCRUDServiceModule[T](), + + // controller + fx.Provide( + fx.Annotate( + controllers.NewCRUDController[T], + fx.ResultTags(`group:"crudControllers"`), + ), + ), + ) +} diff --git a/router/middlewares/JSONHandler.go b/router/middlewares/JSONHandler.go deleted file mode 100644 index 729b8400..00000000 --- a/router/middlewares/JSONHandler.go +++ /dev/null @@ -1,10 +0,0 @@ -package middlewares - -import ( - "net/http" - - "github.com/ditrit/badaas/httperrors" -) - -// This handler return a Marshable object and/or an [github.com/ditrit/badaas/services/httperrors.HTTPError] -type JSONHandler func(w http.ResponseWriter, r *http.Request) (any, httperrors.HTTPError) diff --git a/router/middlewares/middlewareAuthentification.go b/router/middlewares/middlewareAuthentication.go similarity index 89% rename from router/middlewares/middlewareAuthentification.go rename to router/middlewares/middlewareAuthentication.go index aaf8d4b1..269cf024 100644 --- a/router/middlewares/middlewareAuthentification.go +++ b/router/middlewares/middlewareAuthentication.go @@ -3,15 +3,14 @@ package middlewares import ( "net/http" + "go.uber.org/zap" + + "github.com/ditrit/badaas/badorm" "github.com/ditrit/badaas/httperrors" "github.com/ditrit/badaas/services/sessionservice" - "github.com/google/uuid" - "go.uber.org/zap" ) -var ( - NotAuthenticated = httperrors.NewUnauthorizedError("Authentification Error", "not authenticated") -) +var NotAuthenticated = httperrors.NewUnauthorizedError("Authentication Error", "not authenticated") // The authentication middleware type AuthenticationMiddleware interface { @@ -43,7 +42,8 @@ func (authenticationMiddleware *authenticationMiddleware) Handle(next http.Handl NotAuthenticated.Write(response, authenticationMiddleware.logger) return } - extractedUUID, err := uuid.Parse(accessTokenCookie.Value) + + extractedUUID, err := badorm.ParseUUID(accessTokenCookie.Value) if err != nil { NotAuthenticated.Write(response, authenticationMiddleware.logger) return diff --git a/router/middlewares/middlewareJson.go b/router/middlewares/middlewareJson.go index 1abe0343..8662bb89 100644 --- a/router/middlewares/middlewareJson.go +++ b/router/middlewares/middlewareJson.go @@ -4,10 +4,14 @@ import ( "encoding/json" "net/http" - "github.com/ditrit/badaas/httperrors" "go.uber.org/zap" + + "github.com/ditrit/badaas/httperrors" ) +// A JSONHandler is a function that returns a Marshable object and/or an [github.com/ditrit/badaas/httperrors.HTTPError] +type JSONHandler func(w http.ResponseWriter, r *http.Request) (any, httperrors.HTTPError) + // transform a JSON handler into a standard [http.HandlerFunc] // handle [github.com/ditrit/badaas/httperrors.HTTPError] and JSON marshaling type JSONController interface { @@ -27,7 +31,8 @@ func NewJSONController(logger *zap.Logger) JSONController { return &jsonControllerImpl{logger} } -// Marshall the response from the JSONHandler and handle HTTPError if needed +// Transforms a JSONHandler into a standard [http.HandlerFunc] +// It marshalls the response from the JSONHandler and handles HTTPError if needed func (controller *jsonControllerImpl) Wrap(handler JSONHandler) func(response http.ResponseWriter, request *http.Request) { return func(response http.ResponseWriter, request *http.Request) { object, herr := handler(response, request) @@ -35,9 +40,11 @@ func (controller *jsonControllerImpl) Wrap(handler JSONHandler) func(response ht herr.Write(response, controller.logger) return } + if object == nil { return } + payload, err := json.Marshal(object) if err != nil { httperrors.NewInternalServerError( @@ -45,9 +52,18 @@ func (controller *jsonControllerImpl) Wrap(handler JSONHandler) func(response ht "Can't marshall the object returned by the JSON handler", nil, ).Write(response, controller.logger) + return } + response.Header().Set("Content-Type", "application/json") - response.Write(payload) + + _, err = response.Write(payload) + if err != nil { + controller.logger.Error( + "Error while writing http response", + zap.String("error", err.Error()), + ) + } } } diff --git a/router/middlewares/middlewareLogger.go b/router/middlewares/middlewareLogger.go index 00f436ad..88e9a7ec 100644 --- a/router/middlewares/middlewareLogger.go +++ b/router/middlewares/middlewareLogger.go @@ -4,12 +4,18 @@ import ( "fmt" "net/http" - "github.com/ditrit/badaas/configuration" + "github.com/gorilla/mux" "github.com/noirbizarre/gonja" "github.com/noirbizarre/gonja/exec" "go.uber.org/zap" + + "github.com/ditrit/badaas/configuration" ) +func AddLoggerMiddleware(router *mux.Router, middlewareLogger MiddlewareLogger) { + router.Use(middlewareLogger.Handle) +} + // Log the requests data type MiddlewareLogger interface { // [github.com/gorilla/mux] compatible middleware function @@ -36,6 +42,7 @@ func NewMiddlewareLogger( if err != nil { return nil, fmt.Errorf("failed to build jinja template from configuration %w", err) } + return &middlewareLoggerImpl{ logger: logger, template: requestLogTemplate, @@ -60,5 +67,6 @@ func getLogMessage(template *exec.Template, r *http.Request) string { "method": r.Method, "url": r.URL.Path, }) + return result } diff --git a/router/middlewares/middlewarelogger_test.go b/router/middlewares/middlewarelogger_test.go index 4d0b2bbe..711a7582 100644 --- a/router/middlewares/middlewarelogger_test.go +++ b/router/middlewares/middlewarelogger_test.go @@ -6,11 +6,12 @@ import ( "net/url" "testing" - configurationmocks "github.com/ditrit/badaas/mocks/configuration" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap" "go.uber.org/zap/zaptest/observer" + + configurationmocks "github.com/ditrit/badaas/mocks/configuration" ) func TestMiddlewareLogger(t *testing.T) { @@ -18,7 +19,7 @@ func TestMiddlewareLogger(t *testing.T) { observedLogger := zap.New(observedZapCore) req := &http.Request{ - Method: "GET", + Method: http.MethodGet, URL: &url.URL{ Scheme: "http", Host: "localhost", @@ -26,10 +27,10 @@ func TestMiddlewareLogger(t *testing.T) { }, } res := httptest.NewRecorder() - var actuallyRunned bool = false + actuallyRun := false // create a handler to use as "next" which will verify the request - nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - actuallyRunned = true + nextHandler := http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) { + actuallyRun = true }) loggerConfiguration := configurationmocks.NewLoggerConfiguration(t) loggerConfiguration.On("GetRequestTemplate").Return("Receive {{method}} request on {{url}}") @@ -37,7 +38,7 @@ func TestMiddlewareLogger(t *testing.T) { assert.NoError(t, err) loggerMiddleware.Handle(nextHandler).ServeHTTP(res, req) - assert.True(t, actuallyRunned) + assert.True(t, actuallyRun) require.Equal(t, 1, observedLogs.Len()) assert.Equal(t, "Receive GET request on /whatever", observedLogs.All()[0].Message) } diff --git a/router/router.go b/router/router.go index 8a3088cc..24039d72 100644 --- a/router/router.go +++ b/router/router.go @@ -1,42 +1,10 @@ package router import ( - "net/http" - - "github.com/ditrit/badaas/controllers" - "github.com/ditrit/badaas/router/middlewares" "github.com/gorilla/mux" ) -// Default router of badaas, initialize all routes. -func SetupRouter( - //middlewares - jsonController middlewares.JSONController, - middlewareLogger middlewares.MiddlewareLogger, - authenticationMiddleware middlewares.AuthenticationMiddleware, - - // controllers - basicAuthentificationController controllers.BasicAuthentificationController, - informationController controllers.InformationController, -) http.Handler { - router := mux.NewRouter() - router.Use(middlewareLogger.Handle) - - router.HandleFunc( - "/info", - jsonController.Wrap(informationController.Info), - ).Methods("GET") - router.HandleFunc( - "/login", - jsonController.Wrap( - basicAuthentificationController.BasicLoginHandler, - ), - ).Methods("POST") - - protected := router.PathPrefix("").Subrouter() - protected.Use(authenticationMiddleware.Handle) - - protected.HandleFunc("/logout", jsonController.Wrap(basicAuthentificationController.Logout)).Methods("GET") - - return router +// Router to use in Badaas server +func NewRouter() *mux.Router { + return mux.NewRouter() } diff --git a/router/router_test.go b/router/router_test.go deleted file mode 100644 index da2f247a..00000000 --- a/router/router_test.go +++ /dev/null @@ -1,23 +0,0 @@ -package router - -import ( - "net/http" - "testing" - - controllersMocks "github.com/ditrit/badaas/mocks/controllers" - middlewaresMocks "github.com/ditrit/badaas/mocks/router/middlewares" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -func TestSetupRouter(t *testing.T) { - jsonController := middlewaresMocks.NewJSONController(t) - middlewareLogger := middlewaresMocks.NewMiddlewareLogger(t) - authenticationMiddleware := middlewaresMocks.NewAuthenticationMiddleware(t) - - basicController := controllersMocks.NewBasicAuthentificationController(t) - informationController := controllersMocks.NewInformationController(t) - jsonController.On("Wrap", mock.Anything).Return(func(response http.ResponseWriter, request *http.Request) {}) - router := SetupRouter(jsonController, middlewareLogger, authenticationMiddleware, basicController, informationController) - assert.NotNil(t, router) -} diff --git a/router/routes.go b/router/routes.go new file mode 100644 index 00000000..849b8bf6 --- /dev/null +++ b/router/routes.go @@ -0,0 +1,95 @@ +package router + +import ( + "net/http" + "strings" + + "github.com/gorilla/mux" + "go.uber.org/zap" + + "github.com/ditrit/badaas/configuration" + "github.com/ditrit/badaas/controllers" + "github.com/ditrit/badaas/router/middlewares" + "github.com/ditrit/badaas/services/userservice" +) + +func AddInfoRoutes( + router *mux.Router, + jsonController middlewares.JSONController, + infoController controllers.InformationController, +) { + router.HandleFunc( + "/info", + jsonController.Wrap(infoController.Info), + ).Methods(http.MethodGet) +} + +// Adds to the "router" the routes for handling authentication: +// /login +// /logout +// And creates a very first user +func AddAuthRoutes( + logger *zap.Logger, + router *mux.Router, + authenticationMiddleware middlewares.AuthenticationMiddleware, + basicAuthenticationController controllers.BasicAuthenticationController, + jsonController middlewares.JSONController, + config configuration.InitializationConfiguration, + userService userservice.UserService, +) error { + router.HandleFunc( + "/login", + jsonController.Wrap(basicAuthenticationController.BasicLoginHandler), + ).Methods(http.MethodPost) + + protected := router.PathPrefix("").Subrouter() + protected.Use(authenticationMiddleware.Handle) + + protected.HandleFunc( + "/logout", + jsonController.Wrap(basicAuthenticationController.Logout), + ).Methods(http.MethodGet) + + return createSuperUser(logger, config, userService) +} + +// Create a super user +func createSuperUser( + logger *zap.Logger, + config configuration.InitializationConfiguration, + userService userservice.UserService, +) error { + _, err := userService.NewUser("admin", "admin-no-reply@badaas.com", config.GetAdminPassword()) + if err != nil { + if !strings.Contains(err.Error(), "already exist in database") { + logger.Sugar().Errorf("failed to save the super admin %w", err) + return err + } + + logger.Sugar().Infof("The superadmin user already exists in database") + } + + return nil +} + +func AddCRUDRoutes( + crudRoutes []controllers.CRUDRoute, + router *mux.Router, + jsonController middlewares.JSONController, +) { + for _, crudRoute := range crudRoutes { + // Objects CRUD + objectsBase := "/objects/" + crudRoute.TypeName + objectsWithID := objectsBase + "/{id}" + // TODO create, update, delete + // read + router.HandleFunc( + objectsWithID, + jsonController.Wrap(crudRoute.Controller.GetModel), + ).Methods(http.MethodGet) + router.HandleFunc( + objectsBase, + jsonController.Wrap(crudRoute.Controller.GetModels), + ).Methods(http.MethodGet) + } +} diff --git a/router/routes_test.go b/router/routes_test.go new file mode 100644 index 00000000..b9974d47 --- /dev/null +++ b/router/routes_test.go @@ -0,0 +1,186 @@ +package router + +import ( + "errors" + "net/http" + "net/http/httptest" + "testing" + + "github.com/Masterminds/semver/v3" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + "go.uber.org/zap/zaptest/observer" + + "github.com/ditrit/badaas/controllers" + mocks "github.com/ditrit/badaas/mocks/configuration" + mockControllers "github.com/ditrit/badaas/mocks/controllers" + mockMiddlewares "github.com/ditrit/badaas/mocks/router/middlewares" + mockUserServices "github.com/ditrit/badaas/mocks/services/userservice" + "github.com/ditrit/badaas/router/middlewares" +) + +func TestCreateSuperUser(t *testing.T) { + core, _ := observer.New(zap.DebugLevel) + logger := zap.New(core) + initializationConfig := mocks.NewInitializationConfiguration(t) + initializationConfig.On("GetAdminPassword").Return("adminpassword") + + userService := mockUserServices.NewUserService(t) + userService. + On("NewUser", "admin", "admin-no-reply@badaas.com", "adminpassword"). + Return(nil, nil) + + err := createSuperUser( + logger, + initializationConfig, + userService, + ) + assert.NoError(t, err) +} + +func TestCreateSuperUser_UserExists(t *testing.T) { + core, logs := observer.New(zap.DebugLevel) + logger := zap.New(core) + initializationConfig := mocks.NewInitializationConfiguration(t) + initializationConfig.On("GetAdminPassword").Return("adminpassword") + + userService := mockUserServices.NewUserService(t) + userService. + On("NewUser", "admin", "admin-no-reply@badaas.com", "adminpassword"). + Return(nil, errors.New("user already exist in database")) + + err := createSuperUser( + logger, + initializationConfig, + userService, + ) + assert.NoError(t, err) + + require.Equal(t, 1, logs.Len()) +} + +func TestCreateSuperUser_UserServiceError(t *testing.T) { + core, logs := observer.New(zap.DebugLevel) + logger := zap.New(core) + initializationConfig := mocks.NewInitializationConfiguration(t) + initializationConfig.On("GetAdminPassword").Return("adminpassword") + + userService := mockUserServices.NewUserService(t) + userService. + On("NewUser", "admin", "admin-no-reply@badaas.com", "adminpassword"). + Return(nil, errors.New("email not valid")) + + err := createSuperUser( + logger, + initializationConfig, + userService, + ) + assert.Error(t, err) + + require.Equal(t, 1, logs.Len()) +} + +var logger, _ = zap.NewDevelopment() + +func TestAddInfoRoutes(t *testing.T) { + jsonController := middlewares.NewJSONController(logger) + informationController := controllers.NewInfoController(semver.MustParse("1.0.1")) + + router := NewRouter() + AddInfoRoutes( + router, + jsonController, + informationController, + ) + + response := httptest.NewRecorder() + request := httptest.NewRequest( + http.MethodGet, + "/info", + nil, + ) + + router.ServeHTTP(response, request) + assert.Equal(t, response.Code, http.StatusOK) + assert.Equal(t, response.Body.String(), "{\"status\":\"OK\",\"version\":\"1.0.1\"}") +} + +func TestAddLoginRoutes(t *testing.T) { + jsonController := middlewares.NewJSONController(logger) + + initializationConfig := mocks.NewInitializationConfiguration(t) + initializationConfig. + On("GetAdminPassword").Return("adminpassword") + + userService := mockUserServices.NewUserService(t) + userService. + On("NewUser", "admin", "admin-no-reply@badaas.com", "adminpassword"). + Return(nil, nil) + + basicAuthenticationController := mockControllers.NewBasicAuthenticationController(t) + basicAuthenticationController. + On("BasicLoginHandler", mock.Anything, mock.Anything). + Return(map[string]string{"login": "called"}, nil) + + authenticationMiddleware := mockMiddlewares.NewAuthenticationMiddleware(t) + + router := NewRouter() + + err := AddAuthRoutes( + nil, + router, + authenticationMiddleware, + basicAuthenticationController, + jsonController, + initializationConfig, + userService, + ) + if err != nil { + t.Error(err) + } + + response := httptest.NewRecorder() + request := httptest.NewRequest( + http.MethodPost, + "/login", + nil, + ) + + router.ServeHTTP(response, request) + assert.Equal(t, response.Code, http.StatusOK) + assert.Equal(t, response.Body.String(), "{\"login\":\"called\"}") +} + +func TestAddCRUDRoutes(t *testing.T) { + jsonController := middlewares.NewJSONController(logger) + + crudController := mockControllers.NewCRUDController(t) + crudController. + On("GetModels", mock.Anything, mock.Anything). + Return(map[string]string{"GetModels": "called"}, nil) + + router := NewRouter() + AddCRUDRoutes( + []controllers.CRUDRoute{ + { + TypeName: "model", + Controller: crudController, + }, + }, + router, + jsonController, + ) + + response := httptest.NewRecorder() + request := httptest.NewRequest( + http.MethodGet, + "/objects/model", + nil, + ) + + router.ServeHTTP(response, request) + assert.Equal(t, http.StatusOK, response.Code) + assert.Equal(t, "{\"GetModels\":\"called\"}", response.Body.String()) +} diff --git a/scripts/e2e/api/Dockerfile b/scripts/e2e/api/Dockerfile deleted file mode 100644 index 49bdc272..00000000 --- a/scripts/e2e/api/Dockerfile +++ /dev/null @@ -1,12 +0,0 @@ -# builder image -FROM golang:1.19-alpine as builder -RUN apk add build-base -WORKDIR /app -COPY . . -RUN CGO_ENABLED=1 go build --race -a -o badaas . - -FROM alpine:3.16.2 -ENV BADAAS_PORT=8000 -COPY --from=builder /app/badaas . -COPY ./scripts/e2e/api/badaas.yml . -ENTRYPOINT [ "./badaas" ] diff --git a/scripts/e2e/db/Dockerfile b/scripts/e2e/db/Dockerfile deleted file mode 100644 index 6f892635..00000000 --- a/scripts/e2e/db/Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -FROM cockroachdb/cockroach:latest - -LABEL maintainer="tjveil@gmail.com" - -ADD init.sh /cockroach/ -RUN chmod a+x /cockroach/init.sh - -ADD logs.yaml /cockroach/ - -WORKDIR /cockroach/ - -EXPOSE 8080 -EXPOSE 26257 - -ENTRYPOINT ["/cockroach/init.sh"] \ No newline at end of file diff --git a/scripts/e2e/db/init.sh b/scripts/e2e/db/init.sh deleted file mode 100644 index 7231f8bf..00000000 --- a/scripts/e2e/db/init.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/sh -echo "******************************* Listing Env Variables..." -printenv -echo "******************************* starting single cockroach node..." - -./cockroach start-single-node --insecure --log-config-file=logs.yaml --background - - -echo "******************************* Creating user" -# cockroach user set ${COCKROACH_USER} --password 1234 --echo-sql -# cockroach user ls - -echo "******************************* Init database" -echo "******************************* |=> Creating init.sql" - -cat > init.sql < Applying init.sql" - -./cockroach sql --insecure --file init.sql - -echo "******************************* To the moon" - -cd /cockroach/cockroach-data/logs -tail -f cockroach.log \ No newline at end of file diff --git a/scripts/e2e/docker-compose.yml b/scripts/e2e/docker-compose.yml deleted file mode 100644 index a6b936a3..00000000 --- a/scripts/e2e/docker-compose.yml +++ /dev/null @@ -1,25 +0,0 @@ -# DEVELOPMENT ONLY, DO NOT USE FOR PRODUCTION -version: '3.5' - -services: - db: - build: db/. - ports: - - "26257:26257" - - "8080:8080" # Web based dashboard - environment: - - COCKROACH_USER=root - - COCKROACH_DB=badaas_db - - api: - build: - context: ./../.. - dockerfile: ./scripts/e2e/api/Dockerfile - ports: - - "8000:8000" - restart: always - # environment: - # - BADAAS_PORT=8000 - # - BADAAS_MAX_TIMOUT= 15 # in seconds - depends_on: - - db diff --git a/services/ModuleFx.go b/services/ModuleFx.go new file mode 100644 index 00000000..4b40de48 --- /dev/null +++ b/services/ModuleFx.go @@ -0,0 +1,32 @@ +package services + +import ( + "go.uber.org/fx" + + "github.com/ditrit/badaas/badorm" + "github.com/ditrit/badaas/persistence/models" + "github.com/ditrit/badaas/services/sessionservice" + "github.com/ditrit/badaas/services/userservice" +) + +var AuthServiceModule = fx.Module( + "authService", + // models + fx.Provide(getAuthModels), + // repositories + fx.Provide(badorm.NewCRUDRepository[models.Session, badorm.UUID]), + fx.Provide(badorm.NewCRUDRepository[models.User, badorm.UUID]), + + // services + fx.Provide(userservice.NewUserService), + fx.Provide(sessionservice.NewSessionService), +) + +func getAuthModels() badorm.GetModelsResult { + return badorm.GetModelsResult{ + Models: []any{ + models.Session{}, + models.User{}, + }, + } +} diff --git a/services/auth/protocols/basicauth/basic.go b/services/auth/protocols/basicauth/basic.go index ac2e944d..3c202048 100644 --- a/services/auth/protocols/basicauth/basic.go +++ b/services/auth/protocols/basicauth/basic.go @@ -14,6 +14,7 @@ func SaltAndHashPassword(password string) []byte { []byte(password), cost, ) + return bytes } diff --git a/services/auth/protocols/basicauth/basic_test.go b/services/auth/protocols/basicauth/basic_test.go index c788a018..52e3f6e5 100644 --- a/services/auth/protocols/basicauth/basic_test.go +++ b/services/auth/protocols/basicauth/basic_test.go @@ -3,8 +3,9 @@ package basicauth_test import ( "testing" - "github.com/ditrit/badaas/services/auth/protocols/basicauth" "github.com/stretchr/testify/assert" + + "github.com/ditrit/badaas/services/auth/protocols/basicauth" ) func TestSaltAndHashPassword(t *testing.T) { @@ -18,5 +19,4 @@ func TestCheckUserPassword(t *testing.T) { hash := basicauth.SaltAndHashPassword(password) assert.True(t, basicauth.CheckUserPassword(hash, password), "the password and it's hash should match") assert.False(t, basicauth.CheckUserPassword(hash, "wrong password"), "the password and it's hash should match") - } diff --git a/services/sessionservice/session.go b/services/sessionservice/session.go index d4368319..248a4049 100644 --- a/services/sessionservice/session.go +++ b/services/sessionservice/session.go @@ -2,17 +2,16 @@ package sessionservice import ( "fmt" - "net/http" "sync" "time" - "github.com/Masterminds/squirrel" + "go.uber.org/zap" + "gorm.io/gorm" + + "github.com/ditrit/badaas/badorm" "github.com/ditrit/badaas/configuration" "github.com/ditrit/badaas/httperrors" "github.com/ditrit/badaas/persistence/models" - "github.com/ditrit/badaas/persistence/repository" - "github.com/google/uuid" - "go.uber.org/zap" ) // Errors @@ -25,10 +24,11 @@ var ( // SessionService handle sessions type SessionService interface { - IsValid(sessionUUID uuid.UUID) (bool, *SessionClaims) - RollSession(uuid.UUID) httperrors.HTTPError - LogUserIn(user *models.User, response http.ResponseWriter) httperrors.HTTPError - LogUserOut(sessionClaims *SessionClaims, response http.ResponseWriter) httperrors.HTTPError + IsValid(sessionUUID badorm.UUID) (bool, *SessionClaims) + // TODO services should not work with httperrors + RollSession(badorm.UUID) httperrors.HTTPError + LogUserIn(user *models.User) (*models.Session, error) + LogUserOut(sessionClaims *SessionClaims) httperrors.HTTPError } // Check interface compliance @@ -36,82 +36,86 @@ var _ SessionService = (*sessionServiceImpl)(nil) // The SessionService concrete interface type sessionServiceImpl struct { - sessionRepository repository.CRUDRepository[models.Session, uuid.UUID] - cache map[uuid.UUID]*models.Session + sessionRepository badorm.CRUDRepository[models.Session, badorm.UUID] + cache map[badorm.UUID]*models.Session mutex sync.Mutex logger *zap.Logger sessionConfiguration configuration.SessionConfiguration + db *gorm.DB } // The SessionService constructor func NewSessionService( logger *zap.Logger, - sessionRepository repository.CRUDRepository[models.Session, uuid.UUID], + sessionRepository badorm.CRUDRepository[models.Session, badorm.UUID], sessionConfiguration configuration.SessionConfiguration, + db *gorm.DB, ) SessionService { sessionService := &sessionServiceImpl{ - cache: make(map[uuid.UUID]*models.Session), + cache: make(map[badorm.UUID]*models.Session), logger: logger, sessionRepository: sessionRepository, sessionConfiguration: sessionConfiguration, + db: db, } sessionService.init() - return sessionService -} -// Create a new session -func newSession(userID uuid.UUID, sessionDuration time.Duration) *models.Session { - return &models.Session{ - UserID: userID, - ExpiresAt: time.Now().Add(sessionDuration), - } + return sessionService } // Return true if the session exists and is still valid. // A instance of SessionClaims is returned to be added to the request context if the conditions previously mentioned are met. -func (sessionService *sessionServiceImpl) IsValid(sessionUUID uuid.UUID) (bool, *SessionClaims) { +func (sessionService *sessionServiceImpl) IsValid(sessionUUID badorm.UUID) (bool, *SessionClaims) { sessionInstance := sessionService.get(sessionUUID) if sessionInstance == nil { return false, nil } + return true, makeSessionClaims(sessionInstance) } // Get a session from cache // return nil if not found -func (sessionService *sessionServiceImpl) get(sessionUUID uuid.UUID) *models.Session { +func (sessionService *sessionServiceImpl) get(sessionUUID badorm.UUID) *models.Session { sessionService.mutex.Lock() defer sessionService.mutex.Unlock() + session, ok := sessionService.cache[sessionUUID] if ok { return session } - sessionsFoundWithUUID, databaseError := sessionService.sessionRepository.Find(squirrel.Eq{"uuid": sessionUUID.String()}, nil, nil) - if databaseError != nil { + + session, err := sessionService.sessionRepository.GetByID( + sessionService.db, + sessionUUID, + ) + if err != nil { return nil } - if !sessionsFoundWithUUID.HasContent { - return nil // no sessions found in database - } - return sessionsFoundWithUUID.Ressources[0] + + return session } // Add a session to the cache -func (sessionService *sessionServiceImpl) add(session *models.Session) httperrors.HTTPError { +func (sessionService *sessionServiceImpl) add(session *models.Session) error { sessionService.mutex.Lock() defer sessionService.mutex.Unlock() - herr := sessionService.sessionRepository.Create(session) - if herr != nil { - return herr + + err := sessionService.sessionRepository.Create(sessionService.db, session) + if err != nil { + return err } + sessionService.cache[session.ID] = session sessionService.logger.Debug("Added session", zap.String("uuid", session.ID.String())) + return nil } // Initialize the session service -func (sessionService *sessionServiceImpl) init() error { - sessionService.cache = make(map[uuid.UUID]*models.Session) +func (sessionService *sessionServiceImpl) init() { + sessionService.cache = make(map[badorm.UUID]*models.Session) + go func() { for { sessionService.removeExpired() @@ -121,21 +125,23 @@ func (sessionService *sessionServiceImpl) init() error { ) } }() - return nil } // Get all sessions and save them in cache func (sessionService *sessionServiceImpl) pullFromDB() { sessionService.mutex.Lock() defer sessionService.mutex.Unlock() - sessionsFromDatabase, err := sessionService.sessionRepository.GetAll(nil) + + sessionsFromDatabase, err := sessionService.sessionRepository.Query(sessionService.db) if err != nil { panic(err) } - newSessionCache := make(map[uuid.UUID]*models.Session) + + newSessionCache := make(map[badorm.UUID]*models.Session) for _, sessionFromDatabase := range sessionsFromDatabase { newSessionCache[sessionFromDatabase.ID] = sessionFromDatabase } + sessionService.cache = newSessionCache sessionService.logger.Debug( "Pulled sessions from DB", @@ -147,11 +153,13 @@ func (sessionService *sessionServiceImpl) pullFromDB() { func (sessionService *sessionServiceImpl) removeExpired() { sessionService.mutex.Lock() defer sessionService.mutex.Unlock() + var i int + for sessionUUID, session := range sessionService.cache { if session.IsExpired() { // Delete the session in the database - err := sessionService.sessionRepository.Delete(session) + err := sessionService.sessionRepository.Delete(sessionService.db, session) if err != nil { panic(err) } @@ -162,6 +170,7 @@ func (sessionService *sessionServiceImpl) removeExpired() { i++ } } + sessionService.logger.Debug( "Removed expired session", zap.Int("expiredSessionCount", i), @@ -172,8 +181,10 @@ func (sessionService *sessionServiceImpl) removeExpired() { func (sessionService *sessionServiceImpl) delete(session *models.Session) httperrors.HTTPError { sessionService.mutex.Lock() defer sessionService.mutex.Unlock() + sessionUUID := session.ID - err := sessionService.sessionRepository.Delete(session) + + err := sessionService.sessionRepository.Delete(sessionService.db, session) if err != nil { return httperrors.NewInternalServerError( "session error", @@ -181,77 +192,70 @@ func (sessionService *sessionServiceImpl) delete(session *models.Session) httper err, ) } + delete(sessionService.cache, sessionUUID) + return nil } // Roll a session. If the session is close to expiration, extend its duration. -func (sessionService *sessionServiceImpl) RollSession(sessionUUID uuid.UUID) httperrors.HTTPError { +func (sessionService *sessionServiceImpl) RollSession(sessionUUID badorm.UUID) httperrors.HTTPError { rollInterval := sessionService.sessionConfiguration.GetRollDuration() sessionDuration := sessionService.sessionConfiguration.GetSessionDuration() + session := sessionService.get(sessionUUID) if session == nil { // no session to roll, no error return nil } + if session.IsExpired() { return HERRSessionExpired } + if session.CanBeRolled(rollInterval) { sessionService.mutex.Lock() defer sessionService.mutex.Unlock() + session.ExpiresAt = session.ExpiresAt.Add(sessionDuration) - herr := sessionService.sessionRepository.Save(session) - if herr != nil { - return herr + + err := sessionService.sessionRepository.Save(sessionService.db, session) + if err != nil { + return httperrors.NewDBError(err) } + sessionService.logger.Warn("Rolled session", zap.String("userID", session.UserID.String()), zap.String("sessionID", session.ID.String())) } + return nil } // Log in a user -func (sessionService *sessionServiceImpl) LogUserIn(user *models.User, response http.ResponseWriter) httperrors.HTTPError { +func (sessionService *sessionServiceImpl) LogUserIn(user *models.User) (*models.Session, error) { sessionDuration := sessionService.sessionConfiguration.GetSessionDuration() - session := newSession(user.ID, sessionDuration) + session := models.NewSession(user.ID, sessionDuration) + err := sessionService.add(session) if err != nil { - return err + return nil, err } - CreateAndSetAccessTokenCookie(response, session.ID.String()) - return nil + + return session, nil } // Log out a user. -func (sessionService *sessionServiceImpl) LogUserOut(sessionClaims *SessionClaims, response http.ResponseWriter) httperrors.HTTPError { +func (sessionService *sessionServiceImpl) LogUserOut(sessionClaims *SessionClaims) httperrors.HTTPError { session := sessionService.get(sessionClaims.SessionUUID) if session == nil { - return httperrors.NewUnauthorizedError("Authentification Error", "not authenticated") + return httperrors.NewUnauthorizedError("Authentication Error", "not authenticated") } + err := sessionService.delete(session) if err != nil { return err } - CreateAndSetAccessTokenCookie(response, "") - return nil -} -// Create an access token and send it in a cookie -func CreateAndSetAccessTokenCookie(w http.ResponseWriter, sessionUUID string) { - accessToken := &http.Cookie{ - Name: "access_token", - Path: "/", - Value: sessionUUID, - HttpOnly: true, - SameSite: http.SameSiteNoneMode, // change to http.SameSiteStrictMode in prod - Secure: false, // change to true in prod - Expires: time.Now().Add(48 * time.Hour), - } - err := accessToken.Valid() - if err != nil { - panic(err) - } - http.SetCookie(w, accessToken) + return nil } diff --git a/services/sessionservice/session_test.go b/services/sessionservice/session_test.go index 422b831f..ec29c534 100644 --- a/services/sessionservice/session_test.go +++ b/services/sessionservice/session_test.go @@ -1,83 +1,98 @@ package sessionservice import ( - "net/http/httptest" + "errors" "testing" "time" - "github.com/Masterminds/squirrel" - "github.com/ditrit/badaas/httperrors" - configurationmocks "github.com/ditrit/badaas/mocks/configuration" - repositorymocks "github.com/ditrit/badaas/mocks/persistence/repository" - "github.com/ditrit/badaas/persistence/models" - "github.com/ditrit/badaas/persistence/pagination" - "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "go.uber.org/zap" "go.uber.org/zap/zapcore" "go.uber.org/zap/zaptest/observer" + "gorm.io/gorm" + + "github.com/ditrit/badaas/badorm" + "github.com/ditrit/badaas/httperrors" + badormMocks "github.com/ditrit/badaas/mocks/badorm" + configurationMocks "github.com/ditrit/badaas/mocks/configuration" + "github.com/ditrit/badaas/persistence/models" ) -func TestNewSession(t *testing.T) { - sessionInstance := newSession(uuid.Nil, time.Second) - assert.NotNil(t, sessionInstance) - assert.Equal(t, uuid.Nil, sessionInstance.UserID) -} +var gormDB *gorm.DB -func TestLogInUser(t *testing.T) { - sessionRepositoryMock, service, logs, sessionConfigurationMock := setupTest(t) - sessionRepositoryMock.On("Create", mock.Anything).Return(nil) - sessionConfigurationMock.On("GetSessionDuration").Return(time.Minute) - response := httptest.NewRecorder() - user := &models.User{ - Username: "bob", - Email: "bob@email.com", - } - err := service.LogUserIn(user, response) - require.NoError(t, err) - assert.Len(t, service.cache, 1) - assert.Equal(t, 1, logs.Len()) - log := logs.All()[0] - assert.Equal(t, "Added session", log.Message) - require.Len(t, log.Context, 1) +// ExampleErr is an HTTPError instance useful for testing. If the code does not care +// about HTTPError specifics, and only needs to return the HTTPError for example, this +// HTTPError should be used to make the test code more readable. +var ExampleErr httperrors.HTTPError = &httperrors.HTTPErrorImpl{ + Status: -1, + Err: "TESTING ERROR", + Message: "USE ONLY FOR TESTING", + GolangError: nil, } // make values for test func setupTest( t *testing.T, ) ( - *repositorymocks.CRUDRepository[models.Session, uuid.UUID], + *badormMocks.CRUDRepository[models.Session, badorm.UUID], *sessionServiceImpl, *observer.ObservedLogs, - *configurationmocks.SessionConfiguration, + *configurationMocks.SessionConfiguration, ) { core, logs := observer.New(zap.DebugLevel) logger := zap.New(core) - sessionRepositoryMock := repositorymocks.NewCRUDRepository[models.Session, uuid.UUID](t) - sessionConfiguration := configurationmocks.NewSessionConfiguration(t) + sessionRepositoryMock := badormMocks.NewCRUDRepository[models.Session, badorm.UUID](t) + sessionConfiguration := configurationMocks.NewSessionConfiguration(t) service := &sessionServiceImpl{ sessionRepository: sessionRepositoryMock, logger: logger, - cache: make(map[uuid.UUID]*models.Session), + cache: make(map[badorm.UUID]*models.Session), sessionConfiguration: sessionConfiguration, + db: gormDB, } return sessionRepositoryMock, service, logs, sessionConfiguration } +func TestNewSession(t *testing.T) { + sessionInstance := models.NewSession(badorm.NilUUID, time.Second) + assert.NotNil(t, sessionInstance) + assert.Equal(t, badorm.NilUUID, sessionInstance.UserID) +} + +func TestLogInUser(t *testing.T) { + sessionRepositoryMock, service, logs, sessionConfigurationMock := setupTest(t) + sessionRepositoryMock.On("Create", gormDB, mock.Anything).Return(nil) + sessionConfigurationMock.On("GetSessionDuration").Return(time.Minute) + + user := &models.User{ + Username: "bob", + Email: "bob@email.com", + } + _, err := service.LogUserIn(user) + require.NoError(t, err) + assert.Len(t, service.cache, 1) + assert.Equal(t, 1, logs.Len()) + log := logs.All()[0] + assert.Equal(t, "Added session", log.Message) + require.Len(t, log.Context, 1) +} + func TestLogInUserDbError(t *testing.T) { sessionRepositoryMock, service, logs, sessionConfigurationMock := setupTest(t) - sessionRepositoryMock.On("Create", mock.Anything).Return(httperrors.NewInternalServerError("db err", "nil", nil)) + sessionRepositoryMock. + On("Create", gormDB, mock.Anything). + Return(errors.New("db err")) + sessionConfigurationMock.On("GetSessionDuration").Return(time.Minute) - response := httptest.NewRecorder() user := &models.User{ Username: "bob", Email: "bob@email.com", } - err := service.LogUserIn(user, response) + _, err := service.LogUserIn(user) require.Error(t, err) assert.Len(t, service.cache, 0) assert.Equal(t, 0, logs.Len()) @@ -85,23 +100,24 @@ func TestLogInUserDbError(t *testing.T) { func TestIsValid(t *testing.T) { sessionRepositoryMock, service, _, _ := setupTest(t) - sessionRepositoryMock.On("Create", mock.Anything).Return(nil) - uuidSample := uuid.New() + sessionRepositoryMock.On("Create", gormDB, mock.Anything).Return(nil) + + uuidSample := badorm.NewUUID() session := &models.Session{ - BaseModel: models.BaseModel{ + UUIDModel: badorm.UUIDModel{ ID: uuidSample, }, - UserID: uuid.Nil, + UserID: badorm.NilUUID, ExpiresAt: time.Now().Add(time.Hour), } err := service.add(session) require.NoError(t, err) assert.Len(t, service.cache, 1) - assert.Equal(t, uuid.Nil, service.cache[uuidSample].UserID) + assert.Equal(t, badorm.NilUUID, service.cache[uuidSample].UserID) isValid, claims := service.IsValid(uuidSample) require.True(t, isValid) assert.Equal(t, *claims, SessionClaims{ - UserID: uuid.Nil, + UserID: badorm.NilUUID, SessionUUID: uuidSample, }) } @@ -109,46 +125,48 @@ func TestIsValid(t *testing.T) { func TestIsValid_SessionNotFound(t *testing.T) { sessionRepositoryMock, service, _, _ := setupTest(t) sessionRepositoryMock. - On("Find", mock.Anything, mock.Anything, mock.Anything). - Return(pagination.NewPage([]*models.Session{}, 0, 125, 1236), nil) - uuidSample := uuid.New() + On("GetByID", gormDB, mock.Anything). + Return(nil, nil) + + uuidSample := badorm.NewUUID() isValid, _ := service.IsValid(uuidSample) require.False(t, isValid) - // } func TestLogOutUser(t *testing.T) { sessionRepositoryMock, service, _, _ := setupTest(t) - sessionRepositoryMock.On("Delete", mock.Anything).Return(nil) - response := httptest.NewRecorder() - uuidSample := uuid.New() + sessionRepositoryMock.On("Delete", gormDB, mock.Anything).Return(nil) + + uuidSample := badorm.NewUUID() session := &models.Session{ - BaseModel: models.BaseModel{ + UUIDModel: badorm.UUIDModel{ ID: uuidSample, }, - UserID: uuid.Nil, + UserID: badorm.NilUUID, ExpiresAt: time.Now().Add(time.Hour), } service.cache[uuidSample] = session - err := service.LogUserOut(makeSessionClaims(session), response) + err := service.LogUserOut(makeSessionClaims(session)) require.NoError(t, err) assert.Len(t, service.cache, 0) } func TestLogOutUserDbError(t *testing.T) { sessionRepositoryMock, service, _, _ := setupTest(t) - sessionRepositoryMock.On("Delete", mock.Anything).Return(httperrors.NewInternalServerError("db errors", "oh we failed to delete the session", nil)) - response := httptest.NewRecorder() - uuidSample := uuid.New() + sessionRepositoryMock. + On("Delete", gormDB, mock.Anything). + Return(errors.New("db errors")) + + uuidSample := badorm.NewUUID() session := &models.Session{ - BaseModel: models.BaseModel{ + UUIDModel: badorm.UUIDModel{ ID: uuidSample, }, - UserID: uuid.Nil, + UserID: badorm.NilUUID, ExpiresAt: time.Now().Add(time.Hour), } service.cache[uuidSample] = session - err := service.LogUserOut(makeSessionClaims(session), response) + err := service.LogUserOut(makeSessionClaims(session)) require.Error(t, err) assert.Len(t, service.cache, 1) } @@ -156,38 +174,41 @@ func TestLogOutUserDbError(t *testing.T) { func TestLogOutUser_SessionNotFound(t *testing.T) { sessionRepositoryMock, service, _, _ := setupTest(t) sessionRepositoryMock. - On("Find", mock.Anything, nil, nil). - Return(nil, httperrors.NewInternalServerError("db errors", "oh we failed to delete the session", nil)) - response := httptest.NewRecorder() - uuidSample := uuid.New() + On("GetByID", gormDB, mock.Anything). + Return(nil, errors.New("db errors")) + + uuidSample := badorm.NewUUID() session := &models.Session{ - BaseModel: models.BaseModel{ - ID: uuid.Nil, + UUIDModel: badorm.UUIDModel{ + ID: badorm.NilUUID, }, - UserID: uuid.Nil, + UserID: badorm.NilUUID, ExpiresAt: time.Now().Add(time.Hour), } service.cache[uuidSample] = session sessionClaims := makeSessionClaims(session) - sessionClaims.SessionUUID = uuid.Nil - err := service.LogUserOut(sessionClaims, response) + sessionClaims.SessionUUID = badorm.NilUUID + err := service.LogUserOut(sessionClaims) require.Error(t, err) assert.Len(t, service.cache, 1) } func TestRollSession(t *testing.T) { sessionRepositoryMock, service, _, sessionConfigurationMock := setupTest(t) - sessionRepositoryMock.On("Save", mock.Anything).Return(nil) + sessionRepositoryMock.On("Save", gormDB, mock.Anything).Return(nil) + sessionDuration := time.Minute + sessionConfigurationMock.On("GetSessionDuration").Return(sessionDuration) sessionConfigurationMock.On("GetRollDuration").Return(sessionDuration / 4) - uuidSample := uuid.New() + + uuidSample := badorm.NewUUID() originalExpirationTime := time.Now().Add(sessionDuration / 5) session := &models.Session{ - BaseModel: models.BaseModel{ - ID: uuid.Nil, + UUIDModel: badorm.UUIDModel{ + ID: badorm.NilUUID, }, - UserID: uuid.Nil, + UserID: badorm.NilUUID, ExpiresAt: originalExpirationTime, } service.cache[uuidSample] = session @@ -201,13 +222,14 @@ func TestRollSession_Expired(t *testing.T) { sessionDuration := time.Minute sessionConfigurationMock.On("GetSessionDuration").Return(sessionDuration) sessionConfigurationMock.On("GetRollDuration").Return(sessionDuration / 4) - uuidSample := uuid.New() + + uuidSample := badorm.NewUUID() originalExpirationTime := time.Now().Add(-time.Hour) session := &models.Session{ - BaseModel: models.BaseModel{ + UUIDModel: badorm.UUIDModel{ ID: uuidSample, }, - UserID: uuid.Nil, + UserID: badorm.NilUUID, ExpiresAt: originalExpirationTime, } service.cache[uuidSample] = session @@ -221,46 +243,49 @@ func TestRollSession_falseUUID(t *testing.T) { sessionConfigurationMock.On("GetSessionDuration").Return(sessionDuration) sessionConfigurationMock.On("GetRollDuration").Return(sessionDuration / 4) - uuidSample := uuid.New() + uuidSample := badorm.NewUUID() originalExpirationTime := time.Now().Add(-time.Hour) session := &models.Session{ - BaseModel: models.BaseModel{ - ID: uuid.Nil, + UUIDModel: badorm.UUIDModel{ + ID: badorm.NilUUID, }, - UserID: uuid.Nil, + UserID: badorm.NilUUID, ExpiresAt: originalExpirationTime, } service.cache[uuidSample] = session - repoSession.On("Find", mock.Anything, nil, nil).Return(pagination.NewPage([]*models.Session{}, 0, 2, 5), nil) - err := service.RollSession(uuid.New()) + + repoSession. + On("GetByID", gormDB, mock.Anything). + Return(nil, nil) + + err := service.RollSession(badorm.NewUUID()) require.NoError(t, err) } func TestRollSession_sessionNotFound(t *testing.T) { sessionRepositoryMock, service, _, sessionConfigurationMock := setupTest(t) sessionRepositoryMock. - On("Find", squirrel.Eq{"uuid": "00000000-0000-0000-0000-000000000000"}, nil, nil). - Return( - pagination.NewPage([]*models.Session{}, 0, 10, 0), nil) + On("GetByID", gormDB, badorm.NilUUID). + Return(nil, nil) sessionDuration := time.Minute sessionConfigurationMock.On("GetSessionDuration").Return(sessionDuration) sessionConfigurationMock.On("GetRollDuration").Return(sessionDuration) - err := service.RollSession(uuid.Nil) + err := service.RollSession(badorm.NilUUID) require.NoError(t, err) } func Test_pullFromDB(t *testing.T) { sessionRepositoryMock, service, logs, _ := setupTest(t) session := &models.Session{ - BaseModel: models.BaseModel{ - ID: uuid.Nil, + UUIDModel: badorm.UUIDModel{ + ID: badorm.NilUUID, }, - UserID: uuid.Nil, + UserID: badorm.NilUUID, ExpiresAt: time.Now().Add(time.Hour), } - sessionRepositoryMock.On("GetAll", nil).Return([]*models.Session{session}, nil) + sessionRepositoryMock.On("Query", gormDB).Return([]*models.Session{session}, nil) service.pullFromDB() assert.Len(t, service.cache, 1) @@ -274,23 +299,24 @@ func Test_pullFromDB(t *testing.T) { func Test_pullFromDB_repoError(t *testing.T) { sessionRepositoryMock, service, _, _ := setupTest(t) - sessionRepositoryMock.On("GetAll", nil).Return(nil, httperrors.AnError) - assert.PanicsWithError(t, httperrors.AnError.Error(), func() { service.pullFromDB() }) + sessionRepositoryMock.On("Query", gormDB).Return(nil, ExampleErr) + assert.PanicsWithError(t, ExampleErr.Error(), func() { service.pullFromDB() }) } func Test_removeExpired(t *testing.T) { sessionRepositoryMock, service, logs, _ := setupTest(t) - uuidSample := uuid.New() + uuidSample := badorm.NewUUID() session := &models.Session{ - BaseModel: models.BaseModel{ - ID: uuid.Nil, + UUIDModel: badorm.UUIDModel{ + ID: badorm.NilUUID, }, - UserID: uuid.Nil, + UserID: badorm.NilUUID, ExpiresAt: time.Now().Add(-time.Hour), } sessionRepositoryMock. - On("Delete", session). + On("Delete", gormDB, session). Return(nil) + service.cache[uuidSample] = session service.removeExpired() @@ -305,17 +331,18 @@ func Test_removeExpired(t *testing.T) { func Test_removeExpired_RepositoryError(t *testing.T) { sessionRepositoryMock, service, _, _ := setupTest(t) - uuidSample := uuid.New() + uuidSample := badorm.NewUUID() session := &models.Session{ - BaseModel: models.BaseModel{ - ID: uuid.Nil, + UUIDModel: badorm.UUIDModel{ + ID: badorm.NilUUID, }, - UserID: uuid.Nil, + UserID: badorm.NilUUID, ExpiresAt: time.Now().Add(-time.Hour), } sessionRepositoryMock. - On("Delete", session). - Return(httperrors.AnError) + On("Delete", gormDB, session). + Return(ExampleErr) + service.cache[uuidSample] = session assert.Panics(t, func() { service.removeExpired() }) @@ -323,17 +350,17 @@ func Test_removeExpired_RepositoryError(t *testing.T) { func Test_get(t *testing.T) { sessionRepositoryMock, service, _, _ := setupTest(t) - uuidSample := uuid.New() + uuidSample := badorm.NewUUID() session := &models.Session{ - BaseModel: models.BaseModel{ - ID: uuid.Nil, + UUIDModel: badorm.UUIDModel{ + ID: badorm.NilUUID, }, - UserID: uuid.Nil, + UserID: badorm.NilUUID, ExpiresAt: time.Now().Add(-time.Hour), } sessionRepositoryMock. - On("Find", mock.Anything, nil, nil). - Return(pagination.NewPage([]*models.Session{session}, 0, 12, 13), nil) + On("GetByID", gormDB, mock.Anything). + Return(session, nil) sessionFound := service.get(uuidSample) assert.Equal(t, sessionFound, session) diff --git a/services/sessionservice/sessionctx.go b/services/sessionservice/sessionctx.go index 6710766a..0a6d9643 100644 --- a/services/sessionservice/sessionctx.go +++ b/services/sessionservice/sessionctx.go @@ -3,14 +3,14 @@ package sessionservice import ( "context" + "github.com/ditrit/badaas/badorm" "github.com/ditrit/badaas/persistence/models" - "github.com/google/uuid" ) // The session claims passed in the request context type SessionClaims struct { - UserID uuid.UUID - SessionUUID uuid.UUID + UserID badorm.UUID + SessionUUID badorm.UUID } // Unique claim key type @@ -41,5 +41,6 @@ func GetSessionClaimsFromContext(ctx context.Context) *SessionClaims { if !ok { panic("could not extract claims from context") } + return claims } diff --git a/services/sessionservice/sessionctx_test.go b/services/sessionservice/sessionctx_test.go index 88b3f954..e2b7198f 100644 --- a/services/sessionservice/sessionctx_test.go +++ b/services/sessionservice/sessionctx_test.go @@ -4,19 +4,21 @@ import ( "context" "testing" - "github.com/google/uuid" "github.com/stretchr/testify/assert" + + "github.com/ditrit/badaas/badorm" ) func TestSessionCtx(t *testing.T) { ctx := context.Background() - sessionClaims := &SessionClaims{uuid.Nil, uuid.New()} + sessionClaims := &SessionClaims{badorm.NilUUID, badorm.NewUUID()} ctx = SetSessionClaimsContext(ctx, sessionClaims) claims := GetSessionClaimsFromContext(ctx) - assert.Equal(t, uuid.Nil, claims.UserID) + assert.Equal(t, badorm.NilUUID, claims.UserID) } func TestSessionCtxPanic(t *testing.T) { ctx := context.Background() + assert.Panics(t, func() { GetSessionClaimsFromContext(ctx) }) } diff --git a/services/userservice/userservice.go b/services/userservice/userservice.go index 3f3da399..efeb31c4 100644 --- a/services/userservice/userservice.go +++ b/services/userservice/userservice.go @@ -1,82 +1,91 @@ package userservice import ( + "errors" "fmt" - "github.com/Masterminds/squirrel" - "github.com/ditrit/badaas/httperrors" + "go.uber.org/zap" + "gorm.io/gorm" + + "github.com/ditrit/badaas/badorm" "github.com/ditrit/badaas/persistence/models" "github.com/ditrit/badaas/persistence/models/dto" - "github.com/ditrit/badaas/persistence/repository" "github.com/ditrit/badaas/services/auth/protocols/basicauth" - validator "github.com/ditrit/badaas/validators" - "github.com/google/uuid" - "go.uber.org/zap" + "github.com/ditrit/badaas/utils/validators" ) // UserService provide functions related to Users type UserService interface { NewUser(username, email, password string) (*models.User, error) - GetUser(dto.UserLoginDTO) (*models.User, httperrors.HTTPError) + GetUser(dto.UserLoginDTO) (*models.User, error) } +var ErrWrongPassword = errors.New("password is incorrect") + // Check interface compliance var _ UserService = (*userServiceImpl)(nil) // The UserService concrete implementation type userServiceImpl struct { - userRepository repository.CRUDRepository[models.User, uuid.UUID] + userRepository badorm.CRUDRepository[models.User, badorm.UUID] logger *zap.Logger + db *gorm.DB } // UserService constructor func NewUserService( logger *zap.Logger, - userRepository repository.CRUDRepository[models.User, uuid.UUID], + userRepository badorm.CRUDRepository[models.User, badorm.UUID], + db *gorm.DB, ) UserService { return &userServiceImpl{ logger: logger, userRepository: userRepository, + db: db, } } // Create a new user func (userService *userServiceImpl) NewUser(username, email, password string) (*models.User, error) { - sanitizedEmail, err := validator.ValidEmail(email) + sanitizedEmail, err := validators.ValidEmail(email) if err != nil { return nil, fmt.Errorf("the provided email is not valid") } + u := &models.User{ Username: username, Email: sanitizedEmail, Password: basicauth.SaltAndHashPassword(password), } - httpError := userService.userRepository.Create(u) - if httpError != nil { - return nil, httpError + + err = userService.userRepository.Create(userService.db, u) + if err != nil { + return nil, err } - userService.logger.Info("Successfully created a new user", - zap.String("email", sanitizedEmail), zap.String("username", username)) + + userService.logger.Info( + "Successfully created a new user", + zap.String("email", sanitizedEmail), + zap.String("username", username), + ) return u, nil } // Get user if the email and password provided are correct, return an error if not. -func (userService *userServiceImpl) GetUser(userLoginDTO dto.UserLoginDTO) (*models.User, httperrors.HTTPError) { - users, herr := userService.userRepository.Find(squirrel.Eq{"email": userLoginDTO.Email}, nil, nil) - if herr != nil { - return nil, herr - } - if !users.HasContent { - return nil, httperrors.NewErrorNotFound("user", - fmt.Sprintf("no user found with email %q", userLoginDTO.Email)) +func (userService *userServiceImpl) GetUser(userLoginDTO dto.UserLoginDTO) (*models.User, error) { + user, err := userService.userRepository.QueryOne( + userService.db, + models.UserEmailCondition(badorm.Eq(userLoginDTO.Email)), + ) + if err != nil { + return nil, err } - user := users.Ressources[0] - // Check password if !basicauth.CheckUserPassword(user.Password, userLoginDTO.Password) { - return nil, httperrors.NewUnauthorizedError("wrong password", "the provided password is incorrect") + return nil, ErrWrongPassword } + return user, nil } diff --git a/services/userservice/userservice_test.go b/services/userservice/userservice_test.go index df435ed8..b7440e7b 100644 --- a/services/userservice/userservice_test.go +++ b/services/userservice/userservice_test.go @@ -3,29 +3,32 @@ package userservice_test import ( "testing" - "github.com/ditrit/badaas/httperrors" - repositorymocks "github.com/ditrit/badaas/mocks/persistence/repository" - "github.com/ditrit/badaas/persistence/models" - "github.com/ditrit/badaas/persistence/models/dto" - "github.com/ditrit/badaas/persistence/pagination" - "github.com/ditrit/badaas/services/userservice" - "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "go.uber.org/zap" "go.uber.org/zap/zapcore" "go.uber.org/zap/zaptest/observer" + "gorm.io/gorm" + + "github.com/ditrit/badaas/badorm" + badormMocks "github.com/ditrit/badaas/mocks/badorm" + "github.com/ditrit/badaas/persistence/models" + "github.com/ditrit/badaas/persistence/models/dto" + "github.com/ditrit/badaas/services/userservice" ) +var gormDB *gorm.DB + func TestNewUserService(t *testing.T) { // creating logger observedZapCore, observedLogs := observer.New(zap.DebugLevel) observedLogger := zap.New(observedZapCore) - userRespositoryMock := repositorymocks.NewCRUDRepository[models.User, uuid.UUID](t) - userRespositoryMock.On("Create", mock.Anything).Return(nil) - userService := userservice.NewUserService(observedLogger, userRespositoryMock) + userRepositoryMock := badormMocks.NewCRUDRepository[models.User, badorm.UUID](t) + userRepositoryMock.On("Create", gormDB, mock.Anything).Return(nil) + + userService := userservice.NewUserService(observedLogger, userRepositoryMock, gormDB) user, err := userService.NewUser("bob", "bob@email.com", "1234") assert.NoError(t, err) assert.NotNil(t, user) @@ -49,13 +52,14 @@ func TestNewUserServiceDatabaseError(t *testing.T) { observedZapCore, observedLogs := observer.New(zap.DebugLevel) observedLogger := zap.New(observedZapCore) - userRespositoryMock := repositorymocks.NewCRUDRepository[models.User, uuid.UUID](t) - userRespositoryMock.On( - "Create", mock.Anything, + userRepositoryMock := badormMocks.NewCRUDRepository[models.User, badorm.UUID](t) + userRepositoryMock.On( + "Create", gormDB, mock.Anything, ).Return( - httperrors.NewInternalServerError("database error", "test error", nil), + gorm.ErrInvalidTransaction, ) - userService := userservice.NewUserService(observedLogger, userRespositoryMock) + + userService := userservice.NewUserService(observedLogger, userRepositoryMock, gormDB) user, err := userService.NewUser("bob", "bob@email.com", "1234") assert.Error(t, err) assert.Nil(t, user) @@ -69,9 +73,9 @@ func TestNewUserServiceEmailNotValid(t *testing.T) { observedZapCore, observedLogs := observer.New(zap.DebugLevel) observedLogger := zap.New(observedZapCore) - userRespositoryMock := repositorymocks.NewCRUDRepository[models.User, uuid.UUID](t) + userRepositoryMock := badormMocks.NewCRUDRepository[models.User, badorm.UUID](t) - userService := userservice.NewUserService(observedLogger, userRespositoryMock) + userService := userservice.NewUserService(observedLogger, userRepositoryMock, gormDB) user, err := userService.NewUser("bob", "bob@", "1234") assert.Error(t, err) assert.Nil(t, user) @@ -85,20 +89,19 @@ func TestGetUser(t *testing.T) { observedZapCore, observedLogs := observer.New(zap.DebugLevel) observedLogger := zap.New(observedZapCore) - userRespositoryMock := repositorymocks.NewCRUDRepository[models.User, uuid.UUID](t) - userService := userservice.NewUserService(observedLogger, userRespositoryMock) - userRespositoryMock.On( - "Create", mock.Anything, - ).Return( - nil, - ) + userRepositoryMock := badormMocks.NewCRUDRepository[models.User, badorm.UUID](t) + userService := userservice.NewUserService(observedLogger, userRepositoryMock, gormDB) + userRepositoryMock.On( + "Create", gormDB, mock.Anything, + ).Return(nil) + user, err := userService.NewUser("bob", "bob@email.com", "1234") require.NoError(t, err) - userRespositoryMock.On( - "Find", mock.Anything, nil, nil, + userRepositoryMock.On( + "QueryOne", gormDB, models.UserEmailCondition(badorm.Eq("bob@email.com")), ).Return( - pagination.NewPage([]*models.User{user}, 1, 10, 50), + user, nil, ) @@ -121,13 +124,13 @@ func TestGetUserNoUserFound(t *testing.T) { observedZapCore, _ := observer.New(zap.DebugLevel) observedLogger := zap.New(observedZapCore) - userRespositoryMock := repositorymocks.NewCRUDRepository[models.User, uuid.UUID](t) - userService := userservice.NewUserService(observedLogger, userRespositoryMock) - userRespositoryMock.On( - "Find", mock.Anything, nil, nil, + userRepositoryMock := badormMocks.NewCRUDRepository[models.User, badorm.UUID](t) + userService := userservice.NewUserService(observedLogger, userRepositoryMock, gormDB) + userRepositoryMock.On( + "QueryOne", gormDB, models.UserEmailCondition(badorm.Eq("bobnotfound@email.com")), ).Return( - nil, - httperrors.NewErrorNotFound("user", "user with email bobnotfound@email.com"), + &models.User{}, + badorm.ErrObjectNotFound, ) userFound, err := userService.GetUser(dto.UserLoginDTO{Email: "bobnotfound@email.com", Password: "1234"}) @@ -136,53 +139,24 @@ func TestGetUserNoUserFound(t *testing.T) { } // Check what happen if the pass word is not correct -func TestGetUserNotCorrect(t *testing.T) { +func TestGetUserWrongPassword(t *testing.T) { // creating logger observedZapCore, _ := observer.New(zap.DebugLevel) observedLogger := zap.New(observedZapCore) - userRespositoryMock := repositorymocks.NewCRUDRepository[models.User, uuid.UUID](t) - userRespositoryMock.On( - "Create", mock.Anything, - ).Return( - nil, - ) - userService := userservice.NewUserService(observedLogger, userRespositoryMock) - user, err := userService.NewUser("bob", "bob@email.com", "1234") - - require.NoError(t, err) - userRespositoryMock.On( - "Find", mock.Anything, nil, nil, - ).Return( - pagination.NewPage([]*models.User{user}, 1, 10, 50), - nil, - ) - - userFound, err := userService.GetUser(dto.UserLoginDTO{Email: "bob@email.com", Password: " ../ + +require ( + github.com/Masterminds/semver/v3 v3.2.1 + github.com/cucumber/godog v0.12.5 + github.com/cucumber/messages-go/v16 v16.0.1 + github.com/ditrit/badaas v0.0.0-20230607151244-ded438ad025c + github.com/ditrit/verdeter v0.4.0 + github.com/elliotchance/pie/v2 v2.5.2 + github.com/gorilla/handlers v1.5.1 + github.com/gorilla/mux v1.8.0 + github.com/spf13/cobra v1.7.0 + github.com/spf13/pflag v1.0.5 + github.com/spf13/viper v1.16.0 + go.uber.org/fx v1.19.3 + go.uber.org/zap v1.24.0 + gorm.io/gorm v1.25.1 +) + +require ( + github.com/Masterminds/squirrel v1.5.4 // indirect + github.com/cucumber/gherkin-go/v19 v19.0.3 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/felixge/httpsnoop v1.0.1 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/go-sql-driver/mysql v1.7.0 // indirect + github.com/gofrs/uuid v4.0.0+incompatible // indirect + github.com/google/go-cmp v0.5.9 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/goph/emperror v0.17.2 // indirect + github.com/hashicorp/go-immutable-radix v1.3.1 // indirect + github.com/hashicorp/go-memdb v1.3.3 // indirect + github.com/hashicorp/golang-lru v0.5.4 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jackc/chunkreader/v2 v2.0.1 // indirect + github.com/jackc/pgconn v1.14.0 // indirect + github.com/jackc/pgio v1.0.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgproto3/v2 v2.3.2 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgx/v5 v5.3.1 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect + github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/noirbizarre/gonja v0.0.0-20200629003239-4d051fd0be61 // indirect + github.com/pelletier/go-toml/v2 v2.0.8 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/sirupsen/logrus v1.9.2 // indirect + github.com/spf13/afero v1.9.5 // indirect + github.com/spf13/cast v1.5.1 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/stretchr/testify v1.8.4 // indirect + github.com/subosito/gotenv v1.4.2 // indirect + go.uber.org/atomic v1.11.0 // indirect + go.uber.org/dig v1.17.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/crypto v0.9.0 // indirect + golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect + golang.org/x/sys v0.8.0 // indirect + golang.org/x/text v0.9.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + gorm.io/driver/mysql v1.5.1 // indirect + gorm.io/driver/postgres v1.5.2 // indirect + gotest.tools v2.2.0+incompatible // indirect +) diff --git a/test_e2e/go.sum b/test_e2e/go.sum new file mode 100644 index 00000000..84fb6afc --- /dev/null +++ b/test_e2e/go.sum @@ -0,0 +1,840 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= +github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= +github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= +github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/airbrake/gobrake v3.6.1+incompatible/go.mod h1:wM4gu3Cn0W0K7GUuVWnlXZU11AGBXMILnrdOU8Kn00o= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= +github.com/bmuller/arrow v0.0.0-20180318014521-b14bfde8dff2/go.mod h1:+voQMVaya0tr8p3W33Qxj/dKOjZNCepW+k8JJvt91gk= +github.com/bugsnag/bugsnag-go v1.4.0/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= +github.com/bugsnag/panicwrap v1.2.0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/certifi/gocertifi v0.0.0-20190105021004-abcd57078448/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/cucumber/gherkin-go/v19 v19.0.3 h1:mMSKu1077ffLbTJULUfM5HPokgeBcIGboyeNUof1MdE= +github.com/cucumber/gherkin-go/v19 v19.0.3/go.mod h1:jY/NP6jUtRSArQQJ5h1FXOUgk5fZK24qtE7vKi776Vw= +github.com/cucumber/godog v0.12.5 h1:FZIy6VCfMbmGHts9qd6UjBMT9abctws/pQYO/ZcwOVs= +github.com/cucumber/godog v0.12.5/go.mod h1:u6SD7IXC49dLpPN35kal0oYEjsXZWee4pW6Tm9t5pIc= +github.com/cucumber/messages-go/v16 v16.0.0/go.mod h1:EJcyR5Mm5ZuDsKJnT2N9KRnBK30BGjtYotDKpwQ0v6g= +github.com/cucumber/messages-go/v16 v16.0.1 h1:fvkpwsLgnIm0qugftrw2YwNlio+ABe2Iu94Ap8GMYIY= +github.com/cucumber/messages-go/v16 v16.0.1/go.mod h1:EJcyR5Mm5ZuDsKJnT2N9KRnBK30BGjtYotDKpwQ0v6g= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/ditrit/verdeter v0.4.0 h1:DzEOFauuXEGNQYP6OgYtHwEyb3w9riem99u0xE/l7+o= +github.com/ditrit/verdeter v0.4.0/go.mod h1:sKpWuOvYqNabLN4aNXqeBhcWpt7nf0frwqk0B5M6ax0= +github.com/elliotchance/pie/v2 v2.5.2 h1:jRENMmysCljhUmyT8ITKV0Atp6Lukm3XpeqaI87POsM= +github.com/elliotchance/pie/v2 v2.5.2/go.mod h1:18t0dgGFH006g4eVdDtWfgFZPQEgl10IoEO8YWEq3Og= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= +github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-check/check v0.0.0-20180628173108-788fd7840127 h1:0gkP6mzaMqkmpcJYCFOLkIBwI7xFExG03bbkOkCvUPI= +github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/goph/emperror v0.17.1/go.mod h1:+ZbQ+fUNO/6FNiUo0ujtMjhgad9Xa6fQL9KhH4LNHic= +github.com/goph/emperror v0.17.2 h1:yLapQcmEsO0ipe9p5TaN22djm3OFV/TfM/fcYP0/J18= +github.com/goph/emperror v0.17.2/go.mod h1:+ZbQ+fUNO/6FNiUo0ujtMjhgad9Xa6fQL9KhH4LNHic= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= +github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-memdb v1.3.0/go.mod h1:Mluclgwib3R93Hk5fxEfiRhB+6Dar64wWh71LpNSe3g= +github.com/hashicorp/go-memdb v1.3.3 h1:oGfEWrFuxtIUF3W2q/Jzt6G85TrMk9ey6XfYLvVe1Wo= +github.com/hashicorp/go-memdb v1.3.3/go.mod h1:uBTr1oQbtuMgd1SSGoR8YV27eT3sBHbYiNm53bMpgSg= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= +github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= +github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= +github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= +github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= +github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= +github.com/jackc/pgconn v1.14.0 h1:vrbA9Ud87g6JdFWkHTJXppVce58qPIdP7N8y0Ml/A7Q= +github.com/jackc/pgconn v1.14.0/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= +github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= +github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.3.2 h1:7eY55bdBeCz1F2fTzSz69QC+pG46jYq9/jtSPiJ5nn0= +github.com/jackc/pgproto3/v2 v2.3.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= +github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= +github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= +github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= +github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/pgx/v5 v5.3.1 h1:Fcr8QJ1ZeLi5zsPZqQeUZhNhxfkkKBOgJuYkJHoBOtU= +github.com/jackc/pgx/v5 v5.3.1/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8= +github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/noirbizarre/gonja v0.0.0-20200629003239-4d051fd0be61 h1:8HaKr2WO2B5XKEFbJE9Z7W8mWC6+dL3jZCw53Dbl0oI= +github.com/noirbizarre/gonja v0.0.0-20200629003239-4d051fd0be61/go.mod h1:WboHq+I9Ck8PwKsVFJNrpiRyngXhquRSTWBGwuSWOrg= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= +github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rollbar/rollbar-go v1.0.2/go.mod h1:AcFs5f0I+c71bpHlXNNDbOWJiKwjFDtISeXco0L5PKQ= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y= +github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= +github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= +github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= +github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= +github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= +github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg= +github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/dig v1.17.0 h1:5Chju+tUvcC+N7N6EV08BJz41UZuO3BmHcN4A287ZLI= +go.uber.org/dig v1.17.0/go.mod h1:rTxpf7l5I0eBTlE6/9RL+lDybC7WFwY2QH55ZSjy1mU= +go.uber.org/fx v1.19.3 h1:YqMRE4+2IepTYCMOvXqQpRa+QAVdiSTnsHU4XNWBceA= +go.uber.org/fx v1.19.3/go.mod h1:w2HrQg26ql9fLK7hlBiZ6JsRUKV+Lj/atT1KCjT8YhM= +go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= +go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc= +golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/mysql v1.5.1 h1:WUEH5VF9obL/lTtzjmML/5e6VfFR/788coz2uaVCAZw= +gorm.io/driver/mysql v1.5.1/go.mod h1:Jo3Xu7mMhCyj8dlrb3WoCaRd1FhsVh+yMXb1jUInf5o= +gorm.io/driver/postgres v1.5.2 h1:ytTDxxEv+MplXOfFe3Lzm7SjG09fcdb3Z/c056DTBx0= +gorm.io/driver/postgres v1.5.2/go.mod h1:fmpX0m2I1PKuR7mKZiEluwrP3hbs+ps7JIGMUBpCgl8= +gorm.io/gorm v1.25.1 h1:nsSALe5Pr+cM3V1qwwQ7rOkw+6UeLrX5O4v3llhHa64= +gorm.io/gorm v1.25.1/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/test_e2e/http_support_test.go b/test_e2e/http_support_test.go new file mode 100644 index 00000000..d4dc8afb --- /dev/null +++ b/test_e2e/http_support_test.go @@ -0,0 +1,394 @@ +package main + +import ( + "encoding/json" + "fmt" + "io" + "log" + "net/http" + "strconv" + "strings" + + "github.com/cucumber/godog" + "github.com/cucumber/messages-go/v16" + "github.com/elliotchance/pie/v2" + + "github.com/ditrit/badaas/testintegration/models" +) + +const BaseURL = "http://localhost:8000" + +func (t *TestContext) requestGet(url string) error { + return t.request(url, http.MethodGet, nil) +} + +func (t *TestContext) requestWithJSON(url, method string, jsonTable *godog.Table) error { + return t.request(url, method, jsonTable) +} + +func (t *TestContext) request(url, method string, jsonTable *godog.Table) error { + var payload io.Reader + + if jsonTable != nil { + payload = buildJSONFromTable(jsonTable) + } + + method, err := checkMethod(method) + if err != nil { + return err + } + + request, err := http.NewRequest(method, BaseURL+url, payload) + if err != nil { + return fmt.Errorf("failed to build request ERROR=%s", err.Error()) + } + + response, err := t.httpClient.Do(request) + if err != nil { + return fmt.Errorf("failed to run request ERROR=%s", err.Error()) + } + + t.storeResponseInContext(response) + + return nil +} + +func (t *TestContext) storeResponseInContext(response *http.Response) { + t.statusCode = response.StatusCode + + err := json.NewDecoder(response.Body).Decode(&t.json) + if err != nil { + t.json = map[string]any{} + } + + err = response.Body.Close() + if err != nil { + log.Fatalln(err) + } +} + +func (t *TestContext) assertStatusCode(expectedStatusCode int) error { + if t.statusCode != expectedStatusCode { + return fmt.Errorf("expect status code %d but is %d", expectedStatusCode, t.statusCode) + } + + return nil +} + +func (t *TestContext) assertResponseFieldIsEquals(field string, expectedValue string) error { + fields := strings.Split(field, ".") + + jsonMap, ok := t.json.(map[string]any) + if !ok { + log.Fatalln("json is not a map") + } + + for _, field := range fields[:len(fields)-1] { + intValue, present := jsonMap[field] + if !present { + return fmt.Errorf("expected response field %s to be %s but it is not present", field, expectedValue) + } + + intValueMap, ok := intValue.(map[string]any) + if !ok { + log.Fatalln("intValue is not a map") + } + + jsonMap = intValueMap + } + + lastValue, present := jsonMap[pie.Last(fields)] + if !present { + return fmt.Errorf("expected response field %s to be %s but it is not present", field, expectedValue) + } + + if !assertValue(lastValue, expectedValue) { + return fmt.Errorf("expected response field %s to be %s but is %v", field, expectedValue, lastValue) + } + + return nil +} + +func assertValue(value any, expectedValue string) bool { + switch value.(type) { + case string: + return expectedValue == value + case int: + expectedValueInt, err := strconv.Atoi(expectedValue) + if err != nil { + panic(err) + } + + return expectedValueInt == value + case float64: + expectedValueFloat, err := strconv.ParseFloat(expectedValue, 64) + if err != nil { + panic(err) + } + + return expectedValueFloat == value + default: + panic("unsupported format") + } +} + +func verifyHeader(row *messages.PickleTableRow) error { + for indexCell, cell := range row.Cells { + if cell.Value != []string{"key", "value", "type"}[indexCell] { + return fmt.Errorf("should have %q as first line of the table", "| key | value | type |") + } + } + + return nil +} + +func getTableValue(key, valueAsString, valueType string) (any, error) { + switch valueType { + case stringValueType: + return valueAsString, nil + case booleanValueType: + boolean, err := strconv.ParseBool(valueAsString) + if err != nil { + return nil, fmt.Errorf("can't parse %q as boolean for key %q", valueAsString, key) + } + + return boolean, nil + case integerValueType: + integer, err := strconv.ParseInt(valueAsString, 10, 64) + if err != nil { + return nil, fmt.Errorf("can't parse %q as integer for key %q", valueAsString, key) + } + + return integer, nil + case floatValueType: + floatingNumber, err := strconv.ParseFloat(valueAsString, 64) + if err != nil { + return nil, fmt.Errorf("can't parse %q as float for key %q", valueAsString, key) + } + + return floatingNumber, nil + case jsonValueType: + jsonMap := map[string]string{} + + err := json.Unmarshal([]byte(valueAsString), &jsonMap) + if err != nil { + return nil, fmt.Errorf("can't parse %q as json for key %q", valueAsString, key) + } + + return jsonMap, nil + default: + return nil, fmt.Errorf( + "type %q does not exists, please use %v", + valueType, + []string{stringValueType, booleanValueType, integerValueType, floatValueType}, + ) + } +} + +// build a map from a godog.Table +func buildMapFromTable(table *godog.Table) (map[string]any, error) { + data := make(map[string]any, 0) + + err := verifyHeader(table.Rows[0]) + if err != nil { + return nil, err + } + + for _, row := range table.Rows[1:] { + key := row.Cells[0].Value + valueAsString := row.Cells[1].Value + valueType := row.Cells[2].Value + + value, err := getTableValue(key, valueAsString, valueType) + if err != nil { + return nil, err + } + + data[key] = value + } + + return data, nil +} + +// build a json payload in the form of a reader from a godog.Table +func buildJSONFromTable(table *godog.Table) io.Reader { + data, err := buildMapFromTable(table) + if err != nil { + panic("should not return an error") + } + + bytes, err := json.Marshal(data) + if err != nil { + panic("should not return an error") + } + + return strings.NewReader(string(bytes)) +} + +const ( + stringValueType = "string" + booleanValueType = "boolean" + integerValueType = "integer" + floatValueType = "float" + jsonValueType = "json" +) + +// check if the method is allowed and sanitize the string +func checkMethod(method string) (string, error) { + allowedMethods := []string{ + http.MethodGet, + http.MethodHead, + http.MethodPost, + http.MethodPut, + http.MethodPatch, + http.MethodDelete, + http.MethodConnect, + http.MethodOptions, + http.MethodTrace, + } + + sanitizedMethod := strings.TrimSpace(strings.ToUpper(method)) + if !pie.Contains( + allowedMethods, + sanitizedMethod, + ) { + return "", fmt.Errorf("%q is not a valid HTTP method (please choose between %v)", method, allowedMethods) + } + + return sanitizedMethod, nil +} + +func (t *TestContext) objectExists(entityType string, jsonTable *godog.Table) error { + err := t.request( + "/eav/objects/"+entityType, + http.MethodPost, + jsonTable, + ) + if err != nil { + return err + } + + err = t.assertStatusCode(http.StatusCreated) + if err != nil { + return err + } + + return nil +} + +func (t *TestContext) objectExistsWithRelation(entityType string, relationAttribute string, jsonTable *godog.Table) error { + jsonTable.Rows = append(jsonTable.Rows, &messages.PickleTableRow{ + Cells: []*messages.PickleTableCell{ + { + Value: relationAttribute, + }, + { + Value: t.getIDFromJSON(), + }, + { + Value: stringValueType, + }, + }, + }) + + return t.objectExists(entityType, jsonTable) +} + +func (t *TestContext) getIDFromJSON() string { + id, present := t.json.(map[string]any)["id"] + if !present { + log.Fatalln("object id not available") + } + + idString, ok := id.(string) + if !ok { + log.Fatalln("id in json is not a string") + } + + return idString +} + +func (t *TestContext) saleExists(productInt int, code int, description string) { + product := &models.Product{ + Int: productInt, + } + + sale := &models.Sale{ + Code: code, + Description: description, + Product: *product, + } + + if err := t.db.Create(sale).Error; err != nil { + log.Fatalln(err) + } +} + +func (t *TestContext) querySalesWithConditions(jsonTable *godog.Table) error { + err := t.requestWithJSON( + "/objects/sale", + http.MethodGet, + jsonTable, + ) + if err != nil { + return err + } + + err = t.assertStatusCode(http.StatusOK) + if err != nil { + return err + } + + return nil +} + +func (t *TestContext) thereIsSaleWithAttributes(jsonTable *godog.Table) error { + expectedValues, err := buildMapFromTable(jsonTable) + if err != nil { + log.Fatalln(err) + } + + objectMapList := t.getObjectMapListFromJSON() + for _, objectMap := range objectMapList { + if t.areAllAttributesEqual(objectMap, expectedValues) { + return nil + } + } + + return fmt.Errorf("object with attributes %v not found in %v", expectedValues, objectMapList) +} + +func (t *TestContext) getListFromJSON() []any { + objectList, ok := t.json.([]any) + if !ok { + log.Fatalln("json is not a list") + } + + return objectList +} + +func (t *TestContext) getObjectMapListFromJSON() []map[string]any { + objectList := t.getListFromJSON() + + return pie.Map(objectList, func(object any) map[string]any { + objectMap, ok := object.(map[string]any) + if !ok { + log.Fatalln("object in json list is not a map") + } + + return objectMap + }) +} + +func (t *TestContext) areAllAttributesEqual(objectMap, expectedValues map[string]any) bool { + allEqual := true + + for attributeName, expectedValue := range expectedValues { + actualValue, isPresent := objectMap[attributeName] + if !isPresent || actualValue != expectedValue { + allEqual = false + } + } + + return allEqual +} diff --git a/test_e2e/test_api.go b/test_e2e/test_api.go new file mode 100644 index 00000000..48f597a7 --- /dev/null +++ b/test_e2e/test_api.go @@ -0,0 +1,129 @@ +package main + +import ( + "context" + "log" + "net" + "net/http" + + "github.com/Masterminds/semver/v3" + "github.com/gorilla/handlers" + "github.com/gorilla/mux" + "github.com/spf13/cobra" + "go.uber.org/fx" + "go.uber.org/fx/fxevent" + "go.uber.org/zap" + + "github.com/ditrit/badaas" + "github.com/ditrit/badaas/configuration" + "github.com/ditrit/badaas/router" + "github.com/ditrit/badaas/testintegration" + "github.com/ditrit/badaas/testintegration/models" + "github.com/ditrit/verdeter" +) + +var rootCfg = verdeter.BuildVerdeterCommand(verdeter.VerdeterConfig{ + Use: "badaas", + Short: "Backend and Distribution as a Service", + Long: "Badaas stands for Backend and Distribution as a Service.", + Run: runHTTPServer, +}) + +func main() { + err := configuration.NewCommandInitializer().Init(rootCfg) + if err != nil { + panic(err) + } + + rootCfg.Execute() +} + +// Run the http server for badaas +func runHTTPServer(_ *cobra.Command, _ []string) { + fx.New( + fx.Provide(testintegration.GetModels), + badaas.BadaasModule, + + // logger for fx + fx.WithLogger(func(logger *zap.Logger) fxevent.Logger { + return &fxevent.ZapLogger{Logger: logger} + }), + + fx.Provide(NewAPIVersion), + // add routes provided by badaas + router.InfoRouteModule, + router.AuthRoutesModule, + + router.GetCRUDRoutesModule[models.Sale](), + + // create httpServer + fx.Provide(NewHTTPServer), + + // Finally: we invoke the newly created server + fx.Invoke(func(*http.Server) { /* we need this function to be empty*/ }), + ).Run() +} + +func NewAPIVersion() *semver.Version { + return semver.MustParse("0.0.0-unreleased") +} + +func NewHTTPServer( + lc fx.Lifecycle, + logger *zap.Logger, + muxRouter *mux.Router, + httpServerConfig configuration.HTTPServerConfiguration, +) *http.Server { + handler := handlers.CORS( + handlers.AllowedMethods([]string{http.MethodGet, http.MethodPost, http.MethodDelete, http.MethodPut, "OPTIONS"}), + handlers.AllowedHeaders([]string{ + "Accept", "Content-Type", "Content-Length", + "Accept-Encoding", "X-CSRF-Token", "Authorization", + "Access-Control-Request-Headers", "Access-Control-Request-Method", + "Connection", "Host", "Origin", "User-Agent", "Referer", + "Cache-Control", "X-header", + }), + handlers.AllowedOrigins([]string{"*"}), + handlers.AllowCredentials(), + handlers.MaxAge(0), + )(muxRouter) + + srv := createServer(handler, httpServerConfig) + + lc.Append(fx.Hook{ + OnStart: func(ctx context.Context) error { + ln, err := net.Listen("tcp", srv.Addr) + if err != nil { + return err + } + logger.Sugar().Infof("Ready to serve at %s", srv.Addr) + go func() { + err := srv.Serve(ln) + if err != nil { + log.Fatalln(err) + } + }() + return nil + }, + OnStop: func(ctx context.Context) error { + // Flush the logger + _ = logger.Sync() + return srv.Shutdown(ctx) + }, + }) + + return srv +} + +// Create the server from the configuration holder and the http handler +func createServer(handler http.Handler, httpServerConfig configuration.HTTPServerConfiguration) *http.Server { + timeout := httpServerConfig.GetMaxTimeout() + + return &http.Server{ + Handler: handler, + Addr: httpServerConfig.GetAddr(), + + WriteTimeout: timeout, + ReadTimeout: timeout, + } +} diff --git a/testintegration/asserts.go b/testintegration/asserts.go new file mode 100644 index 00000000..6e514505 --- /dev/null +++ b/testintegration/asserts.go @@ -0,0 +1,35 @@ +package testintegration + +import ( + "log" + + "github.com/stretchr/testify/suite" + is "gotest.tools/assert/cmp" +) + +func EqualList[T any](ts *suite.Suite, expectedList, actualList []T) { + expectedLen := len(expectedList) + equalLen := ts.Len(actualList, expectedLen) + + if equalLen { + for i := 0; i < expectedLen; i++ { + j := 0 + for ; j < expectedLen; j++ { + if is.DeepEqual( + actualList[j], + expectedList[i], + )().Success() { + break + } + } + + if j == expectedLen { + for _, element := range actualList { + log.Println(element) + } + + ts.FailNow("Lists not equal", "element %v not in list %v", expectedList[i], actualList) + } + } + } +} diff --git a/testintegration/authService_test.go b/testintegration/authService_test.go new file mode 100644 index 00000000..47faccbb --- /dev/null +++ b/testintegration/authService_test.go @@ -0,0 +1,50 @@ +package testintegration + +import ( + "github.com/stretchr/testify/suite" + "gorm.io/gorm" + + "github.com/ditrit/badaas/persistence/models/dto" + "github.com/ditrit/badaas/services/userservice" +) + +type AuthServiceIntTestSuite struct { + suite.Suite + db *gorm.DB + userService userservice.UserService +} + +func NewAuthServiceIntTestSuite( + db *gorm.DB, + userService userservice.UserService, +) *AuthServiceIntTestSuite { + return &AuthServiceIntTestSuite{ + db: db, + userService: userService, + } +} + +func (ts *AuthServiceIntTestSuite) SetupTest() { + CleanDB(ts.db) +} + +func (ts *AuthServiceIntTestSuite) TearDownSuite() { + CleanDB(ts.db) +} + +func (ts *AuthServiceIntTestSuite) TestGetUser() { + email := "franco@ditrit.io" + password := "1234" + + _, err := ts.userService.NewUser("franco", email, password) + ts.Nil(err) + + user, err := ts.userService.GetUser(dto.UserLoginDTO{ + Email: email, + Password: password, + }) + ts.Nil(err) + ts.Equal(user.Username, "franco") + ts.Equal(user.Email, email) + ts.NotEqual(user.Password, password) +} diff --git a/testintegration/badaas_test.go b/testintegration/badaas_test.go new file mode 100644 index 00000000..172bdcf9 --- /dev/null +++ b/testintegration/badaas_test.go @@ -0,0 +1,72 @@ +package testintegration + +import ( + "os" + "path" + "path/filepath" + "runtime" + "testing" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + "github.com/stretchr/testify/suite" + "go.uber.org/fx" + "go.uber.org/fx/fxevent" + "go.uber.org/zap" + + "github.com/ditrit/badaas" + "github.com/ditrit/badaas/configuration" + "github.com/ditrit/badaas/services" + "github.com/ditrit/verdeter" +) + +var tGlobal *testing.T + +var testsCommand = verdeter.BuildVerdeterCommand(verdeter.VerdeterConfig{ + Run: injectDependencies, +}) + +// In order for 'go test' to run this suite, we need to create +// a normal test function and pass our suite to suite.Run +func TestBaDaaS(t *testing.T) { + _, b, _, _ := runtime.Caller(0) + basePath := filepath.Dir(b) + + viper.Set("config_path", path.Join(basePath, "int_test_config.yml")) + viper.Set(configuration.DatabaseDialectorKey, os.Getenv(dbTypeEnvKey)) + + err := configuration.NewCommandInitializer().Init(testsCommand) + if err != nil { + panic(err) + } + + tGlobal = t + + testsCommand.Execute() +} + +func injectDependencies(_ *cobra.Command, _ []string) { + fx.New( + fx.Provide(GetModels), + badaas.BadaasModule, + + // logger for fx + fx.WithLogger(func(logger *zap.Logger) fxevent.Logger { + return &fxevent.ZapLogger{Logger: logger} + }), + + services.AuthServiceModule, + fx.Provide(NewAuthServiceIntTestSuite), + + fx.Invoke(runBaDaaSTestSuites), + ).Run() +} + +func runBaDaaSTestSuites( + tsAuthService *AuthServiceIntTestSuite, + shutdowner fx.Shutdowner, +) { + suite.Run(tGlobal, tsAuthService) + + shutdowner.Shutdown() +} diff --git a/testintegration/badorm_test.go b/testintegration/badorm_test.go new file mode 100644 index 00000000..5569a814 --- /dev/null +++ b/testintegration/badorm_test.go @@ -0,0 +1,141 @@ +package testintegration + +import ( + "fmt" + "os" + "testing" + "time" + + "github.com/spf13/viper" + "github.com/stretchr/testify/suite" + "go.uber.org/fx" + "go.uber.org/fx/fxevent" + "go.uber.org/zap" + "gorm.io/gorm" + + "github.com/ditrit/badaas/badorm" + "github.com/ditrit/badaas/badorm/unsafe" + "github.com/ditrit/badaas/configuration" + "github.com/ditrit/badaas/logger" + "github.com/ditrit/badaas/testintegration/models" +) + +const dbTypeEnvKey = "DB" + +const ( + username = "badaas" + password = "badaas_password2023" + host = "localhost" + port = 5000 + sslMode = "disable" + dbName = "badaas_db" +) + +func TestBaDORM(t *testing.T) { + tGlobal = t + + fx.New( + fx.Provide(NewLoggerConfiguration), + logger.LoggerModule, + fx.Provide(NewGormDBConnection), + fx.Provide(GetModels), + badorm.BaDORMModule, + + // logger for fx + fx.WithLogger(func(logger *zap.Logger) fxevent.Logger { + return &fxevent.ZapLogger{Logger: logger} + }), + + badorm.GetCRUDServiceModule[models.Seller](), + badorm.GetCRUDServiceModule[models.Company](), + badorm.GetCRUDServiceModule[models.Product](), + badorm.GetCRUDServiceModule[models.Sale](), + badorm.GetCRUDServiceModule[models.City](), + badorm.GetCRUDServiceModule[models.Country](), + badorm.GetCRUDServiceModule[models.Employee](), + badorm.GetCRUDServiceModule[models.Bicycle](), + badorm.GetCRUDServiceModule[models.Phone](), + badorm.GetCRUDServiceModule[models.Brand](), + badorm.GetCRUDServiceModule[models.Child](), + + unsafe.GetCRUDServiceModule[models.Company](), + unsafe.GetCRUDServiceModule[models.Seller](), + unsafe.GetCRUDServiceModule[models.Product](), + unsafe.GetCRUDServiceModule[models.Sale](), + unsafe.GetCRUDServiceModule[models.City](), + unsafe.GetCRUDServiceModule[models.Country](), + unsafe.GetCRUDServiceModule[models.Employee](), + unsafe.GetCRUDServiceModule[models.Person](), + unsafe.GetCRUDServiceModule[models.Bicycle](), + unsafe.GetCRUDServiceModule[models.Phone](), + unsafe.GetCRUDServiceModule[models.Brand](), + + fx.Provide(NewCRUDUnsafeServiceIntTestSuite), + fx.Provide(NewCRUDRepositoryIntTestSuite), + fx.Provide(NewWhereConditionsIntTestSuite), + fx.Provide(NewJoinConditionsIntTestSuite), + fx.Provide(NewPreloadConditionsIntTestSuite), + fx.Provide(NewOperatorIntTestSuite), + + fx.Invoke(runBaDORMTestSuites), + ).Run() +} + +func runBaDORMTestSuites( + tsCRUDRepository *CRUDRepositoryIntTestSuite, + tsCRUDUnsafeService *CRUDUnsafeServiceIntTestSuite, + tsWhereConditions *WhereConditionsIntTestSuite, + tsJoinConditions *JoinConditionsIntTestSuite, + tsPreloadConditions *PreloadConditionsIntTestSuite, + tsOperators *OperatorIntTestSuite, + shutdowner fx.Shutdowner, +) { + suite.Run(tGlobal, tsCRUDRepository) + suite.Run(tGlobal, tsCRUDUnsafeService) + suite.Run(tGlobal, tsWhereConditions) + suite.Run(tGlobal, tsJoinConditions) + suite.Run(tGlobal, tsPreloadConditions) + suite.Run(tGlobal, tsOperators) + + shutdowner.Shutdown() +} + +func NewLoggerConfiguration() configuration.LoggerConfiguration { + viper.Set(configuration.LoggerModeKey, "dev") + return configuration.NewLoggerConfiguration() +} + +func NewGormDBConnection(zapLogger *zap.Logger) (*gorm.DB, error) { + switch getDBDialector() { + case configuration.PostgreSQL: + return badorm.ConnectToDialector( + zapLogger, + badorm.CreatePostgreSQLDialector(host, username, password, sslMode, dbName, port), + 10, time.Duration(5)*time.Second, + ) + case configuration.MySQL: + return badorm.ConnectToDialector( + zapLogger, + badorm.CreateMySQLDialector(host, username, password, dbName, port), + 10, time.Duration(5)*time.Second, + ) + case configuration.SQLite: + return badorm.ConnectToDialector( + zapLogger, + badorm.CreateSQLiteDialector(host), + 10, time.Duration(5)*time.Second, + ) + case configuration.SQLServer: + return badorm.ConnectToDialector( + zapLogger, + badorm.CreateSQLServerDialector(host, username, password, dbName, port), + 10, time.Duration(5)*time.Second, + ) + default: + return nil, fmt.Errorf("unknown db %s", getDBDialector()) + } +} + +func getDBDialector() configuration.DBDialector { + return configuration.DBDialector(os.Getenv(dbTypeEnvKey)) +} diff --git a/testintegration/conditions/badorm.go b/testintegration/conditions/badorm.go new file mode 100644 index 00000000..8891ae4d --- /dev/null +++ b/testintegration/conditions/badorm.go @@ -0,0 +1,3 @@ +package conditions + +//go:generate badctl gen conditions ../models diff --git a/testintegration/conditions/bicycle_conditions.go b/testintegration/conditions/bicycle_conditions.go new file mode 100644 index 00000000..196d3df9 --- /dev/null +++ b/testintegration/conditions/bicycle_conditions.go @@ -0,0 +1,96 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package conditions + +import ( + badorm "github.com/ditrit/badaas/badorm" + models "github.com/ditrit/badaas/testintegration/models" + gorm "gorm.io/gorm" + "reflect" + "time" +) + +var bicycleType = reflect.TypeOf(*new(models.Bicycle)) +var BicycleIdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "ID", + ModelType: bicycleType, +} + +func BicycleId(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[models.Bicycle] { + return badorm.FieldCondition[models.Bicycle, badorm.UUID]{ + FieldIdentifier: BicycleIdField, + Operator: operator, + } +} + +var BicycleCreatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: bicycleType, +} + +func BicycleCreatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[models.Bicycle] { + return badorm.FieldCondition[models.Bicycle, time.Time]{ + FieldIdentifier: BicycleCreatedAtField, + Operator: operator, + } +} + +var BicycleUpdatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: bicycleType, +} + +func BicycleUpdatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[models.Bicycle] { + return badorm.FieldCondition[models.Bicycle, time.Time]{ + FieldIdentifier: BicycleUpdatedAtField, + Operator: operator, + } +} + +var BicycleDeletedAtField = badorm.FieldIdentifier[gorm.DeletedAt]{ + Field: "DeletedAt", + ModelType: bicycleType, +} + +func BicycleDeletedAt(operator badorm.Operator[gorm.DeletedAt]) badorm.WhereCondition[models.Bicycle] { + return badorm.FieldCondition[models.Bicycle, gorm.DeletedAt]{ + FieldIdentifier: BicycleDeletedAtField, + Operator: operator, + } +} + +var BicycleNameField = badorm.FieldIdentifier[string]{ + Field: "Name", + ModelType: bicycleType, +} + +func BicycleName(operator badorm.Operator[string]) badorm.WhereCondition[models.Bicycle] { + return badorm.FieldCondition[models.Bicycle, string]{ + FieldIdentifier: BicycleNameField, + Operator: operator, + } +} +func BicycleOwner(conditions ...badorm.Condition[models.Person]) badorm.IJoinCondition[models.Bicycle] { + return badorm.JoinCondition[models.Bicycle, models.Person]{ + Conditions: conditions, + RelationField: "Owner", + T1Field: "OwnerName", + T1PreloadCondition: BicyclePreloadAttributes, + T2Field: "Name", + } +} + +var BicyclePreloadOwner = BicycleOwner(PersonPreloadAttributes) +var BicycleOwnerNameField = badorm.FieldIdentifier[string]{ + Field: "OwnerName", + ModelType: bicycleType, +} + +func BicycleOwnerName(operator badorm.Operator[string]) badorm.WhereCondition[models.Bicycle] { + return badorm.FieldCondition[models.Bicycle, string]{ + FieldIdentifier: BicycleOwnerNameField, + Operator: operator, + } +} + +var BicyclePreloadAttributes = badorm.NewPreloadCondition[models.Bicycle](BicycleIdField, BicycleCreatedAtField, BicycleUpdatedAtField, BicycleDeletedAtField, BicycleNameField, BicycleOwnerNameField) +var BicyclePreloadRelations = []badorm.Condition[models.Bicycle]{BicyclePreloadOwner} diff --git a/testintegration/conditions/brand_conditions.go b/testintegration/conditions/brand_conditions.go new file mode 100644 index 00000000..aad2c026 --- /dev/null +++ b/testintegration/conditions/brand_conditions.go @@ -0,0 +1,73 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package conditions + +import ( + badorm "github.com/ditrit/badaas/badorm" + models "github.com/ditrit/badaas/testintegration/models" + gorm "gorm.io/gorm" + "reflect" + "time" +) + +var brandType = reflect.TypeOf(*new(models.Brand)) +var BrandIdField = badorm.FieldIdentifier[badorm.UIntID]{ + Field: "ID", + ModelType: brandType, +} + +func BrandId(operator badorm.Operator[badorm.UIntID]) badorm.WhereCondition[models.Brand] { + return badorm.FieldCondition[models.Brand, badorm.UIntID]{ + FieldIdentifier: BrandIdField, + Operator: operator, + } +} + +var BrandCreatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: brandType, +} + +func BrandCreatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[models.Brand] { + return badorm.FieldCondition[models.Brand, time.Time]{ + FieldIdentifier: BrandCreatedAtField, + Operator: operator, + } +} + +var BrandUpdatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: brandType, +} + +func BrandUpdatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[models.Brand] { + return badorm.FieldCondition[models.Brand, time.Time]{ + FieldIdentifier: BrandUpdatedAtField, + Operator: operator, + } +} + +var BrandDeletedAtField = badorm.FieldIdentifier[gorm.DeletedAt]{ + Field: "DeletedAt", + ModelType: brandType, +} + +func BrandDeletedAt(operator badorm.Operator[gorm.DeletedAt]) badorm.WhereCondition[models.Brand] { + return badorm.FieldCondition[models.Brand, gorm.DeletedAt]{ + FieldIdentifier: BrandDeletedAtField, + Operator: operator, + } +} + +var BrandNameField = badorm.FieldIdentifier[string]{ + Field: "Name", + ModelType: brandType, +} + +func BrandName(operator badorm.Operator[string]) badorm.WhereCondition[models.Brand] { + return badorm.FieldCondition[models.Brand, string]{ + FieldIdentifier: BrandNameField, + Operator: operator, + } +} + +var BrandPreloadAttributes = badorm.NewPreloadCondition[models.Brand](BrandIdField, BrandCreatedAtField, BrandUpdatedAtField, BrandDeletedAtField, BrandNameField) diff --git a/testintegration/conditions/child_conditions.go b/testintegration/conditions/child_conditions.go new file mode 100644 index 00000000..ab343b28 --- /dev/null +++ b/testintegration/conditions/child_conditions.go @@ -0,0 +1,130 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package conditions + +import ( + badorm "github.com/ditrit/badaas/badorm" + models "github.com/ditrit/badaas/testintegration/models" + gorm "gorm.io/gorm" + "reflect" + "time" +) + +var childType = reflect.TypeOf(*new(models.Child)) +var ChildIdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "ID", + ModelType: childType, +} + +func ChildId(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[models.Child] { + return badorm.FieldCondition[models.Child, badorm.UUID]{ + FieldIdentifier: ChildIdField, + Operator: operator, + } +} + +var ChildCreatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: childType, +} + +func ChildCreatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[models.Child] { + return badorm.FieldCondition[models.Child, time.Time]{ + FieldIdentifier: ChildCreatedAtField, + Operator: operator, + } +} + +var ChildUpdatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: childType, +} + +func ChildUpdatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[models.Child] { + return badorm.FieldCondition[models.Child, time.Time]{ + FieldIdentifier: ChildUpdatedAtField, + Operator: operator, + } +} + +var ChildDeletedAtField = badorm.FieldIdentifier[gorm.DeletedAt]{ + Field: "DeletedAt", + ModelType: childType, +} + +func ChildDeletedAt(operator badorm.Operator[gorm.DeletedAt]) badorm.WhereCondition[models.Child] { + return badorm.FieldCondition[models.Child, gorm.DeletedAt]{ + FieldIdentifier: ChildDeletedAtField, + Operator: operator, + } +} + +var ChildNameField = badorm.FieldIdentifier[string]{ + Field: "Name", + ModelType: childType, +} + +func ChildName(operator badorm.Operator[string]) badorm.WhereCondition[models.Child] { + return badorm.FieldCondition[models.Child, string]{ + FieldIdentifier: ChildNameField, + Operator: operator, + } +} + +var ChildNumberField = badorm.FieldIdentifier[int]{ + Field: "Number", + ModelType: childType, +} + +func ChildNumber(operator badorm.Operator[int]) badorm.WhereCondition[models.Child] { + return badorm.FieldCondition[models.Child, int]{ + FieldIdentifier: ChildNumberField, + Operator: operator, + } +} +func ChildParent1(conditions ...badorm.Condition[models.Parent1]) badorm.IJoinCondition[models.Child] { + return badorm.JoinCondition[models.Child, models.Parent1]{ + Conditions: conditions, + RelationField: "Parent1", + T1Field: "Parent1ID", + T1PreloadCondition: ChildPreloadAttributes, + T2Field: "ID", + } +} + +var ChildPreloadParent1 = ChildParent1(Parent1PreloadAttributes) +var ChildParent1IdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "Parent1ID", + ModelType: childType, +} + +func ChildParent1Id(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[models.Child] { + return badorm.FieldCondition[models.Child, badorm.UUID]{ + FieldIdentifier: ChildParent1IdField, + Operator: operator, + } +} +func ChildParent2(conditions ...badorm.Condition[models.Parent2]) badorm.IJoinCondition[models.Child] { + return badorm.JoinCondition[models.Child, models.Parent2]{ + Conditions: conditions, + RelationField: "Parent2", + T1Field: "Parent2ID", + T1PreloadCondition: ChildPreloadAttributes, + T2Field: "ID", + } +} + +var ChildPreloadParent2 = ChildParent2(Parent2PreloadAttributes) +var ChildParent2IdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "Parent2ID", + ModelType: childType, +} + +func ChildParent2Id(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[models.Child] { + return badorm.FieldCondition[models.Child, badorm.UUID]{ + FieldIdentifier: ChildParent2IdField, + Operator: operator, + } +} + +var ChildPreloadAttributes = badorm.NewPreloadCondition[models.Child](ChildIdField, ChildCreatedAtField, ChildUpdatedAtField, ChildDeletedAtField, ChildNameField, ChildNumberField, ChildParent1IdField, ChildParent2IdField) +var ChildPreloadRelations = []badorm.Condition[models.Child]{ChildPreloadParent1, ChildPreloadParent2} diff --git a/testintegration/conditions/city_conditions.go b/testintegration/conditions/city_conditions.go new file mode 100644 index 00000000..18440a57 --- /dev/null +++ b/testintegration/conditions/city_conditions.go @@ -0,0 +1,96 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package conditions + +import ( + badorm "github.com/ditrit/badaas/badorm" + models "github.com/ditrit/badaas/testintegration/models" + gorm "gorm.io/gorm" + "reflect" + "time" +) + +var cityType = reflect.TypeOf(*new(models.City)) +var CityIdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "ID", + ModelType: cityType, +} + +func CityId(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[models.City] { + return badorm.FieldCondition[models.City, badorm.UUID]{ + FieldIdentifier: CityIdField, + Operator: operator, + } +} + +var CityCreatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: cityType, +} + +func CityCreatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[models.City] { + return badorm.FieldCondition[models.City, time.Time]{ + FieldIdentifier: CityCreatedAtField, + Operator: operator, + } +} + +var CityUpdatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: cityType, +} + +func CityUpdatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[models.City] { + return badorm.FieldCondition[models.City, time.Time]{ + FieldIdentifier: CityUpdatedAtField, + Operator: operator, + } +} + +var CityDeletedAtField = badorm.FieldIdentifier[gorm.DeletedAt]{ + Field: "DeletedAt", + ModelType: cityType, +} + +func CityDeletedAt(operator badorm.Operator[gorm.DeletedAt]) badorm.WhereCondition[models.City] { + return badorm.FieldCondition[models.City, gorm.DeletedAt]{ + FieldIdentifier: CityDeletedAtField, + Operator: operator, + } +} + +var CityNameField = badorm.FieldIdentifier[string]{ + Field: "Name", + ModelType: cityType, +} + +func CityName(operator badorm.Operator[string]) badorm.WhereCondition[models.City] { + return badorm.FieldCondition[models.City, string]{ + FieldIdentifier: CityNameField, + Operator: operator, + } +} +func CityCountry(conditions ...badorm.Condition[models.Country]) badorm.IJoinCondition[models.City] { + return badorm.JoinCondition[models.City, models.Country]{ + Conditions: conditions, + RelationField: "Country", + T1Field: "CountryID", + T1PreloadCondition: CityPreloadAttributes, + T2Field: "ID", + } +} + +var CityPreloadCountry = CityCountry(CountryPreloadAttributes) +var CityCountryIdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "CountryID", + ModelType: cityType, +} + +func CityCountryId(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[models.City] { + return badorm.FieldCondition[models.City, badorm.UUID]{ + FieldIdentifier: CityCountryIdField, + Operator: operator, + } +} + +var CityPreloadAttributes = badorm.NewPreloadCondition[models.City](CityIdField, CityCreatedAtField, CityUpdatedAtField, CityDeletedAtField, CityNameField, CityCountryIdField) +var CityPreloadRelations = []badorm.Condition[models.City]{CityPreloadCountry} diff --git a/testintegration/conditions/company_conditions.go b/testintegration/conditions/company_conditions.go new file mode 100644 index 00000000..dda5aa0d --- /dev/null +++ b/testintegration/conditions/company_conditions.go @@ -0,0 +1,77 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package conditions + +import ( + badorm "github.com/ditrit/badaas/badorm" + models "github.com/ditrit/badaas/testintegration/models" + gorm "gorm.io/gorm" + "reflect" + "time" +) + +var companyType = reflect.TypeOf(*new(models.Company)) +var CompanyIdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "ID", + ModelType: companyType, +} + +func CompanyId(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[models.Company] { + return badorm.FieldCondition[models.Company, badorm.UUID]{ + FieldIdentifier: CompanyIdField, + Operator: operator, + } +} + +var CompanyCreatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: companyType, +} + +func CompanyCreatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[models.Company] { + return badorm.FieldCondition[models.Company, time.Time]{ + FieldIdentifier: CompanyCreatedAtField, + Operator: operator, + } +} + +var CompanyUpdatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: companyType, +} + +func CompanyUpdatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[models.Company] { + return badorm.FieldCondition[models.Company, time.Time]{ + FieldIdentifier: CompanyUpdatedAtField, + Operator: operator, + } +} + +var CompanyDeletedAtField = badorm.FieldIdentifier[gorm.DeletedAt]{ + Field: "DeletedAt", + ModelType: companyType, +} + +func CompanyDeletedAt(operator badorm.Operator[gorm.DeletedAt]) badorm.WhereCondition[models.Company] { + return badorm.FieldCondition[models.Company, gorm.DeletedAt]{ + FieldIdentifier: CompanyDeletedAtField, + Operator: operator, + } +} + +var CompanyNameField = badorm.FieldIdentifier[string]{ + Field: "Name", + ModelType: companyType, +} + +func CompanyName(operator badorm.Operator[string]) badorm.WhereCondition[models.Company] { + return badorm.FieldCondition[models.Company, string]{ + FieldIdentifier: CompanyNameField, + Operator: operator, + } +} +func CompanyPreloadSellers(nestedPreloads ...badorm.IJoinCondition[models.Seller]) badorm.Condition[models.Company] { + return badorm.NewCollectionPreloadCondition[models.Company, models.Seller]("Sellers", nestedPreloads) +} + +var CompanyPreloadAttributes = badorm.NewPreloadCondition[models.Company](CompanyIdField, CompanyCreatedAtField, CompanyUpdatedAtField, CompanyDeletedAtField, CompanyNameField) +var CompanyPreloadRelations = []badorm.Condition[models.Company]{CompanyPreloadSellers()} diff --git a/testintegration/conditions/country_conditions.go b/testintegration/conditions/country_conditions.go new file mode 100644 index 00000000..e13bee47 --- /dev/null +++ b/testintegration/conditions/country_conditions.go @@ -0,0 +1,84 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package conditions + +import ( + badorm "github.com/ditrit/badaas/badorm" + models "github.com/ditrit/badaas/testintegration/models" + gorm "gorm.io/gorm" + "reflect" + "time" +) + +var countryType = reflect.TypeOf(*new(models.Country)) +var CountryIdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "ID", + ModelType: countryType, +} + +func CountryId(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[models.Country] { + return badorm.FieldCondition[models.Country, badorm.UUID]{ + FieldIdentifier: CountryIdField, + Operator: operator, + } +} + +var CountryCreatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: countryType, +} + +func CountryCreatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[models.Country] { + return badorm.FieldCondition[models.Country, time.Time]{ + FieldIdentifier: CountryCreatedAtField, + Operator: operator, + } +} + +var CountryUpdatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: countryType, +} + +func CountryUpdatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[models.Country] { + return badorm.FieldCondition[models.Country, time.Time]{ + FieldIdentifier: CountryUpdatedAtField, + Operator: operator, + } +} + +var CountryDeletedAtField = badorm.FieldIdentifier[gorm.DeletedAt]{ + Field: "DeletedAt", + ModelType: countryType, +} + +func CountryDeletedAt(operator badorm.Operator[gorm.DeletedAt]) badorm.WhereCondition[models.Country] { + return badorm.FieldCondition[models.Country, gorm.DeletedAt]{ + FieldIdentifier: CountryDeletedAtField, + Operator: operator, + } +} + +var CountryNameField = badorm.FieldIdentifier[string]{ + Field: "Name", + ModelType: countryType, +} + +func CountryName(operator badorm.Operator[string]) badorm.WhereCondition[models.Country] { + return badorm.FieldCondition[models.Country, string]{ + FieldIdentifier: CountryNameField, + Operator: operator, + } +} +func CountryCapital(conditions ...badorm.Condition[models.City]) badorm.IJoinCondition[models.Country] { + return badorm.JoinCondition[models.Country, models.City]{ + Conditions: conditions, + RelationField: "Capital", + T1Field: "ID", + T1PreloadCondition: CountryPreloadAttributes, + T2Field: "CountryID", + } +} + +var CountryPreloadCapital = CountryCapital(CityPreloadAttributes) +var CountryPreloadAttributes = badorm.NewPreloadCondition[models.Country](CountryIdField, CountryCreatedAtField, CountryUpdatedAtField, CountryDeletedAtField, CountryNameField) +var CountryPreloadRelations = []badorm.Condition[models.Country]{CountryPreloadCapital} diff --git a/testintegration/conditions/employee_conditions.go b/testintegration/conditions/employee_conditions.go new file mode 100644 index 00000000..2900b820 --- /dev/null +++ b/testintegration/conditions/employee_conditions.go @@ -0,0 +1,96 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package conditions + +import ( + badorm "github.com/ditrit/badaas/badorm" + models "github.com/ditrit/badaas/testintegration/models" + gorm "gorm.io/gorm" + "reflect" + "time" +) + +var employeeType = reflect.TypeOf(*new(models.Employee)) +var EmployeeIdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "ID", + ModelType: employeeType, +} + +func EmployeeId(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[models.Employee] { + return badorm.FieldCondition[models.Employee, badorm.UUID]{ + FieldIdentifier: EmployeeIdField, + Operator: operator, + } +} + +var EmployeeCreatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: employeeType, +} + +func EmployeeCreatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[models.Employee] { + return badorm.FieldCondition[models.Employee, time.Time]{ + FieldIdentifier: EmployeeCreatedAtField, + Operator: operator, + } +} + +var EmployeeUpdatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: employeeType, +} + +func EmployeeUpdatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[models.Employee] { + return badorm.FieldCondition[models.Employee, time.Time]{ + FieldIdentifier: EmployeeUpdatedAtField, + Operator: operator, + } +} + +var EmployeeDeletedAtField = badorm.FieldIdentifier[gorm.DeletedAt]{ + Field: "DeletedAt", + ModelType: employeeType, +} + +func EmployeeDeletedAt(operator badorm.Operator[gorm.DeletedAt]) badorm.WhereCondition[models.Employee] { + return badorm.FieldCondition[models.Employee, gorm.DeletedAt]{ + FieldIdentifier: EmployeeDeletedAtField, + Operator: operator, + } +} + +var EmployeeNameField = badorm.FieldIdentifier[string]{ + Field: "Name", + ModelType: employeeType, +} + +func EmployeeName(operator badorm.Operator[string]) badorm.WhereCondition[models.Employee] { + return badorm.FieldCondition[models.Employee, string]{ + FieldIdentifier: EmployeeNameField, + Operator: operator, + } +} +func EmployeeBoss(conditions ...badorm.Condition[models.Employee]) badorm.IJoinCondition[models.Employee] { + return badorm.JoinCondition[models.Employee, models.Employee]{ + Conditions: conditions, + RelationField: "Boss", + T1Field: "BossID", + T1PreloadCondition: EmployeePreloadAttributes, + T2Field: "ID", + } +} + +var EmployeePreloadBoss = EmployeeBoss(EmployeePreloadAttributes) +var EmployeeBossIdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "BossID", + ModelType: employeeType, +} + +func EmployeeBossId(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[models.Employee] { + return badorm.FieldCondition[models.Employee, badorm.UUID]{ + FieldIdentifier: EmployeeBossIdField, + Operator: operator, + } +} + +var EmployeePreloadAttributes = badorm.NewPreloadCondition[models.Employee](EmployeeIdField, EmployeeCreatedAtField, EmployeeUpdatedAtField, EmployeeDeletedAtField, EmployeeNameField, EmployeeBossIdField) +var EmployeePreloadRelations = []badorm.Condition[models.Employee]{EmployeePreloadBoss} diff --git a/testintegration/conditions/parent1_conditions.go b/testintegration/conditions/parent1_conditions.go new file mode 100644 index 00000000..56b14dda --- /dev/null +++ b/testintegration/conditions/parent1_conditions.go @@ -0,0 +1,84 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package conditions + +import ( + badorm "github.com/ditrit/badaas/badorm" + models "github.com/ditrit/badaas/testintegration/models" + gorm "gorm.io/gorm" + "reflect" + "time" +) + +var parent1Type = reflect.TypeOf(*new(models.Parent1)) +var Parent1IdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "ID", + ModelType: parent1Type, +} + +func Parent1Id(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[models.Parent1] { + return badorm.FieldCondition[models.Parent1, badorm.UUID]{ + FieldIdentifier: Parent1IdField, + Operator: operator, + } +} + +var Parent1CreatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: parent1Type, +} + +func Parent1CreatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[models.Parent1] { + return badorm.FieldCondition[models.Parent1, time.Time]{ + FieldIdentifier: Parent1CreatedAtField, + Operator: operator, + } +} + +var Parent1UpdatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: parent1Type, +} + +func Parent1UpdatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[models.Parent1] { + return badorm.FieldCondition[models.Parent1, time.Time]{ + FieldIdentifier: Parent1UpdatedAtField, + Operator: operator, + } +} + +var Parent1DeletedAtField = badorm.FieldIdentifier[gorm.DeletedAt]{ + Field: "DeletedAt", + ModelType: parent1Type, +} + +func Parent1DeletedAt(operator badorm.Operator[gorm.DeletedAt]) badorm.WhereCondition[models.Parent1] { + return badorm.FieldCondition[models.Parent1, gorm.DeletedAt]{ + FieldIdentifier: Parent1DeletedAtField, + Operator: operator, + } +} +func Parent1ParentParent(conditions ...badorm.Condition[models.ParentParent]) badorm.IJoinCondition[models.Parent1] { + return badorm.JoinCondition[models.Parent1, models.ParentParent]{ + Conditions: conditions, + RelationField: "ParentParent", + T1Field: "ParentParentID", + T1PreloadCondition: Parent1PreloadAttributes, + T2Field: "ID", + } +} + +var Parent1PreloadParentParent = Parent1ParentParent(ParentParentPreloadAttributes) +var Parent1ParentParentIdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "ParentParentID", + ModelType: parent1Type, +} + +func Parent1ParentParentId(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[models.Parent1] { + return badorm.FieldCondition[models.Parent1, badorm.UUID]{ + FieldIdentifier: Parent1ParentParentIdField, + Operator: operator, + } +} + +var Parent1PreloadAttributes = badorm.NewPreloadCondition[models.Parent1](Parent1IdField, Parent1CreatedAtField, Parent1UpdatedAtField, Parent1DeletedAtField, Parent1ParentParentIdField) +var Parent1PreloadRelations = []badorm.Condition[models.Parent1]{Parent1PreloadParentParent} diff --git a/testintegration/conditions/parent2_conditions.go b/testintegration/conditions/parent2_conditions.go new file mode 100644 index 00000000..c1c8269d --- /dev/null +++ b/testintegration/conditions/parent2_conditions.go @@ -0,0 +1,84 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package conditions + +import ( + badorm "github.com/ditrit/badaas/badorm" + models "github.com/ditrit/badaas/testintegration/models" + gorm "gorm.io/gorm" + "reflect" + "time" +) + +var parent2Type = reflect.TypeOf(*new(models.Parent2)) +var Parent2IdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "ID", + ModelType: parent2Type, +} + +func Parent2Id(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[models.Parent2] { + return badorm.FieldCondition[models.Parent2, badorm.UUID]{ + FieldIdentifier: Parent2IdField, + Operator: operator, + } +} + +var Parent2CreatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: parent2Type, +} + +func Parent2CreatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[models.Parent2] { + return badorm.FieldCondition[models.Parent2, time.Time]{ + FieldIdentifier: Parent2CreatedAtField, + Operator: operator, + } +} + +var Parent2UpdatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: parent2Type, +} + +func Parent2UpdatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[models.Parent2] { + return badorm.FieldCondition[models.Parent2, time.Time]{ + FieldIdentifier: Parent2UpdatedAtField, + Operator: operator, + } +} + +var Parent2DeletedAtField = badorm.FieldIdentifier[gorm.DeletedAt]{ + Field: "DeletedAt", + ModelType: parent2Type, +} + +func Parent2DeletedAt(operator badorm.Operator[gorm.DeletedAt]) badorm.WhereCondition[models.Parent2] { + return badorm.FieldCondition[models.Parent2, gorm.DeletedAt]{ + FieldIdentifier: Parent2DeletedAtField, + Operator: operator, + } +} +func Parent2ParentParent(conditions ...badorm.Condition[models.ParentParent]) badorm.IJoinCondition[models.Parent2] { + return badorm.JoinCondition[models.Parent2, models.ParentParent]{ + Conditions: conditions, + RelationField: "ParentParent", + T1Field: "ParentParentID", + T1PreloadCondition: Parent2PreloadAttributes, + T2Field: "ID", + } +} + +var Parent2PreloadParentParent = Parent2ParentParent(ParentParentPreloadAttributes) +var Parent2ParentParentIdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "ParentParentID", + ModelType: parent2Type, +} + +func Parent2ParentParentId(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[models.Parent2] { + return badorm.FieldCondition[models.Parent2, badorm.UUID]{ + FieldIdentifier: Parent2ParentParentIdField, + Operator: operator, + } +} + +var Parent2PreloadAttributes = badorm.NewPreloadCondition[models.Parent2](Parent2IdField, Parent2CreatedAtField, Parent2UpdatedAtField, Parent2DeletedAtField, Parent2ParentParentIdField) +var Parent2PreloadRelations = []badorm.Condition[models.Parent2]{Parent2PreloadParentParent} diff --git a/testintegration/conditions/parent_parent_conditions.go b/testintegration/conditions/parent_parent_conditions.go new file mode 100644 index 00000000..29c7a852 --- /dev/null +++ b/testintegration/conditions/parent_parent_conditions.go @@ -0,0 +1,85 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package conditions + +import ( + badorm "github.com/ditrit/badaas/badorm" + models "github.com/ditrit/badaas/testintegration/models" + gorm "gorm.io/gorm" + "reflect" + "time" +) + +var parentParentType = reflect.TypeOf(*new(models.ParentParent)) +var ParentParentIdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "ID", + ModelType: parentParentType, +} + +func ParentParentId(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[models.ParentParent] { + return badorm.FieldCondition[models.ParentParent, badorm.UUID]{ + FieldIdentifier: ParentParentIdField, + Operator: operator, + } +} + +var ParentParentCreatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: parentParentType, +} + +func ParentParentCreatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[models.ParentParent] { + return badorm.FieldCondition[models.ParentParent, time.Time]{ + FieldIdentifier: ParentParentCreatedAtField, + Operator: operator, + } +} + +var ParentParentUpdatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: parentParentType, +} + +func ParentParentUpdatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[models.ParentParent] { + return badorm.FieldCondition[models.ParentParent, time.Time]{ + FieldIdentifier: ParentParentUpdatedAtField, + Operator: operator, + } +} + +var ParentParentDeletedAtField = badorm.FieldIdentifier[gorm.DeletedAt]{ + Field: "DeletedAt", + ModelType: parentParentType, +} + +func ParentParentDeletedAt(operator badorm.Operator[gorm.DeletedAt]) badorm.WhereCondition[models.ParentParent] { + return badorm.FieldCondition[models.ParentParent, gorm.DeletedAt]{ + FieldIdentifier: ParentParentDeletedAtField, + Operator: operator, + } +} + +var ParentParentNameField = badorm.FieldIdentifier[string]{ + Field: "Name", + ModelType: parentParentType, +} + +func ParentParentName(operator badorm.Operator[string]) badorm.WhereCondition[models.ParentParent] { + return badorm.FieldCondition[models.ParentParent, string]{ + FieldIdentifier: ParentParentNameField, + Operator: operator, + } +} + +var ParentParentNumberField = badorm.FieldIdentifier[int]{ + Field: "Number", + ModelType: parentParentType, +} + +func ParentParentNumber(operator badorm.Operator[int]) badorm.WhereCondition[models.ParentParent] { + return badorm.FieldCondition[models.ParentParent, int]{ + FieldIdentifier: ParentParentNumberField, + Operator: operator, + } +} + +var ParentParentPreloadAttributes = badorm.NewPreloadCondition[models.ParentParent](ParentParentIdField, ParentParentCreatedAtField, ParentParentUpdatedAtField, ParentParentDeletedAtField, ParentParentNameField, ParentParentNumberField) diff --git a/testintegration/conditions/person_conditions.go b/testintegration/conditions/person_conditions.go new file mode 100644 index 00000000..cc062480 --- /dev/null +++ b/testintegration/conditions/person_conditions.go @@ -0,0 +1,73 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package conditions + +import ( + badorm "github.com/ditrit/badaas/badorm" + models "github.com/ditrit/badaas/testintegration/models" + gorm "gorm.io/gorm" + "reflect" + "time" +) + +var personType = reflect.TypeOf(*new(models.Person)) +var PersonIdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "ID", + ModelType: personType, +} + +func PersonId(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[models.Person] { + return badorm.FieldCondition[models.Person, badorm.UUID]{ + FieldIdentifier: PersonIdField, + Operator: operator, + } +} + +var PersonCreatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: personType, +} + +func PersonCreatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[models.Person] { + return badorm.FieldCondition[models.Person, time.Time]{ + FieldIdentifier: PersonCreatedAtField, + Operator: operator, + } +} + +var PersonUpdatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: personType, +} + +func PersonUpdatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[models.Person] { + return badorm.FieldCondition[models.Person, time.Time]{ + FieldIdentifier: PersonUpdatedAtField, + Operator: operator, + } +} + +var PersonDeletedAtField = badorm.FieldIdentifier[gorm.DeletedAt]{ + Field: "DeletedAt", + ModelType: personType, +} + +func PersonDeletedAt(operator badorm.Operator[gorm.DeletedAt]) badorm.WhereCondition[models.Person] { + return badorm.FieldCondition[models.Person, gorm.DeletedAt]{ + FieldIdentifier: PersonDeletedAtField, + Operator: operator, + } +} + +var PersonNameField = badorm.FieldIdentifier[string]{ + Field: "Name", + ModelType: personType, +} + +func PersonName(operator badorm.Operator[string]) badorm.WhereCondition[models.Person] { + return badorm.FieldCondition[models.Person, string]{ + FieldIdentifier: PersonNameField, + Operator: operator, + } +} + +var PersonPreloadAttributes = badorm.NewPreloadCondition[models.Person](PersonIdField, PersonCreatedAtField, PersonUpdatedAtField, PersonDeletedAtField, PersonNameField) diff --git a/testintegration/conditions/phone_conditions.go b/testintegration/conditions/phone_conditions.go new file mode 100644 index 00000000..344356dd --- /dev/null +++ b/testintegration/conditions/phone_conditions.go @@ -0,0 +1,96 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package conditions + +import ( + badorm "github.com/ditrit/badaas/badorm" + models "github.com/ditrit/badaas/testintegration/models" + gorm "gorm.io/gorm" + "reflect" + "time" +) + +var phoneType = reflect.TypeOf(*new(models.Phone)) +var PhoneIdField = badorm.FieldIdentifier[badorm.UIntID]{ + Field: "ID", + ModelType: phoneType, +} + +func PhoneId(operator badorm.Operator[badorm.UIntID]) badorm.WhereCondition[models.Phone] { + return badorm.FieldCondition[models.Phone, badorm.UIntID]{ + FieldIdentifier: PhoneIdField, + Operator: operator, + } +} + +var PhoneCreatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: phoneType, +} + +func PhoneCreatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[models.Phone] { + return badorm.FieldCondition[models.Phone, time.Time]{ + FieldIdentifier: PhoneCreatedAtField, + Operator: operator, + } +} + +var PhoneUpdatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: phoneType, +} + +func PhoneUpdatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[models.Phone] { + return badorm.FieldCondition[models.Phone, time.Time]{ + FieldIdentifier: PhoneUpdatedAtField, + Operator: operator, + } +} + +var PhoneDeletedAtField = badorm.FieldIdentifier[gorm.DeletedAt]{ + Field: "DeletedAt", + ModelType: phoneType, +} + +func PhoneDeletedAt(operator badorm.Operator[gorm.DeletedAt]) badorm.WhereCondition[models.Phone] { + return badorm.FieldCondition[models.Phone, gorm.DeletedAt]{ + FieldIdentifier: PhoneDeletedAtField, + Operator: operator, + } +} + +var PhoneNameField = badorm.FieldIdentifier[string]{ + Field: "Name", + ModelType: phoneType, +} + +func PhoneName(operator badorm.Operator[string]) badorm.WhereCondition[models.Phone] { + return badorm.FieldCondition[models.Phone, string]{ + FieldIdentifier: PhoneNameField, + Operator: operator, + } +} +func PhoneBrand(conditions ...badorm.Condition[models.Brand]) badorm.IJoinCondition[models.Phone] { + return badorm.JoinCondition[models.Phone, models.Brand]{ + Conditions: conditions, + RelationField: "Brand", + T1Field: "BrandID", + T1PreloadCondition: PhonePreloadAttributes, + T2Field: "ID", + } +} + +var PhonePreloadBrand = PhoneBrand(BrandPreloadAttributes) +var PhoneBrandIdField = badorm.FieldIdentifier[uint]{ + Field: "BrandID", + ModelType: phoneType, +} + +func PhoneBrandId(operator badorm.Operator[uint]) badorm.WhereCondition[models.Phone] { + return badorm.FieldCondition[models.Phone, uint]{ + FieldIdentifier: PhoneBrandIdField, + Operator: operator, + } +} + +var PhonePreloadAttributes = badorm.NewPreloadCondition[models.Phone](PhoneIdField, PhoneCreatedAtField, PhoneUpdatedAtField, PhoneDeletedAtField, PhoneNameField, PhoneBrandIdField) +var PhonePreloadRelations = []badorm.Condition[models.Phone]{PhonePreloadBrand} diff --git a/testintegration/conditions/product_conditions.go b/testintegration/conditions/product_conditions.go new file mode 100644 index 00000000..8f47b064 --- /dev/null +++ b/testintegration/conditions/product_conditions.go @@ -0,0 +1,196 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package conditions + +import ( + "database/sql" + badorm "github.com/ditrit/badaas/badorm" + models "github.com/ditrit/badaas/testintegration/models" + gorm "gorm.io/gorm" + "reflect" + "time" +) + +var productType = reflect.TypeOf(*new(models.Product)) +var ProductIdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "ID", + ModelType: productType, +} + +func ProductId(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[models.Product] { + return badorm.FieldCondition[models.Product, badorm.UUID]{ + FieldIdentifier: ProductIdField, + Operator: operator, + } +} + +var ProductCreatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: productType, +} + +func ProductCreatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[models.Product] { + return badorm.FieldCondition[models.Product, time.Time]{ + FieldIdentifier: ProductCreatedAtField, + Operator: operator, + } +} + +var ProductUpdatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: productType, +} + +func ProductUpdatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[models.Product] { + return badorm.FieldCondition[models.Product, time.Time]{ + FieldIdentifier: ProductUpdatedAtField, + Operator: operator, + } +} + +var ProductDeletedAtField = badorm.FieldIdentifier[gorm.DeletedAt]{ + Field: "DeletedAt", + ModelType: productType, +} + +func ProductDeletedAt(operator badorm.Operator[gorm.DeletedAt]) badorm.WhereCondition[models.Product] { + return badorm.FieldCondition[models.Product, gorm.DeletedAt]{ + FieldIdentifier: ProductDeletedAtField, + Operator: operator, + } +} + +var ProductStringField = badorm.FieldIdentifier[string]{ + Column: "string_something_else", + Field: "String", + ModelType: productType, +} + +func ProductString(operator badorm.Operator[string]) badorm.WhereCondition[models.Product] { + return badorm.FieldCondition[models.Product, string]{ + FieldIdentifier: ProductStringField, + Operator: operator, + } +} + +var ProductIntField = badorm.FieldIdentifier[int]{ + Field: "Int", + ModelType: productType, +} + +func ProductInt(operator badorm.Operator[int]) badorm.WhereCondition[models.Product] { + return badorm.FieldCondition[models.Product, int]{ + FieldIdentifier: ProductIntField, + Operator: operator, + } +} + +var ProductIntPointerField = badorm.FieldIdentifier[int]{ + Field: "IntPointer", + ModelType: productType, +} + +func ProductIntPointer(operator badorm.Operator[int]) badorm.WhereCondition[models.Product] { + return badorm.FieldCondition[models.Product, int]{ + FieldIdentifier: ProductIntPointerField, + Operator: operator, + } +} + +var ProductFloatField = badorm.FieldIdentifier[float64]{ + Field: "Float", + ModelType: productType, +} + +func ProductFloat(operator badorm.Operator[float64]) badorm.WhereCondition[models.Product] { + return badorm.FieldCondition[models.Product, float64]{ + FieldIdentifier: ProductFloatField, + Operator: operator, + } +} + +var ProductNullFloatField = badorm.FieldIdentifier[sql.NullFloat64]{ + Field: "NullFloat", + ModelType: productType, +} + +func ProductNullFloat(operator badorm.Operator[sql.NullFloat64]) badorm.WhereCondition[models.Product] { + return badorm.FieldCondition[models.Product, sql.NullFloat64]{ + FieldIdentifier: ProductNullFloatField, + Operator: operator, + } +} + +var ProductBoolField = badorm.FieldIdentifier[bool]{ + Field: "Bool", + ModelType: productType, +} + +func ProductBool(operator badorm.Operator[bool]) badorm.WhereCondition[models.Product] { + return badorm.FieldCondition[models.Product, bool]{ + FieldIdentifier: ProductBoolField, + Operator: operator, + } +} + +var ProductNullBoolField = badorm.FieldIdentifier[sql.NullBool]{ + Field: "NullBool", + ModelType: productType, +} + +func ProductNullBool(operator badorm.Operator[sql.NullBool]) badorm.WhereCondition[models.Product] { + return badorm.FieldCondition[models.Product, sql.NullBool]{ + FieldIdentifier: ProductNullBoolField, + Operator: operator, + } +} + +var ProductByteArrayField = badorm.FieldIdentifier[[]uint8]{ + Field: "ByteArray", + ModelType: productType, +} + +func ProductByteArray(operator badorm.Operator[[]uint8]) badorm.WhereCondition[models.Product] { + return badorm.FieldCondition[models.Product, []uint8]{ + FieldIdentifier: ProductByteArrayField, + Operator: operator, + } +} + +var ProductMultiStringField = badorm.FieldIdentifier[models.MultiString]{ + Field: "MultiString", + ModelType: productType, +} + +func ProductMultiString(operator badorm.Operator[models.MultiString]) badorm.WhereCondition[models.Product] { + return badorm.FieldCondition[models.Product, models.MultiString]{ + FieldIdentifier: ProductMultiStringField, + Operator: operator, + } +} + +var ProductToBeEmbeddedEmbeddedIntField = badorm.FieldIdentifier[int]{ + Field: "EmbeddedInt", + ModelType: productType, +} + +func ProductToBeEmbeddedEmbeddedInt(operator badorm.Operator[int]) badorm.WhereCondition[models.Product] { + return badorm.FieldCondition[models.Product, int]{ + FieldIdentifier: ProductToBeEmbeddedEmbeddedIntField, + Operator: operator, + } +} + +var ProductGormEmbeddedIntField = badorm.FieldIdentifier[int]{ + ColumnPrefix: "gorm_embedded_", + Field: "Int", + ModelType: productType, +} + +func ProductGormEmbeddedInt(operator badorm.Operator[int]) badorm.WhereCondition[models.Product] { + return badorm.FieldCondition[models.Product, int]{ + FieldIdentifier: ProductGormEmbeddedIntField, + Operator: operator, + } +} + +var ProductPreloadAttributes = badorm.NewPreloadCondition[models.Product](ProductIdField, ProductCreatedAtField, ProductUpdatedAtField, ProductDeletedAtField, ProductStringField, ProductIntField, ProductIntPointerField, ProductFloatField, ProductNullFloatField, ProductBoolField, ProductNullBoolField, ProductByteArrayField, ProductMultiStringField, ProductToBeEmbeddedEmbeddedIntField, ProductGormEmbeddedIntField) diff --git a/testintegration/conditions/sale_conditions.go b/testintegration/conditions/sale_conditions.go new file mode 100644 index 00000000..8e9a7d08 --- /dev/null +++ b/testintegration/conditions/sale_conditions.go @@ -0,0 +1,130 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package conditions + +import ( + badorm "github.com/ditrit/badaas/badorm" + models "github.com/ditrit/badaas/testintegration/models" + gorm "gorm.io/gorm" + "reflect" + "time" +) + +var saleType = reflect.TypeOf(*new(models.Sale)) +var SaleIdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "ID", + ModelType: saleType, +} + +func SaleId(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[models.Sale] { + return badorm.FieldCondition[models.Sale, badorm.UUID]{ + FieldIdentifier: SaleIdField, + Operator: operator, + } +} + +var SaleCreatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: saleType, +} + +func SaleCreatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[models.Sale] { + return badorm.FieldCondition[models.Sale, time.Time]{ + FieldIdentifier: SaleCreatedAtField, + Operator: operator, + } +} + +var SaleUpdatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: saleType, +} + +func SaleUpdatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[models.Sale] { + return badorm.FieldCondition[models.Sale, time.Time]{ + FieldIdentifier: SaleUpdatedAtField, + Operator: operator, + } +} + +var SaleDeletedAtField = badorm.FieldIdentifier[gorm.DeletedAt]{ + Field: "DeletedAt", + ModelType: saleType, +} + +func SaleDeletedAt(operator badorm.Operator[gorm.DeletedAt]) badorm.WhereCondition[models.Sale] { + return badorm.FieldCondition[models.Sale, gorm.DeletedAt]{ + FieldIdentifier: SaleDeletedAtField, + Operator: operator, + } +} + +var SaleCodeField = badorm.FieldIdentifier[int]{ + Field: "Code", + ModelType: saleType, +} + +func SaleCode(operator badorm.Operator[int]) badorm.WhereCondition[models.Sale] { + return badorm.FieldCondition[models.Sale, int]{ + FieldIdentifier: SaleCodeField, + Operator: operator, + } +} + +var SaleDescriptionField = badorm.FieldIdentifier[string]{ + Field: "Description", + ModelType: saleType, +} + +func SaleDescription(operator badorm.Operator[string]) badorm.WhereCondition[models.Sale] { + return badorm.FieldCondition[models.Sale, string]{ + FieldIdentifier: SaleDescriptionField, + Operator: operator, + } +} +func SaleProduct(conditions ...badorm.Condition[models.Product]) badorm.IJoinCondition[models.Sale] { + return badorm.JoinCondition[models.Sale, models.Product]{ + Conditions: conditions, + RelationField: "Product", + T1Field: "ProductID", + T1PreloadCondition: SalePreloadAttributes, + T2Field: "ID", + } +} + +var SalePreloadProduct = SaleProduct(ProductPreloadAttributes) +var SaleProductIdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "ProductID", + ModelType: saleType, +} + +func SaleProductId(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[models.Sale] { + return badorm.FieldCondition[models.Sale, badorm.UUID]{ + FieldIdentifier: SaleProductIdField, + Operator: operator, + } +} +func SaleSeller(conditions ...badorm.Condition[models.Seller]) badorm.IJoinCondition[models.Sale] { + return badorm.JoinCondition[models.Sale, models.Seller]{ + Conditions: conditions, + RelationField: "Seller", + T1Field: "SellerID", + T1PreloadCondition: SalePreloadAttributes, + T2Field: "ID", + } +} + +var SalePreloadSeller = SaleSeller(SellerPreloadAttributes) +var SaleSellerIdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "SellerID", + ModelType: saleType, +} + +func SaleSellerId(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[models.Sale] { + return badorm.FieldCondition[models.Sale, badorm.UUID]{ + FieldIdentifier: SaleSellerIdField, + Operator: operator, + } +} + +var SalePreloadAttributes = badorm.NewPreloadCondition[models.Sale](SaleIdField, SaleCreatedAtField, SaleUpdatedAtField, SaleDeletedAtField, SaleCodeField, SaleDescriptionField, SaleProductIdField, SaleSellerIdField) +var SalePreloadRelations = []badorm.Condition[models.Sale]{SalePreloadProduct, SalePreloadSeller} diff --git a/testintegration/conditions/seller_conditions.go b/testintegration/conditions/seller_conditions.go new file mode 100644 index 00000000..08a0ce87 --- /dev/null +++ b/testintegration/conditions/seller_conditions.go @@ -0,0 +1,118 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package conditions + +import ( + badorm "github.com/ditrit/badaas/badorm" + models "github.com/ditrit/badaas/testintegration/models" + gorm "gorm.io/gorm" + "reflect" + "time" +) + +var sellerType = reflect.TypeOf(*new(models.Seller)) +var SellerIdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "ID", + ModelType: sellerType, +} + +func SellerId(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[models.Seller] { + return badorm.FieldCondition[models.Seller, badorm.UUID]{ + FieldIdentifier: SellerIdField, + Operator: operator, + } +} + +var SellerCreatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: sellerType, +} + +func SellerCreatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[models.Seller] { + return badorm.FieldCondition[models.Seller, time.Time]{ + FieldIdentifier: SellerCreatedAtField, + Operator: operator, + } +} + +var SellerUpdatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: sellerType, +} + +func SellerUpdatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[models.Seller] { + return badorm.FieldCondition[models.Seller, time.Time]{ + FieldIdentifier: SellerUpdatedAtField, + Operator: operator, + } +} + +var SellerDeletedAtField = badorm.FieldIdentifier[gorm.DeletedAt]{ + Field: "DeletedAt", + ModelType: sellerType, +} + +func SellerDeletedAt(operator badorm.Operator[gorm.DeletedAt]) badorm.WhereCondition[models.Seller] { + return badorm.FieldCondition[models.Seller, gorm.DeletedAt]{ + FieldIdentifier: SellerDeletedAtField, + Operator: operator, + } +} + +var SellerNameField = badorm.FieldIdentifier[string]{ + Field: "Name", + ModelType: sellerType, +} + +func SellerName(operator badorm.Operator[string]) badorm.WhereCondition[models.Seller] { + return badorm.FieldCondition[models.Seller, string]{ + FieldIdentifier: SellerNameField, + Operator: operator, + } +} +func SellerCompany(conditions ...badorm.Condition[models.Company]) badorm.IJoinCondition[models.Seller] { + return badorm.JoinCondition[models.Seller, models.Company]{ + Conditions: conditions, + RelationField: "Company", + T1Field: "CompanyID", + T1PreloadCondition: SellerPreloadAttributes, + T2Field: "ID", + } +} + +var SellerPreloadCompany = SellerCompany(CompanyPreloadAttributes) +var SellerCompanyIdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "CompanyID", + ModelType: sellerType, +} + +func SellerCompanyId(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[models.Seller] { + return badorm.FieldCondition[models.Seller, badorm.UUID]{ + FieldIdentifier: SellerCompanyIdField, + Operator: operator, + } +} +func SellerUniversity(conditions ...badorm.Condition[models.University]) badorm.IJoinCondition[models.Seller] { + return badorm.JoinCondition[models.Seller, models.University]{ + Conditions: conditions, + RelationField: "University", + T1Field: "UniversityID", + T1PreloadCondition: SellerPreloadAttributes, + T2Field: "ID", + } +} + +var SellerPreloadUniversity = SellerUniversity(UniversityPreloadAttributes) +var SellerUniversityIdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "UniversityID", + ModelType: sellerType, +} + +func SellerUniversityId(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[models.Seller] { + return badorm.FieldCondition[models.Seller, badorm.UUID]{ + FieldIdentifier: SellerUniversityIdField, + Operator: operator, + } +} + +var SellerPreloadAttributes = badorm.NewPreloadCondition[models.Seller](SellerIdField, SellerCreatedAtField, SellerUpdatedAtField, SellerDeletedAtField, SellerNameField, SellerCompanyIdField, SellerUniversityIdField) +var SellerPreloadRelations = []badorm.Condition[models.Seller]{SellerPreloadCompany, SellerPreloadUniversity} diff --git a/testintegration/conditions/university_conditions.go b/testintegration/conditions/university_conditions.go new file mode 100644 index 00000000..1dbcc988 --- /dev/null +++ b/testintegration/conditions/university_conditions.go @@ -0,0 +1,73 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package conditions + +import ( + badorm "github.com/ditrit/badaas/badorm" + models "github.com/ditrit/badaas/testintegration/models" + gorm "gorm.io/gorm" + "reflect" + "time" +) + +var universityType = reflect.TypeOf(*new(models.University)) +var UniversityIdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "ID", + ModelType: universityType, +} + +func UniversityId(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[models.University] { + return badorm.FieldCondition[models.University, badorm.UUID]{ + FieldIdentifier: UniversityIdField, + Operator: operator, + } +} + +var UniversityCreatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: universityType, +} + +func UniversityCreatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[models.University] { + return badorm.FieldCondition[models.University, time.Time]{ + FieldIdentifier: UniversityCreatedAtField, + Operator: operator, + } +} + +var UniversityUpdatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: universityType, +} + +func UniversityUpdatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[models.University] { + return badorm.FieldCondition[models.University, time.Time]{ + FieldIdentifier: UniversityUpdatedAtField, + Operator: operator, + } +} + +var UniversityDeletedAtField = badorm.FieldIdentifier[gorm.DeletedAt]{ + Field: "DeletedAt", + ModelType: universityType, +} + +func UniversityDeletedAt(operator badorm.Operator[gorm.DeletedAt]) badorm.WhereCondition[models.University] { + return badorm.FieldCondition[models.University, gorm.DeletedAt]{ + FieldIdentifier: UniversityDeletedAtField, + Operator: operator, + } +} + +var UniversityNameField = badorm.FieldIdentifier[string]{ + Field: "Name", + ModelType: universityType, +} + +func UniversityName(operator badorm.Operator[string]) badorm.WhereCondition[models.University] { + return badorm.FieldCondition[models.University, string]{ + FieldIdentifier: UniversityNameField, + Operator: operator, + } +} + +var UniversityPreloadAttributes = badorm.NewPreloadCondition[models.University](UniversityIdField, UniversityCreatedAtField, UniversityUpdatedAtField, UniversityDeletedAtField, UniversityNameField) diff --git a/testintegration/crudRepository.go b/testintegration/crudRepository.go new file mode 100644 index 00000000..39e10c43 --- /dev/null +++ b/testintegration/crudRepository.go @@ -0,0 +1,86 @@ +package testintegration + +import ( + "github.com/stretchr/testify/suite" + "gorm.io/gorm" + "gotest.tools/assert" + + "github.com/ditrit/badaas/badorm" + "github.com/ditrit/badaas/testintegration/conditions" + "github.com/ditrit/badaas/testintegration/models" +) + +type CRUDRepositoryIntTestSuite struct { + suite.Suite + db *gorm.DB + crudProductRepository badorm.CRUDRepository[models.Product, badorm.UUID] +} + +func NewCRUDRepositoryIntTestSuite( + db *gorm.DB, + crudProductRepository badorm.CRUDRepository[models.Product, badorm.UUID], +) *CRUDRepositoryIntTestSuite { + return &CRUDRepositoryIntTestSuite{ + db: db, + crudProductRepository: crudProductRepository, + } +} + +func (ts *CRUDRepositoryIntTestSuite) SetupTest() { + CleanDB(ts.db) +} + +func (ts *CRUDRepositoryIntTestSuite) TearDownSuite() { + CleanDB(ts.db) +} + +// ------------------------- GetByID -------------------------------- + +func (ts *CRUDRepositoryIntTestSuite) TestGetByIDReturnsErrorIfIDDontMatch() { + ts.createProduct(0) + _, err := ts.crudProductRepository.GetByID(ts.db, badorm.NilUUID) + ts.Error(err, gorm.ErrRecordNotFound) +} + +func (ts *CRUDRepositoryIntTestSuite) TestGetByIDReturnsEntityIfIDMatch() { + product := ts.createProduct(0) + ts.createProduct(0) + productReturned, err := ts.crudProductRepository.GetByID(ts.db, product.ID) + ts.Nil(err) + + assert.DeepEqual(ts.T(), product, productReturned) +} + +// ------------------------- QueryOne -------------------------------- + +func (ts *CRUDRepositoryIntTestSuite) TestGetReturnsErrorIfConditionsDontMatch() { + ts.createProduct(0) + _, err := ts.crudProductRepository.QueryOne( + ts.db, + conditions.ProductInt(badorm.Eq(1)), + ) + ts.Error(err, gorm.ErrRecordNotFound) +} + +func (ts *CRUDRepositoryIntTestSuite) TestGetReturnsEntityIfConditionsMatch() { + product := ts.createProduct(1) + productReturned, err := ts.crudProductRepository.QueryOne( + ts.db, + conditions.ProductInt(badorm.Eq(1)), + ) + ts.Nil(err) + + assert.DeepEqual(ts.T(), product, productReturned) +} + +// ------------------------- utils ------------------------- + +func (ts *CRUDRepositoryIntTestSuite) createProduct(intV int) *models.Product { + entity := &models.Product{ + Int: intV, + } + err := ts.db.Create(entity).Error + ts.Nil(err) + + return entity +} diff --git a/testintegration/crudServiceCommon.go b/testintegration/crudServiceCommon.go new file mode 100644 index 00000000..85b64162 --- /dev/null +++ b/testintegration/crudServiceCommon.go @@ -0,0 +1,138 @@ +package testintegration + +import ( + "github.com/stretchr/testify/suite" + "gorm.io/gorm" + + "github.com/ditrit/badaas/badorm" + "github.com/ditrit/badaas/testintegration/models" +) + +type CRUDServiceCommonIntTestSuite struct { + suite.Suite + db *gorm.DB +} + +func (ts *CRUDServiceCommonIntTestSuite) SetupTest() { + CleanDB(ts.db) +} + +func (ts *CRUDServiceCommonIntTestSuite) TearDownSuite() { + CleanDB(ts.db) +} + +func (ts *CRUDServiceCommonIntTestSuite) createProduct(stringV string, intV int, floatV float64, boolV bool, intP *int) *models.Product { + entity := &models.Product{ + String: stringV, + Int: intV, + Float: floatV, + Bool: boolV, + IntPointer: intP, + } + err := ts.db.Create(entity).Error + ts.Nil(err) + + return entity +} + +func (ts *CRUDServiceCommonIntTestSuite) createSale(code int, product *models.Product, seller *models.Seller) *models.Sale { + entity := &models.Sale{ + Code: code, + Product: *product, + Seller: seller, + } + err := ts.db.Create(entity).Error + ts.Nil(err) + + return entity +} + +func (ts *CRUDServiceCommonIntTestSuite) createSeller(name string, company *models.Company) *models.Seller { + var companyID *badorm.UUID + if company != nil { + companyID = &company.ID + } + + entity := &models.Seller{ + Name: name, + CompanyID: companyID, + } + err := ts.db.Create(entity).Error + ts.Nil(err) + + return entity +} + +func (ts *CRUDServiceCommonIntTestSuite) createCompany(name string) *models.Company { + entity := &models.Company{ + Name: name, + } + err := ts.db.Create(entity).Error + ts.Nil(err) + + return entity +} + +func (ts *CRUDServiceCommonIntTestSuite) createCountry(name string, capital models.City) *models.Country { + entity := &models.Country{ + Name: name, + Capital: capital, + } + err := ts.db.Create(entity).Error + ts.Nil(err) + + return entity +} + +func (ts *CRUDServiceCommonIntTestSuite) createEmployee(name string, boss *models.Employee) *models.Employee { + entity := &models.Employee{ + Name: name, + Boss: boss, + } + err := ts.db.Create(entity).Error + ts.Nil(err) + + return entity +} + +func (ts *CRUDServiceCommonIntTestSuite) createBicycle(name string, owner models.Person) *models.Bicycle { + entity := &models.Bicycle{ + Name: name, + Owner: owner, + } + err := ts.db.Create(entity).Error + ts.Nil(err) + + return entity +} + +func (ts *CRUDServiceCommonIntTestSuite) createBrand(name string) *models.Brand { + entity := &models.Brand{ + Name: name, + } + err := ts.db.Create(entity).Error + ts.Nil(err) + + return entity +} + +func (ts *CRUDServiceCommonIntTestSuite) createPhone(name string, brand models.Brand) *models.Phone { + entity := &models.Phone{ + Name: name, + Brand: brand, + } + err := ts.db.Create(entity).Error + ts.Nil(err) + + return entity +} + +func (ts *CRUDServiceCommonIntTestSuite) createUniversity(name string) *models.University { + entity := &models.University{ + Name: name, + } + err := ts.db.Create(entity).Error + ts.Nil(err) + + return entity +} diff --git a/testintegration/db_models.go b/testintegration/db_models.go new file mode 100644 index 00000000..d7553ec8 --- /dev/null +++ b/testintegration/db_models.go @@ -0,0 +1,52 @@ +package testintegration + +import ( + "log" + + "github.com/elliotchance/pie/v2" + "gorm.io/gorm" + + "github.com/ditrit/badaas/badorm" + badaasModels "github.com/ditrit/badaas/persistence/models" + "github.com/ditrit/badaas/testintegration/models" +) + +var ListOfTables = []any{ + models.Product{}, + models.Company{}, + models.Seller{}, + models.Sale{}, + models.Country{}, + models.City{}, + models.Employee{}, + models.Person{}, + models.Bicycle{}, + models.Brand{}, + models.Phone{}, + models.ParentParent{}, + models.Parent1{}, + models.Parent2{}, + models.Child{}, + badaasModels.User{}, + badaasModels.Session{}, +} + +func GetModels() badorm.GetModelsResult { + return badorm.GetModelsResult{ + Models: ListOfTables, + } +} + +func CleanDB(db *gorm.DB) { + CleanDBTables(db, pie.Reverse(ListOfTables)) +} + +func CleanDBTables(db *gorm.DB, listOfTables []any) { + // clean database to ensure independency between tests + for _, table := range listOfTables { + err := db.Unscoped().Where("1 = 1").Delete(table).Error + if err != nil { + log.Fatalln("could not clean database: ", err) + } + } +} diff --git a/testintegration/int_test_config.yml b/testintegration/int_test_config.yml new file mode 100644 index 00000000..e188e00b --- /dev/null +++ b/testintegration/int_test_config.yml @@ -0,0 +1,13 @@ +database: + host: localhost + port: 5000 + sslmode: disable + username: badaas + password: badaas_password2023 + name: badaas_db + init: + retry: 10 + retryTime: 5 + +logger: + mode: dev \ No newline at end of file diff --git a/testintegration/join_conditions_test.go b/testintegration/join_conditions_test.go new file mode 100644 index 00000000..ccfee28c --- /dev/null +++ b/testintegration/join_conditions_test.go @@ -0,0 +1,545 @@ +package testintegration + +import ( + "gorm.io/gorm" + + "github.com/ditrit/badaas/badorm" + "github.com/ditrit/badaas/badorm/dynamic" + "github.com/ditrit/badaas/badorm/multitype" + "github.com/ditrit/badaas/badorm/unsafe" + "github.com/ditrit/badaas/testintegration/conditions" + "github.com/ditrit/badaas/testintegration/models" +) + +type JoinConditionsIntTestSuite struct { + CRUDServiceCommonIntTestSuite + crudSaleService badorm.CRUDService[models.Sale, badorm.UUID] + crudSellerService badorm.CRUDService[models.Seller, badorm.UUID] + crudCountryService badorm.CRUDService[models.Country, badorm.UUID] + crudCityService badorm.CRUDService[models.City, badorm.UUID] + crudEmployeeService badorm.CRUDService[models.Employee, badorm.UUID] + crudBicycleService badorm.CRUDService[models.Bicycle, badorm.UUID] + crudPhoneService badorm.CRUDService[models.Phone, badorm.UIntID] + crudChildService badorm.CRUDService[models.Child, badorm.UUID] +} + +func NewJoinConditionsIntTestSuite( + db *gorm.DB, + crudSaleService badorm.CRUDService[models.Sale, badorm.UUID], + crudSellerService badorm.CRUDService[models.Seller, badorm.UUID], + crudCountryService badorm.CRUDService[models.Country, badorm.UUID], + crudCityService badorm.CRUDService[models.City, badorm.UUID], + crudEmployeeService badorm.CRUDService[models.Employee, badorm.UUID], + crudBicycleService badorm.CRUDService[models.Bicycle, badorm.UUID], + crudPhoneService badorm.CRUDService[models.Phone, badorm.UIntID], + crudChildService badorm.CRUDService[models.Child, badorm.UUID], +) *JoinConditionsIntTestSuite { + return &JoinConditionsIntTestSuite{ + CRUDServiceCommonIntTestSuite: CRUDServiceCommonIntTestSuite{ + db: db, + }, + crudSaleService: crudSaleService, + crudSellerService: crudSellerService, + crudCountryService: crudCountryService, + crudCityService: crudCityService, + crudEmployeeService: crudEmployeeService, + crudBicycleService: crudBicycleService, + crudPhoneService: crudPhoneService, + crudChildService: crudChildService, + } +} + +func (ts *JoinConditionsIntTestSuite) TestConditionThatJoinsUintBelongsTo() { + brand1 := ts.createBrand("google") + brand2 := ts.createBrand("apple") + + match := ts.createPhone("pixel", *brand1) + ts.createPhone("iphone", *brand2) + + entities, err := ts.crudPhoneService.Query( + conditions.PhoneBrand( + conditions.BrandName(badorm.Eq("google")), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Phone{match}, entities) +} + +func (ts *JoinConditionsIntTestSuite) TestConditionThatJoinsBelongsTo() { + product1 := ts.createProduct("", 1, 0.0, false, nil) + product2 := ts.createProduct("", 2, 0.0, false, nil) + + match := ts.createSale(0, product1, nil) + ts.createSale(0, product2, nil) + + entities, err := ts.crudSaleService.Query( + conditions.SaleProduct( + conditions.ProductInt(badorm.Eq(1)), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match}, entities) +} + +func (ts *JoinConditionsIntTestSuite) TestConditionThatJoinsAndFiltersTheMainEntity() { + product1 := ts.createProduct("", 1, 0.0, false, nil) + product2 := ts.createProduct("", 2, 0.0, false, nil) + + seller1 := ts.createSeller("franco", nil) + seller2 := ts.createSeller("agustin", nil) + + match := ts.createSale(1, product1, seller1) + ts.createSale(2, product2, seller2) + ts.createSale(2, product1, seller2) + + entities, err := ts.crudSaleService.Query( + conditions.SaleCode(badorm.Eq(1)), + conditions.SaleProduct( + conditions.ProductInt(badorm.Eq(1)), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match}, entities) +} + +func (ts *JoinConditionsIntTestSuite) TestConditionThatJoinsHasOneOptional() { + product1 := ts.createProduct("", 1, 0.0, false, nil) + product2 := ts.createProduct("", 2, 0.0, false, nil) + + seller1 := ts.createSeller("franco", nil) + seller2 := ts.createSeller("agustin", nil) + + match := ts.createSale(0, product1, seller1) + ts.createSale(0, product2, seller2) + + entities, err := ts.crudSaleService.Query( + conditions.SaleSeller( + conditions.SellerName(badorm.Eq("franco")), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match}, entities) +} + +func (ts *JoinConditionsIntTestSuite) TestConditionThatJoinsHasOneSelfReferential() { + boss1 := &models.Employee{ + Name: "Xavier", + } + boss2 := &models.Employee{ + Name: "Vincent", + } + + match := ts.createEmployee("franco", boss1) + ts.createEmployee("pierre", boss2) + + entities, err := ts.crudEmployeeService.Query( + conditions.EmployeeBoss( + conditions.EmployeeName(badorm.Eq("Xavier")), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Employee{match}, entities) +} + +func (ts *JoinConditionsIntTestSuite) TestConditionThatJoinsOneToOne() { + capital1 := models.City{ + Name: "Buenos Aires", + } + capital2 := models.City{ + Name: "Paris", + } + + ts.createCountry("Argentina", capital1) + ts.createCountry("France", capital2) + + entities, err := ts.crudCityService.Query( + conditions.CityCountry( + conditions.CountryName(badorm.Eq("Argentina")), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.City{&capital1}, entities) +} + +func (ts *JoinConditionsIntTestSuite) TestConditionThatJoinsOneToOneReversed() { + capital1 := models.City{ + Name: "Buenos Aires", + } + capital2 := models.City{ + Name: "Paris", + } + + country1 := ts.createCountry("Argentina", capital1) + ts.createCountry("France", capital2) + + entities, err := ts.crudCountryService.Query( + conditions.CountryCapital( + conditions.CityName(badorm.Eq("Buenos Aires")), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Country{country1}, entities) +} + +func (ts *JoinConditionsIntTestSuite) TestConditionThatJoinsWithEntityThatDefinesTableName() { + person1 := models.Person{ + Name: "franco", + } + person2 := models.Person{ + Name: "xavier", + } + + match := ts.createBicycle("BMX", person1) + ts.createBicycle("Shimano", person2) + + entities, err := ts.crudBicycleService.Query( + conditions.BicycleOwner( + conditions.PersonName(badorm.Eq("franco")), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Bicycle{match}, entities) +} + +func (ts *JoinConditionsIntTestSuite) TestConditionThatJoinsOnHasMany() { + company1 := ts.createCompany("ditrit") + company2 := ts.createCompany("orness") + + match := ts.createSeller("franco", company1) + ts.createSeller("agustin", company2) + + entities, err := ts.crudSellerService.Query( + conditions.SellerCompany( + conditions.CompanyName(badorm.Eq("ditrit")), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Seller{match}, entities) +} + +func (ts *JoinConditionsIntTestSuite) TestConditionThatJoinsOnDifferentAttributes() { + product1 := ts.createProduct("match", 1, 0.0, false, nil) + product2 := ts.createProduct("match", 2, 0.0, false, nil) + + seller1 := ts.createSeller("franco", nil) + seller2 := ts.createSeller("agustin", nil) + + match := ts.createSale(0, product1, seller1) + ts.createSale(0, product2, seller2) + + entities, err := ts.crudSaleService.Query( + conditions.SaleProduct( + conditions.ProductInt(badorm.Eq(1)), + conditions.ProductString(badorm.Eq("match")), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match}, entities) +} + +func (ts *JoinConditionsIntTestSuite) TestConditionThatJoinsAddsDeletedAtAutomatically() { + product1 := ts.createProduct("match", 1, 0.0, false, nil) + product2 := ts.createProduct("match", 2, 0.0, false, nil) + + seller1 := ts.createSeller("franco", nil) + seller2 := ts.createSeller("agustin", nil) + + ts.Nil(ts.db.Delete(product2).Error) + + match := ts.createSale(0, product1, seller1) + ts.createSale(0, product2, seller2) + + entities, err := ts.crudSaleService.Query( + conditions.SaleProduct( + conditions.ProductString(badorm.Eq("match")), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match}, entities) +} + +func (ts *JoinConditionsIntTestSuite) TestConditionThatJoinsOnDeletedAt() { + product1 := ts.createProduct("match", 1, 0.0, false, nil) + product2 := ts.createProduct("match", 2, 0.0, false, nil) + + seller1 := ts.createSeller("franco", nil) + seller2 := ts.createSeller("agustin", nil) + + ts.Nil(ts.db.Delete(product1).Error) + + match := ts.createSale(0, product1, seller1) + ts.createSale(0, product2, seller2) + + entities, err := ts.crudSaleService.Query( + conditions.SaleProduct( + conditions.ProductDeletedAt(badorm.Eq(product1.DeletedAt)), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match}, entities) +} + +func (ts *JoinConditionsIntTestSuite) TestConditionThatJoinsAndFiltersByNil() { + product1 := ts.createProduct("", 1, 0.0, false, nil) + intProduct2 := 2 + product2 := ts.createProduct("", 2, 0.0, false, &intProduct2) + + match := ts.createSale(0, product1, nil) + ts.createSale(0, product2, nil) + + entities, err := ts.crudSaleService.Query( + conditions.SaleProduct( + conditions.ProductIntPointer(badorm.IsNull[int]()), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match}, entities) +} + +func (ts *JoinConditionsIntTestSuite) TestConditionThatJoinsDifferentEntities() { + product1 := ts.createProduct("", 1, 0.0, false, nil) + product2 := ts.createProduct("", 2, 0.0, false, nil) + + seller1 := ts.createSeller("franco", nil) + seller2 := ts.createSeller("agustin", nil) + + match := ts.createSale(0, product1, seller1) + ts.createSale(0, product2, seller2) + ts.createSale(0, product1, seller2) + ts.createSale(0, product2, seller1) + + entities, err := ts.crudSaleService.Query( + conditions.SaleProduct( + conditions.ProductInt(badorm.Eq(1)), + ), + conditions.SaleSeller( + conditions.SellerName(badorm.Eq("franco")), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match}, entities) +} + +func (ts *JoinConditionsIntTestSuite) TestConditionThatJoinsMultipleTimes() { + product1 := ts.createProduct("", 0, 0.0, false, nil) + product2 := ts.createProduct("", 0, 0.0, false, nil) + + company1 := ts.createCompany("ditrit") + company2 := ts.createCompany("orness") + + seller1 := ts.createSeller("franco", company1) + seller2 := ts.createSeller("agustin", company2) + + match := ts.createSale(0, product1, seller1) + ts.createSale(0, product2, seller2) + + entities, err := ts.crudSaleService.Query( + conditions.SaleSeller( + conditions.SellerName(badorm.Eq("franco")), + conditions.SellerCompany( + conditions.CompanyName(badorm.Eq("ditrit")), + ), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match}, entities) +} + +func (ts *JoinConditionsIntTestSuite) TestDynamicOperatorOver2Tables() { + company1 := ts.createCompany("ditrit") + company2 := ts.createCompany("orness") + + seller1 := ts.createSeller("ditrit", company1) + ts.createSeller("agustin", company2) + + entities, err := ts.crudSellerService.Query( + conditions.SellerCompany( + conditions.CompanyName( + dynamic.Eq(conditions.SellerNameField), + ), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Seller{seller1}, entities) +} + +func (ts *JoinConditionsIntTestSuite) TestDynamicOperatorOver2TablesAtMoreLevel() { + product1 := ts.createProduct("", 0, 0.0, false, nil) + product2 := ts.createProduct("", 0, 0.0, false, nil) + + company1 := ts.createCompany("ditrit") + company2 := ts.createCompany("orness") + + seller1 := ts.createSeller("ditrit", company1) + seller2 := ts.createSeller("agustin", company2) + + match := ts.createSale(0, product1, seller1) + ts.createSale(0, product2, seller2) + + entities, err := ts.crudSaleService.Query( + conditions.SaleSeller( + conditions.SellerCompany( + conditions.CompanyName( + dynamic.Eq(conditions.SellerNameField), + ), + ), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match}, entities) +} + +func (ts *JoinConditionsIntTestSuite) TestDynamicOperatorWithNotJoinedModelReturnsError() { + _, err := ts.crudChildService.Query( + conditions.ChildId(dynamic.Eq(conditions.ParentParentIdField)), + ) + ts.ErrorIs(err, badorm.ErrFieldModelNotConcerned) + ts.ErrorContains(err, "not concerned model: models.ParentParent; operator: Eq; model: models.Child, field: ID") +} + +func (ts *JoinConditionsIntTestSuite) TestDynamicOperatorJoinMoreThanOnceWithoutSelectJoinReturnsError() { + _, err := ts.crudChildService.Query( + conditions.ChildParent1( + conditions.Parent1ParentParent(), + ), + conditions.ChildParent2( + conditions.Parent2ParentParent(), + ), + conditions.ChildId(dynamic.Eq(conditions.ParentParentIdField)), + ) + ts.ErrorIs(err, badorm.ErrJoinMustBeSelected) + ts.ErrorContains(err, "joined multiple times model: models.ParentParent; operator: Eq; model: models.Child, field: ID") +} + +func (ts *JoinConditionsIntTestSuite) TestDynamicOperatorJoinMoreThanOnceWithSelectJoin() { + parentParent := &models.ParentParent{Name: "franco"} + parent1 := &models.Parent1{ParentParent: *parentParent} + parent2 := &models.Parent2{ParentParent: *parentParent} + child := &models.Child{Parent1: *parent1, Parent2: *parent2, Name: "franco"} + err := ts.db.Create(child).Error + ts.Nil(err) + + entities, err := ts.crudChildService.Query( + conditions.ChildParent1( + conditions.Parent1ParentParent(), + ), + conditions.ChildParent2( + conditions.Parent2ParentParent(), + ), + conditions.ChildName( + dynamic.Eq(conditions.ParentParentNameField).SelectJoin(0, 0), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Child{child}, entities) +} + +func (ts *JoinConditionsIntTestSuite) TestDynamicOperatorJoinMoreThanOnceWithoutSelectJoinOnMultivalueOperatorReturnsError() { + _, err := ts.crudChildService.Query( + conditions.ChildParent1( + conditions.Parent1ParentParent(), + ), + conditions.ChildParent2( + conditions.Parent2ParentParent(), + ), + conditions.ChildId( + dynamic.Between(conditions.ParentParentIdField, conditions.ParentParentIdField), + ), + ) + ts.ErrorIs(err, badorm.ErrJoinMustBeSelected) + ts.ErrorContains(err, "joined multiple times model: models.ParentParent; operator: Between; model: models.Child, field: ID") +} + +func (ts *JoinConditionsIntTestSuite) TestDynamicOperatorJoinMoreThanOnceWithSelectJoinOnMultivalueOperator() { + parentParent := &models.ParentParent{Name: "franco", Number: 3} + parent1 := &models.Parent1{ParentParent: *parentParent} + parent2 := &models.Parent2{ParentParent: *parentParent} + child := &models.Child{Parent1: *parent1, Parent2: *parent2, Name: "franco", Number: 2} + err := ts.db.Create(child).Error + ts.Nil(err) + + entities, err := ts.crudChildService.Query( + conditions.ChildParent1( + conditions.Parent1ParentParent(), + ), + conditions.ChildParent2( + conditions.Parent2ParentParent(), + ), + conditions.ChildNumber( + multitype.Between[int, int](1, conditions.ParentParentNumberField).SelectJoin(1, 0), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Child{child}, entities) +} + +func (ts *JoinConditionsIntTestSuite) TestJoinWithUnsafeCondition() { + product1 := ts.createProduct("", 0, 0.0, false, nil) + product2 := ts.createProduct("", 0, 0.0, false, nil) + + company1 := ts.createCompany("ditrit") + company2 := ts.createCompany("orness") + + seller1 := ts.createSeller("ditrit", company1) + seller2 := ts.createSeller("agustin", company2) + + match := ts.createSale(0, product1, seller1) + ts.createSale(0, product2, seller2) + + entities, err := ts.crudSaleService.Query( + conditions.SaleSeller( + conditions.SellerCompany( + unsafe.NewCondition[models.Company]("%s.name = Seller.name"), + ), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match}, entities) +} + +func (ts *JoinConditionsIntTestSuite) TestJoinWithEmptyConnectionConditionMakesNothing() { + product1 := ts.createProduct("", 1, 0.0, false, nil) + product2 := ts.createProduct("", 2, 0.0, false, nil) + + match1 := ts.createSale(0, product1, nil) + match2 := ts.createSale(0, product2, nil) + + entities, err := ts.crudSaleService.Query( + conditions.SaleProduct( + badorm.And[models.Product](), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match1, match2}, entities) +} + +func (ts *JoinConditionsIntTestSuite) TestJoinWithEmptyContainerConditionMakesNothing() { + _, err := ts.crudSaleService.Query( + conditions.SaleProduct( + badorm.Not[models.Product](), + ), + ) + ts.ErrorIs(err, badorm.ErrEmptyConditions) + ts.ErrorContains(err, "connector: Not; model: models.Product") +} diff --git a/testintegration/models/badorm.go b/testintegration/models/badorm.go new file mode 100644 index 00000000..1ec4114f --- /dev/null +++ b/testintegration/models/badorm.go @@ -0,0 +1,47 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package models + +import badorm "github.com/ditrit/badaas/badorm" + +func (m Bicycle) GetOwner() (*Person, error) { + return badorm.VerifyStructLoaded[Person](&m.Owner) +} +func (m Child) GetParent1() (*Parent1, error) { + return badorm.VerifyStructLoaded[Parent1](&m.Parent1) +} +func (m Child) GetParent2() (*Parent2, error) { + return badorm.VerifyStructLoaded[Parent2](&m.Parent2) +} +func (m City) GetCountry() (*Country, error) { + return badorm.VerifyPointerWithIDLoaded[Country](m.CountryID, m.Country) +} +func (m Company) GetSellers() ([]Seller, error) { + return badorm.VerifyCollectionLoaded[Seller](m.Sellers) +} +func (m Country) GetCapital() (*City, error) { + return badorm.VerifyStructLoaded[City](&m.Capital) +} +func (m Employee) GetBoss() (*Employee, error) { + return badorm.VerifyPointerLoaded[Employee](m.BossID, m.Boss) +} +func (m Parent1) GetParentParent() (*ParentParent, error) { + return badorm.VerifyStructLoaded[ParentParent](&m.ParentParent) +} +func (m Parent2) GetParentParent() (*ParentParent, error) { + return badorm.VerifyStructLoaded[ParentParent](&m.ParentParent) +} +func (m Phone) GetBrand() (*Brand, error) { + return badorm.VerifyStructLoaded[Brand](&m.Brand) +} +func (m Sale) GetProduct() (*Product, error) { + return badorm.VerifyStructLoaded[Product](&m.Product) +} +func (m Sale) GetSeller() (*Seller, error) { + return badorm.VerifyPointerLoaded[Seller](m.SellerID, m.Seller) +} +func (m Seller) GetCompany() (*Company, error) { + return badorm.VerifyPointerLoaded[Company](m.CompanyID, m.Company) +} +func (m Seller) GetUniversity() (*University, error) { + return badorm.VerifyPointerLoaded[University](m.UniversityID, m.University) +} diff --git a/testintegration/models/models.go b/testintegration/models/models.go new file mode 100644 index 00000000..d66a961d --- /dev/null +++ b/testintegration/models/models.go @@ -0,0 +1,253 @@ +package models + +import ( + "database/sql" + "database/sql/driver" + "fmt" + "strings" + + "gorm.io/gorm" + "gorm.io/gorm/schema" + + "github.com/ditrit/badaas/badorm" +) + +type Company struct { + badorm.UUIDModel + + Name string + Sellers *[]Seller // Company HasMany Sellers (Company 0..1 -> 0..* Seller) +} + +func (m Company) Equal(other Company) bool { + return m.ID == other.ID +} + +type MultiString []string + +func (s *MultiString) Scan(src interface{}) error { + switch typedSrc := src.(type) { + case string: + *s = strings.Split(typedSrc, ",") + return nil + case []byte: + str := string(typedSrc) + *s = strings.Split(str, ",") + + return nil + default: + return fmt.Errorf("failed to scan multistring field - source is not a string, is %T", src) + } +} + +func (s MultiString) Value() (driver.Value, error) { + if len(s) == 0 { + return nil, nil + } + + return strings.Join(s, ","), nil +} + +func (MultiString) GormDataType() string { + return "text" +} + +func (MultiString) GormDBDataType(db *gorm.DB, _ *schema.Field) string { + switch db.Dialector.Name() { + case "sqlserver": + return "varchar(255)" + default: + return "text" + } +} + +type ToBeEmbedded struct { + EmbeddedInt int +} + +type ToBeGormEmbedded struct { + Int int +} + +type Product struct { + badorm.UUIDModel + + String string `gorm:"column:string_something_else"` + Int int + IntPointer *int + Float float64 + NullFloat sql.NullFloat64 + Bool bool + NullBool sql.NullBool + ByteArray []byte + MultiString MultiString + ToBeEmbedded + GormEmbedded ToBeGormEmbedded `gorm:"embedded;embeddedPrefix:gorm_embedded_"` +} + +func (m Product) Equal(other Product) bool { + return m.ID == other.ID +} + +type University struct { + badorm.UUIDModel + + Name string +} + +func (m University) Equal(other University) bool { + return m.ID == other.ID +} + +type Seller struct { + badorm.UUIDModel + + Name string + Company *Company + CompanyID *badorm.UUID // Company HasMany Sellers (Company 0..1 -> 0..* Seller) + + University *University + UniversityID *badorm.UUID +} + +type Sale struct { + badorm.UUIDModel + + Code int + Description string + + // Sale belongsTo Product (Sale 0..* -> 1 Product) + Product Product + ProductID badorm.UUID + + // Sale belongsTo Seller (Sale 0..* -> 0..1 Seller) + Seller *Seller + SellerID *badorm.UUID +} + +func (m Sale) Equal(other Sale) bool { + return m.ID == other.ID +} + +func (m Seller) Equal(other Seller) bool { + return m.Name == other.Name +} + +type Country struct { + badorm.UUIDModel + + Name string + Capital City // Country HasOne City (Country 1 -> 1 City) +} + +type City struct { + badorm.UUIDModel + + Name string + Country *Country + CountryID badorm.UUID // Country HasOne City (Country 1 -> 1 City) +} + +func (m Country) Equal(other Country) bool { + return m.Name == other.Name +} + +func (m City) Equal(other City) bool { + return m.Name == other.Name +} + +type Person struct { + badorm.UUIDModel + + Name string `gorm:"unique;type:VARCHAR(255)"` +} + +func (m Person) TableName() string { + return "persons_and_more_name" +} + +type Bicycle struct { + badorm.UUIDModel + + Name string + // Bicycle BelongsTo Person (Bicycle 0..* -> 1 Person) + Owner Person `gorm:"references:Name;foreignKey:OwnerName"` + OwnerName string +} + +func (m Bicycle) Equal(other Bicycle) bool { + return m.Name == other.Name +} + +type Brand struct { + badorm.UIntModel + + Name string +} + +func (m Brand) Equal(other Brand) bool { + return m.Name == other.Name +} + +type Phone struct { + badorm.UIntModel + + Name string + // Phone belongsTo Brand (Phone 0..* -> 1 Brand) + Brand Brand + BrandID uint +} + +func (m Phone) Equal(other Phone) bool { + return m.Name == other.Name +} + +type ParentParent struct { + badorm.UUIDModel + + Name string + Number int +} + +func (m ParentParent) Equal(other ParentParent) bool { + return m.ID == other.ID +} + +type Parent1 struct { + badorm.UUIDModel + + ParentParent ParentParent + ParentParentID badorm.UUID +} + +func (m Parent1) Equal(other Parent1) bool { + return m.ID == other.ID +} + +type Parent2 struct { + badorm.UUIDModel + + ParentParent ParentParent + ParentParentID badorm.UUID +} + +func (m Parent2) Equal(other Parent2) bool { + return m.ID == other.ID +} + +type Child struct { + badorm.UUIDModel + + Name string + Number int + + Parent1 Parent1 + Parent1ID badorm.UUID + + Parent2 Parent2 + Parent2ID badorm.UUID +} + +func (m Child) Equal(other Child) bool { + return m.ID == other.ID +} diff --git a/testintegration/models/mysql.go b/testintegration/models/mysql.go new file mode 100644 index 00000000..1333a9d8 --- /dev/null +++ b/testintegration/models/mysql.go @@ -0,0 +1,19 @@ +//go:build mysql +// +build mysql + +package models + +import "github.com/ditrit/badaas/badorm" + +type Employee struct { + badorm.UUIDModel + + Name string + // mysql needs OnDelete to work with self-referential fk + Boss *Employee `gorm:"constraint:OnDelete:SET NULL"` // Self-Referential Has One (Employee 0..* -> 0..1 Employee) + BossID *badorm.UUID +} + +func (m Employee) Equal(other Employee) bool { + return m.Name == other.Name +} diff --git a/testintegration/models/others.go b/testintegration/models/others.go new file mode 100644 index 00000000..4957724c --- /dev/null +++ b/testintegration/models/others.go @@ -0,0 +1,18 @@ +//go:build !mysql +// +build !mysql + +package models + +import "github.com/ditrit/badaas/badorm" + +type Employee struct { + badorm.UUIDModel + + Name string + Boss *Employee // Self-Referential Has One (Employee 0..* -> 0..1 Employee) + BossID *badorm.UUID +} + +func (m Employee) Equal(other Employee) bool { + return m.Name == other.Name +} diff --git a/testintegration/operators_test.go b/testintegration/operators_test.go new file mode 100644 index 00000000..f95d11b8 --- /dev/null +++ b/testintegration/operators_test.go @@ -0,0 +1,1324 @@ +package testintegration + +import ( + "database/sql" + "log" + "strings" + "time" + + "gorm.io/gorm" + + "github.com/ditrit/badaas/badorm" + "github.com/ditrit/badaas/badorm/dynamic" + "github.com/ditrit/badaas/badorm/dynamic/mysqldynamic" + "github.com/ditrit/badaas/badorm/multitype" + "github.com/ditrit/badaas/badorm/multitype/mysqlmultitype" + "github.com/ditrit/badaas/badorm/mysql" + "github.com/ditrit/badaas/badorm/psql" + "github.com/ditrit/badaas/badorm/sqlite" + "github.com/ditrit/badaas/badorm/sqlserver" + "github.com/ditrit/badaas/badorm/unsafe" + "github.com/ditrit/badaas/configuration" + "github.com/ditrit/badaas/testintegration/conditions" + "github.com/ditrit/badaas/testintegration/models" +) + +type OperatorIntTestSuite struct { + CRUDServiceCommonIntTestSuite + crudProductService badorm.CRUDService[models.Product, badorm.UUID] +} + +func NewOperatorIntTestSuite( + db *gorm.DB, + crudProductService badorm.CRUDService[models.Product, badorm.UUID], +) *OperatorIntTestSuite { + return &OperatorIntTestSuite{ + CRUDServiceCommonIntTestSuite: CRUDServiceCommonIntTestSuite{ + db: db, + }, + crudProductService: crudProductService, + } +} + +func (ts *OperatorIntTestSuite) TestEqNullableNullReturnsError() { + _, err := ts.crudProductService.Query( + conditions.ProductNullFloat( + badorm.Eq(sql.NullFloat64{Valid: false}), + ), + ) + ts.ErrorIs(err, badorm.ErrValueCantBeNull) + ts.ErrorContains(err, "operator: Eq; model: models.Product, field: NullFloat") +} + +func (ts *OperatorIntTestSuite) TestEqPointers() { + intMatch := 1 + match := ts.createProduct("match", 1, 0, false, &intMatch) + + intNotMatch := 2 + ts.createProduct("match", 3, 0, false, &intNotMatch) + ts.createProduct("not_match", 2, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductIntPointer( + badorm.Eq(1), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *OperatorIntTestSuite) TestEqOrIsNullTNotNil() { + match := ts.createProduct("match", 1, 0, false, nil) + ts.createProduct("match", 3, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductInt( + badorm.EqOrIsNull[int](1), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *OperatorIntTestSuite) TestEqOrIsNullTNil() { + match := ts.createProduct("match", 1, 0, false, nil) + notMatch := ts.createProduct("match", 3, 0, false, nil) + notMatch.ByteArray = []byte{2, 3} + err := ts.db.Save(notMatch).Error + ts.Nil(err) + + entities, err := ts.crudProductService.Query( + conditions.ProductByteArray( + badorm.EqOrIsNull[[]byte](nil), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *OperatorIntTestSuite) TestEqOrIsNullTNilOfType() { + match := ts.createProduct("match", 1, 0, false, nil) + notMatch := ts.createProduct("match", 3, 0, false, nil) + notMatch.ByteArray = []byte{2, 3} + err := ts.db.Save(notMatch).Error + ts.Nil(err) + + var nilOfType []byte + + entities, err := ts.crudProductService.Query( + conditions.ProductByteArray( + badorm.EqOrIsNull[[]byte](nilOfType), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *OperatorIntTestSuite) TestEqOrIsNullNilPointer() { + match := ts.createProduct("match", 1, 0, false, nil) + + notMatchInt := 1 + ts.createProduct("match", 3, 0, false, ¬MatchInt) + + var intPointer *int + + entities, err := ts.crudProductService.Query( + conditions.ProductIntPointer( + badorm.EqOrIsNull[int](intPointer), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *OperatorIntTestSuite) TestEqOrIsNullNotNilPointer() { + matchInt := 1 + match := ts.createProduct("match", 1, 0, false, &matchInt) + + ts.createProduct("match", 3, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductInt( + badorm.EqOrIsNull[int](&matchInt), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *OperatorIntTestSuite) TestEqOrIsNullNullableNil() { + match := ts.createProduct("match", 1, 0, false, nil) + + notMatch := ts.createProduct("match", 3, 0, false, nil) + notMatch.NullFloat = sql.NullFloat64{Valid: true, Float64: 6} + err := ts.db.Save(notMatch).Error + ts.Nil(err) + + entities, err := ts.crudProductService.Query( + conditions.ProductNullFloat( + badorm.EqOrIsNull[sql.NullFloat64](sql.NullFloat64{Valid: false}), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *OperatorIntTestSuite) TestEqOrIsNullNullableNotNil() { + match := ts.createProduct("match", 1, 0, false, nil) + match.NullFloat = sql.NullFloat64{Valid: true, Float64: 6} + err := ts.db.Save(match).Error + ts.Nil(err) + + ts.createProduct("match", 3, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductNullFloat( + badorm.EqOrIsNull[sql.NullFloat64](sql.NullFloat64{Valid: true, Float64: 6}), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *OperatorIntTestSuite) TestEqOrIsNullNotRelated() { + _, err := ts.crudProductService.Query( + conditions.ProductFloat( + badorm.EqOrIsNull[float64]("not_related"), + ), + ) + ts.ErrorIs(err, badorm.ErrNotRelated) + ts.ErrorContains(err, "type: string, T: float64; operator: EqOrIsNull; model: models.Product, field: Float") +} + +func (ts *OperatorIntTestSuite) TestNotEqOrIsNotNullTNotNil() { + match := ts.createProduct("match", 1, 0, false, nil) + ts.createProduct("match", 3, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductInt( + badorm.NotEqOrIsNotNull[int](3), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *OperatorIntTestSuite) TestNotEqOrIsNotNullTNil() { + match := ts.createProduct("match", 1, 0, false, nil) + match.ByteArray = []byte{2, 3} + err := ts.db.Save(match).Error + ts.Nil(err) + + ts.createProduct("match", 3, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductByteArray( + badorm.NotEqOrIsNotNull[[]byte](nil), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *OperatorIntTestSuite) TestNotEq() { + match1 := ts.createProduct("match", 1, 0, false, nil) + match2 := ts.createProduct("match", 3, 0, false, nil) + ts.createProduct("not_match", 2, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductInt( + badorm.NotEq(2), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} + +func (ts *OperatorIntTestSuite) TestLt() { + match1 := ts.createProduct("match", 1, 0, false, nil) + match2 := ts.createProduct("match", 2, 0, false, nil) + ts.createProduct("not_match", 3, 0, false, nil) + ts.createProduct("not_match", 4, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductInt( + badorm.Lt(3), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} + +func (ts *OperatorIntTestSuite) TestLtNullableNullReturnsError() { + _, err := ts.crudProductService.Query( + conditions.ProductNullFloat( + badorm.Lt(sql.NullFloat64{Valid: false}), + ), + ) + ts.ErrorIs(err, badorm.ErrValueCantBeNull) + ts.ErrorContains(err, "operator: Lt; model: models.Product, field: NullFloat") +} + +func (ts *OperatorIntTestSuite) TestLtOrEq() { + match1 := ts.createProduct("match", 1, 0, false, nil) + match2 := ts.createProduct("match", 2, 0, false, nil) + ts.createProduct("not_match", 3, 0, false, nil) + ts.createProduct("not_match", 4, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductInt( + badorm.LtOrEq(2), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} + +func (ts *OperatorIntTestSuite) TestNotLt() { + switch getDBDialector() { + case configuration.SQLServer: + match1 := ts.createProduct("match", 3, 0, false, nil) + match2 := ts.createProduct("match", 4, 0, false, nil) + ts.createProduct("not_match", 1, 0, false, nil) + ts.createProduct("not_match", 2, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductInt( + sqlserver.NotLt(3), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) + case configuration.PostgreSQL, configuration.MySQL, configuration.SQLite: + log.Println("NotLt not supported") + } +} + +func (ts *OperatorIntTestSuite) TestGt() { + match1 := ts.createProduct("match", 3, 0, false, nil) + match2 := ts.createProduct("match", 4, 0, false, nil) + ts.createProduct("not_match", 1, 0, false, nil) + ts.createProduct("not_match", 2, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductInt( + badorm.Gt(2), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} + +func (ts *OperatorIntTestSuite) TestGtOrEq() { + match1 := ts.createProduct("match", 3, 0, false, nil) + match2 := ts.createProduct("match", 4, 0, false, nil) + ts.createProduct("not_match", 1, 0, false, nil) + ts.createProduct("not_match", 2, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductInt( + badorm.GtOrEq(3), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} + +func (ts *OperatorIntTestSuite) TestNotGt() { + switch getDBDialector() { + case configuration.SQLServer: + match1 := ts.createProduct("match", 1, 0, false, nil) + match2 := ts.createProduct("match", 2, 0, false, nil) + ts.createProduct("not_match", 3, 0, false, nil) + ts.createProduct("not_match", 4, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductInt( + sqlserver.NotGt(2), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) + case configuration.PostgreSQL, configuration.MySQL, configuration.SQLite: + log.Println("NotGt not supported") + } +} + +func (ts *OperatorIntTestSuite) TestBetween() { + match1 := ts.createProduct("match", 3, 0, false, nil) + match2 := ts.createProduct("match", 4, 0, false, nil) + ts.createProduct("not_match", 6, 0, false, nil) + ts.createProduct("not_match", 2, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductInt( + badorm.Between(3, 5), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} + +func (ts *OperatorIntTestSuite) TestNotBetween() { + match1 := ts.createProduct("match", 3, 0, false, nil) + match2 := ts.createProduct("match", 4, 0, false, nil) + ts.createProduct("not_match", 1, 0, false, nil) + ts.createProduct("not_match", 2, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductInt( + badorm.NotBetween(0, 2), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} + +func (ts *OperatorIntTestSuite) TestIsDistinct() { + match1 := ts.createProduct("match", 3, 0, false, nil) + match2 := ts.createProduct("match", 4, 0, false, nil) + ts.createProduct("not_match", 2, 0, false, nil) + + switch getDBDialector() { + case configuration.PostgreSQL, configuration.SQLServer, configuration.SQLite: + entities, err := ts.crudProductService.Query( + conditions.ProductInt( + badorm.IsDistinct(2), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) + case configuration.MySQL: + entities, err := ts.crudProductService.Query( + badorm.Not[models.Product]( + conditions.ProductInt(mysql.IsEqual(2)), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) + } +} + +func (ts *OperatorIntTestSuite) TestIsNotDistinct() { + match := ts.createProduct("match", 3, 0, false, nil) + ts.createProduct("not_match", 4, 0, false, nil) + ts.createProduct("not_match", 2, 0, false, nil) + + var isNotEqualOperator badorm.Operator[int] + + switch getDBDialector() { + case configuration.MySQL: + isNotEqualOperator = mysql.IsEqual(3) + case configuration.PostgreSQL, configuration.SQLServer, configuration.SQLite: + isNotEqualOperator = badorm.IsNotDistinct(3) + } + + entities, err := ts.crudProductService.Query( + conditions.ProductInt( + isNotEqualOperator, + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *OperatorIntTestSuite) TestIsNotDistinctNullValue() { + match := ts.createProduct("match", 3, 0, false, nil) + + notMatch := ts.createProduct("not_match", 4, 0, false, nil) + notMatch.NullFloat = sql.NullFloat64{Valid: true, Float64: 6} + err := ts.db.Save(notMatch).Error + ts.Nil(err) + + var isEqualOperator badorm.Operator[sql.NullFloat64] + + switch getDBDialector() { + case configuration.MySQL: + isEqualOperator = mysql.IsEqual(sql.NullFloat64{Valid: false}) + case configuration.PostgreSQL, configuration.SQLServer, configuration.SQLite: + isEqualOperator = badorm.IsNotDistinct(sql.NullFloat64{Valid: false}) + } + + entities, err := ts.crudProductService.Query( + conditions.ProductNullFloat( + isEqualOperator, + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *OperatorIntTestSuite) TestIsNull() { + match := ts.createProduct("match", 0, 0, false, nil) + int1 := 1 + int2 := 2 + + ts.createProduct("not_match", 0, 0, false, &int1) + ts.createProduct("not_match", 0, 0, false, &int2) + + entities, err := ts.crudProductService.Query( + conditions.ProductIntPointer( + badorm.IsNull[int](), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *OperatorIntTestSuite) TestIsNullNotPointers() { + match := ts.createProduct("match", 0, 0, false, nil) + + notMatch := ts.createProduct("not_match", 0, 0, false, nil) + notMatch.NullFloat = sql.NullFloat64{Valid: true, Float64: 6} + err := ts.db.Save(notMatch).Error + ts.Nil(err) + + entities, err := ts.crudProductService.Query( + conditions.ProductNullFloat( + badorm.IsNull[sql.NullFloat64](), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *OperatorIntTestSuite) TestIsNotNull() { + int1 := 1 + match := ts.createProduct("match", 0, 0, false, &int1) + ts.createProduct("not_match", 0, 0, false, nil) + ts.createProduct("not_match", 0, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductIntPointer( + badorm.IsNotNull[int](), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *OperatorIntTestSuite) TestIsNotNullNotPointers() { + match := ts.createProduct("match", 0, 0, false, nil) + match.NullFloat = sql.NullFloat64{Valid: true, Float64: 6} + err := ts.db.Save(match).Error + ts.Nil(err) + + ts.createProduct("not_match", 0, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductNullFloat( + badorm.IsNotNull[sql.NullFloat64](), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *OperatorIntTestSuite) TestIsTrue() { + match := ts.createProduct("match", 0, 0, true, nil) + ts.createProduct("not_match", 0, 0, false, nil) + ts.createProduct("not_match", 0, 0, false, nil) + + var isTrueOperator badorm.Operator[bool] + + switch getDBDialector() { + case configuration.MySQL, configuration.PostgreSQL, configuration.SQLite: + isTrueOperator = badorm.IsTrue[bool]() + case configuration.SQLServer: + // sqlserver doesn't support IsTrue + isTrueOperator = badorm.Eq[bool](true) + } + + entities, err := ts.crudProductService.Query( + conditions.ProductBool( + isTrueOperator, + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *OperatorIntTestSuite) TestIsFalse() { + match := ts.createProduct("match", 0, 0, false, nil) + ts.createProduct("not_match", 0, 0, true, nil) + ts.createProduct("not_match", 0, 0, true, nil) + + var isFalseOperator badorm.Operator[bool] + + switch getDBDialector() { + case configuration.MySQL, configuration.PostgreSQL, configuration.SQLite: + isFalseOperator = badorm.IsFalse[bool]() + case configuration.SQLServer: + // sqlserver doesn't support IsFalse + isFalseOperator = badorm.Eq[bool](false) + } + + entities, err := ts.crudProductService.Query( + conditions.ProductBool( + isFalseOperator, + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +//nolint:dupl // not really duplicated +func (ts *OperatorIntTestSuite) TestIsNotTrue() { + match1 := ts.createProduct("match", 0, 0, false, nil) + match2 := ts.createProduct("match", 0, 0, false, nil) + match2.NullBool = sql.NullBool{Valid: true, Bool: false} + err := ts.db.Save(match2).Error + ts.Nil(err) + + notMatch := ts.createProduct("not_match", 0, 0, false, nil) + notMatch.NullBool = sql.NullBool{Valid: true, Bool: true} + err = ts.db.Save(notMatch).Error + ts.Nil(err) + + var isNotTrueOperator badorm.Operator[sql.NullBool] + + switch getDBDialector() { + case configuration.MySQL, configuration.PostgreSQL, configuration.SQLite: + isNotTrueOperator = badorm.IsNotTrue[sql.NullBool]() + case configuration.SQLServer: + // sqlserver doesn't support IsNotTrue + isNotTrueOperator = badorm.IsDistinct[sql.NullBool](sql.NullBool{Valid: true, Bool: true}) + } + + entities, err := ts.crudProductService.Query( + conditions.ProductNullBool( + isNotTrueOperator, + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} + +//nolint:dupl // not really duplicated +func (ts *OperatorIntTestSuite) TestIsNotFalse() { + match1 := ts.createProduct("match", 0, 0, false, nil) + match2 := ts.createProduct("match", 0, 0, false, nil) + match2.NullBool = sql.NullBool{Valid: true, Bool: true} + err := ts.db.Save(match2).Error + ts.Nil(err) + + notMatch := ts.createProduct("not_match", 0, 0, false, nil) + notMatch.NullBool = sql.NullBool{Valid: true, Bool: false} + err = ts.db.Save(notMatch).Error + ts.Nil(err) + + var isNotFalseOperator badorm.Operator[sql.NullBool] + + switch getDBDialector() { + case configuration.MySQL, configuration.PostgreSQL, configuration.SQLite: + isNotFalseOperator = badorm.IsNotFalse[sql.NullBool]() + case configuration.SQLServer: + // sqlserver doesn't support IsNotFalse + isNotFalseOperator = badorm.IsDistinct[sql.NullBool](sql.NullBool{Valid: true, Bool: false}) + } + + entities, err := ts.crudProductService.Query( + conditions.ProductNullBool( + isNotFalseOperator, + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} + +func (ts *OperatorIntTestSuite) TestIsUnknown() { + match := ts.createProduct("match", 0, 0, false, nil) + + notMatch1 := ts.createProduct("match", 0, 0, false, nil) + notMatch1.NullBool = sql.NullBool{Valid: true, Bool: true} + err := ts.db.Save(notMatch1).Error + ts.Nil(err) + + notMatch2 := ts.createProduct("not_match", 0, 0, false, nil) + notMatch2.NullBool = sql.NullBool{Valid: true, Bool: false} + err = ts.db.Save(notMatch2).Error + ts.Nil(err) + + var isUnknownOperator badorm.Operator[sql.NullBool] + + switch getDBDialector() { + case configuration.MySQL, configuration.PostgreSQL: + isUnknownOperator = badorm.IsUnknown[sql.NullBool]() + case configuration.SQLServer, configuration.SQLite: + // sqlserver doesn't support IsUnknown + isUnknownOperator = badorm.IsNotDistinct[sql.NullBool](sql.NullBool{Valid: false}) + } + + entities, err := ts.crudProductService.Query( + conditions.ProductNullBool( + isUnknownOperator, + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *OperatorIntTestSuite) TestIsNotUnknown() { + match1 := ts.createProduct("", 0, 0, false, nil) + match1.NullBool = sql.NullBool{Valid: true, Bool: true} + err := ts.db.Save(match1).Error + ts.Nil(err) + + match2 := ts.createProduct("", 0, 0, false, nil) + match2.NullBool = sql.NullBool{Valid: true, Bool: false} + err = ts.db.Save(match2).Error + ts.Nil(err) + + ts.createProduct("", 0, 0, false, nil) + + var isNotUnknownOperator badorm.Operator[sql.NullBool] + + switch getDBDialector() { + case configuration.MySQL, configuration.PostgreSQL: + isNotUnknownOperator = badorm.IsNotUnknown[sql.NullBool]() + case configuration.SQLServer, configuration.SQLite: + // sqlserver doesn't support IsNotUnknown + isNotUnknownOperator = badorm.IsDistinct[sql.NullBool](sql.NullBool{Valid: false}) + } + + entities, err := ts.crudProductService.Query( + conditions.ProductNullBool( + isNotUnknownOperator, + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} + +func (ts *OperatorIntTestSuite) TestArrayIn() { + match1 := ts.createProduct("s1", 0, 0, false, nil) + match2 := ts.createProduct("s2", 0, 0, false, nil) + + ts.createProduct("ns1", 0, 0, false, nil) + ts.createProduct("ns2", 0, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductString( + badorm.ArrayIn("s1", "s2", "s3"), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} + +func (ts *OperatorIntTestSuite) TestArrayNotIn() { + match1 := ts.createProduct("s1", 0, 0, false, nil) + match2 := ts.createProduct("s2", 0, 0, false, nil) + + ts.createProduct("ns1", 0, 0, false, nil) + ts.createProduct("ns2", 0, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductString( + badorm.ArrayNotIn("ns1", "ns2"), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} + +func (ts *OperatorIntTestSuite) TestLike() { + match1 := ts.createProduct("basd", 0, 0, false, nil) + match2 := ts.createProduct("cape", 0, 0, false, nil) + + ts.createProduct("bbsd", 0, 0, false, nil) + ts.createProduct("bbasd", 0, 0, false, nil) + + var likeOperator badorm.Operator[string] + + switch getDBDialector() { + case configuration.MySQL, configuration.PostgreSQL, configuration.SQLite: + likeOperator = badorm.Like[string]("_a%") + case configuration.SQLServer: + likeOperator = badorm.Like[string]("[bc]a[^a]%") + } + + entities, err := ts.crudProductService.Query( + conditions.ProductString( + likeOperator, + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} + +func (ts *OperatorIntTestSuite) TestLikeEscape() { + match1 := ts.createProduct("ba_sd", 0, 0, false, nil) + match2 := ts.createProduct("ca_pe", 0, 0, false, nil) + + ts.createProduct("bb_sd", 0, 0, false, nil) + ts.createProduct("bba_sd", 0, 0, false, nil) + + var likeOperator badorm.Operator[string] + + switch getDBDialector() { + case configuration.MySQL, configuration.PostgreSQL, configuration.SQLite: + likeOperator = badorm.Like[string]("_a!_%").Escape('!') + case configuration.SQLServer: + likeOperator = badorm.Like[string]("[bc]a!_[^a]%").Escape('!') + } + + entities, err := ts.crudProductService.Query( + conditions.ProductString( + likeOperator, + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} + +func (ts *OperatorIntTestSuite) TestLikeOnNumeric() { + switch getDBDialector() { + case configuration.PostgreSQL, configuration.SQLServer, configuration.SQLite: + log.Println("Like with numeric not compatible") + case configuration.MySQL: + match1 := ts.createProduct("", 10, 0, false, nil) + match2 := ts.createProduct("", 100, 0, false, nil) + + ts.createProduct("", 20, 0, false, nil) + ts.createProduct("", 3, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductInt( + mysql.Like[int]("1%"), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) + } +} + +func (ts *OperatorIntTestSuite) TestILike() { + switch getDBDialector() { + case configuration.MySQL, configuration.SQLServer, configuration.SQLite: + log.Println("ILike not compatible") + case configuration.PostgreSQL: + match1 := ts.createProduct("basd", 0, 0, false, nil) + match2 := ts.createProduct("cape", 0, 0, false, nil) + match3 := ts.createProduct("bAsd", 0, 0, false, nil) + + ts.createProduct("bbsd", 0, 0, false, nil) + ts.createProduct("bbasd", 0, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductString( + psql.ILike[string]("_a%"), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2, match3}, entities) + } +} + +func (ts *OperatorIntTestSuite) TestSimilarTo() { + switch getDBDialector() { + case configuration.MySQL, configuration.SQLServer, configuration.SQLite: + log.Println("SimilarTo not compatible") + case configuration.PostgreSQL: + match1 := ts.createProduct("abc", 0, 0, false, nil) + match2 := ts.createProduct("aabcc", 0, 0, false, nil) + + ts.createProduct("aec", 0, 0, false, nil) + ts.createProduct("aaaaa", 0, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductString( + psql.SimilarTo[string]("%(b|d)%"), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) + } +} + +func (ts *OperatorIntTestSuite) TestPosixRegexCaseSensitive() { + match1 := ts.createProduct("ab", 0, 0, false, nil) + match2 := ts.createProduct("ax", 0, 0, false, nil) + + ts.createProduct("bb", 0, 0, false, nil) + ts.createProduct("cx", 0, 0, false, nil) + ts.createProduct("AB", 0, 0, false, nil) + + var posixRegexOperator badorm.Operator[string] + + switch getDBDialector() { + case configuration.SQLServer, configuration.MySQL: + log.Println("PosixRegex not compatible") + case configuration.PostgreSQL: + posixRegexOperator = psql.POSIXMatch[string]("^a(b|x)") + case configuration.SQLite: + posixRegexOperator = sqlite.Glob[string]("a[bx]") + } + + if posixRegexOperator != nil { + entities, err := ts.crudProductService.Query( + conditions.ProductString( + posixRegexOperator, + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) + } +} + +func (ts *OperatorIntTestSuite) TestPosixRegexCaseInsensitive() { + match1 := ts.createProduct("ab", 0, 0, false, nil) + match2 := ts.createProduct("ax", 0, 0, false, nil) + match3 := ts.createProduct("AB", 0, 0, false, nil) + + ts.createProduct("bb", 0, 0, false, nil) + ts.createProduct("cx", 0, 0, false, nil) + + var posixRegexOperator badorm.Operator[string] + + switch getDBDialector() { + case configuration.SQLServer, configuration.SQLite: + log.Println("PosixRegex Case Insensitive not compatible") + case configuration.MySQL: + posixRegexOperator = mysql.RegexP[string]("^a(b|x)") + case configuration.PostgreSQL: + posixRegexOperator = psql.POSIXIMatch[string]("^a(b|x)") + } + + if posixRegexOperator != nil { + entities, err := ts.crudProductService.Query( + conditions.ProductString( + posixRegexOperator, + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2, match3}, entities) + } +} + +func (ts *OperatorIntTestSuite) TestPosixRegexNotPosix() { + var posixRegexOperator badorm.Operator[string] + + switch getDBDialector() { + case configuration.SQLServer: + log.Println("PosixRegex not compatible") + case configuration.MySQL: + posixRegexOperator = mysql.RegexP[string]("^a(b|x") + case configuration.PostgreSQL: + posixRegexOperator = psql.POSIXMatch[string]("^a(b|x") + case configuration.SQLite: + posixRegexOperator = sqlite.Glob[string]("^a(b|x") + } + + if posixRegexOperator != nil { + _, err := ts.crudProductService.Query( + conditions.ProductString( + posixRegexOperator, + ), + ) + ts.ErrorContains(err, "error parsing regexp") + } +} + +func (ts *OperatorIntTestSuite) TestDynamicOperatorForBasicType() { + int1 := 1 + product1 := ts.createProduct("", 1, 0.0, false, &int1) + ts.createProduct("", 2, 0.0, false, &int1) + ts.createProduct("", 0, 0.0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductInt( + dynamic.Eq(conditions.ProductIntPointerField), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{product1}, entities) +} + +func (ts *OperatorIntTestSuite) TestDynamicOperatorForCustomType() { + match := ts.createProduct("salut,hola", 1, 0.0, false, nil) + match.MultiString = models.MultiString{"salut", "hola"} + err := ts.db.Save(match).Error + ts.Nil(err) + + ts.createProduct("salut,hola", 1, 0.0, false, nil) + ts.createProduct("hola", 1, 0.0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductMultiString( + dynamic.Eq(conditions.ProductMultiStringField), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *OperatorIntTestSuite) TestDynamicOperatorForBadORMModelAttribute() { + match := ts.createProduct("", 1, 0.0, false, nil) + + var isNotDistinctOperator badorm.Operator[gorm.DeletedAt] + + switch getDBDialector() { + case configuration.MySQL: + isNotDistinctOperator = mysqldynamic.IsEqual(conditions.ProductDeletedAtField) + case configuration.PostgreSQL, configuration.SQLServer, configuration.SQLite: + isNotDistinctOperator = dynamic.IsNotDistinct(conditions.ProductDeletedAtField) + } + + entities, err := ts.crudProductService.Query( + conditions.ProductDeletedAt(isNotDistinctOperator), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *OperatorIntTestSuite) TestMultitypeOperatorWithFieldOfAnotherTypeReturnsError() { + _, err := ts.crudProductService.Query( + conditions.ProductInt( + multitype.Eq[int](conditions.ProductStringField), + ), + ) + ts.ErrorIs(err, multitype.ErrFieldTypeDoesNotMatch) + ts.ErrorContains(err, "field type: string, attribute type: int; operator: Eq; model: models.Product, field: Int") +} + +func (ts *OperatorIntTestSuite) TestMultitypeOperatorForNullableTypeCanBeComparedWithNotNullType() { + match := ts.createProduct("", 1, 1.0, false, nil) + match.NullFloat = sql.NullFloat64{Valid: true, Float64: 1.0} + err := ts.db.Save(match).Error + ts.Nil(err) + + ts.createProduct("", 1, 0.0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductNullFloat( + multitype.Eq[sql.NullFloat64](conditions.ProductFloatField), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *OperatorIntTestSuite) TestMultitypeOperatorForNotNullTypeCanBeComparedWithNullableType() { + match := ts.createProduct("", 1, 1.0, false, nil) + match.NullFloat = sql.NullFloat64{Valid: true, Float64: 1.0} + err := ts.db.Save(match).Error + ts.Nil(err) + + ts.createProduct("", 1, 0.0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductFloat( + multitype.Eq[float64](conditions.ProductNullFloatField), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *OperatorIntTestSuite) TestMultitypeOperatorForBadORMModelAttribute() { + match := ts.createProduct("", 1, 0.0, false, nil) + + var isDistinctCondition badorm.Condition[models.Product] + + switch getDBDialector() { + case configuration.MySQL: + isDistinctCondition = badorm.Not[models.Product]( + conditions.ProductDeletedAt( + mysqlmultitype.IsEqual[gorm.DeletedAt](conditions.ProductCreatedAtField), + ), + ) + case configuration.PostgreSQL, configuration.SQLServer, configuration.SQLite: + isDistinctCondition = conditions.ProductDeletedAt( + multitype.IsDistinct[gorm.DeletedAt](conditions.ProductCreatedAtField), + ) + } + + entities, err := ts.crudProductService.Query(isDistinctCondition) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *OperatorIntTestSuite) TestMultitypeMultivalueOperatorWithValueOfAnotherTypeReturnsError() { + _, err := ts.crudProductService.Query( + conditions.ProductInt( + multitype.Between[int, int]("hola", 1), + ), + ) + ts.ErrorIs(err, multitype.ErrParamNotValueOrField) + ts.ErrorContains(err, "parameter type: string, attribute type: int; operator: Between; model: models.Product, field: Int") +} + +func (ts *OperatorIntTestSuite) TestMultitypeMultivalueOperatorWithFieldOfAnotherTypeReturnsError() { + _, err := ts.crudProductService.Query( + conditions.ProductInt( + multitype.Between[int, int](1, conditions.ProductCreatedAtField), + ), + ) + ts.ErrorIs(err, multitype.ErrParamNotValueOrField) + ts.ErrorContains(err, "parameter type: badorm.FieldIdentifier[time.Time], attribute type: int; operator: Between; model: models.Product, field: Int") +} + +func (ts *OperatorIntTestSuite) TestMultitypeMultivalueOperatorWithFieldOfNotRelatedTypeReturnsError() { + _, err := ts.crudProductService.Query( + conditions.ProductInt( + multitype.Between[int, time.Time](1, conditions.ProductCreatedAtField), + ), + ) + ts.ErrorIs(err, multitype.ErrFieldTypeDoesNotMatch) + ts.ErrorContains(err, "field type: time.Time, attribute type: int; operator: Between; model: models.Product, field: Int") +} + +func (ts *OperatorIntTestSuite) TestMultitypeMultivalueOperatorWithAFieldAndAValue() { + match := ts.createProduct("", 1, 0.0, false, nil) + ts.createProduct("", 0, 0.0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductInt( + multitype.Between[int, int](1, conditions.ProductIntField), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *OperatorIntTestSuite) TestMultitypeMultivalueOperatorWithAFieldRelatedAndAValue() { + match := ts.createProduct("", 1, 1.0, false, nil) + match.NullFloat = sql.NullFloat64{Valid: true, Float64: 2.0} + err := ts.db.Save(match).Error + ts.Nil(err) + + notMatch1 := ts.createProduct("", 0, 0.0, false, nil) + notMatch1.NullFloat = sql.NullFloat64{Valid: true, Float64: 2.0} + err = ts.db.Save(notMatch1).Error + ts.Nil(err) + + notMatch2 := ts.createProduct("", 0, 5.0, false, nil) + notMatch2.NullFloat = sql.NullFloat64{Valid: true, Float64: 2.0} + err = ts.db.Save(notMatch2).Error + ts.Nil(err) + + ts.createProduct("", 0, 0.0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductFloat( + multitype.Between[float64, sql.NullFloat64](1.0, conditions.ProductNullFloatField), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *OperatorIntTestSuite) TestUnsafeOperatorInCaseTypesNotMatchConvertible() { + // comparisons between types are allowed when they are convertible + match := ts.createProduct("", 0, 2.1, false, nil) + ts.createProduct("", 0, 0, false, nil) + ts.createProduct("", 0, 2, false, nil) + ts.createProduct("", 0, 2.3, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductFloat( + unsafe.Eq[float64]("2.1"), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *OperatorIntTestSuite) TestUnsafeOperatorInCaseTypesNotMatchNotConvertible() { + switch getDBDialector() { + case configuration.SQLite: + // comparisons between types are allowed and matches nothing if not convertible + ts.createProduct("", 0, 0, false, nil) + ts.createProduct("", 0, 2, false, nil) + ts.createProduct("", 0, 2.3, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductFloat( + unsafe.Eq[float64]("not_convertible_to_float"), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{}, entities) + case configuration.MySQL: + // comparisons between types are allowed but matches 0s if not convertible + match := ts.createProduct("", 0, 0, false, nil) + ts.createProduct("", 0, 2, false, nil) + ts.createProduct("", 0, 2.3, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductFloat( + unsafe.Eq[float64]("not_convertible_to_float"), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) + case configuration.SQLServer: + // returns an error + _, err := ts.crudProductService.Query( + conditions.ProductFloat( + unsafe.Eq[float64]("not_convertible_to_float"), + ), + ) + ts.ErrorContains(err, "mssql: Error converting data type nvarchar to float.") + case configuration.PostgreSQL: + // returns an error + _, err := ts.crudProductService.Query( + conditions.ProductFloat( + unsafe.Eq[float64]("not_convertible_to_float"), + ), + ) + ts.ErrorContains(err, "not_convertible_to_float") + } +} + +func (ts *OperatorIntTestSuite) TestUnsafeOperatorInCaseFieldWithTypesNotMatch() { + switch getDBDialector() { + case configuration.SQLite: + // comparisons between fields with different types are allowed + match1 := ts.createProduct("0", 0, 0, false, nil) + match2 := ts.createProduct("1", 0, 1, false, nil) + ts.createProduct("0", 0, 1, false, nil) + ts.createProduct("not_convertible", 0, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductFloat( + unsafe.Eq[float64](conditions.ProductStringField), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) + case configuration.MySQL: + // comparisons between fields with different types are allowed but matches 0s on not convertible + match1 := ts.createProduct("0", 1, 0, false, nil) + match2 := ts.createProduct("1", 2, 1, false, nil) + match3 := ts.createProduct("not_convertible", 2, 0, false, nil) + ts.createProduct("0.0", 2, 1.0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductFloat( + unsafe.Eq[float64](conditions.ProductStringField), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2, match3}, entities) + case configuration.SQLServer: + // comparisons between fields with different types are allowed and returns error only if at least one is not convertible + match1 := ts.createProduct("0", 1, 0, false, nil) + match2 := ts.createProduct("1", 2, 1, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductFloat( + unsafe.Eq[float64](conditions.ProductStringField), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) + + ts.createProduct("not_convertible", 3, 0, false, nil) + ts.createProduct("0.0", 4, 1.0, false, nil) + + _, err = ts.crudProductService.Query( + conditions.ProductFloat( + unsafe.Eq[float64](conditions.ProductStringField), + ), + ) + ts.ErrorContains(err, "mssql: Error converting data type nvarchar to float.") + case configuration.PostgreSQL: + // returns an error + _, err := ts.crudProductService.Query( + conditions.ProductFloat( + unsafe.Eq[float64](conditions.ProductStringField), + ), + ) + + ts.True( + strings.Contains( + err.Error(), + "ERROR: operator does not exist: numeric = text (SQLSTATE 42883)", // postgresql + ) || strings.Contains( + err.Error(), + "ERROR: unsupported comparison operator: = (SQLSTATE 22023)", // cockroachdb + ), + ) + } +} + +func (ts *OperatorIntTestSuite) TestUnsafeOperatorCanCompareFieldsThatMapToTheSameType() { + match := ts.createProduct("hola,chau", 1, 1.0, false, nil) + match.MultiString = models.MultiString{"hola", "chau"} + err := ts.db.Save(match).Error + ts.Nil(err) + + notMatch := ts.createProduct("chau", 0, 0.0, false, nil) + notMatch.MultiString = models.MultiString{"hola", "chau"} + err = ts.db.Save(notMatch).Error + ts.Nil(err) + + ts.createProduct("", 0, 0.0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductString( + unsafe.Eq[string](conditions.ProductMultiStringField), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} diff --git a/testintegration/preload_conditions_test.go b/testintegration/preload_conditions_test.go new file mode 100644 index 00000000..b655f002 --- /dev/null +++ b/testintegration/preload_conditions_test.go @@ -0,0 +1,892 @@ +package testintegration + +import ( + "errors" + + "github.com/elliotchance/pie/v2" + "gorm.io/gorm" + "gotest.tools/assert" + + "github.com/ditrit/badaas/badorm" + "github.com/ditrit/badaas/testintegration/conditions" + "github.com/ditrit/badaas/testintegration/models" + "github.com/ditrit/badaas/utils" +) + +type PreloadConditionsIntTestSuite struct { + CRUDServiceCommonIntTestSuite + crudSaleService badorm.CRUDService[models.Sale, badorm.UUID] + crudCompanyService badorm.CRUDService[models.Company, badorm.UUID] + crudSellerService badorm.CRUDService[models.Seller, badorm.UUID] + crudCountryService badorm.CRUDService[models.Country, badorm.UUID] + crudCityService badorm.CRUDService[models.City, badorm.UUID] + crudEmployeeService badorm.CRUDService[models.Employee, badorm.UUID] + crudChildService badorm.CRUDService[models.Child, badorm.UUID] + crudPhoneService badorm.CRUDService[models.Phone, badorm.UIntID] +} + +func NewPreloadConditionsIntTestSuite( + db *gorm.DB, + crudSaleService badorm.CRUDService[models.Sale, badorm.UUID], + crudCompanyService badorm.CRUDService[models.Company, badorm.UUID], + crudSellerService badorm.CRUDService[models.Seller, badorm.UUID], + crudCountryService badorm.CRUDService[models.Country, badorm.UUID], + crudCityService badorm.CRUDService[models.City, badorm.UUID], + crudEmployeeService badorm.CRUDService[models.Employee, badorm.UUID], + crudChildService badorm.CRUDService[models.Child, badorm.UUID], + crudPhoneService badorm.CRUDService[models.Phone, badorm.UIntID], +) *PreloadConditionsIntTestSuite { + return &PreloadConditionsIntTestSuite{ + CRUDServiceCommonIntTestSuite: CRUDServiceCommonIntTestSuite{ + db: db, + }, + crudSaleService: crudSaleService, + crudCompanyService: crudCompanyService, + crudSellerService: crudSellerService, + crudCountryService: crudCountryService, + crudCityService: crudCityService, + crudEmployeeService: crudEmployeeService, + crudChildService: crudChildService, + crudPhoneService: crudPhoneService, + } +} + +func (ts *PreloadConditionsIntTestSuite) TestNoPreloadReturnsErrorOnGetRelation() { + product := ts.createProduct("a_string", 1, 0.0, false, nil) + seller := ts.createSeller("franco", nil) + sale := ts.createSale(0, product, seller) + + entities, err := ts.crudSaleService.Query() + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{sale}, entities) + + saleLoaded := entities[0] + + ts.False(saleLoaded.Product.IsLoaded()) + _, err = saleLoaded.GetProduct() + ts.ErrorIs(err, badorm.ErrRelationNotLoaded) + + ts.Nil(saleLoaded.Seller) // is nil but we cant determine why directly (not loaded or really null) + _, err = saleLoaded.GetSeller() // GetSeller give us that information + ts.ErrorIs(err, badorm.ErrRelationNotLoaded) +} + +func (ts *PreloadConditionsIntTestSuite) TestNoPreloadWhenItsNullKnowsItsReallyNull() { + product := ts.createProduct("a_string", 1, 0.0, false, nil) + sale := ts.createSale(10, product, nil) + + entities, err := ts.crudSaleService.Query() + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{sale}, entities) + + saleLoaded := entities[0] + + ts.False(saleLoaded.Product.IsLoaded()) + _, err = saleLoaded.GetProduct() + ts.ErrorIs(err, badorm.ErrRelationNotLoaded) + + ts.Nil(saleLoaded.Seller) // is nil but we cant determine why directly (not loaded or really null) + saleSeller, err := saleLoaded.GetSeller() // GetSeller give us that information + ts.Nil(err) + ts.Nil(saleSeller) +} + +func (ts *PreloadConditionsIntTestSuite) TestPreloadWithoutWhereConditionDoesNotFilter() { + product1 := ts.createProduct("a_string", 1, 0.0, false, nil) + product2 := ts.createProduct("", 2, 0.0, false, nil) + + seller1 := ts.createSeller("franco", nil) + + withSeller := ts.createSale(0, product1, seller1) + withoutSeller := ts.createSale(0, product2, nil) + + entities, err := ts.crudSaleService.Query( + conditions.SalePreloadSeller, + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{withSeller, withoutSeller}, entities) + ts.True(pie.Any(entities, func(sale *models.Sale) bool { + saleSeller, err := sale.GetSeller() + return err == nil && saleSeller != nil && saleSeller.Equal(*seller1) + })) + ts.True(pie.Any(entities, func(sale *models.Sale) bool { + // in this case sale.Seller will also be nil + // but we can now it's really null in the db because err is nil + saleSeller, err := sale.GetSeller() + return err == nil && saleSeller == nil + })) +} + +func (ts *PreloadConditionsIntTestSuite) TestPreloadNullableAtSecondLevel() { + product1 := ts.createProduct("a_string", 1, 0.0, false, nil) + product2 := ts.createProduct("", 2, 0.0, false, nil) + + company := ts.createCompany("ditrit") + + withCompany := ts.createSeller("with", company) + withoutCompany := ts.createSeller("without", nil) + + sale1 := ts.createSale(0, product1, withoutCompany) + sale2 := ts.createSale(0, product2, withCompany) + + entities, err := ts.crudSaleService.Query( + conditions.SaleSeller( + conditions.SellerPreloadCompany, + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{sale1, sale2}, entities) + ts.True(pie.Any(entities, func(sale *models.Sale) bool { + saleSeller, err := sale.GetSeller() + if err != nil { + return false + } + + sellerCompany, err := saleSeller.GetCompany() + return err == nil && saleSeller.Name == "with" && sellerCompany != nil && sellerCompany.Equal(*company) + })) + ts.True(pie.Any(entities, func(sale *models.Sale) bool { + saleSeller, err := sale.GetSeller() + if err != nil { + return false + } + + sellerCompany, err := saleSeller.GetCompany() + return err == nil && saleSeller.Name == "without" && sellerCompany == nil + })) +} + +func (ts *PreloadConditionsIntTestSuite) TestPreloadAtSecondLevelWorksWithManualPreload() { + product1 := ts.createProduct("a_string", 1, 0.0, false, nil) + product2 := ts.createProduct("", 2, 0.0, false, nil) + + company := ts.createCompany("ditrit") + + withCompany := ts.createSeller("with", company) + withoutCompany := ts.createSeller("without", nil) + + sale1 := ts.createSale(0, product1, withoutCompany) + sale2 := ts.createSale(0, product2, withCompany) + + entities, err := ts.crudSaleService.Query( + conditions.SaleSeller( + conditions.SellerPreloadAttributes, + conditions.SellerPreloadCompany, + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{sale1, sale2}, entities) + ts.True(pie.Any(entities, func(sale *models.Sale) bool { + saleSeller, err := sale.GetSeller() + if err != nil { + return false + } + + sellerCompany, err := saleSeller.GetCompany() + return err == nil && saleSeller.Name == "with" && sellerCompany != nil && sellerCompany.Equal(*company) + })) + ts.True(pie.Any(entities, func(sale *models.Sale) bool { + saleSeller, err := sale.GetSeller() + if err != nil { + return false + } + + sellerCompany, err := saleSeller.GetCompany() + return err == nil && saleSeller.Name == "without" && sellerCompany == nil + })) +} + +func (ts *PreloadConditionsIntTestSuite) TestNoPreloadNullableAtSecondLevel() { + product1 := ts.createProduct("a_string", 1, 0.0, false, nil) + product2 := ts.createProduct("", 2, 0.0, false, nil) + + company := ts.createCompany("ditrit") + + withCompany := ts.createSeller("with", company) + withoutCompany := ts.createSeller("without", nil) + + sale1 := ts.createSale(0, product1, withoutCompany) + sale2 := ts.createSale(0, product2, withCompany) + + entities, err := ts.crudSaleService.Query( + conditions.SalePreloadSeller, + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{sale1, sale2}, entities) + ts.True(pie.Any(entities, func(sale *models.Sale) bool { + saleSeller, err := sale.GetSeller() + if err != nil { + return false + } + + // the not null one is not loaded + sellerCompany, err := saleSeller.GetCompany() + return errors.Is(err, badorm.ErrRelationNotLoaded) && sellerCompany == nil + })) + ts.True(pie.Any(entities, func(sale *models.Sale) bool { + saleSeller, err := sale.GetSeller() + if err != nil { + return false + } + + // we can be sure the null one is null + sellerCompany, err := saleSeller.GetCompany() + return err == nil && sellerCompany == nil + })) +} + +func (ts *PreloadConditionsIntTestSuite) TestPreloadWithoutWhereConditionDoesNotFilterAtSecondLevel() { + product1 := ts.createProduct("a_string", 1, 0.0, false, nil) + product2 := ts.createProduct("", 2, 0.0, false, nil) + + seller1 := ts.createSeller("franco", nil) + + withSeller := ts.createSale(0, product1, seller1) + withoutSeller := ts.createSale(0, product2, nil) + + entities, err := ts.crudSaleService.Query( + conditions.SaleSeller( + conditions.SellerPreloadCompany, + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{withSeller, withoutSeller}, entities) + ts.True(pie.Any(entities, func(sale *models.Sale) bool { + saleSeller, err := sale.GetSeller() + if saleSeller == nil || err != nil { + return false + } + + sellerCompany, err := saleSeller.GetCompany() + + return err == nil && saleSeller.Equal(*seller1) && sellerCompany == nil + })) + ts.True(pie.Any(entities, func(sale *models.Sale) bool { + // in this case sale.Seller will also be nil + // but we can now it's really null in the db because err is nil + saleSeller, err := sale.GetSeller() + return err == nil && saleSeller == nil + })) +} + +func (ts *PreloadConditionsIntTestSuite) TestPreloadUIntModel() { + brand1 := ts.createBrand("google") + brand2 := ts.createBrand("apple") + + phone1 := ts.createPhone("pixel", *brand1) + phone2 := ts.createPhone("iphone", *brand2) + + entities, err := ts.crudPhoneService.Query( + conditions.PhonePreloadBrand, + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Phone{phone1, phone2}, entities) + ts.True(pie.Any(entities, func(phone *models.Phone) bool { + phoneBrand, err := phone.GetBrand() + return err == nil && phoneBrand.Equal(*brand1) + })) + ts.True(pie.Any(entities, func(phone *models.Phone) bool { + phoneBrand, err := phone.GetBrand() + return err == nil && phoneBrand.Equal(*brand2) + })) +} + +func (ts *PreloadConditionsIntTestSuite) TestPreloadSelfReferential() { + boss1 := &models.Employee{ + Name: "Xavier", + } + + employee1 := ts.createEmployee("franco", boss1) + employee2 := ts.createEmployee("pierre", nil) + + entities, err := ts.crudEmployeeService.Query( + conditions.EmployeePreloadBoss, + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Employee{boss1, employee1, employee2}, entities) + + ts.True(pie.Any(entities, func(employee *models.Employee) bool { + employeeBoss, err := employee.GetBoss() + return err == nil && employeeBoss != nil && employeeBoss.Equal(*boss1) + })) + ts.True(pie.Any(entities, func(employee *models.Employee) bool { + employeeBoss, err := employee.GetBoss() + return err == nil && employeeBoss == nil + })) +} + +func (ts *PreloadConditionsIntTestSuite) TestPreloadSelfReferentialAtSecondLevel() { + bossBoss := &models.Employee{ + Name: "Xavier", + } + boss := &models.Employee{ + Name: "Vincent", + Boss: bossBoss, + } + employee := ts.createEmployee("franco", boss) + + entities, err := ts.crudEmployeeService.Query( + conditions.EmployeeBoss( + conditions.EmployeeBoss( + conditions.EmployeePreloadAttributes, + ), + ), + conditions.EmployeeName(badorm.Eq("franco")), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Employee{employee}, entities) + + bossLoaded, err := entities[0].GetBoss() + ts.Nil(err) + ts.True(bossLoaded.Equal(*boss)) + + bossBossLoaded, err := bossLoaded.GetBoss() + ts.Nil(err) + ts.True(bossBossLoaded.Equal(*bossBoss)) +} + +func (ts *PreloadConditionsIntTestSuite) TestPreloadWithWhereConditionFilters() { + product1 := ts.createProduct("a_string", 1, 0.0, false, nil) + product1.EmbeddedInt = 1 + product1.GormEmbedded.Int = 2 + err := ts.db.Save(product1).Error + ts.Nil(err) + + product2 := ts.createProduct("", 2, 0.0, false, nil) + + match := ts.createSale(0, product1, nil) + ts.createSale(0, product2, nil) + + entities, err := ts.crudSaleService.Query( + conditions.SaleProduct( + conditions.ProductPreloadAttributes, + conditions.ProductInt(badorm.Eq(1)), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match}, entities) + saleProduct, err := entities[0].GetProduct() + ts.Nil(err) + assert.DeepEqual(ts.T(), product1, saleProduct) + ts.Equal("a_string", saleProduct.String) + ts.Equal(1, saleProduct.EmbeddedInt) + ts.Equal(2, saleProduct.GormEmbedded.Int) +} + +func (ts *PreloadConditionsIntTestSuite) TestPreloadOneToOne() { + capital1 := models.City{ + Name: "Buenos Aires", + } + capital2 := models.City{ + Name: "Paris", + } + + country1 := ts.createCountry("Argentina", capital1) + country2 := ts.createCountry("France", capital2) + + entities, err := ts.crudCityService.Query( + conditions.CityPreloadCountry, + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.City{&capital1, &capital2}, entities) + ts.True(pie.Any(entities, func(city *models.City) bool { + cityCountry, err := city.GetCountry() + if err != nil { + return false + } + + return cityCountry.Equal(*country1) + })) + ts.True(pie.Any(entities, func(city *models.City) bool { + cityCountry, err := city.GetCountry() + if err != nil { + return false + } + + return cityCountry.Equal(*country2) + })) +} + +func (ts *PreloadConditionsIntTestSuite) TestNoPreloadOneToOne() { + capital1 := models.City{ + Name: "Buenos Aires", + } + + ts.createCountry("Argentina", capital1) + + entities, err := ts.crudCityService.Query() + ts.Nil(err) + + EqualList(&ts.Suite, []*models.City{&capital1}, entities) + _, err = entities[0].GetCountry() + ts.ErrorIs(err, badorm.ErrRelationNotLoaded) +} + +func (ts *PreloadConditionsIntTestSuite) TestPreloadOneToOneReversed() { + capital1 := models.City{ + Name: "Buenos Aires", + } + capital2 := models.City{ + Name: "Paris", + } + + country1 := ts.createCountry("Argentina", capital1) + country2 := ts.createCountry("France", capital2) + + entities, err := ts.crudCountryService.Query( + conditions.CountryPreloadCapital, + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Country{country1, country2}, entities) + ts.True(pie.Any(entities, func(country *models.Country) bool { + countryCapital, err := country.GetCapital() + return err == nil && countryCapital.Equal(capital1) + })) + ts.True(pie.Any(entities, func(country *models.Country) bool { + countryCapital, err := country.GetCapital() + return err == nil && countryCapital.Equal(capital2) + })) +} + +func (ts *PreloadConditionsIntTestSuite) TestPreloadHasManyReversed() { + company1 := ts.createCompany("ditrit") + company2 := ts.createCompany("orness") + + seller1 := ts.createSeller("franco", company1) + seller2 := ts.createSeller("agustin", company2) + + entities, err := ts.crudSellerService.Query( + conditions.SellerPreloadCompany, + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Seller{seller1, seller2}, entities) + ts.True(pie.Any(entities, func(seller *models.Seller) bool { + sellerCompany, err := seller.GetCompany() + return err == nil && sellerCompany.Equal(*company1) + })) + ts.True(pie.Any(entities, func(seller *models.Seller) bool { + sellerCompany, err := seller.GetCompany() + return err == nil && sellerCompany.Equal(*company2) + })) +} + +func (ts *PreloadConditionsIntTestSuite) TestPreloadDifferentEntitiesWithConditions() { + product1 := ts.createProduct("", 1, 0.0, false, nil) + product2 := ts.createProduct("", 2, 0.0, false, nil) + + seller1 := ts.createSeller("franco", nil) + seller2 := ts.createSeller("agustin", nil) + + match := ts.createSale(0, product1, seller1) + ts.createSale(0, product2, seller2) + ts.createSale(0, product1, seller2) + ts.createSale(0, product2, seller1) + + entities, err := ts.crudSaleService.Query( + conditions.SaleProduct( + conditions.ProductPreloadAttributes, + conditions.ProductInt(badorm.Eq(1)), + ), + conditions.SaleSeller( + conditions.SellerPreloadAttributes, + conditions.SellerName(badorm.Eq("franco")), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match}, entities) + saleProduct, err := entities[0].GetProduct() + ts.Nil(err) + assert.DeepEqual(ts.T(), product1, saleProduct) + + saleSeller, err := entities[0].GetSeller() + ts.Nil(err) + assert.DeepEqual(ts.T(), seller1, saleSeller) +} + +func (ts *PreloadConditionsIntTestSuite) TestPreloadDifferentEntitiesWithoutConditions() { + parentParent := &models.ParentParent{} + err := ts.db.Create(parentParent).Error + ts.Nil(err) + + parent1 := &models.Parent1{ParentParent: *parentParent} + err = ts.db.Create(parent1).Error + ts.Nil(err) + + parent2 := &models.Parent2{ParentParent: *parentParent} + err = ts.db.Create(parent2).Error + ts.Nil(err) + + child := &models.Child{Parent1: *parent1, Parent2: *parent2} + err = ts.db.Create(child).Error + ts.Nil(err) + + entities, err := ts.crudChildService.Query( + conditions.ChildPreloadParent1, + conditions.ChildPreloadParent2, + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Child{child}, entities) + childParent1, err := entities[0].GetParent1() + ts.Nil(err) + assert.DeepEqual(ts.T(), parent1, childParent1) + + childParent2, err := entities[0].GetParent2() + ts.Nil(err) + assert.DeepEqual(ts.T(), parent2, childParent2) +} + +func (ts *PreloadConditionsIntTestSuite) TestPreloadRelations() { + parentParent := &models.ParentParent{} + err := ts.db.Create(parentParent).Error + ts.Nil(err) + + parent1 := &models.Parent1{ParentParent: *parentParent} + err = ts.db.Create(parent1).Error + ts.Nil(err) + + parent2 := &models.Parent2{ParentParent: *parentParent} + err = ts.db.Create(parent2).Error + ts.Nil(err) + + child := &models.Child{Parent1: *parent1, Parent2: *parent2} + err = ts.db.Create(child).Error + ts.Nil(err) + + entities, err := ts.crudChildService.Query( + conditions.ChildPreloadRelations..., + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Child{child}, entities) + childParent1, err := entities[0].GetParent1() + ts.Nil(err) + assert.DeepEqual(ts.T(), parent1, childParent1) + + childParent2, err := entities[0].GetParent2() + ts.Nil(err) + assert.DeepEqual(ts.T(), parent2, childParent2) +} + +func (ts *PreloadConditionsIntTestSuite) TestJoinMultipleTimesAndPreloadWithoutCondition() { + parentParent := &models.ParentParent{} + err := ts.db.Create(parentParent).Error + ts.Nil(err) + + parent1 := &models.Parent1{ParentParent: *parentParent} + err = ts.db.Create(parent1).Error + ts.Nil(err) + + parent2 := &models.Parent2{ParentParent: *parentParent} + err = ts.db.Create(parent2).Error + ts.Nil(err) + + child := &models.Child{Parent1: *parent1, Parent2: *parent2} + err = ts.db.Create(child).Error + ts.Nil(err) + + entities, err := ts.crudChildService.Query( + conditions.ChildParent1( + conditions.Parent1PreloadAttributes, + conditions.Parent1PreloadParentParent, + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Child{child}, entities) + childParent1, err := entities[0].GetParent1() + ts.Nil(err) + assert.DeepEqual(ts.T(), parent1, childParent1) + + childParentParent, err := childParent1.GetParentParent() + ts.Nil(err) + assert.DeepEqual(ts.T(), parentParent, childParentParent) +} + +func (ts *PreloadConditionsIntTestSuite) TestJoinMultipleTimesAndPreloadWithCondition() { + parentParent1 := &models.ParentParent{ + Name: "parentParent1", + } + err := ts.db.Create(parentParent1).Error + ts.Nil(err) + + parent11 := &models.Parent1{ParentParent: *parentParent1} + err = ts.db.Create(parent11).Error + ts.Nil(err) + + parent21 := &models.Parent2{ParentParent: *parentParent1} + err = ts.db.Create(parent21).Error + ts.Nil(err) + + child1 := &models.Child{Parent1: *parent11, Parent2: *parent21} + err = ts.db.Create(child1).Error + ts.Nil(err) + + parentParent2 := &models.ParentParent{} + err = ts.db.Create(parentParent2).Error + ts.Nil(err) + + parent12 := &models.Parent1{ParentParent: *parentParent2} + err = ts.db.Create(parent12).Error + ts.Nil(err) + + parent22 := &models.Parent2{ParentParent: *parentParent2} + err = ts.db.Create(parent22).Error + ts.Nil(err) + + child2 := &models.Child{Parent1: *parent12, Parent2: *parent22} + err = ts.db.Create(child2).Error + ts.Nil(err) + + entities, err := ts.crudChildService.Query( + conditions.ChildParent1( + conditions.Parent1PreloadAttributes, + conditions.Parent1ParentParent( + conditions.ParentParentPreloadAttributes, + conditions.ParentParentName(badorm.Eq("parentParent1")), + ), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Child{child1}, entities) + childParent1, err := entities[0].GetParent1() + ts.Nil(err) + assert.DeepEqual(ts.T(), parent11, childParent1) + + childParentParent, err := childParent1.GetParentParent() + ts.Nil(err) + assert.DeepEqual(ts.T(), parentParent1, childParentParent) +} + +func (ts *PreloadConditionsIntTestSuite) TestJoinMultipleTimesAndPreloadDiamond() { + parentParent := &models.ParentParent{} + err := ts.db.Create(parentParent).Error + ts.Nil(err) + + parent1 := &models.Parent1{ParentParent: *parentParent} + err = ts.db.Create(parent1).Error + ts.Nil(err) + + parent2 := &models.Parent2{ParentParent: *parentParent} + err = ts.db.Create(parent2).Error + ts.Nil(err) + + child := &models.Child{Parent1: *parent1, Parent2: *parent2} + err = ts.db.Create(child).Error + ts.Nil(err) + + entities, err := ts.crudChildService.Query( + conditions.ChildParent1( + conditions.Parent1PreloadParentParent, + ), + conditions.ChildParent2( + conditions.Parent2PreloadParentParent, + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Child{child}, entities) + childParent1, err := entities[0].GetParent1() + ts.Nil(err) + assert.DeepEqual(ts.T(), parent1, childParent1) + + childParent2, err := entities[0].GetParent2() + ts.Nil(err) + assert.DeepEqual(ts.T(), parent2, childParent2) + + childParent1Parent, err := childParent1.GetParentParent() + ts.Nil(err) + assert.DeepEqual(ts.T(), parentParent, childParent1Parent) + + childParent2Parent, err := childParent2.GetParentParent() + ts.Nil(err) + assert.DeepEqual(ts.T(), parentParent, childParent2Parent) +} + +func (ts *PreloadConditionsIntTestSuite) TestPreloadCollection() { + company := ts.createCompany("ditrit") + seller1 := ts.createSeller("1", company) + seller2 := ts.createSeller("2", company) + + entities, err := ts.crudCompanyService.Query( + conditions.CompanyPreloadSellers(), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Company{company}, entities) + companySellers, err := entities[0].GetSellers() + ts.Nil(err) + EqualList(&ts.Suite, []models.Seller{*seller1, *seller2}, companySellers) +} + +func (ts *PreloadConditionsIntTestSuite) TestPreloadEmptyCollection() { + company := ts.createCompany("ditrit") + + entities, err := ts.crudCompanyService.Query( + conditions.CompanyPreloadSellers(), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Company{company}, entities) + companySellers, err := entities[0].GetSellers() + ts.Nil(err) + EqualList(&ts.Suite, []models.Seller{}, companySellers) +} + +func (ts *PreloadConditionsIntTestSuite) TestNoPreloadCollection() { + company := ts.createCompany("ditrit") + + entities, err := ts.crudCompanyService.Query() + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Company{company}, entities) + _, err = entities[0].GetSellers() + ts.ErrorIs(err, badorm.ErrRelationNotLoaded) +} + +func (ts *PreloadConditionsIntTestSuite) TestPreloadListAndNestedAttributes() { + company := ts.createCompany("ditrit") + + university1 := ts.createUniversity("uni1") + seller1 := ts.createSeller("1", company) + seller1.University = university1 + err := ts.db.Save(seller1).Error + ts.Nil(err) + + university2 := ts.createUniversity("uni1") + seller2 := ts.createSeller("2", company) + seller2.University = university2 + err = ts.db.Save(seller2).Error + ts.Nil(err) + + entities, err := ts.crudCompanyService.Query( + conditions.CompanyPreloadSellers( + conditions.SellerPreloadUniversity, + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Company{company}, entities) + companySellers, err := entities[0].GetSellers() + ts.Nil(err) + EqualList(&ts.Suite, []models.Seller{*seller1, *seller2}, companySellers) + + ts.True(pie.Any(*entities[0].Sellers, func(seller models.Seller) bool { + sellerUniversity, err := seller.GetUniversity() + return err == nil && sellerUniversity.Equal(*university1) + })) + ts.True(pie.Any(*entities[0].Sellers, func(seller models.Seller) bool { + sellerUniversity, err := seller.GetUniversity() + return err == nil && sellerUniversity.Equal(*university2) + })) +} + +func (ts *PreloadConditionsIntTestSuite) TestPreloadMultipleListsAndNestedAttributes() { + company1 := ts.createCompany("ditrit") + company2 := ts.createCompany("orness") + + university1 := ts.createUniversity("uni1") + seller1 := ts.createSeller("1", company1) + seller1.University = university1 + err := ts.db.Save(seller1).Error + ts.Nil(err) + + university2 := ts.createUniversity("uni1") + seller2 := ts.createSeller("2", company1) + seller2.University = university2 + err = ts.db.Save(seller2).Error + ts.Nil(err) + + seller3 := ts.createSeller("3", company2) + seller3.University = university1 + err = ts.db.Save(seller3).Error + ts.Nil(err) + + seller4 := ts.createSeller("4", company2) + seller4.University = university2 + err = ts.db.Save(seller4).Error + ts.Nil(err) + + entities, err := ts.crudCompanyService.Query( + conditions.CompanyPreloadSellers( + conditions.SellerPreloadUniversity, + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Company{company1, company2}, entities) + + company1Loaded := *utils.FindFirst(entities, func(company *models.Company) bool { + return company.Equal(*company1) + }) + company2Loaded := *utils.FindFirst(entities, func(company *models.Company) bool { + return company.Equal(*company2) + }) + + company1Sellers, err := company1Loaded.GetSellers() + ts.Nil(err) + EqualList(&ts.Suite, []models.Seller{*seller1, *seller2}, company1Sellers) + + var sellerUniversity *models.University + + ts.True(pie.Any(*company1Loaded.Sellers, func(seller models.Seller) bool { + sellerUniversity, err = seller.GetUniversity() + return err == nil && sellerUniversity.Equal(*university1) + })) + ts.True(pie.Any(*company1Loaded.Sellers, func(seller models.Seller) bool { + sellerUniversity, err = seller.GetUniversity() + return err == nil && sellerUniversity.Equal(*university2) + })) + + company2Sellers, err := company2Loaded.GetSellers() + ts.Nil(err) + EqualList(&ts.Suite, []models.Seller{*seller3, *seller4}, company2Sellers) + + ts.True(pie.Any(*company2Loaded.Sellers, func(seller models.Seller) bool { + sellerUniversity, err := seller.GetUniversity() + return err == nil && sellerUniversity.Equal(*university1) + })) + ts.True(pie.Any(*company2Loaded.Sellers, func(seller models.Seller) bool { + sellerUniversity, err := seller.GetUniversity() + return err == nil && sellerUniversity.Equal(*university2) + })) +} + +func (ts *PreloadConditionsIntTestSuite) TestPreloadListAndNestedAttributesWithFiltersReturnsError() { + _, err := ts.crudCompanyService.Query( + conditions.CompanyPreloadSellers( + conditions.SellerUniversity( + conditions.UniversityPreloadAttributes, + conditions.UniversityId(badorm.Eq(badorm.NilUUID)), + ), + ), + ) + ts.ErrorIs(err, badorm.ErrOnlyPreloadsAllowed) + ts.ErrorContains(err, "model: models.Company, field: Sellers") +} + +func (ts *PreloadConditionsIntTestSuite) TestPreloadListAndNestedAttributesWithoutPreloadReturnsError() { + _, err := ts.crudCompanyService.Query( + conditions.CompanyPreloadSellers( + conditions.SellerUniversity(), + ), + ) + ts.ErrorIs(err, badorm.ErrOnlyPreloadsAllowed) + ts.ErrorContains(err, "model: models.Company, field: Sellers") +} diff --git a/testintegration/unsafeCRUDService_test.go b/testintegration/unsafeCRUDService_test.go new file mode 100644 index 00000000..00316b0d --- /dev/null +++ b/testintegration/unsafeCRUDService_test.go @@ -0,0 +1,496 @@ +package testintegration + +import ( + "gorm.io/gorm" + + "github.com/ditrit/badaas/badorm" + "github.com/ditrit/badaas/badorm/unsafe" + "github.com/ditrit/badaas/configuration" + "github.com/ditrit/badaas/testintegration/models" +) + +type CRUDUnsafeServiceIntTestSuite struct { + CRUDServiceCommonIntTestSuite + crudProductService unsafe.CRUDService[models.Product, badorm.UUID] + crudSaleService unsafe.CRUDService[models.Sale, badorm.UUID] + crudSellerService unsafe.CRUDService[models.Seller, badorm.UUID] + crudCountryService unsafe.CRUDService[models.Country, badorm.UUID] + crudCityService unsafe.CRUDService[models.City, badorm.UUID] + crudEmployeeService unsafe.CRUDService[models.Employee, badorm.UUID] + crudBicycleService unsafe.CRUDService[models.Bicycle, badorm.UUID] + crudBrandService unsafe.CRUDService[models.Brand, badorm.UIntID] + crudPhoneService unsafe.CRUDService[models.Phone, badorm.UIntID] +} + +func NewCRUDUnsafeServiceIntTestSuite( + db *gorm.DB, + crudProductService unsafe.CRUDService[models.Product, badorm.UUID], + crudSaleService unsafe.CRUDService[models.Sale, badorm.UUID], + crudSellerService unsafe.CRUDService[models.Seller, badorm.UUID], + crudCountryService unsafe.CRUDService[models.Country, badorm.UUID], + crudCityService unsafe.CRUDService[models.City, badorm.UUID], + crudEmployeeService unsafe.CRUDService[models.Employee, badorm.UUID], + crudBicycleService unsafe.CRUDService[models.Bicycle, badorm.UUID], + crudBrandService unsafe.CRUDService[models.Brand, badorm.UIntID], + crudPhoneService unsafe.CRUDService[models.Phone, badorm.UIntID], +) *CRUDUnsafeServiceIntTestSuite { + return &CRUDUnsafeServiceIntTestSuite{ + CRUDServiceCommonIntTestSuite: CRUDServiceCommonIntTestSuite{ + db: db, + }, + crudProductService: crudProductService, + crudSaleService: crudSaleService, + crudSellerService: crudSellerService, + crudCountryService: crudCountryService, + crudCityService: crudCityService, + crudEmployeeService: crudEmployeeService, + crudBicycleService: crudBicycleService, + crudBrandService: crudBrandService, + crudPhoneService: crudPhoneService, + } +} + +// ------------------------- Query -------------------------------- + +func (ts *CRUDUnsafeServiceIntTestSuite) TestGetEntitiesUnsafeWithoutConditionsReturnsEmptyIfNotEntitiesCreated() { + entities, err := ts.crudProductService.Query(map[string]any{}) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{}, entities) +} + +func (ts *CRUDUnsafeServiceIntTestSuite) TestGetEntitiesUnsafeWithoutConditionsReturnsTheOnlyOneIfOneEntityCreated() { + match := ts.createProduct("", 0, 0, false, nil) + + entities, err := ts.crudProductService.Query(map[string]any{}) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *CRUDUnsafeServiceIntTestSuite) TestGetEntitiesUnsafeWithoutConditionsReturnsTheListWhenMultipleCreated() { + match1 := ts.createProduct("", 0, 0, false, nil) + match2 := ts.createProduct("", 0, 0, false, nil) + match3 := ts.createProduct("", 0, 0, false, nil) + + entities, err := ts.crudProductService.Query(map[string]any{}) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2, match3}, entities) +} + +func (ts *CRUDUnsafeServiceIntTestSuite) TestGetEntitiesUnsafeWithConditionsReturnsEmptyIfNotEntitiesCreated() { + params := map[string]any{ + "string_something_else": "not_created", + } + entities, err := ts.crudProductService.Query(params) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{}, entities) +} + +func (ts *CRUDUnsafeServiceIntTestSuite) TestGetEntitiesUnsafeWithConditionsReturnsEmptyIfNothingMatch() { + ts.createProduct("something_else", 0, 0, false, nil) + + params := map[string]any{ + "string_something_else": "not_match", + } + entities, err := ts.crudProductService.Query(params) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{}, entities) +} + +func (ts *CRUDUnsafeServiceIntTestSuite) TestGetEntitiesUnsafeWithConditionsReturnsOneIfOnlyOneMatch() { + match := ts.createProduct("match", 0, 0, false, nil) + ts.createProduct("not_match", 0, 0, false, nil) + + params := map[string]any{ + "string_something_else": "match", + } + entities, err := ts.crudProductService.Query(params) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *CRUDUnsafeServiceIntTestSuite) TestGetEntitiesUnsafeWithConditionsReturnsMultipleIfMultipleMatch() { + match1 := ts.createProduct("match", 0, 0, false, nil) + match2 := ts.createProduct("match", 0, 0, false, nil) + ts.createProduct("not_match", 0, 0, false, nil) + + params := map[string]any{ + "string_something_else": "match", + } + entities, err := ts.crudProductService.Query(params) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} + +func (ts *CRUDUnsafeServiceIntTestSuite) TestGetEntitiesUnsafeWithConditionThatDoesNotExistReturnsDBError() { + ts.createProduct("match", 0, 0, false, nil) + + params := map[string]any{ + "not_exists": "not_exists", + } + _, err := ts.crudProductService.Query(params) + ts.NotNil(err) +} + +func (ts *CRUDUnsafeServiceIntTestSuite) TestGetEntitiesUnsafeWithConditionOfIntType() { + match := ts.createProduct("match", 1, 0, false, nil) + ts.createProduct("not_match", 2, 0, false, nil) + + params := map[string]any{ + "int": 1, + } + entities, err := ts.crudProductService.Query(params) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *CRUDUnsafeServiceIntTestSuite) TestGetEntitiesUnsafeWithConditionOfIncorrectType() { + ts.createProduct("not_match", 1, 0, false, nil) + + params := map[string]any{ + "int": "not_an_int", + } + result, err := ts.crudProductService.Query(params) + + switch getDBDialector() { + case configuration.MySQL, configuration.SQLite: + // mysql and sqlite simply matches nothing + ts.Nil(err) + ts.Len(result, 0) + case configuration.PostgreSQL, configuration.SQLServer: + // postgresql and sqlserver do the verification + ts.NotNil(err) + } +} + +func (ts *CRUDUnsafeServiceIntTestSuite) TestGetEntitiesUnsafeWithConditionOfFloatType() { + match := ts.createProduct("match", 0, 1.1, false, nil) + ts.createProduct("not_match", 0, 2.2, false, nil) + + params := map[string]any{ + "float": 1.1, + } + entities, err := ts.crudProductService.Query(params) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *CRUDUnsafeServiceIntTestSuite) TestGetEntitiesUnsafeWithConditionOfBoolType() { + match := ts.createProduct("match", 0, 0.0, true, nil) + ts.createProduct("not_match", 0, 0.0, false, nil) + + params := map[string]any{ + "bool": true, + } + entities, err := ts.crudProductService.Query(params) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *CRUDUnsafeServiceIntTestSuite) TestGetEntitiesUnsafeWithConditionOfRelationType() { + product1 := ts.createProduct("", 0, 0.0, false, nil) + product2 := ts.createProduct("", 0, 0.0, false, nil) + + seller1 := ts.createSeller("franco", nil) + seller2 := ts.createSeller("agustin", nil) + + match := ts.createSale(0, product1, seller1) + ts.createSale(0, product2, seller2) + + params := map[string]any{ + "product_id": product1.ID.String(), + } + entities, err := ts.crudSaleService.Query(params) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match}, entities) +} + +func (ts *CRUDUnsafeServiceIntTestSuite) TestGetEntitiesUnsafeWithMultipleConditionsOfDifferentTypesWorks() { + match1 := ts.createProduct("match", 1, 0.0, true, nil) + match2 := ts.createProduct("match", 1, 0.0, true, nil) + + ts.createProduct("not_match", 1, 0.0, true, nil) + ts.createProduct("match", 2, 0.0, true, nil) + + params := map[string]any{ + "string_something_else": "match", + "int": 1, + "bool": true, + } + entities, err := ts.crudProductService.Query(params) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} + +func (ts *CRUDUnsafeServiceIntTestSuite) TestGetEntitiesUnsafeWithConditionsOnUIntModel() { + match := ts.createBrand("match") + ts.createBrand("not_match") + + params := map[string]any{ + "name": "match", + } + entities, err := ts.crudBrandService.Query(params) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Brand{match}, entities) +} + +func (ts *CRUDUnsafeServiceIntTestSuite) TestGetEntitiesUnsafeWithConditionThatJoinsUintBelongsTo() { + brand1 := ts.createBrand("google") + brand2 := ts.createBrand("apple") + + match := ts.createPhone("pixel", *brand1) + ts.createPhone("iphone", *brand2) + + params := map[string]any{ + "Brand": map[string]any{ + "name": "google", + }, + } + entities, err := ts.crudPhoneService.Query(params) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Phone{match}, entities) +} + +func (ts *CRUDUnsafeServiceIntTestSuite) TestGetEntitiesUnsafeWithConditionThatJoinsBelongsTo() { + product1 := ts.createProduct("", 1, 0.0, false, nil) + product2 := ts.createProduct("", 2, 0.0, false, nil) + + match := ts.createSale(0, product1, nil) + ts.createSale(0, product2, nil) + + params := map[string]any{ + "Product": map[string]any{ + "int": 1, + }, + } + entities, err := ts.crudSaleService.Query(params) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match}, entities) +} + +func (ts *CRUDUnsafeServiceIntTestSuite) TestGetEntitiesUnsafeWithConditionThatJoinsHasOneOptional() { + product1 := ts.createProduct("", 1, 0.0, false, nil) + product2 := ts.createProduct("", 2, 0.0, false, nil) + + seller1 := ts.createSeller("franco", nil) + seller2 := ts.createSeller("agustin", nil) + + match := ts.createSale(0, product1, seller1) + ts.createSale(0, product2, seller2) + + params := map[string]any{ + "Seller": map[string]any{ + "name": "franco", + }, + } + entities, err := ts.crudSaleService.Query(params) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match}, entities) +} + +func (ts *CRUDUnsafeServiceIntTestSuite) TestGetEntitiesUnsafeWithConditionThatJoinsHasOneSelfReferential() { + boss1 := &models.Employee{ + Name: "Xavier", + } + boss2 := &models.Employee{ + Name: "Vincent", + } + + match := ts.createEmployee("franco", boss1) + ts.createEmployee("pierre", boss2) + + params := map[string]any{ + "Boss": map[string]any{ + "name": "Xavier", + }, + } + entities, err := ts.crudEmployeeService.Query(params) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Employee{match}, entities) +} + +func (ts *CRUDUnsafeServiceIntTestSuite) TestGetEntitiesUnsafeWithConditionThatJoinsOneToOne() { + capital1 := models.City{ + Name: "Buenos Aires", + } + capital2 := models.City{ + Name: "Paris", + } + + ts.createCountry("Argentina", capital1) + ts.createCountry("France", capital2) + + params := map[string]any{ + "Country": map[string]any{ + "name": "Argentina", + }, + } + entities, err := ts.crudCityService.Query(params) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.City{&capital1}, entities) +} + +func (ts *CRUDUnsafeServiceIntTestSuite) TestGetEntitiesUnsafeWithConditionThatJoinsOneToOneReversed() { + capital1 := models.City{ + Name: "Buenos Aires", + } + capital2 := models.City{ + Name: "Paris", + } + + country1 := ts.createCountry("Argentina", capital1) + ts.createCountry("France", capital2) + + params := map[string]any{ + "Capital": map[string]any{ + "name": "Buenos Aires", + }, + } + entities, err := ts.crudCountryService.Query(params) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Country{country1}, entities) +} + +func (ts *CRUDUnsafeServiceIntTestSuite) TestGetEntitiesUnsafeWithConditionThatJoinsReturnsErrorIfNoRelation() { + params := map[string]any{ + "NotExists": map[string]any{ + "int": 1, + }, + } + _, err := ts.crudSaleService.Query(params) + ts.ErrorContains(err, "Sale has not attribute named NotExists or NotExistsID") +} + +func (ts *CRUDUnsafeServiceIntTestSuite) TestGetEntitiesUnsafeWithConditionThatJoinsOnHasMany() { + company1 := ts.createCompany("ditrit") + company2 := ts.createCompany("orness") + + match := ts.createSeller("franco", company1) + ts.createSeller("agustin", company2) + + params := map[string]any{ + "Company": map[string]any{ + "name": "ditrit", + }, + } + entities, err := ts.crudSellerService.Query(params) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Seller{match}, entities) +} + +func (ts *CRUDUnsafeServiceIntTestSuite) TestGetEntitiesUnsafeWithConditionThatJoinsOnDifferentAttributes() { + product1 := ts.createProduct("match", 1, 0.0, false, nil) + product2 := ts.createProduct("match", 2, 0.0, false, nil) + + seller1 := ts.createSeller("franco", nil) + seller2 := ts.createSeller("agustin", nil) + + match := ts.createSale(0, product1, seller1) + ts.createSale(0, product2, seller2) + + params := map[string]any{ + "Product": map[string]any{ + "int": 1, + "string_something_else": "match", + }, + } + entities, err := ts.crudSaleService.Query(params) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match}, entities) +} + +func (ts *CRUDUnsafeServiceIntTestSuite) TestGetEntitiesUnsafeWithConditionThatJoinsAndFiltersTheMainEntity() { + product1 := ts.createProduct("", 1, 0.0, false, nil) + product2 := ts.createProduct("", 2, 0.0, false, nil) + + seller1 := ts.createSeller("franco", nil) + seller2 := ts.createSeller("agustin", nil) + + match := ts.createSale(1, product1, seller1) + ts.createSale(2, product2, seller2) + ts.createSale(2, product1, seller2) + + params := map[string]any{ + "Product": map[string]any{ + "int": 1, + }, + "code": 1, + } + entities, err := ts.crudSaleService.Query(params) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match}, entities) +} + +func (ts *CRUDUnsafeServiceIntTestSuite) TestGetEntitiesUnsafeWithConditionThatJoinsDifferentEntities() { + product1 := ts.createProduct("", 1, 0.0, false, nil) + product2 := ts.createProduct("", 2, 0.0, false, nil) + + seller1 := ts.createSeller("franco", nil) + seller2 := ts.createSeller("agustin", nil) + + match := ts.createSale(0, product1, seller1) + ts.createSale(0, product2, seller2) + ts.createSale(0, product1, seller2) + ts.createSale(0, product2, seller1) + + params := map[string]any{ + "Product": map[string]any{ + "int": 1, + }, + "Seller": map[string]any{ + "name": "franco", + }, + } + entities, err := ts.crudSaleService.Query(params) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match}, entities) +} + +func (ts *CRUDUnsafeServiceIntTestSuite) TestGetEntitiesUnsafeWithConditionThatJoinsMultipleTimes() { + product1 := ts.createProduct("", 0, 0.0, false, nil) + product2 := ts.createProduct("", 0, 0.0, false, nil) + + company1 := ts.createCompany("ditrit") + company2 := ts.createCompany("orness") + + seller1 := ts.createSeller("franco", company1) + seller2 := ts.createSeller("agustin", company2) + + match := ts.createSale(0, product1, seller1) + ts.createSale(0, product2, seller2) + + params := map[string]any{ + "Seller": map[string]any{ + "name": "franco", + "Company": map[string]any{ + "name": "ditrit", + }, + }, + } + entities, err := ts.crudSaleService.Query(params) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match}, entities) +} diff --git a/testintegration/where_conditions_test.go b/testintegration/where_conditions_test.go new file mode 100644 index 00000000..98da17e0 --- /dev/null +++ b/testintegration/where_conditions_test.go @@ -0,0 +1,603 @@ +package testintegration + +import ( + "log" + + "gorm.io/gorm" + "gotest.tools/assert" + + "github.com/ditrit/badaas/badorm" + "github.com/ditrit/badaas/badorm/mysql" + "github.com/ditrit/badaas/badorm/unsafe" + "github.com/ditrit/badaas/configuration" + "github.com/ditrit/badaas/testintegration/conditions" + "github.com/ditrit/badaas/testintegration/models" +) + +type WhereConditionsIntTestSuite struct { + CRUDServiceCommonIntTestSuite + crudProductService badorm.CRUDService[models.Product, badorm.UUID] + crudSaleService badorm.CRUDService[models.Sale, badorm.UUID] + crudBrandService badorm.CRUDService[models.Brand, badorm.UIntID] +} + +func NewWhereConditionsIntTestSuite( + db *gorm.DB, + crudProductService badorm.CRUDService[models.Product, badorm.UUID], + crudSaleService badorm.CRUDService[models.Sale, badorm.UUID], + crudBrandService badorm.CRUDService[models.Brand, badorm.UIntID], +) *WhereConditionsIntTestSuite { + return &WhereConditionsIntTestSuite{ + CRUDServiceCommonIntTestSuite: CRUDServiceCommonIntTestSuite{ + db: db, + }, + crudProductService: crudProductService, + crudSaleService: crudSaleService, + crudBrandService: crudBrandService, + } +} + +// ------------------------- GetByID -------------------------------- + +func (ts *WhereConditionsIntTestSuite) TestGetEntityReturnsErrorIfNotEntityCreated() { + _, err := ts.crudProductService.GetByID(badorm.NilUUID) + ts.Error(err, gorm.ErrRecordNotFound) +} + +func (ts *WhereConditionsIntTestSuite) TestGetEntityReturnsErrorIfNotEntityMatch() { + ts.createProduct("", 0, 0, false, nil) + + _, err := ts.crudProductService.GetByID(badorm.NewUUID()) + ts.Error(err, gorm.ErrRecordNotFound) +} + +func (ts *WhereConditionsIntTestSuite) TestGetEntityReturnsTheEntityIfItIsCreate() { + match := ts.createProduct("", 0, 0, false, nil) + + entity, err := ts.crudProductService.GetByID(match.ID) + ts.Nil(err) + + assert.DeepEqual(ts.T(), match, entity) +} + +// ------------------------- Query -------------------------------- + +func (ts *WhereConditionsIntTestSuite) TestGetEntitiesReturnsEmptyIfNotEntitiesCreated() { + entities, err := ts.crudProductService.Query() + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestGetEntitiesReturnsTheOnlyOneIfOneEntityCreated() { + match := ts.createProduct("", 0, 0, false, nil) + + entities, err := ts.crudProductService.Query() + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestGetEntitiesReturnsTheListWhenMultipleCreated() { + match1 := ts.createProduct("", 0, 0, false, nil) + match2 := ts.createProduct("", 0, 0, false, nil) + match3 := ts.createProduct("", 0, 0, false, nil) + + entities, err := ts.crudProductService.Query() + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2, match3}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestConditionsReturnsEmptyIfNotEntitiesCreated() { + entities, err := ts.crudProductService.Query( + conditions.ProductString( + badorm.Eq("not_created"), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestConditionsReturnsEmptyIfNothingMatch() { + ts.createProduct("something_else", 0, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductString( + badorm.Eq("not_match"), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestConditionsReturnsOneIfOnlyOneMatch() { + match := ts.createProduct("match", 0, 0, false, nil) + ts.createProduct("not_match", 0, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductString( + badorm.Eq("match"), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestConditionsReturnsMultipleIfMultipleMatch() { + match1 := ts.createProduct("match", 0, 0, false, nil) + match2 := ts.createProduct("match", 0, 0, false, nil) + ts.createProduct("not_match", 0, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductString( + badorm.Eq("match"), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestConditionOfIntType() { + match := ts.createProduct("match", 1, 0, false, nil) + ts.createProduct("not_match", 2, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductInt( + badorm.Eq(1), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestConditionOfFloatType() { + match := ts.createProduct("match", 0, 1.1, false, nil) + ts.createProduct("not_match", 0, 2.2, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductFloat( + badorm.Eq(1.1), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestConditionOfBoolType() { + match := ts.createProduct("match", 0, 0.0, true, nil) + ts.createProduct("not_match", 0, 0.0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductBool( + badorm.Eq(true), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestMultipleConditionsOfDifferentTypesWorks() { + match1 := ts.createProduct("match", 1, 0.0, true, nil) + match2 := ts.createProduct("match", 1, 0.0, true, nil) + + ts.createProduct("not_match", 1, 0.0, true, nil) + ts.createProduct("match", 2, 0.0, true, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductString(badorm.Eq("match")), + conditions.ProductInt(badorm.Eq(1)), + conditions.ProductBool(badorm.Eq(true)), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestConditionOfID() { + match := ts.createProduct("", 0, 0.0, false, nil) + ts.createProduct("", 0, 0.0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductId( + badorm.Eq(match.ID), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestConditionOfCreatedAt() { + match := ts.createProduct("", 0, 0.0, false, nil) + ts.createProduct("", 0, 0.0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductCreatedAt(badorm.Eq(match.CreatedAt)), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestDeletedAtConditionIsAddedAutomatically() { + match := ts.createProduct("", 0, 0.0, false, nil) + deleted := ts.createProduct("", 0, 0.0, false, nil) + + ts.Nil(ts.db.Delete(deleted).Error) + + entities, err := ts.crudProductService.Query() + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +// TODO DeletedAt with nil value but not automatic + +func (ts *WhereConditionsIntTestSuite) TestConditionOfDeletedAtNotNil() { + match := ts.createProduct("", 0, 0.0, false, nil) + ts.createProduct("", 0, 0.0, false, nil) + + ts.Nil(ts.db.Delete(match).Error) + + entities, err := ts.crudProductService.Query( + conditions.ProductDeletedAt(badorm.Eq(match.DeletedAt)), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestConditionOfEmbedded() { + match := ts.createProduct("", 0, 0.0, false, nil) + ts.createProduct("", 0, 0.0, false, nil) + + match.EmbeddedInt = 1 + + err := ts.db.Save(match).Error + ts.Nil(err) + + entities, err := ts.crudProductService.Query( + conditions.ProductToBeEmbeddedEmbeddedInt(badorm.Eq(1)), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestConditionOfGormEmbedded() { + match := ts.createProduct("", 0, 0.0, false, nil) + ts.createProduct("", 0, 0.0, false, nil) + + match.GormEmbedded.Int = 1 + + err := ts.db.Save(match).Error + ts.Nil(err) + + entities, err := ts.crudProductService.Query( + conditions.ProductGormEmbeddedInt(badorm.Eq(1)), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestConditionOfPointerTypeWithValue() { + intMatch := 1 + match := ts.createProduct("match", 1, 0, false, &intMatch) + intNotMatch := 2 + ts.createProduct("not_match", 2, 0, false, &intNotMatch) + ts.createProduct("not_match", 2, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductIntPointer(badorm.Eq(1)), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestConditionOfByteArrayWithContent() { + match := ts.createProduct("match", 1, 0, false, nil) + notMatch1 := ts.createProduct("not_match", 2, 0, false, nil) + ts.createProduct("not_match", 2, 0, false, nil) + + match.ByteArray = []byte{1, 2} + notMatch1.ByteArray = []byte{2, 3} + + err := ts.db.Save(match).Error + ts.Nil(err) + + err = ts.db.Save(notMatch1).Error + ts.Nil(err) + + entities, err := ts.crudProductService.Query( + conditions.ProductByteArray(badorm.Eq([]byte{1, 2})), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestConditionOfByteArrayEmpty() { + match := ts.createProduct("match", 1, 0, false, nil) + notMatch1 := ts.createProduct("not_match", 2, 0, false, nil) + ts.createProduct("not_match", 2, 0, false, nil) + + match.ByteArray = []byte{} + notMatch1.ByteArray = []byte{2, 3} + + err := ts.db.Save(match).Error + ts.Nil(err) + + err = ts.db.Save(notMatch1).Error + ts.Nil(err) + + entities, err := ts.crudProductService.Query( + conditions.ProductByteArray(badorm.Eq([]byte{})), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestConditionOfCustomType() { + match := ts.createProduct("match", 1, 0, false, nil) + notMatch1 := ts.createProduct("not_match", 2, 0, false, nil) + ts.createProduct("not_match", 2, 0, false, nil) + + match.MultiString = models.MultiString{"salut", "hola"} + notMatch1.MultiString = models.MultiString{"salut", "hola", "hello"} + + err := ts.db.Save(match).Error + ts.Nil(err) + + err = ts.db.Save(notMatch1).Error + ts.Nil(err) + + entities, err := ts.crudProductService.Query( + conditions.ProductMultiString(badorm.Eq(models.MultiString{"salut", "hola"})), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestConditionOfRelationType() { + product1 := ts.createProduct("", 0, 0.0, false, nil) + product2 := ts.createProduct("", 0, 0.0, false, nil) + + seller1 := ts.createSeller("franco", nil) + seller2 := ts.createSeller("agustin", nil) + + match := ts.createSale(0, product1, seller1) + ts.createSale(0, product2, seller2) + + entities, err := ts.crudSaleService.Query( + conditions.SaleProductId(badorm.Eq(product1.ID)), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestConditionOfRelationTypeOptionalWithValue() { + product1 := ts.createProduct("", 0, 0.0, false, nil) + product2 := ts.createProduct("", 0, 0.0, false, nil) + + seller1 := ts.createSeller("franco", nil) + seller2 := ts.createSeller("agustin", nil) + + match := ts.createSale(0, product1, seller1) + ts.createSale(0, product2, seller2) + + entities, err := ts.crudSaleService.Query( + conditions.SaleSellerId(badorm.Eq(seller1.ID)), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestConditionOfRelationTypeOptionalByNil() { + product1 := ts.createProduct("", 0, 0.0, false, nil) + product2 := ts.createProduct("", 0, 0.0, false, nil) + + seller2 := ts.createSeller("agustin", nil) + + match := ts.createSale(0, product1, nil) + ts.createSale(0, product2, seller2) + + entities, err := ts.crudSaleService.Query( + conditions.SaleSellerId(badorm.IsNull[badorm.UUID]()), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestConditionsOnUIntModel() { + match := ts.createBrand("match") + ts.createBrand("not_match") + + entities, err := ts.crudBrandService.Query( + conditions.BrandName(badorm.Eq("match")), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Brand{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestMultipleConditionsAreConnectedByAnd() { + match := ts.createProduct("match", 3, 0, false, nil) + ts.createProduct("not_match", 5, 0, false, nil) + ts.createProduct("not_match", 1, 0, false, nil) + ts.createProduct("not_match", 2, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductInt(badorm.GtOrEq(3)), + conditions.ProductInt(badorm.LtOrEq(4)), + conditions.ProductString(badorm.Eq("match")), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestNot() { + match1 := ts.createProduct("match", 1, 0, false, nil) + match2 := ts.createProduct("match", 3, 0, false, nil) + + ts.createProduct("not_match", 2, 0, false, nil) + ts.createProduct("not_match", 2, 0, false, nil) + + entities, err := ts.crudProductService.Query( + badorm.Not(conditions.ProductInt(badorm.Eq(2))), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestNotWithMultipleConditionsAreConnectedByAnd() { + match1 := ts.createProduct("match", 1, 0, false, nil) + match2 := ts.createProduct("match", 5, 0, false, nil) + + ts.createProduct("not_match", 2, 0, false, nil) + ts.createProduct("not_match", 3, 0, false, nil) + + entities, err := ts.crudProductService.Query( + badorm.Not( + conditions.ProductInt(badorm.Gt(1)), + conditions.ProductInt(badorm.Lt(4)), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestOr() { + match1 := ts.createProduct("match", 2, 0, false, nil) + match2 := ts.createProduct("match", 3, 0, false, nil) + match3 := ts.createProduct("match_3", 3, 0, false, nil) + + ts.createProduct("not_match", 1, 0, false, nil) + ts.createProduct("not_match", 4, 0, false, nil) + + entities, err := ts.crudProductService.Query( + badorm.Or( + conditions.ProductInt(badorm.Eq(2)), + conditions.ProductInt(badorm.Eq(3)), + conditions.ProductString(badorm.Eq("match_3")), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2, match3}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestNotOr() { + match1 := ts.createProduct("match", 1, 0, false, nil) + match2 := ts.createProduct("match", 5, 0, false, nil) + match3 := ts.createProduct("match", 4, 0, false, nil) + + ts.createProduct("not_match", 2, 0, false, nil) + ts.createProduct("not_match_string", 3, 0, false, nil) + + entities, err := ts.crudProductService.Query( + badorm.Not( + badorm.Or( + conditions.ProductInt(badorm.Eq(2)), + conditions.ProductString(badorm.Eq("not_match_string")), + ), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2, match3}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestXor() { + match1 := ts.createProduct("", 1, 0, false, nil) + match2 := ts.createProduct("", 7, 0, false, nil) + + ts.createProduct("", 5, 0, false, nil) + ts.createProduct("", 4, 0, false, nil) + + var xorCondition badorm.WhereCondition[models.Product] + + switch getDBDialector() { + case configuration.PostgreSQL, configuration.SQLite, configuration.SQLServer: + log.Println("Xor not compatible") + case configuration.MySQL: + xorCondition = mysql.Xor( + conditions.ProductInt(badorm.Lt(6)), + conditions.ProductInt(badorm.Gt(3)), + ) + } + + if xorCondition != nil { + entities, err := ts.crudProductService.Query(xorCondition) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) + } +} + +func (ts *WhereConditionsIntTestSuite) TestMultipleConditionsDifferentOperators() { + match1 := ts.createProduct("match", 1, 0.0, true, nil) + match2 := ts.createProduct("match", 1, 0.0, true, nil) + + ts.createProduct("not_match", 1, 0.0, true, nil) + ts.createProduct("match", 2, 0.0, true, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductString(badorm.Eq("match")), + conditions.ProductInt(badorm.Lt(2)), + conditions.ProductBool(badorm.NotEq(false)), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestUnsafeCondition() { + match1 := ts.createProduct("match", 1, 0.0, true, nil) + match2 := ts.createProduct("match", 1, 0.0, true, nil) + + ts.createProduct("not_match", 2, 0.0, true, nil) + + entities, err := ts.crudProductService.Query( + unsafe.NewCondition[models.Product]("%s.int = ?", 1), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestEmptyConnectionConditionMakesNothing() { + match1 := ts.createProduct("match", 1, 0.0, true, nil) + match2 := ts.createProduct("match", 1, 0.0, true, nil) + + entities, err := ts.crudProductService.Query( + badorm.And[models.Product](), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestEmptyContainerConditionReturnsError() { + _, err := ts.crudProductService.Query( + badorm.Not[models.Product](), + ) + ts.ErrorIs(err, badorm.ErrEmptyConditions) +} diff --git a/tools/badctl/README.md b/tools/badctl/README.md new file mode 100644 index 00000000..1259eae2 --- /dev/null +++ b/tools/badctl/README.md @@ -0,0 +1,23 @@ +# badctl: the BaDaaS controller + +`badctl` is the command line tool that makes it possible to configure and run your BaDaaS applications easily. + +## Documentation + + + +## Contributing + +See [this section](../../docs/contributing/contributing.md) to view the badaas contribution guidelines. + +You can make modifications to the badctl source code and compile it locally with: + +```bash +go build . +``` + +You can then run the badctl executable directly or add a link in your $GOPATH to run it from a project: + +```bash +ln -sf badctl $GOPATH/bin/badctl +``` diff --git a/tools/badctl/cmd/cmderrors/errors.go b/tools/badctl/cmd/cmderrors/errors.go new file mode 100644 index 00000000..c5530986 --- /dev/null +++ b/tools/badctl/cmd/cmderrors/errors.go @@ -0,0 +1,13 @@ +package cmderrors + +import ( + "fmt" + "os" +) + +func FailErr(err error) { + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } +} diff --git a/tools/badctl/cmd/gen/conditions/codeGenerator.go b/tools/badctl/cmd/gen/conditions/codeGenerator.go new file mode 100644 index 00000000..7671dc32 --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/codeGenerator.go @@ -0,0 +1,6 @@ +package conditions + +type CodeGenerator[T any] interface { + Into(file *File) error + ForEachField(file *File, fields []Field) []T +} diff --git a/tools/badctl/cmd/gen/conditions/condition.go b/tools/badctl/cmd/gen/conditions/condition.go new file mode 100644 index 00000000..174ef1de --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/condition.go @@ -0,0 +1,384 @@ +package conditions + +import ( + "go/types" + + "github.com/dave/jennifer/jen" + "github.com/ettle/strcase" + + "github.com/ditrit/badaas/tools/badctl/cmd/log" +) + +const ( + // badorm/condition.go + badORMCondition = "Condition" + badORMFieldCondition = "FieldCondition" + badORMWhereCondition = "WhereCondition" + badORMJoinCondition = "JoinCondition" + badORMIJoinCondition = "IJoinCondition" + badORMFieldIdentifier = "FieldIdentifier" + badORMNewCollectionPreload = "NewCollectionPreloadCondition" + // badorm/operator.go + badORMOperator = "Operator" + // badorm/baseModels.go + uIntID = "UIntID" + uuid = "UUID" + uuidModel = "UUIDModel" + uIntModel = "UIntModel" +) + +type Condition struct { + codes []jen.Code + param *JenParam + destPkg string + fieldIdentifier string + preloadName string +} + +func NewCondition(destPkg string, objectType Type, field Field) *Condition { + condition := &Condition{ + param: NewJenParam(), + destPkg: destPkg, + } + condition.generate(objectType, field) + + return condition +} + +// Generate the condition between the object and the field +func (condition *Condition) generate(objectType Type, field Field) { + switch fieldType := field.GetType().(type) { + case *types.Basic: + // the field is a basic type (string, int, etc) + // adapt param to that type and generate a WhereCondition + condition.param.ToBasicKind(fieldType) + condition.generateWhere( + objectType, + field, + ) + case *types.Named: + // the field is a named type (user defined structs) + condition.generateForNamedType( + objectType, + field, + ) + case *types.Pointer: + // the field is a pointer + condition.generate( + objectType, + field.ChangeType(fieldType.Elem()), + ) + case *types.Slice: + // the field is a slice + // adapt param to slice and + // generate code for the type of the elements of the slice + condition.param.ToSlice() + condition.generateForSlice( + objectType, + field.ChangeType(fieldType.Elem()), + ) + default: + log.Logger.Debugf("struct field type not handled: %T", fieldType) + } +} + +// Generate condition between the object and the field when the field is a slice +func (condition *Condition) generateForSlice(objectType Type, field Field) { + switch elemType := field.GetType().(type) { + case *types.Basic: + // slice of basic types ([]string, []int, etc.) + // the only one supported directly by gorm is []byte + // but the others can be used after configuration in some dbs + condition.generate( + objectType, + field, + ) + case *types.Pointer: + // slice of pointers, generate code for a slice of the pointed type + condition.generateForSlice( + objectType, + field.ChangeType(elemType.Elem()), + ) + case *types.Named: + _, err := field.Type.BadORMModelStruct() + if err == nil { + // field is a BaDORM Model + condition.generateCollectionPreload(objectType, field) + } + default: + log.Logger.Debugf("struct field list elem type not handled: %T", elemType) + } +} + +// Generate condition between object and field when the field is a named type (user defined struct) +func (condition *Condition) generateForNamedType(objectType Type, field Field) { + _, err := field.Type.BadORMModelStruct() + + if err == nil { + // field is a BaDORM Model + condition.generateForBadormModel(objectType, field) + } else { + // field is not a BaDORM Model + if field.Type.IsGormCustomType() || field.TypeString() == "time.Time" || field.IsBadORMID() { + // field is a Gorm Custom type (implements Scanner and Valuer interfaces) + // or a named type supported by gorm (time.Time, gorm.DeletedAt) + condition.param.ToCustomType(condition.destPkg, field.Type) + condition.generateWhere( + objectType, + field, + ) + } else { + log.Logger.Debugf("struct field type not handled: %s", field.TypeString()) + } + } +} + +// Generate condition between object and field when the field is a BaDORM Model +func (condition *Condition) generateForBadormModel(objectType Type, field Field) { + _, err := objectType.GetFK(field) + if err == nil { + // has the fk -> belongsTo relation + condition.generateJoinWithFK( + objectType, + field, + ) + } else { + // has not the fk -> hasOne relation + condition.generateJoinWithoutFK( + objectType, + field, + ) + } +} + +// Generate a WhereCondition between object and field +func (condition *Condition) generateWhere(objectType Type, field Field) { + objectTypeQual := jen.Qual( + getRelativePackagePath(condition.destPkg, objectType), + objectType.Name(), + ) + + fieldCondition := jen.Qual( + badORMPath, badORMFieldCondition, + ).Types( + objectTypeQual, + condition.param.GenericType(), + ) + + whereCondition := jen.Qual( + badORMPath, badORMWhereCondition, + ).Types( + objectTypeQual, + ) + + conditionName := getConditionName(objectType, field) + log.Logger.Debugf("Generated %q", conditionName) + + condition.codes = append( + condition.codes, + jen.Func().Id( + conditionName, + ).Params( + condition.param.Statement(), + ).Add( + whereCondition, + ).Block( + jen.Return( + fieldCondition.Clone().Values(jen.Dict{ + jen.Id("Operator"): jen.Id("operator"), + jen.Id("FieldIdentifier"): condition.createFieldIdentifier( + objectType.Name(), field, conditionName, + ), + }), + ), + ), + ) +} + +// create a variable containing the definition of the field identifier +// to use it in the where condition and in the preload condition +func (condition *Condition) createFieldIdentifier(objectName string, field Field, conditionName string) *jen.Statement { + fieldIdentifierValues := jen.Dict{ + jen.Id("ModelType"): jen.Id(getObjectTypeName(objectName)), + jen.Id("Field"): jen.Lit(field.Name), + } + + columnName := field.getColumnName() + + if columnName != "" { + fieldIdentifierValues[jen.Id("Column")] = jen.Lit(columnName) + } + + columnPrefix := field.ColumnPrefix + if columnPrefix != "" { + fieldIdentifierValues[jen.Id("ColumnPrefix")] = jen.Lit(columnPrefix) + } + + fieldIdentifierVar := jen.Qual( + badORMPath, badORMFieldIdentifier, + ).Types( + condition.param.GenericType(), + ).Values(fieldIdentifierValues) + + fieldIdentifierName := conditionName + "Field" + + condition.codes = append( + condition.codes, + jen.Var().Id( + fieldIdentifierName, + ).Op("=").Add(fieldIdentifierVar), + ) + + condition.fieldIdentifier = fieldIdentifierName + + return jen.Qual("", fieldIdentifierName) +} + +// Generate a JoinCondition between the object and field's object +// when object has a foreign key to the field's object +func (condition *Condition) generateJoinWithFK(objectType Type, field Field) { + condition.generateJoin( + objectType, + field, + field.getFKAttribute(), + field.getFKReferencesAttribute(), + ) +} + +// Generate a JoinCondition between the object and field's object +// when object has not a foreign key to the field's object +// (so the field's object has it) +func (condition *Condition) generateJoinWithoutFK(objectType Type, field Field) { + condition.generateJoin( + objectType, + field, + field.getFKReferencesAttribute(), + field.getRelatedTypeFKAttribute(objectType.Name()), + ) +} + +// Generate a JoinCondition +func (condition *Condition) generateJoin(objectType Type, field Field, t1Field, t2Field string) { + t1 := jen.Qual( + getRelativePackagePath(condition.destPkg, objectType), + objectType.Name(), + ) + + t2 := jen.Qual( + getRelativePackagePath(condition.destPkg, field.Type), + field.TypeName(), + ) + + conditionName := getConditionName(objectType, field) + log.Logger.Debugf("Generated %q", conditionName) + + badormT1IJoinCondition := jen.Qual( + badORMPath, badORMIJoinCondition, + ).Types(t1) + badormT2Condition := jen.Qual( + badORMPath, badORMCondition, + ).Types(t2) + badormJoinCondition := jen.Qual( + badORMPath, badORMJoinCondition, + ).Types( + t1, t2, + ) + + condition.codes = append( + condition.codes, + jen.Func().Id( + conditionName, + ).Params( + jen.Id("conditions").Op("...").Add(badormT2Condition), + ).Add( + badormT1IJoinCondition, + ).Block( + jen.Return( + badormJoinCondition.Values(jen.Dict{ + jen.Id("T1Field"): jen.Lit(t1Field), + jen.Id("T2Field"): jen.Lit(t2Field), + jen.Id("RelationField"): jen.Lit(field.Name), + jen.Id("Conditions"): jen.Id("conditions"), + jen.Id("T1PreloadCondition"): jen.Id(getPreloadAttributesName(objectType.Name())), + }), + ), + ), + ) + + // preload for the relation + preloadName := getPreloadRelationName(objectType, field) + condition.codes = append( + condition.codes, + jen.Var().Id( + preloadName, + ).Op("=").Add(jen.Id(conditionName)).Call( + jen.Id(getPreloadAttributesName(field.TypeName())), + ), + ) + condition.preloadName = preloadName +} + +func getPreloadRelationName(objectType Type, field Field) string { + return objectType.Name() + "Preload" + field.Name +} + +func (condition *Condition) generateCollectionPreload(objectType Type, field Field) { + t1 := jen.Qual( + getRelativePackagePath(condition.destPkg, objectType), + objectType.Name(), + ) + + t2 := jen.Qual( + getRelativePackagePath(condition.destPkg, field.Type), + field.TypeName(), + ) + + badormT1Condition := jen.Qual( + badORMPath, badORMCondition, + ).Types(t1) + badormT2IJoinCondition := jen.Qual( + badORMPath, badORMIJoinCondition, + ).Types(t2) + badormNewCollectionPreload := jen.Qual( + badORMPath, badORMNewCollectionPreload, + ).Types( + t1, t2, + ) + + preloadName := getPreloadRelationName(objectType, field) + + condition.codes = append( + condition.codes, + jen.Func().Id( + preloadName, + ).Params( + jen.Id("nestedPreloads").Op("...").Add(badormT2IJoinCondition), + ).Add( + badormT1Condition, + ).Block( + jen.Return( + badormNewCollectionPreload.Call( + jen.Lit(field.Name), + jen.Id("nestedPreloads"), + ), + ), + ), + ) + + condition.preloadName = preloadName + "()" +} + +// Generate condition names +func getConditionName(typeV Type, field Field) string { + return typeV.Name() + strcase.ToPascal(field.NamePrefix) + strcase.ToPascal(field.Name) +} + +// Avoid importing the same package as the destination one +func getRelativePackagePath(destPkg string, typeV Type) string { + srcPkg := typeV.Pkg() + if srcPkg == nil || srcPkg.Name() == destPkg { + return "" + } + + return srcPkg.Path() +} diff --git a/tools/badctl/cmd/gen/conditions/conditionsGenerator.go b/tools/badctl/cmd/gen/conditions/conditionsGenerator.go new file mode 100644 index 00000000..73472110 --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/conditionsGenerator.go @@ -0,0 +1,139 @@ +package conditions + +import ( + "go/types" + + "github.com/dave/jennifer/jen" + "github.com/ettle/strcase" + + "github.com/ditrit/badaas/tools/badctl/cmd/log" +) + +const badORMNewPreloadCondition = "NewPreloadCondition" + +type CodeConditionsGenerator struct { + object types.Object + objectType Type +} + +func NewConditionsGenerator(object types.Object) *CodeConditionsGenerator { + return &CodeConditionsGenerator{ + object: object, + objectType: Type{object.Type()}, + } +} + +// Add conditions for an object in the file +func (cg CodeConditionsGenerator) Into(file *File) error { + fields, err := getFields(cg.objectType) + if err != nil { + return err + } + + log.Logger.Infof("Generating conditions for type %q in %s", cg.object.Name(), file.name) + + cg.addConditionsForEachField(file, fields) + + return nil +} + +func getObjectTypeName(objectType string) string { + return strcase.ToCamel(objectType) + "Type" +} + +// Add one condition for each field of the object +func (cg CodeConditionsGenerator) addConditionsForEachField(file *File, fields []Field) { + conditions := cg.ForEachField(file, fields) + + objectName := cg.object.Name() + objectQual := jen.Qual( + getRelativePackagePath(file.destPkg, cg.objectType), + cg.objectType.Name(), + ) + + preloadAttributesCondition := jen.Var().Id( + getPreloadAttributesName(objectName), + ).Op("=").Add(jen.Qual( + badORMPath, badORMNewPreloadCondition, + )).Types( + objectQual, + ) + fieldIdentifiers := []jen.Code{} + + preloadRelationsCondition := jen.Var().Id( + objectName + "PreloadRelations", + ).Op("=").Index().Add(jen.Qual( + badORMPath, badORMCondition, + )).Types( + objectQual, + ) + relationPreloads := []jen.Code{} + + // object reflect type definition + file.Add( + jen.Var().Id( + getObjectTypeName(objectName), + ).Op("=").Add( + jen.Qual( + "reflect", + "TypeOf", + ).Call(jen.Op("*").New(objectQual)), + ), + ) + + for _, condition := range conditions { + file.Add(condition.codes...) + + // add all field names to the list of fields of the preload condition + if condition.fieldIdentifier != "" { + fieldIdentifiers = append( + fieldIdentifiers, + jen.Qual("", condition.fieldIdentifier), + ) + } + + // add the preload to the list of all possible preloads + if condition.preloadName != "" { + relationPreloads = append( + relationPreloads, + jen.Qual("", condition.preloadName), + ) + } + } + + file.Add(preloadAttributesCondition.Call(fieldIdentifiers...)) + + if len(relationPreloads) > 0 { + file.Add(preloadRelationsCondition.Values(relationPreloads...)) + } +} + +func getPreloadAttributesName(objectName string) string { + return objectName + "PreloadAttributes" +} + +// Generate the conditions for each of the object's fields +func (cg CodeConditionsGenerator) ForEachField(file *File, fields []Field) []Condition { + conditions := []Condition{} + + for _, field := range fields { + log.Logger.Debugf("Generating condition for field %q", field.Name) + + if field.Embedded { + conditions = append( + conditions, + generateForEmbeddedField[Condition]( + file, + field, + cg, + )..., + ) + } else { + conditions = append(conditions, *NewCondition( + file.destPkg, cg.objectType, field, + )) + } + } + + return conditions +} diff --git a/tools/badctl/cmd/gen/conditions/embeddedField.go b/tools/badctl/cmd/gen/conditions/embeddedField.go new file mode 100644 index 00000000..b0c0430b --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/embeddedField.go @@ -0,0 +1,36 @@ +package conditions + +import ( + "errors" + "go/types" + + "github.com/elliotchance/pie/v2" + + "github.com/ditrit/badaas/tools/badctl/cmd/cmderrors" +) + +// Generate conditions for a embedded field +// it will generate a condition for each of the field of the embedded field's type +func generateForEmbeddedField[T any](file *File, field Field, generator CodeGenerator[T]) []T { + embeddedStructType, ok := field.Type.Underlying().(*types.Struct) + if !ok { + cmderrors.FailErr(errors.New("unreachable! embedded objects are always structs")) + } + + fields, err := getStructFields(embeddedStructType) + if err != nil { + // embedded field's type has not fields + return []T{} + } + + if !isBadORMBaseModel(field.TypeString()) { + fields = pie.Map(fields, func(embeddedField Field) Field { + embeddedField.ColumnPrefix = field.Tags.getEmbeddedPrefix() + embeddedField.NamePrefix = field.Name + + return embeddedField + }) + } + + return generator.ForEachField(file, fields) +} diff --git a/tools/badctl/cmd/gen/conditions/field.go b/tools/badctl/cmd/gen/conditions/field.go new file mode 100644 index 00000000..040c1959 --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/field.go @@ -0,0 +1,135 @@ +package conditions + +import ( + "errors" + "go/types" + + "github.com/elliotchance/pie/v2" +) + +// badorm/baseModels.go +var badORMIDs = []string{ + badORMPath + "." + uIntID, + badORMPath + "." + uuid, +} + +type Field struct { + Name string + NamePrefix string + Type Type + Embedded bool + Tags GormTags + ColumnPrefix string +} + +// Get the name of the column where the data for a field will be saved +func (field Field) getColumnName() string { + columnTag, isPresent := field.Tags[columnTagName] + if isPresent { + // field has a gorm column tag, so the name of the column will be that tag + return columnTag + } + + return "" +} + +// Get name of the attribute of the object that is a foreign key to the field's object +func (field Field) getFKAttribute() string { + foreignKeyTag, isPresent := field.Tags[foreignKeyTagName] + if isPresent { + // field has a foreign key tag, so the name will be that tag + return foreignKeyTag + } + + // gorm default + return field.Name + "ID" +} + +// Get name of the attribute of the field's object that is references by the foreign key +func (field Field) getFKReferencesAttribute() string { + referencesTag, isPresent := field.Tags[referencesTagName] + if isPresent { + // field has a references tag, so the name will be that tag + return referencesTag + } + + // gorm default + return "ID" +} + +// Get name of the attribute of field's object that is a foreign key to the object +func (field Field) getRelatedTypeFKAttribute(structName string) string { + foreignKeyTag, isPresent := field.Tags[foreignKeyTagName] + if isPresent { + // field has a foreign key tag, so the name will that tag + return foreignKeyTag + } + + // gorm default + return structName + "ID" +} + +func (field Field) GetType() types.Type { + return field.Type.Type +} + +// Get field's type full string (pkg + name) +func (field Field) TypeString() string { + return field.Type.String() +} + +// Get field's type name +func (field Field) TypeName() string { + return field.Type.Name() +} + +// Create a new field with the same name and tags but a different type +func (field Field) ChangeType(newType types.Type) Field { + return Field{ + Name: field.Name, + Type: Type{newType}, + Tags: field.Tags, + } +} + +func (field Field) IsBadORMID() bool { + return pie.Contains(badORMIDs, field.TypeString()) +} + +// Get fields of a BaDORM model +// Returns error is objectType is not a BaDORM model +func getFields(objectType Type) ([]Field, error) { + // The underlying type has to be a struct and a BaDORM Model + // (ignore const, var, func, etc.) + structType, err := objectType.BadORMModelStruct() + if err != nil { + return nil, err + } + + return getStructFields(structType) +} + +// Get fields of a struct +// Returns errors if the struct has not fields +func getStructFields(structType *types.Struct) ([]Field, error) { + numFields := structType.NumFields() + if numFields == 0 { + return nil, errors.New("struct has 0 fields") + } + + fields := []Field{} + + // Iterate over struct fields + for i := 0; i < numFields; i++ { + fieldObject := structType.Field(i) + gormTags := getGormTags(structType.Tag(i)) + fields = append(fields, Field{ + Name: fieldObject.Name(), + Type: Type{fieldObject.Type()}, + Embedded: fieldObject.Embedded() || gormTags.hasEmbedded(), + Tags: gormTags, + }) + } + + return fields, nil +} diff --git a/tools/badctl/cmd/gen/conditions/file.go b/tools/badctl/cmd/gen/conditions/file.go new file mode 100644 index 00000000..ce10aadc --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/file.go @@ -0,0 +1,47 @@ +package conditions + +import ( + "github.com/dave/jennifer/jen" + + "github.com/ditrit/badaas/tools/badctl/cmd/version" +) + +type File struct { + destPkg string + jenFile *jen.File + name string + codesAdded bool +} + +func NewFile(destPkg, name string) *File { + // Start a new file in destination package + f := jen.NewFile(destPkg) + + // Add a package comment, so IDEs detect files as generated + f.PackageComment("Code generated by badctl v" + version.Version + ", DO NOT EDIT.") + + return &File{ + destPkg: destPkg, + name: name, + jenFile: f, + } +} + +func (file *File) Add(codes ...jen.Code) { + if len(codes) > 0 { + file.codesAdded = true + } + + for _, code := range codes { + file.jenFile.Add(code) + } +} + +// Write generated file +func (file File) Save() error { + if file.codesAdded { + return file.jenFile.Save(file.name) + } + + return nil +} diff --git a/tools/badctl/cmd/gen/conditions/gormTag.go b/tools/badctl/cmd/gen/conditions/gormTag.go new file mode 100644 index 00000000..80cde897 --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/gormTag.go @@ -0,0 +1,61 @@ +package conditions + +import ( + "strings" + + "github.com/fatih/structtag" +) + +type GormTag string + +const ( + embeddedTagName GormTag = "embedded" + embeddedPrefixTagName GormTag = "embeddedPrefix" + columnTagName GormTag = "column" + foreignKeyTagName GormTag = "foreignKey" + referencesTagName GormTag = "references" +) + +type GormTags map[GormTag]string + +func (tags GormTags) getEmbeddedPrefix() string { + embeddedPrefix, isPresent := tags[embeddedPrefixTagName] + if !isPresent { + return "" + } + + return embeddedPrefix +} + +func (tags GormTags) hasEmbedded() bool { + _, isPresent := tags[embeddedTagName] + return isPresent +} + +func getGormTags(tag string) GormTags { + tagMap := GormTags{} + + allTags, err := structtag.Parse(tag) + if err != nil { + return tagMap + } + + gormTag, err := allTags.Get("gorm") + if err != nil { + return tagMap + } + + gormTags := strings.Split(gormTag.Name, ";") + for _, tag := range gormTags { + splitted := strings.Split(tag, ":") + tagName := GormTag(splitted[0]) + + if len(splitted) == 1 { + tagMap[tagName] = "" + } else { + tagMap[tagName] = splitted[1] + } + } + + return tagMap +} diff --git a/tools/badctl/cmd/gen/conditions/jenParam.go b/tools/badctl/cmd/gen/conditions/jenParam.go new file mode 100644 index 00000000..90d3911b --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/jenParam.go @@ -0,0 +1,88 @@ +package conditions + +import ( + "errors" + "go/types" + + "github.com/dave/jennifer/jen" + + "github.com/ditrit/badaas/tools/badctl/cmd/cmderrors" +) + +type JenParam struct { + statement *jen.Statement + internalType *jen.Statement +} + +func NewJenParam() *JenParam { + return &JenParam{ + statement: jen.Id("operator").Qual( + badORMPath, badORMOperator, + ), + internalType: &jen.Statement{}, + } +} + +func (param JenParam) Statement() *jen.Statement { + return param.statement.Types(param.internalType) +} + +func (param JenParam) GenericType() *jen.Statement { + return param.internalType +} + +func (param JenParam) ToBasicKind(basicType *types.Basic) { + switch basicType.Kind() { + case types.Bool: + param.internalType.Bool() + case types.Int: + param.internalType.Int() + case types.Int8: + param.internalType.Int8() + case types.Int16: + param.internalType.Int16() + case types.Int32: + param.internalType.Int32() + case types.Int64: + param.internalType.Int64() + case types.Uint: + param.internalType.Uint() + case types.Uint8: + param.internalType.Uint8() + case types.Uint16: + param.internalType.Uint16() + case types.Uint32: + param.internalType.Uint32() + case types.Uint64: + param.internalType.Uint64() + case types.Uintptr: + param.internalType.Uintptr() + case types.Float32: + param.internalType.Float32() + case types.Float64: + param.internalType.Float64() + case types.Complex64: + param.internalType.Complex64() + case types.Complex128: + param.internalType.Complex128() + case types.String: + param.internalType.String() + case types.Invalid, types.UnsafePointer, + types.UntypedBool, types.UntypedInt, + types.UntypedRune, types.UntypedFloat, + types.UntypedComplex, types.UntypedString, + types.UntypedNil: + cmderrors.FailErr(errors.New("unreachable! untyped types can't be inside a struct")) + } +} + +func (param JenParam) ToSlice() { + param.internalType.Index() +} + +func (param JenParam) ToCustomType(destPkg string, typeV Type) { + param.internalType.Qual( + getRelativePackagePath(destPkg, typeV), + typeV.Name(), + ) +} diff --git a/tools/badctl/cmd/gen/conditions/main.go b/tools/badctl/cmd/gen/conditions/main.go new file mode 100644 index 00000000..3c830bc3 --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/main.go @@ -0,0 +1,132 @@ +package conditions + +import ( + "errors" + "fmt" + "go/types" + "os" + "path/filepath" + + "github.com/ettle/strcase" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "golang.org/x/tools/go/packages" + + "github.com/ditrit/badaas/tools/badctl/cmd/cmderrors" + "github.com/ditrit/badaas/tools/badctl/cmd/log" + "github.com/ditrit/verdeter" +) + +var GenConditionsCmd = verdeter.BuildVerdeterCommand(verdeter.VerdeterConfig{ + Use: "conditions", + Short: "Generate conditions to query your objects using BaDORM", + Long: `gen is the command you can use to generate the files and configurations necessary for your project to use BadAss in a simple way.`, + Run: GenerateConditions, + Args: cobra.MinimumNArgs(1), +}) + +const ( + DestPackageKey = "dest_package" + badORMPath = "github.com/ditrit/badaas/badorm" +) + +func init() { + err := GenConditionsCmd.LKey( + DestPackageKey, verdeter.IsStr, "d", + "Destination package (not used if ran with go generate)", + ) + if err != nil { + cmderrors.FailErr(err) + } +} + +// GenConditionsCmd Run func +func GenerateConditions(_ *cobra.Command, args []string) { + log.SetLevel() + // Inspect package and use type checker to infer imported types + pkgs := loadPackages(args) + + // Get the package of the file with go:generate comment or in command params + destPkg := os.Getenv("GOPACKAGE") + if destPkg == "" { + destPkg = viper.GetString(DestPackageKey) + if destPkg == "" { + cmderrors.FailErr(errors.New("config --dest_package or use go generate")) + } + } + + // Generate conditions for each package + for i, pkg := range pkgs { + generateConditionsForPkg(destPkg, args[i], pkg) + } +} + +// Generates a file with conditions for each BaDORM model in the package +func generateConditionsForPkg(destPkg string, pkgPath string, pkg *packages.Package) { + log.Logger.Infof("Generating conditions for types in package %q", pkg.Types.Name()) + + relationGettersFile := NewFile(pkg.Types.Name(), filepath.Join(pkgPath, "badorm.go")) + + for _, name := range pkg.Types.Scope().Names() { + object := getObject(pkg, name) + if object != nil { + generateConditionsForObject(destPkg, object) + _ = NewRelationGettersGenerator(object).Into(relationGettersFile) + } + } + + err := relationGettersFile.Save() + if err != nil { + cmderrors.FailErr(err) + } +} + +func generateConditionsForObject(destPkg string, object types.Object) { + file := NewFile( + destPkg, + strcase.ToSnake(object.Name())+"_conditions.go", + ) + + err := NewConditionsGenerator(object).Into(file) + if err != nil { + // object is not a BaDORM model, do not generate conditions + return + } + + err = file.Save() + if err != nil { + cmderrors.FailErr(err) + } +} + +// Load package information from paths +func loadPackages(paths []string) []*packages.Package { + cfg := &packages.Config{Mode: packages.NeedTypes} + + pkgs, err := packages.Load(cfg, paths...) + if err != nil { + cmderrors.FailErr(fmt.Errorf("loading packages for inspection: %w", err)) + } + + // print compilation errors of source packages + packages.PrintErrors(pkgs) + + return pkgs +} + +// Get object by name in the package +func getObject(pkg *packages.Package, name string) types.Object { + obj := pkg.Types.Scope().Lookup(name) + if obj == nil { + cmderrors.FailErr(fmt.Errorf("%s not found in declared types of %s", + name, pkg)) + } + + // Generate only if it is a declared type + object, ok := obj.(*types.TypeName) + if !ok { + return nil + } + + return object +} diff --git a/tools/badctl/cmd/gen/conditions/main_test.go b/tools/badctl/cmd/gen/conditions/main_test.go new file mode 100644 index 00000000..29393827 --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/main_test.go @@ -0,0 +1,227 @@ +package conditions + +import ( + "bytes" + "io" + "os" + "testing" + + "github.com/spf13/viper" + "gotest.tools/assert" + + "github.com/ditrit/badaas/tools/badctl/cmd/testutils" +) + +const chunkSize = 100000 + +func TestUIntModel(t *testing.T) { + doTest(t, "./tests/uintmodel", []Comparison{ + {Have: "uint_model_conditions.go", Expected: "./tests/results/uintmodel.go"}, + }) + testutils.CheckFileNotExists(t, "./tests/uintmodel/badorm.go") +} + +func TestUUIDModel(t *testing.T) { + doTest(t, "./tests/uuidmodel", []Comparison{ + {Have: "uuid_model_conditions.go", Expected: "./tests/results/uuidmodel.go"}, + }) + testutils.CheckFileNotExists(t, "./tests/uuidmodel/badorm.go") +} + +func TestBasicTypes(t *testing.T) { + doTest(t, "./tests/basictypes", []Comparison{ + {Have: "basic_types_conditions.go", Expected: "./tests/results/basictypes.go"}, + }) + testutils.CheckFileNotExists(t, "./tests/basictypes/badorm.go") +} + +func TestBasicPointers(t *testing.T) { + doTest(t, "./tests/basicpointers", []Comparison{ + {Have: "basic_pointers_conditions.go", Expected: "./tests/results/basicpointers.go"}, + }) + testutils.CheckFileNotExists(t, "./tests/basicpointers/badorm.go") +} + +func TestBasicSlices(t *testing.T) { + doTest(t, "./tests/basicslices", []Comparison{ + {Have: "basic_slices_conditions.go", Expected: "./tests/results/basicslices.go"}, + }) + testutils.CheckFileNotExists(t, "./tests/basicslices/badorm.go") +} + +func TestBasicSlicesPointer(t *testing.T) { + doTest(t, "./tests/basicslicespointer", []Comparison{ + {Have: "basic_slices_pointer_conditions.go", Expected: "./tests/results/basicslicespointer.go"}, + }) + testutils.CheckFileNotExists(t, "./tests/basicslicespointer/badorm.go") +} + +func TestGoEmbedded(t *testing.T) { + doTest(t, "./tests/goembedded", []Comparison{ + {Have: "go_embedded_conditions.go", Expected: "./tests/results/goembedded.go"}, + }) + testutils.CheckFileNotExists(t, "./tests/goembedded/badorm.go") +} + +func TestGormEmbedded(t *testing.T) { + doTest(t, "./tests/gormembedded", []Comparison{ + {Have: "gorm_embedded_conditions.go", Expected: "./tests/results/gormembedded.go"}, + }) + testutils.CheckFileNotExists(t, "./tests/gormembedded/badorm.go") +} + +func TestCustomType(t *testing.T) { + doTest(t, "./tests/customtype", []Comparison{ + {Have: "custom_type_conditions.go", Expected: "./tests/results/customtype.go"}, + }) + testutils.CheckFileNotExists(t, "./tests/customtype/badorm.go") +} + +func TestColumnDefinition(t *testing.T) { + doTest(t, "./tests/columndefinition", []Comparison{ + {Have: "column_definition_conditions.go", Expected: "./tests/results/columndefinition.go"}, + }) + testutils.CheckFileNotExists(t, "./tests/columndefinition/badorm.go") +} + +func TestBelongsTo(t *testing.T) { + doTest(t, "./tests/belongsto", []Comparison{ + {Have: "./tests/belongsto/badorm.go", Expected: "./tests/belongsto/badorm_result.go"}, + {Have: "owner_conditions.go", Expected: "./tests/results/belongsto_owner.go"}, + {Have: "owned_conditions.go", Expected: "./tests/results/belongsto_owned.go"}, + }) +} + +func TestHasOne(t *testing.T) { + doTest(t, "./tests/hasone", []Comparison{ + {Have: "./tests/hasone/badorm.go", Expected: "./tests/hasone/badorm_result.go"}, + {Have: "country_conditions.go", Expected: "./tests/results/hasone_country.go"}, + {Have: "city_conditions.go", Expected: "./tests/results/hasone_city.go"}, + }) +} + +func TestHasMany(t *testing.T) { + doTest(t, "./tests/hasmany", []Comparison{ + {Have: "./tests/hasmany/badorm.go", Expected: "./tests/hasmany/badorm_result.go"}, + {Have: "company_conditions.go", Expected: "./tests/results/hasmany_company.go"}, + {Have: "seller_conditions.go", Expected: "./tests/results/hasmany_seller.go"}, + }) +} + +func TestHasManyWithPointers(t *testing.T) { + doTest(t, "./tests/hasmanywithpointers", []Comparison{ + {Have: "./tests/hasmanywithpointers/badorm.go", Expected: "./tests/hasmanywithpointers/badorm_result.go"}, + {Have: "company_with_pointers_conditions.go", Expected: "./tests/results/hasmanywithpointers_company.go"}, + {Have: "seller_in_pointers_conditions.go", Expected: "./tests/results/hasmanywithpointers_seller.go"}, + }) +} + +func TestSelfReferential(t *testing.T) { + doTest(t, "./tests/selfreferential", []Comparison{ + {Have: "./tests/selfreferential/badorm.go", Expected: "./tests/selfreferential/badorm_result.go"}, + {Have: "employee_conditions.go", Expected: "./tests/results/selfreferential.go"}, + }) +} + +func TestMultiplePackage(t *testing.T) { + doTest(t, "./tests/multiplepackage/package1", []Comparison{ + {Have: "./tests/multiplepackage/package1/badorm.go", Expected: "./tests/multiplepackage/package1/badorm_result.go"}, + {Have: "package1_conditions.go", Expected: "./tests/results/multiplepackage_package1.go"}, + }) + doTest(t, "./tests/multiplepackage/package2", []Comparison{ + {Have: "package2_conditions.go", Expected: "./tests/results/multiplepackage_package2.go"}, + }) + testutils.CheckFileNotExists(t, "./tests/multiplepackage/package2/badorm.go") +} + +func TestOverrideForeignKey(t *testing.T) { + doTest(t, "./tests/overrideforeignkey", []Comparison{ + {Have: "./tests/overrideforeignkey/badorm.go", Expected: "./tests/overrideforeignkey/badorm_result.go"}, + {Have: "bicycle_conditions.go", Expected: "./tests/results/overrideforeignkey_bicycle.go"}, + {Have: "person_conditions.go", Expected: "./tests/results/overrideforeignkey_person.go"}, + }) +} + +func TestOverrideReferences(t *testing.T) { + doTest(t, "./tests/overridereferences", []Comparison{ + {Have: "./tests/overridereferences/badorm.go", Expected: "./tests/overridereferences/badorm_result.go"}, + {Have: "phone_conditions.go", Expected: "./tests/results/overridereferences_phone.go"}, + {Have: "brand_conditions.go", Expected: "./tests/results/overridereferences_brand.go"}, + }) +} + +func TestOverrideForeignKeyInverse(t *testing.T) { + doTest(t, "./tests/overrideforeignkeyinverse", []Comparison{ + {Have: "./tests/overrideforeignkeyinverse/badorm.go", Expected: "./tests/overrideforeignkeyinverse/badorm_result.go"}, + {Have: "user_conditions.go", Expected: "./tests/results/overrideforeignkeyinverse_user.go"}, + {Have: "credit_card_conditions.go", Expected: "./tests/results/overrideforeignkeyinverse_credit_card.go"}, + }) +} + +func TestOverrideReferencesInverse(t *testing.T) { + doTest(t, "./tests/overridereferencesinverse", []Comparison{ + {Have: "./tests/overridereferencesinverse/badorm.go", Expected: "./tests/overridereferencesinverse/badorm_result.go"}, + {Have: "computer_conditions.go", Expected: "./tests/results/overridereferencesinverse_computer.go"}, + {Have: "processor_conditions.go", Expected: "./tests/results/overridereferencesinverse_processor.go"}, + }) +} + +type Comparison struct { + Have string + Expected string +} + +func doTest(t *testing.T, sourcePkg string, comparisons []Comparison) { + viper.Set(DestPackageKey, "conditions") + GenerateConditions(nil, []string{sourcePkg}) + + for _, comparison := range comparisons { + checkFilesEqual(t, comparison.Have, comparison.Expected) + } +} + +func checkFilesEqual(t *testing.T, file1, file2 string) { + stat1 := testutils.CheckFileExists(t, file1) + stat2 := testutils.CheckFileExists(t, file2) + + // do inputs at least have the same size? + assert.Equal(t, stat1.Size(), stat2.Size(), "File lens are not equal") + + // long way: compare contents + f1, err := os.Open(file1) + if err != nil { + t.Error(err) + } + defer f1.Close() + + f2, err := os.Open(file2) + if err != nil { + t.Error(err) + } + defer f2.Close() + + b1 := make([]byte, chunkSize) + b2 := make([]byte, chunkSize) + + for { + n1, err1 := io.ReadFull(f1, b1) + n2, err2 := io.ReadFull(f2, b2) + + assert.Assert(t, bytes.Equal(b1[:n1], b2[:n2])) + + if (err1 == io.EOF && err2 == io.EOF) || (err1 == io.ErrUnexpectedEOF && err2 == io.ErrUnexpectedEOF) { + break + } + + // some other error, like a dropped network connection or a bad transfer + if err1 != nil { + t.Error(err1) + } + + if err2 != nil { + t.Error(err2) + } + } + + testutils.RemoveFile(file1) +} diff --git a/tools/badctl/cmd/gen/conditions/relationGettersGenerator.go b/tools/badctl/cmd/gen/conditions/relationGettersGenerator.go new file mode 100644 index 00000000..b954900e --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/relationGettersGenerator.go @@ -0,0 +1,224 @@ +package conditions + +import ( + "go/types" + + "github.com/dave/jennifer/jen" + "github.com/ettle/strcase" + + "github.com/ditrit/badaas/tools/badctl/cmd/log" +) + +const ( + badORMVerifyStructLoaded = "VerifyStructLoaded" + badORMVerifyPointerLoaded = "VerifyPointerLoaded" + badORMVerifyPointerWithIDLoaded = "VerifyPointerWithIDLoaded" + badORMVerifyCollectionLoaded = "VerifyCollectionLoaded" +) + +type RelationGettersGenerator struct { + object types.Object + objectType Type +} + +func NewRelationGettersGenerator(object types.Object) *RelationGettersGenerator { + return &RelationGettersGenerator{ + object: object, + objectType: Type{object.Type()}, + } +} + +// Add conditions for an object in the file +func (generator RelationGettersGenerator) Into(file *File) error { + fields, err := getFields(generator.objectType) + if err != nil { + return err + } + + log.Logger.Infof("Generating relation getters for type %q in %s", generator.object.Name(), file.name) + + file.Add(generator.ForEachField(file, fields)...) + + return nil +} + +func (generator RelationGettersGenerator) ForEachField(file *File, fields []Field) []jen.Code { + relationGetters := []jen.Code{} + + for _, field := range fields { + if field.Embedded { + relationGetters = append( + relationGetters, + generateForEmbeddedField[jen.Code]( + file, + field, + generator, + )..., + ) + } else { + getterForField := generator.generateForField(field) + if getterForField != nil { + relationGetters = append(relationGetters, getterForField) + } + } + } + + return relationGetters +} + +func (generator RelationGettersGenerator) generateForField(field Field) jen.Code { + switch fieldType := field.GetType().(type) { + case *types.Named: + // the field is a named type (user defined structs) + _, err := field.Type.BadORMModelStruct() + if err == nil { + log.Logger.Debugf("Generating relation getter for type %q and field %s", generator.object.Name(), field.Name) + // field is a BaDORM Model + return generator.verifyStruct(field) + } + case *types.Pointer: + // the field is a pointer + return generator.generateForPointer(field.ChangeType(fieldType.Elem())) + default: + log.Logger.Debugf("struct field type not handled: %T", fieldType) + } + + return nil +} + +func (generator RelationGettersGenerator) generateForPointer(field Field) jen.Code { + switch fieldType := field.GetType().(type) { + case *types.Named: + _, err := field.Type.BadORMModelStruct() + if err == nil { + // field is a pointer to BaDORM Model + fk, err := generator.objectType.GetFK(field) + if err != nil { + log.Logger.Debugf("unhandled: field is a pointer and object not has the fk: %s", field.Type) + return nil + } + + log.Logger.Debugf("Generating relation getter for type %q and field %s", generator.object.Name(), field.Name) + + switch fk.GetType().(type) { + case *types.Named: + if fk.IsBadORMID() { + return generator.verifyPointerWithID(field) + } + case *types.Pointer: + // the fk is a pointer + return generator.verifyPointer(field) + } + } + case *types.Slice: + return generator.generateForSlicePointer( + field.ChangeType(fieldType.Elem()), + nil, + ) + } + + return nil +} + +func (generator RelationGettersGenerator) generateForSlicePointer(field Field, fieldTypePrefix *jen.Statement) jen.Code { + switch fieldType := field.GetType().(type) { + case *types.Named: + _, err := field.Type.BadORMModelStruct() + if err == nil { + // field is a pointer to a slice of BaDORM Model + return generator.verifyCollection(field, fieldTypePrefix) + } + case *types.Pointer: + return generator.generateForSlicePointer( + field.ChangeType(fieldType.Elem()), + jen.Op("*"), + ) + } + + return nil +} + +func getGetterName(field Field) string { + return "Get" + strcase.ToPascal(field.Name) +} + +func (generator RelationGettersGenerator) verifyStruct(field Field) *jen.Statement { + return generator.verifyCommon( + field, + badORMVerifyStructLoaded, + jen.Op("*"), + nil, + jen.Op("&").Id("m").Op(".").Id(field.Name), + ) +} + +func (generator RelationGettersGenerator) verifyPointer(field Field) *jen.Statement { + return generator.verifyPointerCommon(field, badORMVerifyPointerLoaded) +} + +func (generator RelationGettersGenerator) verifyPointerWithID(field Field) *jen.Statement { + return generator.verifyPointerCommon(field, badORMVerifyPointerWithIDLoaded) +} + +func (generator RelationGettersGenerator) verifyCollection(field Field, fieldTypePrefix *jen.Statement) jen.Code { + return generator.verifyCommon( + field, + badORMVerifyCollectionLoaded, + jen.Index(), + fieldTypePrefix, + jen.Id("m").Op(".").Id(field.Name), + ) +} + +func (generator RelationGettersGenerator) verifyPointerCommon(field Field, verifyFunc string) *jen.Statement { + return generator.verifyCommon( + field, + verifyFunc, + jen.Op("*"), + nil, + jen.Id("m").Op(".").Id(field.Name+"ID"), + jen.Id("m").Op(".").Id(field.Name), + ) +} + +func (generator RelationGettersGenerator) verifyCommon( + field Field, + verifyFunc string, + returnType *jen.Statement, + fieldTypePrefix *jen.Statement, + callParams ...jen.Code, +) *jen.Statement { + fieldType := jen.Qual( + getRelativePackagePath( + generator.object.Pkg().Name(), + field.Type, + ), + field.TypeName(), + ) + + if fieldTypePrefix != nil { + fieldType = fieldTypePrefix.Add(fieldType) + } + + return jen.Func().Parens( + jen.Id("m").Id(generator.object.Name()), + ).Id(getGetterName(field)).Params().Add( + jen.Parens( + jen.List( + returnType.Add(fieldType), + jen.Id("error"), + ), + ), + ).Block( + jen.Return( + jen.Qual( + badORMPath, + verifyFunc, + ).Types( + fieldType, + ).Call( + callParams..., + ), + ), + ) +} diff --git a/tools/badctl/cmd/gen/conditions/tests/basicpointers/basicpointers.go b/tools/badctl/cmd/gen/conditions/tests/basicpointers/basicpointers.go new file mode 100644 index 00000000..34efd82c --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/basicpointers/basicpointers.go @@ -0,0 +1,26 @@ +package basicpointers + +import "github.com/ditrit/badaas/badorm" + +type BasicPointers struct { + badorm.UUIDModel + + Bool *bool + Int *int + Int8 *int8 + Int16 *int16 + Int32 *int32 + Int64 *int64 + UInt *uint + UInt8 *uint8 + UInt16 *uint16 + UInt32 *uint32 + UInt64 *uint64 + UIntptr *uintptr + Float32 *float32 + Float64 *float64 + Complex64 *complex64 + Complex128 *complex128 + String *string + Byte *byte +} diff --git a/tools/badctl/cmd/gen/conditions/tests/basicslices/basicslices.go b/tools/badctl/cmd/gen/conditions/tests/basicslices/basicslices.go new file mode 100644 index 00000000..362d76b5 --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/basicslices/basicslices.go @@ -0,0 +1,26 @@ +package basicslices + +import "github.com/ditrit/badaas/badorm" + +type BasicSlices struct { + badorm.UUIDModel + + Bool []bool + Int []int + Int8 []int8 + Int16 []int16 + Int32 []int32 + Int64 []int64 + UInt []uint + UInt8 []uint8 + UInt16 []uint16 + UInt32 []uint32 + UInt64 []uint64 + UIntptr []uintptr + Float32 []float32 + Float64 []float64 + Complex64 []complex64 + Complex128 []complex128 + String []string + Byte []byte +} diff --git a/tools/badctl/cmd/gen/conditions/tests/basicslicespointer/basicslicespointer.go b/tools/badctl/cmd/gen/conditions/tests/basicslicespointer/basicslicespointer.go new file mode 100644 index 00000000..7f692e01 --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/basicslicespointer/basicslicespointer.go @@ -0,0 +1,26 @@ +package basicslicespointer + +import "github.com/ditrit/badaas/badorm" + +type BasicSlicesPointer struct { + badorm.UUIDModel + + Bool []*bool + Int []*int + Int8 []*int8 + Int16 []*int16 + Int32 []*int32 + Int64 []*int64 + UInt []*uint + UInt8 []*uint8 + UInt16 []*uint16 + UInt32 []*uint32 + UInt64 []*uint64 + UIntptr []*uintptr + Float32 []*float32 + Float64 []*float64 + Complex64 []*complex64 + Complex128 []*complex128 + String []*string + Byte []*byte +} diff --git a/tools/badctl/cmd/gen/conditions/tests/basictypes/basictypes.go b/tools/badctl/cmd/gen/conditions/tests/basictypes/basictypes.go new file mode 100644 index 00000000..45488e59 --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/basictypes/basictypes.go @@ -0,0 +1,26 @@ +package basictypes + +import "github.com/ditrit/badaas/badorm" + +type BasicTypes struct { + badorm.UUIDModel + + Bool bool + Int int + Int8 int8 + Int16 int16 + Int32 int32 + Int64 int64 + UInt uint + UInt8 uint8 + UInt16 uint16 + UInt32 uint32 + UInt64 uint64 + UIntptr uintptr + Float32 float32 + Float64 float64 + Complex64 complex64 + Complex128 complex128 + String string + Byte byte +} diff --git a/tools/badctl/cmd/gen/conditions/tests/belongsto/badorm_result.go b/tools/badctl/cmd/gen/conditions/tests/belongsto/badorm_result.go new file mode 100644 index 00000000..23425a10 --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/belongsto/badorm_result.go @@ -0,0 +1,8 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package belongsto + +import badorm "github.com/ditrit/badaas/badorm" + +func (m Owned) GetOwner() (*Owner, error) { + return badorm.VerifyStructLoaded[Owner](&m.Owner) +} diff --git a/tools/badctl/cmd/gen/conditions/tests/belongsto/belongsto.go b/tools/badctl/cmd/gen/conditions/tests/belongsto/belongsto.go new file mode 100644 index 00000000..c8d94b30 --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/belongsto/belongsto.go @@ -0,0 +1,14 @@ +package belongsto + +import "github.com/ditrit/badaas/badorm" + +type Owner struct { + badorm.UUIDModel +} +type Owned struct { + badorm.UUIDModel + + // Owned belongsTo Owner (Owned 0..* -> 1 Owner) + Owner Owner + OwnerID badorm.UUID +} diff --git a/tools/badctl/cmd/gen/conditions/tests/columndefinition/columndefinition.go b/tools/badctl/cmd/gen/conditions/tests/columndefinition/columndefinition.go new file mode 100644 index 00000000..8426b467 --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/columndefinition/columndefinition.go @@ -0,0 +1,9 @@ +package columndefinition + +import "github.com/ditrit/badaas/badorm" + +type ColumnDefinition struct { + badorm.UUIDModel + + String string `gorm:"column:string_something_else"` +} diff --git a/tools/badctl/cmd/gen/conditions/tests/customtype/customtype.go b/tools/badctl/cmd/gen/conditions/tests/customtype/customtype.go new file mode 100644 index 00000000..700e26a8 --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/customtype/customtype.go @@ -0,0 +1,44 @@ +package customtype + +import ( + "database/sql/driver" + "fmt" + "strings" + + "github.com/ditrit/badaas/badorm" +) + +type MultiString []string + +func (s *MultiString) Scan(src interface{}) error { + switch typedSrc := src.(type) { + case string: + *s = strings.Split(typedSrc, ",") + return nil + case []byte: + str := string(typedSrc) + *s = strings.Split(str, ",") + + return nil + default: + return fmt.Errorf("failed to scan multistring field - source is not a string, is %T", src) + } +} + +func (s MultiString) Value() (driver.Value, error) { + if len(s) == 0 { + return nil, nil + } + + return strings.Join(s, ","), nil +} + +func (MultiString) GormDataType() string { + return "text" +} + +type CustomType struct { + badorm.UUIDModel + + Custom MultiString +} diff --git a/tools/badctl/cmd/gen/conditions/tests/goembedded/goembedded.go b/tools/badctl/cmd/gen/conditions/tests/goembedded/goembedded.go new file mode 100644 index 00000000..cdaee23d --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/goembedded/goembedded.go @@ -0,0 +1,14 @@ +package goembedded + +import "github.com/ditrit/badaas/badorm" + +type ToBeEmbedded struct { + Int int +} + +type GoEmbedded struct { + badorm.UIntModel + + Int int + ToBeEmbedded +} diff --git a/tools/badctl/cmd/gen/conditions/tests/gormembedded/gormembedded.go b/tools/badctl/cmd/gen/conditions/tests/gormembedded/gormembedded.go new file mode 100644 index 00000000..00911fe3 --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/gormembedded/gormembedded.go @@ -0,0 +1,15 @@ +package gormembedded + +import "github.com/ditrit/badaas/badorm" + +type ToBeGormEmbedded struct { + Int int +} + +type GormEmbedded struct { + badorm.UIntModel + + Int int + GormEmbedded ToBeGormEmbedded `gorm:"embedded;embeddedPrefix:gorm_embedded_"` + GormEmbeddedNoPrefix ToBeGormEmbedded `gorm:"embedded"` +} diff --git a/tools/badctl/cmd/gen/conditions/tests/hasmany/badorm_result.go b/tools/badctl/cmd/gen/conditions/tests/hasmany/badorm_result.go new file mode 100644 index 00000000..c6434d85 --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/hasmany/badorm_result.go @@ -0,0 +1,11 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package hasmany + +import badorm "github.com/ditrit/badaas/badorm" + +func (m Company) GetSellers() ([]Seller, error) { + return badorm.VerifyCollectionLoaded[Seller](m.Sellers) +} +func (m Seller) GetCompany() (*Company, error) { + return badorm.VerifyPointerLoaded[Company](m.CompanyID, m.Company) +} diff --git a/tools/badctl/cmd/gen/conditions/tests/hasmany/hasmany.go b/tools/badctl/cmd/gen/conditions/tests/hasmany/hasmany.go new file mode 100644 index 00000000..dd05c041 --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/hasmany/hasmany.go @@ -0,0 +1,16 @@ +package hasmany + +import "github.com/ditrit/badaas/badorm" + +type Company struct { + badorm.UUIDModel + + Sellers *[]Seller // Company HasMany Sellers (Company 0..1 -> 0..* Seller) +} + +type Seller struct { + badorm.UUIDModel + + Company *Company + CompanyID *badorm.UUID // Company HasMany Sellers (Company 0..1 -> 0..* Seller) +} diff --git a/tools/badctl/cmd/gen/conditions/tests/hasmanywithpointers/badorm_result.go b/tools/badctl/cmd/gen/conditions/tests/hasmanywithpointers/badorm_result.go new file mode 100644 index 00000000..332b47f0 --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/hasmanywithpointers/badorm_result.go @@ -0,0 +1,11 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package hasmanywithpointers + +import badorm "github.com/ditrit/badaas/badorm" + +func (m CompanyWithPointers) GetSellers() ([]*SellerInPointers, error) { + return badorm.VerifyCollectionLoaded[*SellerInPointers](m.Sellers) +} +func (m SellerInPointers) GetCompany() (*CompanyWithPointers, error) { + return badorm.VerifyPointerLoaded[CompanyWithPointers](m.CompanyID, m.Company) +} diff --git a/tools/badctl/cmd/gen/conditions/tests/hasmanywithpointers/hasmanywithpointers.go b/tools/badctl/cmd/gen/conditions/tests/hasmanywithpointers/hasmanywithpointers.go new file mode 100644 index 00000000..0672f70a --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/hasmanywithpointers/hasmanywithpointers.go @@ -0,0 +1,16 @@ +package hasmanywithpointers + +import "github.com/ditrit/badaas/badorm" + +type CompanyWithPointers struct { + badorm.UUIDModel + + Sellers *[]*SellerInPointers // CompanyWithPointers HasMany SellerInPointers +} + +type SellerInPointers struct { + badorm.UUIDModel + + Company *CompanyWithPointers + CompanyID *badorm.UUID // Company HasMany Seller +} diff --git a/tools/badctl/cmd/gen/conditions/tests/hasone/badorm_result.go b/tools/badctl/cmd/gen/conditions/tests/hasone/badorm_result.go new file mode 100644 index 00000000..000d4b74 --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/hasone/badorm_result.go @@ -0,0 +1,11 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package hasone + +import badorm "github.com/ditrit/badaas/badorm" + +func (m City) GetCountry() (*Country, error) { + return badorm.VerifyPointerWithIDLoaded[Country](m.CountryID, m.Country) +} +func (m Country) GetCapital() (*City, error) { + return badorm.VerifyStructLoaded[City](&m.Capital) +} diff --git a/tools/badctl/cmd/gen/conditions/tests/hasone/hasone.go b/tools/badctl/cmd/gen/conditions/tests/hasone/hasone.go new file mode 100644 index 00000000..9ceb8068 --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/hasone/hasone.go @@ -0,0 +1,16 @@ +package hasone + +import "github.com/ditrit/badaas/badorm" + +type Country struct { + badorm.UUIDModel + + Capital City // Country HasOne City (Country 1 -> 1 City) +} + +type City struct { + badorm.UUIDModel + + Country *Country + CountryID badorm.UUID // Country HasOne City (Country 1 -> 1 City) +} diff --git a/tools/badctl/cmd/gen/conditions/tests/multiplepackage/package1/badorm_result.go b/tools/badctl/cmd/gen/conditions/tests/multiplepackage/package1/badorm_result.go new file mode 100644 index 00000000..b57670ad --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/multiplepackage/package1/badorm_result.go @@ -0,0 +1,11 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package package1 + +import ( + badorm "github.com/ditrit/badaas/badorm" + package2 "github.com/ditrit/badaas/tools/badctl/cmd/gen/conditions/tests/multiplepackage/package2" +) + +func (m Package1) GetPackage2() (*package2.Package2, error) { + return badorm.VerifyStructLoaded[package2.Package2](&m.Package2) +} diff --git a/tools/badctl/cmd/gen/conditions/tests/multiplepackage/package1/package1.go b/tools/badctl/cmd/gen/conditions/tests/multiplepackage/package1/package1.go new file mode 100644 index 00000000..3758027c --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/multiplepackage/package1/package1.go @@ -0,0 +1,12 @@ +package package1 + +import ( + "github.com/ditrit/badaas/badorm" + "github.com/ditrit/badaas/tools/badctl/cmd/gen/conditions/tests/multiplepackage/package2" +) + +type Package1 struct { + badorm.UUIDModel + + Package2 package2.Package2 // Package1 HasOne Package2 (Package1 1 -> 1 Package2) +} diff --git a/tools/badctl/cmd/gen/conditions/tests/multiplepackage/package2/package2.go b/tools/badctl/cmd/gen/conditions/tests/multiplepackage/package2/package2.go new file mode 100644 index 00000000..c9fc430b --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/multiplepackage/package2/package2.go @@ -0,0 +1,9 @@ +package package2 + +import "github.com/ditrit/badaas/badorm" + +type Package2 struct { + badorm.UUIDModel + + Package1ID badorm.UUID // Package1 HasOne Package2 (Package1 1 -> 1 Package2) +} diff --git a/tools/badctl/cmd/gen/conditions/tests/overrideforeignkey/badorm_result.go b/tools/badctl/cmd/gen/conditions/tests/overrideforeignkey/badorm_result.go new file mode 100644 index 00000000..110b3f47 --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/overrideforeignkey/badorm_result.go @@ -0,0 +1,8 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package overrideforeignkey + +import badorm "github.com/ditrit/badaas/badorm" + +func (m Bicycle) GetOwner() (*Person, error) { + return badorm.VerifyStructLoaded[Person](&m.Owner) +} diff --git a/tools/badctl/cmd/gen/conditions/tests/overrideforeignkey/overrideforeignkey.go b/tools/badctl/cmd/gen/conditions/tests/overrideforeignkey/overrideforeignkey.go new file mode 100644 index 00000000..7dcd3489 --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/overrideforeignkey/overrideforeignkey.go @@ -0,0 +1,15 @@ +package overrideforeignkey + +import "github.com/ditrit/badaas/badorm" + +type Person struct { + badorm.UUIDModel +} + +type Bicycle struct { + badorm.UUIDModel + + // Bicycle BelongsTo Person (Bicycle 0..* -> 1 Person) + Owner Person `gorm:"foreignKey:OwnerSomethingID"` + OwnerSomethingID string +} diff --git a/tools/badctl/cmd/gen/conditions/tests/overrideforeignkeyinverse/badorm_result.go b/tools/badctl/cmd/gen/conditions/tests/overrideforeignkeyinverse/badorm_result.go new file mode 100644 index 00000000..9b6aac49 --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/overrideforeignkeyinverse/badorm_result.go @@ -0,0 +1,8 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package overrideforeignkeyinverse + +import badorm "github.com/ditrit/badaas/badorm" + +func (m User) GetCreditCard() (*CreditCard, error) { + return badorm.VerifyStructLoaded[CreditCard](&m.CreditCard) +} diff --git a/tools/badctl/cmd/gen/conditions/tests/overrideforeignkeyinverse/overrideforeignkeyinverse.go b/tools/badctl/cmd/gen/conditions/tests/overrideforeignkeyinverse/overrideforeignkeyinverse.go new file mode 100644 index 00000000..9ca4eb7d --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/overrideforeignkeyinverse/overrideforeignkeyinverse.go @@ -0,0 +1,15 @@ +package overrideforeignkeyinverse + +import ( + "github.com/ditrit/badaas/badorm" +) + +type User struct { + badorm.UUIDModel + CreditCard CreditCard `gorm:"foreignKey:UserReference"` +} + +type CreditCard struct { + badorm.UUIDModel + UserReference badorm.UUID +} diff --git a/tools/badctl/cmd/gen/conditions/tests/overridereferences/badorm_result.go b/tools/badctl/cmd/gen/conditions/tests/overridereferences/badorm_result.go new file mode 100644 index 00000000..f710ffa6 --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/overridereferences/badorm_result.go @@ -0,0 +1,8 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package overridereferences + +import badorm "github.com/ditrit/badaas/badorm" + +func (m Phone) GetBrand() (*Brand, error) { + return badorm.VerifyStructLoaded[Brand](&m.Brand) +} diff --git a/tools/badctl/cmd/gen/conditions/tests/overridereferences/overridereferences.go b/tools/badctl/cmd/gen/conditions/tests/overridereferences/overridereferences.go new file mode 100644 index 00000000..81544034 --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/overridereferences/overridereferences.go @@ -0,0 +1,17 @@ +package overridereferences + +import "github.com/ditrit/badaas/badorm" + +type Brand struct { + badorm.UUIDModel + + Name string `gorm:"unique;type:VARCHAR(255)"` +} + +type Phone struct { + badorm.UUIDModel + + // Bicycle BelongsTo Person (Bicycle 0..* -> 1 Person) + Brand Brand `gorm:"references:Name;foreignKey:BrandName"` + BrandName string +} diff --git a/tools/badctl/cmd/gen/conditions/tests/overridereferencesinverse/badorm_result.go b/tools/badctl/cmd/gen/conditions/tests/overridereferencesinverse/badorm_result.go new file mode 100644 index 00000000..9228c409 --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/overridereferencesinverse/badorm_result.go @@ -0,0 +1,8 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package overridereferencesinverse + +import badorm "github.com/ditrit/badaas/badorm" + +func (m Computer) GetProcessor() (*Processor, error) { + return badorm.VerifyStructLoaded[Processor](&m.Processor) +} diff --git a/tools/badctl/cmd/gen/conditions/tests/overridereferencesinverse/overridereferencesinverse.go b/tools/badctl/cmd/gen/conditions/tests/overridereferencesinverse/overridereferencesinverse.go new file mode 100644 index 00000000..1343a2f1 --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/overridereferencesinverse/overridereferencesinverse.go @@ -0,0 +1,14 @@ +package overridereferencesinverse + +import "github.com/ditrit/badaas/badorm" + +type Computer struct { + badorm.UUIDModel + Name string + Processor Processor `gorm:"foreignKey:ComputerName;references:Name"` +} + +type Processor struct { + badorm.UUIDModel + ComputerName string +} diff --git a/tools/badctl/cmd/gen/conditions/tests/results/basicpointers.go b/tools/badctl/cmd/gen/conditions/tests/results/basicpointers.go new file mode 100644 index 00000000..0e2c9afc --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/results/basicpointers.go @@ -0,0 +1,277 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package conditions + +import ( + badorm "github.com/ditrit/badaas/badorm" + basicpointers "github.com/ditrit/badaas/tools/badctl/cmd/gen/conditions/tests/basicpointers" + gorm "gorm.io/gorm" + "reflect" + "time" +) + +var basicPointersType = reflect.TypeOf(*new(basicpointers.BasicPointers)) +var BasicPointersIdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "ID", + ModelType: basicPointersType, +} + +func BasicPointersId(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[basicpointers.BasicPointers] { + return badorm.FieldCondition[basicpointers.BasicPointers, badorm.UUID]{ + FieldIdentifier: BasicPointersIdField, + Operator: operator, + } +} + +var BasicPointersCreatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: basicPointersType, +} + +func BasicPointersCreatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[basicpointers.BasicPointers] { + return badorm.FieldCondition[basicpointers.BasicPointers, time.Time]{ + FieldIdentifier: BasicPointersCreatedAtField, + Operator: operator, + } +} + +var BasicPointersUpdatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: basicPointersType, +} + +func BasicPointersUpdatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[basicpointers.BasicPointers] { + return badorm.FieldCondition[basicpointers.BasicPointers, time.Time]{ + FieldIdentifier: BasicPointersUpdatedAtField, + Operator: operator, + } +} + +var BasicPointersDeletedAtField = badorm.FieldIdentifier[gorm.DeletedAt]{ + Field: "DeletedAt", + ModelType: basicPointersType, +} + +func BasicPointersDeletedAt(operator badorm.Operator[gorm.DeletedAt]) badorm.WhereCondition[basicpointers.BasicPointers] { + return badorm.FieldCondition[basicpointers.BasicPointers, gorm.DeletedAt]{ + FieldIdentifier: BasicPointersDeletedAtField, + Operator: operator, + } +} + +var BasicPointersBoolField = badorm.FieldIdentifier[bool]{ + Field: "Bool", + ModelType: basicPointersType, +} + +func BasicPointersBool(operator badorm.Operator[bool]) badorm.WhereCondition[basicpointers.BasicPointers] { + return badorm.FieldCondition[basicpointers.BasicPointers, bool]{ + FieldIdentifier: BasicPointersBoolField, + Operator: operator, + } +} + +var BasicPointersIntField = badorm.FieldIdentifier[int]{ + Field: "Int", + ModelType: basicPointersType, +} + +func BasicPointersInt(operator badorm.Operator[int]) badorm.WhereCondition[basicpointers.BasicPointers] { + return badorm.FieldCondition[basicpointers.BasicPointers, int]{ + FieldIdentifier: BasicPointersIntField, + Operator: operator, + } +} + +var BasicPointersInt8Field = badorm.FieldIdentifier[int8]{ + Field: "Int8", + ModelType: basicPointersType, +} + +func BasicPointersInt8(operator badorm.Operator[int8]) badorm.WhereCondition[basicpointers.BasicPointers] { + return badorm.FieldCondition[basicpointers.BasicPointers, int8]{ + FieldIdentifier: BasicPointersInt8Field, + Operator: operator, + } +} + +var BasicPointersInt16Field = badorm.FieldIdentifier[int16]{ + Field: "Int16", + ModelType: basicPointersType, +} + +func BasicPointersInt16(operator badorm.Operator[int16]) badorm.WhereCondition[basicpointers.BasicPointers] { + return badorm.FieldCondition[basicpointers.BasicPointers, int16]{ + FieldIdentifier: BasicPointersInt16Field, + Operator: operator, + } +} + +var BasicPointersInt32Field = badorm.FieldIdentifier[int32]{ + Field: "Int32", + ModelType: basicPointersType, +} + +func BasicPointersInt32(operator badorm.Operator[int32]) badorm.WhereCondition[basicpointers.BasicPointers] { + return badorm.FieldCondition[basicpointers.BasicPointers, int32]{ + FieldIdentifier: BasicPointersInt32Field, + Operator: operator, + } +} + +var BasicPointersInt64Field = badorm.FieldIdentifier[int64]{ + Field: "Int64", + ModelType: basicPointersType, +} + +func BasicPointersInt64(operator badorm.Operator[int64]) badorm.WhereCondition[basicpointers.BasicPointers] { + return badorm.FieldCondition[basicpointers.BasicPointers, int64]{ + FieldIdentifier: BasicPointersInt64Field, + Operator: operator, + } +} + +var BasicPointersUIntField = badorm.FieldIdentifier[uint]{ + Field: "UInt", + ModelType: basicPointersType, +} + +func BasicPointersUInt(operator badorm.Operator[uint]) badorm.WhereCondition[basicpointers.BasicPointers] { + return badorm.FieldCondition[basicpointers.BasicPointers, uint]{ + FieldIdentifier: BasicPointersUIntField, + Operator: operator, + } +} + +var BasicPointersUInt8Field = badorm.FieldIdentifier[uint8]{ + Field: "UInt8", + ModelType: basicPointersType, +} + +func BasicPointersUInt8(operator badorm.Operator[uint8]) badorm.WhereCondition[basicpointers.BasicPointers] { + return badorm.FieldCondition[basicpointers.BasicPointers, uint8]{ + FieldIdentifier: BasicPointersUInt8Field, + Operator: operator, + } +} + +var BasicPointersUInt16Field = badorm.FieldIdentifier[uint16]{ + Field: "UInt16", + ModelType: basicPointersType, +} + +func BasicPointersUInt16(operator badorm.Operator[uint16]) badorm.WhereCondition[basicpointers.BasicPointers] { + return badorm.FieldCondition[basicpointers.BasicPointers, uint16]{ + FieldIdentifier: BasicPointersUInt16Field, + Operator: operator, + } +} + +var BasicPointersUInt32Field = badorm.FieldIdentifier[uint32]{ + Field: "UInt32", + ModelType: basicPointersType, +} + +func BasicPointersUInt32(operator badorm.Operator[uint32]) badorm.WhereCondition[basicpointers.BasicPointers] { + return badorm.FieldCondition[basicpointers.BasicPointers, uint32]{ + FieldIdentifier: BasicPointersUInt32Field, + Operator: operator, + } +} + +var BasicPointersUInt64Field = badorm.FieldIdentifier[uint64]{ + Field: "UInt64", + ModelType: basicPointersType, +} + +func BasicPointersUInt64(operator badorm.Operator[uint64]) badorm.WhereCondition[basicpointers.BasicPointers] { + return badorm.FieldCondition[basicpointers.BasicPointers, uint64]{ + FieldIdentifier: BasicPointersUInt64Field, + Operator: operator, + } +} + +var BasicPointersUIntptrField = badorm.FieldIdentifier[uintptr]{ + Field: "UIntptr", + ModelType: basicPointersType, +} + +func BasicPointersUIntptr(operator badorm.Operator[uintptr]) badorm.WhereCondition[basicpointers.BasicPointers] { + return badorm.FieldCondition[basicpointers.BasicPointers, uintptr]{ + FieldIdentifier: BasicPointersUIntptrField, + Operator: operator, + } +} + +var BasicPointersFloat32Field = badorm.FieldIdentifier[float32]{ + Field: "Float32", + ModelType: basicPointersType, +} + +func BasicPointersFloat32(operator badorm.Operator[float32]) badorm.WhereCondition[basicpointers.BasicPointers] { + return badorm.FieldCondition[basicpointers.BasicPointers, float32]{ + FieldIdentifier: BasicPointersFloat32Field, + Operator: operator, + } +} + +var BasicPointersFloat64Field = badorm.FieldIdentifier[float64]{ + Field: "Float64", + ModelType: basicPointersType, +} + +func BasicPointersFloat64(operator badorm.Operator[float64]) badorm.WhereCondition[basicpointers.BasicPointers] { + return badorm.FieldCondition[basicpointers.BasicPointers, float64]{ + FieldIdentifier: BasicPointersFloat64Field, + Operator: operator, + } +} + +var BasicPointersComplex64Field = badorm.FieldIdentifier[complex64]{ + Field: "Complex64", + ModelType: basicPointersType, +} + +func BasicPointersComplex64(operator badorm.Operator[complex64]) badorm.WhereCondition[basicpointers.BasicPointers] { + return badorm.FieldCondition[basicpointers.BasicPointers, complex64]{ + FieldIdentifier: BasicPointersComplex64Field, + Operator: operator, + } +} + +var BasicPointersComplex128Field = badorm.FieldIdentifier[complex128]{ + Field: "Complex128", + ModelType: basicPointersType, +} + +func BasicPointersComplex128(operator badorm.Operator[complex128]) badorm.WhereCondition[basicpointers.BasicPointers] { + return badorm.FieldCondition[basicpointers.BasicPointers, complex128]{ + FieldIdentifier: BasicPointersComplex128Field, + Operator: operator, + } +} + +var BasicPointersStringField = badorm.FieldIdentifier[string]{ + Field: "String", + ModelType: basicPointersType, +} + +func BasicPointersString(operator badorm.Operator[string]) badorm.WhereCondition[basicpointers.BasicPointers] { + return badorm.FieldCondition[basicpointers.BasicPointers, string]{ + FieldIdentifier: BasicPointersStringField, + Operator: operator, + } +} + +var BasicPointersByteField = badorm.FieldIdentifier[uint8]{ + Field: "Byte", + ModelType: basicPointersType, +} + +func BasicPointersByte(operator badorm.Operator[uint8]) badorm.WhereCondition[basicpointers.BasicPointers] { + return badorm.FieldCondition[basicpointers.BasicPointers, uint8]{ + FieldIdentifier: BasicPointersByteField, + Operator: operator, + } +} + +var BasicPointersPreloadAttributes = badorm.NewPreloadCondition[basicpointers.BasicPointers](BasicPointersIdField, BasicPointersCreatedAtField, BasicPointersUpdatedAtField, BasicPointersDeletedAtField, BasicPointersBoolField, BasicPointersIntField, BasicPointersInt8Field, BasicPointersInt16Field, BasicPointersInt32Field, BasicPointersInt64Field, BasicPointersUIntField, BasicPointersUInt8Field, BasicPointersUInt16Field, BasicPointersUInt32Field, BasicPointersUInt64Field, BasicPointersUIntptrField, BasicPointersFloat32Field, BasicPointersFloat64Field, BasicPointersComplex64Field, BasicPointersComplex128Field, BasicPointersStringField, BasicPointersByteField) diff --git a/tools/badctl/cmd/gen/conditions/tests/results/basicslices.go b/tools/badctl/cmd/gen/conditions/tests/results/basicslices.go new file mode 100644 index 00000000..e1cf97a8 --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/results/basicslices.go @@ -0,0 +1,277 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package conditions + +import ( + badorm "github.com/ditrit/badaas/badorm" + basicslices "github.com/ditrit/badaas/tools/badctl/cmd/gen/conditions/tests/basicslices" + gorm "gorm.io/gorm" + "reflect" + "time" +) + +var basicSlicesType = reflect.TypeOf(*new(basicslices.BasicSlices)) +var BasicSlicesIdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "ID", + ModelType: basicSlicesType, +} + +func BasicSlicesId(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[basicslices.BasicSlices] { + return badorm.FieldCondition[basicslices.BasicSlices, badorm.UUID]{ + FieldIdentifier: BasicSlicesIdField, + Operator: operator, + } +} + +var BasicSlicesCreatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: basicSlicesType, +} + +func BasicSlicesCreatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[basicslices.BasicSlices] { + return badorm.FieldCondition[basicslices.BasicSlices, time.Time]{ + FieldIdentifier: BasicSlicesCreatedAtField, + Operator: operator, + } +} + +var BasicSlicesUpdatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: basicSlicesType, +} + +func BasicSlicesUpdatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[basicslices.BasicSlices] { + return badorm.FieldCondition[basicslices.BasicSlices, time.Time]{ + FieldIdentifier: BasicSlicesUpdatedAtField, + Operator: operator, + } +} + +var BasicSlicesDeletedAtField = badorm.FieldIdentifier[gorm.DeletedAt]{ + Field: "DeletedAt", + ModelType: basicSlicesType, +} + +func BasicSlicesDeletedAt(operator badorm.Operator[gorm.DeletedAt]) badorm.WhereCondition[basicslices.BasicSlices] { + return badorm.FieldCondition[basicslices.BasicSlices, gorm.DeletedAt]{ + FieldIdentifier: BasicSlicesDeletedAtField, + Operator: operator, + } +} + +var BasicSlicesBoolField = badorm.FieldIdentifier[[]bool]{ + Field: "Bool", + ModelType: basicSlicesType, +} + +func BasicSlicesBool(operator badorm.Operator[[]bool]) badorm.WhereCondition[basicslices.BasicSlices] { + return badorm.FieldCondition[basicslices.BasicSlices, []bool]{ + FieldIdentifier: BasicSlicesBoolField, + Operator: operator, + } +} + +var BasicSlicesIntField = badorm.FieldIdentifier[[]int]{ + Field: "Int", + ModelType: basicSlicesType, +} + +func BasicSlicesInt(operator badorm.Operator[[]int]) badorm.WhereCondition[basicslices.BasicSlices] { + return badorm.FieldCondition[basicslices.BasicSlices, []int]{ + FieldIdentifier: BasicSlicesIntField, + Operator: operator, + } +} + +var BasicSlicesInt8Field = badorm.FieldIdentifier[[]int8]{ + Field: "Int8", + ModelType: basicSlicesType, +} + +func BasicSlicesInt8(operator badorm.Operator[[]int8]) badorm.WhereCondition[basicslices.BasicSlices] { + return badorm.FieldCondition[basicslices.BasicSlices, []int8]{ + FieldIdentifier: BasicSlicesInt8Field, + Operator: operator, + } +} + +var BasicSlicesInt16Field = badorm.FieldIdentifier[[]int16]{ + Field: "Int16", + ModelType: basicSlicesType, +} + +func BasicSlicesInt16(operator badorm.Operator[[]int16]) badorm.WhereCondition[basicslices.BasicSlices] { + return badorm.FieldCondition[basicslices.BasicSlices, []int16]{ + FieldIdentifier: BasicSlicesInt16Field, + Operator: operator, + } +} + +var BasicSlicesInt32Field = badorm.FieldIdentifier[[]int32]{ + Field: "Int32", + ModelType: basicSlicesType, +} + +func BasicSlicesInt32(operator badorm.Operator[[]int32]) badorm.WhereCondition[basicslices.BasicSlices] { + return badorm.FieldCondition[basicslices.BasicSlices, []int32]{ + FieldIdentifier: BasicSlicesInt32Field, + Operator: operator, + } +} + +var BasicSlicesInt64Field = badorm.FieldIdentifier[[]int64]{ + Field: "Int64", + ModelType: basicSlicesType, +} + +func BasicSlicesInt64(operator badorm.Operator[[]int64]) badorm.WhereCondition[basicslices.BasicSlices] { + return badorm.FieldCondition[basicslices.BasicSlices, []int64]{ + FieldIdentifier: BasicSlicesInt64Field, + Operator: operator, + } +} + +var BasicSlicesUIntField = badorm.FieldIdentifier[[]uint]{ + Field: "UInt", + ModelType: basicSlicesType, +} + +func BasicSlicesUInt(operator badorm.Operator[[]uint]) badorm.WhereCondition[basicslices.BasicSlices] { + return badorm.FieldCondition[basicslices.BasicSlices, []uint]{ + FieldIdentifier: BasicSlicesUIntField, + Operator: operator, + } +} + +var BasicSlicesUInt8Field = badorm.FieldIdentifier[[]uint8]{ + Field: "UInt8", + ModelType: basicSlicesType, +} + +func BasicSlicesUInt8(operator badorm.Operator[[]uint8]) badorm.WhereCondition[basicslices.BasicSlices] { + return badorm.FieldCondition[basicslices.BasicSlices, []uint8]{ + FieldIdentifier: BasicSlicesUInt8Field, + Operator: operator, + } +} + +var BasicSlicesUInt16Field = badorm.FieldIdentifier[[]uint16]{ + Field: "UInt16", + ModelType: basicSlicesType, +} + +func BasicSlicesUInt16(operator badorm.Operator[[]uint16]) badorm.WhereCondition[basicslices.BasicSlices] { + return badorm.FieldCondition[basicslices.BasicSlices, []uint16]{ + FieldIdentifier: BasicSlicesUInt16Field, + Operator: operator, + } +} + +var BasicSlicesUInt32Field = badorm.FieldIdentifier[[]uint32]{ + Field: "UInt32", + ModelType: basicSlicesType, +} + +func BasicSlicesUInt32(operator badorm.Operator[[]uint32]) badorm.WhereCondition[basicslices.BasicSlices] { + return badorm.FieldCondition[basicslices.BasicSlices, []uint32]{ + FieldIdentifier: BasicSlicesUInt32Field, + Operator: operator, + } +} + +var BasicSlicesUInt64Field = badorm.FieldIdentifier[[]uint64]{ + Field: "UInt64", + ModelType: basicSlicesType, +} + +func BasicSlicesUInt64(operator badorm.Operator[[]uint64]) badorm.WhereCondition[basicslices.BasicSlices] { + return badorm.FieldCondition[basicslices.BasicSlices, []uint64]{ + FieldIdentifier: BasicSlicesUInt64Field, + Operator: operator, + } +} + +var BasicSlicesUIntptrField = badorm.FieldIdentifier[[]uintptr]{ + Field: "UIntptr", + ModelType: basicSlicesType, +} + +func BasicSlicesUIntptr(operator badorm.Operator[[]uintptr]) badorm.WhereCondition[basicslices.BasicSlices] { + return badorm.FieldCondition[basicslices.BasicSlices, []uintptr]{ + FieldIdentifier: BasicSlicesUIntptrField, + Operator: operator, + } +} + +var BasicSlicesFloat32Field = badorm.FieldIdentifier[[]float32]{ + Field: "Float32", + ModelType: basicSlicesType, +} + +func BasicSlicesFloat32(operator badorm.Operator[[]float32]) badorm.WhereCondition[basicslices.BasicSlices] { + return badorm.FieldCondition[basicslices.BasicSlices, []float32]{ + FieldIdentifier: BasicSlicesFloat32Field, + Operator: operator, + } +} + +var BasicSlicesFloat64Field = badorm.FieldIdentifier[[]float64]{ + Field: "Float64", + ModelType: basicSlicesType, +} + +func BasicSlicesFloat64(operator badorm.Operator[[]float64]) badorm.WhereCondition[basicslices.BasicSlices] { + return badorm.FieldCondition[basicslices.BasicSlices, []float64]{ + FieldIdentifier: BasicSlicesFloat64Field, + Operator: operator, + } +} + +var BasicSlicesComplex64Field = badorm.FieldIdentifier[[]complex64]{ + Field: "Complex64", + ModelType: basicSlicesType, +} + +func BasicSlicesComplex64(operator badorm.Operator[[]complex64]) badorm.WhereCondition[basicslices.BasicSlices] { + return badorm.FieldCondition[basicslices.BasicSlices, []complex64]{ + FieldIdentifier: BasicSlicesComplex64Field, + Operator: operator, + } +} + +var BasicSlicesComplex128Field = badorm.FieldIdentifier[[]complex128]{ + Field: "Complex128", + ModelType: basicSlicesType, +} + +func BasicSlicesComplex128(operator badorm.Operator[[]complex128]) badorm.WhereCondition[basicslices.BasicSlices] { + return badorm.FieldCondition[basicslices.BasicSlices, []complex128]{ + FieldIdentifier: BasicSlicesComplex128Field, + Operator: operator, + } +} + +var BasicSlicesStringField = badorm.FieldIdentifier[[]string]{ + Field: "String", + ModelType: basicSlicesType, +} + +func BasicSlicesString(operator badorm.Operator[[]string]) badorm.WhereCondition[basicslices.BasicSlices] { + return badorm.FieldCondition[basicslices.BasicSlices, []string]{ + FieldIdentifier: BasicSlicesStringField, + Operator: operator, + } +} + +var BasicSlicesByteField = badorm.FieldIdentifier[[]uint8]{ + Field: "Byte", + ModelType: basicSlicesType, +} + +func BasicSlicesByte(operator badorm.Operator[[]uint8]) badorm.WhereCondition[basicslices.BasicSlices] { + return badorm.FieldCondition[basicslices.BasicSlices, []uint8]{ + FieldIdentifier: BasicSlicesByteField, + Operator: operator, + } +} + +var BasicSlicesPreloadAttributes = badorm.NewPreloadCondition[basicslices.BasicSlices](BasicSlicesIdField, BasicSlicesCreatedAtField, BasicSlicesUpdatedAtField, BasicSlicesDeletedAtField, BasicSlicesBoolField, BasicSlicesIntField, BasicSlicesInt8Field, BasicSlicesInt16Field, BasicSlicesInt32Field, BasicSlicesInt64Field, BasicSlicesUIntField, BasicSlicesUInt8Field, BasicSlicesUInt16Field, BasicSlicesUInt32Field, BasicSlicesUInt64Field, BasicSlicesUIntptrField, BasicSlicesFloat32Field, BasicSlicesFloat64Field, BasicSlicesComplex64Field, BasicSlicesComplex128Field, BasicSlicesStringField, BasicSlicesByteField) diff --git a/tools/badctl/cmd/gen/conditions/tests/results/basicslicespointer.go b/tools/badctl/cmd/gen/conditions/tests/results/basicslicespointer.go new file mode 100644 index 00000000..5cba7307 --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/results/basicslicespointer.go @@ -0,0 +1,277 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package conditions + +import ( + badorm "github.com/ditrit/badaas/badorm" + basicslicespointer "github.com/ditrit/badaas/tools/badctl/cmd/gen/conditions/tests/basicslicespointer" + gorm "gorm.io/gorm" + "reflect" + "time" +) + +var basicSlicesPointerType = reflect.TypeOf(*new(basicslicespointer.BasicSlicesPointer)) +var BasicSlicesPointerIdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "ID", + ModelType: basicSlicesPointerType, +} + +func BasicSlicesPointerId(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[basicslicespointer.BasicSlicesPointer] { + return badorm.FieldCondition[basicslicespointer.BasicSlicesPointer, badorm.UUID]{ + FieldIdentifier: BasicSlicesPointerIdField, + Operator: operator, + } +} + +var BasicSlicesPointerCreatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: basicSlicesPointerType, +} + +func BasicSlicesPointerCreatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[basicslicespointer.BasicSlicesPointer] { + return badorm.FieldCondition[basicslicespointer.BasicSlicesPointer, time.Time]{ + FieldIdentifier: BasicSlicesPointerCreatedAtField, + Operator: operator, + } +} + +var BasicSlicesPointerUpdatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: basicSlicesPointerType, +} + +func BasicSlicesPointerUpdatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[basicslicespointer.BasicSlicesPointer] { + return badorm.FieldCondition[basicslicespointer.BasicSlicesPointer, time.Time]{ + FieldIdentifier: BasicSlicesPointerUpdatedAtField, + Operator: operator, + } +} + +var BasicSlicesPointerDeletedAtField = badorm.FieldIdentifier[gorm.DeletedAt]{ + Field: "DeletedAt", + ModelType: basicSlicesPointerType, +} + +func BasicSlicesPointerDeletedAt(operator badorm.Operator[gorm.DeletedAt]) badorm.WhereCondition[basicslicespointer.BasicSlicesPointer] { + return badorm.FieldCondition[basicslicespointer.BasicSlicesPointer, gorm.DeletedAt]{ + FieldIdentifier: BasicSlicesPointerDeletedAtField, + Operator: operator, + } +} + +var BasicSlicesPointerBoolField = badorm.FieldIdentifier[[]bool]{ + Field: "Bool", + ModelType: basicSlicesPointerType, +} + +func BasicSlicesPointerBool(operator badorm.Operator[[]bool]) badorm.WhereCondition[basicslicespointer.BasicSlicesPointer] { + return badorm.FieldCondition[basicslicespointer.BasicSlicesPointer, []bool]{ + FieldIdentifier: BasicSlicesPointerBoolField, + Operator: operator, + } +} + +var BasicSlicesPointerIntField = badorm.FieldIdentifier[[]int]{ + Field: "Int", + ModelType: basicSlicesPointerType, +} + +func BasicSlicesPointerInt(operator badorm.Operator[[]int]) badorm.WhereCondition[basicslicespointer.BasicSlicesPointer] { + return badorm.FieldCondition[basicslicespointer.BasicSlicesPointer, []int]{ + FieldIdentifier: BasicSlicesPointerIntField, + Operator: operator, + } +} + +var BasicSlicesPointerInt8Field = badorm.FieldIdentifier[[]int8]{ + Field: "Int8", + ModelType: basicSlicesPointerType, +} + +func BasicSlicesPointerInt8(operator badorm.Operator[[]int8]) badorm.WhereCondition[basicslicespointer.BasicSlicesPointer] { + return badorm.FieldCondition[basicslicespointer.BasicSlicesPointer, []int8]{ + FieldIdentifier: BasicSlicesPointerInt8Field, + Operator: operator, + } +} + +var BasicSlicesPointerInt16Field = badorm.FieldIdentifier[[]int16]{ + Field: "Int16", + ModelType: basicSlicesPointerType, +} + +func BasicSlicesPointerInt16(operator badorm.Operator[[]int16]) badorm.WhereCondition[basicslicespointer.BasicSlicesPointer] { + return badorm.FieldCondition[basicslicespointer.BasicSlicesPointer, []int16]{ + FieldIdentifier: BasicSlicesPointerInt16Field, + Operator: operator, + } +} + +var BasicSlicesPointerInt32Field = badorm.FieldIdentifier[[]int32]{ + Field: "Int32", + ModelType: basicSlicesPointerType, +} + +func BasicSlicesPointerInt32(operator badorm.Operator[[]int32]) badorm.WhereCondition[basicslicespointer.BasicSlicesPointer] { + return badorm.FieldCondition[basicslicespointer.BasicSlicesPointer, []int32]{ + FieldIdentifier: BasicSlicesPointerInt32Field, + Operator: operator, + } +} + +var BasicSlicesPointerInt64Field = badorm.FieldIdentifier[[]int64]{ + Field: "Int64", + ModelType: basicSlicesPointerType, +} + +func BasicSlicesPointerInt64(operator badorm.Operator[[]int64]) badorm.WhereCondition[basicslicespointer.BasicSlicesPointer] { + return badorm.FieldCondition[basicslicespointer.BasicSlicesPointer, []int64]{ + FieldIdentifier: BasicSlicesPointerInt64Field, + Operator: operator, + } +} + +var BasicSlicesPointerUIntField = badorm.FieldIdentifier[[]uint]{ + Field: "UInt", + ModelType: basicSlicesPointerType, +} + +func BasicSlicesPointerUInt(operator badorm.Operator[[]uint]) badorm.WhereCondition[basicslicespointer.BasicSlicesPointer] { + return badorm.FieldCondition[basicslicespointer.BasicSlicesPointer, []uint]{ + FieldIdentifier: BasicSlicesPointerUIntField, + Operator: operator, + } +} + +var BasicSlicesPointerUInt8Field = badorm.FieldIdentifier[[]uint8]{ + Field: "UInt8", + ModelType: basicSlicesPointerType, +} + +func BasicSlicesPointerUInt8(operator badorm.Operator[[]uint8]) badorm.WhereCondition[basicslicespointer.BasicSlicesPointer] { + return badorm.FieldCondition[basicslicespointer.BasicSlicesPointer, []uint8]{ + FieldIdentifier: BasicSlicesPointerUInt8Field, + Operator: operator, + } +} + +var BasicSlicesPointerUInt16Field = badorm.FieldIdentifier[[]uint16]{ + Field: "UInt16", + ModelType: basicSlicesPointerType, +} + +func BasicSlicesPointerUInt16(operator badorm.Operator[[]uint16]) badorm.WhereCondition[basicslicespointer.BasicSlicesPointer] { + return badorm.FieldCondition[basicslicespointer.BasicSlicesPointer, []uint16]{ + FieldIdentifier: BasicSlicesPointerUInt16Field, + Operator: operator, + } +} + +var BasicSlicesPointerUInt32Field = badorm.FieldIdentifier[[]uint32]{ + Field: "UInt32", + ModelType: basicSlicesPointerType, +} + +func BasicSlicesPointerUInt32(operator badorm.Operator[[]uint32]) badorm.WhereCondition[basicslicespointer.BasicSlicesPointer] { + return badorm.FieldCondition[basicslicespointer.BasicSlicesPointer, []uint32]{ + FieldIdentifier: BasicSlicesPointerUInt32Field, + Operator: operator, + } +} + +var BasicSlicesPointerUInt64Field = badorm.FieldIdentifier[[]uint64]{ + Field: "UInt64", + ModelType: basicSlicesPointerType, +} + +func BasicSlicesPointerUInt64(operator badorm.Operator[[]uint64]) badorm.WhereCondition[basicslicespointer.BasicSlicesPointer] { + return badorm.FieldCondition[basicslicespointer.BasicSlicesPointer, []uint64]{ + FieldIdentifier: BasicSlicesPointerUInt64Field, + Operator: operator, + } +} + +var BasicSlicesPointerUIntptrField = badorm.FieldIdentifier[[]uintptr]{ + Field: "UIntptr", + ModelType: basicSlicesPointerType, +} + +func BasicSlicesPointerUIntptr(operator badorm.Operator[[]uintptr]) badorm.WhereCondition[basicslicespointer.BasicSlicesPointer] { + return badorm.FieldCondition[basicslicespointer.BasicSlicesPointer, []uintptr]{ + FieldIdentifier: BasicSlicesPointerUIntptrField, + Operator: operator, + } +} + +var BasicSlicesPointerFloat32Field = badorm.FieldIdentifier[[]float32]{ + Field: "Float32", + ModelType: basicSlicesPointerType, +} + +func BasicSlicesPointerFloat32(operator badorm.Operator[[]float32]) badorm.WhereCondition[basicslicespointer.BasicSlicesPointer] { + return badorm.FieldCondition[basicslicespointer.BasicSlicesPointer, []float32]{ + FieldIdentifier: BasicSlicesPointerFloat32Field, + Operator: operator, + } +} + +var BasicSlicesPointerFloat64Field = badorm.FieldIdentifier[[]float64]{ + Field: "Float64", + ModelType: basicSlicesPointerType, +} + +func BasicSlicesPointerFloat64(operator badorm.Operator[[]float64]) badorm.WhereCondition[basicslicespointer.BasicSlicesPointer] { + return badorm.FieldCondition[basicslicespointer.BasicSlicesPointer, []float64]{ + FieldIdentifier: BasicSlicesPointerFloat64Field, + Operator: operator, + } +} + +var BasicSlicesPointerComplex64Field = badorm.FieldIdentifier[[]complex64]{ + Field: "Complex64", + ModelType: basicSlicesPointerType, +} + +func BasicSlicesPointerComplex64(operator badorm.Operator[[]complex64]) badorm.WhereCondition[basicslicespointer.BasicSlicesPointer] { + return badorm.FieldCondition[basicslicespointer.BasicSlicesPointer, []complex64]{ + FieldIdentifier: BasicSlicesPointerComplex64Field, + Operator: operator, + } +} + +var BasicSlicesPointerComplex128Field = badorm.FieldIdentifier[[]complex128]{ + Field: "Complex128", + ModelType: basicSlicesPointerType, +} + +func BasicSlicesPointerComplex128(operator badorm.Operator[[]complex128]) badorm.WhereCondition[basicslicespointer.BasicSlicesPointer] { + return badorm.FieldCondition[basicslicespointer.BasicSlicesPointer, []complex128]{ + FieldIdentifier: BasicSlicesPointerComplex128Field, + Operator: operator, + } +} + +var BasicSlicesPointerStringField = badorm.FieldIdentifier[[]string]{ + Field: "String", + ModelType: basicSlicesPointerType, +} + +func BasicSlicesPointerString(operator badorm.Operator[[]string]) badorm.WhereCondition[basicslicespointer.BasicSlicesPointer] { + return badorm.FieldCondition[basicslicespointer.BasicSlicesPointer, []string]{ + FieldIdentifier: BasicSlicesPointerStringField, + Operator: operator, + } +} + +var BasicSlicesPointerByteField = badorm.FieldIdentifier[[]uint8]{ + Field: "Byte", + ModelType: basicSlicesPointerType, +} + +func BasicSlicesPointerByte(operator badorm.Operator[[]uint8]) badorm.WhereCondition[basicslicespointer.BasicSlicesPointer] { + return badorm.FieldCondition[basicslicespointer.BasicSlicesPointer, []uint8]{ + FieldIdentifier: BasicSlicesPointerByteField, + Operator: operator, + } +} + +var BasicSlicesPointerPreloadAttributes = badorm.NewPreloadCondition[basicslicespointer.BasicSlicesPointer](BasicSlicesPointerIdField, BasicSlicesPointerCreatedAtField, BasicSlicesPointerUpdatedAtField, BasicSlicesPointerDeletedAtField, BasicSlicesPointerBoolField, BasicSlicesPointerIntField, BasicSlicesPointerInt8Field, BasicSlicesPointerInt16Field, BasicSlicesPointerInt32Field, BasicSlicesPointerInt64Field, BasicSlicesPointerUIntField, BasicSlicesPointerUInt8Field, BasicSlicesPointerUInt16Field, BasicSlicesPointerUInt32Field, BasicSlicesPointerUInt64Field, BasicSlicesPointerUIntptrField, BasicSlicesPointerFloat32Field, BasicSlicesPointerFloat64Field, BasicSlicesPointerComplex64Field, BasicSlicesPointerComplex128Field, BasicSlicesPointerStringField, BasicSlicesPointerByteField) diff --git a/tools/badctl/cmd/gen/conditions/tests/results/basictypes.go b/tools/badctl/cmd/gen/conditions/tests/results/basictypes.go new file mode 100644 index 00000000..6018a927 --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/results/basictypes.go @@ -0,0 +1,277 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package conditions + +import ( + badorm "github.com/ditrit/badaas/badorm" + basictypes "github.com/ditrit/badaas/tools/badctl/cmd/gen/conditions/tests/basictypes" + gorm "gorm.io/gorm" + "reflect" + "time" +) + +var basicTypesType = reflect.TypeOf(*new(basictypes.BasicTypes)) +var BasicTypesIdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "ID", + ModelType: basicTypesType, +} + +func BasicTypesId(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[basictypes.BasicTypes] { + return badorm.FieldCondition[basictypes.BasicTypes, badorm.UUID]{ + FieldIdentifier: BasicTypesIdField, + Operator: operator, + } +} + +var BasicTypesCreatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: basicTypesType, +} + +func BasicTypesCreatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[basictypes.BasicTypes] { + return badorm.FieldCondition[basictypes.BasicTypes, time.Time]{ + FieldIdentifier: BasicTypesCreatedAtField, + Operator: operator, + } +} + +var BasicTypesUpdatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: basicTypesType, +} + +func BasicTypesUpdatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[basictypes.BasicTypes] { + return badorm.FieldCondition[basictypes.BasicTypes, time.Time]{ + FieldIdentifier: BasicTypesUpdatedAtField, + Operator: operator, + } +} + +var BasicTypesDeletedAtField = badorm.FieldIdentifier[gorm.DeletedAt]{ + Field: "DeletedAt", + ModelType: basicTypesType, +} + +func BasicTypesDeletedAt(operator badorm.Operator[gorm.DeletedAt]) badorm.WhereCondition[basictypes.BasicTypes] { + return badorm.FieldCondition[basictypes.BasicTypes, gorm.DeletedAt]{ + FieldIdentifier: BasicTypesDeletedAtField, + Operator: operator, + } +} + +var BasicTypesBoolField = badorm.FieldIdentifier[bool]{ + Field: "Bool", + ModelType: basicTypesType, +} + +func BasicTypesBool(operator badorm.Operator[bool]) badorm.WhereCondition[basictypes.BasicTypes] { + return badorm.FieldCondition[basictypes.BasicTypes, bool]{ + FieldIdentifier: BasicTypesBoolField, + Operator: operator, + } +} + +var BasicTypesIntField = badorm.FieldIdentifier[int]{ + Field: "Int", + ModelType: basicTypesType, +} + +func BasicTypesInt(operator badorm.Operator[int]) badorm.WhereCondition[basictypes.BasicTypes] { + return badorm.FieldCondition[basictypes.BasicTypes, int]{ + FieldIdentifier: BasicTypesIntField, + Operator: operator, + } +} + +var BasicTypesInt8Field = badorm.FieldIdentifier[int8]{ + Field: "Int8", + ModelType: basicTypesType, +} + +func BasicTypesInt8(operator badorm.Operator[int8]) badorm.WhereCondition[basictypes.BasicTypes] { + return badorm.FieldCondition[basictypes.BasicTypes, int8]{ + FieldIdentifier: BasicTypesInt8Field, + Operator: operator, + } +} + +var BasicTypesInt16Field = badorm.FieldIdentifier[int16]{ + Field: "Int16", + ModelType: basicTypesType, +} + +func BasicTypesInt16(operator badorm.Operator[int16]) badorm.WhereCondition[basictypes.BasicTypes] { + return badorm.FieldCondition[basictypes.BasicTypes, int16]{ + FieldIdentifier: BasicTypesInt16Field, + Operator: operator, + } +} + +var BasicTypesInt32Field = badorm.FieldIdentifier[int32]{ + Field: "Int32", + ModelType: basicTypesType, +} + +func BasicTypesInt32(operator badorm.Operator[int32]) badorm.WhereCondition[basictypes.BasicTypes] { + return badorm.FieldCondition[basictypes.BasicTypes, int32]{ + FieldIdentifier: BasicTypesInt32Field, + Operator: operator, + } +} + +var BasicTypesInt64Field = badorm.FieldIdentifier[int64]{ + Field: "Int64", + ModelType: basicTypesType, +} + +func BasicTypesInt64(operator badorm.Operator[int64]) badorm.WhereCondition[basictypes.BasicTypes] { + return badorm.FieldCondition[basictypes.BasicTypes, int64]{ + FieldIdentifier: BasicTypesInt64Field, + Operator: operator, + } +} + +var BasicTypesUIntField = badorm.FieldIdentifier[uint]{ + Field: "UInt", + ModelType: basicTypesType, +} + +func BasicTypesUInt(operator badorm.Operator[uint]) badorm.WhereCondition[basictypes.BasicTypes] { + return badorm.FieldCondition[basictypes.BasicTypes, uint]{ + FieldIdentifier: BasicTypesUIntField, + Operator: operator, + } +} + +var BasicTypesUInt8Field = badorm.FieldIdentifier[uint8]{ + Field: "UInt8", + ModelType: basicTypesType, +} + +func BasicTypesUInt8(operator badorm.Operator[uint8]) badorm.WhereCondition[basictypes.BasicTypes] { + return badorm.FieldCondition[basictypes.BasicTypes, uint8]{ + FieldIdentifier: BasicTypesUInt8Field, + Operator: operator, + } +} + +var BasicTypesUInt16Field = badorm.FieldIdentifier[uint16]{ + Field: "UInt16", + ModelType: basicTypesType, +} + +func BasicTypesUInt16(operator badorm.Operator[uint16]) badorm.WhereCondition[basictypes.BasicTypes] { + return badorm.FieldCondition[basictypes.BasicTypes, uint16]{ + FieldIdentifier: BasicTypesUInt16Field, + Operator: operator, + } +} + +var BasicTypesUInt32Field = badorm.FieldIdentifier[uint32]{ + Field: "UInt32", + ModelType: basicTypesType, +} + +func BasicTypesUInt32(operator badorm.Operator[uint32]) badorm.WhereCondition[basictypes.BasicTypes] { + return badorm.FieldCondition[basictypes.BasicTypes, uint32]{ + FieldIdentifier: BasicTypesUInt32Field, + Operator: operator, + } +} + +var BasicTypesUInt64Field = badorm.FieldIdentifier[uint64]{ + Field: "UInt64", + ModelType: basicTypesType, +} + +func BasicTypesUInt64(operator badorm.Operator[uint64]) badorm.WhereCondition[basictypes.BasicTypes] { + return badorm.FieldCondition[basictypes.BasicTypes, uint64]{ + FieldIdentifier: BasicTypesUInt64Field, + Operator: operator, + } +} + +var BasicTypesUIntptrField = badorm.FieldIdentifier[uintptr]{ + Field: "UIntptr", + ModelType: basicTypesType, +} + +func BasicTypesUIntptr(operator badorm.Operator[uintptr]) badorm.WhereCondition[basictypes.BasicTypes] { + return badorm.FieldCondition[basictypes.BasicTypes, uintptr]{ + FieldIdentifier: BasicTypesUIntptrField, + Operator: operator, + } +} + +var BasicTypesFloat32Field = badorm.FieldIdentifier[float32]{ + Field: "Float32", + ModelType: basicTypesType, +} + +func BasicTypesFloat32(operator badorm.Operator[float32]) badorm.WhereCondition[basictypes.BasicTypes] { + return badorm.FieldCondition[basictypes.BasicTypes, float32]{ + FieldIdentifier: BasicTypesFloat32Field, + Operator: operator, + } +} + +var BasicTypesFloat64Field = badorm.FieldIdentifier[float64]{ + Field: "Float64", + ModelType: basicTypesType, +} + +func BasicTypesFloat64(operator badorm.Operator[float64]) badorm.WhereCondition[basictypes.BasicTypes] { + return badorm.FieldCondition[basictypes.BasicTypes, float64]{ + FieldIdentifier: BasicTypesFloat64Field, + Operator: operator, + } +} + +var BasicTypesComplex64Field = badorm.FieldIdentifier[complex64]{ + Field: "Complex64", + ModelType: basicTypesType, +} + +func BasicTypesComplex64(operator badorm.Operator[complex64]) badorm.WhereCondition[basictypes.BasicTypes] { + return badorm.FieldCondition[basictypes.BasicTypes, complex64]{ + FieldIdentifier: BasicTypesComplex64Field, + Operator: operator, + } +} + +var BasicTypesComplex128Field = badorm.FieldIdentifier[complex128]{ + Field: "Complex128", + ModelType: basicTypesType, +} + +func BasicTypesComplex128(operator badorm.Operator[complex128]) badorm.WhereCondition[basictypes.BasicTypes] { + return badorm.FieldCondition[basictypes.BasicTypes, complex128]{ + FieldIdentifier: BasicTypesComplex128Field, + Operator: operator, + } +} + +var BasicTypesStringField = badorm.FieldIdentifier[string]{ + Field: "String", + ModelType: basicTypesType, +} + +func BasicTypesString(operator badorm.Operator[string]) badorm.WhereCondition[basictypes.BasicTypes] { + return badorm.FieldCondition[basictypes.BasicTypes, string]{ + FieldIdentifier: BasicTypesStringField, + Operator: operator, + } +} + +var BasicTypesByteField = badorm.FieldIdentifier[uint8]{ + Field: "Byte", + ModelType: basicTypesType, +} + +func BasicTypesByte(operator badorm.Operator[uint8]) badorm.WhereCondition[basictypes.BasicTypes] { + return badorm.FieldCondition[basictypes.BasicTypes, uint8]{ + FieldIdentifier: BasicTypesByteField, + Operator: operator, + } +} + +var BasicTypesPreloadAttributes = badorm.NewPreloadCondition[basictypes.BasicTypes](BasicTypesIdField, BasicTypesCreatedAtField, BasicTypesUpdatedAtField, BasicTypesDeletedAtField, BasicTypesBoolField, BasicTypesIntField, BasicTypesInt8Field, BasicTypesInt16Field, BasicTypesInt32Field, BasicTypesInt64Field, BasicTypesUIntField, BasicTypesUInt8Field, BasicTypesUInt16Field, BasicTypesUInt32Field, BasicTypesUInt64Field, BasicTypesUIntptrField, BasicTypesFloat32Field, BasicTypesFloat64Field, BasicTypesComplex64Field, BasicTypesComplex128Field, BasicTypesStringField, BasicTypesByteField) diff --git a/tools/badctl/cmd/gen/conditions/tests/results/belongsto_owned.go b/tools/badctl/cmd/gen/conditions/tests/results/belongsto_owned.go new file mode 100644 index 00000000..02f71c23 --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/results/belongsto_owned.go @@ -0,0 +1,84 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package conditions + +import ( + badorm "github.com/ditrit/badaas/badorm" + belongsto "github.com/ditrit/badaas/tools/badctl/cmd/gen/conditions/tests/belongsto" + gorm "gorm.io/gorm" + "reflect" + "time" +) + +var ownedType = reflect.TypeOf(*new(belongsto.Owned)) +var OwnedIdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "ID", + ModelType: ownedType, +} + +func OwnedId(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[belongsto.Owned] { + return badorm.FieldCondition[belongsto.Owned, badorm.UUID]{ + FieldIdentifier: OwnedIdField, + Operator: operator, + } +} + +var OwnedCreatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: ownedType, +} + +func OwnedCreatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[belongsto.Owned] { + return badorm.FieldCondition[belongsto.Owned, time.Time]{ + FieldIdentifier: OwnedCreatedAtField, + Operator: operator, + } +} + +var OwnedUpdatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: ownedType, +} + +func OwnedUpdatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[belongsto.Owned] { + return badorm.FieldCondition[belongsto.Owned, time.Time]{ + FieldIdentifier: OwnedUpdatedAtField, + Operator: operator, + } +} + +var OwnedDeletedAtField = badorm.FieldIdentifier[gorm.DeletedAt]{ + Field: "DeletedAt", + ModelType: ownedType, +} + +func OwnedDeletedAt(operator badorm.Operator[gorm.DeletedAt]) badorm.WhereCondition[belongsto.Owned] { + return badorm.FieldCondition[belongsto.Owned, gorm.DeletedAt]{ + FieldIdentifier: OwnedDeletedAtField, + Operator: operator, + } +} +func OwnedOwner(conditions ...badorm.Condition[belongsto.Owner]) badorm.IJoinCondition[belongsto.Owned] { + return badorm.JoinCondition[belongsto.Owned, belongsto.Owner]{ + Conditions: conditions, + RelationField: "Owner", + T1Field: "OwnerID", + T1PreloadCondition: OwnedPreloadAttributes, + T2Field: "ID", + } +} + +var OwnedPreloadOwner = OwnedOwner(OwnerPreloadAttributes) +var OwnedOwnerIdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "OwnerID", + ModelType: ownedType, +} + +func OwnedOwnerId(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[belongsto.Owned] { + return badorm.FieldCondition[belongsto.Owned, badorm.UUID]{ + FieldIdentifier: OwnedOwnerIdField, + Operator: operator, + } +} + +var OwnedPreloadAttributes = badorm.NewPreloadCondition[belongsto.Owned](OwnedIdField, OwnedCreatedAtField, OwnedUpdatedAtField, OwnedDeletedAtField, OwnedOwnerIdField) +var OwnedPreloadRelations = []badorm.Condition[belongsto.Owned]{OwnedPreloadOwner} diff --git a/tools/badctl/cmd/gen/conditions/tests/results/belongsto_owner.go b/tools/badctl/cmd/gen/conditions/tests/results/belongsto_owner.go new file mode 100644 index 00000000..fea13eb2 --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/results/belongsto_owner.go @@ -0,0 +1,61 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package conditions + +import ( + badorm "github.com/ditrit/badaas/badorm" + belongsto "github.com/ditrit/badaas/tools/badctl/cmd/gen/conditions/tests/belongsto" + gorm "gorm.io/gorm" + "reflect" + "time" +) + +var ownerType = reflect.TypeOf(*new(belongsto.Owner)) +var OwnerIdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "ID", + ModelType: ownerType, +} + +func OwnerId(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[belongsto.Owner] { + return badorm.FieldCondition[belongsto.Owner, badorm.UUID]{ + FieldIdentifier: OwnerIdField, + Operator: operator, + } +} + +var OwnerCreatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: ownerType, +} + +func OwnerCreatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[belongsto.Owner] { + return badorm.FieldCondition[belongsto.Owner, time.Time]{ + FieldIdentifier: OwnerCreatedAtField, + Operator: operator, + } +} + +var OwnerUpdatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: ownerType, +} + +func OwnerUpdatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[belongsto.Owner] { + return badorm.FieldCondition[belongsto.Owner, time.Time]{ + FieldIdentifier: OwnerUpdatedAtField, + Operator: operator, + } +} + +var OwnerDeletedAtField = badorm.FieldIdentifier[gorm.DeletedAt]{ + Field: "DeletedAt", + ModelType: ownerType, +} + +func OwnerDeletedAt(operator badorm.Operator[gorm.DeletedAt]) badorm.WhereCondition[belongsto.Owner] { + return badorm.FieldCondition[belongsto.Owner, gorm.DeletedAt]{ + FieldIdentifier: OwnerDeletedAtField, + Operator: operator, + } +} + +var OwnerPreloadAttributes = badorm.NewPreloadCondition[belongsto.Owner](OwnerIdField, OwnerCreatedAtField, OwnerUpdatedAtField, OwnerDeletedAtField) diff --git a/tools/badctl/cmd/gen/conditions/tests/results/columndefinition.go b/tools/badctl/cmd/gen/conditions/tests/results/columndefinition.go new file mode 100644 index 00000000..9d4310dc --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/results/columndefinition.go @@ -0,0 +1,74 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package conditions + +import ( + badorm "github.com/ditrit/badaas/badorm" + columndefinition "github.com/ditrit/badaas/tools/badctl/cmd/gen/conditions/tests/columndefinition" + gorm "gorm.io/gorm" + "reflect" + "time" +) + +var columnDefinitionType = reflect.TypeOf(*new(columndefinition.ColumnDefinition)) +var ColumnDefinitionIdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "ID", + ModelType: columnDefinitionType, +} + +func ColumnDefinitionId(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[columndefinition.ColumnDefinition] { + return badorm.FieldCondition[columndefinition.ColumnDefinition, badorm.UUID]{ + FieldIdentifier: ColumnDefinitionIdField, + Operator: operator, + } +} + +var ColumnDefinitionCreatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: columnDefinitionType, +} + +func ColumnDefinitionCreatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[columndefinition.ColumnDefinition] { + return badorm.FieldCondition[columndefinition.ColumnDefinition, time.Time]{ + FieldIdentifier: ColumnDefinitionCreatedAtField, + Operator: operator, + } +} + +var ColumnDefinitionUpdatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: columnDefinitionType, +} + +func ColumnDefinitionUpdatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[columndefinition.ColumnDefinition] { + return badorm.FieldCondition[columndefinition.ColumnDefinition, time.Time]{ + FieldIdentifier: ColumnDefinitionUpdatedAtField, + Operator: operator, + } +} + +var ColumnDefinitionDeletedAtField = badorm.FieldIdentifier[gorm.DeletedAt]{ + Field: "DeletedAt", + ModelType: columnDefinitionType, +} + +func ColumnDefinitionDeletedAt(operator badorm.Operator[gorm.DeletedAt]) badorm.WhereCondition[columndefinition.ColumnDefinition] { + return badorm.FieldCondition[columndefinition.ColumnDefinition, gorm.DeletedAt]{ + FieldIdentifier: ColumnDefinitionDeletedAtField, + Operator: operator, + } +} + +var ColumnDefinitionStringField = badorm.FieldIdentifier[string]{ + Column: "string_something_else", + Field: "String", + ModelType: columnDefinitionType, +} + +func ColumnDefinitionString(operator badorm.Operator[string]) badorm.WhereCondition[columndefinition.ColumnDefinition] { + return badorm.FieldCondition[columndefinition.ColumnDefinition, string]{ + FieldIdentifier: ColumnDefinitionStringField, + Operator: operator, + } +} + +var ColumnDefinitionPreloadAttributes = badorm.NewPreloadCondition[columndefinition.ColumnDefinition](ColumnDefinitionIdField, ColumnDefinitionCreatedAtField, ColumnDefinitionUpdatedAtField, ColumnDefinitionDeletedAtField, ColumnDefinitionStringField) diff --git a/tools/badctl/cmd/gen/conditions/tests/results/customtype.go b/tools/badctl/cmd/gen/conditions/tests/results/customtype.go new file mode 100644 index 00000000..8147bf6e --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/results/customtype.go @@ -0,0 +1,73 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package conditions + +import ( + badorm "github.com/ditrit/badaas/badorm" + customtype "github.com/ditrit/badaas/tools/badctl/cmd/gen/conditions/tests/customtype" + gorm "gorm.io/gorm" + "reflect" + "time" +) + +var customTypeType = reflect.TypeOf(*new(customtype.CustomType)) +var CustomTypeIdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "ID", + ModelType: customTypeType, +} + +func CustomTypeId(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[customtype.CustomType] { + return badorm.FieldCondition[customtype.CustomType, badorm.UUID]{ + FieldIdentifier: CustomTypeIdField, + Operator: operator, + } +} + +var CustomTypeCreatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: customTypeType, +} + +func CustomTypeCreatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[customtype.CustomType] { + return badorm.FieldCondition[customtype.CustomType, time.Time]{ + FieldIdentifier: CustomTypeCreatedAtField, + Operator: operator, + } +} + +var CustomTypeUpdatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: customTypeType, +} + +func CustomTypeUpdatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[customtype.CustomType] { + return badorm.FieldCondition[customtype.CustomType, time.Time]{ + FieldIdentifier: CustomTypeUpdatedAtField, + Operator: operator, + } +} + +var CustomTypeDeletedAtField = badorm.FieldIdentifier[gorm.DeletedAt]{ + Field: "DeletedAt", + ModelType: customTypeType, +} + +func CustomTypeDeletedAt(operator badorm.Operator[gorm.DeletedAt]) badorm.WhereCondition[customtype.CustomType] { + return badorm.FieldCondition[customtype.CustomType, gorm.DeletedAt]{ + FieldIdentifier: CustomTypeDeletedAtField, + Operator: operator, + } +} + +var CustomTypeCustomField = badorm.FieldIdentifier[customtype.MultiString]{ + Field: "Custom", + ModelType: customTypeType, +} + +func CustomTypeCustom(operator badorm.Operator[customtype.MultiString]) badorm.WhereCondition[customtype.CustomType] { + return badorm.FieldCondition[customtype.CustomType, customtype.MultiString]{ + FieldIdentifier: CustomTypeCustomField, + Operator: operator, + } +} + +var CustomTypePreloadAttributes = badorm.NewPreloadCondition[customtype.CustomType](CustomTypeIdField, CustomTypeCreatedAtField, CustomTypeUpdatedAtField, CustomTypeDeletedAtField, CustomTypeCustomField) diff --git a/tools/badctl/cmd/gen/conditions/tests/results/goembedded.go b/tools/badctl/cmd/gen/conditions/tests/results/goembedded.go new file mode 100644 index 00000000..3f6fc728 --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/results/goembedded.go @@ -0,0 +1,85 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package conditions + +import ( + badorm "github.com/ditrit/badaas/badorm" + goembedded "github.com/ditrit/badaas/tools/badctl/cmd/gen/conditions/tests/goembedded" + gorm "gorm.io/gorm" + "reflect" + "time" +) + +var goEmbeddedType = reflect.TypeOf(*new(goembedded.GoEmbedded)) +var GoEmbeddedIdField = badorm.FieldIdentifier[badorm.UIntID]{ + Field: "ID", + ModelType: goEmbeddedType, +} + +func GoEmbeddedId(operator badorm.Operator[badorm.UIntID]) badorm.WhereCondition[goembedded.GoEmbedded] { + return badorm.FieldCondition[goembedded.GoEmbedded, badorm.UIntID]{ + FieldIdentifier: GoEmbeddedIdField, + Operator: operator, + } +} + +var GoEmbeddedCreatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: goEmbeddedType, +} + +func GoEmbeddedCreatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[goembedded.GoEmbedded] { + return badorm.FieldCondition[goembedded.GoEmbedded, time.Time]{ + FieldIdentifier: GoEmbeddedCreatedAtField, + Operator: operator, + } +} + +var GoEmbeddedUpdatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: goEmbeddedType, +} + +func GoEmbeddedUpdatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[goembedded.GoEmbedded] { + return badorm.FieldCondition[goembedded.GoEmbedded, time.Time]{ + FieldIdentifier: GoEmbeddedUpdatedAtField, + Operator: operator, + } +} + +var GoEmbeddedDeletedAtField = badorm.FieldIdentifier[gorm.DeletedAt]{ + Field: "DeletedAt", + ModelType: goEmbeddedType, +} + +func GoEmbeddedDeletedAt(operator badorm.Operator[gorm.DeletedAt]) badorm.WhereCondition[goembedded.GoEmbedded] { + return badorm.FieldCondition[goembedded.GoEmbedded, gorm.DeletedAt]{ + FieldIdentifier: GoEmbeddedDeletedAtField, + Operator: operator, + } +} + +var GoEmbeddedIntField = badorm.FieldIdentifier[int]{ + Field: "Int", + ModelType: goEmbeddedType, +} + +func GoEmbeddedInt(operator badorm.Operator[int]) badorm.WhereCondition[goembedded.GoEmbedded] { + return badorm.FieldCondition[goembedded.GoEmbedded, int]{ + FieldIdentifier: GoEmbeddedIntField, + Operator: operator, + } +} + +var GoEmbeddedToBeEmbeddedIntField = badorm.FieldIdentifier[int]{ + Field: "Int", + ModelType: goEmbeddedType, +} + +func GoEmbeddedToBeEmbeddedInt(operator badorm.Operator[int]) badorm.WhereCondition[goembedded.GoEmbedded] { + return badorm.FieldCondition[goembedded.GoEmbedded, int]{ + FieldIdentifier: GoEmbeddedToBeEmbeddedIntField, + Operator: operator, + } +} + +var GoEmbeddedPreloadAttributes = badorm.NewPreloadCondition[goembedded.GoEmbedded](GoEmbeddedIdField, GoEmbeddedCreatedAtField, GoEmbeddedUpdatedAtField, GoEmbeddedDeletedAtField, GoEmbeddedIntField, GoEmbeddedToBeEmbeddedIntField) diff --git a/tools/badctl/cmd/gen/conditions/tests/results/gormembedded.go b/tools/badctl/cmd/gen/conditions/tests/results/gormembedded.go new file mode 100644 index 00000000..89ab20b0 --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/results/gormembedded.go @@ -0,0 +1,98 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package conditions + +import ( + badorm "github.com/ditrit/badaas/badorm" + gormembedded "github.com/ditrit/badaas/tools/badctl/cmd/gen/conditions/tests/gormembedded" + gorm "gorm.io/gorm" + "reflect" + "time" +) + +var gormEmbeddedType = reflect.TypeOf(*new(gormembedded.GormEmbedded)) +var GormEmbeddedIdField = badorm.FieldIdentifier[badorm.UIntID]{ + Field: "ID", + ModelType: gormEmbeddedType, +} + +func GormEmbeddedId(operator badorm.Operator[badorm.UIntID]) badorm.WhereCondition[gormembedded.GormEmbedded] { + return badorm.FieldCondition[gormembedded.GormEmbedded, badorm.UIntID]{ + FieldIdentifier: GormEmbeddedIdField, + Operator: operator, + } +} + +var GormEmbeddedCreatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: gormEmbeddedType, +} + +func GormEmbeddedCreatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[gormembedded.GormEmbedded] { + return badorm.FieldCondition[gormembedded.GormEmbedded, time.Time]{ + FieldIdentifier: GormEmbeddedCreatedAtField, + Operator: operator, + } +} + +var GormEmbeddedUpdatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: gormEmbeddedType, +} + +func GormEmbeddedUpdatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[gormembedded.GormEmbedded] { + return badorm.FieldCondition[gormembedded.GormEmbedded, time.Time]{ + FieldIdentifier: GormEmbeddedUpdatedAtField, + Operator: operator, + } +} + +var GormEmbeddedDeletedAtField = badorm.FieldIdentifier[gorm.DeletedAt]{ + Field: "DeletedAt", + ModelType: gormEmbeddedType, +} + +func GormEmbeddedDeletedAt(operator badorm.Operator[gorm.DeletedAt]) badorm.WhereCondition[gormembedded.GormEmbedded] { + return badorm.FieldCondition[gormembedded.GormEmbedded, gorm.DeletedAt]{ + FieldIdentifier: GormEmbeddedDeletedAtField, + Operator: operator, + } +} + +var GormEmbeddedIntField = badorm.FieldIdentifier[int]{ + Field: "Int", + ModelType: gormEmbeddedType, +} + +func GormEmbeddedInt(operator badorm.Operator[int]) badorm.WhereCondition[gormembedded.GormEmbedded] { + return badorm.FieldCondition[gormembedded.GormEmbedded, int]{ + FieldIdentifier: GormEmbeddedIntField, + Operator: operator, + } +} + +var GormEmbeddedGormEmbeddedIntField = badorm.FieldIdentifier[int]{ + ColumnPrefix: "gorm_embedded_", + Field: "Int", + ModelType: gormEmbeddedType, +} + +func GormEmbeddedGormEmbeddedInt(operator badorm.Operator[int]) badorm.WhereCondition[gormembedded.GormEmbedded] { + return badorm.FieldCondition[gormembedded.GormEmbedded, int]{ + FieldIdentifier: GormEmbeddedGormEmbeddedIntField, + Operator: operator, + } +} + +var GormEmbeddedGormEmbeddedNoPrefixIntField = badorm.FieldIdentifier[int]{ + Field: "Int", + ModelType: gormEmbeddedType, +} + +func GormEmbeddedGormEmbeddedNoPrefixInt(operator badorm.Operator[int]) badorm.WhereCondition[gormembedded.GormEmbedded] { + return badorm.FieldCondition[gormembedded.GormEmbedded, int]{ + FieldIdentifier: GormEmbeddedGormEmbeddedNoPrefixIntField, + Operator: operator, + } +} + +var GormEmbeddedPreloadAttributes = badorm.NewPreloadCondition[gormembedded.GormEmbedded](GormEmbeddedIdField, GormEmbeddedCreatedAtField, GormEmbeddedUpdatedAtField, GormEmbeddedDeletedAtField, GormEmbeddedIntField, GormEmbeddedGormEmbeddedIntField, GormEmbeddedGormEmbeddedNoPrefixIntField) diff --git a/tools/badctl/cmd/gen/conditions/tests/results/hasmany_company.go b/tools/badctl/cmd/gen/conditions/tests/results/hasmany_company.go new file mode 100644 index 00000000..ba27164a --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/results/hasmany_company.go @@ -0,0 +1,65 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package conditions + +import ( + badorm "github.com/ditrit/badaas/badorm" + hasmany "github.com/ditrit/badaas/tools/badctl/cmd/gen/conditions/tests/hasmany" + gorm "gorm.io/gorm" + "reflect" + "time" +) + +var companyType = reflect.TypeOf(*new(hasmany.Company)) +var CompanyIdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "ID", + ModelType: companyType, +} + +func CompanyId(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[hasmany.Company] { + return badorm.FieldCondition[hasmany.Company, badorm.UUID]{ + FieldIdentifier: CompanyIdField, + Operator: operator, + } +} + +var CompanyCreatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: companyType, +} + +func CompanyCreatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[hasmany.Company] { + return badorm.FieldCondition[hasmany.Company, time.Time]{ + FieldIdentifier: CompanyCreatedAtField, + Operator: operator, + } +} + +var CompanyUpdatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: companyType, +} + +func CompanyUpdatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[hasmany.Company] { + return badorm.FieldCondition[hasmany.Company, time.Time]{ + FieldIdentifier: CompanyUpdatedAtField, + Operator: operator, + } +} + +var CompanyDeletedAtField = badorm.FieldIdentifier[gorm.DeletedAt]{ + Field: "DeletedAt", + ModelType: companyType, +} + +func CompanyDeletedAt(operator badorm.Operator[gorm.DeletedAt]) badorm.WhereCondition[hasmany.Company] { + return badorm.FieldCondition[hasmany.Company, gorm.DeletedAt]{ + FieldIdentifier: CompanyDeletedAtField, + Operator: operator, + } +} +func CompanyPreloadSellers(nestedPreloads ...badorm.IJoinCondition[hasmany.Seller]) badorm.Condition[hasmany.Company] { + return badorm.NewCollectionPreloadCondition[hasmany.Company, hasmany.Seller]("Sellers", nestedPreloads) +} + +var CompanyPreloadAttributes = badorm.NewPreloadCondition[hasmany.Company](CompanyIdField, CompanyCreatedAtField, CompanyUpdatedAtField, CompanyDeletedAtField) +var CompanyPreloadRelations = []badorm.Condition[hasmany.Company]{CompanyPreloadSellers()} diff --git a/tools/badctl/cmd/gen/conditions/tests/results/hasmany_seller.go b/tools/badctl/cmd/gen/conditions/tests/results/hasmany_seller.go new file mode 100644 index 00000000..cc7bb568 --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/results/hasmany_seller.go @@ -0,0 +1,84 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package conditions + +import ( + badorm "github.com/ditrit/badaas/badorm" + hasmany "github.com/ditrit/badaas/tools/badctl/cmd/gen/conditions/tests/hasmany" + gorm "gorm.io/gorm" + "reflect" + "time" +) + +var sellerType = reflect.TypeOf(*new(hasmany.Seller)) +var SellerIdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "ID", + ModelType: sellerType, +} + +func SellerId(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[hasmany.Seller] { + return badorm.FieldCondition[hasmany.Seller, badorm.UUID]{ + FieldIdentifier: SellerIdField, + Operator: operator, + } +} + +var SellerCreatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: sellerType, +} + +func SellerCreatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[hasmany.Seller] { + return badorm.FieldCondition[hasmany.Seller, time.Time]{ + FieldIdentifier: SellerCreatedAtField, + Operator: operator, + } +} + +var SellerUpdatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: sellerType, +} + +func SellerUpdatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[hasmany.Seller] { + return badorm.FieldCondition[hasmany.Seller, time.Time]{ + FieldIdentifier: SellerUpdatedAtField, + Operator: operator, + } +} + +var SellerDeletedAtField = badorm.FieldIdentifier[gorm.DeletedAt]{ + Field: "DeletedAt", + ModelType: sellerType, +} + +func SellerDeletedAt(operator badorm.Operator[gorm.DeletedAt]) badorm.WhereCondition[hasmany.Seller] { + return badorm.FieldCondition[hasmany.Seller, gorm.DeletedAt]{ + FieldIdentifier: SellerDeletedAtField, + Operator: operator, + } +} +func SellerCompany(conditions ...badorm.Condition[hasmany.Company]) badorm.IJoinCondition[hasmany.Seller] { + return badorm.JoinCondition[hasmany.Seller, hasmany.Company]{ + Conditions: conditions, + RelationField: "Company", + T1Field: "CompanyID", + T1PreloadCondition: SellerPreloadAttributes, + T2Field: "ID", + } +} + +var SellerPreloadCompany = SellerCompany(CompanyPreloadAttributes) +var SellerCompanyIdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "CompanyID", + ModelType: sellerType, +} + +func SellerCompanyId(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[hasmany.Seller] { + return badorm.FieldCondition[hasmany.Seller, badorm.UUID]{ + FieldIdentifier: SellerCompanyIdField, + Operator: operator, + } +} + +var SellerPreloadAttributes = badorm.NewPreloadCondition[hasmany.Seller](SellerIdField, SellerCreatedAtField, SellerUpdatedAtField, SellerDeletedAtField, SellerCompanyIdField) +var SellerPreloadRelations = []badorm.Condition[hasmany.Seller]{SellerPreloadCompany} diff --git a/tools/badctl/cmd/gen/conditions/tests/results/hasmanywithpointers_company.go b/tools/badctl/cmd/gen/conditions/tests/results/hasmanywithpointers_company.go new file mode 100644 index 00000000..60fa1660 --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/results/hasmanywithpointers_company.go @@ -0,0 +1,65 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package conditions + +import ( + badorm "github.com/ditrit/badaas/badorm" + hasmanywithpointers "github.com/ditrit/badaas/tools/badctl/cmd/gen/conditions/tests/hasmanywithpointers" + gorm "gorm.io/gorm" + "reflect" + "time" +) + +var companyWithPointersType = reflect.TypeOf(*new(hasmanywithpointers.CompanyWithPointers)) +var CompanyWithPointersIdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "ID", + ModelType: companyWithPointersType, +} + +func CompanyWithPointersId(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[hasmanywithpointers.CompanyWithPointers] { + return badorm.FieldCondition[hasmanywithpointers.CompanyWithPointers, badorm.UUID]{ + FieldIdentifier: CompanyWithPointersIdField, + Operator: operator, + } +} + +var CompanyWithPointersCreatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: companyWithPointersType, +} + +func CompanyWithPointersCreatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[hasmanywithpointers.CompanyWithPointers] { + return badorm.FieldCondition[hasmanywithpointers.CompanyWithPointers, time.Time]{ + FieldIdentifier: CompanyWithPointersCreatedAtField, + Operator: operator, + } +} + +var CompanyWithPointersUpdatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: companyWithPointersType, +} + +func CompanyWithPointersUpdatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[hasmanywithpointers.CompanyWithPointers] { + return badorm.FieldCondition[hasmanywithpointers.CompanyWithPointers, time.Time]{ + FieldIdentifier: CompanyWithPointersUpdatedAtField, + Operator: operator, + } +} + +var CompanyWithPointersDeletedAtField = badorm.FieldIdentifier[gorm.DeletedAt]{ + Field: "DeletedAt", + ModelType: companyWithPointersType, +} + +func CompanyWithPointersDeletedAt(operator badorm.Operator[gorm.DeletedAt]) badorm.WhereCondition[hasmanywithpointers.CompanyWithPointers] { + return badorm.FieldCondition[hasmanywithpointers.CompanyWithPointers, gorm.DeletedAt]{ + FieldIdentifier: CompanyWithPointersDeletedAtField, + Operator: operator, + } +} +func CompanyWithPointersPreloadSellers(nestedPreloads ...badorm.IJoinCondition[hasmanywithpointers.SellerInPointers]) badorm.Condition[hasmanywithpointers.CompanyWithPointers] { + return badorm.NewCollectionPreloadCondition[hasmanywithpointers.CompanyWithPointers, hasmanywithpointers.SellerInPointers]("Sellers", nestedPreloads) +} + +var CompanyWithPointersPreloadAttributes = badorm.NewPreloadCondition[hasmanywithpointers.CompanyWithPointers](CompanyWithPointersIdField, CompanyWithPointersCreatedAtField, CompanyWithPointersUpdatedAtField, CompanyWithPointersDeletedAtField) +var CompanyWithPointersPreloadRelations = []badorm.Condition[hasmanywithpointers.CompanyWithPointers]{CompanyWithPointersPreloadSellers()} diff --git a/tools/badctl/cmd/gen/conditions/tests/results/hasmanywithpointers_seller.go b/tools/badctl/cmd/gen/conditions/tests/results/hasmanywithpointers_seller.go new file mode 100644 index 00000000..40ac3791 --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/results/hasmanywithpointers_seller.go @@ -0,0 +1,84 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package conditions + +import ( + badorm "github.com/ditrit/badaas/badorm" + hasmanywithpointers "github.com/ditrit/badaas/tools/badctl/cmd/gen/conditions/tests/hasmanywithpointers" + gorm "gorm.io/gorm" + "reflect" + "time" +) + +var sellerInPointersType = reflect.TypeOf(*new(hasmanywithpointers.SellerInPointers)) +var SellerInPointersIdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "ID", + ModelType: sellerInPointersType, +} + +func SellerInPointersId(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[hasmanywithpointers.SellerInPointers] { + return badorm.FieldCondition[hasmanywithpointers.SellerInPointers, badorm.UUID]{ + FieldIdentifier: SellerInPointersIdField, + Operator: operator, + } +} + +var SellerInPointersCreatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: sellerInPointersType, +} + +func SellerInPointersCreatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[hasmanywithpointers.SellerInPointers] { + return badorm.FieldCondition[hasmanywithpointers.SellerInPointers, time.Time]{ + FieldIdentifier: SellerInPointersCreatedAtField, + Operator: operator, + } +} + +var SellerInPointersUpdatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: sellerInPointersType, +} + +func SellerInPointersUpdatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[hasmanywithpointers.SellerInPointers] { + return badorm.FieldCondition[hasmanywithpointers.SellerInPointers, time.Time]{ + FieldIdentifier: SellerInPointersUpdatedAtField, + Operator: operator, + } +} + +var SellerInPointersDeletedAtField = badorm.FieldIdentifier[gorm.DeletedAt]{ + Field: "DeletedAt", + ModelType: sellerInPointersType, +} + +func SellerInPointersDeletedAt(operator badorm.Operator[gorm.DeletedAt]) badorm.WhereCondition[hasmanywithpointers.SellerInPointers] { + return badorm.FieldCondition[hasmanywithpointers.SellerInPointers, gorm.DeletedAt]{ + FieldIdentifier: SellerInPointersDeletedAtField, + Operator: operator, + } +} +func SellerInPointersCompany(conditions ...badorm.Condition[hasmanywithpointers.CompanyWithPointers]) badorm.IJoinCondition[hasmanywithpointers.SellerInPointers] { + return badorm.JoinCondition[hasmanywithpointers.SellerInPointers, hasmanywithpointers.CompanyWithPointers]{ + Conditions: conditions, + RelationField: "Company", + T1Field: "CompanyID", + T1PreloadCondition: SellerInPointersPreloadAttributes, + T2Field: "ID", + } +} + +var SellerInPointersPreloadCompany = SellerInPointersCompany(CompanyWithPointersPreloadAttributes) +var SellerInPointersCompanyIdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "CompanyID", + ModelType: sellerInPointersType, +} + +func SellerInPointersCompanyId(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[hasmanywithpointers.SellerInPointers] { + return badorm.FieldCondition[hasmanywithpointers.SellerInPointers, badorm.UUID]{ + FieldIdentifier: SellerInPointersCompanyIdField, + Operator: operator, + } +} + +var SellerInPointersPreloadAttributes = badorm.NewPreloadCondition[hasmanywithpointers.SellerInPointers](SellerInPointersIdField, SellerInPointersCreatedAtField, SellerInPointersUpdatedAtField, SellerInPointersDeletedAtField, SellerInPointersCompanyIdField) +var SellerInPointersPreloadRelations = []badorm.Condition[hasmanywithpointers.SellerInPointers]{SellerInPointersPreloadCompany} diff --git a/tools/badctl/cmd/gen/conditions/tests/results/hasone_city.go b/tools/badctl/cmd/gen/conditions/tests/results/hasone_city.go new file mode 100644 index 00000000..db15efcb --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/results/hasone_city.go @@ -0,0 +1,84 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package conditions + +import ( + badorm "github.com/ditrit/badaas/badorm" + hasone "github.com/ditrit/badaas/tools/badctl/cmd/gen/conditions/tests/hasone" + gorm "gorm.io/gorm" + "reflect" + "time" +) + +var cityType = reflect.TypeOf(*new(hasone.City)) +var CityIdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "ID", + ModelType: cityType, +} + +func CityId(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[hasone.City] { + return badorm.FieldCondition[hasone.City, badorm.UUID]{ + FieldIdentifier: CityIdField, + Operator: operator, + } +} + +var CityCreatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: cityType, +} + +func CityCreatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[hasone.City] { + return badorm.FieldCondition[hasone.City, time.Time]{ + FieldIdentifier: CityCreatedAtField, + Operator: operator, + } +} + +var CityUpdatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: cityType, +} + +func CityUpdatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[hasone.City] { + return badorm.FieldCondition[hasone.City, time.Time]{ + FieldIdentifier: CityUpdatedAtField, + Operator: operator, + } +} + +var CityDeletedAtField = badorm.FieldIdentifier[gorm.DeletedAt]{ + Field: "DeletedAt", + ModelType: cityType, +} + +func CityDeletedAt(operator badorm.Operator[gorm.DeletedAt]) badorm.WhereCondition[hasone.City] { + return badorm.FieldCondition[hasone.City, gorm.DeletedAt]{ + FieldIdentifier: CityDeletedAtField, + Operator: operator, + } +} +func CityCountry(conditions ...badorm.Condition[hasone.Country]) badorm.IJoinCondition[hasone.City] { + return badorm.JoinCondition[hasone.City, hasone.Country]{ + Conditions: conditions, + RelationField: "Country", + T1Field: "CountryID", + T1PreloadCondition: CityPreloadAttributes, + T2Field: "ID", + } +} + +var CityPreloadCountry = CityCountry(CountryPreloadAttributes) +var CityCountryIdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "CountryID", + ModelType: cityType, +} + +func CityCountryId(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[hasone.City] { + return badorm.FieldCondition[hasone.City, badorm.UUID]{ + FieldIdentifier: CityCountryIdField, + Operator: operator, + } +} + +var CityPreloadAttributes = badorm.NewPreloadCondition[hasone.City](CityIdField, CityCreatedAtField, CityUpdatedAtField, CityDeletedAtField, CityCountryIdField) +var CityPreloadRelations = []badorm.Condition[hasone.City]{CityPreloadCountry} diff --git a/tools/badctl/cmd/gen/conditions/tests/results/hasone_country.go b/tools/badctl/cmd/gen/conditions/tests/results/hasone_country.go new file mode 100644 index 00000000..3e0a3616 --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/results/hasone_country.go @@ -0,0 +1,72 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package conditions + +import ( + badorm "github.com/ditrit/badaas/badorm" + hasone "github.com/ditrit/badaas/tools/badctl/cmd/gen/conditions/tests/hasone" + gorm "gorm.io/gorm" + "reflect" + "time" +) + +var countryType = reflect.TypeOf(*new(hasone.Country)) +var CountryIdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "ID", + ModelType: countryType, +} + +func CountryId(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[hasone.Country] { + return badorm.FieldCondition[hasone.Country, badorm.UUID]{ + FieldIdentifier: CountryIdField, + Operator: operator, + } +} + +var CountryCreatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: countryType, +} + +func CountryCreatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[hasone.Country] { + return badorm.FieldCondition[hasone.Country, time.Time]{ + FieldIdentifier: CountryCreatedAtField, + Operator: operator, + } +} + +var CountryUpdatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: countryType, +} + +func CountryUpdatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[hasone.Country] { + return badorm.FieldCondition[hasone.Country, time.Time]{ + FieldIdentifier: CountryUpdatedAtField, + Operator: operator, + } +} + +var CountryDeletedAtField = badorm.FieldIdentifier[gorm.DeletedAt]{ + Field: "DeletedAt", + ModelType: countryType, +} + +func CountryDeletedAt(operator badorm.Operator[gorm.DeletedAt]) badorm.WhereCondition[hasone.Country] { + return badorm.FieldCondition[hasone.Country, gorm.DeletedAt]{ + FieldIdentifier: CountryDeletedAtField, + Operator: operator, + } +} +func CountryCapital(conditions ...badorm.Condition[hasone.City]) badorm.IJoinCondition[hasone.Country] { + return badorm.JoinCondition[hasone.Country, hasone.City]{ + Conditions: conditions, + RelationField: "Capital", + T1Field: "ID", + T1PreloadCondition: CountryPreloadAttributes, + T2Field: "CountryID", + } +} + +var CountryPreloadCapital = CountryCapital(CityPreloadAttributes) +var CountryPreloadAttributes = badorm.NewPreloadCondition[hasone.Country](CountryIdField, CountryCreatedAtField, CountryUpdatedAtField, CountryDeletedAtField) +var CountryPreloadRelations = []badorm.Condition[hasone.Country]{CountryPreloadCapital} diff --git a/tools/badctl/cmd/gen/conditions/tests/results/multiplepackage_package1.go b/tools/badctl/cmd/gen/conditions/tests/results/multiplepackage_package1.go new file mode 100644 index 00000000..4eb25fd7 --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/results/multiplepackage_package1.go @@ -0,0 +1,73 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package conditions + +import ( + badorm "github.com/ditrit/badaas/badorm" + package1 "github.com/ditrit/badaas/tools/badctl/cmd/gen/conditions/tests/multiplepackage/package1" + package2 "github.com/ditrit/badaas/tools/badctl/cmd/gen/conditions/tests/multiplepackage/package2" + gorm "gorm.io/gorm" + "reflect" + "time" +) + +var package1Type = reflect.TypeOf(*new(package1.Package1)) +var Package1IdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "ID", + ModelType: package1Type, +} + +func Package1Id(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[package1.Package1] { + return badorm.FieldCondition[package1.Package1, badorm.UUID]{ + FieldIdentifier: Package1IdField, + Operator: operator, + } +} + +var Package1CreatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: package1Type, +} + +func Package1CreatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[package1.Package1] { + return badorm.FieldCondition[package1.Package1, time.Time]{ + FieldIdentifier: Package1CreatedAtField, + Operator: operator, + } +} + +var Package1UpdatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: package1Type, +} + +func Package1UpdatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[package1.Package1] { + return badorm.FieldCondition[package1.Package1, time.Time]{ + FieldIdentifier: Package1UpdatedAtField, + Operator: operator, + } +} + +var Package1DeletedAtField = badorm.FieldIdentifier[gorm.DeletedAt]{ + Field: "DeletedAt", + ModelType: package1Type, +} + +func Package1DeletedAt(operator badorm.Operator[gorm.DeletedAt]) badorm.WhereCondition[package1.Package1] { + return badorm.FieldCondition[package1.Package1, gorm.DeletedAt]{ + FieldIdentifier: Package1DeletedAtField, + Operator: operator, + } +} +func Package1Package2(conditions ...badorm.Condition[package2.Package2]) badorm.IJoinCondition[package1.Package1] { + return badorm.JoinCondition[package1.Package1, package2.Package2]{ + Conditions: conditions, + RelationField: "Package2", + T1Field: "ID", + T1PreloadCondition: Package1PreloadAttributes, + T2Field: "Package1ID", + } +} + +var Package1PreloadPackage2 = Package1Package2(Package2PreloadAttributes) +var Package1PreloadAttributes = badorm.NewPreloadCondition[package1.Package1](Package1IdField, Package1CreatedAtField, Package1UpdatedAtField, Package1DeletedAtField) +var Package1PreloadRelations = []badorm.Condition[package1.Package1]{Package1PreloadPackage2} diff --git a/tools/badctl/cmd/gen/conditions/tests/results/multiplepackage_package2.go b/tools/badctl/cmd/gen/conditions/tests/results/multiplepackage_package2.go new file mode 100644 index 00000000..0331c6fc --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/results/multiplepackage_package2.go @@ -0,0 +1,73 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package conditions + +import ( + badorm "github.com/ditrit/badaas/badorm" + package2 "github.com/ditrit/badaas/tools/badctl/cmd/gen/conditions/tests/multiplepackage/package2" + gorm "gorm.io/gorm" + "reflect" + "time" +) + +var package2Type = reflect.TypeOf(*new(package2.Package2)) +var Package2IdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "ID", + ModelType: package2Type, +} + +func Package2Id(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[package2.Package2] { + return badorm.FieldCondition[package2.Package2, badorm.UUID]{ + FieldIdentifier: Package2IdField, + Operator: operator, + } +} + +var Package2CreatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: package2Type, +} + +func Package2CreatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[package2.Package2] { + return badorm.FieldCondition[package2.Package2, time.Time]{ + FieldIdentifier: Package2CreatedAtField, + Operator: operator, + } +} + +var Package2UpdatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: package2Type, +} + +func Package2UpdatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[package2.Package2] { + return badorm.FieldCondition[package2.Package2, time.Time]{ + FieldIdentifier: Package2UpdatedAtField, + Operator: operator, + } +} + +var Package2DeletedAtField = badorm.FieldIdentifier[gorm.DeletedAt]{ + Field: "DeletedAt", + ModelType: package2Type, +} + +func Package2DeletedAt(operator badorm.Operator[gorm.DeletedAt]) badorm.WhereCondition[package2.Package2] { + return badorm.FieldCondition[package2.Package2, gorm.DeletedAt]{ + FieldIdentifier: Package2DeletedAtField, + Operator: operator, + } +} + +var Package2Package1IdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "Package1ID", + ModelType: package2Type, +} + +func Package2Package1Id(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[package2.Package2] { + return badorm.FieldCondition[package2.Package2, badorm.UUID]{ + FieldIdentifier: Package2Package1IdField, + Operator: operator, + } +} + +var Package2PreloadAttributes = badorm.NewPreloadCondition[package2.Package2](Package2IdField, Package2CreatedAtField, Package2UpdatedAtField, Package2DeletedAtField, Package2Package1IdField) diff --git a/tools/badctl/cmd/gen/conditions/tests/results/overrideforeignkey_bicycle.go b/tools/badctl/cmd/gen/conditions/tests/results/overrideforeignkey_bicycle.go new file mode 100644 index 00000000..f60cf198 --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/results/overrideforeignkey_bicycle.go @@ -0,0 +1,84 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package conditions + +import ( + badorm "github.com/ditrit/badaas/badorm" + overrideforeignkey "github.com/ditrit/badaas/tools/badctl/cmd/gen/conditions/tests/overrideforeignkey" + gorm "gorm.io/gorm" + "reflect" + "time" +) + +var bicycleType = reflect.TypeOf(*new(overrideforeignkey.Bicycle)) +var BicycleIdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "ID", + ModelType: bicycleType, +} + +func BicycleId(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[overrideforeignkey.Bicycle] { + return badorm.FieldCondition[overrideforeignkey.Bicycle, badorm.UUID]{ + FieldIdentifier: BicycleIdField, + Operator: operator, + } +} + +var BicycleCreatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: bicycleType, +} + +func BicycleCreatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[overrideforeignkey.Bicycle] { + return badorm.FieldCondition[overrideforeignkey.Bicycle, time.Time]{ + FieldIdentifier: BicycleCreatedAtField, + Operator: operator, + } +} + +var BicycleUpdatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: bicycleType, +} + +func BicycleUpdatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[overrideforeignkey.Bicycle] { + return badorm.FieldCondition[overrideforeignkey.Bicycle, time.Time]{ + FieldIdentifier: BicycleUpdatedAtField, + Operator: operator, + } +} + +var BicycleDeletedAtField = badorm.FieldIdentifier[gorm.DeletedAt]{ + Field: "DeletedAt", + ModelType: bicycleType, +} + +func BicycleDeletedAt(operator badorm.Operator[gorm.DeletedAt]) badorm.WhereCondition[overrideforeignkey.Bicycle] { + return badorm.FieldCondition[overrideforeignkey.Bicycle, gorm.DeletedAt]{ + FieldIdentifier: BicycleDeletedAtField, + Operator: operator, + } +} +func BicycleOwner(conditions ...badorm.Condition[overrideforeignkey.Person]) badorm.IJoinCondition[overrideforeignkey.Bicycle] { + return badorm.JoinCondition[overrideforeignkey.Bicycle, overrideforeignkey.Person]{ + Conditions: conditions, + RelationField: "Owner", + T1Field: "OwnerSomethingID", + T1PreloadCondition: BicyclePreloadAttributes, + T2Field: "ID", + } +} + +var BicyclePreloadOwner = BicycleOwner(PersonPreloadAttributes) +var BicycleOwnerSomethingIdField = badorm.FieldIdentifier[string]{ + Field: "OwnerSomethingID", + ModelType: bicycleType, +} + +func BicycleOwnerSomethingId(operator badorm.Operator[string]) badorm.WhereCondition[overrideforeignkey.Bicycle] { + return badorm.FieldCondition[overrideforeignkey.Bicycle, string]{ + FieldIdentifier: BicycleOwnerSomethingIdField, + Operator: operator, + } +} + +var BicyclePreloadAttributes = badorm.NewPreloadCondition[overrideforeignkey.Bicycle](BicycleIdField, BicycleCreatedAtField, BicycleUpdatedAtField, BicycleDeletedAtField, BicycleOwnerSomethingIdField) +var BicyclePreloadRelations = []badorm.Condition[overrideforeignkey.Bicycle]{BicyclePreloadOwner} diff --git a/tools/badctl/cmd/gen/conditions/tests/results/overrideforeignkey_person.go b/tools/badctl/cmd/gen/conditions/tests/results/overrideforeignkey_person.go new file mode 100644 index 00000000..24ec4abb --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/results/overrideforeignkey_person.go @@ -0,0 +1,61 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package conditions + +import ( + badorm "github.com/ditrit/badaas/badorm" + overrideforeignkey "github.com/ditrit/badaas/tools/badctl/cmd/gen/conditions/tests/overrideforeignkey" + gorm "gorm.io/gorm" + "reflect" + "time" +) + +var personType = reflect.TypeOf(*new(overrideforeignkey.Person)) +var PersonIdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "ID", + ModelType: personType, +} + +func PersonId(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[overrideforeignkey.Person] { + return badorm.FieldCondition[overrideforeignkey.Person, badorm.UUID]{ + FieldIdentifier: PersonIdField, + Operator: operator, + } +} + +var PersonCreatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: personType, +} + +func PersonCreatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[overrideforeignkey.Person] { + return badorm.FieldCondition[overrideforeignkey.Person, time.Time]{ + FieldIdentifier: PersonCreatedAtField, + Operator: operator, + } +} + +var PersonUpdatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: personType, +} + +func PersonUpdatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[overrideforeignkey.Person] { + return badorm.FieldCondition[overrideforeignkey.Person, time.Time]{ + FieldIdentifier: PersonUpdatedAtField, + Operator: operator, + } +} + +var PersonDeletedAtField = badorm.FieldIdentifier[gorm.DeletedAt]{ + Field: "DeletedAt", + ModelType: personType, +} + +func PersonDeletedAt(operator badorm.Operator[gorm.DeletedAt]) badorm.WhereCondition[overrideforeignkey.Person] { + return badorm.FieldCondition[overrideforeignkey.Person, gorm.DeletedAt]{ + FieldIdentifier: PersonDeletedAtField, + Operator: operator, + } +} + +var PersonPreloadAttributes = badorm.NewPreloadCondition[overrideforeignkey.Person](PersonIdField, PersonCreatedAtField, PersonUpdatedAtField, PersonDeletedAtField) diff --git a/tools/badctl/cmd/gen/conditions/tests/results/overrideforeignkeyinverse_credit_card.go b/tools/badctl/cmd/gen/conditions/tests/results/overrideforeignkeyinverse_credit_card.go new file mode 100644 index 00000000..33118770 --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/results/overrideforeignkeyinverse_credit_card.go @@ -0,0 +1,73 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package conditions + +import ( + badorm "github.com/ditrit/badaas/badorm" + overrideforeignkeyinverse "github.com/ditrit/badaas/tools/badctl/cmd/gen/conditions/tests/overrideforeignkeyinverse" + gorm "gorm.io/gorm" + "reflect" + "time" +) + +var creditCardType = reflect.TypeOf(*new(overrideforeignkeyinverse.CreditCard)) +var CreditCardIdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "ID", + ModelType: creditCardType, +} + +func CreditCardId(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[overrideforeignkeyinverse.CreditCard] { + return badorm.FieldCondition[overrideforeignkeyinverse.CreditCard, badorm.UUID]{ + FieldIdentifier: CreditCardIdField, + Operator: operator, + } +} + +var CreditCardCreatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: creditCardType, +} + +func CreditCardCreatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[overrideforeignkeyinverse.CreditCard] { + return badorm.FieldCondition[overrideforeignkeyinverse.CreditCard, time.Time]{ + FieldIdentifier: CreditCardCreatedAtField, + Operator: operator, + } +} + +var CreditCardUpdatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: creditCardType, +} + +func CreditCardUpdatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[overrideforeignkeyinverse.CreditCard] { + return badorm.FieldCondition[overrideforeignkeyinverse.CreditCard, time.Time]{ + FieldIdentifier: CreditCardUpdatedAtField, + Operator: operator, + } +} + +var CreditCardDeletedAtField = badorm.FieldIdentifier[gorm.DeletedAt]{ + Field: "DeletedAt", + ModelType: creditCardType, +} + +func CreditCardDeletedAt(operator badorm.Operator[gorm.DeletedAt]) badorm.WhereCondition[overrideforeignkeyinverse.CreditCard] { + return badorm.FieldCondition[overrideforeignkeyinverse.CreditCard, gorm.DeletedAt]{ + FieldIdentifier: CreditCardDeletedAtField, + Operator: operator, + } +} + +var CreditCardUserReferenceField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "UserReference", + ModelType: creditCardType, +} + +func CreditCardUserReference(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[overrideforeignkeyinverse.CreditCard] { + return badorm.FieldCondition[overrideforeignkeyinverse.CreditCard, badorm.UUID]{ + FieldIdentifier: CreditCardUserReferenceField, + Operator: operator, + } +} + +var CreditCardPreloadAttributes = badorm.NewPreloadCondition[overrideforeignkeyinverse.CreditCard](CreditCardIdField, CreditCardCreatedAtField, CreditCardUpdatedAtField, CreditCardDeletedAtField, CreditCardUserReferenceField) diff --git a/tools/badctl/cmd/gen/conditions/tests/results/overrideforeignkeyinverse_user.go b/tools/badctl/cmd/gen/conditions/tests/results/overrideforeignkeyinverse_user.go new file mode 100644 index 00000000..1915cf36 --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/results/overrideforeignkeyinverse_user.go @@ -0,0 +1,72 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package conditions + +import ( + badorm "github.com/ditrit/badaas/badorm" + overrideforeignkeyinverse "github.com/ditrit/badaas/tools/badctl/cmd/gen/conditions/tests/overrideforeignkeyinverse" + gorm "gorm.io/gorm" + "reflect" + "time" +) + +var userType = reflect.TypeOf(*new(overrideforeignkeyinverse.User)) +var UserIdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "ID", + ModelType: userType, +} + +func UserId(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[overrideforeignkeyinverse.User] { + return badorm.FieldCondition[overrideforeignkeyinverse.User, badorm.UUID]{ + FieldIdentifier: UserIdField, + Operator: operator, + } +} + +var UserCreatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: userType, +} + +func UserCreatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[overrideforeignkeyinverse.User] { + return badorm.FieldCondition[overrideforeignkeyinverse.User, time.Time]{ + FieldIdentifier: UserCreatedAtField, + Operator: operator, + } +} + +var UserUpdatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: userType, +} + +func UserUpdatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[overrideforeignkeyinverse.User] { + return badorm.FieldCondition[overrideforeignkeyinverse.User, time.Time]{ + FieldIdentifier: UserUpdatedAtField, + Operator: operator, + } +} + +var UserDeletedAtField = badorm.FieldIdentifier[gorm.DeletedAt]{ + Field: "DeletedAt", + ModelType: userType, +} + +func UserDeletedAt(operator badorm.Operator[gorm.DeletedAt]) badorm.WhereCondition[overrideforeignkeyinverse.User] { + return badorm.FieldCondition[overrideforeignkeyinverse.User, gorm.DeletedAt]{ + FieldIdentifier: UserDeletedAtField, + Operator: operator, + } +} +func UserCreditCard(conditions ...badorm.Condition[overrideforeignkeyinverse.CreditCard]) badorm.IJoinCondition[overrideforeignkeyinverse.User] { + return badorm.JoinCondition[overrideforeignkeyinverse.User, overrideforeignkeyinverse.CreditCard]{ + Conditions: conditions, + RelationField: "CreditCard", + T1Field: "ID", + T1PreloadCondition: UserPreloadAttributes, + T2Field: "UserReference", + } +} + +var UserPreloadCreditCard = UserCreditCard(CreditCardPreloadAttributes) +var UserPreloadAttributes = badorm.NewPreloadCondition[overrideforeignkeyinverse.User](UserIdField, UserCreatedAtField, UserUpdatedAtField, UserDeletedAtField) +var UserPreloadRelations = []badorm.Condition[overrideforeignkeyinverse.User]{UserPreloadCreditCard} diff --git a/tools/badctl/cmd/gen/conditions/tests/results/overridereferences_brand.go b/tools/badctl/cmd/gen/conditions/tests/results/overridereferences_brand.go new file mode 100644 index 00000000..3ad2e14b --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/results/overridereferences_brand.go @@ -0,0 +1,73 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package conditions + +import ( + badorm "github.com/ditrit/badaas/badorm" + overridereferences "github.com/ditrit/badaas/tools/badctl/cmd/gen/conditions/tests/overridereferences" + gorm "gorm.io/gorm" + "reflect" + "time" +) + +var brandType = reflect.TypeOf(*new(overridereferences.Brand)) +var BrandIdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "ID", + ModelType: brandType, +} + +func BrandId(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[overridereferences.Brand] { + return badorm.FieldCondition[overridereferences.Brand, badorm.UUID]{ + FieldIdentifier: BrandIdField, + Operator: operator, + } +} + +var BrandCreatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: brandType, +} + +func BrandCreatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[overridereferences.Brand] { + return badorm.FieldCondition[overridereferences.Brand, time.Time]{ + FieldIdentifier: BrandCreatedAtField, + Operator: operator, + } +} + +var BrandUpdatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: brandType, +} + +func BrandUpdatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[overridereferences.Brand] { + return badorm.FieldCondition[overridereferences.Brand, time.Time]{ + FieldIdentifier: BrandUpdatedAtField, + Operator: operator, + } +} + +var BrandDeletedAtField = badorm.FieldIdentifier[gorm.DeletedAt]{ + Field: "DeletedAt", + ModelType: brandType, +} + +func BrandDeletedAt(operator badorm.Operator[gorm.DeletedAt]) badorm.WhereCondition[overridereferences.Brand] { + return badorm.FieldCondition[overridereferences.Brand, gorm.DeletedAt]{ + FieldIdentifier: BrandDeletedAtField, + Operator: operator, + } +} + +var BrandNameField = badorm.FieldIdentifier[string]{ + Field: "Name", + ModelType: brandType, +} + +func BrandName(operator badorm.Operator[string]) badorm.WhereCondition[overridereferences.Brand] { + return badorm.FieldCondition[overridereferences.Brand, string]{ + FieldIdentifier: BrandNameField, + Operator: operator, + } +} + +var BrandPreloadAttributes = badorm.NewPreloadCondition[overridereferences.Brand](BrandIdField, BrandCreatedAtField, BrandUpdatedAtField, BrandDeletedAtField, BrandNameField) diff --git a/tools/badctl/cmd/gen/conditions/tests/results/overridereferences_phone.go b/tools/badctl/cmd/gen/conditions/tests/results/overridereferences_phone.go new file mode 100644 index 00000000..57e4229b --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/results/overridereferences_phone.go @@ -0,0 +1,84 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package conditions + +import ( + badorm "github.com/ditrit/badaas/badorm" + overridereferences "github.com/ditrit/badaas/tools/badctl/cmd/gen/conditions/tests/overridereferences" + gorm "gorm.io/gorm" + "reflect" + "time" +) + +var phoneType = reflect.TypeOf(*new(overridereferences.Phone)) +var PhoneIdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "ID", + ModelType: phoneType, +} + +func PhoneId(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[overridereferences.Phone] { + return badorm.FieldCondition[overridereferences.Phone, badorm.UUID]{ + FieldIdentifier: PhoneIdField, + Operator: operator, + } +} + +var PhoneCreatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: phoneType, +} + +func PhoneCreatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[overridereferences.Phone] { + return badorm.FieldCondition[overridereferences.Phone, time.Time]{ + FieldIdentifier: PhoneCreatedAtField, + Operator: operator, + } +} + +var PhoneUpdatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: phoneType, +} + +func PhoneUpdatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[overridereferences.Phone] { + return badorm.FieldCondition[overridereferences.Phone, time.Time]{ + FieldIdentifier: PhoneUpdatedAtField, + Operator: operator, + } +} + +var PhoneDeletedAtField = badorm.FieldIdentifier[gorm.DeletedAt]{ + Field: "DeletedAt", + ModelType: phoneType, +} + +func PhoneDeletedAt(operator badorm.Operator[gorm.DeletedAt]) badorm.WhereCondition[overridereferences.Phone] { + return badorm.FieldCondition[overridereferences.Phone, gorm.DeletedAt]{ + FieldIdentifier: PhoneDeletedAtField, + Operator: operator, + } +} +func PhoneBrand(conditions ...badorm.Condition[overridereferences.Brand]) badorm.IJoinCondition[overridereferences.Phone] { + return badorm.JoinCondition[overridereferences.Phone, overridereferences.Brand]{ + Conditions: conditions, + RelationField: "Brand", + T1Field: "BrandName", + T1PreloadCondition: PhonePreloadAttributes, + T2Field: "Name", + } +} + +var PhonePreloadBrand = PhoneBrand(BrandPreloadAttributes) +var PhoneBrandNameField = badorm.FieldIdentifier[string]{ + Field: "BrandName", + ModelType: phoneType, +} + +func PhoneBrandName(operator badorm.Operator[string]) badorm.WhereCondition[overridereferences.Phone] { + return badorm.FieldCondition[overridereferences.Phone, string]{ + FieldIdentifier: PhoneBrandNameField, + Operator: operator, + } +} + +var PhonePreloadAttributes = badorm.NewPreloadCondition[overridereferences.Phone](PhoneIdField, PhoneCreatedAtField, PhoneUpdatedAtField, PhoneDeletedAtField, PhoneBrandNameField) +var PhonePreloadRelations = []badorm.Condition[overridereferences.Phone]{PhonePreloadBrand} diff --git a/tools/badctl/cmd/gen/conditions/tests/results/overridereferencesinverse_computer.go b/tools/badctl/cmd/gen/conditions/tests/results/overridereferencesinverse_computer.go new file mode 100644 index 00000000..b5b04a86 --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/results/overridereferencesinverse_computer.go @@ -0,0 +1,84 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package conditions + +import ( + badorm "github.com/ditrit/badaas/badorm" + overridereferencesinverse "github.com/ditrit/badaas/tools/badctl/cmd/gen/conditions/tests/overridereferencesinverse" + gorm "gorm.io/gorm" + "reflect" + "time" +) + +var computerType = reflect.TypeOf(*new(overridereferencesinverse.Computer)) +var ComputerIdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "ID", + ModelType: computerType, +} + +func ComputerId(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[overridereferencesinverse.Computer] { + return badorm.FieldCondition[overridereferencesinverse.Computer, badorm.UUID]{ + FieldIdentifier: ComputerIdField, + Operator: operator, + } +} + +var ComputerCreatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: computerType, +} + +func ComputerCreatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[overridereferencesinverse.Computer] { + return badorm.FieldCondition[overridereferencesinverse.Computer, time.Time]{ + FieldIdentifier: ComputerCreatedAtField, + Operator: operator, + } +} + +var ComputerUpdatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: computerType, +} + +func ComputerUpdatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[overridereferencesinverse.Computer] { + return badorm.FieldCondition[overridereferencesinverse.Computer, time.Time]{ + FieldIdentifier: ComputerUpdatedAtField, + Operator: operator, + } +} + +var ComputerDeletedAtField = badorm.FieldIdentifier[gorm.DeletedAt]{ + Field: "DeletedAt", + ModelType: computerType, +} + +func ComputerDeletedAt(operator badorm.Operator[gorm.DeletedAt]) badorm.WhereCondition[overridereferencesinverse.Computer] { + return badorm.FieldCondition[overridereferencesinverse.Computer, gorm.DeletedAt]{ + FieldIdentifier: ComputerDeletedAtField, + Operator: operator, + } +} + +var ComputerNameField = badorm.FieldIdentifier[string]{ + Field: "Name", + ModelType: computerType, +} + +func ComputerName(operator badorm.Operator[string]) badorm.WhereCondition[overridereferencesinverse.Computer] { + return badorm.FieldCondition[overridereferencesinverse.Computer, string]{ + FieldIdentifier: ComputerNameField, + Operator: operator, + } +} +func ComputerProcessor(conditions ...badorm.Condition[overridereferencesinverse.Processor]) badorm.IJoinCondition[overridereferencesinverse.Computer] { + return badorm.JoinCondition[overridereferencesinverse.Computer, overridereferencesinverse.Processor]{ + Conditions: conditions, + RelationField: "Processor", + T1Field: "Name", + T1PreloadCondition: ComputerPreloadAttributes, + T2Field: "ComputerName", + } +} + +var ComputerPreloadProcessor = ComputerProcessor(ProcessorPreloadAttributes) +var ComputerPreloadAttributes = badorm.NewPreloadCondition[overridereferencesinverse.Computer](ComputerIdField, ComputerCreatedAtField, ComputerUpdatedAtField, ComputerDeletedAtField, ComputerNameField) +var ComputerPreloadRelations = []badorm.Condition[overridereferencesinverse.Computer]{ComputerPreloadProcessor} diff --git a/tools/badctl/cmd/gen/conditions/tests/results/overridereferencesinverse_processor.go b/tools/badctl/cmd/gen/conditions/tests/results/overridereferencesinverse_processor.go new file mode 100644 index 00000000..6095d9d4 --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/results/overridereferencesinverse_processor.go @@ -0,0 +1,73 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package conditions + +import ( + badorm "github.com/ditrit/badaas/badorm" + overridereferencesinverse "github.com/ditrit/badaas/tools/badctl/cmd/gen/conditions/tests/overridereferencesinverse" + gorm "gorm.io/gorm" + "reflect" + "time" +) + +var processorType = reflect.TypeOf(*new(overridereferencesinverse.Processor)) +var ProcessorIdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "ID", + ModelType: processorType, +} + +func ProcessorId(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[overridereferencesinverse.Processor] { + return badorm.FieldCondition[overridereferencesinverse.Processor, badorm.UUID]{ + FieldIdentifier: ProcessorIdField, + Operator: operator, + } +} + +var ProcessorCreatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: processorType, +} + +func ProcessorCreatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[overridereferencesinverse.Processor] { + return badorm.FieldCondition[overridereferencesinverse.Processor, time.Time]{ + FieldIdentifier: ProcessorCreatedAtField, + Operator: operator, + } +} + +var ProcessorUpdatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: processorType, +} + +func ProcessorUpdatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[overridereferencesinverse.Processor] { + return badorm.FieldCondition[overridereferencesinverse.Processor, time.Time]{ + FieldIdentifier: ProcessorUpdatedAtField, + Operator: operator, + } +} + +var ProcessorDeletedAtField = badorm.FieldIdentifier[gorm.DeletedAt]{ + Field: "DeletedAt", + ModelType: processorType, +} + +func ProcessorDeletedAt(operator badorm.Operator[gorm.DeletedAt]) badorm.WhereCondition[overridereferencesinverse.Processor] { + return badorm.FieldCondition[overridereferencesinverse.Processor, gorm.DeletedAt]{ + FieldIdentifier: ProcessorDeletedAtField, + Operator: operator, + } +} + +var ProcessorComputerNameField = badorm.FieldIdentifier[string]{ + Field: "ComputerName", + ModelType: processorType, +} + +func ProcessorComputerName(operator badorm.Operator[string]) badorm.WhereCondition[overridereferencesinverse.Processor] { + return badorm.FieldCondition[overridereferencesinverse.Processor, string]{ + FieldIdentifier: ProcessorComputerNameField, + Operator: operator, + } +} + +var ProcessorPreloadAttributes = badorm.NewPreloadCondition[overridereferencesinverse.Processor](ProcessorIdField, ProcessorCreatedAtField, ProcessorUpdatedAtField, ProcessorDeletedAtField, ProcessorComputerNameField) diff --git a/tools/badctl/cmd/gen/conditions/tests/results/selfreferential.go b/tools/badctl/cmd/gen/conditions/tests/results/selfreferential.go new file mode 100644 index 00000000..754c0b08 --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/results/selfreferential.go @@ -0,0 +1,84 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package conditions + +import ( + badorm "github.com/ditrit/badaas/badorm" + selfreferential "github.com/ditrit/badaas/tools/badctl/cmd/gen/conditions/tests/selfreferential" + gorm "gorm.io/gorm" + "reflect" + "time" +) + +var employeeType = reflect.TypeOf(*new(selfreferential.Employee)) +var EmployeeIdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "ID", + ModelType: employeeType, +} + +func EmployeeId(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[selfreferential.Employee] { + return badorm.FieldCondition[selfreferential.Employee, badorm.UUID]{ + FieldIdentifier: EmployeeIdField, + Operator: operator, + } +} + +var EmployeeCreatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: employeeType, +} + +func EmployeeCreatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[selfreferential.Employee] { + return badorm.FieldCondition[selfreferential.Employee, time.Time]{ + FieldIdentifier: EmployeeCreatedAtField, + Operator: operator, + } +} + +var EmployeeUpdatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: employeeType, +} + +func EmployeeUpdatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[selfreferential.Employee] { + return badorm.FieldCondition[selfreferential.Employee, time.Time]{ + FieldIdentifier: EmployeeUpdatedAtField, + Operator: operator, + } +} + +var EmployeeDeletedAtField = badorm.FieldIdentifier[gorm.DeletedAt]{ + Field: "DeletedAt", + ModelType: employeeType, +} + +func EmployeeDeletedAt(operator badorm.Operator[gorm.DeletedAt]) badorm.WhereCondition[selfreferential.Employee] { + return badorm.FieldCondition[selfreferential.Employee, gorm.DeletedAt]{ + FieldIdentifier: EmployeeDeletedAtField, + Operator: operator, + } +} +func EmployeeBoss(conditions ...badorm.Condition[selfreferential.Employee]) badorm.IJoinCondition[selfreferential.Employee] { + return badorm.JoinCondition[selfreferential.Employee, selfreferential.Employee]{ + Conditions: conditions, + RelationField: "Boss", + T1Field: "BossID", + T1PreloadCondition: EmployeePreloadAttributes, + T2Field: "ID", + } +} + +var EmployeePreloadBoss = EmployeeBoss(EmployeePreloadAttributes) +var EmployeeBossIdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "BossID", + ModelType: employeeType, +} + +func EmployeeBossId(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[selfreferential.Employee] { + return badorm.FieldCondition[selfreferential.Employee, badorm.UUID]{ + FieldIdentifier: EmployeeBossIdField, + Operator: operator, + } +} + +var EmployeePreloadAttributes = badorm.NewPreloadCondition[selfreferential.Employee](EmployeeIdField, EmployeeCreatedAtField, EmployeeUpdatedAtField, EmployeeDeletedAtField, EmployeeBossIdField) +var EmployeePreloadRelations = []badorm.Condition[selfreferential.Employee]{EmployeePreloadBoss} diff --git a/tools/badctl/cmd/gen/conditions/tests/results/uintmodel.go b/tools/badctl/cmd/gen/conditions/tests/results/uintmodel.go new file mode 100644 index 00000000..d9fcb8a4 --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/results/uintmodel.go @@ -0,0 +1,61 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package conditions + +import ( + badorm "github.com/ditrit/badaas/badorm" + uintmodel "github.com/ditrit/badaas/tools/badctl/cmd/gen/conditions/tests/uintmodel" + gorm "gorm.io/gorm" + "reflect" + "time" +) + +var uintModelType = reflect.TypeOf(*new(uintmodel.UintModel)) +var UintModelIdField = badorm.FieldIdentifier[badorm.UIntID]{ + Field: "ID", + ModelType: uintModelType, +} + +func UintModelId(operator badorm.Operator[badorm.UIntID]) badorm.WhereCondition[uintmodel.UintModel] { + return badorm.FieldCondition[uintmodel.UintModel, badorm.UIntID]{ + FieldIdentifier: UintModelIdField, + Operator: operator, + } +} + +var UintModelCreatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: uintModelType, +} + +func UintModelCreatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[uintmodel.UintModel] { + return badorm.FieldCondition[uintmodel.UintModel, time.Time]{ + FieldIdentifier: UintModelCreatedAtField, + Operator: operator, + } +} + +var UintModelUpdatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: uintModelType, +} + +func UintModelUpdatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[uintmodel.UintModel] { + return badorm.FieldCondition[uintmodel.UintModel, time.Time]{ + FieldIdentifier: UintModelUpdatedAtField, + Operator: operator, + } +} + +var UintModelDeletedAtField = badorm.FieldIdentifier[gorm.DeletedAt]{ + Field: "DeletedAt", + ModelType: uintModelType, +} + +func UintModelDeletedAt(operator badorm.Operator[gorm.DeletedAt]) badorm.WhereCondition[uintmodel.UintModel] { + return badorm.FieldCondition[uintmodel.UintModel, gorm.DeletedAt]{ + FieldIdentifier: UintModelDeletedAtField, + Operator: operator, + } +} + +var UintModelPreloadAttributes = badorm.NewPreloadCondition[uintmodel.UintModel](UintModelIdField, UintModelCreatedAtField, UintModelUpdatedAtField, UintModelDeletedAtField) diff --git a/tools/badctl/cmd/gen/conditions/tests/results/uuidmodel.go b/tools/badctl/cmd/gen/conditions/tests/results/uuidmodel.go new file mode 100644 index 00000000..bb6f8132 --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/results/uuidmodel.go @@ -0,0 +1,61 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package conditions + +import ( + badorm "github.com/ditrit/badaas/badorm" + uuidmodel "github.com/ditrit/badaas/tools/badctl/cmd/gen/conditions/tests/uuidmodel" + gorm "gorm.io/gorm" + "reflect" + "time" +) + +var uuidModelType = reflect.TypeOf(*new(uuidmodel.UUIDModel)) +var UUIDModelIdField = badorm.FieldIdentifier[badorm.UUID]{ + Field: "ID", + ModelType: uuidModelType, +} + +func UUIDModelId(operator badorm.Operator[badorm.UUID]) badorm.WhereCondition[uuidmodel.UUIDModel] { + return badorm.FieldCondition[uuidmodel.UUIDModel, badorm.UUID]{ + FieldIdentifier: UUIDModelIdField, + Operator: operator, + } +} + +var UUIDModelCreatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: uuidModelType, +} + +func UUIDModelCreatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[uuidmodel.UUIDModel] { + return badorm.FieldCondition[uuidmodel.UUIDModel, time.Time]{ + FieldIdentifier: UUIDModelCreatedAtField, + Operator: operator, + } +} + +var UUIDModelUpdatedAtField = badorm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: uuidModelType, +} + +func UUIDModelUpdatedAt(operator badorm.Operator[time.Time]) badorm.WhereCondition[uuidmodel.UUIDModel] { + return badorm.FieldCondition[uuidmodel.UUIDModel, time.Time]{ + FieldIdentifier: UUIDModelUpdatedAtField, + Operator: operator, + } +} + +var UUIDModelDeletedAtField = badorm.FieldIdentifier[gorm.DeletedAt]{ + Field: "DeletedAt", + ModelType: uuidModelType, +} + +func UUIDModelDeletedAt(operator badorm.Operator[gorm.DeletedAt]) badorm.WhereCondition[uuidmodel.UUIDModel] { + return badorm.FieldCondition[uuidmodel.UUIDModel, gorm.DeletedAt]{ + FieldIdentifier: UUIDModelDeletedAtField, + Operator: operator, + } +} + +var UUIDModelPreloadAttributes = badorm.NewPreloadCondition[uuidmodel.UUIDModel](UUIDModelIdField, UUIDModelCreatedAtField, UUIDModelUpdatedAtField, UUIDModelDeletedAtField) diff --git a/tools/badctl/cmd/gen/conditions/tests/selfreferential/badorm_result.go b/tools/badctl/cmd/gen/conditions/tests/selfreferential/badorm_result.go new file mode 100644 index 00000000..2fe691bb --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/selfreferential/badorm_result.go @@ -0,0 +1,8 @@ +// Code generated by badctl v0.0.0, DO NOT EDIT. +package selfreferential + +import badorm "github.com/ditrit/badaas/badorm" + +func (m Employee) GetBoss() (*Employee, error) { + return badorm.VerifyPointerLoaded[Employee](m.BossID, m.Boss) +} diff --git a/tools/badctl/cmd/gen/conditions/tests/selfreferential/selfreferential.go b/tools/badctl/cmd/gen/conditions/tests/selfreferential/selfreferential.go new file mode 100644 index 00000000..d7422e77 --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/selfreferential/selfreferential.go @@ -0,0 +1,10 @@ +package selfreferential + +import "github.com/ditrit/badaas/badorm" + +type Employee struct { + badorm.UUIDModel + + Boss *Employee `gorm:"constraint:OnDelete:SET NULL;"` // Self-Referential Has One (Employee 0..* -> 0..1 Employee) + BossID *badorm.UUID +} diff --git a/tools/badctl/cmd/gen/conditions/tests/uintmodel/uintmodel.go b/tools/badctl/cmd/gen/conditions/tests/uintmodel/uintmodel.go new file mode 100644 index 00000000..088f6e08 --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/uintmodel/uintmodel.go @@ -0,0 +1,7 @@ +package uintmodel + +import "github.com/ditrit/badaas/badorm" + +type UintModel struct { + badorm.UIntModel +} diff --git a/tools/badctl/cmd/gen/conditions/tests/uuidmodel/uuidmodel.go b/tools/badctl/cmd/gen/conditions/tests/uuidmodel/uuidmodel.go new file mode 100644 index 00000000..d82ed6c4 --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/tests/uuidmodel/uuidmodel.go @@ -0,0 +1,7 @@ +package uuidmodel + +import "github.com/ditrit/badaas/badorm" + +type UUIDModel struct { + badorm.UUIDModel +} diff --git a/tools/badctl/cmd/gen/conditions/type.go b/tools/badctl/cmd/gen/conditions/type.go new file mode 100644 index 00000000..2668e058 --- /dev/null +++ b/tools/badctl/cmd/gen/conditions/type.go @@ -0,0 +1,120 @@ +package conditions + +import ( + "errors" + "fmt" + "go/types" + "regexp" + "strings" + + "github.com/elliotchance/pie/v2" + + "github.com/ditrit/badaas/utils" +) + +// badorm/baseModels.go +var badORMModels = []string{ + badORMPath + "." + uuidModel, + badORMPath + "." + uIntModel, +} + +var ErrFkNotInTypeFields = errors.New("fk not in type's fields") + +type Type struct { + types.Type +} + +// Get the name of the type depending of the internal type +func (t Type) Name() string { + switch typeTyped := t.Type.(type) { + case *types.Named: + return typeTyped.Obj().Name() + default: + return pie.Last(strings.Split(t.String(), ".")) + } +} + +// Get the package of the type depending of the internal type +func (t Type) Pkg() *types.Package { + switch typeTyped := t.Type.(type) { + case *types.Named: + return typeTyped.Obj().Pkg() + default: + return nil + } +} + +// Get the struct under type if it is a BaDORM model +// Returns error if the type is not a BaDORM model +func (t Type) BadORMModelStruct() (*types.Struct, error) { + structType, ok := t.Underlying().(*types.Struct) + if !ok || !isBadORMModel(structType) { + return nil, fmt.Errorf("type %s is not a BaDORM Model", t.String()) + } + + return structType, nil +} + +// Returns true if the type is a BaDORM model +func isBadORMModel(structType *types.Struct) bool { + for i := 0; i < structType.NumFields(); i++ { + field := structType.Field(i) + + if field.Embedded() && isBadORMBaseModel(field.Type().String()) { + return true + } + } + + return false +} + +func isBadORMBaseModel(fieldName string) bool { + return pie.Contains(badORMModels, fieldName) +} + +// Returns the fk field of the type to the "field"'s object +// (another field that references that object) +func (t Type) GetFK(field Field) (*Field, error) { + objectFields, err := getFields(t) + if err != nil { + return nil, err + } + + fk := utils.FindFirst(objectFields, func(otherField Field) bool { + return strings.EqualFold(otherField.Name, field.getFKAttribute()) + }) + + if fk == nil { + return nil, ErrFkNotInTypeFields + } + + return fk, nil +} + +var ( + scanMethod = regexp.MustCompile(`func \(\*.*\)\.Scan\([a-zA-Z0-9_-]* (interface\{\}|any)\) error$`) + valueMethod = regexp.MustCompile(`func \(.*\)\.Value\(\) \(database/sql/driver\.Value\, error\)$`) +) + +// Returns true if the type is a Gorm Custom type (https://gorm.io/docs/data_types.html) +func (t Type) IsGormCustomType() bool { + typeNamed, isNamedType := t.Type.(*types.Named) + if !isNamedType { + return false + } + + hasScanMethod := false + hasValueMethod := false + + for i := 0; i < typeNamed.NumMethods(); i++ { + methodSignature := typeNamed.Method(i).String() + + if !hasScanMethod && scanMethod.MatchString(methodSignature) { + hasScanMethod = true + } else if !hasValueMethod && valueMethod.MatchString(methodSignature) { + hasValueMethod = true + } + } + + return hasScanMethod && hasValueMethod +} diff --git a/tools/badctl/cmd/gen/config/badaas.yml b/tools/badctl/cmd/gen/config/badaas.yml new file mode 100644 index 00000000..aad03146 --- /dev/null +++ b/tools/badctl/cmd/gen/config/badaas.yml @@ -0,0 +1,27 @@ +database: + host: badaas-db + name: badaas_db + password: badaas + sslmode: disable + username: badaas + init: + retry: 10 + retryTime: 5 +server: + host: "" + port: 8000 + timeout: 15 + pagination: + page: + max: 100 +logger: + mode: prod + request: + template: "Receive {{method}} request on {{url}}" +session: + duration: 14400 + pullInterval: 30 + rollDuration: 3600 +default: + admin: + password: admin \ No newline at end of file diff --git a/tools/badctl/cmd/gen/docker.go b/tools/badctl/cmd/gen/docker.go new file mode 100644 index 00000000..1bfc4c72 --- /dev/null +++ b/tools/badctl/cmd/gen/docker.go @@ -0,0 +1,191 @@ +package gen + +import ( + "embed" + "errors" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + "gopkg.in/yaml.v3" + + "github.com/ditrit/badaas/configuration" + "github.com/ditrit/badaas/tools/badctl/cmd/cmderrors" + "github.com/ditrit/verdeter" + "github.com/ditrit/verdeter/validators" +) + +//go:embed docker/* +//go:embed config/* +var genEmbedFS embed.FS + +var genDockerCmd = verdeter.BuildVerdeterCommand(verdeter.VerdeterConfig{ + Use: "docker", + Short: "Generate files and configurations necessary to use BadAss over Docker", + Long: `gen is the command you can use to generate the files and configurations necessary for your project to use BadAss in a simple way.`, + Run: GenerateDockerFiles, +}) + +const destBadaasDir = "badaas" + +const ( + DBProviderKey = "db_provider" + Cockroachdb = "cockroachdb" + Postgres = "postgres" + MySQL = "mysql" +) + +var DBProviders = []string{Cockroachdb, Postgres, MySQL} + +const ( + CockroachdbDefaultPort = 26257 + PostgresDefaultPort = 5432 + MySQLDefaultPort = 3306 +) + +var DBPorts = map[string]int{ + Cockroachdb: CockroachdbDefaultPort, + Postgres: PostgresDefaultPort, + MySQL: MySQLDefaultPort, +} + +var DBDialectors = map[string]configuration.DBDialector{ + Cockroachdb: configuration.PostgreSQL, + Postgres: configuration.PostgreSQL, + MySQL: configuration.MySQL, +} + +const FilePermissions = 0o0600 + +func init() { + err := genDockerCmd.LKey( + DBProviderKey, verdeter.IsStr, "p", + fmt.Sprintf( + "Database provider (%s), default: %s", + strings.Join(DBProviders, "|"), + Cockroachdb, + ), + ) + if err != nil { + cmderrors.FailErr(err) + } + + genDockerCmd.SetDefault(DBProviderKey, Cockroachdb) + genDockerCmd.AddValidator( + DBProviderKey, + validators.AuthorizedValues(DBProviders...), + ) +} + +func GenerateDockerFiles(_ *cobra.Command, _ []string) { + sourceDockerDir := "docker" + destDockerDir := filepath.Join(destBadaasDir, "docker") + + copyDir( + filepath.Join(sourceDockerDir, "api"), + filepath.Join(destDockerDir, "api"), + ) + + dbProvider := viper.GetString(DBProviderKey) + copyDir( + filepath.Join(sourceDockerDir, dbProvider), + filepath.Join(destDockerDir, "db"), + ) + + copyFile( + filepath.Join(sourceDockerDir, ".dockerignore"), + ".dockerignore", + ) + + copyFile( + filepath.Join(sourceDockerDir, "Makefile"), + "Makefile", + ) + + copyBadaasConfig(dbProvider) +} + +func copyBadaasConfig(dbProvider string) { + configFile, err := genEmbedFS.ReadFile( + filepath.Join("config", "badaas.yml"), + ) + if err != nil { + cmderrors.FailErr(err) + } + + configData := map[string]any{} + + err = yaml.Unmarshal(configFile, &configData) + if err != nil { + cmderrors.FailErr(err) + } + + databaseConfigMap, ok := configData["database"].(map[string]any) + if !ok { + cmderrors.FailErr(errors.New("database configuration is not a map")) + } + + databaseConfigMap["port"] = DBPorts[dbProvider] + databaseConfigMap["dialector"] = string(DBDialectors[dbProvider]) + configData["database"] = databaseConfigMap + + configBytes, err := yaml.Marshal(&configData) + if err != nil { + cmderrors.FailErr(err) + } + + destConfigDir := filepath.Join(destBadaasDir, "config") + + err = os.MkdirAll(destConfigDir, os.ModePerm) + if err != nil { + cmderrors.FailErr(err) + } + + err = os.WriteFile( + filepath.Join(destConfigDir, "badaas.yml"), + configBytes, FilePermissions, + ) + if err != nil { + cmderrors.FailErr(err) + } +} + +func copyFile(sourcePath, destPath string) { + fileContent, err := genEmbedFS.ReadFile(sourcePath) + if err != nil { + cmderrors.FailErr(err) + } + + if err := os.WriteFile(destPath, fileContent, FilePermissions); err != nil { + cmderrors.FailErr(err) + } +} + +func copyDir(sourceDir, destDir string) { + files, err := genEmbedFS.ReadDir(sourceDir) + if err != nil { + cmderrors.FailErr(err) + } + + _, err = os.Stat(destDir) + if err != nil { + if !os.IsNotExist(err) { + cmderrors.FailErr(err) + } + + err = os.MkdirAll(destDir, os.ModePerm) + if err != nil { + cmderrors.FailErr(err) + } + } + + for _, file := range files { + copyFile( + filepath.Join(sourceDir, file.Name()), + filepath.Join(destDir, file.Name()), + ) + } +} diff --git a/tools/badctl/cmd/gen/docker/.dockerignore b/tools/badctl/cmd/gen/docker/.dockerignore new file mode 100644 index 00000000..f7db6083 --- /dev/null +++ b/tools/badctl/cmd/gen/docker/.dockerignore @@ -0,0 +1,11 @@ +# general +.editorconfig +.git +.gitignore +.github +*.md +LICENSE +.vscode + +# badaas +badaas/docker diff --git a/tools/badctl/cmd/gen/docker/Makefile b/tools/badctl/cmd/gen/docker/Makefile new file mode 100644 index 00000000..634c8ef6 --- /dev/null +++ b/tools/badctl/cmd/gen/docker/Makefile @@ -0,0 +1,2 @@ +badaas_run: + docker compose -f badaas/docker/db/docker-compose.yml -f badaas/docker/api/docker-compose.yml up --build -d \ No newline at end of file diff --git a/tools/badctl/cmd/gen/docker/api/Dockerfile b/tools/badctl/cmd/gen/docker/api/Dockerfile new file mode 100644 index 00000000..1ea62ae9 --- /dev/null +++ b/tools/badctl/cmd/gen/docker/api/Dockerfile @@ -0,0 +1,17 @@ +# builder image +FROM golang:1.19-alpine AS builder +RUN apk add build-base +WORKDIR /app +COPY . . +RUN CGO_ENABLED=1 go build -o badaas_service -a . + + +# final image for end users +FROM alpine:3.16.2 +RUN addgroup -S badaas \ + && adduser -S badaas -G badaas +USER badaas +COPY --from=builder /app/badaas_service . +COPY ./badaas/config/badaas.yml . +EXPOSE 8000 +ENTRYPOINT ["./badaas_service", "--config_path", "badaas.yml"] \ No newline at end of file diff --git a/tools/badctl/cmd/gen/docker/api/docker-compose.yml b/tools/badctl/cmd/gen/docker/api/docker-compose.yml new file mode 100644 index 00000000..6fc11871 --- /dev/null +++ b/tools/badctl/cmd/gen/docker/api/docker-compose.yml @@ -0,0 +1,13 @@ +version: '3.5' + +services: + api: + image: badaas-api:latest + build: + context: ../../.. + dockerfile: ./badaas/docker/api/Dockerfile + container_name: "badaas-api" + ports: + - "8000:8000" + depends_on: + - db diff --git a/tools/badctl/cmd/gen/docker/cockroachdb/docker-compose.yml b/tools/badctl/cmd/gen/docker/cockroachdb/docker-compose.yml new file mode 100644 index 00000000..c97e448a --- /dev/null +++ b/tools/badctl/cmd/gen/docker/cockroachdb/docker-compose.yml @@ -0,0 +1,14 @@ +version: '3.5' + +services: + db: + image: cockroachdb/cockroach:latest + container_name: badaas-db + ports: + - "8080:8080" # Web based dashboard + # TODO make this container secure + command: start-single-node --insecure + volumes: + - "${PWD}/badaas/docker/db/.cockroach-data:/cockroach/cockroach-data" + environment: + - COCKROACH_DATABASE=badaas_db diff --git a/tools/badctl/cmd/gen/docker/mysql/docker-compose.yml b/tools/badctl/cmd/gen/docker/mysql/docker-compose.yml new file mode 100644 index 00000000..62dbef89 --- /dev/null +++ b/tools/badctl/cmd/gen/docker/mysql/docker-compose.yml @@ -0,0 +1,13 @@ +version: '3.5' + +services: + db: + image: mysql:latest + container_name: badaas-db + environment: + MYSQL_DATABASE: 'badaas_db' + MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-badaas} + MYSQL_USER: ${MYSQL_USER:-badaas} + MYSQL_PASSWORD: ${MYSQL_PASSWORD:-badaas} + volumes: + - "${PWD}/badaas/docker/db/.mysql-data:/var/lib/mysql" \ No newline at end of file diff --git a/tools/badctl/cmd/gen/docker/postgres/docker-compose.yml b/tools/badctl/cmd/gen/docker/postgres/docker-compose.yml new file mode 100644 index 00000000..c1d04315 --- /dev/null +++ b/tools/badctl/cmd/gen/docker/postgres/docker-compose.yml @@ -0,0 +1,14 @@ +version: '3.5' + +services: + db: + image: postgres:latest + container_name: badaas-db + environment: + POSTGRES_USER: ${POSTGRES_USER:-badaas} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-badaas} + POSTGRES_DB: badaas_db + PGDATA: /data/postgres + volumes: + - .:/docker-entrypoint-initdb.d/ + - "${PWD}/badaas/docker/db/.postgres-data:/data/postgres" \ No newline at end of file diff --git a/tools/badctl/cmd/gen/docker/postgres/init.sql b/tools/badctl/cmd/gen/docker/postgres/init.sql new file mode 100644 index 00000000..682131d3 --- /dev/null +++ b/tools/badctl/cmd/gen/docker/postgres/init.sql @@ -0,0 +1 @@ +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; \ No newline at end of file diff --git a/tools/badctl/cmd/gen/docker_test.go b/tools/badctl/cmd/gen/docker_test.go new file mode 100644 index 00000000..497b0eb3 --- /dev/null +++ b/tools/badctl/cmd/gen/docker_test.go @@ -0,0 +1,66 @@ +package gen + +import ( + "log" + "os" + "testing" + + "github.com/magiconair/properties/assert" + "github.com/spf13/viper" + "gopkg.in/yaml.v3" + + "github.com/ditrit/badaas/tools/badctl/cmd/testutils" +) + +func TestGenCockroach(t *testing.T) { + GenerateDockerFiles(nil, nil) + checkFilesExist(t) + checkDBPort(t, 26257) + teardown() +} + +func TestGenPostgres(t *testing.T) { + viper.Set(DBProviderKey, Postgres) + GenerateDockerFiles(nil, nil) + checkFilesExist(t) + testutils.CheckFileExists(t, "badaas/docker/db/init.sql") + checkDBPort(t, 5432) + + teardown() +} + +func checkFilesExist(t *testing.T) { + testutils.CheckFileExists(t, ".dockerignore") + testutils.CheckFileExists(t, "Makefile") + testutils.CheckFileExists(t, "badaas/config/badaas.yml") + testutils.CheckFileExists(t, "badaas/docker/api/docker-compose.yml") + testutils.CheckFileExists(t, "badaas/docker/api/Dockerfile") + testutils.CheckFileExists(t, "badaas/docker/db/docker-compose.yml") +} + +func checkDBPort(t *testing.T, port int) { + yamlFile, err := os.ReadFile("badaas/config/badaas.yml") + if err != nil { + t.Error(err) + } + + configData := map[string]any{} + + err = yaml.Unmarshal(yamlFile, &configData) + if err != nil { + t.Error(err) + } + + databaseConfigMap, ok := configData["database"].(map[string]any) + if !ok { + log.Fatalln("Database configuration is not a map") + } + + assert.Equal(t, databaseConfigMap["port"], port) +} + +func teardown() { + testutils.RemoveFile(".dockerignore") + testutils.RemoveFile("Makefile") + testutils.RemoveFile("badaas") +} diff --git a/tools/badctl/cmd/gen/gen.go b/tools/badctl/cmd/gen/gen.go new file mode 100644 index 00000000..7d0ffd3a --- /dev/null +++ b/tools/badctl/cmd/gen/gen.go @@ -0,0 +1,17 @@ +package gen + +import ( + "github.com/ditrit/badaas/tools/badctl/cmd/gen/conditions" + "github.com/ditrit/verdeter" +) + +var GenCmd = verdeter.BuildVerdeterCommand(verdeter.VerdeterConfig{ + Use: "gen", + Short: "Files and configurations generator", + Long: `gen is the command you can use to generate the files and configurations necessary for your project to use BadAss in a simple way.`, +}) + +func init() { + GenCmd.AddSubCommand(genDockerCmd) + GenCmd.AddSubCommand(conditions.GenConditionsCmd) +} diff --git a/tools/badctl/cmd/log/config.go b/tools/badctl/cmd/log/config.go new file mode 100644 index 00000000..de769a93 --- /dev/null +++ b/tools/badctl/cmd/log/config.go @@ -0,0 +1,24 @@ +package log + +import ( + log "github.com/sirupsen/logrus" + "github.com/spf13/viper" + + "github.com/ditrit/badaas/tools/badctl/cmd/version" +) + +var Logger = log.WithField("version", version.Version) + +const VerboseKey = "verbose" + +func init() { + log.SetFormatter(&log.TextFormatter{}) + log.SetLevel(log.InfoLevel) +} + +func SetLevel() { + verbose := viper.GetBool(VerboseKey) + if verbose { + log.SetLevel(log.DebugLevel) + } +} diff --git a/tools/badctl/cmd/root.go b/tools/badctl/cmd/root.go new file mode 100644 index 00000000..317d432d --- /dev/null +++ b/tools/badctl/cmd/root.go @@ -0,0 +1,37 @@ +package cmd + +import ( + "github.com/ditrit/badaas/tools/badctl/cmd/cmderrors" + "github.com/ditrit/badaas/tools/badctl/cmd/gen" + "github.com/ditrit/badaas/tools/badctl/cmd/log" + "github.com/ditrit/badaas/tools/badctl/cmd/version" + "github.com/ditrit/verdeter" +) + +// rootCmd represents the base command when called without any subcommands +var rootCmd = verdeter.BuildVerdeterCommand(verdeter.VerdeterConfig{ + Use: "badctl", + Short: "the BadAas controller", + Long: `badctl is the command line tool that makes it possible to configure and run your BadAas applications easily.`, + Version: version.Version, +}) + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + rootCmd.Execute() +} + +func init() { + rootCmd.AddSubCommand(gen.GenCmd) + + err := rootCmd.GKey( + log.VerboseKey, verdeter.IsBool, "v", + "Verbose logging", + ) + if err != nil { + cmderrors.FailErr(err) + } + + rootCmd.SetDefault(log.VerboseKey, false) +} diff --git a/tools/badctl/cmd/testutils/file.go b/tools/badctl/cmd/testutils/file.go new file mode 100644 index 00000000..a0c8c5d9 --- /dev/null +++ b/tools/badctl/cmd/testutils/file.go @@ -0,0 +1,30 @@ +package testutils + +import ( + "io/fs" + "log" + "os" + "testing" +) + +func CheckFileNotExists(t *testing.T, name string) { + _, err := os.Stat(name) + if err == nil { + t.Error(err, "Should have been an error") + } +} + +func CheckFileExists(t *testing.T, name string) fs.FileInfo { + stat, err := os.Stat(name) + if err != nil { + t.Error(err) + } + + return stat +} + +func RemoveFile(name string) { + if err := os.RemoveAll(name); err != nil { + log.Fatal(err) + } +} diff --git a/tools/badctl/cmd/version/version.go b/tools/badctl/cmd/version/version.go new file mode 100644 index 00000000..415100dc --- /dev/null +++ b/tools/badctl/cmd/version/version.go @@ -0,0 +1,3 @@ +package version + +var Version = "0.0.0" diff --git a/tools/badctl/go.mod b/tools/badctl/go.mod new file mode 100644 index 00000000..8170907d --- /dev/null +++ b/tools/badctl/go.mod @@ -0,0 +1,59 @@ +module github.com/ditrit/badaas/tools/badctl + +go 1.18 + +// replace github.com/ditrit/badaas => ../.. + +require ( + github.com/dave/jennifer v1.6.1 + github.com/ditrit/badaas v0.0.0-20230607151244-ded438ad025c + github.com/ditrit/verdeter v0.4.0 + github.com/elliotchance/pie/v2 v2.5.2 + github.com/ettle/strcase v0.1.1 + github.com/fatih/structtag v1.2.0 + github.com/magiconair/properties v1.8.7 + github.com/sirupsen/logrus v1.9.2 + github.com/spf13/cobra v1.7.0 + github.com/spf13/viper v1.16.0 + golang.org/x/tools v0.6.0 + gopkg.in/yaml.v3 v3.0.1 + gorm.io/gorm v1.25.1 + gotest.tools v2.2.0+incompatible + mvdan.cc/gofumpt v0.5.0 +) + +require ( + github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/go-sql-driver/mysql v1.7.0 // indirect + github.com/google/go-cmp v0.5.9 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgx/v5 v5.3.1 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/pelletier/go-toml/v2 v2.0.8 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/spf13/afero v1.9.5 // indirect + github.com/spf13/cast v1.5.1 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/subosito/gotenv v1.4.2 // indirect + go.uber.org/atomic v1.11.0 // indirect + go.uber.org/dig v1.17.0 // indirect + go.uber.org/fx v1.19.3 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.24.0 // indirect + golang.org/x/crypto v0.9.0 // indirect + golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect + golang.org/x/mod v0.8.0 // indirect + golang.org/x/sys v0.8.0 // indirect + golang.org/x/text v0.9.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gorm.io/driver/mysql v1.5.1 // indirect + gorm.io/driver/postgres v1.5.2 // indirect +) diff --git a/tools/badctl/go.sum b/tools/badctl/go.sum new file mode 100644 index 00000000..5e3ac43f --- /dev/null +++ b/tools/badctl/go.sum @@ -0,0 +1,548 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/dave/jennifer v1.6.1 h1:T4T/67t6RAA5AIV6+NP8Uk/BIsXgDoqEowgycdQQLuk= +github.com/dave/jennifer v1.6.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/ditrit/verdeter v0.4.0 h1:DzEOFauuXEGNQYP6OgYtHwEyb3w9riem99u0xE/l7+o= +github.com/ditrit/verdeter v0.4.0/go.mod h1:sKpWuOvYqNabLN4aNXqeBhcWpt7nf0frwqk0B5M6ax0= +github.com/elliotchance/pie/v2 v2.5.2 h1:jRENMmysCljhUmyT8ITKV0Atp6Lukm3XpeqaI87POsM= +github.com/elliotchance/pie/v2 v2.5.2/go.mod h1:18t0dgGFH006g4eVdDtWfgFZPQEgl10IoEO8YWEq3Og= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/ettle/strcase v0.1.1 h1:htFueZyVeE1XNnMEfbqp5r67qAN/4r6ya1ysq8Q+Zcw= +github.com/ettle/strcase v0.1.1/go.mod h1:hzDLsPC7/lwKyBOywSHEP89nt2pDgdy+No1NBA9o9VY= +github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= +github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= +github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.3.1 h1:Fcr8QJ1ZeLi5zsPZqQeUZhNhxfkkKBOgJuYkJHoBOtU= +github.com/jackc/pgx/v5 v5.3.1/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= +github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y= +github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= +github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= +github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= +github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= +github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= +github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/dig v1.17.0 h1:5Chju+tUvcC+N7N6EV08BJz41UZuO3BmHcN4A287ZLI= +go.uber.org/dig v1.17.0/go.mod h1:rTxpf7l5I0eBTlE6/9RL+lDybC7WFwY2QH55ZSjy1mU= +go.uber.org/fx v1.19.3 h1:YqMRE4+2IepTYCMOvXqQpRa+QAVdiSTnsHU4XNWBceA= +go.uber.org/fx v1.19.3/go.mod h1:w2HrQg26ql9fLK7hlBiZ6JsRUKV+Lj/atT1KCjT8YhM= +go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= +go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc= +golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/mysql v1.5.1 h1:WUEH5VF9obL/lTtzjmML/5e6VfFR/788coz2uaVCAZw= +gorm.io/driver/mysql v1.5.1/go.mod h1:Jo3Xu7mMhCyj8dlrb3WoCaRd1FhsVh+yMXb1jUInf5o= +gorm.io/driver/postgres v1.5.2 h1:ytTDxxEv+MplXOfFe3Lzm7SjG09fcdb3Z/c056DTBx0= +gorm.io/driver/postgres v1.5.2/go.mod h1:fmpX0m2I1PKuR7mKZiEluwrP3hbs+ps7JIGMUBpCgl8= +gorm.io/gorm v1.25.1 h1:nsSALe5Pr+cM3V1qwwQ7rOkw+6UeLrX5O4v3llhHa64= +gorm.io/gorm v1.25.1/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/tools/badctl/main.go b/tools/badctl/main.go new file mode 100644 index 00000000..7d838876 --- /dev/null +++ b/tools/badctl/main.go @@ -0,0 +1,7 @@ +package main + +import "github.com/ditrit/badaas/tools/badctl/cmd" + +func main() { + cmd.Execute() +} diff --git a/utils/math.go b/utils/math.go new file mode 100644 index 00000000..80ebfd8a --- /dev/null +++ b/utils/math.go @@ -0,0 +1,8 @@ +package utils + +import "math" + +// if v=6.36 return false if v=6.000 return true +func IsAnInt(v float64) bool { + return math.Mod(v, 1) == 0 +} diff --git a/utils/math_test.go b/utils/math_test.go new file mode 100644 index 00000000..08a7e9c6 --- /dev/null +++ b/utils/math_test.go @@ -0,0 +1,23 @@ +package utils_test + +import ( + "testing" + + "github.com/ditrit/badaas/utils" +) + +func TestIsAnINT(t *testing.T) { + data := map[float64]bool{ + 6.32: false, + -45.3: false, + -45.0: true, + 0.0: true, + 1.0000001: false, + } + for k, v := range data { + res := utils.IsAnInt(k) + if res != v { + t.Errorf("expected %v got %v for %f", v, res, k) + } + } +} diff --git a/utils/slice.go b/utils/slice.go new file mode 100644 index 00000000..2b370951 --- /dev/null +++ b/utils/slice.go @@ -0,0 +1,15 @@ +package utils + +import ( + "github.com/elliotchance/pie/v2" +) + +func FindFirst[T any](ss []T, fn func(value T) bool) *T { + index := pie.FindFirstUsing(ss, fn) + + if index == -1 { + return nil + } + + return &ss[index] +} diff --git a/utils/slice_test.go b/utils/slice_test.go new file mode 100644 index 00000000..df05c090 --- /dev/null +++ b/utils/slice_test.go @@ -0,0 +1,59 @@ +package utils_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/ditrit/badaas/utils" +) + +var ( + testResult3 = 33.04 + testResult4 = 0.11 +) + +var findFirstTests = []struct { + ss []float64 + expression func(value float64) bool + expected *float64 +}{ + { + nil, + func(value float64) bool { return value == 1.5 }, + nil, + }, + { + []float64{}, + func(value float64) bool { return value == 0.1 }, + nil, + }, + { + []float64{0.0, 1.5, 3.2}, + func(value float64) bool { return value == 9.99 }, + nil, + }, + { + []float64{5.4, 6.98, 4.987, 33.04}, + func(value float64) bool { return value == 33.04 }, + &testResult3, + }, + { + []float64{9.0, 0.11, 150.44, 33.04}, + func(value float64) bool { return value == 0.11 }, + &testResult4, + }, +} + +func TestFindFirst(t *testing.T) { + for _, test := range findFirstTests { + t.Run("", func(t *testing.T) { + result := utils.FindFirst(test.ss, test.expression) + if result == nil { + assert.Nil(t, test.expected) + } else { + assert.Equal(t, *test.expected, *result) + } + }) + } +} diff --git a/configuration/time.go b/utils/time.go similarity index 60% rename from configuration/time.go rename to utils/time.go index f71ef280..b075cbb1 100644 --- a/configuration/time.go +++ b/utils/time.go @@ -1,8 +1,8 @@ -package configuration +package utils import "time" // Convert int (seconds) to [time.Duration] -func intToSecond(numberOfSeconds int) time.Duration { +func IntToSecond(numberOfSeconds int) time.Duration { return time.Duration(numberOfSeconds) * time.Second } diff --git a/configuration/time_test.go b/utils/time_test.go similarity index 62% rename from configuration/time_test.go rename to utils/time_test.go index 56134aab..6261ca46 100644 --- a/configuration/time_test.go +++ b/utils/time_test.go @@ -1,4 +1,4 @@ -package configuration +package utils import ( "testing" @@ -10,20 +10,20 @@ import ( func TestIntToSecond(t *testing.T) { assert.Equal( t, - intToSecond(20), - time.Duration(20*time.Second), + IntToSecond(20), + 20*time.Second, "the duration should be equals", ) assert.Equal( t, - intToSecond(-5), - time.Duration(-5*time.Second), + IntToSecond(-5), + -5*time.Second, "the duration should be equals", ) assert.Equal( t, - intToSecond(3600), - time.Duration(time.Hour), + IntToSecond(3600), + time.Hour, "the duration should be equals", ) } diff --git a/validators/email.go b/utils/validators/email.go similarity index 92% rename from validators/email.go rename to utils/validators/email.go index 99d9d1e1..1a10ab72 100644 --- a/validators/email.go +++ b/utils/validators/email.go @@ -1,4 +1,4 @@ -package validator +package validators import "net/mail" @@ -8,5 +8,6 @@ func ValidEmail(email string) (string, error) { if err != nil { return "", err } + return addr.Address, nil } diff --git a/validators/email_test.go b/utils/validators/email_test.go similarity index 53% rename from validators/email_test.go rename to utils/validators/email_test.go index c41f0853..600741ed 100644 --- a/validators/email_test.go +++ b/utils/validators/email_test.go @@ -1,30 +1,31 @@ -package validator_test +package validators_test import ( "testing" - validator "github.com/ditrit/badaas/validators" "github.com/stretchr/testify/assert" + + "github.com/ditrit/badaas/utils/validators" ) func TestValidEmail(t *testing.T) { - mail, err := validator.ValidEmail("bob.bobemail.com") + mail, err := validators.ValidEmail("bob.bobemail.com") assert.Error(t, err) assert.Equal(t, "", mail) - mail, err = validator.ValidEmail("bob.bob@") + mail, err = validators.ValidEmail("bob.bob@") assert.Error(t, err) assert.Equal(t, "", mail) - mail, err = validator.ValidEmail("bob.bob@email.com") + mail, err = validators.ValidEmail("bob.bob@email.com") assert.NoError(t, err) assert.Equal(t, "bob.bob@email.com", mail) - mail, err = validator.ValidEmail("Gopher ") + mail, err = validators.ValidEmail("Gopher ") assert.NoError(t, err) assert.Equal(t, "from@example.com", mail) - mail, err = validator.ValidEmail("bob.bob%@email.com") + mail, err = validators.ValidEmail("bob.bob%@email.com") assert.NoError(t, err) assert.Equal(t, "bob.bob%@email.com", mail) }