From 4c3df487c134a29ebb48f0bdac9cc5cdacd8087c Mon Sep 17 00:00:00 2001 From: Keenan Nemetz Date: Mon, 2 Oct 2023 12:16:08 -0700 Subject: [PATCH] refactor: CLI client interface (#1839) ## Relevant issue(s) Closes #1472 Closes #1507 Closes #1860 ## Description This is a follow up to #1776 This PR adds a CLI implementation that implements the client.DB interface and runs through the existing integration test suite. - [x] Merge existing server config code - [x] Refactor CLI to use new HTTP client - [x] Remove `net/api` package - [x] Remove `api/http` package - [x] Lens tests are timing out in CI: fixed #1862 - [x] Code coverage is incorrectly reporting: fixed #1861 - [x] Flaky test causing failures: fixed #1912 Renamed Commands: - `peerid` to `client peer info` - `client p2pcollection` to `client p2p collection` - `client replicator` to `client p2p replicator` - `client schema list` to `client collection describe` Removed Commands: - `block get` - `ping` - `rpc` Added Commands: - `client collection create` - `client collection delete` - `client collection get` - `client collection keys` - `client collection update` - `client tx create` - `client tx discard` - `client tx commit` - `client schema migration up` - `client schema migration down` - `client schema migration reload` **Notes for reviewers**: - `.github` changes are merged from #1871 - `Makefile` most of these changes are also from #1871 - `docs/cli` ignore these changes, it will be updated next release - sorry for all of the merge commits, I am working on learning rebase flow ## Tasks - [x] I made sure the code is well commented, particularly hard-to-understand areas. - [x] I made sure the repository-held documentation is changed accordingly. - [x] I made sure the pull request title adheres to the conventional commit style (the subset used in the project can be found in [tools/configs/chglog/config.yml](tools/configs/chglog/config.yml)). - [x] I made sure to discuss its limitations such as threats to validity, vulnerability to mistake and misuse, robustness to invalidation of assumptions, resource requirements, ... ## How has this been tested? `make test` Specify the platform(s) on which this was tested: - MacOS --- .github/workflows/code-test-coverage.yml | 76 - .github/workflows/detect-change.yml | 55 - .../run-tests-and-upload-coverage.yml | 103 + .github/workflows/run-tests.yml | 50 - .github/workflows/test-collection-named.yml | 54 - .github/workflows/test-gql-mutations.yml | 48 - .gitignore | 1 + Makefile | 82 +- api/http/errors.go | 89 - api/http/errors_test.go | 169 -- api/http/handler.go | 139 - api/http/handler_test.go | 312 --- api/http/handlerfuncs.go | 475 ---- api/http/handlerfuncs_backup.go | 123 - api/http/handlerfuncs_backup_test.go | 623 ----- api/http/handlerfuncs_index.go | 144 - api/http/handlerfuncs_index_test.go | 239 -- api/http/handlerfuncs_test.go | 1184 --------- api/http/logger.go | 84 - api/http/logger_test.go | 124 - api/http/request_result.go | 31 - api/http/router.go | 83 - api/http/router_test.go | 50 - api/http/server.go | 322 --- cli/backup_export.go | 77 +- cli/backup_export_test.go | 300 --- cli/backup_import.go | 76 +- cli/backup_import_test.go | 129 - cli/blocks_get.go | 80 - cli/cli.go | 224 +- cli/cli_test.go | 59 - cli/client.go | 16 +- cli/collection.go | 77 + cli/collection_create.go | 102 + cli/collection_delete.go | 78 + cli/collection_describe.go | 57 + cli/collection_get.go | 53 + cli/collection_keys.go | 53 + cli/collection_update.go | 99 + cli/dump.go | 59 +- cli/errors.go | 131 +- cli/index_create.go | 92 +- cli/index_create_test.go | 244 -- cli/index_drop.go | 90 +- cli/index_drop_test.go | 121 - cli/index_list.go | 87 +- cli/index_list_test.go | 145 -- cli/{blocks.go => p2p.go} | 8 +- cli/p2p_collection.go | 2 +- cli/p2p_collection_add.go | 40 +- cli/p2p_collection_getall.go | 48 +- cli/p2p_collection_remove.go | 40 +- cli/p2p_info.go | 35 + cli/{replicator.go => p2p_replicator.go} | 2 +- cli/p2p_replicator_delete.go | 37 + cli/p2p_replicator_getall.go | 34 + cli/p2p_replicator_set.go | 47 + cli/peerid.go | 101 - cli/peerid_test.go | 100 - cli/ping.go | 79 - cli/replicator_delete.go | 81 - cli/replicator_getall.go | 82 - cli/replicator_set.go | 86 - cli/request.go | 115 +- cli/root.go | 25 +- cli/rpc.go | 36 - cli/schema_add.go | 130 +- cli/schema_list.go | 89 - cli/schema_migration_down.go | 91 + cli/schema_migration_get.go | 72 +- cli/schema_migration_reload.go | 35 + cli/schema_migration_set.go | 132 +- cli/schema_migration_up.go | 91 + cli/schema_patch.go | 125 +- cli/schema_set_default.go | 29 + cli/{serverdump.go => server_dump.go} | 0 cli/start.go | 37 +- api/http/http.go => cli/tx.go | 21 +- cli/tx_commit.go | 41 + cli/tx_create.go | 46 + cli/tx_discard.go | 42 + cli/utils.go | 112 + cli/version.go | 32 +- client/document.go | 20 + cmd/defradb/main.go | 12 +- cmd/genclidocs/{genclidocs.go => main.go} | 27 +- cmd/genmanpages/main.go | 37 +- docs/cli/defradb_client.md | 11 +- docs/cli/defradb_client_backup.md | 1 + docs/cli/defradb_client_backup_export.md | 1 + docs/cli/defradb_client_backup_import.md | 1 + docs/cli/defradb_client_collection.md | 52 + docs/cli/defradb_client_document.md | 38 + docs/cli/defradb_client_document_create.md | 44 + docs/cli/defradb_client_document_delete.md | 46 + docs/cli/defradb_client_document_get.md | 42 + docs/cli/defradb_client_document_keys.md | 41 + docs/cli/defradb_client_document_save.md | 42 + docs/cli/defradb_client_document_update.md | 52 + docs/cli/defradb_client_dump.md | 1 + docs/cli/defradb_client_index.md | 1 + docs/cli/defradb_client_index_create.md | 3 +- docs/cli/defradb_client_index_drop.md | 1 + docs/cli/defradb_client_index_list.md | 1 + ...db_client_rpc.md => defradb_client_p2p.md} | 14 +- ...on.md => defradb_client_p2p_collection.md} | 14 +- ...d => defradb_client_p2p_collection_add.md} | 8 +- ...> defradb_client_p2p_collection_getall.md} | 8 +- ...> defradb_client_p2p_collection_remove.md} | 8 +- ...or.md => defradb_client_p2p_replicator.md} | 12 +- ...> defradb_client_p2p_replicator_delete.md} | 15 +- ...> defradb_client_p2p_replicator_getall.md} | 8 +- ...d => defradb_client_p2p_replicator_set.md} | 13 +- docs/cli/defradb_client_query.md | 1 + docs/cli/defradb_client_rpc_addreplicator.md | 37 - .../defradb_client_rpc_replicator_delete.md | 38 - docs/cli/defradb_client_schema.md | 2 +- docs/cli/defradb_client_schema_add.md | 1 + docs/cli/defradb_client_schema_migration.md | 4 + .../defradb_client_schema_migration_down.md | 37 + .../defradb_client_schema_migration_get.md | 1 + ...defradb_client_schema_migration_reload.md} | 15 +- .../defradb_client_schema_migration_set.md | 1 + .../cli/defradb_client_schema_migration_up.md | 37 + docs/cli/defradb_client_schema_patch.md | 1 + ..._client_blocks.md => defradb_client_tx.md} | 15 +- ..._peerid.md => defradb_client_tx_commit.md} | 13 +- docs/cli/defradb_client_tx_create.md | 38 + ...ks_get.md => defradb_client_tx_discard.md} | 15 +- go.mod | 5 +- go.sum | 2 - http/client.go | 17 +- http/client_collection.go | 11 +- http/client_tx.go | 8 + http/errors.go | 36 +- http/handler.go | 138 + http/handler_ccip_test.go | 12 +- http/handler_collection.go | 9 +- http/handler_lens.go | 20 +- .../handler_playground.go | 0 http/handler_store.go | 12 + http/http_client.go | 14 +- http/middleware.go | 27 +- http/server.go | 397 ++- {api/http => http}/server_test.go | 24 +- http/utils.go | 19 - logging/registry.go | 3 + net/api/client/client.go | 169 -- net/api/pb/Makefile | 18 - net/api/pb/api.pb.go | 1100 -------- net/api/pb/api.proto | 82 - net/api/pb/api_grpc.pb.go | 300 --- net/api/pb/api_vtproto.pb.go | 2316 ----------------- tests/clients/cli/wrapper.go | 419 +++ tests/clients/cli/wrapper_cli.go | 85 + tests/clients/cli/wrapper_collection.go | 405 +++ tests/clients/cli/wrapper_lens.go | 145 ++ tests/clients/cli/wrapper_tx.go | 76 + {http => tests/clients/http}/wrapper.go | 30 +- {http => tests/clients/http}/wrapper_tx.go | 0 .../cli/client_backup_export_test.go | 118 - .../cli/client_backup_import_test.go | 109 - tests/integration/cli/client_blocks_test.go | 41 - .../cli/client_index_create_test.go | 102 - .../integration/cli/client_index_drop_test.go | 118 - .../integration/cli/client_index_list_test.go | 96 - tests/integration/cli/client_peerid_test.go | 34 - tests/integration/cli/client_ping_test.go | 63 - tests/integration/cli/client_query_test.go | 102 - .../cli/client_rpc_p2p_collection_test.go | 13 - .../cli/client_rpc_replicator_test.go | 35 - .../integration/cli/client_schema_add_test.go | 53 - .../cli/client_schema_migration_get_test.go | 110 - .../cli/client_schema_migration_set_test.go | 244 -- .../cli/client_schema_patch_test.go | 53 - tests/integration/cli/init_test.go | 51 - tests/integration/cli/log_config_test.go | 116 - tests/integration/cli/root_test.go | 43 - tests/integration/cli/serverdump_test.go | 28 - tests/integration/cli/start_test.go | 90 - tests/integration/cli/utils.go | 263 -- tests/integration/cli/version_test.go | 46 - tests/integration/results.go | 4 +- tests/integration/utils2.go | 21 +- version/version.go | 2 +- 185 files changed, 4000 insertions(+), 13916 deletions(-) delete mode 100644 .github/workflows/code-test-coverage.yml delete mode 100644 .github/workflows/detect-change.yml create mode 100644 .github/workflows/run-tests-and-upload-coverage.yml delete mode 100644 .github/workflows/run-tests.yml delete mode 100644 .github/workflows/test-collection-named.yml delete mode 100644 .github/workflows/test-gql-mutations.yml delete mode 100644 api/http/errors.go delete mode 100644 api/http/errors_test.go delete mode 100644 api/http/handler.go delete mode 100644 api/http/handler_test.go delete mode 100644 api/http/handlerfuncs.go delete mode 100644 api/http/handlerfuncs_backup.go delete mode 100644 api/http/handlerfuncs_backup_test.go delete mode 100644 api/http/handlerfuncs_index.go delete mode 100644 api/http/handlerfuncs_index_test.go delete mode 100644 api/http/handlerfuncs_test.go delete mode 100644 api/http/logger.go delete mode 100644 api/http/logger_test.go delete mode 100644 api/http/request_result.go delete mode 100644 api/http/router.go delete mode 100644 api/http/router_test.go delete mode 100644 api/http/server.go delete mode 100644 cli/backup_export_test.go delete mode 100644 cli/backup_import_test.go delete mode 100644 cli/blocks_get.go delete mode 100644 cli/cli_test.go create mode 100644 cli/collection.go create mode 100644 cli/collection_create.go create mode 100644 cli/collection_delete.go create mode 100644 cli/collection_describe.go create mode 100644 cli/collection_get.go create mode 100644 cli/collection_keys.go create mode 100644 cli/collection_update.go delete mode 100644 cli/index_create_test.go delete mode 100644 cli/index_drop_test.go delete mode 100644 cli/index_list_test.go rename cli/{blocks.go => p2p.go} (75%) create mode 100644 cli/p2p_info.go rename cli/{replicator.go => p2p_replicator.go} (93%) create mode 100644 cli/p2p_replicator_delete.go create mode 100644 cli/p2p_replicator_getall.go create mode 100644 cli/p2p_replicator_set.go delete mode 100644 cli/peerid.go delete mode 100644 cli/peerid_test.go delete mode 100644 cli/ping.go delete mode 100644 cli/replicator_delete.go delete mode 100644 cli/replicator_getall.go delete mode 100644 cli/replicator_set.go delete mode 100644 cli/rpc.go delete mode 100644 cli/schema_list.go create mode 100644 cli/schema_migration_down.go create mode 100644 cli/schema_migration_reload.go create mode 100644 cli/schema_migration_up.go create mode 100644 cli/schema_set_default.go rename cli/{serverdump.go => server_dump.go} (100%) rename api/http/http.go => cli/tx.go (51%) create mode 100644 cli/tx_commit.go create mode 100644 cli/tx_create.go create mode 100644 cli/tx_discard.go create mode 100644 cli/utils.go rename cmd/genclidocs/{genclidocs.go => main.go} (59%) create mode 100644 docs/cli/defradb_client_collection.md create mode 100644 docs/cli/defradb_client_document.md create mode 100644 docs/cli/defradb_client_document_create.md create mode 100644 docs/cli/defradb_client_document_delete.md create mode 100644 docs/cli/defradb_client_document_get.md create mode 100644 docs/cli/defradb_client_document_keys.md create mode 100644 docs/cli/defradb_client_document_save.md create mode 100644 docs/cli/defradb_client_document_update.md rename docs/cli/{defradb_client_rpc.md => defradb_client_p2p.md} (70%) rename docs/cli/{defradb_client_rpc_p2pcollection.md => defradb_client_p2p_collection.md} (62%) rename docs/cli/{defradb_client_rpc_p2pcollection_add.md => defradb_client_p2p_collection_add.md} (77%) rename docs/cli/{defradb_client_rpc_p2pcollection_getall.md => defradb_client_p2p_collection_getall.md} (78%) rename docs/cli/{defradb_client_rpc_p2pcollection_remove.md => defradb_client_p2p_collection_remove.md} (77%) rename docs/cli/{defradb_client_rpc_replicator.md => defradb_client_p2p_replicator.md} (75%) rename docs/cli/{defradb_client_ping.md => defradb_client_p2p_replicator_delete.md} (67%) rename docs/cli/{defradb_client_rpc_replicator_getall.md => defradb_client_p2p_replicator_getall.md} (82%) rename docs/cli/{defradb_client_rpc_replicator_set.md => defradb_client_p2p_replicator_set.md} (68%) delete mode 100644 docs/cli/defradb_client_rpc_addreplicator.md delete mode 100644 docs/cli/defradb_client_rpc_replicator_delete.md create mode 100644 docs/cli/defradb_client_schema_migration_down.md rename docs/cli/{defradb_client_schema_list.md => defradb_client_schema_migration_reload.md} (65%) create mode 100644 docs/cli/defradb_client_schema_migration_up.md rename docs/cli/{defradb_client_blocks.md => defradb_client_tx.md} (63%) rename docs/cli/{defradb_client_peerid.md => defradb_client_tx_commit.md} (73%) create mode 100644 docs/cli/defradb_client_tx_create.md rename docs/cli/{defradb_client_blocks_get.md => defradb_client_tx_discard.md} (71%) create mode 100644 http/handler.go rename api/http/playground.go => http/handler_playground.go (100%) rename {api/http => http}/server_test.go (92%) delete mode 100644 net/api/client/client.go delete mode 100644 net/api/pb/Makefile delete mode 100644 net/api/pb/api.pb.go delete mode 100644 net/api/pb/api.proto delete mode 100644 net/api/pb/api_grpc.pb.go delete mode 100644 net/api/pb/api_vtproto.pb.go create mode 100644 tests/clients/cli/wrapper.go create mode 100644 tests/clients/cli/wrapper_cli.go create mode 100644 tests/clients/cli/wrapper_collection.go create mode 100644 tests/clients/cli/wrapper_lens.go create mode 100644 tests/clients/cli/wrapper_tx.go rename {http => tests/clients/http}/wrapper.go (90%) rename {http => tests/clients/http}/wrapper_tx.go (100%) delete mode 100644 tests/integration/cli/client_backup_export_test.go delete mode 100644 tests/integration/cli/client_backup_import_test.go delete mode 100644 tests/integration/cli/client_blocks_test.go delete mode 100644 tests/integration/cli/client_index_create_test.go delete mode 100644 tests/integration/cli/client_index_drop_test.go delete mode 100644 tests/integration/cli/client_index_list_test.go delete mode 100644 tests/integration/cli/client_peerid_test.go delete mode 100644 tests/integration/cli/client_ping_test.go delete mode 100644 tests/integration/cli/client_query_test.go delete mode 100644 tests/integration/cli/client_rpc_p2p_collection_test.go delete mode 100644 tests/integration/cli/client_rpc_replicator_test.go delete mode 100644 tests/integration/cli/client_schema_add_test.go delete mode 100644 tests/integration/cli/client_schema_migration_get_test.go delete mode 100644 tests/integration/cli/client_schema_migration_set_test.go delete mode 100644 tests/integration/cli/client_schema_patch_test.go delete mode 100644 tests/integration/cli/init_test.go delete mode 100644 tests/integration/cli/log_config_test.go delete mode 100644 tests/integration/cli/root_test.go delete mode 100644 tests/integration/cli/serverdump_test.go delete mode 100644 tests/integration/cli/start_test.go delete mode 100644 tests/integration/cli/utils.go delete mode 100644 tests/integration/cli/version_test.go diff --git a/.github/workflows/code-test-coverage.yml b/.github/workflows/code-test-coverage.yml deleted file mode 100644 index 65c0a92f1f..0000000000 --- a/.github/workflows/code-test-coverage.yml +++ /dev/null @@ -1,76 +0,0 @@ -# Copyright 2022 Democratized Data Foundation -# -# Use of this software is governed by the Business Source License -# included in the file licenses/BSL.txt. -# -# As of the Change Date specified in that file, in accordance with -# the Business Source License, use of this software will be governed -# by the Apache License, Version 2.0, included in the file -# licenses/APL.txt. - -name: Code Test Coverage Workflow - -on: - pull_request: - branches: - - master - - develop - - push: - tags: - - 'v[0-9]+.[0-9]+.[0-9]+' - branches: - - master - - develop - -jobs: - code-test-coverage: - name: Code test coverage job - - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v3 - - - name: Setup Go environment explicitly - uses: actions/setup-go@v3 - with: - go-version: "1.20" - check-latest: true - - - name: Generate full test coverage report using go-acc - run: make test:coverage - - - name: Upload coverage to Codecov without token, retry on failure - env: - codecov_secret: ${{ secrets.CODECOV_TOKEN }} - if: env.codecov_secret == '' - uses: Wandalen/wretry.action@v1.0.36 - with: - attempt_limit: 5 - attempt_delay: 10000 - action: codecov/codecov-action@v3 - with: | - name: defradb-codecov - files: ./coverage.txt - flags: all-tests - os: 'linux' - fail_ci_if_error: true - verbose: true - - - name: Upload coverage to Codecov with token - env: - codecov_secret: ${{ secrets.CODECOV_TOKEN }} - if: env.codecov_secret != '' - uses: codecov/codecov-action@v3 - with: - token: ${{ env.codecov_secret }} - name: defradb-codecov - files: ./coverage.txt - flags: all-tests - os: 'linux' - fail_ci_if_error: true - verbose: true - # path_to_write_report: ./coverage/codecov_report.txt - # directory: ./coverage/reports/ diff --git a/.github/workflows/detect-change.yml b/.github/workflows/detect-change.yml deleted file mode 100644 index b6272c21cd..0000000000 --- a/.github/workflows/detect-change.yml +++ /dev/null @@ -1,55 +0,0 @@ -# Copyright 2022 Democratized Data Foundation -# -# Use of this software is governed by the Business Source License -# included in the file licenses/BSL.txt. -# -# As of the Change Date specified in that file, in accordance with -# the Business Source License, use of this software will be governed -# by the Apache License, Version 2.0, included in the file -# licenses/APL.txt. - -name: Detect Change Workflow - -on: - pull_request: - branches: - - master - - develop - - push: - tags: - - 'v[0-9]+.[0-9]+.[0-9]+' - branches: - - master - - develop - -jobs: - detect-change: - name: Detect change job - - runs-on: ubuntu-latest - - steps: - - name: Checkout code into the directory - uses: actions/checkout@v3 - - - name: Setup Go environment explicitly - uses: actions/setup-go@v3 - with: - go-version: "1.20" - check-latest: true - - - name: Build dependencies - run: | - make deps:modules - make deps:test - - - name: Run detection for changes - run: make test:changes - - ## Uncomment to enable ability to SSH into the runner. - #- name: Setup upterm ssh session for debugging - # uses: lhotari/action-upterm@v1 - # with: - # limit-access-to-actor: true - # limit-access-to-users: shahzadlone diff --git a/.github/workflows/run-tests-and-upload-coverage.yml b/.github/workflows/run-tests-and-upload-coverage.yml new file mode 100644 index 0000000000..f1f8724ced --- /dev/null +++ b/.github/workflows/run-tests-and-upload-coverage.yml @@ -0,0 +1,103 @@ +# Copyright 2022 Democratized Data Foundation +# +# Use of this software is governed by the Business Source License +# included in the file licenses/BSL.txt. +# +# As of the Change Date specified in that file, in accordance with +# the Business Source License, use of this software will be governed +# by the Apache License, Version 2.0, included in the file +# licenses/APL.txt. + +name: Run Tests And Upload Coverage Workflow + +on: + push: + tags: + - 'v[0-9]+.[0-9]+.[0-9]+' + branches: + - master + - develop + +jobs: + run-tests: + name: Run tests matrix job + + runs-on: ubuntu-latest + + strategy: + matrix: + client-type: [go, http, cli] + database-type: [badger-file, badger-memory] + mutation-type: [gql, collection-named, collection-save] + detect-changes: [false] + include: + - client-type: go + database-type: badger-memory + mutation-type: collection-save + detect-changes: true + + env: + DEFRA_CLIENT_GO: ${{ matrix.client-type == 'go' }} + DEFRA_CLIENT_HTTP: ${{ matrix.client-type == 'http' }} + DEFRA_CLIENT_CLI: ${{ matrix.client-type == 'cli' }} + DEFRA_BADGER_MEMORY: ${{ matrix.database-type == 'badger-memory' }} + DEFRA_BADGER_FILE: ${{ matrix.database-type == 'badger-file' }} + DEFRA_MUTATION_TYPE: ${{ matrix.mutation-type }} + + steps: + - name: Checkout code into the directory + uses: actions/checkout@v3 + + - name: Setup Go environment explicitly + uses: actions/setup-go@v3 + with: + go-version: "1.20" + check-latest: true + + - name: Build dependencies + run: | + make deps:modules + make deps:test + + - name: Run integration tests + if: ${{ !matrix.detect-changes }} + run: make test:coverage + + - name: Run change detector tests + if: ${{ matrix.detect-changes }} + run: make test:changes + + - name: Upload coverage artifact + if: ${{ !matrix.detect-changes }} + uses: actions/upload-artifact@v3 + with: + name: ${{ matrix.client-type }}_${{ matrix.database-type }}_${{ matrix.mutation-type }} + path: coverage.txt + if-no-files-found: error + retention-days: 1 + + upload-coverage: + name: Upload test code coverage job + + runs-on: ubuntu-latest + + needs: run-tests + + steps: + - name: Checkout code into the directory + uses: actions/checkout@v3 + + - name: Download coverage reports + uses: actions/download-artifact@v3 + with: + path: coverage_reports + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + token: ${{ secrets.CODECOV_TOKEN }} + name: defradb-codecov + flags: all-tests + os: 'linux' + fail_ci_if_error: true + verbose: true diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml deleted file mode 100644 index bfa696a283..0000000000 --- a/.github/workflows/run-tests.yml +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright 2022 Democratized Data Foundation -# -# Use of this software is governed by the Business Source License -# included in the file licenses/BSL.txt. -# -# As of the Change Date specified in that file, in accordance with -# the Business Source License, use of this software will be governed -# by the Apache License, Version 2.0, included in the file -# licenses/APL.txt. - -name: Run Tests Workflow - -on: - pull_request: - branches: - - master - - develop - - push: - -jobs: - run-tests: - name: Run tests job - - runs-on: ubuntu-latest - - steps: - - name: Checkout code into the directory - uses: actions/checkout@v3 - - - name: Setup Go environment explicitly - uses: actions/setup-go@v3 - with: - go-version: "1.20" - check-latest: true - - - name: Build dependencies - run: | - make deps:modules - make deps:test - - - name: Build binary - run: make build - - # This is to ensure tests pass with a running server. - - name: Start server from binary - run: ./build/defradb start & - - - name: Run the tests, showing name of each test - run: make test:ci diff --git a/.github/workflows/test-collection-named.yml b/.github/workflows/test-collection-named.yml deleted file mode 100644 index 5adabe4fdf..0000000000 --- a/.github/workflows/test-collection-named.yml +++ /dev/null @@ -1,54 +0,0 @@ -# Copyright 2023 Democratized Data Foundation -# -# Use of this software is governed by the Business Source License -# included in the file licenses/BSL.txt. -# -# As of the Change Date specified in that file, in accordance with -# the Business Source License, use of this software will be governed -# by the Apache License, Version 2.0, included in the file -# licenses/APL.txt. - -name: Run Collection Named Mutations Tests Workflow - -# This workflow runs the test suite with any supporting mutation test actions -# running their mutations via their corresponding named [Collection] call. -# -# For example, CreateDoc will call [Collection.Create], and -# UpdateDoc will call [Collection.Update]. - -on: - pull_request: - branches: - - master - - develop - - push: - tags: - - 'v[0-9]+.[0-9]+.[0-9]+' - branches: - - master - - develop - -jobs: - test-collection-named-mutations: - name: Test Collection Named Mutations job - - runs-on: ubuntu-latest - - steps: - - name: Checkout code into the directory - uses: actions/checkout@v3 - - - name: Setup Go environment explicitly - uses: actions/setup-go@v3 - with: - go-version: "1.20" - check-latest: true - - - name: Build dependencies - run: | - make deps:modules - make deps:test - - - name: Run tests with Collection Named mutations - run: make test:ci-col-named-mutations diff --git a/.github/workflows/test-gql-mutations.yml b/.github/workflows/test-gql-mutations.yml deleted file mode 100644 index 827dd22098..0000000000 --- a/.github/workflows/test-gql-mutations.yml +++ /dev/null @@ -1,48 +0,0 @@ -# Copyright 2022 Democratized Data Foundation -# -# Use of this software is governed by the Business Source License -# included in the file licenses/BSL.txt. -# -# As of the Change Date specified in that file, in accordance with -# the Business Source License, use of this software will be governed -# by the Apache License, Version 2.0, included in the file -# licenses/APL.txt. - -name: Run GQL Mutations Tests Workflow - -on: - pull_request: - branches: - - master - - develop - - push: - tags: - - 'v[0-9]+.[0-9]+.[0-9]+' - branches: - - master - - develop - -jobs: - test-gql-mutations: - name: Test GQL mutations job - - runs-on: ubuntu-latest - - steps: - - name: Checkout code into the directory - uses: actions/checkout@v3 - - - name: Setup Go environment explicitly - uses: actions/setup-go@v3 - with: - go-version: "1.20" - check-latest: true - - - name: Build dependencies - run: | - make deps:modules - make deps:test - - - name: Run tests with gql mutations - run: make test:ci-gql-mutations diff --git a/.gitignore b/.gitignore index b19a6d9259..81c1a16d62 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ cmd/defradb/defradb cmd/genclidocs/genclidocs cmd/genmanpages/genmanpages coverage.txt +coverage tests/bench/*.log tests/bench/*.svg diff --git a/Makefile b/Makefile index 60350a6046..7268834d5a 100644 --- a/Makefile +++ b/Makefile @@ -29,13 +29,16 @@ ifdef BUILD_TAGS BUILD_FLAGS+=-tags $(BUILD_TAGS) endif -TEST_FLAGS=-race -shuffle=on -timeout 300s +TEST_FLAGS=-race -shuffle=on -timeout 5m + +COVERAGE_DIRECTORY=$(PWD)/coverage +COVERAGE_FILE=coverage.txt +COVERAGE_FLAGS=-covermode=atomic -coverpkg=./... -args -test.gocoverdir=$(COVERAGE_DIRECTORY) PLAYGROUND_DIRECTORY=playground LENS_TEST_DIRECTORY=tests/integration/schema/migrations -CLI_TEST_DIRECTORY=tests/integration/cli CHANGE_DETECTOR_TEST_DIRECTORY=tests/change_detector -DEFAULT_TEST_DIRECTORIES=$$(go list ./... | grep -v -e $(LENS_TEST_DIRECTORY) -e $(CLI_TEST_DIRECTORY)) +DEFAULT_TEST_DIRECTORIES=$$(go list ./... | grep -v -e $(LENS_TEST_DIRECTORY)) default: @go run $(BUILD_FLAGS) cmd/defradb/main.go @@ -88,11 +91,6 @@ deps\:lens: rustup target add wasm32-unknown-unknown @$(MAKE) -C ./tests/lenses build -.PHONY: deps\:coverage -deps\:coverage: - go install github.com/ory/go-acc@latest - @$(MAKE) deps:lens - .PHONY: deps\:bench deps\:bench: go install golang.org/x/perf/cmd/benchstat@latest @@ -118,7 +116,6 @@ deps: @$(MAKE) deps:modules && \ $(MAKE) deps:bench && \ $(MAKE) deps:chglog && \ - $(MAKE) deps:coverage && \ $(MAKE) deps:lint && \ $(MAKE) deps:test && \ $(MAKE) deps:mock @@ -161,6 +158,11 @@ clean: clean\:test: go clean -testcache +.PHONY: clean\:coverage +clean\:coverage: + rm -rf $(COVERAGE_DIRECTORY) + rm -f $(COVERAGE_FILE) + # Example: `make tls-certs path="~/.defradb/certs"` .PHONY: tls-certs tls-certs: @@ -186,18 +188,6 @@ test\:quick: test\:build: gotestsum --format pkgname -- $(DEFAULT_TEST_DIRECTORIES) $(TEST_FLAGS) -run=nope -.PHONY: test\:ci -test\:ci: - DEFRA_BADGER_MEMORY=true DEFRA_BADGER_FILE=true \ - DEFRA_CLIENT_GO=true DEFRA_CLIENT_HTTP=true \ - $(MAKE) test:all - -.PHONY: test\:ci-gql-mutations -test\:ci-gql-mutations: - DEFRA_MUTATION_TYPE=gql DEFRA_BADGER_MEMORY=true \ - DEFRA_CLIENT_GO=true DEFRA_CLIENT_HTTP=true \ - $(MAKE) test:all - .PHONY: test\:gql-mutations test\:gql-mutations: DEFRA_MUTATION_TYPE=gql DEFRA_BADGER_MEMORY=true gotestsum --format pkgname -- $(DEFAULT_TEST_DIRECTORIES) @@ -207,12 +197,6 @@ test\:gql-mutations: # # For example, CreateDoc will call [Collection.Create], and # UpdateDoc will call [Collection.Update]. -.PHONY: test\:ci-col-named-mutations -test\:ci-col-named-mutations: - DEFRA_MUTATION_TYPE=collection-named DEFRA_BADGER_MEMORY=true \ - DEFRA_CLIENT_GO=true DEFRA_CLIENT_HTTP=true \ - $(MAKE) test:all - .PHONY: test\:col-named-mutations test\:col-named-mutations: DEFRA_MUTATION_TYPE=collection-named DEFRA_BADGER_MEMORY=true gotestsum --format pkgname -- $(DEFAULT_TEST_DIRECTORIES) @@ -225,6 +209,10 @@ test\:go: test\:http: DEFRA_CLIENT_HTTP=true go test $(DEFAULT_TEST_DIRECTORIES) $(TEST_FLAGS) +.PHONY: test\:cli +test\:cli: + DEFRA_CLIENT_CLI=true go test $(DEFAULT_TEST_DIRECTORIES) $(TEST_FLAGS) + .PHONY: test\:names test\:names: gotestsum --format testname -- $(DEFAULT_TEST_DIRECTORIES) $(TEST_FLAGS) @@ -233,7 +221,6 @@ test\:names: test\:all: @$(MAKE) test:names @$(MAKE) test:lens - @$(MAKE) test:cli .PHONY: test\:verbose test\:verbose: @@ -264,38 +251,27 @@ test\:lens: @$(MAKE) deps:lens gotestsum --format testname -- ./$(LENS_TEST_DIRECTORY)/... $(TEST_FLAGS) -.PHONY: test\:cli -test\:cli: - @$(MAKE) deps:lens - gotestsum --format testname -- ./$(CLI_TEST_DIRECTORY)/... $(TEST_FLAGS) - -# Using go-acc to ensure integration tests are included. -# Usage: `make test:coverage` or `make test:coverage path="{pathToPackage}"` -# Example: `make test:coverage path="./api/..."` .PHONY: test\:coverage test\:coverage: - @$(MAKE) deps:coverage -ifeq ($(path),) - go-acc ./... --output=coverage.txt --covermode=atomic -- -failfast -coverpkg=./... - @echo "Show coverage information for each function in ./..." -else - go-acc $(path) --output=coverage.txt --covermode=atomic -- -failfast -coverpkg=$(path) - @echo "Show coverage information for each function in" path=$(path) -endif - go tool cover -func coverage.txt | grep total | awk '{print $$3}' + @$(MAKE) deps:lens + @$(MAKE) clean:coverage + mkdir $(COVERAGE_DIRECTORY) + gotestsum --format testname -- ./... $(TEST_FLAGS) $(COVERAGE_FLAGS) + go tool covdata textfmt -i=$(COVERAGE_DIRECTORY) -o $(COVERAGE_FILE) + +.PHONY: test\:coverage-func +test\:coverage-func: + @$(MAKE) test:coverage + go tool cover -func=$(COVERAGE_FILE) -# Usage: `make test:coverage-html` or `make test:coverage-html path="{pathToPackage}"` -# Example: `make test:coverage-html path="./api/..."` .PHONY: test\:coverage-html test\:coverage-html: - @$(MAKE) test:coverage path=$(path) - @echo "Generate coverage information in HTML" - go tool cover -html=coverage.txt - rm ./coverage.txt + @$(MAKE) test:coverage + go tool cover -html=$(COVERAGE_FILE) .PHONY: test\:changes test\:changes: - gotestsum --format testname -- ./$(CHANGE_DETECTOR_TEST_DIRECTORY)/... --tags change_detector + gotestsum --format testname -- ./$(CHANGE_DETECTOR_TEST_DIRECTORY)/... -timeout 15m --tags change_detector .PHONY: validate\:codecov validate\:codecov: @@ -332,7 +308,7 @@ docs: .PHONY: docs\:cli docs\:cli: - go run cmd/genclidocs/genclidocs.go -o docs/cli/ + go run cmd/genclidocs/main.go -o docs/cli/ .PHONY: docs\:manpages docs\:manpages: diff --git a/api/http/errors.go b/api/http/errors.go deleted file mode 100644 index 4acf9abd25..0000000000 --- a/api/http/errors.go +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright 2022 Democratized Data Foundation -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package http - -import ( - "context" - "fmt" - "net/http" - "os" - "strings" - - "github.com/sourcenetwork/defradb/errors" -) - -var env = os.Getenv("DEFRA_ENV") - -// Errors returnable from this package. -// -// This list is incomplete. Undefined errors may also be returned. -// Errors returned from this package may be tested against these errors with errors.Is. -var ( - ErrNoListener = errors.New("cannot serve with no listener") - ErrSchema = errors.New("base must start with the http or https scheme") - ErrDatabaseNotAvailable = errors.New("no database available") - ErrFormNotSupported = errors.New("content type application/x-www-form-urlencoded not yet supported") - ErrBodyEmpty = errors.New("body cannot be empty") - ErrMissingGQLRequest = errors.New("missing GraphQL request") - ErrPeerIdUnavailable = errors.New("no PeerID available. P2P might be disabled") - ErrStreamingUnsupported = errors.New("streaming unsupported") - ErrNoEmail = errors.New("email address must be specified for tls with autocert") - ErrPayloadFormat = errors.New("invalid payload format") - ErrMissingNewKey = errors.New("missing _newKey for imported doc") -) - -// ErrorResponse is the GQL top level object holding error items for the response payload. -type ErrorResponse struct { - Errors []ErrorItem `json:"errors"` -} - -// ErrorItem hold an error message and extensions that might be pertinent to the request. -type ErrorItem struct { - Message string `json:"message"` - Extensions extensions `json:"extensions,omitempty"` -} - -type extensions struct { - Status int `json:"status"` - HTTPError string `json:"httpError"` - Stack string `json:"stack,omitempty"` -} - -func handleErr(ctx context.Context, rw http.ResponseWriter, err error, status int) { - if status == http.StatusInternalServerError { - log.ErrorE(ctx, http.StatusText(status), err) - } - - sendJSON( - ctx, - rw, - ErrorResponse{ - Errors: []ErrorItem{ - { - Message: err.Error(), - Extensions: extensions{ - Status: status, - HTTPError: http.StatusText(status), - Stack: formatError(err), - }, - }, - }, - }, - status, - ) -} - -func formatError(err error) string { - if strings.ToLower(env) == "dev" || strings.ToLower(env) == "development" { - return fmt.Sprintf("[DEV] %+v\n", err) - } - return "" -} diff --git a/api/http/errors_test.go b/api/http/errors_test.go deleted file mode 100644 index 9e4a5885c8..0000000000 --- a/api/http/errors_test.go +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright 2022 Democratized Data Foundation -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package http - -import ( - "encoding/json" - "net/http" - "net/http/httptest" - "strings" - "testing" - - "github.com/pkg/errors" - "github.com/stretchr/testify/assert" -) - -func CleanupEnv() { - env = "" -} - -func TestFormatError(t *testing.T) { - t.Cleanup(CleanupEnv) - env = "prod" - s := formatError(errors.New("test error")) - assert.Equal(t, "", s) - - env = "dev" - s = formatError(errors.New("test error")) - lines := strings.Split(s, "\n") - assert.Equal(t, "[DEV] test error", lines[0]) -} - -func TestHandleErrOnBadRequest(t *testing.T) { - t.Cleanup(CleanupEnv) - env = "dev" - f := func(rw http.ResponseWriter, req *http.Request) { - handleErr(req.Context(), rw, errors.New("test error"), http.StatusBadRequest) - } - req, err := http.NewRequest("GET", "/test", nil) - if err != nil { - t.Fatal(err) - } - - rec := httptest.NewRecorder() - - f(rec, req) - - resp := rec.Result() - - errResponse := ErrorResponse{} - err = json.NewDecoder(resp.Body).Decode(&errResponse) - if err != nil { - t.Fatal(err) - } - - if len(errResponse.Errors) != 1 { - t.Fatal("expecting exactly one error") - } - - assert.Equal(t, http.StatusBadRequest, errResponse.Errors[0].Extensions.Status) - assert.Equal(t, http.StatusText(http.StatusBadRequest), errResponse.Errors[0].Extensions.HTTPError) - assert.Equal(t, "test error", errResponse.Errors[0].Message) - assert.Contains(t, errResponse.Errors[0].Extensions.Stack, "[DEV] test error") -} - -func TestHandleErrOnInternalServerError(t *testing.T) { - t.Cleanup(CleanupEnv) - env = "dev" - f := func(rw http.ResponseWriter, req *http.Request) { - handleErr(req.Context(), rw, errors.New("test error"), http.StatusInternalServerError) - } - req, err := http.NewRequest("GET", "/test", nil) - if err != nil { - t.Fatal(err) - } - - rec := httptest.NewRecorder() - - f(rec, req) - - resp := rec.Result() - - errResponse := ErrorResponse{} - err = json.NewDecoder(resp.Body).Decode(&errResponse) - if err != nil { - t.Fatal(err) - } - - if len(errResponse.Errors) != 1 { - t.Fatal("expecting exactly one error") - } - assert.Equal(t, http.StatusInternalServerError, errResponse.Errors[0].Extensions.Status) - assert.Equal(t, http.StatusText(http.StatusInternalServerError), errResponse.Errors[0].Extensions.HTTPError) - assert.Equal(t, "test error", errResponse.Errors[0].Message) - assert.Contains(t, errResponse.Errors[0].Extensions.Stack, "[DEV] test error") -} - -func TestHandleErrOnNotFound(t *testing.T) { - t.Cleanup(CleanupEnv) - env = "dev" - f := func(rw http.ResponseWriter, req *http.Request) { - handleErr(req.Context(), rw, errors.New("test error"), http.StatusNotFound) - } - req, err := http.NewRequest("GET", "/test", nil) - if err != nil { - t.Fatal(err) - } - - rec := httptest.NewRecorder() - - f(rec, req) - - resp := rec.Result() - - errResponse := ErrorResponse{} - err = json.NewDecoder(resp.Body).Decode(&errResponse) - if err != nil { - t.Fatal(err) - } - - if len(errResponse.Errors) != 1 { - t.Fatal("expecting exactly one error") - } - - assert.Equal(t, http.StatusNotFound, errResponse.Errors[0].Extensions.Status) - assert.Equal(t, http.StatusText(http.StatusNotFound), errResponse.Errors[0].Extensions.HTTPError) - assert.Equal(t, "test error", errResponse.Errors[0].Message) - assert.Contains(t, errResponse.Errors[0].Extensions.Stack, "[DEV] test error") -} - -func TestHandleErrOnDefault(t *testing.T) { - t.Cleanup(CleanupEnv) - env = "dev" - f := func(rw http.ResponseWriter, req *http.Request) { - handleErr(req.Context(), rw, errors.New("unauthorized"), http.StatusUnauthorized) - } - req, err := http.NewRequest("GET", "/test", nil) - if err != nil { - t.Fatal(err) - } - - rec := httptest.NewRecorder() - - f(rec, req) - - resp := rec.Result() - - errResponse := ErrorResponse{} - err = json.NewDecoder(resp.Body).Decode(&errResponse) - if err != nil { - t.Fatal(err) - } - - if len(errResponse.Errors) != 1 { - t.Fatal("expecting exactly one error") - } - - assert.Equal(t, http.StatusUnauthorized, errResponse.Errors[0].Extensions.Status) - assert.Equal(t, http.StatusText(http.StatusUnauthorized), errResponse.Errors[0].Extensions.HTTPError) - assert.Equal(t, "unauthorized", errResponse.Errors[0].Message) - assert.Contains(t, errResponse.Errors[0].Extensions.Stack, "[DEV] unauthorized") -} diff --git a/api/http/handler.go b/api/http/handler.go deleted file mode 100644 index aa7b828f29..0000000000 --- a/api/http/handler.go +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright 2022 Democratized Data Foundation -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package http - -import ( - "context" - "encoding/json" - "fmt" - "io" - "net/http" - - "github.com/go-chi/chi/v5" - "github.com/go-chi/cors" - "github.com/pkg/errors" - - "github.com/sourcenetwork/defradb/client" -) - -type handler struct { - db client.DB - *chi.Mux - - // user configurable options - options serverOptions -} - -// context variables -type ( - ctxDB struct{} - ctxPeerID struct{} -) - -// DataResponse is the GQL top level object holding data for the response payload. -type DataResponse struct { - Data any `json:"data"` -} - -// simpleDataResponse is a helper function that returns a DataResponse struct. -// Odd arguments are the keys and must be strings otherwise they are ignored. -// Even arguments are the values associated with the previous key. -// Odd arguments are also ignored if there are no following arguments. -func simpleDataResponse(args ...any) DataResponse { - data := make(map[string]any) - - for i := 0; i < len(args); i += 2 { - if len(args) >= i+2 { - switch a := args[i].(type) { - case string: - data[a] = args[i+1] - - default: - continue - } - } - } - - return DataResponse{ - Data: data, - } -} - -// newHandler returns a handler with the router instantiated. -func newHandler(db client.DB, opts serverOptions) *handler { - mux := chi.NewRouter() - mux.Use(loggerMiddleware) - - if len(opts.allowedOrigins) != 0 { - mux.Use(cors.Handler(cors.Options{ - AllowedOrigins: opts.allowedOrigins, - AllowedMethods: []string{"GET", "POST", "PATCH", "OPTIONS"}, - AllowedHeaders: []string{"Content-Type"}, - MaxAge: 300, - })) - } - - mux.Use(func(next http.Handler) http.Handler { - return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - if opts.tls.HasValue() { - rw.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains") - } - ctx := context.WithValue(req.Context(), ctxDB{}, db) - if opts.peerID != "" { - ctx = context.WithValue(ctx, ctxPeerID{}, opts.peerID) - } - next.ServeHTTP(rw, req.WithContext(ctx)) - }) - }) - - return setRoutes(&handler{ - Mux: mux, - db: db, - options: opts, - }) -} - -func getJSON(req *http.Request, v any) error { - err := json.NewDecoder(req.Body).Decode(v) - if err != nil { - return errors.Wrap(err, "unmarshal error") - } - return nil -} - -func sendJSON(ctx context.Context, rw http.ResponseWriter, v any, code int) { - rw.Header().Set("Content-Type", "application/json") - - b, err := json.Marshal(v) - if err != nil { - log.Error(ctx, fmt.Sprintf("Error while encoding JSON: %v", err)) - rw.WriteHeader(http.StatusInternalServerError) - if _, err := io.WriteString(rw, `{"error": "Internal server error"}`); err != nil { - log.Error(ctx, err.Error()) - } - return - } - - rw.WriteHeader(code) - if _, err = rw.Write(b); err != nil { - rw.WriteHeader(http.StatusInternalServerError) - log.Error(ctx, err.Error()) - } -} - -func dbFromContext(ctx context.Context) (client.DB, error) { - db, ok := ctx.Value(ctxDB{}).(client.DB) - if !ok { - return nil, ErrDatabaseNotAvailable - } - - return db, nil -} diff --git a/api/http/handler_test.go b/api/http/handler_test.go deleted file mode 100644 index 2015c7a0ba..0000000000 --- a/api/http/handler_test.go +++ /dev/null @@ -1,312 +0,0 @@ -// Copyright 2022 Democratized Data Foundation -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package http - -import ( - "bytes" - "context" - "io" - "math" - "net/http" - "net/http/httptest" - "path" - "testing" - - badger "github.com/dgraph-io/badger/v4" - "github.com/pkg/errors" - "github.com/stretchr/testify/assert" - - badgerds "github.com/sourcenetwork/defradb/datastore/badger/v4" - "github.com/sourcenetwork/defradb/db" - "github.com/sourcenetwork/defradb/logging" -) - -func TestSimpleDataResponse(t *testing.T) { - resp := simpleDataResponse("key", "value", "key2", "value2") - switch v := resp.Data.(type) { - case map[string]any: - assert.Equal(t, "value", v["key"]) - assert.Equal(t, "value2", v["key2"]) - default: - t.Fatalf("data should be of type map[string]any but got %T", resp.Data) - } - - resp2 := simpleDataResponse("key", "value", "key2") - switch v := resp2.Data.(type) { - case map[string]any: - assert.Equal(t, "value", v["key"]) - assert.Equal(t, nil, v["key2"]) - default: - t.Fatalf("data should be of type map[string]any but got %T", resp.Data) - } - - resp3 := simpleDataResponse("key", "value", 2, "value2") - switch v := resp3.Data.(type) { - case map[string]any: - assert.Equal(t, "value", v["key"]) - assert.Equal(t, nil, v["2"]) - default: - t.Fatalf("data should be of type map[string]any but got %T", resp.Data) - } -} - -func TestNewHandlerWithLogger(t *testing.T) { - h := newHandler(nil, serverOptions{}) - - dir := t.TempDir() - - // send logs to temp file so we can inspect it - logFile := path.Join(dir, "http_test.log") - log.ApplyConfig(logging.Config{ - EncoderFormat: logging.NewEncoderFormatOption(logging.JSON), - OutputPaths: []string{logFile}, - }) - - req, err := http.NewRequest("GET", PingPath, nil) - if err != nil { - t.Fatal(err) - } - - rec := httptest.NewRecorder() - lrw := newLoggingResponseWriter(rec) - h.ServeHTTP(lrw, req) - assert.Equal(t, 200, rec.Result().StatusCode) - - // inspect the log file - kv, err := readLog(logFile) - if err != nil { - t.Fatal(err) - } - - assert.Equal(t, "http", kv["logger"]) -} - -func TestGetJSON(t *testing.T) { - var obj struct { - Name string - } - - jsonStr := ` -{ - "Name": "John Doe" -}` - - req, err := http.NewRequest("POST", "/ping", bytes.NewBuffer([]byte(jsonStr))) - if err != nil { - t.Fatal(err) - } - - err = getJSON(req, &obj) - if err != nil { - t.Fatal(err) - } - - assert.Equal(t, "John Doe", obj.Name) -} - -func TestGetJSONWithError(t *testing.T) { - var obj struct { - Name string - } - - jsonStr := ` -{ - "Name": 10 -}` - - req, err := http.NewRequest("POST", "/ping", bytes.NewBuffer([]byte(jsonStr))) - if err != nil { - t.Fatal(err) - } - - err = getJSON(req, &obj) - assert.Error(t, err) -} - -func TestSendJSONWithNoErrors(t *testing.T) { - obj := struct { - Name string - }{ - Name: "John Doe", - } - - rec := httptest.NewRecorder() - - sendJSON(context.Background(), rec, obj, 200) - - body, err := io.ReadAll(rec.Result().Body) - if err != nil { - t.Fatal(err) - } - assert.Equal(t, []byte("{\"Name\":\"John Doe\"}"), body) -} - -func TestSendJSONWithMarshallFailure(t *testing.T) { - rec := httptest.NewRecorder() - - sendJSON(context.Background(), rec, math.Inf(1), 200) - - assert.Equal(t, http.StatusInternalServerError, rec.Result().StatusCode) -} - -type loggerTest struct { - loggingResponseWriter -} - -func (lt *loggerTest) Write(b []byte) (int, error) { - return 0, errors.New("this write will fail") -} - -func TestSendJSONWithMarshallFailureAndWriteFailer(t *testing.T) { - rec := httptest.NewRecorder() - lrw := loggerTest{} - lrw.ResponseWriter = rec - - sendJSON(context.Background(), &lrw, math.Inf(1), 200) - - assert.Equal(t, http.StatusInternalServerError, rec.Result().StatusCode) -} - -func TestSendJSONWithWriteFailure(t *testing.T) { - obj := struct { - Name string - }{ - Name: "John Doe", - } - - rec := httptest.NewRecorder() - lrw := loggerTest{} - lrw.ResponseWriter = rec - - sendJSON(context.Background(), &lrw, obj, 200) - - assert.Equal(t, http.StatusInternalServerError, lrw.statusCode) -} - -func TestDbFromContext(t *testing.T) { - _, err := dbFromContext(context.Background()) - assert.Error(t, err) - - opts := badgerds.Options{Options: badger.DefaultOptions("").WithInMemory(true)} - rootstore, err := badgerds.NewDatastore("", &opts) - if err != nil { - t.Fatal(err) - } - - var options []db.Option - ctx := context.Background() - - defra, err := db.NewDB(ctx, rootstore, options...) - if err != nil { - t.Fatal(err) - } - - reqCtx := context.WithValue(ctx, ctxDB{}, defra) - - _, err = dbFromContext(reqCtx) - assert.NoError(t, err) -} - -func TestCORSRequest(t *testing.T) { - cases := []struct { - name string - method string - reqHeaders map[string]string - resHeaders map[string]string - }{ - { - "DisallowedOrigin", - "OPTIONS", - map[string]string{ - "Origin": "https://notsource.network", - }, - map[string]string{ - "Vary": "Origin", - }, - }, - { - "AllowedOrigin", - "OPTIONS", - map[string]string{ - "Origin": "https://source.network", - }, - map[string]string{ - "Access-Control-Allow-Origin": "https://source.network", - "Vary": "Origin", - }, - }, - } - - s := NewServer(nil, WithAllowedOrigins("https://source.network")) - - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - req, err := http.NewRequest(c.method, PingPath, nil) - if err != nil { - t.Fatal(err) - } - - for header, value := range c.reqHeaders { - req.Header.Add(header, value) - } - - rec := httptest.NewRecorder() - - s.Handler.ServeHTTP(rec, req) - - for header, value := range c.resHeaders { - assert.Equal(t, value, rec.Result().Header.Get(header)) - } - }) - } -} - -func TestTLSRequestResponseHeader(t *testing.T) { - cases := []struct { - name string - method string - reqHeaders map[string]string - resHeaders map[string]string - }{ - { - "TLSHeader", - "GET", - map[string]string{}, - map[string]string{ - "Strict-Transport-Security": "max-age=63072000; includeSubDomains", - }, - }, - } - dir := t.TempDir() - - s := NewServer(nil, WithTLS(), WithAddress("example.com"), WithRootDir(dir)) - - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - req, err := http.NewRequest(c.method, PingPath, nil) - if err != nil { - t.Fatal(err) - } - - for header, value := range c.reqHeaders { - req.Header.Add(header, value) - } - - rec := httptest.NewRecorder() - - s.Handler.ServeHTTP(rec, req) - - for header, value := range c.resHeaders { - assert.Equal(t, value, rec.Result().Header.Get(header)) - } - }) - } -} diff --git a/api/http/handlerfuncs.go b/api/http/handlerfuncs.go deleted file mode 100644 index 2a248d7d81..0000000000 --- a/api/http/handlerfuncs.go +++ /dev/null @@ -1,475 +0,0 @@ -// Copyright 2022 Democratized Data Foundation -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package http - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "mime" - "net/http" - - "github.com/go-chi/chi/v5" - dshelp "github.com/ipfs/boxo/datastore/dshelp" - dag "github.com/ipfs/boxo/ipld/merkledag" - "github.com/ipfs/go-cid" - ds "github.com/ipfs/go-datastore" - "github.com/multiformats/go-multihash" - - "github.com/sourcenetwork/defradb/client" - corecrdt "github.com/sourcenetwork/defradb/core/crdt" - "github.com/sourcenetwork/defradb/errors" - "github.com/sourcenetwork/defradb/events" -) - -const ( - contentTypeJSON = "application/json" - contentTypeGraphQL = "application/graphql" - contentTypeFormURLEncoded = "application/x-www-form-urlencoded" -) - -func rootHandler(rw http.ResponseWriter, req *http.Request) { - sendJSON( - req.Context(), - rw, - simpleDataResponse( - "response", "Welcome to the DefraDB HTTP API. Use /graphql to send queries to the database."+ - " Read the documentation at https://docs.source.network/.", - ), - http.StatusOK, - ) -} - -func pingHandler(rw http.ResponseWriter, req *http.Request) { - sendJSON( - req.Context(), - rw, - simpleDataResponse("response", "pong"), - http.StatusOK, - ) -} - -func dumpHandler(rw http.ResponseWriter, req *http.Request) { - db, err := dbFromContext(req.Context()) - if err != nil { - handleErr(req.Context(), rw, err, http.StatusInternalServerError) - return - } - - err = db.PrintDump(req.Context()) - if err != nil { - handleErr(req.Context(), rw, err, http.StatusInternalServerError) - return - } - - sendJSON( - req.Context(), - rw, - simpleDataResponse("response", "ok"), - http.StatusOK, - ) -} - -type gqlRequest struct { - Request string `json:"query"` -} - -func execGQLHandler(rw http.ResponseWriter, req *http.Request) { - request := req.URL.Query().Get("query") - if request == "" { - // extract the media type from the content-type header - contentType, _, err := mime.ParseMediaType(req.Header.Get("Content-Type")) - // mime.ParseMediaType will return an error (mime: no media type) - // if there is no media type set (i.e. application/json). - // This however is not a failing condition as not setting the content-type header - // should still make for a valid request and hit our default switch case. - if err != nil && err.Error() != "mime: no media type" { - handleErr(req.Context(), rw, err, http.StatusInternalServerError) - return - } - - switch contentType { - case contentTypeJSON: - gqlReq := gqlRequest{} - - err := getJSON(req, &gqlReq) - if err != nil { - handleErr(req.Context(), rw, err, http.StatusBadRequest) - return - } - - request = gqlReq.Request - - case contentTypeFormURLEncoded: - handleErr( - req.Context(), - rw, - ErrFormNotSupported, - http.StatusBadRequest, - ) - return - - case contentTypeGraphQL: - fallthrough - - default: - if req.Body == nil { - handleErr(req.Context(), rw, ErrBodyEmpty, http.StatusBadRequest) - return - } - body, err := readWithLimit(req.Body, rw) - if err != nil { - handleErr(req.Context(), rw, errors.WithStack(err), http.StatusInternalServerError) - return - } - request = string(body) - } - } - - // if at this point request is still empty, return an error - if request == "" { - handleErr(req.Context(), rw, ErrMissingGQLRequest, http.StatusBadRequest) - return - } - - db, err := dbFromContext(req.Context()) - if err != nil { - handleErr(req.Context(), rw, err, http.StatusInternalServerError) - return - } - result := db.ExecRequest(req.Context(), request) - - if result.Pub != nil { - subscriptionHandler(result.Pub, rw, req) - return - } - - sendJSON(req.Context(), rw, newGQLResult(result.GQL), http.StatusOK) -} - -type fieldResponse struct { - ID string `json:"id"` - Name string `json:"name"` - Kind string `json:"kind"` - Internal bool `json:"internal"` -} - -type collectionResponse struct { - Name string `json:"name"` - ID string `json:"id"` - VersionID string `json:"version_id"` - Fields []fieldResponse `json:"fields,omitempty"` -} - -func listSchemaHandler(rw http.ResponseWriter, req *http.Request) { - db, err := dbFromContext(req.Context()) - if err != nil { - handleErr(req.Context(), rw, err, http.StatusInternalServerError) - return - } - - cols, err := db.GetAllCollections(req.Context()) - if err != nil { - handleErr(req.Context(), rw, err, http.StatusInternalServerError) - return - } - - colResp := make([]collectionResponse, len(cols)) - for i, col := range cols { - var fields []fieldResponse - for _, field := range col.Schema().Fields { - fieldRes := fieldResponse{ - ID: field.ID.String(), - Name: field.Name, - Internal: field.IsInternal(), - } - if field.IsObjectArray() { - fieldRes.Kind = fmt.Sprintf("[%s]", field.Schema) - } else if field.IsObject() { - fieldRes.Kind = field.Schema - } else { - fieldRes.Kind = field.Kind.String() - } - fields = append(fields, fieldRes) - } - colResp[i] = collectionResponse{ - Name: col.Name(), - ID: col.SchemaID(), - VersionID: col.Schema().VersionID, - Fields: fields, - } - } - - sendJSON( - req.Context(), - rw, - simpleDataResponse("collections", colResp), - http.StatusOK, - ) -} - -func loadSchemaHandler(rw http.ResponseWriter, req *http.Request) { - sdl, err := readWithLimit(req.Body, rw) - if err != nil { - handleErr(req.Context(), rw, err, http.StatusInternalServerError) - return - } - - db, err := dbFromContext(req.Context()) - if err != nil { - handleErr(req.Context(), rw, err, http.StatusInternalServerError) - return - } - - colDescs, err := db.AddSchema(req.Context(), string(sdl)) - if err != nil { - handleErr(req.Context(), rw, err, http.StatusInternalServerError) - return - } - - colResp := make([]collectionResponse, len(colDescs)) - for i, desc := range colDescs { - col, err := db.GetCollectionByName(req.Context(), desc.Name) - if err != nil { - handleErr(req.Context(), rw, err, http.StatusInternalServerError) - return - } - colResp[i] = collectionResponse{ - Name: col.Name(), - ID: col.SchemaID(), - VersionID: col.Schema().VersionID, - } - } - - sendJSON( - req.Context(), - rw, - simpleDataResponse("result", "success", "collections", colResp), - http.StatusOK, - ) -} - -func patchSchemaHandler(rw http.ResponseWriter, req *http.Request) { - patch, err := readWithLimit(req.Body, rw) - if err != nil { - handleErr(req.Context(), rw, err, http.StatusInternalServerError) - return - } - - db, err := dbFromContext(req.Context()) - if err != nil { - handleErr(req.Context(), rw, err, http.StatusInternalServerError) - return - } - - // Hardcode setDefault to true here, as that preserves the existing behaviour. - // This function will be ripped out very shortly and I don't think it is worth - // spending time/thought here. The new http api handles this correctly. - err = db.PatchSchema(req.Context(), string(patch), true) - if err != nil { - handleErr(req.Context(), rw, err, http.StatusInternalServerError) - return - } - - sendJSON( - req.Context(), - rw, - simpleDataResponse("result", "success"), - http.StatusOK, - ) -} - -func setMigrationHandler(rw http.ResponseWriter, req *http.Request) { - cfgStr, err := readWithLimit(req.Body, rw) - if err != nil { - handleErr(req.Context(), rw, err, http.StatusInternalServerError) - return - } - - db, err := dbFromContext(req.Context()) - if err != nil { - handleErr(req.Context(), rw, err, http.StatusInternalServerError) - return - } - - var cfg client.LensConfig - err = json.Unmarshal(cfgStr, &cfg) - if err != nil { - handleErr(req.Context(), rw, err, http.StatusInternalServerError) - return - } - - err = db.LensRegistry().SetMigration(req.Context(), cfg) - if err != nil { - handleErr(req.Context(), rw, err, http.StatusInternalServerError) - return - } - - sendJSON( - req.Context(), - rw, - simpleDataResponse("result", "success"), - http.StatusOK, - ) -} - -func getMigrationHandler(rw http.ResponseWriter, req *http.Request) { - db, err := dbFromContext(req.Context()) - if err != nil { - handleErr(req.Context(), rw, err, http.StatusInternalServerError) - return - } - - cfgs, err := db.LensRegistry().Config(req.Context()) - if err != nil { - handleErr(req.Context(), rw, err, http.StatusInternalServerError) - return - } - - sendJSON( - req.Context(), - rw, - simpleDataResponse("configuration", cfgs), - http.StatusOK, - ) -} - -func getBlockHandler(rw http.ResponseWriter, req *http.Request) { - cidStr := chi.URLParam(req, "cid") - - // try to parse CID - cID, err := cid.Decode(cidStr) - if err != nil { - // If we can't try to parse DSKeyToCID - // return error if we still can't - key := ds.NewKey(cidStr) - var hash multihash.Multihash - hash, err = dshelp.DsKeyToMultihash(key) - if err != nil { - handleErr(req.Context(), rw, err, http.StatusBadRequest) - return - } - cID = cid.NewCidV1(cid.Raw, hash) - } - - db, err := dbFromContext(req.Context()) - if err != nil { - handleErr(req.Context(), rw, err, http.StatusInternalServerError) - return - } - - block, err := db.Blockstore().Get(req.Context(), cID) - if err != nil { - handleErr(req.Context(), rw, err, http.StatusInternalServerError) - return - } - - nd, err := dag.DecodeProtobuf(block.RawData()) - if err != nil { - handleErr(req.Context(), rw, err, http.StatusInternalServerError) - return - } - - buf, err := nd.MarshalJSON() - if err != nil { - handleErr(req.Context(), rw, err, http.StatusInternalServerError) - return - } - - reg := corecrdt.LWWRegister{} - delta, err := reg.DeltaDecode(nd) - if err != nil { - handleErr(req.Context(), rw, err, http.StatusInternalServerError) - return - } - - data, err := delta.Marshal() - if err != nil { - handleErr(req.Context(), rw, err, http.StatusInternalServerError) - return - } - - sendJSON( - req.Context(), - rw, - simpleDataResponse( - "block", string(buf), - "delta", string(data), - "val", delta.Value(), - ), - http.StatusOK, - ) -} - -func peerIDHandler(rw http.ResponseWriter, req *http.Request) { - peerID, ok := req.Context().Value(ctxPeerID{}).(string) - if !ok || peerID == "" { - handleErr(req.Context(), rw, ErrPeerIdUnavailable, http.StatusNotFound) - return - } - - sendJSON( - req.Context(), - rw, - simpleDataResponse( - "peerID", peerID, - ), - http.StatusOK, - ) -} - -func subscriptionHandler(pub *events.Publisher[events.Update], rw http.ResponseWriter, req *http.Request) { - flusher, ok := rw.(http.Flusher) - if !ok { - handleErr(req.Context(), rw, ErrStreamingUnsupported, http.StatusInternalServerError) - return - } - - rw.Header().Set("Content-Type", "text/event-stream") - rw.Header().Set("Cache-Control", "no-cache") - rw.Header().Set("Connection", "keep-alive") - - for { - select { - case <-req.Context().Done(): - pub.Unsubscribe() - return - case s, open := <-pub.Stream(): - if !open { - return - } - b, err := json.Marshal(s) - if err != nil { - handleErr(req.Context(), rw, err, http.StatusInternalServerError) - return - } - fmt.Fprintf(rw, "data: %s\n\n", b) - flusher.Flush() - } - } -} - -// maxBytes is an arbitrary limit to prevent unbounded message bodies being sent and read. -const maxBytes int64 = 100 * (1 << (10 * 2)) // 100MB - -// readWithLimit reads from the reader until either EoF or the maximum number of bytes have been read. -func readWithLimit(reader io.ReadCloser, rw http.ResponseWriter) ([]byte, error) { - reader = http.MaxBytesReader(rw, reader, maxBytes) - - var buf bytes.Buffer - _, err := io.Copy(&buf, reader) - if err != nil { - return nil, err - } - - return buf.Bytes(), nil -} diff --git a/api/http/handlerfuncs_backup.go b/api/http/handlerfuncs_backup.go deleted file mode 100644 index 3961263995..0000000000 --- a/api/http/handlerfuncs_backup.go +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright 2023 Democratized Data Foundation -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package http - -import ( - "context" - "net/http" - "os" - "strings" - - "github.com/sourcenetwork/defradb/client" - "github.com/sourcenetwork/defradb/errors" -) - -func exportHandler(rw http.ResponseWriter, req *http.Request) { - db, err := dbFromContext(req.Context()) - if err != nil { - handleErr(req.Context(), rw, err, http.StatusInternalServerError) - return - } - - cfg := &client.BackupConfig{} - err = getJSON(req, cfg) - if err != nil { - handleErr(req.Context(), rw, err, http.StatusBadRequest) - return - } - - err = validateBackupConfig(req.Context(), cfg, db) - if err != nil { - handleErr(req.Context(), rw, err, http.StatusBadRequest) - return - } - - err = db.BasicExport(req.Context(), cfg) - if err != nil { - handleErr(req.Context(), rw, err, http.StatusInternalServerError) - return - } - - sendJSON( - req.Context(), - rw, - simpleDataResponse("result", "success"), - http.StatusOK, - ) -} - -func importHandler(rw http.ResponseWriter, req *http.Request) { - db, err := dbFromContext(req.Context()) - if err != nil { - handleErr(req.Context(), rw, err, http.StatusInternalServerError) - return - } - - cfg := &client.BackupConfig{} - err = getJSON(req, cfg) - if err != nil { - handleErr(req.Context(), rw, err, http.StatusBadRequest) - return - } - - err = validateBackupConfig(req.Context(), cfg, db) - if err != nil { - handleErr(req.Context(), rw, err, http.StatusBadRequest) - return - } - - err = db.BasicImport(req.Context(), cfg.Filepath) - if err != nil { - handleErr(req.Context(), rw, err, http.StatusInternalServerError) - return - } - - sendJSON( - req.Context(), - rw, - simpleDataResponse("result", "success"), - http.StatusOK, - ) -} - -func validateBackupConfig(ctx context.Context, cfg *client.BackupConfig, db client.DB) error { - if !isValidPath(cfg.Filepath) { - return errors.New("invalid file path") - } - - if cfg.Format != "" && strings.ToLower(cfg.Format) != "json" { - return errors.New("only JSON format is supported at the moment") - } - for _, colName := range cfg.Collections { - _, err := db.GetCollectionByName(ctx, colName) - if err != nil { - return errors.Wrap("collection does not exist", err) - } - } - return nil -} - -func isValidPath(filepath string) bool { - // if a file exists, return true - if _, err := os.Stat(filepath); err == nil { - return true - } - - // if not, attempt to write to the path and if successful, - // remove the file and return true - var d []byte - if err := os.WriteFile(filepath, d, 0o644); err == nil { - _ = os.Remove(filepath) - return true - } - - return false -} diff --git a/api/http/handlerfuncs_backup_test.go b/api/http/handlerfuncs_backup_test.go deleted file mode 100644 index 67af6015a1..0000000000 --- a/api/http/handlerfuncs_backup_test.go +++ /dev/null @@ -1,623 +0,0 @@ -// Copyright 2023 Democratized Data Foundation -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package http - -import ( - "bytes" - "context" - "encoding/json" - "net/http" - "os" - "testing" - - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - - "github.com/sourcenetwork/defradb/client" - "github.com/sourcenetwork/defradb/client/mocks" - "github.com/sourcenetwork/defradb/errors" -) - -func TestExportHandler_WithNoDB_NoDatabaseAvailableError(t *testing.T) { - t.Cleanup(CleanupEnv) - env = "dev" - errResponse := ErrorResponse{} - testRequest(testOptions{ - Testing: t, - DB: nil, - Method: "POST", - Path: ExportPath, - Body: nil, - ExpectedStatus: 500, - ResponseData: &errResponse, - }) - require.Contains(t, errResponse.Errors[0].Extensions.Stack, "no database available") - require.Equal(t, http.StatusInternalServerError, errResponse.Errors[0].Extensions.Status) - require.Equal(t, "Internal Server Error", errResponse.Errors[0].Extensions.HTTPError) - require.Equal(t, "no database available", errResponse.Errors[0].Message) -} - -func TestExportHandler_WithWrongPayload_ReturnError(t *testing.T) { - t.Cleanup(CleanupEnv) - env = "dev" - ctx := context.Background() - defra := testNewInMemoryDB(t, ctx) - defer defra.Close(ctx) - - buf := bytes.NewBuffer([]byte("[]")) - errResponse := ErrorResponse{} - testRequest(testOptions{ - Testing: t, - DB: defra, - Method: "POST", - Path: ExportPath, - Body: buf, - ExpectedStatus: 400, - ResponseData: &errResponse, - }) - require.Contains(t, errResponse.Errors[0].Extensions.Stack, "json: cannot unmarshal array into Go value of type client.BackupConfig") - require.Equal(t, http.StatusBadRequest, errResponse.Errors[0].Extensions.Status) - require.Equal(t, "Bad Request", errResponse.Errors[0].Extensions.HTTPError) - require.Equal(t, "unmarshal error: json: cannot unmarshal array into Go value of type client.BackupConfig", errResponse.Errors[0].Message) -} - -func TestExportHandler_WithInvalidFilePath_ReturnError(t *testing.T) { - t.Cleanup(CleanupEnv) - env = "dev" - ctx := context.Background() - defra := testNewInMemoryDB(t, ctx) - defer defra.Close(ctx) - - filepath := t.TempDir() + "/some/test.json" - cfg := client.BackupConfig{ - Filepath: filepath, - } - b, err := json.Marshal(cfg) - require.NoError(t, err) - buf := bytes.NewBuffer(b) - - errResponse := ErrorResponse{} - testRequest(testOptions{ - Testing: t, - DB: defra, - Method: "POST", - Path: ExportPath, - Body: buf, - ExpectedStatus: 400, - ResponseData: &errResponse, - }) - require.Contains(t, errResponse.Errors[0].Extensions.Stack, "invalid file path") - require.Equal(t, http.StatusBadRequest, errResponse.Errors[0].Extensions.Status) - require.Equal(t, "Bad Request", errResponse.Errors[0].Extensions.HTTPError) - require.Equal(t, "invalid file path", errResponse.Errors[0].Message) -} - -func TestExportHandler_WithInvalidFomat_ReturnError(t *testing.T) { - t.Cleanup(CleanupEnv) - env = "dev" - ctx := context.Background() - defra := testNewInMemoryDB(t, ctx) - defer defra.Close(ctx) - - filepath := t.TempDir() + "/test.json" - cfg := client.BackupConfig{ - Filepath: filepath, - Format: "csv", - } - b, err := json.Marshal(cfg) - require.NoError(t, err) - buf := bytes.NewBuffer(b) - - errResponse := ErrorResponse{} - testRequest(testOptions{ - Testing: t, - DB: defra, - Method: "POST", - Path: ExportPath, - Body: buf, - ExpectedStatus: 400, - ResponseData: &errResponse, - }) - require.Contains(t, errResponse.Errors[0].Extensions.Stack, "only JSON format is supported at the moment") - require.Equal(t, http.StatusBadRequest, errResponse.Errors[0].Extensions.Status) - require.Equal(t, "Bad Request", errResponse.Errors[0].Extensions.HTTPError) - require.Equal(t, "only JSON format is supported at the moment", errResponse.Errors[0].Message) -} - -func TestExportHandler_WithInvalidCollection_ReturnError(t *testing.T) { - t.Cleanup(CleanupEnv) - env = "dev" - ctx := context.Background() - defra := testNewInMemoryDB(t, ctx) - defer defra.Close(ctx) - - filepath := t.TempDir() + "/test.json" - cfg := client.BackupConfig{ - Filepath: filepath, - Format: "json", - Collections: []string{"invalid"}, - } - b, err := json.Marshal(cfg) - require.NoError(t, err) - buf := bytes.NewBuffer(b) - - errResponse := ErrorResponse{} - testRequest(testOptions{ - Testing: t, - DB: defra, - Method: "POST", - Path: ExportPath, - Body: buf, - ExpectedStatus: 400, - ResponseData: &errResponse, - }) - require.Contains(t, errResponse.Errors[0].Extensions.Stack, "collection does not exist: datastore: key not found") - require.Equal(t, http.StatusBadRequest, errResponse.Errors[0].Extensions.Status) - require.Equal(t, "Bad Request", errResponse.Errors[0].Extensions.HTTPError) - require.Equal(t, "collection does not exist: datastore: key not found", errResponse.Errors[0].Message) -} - -func TestExportHandler_WithBasicExportError_ReturnError(t *testing.T) { - t.Cleanup(CleanupEnv) - env = "dev" - db := mocks.NewDB(t) - testError := errors.New("test error") - db.EXPECT().BasicExport(mock.Anything, mock.Anything).Return(testError) - - filepath := t.TempDir() + "/test.json" - cfg := client.BackupConfig{ - Filepath: filepath, - } - b, err := json.Marshal(cfg) - require.NoError(t, err) - buf := bytes.NewBuffer(b) - - errResponse := ErrorResponse{} - testRequest(testOptions{ - Testing: t, - DB: db, - Method: "POST", - Path: ExportPath, - Body: buf, - ExpectedStatus: 500, - ResponseData: &errResponse, - }) - require.Contains(t, errResponse.Errors[0].Extensions.Stack, "test error") - require.Equal(t, http.StatusInternalServerError, errResponse.Errors[0].Extensions.Status) - require.Equal(t, "Internal Server Error", errResponse.Errors[0].Extensions.HTTPError) - require.Equal(t, "test error", errResponse.Errors[0].Message) -} - -func TestExportHandler_AllCollections_NoError(t *testing.T) { - ctx := context.Background() - defra := testNewInMemoryDB(t, ctx) - defer defra.Close(ctx) - - testLoadSchema(t, ctx, defra) - - col, err := defra.GetCollectionByName(ctx, "User") - require.NoError(t, err) - - doc, err := client.NewDocFromJSON([]byte(`{"age": 31, "verified": true, "points": 90, "name": "Bob"}`)) - require.NoError(t, err) - - err = col.Create(ctx, doc) - require.NoError(t, err) - - filepath := t.TempDir() + "/test.json" - cfg := client.BackupConfig{ - Filepath: filepath, - } - b, err := json.Marshal(cfg) - require.NoError(t, err) - buf := bytes.NewBuffer(b) - - respBody := testRequest(testOptions{ - Testing: t, - DB: defra, - Method: "POST", - Path: ExportPath, - Body: buf, - ExpectedStatus: 200, - }) - - b, err = os.ReadFile(filepath) - require.NoError(t, err) - - require.Equal( - t, - `{"data":{"result":"success"}}`, - string(respBody), - ) - - require.Equal( - t, - `{"User":[{"_key":"bae-91171025-ed21-50e3-b0dc-e31bccdfa1ab","_newKey":"bae-91171025-ed21-50e3-b0dc-e31bccdfa1ab","age":31,"name":"Bob","points":90,"verified":true}]}`, - string(b), - ) -} - -func TestExportHandler_UserCollection_NoError(t *testing.T) { - ctx := context.Background() - defra := testNewInMemoryDB(t, ctx) - defer defra.Close(ctx) - - testLoadSchema(t, ctx, defra) - - col, err := defra.GetCollectionByName(ctx, "User") - require.NoError(t, err) - - doc, err := client.NewDocFromJSON([]byte(`{"age": 31, "verified": true, "points": 90, "name": "Bob"}`)) - require.NoError(t, err) - - err = col.Create(ctx, doc) - require.NoError(t, err) - - filepath := t.TempDir() + "/test.json" - cfg := client.BackupConfig{ - Filepath: filepath, - Collections: []string{"User"}, - } - b, err := json.Marshal(cfg) - require.NoError(t, err) - buf := bytes.NewBuffer(b) - - respBody := testRequest(testOptions{ - Testing: t, - DB: defra, - Method: "POST", - Path: ExportPath, - Body: buf, - ExpectedStatus: 200, - }) - - b, err = os.ReadFile(filepath) - require.NoError(t, err) - - require.Equal( - t, - `{"data":{"result":"success"}}`, - string(respBody), - ) - - require.Equal( - t, - `{"User":[{"_key":"bae-91171025-ed21-50e3-b0dc-e31bccdfa1ab","_newKey":"bae-91171025-ed21-50e3-b0dc-e31bccdfa1ab","age":31,"name":"Bob","points":90,"verified":true}]}`, - string(b), - ) -} - -func TestExportHandler_UserCollectionWithModifiedDoc_NoError(t *testing.T) { - ctx := context.Background() - defra := testNewInMemoryDB(t, ctx) - defer defra.Close(ctx) - - testLoadSchema(t, ctx, defra) - - col, err := defra.GetCollectionByName(ctx, "User") - require.NoError(t, err) - - doc, err := client.NewDocFromJSON([]byte(`{"age": 31, "verified": true, "points": 90, "name": "Bob"}`)) - require.NoError(t, err) - - err = col.Create(ctx, doc) - require.NoError(t, err) - - err = doc.Set("points", 1000) - require.NoError(t, err) - - err = col.Update(ctx, doc) - require.NoError(t, err) - - filepath := t.TempDir() + "/test.json" - cfg := client.BackupConfig{ - Filepath: filepath, - Collections: []string{"User"}, - } - b, err := json.Marshal(cfg) - require.NoError(t, err) - buf := bytes.NewBuffer(b) - - respBody := testRequest(testOptions{ - Testing: t, - DB: defra, - Method: "POST", - Path: ExportPath, - Body: buf, - ExpectedStatus: 200, - }) - - b, err = os.ReadFile(filepath) - require.NoError(t, err) - - require.Equal( - t, - `{"data":{"result":"success"}}`, - string(respBody), - ) - - require.Equal( - t, - `{"User":[{"_key":"bae-91171025-ed21-50e3-b0dc-e31bccdfa1ab","_newKey":"bae-36697142-d46a-57b1-b25e-6336706854ea","age":31,"name":"Bob","points":1000,"verified":true}]}`, - string(b), - ) -} - -func TestImportHandler_WithNoDB_NoDatabaseAvailableError(t *testing.T) { - t.Cleanup(CleanupEnv) - env = "dev" - errResponse := ErrorResponse{} - testRequest(testOptions{ - Testing: t, - DB: nil, - Method: "POST", - Path: ImportPath, - Body: nil, - ExpectedStatus: 500, - ResponseData: &errResponse, - }) - require.Contains(t, errResponse.Errors[0].Extensions.Stack, "no database available") - require.Equal(t, http.StatusInternalServerError, errResponse.Errors[0].Extensions.Status) - require.Equal(t, "Internal Server Error", errResponse.Errors[0].Extensions.HTTPError) - require.Equal(t, "no database available", errResponse.Errors[0].Message) -} - -func TestImportHandler_WithWrongPayloadFormat_UnmarshalError(t *testing.T) { - t.Cleanup(CleanupEnv) - env = "dev" - - ctx := context.Background() - defra := testNewInMemoryDB(t, ctx) - defer defra.Close(ctx) - - buf := bytes.NewBuffer([]byte(`[]`)) - - errResponse := ErrorResponse{} - testRequest(testOptions{ - Testing: t, - DB: defra, - Method: "POST", - Path: ImportPath, - Body: buf, - ExpectedStatus: 400, - ResponseData: &errResponse, - }) - require.Contains( - t, - errResponse.Errors[0].Extensions.Stack, - "json: cannot unmarshal array into Go value of type client.BackupConfig", - ) - require.Equal(t, http.StatusBadRequest, errResponse.Errors[0].Extensions.Status) - require.Equal(t, "Bad Request", errResponse.Errors[0].Extensions.HTTPError) - require.Equal( - t, - "unmarshal error: json: cannot unmarshal array into Go value of type client.BackupConfig", - errResponse.Errors[0].Message, - ) -} - -func TestImportHandler_WithInvalidFilepath_ReturnError(t *testing.T) { - t.Cleanup(CleanupEnv) - env = "dev" - - ctx := context.Background() - defra := testNewInMemoryDB(t, ctx) - defer defra.Close(ctx) - - filepath := t.TempDir() + "/some/test.json" - cfg := client.BackupConfig{ - Filepath: filepath, - } - b, err := json.Marshal(cfg) - require.NoError(t, err) - buf := bytes.NewBuffer(b) - - errResponse := ErrorResponse{} - testRequest(testOptions{ - Testing: t, - DB: defra, - Method: "POST", - Path: ImportPath, - Body: buf, - ExpectedStatus: 400, - ResponseData: &errResponse, - }) - require.Contains(t, errResponse.Errors[0].Extensions.Stack, "invalid file path") - require.Equal(t, http.StatusBadRequest, errResponse.Errors[0].Extensions.Status) - require.Equal(t, "Bad Request", errResponse.Errors[0].Extensions.HTTPError) - require.Equal(t, "invalid file path", errResponse.Errors[0].Message) -} - -func TestImportHandler_WithDBClosed_DatastoreClosedError(t *testing.T) { - t.Cleanup(CleanupEnv) - env = "dev" - - ctx := context.Background() - defra := testNewInMemoryDB(t, ctx) - defra.Close(ctx) - - filepath := t.TempDir() + "/test.json" - cfg := client.BackupConfig{ - Filepath: filepath, - } - b, err := json.Marshal(cfg) - require.NoError(t, err) - buf := bytes.NewBuffer(b) - - errResponse := ErrorResponse{} - testRequest(testOptions{ - Testing: t, - DB: defra, - Method: "POST", - Path: ImportPath, - Body: buf, - ExpectedStatus: 500, - ResponseData: &errResponse, - }) - require.Contains(t, errResponse.Errors[0].Extensions.Stack, "datastore closed") - require.Equal(t, http.StatusInternalServerError, errResponse.Errors[0].Extensions.Status) - require.Equal(t, "Internal Server Error", errResponse.Errors[0].Extensions.HTTPError) - require.Equal(t, "datastore closed", errResponse.Errors[0].Message) -} - -func TestImportHandler_WithUnknownCollection_KeyNotFoundError(t *testing.T) { - t.Cleanup(CleanupEnv) - env = "dev" - - ctx := context.Background() - defra := testNewInMemoryDB(t, ctx) - defer defra.Close(ctx) - - filepath := t.TempDir() + "/test.json" - err := os.WriteFile( - filepath, - []byte(`{"User":[{"_key":"bae-91171025-ed21-50e3-b0dc-e31bccdfa1ab","age":31,"name":"Bob","points":90,"verified":true}]}`), - 0644, - ) - require.NoError(t, err) - - cfg := client.BackupConfig{ - Filepath: filepath, - } - - b, err := json.Marshal(cfg) - require.NoError(t, err) - buf := bytes.NewBuffer(b) - - errResponse := ErrorResponse{} - testRequest(testOptions{ - Testing: t, - DB: defra, - Method: "POST", - Path: ImportPath, - Body: buf, - ExpectedStatus: 500, - ResponseData: &errResponse, - }) - require.Contains(t, errResponse.Errors[0].Extensions.Stack, "failed to get collection: datastore: key not found. Name: User") - require.Equal(t, http.StatusInternalServerError, errResponse.Errors[0].Extensions.Status) - require.Equal(t, "Internal Server Error", errResponse.Errors[0].Extensions.HTTPError) - require.Equal(t, "failed to get collection: datastore: key not found. Name: User", errResponse.Errors[0].Message) -} - -func TestImportHandler_UserCollection_NoError(t *testing.T) { - ctx := context.Background() - defra := testNewInMemoryDB(t, ctx) - defer defra.Close(ctx) - - testLoadSchema(t, ctx, defra) - - filepath := t.TempDir() + "/test.json" - err := os.WriteFile( - filepath, - []byte(`{"User":[{"_key":"bae-91171025-ed21-50e3-b0dc-e31bccdfa1ab","_newKey":"bae-91171025-ed21-50e3-b0dc-e31bccdfa1ab","age":31,"name":"Bob","points":90,"verified":true}]}`), - 0644, - ) - require.NoError(t, err) - - cfg := client.BackupConfig{ - Filepath: filepath, - } - - b, err := json.Marshal(cfg) - require.NoError(t, err) - buf := bytes.NewBuffer(b) - - resp := DataResponse{} - _ = testRequest(testOptions{ - Testing: t, - DB: defra, - Method: "POST", - Path: ImportPath, - Body: buf, - ExpectedStatus: 200, - ResponseData: &resp, - }) - - switch v := resp.Data.(type) { - case map[string]any: - require.Equal(t, "success", v["result"]) - default: - t.Fatalf("data should be of type map[string]any but got %T", resp.Data) - } - - doc, err := client.NewDocFromJSON([]byte(`{"age": 31, "verified": true, "points": 90, "name": "Bob"}`)) - require.NoError(t, err) - - col, err := defra.GetCollectionByName(ctx, "User") - require.NoError(t, err) - - importedDoc, err := col.Get(ctx, doc.Key(), false) - require.NoError(t, err) - - require.Equal(t, doc.Key().String(), importedDoc.Key().String()) -} - -func TestImportHandler_WithExistingDoc_DocumentExistError(t *testing.T) { - t.Cleanup(CleanupEnv) - env = "dev" - - ctx := context.Background() - defra := testNewInMemoryDB(t, ctx) - defer defra.Close(ctx) - - testLoadSchema(t, ctx, defra) - - col, err := defra.GetCollectionByName(ctx, "User") - require.NoError(t, err) - - doc, err := client.NewDocFromJSON([]byte(`{"age": 31, "verified": true, "points": 90, "name": "Bob"}`)) - require.NoError(t, err) - - err = col.Create(ctx, doc) - require.NoError(t, err) - - filepath := t.TempDir() + "/test.json" - err = os.WriteFile( - filepath, - []byte(`{"User":[{"_key":"bae-91171025-ed21-50e3-b0dc-e31bccdfa1ab","_newKey":"bae-91171025-ed21-50e3-b0dc-e31bccdfa1ab","age":31,"name":"Bob","points":90,"verified":true}]}`), - 0644, - ) - require.NoError(t, err) - - cfg := client.BackupConfig{ - Filepath: filepath, - } - - b, err := json.Marshal(cfg) - require.NoError(t, err) - buf := bytes.NewBuffer(b) - - errResponse := ErrorResponse{} - _ = testRequest(testOptions{ - Testing: t, - DB: defra, - Method: "POST", - Path: ImportPath, - QueryParams: map[string]string{"collections": "User"}, - Body: buf, - ExpectedStatus: 500, - ResponseData: &errResponse, - }) - - require.Contains( - t, - errResponse.Errors[0].Extensions.Stack, - "failed to save a new doc to collection: a document with the given dockey already exists", - ) - require.Equal(t, http.StatusInternalServerError, errResponse.Errors[0].Extensions.Status) - require.Equal(t, "Internal Server Error", errResponse.Errors[0].Extensions.HTTPError) - require.Equal( - t, - "failed to save a new doc to collection: a document with the given dockey already exists. DocKey: bae-91171025-ed21-50e3-b0dc-e31bccdfa1ab", - errResponse.Errors[0].Message, - ) -} diff --git a/api/http/handlerfuncs_index.go b/api/http/handlerfuncs_index.go deleted file mode 100644 index e8d10d900e..0000000000 --- a/api/http/handlerfuncs_index.go +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright 2023 Democratized Data Foundation -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package http - -import ( - "net/http" - "strings" - - "github.com/sourcenetwork/defradb/client" -) - -func createIndexHandler(rw http.ResponseWriter, req *http.Request) { - db, err := dbFromContext(req.Context()) - if err != nil { - handleErr(req.Context(), rw, err, http.StatusInternalServerError) - return - } - - var data map[string]string - err = getJSON(req, &data) - if err != nil { - handleErr(req.Context(), rw, err, http.StatusBadRequest) - return - } - - colNameArg := data["collection"] - fieldsArg := data["fields"] - indexNameArg := data["name"] - - col, err := db.GetCollectionByName(req.Context(), colNameArg) - if err != nil { - handleErr(req.Context(), rw, err, http.StatusInternalServerError) - return - } - - fields := strings.Split(fieldsArg, ",") - fieldDescriptions := make([]client.IndexedFieldDescription, 0, len(fields)) - for _, field := range fields { - fieldDescriptions = append(fieldDescriptions, client.IndexedFieldDescription{Name: field}) - } - indexDesc := client.IndexDescription{ - Name: indexNameArg, - Fields: fieldDescriptions, - } - indexDesc, err = col.CreateIndex(req.Context(), indexDesc) - if err != nil { - handleErr(req.Context(), rw, err, http.StatusInternalServerError) - return - } - - sendJSON( - req.Context(), - rw, - simpleDataResponse("index", indexDesc), - http.StatusOK, - ) -} - -func dropIndexHandler(rw http.ResponseWriter, req *http.Request) { - db, err := dbFromContext(req.Context()) - if err != nil { - handleErr(req.Context(), rw, err, http.StatusInternalServerError) - return - } - - var data map[string]string - err = getJSON(req, &data) - if err != nil { - handleErr(req.Context(), rw, err, http.StatusBadRequest) - return - } - - colNameArg := data["collection"] - indexNameArg := data["name"] - - col, err := db.GetCollectionByName(req.Context(), colNameArg) - if err != nil { - handleErr(req.Context(), rw, err, http.StatusInternalServerError) - return - } - - err = col.DropIndex(req.Context(), indexNameArg) - if err != nil { - handleErr(req.Context(), rw, err, http.StatusInternalServerError) - return - } - - sendJSON( - req.Context(), - rw, - simpleDataResponse("result", "success"), - http.StatusOK, - ) -} - -func listIndexHandler(rw http.ResponseWriter, req *http.Request) { - db, err := dbFromContext(req.Context()) - if err != nil { - handleErr(req.Context(), rw, err, http.StatusInternalServerError) - return - } - - queryParams := req.URL.Query() - collectionParam := queryParams.Get("collection") - - if collectionParam == "" { - indexesPerCol, err := db.GetAllIndexes(req.Context()) - if err != nil { - handleErr(req.Context(), rw, err, http.StatusInternalServerError) - return - } - sendJSON( - req.Context(), - rw, - simpleDataResponse("collections", indexesPerCol), - http.StatusOK, - ) - } else { - col, err := db.GetCollectionByName(req.Context(), collectionParam) - if err != nil { - handleErr(req.Context(), rw, err, http.StatusInternalServerError) - return - } - indexes, err := col.GetIndexes(req.Context()) - if err != nil { - handleErr(req.Context(), rw, err, http.StatusInternalServerError) - return - } - sendJSON( - req.Context(), - rw, - simpleDataResponse("indexes", indexes), - http.StatusOK, - ) - } -} diff --git a/api/http/handlerfuncs_index_test.go b/api/http/handlerfuncs_index_test.go deleted file mode 100644 index 3e82249ef8..0000000000 --- a/api/http/handlerfuncs_index_test.go +++ /dev/null @@ -1,239 +0,0 @@ -// Copyright 2023 Democratized Data Foundation -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package http - -import ( - "bytes" - "context" - "net/http" - "net/http/httptest" - "net/url" - "testing" - - "github.com/sourcenetwork/defradb/client" - "github.com/sourcenetwork/defradb/client/mocks" - "github.com/sourcenetwork/defradb/errors" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -func addDBToContext(t *testing.T, req *http.Request, db *mocks.DB) *http.Request { - if db == nil { - db = mocks.NewDB(t) - } - ctx := context.WithValue(req.Context(), ctxDB{}, db) - return req.WithContext(ctx) -} - -func TestCreateIndexHandler_IfNoDBInContext_ReturnError(t *testing.T) { - handler := http.HandlerFunc(createIndexHandler) - assert.HTTPBodyContains(t, handler, "POST", IndexPath, nil, "no database available") -} - -func TestCreateIndexHandler_IfFailsToParseParams_ReturnError(t *testing.T) { - req, err := http.NewRequest("POST", IndexPath, bytes.NewBuffer([]byte("invalid map"))) - if err != nil { - t.Fatal(err) - } - req = addDBToContext(t, req, nil) - - rr := httptest.NewRecorder() - handler := http.HandlerFunc(createIndexHandler) - - handler.ServeHTTP(rr, req) - - assert.Equal(t, http.StatusBadRequest, rr.Code, "handler returned wrong status code") - assert.Contains(t, rr.Body.String(), "invalid character", "handler returned unexpected body") -} - -func TestCreateIndexHandler_IfFailsToGetCollection_ReturnError(t *testing.T) { - testError := errors.New("test error") - db := mocks.NewDB(t) - db.EXPECT().GetCollectionByName(mock.Anything, mock.Anything).Return(nil, testError) - - req, err := http.NewRequest("POST", IndexPath, bytes.NewBuffer([]byte(`{}`))) - if err != nil { - t.Fatal(err) - } - - req = addDBToContext(t, req, db) - - rr := httptest.NewRecorder() - handler := http.HandlerFunc(createIndexHandler) - handler.ServeHTTP(rr, req) - - assert.Equal(t, http.StatusInternalServerError, rr.Code, "handler returned wrong status code") - assert.Contains(t, rr.Body.String(), testError.Error()) -} - -func TestCreateIndexHandler_IfFailsToCreateIndex_ReturnError(t *testing.T) { - testError := errors.New("test error") - col := mocks.NewCollection(t) - col.EXPECT().CreateIndex(mock.Anything, mock.Anything). - Return(client.IndexDescription{}, testError) - - db := mocks.NewDB(t) - db.EXPECT().GetCollectionByName(mock.Anything, mock.Anything).Return(col, nil) - - req, err := http.NewRequest("POST", IndexPath, bytes.NewBuffer([]byte(`{}`))) - if err != nil { - t.Fatal(err) - } - req = addDBToContext(t, req, db) - - rr := httptest.NewRecorder() - handler := http.HandlerFunc(createIndexHandler) - handler.ServeHTTP(rr, req) - - assert.Equal(t, http.StatusInternalServerError, rr.Code, "handler returned wrong status code") - assert.Contains(t, rr.Body.String(), testError.Error()) -} - -func TestDropIndexHandler_IfNoDBInContext_ReturnError(t *testing.T) { - handler := http.HandlerFunc(dropIndexHandler) - assert.HTTPBodyContains(t, handler, "DELETE", IndexPath, nil, "no database available") -} - -func TestDropIndexHandler_IfFailsToParseParams_ReturnError(t *testing.T) { - req, err := http.NewRequest("DELETE", IndexPath, bytes.NewBuffer([]byte("invalid map"))) - if err != nil { - t.Fatal(err) - } - req = addDBToContext(t, req, nil) - - rr := httptest.NewRecorder() - handler := http.HandlerFunc(dropIndexHandler) - - handler.ServeHTTP(rr, req) - - assert.Equal(t, http.StatusBadRequest, rr.Code, "handler returned wrong status code") - assert.Contains(t, rr.Body.String(), "invalid character", "handler returned unexpected body") -} - -func TestDropIndexHandler_IfFailsToGetCollection_ReturnError(t *testing.T) { - testError := errors.New("test error") - db := mocks.NewDB(t) - db.EXPECT().GetCollectionByName(mock.Anything, mock.Anything).Return(nil, testError) - - req, err := http.NewRequest("DELETE", IndexPath, bytes.NewBuffer([]byte(`{}`))) - if err != nil { - t.Fatal(err) - } - - req = addDBToContext(t, req, db) - - rr := httptest.NewRecorder() - handler := http.HandlerFunc(dropIndexHandler) - handler.ServeHTTP(rr, req) - - assert.Equal(t, http.StatusInternalServerError, rr.Code, "handler returned wrong status code") - assert.Contains(t, rr.Body.String(), testError.Error()) -} - -func TestDropIndexHandler_IfFailsToDropIndex_ReturnError(t *testing.T) { - testError := errors.New("test error") - col := mocks.NewCollection(t) - col.EXPECT().DropIndex(mock.Anything, mock.Anything).Return(testError) - - db := mocks.NewDB(t) - db.EXPECT().GetCollectionByName(mock.Anything, mock.Anything).Return(col, nil) - - req, err := http.NewRequest("DELETE", IndexPath, bytes.NewBuffer([]byte(`{}`))) - if err != nil { - t.Fatal(err) - } - req = addDBToContext(t, req, db) - - rr := httptest.NewRecorder() - handler := http.HandlerFunc(dropIndexHandler) - handler.ServeHTTP(rr, req) - - assert.Equal(t, http.StatusInternalServerError, rr.Code, "handler returned wrong status code") - assert.Contains(t, rr.Body.String(), testError.Error()) -} - -func TestListIndexHandler_IfNoDBInContext_ReturnError(t *testing.T) { - handler := http.HandlerFunc(listIndexHandler) - assert.HTTPBodyContains(t, handler, "GET", IndexPath, nil, "no database available") -} - -func TestListIndexHandler_IfFailsToGetAllIndexes_ReturnError(t *testing.T) { - testError := errors.New("test error") - db := mocks.NewDB(t) - db.EXPECT().GetAllIndexes(mock.Anything).Return(nil, testError) - - req, err := http.NewRequest("GET", IndexPath, bytes.NewBuffer([]byte(`{}`))) - if err != nil { - t.Fatal(err) - } - - req = addDBToContext(t, req, db) - - rr := httptest.NewRecorder() - handler := http.HandlerFunc(listIndexHandler) - handler.ServeHTTP(rr, req) - - assert.Equal(t, http.StatusInternalServerError, rr.Code, "handler returned wrong status code") - assert.Contains(t, rr.Body.String(), testError.Error()) -} - -func TestListIndexHandler_IfFailsToGetCollection_ReturnError(t *testing.T) { - testError := errors.New("test error") - db := mocks.NewDB(t) - db.EXPECT().GetCollectionByName(mock.Anything, mock.Anything).Return(nil, testError) - - u, _ := url.Parse("http://defradb.com" + IndexPath) - params := url.Values{} - params.Add("collection", "testCollection") - u.RawQuery = params.Encode() - - req, err := http.NewRequest("GET", u.String(), nil) - if err != nil { - t.Fatal(err) - } - - req = addDBToContext(t, req, db) - - rr := httptest.NewRecorder() - handler := http.HandlerFunc(listIndexHandler) - handler.ServeHTTP(rr, req) - - assert.Equal(t, http.StatusInternalServerError, rr.Code, "handler returned wrong status code") - assert.Contains(t, rr.Body.String(), testError.Error()) -} - -func TestListIndexHandler_IfFailsToCollectionGetIndexes_ReturnError(t *testing.T) { - testError := errors.New("test error") - col := mocks.NewCollection(t) - col.EXPECT().GetIndexes(mock.Anything).Return(nil, testError) - - db := mocks.NewDB(t) - db.EXPECT().GetCollectionByName(mock.Anything, mock.Anything).Return(col, nil) - - u, _ := url.Parse("http://defradb.com" + IndexPath) - params := url.Values{} - params.Add("collection", "testCollection") - u.RawQuery = params.Encode() - - req, err := http.NewRequest("GET", u.String(), nil) - if err != nil { - t.Fatal(err) - } - req = addDBToContext(t, req, db) - - rr := httptest.NewRecorder() - handler := http.HandlerFunc(listIndexHandler) - handler.ServeHTTP(rr, req) - - assert.Equal(t, http.StatusInternalServerError, rr.Code, "handler returned wrong status code") - assert.Contains(t, rr.Body.String(), testError.Error()) -} diff --git a/api/http/handlerfuncs_test.go b/api/http/handlerfuncs_test.go deleted file mode 100644 index ee7389a250..0000000000 --- a/api/http/handlerfuncs_test.go +++ /dev/null @@ -1,1184 +0,0 @@ -// Copyright 2022 Democratized Data Foundation -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package http - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "net/http/httptest" - "strings" - "testing" - "time" - - badger "github.com/dgraph-io/badger/v4" - dshelp "github.com/ipfs/boxo/datastore/dshelp" - "github.com/ipfs/go-cid" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - - "github.com/sourcenetwork/defradb/client" - badgerds "github.com/sourcenetwork/defradb/datastore/badger/v4" - "github.com/sourcenetwork/defradb/db" - "github.com/sourcenetwork/defradb/errors" -) - -type testOptions struct { - Testing *testing.T - DB client.DB - Handlerfunc http.HandlerFunc - Method string - Path string - Body io.Reader - Headers map[string]string - QueryParams map[string]string - ExpectedStatus int - ResponseData any - ServerOptions serverOptions -} - -type testUser struct { - Key string `json:"_key"` - Versions []testVersion `json:"_version"` -} - -type testVersion struct { - CID string `json:"cid"` -} - -func TestRootHandler(t *testing.T) { - resp := DataResponse{} - testRequest(testOptions{ - Testing: t, - DB: nil, - Method: "GET", - Path: RootPath, - Body: nil, - ExpectedStatus: 200, - ResponseData: &resp, - }) - switch v := resp.Data.(type) { - case map[string]any: - require.Equal(t, "Welcome to the DefraDB HTTP API. Use /graphql to send queries to the database. Read the documentation at https://docs.source.network/.", v["response"]) - default: - t.Fatalf("data should be of type map[string]any but got %T", resp.Data) - } -} - -func TestPingHandler(t *testing.T) { - resp := DataResponse{} - testRequest(testOptions{ - Testing: t, - DB: nil, - Method: "GET", - Path: PingPath, - Body: nil, - ExpectedStatus: 200, - ResponseData: &resp, - }) - - switch v := resp.Data.(type) { - case map[string]any: - require.Equal(t, "pong", v["response"]) - default: - t.Fatalf("data should be of type map[string]any but got %T", resp.Data) - } -} - -func TestDumpHandlerWithNoError(t *testing.T) { - ctx := context.Background() - defra := testNewInMemoryDB(t, ctx) - defer defra.Close(ctx) - - resp := DataResponse{} - testRequest(testOptions{ - Testing: t, - DB: defra, - Method: "GET", - Path: DumpPath, - Body: nil, - ExpectedStatus: 200, - ResponseData: &resp, - }) - - switch v := resp.Data.(type) { - case map[string]any: - require.Equal(t, "ok", v["response"]) - default: - t.Fatalf("data should be of type map[string]any but got %T", resp.Data) - } -} - -func TestDumpHandlerWithDBError(t *testing.T) { - t.Cleanup(CleanupEnv) - env = "dev" - errResponse := ErrorResponse{} - testRequest(testOptions{ - Testing: t, - DB: nil, - Method: "GET", - Path: DumpPath, - Body: nil, - ExpectedStatus: 500, - ResponseData: &errResponse, - }) - require.Contains(t, errResponse.Errors[0].Extensions.Stack, "no database available") - require.Equal(t, http.StatusInternalServerError, errResponse.Errors[0].Extensions.Status) - require.Equal(t, "Internal Server Error", errResponse.Errors[0].Extensions.HTTPError) - require.Equal(t, "no database available", errResponse.Errors[0].Message) -} - -func TestExecGQLWithNilBody(t *testing.T) { - t.Cleanup(CleanupEnv) - env = "dev" - errResponse := ErrorResponse{} - testRequest(testOptions{ - Testing: t, - DB: nil, - Method: "POST", - Path: GraphQLPath, - Body: nil, - ExpectedStatus: 400, - ResponseData: &errResponse, - }) - - require.Contains(t, errResponse.Errors[0].Extensions.Stack, "body cannot be empty") - require.Equal(t, http.StatusBadRequest, errResponse.Errors[0].Extensions.Status) - require.Equal(t, "Bad Request", errResponse.Errors[0].Extensions.HTTPError) - require.Equal(t, "body cannot be empty", errResponse.Errors[0].Message) -} - -func TestExecGQLWithEmptyBody(t *testing.T) { - t.Cleanup(CleanupEnv) - env = "dev" - errResponse := ErrorResponse{} - testRequest(testOptions{ - Testing: t, - DB: nil, - Method: "POST", - Path: GraphQLPath, - Body: bytes.NewBuffer([]byte("")), - ExpectedStatus: 400, - ResponseData: &errResponse, - }) - - require.Contains(t, errResponse.Errors[0].Extensions.Stack, "missing GraphQL request") - require.Equal(t, http.StatusBadRequest, errResponse.Errors[0].Extensions.Status) - require.Equal(t, "Bad Request", errResponse.Errors[0].Extensions.HTTPError) - require.Equal(t, "missing GraphQL request", errResponse.Errors[0].Message) -} - -type mockReadCloser struct { - mock.Mock -} - -func (m *mockReadCloser) Read(p []byte) (n int, err error) { - args := m.Called(p) - return args.Int(0), args.Error(1) -} - -func TestExecGQLWithMockBody(t *testing.T) { - t.Cleanup(CleanupEnv) - env = "dev" - mockReadCloser := mockReadCloser{} - // if Read is called, it will return error - mockReadCloser.On("Read", mock.AnythingOfType("[]uint8")).Return(0, errors.New("error reading")) - - errResponse := ErrorResponse{} - testRequest(testOptions{ - Testing: t, - DB: nil, - Method: "POST", - Path: GraphQLPath, - Body: &mockReadCloser, - ExpectedStatus: 500, - ResponseData: &errResponse, - }) - - require.Contains(t, errResponse.Errors[0].Extensions.Stack, "error reading") - require.Equal(t, http.StatusInternalServerError, errResponse.Errors[0].Extensions.Status) - require.Equal(t, "Internal Server Error", errResponse.Errors[0].Extensions.HTTPError) - require.Equal(t, "error reading", errResponse.Errors[0].Message) -} - -func TestExecGQLWithInvalidContentType(t *testing.T) { - t.Cleanup(CleanupEnv) - env = "dev" - errResponse := ErrorResponse{} - stmt := ` -mutation { - create_User(data: "{\"age\": 31, \"verified\": true, \"points\": 90, \"name\": \"Bob\"}") { - _key - } -}` - - buf := bytes.NewBuffer([]byte(stmt)) - testRequest(testOptions{ - Testing: t, - Method: "POST", - Path: GraphQLPath, - Body: buf, - ExpectedStatus: 500, - Headers: map[string]string{"Content-Type": contentTypeJSON + "; this-is-wrong"}, - ResponseData: &errResponse, - }) - - require.Contains(t, errResponse.Errors[0].Extensions.Stack, "mime: invalid media parameter") - require.Equal(t, http.StatusInternalServerError, errResponse.Errors[0].Extensions.Status) - require.Equal(t, "Internal Server Error", errResponse.Errors[0].Extensions.HTTPError) - require.Equal(t, "mime: invalid media parameter", errResponse.Errors[0].Message) -} - -func TestExecGQLWithNoDB(t *testing.T) { - t.Cleanup(CleanupEnv) - env = "dev" - errResponse := ErrorResponse{} - stmt := ` -mutation { - create_User(data: "{\"age\": 31, \"verified\": true, \"points\": 90, \"name\": \"Bob\"}") { - _key - } -}` - - buf := bytes.NewBuffer([]byte(stmt)) - testRequest(testOptions{ - Testing: t, - Method: "POST", - Path: GraphQLPath, - Body: buf, - ExpectedStatus: 500, - ResponseData: &errResponse, - }) - - require.Contains(t, errResponse.Errors[0].Extensions.Stack, "no database available") - require.Equal(t, http.StatusInternalServerError, errResponse.Errors[0].Extensions.Status) - require.Equal(t, "Internal Server Error", errResponse.Errors[0].Extensions.HTTPError) - require.Equal(t, "no database available", errResponse.Errors[0].Message) -} - -func TestExecGQLHandlerContentTypeJSONWithJSONError(t *testing.T) { - t.Cleanup(CleanupEnv) - env = "dev" - // statement with JSON formatting error - stmt := ` -[ - "query": "mutation { - create_User( - data: \"{ - \\\"age\\\": 31, - \\\"verified\\\": true, - \\\"points\\\": 90, - \\\"name\\\": \\\"Bob\\\" - }\" - ) {_key} - }" -]` - - buf := bytes.NewBuffer([]byte(stmt)) - errResponse := ErrorResponse{} - testRequest(testOptions{ - Testing: t, - DB: nil, - Method: "POST", - Path: GraphQLPath, - Body: buf, - Headers: map[string]string{"Content-Type": contentTypeJSON}, - ExpectedStatus: 400, - ResponseData: &errResponse, - }) - - require.Contains(t, errResponse.Errors[0].Extensions.Stack, "invalid character") - require.Equal(t, http.StatusBadRequest, errResponse.Errors[0].Extensions.Status) - require.Equal(t, "Bad Request", errResponse.Errors[0].Extensions.HTTPError) - require.Equal(t, "unmarshal error: invalid character ':' after array element", errResponse.Errors[0].Message) -} - -func TestExecGQLHandlerContentTypeJSON(t *testing.T) { - ctx := context.Background() - defra := testNewInMemoryDB(t, ctx) - defer defra.Close(ctx) - - // load schema - testLoadSchema(t, ctx, defra) - - // add document - stmt := ` -{ - "query": "mutation { - create_User( - data: \"{ - \\\"age\\\": 31, - \\\"verified\\\": true, - \\\"points\\\": 90, - \\\"name\\\": \\\"Bob\\\" - }\" - ) {_key} - }" -}` - // remove line returns and tabulation from formatted statement - stmt = strings.ReplaceAll(strings.ReplaceAll(stmt, "\t", ""), "\n", "") - - buf := bytes.NewBuffer([]byte(stmt)) - users := []testUser{} - resp := DataResponse{ - Data: &users, - } - testRequest(testOptions{ - Testing: t, - DB: defra, - Method: "POST", - Path: GraphQLPath, - Body: buf, - Headers: map[string]string{"Content-Type": contentTypeJSON}, - ExpectedStatus: 200, - ResponseData: &resp, - }) - - require.Contains(t, users[0].Key, "bae-") -} - -func TestExecGQLHandlerContentTypeJSONWithError(t *testing.T) { - ctx := context.Background() - defra := testNewInMemoryDB(t, ctx) - defer defra.Close(ctx) - - // load schema - testLoadSchema(t, ctx, defra) - - // add document - stmt := ` - { - "query": "mutation { - create_User( - data: \"{ - \\\"age\\\": 31, - \\\"notAField\\\": true - }\" - ) {_key} - }" - }` - - // remove line returns and tabulation from formatted statement - stmt = strings.ReplaceAll(strings.ReplaceAll(stmt, "\t", ""), "\n", "") - - buf := bytes.NewBuffer([]byte(stmt)) - resp := GQLResult{} - testRequest(testOptions{ - Testing: t, - DB: defra, - Method: "POST", - Path: GraphQLPath, - Body: buf, - Headers: map[string]string{"Content-Type": contentTypeJSON}, - ExpectedStatus: 200, - ResponseData: &resp, - }) - - require.Contains(t, resp.Errors, "The given field does not exist. Name: notAField") - require.Len(t, resp.Errors, 1) -} - -func TestExecGQLHandlerContentTypeJSONWithCharset(t *testing.T) { - ctx := context.Background() - defra := testNewInMemoryDB(t, ctx) - defer defra.Close(ctx) - - // load schema - testLoadSchema(t, ctx, defra) - - // add document - stmt := ` -{ - "query": "mutation { - create_User( - data: \"{ - \\\"age\\\": 31, - \\\"verified\\\": true, - \\\"points\\\": 90, - \\\"name\\\": \\\"Bob\\\" - }\" - ) {_key} - }" -}` - // remote line returns and tabulation from formatted statement - stmt = strings.ReplaceAll(strings.ReplaceAll(stmt, "\t", ""), "\n", "") - - buf := bytes.NewBuffer([]byte(stmt)) - users := []testUser{} - resp := DataResponse{ - Data: &users, - } - testRequest(testOptions{ - Testing: t, - DB: defra, - Method: "POST", - Path: GraphQLPath, - Body: buf, - Headers: map[string]string{"Content-Type": contentTypeJSON + "; charset=utf8"}, - ExpectedStatus: 200, - ResponseData: &resp, - }) - - require.Contains(t, users[0].Key, "bae-") -} - -func TestExecGQLHandlerContentTypeFormURLEncoded(t *testing.T) { - t.Cleanup(CleanupEnv) - env = "dev" - errResponse := ErrorResponse{} - testRequest(testOptions{ - Testing: t, - DB: nil, - Method: "POST", - Path: GraphQLPath, - Body: nil, - Headers: map[string]string{"Content-Type": contentTypeFormURLEncoded}, - ExpectedStatus: 400, - ResponseData: &errResponse, - }) - - require.Contains(t, errResponse.Errors[0].Extensions.Stack, "content type application/x-www-form-urlencoded not yet supported") - require.Equal(t, http.StatusBadRequest, errResponse.Errors[0].Extensions.Status) - require.Equal(t, "Bad Request", errResponse.Errors[0].Extensions.HTTPError) - require.Equal(t, "content type application/x-www-form-urlencoded not yet supported", errResponse.Errors[0].Message) -} - -func TestExecGQLHandlerContentTypeGraphQL(t *testing.T) { - ctx := context.Background() - defra := testNewInMemoryDB(t, ctx) - defer defra.Close(ctx) - - // load schema - testLoadSchema(t, ctx, defra) - - // add document - stmt := ` -mutation { - create_User(data: "{\"age\": 31, \"verified\": true, \"points\": 90, \"name\": \"Bob\"}") { - _key - } -}` - - buf := bytes.NewBuffer([]byte(stmt)) - users := []testUser{} - resp := DataResponse{ - Data: &users, - } - testRequest(testOptions{ - Testing: t, - DB: defra, - Method: "POST", - Path: GraphQLPath, - Body: buf, - Headers: map[string]string{"Content-Type": contentTypeGraphQL}, - ExpectedStatus: 200, - ResponseData: &resp, - }) - - require.Contains(t, users[0].Key, "bae-") -} - -func TestExecGQLHandlerContentTypeText(t *testing.T) { - ctx := context.Background() - defra := testNewInMemoryDB(t, ctx) - defer defra.Close(ctx) - - // load schema - testLoadSchema(t, ctx, defra) - - // add document - stmt := ` -mutation { - create_User(data: "{\"age\": 31, \"verified\": true, \"points\": 90, \"name\": \"Bob\"}") { - _key - } -}` - - buf := bytes.NewBuffer([]byte(stmt)) - users := []testUser{} - resp := DataResponse{ - Data: &users, - } - testRequest(testOptions{ - Testing: t, - DB: defra, - Method: "POST", - Path: GraphQLPath, - Body: buf, - ExpectedStatus: 200, - ResponseData: &resp, - }) - - require.Contains(t, users[0].Key, "bae-") -} - -func TestExecGQLHandlerWithSubsctiption(t *testing.T) { - ctx := context.Background() - defra := testNewInMemoryDB(t, ctx) - defer defra.Close(ctx) - - // load schema - testLoadSchema(t, ctx, defra) - - stmt := ` -subscription { - User { - _key - age - name - } -}` - - buf := bytes.NewBuffer([]byte(stmt)) - - ch := make(chan []byte) - errCh := make(chan error) - - // We need to set a timeout otherwise the testSubscriptionRequest function will block until the - // http.ServeHTTP call returns, which in this case will only happen with a timeout. - ctxTimeout, cancel := context.WithTimeout(ctx, time.Second) - defer cancel() - - go testSubscriptionRequest(ctxTimeout, testOptions{ - Testing: t, - DB: defra, - Method: "POST", - Path: GraphQLPath, - Body: buf, - Headers: map[string]string{"Content-Type": contentTypeGraphQL}, - ExpectedStatus: 200, - }, ch, errCh) - - // We wait to ensure the subscription requests can subscribe to the event channel. - time.Sleep(time.Second / 2) - - // add document - stmt2 := ` -mutation { - create_User(data: "{\"age\": 31, \"verified\": true, \"points\": 90, \"name\": \"Bob\"}") { - _key - } -}` - - buf2 := bytes.NewBuffer([]byte(stmt2)) - users := []testUser{} - resp := DataResponse{ - Data: &users, - } - testRequest(testOptions{ - Testing: t, - DB: defra, - Method: "POST", - Path: GraphQLPath, - Body: buf2, - ExpectedStatus: 200, - ResponseData: &resp, - }) - select { - case data := <-ch: - require.Contains(t, string(data), users[0].Key) - case err := <-errCh: - t.Fatal(err) - } -} - -func TestListSchemaHandlerWithoutDB(t *testing.T) { - t.Cleanup(CleanupEnv) - env = "dev" - - errResponse := ErrorResponse{} - testRequest(testOptions{ - Testing: t, - DB: nil, - Method: "GET", - Path: SchemaPath, - ExpectedStatus: 500, - ResponseData: &errResponse, - }) - - assert.Contains(t, errResponse.Errors[0].Extensions.Stack, "no database available") - assert.Equal(t, http.StatusInternalServerError, errResponse.Errors[0].Extensions.Status) - assert.Equal(t, "Internal Server Error", errResponse.Errors[0].Extensions.HTTPError) - assert.Equal(t, "no database available", errResponse.Errors[0].Message) -} - -func TestListSchemaHandlerWitNoError(t *testing.T) { - ctx := context.Background() - defra := testNewInMemoryDB(t, ctx) - defer defra.Close(ctx) - - stmt := ` -type user { - name: String - age: Int - verified: Boolean - points: Float -} -type group { - owner: user - members: [user] -}` - - _, err := defra.AddSchema(ctx, stmt) - if err != nil { - t.Fatal(err) - } - - resp := DataResponse{} - testRequest(testOptions{ - Testing: t, - DB: defra, - Method: "GET", - Path: SchemaPath, - ExpectedStatus: 200, - ResponseData: &resp, - }) - - switch v := resp.Data.(type) { - case map[string]any: - assert.Equal(t, map[string]any{ - "collections": []any{ - map[string]any{ - "name": "group", - "id": "bafkreicdtcgmgjjjao4zzaoacy26cl7xtnnev4qotvflellmdrzi57m5re", - "version_id": "bafkreicdtcgmgjjjao4zzaoacy26cl7xtnnev4qotvflellmdrzi57m5re", - "fields": []any{ - map[string]any{ - "id": "0", - "kind": "ID", - "name": "_key", - "internal": true, - }, - map[string]any{ - "id": "1", - "kind": "[user]", - "name": "members", - "internal": false, - }, - map[string]any{ - "id": "2", - "kind": "user", - "name": "owner", - "internal": false, - }, - map[string]any{ - "id": "3", - "kind": "ID", - "name": "owner_id", - "internal": true, - }, - }, - }, - map[string]any{ - "name": "user", - "id": "bafkreigl2v5trzfznb7dm3dubmsbzkw73s3phjm6laegswzl4625wc2grm", - "version_id": "bafkreigl2v5trzfznb7dm3dubmsbzkw73s3phjm6laegswzl4625wc2grm", - "fields": []any{ - map[string]any{ - "id": "0", - "kind": "ID", - "name": "_key", - "internal": true, - }, - map[string]any{ - "id": "1", - "kind": "Int", - "name": "age", - "internal": false, - }, - map[string]any{ - "id": "2", - "kind": "String", - "name": "name", - "internal": false, - }, - map[string]any{ - "id": "3", - "kind": "Float", - "name": "points", - "internal": false, - }, - map[string]any{ - "id": "4", - "kind": "Boolean", - "name": "verified", - "internal": false, - }, - }, - }, - }, - }, v) - - default: - t.Fatalf("data should be of type map[string]any but got %T\n%v", resp.Data, v) - } -} - -func TestLoadSchemaHandlerWithReadBodyError(t *testing.T) { - t.Cleanup(CleanupEnv) - env = "dev" - mockReadCloser := mockReadCloser{} - // if Read is called, it will return error - mockReadCloser.On("Read", mock.AnythingOfType("[]uint8")).Return(0, errors.New("error reading")) - - errResponse := ErrorResponse{} - testRequest(testOptions{ - Testing: t, - DB: nil, - Method: "POST", - Path: SchemaPath, - Body: &mockReadCloser, - ExpectedStatus: 500, - ResponseData: &errResponse, - }) - - require.Contains(t, errResponse.Errors[0].Extensions.Stack, "error reading") - require.Equal(t, http.StatusInternalServerError, errResponse.Errors[0].Extensions.Status) - require.Equal(t, "Internal Server Error", errResponse.Errors[0].Extensions.HTTPError) - require.Equal(t, "error reading", errResponse.Errors[0].Message) -} - -func TestLoadSchemaHandlerWithoutDB(t *testing.T) { - t.Cleanup(CleanupEnv) - env = "dev" - stmt := ` -type User { - name: String - age: Int - verified: Boolean - points: Float -}` - - buf := bytes.NewBuffer([]byte(stmt)) - - errResponse := ErrorResponse{} - testRequest(testOptions{ - Testing: t, - DB: nil, - Method: "POST", - Path: SchemaPath, - Body: buf, - ExpectedStatus: 500, - ResponseData: &errResponse, - }) - - require.Contains(t, errResponse.Errors[0].Extensions.Stack, "no database available") - require.Equal(t, http.StatusInternalServerError, errResponse.Errors[0].Extensions.Status) - require.Equal(t, "Internal Server Error", errResponse.Errors[0].Extensions.HTTPError) - require.Equal(t, "no database available", errResponse.Errors[0].Message) -} - -func TestLoadSchemaHandlerWithAddSchemaError(t *testing.T) { - t.Cleanup(CleanupEnv) - env = "dev" - ctx := context.Background() - defra := testNewInMemoryDB(t, ctx) - defer defra.Close(ctx) - - // statement with types instead of type - stmt := ` -types User { - name: String - age: Int - verified: Boolean - points: Float -}` - - buf := bytes.NewBuffer([]byte(stmt)) - - errResponse := ErrorResponse{} - testRequest(testOptions{ - Testing: t, - DB: defra, - Method: "POST", - Path: SchemaPath, - Body: buf, - ExpectedStatus: 500, - ResponseData: &errResponse, - }) - - require.Contains(t, errResponse.Errors[0].Extensions.Stack, "Syntax Error GraphQL (2:1) Unexpected Name") - require.Equal(t, http.StatusInternalServerError, errResponse.Errors[0].Extensions.Status) - require.Equal(t, "Internal Server Error", errResponse.Errors[0].Extensions.HTTPError) - require.Equal( - t, - "Syntax Error GraphQL (2:1) Unexpected Name \"types\"\n\n1: \n2: types User {\n ^\n3: \\u0009name: String\n", - errResponse.Errors[0].Message, - ) -} - -func TestLoadSchemaHandlerWitNoError(t *testing.T) { - ctx := context.Background() - defra := testNewInMemoryDB(t, ctx) - defer defra.Close(ctx) - - stmt := ` -type User { - name: String - age: Int - verified: Boolean - points: Float -}` - - buf := bytes.NewBuffer([]byte(stmt)) - - resp := DataResponse{} - testRequest(testOptions{ - Testing: t, - DB: defra, - Method: "POST", - Path: SchemaPath, - Body: buf, - ExpectedStatus: 200, - ResponseData: &resp, - }) - - switch v := resp.Data.(type) { - case map[string]any: - require.Equal(t, map[string]any{ - "result": "success", - "collections": []any{ - map[string]any{ - "name": "User", - "id": "bafkreiet7xqehjsjsthy6nafvtbz4el376uudhkjyeifuvvsr64se33swm", - "version_id": "bafkreiet7xqehjsjsthy6nafvtbz4el376uudhkjyeifuvvsr64se33swm", - }, - }, - }, v) - - default: - t.Fatalf("data should be of type map[string]any but got %T\n%v", resp.Data, v) - } -} - -func TestGetBlockHandlerWithMultihashError(t *testing.T) { - t.Cleanup(CleanupEnv) - env = "dev" - errResponse := ErrorResponse{} - testRequest(testOptions{ - Testing: t, - DB: nil, - Method: "GET", - Path: BlocksPath + "/1234", - Body: nil, - ExpectedStatus: 400, - ResponseData: &errResponse, - }) - - require.Contains(t, errResponse.Errors[0].Extensions.Stack, "illegal base32 data at input byte 0") - require.Equal(t, http.StatusBadRequest, errResponse.Errors[0].Extensions.Status) - require.Equal(t, "Bad Request", errResponse.Errors[0].Extensions.HTTPError) - require.Equal(t, "illegal base32 data at input byte 0", errResponse.Errors[0].Message) -} - -func TestGetBlockHandlerWithDSKeyWithNoDB(t *testing.T) { - t.Cleanup(CleanupEnv) - env = "dev" - cID, err := cid.Parse("bafybeidembipteezluioakc2zyke4h5fnj4rr3uaougfyxd35u3qzefzhm") - if err != nil { - t.Fatal(err) - } - dsKey := dshelp.MultihashToDsKey(cID.Hash()) - - errResponse := ErrorResponse{} - testRequest(testOptions{ - Testing: t, - DB: nil, - Method: "GET", - Path: BlocksPath + dsKey.String(), - Body: nil, - ExpectedStatus: 500, - ResponseData: &errResponse, - }) - - require.Contains(t, errResponse.Errors[0].Extensions.Stack, "no database available") - require.Equal(t, http.StatusInternalServerError, errResponse.Errors[0].Extensions.Status) - require.Equal(t, "Internal Server Error", errResponse.Errors[0].Extensions.HTTPError) - require.Equal(t, "no database available", errResponse.Errors[0].Message) -} - -func TestGetBlockHandlerWithNoDB(t *testing.T) { - t.Cleanup(CleanupEnv) - env = "dev" - errResponse := ErrorResponse{} - testRequest(testOptions{ - Testing: t, - DB: nil, - Method: "GET", - Path: BlocksPath + "/bafybeidembipteezluioakc2zyke4h5fnj4rr3uaougfyxd35u3qzefzhm", - Body: nil, - ExpectedStatus: 500, - ResponseData: &errResponse, - }) - - require.Contains(t, errResponse.Errors[0].Extensions.Stack, "no database available") - require.Equal(t, http.StatusInternalServerError, errResponse.Errors[0].Extensions.Status) - require.Equal(t, "Internal Server Error", errResponse.Errors[0].Extensions.HTTPError) - require.Equal(t, "no database available", errResponse.Errors[0].Message) -} - -func TestGetBlockHandlerWithGetBlockstoreError(t *testing.T) { - t.Cleanup(CleanupEnv) - env = "dev" - ctx := context.Background() - defra := testNewInMemoryDB(t, ctx) - defer defra.Close(ctx) - - errResponse := ErrorResponse{} - testRequest(testOptions{ - Testing: t, - DB: defra, - Method: "GET", - Path: BlocksPath + "/bafybeidembipteezluioakc2zyke4h5fnj4rr3uaougfyxd35u3qzefzhm", - Body: nil, - ExpectedStatus: 500, - ResponseData: &errResponse, - }) - - require.Contains(t, errResponse.Errors[0].Extensions.Stack, "ipld: could not find bafybeidembipteezluioakc2zyke4h5fnj4rr3uaougfyxd35u3qzefzhm") - require.Equal(t, http.StatusInternalServerError, errResponse.Errors[0].Extensions.Status) - require.Equal(t, "Internal Server Error", errResponse.Errors[0].Extensions.HTTPError) - require.Equal(t, "ipld: could not find bafybeidembipteezluioakc2zyke4h5fnj4rr3uaougfyxd35u3qzefzhm", errResponse.Errors[0].Message) -} - -func TestGetBlockHandlerWithValidBlockstore(t *testing.T) { - ctx := context.Background() - defra := testNewInMemoryDB(t, ctx) - defer defra.Close(ctx) - - testLoadSchema(t, ctx, defra) - - // add document - stmt := ` -mutation { - create_User(data: "{\"age\": 31, \"verified\": true, \"points\": 90, \"name\": \"Bob\"}") { - _key - } -}` - - buf := bytes.NewBuffer([]byte(stmt)) - - users := []testUser{} - resp := DataResponse{ - Data: &users, - } - testRequest(testOptions{ - Testing: t, - DB: defra, - Method: "POST", - Path: GraphQLPath, - Body: buf, - ExpectedStatus: 200, - ResponseData: &resp, - }) - - if !strings.Contains(users[0].Key, "bae-") { - t.Fatal("expected valid document key") - } - - // get document cid - stmt2 := ` -query { - User (dockey: "%s") { - _version { - cid - } - } -}` - buf2 := bytes.NewBuffer([]byte(fmt.Sprintf(stmt2, users[0].Key))) - - users2 := []testUser{} - resp2 := DataResponse{ - Data: &users2, - } - testRequest(testOptions{ - Testing: t, - DB: defra, - Method: "POST", - Path: GraphQLPath, - Body: buf2, - ExpectedStatus: 200, - ResponseData: &resp2, - }) - - _, err := cid.Decode(users2[0].Versions[0].CID) - if err != nil { - t.Fatal(err) - } - - resp3 := DataResponse{} - testRequest(testOptions{ - Testing: t, - DB: defra, - Method: "GET", - Path: BlocksPath + "/" + users2[0].Versions[0].CID, - Body: buf, - ExpectedStatus: 200, - ResponseData: &resp3, - }) - - switch d := resp3.Data.(type) { - case map[string]any: - switch val := d["val"].(type) { - case string: - require.Equal(t, "pGNhZ2UYH2RuYW1lY0JvYmZwb2ludHMYWmh2ZXJpZmllZPU=", val) - default: - t.Fatalf("expecting string but got %T", val) - } - default: - t.Fatalf("expecting map[string]any but got %T", d) - } -} - -func TestPeerIDHandler(t *testing.T) { - resp := DataResponse{} - testRequest(testOptions{ - Testing: t, - DB: nil, - Method: "GET", - Path: PeerIDPath, - Body: nil, - ExpectedStatus: 200, - ResponseData: &resp, - ServerOptions: serverOptions{ - peerID: "12D3KooWFpi6VTYKLtxUftJKEyfX8jDfKi8n15eaygH8ggfYFZbR", - }, - }) - - switch v := resp.Data.(type) { - case map[string]any: - require.Equal(t, "12D3KooWFpi6VTYKLtxUftJKEyfX8jDfKi8n15eaygH8ggfYFZbR", v["peerID"]) - default: - t.Fatalf("data should be of type map[string]any but got %T", resp.Data) - } -} - -func TestPeerIDHandlerWithNoPeerIDInContext(t *testing.T) { - t.Cleanup(CleanupEnv) - env = "dev" - - errResponse := ErrorResponse{} - testRequest(testOptions{ - Testing: t, - DB: nil, - Method: "GET", - Path: PeerIDPath, - Body: nil, - ExpectedStatus: 404, - ResponseData: &errResponse, - }) - - require.Contains(t, errResponse.Errors[0].Extensions.Stack, "no PeerID available. P2P might be disabled") - require.Equal(t, http.StatusNotFound, errResponse.Errors[0].Extensions.Status) - require.Equal(t, "Not Found", errResponse.Errors[0].Extensions.HTTPError) - require.Equal(t, "no PeerID available. P2P might be disabled", errResponse.Errors[0].Message) -} - -func testRequest(opt testOptions) []byte { - req, err := http.NewRequest(opt.Method, opt.Path, opt.Body) - if err != nil { - opt.Testing.Fatal(err) - } - - for k, v := range opt.Headers { - req.Header.Set(k, v) - } - - q := req.URL.Query() - for k, v := range opt.QueryParams { - q.Add(k, v) - } - req.URL.RawQuery = q.Encode() - - h := newHandler(opt.DB, opt.ServerOptions) - rec := httptest.NewRecorder() - h.ServeHTTP(rec, req) - assert.Equal(opt.Testing, opt.ExpectedStatus, rec.Result().StatusCode) - - resBody, err := io.ReadAll(rec.Result().Body) - if err != nil { - opt.Testing.Fatal(err) - } - - if opt.ResponseData != nil { - err = json.Unmarshal(resBody, &opt.ResponseData) - if err != nil { - opt.Testing.Fatal(err) - } - } - - return resBody -} - -func testSubscriptionRequest(ctx context.Context, opt testOptions, ch chan []byte, errCh chan error) { - req, err := http.NewRequest(opt.Method, opt.Path, opt.Body) - if err != nil { - errCh <- err - return - } - - req = req.WithContext(ctx) - - for k, v := range opt.Headers { - req.Header.Set(k, v) - } - - h := newHandler(opt.DB, opt.ServerOptions) - rec := httptest.NewRecorder() - h.ServeHTTP(rec, req) - require.Equal(opt.Testing, opt.ExpectedStatus, rec.Result().StatusCode) - - respBody, err := io.ReadAll(rec.Result().Body) - if err != nil { - errCh <- err - return - } - - ch <- respBody -} - -func testNewInMemoryDB(t *testing.T, ctx context.Context) client.DB { - // init in memory DB - opts := badgerds.Options{Options: badger.DefaultOptions("").WithInMemory(true)} - rootstore, err := badgerds.NewDatastore("", &opts) - if err != nil { - t.Fatal(err) - } - - options := []db.Option{ - db.WithUpdateEvents(), - } - - defra, err := db.NewDB(ctx, rootstore, options...) - if err != nil { - t.Fatal(err) - } - - return defra -} - -func testLoadSchema(t *testing.T, ctx context.Context, db client.DB) { - stmt := ` -type User { - name: String - age: Int - verified: Boolean - points: Float -}` - _, err := db.AddSchema(ctx, stmt) - if err != nil { - t.Fatal(err) - } -} diff --git a/api/http/logger.go b/api/http/logger.go deleted file mode 100644 index 2a91a271c2..0000000000 --- a/api/http/logger.go +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright 2022 Democratized Data Foundation -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package http - -import ( - "net/http" - "time" - - "github.com/sourcenetwork/defradb/logging" -) - -type loggingResponseWriter struct { - statusCode int - contentLength int - - http.ResponseWriter -} - -func newLoggingResponseWriter(w http.ResponseWriter) *loggingResponseWriter { - return &loggingResponseWriter{ - statusCode: http.StatusOK, - contentLength: 0, - ResponseWriter: w, - } -} - -func (lrw *loggingResponseWriter) Flush() { - lrw.ResponseWriter.(http.Flusher).Flush() -} - -func (lrw *loggingResponseWriter) Header() http.Header { - return lrw.ResponseWriter.Header() -} - -func (lrw *loggingResponseWriter) WriteHeader(code int) { - lrw.statusCode = code - lrw.ResponseWriter.WriteHeader(code) -} - -func (lrw *loggingResponseWriter) Write(b []byte) (int, error) { - lrw.contentLength = len(b) - return lrw.ResponseWriter.Write(b) -} - -func loggerMiddleware(next http.Handler) http.Handler { - return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - start := time.Now() - lrw := newLoggingResponseWriter(rw) - next.ServeHTTP(lrw, req) - elapsed := time.Since(start) - log.Info( - req.Context(), - "Request", - logging.NewKV( - "Method", - req.Method, - ), - logging.NewKV( - "Path", - req.URL.Path, - ), - logging.NewKV( - "Status", - lrw.statusCode, - ), - logging.NewKV( - "LengthBytes", - lrw.contentLength, - ), - logging.NewKV( - "ElapsedTime", - elapsed.String(), - ), - ) - }) -} diff --git a/api/http/logger_test.go b/api/http/logger_test.go deleted file mode 100644 index 9c2791d9df..0000000000 --- a/api/http/logger_test.go +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright 2022 Democratized Data Foundation -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package http - -import ( - "bufio" - "encoding/json" - "net/http" - "net/http/httptest" - "os" - "path" - "strconv" - "testing" - - "github.com/pkg/errors" - "github.com/stretchr/testify/assert" - - "github.com/sourcenetwork/defradb/logging" -) - -func TestNewLoggingResponseWriterLogger(t *testing.T) { - rec := httptest.NewRecorder() - lrw := newLoggingResponseWriter(rec) - - lrw.WriteHeader(400) - assert.Equal(t, 400, lrw.statusCode) - - content := "Hello world!" - - length, err := lrw.Write([]byte(content)) - if err != nil { - t.Fatal(err) - } - assert.Equal(t, length, lrw.contentLength) - assert.Equal(t, rec.Body.String(), content) -} - -func TestLogginResponseWriterWriteWithChunks(t *testing.T) { - rec := httptest.NewRecorder() - lrw := newLoggingResponseWriter(rec) - - content := "Hello world!" - contentLength := len(content) - - lrw.Header().Set("Content-Length", strconv.Itoa(contentLength)) - - length1, err := lrw.Write([]byte(content[:contentLength/2])) - if err != nil { - t.Fatal(err) - } - - length2, err := lrw.Write([]byte(content[contentLength/2:])) - if err != nil { - t.Fatal(err) - } - - assert.Equal(t, contentLength, length1+length2) - assert.Equal(t, rec.Body.String(), content) -} - -func TestLoggerKeyValueOutput(t *testing.T) { - dir := t.TempDir() - - // send logs to temp file so we can inspect it - logFile := path.Join(dir, "http_test.log") - - req, err := http.NewRequest("GET", "/ping", nil) - if err != nil { - t.Fatal(err) - } - - rec2 := httptest.NewRecorder() - - log.ApplyConfig(logging.Config{ - EncoderFormat: logging.NewEncoderFormatOption(logging.JSON), - OutputPaths: []string{logFile}, - }) - loggerMiddleware(http.HandlerFunc(pingHandler)).ServeHTTP(rec2, req) - assert.Equal(t, 200, rec2.Result().StatusCode) - - // inspect the log file - kv, err := readLog(logFile) - if err != nil { - t.Fatal(err) - } - - // check that everything is as expected - assert.Equal(t, "{\"data\":{\"response\":\"pong\"}}", rec2.Body.String()) - assert.Equal(t, "INFO", kv["level"]) - assert.Equal(t, "http", kv["logger"]) - assert.Equal(t, "Request", kv["msg"]) - assert.Equal(t, "GET", kv["Method"]) - assert.Equal(t, "/ping", kv["Path"]) - assert.Equal(t, float64(200), kv["Status"]) - assert.Equal(t, float64(28), kv["LengthBytes"]) -} - -func readLog(path string) (map[string]any, error) { - // inspect the log file - f, err := os.Open(path) - if err != nil { - return nil, errors.WithStack(err) - } - - scanner := bufio.NewScanner(f) - scanner.Scan() - logLine := scanner.Text() - - kv := make(map[string]any) - err = json.Unmarshal([]byte(logLine), &kv) - if err != nil { - return nil, errors.WithStack(err) - } - - return kv, nil -} diff --git a/api/http/request_result.go b/api/http/request_result.go deleted file mode 100644 index f5bf7912e9..0000000000 --- a/api/http/request_result.go +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2022 Democratized Data Foundation -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package http - -import "github.com/sourcenetwork/defradb/client" - -type GQLResult struct { - Errors []string `json:"errors,omitempty"` - - Data any `json:"data"` -} - -func newGQLResult(r client.GQLResult) *GQLResult { - errors := make([]string, len(r.Errors)) - for i := range r.Errors { - errors[i] = r.Errors[i].Error() - } - - return &GQLResult{ - Errors: errors, - Data: r.Data, - } -} diff --git a/api/http/router.go b/api/http/router.go deleted file mode 100644 index 2d54a16560..0000000000 --- a/api/http/router.go +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2022 Democratized Data Foundation -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package http - -import ( - "net/http" - "net/url" - "path" - "strings" - - "github.com/pkg/errors" -) - -const ( - // Version is the current version of the HTTP API. - Version string = "v0" - versionedAPIPath string = "/api/" + Version - - RootPath string = versionedAPIPath + "" - PingPath string = versionedAPIPath + "/ping" - DumpPath string = versionedAPIPath + "/debug/dump" - BlocksPath string = versionedAPIPath + "/blocks" - GraphQLPath string = versionedAPIPath + "/graphql" - SchemaPath string = versionedAPIPath + "/schema" - SchemaMigrationPath string = SchemaPath + "/migration" - IndexPath string = versionedAPIPath + "/index" - PeerIDPath string = versionedAPIPath + "/peerid" - BackupPath string = versionedAPIPath + "/backup" - ExportPath string = BackupPath + "/export" - ImportPath string = BackupPath + "/import" -) - -// playgroundHandler is set when building with the playground build tag -var playgroundHandler http.Handler - -func setRoutes(h *handler) *handler { - h.Get(RootPath, rootHandler) - h.Get(PingPath, pingHandler) - h.Get(DumpPath, dumpHandler) - h.Get(BlocksPath+"/{cid}", getBlockHandler) - h.Get(GraphQLPath, execGQLHandler) - h.Post(GraphQLPath, execGQLHandler) - h.Get(SchemaPath, listSchemaHandler) - h.Post(SchemaPath, loadSchemaHandler) - h.Patch(SchemaPath, patchSchemaHandler) - h.Post(SchemaMigrationPath, setMigrationHandler) - h.Get(SchemaMigrationPath, getMigrationHandler) - h.Post(IndexPath, createIndexHandler) - h.Delete(IndexPath, dropIndexHandler) - h.Get(IndexPath, listIndexHandler) - h.Get(PeerIDPath, peerIDHandler) - h.Post(ExportPath, exportHandler) - h.Post(ImportPath, importHandler) - h.Handle("/*", playgroundHandler) - - return h -} - -// JoinPaths takes a base path and any number of additional paths -// and combines them safely to form a full URL path. -// The base must start with a http or https. -func JoinPaths(base string, paths ...string) (*url.URL, error) { - if !strings.HasPrefix(base, "http") { - return nil, ErrSchema - } - - u, err := url.Parse(base) - if err != nil { - return nil, errors.WithStack(err) - } - - u.Path = path.Join(u.Path, strings.Join(paths, "/")) - - return u, nil -} diff --git a/api/http/router_test.go b/api/http/router_test.go deleted file mode 100644 index e43260ef43..0000000000 --- a/api/http/router_test.go +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2022 Democratized Data Foundation -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package http - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestJoinPathsWithBase(t *testing.T) { - path, err := JoinPaths("http://localhost:9181", BlocksPath, "cid_of_some_sort") - if err != nil { - t.Fatal(err) - } - - assert.Equal(t, "http://localhost:9181"+BlocksPath+"/cid_of_some_sort", path.String()) -} - -func TestJoinPathsWithNoBase(t *testing.T) { - _, err := JoinPaths("", BlocksPath, "cid_of_some_sort") - assert.ErrorIs(t, ErrSchema, err) -} - -func TestJoinPathsWithBaseWithoutHttpPrefix(t *testing.T) { - _, err := JoinPaths("localhost:9181", BlocksPath, "cid_of_some_sort") - assert.ErrorIs(t, ErrSchema, err) -} - -func TestJoinPathsWithNoPaths(t *testing.T) { - path, err := JoinPaths("http://localhost:9181") - if err != nil { - t.Fatal(err) - } - - assert.Equal(t, "http://localhost:9181", path.String()) -} - -func TestJoinPathsWithInvalidCharacter(t *testing.T) { - _, err := JoinPaths("https://%gh&%ij") - assert.Error(t, err) -} diff --git a/api/http/server.go b/api/http/server.go deleted file mode 100644 index a71dccb0ec..0000000000 --- a/api/http/server.go +++ /dev/null @@ -1,322 +0,0 @@ -// Copyright 2022 Democratized Data Foundation -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package http - -import ( - "context" - "crypto/tls" - "fmt" - "net" - "net/http" - "path" - "strings" - - "github.com/sourcenetwork/immutable" - "golang.org/x/crypto/acme/autocert" - - "github.com/sourcenetwork/defradb/client" - "github.com/sourcenetwork/defradb/config" - "github.com/sourcenetwork/defradb/errors" - "github.com/sourcenetwork/defradb/logging" -) - -const ( - // These constants are best effort durations that fit our current API - // and possibly prevent from running out of file descriptors. - // readTimeout = 5 * time.Second - // writeTimeout = 10 * time.Second - // idleTimeout = 120 * time.Second - - // Temparily disabling timeouts until [this proposal](https://github.com/golang/go/issues/54136) is merged. - // https://github.com/sourcenetwork/defradb/issues/927 - readTimeout = 0 - writeTimeout = 0 - idleTimeout = 0 -) - -const ( - httpPort = ":80" - httpsPort = ":443" -) - -// Server struct holds the Handler for the HTTP API. -type Server struct { - options serverOptions - listener net.Listener - certManager *autocert.Manager - // address that is assigned to the server on listen - address string - - http.Server -} - -type serverOptions struct { - // list of allowed origins for CORS. - allowedOrigins []string - // ID of the server node. - peerID string - // when the value is present, the server will run with tls - tls immutable.Option[tlsOptions] - // root directory for the node config. - rootDir string - // The domain for the API (optional). - domain immutable.Option[string] -} - -type tlsOptions struct { - // Public key for TLS. Ignored if domain is set. - pubKey string - // Private key for TLS. Ignored if domain is set. - privKey string - // email address for the CA to send problem notifications (optional) - email string - // specify the tls port - port string -} - -// NewServer instantiates a new server with the given http.Handler. -func NewServer(db client.DB, options ...func(*Server)) *Server { - srv := &Server{ - Server: http.Server{ - ReadTimeout: readTimeout, - WriteTimeout: writeTimeout, - IdleTimeout: idleTimeout, - }, - } - - for _, opt := range append(options, DefaultOpts()) { - opt(srv) - } - - srv.Handler = newHandler(db, srv.options) - - return srv -} - -func newHTTPRedirServer(m *autocert.Manager) *Server { - srv := &Server{ - Server: http.Server{ - ReadTimeout: readTimeout, - WriteTimeout: writeTimeout, - IdleTimeout: idleTimeout, - }, - } - - srv.Addr = httpPort - srv.Handler = m.HTTPHandler(nil) - - return srv -} - -// DefaultOpts returns the default options for the server. -func DefaultOpts() func(*Server) { - return func(s *Server) { - if s.Addr == "" { - s.Addr = "localhost:9181" - } - } -} - -// WithAllowedOrigins returns an option to set the allowed origins for CORS. -func WithAllowedOrigins(origins ...string) func(*Server) { - return func(s *Server) { - s.options.allowedOrigins = append(s.options.allowedOrigins, origins...) - } -} - -// WithAddress returns an option to set the address for the server. -func WithAddress(addr string) func(*Server) { - return func(s *Server) { - s.Addr = addr - - // If the address is not localhost, we check to see if it's a valid IP address. - // If it's not a valid IP, we assume that it's a domain name to be used with TLS. - if !strings.HasPrefix(addr, "localhost:") && !strings.HasPrefix(addr, ":") { - host, _, err := net.SplitHostPort(addr) - if err != nil { - host = addr - } - ip := net.ParseIP(host) - if ip == nil { - s.Addr = httpPort - s.options.domain = immutable.Some(host) - } - } - } -} - -// WithCAEmail returns an option to set the email address for the CA to send problem notifications. -func WithCAEmail(email string) func(*Server) { - return func(s *Server) { - tlsOpt := s.options.tls.Value() - tlsOpt.email = email - s.options.tls = immutable.Some(tlsOpt) - } -} - -// WithPeerID returns an option to set the identifier of the server node. -func WithPeerID(id string) func(*Server) { - return func(s *Server) { - s.options.peerID = id - } -} - -// WithRootDir returns an option to set the root directory for the node config. -func WithRootDir(rootDir string) func(*Server) { - return func(s *Server) { - s.options.rootDir = rootDir - } -} - -// WithSelfSignedCert returns an option to set the public and private keys for TLS. -func WithSelfSignedCert(pubKey, privKey string) func(*Server) { - return func(s *Server) { - tlsOpt := s.options.tls.Value() - tlsOpt.pubKey = pubKey - tlsOpt.privKey = privKey - s.options.tls = immutable.Some(tlsOpt) - } -} - -// WithTLS returns an option to enable TLS. -func WithTLS() func(*Server) { - return func(s *Server) { - tlsOpt := s.options.tls.Value() - tlsOpt.port = httpsPort - s.options.tls = immutable.Some(tlsOpt) - } -} - -// WithTLSPort returns an option to set the port for TLS. -func WithTLSPort(port int) func(*Server) { - return func(s *Server) { - tlsOpt := s.options.tls.Value() - tlsOpt.port = fmt.Sprintf(":%d", port) - s.options.tls = immutable.Some(tlsOpt) - } -} - -// Listen creates a new net.Listener and saves it on the receiver. -func (s *Server) Listen(ctx context.Context) error { - var err error - if s.options.tls.HasValue() { - return s.listenWithTLS(ctx) - } - - lc := net.ListenConfig{} - s.listener, err = lc.Listen(ctx, "tcp", s.Addr) - if err != nil { - return errors.WithStack(err) - } - - // Save the address on the server in case the port was set to random - // and that we want to see what was assigned. - s.address = s.listener.Addr().String() - - return nil -} - -func (s *Server) listenWithTLS(ctx context.Context) error { - cfg := &tls.Config{ - MinVersion: tls.VersionTLS12, - // We only allow cipher suites that are marked secure - // by ssllabs - CipherSuites: []uint16{ - tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, - tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, - tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, - tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, - tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - }, - ServerName: "DefraDB", - } - - if s.options.domain.HasValue() && s.options.domain.Value() != "" { - s.Addr = s.options.tls.Value().port - - if s.options.tls.Value().email == "" || s.options.tls.Value().email == config.DefaultAPIEmail { - return ErrNoEmail - } - - certCache := path.Join(s.options.rootDir, "autocerts") - - log.FeedbackInfo( - ctx, - "Generating auto certificate", - logging.NewKV("Domain", s.options.domain.Value()), - logging.NewKV("Certificate cache", certCache), - ) - - m := &autocert.Manager{ - Cache: autocert.DirCache(certCache), - Prompt: autocert.AcceptTOS, - Email: s.options.tls.Value().email, - HostPolicy: autocert.HostWhitelist(s.options.domain.Value()), - } - - cfg.GetCertificate = m.GetCertificate - - // We set manager on the server instance to later start - // a redirection server. - s.certManager = m - } else { - // When not using auto cert, we create a self signed certificate - // with the provided public and prive keys. - log.FeedbackInfo(ctx, "Generating self signed certificate") - - cert, err := tls.LoadX509KeyPair( - s.options.tls.Value().privKey, - s.options.tls.Value().pubKey, - ) - if err != nil { - return errors.WithStack(err) - } - - cfg.Certificates = []tls.Certificate{cert} - } - - var err error - s.listener, err = tls.Listen("tcp", s.Addr, cfg) - if err != nil { - return errors.WithStack(err) - } - - // Save the address on the server in case the port was set to random - // and that we want to see what was assigned. - s.address = s.listener.Addr().String() - - return nil -} - -// Run calls Serve with the receiver's listener. -func (s *Server) Run(ctx context.Context) error { - if s.listener == nil { - return ErrNoListener - } - - if s.certManager != nil { - // When using TLS it's important to redirect http requests to https - go func() { - srv := newHTTPRedirServer(s.certManager) - err := srv.ListenAndServe() - if err != nil && !errors.Is(err, http.ErrServerClosed) { - log.Info(ctx, "Something went wrong with the redirection server", logging.NewKV("Error", err)) - } - }() - } - return s.Serve(s.listener) -} - -// AssignedAddr returns the address that was assigned to the server on calls to listen. -func (s *Server) AssignedAddr() string { - return s.address -} diff --git a/cli/backup_export.go b/cli/backup_export.go index 32184bfe35..9e8d1c056e 100644 --- a/cli/backup_export.go +++ b/cli/backup_export.go @@ -11,24 +11,16 @@ package cli import ( - "bytes" - "encoding/json" - "io" - "net/http" - "os" "strings" "github.com/spf13/cobra" - httpapi "github.com/sourcenetwork/defradb/api/http" "github.com/sourcenetwork/defradb/client" - "github.com/sourcenetwork/defradb/config" - "github.com/sourcenetwork/defradb/logging" ) const jsonFileType = "json" -func MakeBackupExportCommand(cfg *config.Config) *cobra.Command { +func MakeBackupExportCommand() *cobra.Command { var collections []string var pretty bool var format string @@ -44,21 +36,14 @@ If the --pretty flag is provided, the JSON will be pretty printed. Example: export data for the 'Users' collection: defradb client export --collection Users user_data.json`, - Args: func(cmd *cobra.Command, args []string) error { - if err := cobra.ExactArgs(1)(cmd, args); err != nil { - return NewErrInvalidArgumentLength(err, 1) - } - return nil - }, - RunE: func(cmd *cobra.Command, args []string) (err error) { + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + store := mustGetStoreContext(cmd) + if !isValidExportFormat(format) { return ErrInvalidExportFormat } outputPath := args[0] - endpoint, err := httpapi.JoinPaths(cfg.API.AddressToURL(), httpapi.ExportPath) - if err != nil { - return NewErrFailedToJoinEndpoint(err) - } for i := range collections { collections[i] = strings.Trim(collections[i], " ") @@ -71,57 +56,7 @@ Example: export data for the 'Users' collection: Collections: collections, } - b, err := json.Marshal(data) - if err != nil { - return err - } - - res, err := http.Post(endpoint.String(), "application/json", bytes.NewBuffer(b)) - if err != nil { - return NewErrFailedToSendRequest(err) - } - - defer func() { - if e := res.Body.Close(); e != nil { - err = NewErrFailedToCloseResponseBody(e, err) - } - }() - - response, err := io.ReadAll(res.Body) - if err != nil { - return NewErrFailedToReadResponseBody(err) - } - - stdout, err := os.Stdout.Stat() - if err != nil { - return err - } - - if isFileInfoPipe(stdout) { - cmd.Println(string(response)) - } else { - type exportResponse struct { - Errors []struct { - Message string `json:"message"` - } `json:"errors"` - } - r := exportResponse{} - err = json.Unmarshal(response, &r) - if err != nil { - return NewErrFailedToUnmarshalResponse(err) - } - if len(r.Errors) > 0 { - log.FeedbackError(cmd.Context(), "Failed to export data", - logging.NewKV("Errors", r.Errors)) - } else if len(collections) == 1 { - log.FeedbackInfo(cmd.Context(), "Data exported for collection "+collections[0]) - } else if len(collections) > 1 { - log.FeedbackInfo(cmd.Context(), "Data exported for collections "+strings.Join(collections, ", ")) - } else { - log.FeedbackInfo(cmd.Context(), "Data exported for all collections") - } - } - return nil + return store.BasicExport(cmd.Context(), &data) }, } cmd.Flags().BoolVarP(&pretty, "pretty", "p", false, "Set the output JSON to be pretty printed") diff --git a/cli/backup_export_test.go b/cli/backup_export_test.go deleted file mode 100644 index 9539a1cdb1..0000000000 --- a/cli/backup_export_test.go +++ /dev/null @@ -1,300 +0,0 @@ -// Copyright 2023 Democratized Data Foundation -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package cli - -import ( - "context" - "encoding/json" - "os" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/sourcenetwork/defradb/client" -) - -func TestBackupExportCmd_WithNoArgument_ReturnError(t *testing.T) { - cfg := getTestConfig(t) - - dbExportCmd := MakeBackupExportCommand(cfg) - err := dbExportCmd.ValidateArgs([]string{}) - require.ErrorIs(t, err, ErrInvalidArgumentLength) -} - -func TestBackupExportCmd_WithInvalidExportFormat_ReturnError(t *testing.T) { - cfg := getTestConfig(t) - dbExportCmd := MakeBackupExportCommand(cfg) - - filepath := t.TempDir() + "/test.json" - - dbExportCmd.Flags().Set("format", "invalid") - err := dbExportCmd.RunE(dbExportCmd, []string{filepath}) - require.ErrorIs(t, err, ErrInvalidExportFormat) -} - -func TestBackupExportCmd_IfInvalidAddress_ReturnError(t *testing.T) { - cfg := getTestConfig(t) - cfg.API.Address = "invalid address" - - filepath := t.TempDir() + "/test.json" - - dbExportCmd := MakeBackupExportCommand(cfg) - err := dbExportCmd.RunE(dbExportCmd, []string{filepath}) - require.ErrorIs(t, err, NewErrFailedToJoinEndpoint(err)) -} - -func TestBackupExportCmd_WithEmptyDatastore_NoError(t *testing.T) { - cfg, _, close := startTestNode(t) - defer close() - - filepath := t.TempDir() + "/test.json" - - outputBuf, revertOutput := simulateConsoleOutput(t) - defer revertOutput() - - dbExportCmd := MakeBackupExportCommand(cfg) - err := dbExportCmd.RunE(dbExportCmd, []string{filepath}) - require.NoError(t, err) - - logLines, err := parseLines(outputBuf) - require.NoError(t, err) - require.True(t, lineHas(logLines, "msg", "Data exported for all collections")) - - b, err := os.ReadFile(filepath) - require.NoError(t, err) - - require.Len(t, b, 2) // file should be an empty json object -} - -func TestBackupExportCmd_WithInvalidCollection_ReturnError(t *testing.T) { - cfg, _, close := startTestNode(t) - defer close() - - filepath := t.TempDir() + "/test.json" - - outputBuf, revertOutput := simulateConsoleOutput(t) - defer revertOutput() - - dbExportCmd := MakeBackupExportCommand(cfg) - dbExportCmd.Flags().Set("collections", "User") - err := dbExportCmd.RunE(dbExportCmd, []string{filepath}) - require.NoError(t, err) - - logLines, err := parseLines(outputBuf) - require.NoError(t, err) - require.True(t, lineHas(logLines, "msg", "Failed to export data")) -} - -func TestBackupExportCmd_WithAllCollection_NoError(t *testing.T) { - ctx := context.Background() - - cfg, di, close := startTestNode(t) - defer close() - - _, err := di.db.AddSchema(ctx, `type User { - name: String - age: Int - }`) - require.NoError(t, err) - - doc, err := client.NewDocFromJSON([]byte(`{"name": "John", "age": 30}`)) - require.NoError(t, err) - - col, err := di.db.GetCollectionByName(ctx, "User") - require.NoError(t, err) - - err = col.Create(ctx, doc) - require.NoError(t, err) - - filepath := t.TempDir() + "/test.json" - - outputBuf, revertOutput := simulateConsoleOutput(t) - defer revertOutput() - - dbExportCmd := MakeBackupExportCommand(cfg) - err = dbExportCmd.RunE(dbExportCmd, []string{filepath}) - require.NoError(t, err) - - logLines, err := parseLines(outputBuf) - require.NoError(t, err) - require.True(t, lineHas(logLines, "msg", "Data exported for all collections")) - - b, err := os.ReadFile(filepath) - require.NoError(t, err) - - require.Equal( - t, - `{"User":[{"_key":"bae-e933420a-988a-56f8-8952-6c245aebd519","_newKey":"bae-e933420a-988a-56f8-8952-6c245aebd519","age":30,"name":"John"}]}`, - string(b), - ) -} - -func TestBackupExportCmd_WithAllCollectionAndPrettyFormating_NoError(t *testing.T) { - ctx := context.Background() - - cfg, di, close := startTestNode(t) - defer close() - - _, err := di.db.AddSchema(ctx, `type User { - name: String - age: Int - }`) - require.NoError(t, err) - - doc, err := client.NewDocFromJSON([]byte(`{"name": "John", "age": 30}`)) - require.NoError(t, err) - - col, err := di.db.GetCollectionByName(ctx, "User") - require.NoError(t, err) - - err = col.Create(ctx, doc) - require.NoError(t, err) - - filepath := t.TempDir() + "/test.json" - - outputBuf, revertOutput := simulateConsoleOutput(t) - defer revertOutput() - - dbExportCmd := MakeBackupExportCommand(cfg) - dbExportCmd.Flags().Set("pretty", "true") - err = dbExportCmd.RunE(dbExportCmd, []string{filepath}) - require.NoError(t, err) - - logLines, err := parseLines(outputBuf) - require.NoError(t, err) - require.True(t, lineHas(logLines, "msg", "Data exported for all collections")) - - b, err := os.ReadFile(filepath) - require.NoError(t, err) - - require.Equal( - t, - `{ - "User": [ - { - "_key": "bae-e933420a-988a-56f8-8952-6c245aebd519", - "_newKey": "bae-e933420a-988a-56f8-8952-6c245aebd519", - "age": 30, - "name": "John" - } - ] -}`, - string(b), - ) -} - -func TestBackupExportCmd_WithSingleCollection_NoError(t *testing.T) { - ctx := context.Background() - - cfg, di, close := startTestNode(t) - defer close() - - _, err := di.db.AddSchema(ctx, `type User { - name: String - age: Int - }`) - require.NoError(t, err) - - doc, err := client.NewDocFromJSON([]byte(`{"name": "John", "age": 30}`)) - require.NoError(t, err) - - col, err := di.db.GetCollectionByName(ctx, "User") - require.NoError(t, err) - - err = col.Create(ctx, doc) - require.NoError(t, err) - - filepath := t.TempDir() + "/test.json" - - outputBuf, revertOutput := simulateConsoleOutput(t) - defer revertOutput() - - dbExportCmd := MakeBackupExportCommand(cfg) - dbExportCmd.Flags().Set("collections", "User") - err = dbExportCmd.RunE(dbExportCmd, []string{filepath}) - require.NoError(t, err) - - logLines, err := parseLines(outputBuf) - require.NoError(t, err) - require.True(t, lineHas(logLines, "msg", "Data exported for collection User")) - - b, err := os.ReadFile(filepath) - require.NoError(t, err) - - require.Equal( - t, - `{"User":[{"_key":"bae-e933420a-988a-56f8-8952-6c245aebd519","_newKey":"bae-e933420a-988a-56f8-8952-6c245aebd519","age":30,"name":"John"}]}`, - string(b), - ) -} - -func TestBackupExportCmd_WithMultipleCollections_NoError(t *testing.T) { - ctx := context.Background() - - cfg, di, close := startTestNode(t) - defer close() - - _, err := di.db.AddSchema(ctx, `type User { - name: String - age: Int - } - - type Address { - street: String - city: String - }`) - require.NoError(t, err) - - doc1, err := client.NewDocFromJSON([]byte(`{"name": "John", "age": 30}`)) - require.NoError(t, err) - - col1, err := di.db.GetCollectionByName(ctx, "User") - require.NoError(t, err) - - err = col1.Create(ctx, doc1) - require.NoError(t, err) - - doc2, err := client.NewDocFromJSON([]byte(`{"street": "101 Maple St", "city": "Toronto"}`)) - require.NoError(t, err) - - col2, err := di.db.GetCollectionByName(ctx, "Address") - require.NoError(t, err) - - err = col2.Create(ctx, doc2) - require.NoError(t, err) - - filepath := t.TempDir() + "/test.json" - - outputBuf, revertOutput := simulateConsoleOutput(t) - defer revertOutput() - - dbExportCmd := MakeBackupExportCommand(cfg) - dbExportCmd.Flags().Set("collections", "User, Address") - err = dbExportCmd.RunE(dbExportCmd, []string{filepath}) - require.NoError(t, err) - - logLines, err := parseLines(outputBuf) - require.NoError(t, err) - require.True(t, lineHas(logLines, "msg", "Data exported for collections User, Address")) - - b, err := os.ReadFile(filepath) - require.NoError(t, err) - fileMap := map[string]any{} - err = json.Unmarshal(b, &fileMap) - require.NoError(t, err) - - expectedMap := map[string]any{} - data := []byte(`{"Address":[{"_key":"bae-8096f2c1-ea4c-5226-8ba5-17fc4b68ac1f","_newKey":"bae-8096f2c1-ea4c-5226-8ba5-17fc4b68ac1f","city":"Toronto","street":"101 Maple St"}],"User":[{"_key":"bae-e933420a-988a-56f8-8952-6c245aebd519","_newKey":"bae-e933420a-988a-56f8-8952-6c245aebd519","age":30,"name":"John"}]}`) - err = json.Unmarshal(data, &expectedMap) - require.NoError(t, err) - - require.EqualValues(t, expectedMap, fileMap) -} diff --git a/cli/backup_import.go b/cli/backup_import.go index 6802230aa0..35af345a0a 100644 --- a/cli/backup_import.go +++ b/cli/backup_import.go @@ -11,20 +11,10 @@ package cli import ( - "bytes" - "encoding/json" - "io" - "net/http" - "os" - "github.com/spf13/cobra" - - httpapi "github.com/sourcenetwork/defradb/api/http" - "github.com/sourcenetwork/defradb/config" - "github.com/sourcenetwork/defradb/logging" ) -func MakeBackupImportCommand(cfg *config.Config) *cobra.Command { +func MakeBackupImportCommand() *cobra.Command { var cmd = &cobra.Command{ Use: "import ", Short: "Import a JSON data file to the database", @@ -32,66 +22,10 @@ func MakeBackupImportCommand(cfg *config.Config) *cobra.Command { Example: import data to the database: defradb client import user_data.json`, - Args: func(cmd *cobra.Command, args []string) error { - if err := cobra.ExactArgs(1)(cmd, args); err != nil { - return NewErrInvalidArgumentLength(err, 1) - } - return nil - }, - RunE: func(cmd *cobra.Command, args []string) (err error) { - endpoint, err := httpapi.JoinPaths(cfg.API.AddressToURL(), httpapi.ImportPath) - if err != nil { - return NewErrFailedToJoinEndpoint(err) - } - - inputPath := args[0] - data := map[string]string{ - "filepath": inputPath, - } - - b, err := json.Marshal(data) - if err != nil { - return err - } - - res, err := http.Post(endpoint.String(), "application/json", bytes.NewBuffer(b)) - if err != nil { - return NewErrFailedToSendRequest(err) - } - - defer func() { - if e := res.Body.Close(); e != nil { - err = NewErrFailedToCloseResponseBody(e, err) - } - }() - - response, err := io.ReadAll(res.Body) - if err != nil { - return NewErrFailedToReadResponseBody(err) - } - - stdout, err := os.Stdout.Stat() - if err != nil { - return err - } - - if isFileInfoPipe(stdout) { - cmd.Println(string(response)) - } else { - r := indexCreateResponse{} - err = json.Unmarshal(response, &r) - if err != nil { - return NewErrFailedToUnmarshalResponse(err) - } - if len(r.Errors) > 0 { - log.FeedbackError(cmd.Context(), "Failed to import data", - logging.NewKV("Errors", r.Errors)) - } else { - log.FeedbackInfo(cmd.Context(), "Successfully imported data from file", - logging.NewKV("File", inputPath)) - } - } - return nil + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + store := mustGetStoreContext(cmd) + return store.BasicImport(cmd.Context(), args[0]) }, } return cmd diff --git a/cli/backup_import_test.go b/cli/backup_import_test.go deleted file mode 100644 index 101792dd0c..0000000000 --- a/cli/backup_import_test.go +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright 2023 Democratized Data Foundation -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package cli - -import ( - "context" - "os" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/sourcenetwork/defradb/client" -) - -func TestBackupImportCmd_WithNoArgument_ReturnError(t *testing.T) { - cfg := getTestConfig(t) - - dbImportCmd := MakeBackupImportCommand(cfg) - err := dbImportCmd.ValidateArgs([]string{}) - require.ErrorIs(t, err, ErrInvalidArgumentLength) -} - -func TestBackupImportCmd_IfInvalidAddress_ReturnError(t *testing.T) { - cfg := getTestConfig(t) - cfg.API.Address = "invalid address" - - filepath := t.TempDir() + "/test.json" - - dbImportCmd := MakeBackupImportCommand(cfg) - err := dbImportCmd.RunE(dbImportCmd, []string{filepath}) - require.ErrorIs(t, err, NewErrFailedToJoinEndpoint(err)) -} - -func TestBackupImportCmd_WithNonExistantFile_ReturnError(t *testing.T) { - cfg, _, close := startTestNode(t) - defer close() - - filepath := t.TempDir() + "/test.json" - - outputBuf, revertOutput := simulateConsoleOutput(t) - defer revertOutput() - - dbImportCmd := MakeBackupImportCommand(cfg) - err := dbImportCmd.RunE(dbImportCmd, []string{filepath}) - require.NoError(t, err) - - logLines, err := parseLines(outputBuf) - require.NoError(t, err) - require.True(t, lineHas(logLines, "msg", "Failed to import data")) -} - -func TestBackupImportCmd_WithEmptyDatastore_ReturnError(t *testing.T) { - cfg, _, close := startTestNode(t) - defer close() - - filepath := t.TempDir() + "/test.json" - - err := os.WriteFile( - filepath, - []byte(`{"User":[{"_key":"bae-e933420a-988a-56f8-8952-6c245aebd519","_newKey":"bae-e933420a-988a-56f8-8952-6c245aebd519","age":30,"name":"John"}]}`), - 0664, - ) - require.NoError(t, err) - - outputBuf, revertOutput := simulateConsoleOutput(t) - defer revertOutput() - - dbImportCmd := MakeBackupImportCommand(cfg) - err = dbImportCmd.RunE(dbImportCmd, []string{filepath}) - require.NoError(t, err) - - logLines, err := parseLines(outputBuf) - require.NoError(t, err) - require.True(t, lineHas(logLines, "msg", "Failed to import data")) -} - -func TestBackupImportCmd_WithExistingCollection_NoError(t *testing.T) { - ctx := context.Background() - - cfg, di, close := startTestNode(t) - defer close() - - _, err := di.db.AddSchema(ctx, `type User { - name: String - age: Int - }`) - require.NoError(t, err) - - filepath := t.TempDir() + "/test.json" - - err = os.WriteFile( - filepath, - []byte(`{"User":[{"_key":"bae-e933420a-988a-56f8-8952-6c245aebd519","_newKey":"bae-e933420a-988a-56f8-8952-6c245aebd519","age":30,"name":"John"}]}`), - 0664, - ) - require.NoError(t, err) - - outputBuf, revertOutput := simulateConsoleOutput(t) - defer revertOutput() - - dbImportCmd := MakeBackupImportCommand(cfg) - err = dbImportCmd.RunE(dbImportCmd, []string{filepath}) - require.NoError(t, err) - - logLines, err := parseLines(outputBuf) - require.NoError(t, err) - require.True(t, lineHas(logLines, "msg", "Successfully imported data from file")) - - col, err := di.db.GetCollectionByName(ctx, "User") - require.NoError(t, err) - - key, err := client.NewDocKeyFromString("bae-e933420a-988a-56f8-8952-6c245aebd519") - require.NoError(t, err) - doc, err := col.Get(ctx, key, false) - require.NoError(t, err) - - val, err := doc.Get("name") - require.NoError(t, err) - - require.Equal(t, "John", val.(string)) -} diff --git a/cli/blocks_get.go b/cli/blocks_get.go deleted file mode 100644 index c3519f99e7..0000000000 --- a/cli/blocks_get.go +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright 2022 Democratized Data Foundation -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package cli - -import ( - "io" - "net/http" - "os" - - "github.com/spf13/cobra" - - httpapi "github.com/sourcenetwork/defradb/api/http" - "github.com/sourcenetwork/defradb/config" -) - -func MakeBlocksGetCommand(cfg *config.Config) *cobra.Command { - var cmd = &cobra.Command{ - Use: "get [CID]", - Short: "Get a block by its CID from the blockstore", - RunE: func(cmd *cobra.Command, args []string) (err error) { - if len(args) != 1 { - return NewErrMissingArg("CID") - } - cid := args[0] - - endpoint, err := httpapi.JoinPaths(cfg.API.AddressToURL(), httpapi.BlocksPath, cid) - if err != nil { - return NewErrFailedToJoinEndpoint(err) - } - - res, err := http.Get(endpoint.String()) - if err != nil { - return NewErrFailedToSendRequest(err) - } - - defer func() { - if e := res.Body.Close(); e != nil { - err = NewErrFailedToReadResponseBody(err) - } - }() - - response, err := io.ReadAll(res.Body) - if err != nil { - return NewErrFailedToReadResponseBody(err) - } - - stdout, err := os.Stdout.Stat() - if err != nil { - return NewErrFailedToStatStdOut(err) - } - if isFileInfoPipe(stdout) { - cmd.Println(string(response)) - } else { - graphlErr, err := hasGraphQLErrors(response) - if err != nil { - return NewErrFailedToHandleGQLErrors(err) - } - indentedResult, err := indentJSON(response) - if err != nil { - return NewErrFailedToPrettyPrintResponse(err) - } - if graphlErr { - log.FeedbackError(cmd.Context(), indentedResult) - } else { - log.FeedbackInfo(cmd.Context(), indentedResult) - } - } - return nil - }, - } - return cmd -} diff --git a/cli/cli.go b/cli/cli.go index 707adbab7c..0cb9fbb5bc 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -14,176 +14,104 @@ Package cli provides the command-line interface. package cli import ( - "bufio" - "bytes" - "context" - "encoding/json" - "os" - "strings" - "github.com/spf13/cobra" "github.com/sourcenetwork/defradb/config" - "github.com/sourcenetwork/defradb/errors" "github.com/sourcenetwork/defradb/logging" ) var log = logging.MustNewLogger("cli") -const badgerDatastoreName = "badger" - -// Errors with how the command is invoked by user -var usageErrors = []string{ - // cobra errors - subject to change with new versions of cobra - "flag needs an argument", - "invalid syntax", - "unknown flag", - "unknown shorthand flag", - "unknown command", - // custom defradb errors - errMissingArg, - errMissingArgs, - errTooManyArgs, -} - -type DefraCommand struct { - RootCmd *cobra.Command - Cfg *config.Config -} - // NewDefraCommand returns the root command instanciated with its tree of subcommands. -func NewDefraCommand(cfg *config.Config) DefraCommand { - rootCmd := MakeRootCommand(cfg) - rpcCmd := MakeRPCCommand(cfg) - blocksCmd := MakeBlocksCommand() - schemaCmd := MakeSchemaCommand() - schemaMigrationCmd := MakeSchemaMigrationCommand() - indexCmd := MakeIndexCommand() - clientCmd := MakeClientCommand() - backupCmd := MakeBackupCommand() - rpcReplicatorCmd := MakeReplicatorCommand() - p2pCollectionCmd := MakeP2PCollectionCommand() - p2pCollectionCmd.AddCommand( - MakeP2PCollectionAddCommand(cfg), - MakeP2PCollectionRemoveCommand(cfg), - MakeP2PCollectionGetallCommand(cfg), +func NewDefraCommand(cfg *config.Config) *cobra.Command { + p2p_collection := MakeP2PCollectionCommand() + p2p_collection.AddCommand( + MakeP2PCollectionAddCommand(), + MakeP2PCollectionRemoveCommand(), + MakeP2PCollectionGetAllCommand(), + ) + + p2p_replicator := MakeP2PReplicatorCommand() + p2p_replicator.AddCommand( + MakeP2PReplicatorGetAllCommand(), + MakeP2PReplicatorSetCommand(), + MakeP2PReplicatorDeleteCommand(), ) - rpcReplicatorCmd.AddCommand( - MakeReplicatorGetallCommand(cfg), - MakeReplicatorSetCommand(cfg), - MakeReplicatorDeleteCommand(cfg), + + p2p := MakeP2PCommand() + p2p.AddCommand( + p2p_replicator, + p2p_collection, + MakeP2PInfoCommand(), ) - rpcCmd.AddCommand( - rpcReplicatorCmd, - p2pCollectionCmd, + + schema_migrate := MakeSchemaMigrationCommand() + schema_migrate.AddCommand( + MakeSchemaMigrationSetCommand(), + MakeSchemaMigrationGetCommand(), + MakeSchemaMigrationReloadCommand(), + MakeSchemaMigrationUpCommand(), + MakeSchemaMigrationDownCommand(), ) - blocksCmd.AddCommand( - MakeBlocksGetCommand(cfg), + + schema := MakeSchemaCommand() + schema.AddCommand( + MakeSchemaAddCommand(), + MakeSchemaPatchCommand(), + MakeSchemaSetDefaultCommand(), + schema_migrate, ) - schemaMigrationCmd.AddCommand( - MakeSchemaMigrationSetCommand(cfg), - MakeSchemaMigrationGetCommand(cfg), + + index := MakeIndexCommand() + index.AddCommand( + MakeIndexCreateCommand(), + MakeIndexDropCommand(), + MakeIndexListCommand(), ) - schemaCmd.AddCommand( - MakeSchemaAddCommand(cfg), - MakeSchemaListCommand(cfg), - MakeSchemaPatchCommand(cfg), - schemaMigrationCmd, + + backup := MakeBackupCommand() + backup.AddCommand( + MakeBackupExportCommand(), + MakeBackupImportCommand(), ) - indexCmd.AddCommand( - MakeIndexCreateCommand(cfg), - MakeIndexDropCommand(cfg), - MakeIndexListCommand(cfg), + + tx := MakeTxCommand() + tx.AddCommand( + MakeTxCreateCommand(cfg), + MakeTxCommitCommand(cfg), + MakeTxDiscardCommand(cfg), ) - backupCmd.AddCommand( - MakeBackupExportCommand(cfg), - MakeBackupImportCommand(cfg), + + collection := MakeCollectionCommand(cfg) + collection.AddCommand( + MakeCollectionGetCommand(), + MakeCollectionKeysCommand(), + MakeCollectionDeleteCommand(), + MakeCollectionUpdateCommand(), + MakeCollectionCreateCommand(), + MakeCollectionDescribeCommand(), ) - clientCmd.AddCommand( - MakeDumpCommand(cfg), - MakePingCommand(cfg), - MakeRequestCommand(cfg), - MakePeerIDCommand(cfg), - schemaCmd, - indexCmd, - rpcCmd, - blocksCmd, - backupCmd, + + client := MakeClientCommand(cfg) + client.AddCommand( + MakeDumpCommand(), + MakeRequestCommand(), + schema, + index, + p2p, + backup, + tx, + collection, ) - rootCmd.AddCommand( - clientCmd, + + root := MakeRootCommand(cfg) + root.AddCommand( + client, MakeStartCommand(cfg), MakeServerDumpCmd(cfg), MakeVersionCommand(), MakeInitCommand(cfg), ) - return DefraCommand{rootCmd, cfg} -} - -func (defraCmd *DefraCommand) Execute(ctx context.Context) error { - // Silence cobra's default output to control usage and error display. - defraCmd.RootCmd.SilenceUsage = true - defraCmd.RootCmd.SilenceErrors = true - defraCmd.RootCmd.SetOut(os.Stdout) - cmd, err := defraCmd.RootCmd.ExecuteContextC(ctx) - if err != nil { - // Intentional cancellation. - if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { - return nil - } - // User error. - for _, cobraError := range usageErrors { - if strings.HasPrefix(err.Error(), cobraError) { - log.FeedbackErrorE(ctx, "Usage error", err) - if usageErr := cmd.Usage(); usageErr != nil { - log.FeedbackFatalE(ctx, "error displaying usage help", usageErr) - } - return err - } - } - // Internal error. - log.FeedbackErrorE(ctx, "Execution error", err) - return err - } - return nil -} - -func isFileInfoPipe(fi os.FileInfo) bool { - return fi.Mode()&os.ModeNamedPipe != 0 -} - -func readStdin() (string, error) { - var s strings.Builder - scanner := bufio.NewScanner(os.Stdin) - for scanner.Scan() { - s.Write(scanner.Bytes()) - } - if err := scanner.Err(); err != nil { - return "", errors.Wrap("reading standard input", err) - } - return s.String(), nil -} - -func indentJSON(b []byte) (string, error) { - var indentedJSON bytes.Buffer - err := json.Indent(&indentedJSON, b, "", " ") - return indentedJSON.String(), err -} - -type graphqlErrors struct { - Errors any `json:"errors"` -} - -func hasGraphQLErrors(buf []byte) (bool, error) { - errs := graphqlErrors{} - err := json.Unmarshal(buf, &errs) - if err != nil { - return false, errors.Wrap("couldn't parse GraphQL response %w", err) - } - if errs.Errors != nil { - return true, nil - } else { - return false, nil - } + return root } diff --git a/cli/cli_test.go b/cli/cli_test.go deleted file mode 100644 index 877dd7b69f..0000000000 --- a/cli/cli_test.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2022 Democratized Data Foundation -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package cli - -import ( - "testing" - - "github.com/spf13/cobra" - "github.com/stretchr/testify/assert" - - "github.com/sourcenetwork/defradb/config" -) - -// Verify that the top-level commands are registered, and if particular ones have subcommands. -func TestNewDefraCommand(t *testing.T) { - expectedCommandNames := []string{ - "client", - "init", - "server-dump", - "start", - "version", - } - actualCommandNames := []string{} - r := NewDefraCommand(config.DefaultConfig()) - for _, c := range r.RootCmd.Commands() { - actualCommandNames = append(actualCommandNames, c.Name()) - } - for _, expectedCommandName := range expectedCommandNames { - assert.Contains(t, actualCommandNames, expectedCommandName) - } - for _, c := range r.RootCmd.Commands() { - if c.Name() == "client" { - assert.NotEmpty(t, c.Commands()) - } - } -} - -func TestAllHaveUsage(t *testing.T) { - cfg := config.DefaultConfig() - defra := NewDefraCommand(cfg) - walkCommandTree(t, defra.RootCmd, func(c *cobra.Command) { - assert.NotEmpty(t, c.Use) - }) -} - -func walkCommandTree(t *testing.T, cmd *cobra.Command, f func(*cobra.Command)) { - f(cmd) - for _, c := range cmd.Commands() { - walkCommandTree(t, c, f) - } -} diff --git a/cli/client.go b/cli/client.go index 2456df8d43..8866294f69 100644 --- a/cli/client.go +++ b/cli/client.go @@ -12,15 +12,27 @@ package cli import ( "github.com/spf13/cobra" + + "github.com/sourcenetwork/defradb/config" ) -func MakeClientCommand() *cobra.Command { +func MakeClientCommand(cfg *config.Config) *cobra.Command { + var txID uint64 var cmd = &cobra.Command{ Use: "client", Short: "Interact with a DefraDB node", Long: `Interact with a DefraDB node. Execute queries, add schema types, obtain node info, etc.`, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + if err := loadConfig(cfg); err != nil { + return err + } + if err := setTransactionContext(cmd, cfg, txID); err != nil { + return err + } + return setStoreContext(cmd, cfg) + }, } - + cmd.PersistentFlags().Uint64Var(&txID, "tx", 0, "Transaction ID") return cmd } diff --git a/cli/collection.go b/cli/collection.go new file mode 100644 index 0000000000..e21c29283b --- /dev/null +++ b/cli/collection.go @@ -0,0 +1,77 @@ +// Copyright 2023 Democratized Data Foundation +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +package cli + +import ( + "context" + + "github.com/spf13/cobra" + + "github.com/sourcenetwork/defradb/client" + "github.com/sourcenetwork/defradb/config" + "github.com/sourcenetwork/defradb/datastore" +) + +func MakeCollectionCommand(cfg *config.Config) *cobra.Command { + var txID uint64 + var name string + var schemaID string + var versionID string + var cmd = &cobra.Command{ + Use: "collection [--name --schema --version ]", + Short: "Interact with a collection.", + Long: `Create, read, update, and delete documents within a collection.`, + PersistentPreRunE: func(cmd *cobra.Command, args []string) (err error) { + // cobra does not chain pre run calls so we have to run them again here + if err := loadConfig(cfg); err != nil { + return err + } + if err := setTransactionContext(cmd, cfg, txID); err != nil { + return err + } + if err := setStoreContext(cmd, cfg); err != nil { + return err + } + store := mustGetStoreContext(cmd) + + var col client.Collection + switch { + case versionID != "": + col, err = store.GetCollectionByVersionID(cmd.Context(), versionID) + + case schemaID != "": + col, err = store.GetCollectionBySchemaID(cmd.Context(), schemaID) + + case name != "": + col, err = store.GetCollectionByName(cmd.Context(), name) + + default: + return nil + } + + if err != nil { + return err + } + if tx, ok := cmd.Context().Value(txContextKey).(datastore.Txn); ok { + col = col.WithTxn(tx) + } + + ctx := context.WithValue(cmd.Context(), colContextKey, col) + cmd.SetContext(ctx) + return nil + }, + } + cmd.PersistentFlags().Uint64Var(&txID, "tx", 0, "Transaction ID") + cmd.PersistentFlags().StringVar(&name, "name", "", "Collection name") + cmd.PersistentFlags().StringVar(&schemaID, "schema", "", "Collection schema ID") + cmd.PersistentFlags().StringVar(&versionID, "version", "", "Collection version ID") + return cmd +} diff --git a/cli/collection_create.go b/cli/collection_create.go new file mode 100644 index 0000000000..4dca9be33a --- /dev/null +++ b/cli/collection_create.go @@ -0,0 +1,102 @@ +// Copyright 2023 Democratized Data Foundation +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +package cli + +import ( + "encoding/json" + "io" + "os" + + "github.com/spf13/cobra" + + "github.com/sourcenetwork/defradb/client" +) + +func MakeCollectionCreateCommand() *cobra.Command { + var file string + var cmd = &cobra.Command{ + Use: "create ", + Short: "Create a new document.", + Long: `Create a new document. + +Example: create from string + defradb client collection create --name User '{ "name": "Bob" }' + +Example: create multiple from string + defradb client collection create --name User '[{ "name": "Alice" }, { "name": "Bob" }]' + +Example: create from file + defradb client collection create --name User -f document.json + +Example: create from stdin + cat document.json | defradb client collection create --name User - + `, + Args: cobra.RangeArgs(0, 1), + RunE: func(cmd *cobra.Command, args []string) error { + col, ok := tryGetCollectionContext(cmd) + if !ok { + return cmd.Usage() + } + + var docData []byte + switch { + case file != "": + data, err := os.ReadFile(file) + if err != nil { + return err + } + docData = data + case len(args) == 1 && args[0] == "-": + data, err := io.ReadAll(cmd.InOrStdin()) + if err != nil { + return err + } + docData = data + case len(args) == 1: + docData = []byte(args[0]) + default: + return ErrNoDocOrFile + } + + var docMap any + if err := json.Unmarshal(docData, &docMap); err != nil { + return err + } + + switch t := docMap.(type) { + case map[string]any: + doc, err := client.NewDocFromMap(t) + if err != nil { + return err + } + return col.Create(cmd.Context(), doc) + case []any: + docs := make([]*client.Document, len(t)) + for i, v := range t { + docMap, ok := v.(map[string]any) + if !ok { + return ErrInvalidDocument + } + doc, err := client.NewDocFromMap(docMap) + if err != nil { + return err + } + docs[i] = doc + } + return col.CreateMany(cmd.Context(), docs) + default: + return ErrInvalidDocument + } + }, + } + cmd.Flags().StringVarP(&file, "file", "f", "", "File containing document(s)") + return cmd +} diff --git a/cli/collection_delete.go b/cli/collection_delete.go new file mode 100644 index 0000000000..85539d5eb3 --- /dev/null +++ b/cli/collection_delete.go @@ -0,0 +1,78 @@ +// Copyright 2023 Democratized Data Foundation +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +package cli + +import ( + "github.com/spf13/cobra" + + "github.com/sourcenetwork/defradb/client" +) + +func MakeCollectionDeleteCommand() *cobra.Command { + var keys []string + var filter string + var cmd = &cobra.Command{ + Use: "delete [--filter --key ]", + Short: "Delete documents by key or filter.", + Long: `Delete documents by key or filter and lists the number of documents deleted. + +Example: delete by key(s) + defradb client collection delete --name User --key bae-123,bae-456 + +Example: delete by filter + defradb client collection delete --name User --filter '{ "_gte": { "points": 100 } }' + `, + RunE: func(cmd *cobra.Command, args []string) error { + col, ok := tryGetCollectionContext(cmd) + if !ok { + return cmd.Usage() + } + + switch { + case len(keys) == 1: + docKey, err := client.NewDocKeyFromString(keys[0]) + if err != nil { + return err + } + res, err := col.DeleteWithKey(cmd.Context(), docKey) + if err != nil { + return err + } + return writeJSON(cmd, res) + case len(keys) > 1: + docKeys := make([]client.DocKey, len(keys)) + for i, v := range keys { + docKey, err := client.NewDocKeyFromString(v) + if err != nil { + return err + } + docKeys[i] = docKey + } + res, err := col.DeleteWithKeys(cmd.Context(), docKeys) + if err != nil { + return err + } + return writeJSON(cmd, res) + case filter != "": + res, err := col.DeleteWithFilter(cmd.Context(), filter) + if err != nil { + return err + } + return writeJSON(cmd, res) + default: + return ErrNoDocKeyOrFilter + } + }, + } + cmd.Flags().StringSliceVar(&keys, "key", nil, "Document key") + cmd.Flags().StringVar(&filter, "filter", "", "Document filter") + return cmd +} diff --git a/cli/collection_describe.go b/cli/collection_describe.go new file mode 100644 index 0000000000..1d6ee55821 --- /dev/null +++ b/cli/collection_describe.go @@ -0,0 +1,57 @@ +// Copyright 2023 Democratized Data Foundation +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +package cli + +import ( + "github.com/spf13/cobra" + + "github.com/sourcenetwork/defradb/client" +) + +func MakeCollectionDescribeCommand() *cobra.Command { + var cmd = &cobra.Command{ + Use: "describe", + Short: "View collection description.", + Long: `Introspect collection types. + +Example: view all collections + defradb client collection describe + +Example: view collection by name + defradb client collection describe --name User + +Example: view collection by schema id + defradb client collection describe --schema bae123 + +Example: view collection by version id + defradb client collection describe --version bae123 + `, + RunE: func(cmd *cobra.Command, args []string) error { + store := mustGetStoreContext(cmd) + + col, ok := tryGetCollectionContext(cmd) + if ok { + return writeJSON(cmd, col.Description()) + } + // if no collection specified list all collections + cols, err := store.GetAllCollections(cmd.Context()) + if err != nil { + return err + } + colDesc := make([]client.CollectionDescription, len(cols)) + for i, col := range cols { + colDesc[i] = col.Description() + } + return writeJSON(cmd, colDesc) + }, + } + return cmd +} diff --git a/cli/collection_get.go b/cli/collection_get.go new file mode 100644 index 0000000000..d908bbdb7a --- /dev/null +++ b/cli/collection_get.go @@ -0,0 +1,53 @@ +// Copyright 2023 Democratized Data Foundation +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +package cli + +import ( + "github.com/spf13/cobra" + + "github.com/sourcenetwork/defradb/client" +) + +func MakeCollectionGetCommand() *cobra.Command { + var showDeleted bool + var cmd = &cobra.Command{ + Use: "get [--show-deleted]", + Short: "View document fields.", + Long: `View document fields. + +Example: + defradb client collection get --name User bae-123 + `, + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + col, ok := tryGetCollectionContext(cmd) + if !ok { + return cmd.Usage() + } + + docKey, err := client.NewDocKeyFromString(args[0]) + if err != nil { + return err + } + doc, err := col.Get(cmd.Context(), docKey, showDeleted) + if err != nil { + return err + } + docMap, err := doc.ToMap() + if err != nil { + return err + } + return writeJSON(cmd, docMap) + }, + } + cmd.Flags().BoolVar(&showDeleted, "show-deleted", false, "Show deleted documents") + return cmd +} diff --git a/cli/collection_keys.go b/cli/collection_keys.go new file mode 100644 index 0000000000..a453c16a86 --- /dev/null +++ b/cli/collection_keys.go @@ -0,0 +1,53 @@ +// Copyright 2023 Democratized Data Foundation +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +package cli + +import ( + "github.com/spf13/cobra" + + "github.com/sourcenetwork/defradb/http" +) + +func MakeCollectionKeysCommand() *cobra.Command { + var cmd = &cobra.Command{ + Use: "keys", + Short: "List all document keys.", + Long: `List all document keys. + +Example: + defradb client collection keys --name User + `, + RunE: func(cmd *cobra.Command, args []string) error { + col, ok := tryGetCollectionContext(cmd) + if !ok { + return cmd.Usage() + } + + docCh, err := col.GetAllDocKeys(cmd.Context()) + if err != nil { + return err + } + for docKey := range docCh { + results := &http.DocKeyResult{ + Key: docKey.Key.String(), + } + if docKey.Err != nil { + results.Error = docKey.Err.Error() + } + if err := writeJSON(cmd, results); err != nil { + return err + } + } + return nil + }, + } + return cmd +} diff --git a/cli/collection_update.go b/cli/collection_update.go new file mode 100644 index 0000000000..317a2e8119 --- /dev/null +++ b/cli/collection_update.go @@ -0,0 +1,99 @@ +// Copyright 2023 Democratized Data Foundation +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +package cli + +import ( + "github.com/spf13/cobra" + + "github.com/sourcenetwork/defradb/client" +) + +func MakeCollectionUpdateCommand() *cobra.Command { + var keys []string + var filter string + var updater string + var cmd = &cobra.Command{ + Use: "update [--filter --key --updater ] ", + Short: "Update documents by key or filter.", + Long: `Update documents by key or filter. + +Example: update from string + defradb client collection update --name User --key bae-123 '{ "name": "Bob" }' + +Example: update by filter + defradb client collection update --name User \ + --filter '{ "_gte": { "points": 100 } }' --updater '{ "verified": true }' + +Example: update by keys + defradb client collection update --name User \ + --key bae-123,bae-456 --updater '{ "verified": true }' + `, + Args: cobra.RangeArgs(0, 1), + RunE: func(cmd *cobra.Command, args []string) error { + col, ok := tryGetCollectionContext(cmd) + if !ok { + return cmd.Usage() + } + + switch { + case len(keys) == 1 && updater != "": + docKey, err := client.NewDocKeyFromString(keys[0]) + if err != nil { + return err + } + res, err := col.UpdateWithKey(cmd.Context(), docKey, updater) + if err != nil { + return err + } + return writeJSON(cmd, res) + case len(keys) > 1 && updater != "": + docKeys := make([]client.DocKey, len(keys)) + for i, v := range keys { + docKey, err := client.NewDocKeyFromString(v) + if err != nil { + return err + } + docKeys[i] = docKey + } + res, err := col.UpdateWithKeys(cmd.Context(), docKeys, updater) + if err != nil { + return err + } + return writeJSON(cmd, res) + case filter != "" && updater != "": + res, err := col.UpdateWithFilter(cmd.Context(), filter, updater) + if err != nil { + return err + } + return writeJSON(cmd, res) + case len(keys) == 1 && len(args) == 1: + docKey, err := client.NewDocKeyFromString(keys[0]) + if err != nil { + return err + } + doc, err := col.Get(cmd.Context(), docKey, true) + if err != nil { + return err + } + if err := doc.SetWithJSON([]byte(args[0])); err != nil { + return err + } + return col.Update(cmd.Context(), doc) + default: + return ErrNoDocKeyOrFilter + } + }, + } + cmd.Flags().StringSliceVar(&keys, "key", nil, "Document key") + cmd.Flags().StringVar(&filter, "filter", "", "Document filter") + cmd.Flags().StringVar(&updater, "updater", "", "Document updater") + return cmd +} diff --git a/cli/dump.go b/cli/dump.go index f35e9232b1..a3d155605b 100644 --- a/cli/dump.go +++ b/cli/dump.go @@ -11,69 +11,18 @@ package cli import ( - "encoding/json" - "io" - "net/http" - "os" - "github.com/spf13/cobra" - httpapi "github.com/sourcenetwork/defradb/api/http" - "github.com/sourcenetwork/defradb/config" - "github.com/sourcenetwork/defradb/errors" + "github.com/sourcenetwork/defradb/client" ) -func MakeDumpCommand(cfg *config.Config) *cobra.Command { +func MakeDumpCommand() *cobra.Command { var cmd = &cobra.Command{ Use: "dump", Short: "Dump the contents of DefraDB node-side", RunE: func(cmd *cobra.Command, _ []string) (err error) { - stdout, err := os.Stdout.Stat() - if err != nil { - return errors.Wrap("failed to stat stdout", err) - } - if !isFileInfoPipe(stdout) { - log.FeedbackInfo(cmd.Context(), "Requesting the database to dump its state, server-side...") - } - - endpoint, err := httpapi.JoinPaths(cfg.API.AddressToURL(), httpapi.DumpPath) - if err != nil { - return errors.Wrap("failed to join endpoint", err) - } - - res, err := http.Get(endpoint.String()) - if err != nil { - return errors.Wrap("failed dump request", err) - } - - defer func() { - if e := res.Body.Close(); e != nil { - err = NewErrFailedToCloseResponseBody(e, err) - } - }() - - response, err := io.ReadAll(res.Body) - if err != nil { - return errors.Wrap("failed to read response body", err) - } - - if isFileInfoPipe(stdout) { - cmd.Println(string(response)) - } else { - // dumpResponse follows structure of HTTP API's response - type dumpResponse struct { - Data struct { - Response string `json:"response"` - } `json:"data"` - } - r := dumpResponse{} - err = json.Unmarshal(response, &r) - if err != nil { - return errors.Wrap("failed parsing of response", err) - } - log.FeedbackInfo(cmd.Context(), r.Data.Response) - } - return nil + db := cmd.Context().Value(dbContextKey).(client.DB) + return db.PrintDump(cmd.Context()) }, } return cmd diff --git a/cli/errors.go b/cli/errors.go index 17e4819a8b..a7d6cbd26b 100644 --- a/cli/errors.go +++ b/cli/errors.go @@ -11,133 +11,20 @@ package cli import ( - "strings" - "github.com/sourcenetwork/defradb/errors" ) -const ( - errMissingArg string = "missing argument" - errMissingArgs string = "missing arguments" - errTooManyArgs string = "too many arguments" - errEmptyStdin string = "empty stdin" - errEmptyFile string = "empty file" - errFailedToReadFile string = "failed to read file" - errFailedToReadStdin string = "failed to read stdin" - errFailedToCreateRPCClient string = "failed to create RPC client" - errFailedToAddReplicator string = "failed to add replicator, request failed" - errFailedToJoinEndpoint string = "failed to join endpoint" - errFailedToSendRequest string = "failed to send request" - errFailedToReadResponseBody string = "failed to read response body" - errFailedToCloseResponseBody string = "failed to close response body" - errFailedToStatStdOut string = "failed to stat stdout" - errFailedToHandleGQLErrors string = "failed to handle GraphQL errors" - errFailedToPrettyPrintResponse string = "failed to pretty print response" - errFailedToUnmarshalResponse string = "failed to unmarshal response" - errFailedParsePeerID string = "failed to parse PeerID" - errFailedToMarshalData string = "failed to marshal data" - errInvalidArgumentLength string = "invalid argument length" -) +const errInvalidLensConfig = "invalid lens configuration" -// Errors returnable from this package. -// -// This list is incomplete and undefined errors may also be returned. -// Errors returned from this package may be tested against these errors with errors.Is. var ( - ErrMissingArg = errors.New(errMissingArg) - ErrMissingArgs = errors.New(errMissingArgs) - ErrTooManyArgs = errors.New(errTooManyArgs) - ErrEmptyFile = errors.New(errEmptyFile) - ErrEmptyStdin = errors.New(errEmptyStdin) - ErrFailedToReadFile = errors.New(errFailedToReadFile) - ErrFailedToReadStdin = errors.New(errFailedToReadStdin) - ErrFailedToCreateRPCClient = errors.New(errFailedToCreateRPCClient) - ErrFailedToAddReplicator = errors.New(errFailedToAddReplicator) - ErrFailedToJoinEndpoint = errors.New(errFailedToJoinEndpoint) - ErrFailedToSendRequest = errors.New(errFailedToSendRequest) - ErrFailedToReadResponseBody = errors.New(errFailedToReadResponseBody) - ErrFailedToStatStdOut = errors.New(errFailedToStatStdOut) - ErrFailedToHandleGQLErrors = errors.New(errFailedToHandleGQLErrors) - ErrFailedToPrettyPrintResponse = errors.New(errFailedToPrettyPrintResponse) - ErrFailedToUnmarshalResponse = errors.New(errFailedToUnmarshalResponse) - ErrFailedParsePeerID = errors.New(errFailedParsePeerID) - ErrInvalidExportFormat = errors.New("invalid export format") - ErrInvalidArgumentLength = errors.New(errInvalidArgumentLength) + ErrNoDocOrFile = errors.New("document or file must be defined") + ErrInvalidDocument = errors.New("invalid document") + ErrNoDocKeyOrFilter = errors.New("document key or filter must be defined") + ErrInvalidExportFormat = errors.New("invalid export format") + ErrNoLensConfig = errors.New("lens config cannot be empty") + ErrInvalidLensConfig = errors.New("invalid lens configuration") ) -func NewErrMissingArg(name string) error { - return errors.New(errMissingArg, errors.NewKV("Name", name)) -} - -func NewErrMissingArgs(names []string) error { - return errors.New(errMissingArgs, errors.NewKV("Required", strings.Join(names, ", "))) -} - -func NewErrTooManyArgs(max, actual int) error { - return errors.New(errTooManyArgs, errors.NewKV("Max", max), errors.NewKV("Actual", actual)) -} - -func NewFailedToReadFile(inner error) error { - return errors.Wrap(errFailedToReadFile, inner) -} - -func NewFailedToReadStdin(inner error) error { - return errors.Wrap(errFailedToReadStdin, inner) -} - -func NewErrFailedToCreateRPCClient(inner error) error { - return errors.Wrap(errFailedToCreateRPCClient, inner) -} - -func NewErrFailedToAddReplicator(inner error) error { - return errors.Wrap(errFailedToAddReplicator, inner) -} - -func NewErrFailedToJoinEndpoint(inner error) error { - return errors.Wrap(errFailedToJoinEndpoint, inner) -} - -func NewErrFailedToSendRequest(inner error) error { - return errors.Wrap(errFailedToSendRequest, inner) -} - -func NewErrFailedToReadResponseBody(inner error) error { - return errors.Wrap(errFailedToReadResponseBody, inner) -} - -func NewErrFailedToCloseResponseBody(closeErr, other error) error { - if other != nil { - return errors.Wrap(errFailedToCloseResponseBody, closeErr, errors.NewKV("Other error", other)) - } - return errors.Wrap(errFailedToCloseResponseBody, closeErr) -} - -func NewErrFailedToStatStdOut(inner error) error { - return errors.Wrap(errFailedToStatStdOut, inner) -} - -func NewErrFailedToHandleGQLErrors(inner error) error { - return errors.Wrap(errFailedToHandleGQLErrors, inner) -} - -func NewErrFailedToPrettyPrintResponse(inner error) error { - return errors.Wrap(errFailedToPrettyPrintResponse, inner) -} - -func NewErrFailedToUnmarshalResponse(inner error) error { - return errors.Wrap(errFailedToUnmarshalResponse, inner) -} - -func NewErrFailedParsePeerID(inner error) error { - return errors.Wrap(errFailedParsePeerID, inner) -} - -// NewFailedToMarshalData returns an error indicating that a there was a problem with mashalling. -func NewFailedToMarshalData(inner error) error { - return errors.Wrap(errFailedToMarshalData, inner) -} - -// NewErrInvalidArgumentLength returns an error indicating an incorrect number of arguments. -func NewErrInvalidArgumentLength(inner error, expected int) error { - return errors.Wrap(errInvalidArgumentLength, inner, errors.NewKV("Expected", expected)) +func NewErrInvalidLensConfig(inner error) error { + return errors.Wrap(errInvalidLensConfig, inner) } diff --git a/cli/index_create.go b/cli/index_create.go index a91a76d2d0..42866267fc 100644 --- a/cli/index_create.go +++ b/cli/index_create.go @@ -11,33 +11,16 @@ package cli import ( - "bytes" - "encoding/json" - "io" - "net/http" - "os" - "github.com/spf13/cobra" - httpapi "github.com/sourcenetwork/defradb/api/http" "github.com/sourcenetwork/defradb/client" - "github.com/sourcenetwork/defradb/config" - "github.com/sourcenetwork/defradb/logging" + "github.com/sourcenetwork/defradb/datastore" ) -type indexCreateResponse struct { - Data struct { - Index client.IndexDescription `json:"index"` - } `json:"data"` - Errors []struct { - Message string `json:"message"` - } `json:"errors"` -} - -func MakeIndexCreateCommand(cfg *config.Config) *cobra.Command { +func MakeIndexCreateCommand() *cobra.Command { var collectionArg string var nameArg string - var fieldsArg string + var fieldsArg []string var cmd = &cobra.Command{ Use: "create -c --collection --fields [-n --name ]", Short: "Creates a secondary index on a collection's field(s)", @@ -51,75 +34,34 @@ Example: create an index for 'Users' collection on 'name' field: Example: create a named index for 'Users' collection on 'name' field: defradb client index create --collection Users --fields name --name UsersByName`, ValidArgs: []string{"collection", "fields", "name"}, - RunE: func(cmd *cobra.Command, args []string) (err error) { - if collectionArg == "" || fieldsArg == "" { - if collectionArg == "" { - return NewErrMissingArg("collection") - } else { - return NewErrMissingArg("fields") - } - } + RunE: func(cmd *cobra.Command, args []string) error { + store := mustGetStoreContext(cmd) - endpoint, err := httpapi.JoinPaths(cfg.API.AddressToURL(), httpapi.IndexPath) - if err != nil { - return NewErrFailedToJoinEndpoint(err) - } - - data := map[string]string{ - "collection": collectionArg, - "fields": fieldsArg, + var fields []client.IndexedFieldDescription + for _, name := range fieldsArg { + fields = append(fields, client.IndexedFieldDescription{Name: name}) } - if nameArg != "" { - data["name"] = nameArg + desc := client.IndexDescription{ + Name: nameArg, + Fields: fields, } - - jsonData, err := json.Marshal(data) + col, err := store.GetCollectionByName(cmd.Context(), collectionArg) if err != nil { return err } - - res, err := http.Post(endpoint.String(), "application/json", bytes.NewBuffer(jsonData)) - if err != nil { - return NewErrFailedToSendRequest(err) - } - defer func() { - if e := res.Body.Close(); e != nil { - err = NewErrFailedToCloseResponseBody(e, err) - } - }() - - response, err := io.ReadAll(res.Body) - if err != nil { - return NewErrFailedToReadResponseBody(err) + if tx, ok := cmd.Context().Value(txContextKey).(datastore.Txn); ok { + col = col.WithTxn(tx) } - - stdout, err := os.Stdout.Stat() + desc, err = col.CreateIndex(cmd.Context(), desc) if err != nil { return err } - - if isFileInfoPipe(stdout) { - cmd.Println(string(response)) - } else { - r := indexCreateResponse{} - err = json.Unmarshal(response, &r) - if err != nil { - return NewErrFailedToUnmarshalResponse(err) - } - if len(r.Errors) > 0 { - log.FeedbackError(cmd.Context(), "Failed to create index", - logging.NewKV("Errors", r.Errors)) - } else { - log.FeedbackInfo(cmd.Context(), "Successfully created index", - logging.NewKV("Index", r.Data.Index)) - } - } - return nil + return writeJSON(cmd, desc) }, } cmd.Flags().StringVarP(&collectionArg, "collection", "c", "", "Collection name") cmd.Flags().StringVarP(&nameArg, "name", "n", "", "Index name") - cmd.Flags().StringVar(&fieldsArg, "fields", "", "Fields to index") + cmd.Flags().StringSliceVar(&fieldsArg, "fields", []string{}, "Fields to index") return cmd } diff --git a/cli/index_create_test.go b/cli/index_create_test.go deleted file mode 100644 index ac75248c10..0000000000 --- a/cli/index_create_test.go +++ /dev/null @@ -1,244 +0,0 @@ -// Copyright 2023 Democratized Data Foundation -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package cli - -import ( - "bufio" - "bytes" - "context" - "encoding/json" - "io" - "os" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/sourcenetwork/defradb/config" - "github.com/sourcenetwork/defradb/logging" -) - -const randomMultiaddr = "/ip4/0.0.0.0/tcp/0" - -func getTestConfig(t *testing.T) *config.Config { - cfg := config.DefaultConfig() - dir := t.TempDir() - cfg.Datastore.Store = "memory" - cfg.Datastore.Badger.Path = dir - cfg.Net.P2PDisabled = false - cfg.Net.P2PAddress = randomMultiaddr - cfg.Net.RPCAddress = "0.0.0.0:0" - cfg.Net.TCPAddress = randomMultiaddr - cfg.API.Address = "0.0.0.0:0" - return cfg -} - -func startTestNode(t *testing.T) (*config.Config, *defraInstance, func()) { - cfg := getTestConfig(t) - - ctx := context.Background() - di, err := start(ctx, cfg) - require.NoError(t, err) - return cfg, di, func() { di.close(ctx) } -} - -func parseLines(r io.Reader) ([]map[string]any, error) { - fileScanner := bufio.NewScanner(r) - - fileScanner.Split(bufio.ScanLines) - - logLines := []map[string]any{} - for fileScanner.Scan() { - loggedLine := make(map[string]any) - err := json.Unmarshal(fileScanner.Bytes(), &loggedLine) - if err != nil { - return nil, err - } - logLines = append(logLines, loggedLine) - } - - return logLines, nil -} - -func lineHas(lines []map[string]any, key, value string) bool { - for _, line := range lines { - if line[key] == value { - return true - } - } - return false -} - -func simulateConsoleOutput(t *testing.T) (*bytes.Buffer, func()) { - b := &bytes.Buffer{} - log.ApplyConfig(logging.Config{ - EncoderFormat: logging.NewEncoderFormatOption(logging.JSON), - Pipe: b, - }) - - f, err := os.CreateTemp(t.TempDir(), "tmpFile") - require.NoError(t, err) - originalStdout := os.Stdout - os.Stdout = f - - return b, func() { - os.Stdout = originalStdout - f.Close() - os.Remove(f.Name()) - } -} - -func execAddSchemaCmd(t *testing.T, cfg *config.Config, schema string) { - addSchemaCmd := MakeSchemaAddCommand(cfg) - err := addSchemaCmd.RunE(addSchemaCmd, []string{schema}) - require.NoError(t, err) -} - -func execCreateIndexCmd(t *testing.T, cfg *config.Config, collection, fields, name string) { - indexCreateCmd := MakeIndexCreateCommand(cfg) - indexCreateCmd.SetArgs([]string{ - "--collection", collection, - "--fields", fields, - "--name", name, - }) - err := indexCreateCmd.Execute() - require.NoError(t, err) -} - -func hasLogWithKey(logLines []map[string]any, key string) bool { - for _, logLine := range logLines { - if _, ok := logLine[key]; ok { - return true - } - } - return false -} - -func TestIndexCreateCmd_IfInvalidAddress_ReturnError(t *testing.T) { - cfg := getTestConfig(t) - cfg.API.Address = "invalid address" - indexCreateCmd := MakeIndexCreateCommand(cfg) - - indexCreateCmd.SetArgs([]string{ - "--collection", "User", - "--fields", "Name", - "--name", "users_name_index", - }) - err := indexCreateCmd.Execute() - require.ErrorIs(t, err, NewErrFailedToJoinEndpoint(err)) -} - -func TestIndexCreateCmd_IfNoCollection_ReturnError(t *testing.T) { - cfg, _, close := startTestNode(t) - defer close() - indexCreateCmd := MakeIndexCreateCommand(cfg) - - outputBuf := bytes.NewBufferString("") - indexCreateCmd.SetOut(outputBuf) - - indexCreateCmd.SetArgs([]string{ - "--collection", "User", - "--fields", "Name", - "--name", "users_name_index", - }) - err := indexCreateCmd.Execute() - require.NoError(t, err) - - out, err := io.ReadAll(outputBuf) - require.NoError(t, err) - - r := make(map[string]any) - err = json.Unmarshal(out, &r) - require.NoError(t, err) - - _, hasErrors := r["errors"] - assert.True(t, hasErrors, "command should return error") -} - -func TestIndexCreateCmd_IfNoErrors_ReturnData(t *testing.T) { - cfg, _, close := startTestNode(t) - defer close() - - execAddSchemaCmd(t, cfg, `type User { name: String }`) - - indexCreateCmd := MakeIndexCreateCommand(cfg) - outputBuf := bytes.NewBufferString("") - indexCreateCmd.SetOut(outputBuf) - - indexCreateCmd.SetArgs([]string{ - "--collection", "User", - "--fields", "name", - "--name", "users_name_index", - }) - err := indexCreateCmd.Execute() - require.NoError(t, err) - - out, err := io.ReadAll(outputBuf) - require.NoError(t, err) - - r := make(map[string]any) - err = json.Unmarshal(out, &r) - require.NoError(t, err) - - _, hasData := r["data"] - assert.True(t, hasData, "command should return data") -} - -func TestIndexCreateCmd_WithConsoleOutputIfNoCollection_ReturnError(t *testing.T) { - cfg, _, close := startTestNode(t) - defer close() - indexCreateCmd := MakeIndexCreateCommand(cfg) - indexCreateCmd.SetArgs([]string{ - "--collection", "User", - "--fields", "Name", - "--name", "users_name_index", - }) - - outputBuf, revertOutput := simulateConsoleOutput(t) - defer revertOutput() - - err := indexCreateCmd.Execute() - require.NoError(t, err) - - logLines, err := parseLines(outputBuf) - require.NoError(t, err) - assert.True(t, hasLogWithKey(logLines, "Errors")) -} - -func TestIndexCreateCmd_WithConsoleOutputIfNoErrors_ReturnData(t *testing.T) { - cfg, _, close := startTestNode(t) - defer close() - - execAddSchemaCmd(t, cfg, `type User { name: String }`) - - const indexName = "users_name_index" - indexCreateCmd := MakeIndexCreateCommand(cfg) - indexCreateCmd.SetArgs([]string{ - "--collection", "User", - "--fields", "name", - "--name", indexName, - }) - - outputBuf, revertOutput := simulateConsoleOutput(t) - defer revertOutput() - - err := indexCreateCmd.Execute() - require.NoError(t, err) - - logLines, err := parseLines(outputBuf) - require.NoError(t, err) - require.Len(t, logLines, 1) - result, ok := logLines[0]["Index"].(map[string]any) - require.True(t, ok) - assert.Equal(t, indexName, result["Name"]) - - assert.False(t, hasLogWithKey(logLines, "Errors")) -} diff --git a/cli/index_drop.go b/cli/index_drop.go index ef0a37db0c..03639fb277 100644 --- a/cli/index_drop.go +++ b/cli/index_drop.go @@ -11,29 +11,12 @@ package cli import ( - "bytes" - "encoding/json" - "io" - "net/http" - "os" - "github.com/spf13/cobra" - httpapi "github.com/sourcenetwork/defradb/api/http" - "github.com/sourcenetwork/defradb/config" - "github.com/sourcenetwork/defradb/logging" + "github.com/sourcenetwork/defradb/datastore" ) -type indexDropResponse struct { - Data struct { - Result string `json:"result"` - } `json:"data"` - Errors []struct { - Message string `json:"message"` - } `json:"errors"` -} - -func MakeIndexDropCommand(cfg *config.Config) *cobra.Command { +func MakeIndexDropCommand() *cobra.Command { var collectionArg string var nameArg string var cmd = &cobra.Command{ @@ -44,74 +27,17 @@ func MakeIndexDropCommand(cfg *config.Config) *cobra.Command { Example: drop the index 'UsersByName' for 'Users' collection: defradb client index create --collection Users --name UsersByName`, ValidArgs: []string{"collection", "name"}, - RunE: func(cmd *cobra.Command, args []string) (err error) { - if collectionArg == "" || nameArg == "" { - if collectionArg == "" { - return NewErrMissingArg("collection") - } else { - return NewErrMissingArg("name") - } - } - - endpoint, err := httpapi.JoinPaths(cfg.API.AddressToURL(), httpapi.IndexPath) - if err != nil { - return NewErrFailedToJoinEndpoint(err) - } - - data := map[string]string{ - "collection": collectionArg, - "name": nameArg, - } + RunE: func(cmd *cobra.Command, args []string) error { + store := mustGetStoreContext(cmd) - jsonData, err := json.Marshal(data) + col, err := store.GetCollectionByName(cmd.Context(), collectionArg) if err != nil { return err } - - req, err := http.NewRequest("DELETE", endpoint.String(), bytes.NewBuffer(jsonData)) - if err != nil { - return NewErrFailedToSendRequest(err) - } - req.Header.Add("Content-Type", "application/json") - client := &http.Client{} - res, err := client.Do(req) - if err != nil { - return NewErrFailedToSendRequest(err) - } - - defer func() { - if e := res.Body.Close(); e != nil { - err = NewErrFailedToCloseResponseBody(e, err) - } - }() - - response, err := io.ReadAll(res.Body) - if err != nil { - return NewErrFailedToReadResponseBody(err) - } - - stdout, err := os.Stdout.Stat() - if err != nil { - return err - } - - if isFileInfoPipe(stdout) { - cmd.Println(string(response)) - } else { - r := indexDropResponse{} - err = json.Unmarshal(response, &r) - if err != nil { - return NewErrFailedToUnmarshalResponse(err) - } - if len(r.Errors) > 0 { - log.FeedbackError(cmd.Context(), "Failed to drop index", - logging.NewKV("Errors", r.Errors)) - } else { - log.FeedbackInfo(cmd.Context(), "Successfully dropped index", - logging.NewKV("Result", r.Data.Result)) - } + if tx, ok := cmd.Context().Value(txContextKey).(datastore.Txn); ok { + col = col.WithTxn(tx) } - return nil + return col.DropIndex(cmd.Context(), nameArg) }, } cmd.Flags().StringVarP(&collectionArg, "collection", "c", "", "Collection name") diff --git a/cli/index_drop_test.go b/cli/index_drop_test.go deleted file mode 100644 index 7fa368a458..0000000000 --- a/cli/index_drop_test.go +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright 2023 Democratized Data Foundation -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package cli - -import ( - "bytes" - "encoding/json" - "io" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestIndexDropCmd_IfInvalidAddress_ReturnError(t *testing.T) { - cfg := getTestConfig(t) - cfg.API.Address = "invalid address" - indexDropCmd := MakeIndexDropCommand(cfg) - - indexDropCmd.SetArgs([]string{"--collection", "User", "--name", "users_name_index"}) - err := indexDropCmd.Execute() - require.ErrorIs(t, err, NewErrFailedToJoinEndpoint(err)) -} - -func TestIndexDropCmd_IfNoCollection_ReturnError(t *testing.T) { - cfg, _, close := startTestNode(t) - defer close() - indexDropCmd := MakeIndexDropCommand(cfg) - - outputBuf := bytes.NewBufferString("") - indexDropCmd.SetOut(outputBuf) - - indexDropCmd.SetArgs([]string{"--collection", "User", "--name", "users_name_index"}) - err := indexDropCmd.Execute() - require.NoError(t, err) - - out, err := io.ReadAll(outputBuf) - require.NoError(t, err) - - r := make(map[string]any) - err = json.Unmarshal(out, &r) - require.NoError(t, err) - - _, hasErrors := r["errors"] - assert.True(t, hasErrors, "command should return error") -} - -func TestIndexDropCmd_IfNoErrors_ShouldReturnData(t *testing.T) { - cfg, _, close := startTestNode(t) - defer close() - - execAddSchemaCmd(t, cfg, `type User { name: String }`) - execCreateIndexCmd(t, cfg, "User", "name", "users_name_index") - - indexDropCmd := MakeIndexDropCommand(cfg) - outputBuf := bytes.NewBufferString("") - indexDropCmd.SetOut(outputBuf) - - indexDropCmd.SetArgs([]string{"--collection", "User", "--name", "users_name_index"}) - err := indexDropCmd.Execute() - require.NoError(t, err) - - out, err := io.ReadAll(outputBuf) - require.NoError(t, err) - - r := make(map[string]any) - err = json.Unmarshal(out, &r) - require.NoError(t, err) - - _, hasData := r["data"] - assert.True(t, hasData, "command should return data") -} - -func TestIndexDropCmd_WithConsoleOutputIfNoCollection_ReturnError(t *testing.T) { - cfg, _, close := startTestNode(t) - defer close() - indexDropCmd := MakeIndexDropCommand(cfg) - - outputBuf, revertOutput := simulateConsoleOutput(t) - defer revertOutput() - - indexDropCmd.SetArgs([]string{"--collection", "User", "--name", "users_name_index"}) - err := indexDropCmd.Execute() - require.NoError(t, err) - - logLines, err := parseLines(outputBuf) - require.NoError(t, err) - assert.True(t, hasLogWithKey(logLines, "Errors")) -} - -func TestIndexDropCmd_WithConsoleOutputIfNoErrors_ShouldReturnData(t *testing.T) { - cfg, _, close := startTestNode(t) - defer close() - - execAddSchemaCmd(t, cfg, `type User { name: String }`) - execCreateIndexCmd(t, cfg, "User", "name", "users_name_index") - - indexDropCmd := MakeIndexDropCommand(cfg) - indexDropCmd.SetArgs([]string{"--collection", "User", "--name", "users_name_index"}) - - outputBuf, revertOutput := simulateConsoleOutput(t) - defer revertOutput() - - err := indexDropCmd.Execute() - require.NoError(t, err) - - logLines, err := parseLines(outputBuf) - require.NoError(t, err) - require.Len(t, logLines, 1) - assert.Equal(t, "success", logLines[0]["Result"]) - - assert.False(t, hasLogWithKey(logLines, "Errors")) -} diff --git a/cli/index_list.go b/cli/index_list.go index 131782cfe5..92ada3e007 100644 --- a/cli/index_list.go +++ b/cli/index_list.go @@ -11,31 +11,12 @@ package cli import ( - "encoding/json" - "io" - "net/http" - "net/url" - "os" - "github.com/spf13/cobra" - httpapi "github.com/sourcenetwork/defradb/api/http" - "github.com/sourcenetwork/defradb/client" - "github.com/sourcenetwork/defradb/config" - "github.com/sourcenetwork/defradb/logging" + "github.com/sourcenetwork/defradb/datastore" ) -type indexListResponse struct { - Data struct { - Collections map[string][]client.IndexDescription `json:"collections"` - Indexes []client.IndexDescription `json:"indexes"` - } `json:"data"` - Errors []struct { - Message string `json:"message"` - } `json:"errors"` -} - -func MakeIndexListCommand(cfg *config.Config) *cobra.Command { +func MakeIndexListCommand() *cobra.Command { var collectionArg string var cmd = &cobra.Command{ Use: "list [-c --collection ]", @@ -48,60 +29,30 @@ Otherwise, all indexes in the database will be shown. Example: show all index for 'Users' collection: defradb client index list --collection Users`, ValidArgs: []string{"collection"}, - RunE: func(cmd *cobra.Command, args []string) (err error) { - endpoint, err := httpapi.JoinPaths(cfg.API.AddressToURL(), httpapi.IndexPath) - if err != nil { - return NewErrFailedToJoinEndpoint(err) - } + RunE: func(cmd *cobra.Command, args []string) error { + store := mustGetStoreContext(cmd) - if collectionArg != "" { - values := url.Values{ - "collection": {collectionArg}, + switch { + case collectionArg != "": + col, err := store.GetCollectionByName(cmd.Context(), collectionArg) + if err != nil { + return err } - endpoint.RawQuery = values.Encode() - } - - res, err := http.Get(endpoint.String()) - if err != nil { - return NewErrFailedToSendRequest(err) - } - - defer func() { - if e := res.Body.Close(); e != nil { - err = NewErrFailedToCloseResponseBody(e, err) + if tx, ok := cmd.Context().Value(txContextKey).(datastore.Txn); ok { + col = col.WithTxn(tx) } - }() - - response, err := io.ReadAll(res.Body) - if err != nil { - return NewErrFailedToReadResponseBody(err) - } - - stdout, err := os.Stdout.Stat() - if err != nil { - return err - } - - if isFileInfoPipe(stdout) { - cmd.Println(string(response)) - } else { - r := indexListResponse{} - err = json.Unmarshal(response, &r) + indexes, err := col.GetIndexes(cmd.Context()) if err != nil { - return NewErrFailedToUnmarshalResponse(err) + return err } - if len(r.Errors) > 0 { - log.FeedbackError(cmd.Context(), "Failed to list index", - logging.NewKV("Errors", r.Errors)) - } else if collectionArg != "" { - log.FeedbackInfo(cmd.Context(), "Fetched indexes for collection "+collectionArg, - logging.NewKV("Indexes", r.Data.Indexes)) - } else { - log.FeedbackInfo(cmd.Context(), "Fetched all indexes", - logging.NewKV("Collections", r.Data.Collections)) + return writeJSON(cmd, indexes) + default: + indexes, err := store.GetAllIndexes(cmd.Context()) + if err != nil { + return err } + return writeJSON(cmd, indexes) } - return nil }, } cmd.Flags().StringVarP(&collectionArg, "collection", "c", "", "Collection name") diff --git a/cli/index_list_test.go b/cli/index_list_test.go deleted file mode 100644 index 548d2af040..0000000000 --- a/cli/index_list_test.go +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright 2023 Democratized Data Foundation -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package cli - -import ( - "bytes" - "encoding/json" - "io" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestIndexListCmd_IfInvalidAddress_ReturnError(t *testing.T) { - cfg := getTestConfig(t) - cfg.API.Address = "invalid address" - indexCreateCmd := MakeIndexListCommand(cfg) - - err := indexCreateCmd.RunE(indexCreateCmd, nil) - require.ErrorIs(t, err, NewErrFailedToJoinEndpoint(err)) -} - -func TestIndexListCmd_IfNoErrors_ShouldReturnData(t *testing.T) { - cfg, _, close := startTestNode(t) - defer close() - - execAddSchemaCmd(t, cfg, `type User { name: String }`) - execCreateIndexCmd(t, cfg, "User", "name", "users_name_index") - - indexListCmd := MakeIndexListCommand(cfg) - outputBuf := bytes.NewBufferString("") - indexListCmd.SetOut(outputBuf) - - err := indexListCmd.Execute() - require.NoError(t, err) - - out, err := io.ReadAll(outputBuf) - require.NoError(t, err) - - r := make(map[string]any) - err = json.Unmarshal(out, &r) - require.NoError(t, err) - - _, hasData := r["data"] - assert.True(t, hasData, "command should return data") -} - -func TestIndexListCmd_WithConsoleOutputIfCollectionDoesNotExist_ReturnError(t *testing.T) { - cfg, _, close := startTestNode(t) - defer close() - - indexListCmd := MakeIndexListCommand(cfg) - indexListCmd.SetArgs([]string{"--collection", "User"}) - - outputBuf, revertOutput := simulateConsoleOutput(t) - defer revertOutput() - - err := indexListCmd.Execute() - require.NoError(t, err) - - logLines, err := parseLines(outputBuf) - require.NoError(t, err) - require.True(t, hasLogWithKey(logLines, "Errors")) -} - -func TestIndexListCmd_WithConsoleOutputIfCollectionIsGiven_ReturnCollectionList(t *testing.T) { - cfg, _, close := startTestNode(t) - defer close() - - const indexName = "users_name_index" - execAddSchemaCmd(t, cfg, `type User { name: String }`) - execCreateIndexCmd(t, cfg, "User", "name", indexName) - - indexListCmd := MakeIndexListCommand(cfg) - indexListCmd.SetArgs([]string{"--collection", "User"}) - - outputBuf, revertOutput := simulateConsoleOutput(t) - defer revertOutput() - - err := indexListCmd.Execute() - require.NoError(t, err) - - logLines, err := parseLines(outputBuf) - require.NoError(t, err) - require.Len(t, logLines, 1) - resultList, ok := logLines[0]["Indexes"].([]any) - require.True(t, ok) - require.Len(t, resultList, 1) - result, ok := resultList[0].(map[string]any) - require.True(t, ok) - assert.Equal(t, indexName, result["Name"]) - - assert.False(t, hasLogWithKey(logLines, "Errors")) -} - -func TestIndexListCmd_WithConsoleOutputIfNoArgs_ReturnAllIndexes(t *testing.T) { - cfg, _, close := startTestNode(t) - defer close() - - const userIndexName = "users_name_index" - const productIndexName = "product_price_index" - execAddSchemaCmd(t, cfg, `type User { name: String }`) - execAddSchemaCmd(t, cfg, `type Product { price: Int }`) - execCreateIndexCmd(t, cfg, "User", "name", userIndexName) - execCreateIndexCmd(t, cfg, "Product", "price", productIndexName) - - indexListCmd := MakeIndexListCommand(cfg) - - outputBuf, revertOutput := simulateConsoleOutput(t) - defer revertOutput() - - err := indexListCmd.Execute() - require.NoError(t, err) - - logLines, err := parseLines(outputBuf) - require.NoError(t, err) - require.Len(t, logLines, 1) - resultCollections, ok := logLines[0]["Collections"].(map[string]any) - require.True(t, ok) - - userCollection, ok := resultCollections["User"].([]any) - require.True(t, ok) - require.Len(t, userCollection, 1) - userIndex, ok := userCollection[0].(map[string]any) - require.True(t, ok) - require.Equal(t, userIndexName, userIndex["Name"]) - - productCollection, ok := resultCollections["Product"].([]any) - require.True(t, ok) - require.Len(t, productCollection, 1) - productIndex, ok := productCollection[0].(map[string]any) - require.True(t, ok) - require.Equal(t, productIndexName, productIndex["Name"]) - - assert.False(t, hasLogWithKey(logLines, "Errors")) -} diff --git a/cli/blocks.go b/cli/p2p.go similarity index 75% rename from cli/blocks.go rename to cli/p2p.go index 9e55c36d22..ee084cc67b 100644 --- a/cli/blocks.go +++ b/cli/p2p.go @@ -14,11 +14,11 @@ import ( "github.com/spf13/cobra" ) -func MakeBlocksCommand() *cobra.Command { +func MakeP2PCommand() *cobra.Command { var cmd = &cobra.Command{ - Use: "blocks", - Short: "Interact with the database's blockstore", + Use: "p2p", + Short: "Interact with the DefraDB P2P system", + Long: "Interact with the DefraDB P2P system", } - return cmd } diff --git a/cli/p2p_collection.go b/cli/p2p_collection.go index 6ce6d8e7c7..140ac4cc34 100644 --- a/cli/p2p_collection.go +++ b/cli/p2p_collection.go @@ -16,7 +16,7 @@ import ( func MakeP2PCollectionCommand() *cobra.Command { var cmd = &cobra.Command{ - Use: "p2pcollection", + Use: "collection", Short: "Configure the P2P collection system", Long: `Add, delete, or get the list of P2P collections. The selected collections synchronize their events on the pubsub network.`, diff --git a/cli/p2p_collection_add.go b/cli/p2p_collection_add.go index 46a4f171e1..6970e8daec 100644 --- a/cli/p2p_collection_add.go +++ b/cli/p2p_collection_add.go @@ -11,51 +11,19 @@ package cli import ( - "context" - "github.com/spf13/cobra" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" - - "github.com/sourcenetwork/defradb/config" - "github.com/sourcenetwork/defradb/errors" - "github.com/sourcenetwork/defradb/logging" - netclient "github.com/sourcenetwork/defradb/net/api/client" ) -func MakeP2PCollectionAddCommand(cfg *config.Config) *cobra.Command { +func MakeP2PCollectionAddCommand() *cobra.Command { var cmd = &cobra.Command{ Use: "add [collectionID]", Short: "Add P2P collections", Long: `Add P2P collections to the synchronized pubsub topics. The collections are synchronized between nodes of a pubsub network.`, - Args: func(cmd *cobra.Command, args []string) error { - if err := cobra.MinimumNArgs(1)(cmd, args); err != nil { - return errors.New("must specify at least one collectionID") - } - return nil - }, + Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - cred := insecure.NewCredentials() - client, err := netclient.NewClient(cfg.Net.RPCAddress, grpc.WithTransportCredentials(cred)) - if err != nil { - return ErrFailedToCreateRPCClient - } - - rpcTimeoutDuration, err := cfg.Net.RPCTimeoutDuration() - if err != nil { - return errors.Wrap("failed to parse RPC timeout duration", err) - } - - ctx, cancel := context.WithTimeout(cmd.Context(), rpcTimeoutDuration) - defer cancel() - - err = client.AddP2PCollections(ctx, args...) - if err != nil { - return errors.Wrap("failed to add P2P collections, request failed", err) - } - log.FeedbackInfo(ctx, "Successfully added P2P collections", logging.NewKV("Collections", args)) - return nil + store := mustGetStoreContext(cmd) + return store.AddP2PCollection(cmd.Context(), args[0]) }, } return cmd diff --git a/cli/p2p_collection_getall.go b/cli/p2p_collection_getall.go index cb9c9f4025..c07a63f453 100644 --- a/cli/p2p_collection_getall.go +++ b/cli/p2p_collection_getall.go @@ -11,60 +11,24 @@ package cli import ( - "context" - "github.com/spf13/cobra" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" - - "github.com/sourcenetwork/defradb/config" - "github.com/sourcenetwork/defradb/errors" - "github.com/sourcenetwork/defradb/logging" - netclient "github.com/sourcenetwork/defradb/net/api/client" ) -func MakeP2PCollectionGetallCommand(cfg *config.Config) *cobra.Command { +func MakeP2PCollectionGetAllCommand() *cobra.Command { var cmd = &cobra.Command{ Use: "getall", Short: "Get all P2P collections", Long: `Get all P2P collections in the pubsub topics. This is the list of collections of the node that are synchronized on the pubsub network.`, - Args: func(cmd *cobra.Command, args []string) error { - if err := cobra.NoArgs(cmd, args); err != nil { - return errors.New("must specify no argument") - } - return nil - }, + Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - cred := insecure.NewCredentials() - client, err := netclient.NewClient(cfg.Net.RPCAddress, grpc.WithTransportCredentials(cred)) - if err != nil { - return ErrFailedToCreateRPCClient - } + store := mustGetStoreContext(cmd) - rpcTimeoutDuration, err := cfg.Net.RPCTimeoutDuration() + cols, err := store.GetAllP2PCollections(cmd.Context()) if err != nil { - return errors.Wrap("failed to parse RPC timeout duration", err) + return err } - - ctx, cancel := context.WithTimeout(cmd.Context(), rpcTimeoutDuration) - defer cancel() - - collections, err := client.GetAllP2PCollections(ctx) - if err != nil { - return errors.Wrap("failed to add P2P collections, request failed", err) - } - - if len(collections) > 0 { - log.FeedbackInfo(ctx, "Successfully got all P2P collections") - for _, col := range collections { - log.FeedbackInfo(ctx, col.Name, logging.NewKV("CollectionID", col.ID)) - } - } else { - log.FeedbackInfo(ctx, "No P2P collection found") - } - - return nil + return writeJSON(cmd, cols) }, } return cmd diff --git a/cli/p2p_collection_remove.go b/cli/p2p_collection_remove.go index 66dbd5fa16..ed67f5e7c6 100644 --- a/cli/p2p_collection_remove.go +++ b/cli/p2p_collection_remove.go @@ -11,51 +11,19 @@ package cli import ( - "context" - "github.com/spf13/cobra" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" - - "github.com/sourcenetwork/defradb/config" - "github.com/sourcenetwork/defradb/errors" - "github.com/sourcenetwork/defradb/logging" - netclient "github.com/sourcenetwork/defradb/net/api/client" ) -func MakeP2PCollectionRemoveCommand(cfg *config.Config) *cobra.Command { +func MakeP2PCollectionRemoveCommand() *cobra.Command { var cmd = &cobra.Command{ Use: "remove [collectionID]", Short: "Remove P2P collections", Long: `Remove P2P collections from the followed pubsub topics. The removed collections will no longer be synchronized between nodes.`, - Args: func(cmd *cobra.Command, args []string) error { - if err := cobra.MinimumNArgs(1)(cmd, args); err != nil { - return errors.New("must specify at least one collectionID") - } - return nil - }, + Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - cred := insecure.NewCredentials() - client, err := netclient.NewClient(cfg.Net.RPCAddress, grpc.WithTransportCredentials(cred)) - if err != nil { - return ErrFailedToCreateRPCClient - } - - rpcTimeoutDuration, err := cfg.Net.RPCTimeoutDuration() - if err != nil { - return errors.Wrap("failed to parse RPC timeout duration", err) - } - - ctx, cancel := context.WithTimeout(cmd.Context(), rpcTimeoutDuration) - defer cancel() - - err = client.RemoveP2PCollections(ctx, args...) - if err != nil { - return errors.Wrap("failed to remove P2P collections, request failed", err) - } - log.FeedbackInfo(ctx, "Successfully removed P2P collections", logging.NewKV("Collections", args)) - return nil + store := mustGetStoreContext(cmd) + return store.RemoveP2PCollection(cmd.Context(), args[0]) }, } return cmd diff --git a/cli/p2p_info.go b/cli/p2p_info.go new file mode 100644 index 0000000000..1ddad18a52 --- /dev/null +++ b/cli/p2p_info.go @@ -0,0 +1,35 @@ +// Copyright 2023 Democratized Data Foundation +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +package cli + +import ( + "github.com/spf13/cobra" + + "github.com/sourcenetwork/defradb/http" +) + +func MakeP2PInfoCommand() *cobra.Command { + var cmd = &cobra.Command{ + Use: "info", + Short: "Get peer info from a DefraDB node", + Long: `Get peer info from a DefraDB node`, + RunE: func(cmd *cobra.Command, args []string) error { + db := cmd.Context().Value(dbContextKey).(*http.Client) + + res, err := db.PeerInfo(cmd.Context()) + if err != nil { + return err + } + return writeJSON(cmd, res) + }, + } + return cmd +} diff --git a/cli/replicator.go b/cli/p2p_replicator.go similarity index 93% rename from cli/replicator.go rename to cli/p2p_replicator.go index c7956c80a6..d12684be51 100644 --- a/cli/replicator.go +++ b/cli/p2p_replicator.go @@ -14,7 +14,7 @@ import ( "github.com/spf13/cobra" ) -func MakeReplicatorCommand() *cobra.Command { +func MakeP2PReplicatorCommand() *cobra.Command { var cmd = &cobra.Command{ Use: "replicator", Short: "Configure the replicator system", diff --git a/cli/p2p_replicator_delete.go b/cli/p2p_replicator_delete.go new file mode 100644 index 0000000000..7504d0c932 --- /dev/null +++ b/cli/p2p_replicator_delete.go @@ -0,0 +1,37 @@ +// Copyright 2023 Democratized Data Foundation +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +package cli + +import ( + "github.com/libp2p/go-libp2p/core/peer" + "github.com/spf13/cobra" + + "github.com/sourcenetwork/defradb/client" +) + +func MakeP2PReplicatorDeleteCommand() *cobra.Command { + var cmd = &cobra.Command{ + Use: "delete ", + Short: "Delete a replicator. It will stop synchronizing", + Long: `Delete a replicator. It will stop synchronizing.`, + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + store := mustGetStoreContext(cmd) + + addr, err := peer.AddrInfoFromString(args[0]) + if err != nil { + return err + } + return store.DeleteReplicator(cmd.Context(), client.Replicator{Info: *addr}) + }, + } + return cmd +} diff --git a/cli/p2p_replicator_getall.go b/cli/p2p_replicator_getall.go new file mode 100644 index 0000000000..9192ed4d10 --- /dev/null +++ b/cli/p2p_replicator_getall.go @@ -0,0 +1,34 @@ +// Copyright 2023 Democratized Data Foundation +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +package cli + +import ( + "github.com/spf13/cobra" +) + +func MakeP2PReplicatorGetAllCommand() *cobra.Command { + var cmd = &cobra.Command{ + Use: "getall", + Short: "Get all replicators", + Long: `Get all the replicators active in the P2P data sync system. +These are the replicators that are currently replicating data from one node to another.`, + RunE: func(cmd *cobra.Command, args []string) error { + store := mustGetStoreContext(cmd) + + reps, err := store.GetAllReplicators(cmd.Context()) + if err != nil { + return err + } + return writeJSON(cmd, reps) + }, + } + return cmd +} diff --git a/cli/p2p_replicator_set.go b/cli/p2p_replicator_set.go new file mode 100644 index 0000000000..6b590b6ea7 --- /dev/null +++ b/cli/p2p_replicator_set.go @@ -0,0 +1,47 @@ +// Copyright 2023 Democratized Data Foundation +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +package cli + +import ( + "github.com/libp2p/go-libp2p/core/peer" + "github.com/spf13/cobra" + + "github.com/sourcenetwork/defradb/client" +) + +func MakeP2PReplicatorSetCommand() *cobra.Command { + var collections []string + var cmd = &cobra.Command{ + Use: "set [-c, --collection] ", + Short: "Set a P2P replicator", + Long: `Add a new target replicator. +A replicator replicates one or all collection(s) from this node to another. +`, + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + store := mustGetStoreContext(cmd) + + addr, err := peer.AddrInfoFromString(args[0]) + if err != nil { + return err + } + rep := client.Replicator{ + Info: *addr, + Schemas: collections, + } + return store.SetReplicator(cmd.Context(), rep) + }, + } + + cmd.Flags().StringSliceVarP(&collections, "collection", "c", + []string{}, "Define the collection for the replicator") + return cmd +} diff --git a/cli/peerid.go b/cli/peerid.go deleted file mode 100644 index a3d269fb2d..0000000000 --- a/cli/peerid.go +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright 2022 Democratized Data Foundation -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package cli - -import ( - "encoding/json" - "io" - "net/http" - "os" - - "github.com/spf13/cobra" - - httpapi "github.com/sourcenetwork/defradb/api/http" - "github.com/sourcenetwork/defradb/config" - "github.com/sourcenetwork/defradb/errors" -) - -func MakePeerIDCommand(cfg *config.Config) *cobra.Command { - var cmd = &cobra.Command{ - Use: "peerid", - Short: "Get the PeerID of the node", - Long: `Get the PeerID of the node.`, - RunE: func(cmd *cobra.Command, _ []string) (err error) { - stdout, err := os.Stdout.Stat() - if err != nil { - return errors.Wrap("failed to stat stdout", err) - } - if !isFileInfoPipe(stdout) { - log.FeedbackInfo(cmd.Context(), "Requesting PeerID...") - } - - endpoint, err := httpapi.JoinPaths(cfg.API.AddressToURL(), httpapi.PeerIDPath) - if err != nil { - return errors.Wrap("failed to join endpoint", err) - } - - res, err := http.Get(endpoint.String()) - if err != nil { - return errors.Wrap("failed to request PeerID", err) - } - - defer func() { - if e := res.Body.Close(); e != nil { - err = NewErrFailedToCloseResponseBody(e, err) - } - }() - - response, err := io.ReadAll(res.Body) - if err != nil { - return errors.Wrap("failed to read response body", err) - } - - if res.StatusCode == http.StatusNotFound { - r := httpapi.ErrorResponse{} - err = json.Unmarshal(response, &r) - if err != nil { - return errors.Wrap("parsing of response failed", err) - } - if len(r.Errors) > 0 { - if isFileInfoPipe(stdout) { - b, err := json.Marshal(r.Errors[0]) - if err != nil { - return errors.Wrap("mashalling error response failed", err) - } - cmd.Println(string(b)) - } else { - log.FeedbackInfo(cmd.Context(), r.Errors[0].Message) - } - return nil - } - return errors.New("no PeerID available. P2P might be disabled") - } - - r := httpapi.DataResponse{} - err = json.Unmarshal(response, &r) - if err != nil { - return errors.Wrap("parsing of response failed", err) - } - if isFileInfoPipe(stdout) { - b, err := json.Marshal(r.Data) - if err != nil { - return errors.Wrap("mashalling data response failed", err) - } - cmd.Println(string(b)) - } else if data, ok := r.Data.(map[string]any); ok { - log.FeedbackInfo(cmd.Context(), data["peerID"].(string)) - } - - return nil - }, - } - return cmd -} diff --git a/cli/peerid_test.go b/cli/peerid_test.go deleted file mode 100644 index 34874ef80d..0000000000 --- a/cli/peerid_test.go +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright 2022 Democratized Data Foundation -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package cli - -import ( - "bytes" - "context" - "encoding/json" - "io" - "net/http" - "testing" - - "github.com/stretchr/testify/assert" - - httpapi "github.com/sourcenetwork/defradb/api/http" -) - -func TestGetPeerIDCmd(t *testing.T) { - cfg := getTestConfig(t) - peerIDCmd := MakePeerIDCommand(cfg) - dir := t.TempDir() - ctx := context.Background() - cfg.Datastore.Store = "memory" - cfg.Datastore.Badger.Path = dir - cfg.Net.P2PDisabled = false - - di, err := start(ctx, cfg) - if err != nil { - t.Fatal(err) - } - defer di.close(ctx) - - b := bytes.NewBufferString("") - peerIDCmd.SetOut(b) - - err = peerIDCmd.RunE(peerIDCmd, nil) - if err != nil { - t.Fatal(err) - } - - out, err := io.ReadAll(b) - if err != nil { - t.Fatal(err) - } - - r := make(map[string]any) - err = json.Unmarshal(out, &r) - if err != nil { - t.Fatal(err) - } - - assert.Equal(t, di.node.PeerID().String(), r["peerID"]) -} - -func TestGetPeerIDCmdWithNoP2P(t *testing.T) { - cfg := getTestConfig(t) - peerIDCmd := MakePeerIDCommand(cfg) - dir := t.TempDir() - ctx := context.Background() - cfg.Datastore.Store = "memory" - cfg.Datastore.Badger.Path = dir - cfg.Net.P2PDisabled = true - - di, err := start(ctx, cfg) - if err != nil { - t.Fatal(err) - } - defer di.close(ctx) - - b := bytes.NewBufferString("") - peerIDCmd.SetOut(b) - - err = peerIDCmd.RunE(peerIDCmd, nil) - if err != nil { - t.Fatal(err) - } - - out, err := io.ReadAll(b) - if err != nil { - t.Fatal(err) - } - - r := httpapi.ErrorItem{} - err = json.Unmarshal(out, &r) - if err != nil { - t.Fatal(err) - } - - assert.Equal(t, http.StatusNotFound, r.Extensions.Status) - assert.Equal(t, "Not Found", r.Extensions.HTTPError) - assert.Equal(t, "no PeerID available. P2P might be disabled", r.Message) -} diff --git a/cli/ping.go b/cli/ping.go deleted file mode 100644 index 210847dfcc..0000000000 --- a/cli/ping.go +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright 2022 Democratized Data Foundation -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package cli - -import ( - "encoding/json" - "io" - "net/http" - "os" - - "github.com/spf13/cobra" - - httpapi "github.com/sourcenetwork/defradb/api/http" - "github.com/sourcenetwork/defradb/config" - "github.com/sourcenetwork/defradb/errors" -) - -func MakePingCommand(cfg *config.Config) *cobra.Command { - var cmd = &cobra.Command{ - Use: "ping", - Short: "Ping to test connection with a node", - RunE: func(cmd *cobra.Command, _ []string) (err error) { - stdout, err := os.Stdout.Stat() - if err != nil { - return errors.Wrap("failed to stat stdout", err) - } - if !isFileInfoPipe(stdout) { - log.FeedbackInfo(cmd.Context(), "Sending ping...") - } - - endpoint, err := httpapi.JoinPaths(cfg.API.AddressToURL(), httpapi.PingPath) - if err != nil { - return errors.Wrap("failed to join endpoint", err) - } - - res, err := http.Get(endpoint.String()) - if err != nil { - return errors.Wrap("failed to send ping", err) - } - - defer func() { - if e := res.Body.Close(); e != nil { - err = NewErrFailedToCloseResponseBody(e, err) - } - }() - - response, err := io.ReadAll(res.Body) - if err != nil { - return errors.Wrap("failed to read response body", err) - } - - if isFileInfoPipe(stdout) { - cmd.Println(string(response)) - } else { - type pingResponse struct { - Data struct { - Response string `json:"response"` - } `json:"data"` - } - r := pingResponse{} - err = json.Unmarshal(response, &r) - if err != nil { - return errors.Wrap("parsing of response failed", err) - } - log.FeedbackInfo(cmd.Context(), r.Data.Response) - } - return nil - }, - } - return cmd -} diff --git a/cli/replicator_delete.go b/cli/replicator_delete.go deleted file mode 100644 index eb7e580f12..0000000000 --- a/cli/replicator_delete.go +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright 2022 Democratized Data Foundation -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package cli - -import ( - "context" - - "github.com/libp2p/go-libp2p/core/peer" - "github.com/spf13/cobra" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" - - "github.com/sourcenetwork/defradb/config" - "github.com/sourcenetwork/defradb/errors" - "github.com/sourcenetwork/defradb/logging" - netclient "github.com/sourcenetwork/defradb/net/api/client" -) - -func MakeReplicatorDeleteCommand(cfg *config.Config) *cobra.Command { - var ( - fullRep bool - col []string - ) - var cmd = &cobra.Command{ - Use: "delete [-f, --full | -c, --collection] ", - Short: "Delete a replicator. It will stop synchronizing", - Long: `Delete a replicator. It will stop synchronizing.`, - Args: func(cmd *cobra.Command, args []string) error { - if err := cobra.ExactArgs(1)(cmd, args); err != nil { - return errors.New("must specify one argument: PeerID") - } - return nil - }, - RunE: func(cmd *cobra.Command, args []string) error { - pidString := args[0] - - if len(col) == 0 && !fullRep { - return errors.New("must run with either --full or --collection") - } - - cred := insecure.NewCredentials() - client, err := netclient.NewClient(cfg.Net.RPCAddress, grpc.WithTransportCredentials(cred)) - if err != nil { - return ErrFailedToCreateRPCClient - } - - rpcTimeoutDuration, err := cfg.Net.RPCTimeoutDuration() - if err != nil { - return errors.Wrap("failed to parse RPC timeout duration", err) - } - - ctx, cancel := context.WithTimeout(cmd.Context(), rpcTimeoutDuration) - defer cancel() - - pid, err := peer.Decode(pidString) - if err != nil { - return NewErrFailedParsePeerID(err) - } - - err = client.DeleteReplicator(ctx, pid) - if err != nil { - return errors.Wrap("failed to delete replicator, request failed", err) - } - log.FeedbackInfo(ctx, "Successfully deleted replicator", logging.NewKV("PeerID", pid.String())) - return nil - }, - } - cmd.Flags().BoolVarP(&fullRep, "full", "f", false, "Set the replicator to act on all collections") - cmd.Flags().StringArrayVarP(&col, "collection", "c", - []string{}, "Define the collection for the replicator") - cmd.MarkFlagsMutuallyExclusive("full", "collection") - return cmd -} diff --git a/cli/replicator_getall.go b/cli/replicator_getall.go deleted file mode 100644 index 63cd6533ba..0000000000 --- a/cli/replicator_getall.go +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright 2022 Democratized Data Foundation -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package cli - -import ( - "context" - - "github.com/spf13/cobra" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" - - "github.com/sourcenetwork/defradb/config" - "github.com/sourcenetwork/defradb/errors" - "github.com/sourcenetwork/defradb/logging" - netclient "github.com/sourcenetwork/defradb/net/api/client" -) - -func MakeReplicatorGetallCommand(cfg *config.Config) *cobra.Command { - var cmd = &cobra.Command{ - Use: "getall", - Short: "Get all replicators", - Long: `Get all the replicators active in the P2P data sync system. -These are the replicators that are currently replicating data from one node to another.`, - RunE: func(cmd *cobra.Command, args []string) error { - if len(args) != 0 { - if err := cmd.Usage(); err != nil { - return err - } - return errors.New("must specify no argument") - } - - log.FeedbackInfo( - cmd.Context(), - "Getting all replicators", - logging.NewKV("RPCAddress", cfg.Net.RPCAddress), - ) - - cred := insecure.NewCredentials() - client, err := netclient.NewClient(cfg.Net.RPCAddress, grpc.WithTransportCredentials(cred)) - if err != nil { - return errors.Wrap("failed to create RPC client", err) - } - - rpcTimeoutDuration, err := cfg.Net.RPCTimeoutDuration() - if err != nil { - return errors.Wrap("failed to parse RPC timeout duration", err) - } - - ctx, cancel := context.WithTimeout(cmd.Context(), rpcTimeoutDuration) - defer cancel() - - reps, err := client.GetAllReplicators(ctx) - if err != nil { - return errors.Wrap("failed to get replicators, request failed", err) - } - if len(reps) > 0 { - log.FeedbackInfo(ctx, "Successfully got all replicators") - for _, rep := range reps { - log.FeedbackInfo( - ctx, - rep.Info.ID.String(), - logging.NewKV("Schemas", rep.Schemas), - logging.NewKV("Addrs", rep.Info.Addrs), - ) - } - } else { - log.FeedbackInfo(ctx, "No replicator found") - } - - return nil - }, - } - return cmd -} diff --git a/cli/replicator_set.go b/cli/replicator_set.go deleted file mode 100644 index acb70d0cfd..0000000000 --- a/cli/replicator_set.go +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright 2022 Democratized Data Foundation -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package cli - -import ( - "context" - - ma "github.com/multiformats/go-multiaddr" - "github.com/spf13/cobra" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" - - "github.com/sourcenetwork/defradb/config" - "github.com/sourcenetwork/defradb/errors" - "github.com/sourcenetwork/defradb/logging" - netclient "github.com/sourcenetwork/defradb/net/api/client" -) - -func MakeReplicatorSetCommand(cfg *config.Config) *cobra.Command { - var ( - fullRep bool - col []string - ) - var cmd = &cobra.Command{ - Use: "set [-f, --full | -c, --collection] ", - Short: "Set a P2P replicator", - Long: `Add a new target replicator. -A replicator replicates one or all collection(s) from this node to another. -`, - Args: func(cmd *cobra.Command, args []string) error { - if err := cobra.ExactArgs(1)(cmd, args); err != nil { - return errors.New("must specify one argument: peer") - } - return nil - }, - RunE: func(cmd *cobra.Command, args []string) error { - peerAddr, err := ma.NewMultiaddr(args[0]) - if err != nil { - return NewErrFailedParsePeerID(err) - } - if len(col) == 0 && !fullRep { - return errors.New("must run with either --full or --collection") - } - - cred := insecure.NewCredentials() - client, err := netclient.NewClient(cfg.Net.RPCAddress, grpc.WithTransportCredentials(cred)) - if err != nil { - return ErrFailedToCreateRPCClient - } - - rpcTimeoutDuration, err := cfg.Net.RPCTimeoutDuration() - if err != nil { - return errors.Wrap("failed to parse RPC timeout duration", err) - } - - ctx, cancel := context.WithTimeout(cmd.Context(), rpcTimeoutDuration) - defer cancel() - - pid, err := client.SetReplicator(ctx, peerAddr, col...) - if err != nil { - return errors.Wrap("failed to add replicator, request failed", err) - } - log.FeedbackInfo( - ctx, - "Successfully added replicator", - logging.NewKV("PeerID", pid), - logging.NewKV("Collections", col), - ) - return nil - }, - } - - cmd.Flags().BoolVarP(&fullRep, "full", "f", false, "Set the replicator to act on all collections") - cmd.Flags().StringArrayVarP(&col, "collection", "c", - []string{}, "Define the collection for the replicator") - cmd.MarkFlagsMutuallyExclusive("full", "collection") - return cmd -} diff --git a/cli/request.go b/cli/request.go index 1b8f86ced8..56e33d7c4a 100644 --- a/cli/request.go +++ b/cli/request.go @@ -12,18 +12,19 @@ package cli import ( "io" - "net/http" - "net/url" "os" "github.com/spf13/cobra" - httpapi "github.com/sourcenetwork/defradb/api/http" - "github.com/sourcenetwork/defradb/config" "github.com/sourcenetwork/defradb/errors" ) -func MakeRequestCommand(cfg *config.Config) *cobra.Command { +const ( + REQ_RESULTS_HEADER = "------ Request Results ------\n" + SUB_RESULTS_HEADER = "------ Subscription Results ------\n" +) + +func MakeRequestCommand() *cobra.Command { var filePath string var cmd = &cobra.Command{ Use: "query [query request]", @@ -43,101 +44,43 @@ A GraphQL client such as GraphiQL (https://github.com/graphql/graphiql) can be u with the database more conveniently. To learn more about the DefraDB GraphQL Query Language, refer to https://docs.source.network.`, - RunE: func(cmd *cobra.Command, args []string) (err error) { - var request string + RunE: func(cmd *cobra.Command, args []string) error { + store := mustGetStoreContext(cmd) - fi, err := os.Stdin.Stat() - if err != nil { - return err - } - - if filePath != "" { - bytes, err := os.ReadFile(filePath) + var request string + switch { + case filePath != "": + data, err := os.ReadFile(filePath) if err != nil { - return ErrFailedToReadFile - } - request = string(bytes) - } else if len(args) > 1 { - if err = cmd.Usage(); err != nil { return err } - return errors.New("too many arguments") - } else if isFileInfoPipe(fi) && (len(args) == 0 || args[0] != "-") { - log.FeedbackInfo( - cmd.Context(), - "Run 'defradb client query -' to read from stdin. Example: 'cat my.graphql | defradb client query -').", - ) - return nil - } else if len(args) == 0 { - err := cmd.Help() + request = string(data) + case len(args) > 0 && args[0] == "-": + data, err := io.ReadAll(cmd.InOrStdin()) if err != nil { - return errors.Wrap("failed to print help", err) - } - return nil - } else if args[0] == "-" { - stdin, err := readStdin() - if err != nil { - return errors.Wrap("failed to read stdin", err) - } - if len(stdin) == 0 { - return errors.New("no query request in stdin provided") - } else { - request = stdin + return err } - } else { - request = args[0] + request = string(data) + case len(args) > 0: + request = string(args[0]) } if request == "" { return errors.New("request cannot be empty") } + result := store.ExecRequest(cmd.Context(), request) - endpoint, err := httpapi.JoinPaths(cfg.API.AddressToURL(), httpapi.GraphQLPath) - if err != nil { - return errors.Wrap("joining paths failed", err) + var errors []string + for _, err := range result.GQL.Errors { + errors = append(errors, err.Error()) } - - p := url.Values{} - p.Add("query", request) - endpoint.RawQuery = p.Encode() - - res, err := http.Get(endpoint.String()) - if err != nil { - return errors.Wrap("failed request", err) + if result.Pub == nil { + cmd.Print(REQ_RESULTS_HEADER) + return writeJSON(cmd, map[string]any{"data": result.GQL.Data, "errors": errors}) } - - defer func() { - if e := res.Body.Close(); e != nil { - err = NewErrFailedToCloseResponseBody(e, err) - } - }() - - response, err := io.ReadAll(res.Body) - if err != nil { - return errors.Wrap("failed to read response body", err) - } - - fi, err = os.Stdout.Stat() - if err != nil { - return errors.Wrap("failed to stat stdout", err) - } - - if isFileInfoPipe(fi) { - cmd.Println(string(response)) - } else { - graphlErr, err := hasGraphQLErrors(response) - if err != nil { - return errors.Wrap("failed to handle GraphQL errors", err) - } - indentedResult, err := indentJSON(response) - if err != nil { - return errors.Wrap("failed to pretty print result", err) - } - if graphlErr { - log.FeedbackError(cmd.Context(), indentedResult) - } else { - log.FeedbackInfo(cmd.Context(), indentedResult) - } + cmd.Print(SUB_RESULTS_HEADER) + for item := range result.Pub.Stream() { + writeJSON(cmd, item) //nolint:errcheck } return nil }, diff --git a/cli/root.go b/cli/root.go index e639cde785..729b638f02 100644 --- a/cli/root.go +++ b/cli/root.go @@ -16,34 +16,19 @@ import ( "github.com/spf13/cobra" "github.com/sourcenetwork/defradb/config" - "github.com/sourcenetwork/defradb/errors" ) func MakeRootCommand(cfg *config.Config) *cobra.Command { var cmd = &cobra.Command{ - Use: "defradb", - Short: "DefraDB Edge Database", + SilenceUsage: true, + Use: "defradb", + Short: "DefraDB Edge Database", Long: `DefraDB is the edge database to power the user-centric future. Start a DefraDB node, interact with a local or remote node, and much more. `, - // Runs on subcommands before their Run function, to handle configuration and top-level flags. - // Loads the rootDir containing the configuration file, otherwise warn about it and load a default configuration. - // This allows some subcommands (`init`, `start`) to override the PreRun to create a rootDir by default. - PersistentPreRunE: func(cmd *cobra.Command, _ []string) error { - if err := cfg.LoadRootDirFromFlagOrDefault(); err != nil { - return err - } - if cfg.ConfigFileExists() { - if err := cfg.LoadWithRootdir(true); err != nil { - return errors.Wrap("failed to load config", err) - } - } else { - if err := cfg.LoadWithRootdir(false); err != nil { - return errors.Wrap("failed to load config", err) - } - } - return nil + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + return loadConfig(cfg) }, } diff --git a/cli/rpc.go b/cli/rpc.go deleted file mode 100644 index afb1a007e2..0000000000 --- a/cli/rpc.go +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2022 Democratized Data Foundation -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package cli - -import ( - "context" - - "github.com/spf13/cobra" - - "github.com/sourcenetwork/defradb/config" -) - -func MakeRPCCommand(cfg *config.Config) *cobra.Command { - var cmd = &cobra.Command{ - Use: "rpc", - Short: "Interact with a DefraDB node via RPC", - Long: "Interact with a DefraDB node via RPC.", - } - cmd.PersistentFlags().String( - "addr", cfg.Net.RPCAddress, - "RPC endpoint address", - ) - - if err := cfg.BindFlag("net.rpcaddress", cmd.PersistentFlags().Lookup("addr")); err != nil { - log.FeedbackFatalE(context.Background(), "Could not bind net.rpcaddress", err) - } - return cmd -} diff --git a/cli/schema_add.go b/cli/schema_add.go index b5f28f15d3..b93427a883 100644 --- a/cli/schema_add.go +++ b/cli/schema_add.go @@ -11,21 +11,14 @@ package cli import ( - "encoding/json" + "fmt" "io" - "net/http" "os" - "strings" "github.com/spf13/cobra" - - httpapi "github.com/sourcenetwork/defradb/api/http" - "github.com/sourcenetwork/defradb/config" - "github.com/sourcenetwork/defradb/errors" - "github.com/sourcenetwork/defradb/logging" ) -func MakeSchemaAddCommand(cfg *config.Config) *cobra.Command { +func MakeSchemaAddCommand() *cobra.Command { var schemaFile string var cmd = &cobra.Command{ Use: "add [schema]", @@ -42,117 +35,34 @@ Example: add from stdin: cat schema.graphql | defradb client schema add - Learn more about the DefraDB GraphQL Schema Language on https://docs.source.network.`, - RunE: func(cmd *cobra.Command, args []string) (err error) { - var schema string - fi, err := os.Stdin.Stat() - if err != nil { - return err - } - - if len(args) > 1 { - if err = cmd.Usage(); err != nil { - return err - } - return errors.New("too many arguments") - } + RunE: func(cmd *cobra.Command, args []string) error { + store := mustGetStoreContext(cmd) - if schemaFile != "" { - buf, err := os.ReadFile(schemaFile) - if err != nil { - return errors.Wrap("failed to read schema file", err) - } - schema = string(buf) - } else if isFileInfoPipe(fi) && (len(args) == 0 || args[0] != "-") { - log.FeedbackInfo( - cmd.Context(), - "Run 'defradb client schema add -' to read from stdin."+ - " Example: 'cat schema.graphql | defradb client schema add -').", - ) - return nil - } else if len(args) == 0 { - err := cmd.Help() + var schema string + switch { + case schemaFile != "": + data, err := os.ReadFile(schemaFile) if err != nil { - return errors.Wrap("failed to print help", err) + return err } - return nil - } else if args[0] == "-" { - stdin, err := readStdin() + schema = string(data) + case len(args) > 0 && args[0] == "-": + data, err := io.ReadAll(cmd.InOrStdin()) if err != nil { - return errors.Wrap("failed to read stdin", err) - } - if len(stdin) == 0 { - return errors.New("no schema in stdin provided") - } else { - schema = stdin + return err } - } else { + schema = string(data) + case len(args) > 0: schema = args[0] + default: + return fmt.Errorf("schema cannot be empty") } - if schema == "" { - return errors.New("empty schema provided") - } - - endpoint, err := httpapi.JoinPaths(cfg.API.AddressToURL(), httpapi.SchemaPath) + cols, err := store.AddSchema(cmd.Context(), schema) if err != nil { - return errors.Wrap("join paths failed", err) - } - - res, err := http.Post(endpoint.String(), "text", strings.NewReader(schema)) - if err != nil { - return errors.Wrap("failed to post schema", err) - } - - defer func() { - if e := res.Body.Close(); e != nil { - err = NewErrFailedToCloseResponseBody(e, err) - } - }() - - response, err := io.ReadAll(res.Body) - if err != nil { - return errors.Wrap("failed to read response body", err) - } - - stdout, err := os.Stdout.Stat() - if err != nil { - return errors.Wrap("failed to stat stdout", err) - } - if isFileInfoPipe(stdout) { - cmd.Println(string(response)) - } else { - graphlErr, err := hasGraphQLErrors(response) - if err != nil { - return errors.Wrap("failed to handle GraphQL errors", err) - } - if graphlErr { - indentedResult, err := indentJSON(response) - if err != nil { - return errors.Wrap("failed to pretty print result", err) - } - log.FeedbackError(cmd.Context(), indentedResult) - } else { - type schemaResponse struct { - Data struct { - Result string `json:"result"` - Collections []struct { - Name string `json:"name"` - ID string `json:"id"` - } `json:"collections"` - } `json:"data"` - } - r := schemaResponse{} - err = json.Unmarshal(response, &r) - if err != nil { - return errors.Wrap("failed to unmarshal response", err) - } - if r.Data.Result == "success" { - log.FeedbackInfo(cmd.Context(), "Successfully added schema.", logging.NewKV("Collections", r.Data.Collections)) - } - log.FeedbackInfo(cmd.Context(), r.Data.Result) - } + return err } - return nil + return writeJSON(cmd, cols) }, } cmd.Flags().StringVarP(&schemaFile, "file", "f", "", "File to load a schema from") diff --git a/cli/schema_list.go b/cli/schema_list.go deleted file mode 100644 index 3a0e32bcce..0000000000 --- a/cli/schema_list.go +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright 2023 Democratized Data Foundation -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package cli - -import ( - "encoding/json" - "io" - "net/http" - - "github.com/spf13/cobra" - - httpapi "github.com/sourcenetwork/defradb/api/http" - "github.com/sourcenetwork/defradb/config" - "github.com/sourcenetwork/defradb/errors" -) - -type schemaListResponse struct { - Data struct { - Collections []struct { - Name string `json:"name"` - ID string `json:"id"` - VersionID string `json:"version_id"` - Fields []struct { - ID string `json:"id"` - Name string `json:"name"` - Kind string `json:"kind"` - Internal bool `json:"internal"` - } `json:"fields"` - } `json:"collections"` - } `json:"data"` - Errors []struct { - Message string `json:"message"` - } `json:"errors"` -} - -func MakeSchemaListCommand(cfg *config.Config) *cobra.Command { - var cmd = &cobra.Command{ - Use: "list", - Short: "List schema types with their respective fields", - RunE: func(cmd *cobra.Command, args []string) (err error) { - endpoint, err := httpapi.JoinPaths(cfg.API.AddressToURL(), httpapi.SchemaPath) - if err != nil { - return NewErrFailedToJoinEndpoint(err) - } - - res, err := http.Get(endpoint.String()) - if err != nil { - return NewErrFailedToSendRequest(err) - } - defer res.Body.Close() //nolint:errcheck - - data, err := io.ReadAll(res.Body) - if err != nil { - return NewErrFailedToReadResponseBody(err) - } - - var r schemaListResponse - if err := json.Unmarshal(data, &r); err != nil { - return NewErrFailedToUnmarshalResponse(err) - } - if len(r.Errors) > 0 { - return errors.New("failed to list schemas", errors.NewKV("errors", r.Errors)) - } - - for _, c := range r.Data.Collections { - cmd.Printf("# Schema ID: %s\n", c.ID) - cmd.Printf("# Version ID: %s\n", c.VersionID) - cmd.Printf("type %s {\n", c.Name) - for _, f := range c.Fields { - if !f.Internal { - cmd.Printf("\t%s: %s\n", f.Name, f.Kind) - } - } - cmd.Printf("}\n\n") - } - - return nil - }, - } - return cmd -} diff --git a/cli/schema_migration_down.go b/cli/schema_migration_down.go new file mode 100644 index 0000000000..1dcb5e64da --- /dev/null +++ b/cli/schema_migration_down.go @@ -0,0 +1,91 @@ +// Copyright 2023 Democratized Data Foundation +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +package cli + +import ( + "encoding/json" + "io" + "os" + + "github.com/sourcenetwork/immutable/enumerable" + "github.com/spf13/cobra" + + "github.com/sourcenetwork/defradb/datastore" +) + +func MakeSchemaMigrationDownCommand() *cobra.Command { + var file string + var schemaVersionID string + var cmd = &cobra.Command{ + Use: "down --version ", + Short: "Reverses the migration from the specified schema version.", + Long: `Reverses the migration from the specified schema version. +Documents is a list of documents to reverse the migration from. + +Example: migrate from string + defradb client schema migration down --version bae123 '[{"name": "Bob"}]' + +Example: migrate from file + defradb client schema migration down --version bae123 -f documents.json + +Example: migrate from stdin + cat documents.json | defradb client schema migration down --version bae123 - + `, + Args: cobra.RangeArgs(0, 1), + RunE: func(cmd *cobra.Command, args []string) error { + store := mustGetStoreContext(cmd) + + var srcData []byte + switch { + case file != "": + data, err := os.ReadFile(file) + if err != nil { + return err + } + srcData = data + case len(args) == 1 && args[0] == "-": + data, err := io.ReadAll(cmd.InOrStdin()) + if err != nil { + return err + } + srcData = data + case len(args) == 1: + srcData = []byte(args[0]) + default: + return ErrNoDocOrFile + } + + var src []map[string]any + if err := json.Unmarshal(srcData, &src); err != nil { + return err + } + lens := store.LensRegistry() + if tx, ok := cmd.Context().Value(txContextKey).(datastore.Txn); ok { + lens = lens.WithTxn(tx) + } + out, err := lens.MigrateDown(cmd.Context(), enumerable.New(src), schemaVersionID) + if err != nil { + return err + } + var value []map[string]any + err = enumerable.ForEach(out, func(item map[string]any) { + value = append(value, item) + }) + if err != nil { + return err + } + return writeJSON(cmd, value) + }, + } + cmd.Flags().StringVarP(&file, "file", "f", "", "File containing document(s)") + cmd.Flags().StringVar(&schemaVersionID, "version", "", "Schema version id") + return cmd +} diff --git a/cli/schema_migration_get.go b/cli/schema_migration_get.go index 333c2d9cf4..43b66599b7 100644 --- a/cli/schema_migration_get.go +++ b/cli/schema_migration_get.go @@ -11,21 +11,10 @@ package cli import ( - "encoding/json" - "io" - "net/http" - "os" - "github.com/spf13/cobra" - - httpapi "github.com/sourcenetwork/defradb/api/http" - "github.com/sourcenetwork/defradb/client" - "github.com/sourcenetwork/defradb/config" - "github.com/sourcenetwork/defradb/errors" - "github.com/sourcenetwork/defradb/logging" ) -func MakeSchemaMigrationGetCommand(cfg *config.Config) *cobra.Command { +func MakeSchemaMigrationGetCommand() *cobra.Command { var cmd = &cobra.Command{ Use: "get", Short: "Gets the schema migrations within DefraDB", @@ -35,63 +24,14 @@ Example: defradb client schema migration get' Learn more about the DefraDB GraphQL Schema Language on https://docs.source.network.`, - RunE: func(cmd *cobra.Command, args []string) (err error) { - if err := cobra.NoArgs(cmd, args); err != nil { - return NewErrTooManyArgs(0, len(args)) - } - - endpoint, err := httpapi.JoinPaths(cfg.API.AddressToURL(), httpapi.SchemaMigrationPath) - if err != nil { - return errors.Wrap("join paths failed", err) - } + RunE: func(cmd *cobra.Command, args []string) error { + store := mustGetStoreContext(cmd) - res, err := http.Get(endpoint.String()) + cfgs, err := store.LensRegistry().Config(cmd.Context()) if err != nil { - return errors.Wrap("failed to get schema migrations", err) + return err } - - defer func() { - if e := res.Body.Close(); e != nil { - err = NewErrFailedToCloseResponseBody(e, err) - } - }() - - response, err := io.ReadAll(res.Body) - if err != nil { - return errors.Wrap("failed to read response body", err) - } - - stdout, err := os.Stdout.Stat() - if err != nil { - return errors.Wrap("failed to stat stdout", err) - } - if isFileInfoPipe(stdout) { - cmd.Println(string(response)) - } else { - type migrationGetResponse struct { - Data struct { - Configuration []client.LensConfig `json:"configuration"` - } `json:"data"` - Errors []struct { - Message string `json:"message"` - } `json:"errors"` - } - r := migrationGetResponse{} - err = json.Unmarshal(response, &r) - log.FeedbackInfo(cmd.Context(), string(response)) - if err != nil { - return NewErrFailedToUnmarshalResponse(err) - } - if len(r.Errors) > 0 { - log.FeedbackError(cmd.Context(), "Failed to get schema migrations", - logging.NewKV("Errors", r.Errors)) - } else { - log.FeedbackInfo(cmd.Context(), "Successfully got schema migrations", - logging.NewKV("Configuration", r.Data.Configuration)) - } - } - - return nil + return writeJSON(cmd, cfgs) }, } return cmd diff --git a/cli/schema_migration_reload.go b/cli/schema_migration_reload.go new file mode 100644 index 0000000000..d04aebed65 --- /dev/null +++ b/cli/schema_migration_reload.go @@ -0,0 +1,35 @@ +// Copyright 2023 Democratized Data Foundation +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +package cli + +import ( + "github.com/spf13/cobra" + + "github.com/sourcenetwork/defradb/datastore" +) + +func MakeSchemaMigrationReloadCommand() *cobra.Command { + var cmd = &cobra.Command{ + Use: "reload", + Short: "Reload the schema migrations within DefraDB", + Long: `Reload the schema migrations within DefraDB`, + RunE: func(cmd *cobra.Command, args []string) error { + store := mustGetStoreContext(cmd) + + lens := store.LensRegistry() + if tx, ok := cmd.Context().Value(txContextKey).(datastore.Txn); ok { + lens = lens.WithTxn(tx) + } + return lens.ReloadLenses(cmd.Context()) + }, + } + return cmd +} diff --git a/cli/schema_migration_set.go b/cli/schema_migration_set.go index 633cbf0115..280130b8db 100644 --- a/cli/schema_migration_set.go +++ b/cli/schema_migration_set.go @@ -13,21 +13,16 @@ package cli import ( "encoding/json" "io" - "net/http" "os" "strings" "github.com/lens-vm/lens/host-go/config/model" "github.com/spf13/cobra" - httpapi "github.com/sourcenetwork/defradb/api/http" "github.com/sourcenetwork/defradb/client" - "github.com/sourcenetwork/defradb/config" - "github.com/sourcenetwork/defradb/errors" - "github.com/sourcenetwork/defradb/logging" ) -func MakeSchemaMigrationSetCommand(cfg *config.Config) *cobra.Command { +func MakeSchemaMigrationSetCommand() *cobra.Command { var lensFile string var cmd = &cobra.Command{ Use: "set [src] [dst] [cfg]", @@ -44,73 +39,39 @@ Example: add from stdin: cat schema_migration.lens | defradb client schema migration set bae123 bae456 - Learn more about the DefraDB GraphQL Schema Language on https://docs.source.network.`, - RunE: func(cmd *cobra.Command, args []string) (err error) { - if err := cobra.MinimumNArgs(2)(cmd, args); err != nil { - return NewErrMissingArgs([]string{"src", "dst", "cfg"}) - } - if err := cobra.MaximumNArgs(3)(cmd, args); err != nil { - return NewErrTooManyArgs(3, len(args)) - } + Args: cobra.RangeArgs(2, 3), + RunE: func(cmd *cobra.Command, args []string) error { + store := mustGetStoreContext(cmd) var lensCfgJson string - var srcSchemaVersionID string - var dstSchemaVersionID string - fi, err := os.Stdin.Stat() - if err != nil { - return err - } - - if lensFile != "" { - buf, err := os.ReadFile(lensFile) + switch { + case lensFile != "": + data, err := os.ReadFile(lensFile) if err != nil { - return errors.Wrap("failed to read schema file", err) + return err } - lensCfgJson = string(buf) - } else if len(args) == 2 { - // If the lensFile flag has not been provided then it must be provided as an arg - // and thus len(args) cannot be 2 - return NewErrMissingArg("cfg") - } else if isFileInfoPipe(fi) && args[2] != "-" { - log.FeedbackInfo( - cmd.Context(), - "Run 'defradb client schema migration set -' to read from stdin."+ - " Example: 'cat schema_migration.lens | defradb client schema migration set -').", - ) - return nil - } else if args[2] == "-" { - stdin, err := readStdin() + lensCfgJson = string(data) + case len(args) == 3 && args[2] == "-": + data, err := io.ReadAll(cmd.InOrStdin()) if err != nil { - return errors.Wrap("failed to read stdin", err) + return err } - if len(stdin) == 0 { - return errors.New("no lens cfg in stdin provided") - } else { - lensCfgJson = stdin - } - } else { + lensCfgJson = string(data) + case len(args) == 3: lensCfgJson = args[2] + default: + return ErrNoLensConfig } - srcSchemaVersionID = args[0] - dstSchemaVersionID = args[1] - - if lensCfgJson == "" { - return NewErrMissingArg("cfg") - } - if srcSchemaVersionID == "" { - return NewErrMissingArg("src") - } - if dstSchemaVersionID == "" { - return NewErrMissingArg("dst") - } + srcSchemaVersionID := args[0] + dstSchemaVersionID := args[1] decoder := json.NewDecoder(strings.NewReader(lensCfgJson)) decoder.DisallowUnknownFields() var lensCfg model.Lens - err = decoder.Decode(&lensCfg) - if err != nil { - return errors.Wrap("invalid lens configuration", err) + if err := decoder.Decode(&lensCfg); err != nil { + return NewErrInvalidLensConfig(err) } migrationCfg := client.LensConfig{ @@ -119,58 +80,7 @@ Learn more about the DefraDB GraphQL Schema Language on https://docs.source.netw Lens: lensCfg, } - migrationCfgJson, err := json.Marshal(migrationCfg) - if err != nil { - return errors.Wrap("failed to marshal cfg", err) - } - - endpoint, err := httpapi.JoinPaths(cfg.API.AddressToURL(), httpapi.SchemaMigrationPath) - if err != nil { - return errors.Wrap("join paths failed", err) - } - - res, err := http.Post(endpoint.String(), "application/json", strings.NewReader(string(migrationCfgJson))) - if err != nil { - return errors.Wrap("failed to post schema migration", err) - } - - defer func() { - if e := res.Body.Close(); e != nil { - err = NewErrFailedToCloseResponseBody(e, err) - } - }() - - response, err := io.ReadAll(res.Body) - if err != nil { - return errors.Wrap("failed to read response body", err) - } - - stdout, err := os.Stdout.Stat() - if err != nil { - return errors.Wrap("failed to stat stdout", err) - } - if isFileInfoPipe(stdout) { - cmd.Println(string(response)) - } else { - type migrationSetResponse struct { - Errors []struct { - Message string `json:"message"` - } `json:"errors"` - } - r := migrationSetResponse{} - err = json.Unmarshal(response, &r) - if err != nil { - return NewErrFailedToUnmarshalResponse(err) - } - if len(r.Errors) > 0 { - log.FeedbackError(cmd.Context(), "Failed to set schema migration", - logging.NewKV("Errors", r.Errors)) - } else { - log.FeedbackInfo(cmd.Context(), "Successfully set schema migration") - } - } - - return nil + return store.LensRegistry().SetMigration(cmd.Context(), migrationCfg) }, } cmd.Flags().StringVarP(&lensFile, "file", "f", "", "Lens configuration file") diff --git a/cli/schema_migration_up.go b/cli/schema_migration_up.go new file mode 100644 index 0000000000..3b0b522349 --- /dev/null +++ b/cli/schema_migration_up.go @@ -0,0 +1,91 @@ +// Copyright 2023 Democratized Data Foundation +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +package cli + +import ( + "encoding/json" + "io" + "os" + + "github.com/sourcenetwork/immutable/enumerable" + "github.com/spf13/cobra" + + "github.com/sourcenetwork/defradb/datastore" +) + +func MakeSchemaMigrationUpCommand() *cobra.Command { + var file string + var schemaVersionID string + var cmd = &cobra.Command{ + Use: "up --version ", + Short: "Applies the migration to the specified schema version.", + Long: `Applies the migration to the specified schema version. +Documents is a list of documents to apply the migration to. + +Example: migrate from string + defradb client schema migration up --version bae123 '[{"name": "Bob"}]' + +Example: migrate from file + defradb client schema migration up --version bae123 -f documents.json + +Example: migrate from stdin + cat documents.json | defradb client schema migration up --version bae123 - + `, + Args: cobra.RangeArgs(0, 1), + RunE: func(cmd *cobra.Command, args []string) error { + store := mustGetStoreContext(cmd) + + var srcData []byte + switch { + case file != "": + data, err := os.ReadFile(file) + if err != nil { + return err + } + srcData = data + case len(args) == 1 && args[0] == "-": + data, err := io.ReadAll(cmd.InOrStdin()) + if err != nil { + return err + } + srcData = data + case len(args) == 1: + srcData = []byte(args[0]) + default: + return ErrNoDocOrFile + } + + var src []map[string]any + if err := json.Unmarshal(srcData, &src); err != nil { + return err + } + lens := store.LensRegistry() + if tx, ok := cmd.Context().Value(txContextKey).(datastore.Txn); ok { + lens = lens.WithTxn(tx) + } + out, err := lens.MigrateUp(cmd.Context(), enumerable.New(src), schemaVersionID) + if err != nil { + return err + } + var value []map[string]any + err = enumerable.ForEach(out, func(item map[string]any) { + value = append(value, item) + }) + if err != nil { + return err + } + return writeJSON(cmd, value) + }, + } + cmd.Flags().StringVarP(&file, "file", "f", "", "File containing document(s)") + cmd.Flags().StringVar(&schemaVersionID, "version", "", "Schema version id") + return cmd +} diff --git a/cli/schema_patch.go b/cli/schema_patch.go index b1e962c51a..70f4283c85 100644 --- a/cli/schema_patch.go +++ b/cli/schema_patch.go @@ -11,21 +11,16 @@ package cli import ( - "encoding/json" + "fmt" "io" - "net/http" "os" - "strings" "github.com/spf13/cobra" - - httpapi "github.com/sourcenetwork/defradb/api/http" - "github.com/sourcenetwork/defradb/config" ) -func MakeSchemaPatchCommand(cfg *config.Config) *cobra.Command { +func MakeSchemaPatchCommand() *cobra.Command { var patchFile string - + var setDefault bool var cmd = &cobra.Command{ Use: "patch [schema]", Short: "Patch an existing schema type", @@ -43,113 +38,33 @@ Example: patch from stdin: cat patch.json | defradb client schema patch - To learn more about the DefraDB GraphQL Schema Language, refer to https://docs.source.network.`, - RunE: func(cmd *cobra.Command, args []string) (err error) { - var patch string - fi, err := os.Stdin.Stat() - if err != nil { - return err - } + RunE: func(cmd *cobra.Command, args []string) error { + store := mustGetStoreContext(cmd) - if len(args) > 1 { - if err = cmd.Usage(); err != nil { - return err - } - return NewErrTooManyArgs(1, len(args)) - } - - if patchFile != "" { - buf, err := os.ReadFile(patchFile) + var patch string + switch { + case patchFile != "": + data, err := os.ReadFile(patchFile) if err != nil { - return NewFailedToReadFile(err) + return err } - patch = string(buf) - } else if isFileInfoPipe(fi) && (len(args) == 0 || args[0] != "-") { - log.FeedbackInfo( - cmd.Context(), - "Run 'defradb client schema patch -' to read from stdin."+ - " Example: 'cat patch.json | defradb client schema patch -').", - ) - return nil - } else if len(args) == 0 { - // ignore error, nothing we can do about it - // as printing an error about failing to print help - // is useless - //nolint:errcheck - cmd.Help() - return nil - } else if args[0] == "-" { - stdin, err := readStdin() + patch = string(data) + case len(args) > 0 && args[0] == "-": + data, err := io.ReadAll(cmd.InOrStdin()) if err != nil { - return NewFailedToReadStdin(err) - } - if len(stdin) == 0 { - return ErrEmptyStdin - } else { - patch = stdin + return err } - } else { + patch = string(data) + case len(args) > 0: patch = args[0] + default: + return fmt.Errorf("patch cannot be empty") } - if patch == "" { - return ErrEmptyFile - } - - endpoint, err := httpapi.JoinPaths(cfg.API.AddressToURL(), httpapi.SchemaPath) - if err != nil { - return err - } - - req, err := http.NewRequest(http.MethodPatch, endpoint.String(), strings.NewReader(patch)) - if err != nil { - return NewErrFailedToSendRequest(err) - } - res, err := http.DefaultClient.Do(req) - if err != nil { - return NewErrFailedToSendRequest(err) - } - - //nolint:errcheck - defer res.Body.Close() - response, err := io.ReadAll(res.Body) - if err != nil { - return NewErrFailedToReadResponseBody(err) - } - - stdout, err := os.Stdout.Stat() - if err != nil { - return NewErrFailedToStatStdOut(err) - } - if isFileInfoPipe(stdout) { - cmd.Println(string(response)) - } else { - graphlErr, err := hasGraphQLErrors(response) - if err != nil { - return NewErrFailedToHandleGQLErrors(err) - } - if graphlErr { - indentedResult, err := indentJSON(response) - if err != nil { - return NewErrFailedToPrettyPrintResponse(err) - } - log.FeedbackError(cmd.Context(), indentedResult) - } else { - type schemaResponse struct { - Data struct { - Result string `json:"result"` - } `json:"data"` - } - r := schemaResponse{} - err = json.Unmarshal(response, &r) - if err != nil { - return NewErrFailedToUnmarshalResponse(err) - } - log.FeedbackInfo(cmd.Context(), r.Data.Result) - } - } - return nil + return store.PatchSchema(cmd.Context(), patch, setDefault) }, } + cmd.Flags().BoolVar(&setDefault, "set-default", false, "Set default schema version") cmd.Flags().StringVarP(&patchFile, "file", "f", "", "File to load a patch from") return cmd } diff --git a/cli/schema_set_default.go b/cli/schema_set_default.go new file mode 100644 index 0000000000..cdb6bd8bd8 --- /dev/null +++ b/cli/schema_set_default.go @@ -0,0 +1,29 @@ +// Copyright 2023 Democratized Data Foundation +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +package cli + +import ( + "github.com/spf13/cobra" +) + +func MakeSchemaSetDefaultCommand() *cobra.Command { + var cmd = &cobra.Command{ + Use: "set-default [versionID]", + Short: "Set the default schema version", + Long: `Set the default schema version`, + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + store := mustGetStoreContext(cmd) + return store.SetDefaultSchemaVersion(cmd.Context(), args[0]) + }, + } + return cmd +} diff --git a/cli/serverdump.go b/cli/server_dump.go similarity index 100% rename from cli/serverdump.go rename to cli/server_dump.go diff --git a/cli/start.go b/cli/start.go index 9185af8c92..c3b869fbf8 100644 --- a/cli/start.go +++ b/cli/start.go @@ -28,19 +28,21 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/keepalive" - httpapi "github.com/sourcenetwork/defradb/api/http" "github.com/sourcenetwork/defradb/client" "github.com/sourcenetwork/defradb/config" ds "github.com/sourcenetwork/defradb/datastore" badgerds "github.com/sourcenetwork/defradb/datastore/badger/v4" "github.com/sourcenetwork/defradb/db" "github.com/sourcenetwork/defradb/errors" + httpapi "github.com/sourcenetwork/defradb/http" "github.com/sourcenetwork/defradb/logging" "github.com/sourcenetwork/defradb/net" netpb "github.com/sourcenetwork/defradb/net/pb" netutils "github.com/sourcenetwork/defradb/net/utils" ) +const badgerDatastoreName = "badger" + func MakeStartCommand(cfg *config.Config) *cobra.Command { var cmd = &cobra.Command{ Use: "start", @@ -48,27 +50,11 @@ func MakeStartCommand(cfg *config.Config) *cobra.Command { Long: "Start a DefraDB node.", // Load the root config if it exists, otherwise create it. PersistentPreRunE: func(cmd *cobra.Command, _ []string) error { - if err := cfg.LoadRootDirFromFlagOrDefault(); err != nil { + if err := loadConfig(cfg); err != nil { return err } - if cfg.ConfigFileExists() { - if err := cfg.LoadWithRootdir(true); err != nil { - return config.NewErrLoadingConfig(err) - } - log.FeedbackInfo(cmd.Context(), fmt.Sprintf("Configuration loaded from DefraDB directory %v", cfg.Rootdir)) - } else { - if err := cfg.LoadWithRootdir(false); err != nil { - return config.NewErrLoadingConfig(err) - } - if config.FolderExists(cfg.Rootdir) { - if err := cfg.WriteConfigFile(); err != nil { - return err - } - } else { - if err := cfg.CreateRootDirAndConfigFile(); err != nil { - return err - } - } + if !cfg.ConfigFileExists() { + return createConfig(cfg) } return nil }, @@ -351,16 +337,7 @@ func start(ctx context.Context, cfg *config.Config) (*defraInstance, error) { // run the server in a separate goroutine go func() { - log.FeedbackInfo( - ctx, - fmt.Sprintf( - "Providing HTTP API at %s%s. Use the GraphQL request endpoint at %s%s/graphql ", - cfg.API.AddressToURL(), - httpapi.RootPath, - cfg.API.AddressToURL(), - httpapi.RootPath, - ), - ) + log.FeedbackInfo(ctx, fmt.Sprintf("Providing HTTP API at %s.", cfg.API.AddressToURL())) if err := s.Run(ctx); err != nil && !errors.Is(err, http.ErrServerClosed) { log.FeedbackErrorE(ctx, "Failed to run the HTTP server", err) if n != nil { diff --git a/api/http/http.go b/cli/tx.go similarity index 51% rename from api/http/http.go rename to cli/tx.go index 3ac3d62bdd..b4d278df6d 100644 --- a/api/http/http.go +++ b/cli/tx.go @@ -1,4 +1,4 @@ -// Copyright 2022 Democratized Data Foundation +// Copyright 2023 Democratized Data Foundation // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt. @@ -8,11 +8,18 @@ // by the Apache License, Version 2.0, included in the file // licenses/APL.txt. -/* -Package http provides DefraDB's HTTP API, offering various capabilities. -*/ -package http +package cli -import "github.com/sourcenetwork/defradb/logging" +import ( + "github.com/spf13/cobra" +) -var log = logging.MustNewLogger("http") +func MakeTxCommand() *cobra.Command { + var cmd = &cobra.Command{ + Use: "tx", + Short: "Create, commit, and discard DefraDB transactions", + Long: `Create, commit, and discard DefraDB transactions`, + } + + return cmd +} diff --git a/cli/tx_commit.go b/cli/tx_commit.go new file mode 100644 index 0000000000..260a274a08 --- /dev/null +++ b/cli/tx_commit.go @@ -0,0 +1,41 @@ +// Copyright 2023 Democratized Data Foundation +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +package cli + +import ( + "strconv" + + "github.com/spf13/cobra" + + "github.com/sourcenetwork/defradb/config" + "github.com/sourcenetwork/defradb/http" +) + +func MakeTxCommitCommand(cfg *config.Config) *cobra.Command { + var cmd = &cobra.Command{ + Use: "commit [id]", + Short: "Commit a DefraDB transaction.", + Long: `Commit a DefraDB transaction.`, + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) (err error) { + id, err := strconv.ParseUint(args[0], 10, 64) + if err != nil { + return err + } + tx, err := http.NewTransaction(cfg.API.Address, id) + if err != nil { + return err + } + return tx.Commit(cmd.Context()) + }, + } + return cmd +} diff --git a/cli/tx_create.go b/cli/tx_create.go new file mode 100644 index 0000000000..987a784077 --- /dev/null +++ b/cli/tx_create.go @@ -0,0 +1,46 @@ +// Copyright 2023 Democratized Data Foundation +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +package cli + +import ( + "github.com/spf13/cobra" + + "github.com/sourcenetwork/defradb/client" + "github.com/sourcenetwork/defradb/config" + "github.com/sourcenetwork/defradb/datastore" +) + +func MakeTxCreateCommand(cfg *config.Config) *cobra.Command { + var concurrent bool + var readOnly bool + var cmd = &cobra.Command{ + Use: "create", + Short: "Create a new DefraDB transaction.", + Long: `Create a new DefraDB transaction.`, + RunE: func(cmd *cobra.Command, args []string) (err error) { + db := cmd.Context().Value(dbContextKey).(client.DB) + + var tx datastore.Txn + if concurrent { + tx, err = db.NewConcurrentTxn(cmd.Context(), readOnly) + } else { + tx, err = db.NewTxn(cmd.Context(), readOnly) + } + if err != nil { + return err + } + return writeJSON(cmd, map[string]any{"id": tx.ID()}) + }, + } + cmd.Flags().BoolVar(&concurrent, "concurrent", false, "Transaction is concurrent") + cmd.Flags().BoolVar(&readOnly, "read-only", false, "Transaction is read only") + return cmd +} diff --git a/cli/tx_discard.go b/cli/tx_discard.go new file mode 100644 index 0000000000..351f919f53 --- /dev/null +++ b/cli/tx_discard.go @@ -0,0 +1,42 @@ +// Copyright 2023 Democratized Data Foundation +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +package cli + +import ( + "strconv" + + "github.com/spf13/cobra" + + "github.com/sourcenetwork/defradb/config" + "github.com/sourcenetwork/defradb/http" +) + +func MakeTxDiscardCommand(cfg *config.Config) *cobra.Command { + var cmd = &cobra.Command{ + Use: "discard [id]", + Short: "Discard a DefraDB transaction.", + Long: `Discard a DefraDB transaction.`, + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) (err error) { + id, err := strconv.ParseUint(args[0], 10, 64) + if err != nil { + return err + } + tx, err := http.NewTransaction(cfg.API.Address, id) + if err != nil { + return err + } + tx.Discard(cmd.Context()) + return nil + }, + } + return cmd +} diff --git a/cli/utils.go b/cli/utils.go new file mode 100644 index 0000000000..b9e4d1a710 --- /dev/null +++ b/cli/utils.go @@ -0,0 +1,112 @@ +// Copyright 2023 Democratized Data Foundation +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +package cli + +import ( + "context" + "encoding/json" + + "github.com/spf13/cobra" + + "github.com/sourcenetwork/defradb/client" + "github.com/sourcenetwork/defradb/config" + "github.com/sourcenetwork/defradb/datastore" + "github.com/sourcenetwork/defradb/http" +) + +type contextKey string + +var ( + // txContextKey is the context key for the datastore.Txn + // + // This will only be set if a transaction id is specified. + txContextKey = contextKey("tx") + // dbContextKey is the context key for the client.DB + dbContextKey = contextKey("db") + // storeContextKey is the context key for the client.Store + // + // If a transaction exists, all operations will be executed + // in the current transaction context. + storeContextKey = contextKey("store") + // colContextKey is the context key for the client.Collection + // + // If a transaction exists, all operations will be executed + // in the current transaction context. + colContextKey = contextKey("col") +) + +// mustGetStoreContext returns the store for the current command context. +// +// If a store is not set in the current context this function panics. +func mustGetStoreContext(cmd *cobra.Command) client.Store { + return cmd.Context().Value(storeContextKey).(client.Store) +} + +// tryGetCollectionContext returns the collection for the current command context +// and a boolean indicating if the collection was set. +func tryGetCollectionContext(cmd *cobra.Command) (client.Collection, bool) { + col, ok := cmd.Context().Value(colContextKey).(client.Collection) + return col, ok +} + +// setTransactionContext sets the transaction for the current command context. +func setTransactionContext(cmd *cobra.Command, cfg *config.Config, txId uint64) error { + if txId == 0 { + return nil + } + tx, err := http.NewTransaction(cfg.API.Address, txId) + if err != nil { + return err + } + ctx := context.WithValue(cmd.Context(), txContextKey, tx) + cmd.SetContext(ctx) + return nil +} + +// setStoreContext sets the store for the current command context. +func setStoreContext(cmd *cobra.Command, cfg *config.Config) error { + db, err := http.NewClient(cfg.API.Address) + if err != nil { + return err + } + ctx := context.WithValue(cmd.Context(), dbContextKey, db) + if tx, ok := ctx.Value(txContextKey).(datastore.Txn); ok { + ctx = context.WithValue(ctx, storeContextKey, db.WithTxn(tx)) + } else { + ctx = context.WithValue(ctx, storeContextKey, db) + } + cmd.SetContext(ctx) + return nil +} + +// loadConfig loads the rootDir containing the configuration file, +// otherwise warn about it and load a default configuration. +func loadConfig(cfg *config.Config) error { + if err := cfg.LoadRootDirFromFlagOrDefault(); err != nil { + return err + } + return cfg.LoadWithRootdir(cfg.ConfigFileExists()) +} + +// createConfig creates the config directories and writes +// the current config to a file. +func createConfig(cfg *config.Config) error { + if config.FolderExists(cfg.Rootdir) { + return cfg.WriteConfigFile() + } + return cfg.CreateRootDirAndConfigFile() +} + +func writeJSON(cmd *cobra.Command, out any) error { + enc := json.NewEncoder(cmd.OutOrStdout()) + enc.SetIndent("", " ") + return enc.Encode(out) +} diff --git a/cli/version.go b/cli/version.go index 8842697699..f61ecbc9d5 100644 --- a/cli/version.go +++ b/cli/version.go @@ -11,9 +11,7 @@ package cli import ( - "bytes" - - "encoding/json" + "strings" "github.com/spf13/cobra" @@ -31,25 +29,17 @@ func MakeVersionCommand() *cobra.Command { if err != nil { return err } - switch format { - case "json": - var buf bytes.Buffer - dvj, err := json.Marshal(dv) - if err != nil { - return err - } - err = json.Indent(&buf, dvj, "", " ") - if err != nil { - return err - } - cmd.Println(buf.String()) - default: - if full { - cmd.Println(dv.StringFull()) - } else { - cmd.Println(dv.String()) - } + + if strings.ToLower(format) == "json" { + return writeJSON(cmd, dv) } + + if full { + cmd.Println(dv.StringFull()) + } else { + cmd.Println(dv.String()) + } + return nil }, } diff --git a/client/document.go b/client/document.go index c48ccfce88..bcb8ae6070 100644 --- a/client/document.go +++ b/client/document.go @@ -398,6 +398,26 @@ func (doc *Document) ToMap() (map[string]any, error) { return doc.toMapWithKey() } +// ToJSONPatch returns a json patch that can be used to update +// a document by calling SetWithJSON. +func (doc *Document) ToJSONPatch() ([]byte, error) { + docMap, err := doc.toMap() + if err != nil { + return nil, err + } + + for field, value := range doc.Values() { + if !value.IsDirty() { + delete(docMap, field.Name()) + } + if value.IsDelete() { + docMap[field.Name()] = nil + } + } + + return json.Marshal(docMap) +} + // Clean cleans the document by removing all dirty fields. func (doc *Document) Clean() { for _, v := range doc.Fields() { diff --git a/cmd/defradb/main.go b/cmd/defradb/main.go index 761666bea7..2406885a76 100644 --- a/cmd/defradb/main.go +++ b/cmd/defradb/main.go @@ -12,7 +12,6 @@ package main import ( - "context" "os" "github.com/sourcenetwork/defradb/cli" @@ -21,10 +20,13 @@ import ( // Execute adds all child commands to the root command and sets flags appropriately. func main() { - cfg := config.DefaultConfig() - ctx := context.Background() - defraCmd := cli.NewDefraCommand(cfg) - if err := defraCmd.Execute(ctx); err != nil { + defraCmd := cli.NewDefraCommand(config.DefaultConfig()) + if err := defraCmd.Execute(); err != nil { + // this error is okay to discard because cobra + // logs any errors encountered during execution + // + // exiting with a non-zero status code signals + // that an error has ocurred during execution os.Exit(1) } } diff --git a/cmd/genclidocs/genclidocs.go b/cmd/genclidocs/main.go similarity index 59% rename from cmd/genclidocs/genclidocs.go rename to cmd/genclidocs/main.go index bccc96b38c..f556c26d20 100644 --- a/cmd/genclidocs/genclidocs.go +++ b/cmd/genclidocs/main.go @@ -14,30 +14,33 @@ genclidocs is a tool to generate the command line interface documentation. package main import ( - "context" "flag" + "log" "os" "github.com/spf13/cobra/doc" "github.com/sourcenetwork/defradb/cli" "github.com/sourcenetwork/defradb/config" - "github.com/sourcenetwork/defradb/logging" ) -var log = logging.MustNewLogger("genclidocs") +var path string + +func init() { + flag.StringVar(&path, "o", "docs/cmd", "path to write the cmd docs to") +} func main() { - path := flag.String("o", "docs/cmd", "path to write the cmd docs to") flag.Parse() - err := os.MkdirAll(*path, os.ModePerm) - if err != nil { - log.FatalE(context.Background(), "Creating the filesystem path failed", err) - } + defraCmd := cli.NewDefraCommand(config.DefaultConfig()) - defraCmd.RootCmd.DisableAutoGenTag = true - err = doc.GenMarkdownTree(defraCmd.RootCmd, *path) - if err != nil { - log.FatalE(context.Background(), "Generating cmd docs failed", err) + defraCmd.DisableAutoGenTag = true + + if err := os.MkdirAll(path, os.ModePerm); err != nil { + log.Fatal("Creating the filesystem path failed", err) + } + + if err := doc.GenMarkdownTree(defraCmd, path); err != nil { + log.Fatal("Generating cmd docs failed", err) } } diff --git a/cmd/genmanpages/main.go b/cmd/genmanpages/main.go index 7ec7a3ce59..1a9b43df7c 100644 --- a/cmd/genmanpages/main.go +++ b/cmd/genmanpages/main.go @@ -15,40 +15,39 @@ installation is packaging and system dependent. package main import ( - "context" "flag" + "log" "os" "github.com/spf13/cobra/doc" "github.com/sourcenetwork/defradb/cli" "github.com/sourcenetwork/defradb/config" - "github.com/sourcenetwork/defradb/logging" ) const defaultPerm os.FileMode = 0o777 -var log = logging.MustNewLogger("genmanpages") +var dir string + +var header = &doc.GenManHeader{ + Title: "defradb - Peer-to-Peer Edge Database", + Section: "1", +} + +func init() { + flag.StringVar(&dir, "o", "build/man", "Directory in which to generate DefraDB man pages") +} func main() { - dirFlag := flag.String("o", "build/man", "Directory in which to generate DefraDB man pages") flag.Parse() - genRootManPages(*dirFlag) -} -func genRootManPages(dir string) { - ctx := context.Background() - header := &doc.GenManHeader{ - Title: "defradb - Peer-to-Peer Edge Database", - Section: "1", - } - err := os.MkdirAll(dir, defaultPerm) - if err != nil { - log.FatalE(ctx, "Failed to create directory", err, logging.NewKV("dir", dir)) - } defraCmd := cli.NewDefraCommand(config.DefaultConfig()) - err = doc.GenManTree(defraCmd.RootCmd, header, dir) - if err != nil { - log.FatalE(ctx, "Failed generation of man pages", err) + + if err := os.MkdirAll(dir, defaultPerm); err != nil { + log.Fatal("Failed to create directory", err) + } + + if err := doc.GenManTree(defraCmd, header, dir); err != nil { + log.Fatal("Failed generation of man pages", err) } } diff --git a/docs/cli/defradb_client.md b/docs/cli/defradb_client.md index 7173befb6b..b538592ccc 100644 --- a/docs/cli/defradb_client.md +++ b/docs/cli/defradb_client.md @@ -10,7 +10,8 @@ Execute queries, add schema types, obtain node info, etc. ### Options ``` - -h, --help help for client + -h, --help help for client + --tx uint Transaction ID ``` ### Options inherited from parent commands @@ -30,12 +31,12 @@ Execute queries, add schema types, obtain node info, etc. * [defradb](defradb.md) - DefraDB Edge Database * [defradb client backup](defradb_client_backup.md) - Interact with the backup utility -* [defradb client blocks](defradb_client_blocks.md) - Interact with the database's blockstore +* [defradb client collection](defradb_client_collection.md) - View detailed collection info. +* [defradb client document](defradb_client_document.md) - Create, read, update, and delete documents. * [defradb client dump](defradb_client_dump.md) - Dump the contents of DefraDB node-side * [defradb client index](defradb_client_index.md) - Manage collections' indexes of a running DefraDB instance -* [defradb client peerid](defradb_client_peerid.md) - Get the PeerID of the node -* [defradb client ping](defradb_client_ping.md) - Ping to test connection with a node +* [defradb client p2p](defradb_client_p2p.md) - Interact with the DefraDB P2P system * [defradb client query](defradb_client_query.md) - Send a DefraDB GraphQL query request -* [defradb client rpc](defradb_client_rpc.md) - Interact with a DefraDB node via RPC * [defradb client schema](defradb_client_schema.md) - Interact with the schema system of a DefraDB node +* [defradb client tx](defradb_client_tx.md) - Create, commit, and discard DefraDB transactions diff --git a/docs/cli/defradb_client_backup.md b/docs/cli/defradb_client_backup.md index baa08725e1..77e111795d 100644 --- a/docs/cli/defradb_client_backup.md +++ b/docs/cli/defradb_client_backup.md @@ -23,6 +23,7 @@ Currently only supports JSON format. --logoutput string Log output path (default "stderr") --logtrace Include stacktrace in error and fatal logs --rootdir string Directory for data and configuration to use (default: $HOME/.defradb) + --tx uint Transaction ID --url string URL of HTTP endpoint to listen on or connect to (default "localhost:9181") ``` diff --git a/docs/cli/defradb_client_backup_export.md b/docs/cli/defradb_client_backup_export.md index ea8a22d634..b7547ea641 100644 --- a/docs/cli/defradb_client_backup_export.md +++ b/docs/cli/defradb_client_backup_export.md @@ -37,6 +37,7 @@ defradb client backup export [-c --collections | -p --pretty | -f --format] [flags] --logoutput string Log output path (default "stderr") --logtrace Include stacktrace in error and fatal logs --rootdir string Directory for data and configuration to use (default: $HOME/.defradb) + --tx uint Transaction ID --url string URL of HTTP endpoint to listen on or connect to (default "localhost:9181") ``` diff --git a/docs/cli/defradb_client_collection.md b/docs/cli/defradb_client_collection.md new file mode 100644 index 0000000000..2a1e9058be --- /dev/null +++ b/docs/cli/defradb_client_collection.md @@ -0,0 +1,52 @@ +## defradb client collection + +View detailed collection info. + +### Synopsis + +View detailed collection info. + +Example: view all collections + defradb client collection + +Example: view collection by name + defradb client collection --name User + +Example: view collection by schema id + defradb client collection --schema bae123 + +Example: view collection by version id + defradb client collection --version bae123 + + +``` +defradb client collection [--name --schema --version ] [flags] +``` + +### Options + +``` + -h, --help help for collection + --name string Get collection by name + --schema string Get collection by schema ID + --version string Get collection by version ID +``` + +### Options inherited from parent commands + +``` + --logformat string Log format to use. Options are csv, json (default "csv") + --logger stringArray Override logger parameters. Usage: --logger ,level=,output=,... + --loglevel string Log level to use. Options are debug, info, error, fatal (default "info") + --lognocolor Disable colored log output + --logoutput string Log output path (default "stderr") + --logtrace Include stacktrace in error and fatal logs + --rootdir string Directory for data and configuration to use (default: $HOME/.defradb) + --tx uint Transaction ID + --url string URL of HTTP endpoint to listen on or connect to (default "localhost:9181") +``` + +### SEE ALSO + +* [defradb client](defradb_client.md) - Interact with a DefraDB node + diff --git a/docs/cli/defradb_client_document.md b/docs/cli/defradb_client_document.md new file mode 100644 index 0000000000..bc527357e7 --- /dev/null +++ b/docs/cli/defradb_client_document.md @@ -0,0 +1,38 @@ +## defradb client document + +Create, read, update, and delete documents. + +### Synopsis + +Create, read, update, and delete documents. + +### Options + +``` + -h, --help help for document +``` + +### Options inherited from parent commands + +``` + --logformat string Log format to use. Options are csv, json (default "csv") + --logger stringArray Override logger parameters. Usage: --logger ,level=,output=,... + --loglevel string Log level to use. Options are debug, info, error, fatal (default "info") + --lognocolor Disable colored log output + --logoutput string Log output path (default "stderr") + --logtrace Include stacktrace in error and fatal logs + --rootdir string Directory for data and configuration to use (default: $HOME/.defradb) + --tx uint Transaction ID + --url string URL of HTTP endpoint to listen on or connect to (default "localhost:9181") +``` + +### SEE ALSO + +* [defradb client](defradb_client.md) - Interact with a DefraDB node +* [defradb client document create](defradb_client_document_create.md) - Create a new document. +* [defradb client document delete](defradb_client_document_delete.md) - Delete documents by key or filter. +* [defradb client document get](defradb_client_document_get.md) - View detailed document info. +* [defradb client document keys](defradb_client_document_keys.md) - List all collection document keys. +* [defradb client document save](defradb_client_document_save.md) - Create or update a document. +* [defradb client document update](defradb_client_document_update.md) - Update documents by key or filter. + diff --git a/docs/cli/defradb_client_document_create.md b/docs/cli/defradb_client_document_create.md new file mode 100644 index 0000000000..99dbd0d7f5 --- /dev/null +++ b/docs/cli/defradb_client_document_create.md @@ -0,0 +1,44 @@ +## defradb client document create + +Create a new document. + +### Synopsis + +Create a new document. + +Example: create document + defradb client document create --collection User '{ "name": "Bob" }' + +Example: create documents + defradb client document create --collection User '[{ "name": "Alice" }, { "name": "Bob" }]' + + +``` +defradb client document create --collection [flags] +``` + +### Options + +``` + -c, --collection string Collection name + -h, --help help for create +``` + +### Options inherited from parent commands + +``` + --logformat string Log format to use. Options are csv, json (default "csv") + --logger stringArray Override logger parameters. Usage: --logger ,level=,output=,... + --loglevel string Log level to use. Options are debug, info, error, fatal (default "info") + --lognocolor Disable colored log output + --logoutput string Log output path (default "stderr") + --logtrace Include stacktrace in error and fatal logs + --rootdir string Directory for data and configuration to use (default: $HOME/.defradb) + --tx uint Transaction ID + --url string URL of HTTP endpoint to listen on or connect to (default "localhost:9181") +``` + +### SEE ALSO + +* [defradb client document](defradb_client_document.md) - Create, read, update, and delete documents. + diff --git a/docs/cli/defradb_client_document_delete.md b/docs/cli/defradb_client_document_delete.md new file mode 100644 index 0000000000..96a0b1e973 --- /dev/null +++ b/docs/cli/defradb_client_document_delete.md @@ -0,0 +1,46 @@ +## defradb client document delete + +Delete documents by key or filter. + +### Synopsis + +Delete documents by key or filter and lists the number of documents deleted. + +Example: delete by key(s) + defradb client document delete --collection User --key bae-123,bae-456 + +Example: delete by filter + defradb client document delete --collection User --filter '{ "_gte": { "points": 100 } }' + + +``` +defradb client document delete --collection [--filter --key ] [flags] +``` + +### Options + +``` + -c, --collection string Collection name + --filter string Document filter + -h, --help help for delete + --key strings Document key +``` + +### Options inherited from parent commands + +``` + --logformat string Log format to use. Options are csv, json (default "csv") + --logger stringArray Override logger parameters. Usage: --logger ,level=,output=,... + --loglevel string Log level to use. Options are debug, info, error, fatal (default "info") + --lognocolor Disable colored log output + --logoutput string Log output path (default "stderr") + --logtrace Include stacktrace in error and fatal logs + --rootdir string Directory for data and configuration to use (default: $HOME/.defradb) + --tx uint Transaction ID + --url string URL of HTTP endpoint to listen on or connect to (default "localhost:9181") +``` + +### SEE ALSO + +* [defradb client document](defradb_client_document.md) - Create, read, update, and delete documents. + diff --git a/docs/cli/defradb_client_document_get.md b/docs/cli/defradb_client_document_get.md new file mode 100644 index 0000000000..600712ec0b --- /dev/null +++ b/docs/cli/defradb_client_document_get.md @@ -0,0 +1,42 @@ +## defradb client document get + +View detailed document info. + +### Synopsis + +View detailed document info. + +Example: + defradb client document get --collection User bae-123 + + +``` +defradb client document get --collection [--show-deleted] [flags] +``` + +### Options + +``` + -c, --collection string Collection name + -h, --help help for get + --show-deleted Show deleted documents +``` + +### Options inherited from parent commands + +``` + --logformat string Log format to use. Options are csv, json (default "csv") + --logger stringArray Override logger parameters. Usage: --logger ,level=,output=,... + --loglevel string Log level to use. Options are debug, info, error, fatal (default "info") + --lognocolor Disable colored log output + --logoutput string Log output path (default "stderr") + --logtrace Include stacktrace in error and fatal logs + --rootdir string Directory for data and configuration to use (default: $HOME/.defradb) + --tx uint Transaction ID + --url string URL of HTTP endpoint to listen on or connect to (default "localhost:9181") +``` + +### SEE ALSO + +* [defradb client document](defradb_client_document.md) - Create, read, update, and delete documents. + diff --git a/docs/cli/defradb_client_document_keys.md b/docs/cli/defradb_client_document_keys.md new file mode 100644 index 0000000000..e436f4df6b --- /dev/null +++ b/docs/cli/defradb_client_document_keys.md @@ -0,0 +1,41 @@ +## defradb client document keys + +List all collection document keys. + +### Synopsis + +List all collection document keys. + +Example: + defradb client document keys --collection User keys + + +``` +defradb client document keys --collection [flags] +``` + +### Options + +``` + -c, --collection string Collection name + -h, --help help for keys +``` + +### Options inherited from parent commands + +``` + --logformat string Log format to use. Options are csv, json (default "csv") + --logger stringArray Override logger parameters. Usage: --logger ,level=,output=,... + --loglevel string Log level to use. Options are debug, info, error, fatal (default "info") + --lognocolor Disable colored log output + --logoutput string Log output path (default "stderr") + --logtrace Include stacktrace in error and fatal logs + --rootdir string Directory for data and configuration to use (default: $HOME/.defradb) + --tx uint Transaction ID + --url string URL of HTTP endpoint to listen on or connect to (default "localhost:9181") +``` + +### SEE ALSO + +* [defradb client document](defradb_client_document.md) - Create, read, update, and delete documents. + diff --git a/docs/cli/defradb_client_document_save.md b/docs/cli/defradb_client_document_save.md new file mode 100644 index 0000000000..41f59a860c --- /dev/null +++ b/docs/cli/defradb_client_document_save.md @@ -0,0 +1,42 @@ +## defradb client document save + +Create or update a document. + +### Synopsis + +Create or update a document. + +Example: + defradb client document save --collection User --key bae-123 '{ "name": "Bob" }' + + +``` +defradb client document save --collection --key [flags] +``` + +### Options + +``` + -c, --collection string Collection name + -h, --help help for save + --key string Document key +``` + +### Options inherited from parent commands + +``` + --logformat string Log format to use. Options are csv, json (default "csv") + --logger stringArray Override logger parameters. Usage: --logger ,level=,output=,... + --loglevel string Log level to use. Options are debug, info, error, fatal (default "info") + --lognocolor Disable colored log output + --logoutput string Log output path (default "stderr") + --logtrace Include stacktrace in error and fatal logs + --rootdir string Directory for data and configuration to use (default: $HOME/.defradb) + --tx uint Transaction ID + --url string URL of HTTP endpoint to listen on or connect to (default "localhost:9181") +``` + +### SEE ALSO + +* [defradb client document](defradb_client_document.md) - Create, read, update, and delete documents. + diff --git a/docs/cli/defradb_client_document_update.md b/docs/cli/defradb_client_document_update.md new file mode 100644 index 0000000000..3efc67ebf0 --- /dev/null +++ b/docs/cli/defradb_client_document_update.md @@ -0,0 +1,52 @@ +## defradb client document update + +Update documents by key or filter. + +### Synopsis + +Update documents by key or filter. + +Example: + defradb client document update --collection User --key bae-123 '{ "name": "Bob" }' + +Example: update by filter + defradb client document update --collection User \ + --filter '{ "_gte": { "points": 100 } }' --updater '{ "verified": true }' + +Example: update by keys + defradb client document update --collection User \ + --key bae-123,bae-456 --updater '{ "verified": true }' + + +``` +defradb client document update --collection [--filter --key --updater ] [flags] +``` + +### Options + +``` + -c, --collection string Collection name + --filter string Document filter + -h, --help help for update + --key strings Document key + --updater string Document updater +``` + +### Options inherited from parent commands + +``` + --logformat string Log format to use. Options are csv, json (default "csv") + --logger stringArray Override logger parameters. Usage: --logger ,level=,output=,... + --loglevel string Log level to use. Options are debug, info, error, fatal (default "info") + --lognocolor Disable colored log output + --logoutput string Log output path (default "stderr") + --logtrace Include stacktrace in error and fatal logs + --rootdir string Directory for data and configuration to use (default: $HOME/.defradb) + --tx uint Transaction ID + --url string URL of HTTP endpoint to listen on or connect to (default "localhost:9181") +``` + +### SEE ALSO + +* [defradb client document](defradb_client_document.md) - Create, read, update, and delete documents. + diff --git a/docs/cli/defradb_client_dump.md b/docs/cli/defradb_client_dump.md index 862154bc17..3ebd35343c 100644 --- a/docs/cli/defradb_client_dump.md +++ b/docs/cli/defradb_client_dump.md @@ -22,6 +22,7 @@ defradb client dump [flags] --logoutput string Log output path (default "stderr") --logtrace Include stacktrace in error and fatal logs --rootdir string Directory for data and configuration to use (default: $HOME/.defradb) + --tx uint Transaction ID --url string URL of HTTP endpoint to listen on or connect to (default "localhost:9181") ``` diff --git a/docs/cli/defradb_client_index.md b/docs/cli/defradb_client_index.md index 4babb57d46..a876bbcc4f 100644 --- a/docs/cli/defradb_client_index.md +++ b/docs/cli/defradb_client_index.md @@ -22,6 +22,7 @@ Manage (create, drop, or list) collection indexes on a DefraDB node. --logoutput string Log output path (default "stderr") --logtrace Include stacktrace in error and fatal logs --rootdir string Directory for data and configuration to use (default: $HOME/.defradb) + --tx uint Transaction ID --url string URL of HTTP endpoint to listen on or connect to (default "localhost:9181") ``` diff --git a/docs/cli/defradb_client_index_create.md b/docs/cli/defradb_client_index_create.md index 7f67e58075..96b6418440 100644 --- a/docs/cli/defradb_client_index_create.md +++ b/docs/cli/defradb_client_index_create.md @@ -22,7 +22,7 @@ defradb client index create -c --collection --fields [-n - ``` -c, --collection string Collection name - --fields string Fields to index + --fields strings Fields to index -h, --help help for create -n, --name string Index name ``` @@ -37,6 +37,7 @@ defradb client index create -c --collection --fields [-n - --logoutput string Log output path (default "stderr") --logtrace Include stacktrace in error and fatal logs --rootdir string Directory for data and configuration to use (default: $HOME/.defradb) + --tx uint Transaction ID --url string URL of HTTP endpoint to listen on or connect to (default "localhost:9181") ``` diff --git a/docs/cli/defradb_client_index_drop.md b/docs/cli/defradb_client_index_drop.md index f551fe4658..c5171b756e 100644 --- a/docs/cli/defradb_client_index_drop.md +++ b/docs/cli/defradb_client_index_drop.md @@ -31,6 +31,7 @@ defradb client index drop -c --collection -n --name [flags] --logoutput string Log output path (default "stderr") --logtrace Include stacktrace in error and fatal logs --rootdir string Directory for data and configuration to use (default: $HOME/.defradb) + --tx uint Transaction ID --url string URL of HTTP endpoint to listen on or connect to (default "localhost:9181") ``` diff --git a/docs/cli/defradb_client_index_list.md b/docs/cli/defradb_client_index_list.md index bf434d30f2..c7e96d4e4f 100644 --- a/docs/cli/defradb_client_index_list.md +++ b/docs/cli/defradb_client_index_list.md @@ -33,6 +33,7 @@ defradb client index list [-c --collection ] [flags] --logoutput string Log output path (default "stderr") --logtrace Include stacktrace in error and fatal logs --rootdir string Directory for data and configuration to use (default: $HOME/.defradb) + --tx uint Transaction ID --url string URL of HTTP endpoint to listen on or connect to (default "localhost:9181") ``` diff --git a/docs/cli/defradb_client_rpc.md b/docs/cli/defradb_client_p2p.md similarity index 70% rename from docs/cli/defradb_client_rpc.md rename to docs/cli/defradb_client_p2p.md index d7046433c5..1132ee22ad 100644 --- a/docs/cli/defradb_client_rpc.md +++ b/docs/cli/defradb_client_p2p.md @@ -1,16 +1,15 @@ -## defradb client rpc +## defradb client p2p -Interact with a DefraDB node via RPC +Interact with the DefraDB P2P system ### Synopsis -Interact with a DefraDB node via RPC. +Interact with the DefraDB P2P system ### Options ``` - --addr string RPC endpoint address (default "0.0.0.0:9161") - -h, --help help for rpc + -h, --help help for p2p ``` ### Options inherited from parent commands @@ -23,12 +22,13 @@ Interact with a DefraDB node via RPC. --logoutput string Log output path (default "stderr") --logtrace Include stacktrace in error and fatal logs --rootdir string Directory for data and configuration to use (default: $HOME/.defradb) + --tx uint Transaction ID --url string URL of HTTP endpoint to listen on or connect to (default "localhost:9181") ``` ### SEE ALSO * [defradb client](defradb_client.md) - Interact with a DefraDB node -* [defradb client rpc p2pcollection](defradb_client_rpc_p2pcollection.md) - Configure the P2P collection system -* [defradb client rpc replicator](defradb_client_rpc_replicator.md) - Configure the replicator system +* [defradb client p2p collection](defradb_client_p2p_collection.md) - Configure the P2P collection system +* [defradb client p2p replicator](defradb_client_p2p_replicator.md) - Configure the replicator system diff --git a/docs/cli/defradb_client_rpc_p2pcollection.md b/docs/cli/defradb_client_p2p_collection.md similarity index 62% rename from docs/cli/defradb_client_rpc_p2pcollection.md rename to docs/cli/defradb_client_p2p_collection.md index ede32521d4..6fec3171da 100644 --- a/docs/cli/defradb_client_rpc_p2pcollection.md +++ b/docs/cli/defradb_client_p2p_collection.md @@ -1,4 +1,4 @@ -## defradb client rpc p2pcollection +## defradb client p2p collection Configure the P2P collection system @@ -10,13 +10,12 @@ The selected collections synchronize their events on the pubsub network. ### Options ``` - -h, --help help for p2pcollection + -h, --help help for collection ``` ### Options inherited from parent commands ``` - --addr string RPC endpoint address (default "0.0.0.0:9161") --logformat string Log format to use. Options are csv, json (default "csv") --logger stringArray Override logger parameters. Usage: --logger ,level=,output=,... --loglevel string Log level to use. Options are debug, info, error, fatal (default "info") @@ -24,13 +23,14 @@ The selected collections synchronize their events on the pubsub network. --logoutput string Log output path (default "stderr") --logtrace Include stacktrace in error and fatal logs --rootdir string Directory for data and configuration to use (default: $HOME/.defradb) + --tx uint Transaction ID --url string URL of HTTP endpoint to listen on or connect to (default "localhost:9181") ``` ### SEE ALSO -* [defradb client rpc](defradb_client_rpc.md) - Interact with a DefraDB node via RPC -* [defradb client rpc p2pcollection add](defradb_client_rpc_p2pcollection_add.md) - Add P2P collections -* [defradb client rpc p2pcollection getall](defradb_client_rpc_p2pcollection_getall.md) - Get all P2P collections -* [defradb client rpc p2pcollection remove](defradb_client_rpc_p2pcollection_remove.md) - Remove P2P collections +* [defradb client p2p](defradb_client_p2p.md) - Interact with the DefraDB P2P system +* [defradb client p2p collection add](defradb_client_p2p_collection_add.md) - Add P2P collections +* [defradb client p2p collection getall](defradb_client_p2p_collection_getall.md) - Get all P2P collections +* [defradb client p2p collection remove](defradb_client_p2p_collection_remove.md) - Remove P2P collections diff --git a/docs/cli/defradb_client_rpc_p2pcollection_add.md b/docs/cli/defradb_client_p2p_collection_add.md similarity index 77% rename from docs/cli/defradb_client_rpc_p2pcollection_add.md rename to docs/cli/defradb_client_p2p_collection_add.md index 92ac0d82e6..b5f3586144 100644 --- a/docs/cli/defradb_client_rpc_p2pcollection_add.md +++ b/docs/cli/defradb_client_p2p_collection_add.md @@ -1,4 +1,4 @@ -## defradb client rpc p2pcollection add +## defradb client p2p collection add Add P2P collections @@ -8,7 +8,7 @@ Add P2P collections to the synchronized pubsub topics. The collections are synchronized between nodes of a pubsub network. ``` -defradb client rpc p2pcollection add [collectionID] [flags] +defradb client p2p collection add [collectionID] [flags] ``` ### Options @@ -20,7 +20,6 @@ defradb client rpc p2pcollection add [collectionID] [flags] ### Options inherited from parent commands ``` - --addr string RPC endpoint address (default "0.0.0.0:9161") --logformat string Log format to use. Options are csv, json (default "csv") --logger stringArray Override logger parameters. Usage: --logger ,level=,output=,... --loglevel string Log level to use. Options are debug, info, error, fatal (default "info") @@ -28,10 +27,11 @@ defradb client rpc p2pcollection add [collectionID] [flags] --logoutput string Log output path (default "stderr") --logtrace Include stacktrace in error and fatal logs --rootdir string Directory for data and configuration to use (default: $HOME/.defradb) + --tx uint Transaction ID --url string URL of HTTP endpoint to listen on or connect to (default "localhost:9181") ``` ### SEE ALSO -* [defradb client rpc p2pcollection](defradb_client_rpc_p2pcollection.md) - Configure the P2P collection system +* [defradb client p2p collection](defradb_client_p2p_collection.md) - Configure the P2P collection system diff --git a/docs/cli/defradb_client_rpc_p2pcollection_getall.md b/docs/cli/defradb_client_p2p_collection_getall.md similarity index 78% rename from docs/cli/defradb_client_rpc_p2pcollection_getall.md rename to docs/cli/defradb_client_p2p_collection_getall.md index 946a2e0156..46fcefc407 100644 --- a/docs/cli/defradb_client_rpc_p2pcollection_getall.md +++ b/docs/cli/defradb_client_p2p_collection_getall.md @@ -1,4 +1,4 @@ -## defradb client rpc p2pcollection getall +## defradb client p2p collection getall Get all P2P collections @@ -8,7 +8,7 @@ Get all P2P collections in the pubsub topics. This is the list of collections of the node that are synchronized on the pubsub network. ``` -defradb client rpc p2pcollection getall [flags] +defradb client p2p collection getall [flags] ``` ### Options @@ -20,7 +20,6 @@ defradb client rpc p2pcollection getall [flags] ### Options inherited from parent commands ``` - --addr string RPC endpoint address (default "0.0.0.0:9161") --logformat string Log format to use. Options are csv, json (default "csv") --logger stringArray Override logger parameters. Usage: --logger ,level=,output=,... --loglevel string Log level to use. Options are debug, info, error, fatal (default "info") @@ -28,10 +27,11 @@ defradb client rpc p2pcollection getall [flags] --logoutput string Log output path (default "stderr") --logtrace Include stacktrace in error and fatal logs --rootdir string Directory for data and configuration to use (default: $HOME/.defradb) + --tx uint Transaction ID --url string URL of HTTP endpoint to listen on or connect to (default "localhost:9181") ``` ### SEE ALSO -* [defradb client rpc p2pcollection](defradb_client_rpc_p2pcollection.md) - Configure the P2P collection system +* [defradb client p2p collection](defradb_client_p2p_collection.md) - Configure the P2P collection system diff --git a/docs/cli/defradb_client_rpc_p2pcollection_remove.md b/docs/cli/defradb_client_p2p_collection_remove.md similarity index 77% rename from docs/cli/defradb_client_rpc_p2pcollection_remove.md rename to docs/cli/defradb_client_p2p_collection_remove.md index 77658b4d50..04492d2871 100644 --- a/docs/cli/defradb_client_rpc_p2pcollection_remove.md +++ b/docs/cli/defradb_client_p2p_collection_remove.md @@ -1,4 +1,4 @@ -## defradb client rpc p2pcollection remove +## defradb client p2p collection remove Remove P2P collections @@ -8,7 +8,7 @@ Remove P2P collections from the followed pubsub topics. The removed collections will no longer be synchronized between nodes. ``` -defradb client rpc p2pcollection remove [collectionID] [flags] +defradb client p2p collection remove [collectionID] [flags] ``` ### Options @@ -20,7 +20,6 @@ defradb client rpc p2pcollection remove [collectionID] [flags] ### Options inherited from parent commands ``` - --addr string RPC endpoint address (default "0.0.0.0:9161") --logformat string Log format to use. Options are csv, json (default "csv") --logger stringArray Override logger parameters. Usage: --logger ,level=,output=,... --loglevel string Log level to use. Options are debug, info, error, fatal (default "info") @@ -28,10 +27,11 @@ defradb client rpc p2pcollection remove [collectionID] [flags] --logoutput string Log output path (default "stderr") --logtrace Include stacktrace in error and fatal logs --rootdir string Directory for data and configuration to use (default: $HOME/.defradb) + --tx uint Transaction ID --url string URL of HTTP endpoint to listen on or connect to (default "localhost:9181") ``` ### SEE ALSO -* [defradb client rpc p2pcollection](defradb_client_rpc_p2pcollection.md) - Configure the P2P collection system +* [defradb client p2p collection](defradb_client_p2p_collection.md) - Configure the P2P collection system diff --git a/docs/cli/defradb_client_rpc_replicator.md b/docs/cli/defradb_client_p2p_replicator.md similarity index 75% rename from docs/cli/defradb_client_rpc_replicator.md rename to docs/cli/defradb_client_p2p_replicator.md index e88933791c..26f4041802 100644 --- a/docs/cli/defradb_client_rpc_replicator.md +++ b/docs/cli/defradb_client_p2p_replicator.md @@ -1,4 +1,4 @@ -## defradb client rpc replicator +## defradb client p2p replicator Configure the replicator system @@ -16,7 +16,6 @@ A replicator replicates one or all collection(s) from one node to another. ### Options inherited from parent commands ``` - --addr string RPC endpoint address (default "0.0.0.0:9161") --logformat string Log format to use. Options are csv, json (default "csv") --logger stringArray Override logger parameters. Usage: --logger ,level=,output=,... --loglevel string Log level to use. Options are debug, info, error, fatal (default "info") @@ -24,13 +23,14 @@ A replicator replicates one or all collection(s) from one node to another. --logoutput string Log output path (default "stderr") --logtrace Include stacktrace in error and fatal logs --rootdir string Directory for data and configuration to use (default: $HOME/.defradb) + --tx uint Transaction ID --url string URL of HTTP endpoint to listen on or connect to (default "localhost:9181") ``` ### SEE ALSO -* [defradb client rpc](defradb_client_rpc.md) - Interact with a DefraDB node via RPC -* [defradb client rpc replicator delete](defradb_client_rpc_replicator_delete.md) - Delete a replicator. It will stop synchronizing -* [defradb client rpc replicator getall](defradb_client_rpc_replicator_getall.md) - Get all replicators -* [defradb client rpc replicator set](defradb_client_rpc_replicator_set.md) - Set a P2P replicator +* [defradb client p2p](defradb_client_p2p.md) - Interact with the DefraDB P2P system +* [defradb client p2p replicator delete](defradb_client_p2p_replicator_delete.md) - Delete a replicator. It will stop synchronizing +* [defradb client p2p replicator getall](defradb_client_p2p_replicator_getall.md) - Get all replicators +* [defradb client p2p replicator set](defradb_client_p2p_replicator_set.md) - Set a P2P replicator diff --git a/docs/cli/defradb_client_ping.md b/docs/cli/defradb_client_p2p_replicator_delete.md similarity index 67% rename from docs/cli/defradb_client_ping.md rename to docs/cli/defradb_client_p2p_replicator_delete.md index 8edd7aff94..9ffbc115d3 100644 --- a/docs/cli/defradb_client_ping.md +++ b/docs/cli/defradb_client_p2p_replicator_delete.md @@ -1,15 +1,19 @@ -## defradb client ping +## defradb client p2p replicator delete -Ping to test connection with a node +Delete a replicator. It will stop synchronizing + +### Synopsis + +Delete a replicator. It will stop synchronizing. ``` -defradb client ping [flags] +defradb client p2p replicator delete [flags] ``` ### Options ``` - -h, --help help for ping + -h, --help help for delete ``` ### Options inherited from parent commands @@ -22,10 +26,11 @@ defradb client ping [flags] --logoutput string Log output path (default "stderr") --logtrace Include stacktrace in error and fatal logs --rootdir string Directory for data and configuration to use (default: $HOME/.defradb) + --tx uint Transaction ID --url string URL of HTTP endpoint to listen on or connect to (default "localhost:9181") ``` ### SEE ALSO -* [defradb client](defradb_client.md) - Interact with a DefraDB node +* [defradb client p2p replicator](defradb_client_p2p_replicator.md) - Configure the replicator system diff --git a/docs/cli/defradb_client_rpc_replicator_getall.md b/docs/cli/defradb_client_p2p_replicator_getall.md similarity index 82% rename from docs/cli/defradb_client_rpc_replicator_getall.md rename to docs/cli/defradb_client_p2p_replicator_getall.md index 2449dba1fd..080011ae65 100644 --- a/docs/cli/defradb_client_rpc_replicator_getall.md +++ b/docs/cli/defradb_client_p2p_replicator_getall.md @@ -1,4 +1,4 @@ -## defradb client rpc replicator getall +## defradb client p2p replicator getall Get all replicators @@ -8,7 +8,7 @@ Get all the replicators active in the P2P data sync system. These are the replicators that are currently replicating data from one node to another. ``` -defradb client rpc replicator getall [flags] +defradb client p2p replicator getall [flags] ``` ### Options @@ -20,7 +20,6 @@ defradb client rpc replicator getall [flags] ### Options inherited from parent commands ``` - --addr string RPC endpoint address (default "0.0.0.0:9161") --logformat string Log format to use. Options are csv, json (default "csv") --logger stringArray Override logger parameters. Usage: --logger ,level=,output=,... --loglevel string Log level to use. Options are debug, info, error, fatal (default "info") @@ -28,10 +27,11 @@ defradb client rpc replicator getall [flags] --logoutput string Log output path (default "stderr") --logtrace Include stacktrace in error and fatal logs --rootdir string Directory for data and configuration to use (default: $HOME/.defradb) + --tx uint Transaction ID --url string URL of HTTP endpoint to listen on or connect to (default "localhost:9181") ``` ### SEE ALSO -* [defradb client rpc replicator](defradb_client_rpc_replicator.md) - Configure the replicator system +* [defradb client p2p replicator](defradb_client_p2p_replicator.md) - Configure the replicator system diff --git a/docs/cli/defradb_client_rpc_replicator_set.md b/docs/cli/defradb_client_p2p_replicator_set.md similarity index 68% rename from docs/cli/defradb_client_rpc_replicator_set.md rename to docs/cli/defradb_client_p2p_replicator_set.md index 24b7add648..23d7b81404 100644 --- a/docs/cli/defradb_client_rpc_replicator_set.md +++ b/docs/cli/defradb_client_p2p_replicator_set.md @@ -1,4 +1,4 @@ -## defradb client rpc replicator set +## defradb client p2p replicator set Set a P2P replicator @@ -9,21 +9,19 @@ A replicator replicates one or all collection(s) from this node to another. ``` -defradb client rpc replicator set [-f, --full | -c, --collection] [flags] +defradb client p2p replicator set [-c, --collection] [flags] ``` ### Options ``` - -c, --collection stringArray Define the collection for the replicator - -f, --full Set the replicator to act on all collections - -h, --help help for set + -c, --collection strings Define the collection for the replicator + -h, --help help for set ``` ### Options inherited from parent commands ``` - --addr string RPC endpoint address (default "0.0.0.0:9161") --logformat string Log format to use. Options are csv, json (default "csv") --logger stringArray Override logger parameters. Usage: --logger ,level=,output=,... --loglevel string Log level to use. Options are debug, info, error, fatal (default "info") @@ -31,10 +29,11 @@ defradb client rpc replicator set [-f, --full | -c, --collection] [flags] --logoutput string Log output path (default "stderr") --logtrace Include stacktrace in error and fatal logs --rootdir string Directory for data and configuration to use (default: $HOME/.defradb) + --tx uint Transaction ID --url string URL of HTTP endpoint to listen on or connect to (default "localhost:9181") ``` ### SEE ALSO -* [defradb client rpc replicator](defradb_client_rpc_replicator.md) - Configure the replicator system +* [defradb client p2p replicator](defradb_client_p2p_replicator.md) - Configure the replicator system diff --git a/docs/cli/defradb_client_query.md b/docs/cli/defradb_client_query.md index 8f5c3477c3..5e748229e2 100644 --- a/docs/cli/defradb_client_query.md +++ b/docs/cli/defradb_client_query.md @@ -41,6 +41,7 @@ defradb client query [query request] [flags] --logoutput string Log output path (default "stderr") --logtrace Include stacktrace in error and fatal logs --rootdir string Directory for data and configuration to use (default: $HOME/.defradb) + --tx uint Transaction ID --url string URL of HTTP endpoint to listen on or connect to (default "localhost:9181") ``` diff --git a/docs/cli/defradb_client_rpc_addreplicator.md b/docs/cli/defradb_client_rpc_addreplicator.md deleted file mode 100644 index e80b667f18..0000000000 --- a/docs/cli/defradb_client_rpc_addreplicator.md +++ /dev/null @@ -1,37 +0,0 @@ -## defradb client rpc addreplicator - -Add a new replicator - -### Synopsis - -Use this command if you wish to add a new target replicator -for the P2P data sync system. - -``` -defradb client rpc addreplicator [flags] -``` - -### Options - -``` - -h, --help help for addreplicator -``` - -### Options inherited from parent commands - -``` - --addr string gRPC endpoint address (default "0.0.0.0:9161") - --logformat string Log format to use. Options are csv, json (default "csv") - --logger stringArray Override logger parameters. Usage: --logger ,level=,output=,... - --loglevel string Log level to use. Options are debug, info, error, fatal (default "info") - --lognocolor Disable colored log output - --logoutput string Log output path (default "stderr") - --logtrace Include stacktrace in error and fatal logs - --rootdir string Directory for data and configuration to use (default "$HOME/.defradb") - --url string URL of HTTP endpoint to listen on or connect to (default "localhost:9181") -``` - -### SEE ALSO - -* [defradb client rpc](defradb_client_rpc.md) - Interact with a DefraDB gRPC server - diff --git a/docs/cli/defradb_client_rpc_replicator_delete.md b/docs/cli/defradb_client_rpc_replicator_delete.md deleted file mode 100644 index c851d2f508..0000000000 --- a/docs/cli/defradb_client_rpc_replicator_delete.md +++ /dev/null @@ -1,38 +0,0 @@ -## defradb client rpc replicator delete - -Delete a replicator. It will stop synchronizing - -### Synopsis - -Delete a replicator. It will stop synchronizing. - -``` -defradb client rpc replicator delete [-f, --full | -c, --collection] [flags] -``` - -### Options - -``` - -c, --collection stringArray Define the collection for the replicator - -f, --full Set the replicator to act on all collections - -h, --help help for delete -``` - -### Options inherited from parent commands - -``` - --addr string RPC endpoint address (default "0.0.0.0:9161") - --logformat string Log format to use. Options are csv, json (default "csv") - --logger stringArray Override logger parameters. Usage: --logger ,level=,output=,... - --loglevel string Log level to use. Options are debug, info, error, fatal (default "info") - --lognocolor Disable colored log output - --logoutput string Log output path (default "stderr") - --logtrace Include stacktrace in error and fatal logs - --rootdir string Directory for data and configuration to use (default: $HOME/.defradb) - --url string URL of HTTP endpoint to listen on or connect to (default "localhost:9181") -``` - -### SEE ALSO - -* [defradb client rpc replicator](defradb_client_rpc_replicator.md) - Configure the replicator system - diff --git a/docs/cli/defradb_client_schema.md b/docs/cli/defradb_client_schema.md index c36c8d4bce..6b04bb2a5d 100644 --- a/docs/cli/defradb_client_schema.md +++ b/docs/cli/defradb_client_schema.md @@ -22,6 +22,7 @@ Make changes, updates, or look for existing schema types. --logoutput string Log output path (default "stderr") --logtrace Include stacktrace in error and fatal logs --rootdir string Directory for data and configuration to use (default: $HOME/.defradb) + --tx uint Transaction ID --url string URL of HTTP endpoint to listen on or connect to (default "localhost:9181") ``` @@ -29,7 +30,6 @@ Make changes, updates, or look for existing schema types. * [defradb client](defradb_client.md) - Interact with a DefraDB node * [defradb client schema add](defradb_client_schema_add.md) - Add new schema -* [defradb client schema list](defradb_client_schema_list.md) - List schema types with their respective fields * [defradb client schema migration](defradb_client_schema_migration.md) - Interact with the schema migration system of a running DefraDB instance * [defradb client schema patch](defradb_client_schema_patch.md) - Patch an existing schema type diff --git a/docs/cli/defradb_client_schema_add.md b/docs/cli/defradb_client_schema_add.md index b278431034..aa73039d0c 100644 --- a/docs/cli/defradb_client_schema_add.md +++ b/docs/cli/defradb_client_schema_add.md @@ -38,6 +38,7 @@ defradb client schema add [schema] [flags] --logoutput string Log output path (default "stderr") --logtrace Include stacktrace in error and fatal logs --rootdir string Directory for data and configuration to use (default: $HOME/.defradb) + --tx uint Transaction ID --url string URL of HTTP endpoint to listen on or connect to (default "localhost:9181") ``` diff --git a/docs/cli/defradb_client_schema_migration.md b/docs/cli/defradb_client_schema_migration.md index 0a20968378..91f2f324e3 100644 --- a/docs/cli/defradb_client_schema_migration.md +++ b/docs/cli/defradb_client_schema_migration.md @@ -22,12 +22,16 @@ Make set or look for existing schema migrations on a DefraDB node. --logoutput string Log output path (default "stderr") --logtrace Include stacktrace in error and fatal logs --rootdir string Directory for data and configuration to use (default: $HOME/.defradb) + --tx uint Transaction ID --url string URL of HTTP endpoint to listen on or connect to (default "localhost:9181") ``` ### SEE ALSO * [defradb client schema](defradb_client_schema.md) - Interact with the schema system of a DefraDB node +* [defradb client schema migration down](defradb_client_schema_migration_down.md) - Reverse a migration on the specified schema version. * [defradb client schema migration get](defradb_client_schema_migration_get.md) - Gets the schema migrations within DefraDB +* [defradb client schema migration reload](defradb_client_schema_migration_reload.md) - Reload the schema migrations within DefraDB * [defradb client schema migration set](defradb_client_schema_migration_set.md) - Set a schema migration within DefraDB +* [defradb client schema migration up](defradb_client_schema_migration_up.md) - Runs a migration on the specified schema version. diff --git a/docs/cli/defradb_client_schema_migration_down.md b/docs/cli/defradb_client_schema_migration_down.md new file mode 100644 index 0000000000..3d8a2eb6a5 --- /dev/null +++ b/docs/cli/defradb_client_schema_migration_down.md @@ -0,0 +1,37 @@ +## defradb client schema migration down + +Reverse a migration on the specified schema version. + +### Synopsis + +Reverse a migration on the specified schema version. + +``` +defradb client schema migration down --version [flags] +``` + +### Options + +``` + -h, --help help for down + --version string Schema version id +``` + +### Options inherited from parent commands + +``` + --logformat string Log format to use. Options are csv, json (default "csv") + --logger stringArray Override logger parameters. Usage: --logger ,level=,output=,... + --loglevel string Log level to use. Options are debug, info, error, fatal (default "info") + --lognocolor Disable colored log output + --logoutput string Log output path (default "stderr") + --logtrace Include stacktrace in error and fatal logs + --rootdir string Directory for data and configuration to use (default: $HOME/.defradb) + --tx uint Transaction ID + --url string URL of HTTP endpoint to listen on or connect to (default "localhost:9181") +``` + +### SEE ALSO + +* [defradb client schema migration](defradb_client_schema_migration.md) - Interact with the schema migration system of a running DefraDB instance + diff --git a/docs/cli/defradb_client_schema_migration_get.md b/docs/cli/defradb_client_schema_migration_get.md index d2164ed6bd..20ed8edb91 100644 --- a/docs/cli/defradb_client_schema_migration_get.md +++ b/docs/cli/defradb_client_schema_migration_get.md @@ -31,6 +31,7 @@ defradb client schema migration get [flags] --logoutput string Log output path (default "stderr") --logtrace Include stacktrace in error and fatal logs --rootdir string Directory for data and configuration to use (default: $HOME/.defradb) + --tx uint Transaction ID --url string URL of HTTP endpoint to listen on or connect to (default "localhost:9181") ``` diff --git a/docs/cli/defradb_client_schema_list.md b/docs/cli/defradb_client_schema_migration_reload.md similarity index 65% rename from docs/cli/defradb_client_schema_list.md rename to docs/cli/defradb_client_schema_migration_reload.md index ffbe253e31..f9acfd2d19 100644 --- a/docs/cli/defradb_client_schema_list.md +++ b/docs/cli/defradb_client_schema_migration_reload.md @@ -1,15 +1,19 @@ -## defradb client schema list +## defradb client schema migration reload -List schema types with their respective fields +Reload the schema migrations within DefraDB + +### Synopsis + +Reload the schema migrations within DefraDB ``` -defradb client schema list [flags] +defradb client schema migration reload [flags] ``` ### Options ``` - -h, --help help for list + -h, --help help for reload ``` ### Options inherited from parent commands @@ -22,10 +26,11 @@ defradb client schema list [flags] --logoutput string Log output path (default "stderr") --logtrace Include stacktrace in error and fatal logs --rootdir string Directory for data and configuration to use (default: $HOME/.defradb) + --tx uint Transaction ID --url string URL of HTTP endpoint to listen on or connect to (default "localhost:9181") ``` ### SEE ALSO -* [defradb client schema](defradb_client_schema.md) - Interact with the schema system of a DefraDB node +* [defradb client schema migration](defradb_client_schema_migration.md) - Interact with the schema migration system of a running DefraDB instance diff --git a/docs/cli/defradb_client_schema_migration_set.md b/docs/cli/defradb_client_schema_migration_set.md index 8013fd2a29..b9626bfeed 100644 --- a/docs/cli/defradb_client_schema_migration_set.md +++ b/docs/cli/defradb_client_schema_migration_set.md @@ -38,6 +38,7 @@ defradb client schema migration set [src] [dst] [cfg] [flags] --logoutput string Log output path (default "stderr") --logtrace Include stacktrace in error and fatal logs --rootdir string Directory for data and configuration to use (default: $HOME/.defradb) + --tx uint Transaction ID --url string URL of HTTP endpoint to listen on or connect to (default "localhost:9181") ``` diff --git a/docs/cli/defradb_client_schema_migration_up.md b/docs/cli/defradb_client_schema_migration_up.md new file mode 100644 index 0000000000..a637f2f28d --- /dev/null +++ b/docs/cli/defradb_client_schema_migration_up.md @@ -0,0 +1,37 @@ +## defradb client schema migration up + +Runs a migration on the specified schema version. + +### Synopsis + +Runs a migration on the specified schema version. + +``` +defradb client schema migration up --version [flags] +``` + +### Options + +``` + -h, --help help for up + --version string Schema version id +``` + +### Options inherited from parent commands + +``` + --logformat string Log format to use. Options are csv, json (default "csv") + --logger stringArray Override logger parameters. Usage: --logger ,level=,output=,... + --loglevel string Log level to use. Options are debug, info, error, fatal (default "info") + --lognocolor Disable colored log output + --logoutput string Log output path (default "stderr") + --logtrace Include stacktrace in error and fatal logs + --rootdir string Directory for data and configuration to use (default: $HOME/.defradb) + --tx uint Transaction ID + --url string URL of HTTP endpoint to listen on or connect to (default "localhost:9181") +``` + +### SEE ALSO + +* [defradb client schema migration](defradb_client_schema_migration.md) - Interact with the schema migration system of a running DefraDB instance + diff --git a/docs/cli/defradb_client_schema_patch.md b/docs/cli/defradb_client_schema_patch.md index ec64d293e0..ba04faddf2 100644 --- a/docs/cli/defradb_client_schema_patch.md +++ b/docs/cli/defradb_client_schema_patch.md @@ -40,6 +40,7 @@ defradb client schema patch [schema] [flags] --logoutput string Log output path (default "stderr") --logtrace Include stacktrace in error and fatal logs --rootdir string Directory for data and configuration to use (default: $HOME/.defradb) + --tx uint Transaction ID --url string URL of HTTP endpoint to listen on or connect to (default "localhost:9181") ``` diff --git a/docs/cli/defradb_client_blocks.md b/docs/cli/defradb_client_tx.md similarity index 63% rename from docs/cli/defradb_client_blocks.md rename to docs/cli/defradb_client_tx.md index e05a853440..4feab4af7b 100644 --- a/docs/cli/defradb_client_blocks.md +++ b/docs/cli/defradb_client_tx.md @@ -1,11 +1,15 @@ -## defradb client blocks +## defradb client tx -Interact with the database's blockstore +Create, commit, and discard DefraDB transactions + +### Synopsis + +Create, commit, and discard DefraDB transactions ### Options ``` - -h, --help help for blocks + -h, --help help for tx ``` ### Options inherited from parent commands @@ -18,11 +22,14 @@ Interact with the database's blockstore --logoutput string Log output path (default "stderr") --logtrace Include stacktrace in error and fatal logs --rootdir string Directory for data and configuration to use (default: $HOME/.defradb) + --tx uint Transaction ID --url string URL of HTTP endpoint to listen on or connect to (default "localhost:9181") ``` ### SEE ALSO * [defradb client](defradb_client.md) - Interact with a DefraDB node -* [defradb client blocks get](defradb_client_blocks_get.md) - Get a block by its CID from the blockstore +* [defradb client tx commit](defradb_client_tx_commit.md) - Commit a DefraDB transaction. +* [defradb client tx create](defradb_client_tx_create.md) - Create a new DefraDB transaction. +* [defradb client tx discard](defradb_client_tx_discard.md) - Discard a DefraDB transaction. diff --git a/docs/cli/defradb_client_peerid.md b/docs/cli/defradb_client_tx_commit.md similarity index 73% rename from docs/cli/defradb_client_peerid.md rename to docs/cli/defradb_client_tx_commit.md index f4596111c8..21f0b50325 100644 --- a/docs/cli/defradb_client_peerid.md +++ b/docs/cli/defradb_client_tx_commit.md @@ -1,19 +1,19 @@ -## defradb client peerid +## defradb client tx commit -Get the PeerID of the node +Commit a DefraDB transaction. ### Synopsis -Get the PeerID of the node. +Commit a DefraDB transaction. ``` -defradb client peerid [flags] +defradb client tx commit [id] [flags] ``` ### Options ``` - -h, --help help for peerid + -h, --help help for commit ``` ### Options inherited from parent commands @@ -26,10 +26,11 @@ defradb client peerid [flags] --logoutput string Log output path (default "stderr") --logtrace Include stacktrace in error and fatal logs --rootdir string Directory for data and configuration to use (default: $HOME/.defradb) + --tx uint Transaction ID --url string URL of HTTP endpoint to listen on or connect to (default "localhost:9181") ``` ### SEE ALSO -* [defradb client](defradb_client.md) - Interact with a DefraDB node +* [defradb client tx](defradb_client_tx.md) - Create, commit, and discard DefraDB transactions diff --git a/docs/cli/defradb_client_tx_create.md b/docs/cli/defradb_client_tx_create.md new file mode 100644 index 0000000000..8ba600b611 --- /dev/null +++ b/docs/cli/defradb_client_tx_create.md @@ -0,0 +1,38 @@ +## defradb client tx create + +Create a new DefraDB transaction. + +### Synopsis + +Create a new DefraDB transaction. + +``` +defradb client tx create [flags] +``` + +### Options + +``` + --concurrent Transaction is concurrent + -h, --help help for create + --read-only Transaction is read only +``` + +### Options inherited from parent commands + +``` + --logformat string Log format to use. Options are csv, json (default "csv") + --logger stringArray Override logger parameters. Usage: --logger ,level=,output=,... + --loglevel string Log level to use. Options are debug, info, error, fatal (default "info") + --lognocolor Disable colored log output + --logoutput string Log output path (default "stderr") + --logtrace Include stacktrace in error and fatal logs + --rootdir string Directory for data and configuration to use (default: $HOME/.defradb) + --tx uint Transaction ID + --url string URL of HTTP endpoint to listen on or connect to (default "localhost:9181") +``` + +### SEE ALSO + +* [defradb client tx](defradb_client_tx.md) - Create, commit, and discard DefraDB transactions + diff --git a/docs/cli/defradb_client_blocks_get.md b/docs/cli/defradb_client_tx_discard.md similarity index 71% rename from docs/cli/defradb_client_blocks_get.md rename to docs/cli/defradb_client_tx_discard.md index 38ff02b63c..d1f0bb6025 100644 --- a/docs/cli/defradb_client_blocks_get.md +++ b/docs/cli/defradb_client_tx_discard.md @@ -1,15 +1,19 @@ -## defradb client blocks get +## defradb client tx discard -Get a block by its CID from the blockstore +Discard a DefraDB transaction. + +### Synopsis + +Discard a DefraDB transaction. ``` -defradb client blocks get [CID] [flags] +defradb client tx discard [id] [flags] ``` ### Options ``` - -h, --help help for get + -h, --help help for discard ``` ### Options inherited from parent commands @@ -22,10 +26,11 @@ defradb client blocks get [CID] [flags] --logoutput string Log output path (default "stderr") --logtrace Include stacktrace in error and fatal logs --rootdir string Directory for data and configuration to use (default: $HOME/.defradb) + --tx uint Transaction ID --url string URL of HTTP endpoint to listen on or connect to (default "localhost:9181") ``` ### SEE ALSO -* [defradb client blocks](defradb_client_blocks.md) - Interact with the database's blockstore +* [defradb client tx](defradb_client_tx.md) - Create, commit, and discard DefraDB transactions diff --git a/go.mod b/go.mod index 5c99acb439..83ee818703 100644 --- a/go.mod +++ b/go.mod @@ -33,8 +33,6 @@ require ( github.com/multiformats/go-multiaddr v0.10.1 github.com/multiformats/go-multibase v0.2.0 github.com/multiformats/go-multihash v0.2.3 - github.com/pkg/errors v0.9.1 - github.com/planetscale/vtprotobuf v0.5.0 github.com/sourcenetwork/immutable v0.3.0 github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 @@ -49,6 +47,7 @@ require ( go.opentelemetry.io/otel/sdk/metric v0.40.0 go.uber.org/zap v1.25.0 golang.org/x/crypto v0.13.0 + golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 golang.org/x/net v0.14.0 google.golang.org/grpc v1.58.1 google.golang.org/protobuf v1.31.0 @@ -156,6 +155,7 @@ require ( github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // 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/polydawn/refmt v0.89.0 // indirect github.com/prometheus/client_golang v1.14.0 // indirect @@ -187,7 +187,6 @@ require ( go.uber.org/dig v1.17.0 // indirect go.uber.org/fx v1.20.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 // indirect golang.org/x/mod v0.12.0 // indirect golang.org/x/sync v0.3.0 // indirect golang.org/x/sys v0.12.0 // indirect diff --git a/go.sum b/go.sum index 3cb6fe1269..e198ec35a4 100644 --- a/go.sum +++ b/go.sum @@ -1086,8 +1086,6 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= -github.com/planetscale/vtprotobuf v0.5.0 h1:l8PXm6Colok5z6qQLNhAj2Jq5BfoMTIHxLER5a6nDqM= -github.com/planetscale/vtprotobuf v0.5.0/go.mod h1:wm1N3qk9G/4+VM1WhpkLbvY/d8+0PbwYYpP5P5VhTks= 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/polydawn/refmt v0.0.0-20190221155625-df39d6c2d992/go.mod h1:uIp+gprXxxrWSjjklXD+mN4wed/tMfjMMmN/9+JsA9o= diff --git a/http/client.go b/http/client.go index 867cdc3bb1..9dd7b7b065 100644 --- a/http/client.go +++ b/http/client.go @@ -35,11 +35,10 @@ type Client struct { } func NewClient(rawURL string) (*Client, error) { - baseURL, err := url.Parse(rawURL) + httpClient, err := newHttpClient(rawURL) if err != nil { return nil, err } - httpClient := newHttpClient(baseURL.JoinPath("/api/v0")) return &Client{httpClient}, nil } @@ -418,6 +417,20 @@ func (c *Client) PrintDump(ctx context.Context) error { return err } +func (c *Client) PeerInfo(ctx context.Context) (*PeerInfoResponse, error) { + methodURL := c.http.baseURL.JoinPath("p2p", "info") + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, methodURL.String(), nil) + if err != nil { + return nil, err + } + var res PeerInfoResponse + if err := c.http.requestJson(req, &res); err != nil { + return nil, err + } + return &res, nil +} + func (c *Client) Close(ctx context.Context) { // do nothing } diff --git a/http/client_collection.go b/http/client_collection.go index 16157a9f96..9641157d1b 100644 --- a/http/client_collection.go +++ b/http/client_collection.go @@ -93,7 +93,7 @@ func (c *Collection) CreateMany(ctx context.Context, docs []*client.Document) er return err } - docMap, err := documentJSON(doc) + docMap, err := doc.ToJSONPatch() if err != nil { return err } @@ -120,7 +120,7 @@ func (c *Collection) CreateMany(ctx context.Context, docs []*client.Document) er func (c *Collection) Update(ctx context.Context, doc *client.Document) error { methodURL := c.http.baseURL.JoinPath("collections", c.desc.Name, doc.Key().String()) - body, err := documentJSON(doc) + body, err := doc.ToJSONPatch() if err != nil { return err } @@ -313,7 +313,12 @@ func (c *Collection) Get(ctx context.Context, key client.DocKey, showDeleted boo if err := c.http.requestJson(req, &docMap); err != nil { return nil, err } - return client.NewDocFromMap(docMap) + doc, err := client.NewDocFromMap(docMap) + if err != nil { + return nil, err + } + doc.Clean() + return doc, nil } func (c *Collection) WithTxn(tx datastore.Txn) client.Collection { diff --git a/http/client_tx.go b/http/client_tx.go index 8df82007a6..ac16c29288 100644 --- a/http/client_tx.go +++ b/http/client_tx.go @@ -26,6 +26,14 @@ type Transaction struct { http *httpClient } +func NewTransaction(rawURL string, id uint64) (*Transaction, error) { + httpClient, err := newHttpClient(rawURL) + if err != nil { + return nil, err + } + return &Transaction{id, httpClient}, nil +} + func (c *Transaction) ID() uint64 { return c.id } diff --git a/http/errors.go b/http/errors.go index c2808603cf..848d293a91 100644 --- a/http/errors.go +++ b/http/errors.go @@ -15,22 +15,28 @@ import ( "errors" ) -const ( - errInvalidRequestBody = "invalid request body" - errDocKeyDoesNotMatch = "document key does not match" - errStreamingNotSupported = "streaming not supported" - errMigrationNotFound = "migration not found" - errMissingRequest = "missing request" - errInvalidTransactionId = "invalid transaction id" -) - +// Errors returnable from this package. +// +// This list is incomplete. Undefined errors may also be returned. +// Errors returned from this package may be tested against these errors with errors.Is. var ( - ErrInvalidRequestBody = errors.New(errInvalidRequestBody) - ErrDocKeyDoesNotMatch = errors.New(errDocKeyDoesNotMatch) - ErrStreamingNotSupported = errors.New(errStreamingNotSupported) - ErrMigrationNotFound = errors.New(errMigrationNotFound) - ErrMissingRequest = errors.New(errMissingRequest) - ErrInvalidTransactionId = errors.New(errInvalidTransactionId) + ErrNoListener = errors.New("cannot serve with no listener") + ErrSchema = errors.New("base must start with the http or https scheme") + ErrDatabaseNotAvailable = errors.New("no database available") + ErrFormNotSupported = errors.New("content type application/x-www-form-urlencoded not yet supported") + ErrBodyEmpty = errors.New("body cannot be empty") + ErrMissingGQLRequest = errors.New("missing GraphQL request") + ErrPeerIdUnavailable = errors.New("no PeerID available. P2P might be disabled") + ErrStreamingUnsupported = errors.New("streaming unsupported") + ErrNoEmail = errors.New("email address must be specified for tls with autocert") + ErrPayloadFormat = errors.New("invalid payload format") + ErrMissingNewKey = errors.New("missing _newKey for imported doc") + ErrInvalidRequestBody = errors.New("invalid request body") + ErrDocKeyDoesNotMatch = errors.New("document key does not match") + ErrStreamingNotSupported = errors.New("streaming not supported") + ErrMigrationNotFound = errors.New("migration not found") + ErrMissingRequest = errors.New("missing request") + ErrInvalidTransactionId = errors.New("invalid transaction id") ) type errorResponse struct { diff --git a/http/handler.go b/http/handler.go new file mode 100644 index 0000000000..242dc5938c --- /dev/null +++ b/http/handler.go @@ -0,0 +1,138 @@ +// Copyright 2023 Democratized Data Foundation +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +package http + +import ( + "fmt" + "net/http" + "sync" + + "github.com/sourcenetwork/defradb/client" + "github.com/sourcenetwork/defradb/datastore" + + "github.com/go-chi/chi/v5" + "github.com/go-chi/chi/v5/middleware" +) + +// Version is the identifier for the current API version. +var Version string = "v0" + +// playgroundHandler is set when building with the playground build tag +var playgroundHandler http.Handler = http.HandlerFunc(http.NotFound) + +type Handler struct { + db client.DB + router *chi.Mux + txs *sync.Map +} + +func NewHandler(db client.DB, opts ServerOptions) *Handler { + txs := &sync.Map{} + + tx_handler := &txHandler{} + store_handler := &storeHandler{} + collection_handler := &collectionHandler{} + lens_handler := &lensHandler{} + ccip_handler := &ccipHandler{} + + router := chi.NewRouter() + router.Use(middleware.RequestLogger(&logFormatter{})) + router.Use(middleware.Recoverer) + router.Use(CorsMiddleware(opts)) + router.Use(ApiMiddleware(db, txs, opts)) + + router.Route("/api/"+Version, func(api chi.Router) { + api.Use(TransactionMiddleware, StoreMiddleware) + api.Route("/tx", func(tx chi.Router) { + tx.Post("/", tx_handler.NewTxn) + tx.Post("/concurrent", tx_handler.NewConcurrentTxn) + tx.Post("/{id}", tx_handler.Commit) + tx.Delete("/{id}", tx_handler.Discard) + }) + api.Route("/backup", func(backup chi.Router) { + backup.Post("/export", store_handler.BasicExport) + backup.Post("/import", store_handler.BasicImport) + }) + api.Route("/schema", func(schema chi.Router) { + schema.Post("/", store_handler.AddSchema) + schema.Patch("/", store_handler.PatchSchema) + schema.Post("/default", store_handler.SetDefaultSchemaVersion) + }) + api.Route("/collections", func(collections chi.Router) { + collections.Get("/", store_handler.GetCollection) + // with collection middleware + collections_tx := collections.With(CollectionMiddleware) + collections_tx.Get("/{name}", collection_handler.GetAllDocKeys) + collections_tx.Post("/{name}", collection_handler.Create) + collections_tx.Patch("/{name}", collection_handler.UpdateWith) + collections_tx.Delete("/{name}", collection_handler.DeleteWith) + collections_tx.Post("/{name}/indexes", collection_handler.CreateIndex) + collections_tx.Get("/{name}/indexes", collection_handler.GetIndexes) + collections_tx.Delete("/{name}/indexes/{index}", collection_handler.DropIndex) + collections_tx.Get("/{name}/{key}", collection_handler.Get) + collections_tx.Patch("/{name}/{key}", collection_handler.Update) + collections_tx.Delete("/{name}/{key}", collection_handler.Delete) + }) + api.Route("/lens", func(lens chi.Router) { + lens.Use(LensMiddleware) + lens.Get("/", lens_handler.Config) + lens.Post("/", lens_handler.SetMigration) + lens.Post("/reload", lens_handler.ReloadLenses) + lens.Get("/{version}", lens_handler.HasMigration) + lens.Post("/{version}/up", lens_handler.MigrateUp) + lens.Post("/{version}/down", lens_handler.MigrateDown) + }) + api.Route("/graphql", func(graphQL chi.Router) { + graphQL.Get("/", store_handler.ExecRequest) + graphQL.Post("/", store_handler.ExecRequest) + }) + api.Route("/ccip", func(ccip chi.Router) { + ccip.Get("/{sender}/{data}", ccip_handler.ExecCCIP) + ccip.Post("/", ccip_handler.ExecCCIP) + }) + api.Route("/p2p", func(p2p chi.Router) { + p2p.Get("/info", store_handler.PeerInfo) + p2p.Route("/replicators", func(p2p_replicators chi.Router) { + p2p_replicators.Get("/", store_handler.GetAllReplicators) + p2p_replicators.Post("/", store_handler.SetReplicator) + p2p_replicators.Delete("/", store_handler.DeleteReplicator) + }) + p2p.Route("/collections", func(p2p_collections chi.Router) { + p2p_collections.Get("/", store_handler.GetAllP2PCollections) + p2p_collections.Post("/{id}", store_handler.AddP2PCollection) + p2p_collections.Delete("/{id}", store_handler.RemoveP2PCollection) + }) + }) + api.Route("/debug", func(debug chi.Router) { + debug.Get("/dump", store_handler.PrintDump) + }) + }) + + router.Handle("/*", playgroundHandler) + + return &Handler{ + db: db, + router: router, + txs: txs, + } +} + +func (h *Handler) Transaction(id uint64) (datastore.Txn, error) { + tx, ok := h.txs.Load(id) + if !ok { + return nil, fmt.Errorf("invalid transaction id") + } + return tx.(datastore.Txn), nil +} + +func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) { + h.router.ServeHTTP(w, req) +} diff --git a/http/handler_ccip_test.go b/http/handler_ccip_test.go index 7884e16df7..4fb9e5259c 100644 --- a/http/handler_ccip_test.go +++ b/http/handler_ccip_test.go @@ -49,7 +49,7 @@ func TestCCIPGet_WithValidData(t *testing.T) { req := httptest.NewRequest(http.MethodGet, url, nil) rec := httptest.NewRecorder() - handler := NewServer(cdb) + handler := NewHandler(cdb, ServerOptions{}) handler.ServeHTTP(rec, req) res := rec.Result() @@ -87,7 +87,7 @@ func TestCCIPGet_WithSubscription(t *testing.T) { req := httptest.NewRequest(http.MethodGet, url, nil) rec := httptest.NewRecorder() - handler := NewServer(cdb) + handler := NewHandler(cdb, ServerOptions{}) handler.ServeHTTP(rec, req) res := rec.Result() @@ -104,7 +104,7 @@ func TestCCIPGet_WithInvalidData(t *testing.T) { req := httptest.NewRequest(http.MethodGet, url, nil) rec := httptest.NewRecorder() - handler := NewServer(cdb) + handler := NewHandler(cdb, ServerOptions{}) handler.ServeHTTP(rec, req) res := rec.Result() @@ -132,7 +132,7 @@ func TestCCIPPost_WithValidData(t *testing.T) { req := httptest.NewRequest(http.MethodPost, "http://localhost:9181/api/v0/ccip", bytes.NewBuffer(body)) rec := httptest.NewRecorder() - handler := NewServer(cdb) + handler := NewHandler(cdb, ServerOptions{}) handler.ServeHTTP(rec, req) res := rec.Result() @@ -163,7 +163,7 @@ func TestCCIPPost_WithInvalidGraphQLRequest(t *testing.T) { req := httptest.NewRequest(http.MethodPost, "http://localhost:9181/api/v0/ccip", bytes.NewBuffer(body)) rec := httptest.NewRecorder() - handler := NewServer(cdb) + handler := NewHandler(cdb, ServerOptions{}) handler.ServeHTTP(rec, req) res := rec.Result() @@ -176,7 +176,7 @@ func TestCCIPPost_WithInvalidBody(t *testing.T) { req := httptest.NewRequest(http.MethodPost, "http://localhost:9181/api/v0/ccip", nil) rec := httptest.NewRecorder() - handler := NewServer(cdb) + handler := NewHandler(cdb, ServerOptions{}) handler.ServeHTTP(rec, req) res := rec.Result() diff --git a/http/handler_collection.go b/http/handler_collection.go index 8f8ff8423b..607c1f1b21 100644 --- a/http/handler_collection.go +++ b/http/handler_collection.go @@ -47,9 +47,14 @@ func (s *collectionHandler) Create(rw http.ResponseWriter, req *http.Request) { } switch t := body.(type) { - case []map[string]any: + case []any: var docList []*client.Document - for _, docMap := range t { + for _, v := range t { + docMap, ok := v.(map[string]any) + if !ok { + responseJSON(rw, http.StatusBadRequest, errorResponse{ErrInvalidRequestBody}) + return + } doc, err := client.NewDocFromMap(docMap) if err != nil { responseJSON(rw, http.StatusBadRequest, errorResponse{err}) diff --git a/http/handler_lens.go b/http/handler_lens.go index ccf8dd01a8..d5ddb704c8 100644 --- a/http/handler_lens.go +++ b/http/handler_lens.go @@ -61,7 +61,15 @@ func (s *lensHandler) MigrateUp(rw http.ResponseWriter, req *http.Request) { responseJSON(rw, http.StatusBadRequest, errorResponse{err}) return } - responseJSON(rw, http.StatusOK, result) + var value []map[string]any + err = enumerable.ForEach(result, func(item map[string]any) { + value = append(value, item) + }) + if err != nil { + responseJSON(rw, http.StatusBadRequest, errorResponse{err}) + return + } + responseJSON(rw, http.StatusOK, value) } func (s *lensHandler) MigrateDown(rw http.ResponseWriter, req *http.Request) { @@ -77,7 +85,15 @@ func (s *lensHandler) MigrateDown(rw http.ResponseWriter, req *http.Request) { responseJSON(rw, http.StatusBadRequest, errorResponse{err}) return } - responseJSON(rw, http.StatusOK, result) + var value []map[string]any + err = enumerable.ForEach(result, func(item map[string]any) { + value = append(value, item) + }) + if err != nil { + responseJSON(rw, http.StatusBadRequest, errorResponse{err}) + return + } + responseJSON(rw, http.StatusOK, value) } func (s *lensHandler) Config(rw http.ResponseWriter, req *http.Request) { diff --git a/api/http/playground.go b/http/handler_playground.go similarity index 100% rename from api/http/playground.go rename to http/handler_playground.go diff --git a/http/handler_store.go b/http/handler_store.go index 945f6115f8..120b9f9018 100644 --- a/http/handler_store.go +++ b/http/handler_store.go @@ -242,6 +242,18 @@ func (s *storeHandler) PrintDump(rw http.ResponseWriter, req *http.Request) { rw.WriteHeader(http.StatusOK) } +type PeerInfoResponse struct { + PeerID string `json:"peerID"` +} + +func (s *storeHandler) PeerInfo(rw http.ResponseWriter, req *http.Request) { + var res PeerInfoResponse + if value, ok := req.Context().Value(peerIdContextKey).(string); ok { + res.PeerID = value + } + responseJSON(rw, http.StatusOK, &res) +} + type GraphQLRequest struct { Query string `json:"query"` } diff --git a/http/http_client.go b/http/http_client.go index 48323607ab..13abb3c6d0 100644 --- a/http/http_client.go +++ b/http/http_client.go @@ -16,6 +16,7 @@ import ( "io" "net/http" "net/url" + "strings" ) type httpClient struct { @@ -24,12 +25,19 @@ type httpClient struct { txValue string } -func newHttpClient(baseURL *url.URL) *httpClient { +func newHttpClient(rawURL string) (*httpClient, error) { + if !strings.HasPrefix(rawURL, "http") { + rawURL = "http://" + rawURL + } + baseURL, err := url.Parse(rawURL) + if err != nil { + return nil, err + } client := httpClient{ client: http.DefaultClient, - baseURL: baseURL, + baseURL: baseURL.JoinPath("/api/v0"), } - return &client + return &client, nil } func (c *httpClient) withTxn(value uint64) *httpClient { diff --git a/http/middleware.go b/http/middleware.go index 28f1e0ff1e..932797ff2c 100644 --- a/http/middleware.go +++ b/http/middleware.go @@ -14,9 +14,12 @@ import ( "context" "net/http" "strconv" + "strings" "sync" "github.com/go-chi/chi/v5" + "github.com/go-chi/cors" + "golang.org/x/exp/slices" "github.com/sourcenetwork/defradb/client" "github.com/sourcenetwork/defradb/datastore" @@ -50,15 +53,37 @@ var ( // If a transaction exists, all operations will be executed // in the current transaction context. colContextKey = contextKey("col") + // peerIdContextKey contains the peerId of the DefraDB node. + peerIdContextKey = contextKey("peerId") ) +// CorsMiddleware handles cross origin request +func CorsMiddleware(opts ServerOptions) func(http.Handler) http.Handler { + return cors.Handler(cors.Options{ + AllowOriginFunc: func(r *http.Request, origin string) bool { + if slices.Contains(opts.AllowedOrigins, "*") { + return true + } + return slices.Contains(opts.AllowedOrigins, strings.ToLower(origin)) + }, + AllowedMethods: []string{"GET", "HEAD", "POST", "PATCH", "DELETE"}, + AllowedHeaders: []string{"Content-Type"}, + MaxAge: 300, + }) +} + // ApiMiddleware sets the required context values for all API requests. -func ApiMiddleware(db client.DB, txs *sync.Map) func(http.Handler) http.Handler { +func ApiMiddleware(db client.DB, txs *sync.Map, opts ServerOptions) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + if opts.TLS.HasValue() { + rw.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains") + } + ctx := req.Context() ctx = context.WithValue(ctx, dbContextKey, db) ctx = context.WithValue(ctx, txsContextKey, txs) + ctx = context.WithValue(ctx, peerIdContextKey, opts.PeerID) next.ServeHTTP(rw, req.WithContext(ctx)) }) } diff --git a/http/server.go b/http/server.go index 7ad21e0632..ccfefb08b1 100644 --- a/http/server.go +++ b/http/server.go @@ -11,107 +11,312 @@ package http import ( + "context" + "crypto/tls" + "fmt" + "net" "net/http" - "sync" + "path" + "strings" - "github.com/go-chi/chi/v5" - "github.com/go-chi/chi/v5/middleware" + "github.com/sourcenetwork/immutable" + "golang.org/x/crypto/acme/autocert" "github.com/sourcenetwork/defradb/client" + "github.com/sourcenetwork/defradb/config" + "github.com/sourcenetwork/defradb/errors" + "github.com/sourcenetwork/defradb/logging" ) +const ( + // These constants are best effort durations that fit our current API + // and possibly prevent from running out of file descriptors. + // readTimeout = 5 * time.Second + // writeTimeout = 10 * time.Second + // idleTimeout = 120 * time.Second + + // Temparily disabling timeouts until [this proposal](https://github.com/golang/go/issues/54136) is merged. + // https://github.com/sourcenetwork/defradb/issues/927 + readTimeout = 0 + writeTimeout = 0 + idleTimeout = 0 +) + +const ( + httpPort = ":80" + httpsPort = ":443" +) + +// Server struct holds the Handler for the HTTP API. type Server struct { - db client.DB - router *chi.Mux - txs *sync.Map -} - -func NewServer(db client.DB) *Server { - txs := &sync.Map{} - - tx_handler := &txHandler{} - store_handler := &storeHandler{} - collection_handler := &collectionHandler{} - lens_handler := &lensHandler{} - ccip_handler := &ccipHandler{} - - router := chi.NewRouter() - router.Use(middleware.RequestLogger(&logFormatter{})) - router.Use(middleware.Recoverer) - - router.Route("/api/v0", func(api chi.Router) { - api.Use(ApiMiddleware(db, txs), TransactionMiddleware, StoreMiddleware) - api.Route("/tx", func(tx chi.Router) { - tx.Post("/", tx_handler.NewTxn) - tx.Post("/concurrent", tx_handler.NewConcurrentTxn) - tx.Post("/{id}", tx_handler.Commit) - tx.Delete("/{id}", tx_handler.Discard) - }) - api.Route("/backup", func(backup chi.Router) { - backup.Post("/export", store_handler.BasicExport) - backup.Post("/import", store_handler.BasicImport) - }) - api.Route("/schema", func(schema chi.Router) { - schema.Post("/", store_handler.AddSchema) - schema.Patch("/", store_handler.PatchSchema) - schema.Post("/default", store_handler.SetDefaultSchemaVersion) - }) - api.Route("/collections", func(collections chi.Router) { - collections.Get("/", store_handler.GetCollection) - // with collection middleware - collections_tx := collections.With(CollectionMiddleware) - collections_tx.Get("/{name}", collection_handler.GetAllDocKeys) - collections_tx.Post("/{name}", collection_handler.Create) - collections_tx.Patch("/{name}", collection_handler.UpdateWith) - collections_tx.Delete("/{name}", collection_handler.DeleteWith) - collections_tx.Post("/{name}/indexes", collection_handler.CreateIndex) - collections_tx.Get("/{name}/indexes", collection_handler.GetIndexes) - collections_tx.Delete("/{name}/indexes/{index}", collection_handler.DropIndex) - collections_tx.Get("/{name}/{key}", collection_handler.Get) - collections_tx.Patch("/{name}/{key}", collection_handler.Update) - collections_tx.Delete("/{name}/{key}", collection_handler.Delete) - }) - api.Route("/lens", func(lens chi.Router) { - lens.Use(LensMiddleware) - lens.Get("/", lens_handler.Config) - lens.Post("/", lens_handler.SetMigration) - lens.Post("/reload", lens_handler.ReloadLenses) - lens.Get("/{version}", lens_handler.HasMigration) - lens.Post("/{version}/up", lens_handler.MigrateUp) - lens.Post("/{version}/down", lens_handler.MigrateDown) - }) - api.Route("/graphql", func(graphQL chi.Router) { - graphQL.Get("/", store_handler.ExecRequest) - graphQL.Post("/", store_handler.ExecRequest) - }) - api.Route("/ccip", func(ccip chi.Router) { - ccip.Get("/{sender}/{data}", ccip_handler.ExecCCIP) - ccip.Post("/", ccip_handler.ExecCCIP) - }) - api.Route("/p2p", func(p2p chi.Router) { - p2p.Route("/replicators", func(p2p_replicators chi.Router) { - p2p_replicators.Get("/", store_handler.GetAllReplicators) - p2p_replicators.Post("/", store_handler.SetReplicator) - p2p_replicators.Delete("/", store_handler.DeleteReplicator) - }) - p2p.Route("/collections", func(p2p_collections chi.Router) { - p2p_collections.Get("/", store_handler.GetAllP2PCollections) - p2p_collections.Post("/{id}", store_handler.AddP2PCollection) - p2p_collections.Delete("/{id}", store_handler.RemoveP2PCollection) - }) - }) - api.Route("/debug", func(debug chi.Router) { - debug.Get("/dump", store_handler.PrintDump) - }) - }) - - return &Server{ - db: db, - router: router, - txs: txs, - } -} - -func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) { - s.router.ServeHTTP(w, req) + options ServerOptions + listener net.Listener + certManager *autocert.Manager + // address that is assigned to the server on listen + address string + + http.Server +} + +type ServerOptions struct { + // AllowedOrigins is the list of allowed origins for CORS. + AllowedOrigins []string + // PeerID is the p2p id of the server node. + PeerID string + // TLS enables https when the value is present. + TLS immutable.Option[TLSOptions] + // RootDirectory is the directory for the node config. + RootDir string + // Domain is the domain for the API (optional). + Domain immutable.Option[string] +} + +type TLSOptions struct { + // PublicKey is the public key for TLS. Ignored if domain is set. + PublicKey string + // PrivateKey is the private key for TLS. Ignored if domain is set. + PrivateKey string + // Email is the address for the CA to send problem notifications (optional) + Email string + // Port is the tls port + Port string +} + +// NewServer instantiates a new server with the given http.Handler. +func NewServer(db client.DB, options ...func(*Server)) *Server { + srv := &Server{ + Server: http.Server{ + ReadTimeout: readTimeout, + WriteTimeout: writeTimeout, + IdleTimeout: idleTimeout, + }, + } + + for _, opt := range append(options, DefaultOpts()) { + opt(srv) + } + + srv.Handler = NewHandler(db, srv.options) + + return srv +} + +func newHTTPRedirServer(m *autocert.Manager) *Server { + srv := &Server{ + Server: http.Server{ + ReadTimeout: readTimeout, + WriteTimeout: writeTimeout, + IdleTimeout: idleTimeout, + }, + } + + srv.Addr = httpPort + srv.Handler = m.HTTPHandler(nil) + + return srv +} + +// DefaultOpts returns the default options for the server. +func DefaultOpts() func(*Server) { + return func(s *Server) { + if s.Addr == "" { + s.Addr = "localhost:9181" + } + } +} + +// WithAllowedOrigins returns an option to set the allowed origins for CORS. +func WithAllowedOrigins(origins ...string) func(*Server) { + return func(s *Server) { + s.options.AllowedOrigins = append(s.options.AllowedOrigins, origins...) + } +} + +// WithAddress returns an option to set the address for the server. +func WithAddress(addr string) func(*Server) { + return func(s *Server) { + s.Addr = addr + + // If the address is not localhost, we check to see if it's a valid IP address. + // If it's not a valid IP, we assume that it's a domain name to be used with TLS. + if !strings.HasPrefix(addr, "localhost:") && !strings.HasPrefix(addr, ":") { + host, _, err := net.SplitHostPort(addr) + if err != nil { + host = addr + } + ip := net.ParseIP(host) + if ip == nil { + s.Addr = httpPort + s.options.Domain = immutable.Some(host) + } + } + } +} + +// WithCAEmail returns an option to set the email address for the CA to send problem notifications. +func WithCAEmail(email string) func(*Server) { + return func(s *Server) { + tlsOpt := s.options.TLS.Value() + tlsOpt.Email = email + s.options.TLS = immutable.Some(tlsOpt) + } +} + +// WithPeerID returns an option to set the identifier of the server node. +func WithPeerID(id string) func(*Server) { + return func(s *Server) { + s.options.PeerID = id + } +} + +// WithRootDir returns an option to set the root directory for the node config. +func WithRootDir(rootDir string) func(*Server) { + return func(s *Server) { + s.options.RootDir = rootDir + } +} + +// WithSelfSignedCert returns an option to set the public and private keys for TLS. +func WithSelfSignedCert(pubKey, privKey string) func(*Server) { + return func(s *Server) { + tlsOpt := s.options.TLS.Value() + tlsOpt.PublicKey = pubKey + tlsOpt.PrivateKey = privKey + s.options.TLS = immutable.Some(tlsOpt) + } +} + +// WithTLS returns an option to enable TLS. +func WithTLS() func(*Server) { + return func(s *Server) { + tlsOpt := s.options.TLS.Value() + tlsOpt.Port = httpsPort + s.options.TLS = immutable.Some(tlsOpt) + } +} + +// WithTLSPort returns an option to set the port for TLS. +func WithTLSPort(port int) func(*Server) { + return func(s *Server) { + tlsOpt := s.options.TLS.Value() + tlsOpt.Port = fmt.Sprintf(":%d", port) + s.options.TLS = immutable.Some(tlsOpt) + } +} + +// Listen creates a new net.Listener and saves it on the receiver. +func (s *Server) Listen(ctx context.Context) error { + var err error + if s.options.TLS.HasValue() { + return s.listenWithTLS(ctx) + } + + lc := net.ListenConfig{} + s.listener, err = lc.Listen(ctx, "tcp", s.Addr) + if err != nil { + return errors.WithStack(err) + } + + // Save the address on the server in case the port was set to random + // and that we want to see what was assigned. + s.address = s.listener.Addr().String() + + return nil +} + +func (s *Server) listenWithTLS(ctx context.Context) error { + cfg := &tls.Config{ + MinVersion: tls.VersionTLS12, + // We only allow cipher suites that are marked secure + // by ssllabs + CipherSuites: []uint16{ + tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, + tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + }, + ServerName: "DefraDB", + } + + if s.options.Domain.HasValue() && s.options.Domain.Value() != "" { + s.Addr = s.options.TLS.Value().Port + + if s.options.TLS.Value().Email == "" || s.options.TLS.Value().Email == config.DefaultAPIEmail { + return ErrNoEmail + } + + certCache := path.Join(s.options.RootDir, "autocerts") + + log.FeedbackInfo( + ctx, + "Generating auto certificate", + logging.NewKV("Domain", s.options.Domain.Value()), + logging.NewKV("Certificate cache", certCache), + ) + + m := &autocert.Manager{ + Cache: autocert.DirCache(certCache), + Prompt: autocert.AcceptTOS, + Email: s.options.TLS.Value().Email, + HostPolicy: autocert.HostWhitelist(s.options.Domain.Value()), + } + + cfg.GetCertificate = m.GetCertificate + + // We set manager on the server instance to later start + // a redirection server. + s.certManager = m + } else { + // When not using auto cert, we create a self signed certificate + // with the provided public and prive keys. + log.FeedbackInfo(ctx, "Generating self signed certificate") + + cert, err := tls.LoadX509KeyPair( + s.options.TLS.Value().PrivateKey, + s.options.TLS.Value().PublicKey, + ) + if err != nil { + return errors.WithStack(err) + } + + cfg.Certificates = []tls.Certificate{cert} + } + + var err error + s.listener, err = tls.Listen("tcp", s.Addr, cfg) + if err != nil { + return errors.WithStack(err) + } + + // Save the address on the server in case the port was set to random + // and that we want to see what was assigned. + s.address = s.listener.Addr().String() + + return nil +} + +// Run calls Serve with the receiver's listener. +func (s *Server) Run(ctx context.Context) error { + if s.listener == nil { + return ErrNoListener + } + + if s.certManager != nil { + // When using TLS it's important to redirect http requests to https + go func() { + srv := newHTTPRedirServer(s.certManager) + err := srv.ListenAndServe() + if err != nil && !errors.Is(err, http.ErrServerClosed) { + log.Info(ctx, "Something went wrong with the redirection server", logging.NewKV("Error", err)) + } + }() + } + return s.Serve(s.listener) +} + +// AssignedAddr returns the address that was assigned to the server on calls to listen. +func (s *Server) AssignedAddr() string { + return s.address } diff --git a/api/http/server_test.go b/http/server_test.go similarity index 92% rename from api/http/server_test.go rename to http/server_test.go index c19e60a2ac..790f710249 100644 --- a/api/http/server_test.go +++ b/http/server_test.go @@ -1,4 +1,4 @@ -// Copyright 2022 Democratized Data Foundation +// Copyright 2023 Democratized Data Foundation // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt. @@ -197,7 +197,7 @@ func TestNewServerAndRunWithSelfSignedCert(t *testing.T) { func TestNewServerWithoutOptions(t *testing.T) { s := NewServer(nil) assert.Equal(t, "localhost:9181", s.Addr) - assert.Equal(t, []string(nil), s.options.allowedOrigins) + assert.Equal(t, []string(nil), s.options.AllowedOrigins) } func TestNewServerWithAddress(t *testing.T) { @@ -207,41 +207,41 @@ func TestNewServerWithAddress(t *testing.T) { func TestNewServerWithDomainAddress(t *testing.T) { s := NewServer(nil, WithAddress("example.com")) - assert.Equal(t, "example.com", s.options.domain.Value()) - assert.NotNil(t, s.options.tls) + assert.Equal(t, "example.com", s.options.Domain.Value()) + assert.NotNil(t, s.options.TLS) } func TestNewServerWithAllowedOrigins(t *testing.T) { s := NewServer(nil, WithAllowedOrigins("https://source.network", "https://app.source.network")) - assert.Equal(t, []string{"https://source.network", "https://app.source.network"}, s.options.allowedOrigins) + assert.Equal(t, []string{"https://source.network", "https://app.source.network"}, s.options.AllowedOrigins) } func TestNewServerWithCAEmail(t *testing.T) { s := NewServer(nil, WithCAEmail("me@example.com")) - assert.Equal(t, "me@example.com", s.options.tls.Value().email) + assert.Equal(t, "me@example.com", s.options.TLS.Value().Email) } func TestNewServerWithPeerID(t *testing.T) { s := NewServer(nil, WithPeerID("12D3KooWFpi6VTYKLtxUftJKEyfX8jDfKi8n15eaygH8ggfYFZbR")) - assert.Equal(t, "12D3KooWFpi6VTYKLtxUftJKEyfX8jDfKi8n15eaygH8ggfYFZbR", s.options.peerID) + assert.Equal(t, "12D3KooWFpi6VTYKLtxUftJKEyfX8jDfKi8n15eaygH8ggfYFZbR", s.options.PeerID) } func TestNewServerWithRootDir(t *testing.T) { dir := t.TempDir() s := NewServer(nil, WithRootDir(dir)) - assert.Equal(t, dir, s.options.rootDir) + assert.Equal(t, dir, s.options.RootDir) } func TestNewServerWithTLSPort(t *testing.T) { s := NewServer(nil, WithTLSPort(44343)) - assert.Equal(t, ":44343", s.options.tls.Value().port) + assert.Equal(t, ":44343", s.options.TLS.Value().Port) } func TestNewServerWithSelfSignedCert(t *testing.T) { s := NewServer(nil, WithSelfSignedCert("pub.key", "priv.key")) - assert.Equal(t, "pub.key", s.options.tls.Value().pubKey) - assert.Equal(t, "priv.key", s.options.tls.Value().privKey) - assert.NotNil(t, s.options.tls) + assert.Equal(t, "pub.key", s.options.TLS.Value().PublicKey) + assert.Equal(t, "priv.key", s.options.TLS.Value().PrivateKey) + assert.NotNil(t, s.options.TLS) } func TestNewHTTPRedirServer(t *testing.T) { diff --git a/http/utils.go b/http/utils.go index a171e0ed38..c7b1507c4e 100644 --- a/http/utils.go +++ b/http/utils.go @@ -34,25 +34,6 @@ func responseJSON(rw http.ResponseWriter, status int, out any) { json.NewEncoder(rw).Encode(out) //nolint:errcheck } -func documentJSON(doc *client.Document) ([]byte, error) { - docMap, err := doc.ToMap() - if err != nil { - return nil, err - } - delete(docMap, "_key") - - for field, value := range doc.Values() { - if !value.IsDirty() { - delete(docMap, field.Name()) - } - if value.IsDelete() { - docMap[field.Name()] = nil - } - } - - return json.Marshal(docMap) -} - func parseError(msg any) error { switch msg { case client.ErrDocumentNotFound.Error(): diff --git a/logging/registry.go b/logging/registry.go index 7cd7b808a2..9410498a72 100644 --- a/logging/registry.go +++ b/logging/registry.go @@ -44,6 +44,9 @@ func setConfig(newConfig Config) Config { } func updateLoggers(config Config) { + registryMutex.Lock() + defer registryMutex.Unlock() + for loggerName, loggers := range registry { newLoggerConfig := config.forLogger(loggerName) diff --git a/net/api/client/client.go b/net/api/client/client.go deleted file mode 100644 index 2ea92bd14c..0000000000 --- a/net/api/client/client.go +++ /dev/null @@ -1,169 +0,0 @@ -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package client - -import ( - "context" - "fmt" - - "github.com/libp2p/go-libp2p/core/peer" - ma "github.com/multiformats/go-multiaddr" - codec "github.com/planetscale/vtprotobuf/codec/grpc" - "google.golang.org/grpc" - "google.golang.org/grpc/encoding" - _ "google.golang.org/grpc/encoding/proto" - - "github.com/sourcenetwork/defradb/client" - "github.com/sourcenetwork/defradb/errors" - pb "github.com/sourcenetwork/defradb/net/pb" -) - -func init() { - encoding.RegisterCodec(codec.Codec{}) -} - -type Client struct { - c pb.CollectionClient - conn *grpc.ClientConn -} - -// NewClient returns a new defra gRPC client connected to the target address. -func NewClient(target string, opts ...grpc.DialOption) (*Client, error) { - conn, err := grpc.Dial(target, opts...) - if err != nil { - return nil, err - } - - return &Client{ - c: pb.NewCollectionClient(conn), - conn: conn, - }, nil -} - -func (c *Client) Close() error { - return c.conn.Close() -} - -// SetReplicator sends a request to add a target replicator to the DB peer. -func (c *Client) SetReplicator( - ctx context.Context, - paddr ma.Multiaddr, - collections ...string, -) (peer.ID, error) { - if paddr == nil { - return "", errors.New("target address can't be empty") - } - resp, err := c.c.SetReplicator(ctx, &pb.SetReplicatorRequest{ - Collections: collections, - Addr: paddr.Bytes(), - }) - if err != nil { - return "", errors.Wrap("could not add replicator", err) - } - return peer.IDFromBytes(resp.PeerID) -} - -// DeleteReplicator sends a request to add a target replicator to the DB peer. -func (c *Client) DeleteReplicator( - ctx context.Context, - pid peer.ID, - collections ...string, -) error { - _, err := c.c.DeleteReplicator(ctx, &pb.DeleteReplicatorRequest{ - PeerID: []byte(pid), - }) - return err -} - -// GetAllReplicators sends a request to add a target replicator to the DB peer. -func (c *Client) GetAllReplicators( - ctx context.Context, -) ([]client.Replicator, error) { - resp, err := c.c.GetAllReplicators(ctx, &pb.GetAllReplicatorRequest{}) - if err != nil { - return nil, errors.Wrap("could not get replicators", err) - } - reps := []client.Replicator{} - for _, rep := range resp.Replicators { - addr, err := ma.NewMultiaddrBytes(rep.Info.Addrs) - if err != nil { - return nil, errors.WithStack(err) - } - - pid, err := peer.IDFromBytes(rep.Info.Id) - if err != nil { - return nil, errors.WithStack(err) - } - - reps = append(reps, client.Replicator{ - Info: peer.AddrInfo{ - ID: pid, - Addrs: []ma.Multiaddr{addr}, - }, - Schemas: rep.Schemas, - }) - } - return reps, nil -} - -// AddP2PCollections sends a request to add P2P collecctions to the stored list. -func (c *Client) AddP2PCollections( - ctx context.Context, - collections ...string, -) error { - resp, err := c.c.AddP2PCollections(ctx, &pb.AddP2PCollectionsRequest{ - Collections: collections, - }) - if err != nil { - return errors.Wrap("could not add P2P collection topics", err) - } - if resp.Err != "" { - return errors.New(fmt.Sprintf("could not add P2P collection topics: %s", resp)) - } - return nil -} - -// RemoveP2PCollections sends a request to remove P2P collecctions from the stored list. -func (c *Client) RemoveP2PCollections( - ctx context.Context, - collections ...string, -) error { - resp, err := c.c.RemoveP2PCollections(ctx, &pb.RemoveP2PCollectionsRequest{ - Collections: collections, - }) - if err != nil { - return errors.Wrap("could not remove P2P collection topics", err) - } - if resp.Err != "" { - return errors.New(fmt.Sprintf("could not remove P2P collection topics: %s", resp)) - } - return nil -} - -// RemoveP2PCollections sends a request to get all P2P collecctions from the stored list. -func (c *Client) GetAllP2PCollections( - ctx context.Context, -) ([]client.P2PCollection, error) { - resp, err := c.c.GetAllP2PCollections(ctx, &pb.GetAllP2PCollectionsRequest{}) - if err != nil { - return nil, errors.Wrap("could not get all P2P collection topics", err) - } - var collections []client.P2PCollection - for _, col := range resp.Collections { - collections = append(collections, client.P2PCollection{ - ID: col.Id, - Name: col.Name, - }) - } - return collections, nil -} diff --git a/net/api/pb/Makefile b/net/api/pb/Makefile deleted file mode 100644 index 62eef77354..0000000000 --- a/net/api/pb/Makefile +++ /dev/null @@ -1,18 +0,0 @@ -PB = $(wildcard *.proto) -GO = $(PB:.proto=.pb.go) - -all: $(GO) - -%.pb.go: %.proto - protoc \ - --go_out=. --plugin protoc-gen-go="${GOBIN}/protoc-gen-go" \ - --go-grpc_out=. --plugin protoc-gen-go-grpc="${GOBIN}/protoc-gen-go-grpc" \ - --go-vtproto_out=. --plugin protoc-gen-go-vtproto="${GOBIN}/protoc-gen-go-vtproto" \ - --go-vtproto_opt=features=marshal+unmarshal+size \ - $< - -clean: - rm -f *.pb.go - rm -f *pb_test.go - -.PHONY: clean \ No newline at end of file diff --git a/net/api/pb/api.pb.go b/net/api/pb/api.pb.go deleted file mode 100644 index ad48069b8f..0000000000 --- a/net/api/pb/api.pb.go +++ /dev/null @@ -1,1100 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.30.0 -// protoc v3.21.9 -// source: api.proto - -package api_pb - -import ( - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - reflect "reflect" - sync "sync" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -type SetReplicatorRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Collections []string `protobuf:"bytes,1,rep,name=collections,proto3" json:"collections,omitempty"` - Addr []byte `protobuf:"bytes,2,opt,name=addr,proto3" json:"addr,omitempty"` -} - -func (x *SetReplicatorRequest) Reset() { - *x = SetReplicatorRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *SetReplicatorRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*SetReplicatorRequest) ProtoMessage() {} - -func (x *SetReplicatorRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use SetReplicatorRequest.ProtoReflect.Descriptor instead. -func (*SetReplicatorRequest) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{0} -} - -func (x *SetReplicatorRequest) GetCollections() []string { - if x != nil { - return x.Collections - } - return nil -} - -func (x *SetReplicatorRequest) GetAddr() []byte { - if x != nil { - return x.Addr - } - return nil -} - -type SetReplicatorReply struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - PeerID []byte `protobuf:"bytes,1,opt,name=peerID,proto3" json:"peerID,omitempty"` -} - -func (x *SetReplicatorReply) Reset() { - *x = SetReplicatorReply{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *SetReplicatorReply) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*SetReplicatorReply) ProtoMessage() {} - -func (x *SetReplicatorReply) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use SetReplicatorReply.ProtoReflect.Descriptor instead. -func (*SetReplicatorReply) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{1} -} - -func (x *SetReplicatorReply) GetPeerID() []byte { - if x != nil { - return x.PeerID - } - return nil -} - -type DeleteReplicatorRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - PeerID []byte `protobuf:"bytes,1,opt,name=peerID,proto3" json:"peerID,omitempty"` -} - -func (x *DeleteReplicatorRequest) Reset() { - *x = DeleteReplicatorRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *DeleteReplicatorRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*DeleteReplicatorRequest) ProtoMessage() {} - -func (x *DeleteReplicatorRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use DeleteReplicatorRequest.ProtoReflect.Descriptor instead. -func (*DeleteReplicatorRequest) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{2} -} - -func (x *DeleteReplicatorRequest) GetPeerID() []byte { - if x != nil { - return x.PeerID - } - return nil -} - -type DeleteReplicatorReply struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - PeerID []byte `protobuf:"bytes,1,opt,name=peerID,proto3" json:"peerID,omitempty"` -} - -func (x *DeleteReplicatorReply) Reset() { - *x = DeleteReplicatorReply{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *DeleteReplicatorReply) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*DeleteReplicatorReply) ProtoMessage() {} - -func (x *DeleteReplicatorReply) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[3] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use DeleteReplicatorReply.ProtoReflect.Descriptor instead. -func (*DeleteReplicatorReply) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{3} -} - -func (x *DeleteReplicatorReply) GetPeerID() []byte { - if x != nil { - return x.PeerID - } - return nil -} - -type GetAllReplicatorRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields -} - -func (x *GetAllReplicatorRequest) Reset() { - *x = GetAllReplicatorRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetAllReplicatorRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetAllReplicatorRequest) ProtoMessage() {} - -func (x *GetAllReplicatorRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[4] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetAllReplicatorRequest.ProtoReflect.Descriptor instead. -func (*GetAllReplicatorRequest) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{4} -} - -type GetAllReplicatorReply struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Replicators []*GetAllReplicatorReply_Replicators `protobuf:"bytes,1,rep,name=replicators,proto3" json:"replicators,omitempty"` -} - -func (x *GetAllReplicatorReply) Reset() { - *x = GetAllReplicatorReply{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetAllReplicatorReply) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetAllReplicatorReply) ProtoMessage() {} - -func (x *GetAllReplicatorReply) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[5] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetAllReplicatorReply.ProtoReflect.Descriptor instead. -func (*GetAllReplicatorReply) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{5} -} - -func (x *GetAllReplicatorReply) GetReplicators() []*GetAllReplicatorReply_Replicators { - if x != nil { - return x.Replicators - } - return nil -} - -type AddP2PCollectionsRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Collections []string `protobuf:"bytes,1,rep,name=collections,proto3" json:"collections,omitempty"` -} - -func (x *AddP2PCollectionsRequest) Reset() { - *x = AddP2PCollectionsRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *AddP2PCollectionsRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*AddP2PCollectionsRequest) ProtoMessage() {} - -func (x *AddP2PCollectionsRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[6] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use AddP2PCollectionsRequest.ProtoReflect.Descriptor instead. -func (*AddP2PCollectionsRequest) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{6} -} - -func (x *AddP2PCollectionsRequest) GetCollections() []string { - if x != nil { - return x.Collections - } - return nil -} - -type AddP2PCollectionsReply struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Err string `protobuf:"bytes,1,opt,name=err,proto3" json:"err,omitempty"` -} - -func (x *AddP2PCollectionsReply) Reset() { - *x = AddP2PCollectionsReply{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *AddP2PCollectionsReply) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*AddP2PCollectionsReply) ProtoMessage() {} - -func (x *AddP2PCollectionsReply) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[7] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use AddP2PCollectionsReply.ProtoReflect.Descriptor instead. -func (*AddP2PCollectionsReply) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{7} -} - -func (x *AddP2PCollectionsReply) GetErr() string { - if x != nil { - return x.Err - } - return "" -} - -type RemoveP2PCollectionsRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Collections []string `protobuf:"bytes,1,rep,name=collections,proto3" json:"collections,omitempty"` -} - -func (x *RemoveP2PCollectionsRequest) Reset() { - *x = RemoveP2PCollectionsRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_msgTypes[8] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *RemoveP2PCollectionsRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*RemoveP2PCollectionsRequest) ProtoMessage() {} - -func (x *RemoveP2PCollectionsRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[8] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use RemoveP2PCollectionsRequest.ProtoReflect.Descriptor instead. -func (*RemoveP2PCollectionsRequest) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{8} -} - -func (x *RemoveP2PCollectionsRequest) GetCollections() []string { - if x != nil { - return x.Collections - } - return nil -} - -type RemoveP2PCollectionsReply struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Err string `protobuf:"bytes,1,opt,name=err,proto3" json:"err,omitempty"` -} - -func (x *RemoveP2PCollectionsReply) Reset() { - *x = RemoveP2PCollectionsReply{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_msgTypes[9] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *RemoveP2PCollectionsReply) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*RemoveP2PCollectionsReply) ProtoMessage() {} - -func (x *RemoveP2PCollectionsReply) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[9] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use RemoveP2PCollectionsReply.ProtoReflect.Descriptor instead. -func (*RemoveP2PCollectionsReply) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{9} -} - -func (x *RemoveP2PCollectionsReply) GetErr() string { - if x != nil { - return x.Err - } - return "" -} - -type GetAllP2PCollectionsRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields -} - -func (x *GetAllP2PCollectionsRequest) Reset() { - *x = GetAllP2PCollectionsRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_msgTypes[10] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetAllP2PCollectionsRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetAllP2PCollectionsRequest) ProtoMessage() {} - -func (x *GetAllP2PCollectionsRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[10] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetAllP2PCollectionsRequest.ProtoReflect.Descriptor instead. -func (*GetAllP2PCollectionsRequest) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{10} -} - -type GetAllP2PCollectionsReply struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Collections []*GetAllP2PCollectionsReply_Collection `protobuf:"bytes,1,rep,name=collections,proto3" json:"collections,omitempty"` -} - -func (x *GetAllP2PCollectionsReply) Reset() { - *x = GetAllP2PCollectionsReply{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_msgTypes[11] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetAllP2PCollectionsReply) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetAllP2PCollectionsReply) ProtoMessage() {} - -func (x *GetAllP2PCollectionsReply) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[11] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetAllP2PCollectionsReply.ProtoReflect.Descriptor instead. -func (*GetAllP2PCollectionsReply) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{11} -} - -func (x *GetAllP2PCollectionsReply) GetCollections() []*GetAllP2PCollectionsReply_Collection { - if x != nil { - return x.Collections - } - return nil -} - -type GetAllReplicatorReply_Replicators struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Info *GetAllReplicatorReply_Replicators_Info `protobuf:"bytes,1,opt,name=info,proto3" json:"info,omitempty"` - Schemas []string `protobuf:"bytes,2,rep,name=schemas,proto3" json:"schemas,omitempty"` -} - -func (x *GetAllReplicatorReply_Replicators) Reset() { - *x = GetAllReplicatorReply_Replicators{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_msgTypes[12] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetAllReplicatorReply_Replicators) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetAllReplicatorReply_Replicators) ProtoMessage() {} - -func (x *GetAllReplicatorReply_Replicators) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[12] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetAllReplicatorReply_Replicators.ProtoReflect.Descriptor instead. -func (*GetAllReplicatorReply_Replicators) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{5, 0} -} - -func (x *GetAllReplicatorReply_Replicators) GetInfo() *GetAllReplicatorReply_Replicators_Info { - if x != nil { - return x.Info - } - return nil -} - -func (x *GetAllReplicatorReply_Replicators) GetSchemas() []string { - if x != nil { - return x.Schemas - } - return nil -} - -type GetAllReplicatorReply_Replicators_Info struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Id []byte `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - Addrs []byte `protobuf:"bytes,2,opt,name=addrs,proto3" json:"addrs,omitempty"` -} - -func (x *GetAllReplicatorReply_Replicators_Info) Reset() { - *x = GetAllReplicatorReply_Replicators_Info{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_msgTypes[13] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetAllReplicatorReply_Replicators_Info) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetAllReplicatorReply_Replicators_Info) ProtoMessage() {} - -func (x *GetAllReplicatorReply_Replicators_Info) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[13] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetAllReplicatorReply_Replicators_Info.ProtoReflect.Descriptor instead. -func (*GetAllReplicatorReply_Replicators_Info) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{5, 0, 0} -} - -func (x *GetAllReplicatorReply_Replicators_Info) GetId() []byte { - if x != nil { - return x.Id - } - return nil -} - -func (x *GetAllReplicatorReply_Replicators_Info) GetAddrs() []byte { - if x != nil { - return x.Addrs - } - return nil -} - -type GetAllP2PCollectionsReply_Collection struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` -} - -func (x *GetAllP2PCollectionsReply_Collection) Reset() { - *x = GetAllP2PCollectionsReply_Collection{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_msgTypes[14] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetAllP2PCollectionsReply_Collection) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetAllP2PCollectionsReply_Collection) ProtoMessage() {} - -func (x *GetAllP2PCollectionsReply_Collection) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[14] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetAllP2PCollectionsReply_Collection.ProtoReflect.Descriptor instead. -func (*GetAllP2PCollectionsReply_Collection) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{11, 0} -} - -func (x *GetAllP2PCollectionsReply_Collection) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -func (x *GetAllP2PCollectionsReply_Collection) GetName() string { - if x != nil { - return x.Name - } - return "" -} - -var File_api_proto protoreflect.FileDescriptor - -var file_api_proto_rawDesc = []byte{ - 0x0a, 0x09, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x61, 0x70, 0x69, - 0x2e, 0x70, 0x62, 0x22, 0x4c, 0x0a, 0x14, 0x53, 0x65, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, - 0x61, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x0b, 0x63, - 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, - 0x52, 0x0b, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x12, 0x0a, - 0x04, 0x61, 0x64, 0x64, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x61, 0x64, 0x64, - 0x72, 0x22, 0x2c, 0x0a, 0x12, 0x53, 0x65, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, - 0x6f, 0x72, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x65, 0x65, 0x72, 0x49, - 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x65, 0x65, 0x72, 0x49, 0x44, 0x22, - 0x31, 0x0a, 0x17, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, - 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x65, - 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x65, 0x65, 0x72, - 0x49, 0x44, 0x22, 0x2f, 0x0a, 0x15, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x70, 0x6c, - 0x69, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x70, - 0x65, 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x65, 0x65, - 0x72, 0x49, 0x44, 0x22, 0x19, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x52, 0x65, 0x70, - 0x6c, 0x69, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x80, - 0x02, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, - 0x74, 0x6f, 0x72, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x4b, 0x0a, 0x0b, 0x72, 0x65, 0x70, 0x6c, - 0x69, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, - 0x61, 0x70, 0x69, 0x2e, 0x70, 0x62, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x52, 0x65, 0x70, - 0x6c, 0x69, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x2e, 0x52, 0x65, 0x70, - 0x6c, 0x69, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x73, 0x52, 0x0b, 0x72, 0x65, 0x70, 0x6c, 0x69, 0x63, - 0x61, 0x74, 0x6f, 0x72, 0x73, 0x1a, 0x99, 0x01, 0x0a, 0x0b, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, - 0x61, 0x74, 0x6f, 0x72, 0x73, 0x12, 0x42, 0x0a, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x62, 0x2e, 0x47, 0x65, 0x74, - 0x41, 0x6c, 0x6c, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x70, - 0x6c, 0x79, 0x2e, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x73, 0x2e, 0x49, - 0x6e, 0x66, 0x6f, 0x52, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x63, 0x68, - 0x65, 0x6d, 0x61, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x73, 0x63, 0x68, 0x65, - 0x6d, 0x61, 0x73, 0x1a, 0x2c, 0x0a, 0x04, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x0e, 0x0a, 0x02, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x61, - 0x64, 0x64, 0x72, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x61, 0x64, 0x64, 0x72, - 0x73, 0x22, 0x3c, 0x0a, 0x18, 0x41, 0x64, 0x64, 0x50, 0x32, 0x50, 0x43, 0x6f, 0x6c, 0x6c, 0x65, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, - 0x0b, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, - 0x2a, 0x0a, 0x16, 0x41, 0x64, 0x64, 0x50, 0x32, 0x50, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x72, 0x72, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x65, 0x72, 0x72, 0x22, 0x3f, 0x0a, 0x1b, 0x52, - 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x50, 0x32, 0x50, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x0b, 0x63, 0x6f, - 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, - 0x0b, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x2d, 0x0a, 0x19, - 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x50, 0x32, 0x50, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x72, 0x72, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x65, 0x72, 0x72, 0x22, 0x1d, 0x0a, 0x1b, 0x47, - 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x50, 0x32, 0x50, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x9d, 0x01, 0x0a, 0x19, 0x47, - 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x50, 0x32, 0x50, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x4e, 0x0a, 0x0b, 0x63, 0x6f, 0x6c, 0x6c, - 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 0x2e, - 0x61, 0x70, 0x69, 0x2e, 0x70, 0x62, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x50, 0x32, 0x50, - 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, - 0x2e, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x63, 0x6f, 0x6c, - 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x30, 0x0a, 0x0a, 0x43, 0x6f, 0x6c, 0x6c, - 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x32, 0xa0, 0x04, 0x0a, 0x07, 0x53, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x4b, 0x0a, 0x0d, 0x53, 0x65, 0x74, 0x52, 0x65, 0x70, - 0x6c, 0x69, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x1c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x62, - 0x2e, 0x53, 0x65, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x62, 0x2e, 0x53, - 0x65, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x70, 0x6c, - 0x79, 0x22, 0x00, 0x12, 0x54, 0x0a, 0x10, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x70, - 0x6c, 0x69, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x1f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x62, - 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x6f, - 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x70, - 0x62, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, - 0x6f, 0x72, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x55, 0x0a, 0x11, 0x47, 0x65, 0x74, - 0x41, 0x6c, 0x6c, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x73, 0x12, 0x1f, - 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x62, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x52, 0x65, - 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x62, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x52, - 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, - 0x12, 0x57, 0x0a, 0x11, 0x41, 0x64, 0x64, 0x50, 0x32, 0x50, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x20, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x62, 0x2e, 0x41, - 0x64, 0x64, 0x50, 0x32, 0x50, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x62, - 0x2e, 0x41, 0x64, 0x64, 0x50, 0x32, 0x50, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x60, 0x0a, 0x14, 0x52, 0x65, 0x6d, - 0x6f, 0x76, 0x65, 0x50, 0x32, 0x50, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x12, 0x23, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, - 0x65, 0x50, 0x32, 0x50, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x62, 0x2e, - 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x50, 0x32, 0x50, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x60, 0x0a, 0x14, 0x47, - 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x50, 0x32, 0x50, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x12, 0x23, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x62, 0x2e, 0x47, 0x65, 0x74, - 0x41, 0x6c, 0x6c, 0x50, 0x32, 0x50, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x70, - 0x62, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x50, 0x32, 0x50, 0x43, 0x6f, 0x6c, 0x6c, 0x65, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x42, 0x0a, 0x5a, - 0x08, 0x2f, 0x3b, 0x61, 0x70, 0x69, 0x5f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x33, -} - -var ( - file_api_proto_rawDescOnce sync.Once - file_api_proto_rawDescData = file_api_proto_rawDesc -) - -func file_api_proto_rawDescGZIP() []byte { - file_api_proto_rawDescOnce.Do(func() { - file_api_proto_rawDescData = protoimpl.X.CompressGZIP(file_api_proto_rawDescData) - }) - return file_api_proto_rawDescData -} - -var file_api_proto_msgTypes = make([]protoimpl.MessageInfo, 15) -var file_api_proto_goTypes = []interface{}{ - (*SetReplicatorRequest)(nil), // 0: api.pb.SetReplicatorRequest - (*SetReplicatorReply)(nil), // 1: api.pb.SetReplicatorReply - (*DeleteReplicatorRequest)(nil), // 2: api.pb.DeleteReplicatorRequest - (*DeleteReplicatorReply)(nil), // 3: api.pb.DeleteReplicatorReply - (*GetAllReplicatorRequest)(nil), // 4: api.pb.GetAllReplicatorRequest - (*GetAllReplicatorReply)(nil), // 5: api.pb.GetAllReplicatorReply - (*AddP2PCollectionsRequest)(nil), // 6: api.pb.AddP2PCollectionsRequest - (*AddP2PCollectionsReply)(nil), // 7: api.pb.AddP2PCollectionsReply - (*RemoveP2PCollectionsRequest)(nil), // 8: api.pb.RemoveP2PCollectionsRequest - (*RemoveP2PCollectionsReply)(nil), // 9: api.pb.RemoveP2PCollectionsReply - (*GetAllP2PCollectionsRequest)(nil), // 10: api.pb.GetAllP2PCollectionsRequest - (*GetAllP2PCollectionsReply)(nil), // 11: api.pb.GetAllP2PCollectionsReply - (*GetAllReplicatorReply_Replicators)(nil), // 12: api.pb.GetAllReplicatorReply.Replicators - (*GetAllReplicatorReply_Replicators_Info)(nil), // 13: api.pb.GetAllReplicatorReply.Replicators.Info - (*GetAllP2PCollectionsReply_Collection)(nil), // 14: api.pb.GetAllP2PCollectionsReply.Collection -} -var file_api_proto_depIdxs = []int32{ - 12, // 0: api.pb.GetAllReplicatorReply.replicators:type_name -> api.pb.GetAllReplicatorReply.Replicators - 14, // 1: api.pb.GetAllP2PCollectionsReply.collections:type_name -> api.pb.GetAllP2PCollectionsReply.Collection - 13, // 2: api.pb.GetAllReplicatorReply.Replicators.info:type_name -> api.pb.GetAllReplicatorReply.Replicators.Info - 0, // 3: api.pb.Service.SetReplicator:input_type -> api.pb.SetReplicatorRequest - 2, // 4: api.pb.Service.DeleteReplicator:input_type -> api.pb.DeleteReplicatorRequest - 4, // 5: api.pb.Service.GetAllReplicators:input_type -> api.pb.GetAllReplicatorRequest - 6, // 6: api.pb.Service.AddP2PCollections:input_type -> api.pb.AddP2PCollectionsRequest - 8, // 7: api.pb.Service.RemoveP2PCollections:input_type -> api.pb.RemoveP2PCollectionsRequest - 10, // 8: api.pb.Service.GetAllP2PCollections:input_type -> api.pb.GetAllP2PCollectionsRequest - 1, // 9: api.pb.Service.SetReplicator:output_type -> api.pb.SetReplicatorReply - 3, // 10: api.pb.Service.DeleteReplicator:output_type -> api.pb.DeleteReplicatorReply - 5, // 11: api.pb.Service.GetAllReplicators:output_type -> api.pb.GetAllReplicatorReply - 7, // 12: api.pb.Service.AddP2PCollections:output_type -> api.pb.AddP2PCollectionsReply - 9, // 13: api.pb.Service.RemoveP2PCollections:output_type -> api.pb.RemoveP2PCollectionsReply - 11, // 14: api.pb.Service.GetAllP2PCollections:output_type -> api.pb.GetAllP2PCollectionsReply - 9, // [9:15] is the sub-list for method output_type - 3, // [3:9] is the sub-list for method input_type - 3, // [3:3] is the sub-list for extension type_name - 3, // [3:3] is the sub-list for extension extendee - 0, // [0:3] is the sub-list for field type_name -} - -func init() { file_api_proto_init() } -func file_api_proto_init() { - if File_api_proto != nil { - return - } - if !protoimpl.UnsafeEnabled { - file_api_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SetReplicatorRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SetReplicatorReply); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DeleteReplicatorRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DeleteReplicatorReply); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetAllReplicatorRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetAllReplicatorReply); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AddP2PCollectionsRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AddP2PCollectionsReply); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RemoveP2PCollectionsRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RemoveP2PCollectionsReply); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetAllP2PCollectionsRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetAllP2PCollectionsReply); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetAllReplicatorReply_Replicators); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetAllReplicatorReply_Replicators_Info); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetAllP2PCollectionsReply_Collection); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_api_proto_rawDesc, - NumEnums: 0, - NumMessages: 15, - NumExtensions: 0, - NumServices: 1, - }, - GoTypes: file_api_proto_goTypes, - DependencyIndexes: file_api_proto_depIdxs, - MessageInfos: file_api_proto_msgTypes, - }.Build() - File_api_proto = out.File - file_api_proto_rawDesc = nil - file_api_proto_goTypes = nil - file_api_proto_depIdxs = nil -} diff --git a/net/api/pb/api.proto b/net/api/pb/api.proto deleted file mode 100644 index 367997c7af..0000000000 --- a/net/api/pb/api.proto +++ /dev/null @@ -1,82 +0,0 @@ -syntax = "proto3"; -package api.pb; - -option go_package = "/;api_pb"; - -message SetReplicatorRequest { - repeated string collections = 1; - bytes addr = 2; -} - -message SetReplicatorReply { - bytes peerID = 1; -} - -message DeleteReplicatorRequest { - bytes peerID = 1; -} - -message DeleteReplicatorReply { - bytes peerID = 1; -} - -message GetAllReplicatorRequest {} - -message GetAllReplicatorReply { - message Replicators { - message Info { - bytes id = 1; - bytes addrs = 2; - } - Info info = 1; - repeated string schemas = 2; - } - - repeated Replicators replicators = 1; - -} - -message AddP2PCollectionsRequest { - repeated string collections = 1; -} - -message AddP2PCollectionsReply { - string err = 1; -} - -message RemoveP2PCollectionsRequest { - repeated string collections = 1; -} - -message RemoveP2PCollectionsReply { - string err = 1; -} - -message GetAllP2PCollectionsRequest {} - -message GetAllP2PCollectionsReply { - message Collection { - string id = 1; - string name = 2; - } - repeated Collection collections = 1; -} - - -// Service is the peer-to-peer network API for document sync -service Service { - // SetReplicator for this peer - rpc SetReplicator(SetReplicatorRequest) returns (SetReplicatorReply) {} - - // DeleteReplicator for this peer - rpc DeleteReplicator(DeleteReplicatorRequest) returns (DeleteReplicatorReply) {} - - // DeleteReplicator for this peer - rpc GetAllReplicators(GetAllReplicatorRequest) returns (GetAllReplicatorReply) {} - - rpc AddP2PCollections(AddP2PCollectionsRequest) returns (AddP2PCollectionsReply) {} - - rpc RemoveP2PCollections(RemoveP2PCollectionsRequest) returns (RemoveP2PCollectionsReply) {} - - rpc GetAllP2PCollections(GetAllP2PCollectionsRequest) returns (GetAllP2PCollectionsReply) {} -} \ No newline at end of file diff --git a/net/api/pb/api_grpc.pb.go b/net/api/pb/api_grpc.pb.go deleted file mode 100644 index 5d1bc204d3..0000000000 --- a/net/api/pb/api_grpc.pb.go +++ /dev/null @@ -1,300 +0,0 @@ -// Code generated by protoc-gen-go-grpc. DO NOT EDIT. -// versions: -// - protoc-gen-go-grpc v1.3.0 -// - protoc v3.21.9 -// source: api.proto - -package api_pb - -import ( - context "context" - grpc "google.golang.org/grpc" - codes "google.golang.org/grpc/codes" - status "google.golang.org/grpc/status" -) - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the grpc package it is being compiled against. -// Requires gRPC-Go v1.32.0 or later. -const _ = grpc.SupportPackageIsVersion7 - -const ( - Service_SetReplicator_FullMethodName = "/api.pb.Service/SetReplicator" - Service_DeleteReplicator_FullMethodName = "/api.pb.Service/DeleteReplicator" - Service_GetAllReplicators_FullMethodName = "/api.pb.Service/GetAllReplicators" - Service_AddP2PCollections_FullMethodName = "/api.pb.Service/AddP2PCollections" - Service_RemoveP2PCollections_FullMethodName = "/api.pb.Service/RemoveP2PCollections" - Service_GetAllP2PCollections_FullMethodName = "/api.pb.Service/GetAllP2PCollections" -) - -// ServiceClient is the client API for Service service. -// -// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. -type ServiceClient interface { - // SetReplicator for this peer - SetReplicator(ctx context.Context, in *SetReplicatorRequest, opts ...grpc.CallOption) (*SetReplicatorReply, error) - // DeleteReplicator for this peer - DeleteReplicator(ctx context.Context, in *DeleteReplicatorRequest, opts ...grpc.CallOption) (*DeleteReplicatorReply, error) - // DeleteReplicator for this peer - GetAllReplicators(ctx context.Context, in *GetAllReplicatorRequest, opts ...grpc.CallOption) (*GetAllReplicatorReply, error) - AddP2PCollections(ctx context.Context, in *AddP2PCollectionsRequest, opts ...grpc.CallOption) (*AddP2PCollectionsReply, error) - RemoveP2PCollections(ctx context.Context, in *RemoveP2PCollectionsRequest, opts ...grpc.CallOption) (*RemoveP2PCollectionsReply, error) - GetAllP2PCollections(ctx context.Context, in *GetAllP2PCollectionsRequest, opts ...grpc.CallOption) (*GetAllP2PCollectionsReply, error) -} - -type serviceClient struct { - cc grpc.ClientConnInterface -} - -func NewServiceClient(cc grpc.ClientConnInterface) ServiceClient { - return &serviceClient{cc} -} - -func (c *serviceClient) SetReplicator(ctx context.Context, in *SetReplicatorRequest, opts ...grpc.CallOption) (*SetReplicatorReply, error) { - out := new(SetReplicatorReply) - err := c.cc.Invoke(ctx, Service_SetReplicator_FullMethodName, in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *serviceClient) DeleteReplicator(ctx context.Context, in *DeleteReplicatorRequest, opts ...grpc.CallOption) (*DeleteReplicatorReply, error) { - out := new(DeleteReplicatorReply) - err := c.cc.Invoke(ctx, Service_DeleteReplicator_FullMethodName, in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *serviceClient) GetAllReplicators(ctx context.Context, in *GetAllReplicatorRequest, opts ...grpc.CallOption) (*GetAllReplicatorReply, error) { - out := new(GetAllReplicatorReply) - err := c.cc.Invoke(ctx, Service_GetAllReplicators_FullMethodName, in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *serviceClient) AddP2PCollections(ctx context.Context, in *AddP2PCollectionsRequest, opts ...grpc.CallOption) (*AddP2PCollectionsReply, error) { - out := new(AddP2PCollectionsReply) - err := c.cc.Invoke(ctx, Service_AddP2PCollections_FullMethodName, in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *serviceClient) RemoveP2PCollections(ctx context.Context, in *RemoveP2PCollectionsRequest, opts ...grpc.CallOption) (*RemoveP2PCollectionsReply, error) { - out := new(RemoveP2PCollectionsReply) - err := c.cc.Invoke(ctx, Service_RemoveP2PCollections_FullMethodName, in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *serviceClient) GetAllP2PCollections(ctx context.Context, in *GetAllP2PCollectionsRequest, opts ...grpc.CallOption) (*GetAllP2PCollectionsReply, error) { - out := new(GetAllP2PCollectionsReply) - err := c.cc.Invoke(ctx, Service_GetAllP2PCollections_FullMethodName, in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -// ServiceServer is the server API for Service service. -// All implementations must embed UnimplementedServiceServer -// for forward compatibility -type ServiceServer interface { - // SetReplicator for this peer - SetReplicator(context.Context, *SetReplicatorRequest) (*SetReplicatorReply, error) - // DeleteReplicator for this peer - DeleteReplicator(context.Context, *DeleteReplicatorRequest) (*DeleteReplicatorReply, error) - // DeleteReplicator for this peer - GetAllReplicators(context.Context, *GetAllReplicatorRequest) (*GetAllReplicatorReply, error) - AddP2PCollections(context.Context, *AddP2PCollectionsRequest) (*AddP2PCollectionsReply, error) - RemoveP2PCollections(context.Context, *RemoveP2PCollectionsRequest) (*RemoveP2PCollectionsReply, error) - GetAllP2PCollections(context.Context, *GetAllP2PCollectionsRequest) (*GetAllP2PCollectionsReply, error) - mustEmbedUnimplementedServiceServer() -} - -// UnimplementedServiceServer must be embedded to have forward compatible implementations. -type UnimplementedServiceServer struct { -} - -func (UnimplementedServiceServer) SetReplicator(context.Context, *SetReplicatorRequest) (*SetReplicatorReply, error) { - return nil, status.Errorf(codes.Unimplemented, "method SetReplicator not implemented") -} -func (UnimplementedServiceServer) DeleteReplicator(context.Context, *DeleteReplicatorRequest) (*DeleteReplicatorReply, error) { - return nil, status.Errorf(codes.Unimplemented, "method DeleteReplicator not implemented") -} -func (UnimplementedServiceServer) GetAllReplicators(context.Context, *GetAllReplicatorRequest) (*GetAllReplicatorReply, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetAllReplicators not implemented") -} -func (UnimplementedServiceServer) AddP2PCollections(context.Context, *AddP2PCollectionsRequest) (*AddP2PCollectionsReply, error) { - return nil, status.Errorf(codes.Unimplemented, "method AddP2PCollections not implemented") -} -func (UnimplementedServiceServer) RemoveP2PCollections(context.Context, *RemoveP2PCollectionsRequest) (*RemoveP2PCollectionsReply, error) { - return nil, status.Errorf(codes.Unimplemented, "method RemoveP2PCollections not implemented") -} -func (UnimplementedServiceServer) GetAllP2PCollections(context.Context, *GetAllP2PCollectionsRequest) (*GetAllP2PCollectionsReply, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetAllP2PCollections not implemented") -} -func (UnimplementedServiceServer) mustEmbedUnimplementedServiceServer() {} - -// UnsafeServiceServer may be embedded to opt out of forward compatibility for this service. -// Use of this interface is not recommended, as added methods to ServiceServer will -// result in compilation errors. -type UnsafeServiceServer interface { - mustEmbedUnimplementedServiceServer() -} - -func RegisterServiceServer(s grpc.ServiceRegistrar, srv ServiceServer) { - s.RegisterService(&Service_ServiceDesc, srv) -} - -func _Service_SetReplicator_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(SetReplicatorRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ServiceServer).SetReplicator(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: Service_SetReplicator_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ServiceServer).SetReplicator(ctx, req.(*SetReplicatorRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _Service_DeleteReplicator_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(DeleteReplicatorRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ServiceServer).DeleteReplicator(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: Service_DeleteReplicator_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ServiceServer).DeleteReplicator(ctx, req.(*DeleteReplicatorRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _Service_GetAllReplicators_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(GetAllReplicatorRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ServiceServer).GetAllReplicators(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: Service_GetAllReplicators_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ServiceServer).GetAllReplicators(ctx, req.(*GetAllReplicatorRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _Service_AddP2PCollections_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(AddP2PCollectionsRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ServiceServer).AddP2PCollections(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: Service_AddP2PCollections_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ServiceServer).AddP2PCollections(ctx, req.(*AddP2PCollectionsRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _Service_RemoveP2PCollections_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(RemoveP2PCollectionsRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ServiceServer).RemoveP2PCollections(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: Service_RemoveP2PCollections_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ServiceServer).RemoveP2PCollections(ctx, req.(*RemoveP2PCollectionsRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _Service_GetAllP2PCollections_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(GetAllP2PCollectionsRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ServiceServer).GetAllP2PCollections(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: Service_GetAllP2PCollections_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ServiceServer).GetAllP2PCollections(ctx, req.(*GetAllP2PCollectionsRequest)) - } - return interceptor(ctx, in, info, handler) -} - -// Service_ServiceDesc is the grpc.ServiceDesc for Service service. -// It's only intended for direct use with grpc.RegisterService, -// and not to be introspected or modified (even as a copy) -var Service_ServiceDesc = grpc.ServiceDesc{ - ServiceName: "api.pb.Service", - HandlerType: (*ServiceServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "SetReplicator", - Handler: _Service_SetReplicator_Handler, - }, - { - MethodName: "DeleteReplicator", - Handler: _Service_DeleteReplicator_Handler, - }, - { - MethodName: "GetAllReplicators", - Handler: _Service_GetAllReplicators_Handler, - }, - { - MethodName: "AddP2PCollections", - Handler: _Service_AddP2PCollections_Handler, - }, - { - MethodName: "RemoveP2PCollections", - Handler: _Service_RemoveP2PCollections_Handler, - }, - { - MethodName: "GetAllP2PCollections", - Handler: _Service_GetAllP2PCollections_Handler, - }, - }, - Streams: []grpc.StreamDesc{}, - Metadata: "api.proto", -} diff --git a/net/api/pb/api_vtproto.pb.go b/net/api/pb/api_vtproto.pb.go deleted file mode 100644 index e4ddfb9bcb..0000000000 --- a/net/api/pb/api_vtproto.pb.go +++ /dev/null @@ -1,2316 +0,0 @@ -// Code generated by protoc-gen-go-vtproto. DO NOT EDIT. -// protoc-gen-go-vtproto version: v0.4.0 -// source: api.proto - -package api_pb - -import ( - fmt "fmt" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - io "io" - bits "math/bits" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -func (m *SetReplicatorRequest) MarshalVT() (dAtA []byte, err error) { - if m == nil { - return nil, nil - } - size := m.SizeVT() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBufferVT(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *SetReplicatorRequest) MarshalToVT(dAtA []byte) (int, error) { - size := m.SizeVT() - return m.MarshalToSizedBufferVT(dAtA[:size]) -} - -func (m *SetReplicatorRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) { - if m == nil { - return 0, nil - } - i := len(dAtA) - _ = i - var l int - _ = l - if m.unknownFields != nil { - i -= len(m.unknownFields) - copy(dAtA[i:], m.unknownFields) - } - if len(m.Addr) > 0 { - i -= len(m.Addr) - copy(dAtA[i:], m.Addr) - i = encodeVarint(dAtA, i, uint64(len(m.Addr))) - i-- - dAtA[i] = 0x12 - } - if len(m.Collections) > 0 { - for iNdEx := len(m.Collections) - 1; iNdEx >= 0; iNdEx-- { - i -= len(m.Collections[iNdEx]) - copy(dAtA[i:], m.Collections[iNdEx]) - i = encodeVarint(dAtA, i, uint64(len(m.Collections[iNdEx]))) - i-- - dAtA[i] = 0xa - } - } - return len(dAtA) - i, nil -} - -func (m *SetReplicatorReply) MarshalVT() (dAtA []byte, err error) { - if m == nil { - return nil, nil - } - size := m.SizeVT() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBufferVT(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *SetReplicatorReply) MarshalToVT(dAtA []byte) (int, error) { - size := m.SizeVT() - return m.MarshalToSizedBufferVT(dAtA[:size]) -} - -func (m *SetReplicatorReply) MarshalToSizedBufferVT(dAtA []byte) (int, error) { - if m == nil { - return 0, nil - } - i := len(dAtA) - _ = i - var l int - _ = l - if m.unknownFields != nil { - i -= len(m.unknownFields) - copy(dAtA[i:], m.unknownFields) - } - if len(m.PeerID) > 0 { - i -= len(m.PeerID) - copy(dAtA[i:], m.PeerID) - i = encodeVarint(dAtA, i, uint64(len(m.PeerID))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func (m *DeleteReplicatorRequest) MarshalVT() (dAtA []byte, err error) { - if m == nil { - return nil, nil - } - size := m.SizeVT() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBufferVT(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *DeleteReplicatorRequest) MarshalToVT(dAtA []byte) (int, error) { - size := m.SizeVT() - return m.MarshalToSizedBufferVT(dAtA[:size]) -} - -func (m *DeleteReplicatorRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) { - if m == nil { - return 0, nil - } - i := len(dAtA) - _ = i - var l int - _ = l - if m.unknownFields != nil { - i -= len(m.unknownFields) - copy(dAtA[i:], m.unknownFields) - } - if len(m.PeerID) > 0 { - i -= len(m.PeerID) - copy(dAtA[i:], m.PeerID) - i = encodeVarint(dAtA, i, uint64(len(m.PeerID))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func (m *DeleteReplicatorReply) MarshalVT() (dAtA []byte, err error) { - if m == nil { - return nil, nil - } - size := m.SizeVT() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBufferVT(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *DeleteReplicatorReply) MarshalToVT(dAtA []byte) (int, error) { - size := m.SizeVT() - return m.MarshalToSizedBufferVT(dAtA[:size]) -} - -func (m *DeleteReplicatorReply) MarshalToSizedBufferVT(dAtA []byte) (int, error) { - if m == nil { - return 0, nil - } - i := len(dAtA) - _ = i - var l int - _ = l - if m.unknownFields != nil { - i -= len(m.unknownFields) - copy(dAtA[i:], m.unknownFields) - } - if len(m.PeerID) > 0 { - i -= len(m.PeerID) - copy(dAtA[i:], m.PeerID) - i = encodeVarint(dAtA, i, uint64(len(m.PeerID))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func (m *GetAllReplicatorRequest) MarshalVT() (dAtA []byte, err error) { - if m == nil { - return nil, nil - } - size := m.SizeVT() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBufferVT(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *GetAllReplicatorRequest) MarshalToVT(dAtA []byte) (int, error) { - size := m.SizeVT() - return m.MarshalToSizedBufferVT(dAtA[:size]) -} - -func (m *GetAllReplicatorRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) { - if m == nil { - return 0, nil - } - i := len(dAtA) - _ = i - var l int - _ = l - if m.unknownFields != nil { - i -= len(m.unknownFields) - copy(dAtA[i:], m.unknownFields) - } - return len(dAtA) - i, nil -} - -func (m *GetAllReplicatorReply_Replicators_Info) MarshalVT() (dAtA []byte, err error) { - if m == nil { - return nil, nil - } - size := m.SizeVT() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBufferVT(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *GetAllReplicatorReply_Replicators_Info) MarshalToVT(dAtA []byte) (int, error) { - size := m.SizeVT() - return m.MarshalToSizedBufferVT(dAtA[:size]) -} - -func (m *GetAllReplicatorReply_Replicators_Info) MarshalToSizedBufferVT(dAtA []byte) (int, error) { - if m == nil { - return 0, nil - } - i := len(dAtA) - _ = i - var l int - _ = l - if m.unknownFields != nil { - i -= len(m.unknownFields) - copy(dAtA[i:], m.unknownFields) - } - if len(m.Addrs) > 0 { - i -= len(m.Addrs) - copy(dAtA[i:], m.Addrs) - i = encodeVarint(dAtA, i, uint64(len(m.Addrs))) - i-- - dAtA[i] = 0x12 - } - if len(m.Id) > 0 { - i -= len(m.Id) - copy(dAtA[i:], m.Id) - i = encodeVarint(dAtA, i, uint64(len(m.Id))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func (m *GetAllReplicatorReply_Replicators) MarshalVT() (dAtA []byte, err error) { - if m == nil { - return nil, nil - } - size := m.SizeVT() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBufferVT(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *GetAllReplicatorReply_Replicators) MarshalToVT(dAtA []byte) (int, error) { - size := m.SizeVT() - return m.MarshalToSizedBufferVT(dAtA[:size]) -} - -func (m *GetAllReplicatorReply_Replicators) MarshalToSizedBufferVT(dAtA []byte) (int, error) { - if m == nil { - return 0, nil - } - i := len(dAtA) - _ = i - var l int - _ = l - if m.unknownFields != nil { - i -= len(m.unknownFields) - copy(dAtA[i:], m.unknownFields) - } - if len(m.Schemas) > 0 { - for iNdEx := len(m.Schemas) - 1; iNdEx >= 0; iNdEx-- { - i -= len(m.Schemas[iNdEx]) - copy(dAtA[i:], m.Schemas[iNdEx]) - i = encodeVarint(dAtA, i, uint64(len(m.Schemas[iNdEx]))) - i-- - dAtA[i] = 0x12 - } - } - if m.Info != nil { - size, err := m.Info.MarshalToSizedBufferVT(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarint(dAtA, i, uint64(size)) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func (m *GetAllReplicatorReply) MarshalVT() (dAtA []byte, err error) { - if m == nil { - return nil, nil - } - size := m.SizeVT() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBufferVT(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *GetAllReplicatorReply) MarshalToVT(dAtA []byte) (int, error) { - size := m.SizeVT() - return m.MarshalToSizedBufferVT(dAtA[:size]) -} - -func (m *GetAllReplicatorReply) MarshalToSizedBufferVT(dAtA []byte) (int, error) { - if m == nil { - return 0, nil - } - i := len(dAtA) - _ = i - var l int - _ = l - if m.unknownFields != nil { - i -= len(m.unknownFields) - copy(dAtA[i:], m.unknownFields) - } - if len(m.Replicators) > 0 { - for iNdEx := len(m.Replicators) - 1; iNdEx >= 0; iNdEx-- { - size, err := m.Replicators[iNdEx].MarshalToSizedBufferVT(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarint(dAtA, i, uint64(size)) - i-- - dAtA[i] = 0xa - } - } - return len(dAtA) - i, nil -} - -func (m *AddP2PCollectionsRequest) MarshalVT() (dAtA []byte, err error) { - if m == nil { - return nil, nil - } - size := m.SizeVT() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBufferVT(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *AddP2PCollectionsRequest) MarshalToVT(dAtA []byte) (int, error) { - size := m.SizeVT() - return m.MarshalToSizedBufferVT(dAtA[:size]) -} - -func (m *AddP2PCollectionsRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) { - if m == nil { - return 0, nil - } - i := len(dAtA) - _ = i - var l int - _ = l - if m.unknownFields != nil { - i -= len(m.unknownFields) - copy(dAtA[i:], m.unknownFields) - } - if len(m.Collections) > 0 { - for iNdEx := len(m.Collections) - 1; iNdEx >= 0; iNdEx-- { - i -= len(m.Collections[iNdEx]) - copy(dAtA[i:], m.Collections[iNdEx]) - i = encodeVarint(dAtA, i, uint64(len(m.Collections[iNdEx]))) - i-- - dAtA[i] = 0xa - } - } - return len(dAtA) - i, nil -} - -func (m *AddP2PCollectionsReply) MarshalVT() (dAtA []byte, err error) { - if m == nil { - return nil, nil - } - size := m.SizeVT() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBufferVT(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *AddP2PCollectionsReply) MarshalToVT(dAtA []byte) (int, error) { - size := m.SizeVT() - return m.MarshalToSizedBufferVT(dAtA[:size]) -} - -func (m *AddP2PCollectionsReply) MarshalToSizedBufferVT(dAtA []byte) (int, error) { - if m == nil { - return 0, nil - } - i := len(dAtA) - _ = i - var l int - _ = l - if m.unknownFields != nil { - i -= len(m.unknownFields) - copy(dAtA[i:], m.unknownFields) - } - if len(m.Err) > 0 { - i -= len(m.Err) - copy(dAtA[i:], m.Err) - i = encodeVarint(dAtA, i, uint64(len(m.Err))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func (m *RemoveP2PCollectionsRequest) MarshalVT() (dAtA []byte, err error) { - if m == nil { - return nil, nil - } - size := m.SizeVT() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBufferVT(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *RemoveP2PCollectionsRequest) MarshalToVT(dAtA []byte) (int, error) { - size := m.SizeVT() - return m.MarshalToSizedBufferVT(dAtA[:size]) -} - -func (m *RemoveP2PCollectionsRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) { - if m == nil { - return 0, nil - } - i := len(dAtA) - _ = i - var l int - _ = l - if m.unknownFields != nil { - i -= len(m.unknownFields) - copy(dAtA[i:], m.unknownFields) - } - if len(m.Collections) > 0 { - for iNdEx := len(m.Collections) - 1; iNdEx >= 0; iNdEx-- { - i -= len(m.Collections[iNdEx]) - copy(dAtA[i:], m.Collections[iNdEx]) - i = encodeVarint(dAtA, i, uint64(len(m.Collections[iNdEx]))) - i-- - dAtA[i] = 0xa - } - } - return len(dAtA) - i, nil -} - -func (m *RemoveP2PCollectionsReply) MarshalVT() (dAtA []byte, err error) { - if m == nil { - return nil, nil - } - size := m.SizeVT() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBufferVT(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *RemoveP2PCollectionsReply) MarshalToVT(dAtA []byte) (int, error) { - size := m.SizeVT() - return m.MarshalToSizedBufferVT(dAtA[:size]) -} - -func (m *RemoveP2PCollectionsReply) MarshalToSizedBufferVT(dAtA []byte) (int, error) { - if m == nil { - return 0, nil - } - i := len(dAtA) - _ = i - var l int - _ = l - if m.unknownFields != nil { - i -= len(m.unknownFields) - copy(dAtA[i:], m.unknownFields) - } - if len(m.Err) > 0 { - i -= len(m.Err) - copy(dAtA[i:], m.Err) - i = encodeVarint(dAtA, i, uint64(len(m.Err))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func (m *GetAllP2PCollectionsRequest) MarshalVT() (dAtA []byte, err error) { - if m == nil { - return nil, nil - } - size := m.SizeVT() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBufferVT(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *GetAllP2PCollectionsRequest) MarshalToVT(dAtA []byte) (int, error) { - size := m.SizeVT() - return m.MarshalToSizedBufferVT(dAtA[:size]) -} - -func (m *GetAllP2PCollectionsRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) { - if m == nil { - return 0, nil - } - i := len(dAtA) - _ = i - var l int - _ = l - if m.unknownFields != nil { - i -= len(m.unknownFields) - copy(dAtA[i:], m.unknownFields) - } - return len(dAtA) - i, nil -} - -func (m *GetAllP2PCollectionsReply_Collection) MarshalVT() (dAtA []byte, err error) { - if m == nil { - return nil, nil - } - size := m.SizeVT() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBufferVT(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *GetAllP2PCollectionsReply_Collection) MarshalToVT(dAtA []byte) (int, error) { - size := m.SizeVT() - return m.MarshalToSizedBufferVT(dAtA[:size]) -} - -func (m *GetAllP2PCollectionsReply_Collection) MarshalToSizedBufferVT(dAtA []byte) (int, error) { - if m == nil { - return 0, nil - } - i := len(dAtA) - _ = i - var l int - _ = l - if m.unknownFields != nil { - i -= len(m.unknownFields) - copy(dAtA[i:], m.unknownFields) - } - if len(m.Name) > 0 { - i -= len(m.Name) - copy(dAtA[i:], m.Name) - i = encodeVarint(dAtA, i, uint64(len(m.Name))) - i-- - dAtA[i] = 0x12 - } - if len(m.Id) > 0 { - i -= len(m.Id) - copy(dAtA[i:], m.Id) - i = encodeVarint(dAtA, i, uint64(len(m.Id))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func (m *GetAllP2PCollectionsReply) MarshalVT() (dAtA []byte, err error) { - if m == nil { - return nil, nil - } - size := m.SizeVT() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBufferVT(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *GetAllP2PCollectionsReply) MarshalToVT(dAtA []byte) (int, error) { - size := m.SizeVT() - return m.MarshalToSizedBufferVT(dAtA[:size]) -} - -func (m *GetAllP2PCollectionsReply) MarshalToSizedBufferVT(dAtA []byte) (int, error) { - if m == nil { - return 0, nil - } - i := len(dAtA) - _ = i - var l int - _ = l - if m.unknownFields != nil { - i -= len(m.unknownFields) - copy(dAtA[i:], m.unknownFields) - } - if len(m.Collections) > 0 { - for iNdEx := len(m.Collections) - 1; iNdEx >= 0; iNdEx-- { - size, err := m.Collections[iNdEx].MarshalToSizedBufferVT(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarint(dAtA, i, uint64(size)) - i-- - dAtA[i] = 0xa - } - } - return len(dAtA) - i, nil -} - -func encodeVarint(dAtA []byte, offset int, v uint64) int { - offset -= sov(v) - base := offset - for v >= 1<<7 { - dAtA[offset] = uint8(v&0x7f | 0x80) - v >>= 7 - offset++ - } - dAtA[offset] = uint8(v) - return base -} -func (m *SetReplicatorRequest) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if len(m.Collections) > 0 { - for _, s := range m.Collections { - l = len(s) - n += 1 + l + sov(uint64(l)) - } - } - l = len(m.Addr) - if l > 0 { - n += 1 + l + sov(uint64(l)) - } - n += len(m.unknownFields) - return n -} - -func (m *SetReplicatorReply) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.PeerID) - if l > 0 { - n += 1 + l + sov(uint64(l)) - } - n += len(m.unknownFields) - return n -} - -func (m *DeleteReplicatorRequest) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.PeerID) - if l > 0 { - n += 1 + l + sov(uint64(l)) - } - n += len(m.unknownFields) - return n -} - -func (m *DeleteReplicatorReply) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.PeerID) - if l > 0 { - n += 1 + l + sov(uint64(l)) - } - n += len(m.unknownFields) - return n -} - -func (m *GetAllReplicatorRequest) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - n += len(m.unknownFields) - return n -} - -func (m *GetAllReplicatorReply_Replicators_Info) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Id) - if l > 0 { - n += 1 + l + sov(uint64(l)) - } - l = len(m.Addrs) - if l > 0 { - n += 1 + l + sov(uint64(l)) - } - n += len(m.unknownFields) - return n -} - -func (m *GetAllReplicatorReply_Replicators) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.Info != nil { - l = m.Info.SizeVT() - n += 1 + l + sov(uint64(l)) - } - if len(m.Schemas) > 0 { - for _, s := range m.Schemas { - l = len(s) - n += 1 + l + sov(uint64(l)) - } - } - n += len(m.unknownFields) - return n -} - -func (m *GetAllReplicatorReply) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if len(m.Replicators) > 0 { - for _, e := range m.Replicators { - l = e.SizeVT() - n += 1 + l + sov(uint64(l)) - } - } - n += len(m.unknownFields) - return n -} - -func (m *AddP2PCollectionsRequest) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if len(m.Collections) > 0 { - for _, s := range m.Collections { - l = len(s) - n += 1 + l + sov(uint64(l)) - } - } - n += len(m.unknownFields) - return n -} - -func (m *AddP2PCollectionsReply) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Err) - if l > 0 { - n += 1 + l + sov(uint64(l)) - } - n += len(m.unknownFields) - return n -} - -func (m *RemoveP2PCollectionsRequest) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if len(m.Collections) > 0 { - for _, s := range m.Collections { - l = len(s) - n += 1 + l + sov(uint64(l)) - } - } - n += len(m.unknownFields) - return n -} - -func (m *RemoveP2PCollectionsReply) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Err) - if l > 0 { - n += 1 + l + sov(uint64(l)) - } - n += len(m.unknownFields) - return n -} - -func (m *GetAllP2PCollectionsRequest) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - n += len(m.unknownFields) - return n -} - -func (m *GetAllP2PCollectionsReply_Collection) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Id) - if l > 0 { - n += 1 + l + sov(uint64(l)) - } - l = len(m.Name) - if l > 0 { - n += 1 + l + sov(uint64(l)) - } - n += len(m.unknownFields) - return n -} - -func (m *GetAllP2PCollectionsReply) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if len(m.Collections) > 0 { - for _, e := range m.Collections { - l = e.SizeVT() - n += 1 + l + sov(uint64(l)) - } - } - n += len(m.unknownFields) - return n -} - -func sov(x uint64) (n int) { - return (bits.Len64(x|1) + 6) / 7 -} -func soz(x uint64) (n int) { - return sov(uint64((x << 1) ^ uint64((int64(x) >> 63)))) -} -func (m *SetReplicatorRequest) UnmarshalVT(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: SetReplicatorRequest: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: SetReplicatorRequest: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Collections", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLength - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Collections = append(m.Collections, string(dAtA[iNdEx:postIndex])) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Addr", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLength - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Addr = append(m.Addr[:0], dAtA[iNdEx:postIndex]...) - if m.Addr == nil { - m.Addr = []byte{} - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skip(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLength - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *SetReplicatorReply) UnmarshalVT(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: SetReplicatorReply: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: SetReplicatorReply: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field PeerID", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLength - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.PeerID = append(m.PeerID[:0], dAtA[iNdEx:postIndex]...) - if m.PeerID == nil { - m.PeerID = []byte{} - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skip(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLength - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *DeleteReplicatorRequest) UnmarshalVT(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: DeleteReplicatorRequest: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: DeleteReplicatorRequest: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field PeerID", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLength - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.PeerID = append(m.PeerID[:0], dAtA[iNdEx:postIndex]...) - if m.PeerID == nil { - m.PeerID = []byte{} - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skip(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLength - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *DeleteReplicatorReply) UnmarshalVT(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: DeleteReplicatorReply: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: DeleteReplicatorReply: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field PeerID", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLength - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.PeerID = append(m.PeerID[:0], dAtA[iNdEx:postIndex]...) - if m.PeerID == nil { - m.PeerID = []byte{} - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skip(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLength - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *GetAllReplicatorRequest) UnmarshalVT(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: GetAllReplicatorRequest: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: GetAllReplicatorRequest: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - default: - iNdEx = preIndex - skippy, err := skip(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLength - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *GetAllReplicatorReply_Replicators_Info) UnmarshalVT(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: GetAllReplicatorReply_Replicators_Info: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: GetAllReplicatorReply_Replicators_Info: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Id", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLength - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Id = append(m.Id[:0], dAtA[iNdEx:postIndex]...) - if m.Id == nil { - m.Id = []byte{} - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Addrs", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLength - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Addrs = append(m.Addrs[:0], dAtA[iNdEx:postIndex]...) - if m.Addrs == nil { - m.Addrs = []byte{} - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skip(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLength - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *GetAllReplicatorReply_Replicators) UnmarshalVT(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: GetAllReplicatorReply_Replicators: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: GetAllReplicatorReply_Replicators: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Info", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLength - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Info == nil { - m.Info = &GetAllReplicatorReply_Replicators_Info{} - } - if err := m.Info.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Schemas", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLength - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Schemas = append(m.Schemas, string(dAtA[iNdEx:postIndex])) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skip(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLength - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *GetAllReplicatorReply) UnmarshalVT(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: GetAllReplicatorReply: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: GetAllReplicatorReply: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Replicators", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLength - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Replicators = append(m.Replicators, &GetAllReplicatorReply_Replicators{}) - if err := m.Replicators[len(m.Replicators)-1].UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skip(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLength - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *AddP2PCollectionsRequest) UnmarshalVT(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: AddP2PCollectionsRequest: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: AddP2PCollectionsRequest: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Collections", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLength - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Collections = append(m.Collections, string(dAtA[iNdEx:postIndex])) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skip(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLength - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *AddP2PCollectionsReply) UnmarshalVT(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: AddP2PCollectionsReply: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: AddP2PCollectionsReply: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Err", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLength - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Err = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skip(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLength - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *RemoveP2PCollectionsRequest) UnmarshalVT(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: RemoveP2PCollectionsRequest: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: RemoveP2PCollectionsRequest: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Collections", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLength - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Collections = append(m.Collections, string(dAtA[iNdEx:postIndex])) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skip(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLength - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *RemoveP2PCollectionsReply) UnmarshalVT(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: RemoveP2PCollectionsReply: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: RemoveP2PCollectionsReply: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Err", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLength - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Err = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skip(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLength - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *GetAllP2PCollectionsRequest) UnmarshalVT(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: GetAllP2PCollectionsRequest: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: GetAllP2PCollectionsRequest: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - default: - iNdEx = preIndex - skippy, err := skip(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLength - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *GetAllP2PCollectionsReply_Collection) UnmarshalVT(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: GetAllP2PCollectionsReply_Collection: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: GetAllP2PCollectionsReply_Collection: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Id", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLength - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Id = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLength - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Name = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skip(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLength - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *GetAllP2PCollectionsReply) UnmarshalVT(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: GetAllP2PCollectionsReply: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: GetAllP2PCollectionsReply: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Collections", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLength - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Collections = append(m.Collections, &GetAllP2PCollectionsReply_Collection{}) - if err := m.Collections[len(m.Collections)-1].UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skip(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLength - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} - -func skip(dAtA []byte) (n int, err error) { - l := len(dAtA) - iNdEx := 0 - depth := 0 - for iNdEx < l { - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflow - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - wireType := int(wire & 0x7) - switch wireType { - case 0: - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflow - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - iNdEx++ - if dAtA[iNdEx-1] < 0x80 { - break - } - } - case 1: - iNdEx += 8 - case 2: - var length int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflow - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - length |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if length < 0 { - return 0, ErrInvalidLength - } - iNdEx += length - case 3: - depth++ - case 4: - if depth == 0 { - return 0, ErrUnexpectedEndOfGroup - } - depth-- - case 5: - iNdEx += 4 - default: - return 0, fmt.Errorf("proto: illegal wireType %d", wireType) - } - if iNdEx < 0 { - return 0, ErrInvalidLength - } - if depth == 0 { - return iNdEx, nil - } - } - return 0, io.ErrUnexpectedEOF -} - -var ( - ErrInvalidLength = fmt.Errorf("proto: negative length found during unmarshaling") - ErrIntOverflow = fmt.Errorf("proto: integer overflow") - ErrUnexpectedEndOfGroup = fmt.Errorf("proto: unexpected end of group") -) diff --git a/tests/clients/cli/wrapper.go b/tests/clients/cli/wrapper.go new file mode 100644 index 0000000000..f167b882d8 --- /dev/null +++ b/tests/clients/cli/wrapper.go @@ -0,0 +1,419 @@ +// Copyright 2023 Democratized Data Foundation +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +package cli + +import ( + "bufio" + "context" + "encoding/json" + "fmt" + "io" + "net/http/httptest" + "strings" + + blockstore "github.com/ipfs/boxo/blockstore" + "github.com/libp2p/go-libp2p/core/peer" + + "github.com/sourcenetwork/defradb/cli" + "github.com/sourcenetwork/defradb/client" + "github.com/sourcenetwork/defradb/datastore" + "github.com/sourcenetwork/defradb/events" + "github.com/sourcenetwork/defradb/http" +) + +var _ client.DB = (*Wrapper)(nil) + +type Wrapper struct { + db client.DB + store client.Store + cmd *cliWrapper + handler *http.Handler + httpServer *httptest.Server +} + +func NewWrapper(db client.DB) *Wrapper { + handler := http.NewHandler(db, http.ServerOptions{}) + httpServer := httptest.NewServer(handler) + cmd := newCliWrapper(httpServer.URL) + + return &Wrapper{ + db: db, + store: db, + cmd: cmd, + httpServer: httpServer, + handler: handler, + } +} + +func (w *Wrapper) SetReplicator(ctx context.Context, rep client.Replicator) error { + args := []string{"client", "p2p", "replicator", "set"} + args = append(args, "--collection", strings.Join(rep.Schemas, ",")) + + addrs, err := peer.AddrInfoToP2pAddrs(&rep.Info) + if err != nil { + return err + } + args = append(args, addrs[0].String()) + + _, err = w.cmd.execute(ctx, args) + return err +} + +func (w *Wrapper) DeleteReplicator(ctx context.Context, rep client.Replicator) error { + args := []string{"client", "p2p", "replicator", "delete"} + + addrs, err := peer.AddrInfoToP2pAddrs(&rep.Info) + if err != nil { + return err + } + args = append(args, addrs[0].String()) + + _, err = w.cmd.execute(ctx, args) + return err +} + +func (w *Wrapper) GetAllReplicators(ctx context.Context) ([]client.Replicator, error) { + args := []string{"client", "p2p", "replicator", "getall"} + + data, err := w.cmd.execute(ctx, args) + if err != nil { + return nil, err + } + var reps []client.Replicator + if err := json.Unmarshal(data, &reps); err != nil { + return nil, err + } + return reps, nil +} + +func (w *Wrapper) AddP2PCollection(ctx context.Context, collectionID string) error { + args := []string{"client", "p2p", "collection", "add"} + args = append(args, collectionID) + + _, err := w.cmd.execute(ctx, args) + return err +} + +func (w *Wrapper) RemoveP2PCollection(ctx context.Context, collectionID string) error { + args := []string{"client", "p2p", "collection", "remove"} + args = append(args, collectionID) + + _, err := w.cmd.execute(ctx, args) + return err +} + +func (w *Wrapper) GetAllP2PCollections(ctx context.Context) ([]string, error) { + args := []string{"client", "p2p", "collection", "getall"} + + data, err := w.cmd.execute(ctx, args) + if err != nil { + return nil, err + } + var cols []string + if err := json.Unmarshal(data, &cols); err != nil { + return nil, err + } + return cols, nil +} + +func (w *Wrapper) BasicImport(ctx context.Context, filepath string) error { + args := []string{"client", "backup", "import"} + args = append(args, filepath) + + _, err := w.cmd.execute(ctx, args) + return err +} + +func (w *Wrapper) BasicExport(ctx context.Context, config *client.BackupConfig) error { + args := []string{"client", "backup", "export"} + + if len(config.Collections) > 0 { + args = append(args, "--collections", strings.Join(config.Collections, ",")) + } + if config.Format != "" { + args = append(args, "--format", config.Format) + } + if config.Pretty { + args = append(args, "--pretty") + } + args = append(args, config.Filepath) + + _, err := w.cmd.execute(ctx, args) + return err +} + +func (w *Wrapper) AddSchema(ctx context.Context, schema string) ([]client.CollectionDescription, error) { + args := []string{"client", "schema", "add"} + args = append(args, schema) + + data, err := w.cmd.execute(ctx, args) + if err != nil { + return nil, err + } + var cols []client.CollectionDescription + if err := json.Unmarshal(data, &cols); err != nil { + return nil, err + } + return cols, nil +} + +func (w *Wrapper) PatchSchema(ctx context.Context, patch string, setDefault bool) error { + args := []string{"client", "schema", "patch"} + if setDefault { + args = append(args, "--set-default") + } + args = append(args, patch) + + _, err := w.cmd.execute(ctx, args) + return err +} + +func (w *Wrapper) SetDefaultSchemaVersion(ctx context.Context, schemaVersionID string) error { + args := []string{"client", "schema", "set-default"} + args = append(args, schemaVersionID) + + _, err := w.cmd.execute(ctx, args) + return err +} + +func (w *Wrapper) SetMigration(ctx context.Context, config client.LensConfig) error { + return w.LensRegistry().SetMigration(ctx, config) +} + +func (w *Wrapper) LensRegistry() client.LensRegistry { + return &LensRegistry{w.cmd} +} + +func (w *Wrapper) GetCollectionByName(ctx context.Context, name client.CollectionName) (client.Collection, error) { + args := []string{"client", "collection", "describe"} + args = append(args, "--name", name) + + data, err := w.cmd.execute(ctx, args) + if err != nil { + return nil, err + } + var colDesc client.CollectionDescription + if err := json.Unmarshal(data, &colDesc); err != nil { + return nil, err + } + return &Collection{w.cmd, colDesc}, nil +} + +func (w *Wrapper) GetCollectionBySchemaID(ctx context.Context, schemaId string) (client.Collection, error) { + args := []string{"client", "collection", "describe"} + args = append(args, "--schema", schemaId) + + data, err := w.cmd.execute(ctx, args) + if err != nil { + return nil, err + } + var colDesc client.CollectionDescription + if err := json.Unmarshal(data, &colDesc); err != nil { + return nil, err + } + return &Collection{w.cmd, colDesc}, nil +} + +func (w *Wrapper) GetCollectionByVersionID(ctx context.Context, versionId string) (client.Collection, error) { + args := []string{"client", "collection", "describe"} + args = append(args, "--version", versionId) + + data, err := w.cmd.execute(ctx, args) + if err != nil { + return nil, err + } + var colDesc client.CollectionDescription + if err := json.Unmarshal(data, &colDesc); err != nil { + return nil, err + } + return &Collection{w.cmd, colDesc}, nil +} + +func (w *Wrapper) GetAllCollections(ctx context.Context) ([]client.Collection, error) { + args := []string{"client", "collection", "describe"} + + data, err := w.cmd.execute(ctx, args) + if err != nil { + return nil, err + } + var colDesc []client.CollectionDescription + if err := json.Unmarshal(data, &colDesc); err != nil { + return nil, err + } + cols := make([]client.Collection, len(colDesc)) + for i, v := range colDesc { + cols[i] = &Collection{w.cmd, v} + } + return cols, err +} + +func (w *Wrapper) GetAllIndexes(ctx context.Context) (map[client.CollectionName][]client.IndexDescription, error) { + args := []string{"client", "index", "list"} + + data, err := w.cmd.execute(ctx, args) + if err != nil { + return nil, err + } + var indexes map[client.CollectionName][]client.IndexDescription + if err := json.Unmarshal(data, &indexes); err != nil { + return nil, err + } + return indexes, nil +} + +func (w *Wrapper) ExecRequest(ctx context.Context, query string) *client.RequestResult { + args := []string{"client", "query"} + args = append(args, query) + + result := &client.RequestResult{} + + stdOut, stdErr, err := w.cmd.executeStream(ctx, args) + if err != nil { + result.GQL.Errors = []error{err} + return result + } + buffer := bufio.NewReader(stdOut) + header, err := buffer.ReadString('\n') + if err != nil { + result.GQL.Errors = []error{err} + return result + } + if header == cli.SUB_RESULTS_HEADER { + result.Pub = w.execRequestSubscription(ctx, buffer) + return result + } + data, err := io.ReadAll(buffer) + if err != nil { + result.GQL.Errors = []error{err} + return result + } + errData, err := io.ReadAll(stdErr) + if err != nil { + result.GQL.Errors = []error{err} + return result + } + if len(errData) > 0 { + result.GQL.Errors = []error{fmt.Errorf("%s", errData)} + return result + } + + var response http.GraphQLResponse + if err = json.Unmarshal(data, &response); err != nil { + result.GQL.Errors = []error{err} + return result + } + result.GQL.Data = response.Data + result.GQL.Errors = response.Errors + return result +} + +func (w *Wrapper) execRequestSubscription(ctx context.Context, r io.Reader) *events.Publisher[events.Update] { + pubCh := events.New[events.Update](0, 0) + pub, err := events.NewPublisher[events.Update](pubCh, 0) + if err != nil { + return nil + } + + go func() { + dec := json.NewDecoder(r) + + for { + var response http.GraphQLResponse + if err := dec.Decode(&response); err != nil { + return + } + pub.Publish(client.GQLResult{ + Errors: response.Errors, + Data: response.Data, + }) + } + }() + + return pub +} + +func (w *Wrapper) NewTxn(ctx context.Context, readOnly bool) (datastore.Txn, error) { + args := []string{"client", "tx", "create"} + if readOnly { + args = append(args, "--read-only") + } + + data, err := w.cmd.execute(ctx, args) + if err != nil { + return nil, err + } + var res http.CreateTxResponse + if err := json.Unmarshal(data, &res); err != nil { + return nil, err + } + tx, err := w.handler.Transaction(res.ID) + if err != nil { + return nil, err + } + return &Transaction{tx, w.cmd}, nil +} + +func (w *Wrapper) NewConcurrentTxn(ctx context.Context, readOnly bool) (datastore.Txn, error) { + args := []string{"client", "tx", "create"} + args = append(args, "--concurrent") + + if readOnly { + args = append(args, "--read-only") + } + + data, err := w.cmd.execute(ctx, args) + if err != nil { + return nil, err + } + var res http.CreateTxResponse + if err := json.Unmarshal(data, &res); err != nil { + return nil, err + } + tx, err := w.handler.Transaction(res.ID) + if err != nil { + return nil, err + } + return &Transaction{tx, w.cmd}, nil +} + +func (w *Wrapper) WithTxn(tx datastore.Txn) client.Store { + return &Wrapper{ + db: w.db, + store: w.db.WithTxn(tx), + cmd: w.cmd.withTxn(tx), + } +} + +func (w *Wrapper) Root() datastore.RootStore { + return w.db.Root() +} + +func (w *Wrapper) Blockstore() blockstore.Blockstore { + return w.db.Blockstore() +} + +func (w *Wrapper) Close(ctx context.Context) { + w.httpServer.CloseClientConnections() + w.httpServer.Close() + w.db.Close(ctx) +} + +func (w *Wrapper) Events() events.Events { + return w.db.Events() +} + +func (w *Wrapper) MaxTxnRetries() int { + return w.db.MaxTxnRetries() +} + +func (w *Wrapper) PrintDump(ctx context.Context) error { + return w.db.PrintDump(ctx) +} diff --git a/tests/clients/cli/wrapper_cli.go b/tests/clients/cli/wrapper_cli.go new file mode 100644 index 0000000000..1f73b20e25 --- /dev/null +++ b/tests/clients/cli/wrapper_cli.go @@ -0,0 +1,85 @@ +// Copyright 2023 Democratized Data Foundation +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +package cli + +import ( + "context" + "fmt" + "io" + "strings" + + "github.com/sourcenetwork/defradb/cli" + "github.com/sourcenetwork/defradb/config" + "github.com/sourcenetwork/defradb/datastore" +) + +type cliWrapper struct { + address string + txValue string +} + +func newCliWrapper(address string) *cliWrapper { + return &cliWrapper{ + address: strings.TrimPrefix(address, "http://"), + } +} + +func (w *cliWrapper) withTxn(tx datastore.Txn) *cliWrapper { + return &cliWrapper{ + address: w.address, + txValue: fmt.Sprintf("%d", tx.ID()), + } +} + +func (w *cliWrapper) execute(ctx context.Context, args []string) ([]byte, error) { + stdOut, stdErr, err := w.executeStream(ctx, args) + if err != nil { + return nil, err + } + stdOutData, err := io.ReadAll(stdOut) + if err != nil { + return nil, err + } + stdErrData, err := io.ReadAll(stdErr) + if err != nil { + return nil, err + } + if len(stdErrData) != 0 { + return nil, fmt.Errorf("%s", stdErrData) + } + return stdOutData, nil +} + +func (w *cliWrapper) executeStream(ctx context.Context, args []string) (io.ReadCloser, io.ReadCloser, error) { + stdOutRead, stdOutWrite := io.Pipe() + stdErrRead, stdErrWrite := io.Pipe() + + if w.txValue != "" { + args = append(args, "--tx", w.txValue) + } + args = append(args, "--url", w.address) + + cmd := cli.NewDefraCommand(config.DefaultConfig()) + cmd.SetOut(stdOutWrite) + cmd.SetErr(stdErrWrite) + cmd.SetArgs(args) + + cmd.SilenceErrors = true + cmd.SilenceUsage = true + + go func() { + err := cmd.Execute() + stdOutWrite.CloseWithError(err) + stdErrWrite.CloseWithError(err) + }() + + return stdOutRead, stdErrRead, nil +} diff --git a/tests/clients/cli/wrapper_collection.go b/tests/clients/cli/wrapper_collection.go new file mode 100644 index 0000000000..3500bdce7c --- /dev/null +++ b/tests/clients/cli/wrapper_collection.go @@ -0,0 +1,405 @@ +// Copyright 2023 Democratized Data Foundation +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +package cli + +import ( + "context" + "encoding/json" + "fmt" + "strings" + + "github.com/sourcenetwork/defradb/client" + "github.com/sourcenetwork/defradb/client/request" + "github.com/sourcenetwork/defradb/datastore" + "github.com/sourcenetwork/defradb/errors" + "github.com/sourcenetwork/defradb/http" +) + +var _ client.Collection = (*Collection)(nil) + +type Collection struct { + cmd *cliWrapper + desc client.CollectionDescription +} + +func (c *Collection) Description() client.CollectionDescription { + return c.desc +} + +func (c *Collection) Name() string { + return c.desc.Name +} + +func (c *Collection) Schema() client.SchemaDescription { + return c.desc.Schema +} + +func (c *Collection) ID() uint32 { + return c.desc.ID +} + +func (c *Collection) SchemaID() string { + return c.desc.Schema.SchemaID +} + +func (c *Collection) Create(ctx context.Context, doc *client.Document) error { + args := []string{"client", "collection", "create"} + args = append(args, "--name", c.desc.Name) + + // We must call this here, else the doc key on the given object will not match + // that of the document saved in the database + err := doc.RemapAliasFieldsAndDockey(c.Description().Schema.Fields) + if err != nil { + return err + } + document, err := doc.String() + if err != nil { + return err + } + args = append(args, string(document)) + + _, err = c.cmd.execute(ctx, args) + if err != nil { + return err + } + doc.Clean() + return nil +} + +func (c *Collection) CreateMany(ctx context.Context, docs []*client.Document) error { + args := []string{"client", "collection", "create"} + args = append(args, "--name", c.desc.Name) + + docMapList := make([]map[string]any, len(docs)) + for i, doc := range docs { + // We must call this here, else the doc key on the given object will not match + // that of the document saved in the database + err := doc.RemapAliasFieldsAndDockey(c.Description().Schema.Fields) + if err != nil { + return err + } + docMap, err := doc.ToMap() + if err != nil { + return err + } + docMapList[i] = docMap + } + documents, err := json.Marshal(docMapList) + if err != nil { + return err + } + args = append(args, string(documents)) + + _, err = c.cmd.execute(ctx, args) + if err != nil { + return err + } + for _, doc := range docs { + doc.Clean() + } + return nil +} + +func (c *Collection) Update(ctx context.Context, doc *client.Document) error { + args := []string{"client", "collection", "update"} + args = append(args, "--name", c.desc.Name) + args = append(args, "--key", doc.Key().String()) + + document, err := doc.ToJSONPatch() + if err != nil { + return err + } + args = append(args, string(document)) + + _, err = c.cmd.execute(ctx, args) + if err != nil { + return err + } + doc.Clean() + return nil +} + +func (c *Collection) Save(ctx context.Context, doc *client.Document) error { + _, err := c.Get(ctx, doc.Key(), true) + if err == nil { + return c.Update(ctx, doc) + } + if errors.Is(err, client.ErrDocumentNotFound) { + return c.Create(ctx, doc) + } + return err +} + +func (c *Collection) Delete(ctx context.Context, docKey client.DocKey) (bool, error) { + res, err := c.DeleteWithKey(ctx, docKey) + if err != nil { + return false, err + } + return res.Count == 1, nil +} + +func (c *Collection) Exists(ctx context.Context, docKey client.DocKey) (bool, error) { + _, err := c.Get(ctx, docKey, false) + if err != nil { + return false, err + } + return true, nil +} + +func (c *Collection) UpdateWith(ctx context.Context, target any, updater string) (*client.UpdateResult, error) { + switch t := target.(type) { + case string, map[string]any, *request.Filter: + return c.UpdateWithFilter(ctx, t, updater) + case client.DocKey: + return c.UpdateWithKey(ctx, t, updater) + case []client.DocKey: + return c.UpdateWithKeys(ctx, t, updater) + default: + return nil, client.ErrInvalidUpdateTarget + } +} + +func (c *Collection) updateWith( + ctx context.Context, + args []string, +) (*client.UpdateResult, error) { + data, err := c.cmd.execute(ctx, args) + if err != nil { + return nil, err + } + var res client.UpdateResult + if err := json.Unmarshal(data, &res); err != nil { + return nil, err + } + return &res, nil +} + +func (c *Collection) UpdateWithFilter( + ctx context.Context, + filter any, + updater string, +) (*client.UpdateResult, error) { + args := []string{"client", "collection", "update"} + args = append(args, "--name", c.desc.Name) + args = append(args, "--updater", updater) + + filterJSON, err := json.Marshal(filter) + if err != nil { + return nil, err + } + args = append(args, "--filter", string(filterJSON)) + + return c.updateWith(ctx, args) +} + +func (c *Collection) UpdateWithKey( + ctx context.Context, + key client.DocKey, + updater string, +) (*client.UpdateResult, error) { + args := []string{"client", "collection", "update"} + args = append(args, "--name", c.desc.Name) + args = append(args, "--key", key.String()) + args = append(args, "--updater", updater) + + return c.updateWith(ctx, args) +} + +func (c *Collection) UpdateWithKeys( + ctx context.Context, + docKeys []client.DocKey, + updater string, +) (*client.UpdateResult, error) { + args := []string{"client", "collection", "update"} + args = append(args, "--name", c.desc.Name) + args = append(args, "--updater", updater) + + keys := make([]string, len(docKeys)) + for i, v := range docKeys { + keys[i] = v.String() + } + args = append(args, "--key", strings.Join(keys, ",")) + + return c.updateWith(ctx, args) +} + +func (c *Collection) DeleteWith(ctx context.Context, target any) (*client.DeleteResult, error) { + switch t := target.(type) { + case string, map[string]any, *request.Filter: + return c.DeleteWithFilter(ctx, t) + case client.DocKey: + return c.DeleteWithKey(ctx, t) + case []client.DocKey: + return c.DeleteWithKeys(ctx, t) + default: + return nil, client.ErrInvalidDeleteTarget + } +} + +func (c *Collection) deleteWith( + ctx context.Context, + args []string, +) (*client.DeleteResult, error) { + data, err := c.cmd.execute(ctx, args) + if err != nil { + return nil, err + } + var res client.DeleteResult + if err := json.Unmarshal(data, &res); err != nil { + return nil, err + } + return &res, nil +} + +func (c *Collection) DeleteWithFilter(ctx context.Context, filter any) (*client.DeleteResult, error) { + args := []string{"client", "collection", "delete"} + args = append(args, "--name", c.desc.Name) + + filterJSON, err := json.Marshal(filter) + if err != nil { + return nil, err + } + args = append(args, "--filter", string(filterJSON)) + + return c.deleteWith(ctx, args) +} + +func (c *Collection) DeleteWithKey(ctx context.Context, docKey client.DocKey) (*client.DeleteResult, error) { + args := []string{"client", "collection", "delete"} + args = append(args, "--name", c.desc.Name) + args = append(args, "--key", docKey.String()) + + return c.deleteWith(ctx, args) +} + +func (c *Collection) DeleteWithKeys(ctx context.Context, docKeys []client.DocKey) (*client.DeleteResult, error) { + args := []string{"client", "collection", "delete"} + args = append(args, "--name", c.desc.Name) + + keys := make([]string, len(docKeys)) + for i, v := range docKeys { + keys[i] = v.String() + } + args = append(args, "--key", strings.Join(keys, ",")) + + return c.deleteWith(ctx, args) +} + +func (c *Collection) Get(ctx context.Context, key client.DocKey, showDeleted bool) (*client.Document, error) { + args := []string{"client", "collection", "get"} + args = append(args, "--name", c.desc.Name) + args = append(args, key.String()) + + if showDeleted { + args = append(args, "--show-deleted") + } + + data, err := c.cmd.execute(ctx, args) + if err != nil { + return nil, err + } + var docMap map[string]any + if err := json.Unmarshal(data, &docMap); err != nil { + return nil, err + } + return client.NewDocFromMap(docMap) +} + +func (c *Collection) WithTxn(tx datastore.Txn) client.Collection { + return &Collection{ + cmd: c.cmd.withTxn(tx), + desc: c.desc, + } +} + +func (c *Collection) GetAllDocKeys(ctx context.Context) (<-chan client.DocKeysResult, error) { + args := []string{"client", "collection", "keys"} + args = append(args, "--name", c.desc.Name) + + stdOut, _, err := c.cmd.executeStream(ctx, args) + if err != nil { + return nil, err + } + docKeyCh := make(chan client.DocKeysResult) + + go func() { + dec := json.NewDecoder(stdOut) + defer close(docKeyCh) + + for { + var res http.DocKeyResult + if err := dec.Decode(&res); err != nil { + return + } + key, err := client.NewDocKeyFromString(res.Key) + if err != nil { + return + } + docKey := client.DocKeysResult{ + Key: key, + } + if res.Error != "" { + docKey.Err = fmt.Errorf(res.Error) + } + docKeyCh <- docKey + } + }() + + return docKeyCh, nil +} + +func (c *Collection) CreateIndex( + ctx context.Context, + indexDesc client.IndexDescription, +) (index client.IndexDescription, err error) { + args := []string{"client", "index", "create"} + args = append(args, "--collection", c.desc.Name) + args = append(args, "--name", indexDesc.Name) + + fields := make([]string, len(indexDesc.Fields)) + for i := range indexDesc.Fields { + fields[i] = indexDesc.Fields[i].Name + } + args = append(args, "--fields", strings.Join(fields, ",")) + + data, err := c.cmd.execute(ctx, args) + if err != nil { + return index, err + } + if err := json.Unmarshal(data, &index); err != nil { + return index, err + } + return index, nil +} + +func (c *Collection) DropIndex(ctx context.Context, indexName string) error { + args := []string{"client", "index", "drop"} + args = append(args, "--collection", c.desc.Name) + args = append(args, "--name", indexName) + + _, err := c.cmd.execute(ctx, args) + return err +} + +func (c *Collection) GetIndexes(ctx context.Context) ([]client.IndexDescription, error) { + args := []string{"client", "index", "list"} + args = append(args, "--collection", c.desc.Name) + + data, err := c.cmd.execute(ctx, args) + if err != nil { + return nil, err + } + var indexes []client.IndexDescription + if err := json.Unmarshal(data, &indexes); err != nil { + return nil, err + } + return indexes, nil +} diff --git a/tests/clients/cli/wrapper_lens.go b/tests/clients/cli/wrapper_lens.go new file mode 100644 index 0000000000..679a792662 --- /dev/null +++ b/tests/clients/cli/wrapper_lens.go @@ -0,0 +1,145 @@ +// Copyright 2023 Democratized Data Foundation +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +package cli + +import ( + "context" + "encoding/json" + + "github.com/sourcenetwork/immutable/enumerable" + + "github.com/sourcenetwork/defradb/client" + "github.com/sourcenetwork/defradb/datastore" +) + +var _ client.LensRegistry = (*LensRegistry)(nil) + +type LensRegistry struct { + cmd *cliWrapper +} + +func (w *LensRegistry) WithTxn(tx datastore.Txn) client.LensRegistry { + return &LensRegistry{w.cmd.withTxn(tx)} +} + +func (w *LensRegistry) SetMigration(ctx context.Context, config client.LensConfig) error { + args := []string{"client", "schema", "migration", "set"} + args = append(args, config.SourceSchemaVersionID) + args = append(args, config.DestinationSchemaVersionID) + + lensCfg, err := json.Marshal(config.Lens) + if err != nil { + return err + } + args = append(args, string(lensCfg)) + + _, err = w.cmd.execute(ctx, args) + return err +} + +func (w *LensRegistry) ReloadLenses(ctx context.Context) error { + args := []string{"client", "schema", "migration", "reload"} + + _, err := w.cmd.execute(ctx, args) + return err +} + +func (w *LensRegistry) MigrateUp( + ctx context.Context, + src enumerable.Enumerable[map[string]any], + schemaVersionID string, +) (enumerable.Enumerable[map[string]any], error) { + args := []string{"client", "schema", "migration", "up"} + args = append(args, "--version", schemaVersionID) + + var srcData []map[string]any + err := enumerable.ForEach(src, func(item map[string]any) { + srcData = append(srcData, item) + }) + if err != nil { + return nil, err + } + srcJSON, err := json.Marshal(srcData) + if err != nil { + return nil, err + } + args = append(args, string(srcJSON)) + + data, err := w.cmd.execute(ctx, args) + if err != nil { + return nil, err + } + var out enumerable.Enumerable[map[string]any] + if err := json.Unmarshal(data, &out); err != nil { + return nil, err + } + return out, nil +} + +func (w *LensRegistry) MigrateDown( + ctx context.Context, + src enumerable.Enumerable[map[string]any], + schemaVersionID string, +) (enumerable.Enumerable[map[string]any], error) { + args := []string{"client", "schema", "migration", "down"} + args = append(args, "--version", schemaVersionID) + + var srcData []map[string]any + err := enumerable.ForEach(src, func(item map[string]any) { + srcData = append(srcData, item) + }) + if err != nil { + return nil, err + } + srcJSON, err := json.Marshal(srcData) + if err != nil { + return nil, err + } + args = append(args, string(srcJSON)) + + data, err := w.cmd.execute(ctx, args) + if err != nil { + return nil, err + } + var out enumerable.Enumerable[map[string]any] + if err := json.Unmarshal(data, &out); err != nil { + return nil, err + } + return out, nil +} + +func (w *LensRegistry) Config(ctx context.Context) ([]client.LensConfig, error) { + args := []string{"client", "schema", "migration", "get"} + + data, err := w.cmd.execute(ctx, args) + if err != nil { + return nil, err + } + var cfgs []client.LensConfig + if err := json.Unmarshal(data, &cfgs); err != nil { + return nil, err + } + return cfgs, nil +} + +func (w *LensRegistry) HasMigration(ctx context.Context, schemaVersionID string) (bool, error) { + cfgs, err := w.Config(ctx) + if err != nil { + return false, err + } + found := false + for _, cfg := range cfgs { + if cfg.SourceSchemaVersionID == schemaVersionID { + found = true + } + } + return found, nil +} diff --git a/tests/clients/cli/wrapper_tx.go b/tests/clients/cli/wrapper_tx.go new file mode 100644 index 0000000000..6656c7b058 --- /dev/null +++ b/tests/clients/cli/wrapper_tx.go @@ -0,0 +1,76 @@ +// Copyright 2023 Democratized Data Foundation +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +package cli + +import ( + "context" + "fmt" + + "github.com/sourcenetwork/defradb/datastore" +) + +var _ datastore.Txn = (*Transaction)(nil) + +type Transaction struct { + tx datastore.Txn + cmd *cliWrapper +} + +func (w *Transaction) ID() uint64 { + return w.tx.ID() +} + +func (w *Transaction) Commit(ctx context.Context) error { + args := []string{"client", "tx", "commit"} + args = append(args, fmt.Sprintf("%d", w.tx.ID())) + + _, err := w.cmd.execute(ctx, args) + return err +} + +func (w *Transaction) Discard(ctx context.Context) { + args := []string{"client", "tx", "discard"} + args = append(args, fmt.Sprintf("%d", w.tx.ID())) + + w.cmd.execute(ctx, args) //nolint:errcheck +} + +func (w *Transaction) OnSuccess(fn func()) { + w.tx.OnSuccess(fn) +} + +func (w *Transaction) OnError(fn func()) { + w.tx.OnError(fn) +} + +func (w *Transaction) OnDiscard(fn func()) { + w.tx.OnDiscard(fn) +} + +func (w *Transaction) Rootstore() datastore.DSReaderWriter { + return w.tx.Rootstore() +} + +func (w *Transaction) Datastore() datastore.DSReaderWriter { + return w.tx.Datastore() +} + +func (w *Transaction) Headstore() datastore.DSReaderWriter { + return w.tx.Headstore() +} + +func (w *Transaction) DAGstore() datastore.DAGStore { + return w.tx.DAGstore() +} + +func (w *Transaction) Systemstore() datastore.DSReaderWriter { + return w.tx.Systemstore() +} diff --git a/http/wrapper.go b/tests/clients/http/wrapper.go similarity index 90% rename from http/wrapper.go rename to tests/clients/http/wrapper.go index eb91ffdb7a..10b34129d8 100644 --- a/http/wrapper.go +++ b/tests/clients/http/wrapper.go @@ -12,7 +12,6 @@ package http import ( "context" - "fmt" "net/http/httptest" blockstore "github.com/ipfs/boxo/blockstore" @@ -20,6 +19,7 @@ import ( "github.com/sourcenetwork/defradb/client" "github.com/sourcenetwork/defradb/datastore" "github.com/sourcenetwork/defradb/events" + "github.com/sourcenetwork/defradb/http" ) var _ client.DB = (*Wrapper)(nil) @@ -28,23 +28,23 @@ var _ client.DB = (*Wrapper)(nil) // single struct that implements the client.DB interface. type Wrapper struct { db client.DB - server *Server - client *Client + handler *http.Handler + client *http.Client httpServer *httptest.Server } func NewWrapper(db client.DB) (*Wrapper, error) { - server := NewServer(db) - httpServer := httptest.NewServer(server) + handler := http.NewHandler(db, http.ServerOptions{}) + httpServer := httptest.NewServer(handler) - client, err := NewClient(httpServer.URL) + client, err := http.NewClient(httpServer.URL) if err != nil { return nil, err } return &Wrapper{ db, - server, + handler, client, httpServer, }, nil @@ -131,11 +131,11 @@ func (w *Wrapper) NewTxn(ctx context.Context, readOnly bool) (datastore.Txn, err if err != nil { return nil, err } - server, ok := w.server.txs.Load(client.ID()) - if !ok { - return nil, fmt.Errorf("failed to get server transaction") + server, err := w.handler.Transaction(client.ID()) + if err != nil { + return nil, err } - return &TxWrapper{server.(datastore.Txn), client}, nil + return &TxWrapper{server, client}, nil } func (w *Wrapper) NewConcurrentTxn(ctx context.Context, readOnly bool) (datastore.Txn, error) { @@ -143,11 +143,11 @@ func (w *Wrapper) NewConcurrentTxn(ctx context.Context, readOnly bool) (datastor if err != nil { return nil, err } - server, ok := w.server.txs.Load(client.ID()) - if !ok { - return nil, fmt.Errorf("failed to get server transaction") + server, err := w.handler.Transaction(client.ID()) + if err != nil { + return nil, err } - return &TxWrapper{server.(datastore.Txn), client}, nil + return &TxWrapper{server, client}, nil } func (w *Wrapper) WithTxn(tx datastore.Txn) client.Store { diff --git a/http/wrapper_tx.go b/tests/clients/http/wrapper_tx.go similarity index 100% rename from http/wrapper_tx.go rename to tests/clients/http/wrapper_tx.go diff --git a/tests/integration/cli/client_backup_export_test.go b/tests/integration/cli/client_backup_export_test.go deleted file mode 100644 index 62f2677c7b..0000000000 --- a/tests/integration/cli/client_backup_export_test.go +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright 2023 Democratized Data Foundation -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package clitest - -import ( - "os" - "testing" - - "github.com/stretchr/testify/require" -) - -func createUser(t *testing.T, conf DefraNodeConfig) { - _, _ = runDefraCommand(t, conf, []string{ - "client", "query", `mutation { create_User(data: "{\"name\": \"John\"}") { _key } }`, - }) -} - -func TestBackup_IfNoArgs_ShowUsage(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stdout, _ := runDefraCommand(t, conf, []string{"client", "backup"}) - assertContainsSubstring(t, stdout, "Usage:") -} - -func TestBackupExport_ForAllCollections_ShouldExport(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stopDefra := runDefraNode(t, conf) - - createUserCollection(t, conf) - - createUser(t, conf) - - filepath := t.TempDir() + "/test.json" - - stdout, _ := runDefraCommand(t, conf, []string{ - "client", "backup", "export", filepath, - }) - stopDefra() - - assertContainsSubstring(t, stdout, "success") - - b, err := os.ReadFile(filepath) - require.NoError(t, err) - require.Equal( - t, - `{"User":[{"_key":"bae-decf6467-4c7c-50d7-b09d-0a7097ef6bad","_newKey":"bae-decf6467-4c7c-50d7-b09d-0a7097ef6bad","name":"John"}]}`, - string(b), - ) -} - -func TestBackupExport_ForUserCollection_ShouldExport(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stopDefra := runDefraNode(t, conf) - - createUserCollection(t, conf) - - createUser(t, conf) - - filepath := t.TempDir() + "/test.json" - - stdout, _ := runDefraCommand(t, conf, []string{ - "client", "backup", "export", filepath, "--collections", "User", - }) - stopDefra() - - assertContainsSubstring(t, stdout, "success") - - b, err := os.ReadFile(filepath) - require.NoError(t, err) - require.Equal( - t, - `{"User":[{"_key":"bae-decf6467-4c7c-50d7-b09d-0a7097ef6bad","_newKey":"bae-decf6467-4c7c-50d7-b09d-0a7097ef6bad","name":"John"}]}`, - string(b), - ) -} - -func TestBackupExport_ForInvalidCollection_ShouldFail(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stopDefra := runDefraNode(t, conf) - - createUserCollection(t, conf) - - createUser(t, conf) - - filepath := t.TempDir() + "/test.json" - - stdout, _ := runDefraCommand(t, conf, []string{ - "client", "backup", "export", filepath, "--collections", "Invalid", - }) - stopDefra() - - assertContainsSubstring(t, stdout, "collection does not exist") -} - -func TestBackupExport_InvalidFilePath_ShouldFail(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stopDefra := runDefraNode(t, conf) - - createUserCollection(t, conf) - - createUser(t, conf) - - filepath := t.TempDir() + "/some/test.json" - - stdout, _ := runDefraCommand(t, conf, []string{ - "client", "backup", "export", filepath, "--collections", "Invalid", - }) - stopDefra() - - assertContainsSubstring(t, stdout, "invalid file path") -} diff --git a/tests/integration/cli/client_backup_import_test.go b/tests/integration/cli/client_backup_import_test.go deleted file mode 100644 index 8290dbe6de..0000000000 --- a/tests/integration/cli/client_backup_import_test.go +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright 2023 Democratized Data Foundation -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package clitest - -import ( - "os" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestBackupImport_WithValidFile_ShouldImport(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stopDefra := runDefraNode(t, conf) - - createUserCollection(t, conf) - - filepath := t.TempDir() + "/test.json" - - err := os.WriteFile( - filepath, - []byte(`{"User":[{"_key":"bae-decf6467-4c7c-50d7-b09d-0a7097ef6bad","_newKey":"bae-decf6467-4c7c-50d7-b09d-0a7097ef6bad","name":"John"}]}`), - 0644, - ) - require.NoError(t, err) - - stdout, _ := runDefraCommand(t, conf, []string{ - "client", "backup", "import", filepath, - }) - stopDefra() - - assertContainsSubstring(t, stdout, "success") -} - -func TestBackupImport_WithExistingDoc_ShouldFail(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stopDefra := runDefraNode(t, conf) - - createUserCollection(t, conf) - - createUser(t, conf) - - filepath := t.TempDir() + "/test.json" - - err := os.WriteFile( - filepath, - []byte(`{"User":[{"_key":"bae-decf6467-4c7c-50d7-b09d-0a7097ef6bad","_newKey":"bae-decf6467-4c7c-50d7-b09d-0a7097ef6bad","name":"John"}]}`), - 0644, - ) - require.NoError(t, err) - - stdout, _ := runDefraCommand(t, conf, []string{ - "client", "backup", "import", filepath, - }) - stopDefra() - - assertContainsSubstring(t, stdout, "a document with the given dockey already exists") -} - -func TestBackupImport_ForInvalidCollection_ShouldFail(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stopDefra := runDefraNode(t, conf) - - createUserCollection(t, conf) - - createUser(t, conf) - - filepath := t.TempDir() + "/test.json" - - err := os.WriteFile( - filepath, - []byte(`{"Invalid":[{"_key":"bae-decf6467-4c7c-50d7-b09d-0a7097ef6bad","_newKey":"bae-decf6467-4c7c-50d7-b09d-0a7097ef6bad","name":"John"}]}`), - 0644, - ) - require.NoError(t, err) - - stdout, _ := runDefraCommand(t, conf, []string{ - "client", "backup", "import", filepath, - }) - stopDefra() - - assertContainsSubstring(t, stdout, "failed to get collection: datastore: key not found. Name: Invalid") -} - -func TestBackupImport_InvalidFilePath_ShouldFail(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stopDefra := runDefraNode(t, conf) - - createUserCollection(t, conf) - - createUser(t, conf) - - filepath := t.TempDir() + "/some/test.json" - - stdout, _ := runDefraCommand(t, conf, []string{ - "client", "backup", "import", filepath, - }) - stopDefra() - - assertContainsSubstring(t, stdout, "invalid file path") -} diff --git a/tests/integration/cli/client_blocks_test.go b/tests/integration/cli/client_blocks_test.go deleted file mode 100644 index 08d1c22684..0000000000 --- a/tests/integration/cli/client_blocks_test.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2022 Democratized Data Foundation -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package clitest - -import "testing" - -func TestClientBlocksEmpty(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stdout, _ := runDefraCommand(t, conf, []string{"client", "blocks"}) - assertContainsSubstring(t, stdout, "Usage:") -} - -func TestClientBlocksGetEmpty(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stdout, _ := runDefraCommand(t, conf, []string{"client", "blocks", "get"}) - assertContainsSubstring(t, stdout, "Usage:") -} - -func TestClientBlocksGetInvalidCID(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stopDefra := runDefraNode(t, conf) - stdout, _ := runDefraCommand(t, conf, []string{"client", "blocks", "get", "invalid-cid"}) - _ = stopDefra() - assertContainsSubstring(t, stdout, "\"errors\"") -} - -func TestClientBlocksGetNonExistentCID(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stopDefra := runDefraNode(t, conf) - stdout, _ := runDefraCommand(t, conf, []string{"client", "blocks", "get", "bafybeieelb43ol5e5jiick2p7k4p577ph72ecwcuowlhbops4hpz24zhz4"}) - _ = stopDefra() - assertContainsSubstring(t, stdout, "could not find") -} diff --git a/tests/integration/cli/client_index_create_test.go b/tests/integration/cli/client_index_create_test.go deleted file mode 100644 index 89d6a4a18a..0000000000 --- a/tests/integration/cli/client_index_create_test.go +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright 2023 Democratized Data Foundation -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package clitest - -import ( - "testing" -) - -func createUserCollection(t *testing.T, conf DefraNodeConfig) { - createCollection(t, conf, `type User { name: String }`) -} - -func createCollection(t *testing.T, conf DefraNodeConfig, colSchema string) { - fileName := schemaFileFixture(t, "schema.graphql", colSchema) - stdout, _ := runDefraCommand(t, conf, []string{"client", "schema", "add", "-f", fileName}) - assertContainsSubstring(t, stdout, "success") -} - -func TestIndex_IfNoArgs_ShowUsage(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stdout, _ := runDefraCommand(t, conf, []string{"client", "index"}) - assertContainsSubstring(t, stdout, "Usage:") -} - -func TestIndexCreate_IfNoArgs_ShowUsage(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - _, stderr := runDefraCommand(t, conf, []string{"client", "index", "create"}) - assertContainsSubstring(t, stderr, "Usage") -} - -func TestIndexCreate_IfNoFieldsArg_ShouldFail(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stopDefra := runDefraNode(t, conf) - - createUserCollection(t, conf) - - _, stderr := runDefraCommand(t, conf, []string{ - "client", "index", "create", - "--collection", "User", - }) - stopDefra() - - assertContainsSubstring(t, stderr, "missing argument") -} - -func TestIndexCreate_IfNoCollectionArg_ShouldFail(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stopDefra := runDefraNode(t, conf) - - createUserCollection(t, conf) - - _, stderr := runDefraCommand(t, conf, []string{ - "client", "index", "create", - "--fields", "Name", - }) - stopDefra() - - assertContainsSubstring(t, stderr, "missing argument") -} - -func TestIndexCreate_IfCollectionExists_ShouldCreateIndex(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stopDefra := runDefraNode(t, conf) - - createUserCollection(t, conf) - - stdout, _ := runDefraCommand(t, conf, []string{ - "client", "index", "create", - "--collection", "User", - "--fields", "name", - "--name", "users_name_index", - }) - nodeLog := stopDefra() - - jsonResponse := `{"data":{"index":{"Name":"users_name_index","ID":1,"Fields":[{"Name":"name","Direction":"ASC"}]}}}` - assertContainsSubstring(t, stdout, jsonResponse) - assertNotContainsSubstring(t, stdout, "errors") - assertNotContainsSubstring(t, nodeLog, "errors") -} - -func TestIndexCreate_IfInternalError_ShouldFail(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stopDefra := runDefraNode(t, conf) - - stdout, _ := runDefraCommand(t, conf, []string{ - "client", "index", "create", - "--collection", "User", - "--fields", "Name", - "--name", "users_name_index", - }) - stopDefra() - - assertContainsSubstring(t, stdout, "errors") -} diff --git a/tests/integration/cli/client_index_drop_test.go b/tests/integration/cli/client_index_drop_test.go deleted file mode 100644 index ce03e29524..0000000000 --- a/tests/integration/cli/client_index_drop_test.go +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright 2023 Democratized Data Foundation -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package clitest - -import ( - "testing" -) - -func TestIndexDrop_IfNoArgs_ShowUsage(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - _, stderr := runDefraCommand(t, conf, []string{"client", "index", "drop"}) - assertContainsSubstring(t, stderr, "Usage") -} - -const userColIndexOnNameFieldName = "users_name_index" - -func createIndexOnName(t *testing.T, conf DefraNodeConfig) { - createIndexOnField(t, conf, "User", "name", userColIndexOnNameFieldName) -} - -func createIndexOnField(t *testing.T, conf DefraNodeConfig, colName, fieldName, indexName string) { - runDefraCommand(t, conf, []string{ - "client", "index", "create", - "--collection", colName, - "--fields", fieldName, - "--name", indexName, - }) -} - -func TestIndexDrop_IfNoNameArg_ShouldFail(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stopDefra := runDefraNode(t, conf) - - createUserCollection(t, conf) - createIndexOnName(t, conf) - - _, stderr := runDefraCommand(t, conf, []string{ - "client", "index", "drop", - "--collection", "User", - }) - stopDefra() - - assertContainsSubstring(t, stderr, "missing argument") -} - -func TestIndexDrop_IfNoCollectionArg_ShouldFail(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stopDefra := runDefraNode(t, conf) - - createUserCollection(t, conf) - createIndexOnName(t, conf) - - _, stderr := runDefraCommand(t, conf, []string{ - "client", "index", "drop", - "--name", "users_name_index", - }) - stopDefra() - - assertContainsSubstring(t, stderr, "missing argument") -} - -func TestIndexDrop_IfCollectionWithIndexExists_ShouldDropIndex(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stopDefra := runDefraNode(t, conf) - - createUserCollection(t, conf) - createIndexOnName(t, conf) - - stdout, _ := runDefraCommand(t, conf, []string{ - "client", "index", "drop", - "--collection", "User", - "--name", "users_name_index", - }) - nodeLog := stopDefra() - - jsonResponse := `{"data":{"result":"success"}}` - assertContainsSubstring(t, stdout, jsonResponse) - assertNotContainsSubstring(t, stdout, "errors") - assertNotContainsSubstring(t, nodeLog, "errors") -} - -func TestIndexDrop_IfCollectionDoesNotExist_ShouldFail(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stopDefra := runDefraNode(t, conf) - - stdout, _ := runDefraCommand(t, conf, []string{ - "client", "index", "drop", - "--collection", "User", - "--name", "users_name_index", - }) - stopDefra() - - assertContainsSubstring(t, stdout, "errors") -} - -func TestIndexDrop_IfInternalError_ShouldFail(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stopDefra := runDefraNode(t, conf) - - createUserCollection(t, conf) - - stdout, _ := runDefraCommand(t, conf, []string{ - "client", "index", "drop", - "--collection", "User", - "--name", "users_name_index", - }) - stopDefra() - - assertContainsSubstring(t, stdout, "errors") -} diff --git a/tests/integration/cli/client_index_list_test.go b/tests/integration/cli/client_index_list_test.go deleted file mode 100644 index cb2f7d5fac..0000000000 --- a/tests/integration/cli/client_index_list_test.go +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright 2023 Democratized Data Foundation -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package clitest - -import ( - "encoding/json" - "testing" - - "github.com/sourcenetwork/defradb/client" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestIndexList_IfCollectionIsNotSpecified_ShouldReturnAllIndexes(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stopDefra := runDefraNode(t, conf) - - createCollection(t, conf, `type User { name: String }`) - createCollection(t, conf, `type Product { name: String price: Int }`) - createIndexOnField(t, conf, "User", "name", "") - createIndexOnField(t, conf, "Product", "name", "") - createIndexOnField(t, conf, "Product", "price", "") - - stdout, _ := runDefraCommand(t, conf, []string{"client", "index", "list"}) - nodeLog := stopDefra() - - var resp struct { - Data struct { - Collections map[string][]client.IndexDescription `json:"collections"` - } `json:"data"` - } - err := json.Unmarshal([]byte(stdout[0]), &resp) - require.NoError(t, err) - - assert.Equal(t, len(resp.Data.Collections), 2) - assert.Equal(t, len(resp.Data.Collections["User"]), 1) - assert.Equal(t, len(resp.Data.Collections["Product"]), 2) - - assertNotContainsSubstring(t, stdout, "errors") - assertNotContainsSubstring(t, nodeLog, "errors") -} - -func TestIndexList_IfCollectionIsSpecified_ShouldReturnCollectionsIndexes(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stopDefra := runDefraNode(t, conf) - - createUserCollection(t, conf) - createIndexOnName(t, conf) - - createCollection(t, conf, `type Product { name: String price: Int }`) - createIndexOnField(t, conf, "Product", "name", "") - createIndexOnField(t, conf, "Product", "price", "") - - stdout, _ := runDefraCommand(t, conf, []string{ - "client", "index", "list", - "--collection", "User", - }) - nodeLog := stopDefra() - - var resp struct { - Data struct { - Indexes []client.IndexDescription `json:"indexes"` - } `json:"data"` - } - err := json.Unmarshal([]byte(stdout[0]), &resp) - require.NoError(t, err) - - expectedDesc := client.IndexDescription{Name: userColIndexOnNameFieldName, ID: 1, Fields: []client.IndexedFieldDescription{{Name: "name", Direction: client.Ascending}}} - assert.Equal(t, 1, len(resp.Data.Indexes)) - assert.Equal(t, expectedDesc, resp.Data.Indexes[0]) - - assertNotContainsSubstring(t, stdout, "errors") - assertNotContainsSubstring(t, nodeLog, "errors") -} - -func TestIndexList_IfInternalError_ShouldFail(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stopDefra := runDefraNode(t, conf) - - stdout, _ := runDefraCommand(t, conf, []string{ - "client", "index", "list", - "--collection", "User", - }) - stopDefra() - - assertContainsSubstring(t, stdout, "errors") -} diff --git a/tests/integration/cli/client_peerid_test.go b/tests/integration/cli/client_peerid_test.go deleted file mode 100644 index 0592fd4aa1..0000000000 --- a/tests/integration/cli/client_peerid_test.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2022 Democratized Data Foundation -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package clitest - -import ( - "testing" -) - -func TestPeerID(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stopDefra := runDefraNode(t, conf) - - stdout, _ := runDefraCommand(t, conf, []string{"client", "peerid"}) - - defraLogLines := stopDefra() - - assertNotContainsSubstring(t, defraLogLines, "ERROR") - - assertContainsSubstring(t, stdout, "peerID") -} - -func TestPeerIDWithNoHost(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - _, stderr := runDefraCommand(t, conf, []string{"client", "peerid"}) - assertContainsSubstring(t, stderr, "failed to request PeerID") -} diff --git a/tests/integration/cli/client_ping_test.go b/tests/integration/cli/client_ping_test.go deleted file mode 100644 index a4e1eef96f..0000000000 --- a/tests/integration/cli/client_ping_test.go +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2022 Democratized Data Foundation -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package clitest - -import ( - "fmt" - "strings" - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/sourcenetwork/defradb/config" -) - -func TestPingSimple(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stopDefra := runDefraNode(t, conf) - - stdout, _ := runDefraCommand(t, conf, []string{"client", "ping"}) - - nodeLog := stopDefra() - - assert.Contains(t, stdout, `{"data":{"response":"pong"}}`) - for _, line := range nodeLog { - assert.NotContains(t, line, "ERROR") - } -} - -func TestPingCommandToInvalidHost(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stopDefra := runDefraNode(t, conf) - _, stderr := runDefraCommand(t, conf, []string{"client", "ping", "--url", "'1!2:3!4'"}) - - nodeLog := stopDefra() - - for _, line := range nodeLog { - assert.NotContains(t, line, "ERROR") - } - // for some line in stderr to contain the error message - for _, line := range stderr { - if strings.Contains(line, config.ErrFailedToValidateConfig.Error()) { - return - } - } - t.Error("expected error message not found in stderr") -} - -func TestPingCommandNoHost(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - p, err := findFreePortInRange(t, 49152, 65535) - assert.NoError(t, err) - addr := fmt.Sprintf("localhost:%d", p) - _, stderr := runDefraCommand(t, conf, []string{"client", "ping", "--url", addr}) - assertContainsSubstring(t, stderr, "failed to send ping") -} diff --git a/tests/integration/cli/client_query_test.go b/tests/integration/cli/client_query_test.go deleted file mode 100644 index 6ca98cbade..0000000000 --- a/tests/integration/cli/client_query_test.go +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright 2022 Democratized Data Foundation -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package clitest - -import ( - "testing" -) - -func TestRequestSimple(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stopDefra := runDefraNode(t, conf) - - stdout, _ := runDefraCommand(t, conf, []string{"client", "query", - "query IntrospectionQuery {__schema {queryType { name }}}", - }) - nodeLog := stopDefra() - - assertContainsSubstring(t, stdout, "Query") - assertNotContainsSubstring(t, nodeLog, "ERROR") -} - -func TestRequestInvalidQuery(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stopDefra := runDefraNode(t, conf) - - stdout, _ := runDefraCommand(t, conf, []string{"client", "query", "{}}"}) - _ = stopDefra() - - assertContainsSubstring(t, stdout, "Syntax Error") -} - -func TestRequestWithErrorNoType(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stopDefra := runDefraNode(t, conf) - defer stopDefra() - - stdout, _ := runDefraCommand(t, conf, []string{"client", "query", "query { User { whatever } }"}) - - assertContainsSubstring(t, stdout, "Cannot query field") -} - -func TestRequestWithErrorNoField(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stopDefra := runDefraNode(t, conf) - defer stopDefra() - - fname := schemaFileFixture(t, "schema.graphql", ` - type User { - id: ID - name: String - }`) - stdout, _ := runDefraCommand(t, conf, []string{"client", "schema", "add", "-f", fname}) - assertContainsSubstring(t, stdout, "success") - - stdout, _ = runDefraCommand(t, conf, []string{"client", "query", "query { User { nonexistent } }"}) - - assertContainsSubstring(t, stdout, `Cannot query field \"nonexistent\"`) -} - -func TestRequestQueryFromFile(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stopDefra := runDefraNode(t, conf) - defer stopDefra() - - fname := schemaFileFixture(t, "schema.graphql", ` - type User123 { - XYZ: String - }`) - stdout, _ := runDefraCommand(t, conf, []string{"client", "schema", "add", "-f", fname}) - assertContainsSubstring(t, stdout, "success") - - fname = schemaFileFixture(t, "query.graphql", ` - query { - __schema { - types { - name - fields { - name - type { - name - kind - } - } - } - } - }`) - stdout, _ = runDefraCommand(t, conf, []string{"client", "query", "-f", fname}) - - assertContainsSubstring(t, stdout, "Query") - - // Check that the User type is correctly returned - assertContainsSubstring(t, stdout, "User123") - assertContainsSubstring(t, stdout, "XYZ") -} diff --git a/tests/integration/cli/client_rpc_p2p_collection_test.go b/tests/integration/cli/client_rpc_p2p_collection_test.go deleted file mode 100644 index b44abcaefb..0000000000 --- a/tests/integration/cli/client_rpc_p2p_collection_test.go +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2022 Democratized Data Foundation -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package clitest - -// TBD diff --git a/tests/integration/cli/client_rpc_replicator_test.go b/tests/integration/cli/client_rpc_replicator_test.go deleted file mode 100644 index 1fd0e3c351..0000000000 --- a/tests/integration/cli/client_rpc_replicator_test.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2022 Democratized Data Foundation -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package clitest - -import ( - "fmt" - "testing" -) - -func TestReplicatorGetAllEmpty(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - portTCP, err := findFreePortInRange(t, 49152, 65535) - if err != nil { - t.Fatal(err) - } - conf.GRPCAddr = fmt.Sprintf("/ip4/0.0.0.0/tcp/%d", portTCP) - if err != nil { - t.Fatal(err) - } - - stopDefra := runDefraNode(t, conf) - defer stopDefra() - - tcpAddr := fmt.Sprintf("localhost:%d", portTCP) - _, stderr := runDefraCommand(t, conf, []string{"client", "--addr", tcpAddr, "rpc", "replicator", "getall"}) - assertContainsSubstring(t, stderr, "No replicator found") -} diff --git a/tests/integration/cli/client_schema_add_test.go b/tests/integration/cli/client_schema_add_test.go deleted file mode 100644 index 124fcba82a..0000000000 --- a/tests/integration/cli/client_schema_add_test.go +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2022 Democratized Data Foundation -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package clitest - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestAddSchemaFromFile(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stopDefra := runDefraNode(t, conf) - - fname := schemaFileFixture(t, "schema.graphql", ` - type User { - id: ID - name: String - }`) - - stdout, _ := runDefraCommand(t, conf, []string{"client", "schema", "add", "-f", fname}) - - nodeLog := stopDefra() - - jsonReponse := `{"data":{"collections":[{"name":"User","id":"bafkreifxwnqwcg3uqqr3iydebnmeadmjxg722qauocdtjbusinjtzja7py","version_id":"bafkreifxwnqwcg3uqqr3iydebnmeadmjxg722qauocdtjbusinjtzja7py"}],"result":"success"}}` - assert.Contains(t, stdout, jsonReponse) - assertNotContainsSubstring(t, nodeLog, "ERROR") -} - -func TestAddSchemaWithDuplicateType(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stopDefra := runDefraNode(t, conf) - - fname1 := schemaFileFixture(t, "schema1.graphql", `type Post { id: ID title: String }`) - fname2 := schemaFileFixture(t, "schema2.graphql", `type Post { id: ID author: String }`) - - stdout1, _ := runDefraCommand(t, conf, []string{"client", "schema", "add", "-f", fname1}) - stdout2, _ := runDefraCommand(t, conf, []string{"client", "schema", "add", "-f", fname2}) - - _ = stopDefra() - - jsonReponse := `{"data":{"collections":[{"name":"Post","id":"bafkreibamgkyo3juvgx2b3ice4tjldcuxiibwo32kq22vfuyvzzgg7kfga","version_id":"bafkreibamgkyo3juvgx2b3ice4tjldcuxiibwo32kq22vfuyvzzgg7kfga"}],"result":"success"}}` - assertContainsSubstring(t, stdout1, jsonReponse) - assertContainsSubstring(t, stdout2, `schema type already exists. Name: Post`) -} diff --git a/tests/integration/cli/client_schema_migration_get_test.go b/tests/integration/cli/client_schema_migration_get_test.go deleted file mode 100644 index dd70879433..0000000000 --- a/tests/integration/cli/client_schema_migration_get_test.go +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright 2023 Democratized Data Foundation -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package clitest - -import ( - "fmt" - "testing" - - "github.com/sourcenetwork/defradb/tests/lenses" -) - -func TestSchemaMigrationGet_GivenOneArg_ShouldReturnError(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stopDefra := runDefraNode(t, conf) - - _, stderr := runDefraCommand(t, conf, []string{ - "client", "schema", "migration", "get", - "notAnArg", - }) - _ = stopDefra() - - assertContainsSubstring(t, stderr, "too many arguments. Max: 0, Actual: 1") -} - -func TestSchemaMigrationGet_GivenNoMigrations_ShouldSucceed(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stopDefra := runDefraNode(t, conf) - - stdout, _ := runDefraCommand(t, conf, []string{ - "client", "schema", "migration", "get", - }) - _ = stopDefra() - - assertContainsSubstring(t, stdout, `{"data":{"configuration":[]}}`) -} - -func TestSchemaMigrationGet_GivenEmptyMigrationObj_ShouldSucceed(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stopDefra := runDefraNode(t, conf) - - stdout, _ := runDefraCommand(t, conf, []string{ - "client", "schema", "migration", "set", - "bae123", "bae456", "{}", - }) - assertContainsSubstring(t, stdout, "success") - - stdout, _ = runDefraCommand(t, conf, []string{ - "client", "schema", "migration", "get", - }) - _ = stopDefra() - - assertContainsSubstring(t, stdout, - `{"data":{"configuration":[{"SourceSchemaVersionID":"bae123","DestinationSchemaVersionID":"bae456","Lenses":null}]}}`, - ) -} - -func TestSchemaMigrationGet_GivenEmptyMigration_ShouldSucceed(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stopDefra := runDefraNode(t, conf) - - stdout, _ := runDefraCommand(t, conf, []string{ - "client", "schema", "migration", "set", - "bae123", "bae456", `{"lenses": []}`, - }) - assertContainsSubstring(t, stdout, "success") - - stdout, _ = runDefraCommand(t, conf, []string{ - "client", "schema", "migration", "get", - }) - _ = stopDefra() - - assertContainsSubstring(t, stdout, - `{"data":{"configuration":[{"SourceSchemaVersionID":"bae123","DestinationSchemaVersionID":"bae456","Lenses":[]}]}}`, - ) -} - -func TestSchemaMigrationGet_GivenMigration_ShouldSucceed(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stopDefra := runDefraNode(t, conf) - - stdout, _ := runDefraCommand(t, conf, []string{ - "client", "schema", "migration", "set", - "bae123", "bae456", - fmt.Sprintf(`{"lenses": [{"path":"%s","arguments":{"dst":"verified","value":true}}]}`, lenses.SetDefaultModulePath), - }) - assertContainsSubstring(t, stdout, "success") - - stdout, _ = runDefraCommand(t, conf, []string{ - "client", "schema", "migration", "get", - }) - _ = stopDefra() - - assertContainsSubstring(t, stdout, - `{"data":{"configuration":[{"SourceSchemaVersionID":"bae123","DestinationSchemaVersionID":"bae456","Lenses":[`+ - fmt.Sprintf( - `{"Path":"%s",`, - lenses.SetDefaultModulePath, - )+ - `"Inverse":false,"Arguments":{"dst":"verified","value":true}}`+ - `]}]}}`, - ) -} diff --git a/tests/integration/cli/client_schema_migration_set_test.go b/tests/integration/cli/client_schema_migration_set_test.go deleted file mode 100644 index b9c0c5009f..0000000000 --- a/tests/integration/cli/client_schema_migration_set_test.go +++ /dev/null @@ -1,244 +0,0 @@ -// Copyright 2023 Democratized Data Foundation -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package clitest - -import ( - "fmt" - "testing" - - "github.com/sourcenetwork/defradb/tests/lenses" -) - -func TestSchemaMigrationSet_GivenEmptyArgs_ShouldReturnError(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stopDefra := runDefraNode(t, conf) - - _, stderr := runDefraCommand(t, conf, []string{"client", "schema", "migration", "set"}) - _ = stopDefra() - - assertContainsSubstring(t, stderr, "missing arguments. Required: src, dst, cfg") -} - -func TestSchemaMigrationSet_GivenOneArg_ShouldReturnError(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stopDefra := runDefraNode(t, conf) - - _, stderr := runDefraCommand(t, conf, []string{ - "client", "schema", "migration", "set", - "bae123", - }) - _ = stopDefra() - - assertContainsSubstring(t, stderr, "missing arguments. Required: src, dst, cfg") -} - -func TestSchemaMigrationSet_GivenTwoArgs_ShouldReturnError(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stopDefra := runDefraNode(t, conf) - - _, stderr := runDefraCommand(t, conf, []string{ - "client", "schema", "migration", "set", - "bae123", "bae456", - }) - _ = stopDefra() - - assertContainsSubstring(t, stderr, "missing argument. Name: cfg") -} - -func TestSchemaMigrationSet_GivenFourArgs_ShouldReturnError(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stopDefra := runDefraNode(t, conf) - - _, stderr := runDefraCommand(t, conf, []string{ - "client", "schema", "migration", "set", - "bae123", "bae456", "cfg", "extraArg", - }) - _ = stopDefra() - - assertContainsSubstring(t, stderr, "too many arguments. Max: 3, Actual: 4") -} - -func TestSchemaMigrationSet_GivenEmptySrcArg_ShouldReturnError(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stopDefra := runDefraNode(t, conf) - - _, stderr := runDefraCommand(t, conf, []string{ - "client", "schema", "migration", "set", - "", "bae", "path", - }) - _ = stopDefra() - - assertContainsSubstring(t, stderr, "missing argument. Name: src") -} - -func TestSchemaMigrationSet_GivenEmptyDstArg_ShouldReturnError(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stopDefra := runDefraNode(t, conf) - - _, stderr := runDefraCommand(t, conf, []string{ - "client", "schema", "migration", "set", - "bae", "", "path", - }) - _ = stopDefra() - - assertContainsSubstring(t, stderr, "missing argument. Name: dst") -} - -func TestSchemaMigrationSet_GivenEmptyCfgArg_ShouldReturnError(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stopDefra := runDefraNode(t, conf) - - _, stderr := runDefraCommand(t, conf, []string{ - "client", "schema", "migration", "set", - "bae123", "bae456", "", - }) - _ = stopDefra() - - assertContainsSubstring(t, stderr, "missing argument. Name: cfg") -} - -func TestSchemaMigrationSet_GivenInvalidCfgJsonObject_ShouldError(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stopDefra := runDefraNode(t, conf) - - _, stderr := runDefraCommand(t, conf, []string{ - "client", "schema", "migration", "set", - "bae123", "bae456", "{--notvalidjson", - }) - _ = stopDefra() - - assertContainsSubstring(t, stderr, "invalid lens configuration: invalid character") -} - -func TestSchemaMigrationSet_GivenEmptyCfgObject_ShouldSucceed(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stopDefra := runDefraNode(t, conf) - - stdout, _ := runDefraCommand(t, conf, []string{ - "client", "schema", "migration", "set", - "bae123", "bae456", "{}", - }) - _ = stopDefra() - - assertContainsSubstring(t, stdout, "success") -} - -func TestSchemaMigrationSet_GivenCfgWithNoLenses_ShouldSucceed(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stopDefra := runDefraNode(t, conf) - - stdout, _ := runDefraCommand(t, conf, []string{ - "client", "schema", "migration", "set", - "bae123", "bae456", `{"lenses": []}`, - }) - _ = stopDefra() - - assertContainsSubstring(t, stdout, "success") -} - -func TestSchemaMigrationSet_GivenCfgWithNoLensesUppercase_ShouldSucceed(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stopDefra := runDefraNode(t, conf) - - stdout, _ := runDefraCommand(t, conf, []string{ - "client", "schema", "migration", "set", - "bae123", "bae456", `{"Lenses": []}`, - }) - _ = stopDefra() - - assertContainsSubstring(t, stdout, "success") -} - -func TestSchemaMigrationSet_GivenCfgWithUnknownProp_ShouldError(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stopDefra := runDefraNode(t, conf) - - _, stderr := runDefraCommand(t, conf, []string{ - "client", "schema", "migration", "set", - "bae123", "bae456", `{"NotAProp": []}`, - }) - _ = stopDefra() - - assertContainsSubstring(t, stderr, "invalid lens configuration: json: unknown field") -} - -func TestSchemaMigrationSet_GivenCfgWithUnknownPath_ShouldError(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stopDefra := runDefraNode(t, conf) - - _, stderr := runDefraCommand(t, conf, []string{ - "client", "schema", "migration", "set", - "bae123", "bae456", `{"Lenses": [{"path":"notAPath"}]}`, - }) - _ = stopDefra() - - assertContainsSubstring(t, stderr, "no such file or directory") -} - -func TestSchemaMigrationSet_GivenCfgWithLenses_ShouldSucceedAndMigrateDoc(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stopDefra := runDefraNode(t, conf) - - stdout, _ := runDefraCommand(t, conf, []string{"client", "schema", "add", `type Users { name: String }`}) - assertContainsSubstring(t, stdout, "success") - - stdout, _ = runDefraCommand(t, conf, []string{"client", "query", `mutation { create_Users(data:"{\"name\":\"John\"}") { name } }`}) - assertContainsSubstring(t, stdout, `{"data":[{"name":"John"}]}`) - - stdout, _ = runDefraCommand(t, conf, []string{"client", "schema", "patch", - `[{ "op": "add", "path": "/Users/Schema/Fields/-", "value": {"Name": "verified", "Kind": "Boolean"} }]`, - }) - assertContainsSubstring(t, stdout, "success") - - stdout, _ = runDefraCommand(t, conf, []string{ - "client", "schema", "migration", "set", - "bafkreibqw2l325up2tljc5oyjpjzftg4x7nhluzqoezrmz645jto6tnylu", - "bafkreihcyy243ed46jxlpwyryo3cfcvxcbnilpj63gy7smf4fqzyzxadze", - fmt.Sprintf(`{"lenses": [{"path":"%s","arguments":{"dst":"verified","value":true}}]}`, lenses.SetDefaultModulePath), - }) - assertContainsSubstring(t, stdout, "success") - - stdout, _ = runDefraCommand(t, conf, []string{"client", "query", "query { Users { name verified } }"}) - _ = stopDefra() - - assertContainsSubstring(t, stdout, `{"data":[{"name":"John","verified":true}]}`) -} - -func TestSchemaMigrationSet_GivenCfgWithLenseError_ShouldError(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stopDefra := runDefraNode(t, conf) - - stdout, _ := runDefraCommand(t, conf, []string{"client", "schema", "add", `type Users { name: String }`}) - assertContainsSubstring(t, stdout, "success") - - stdout, _ = runDefraCommand(t, conf, []string{"client", "query", `mutation { create_Users(data:"{\"name\":\"John\"}") { name } }`}) - assertContainsSubstring(t, stdout, `{"data":[{"name":"John"}]}`) - - stdout, _ = runDefraCommand(t, conf, []string{"client", "schema", "patch", - `[{ "op": "add", "path": "/Users/Schema/Fields/-", "value": {"Name": "verified", "Kind": "Boolean"} }]`, - }) - assertContainsSubstring(t, stdout, "success") - - stdout, _ = runDefraCommand(t, conf, []string{ - "client", "schema", "migration", "set", - "bafkreibqw2l325up2tljc5oyjpjzftg4x7nhluzqoezrmz645jto6tnylu", - "bafkreihcyy243ed46jxlpwyryo3cfcvxcbnilpj63gy7smf4fqzyzxadze", - // Do not set lens parameters in order to generate error - fmt.Sprintf(`{"lenses": [{"path":"%s"}]}`, lenses.SetDefaultModulePath), - }) - assertContainsSubstring(t, stdout, "success") - - stdout, _ = runDefraCommand(t, conf, []string{"client", "query", "query { Users { name verified } }"}) - _ = stopDefra() - - // Error generated from within lens module lazily executing within the query - assertContainsSubstring(t, stdout, "Parameters have not been set.") -} diff --git a/tests/integration/cli/client_schema_patch_test.go b/tests/integration/cli/client_schema_patch_test.go deleted file mode 100644 index 487dc9eda5..0000000000 --- a/tests/integration/cli/client_schema_patch_test.go +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2022 Democratized Data Foundation -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package clitest - -import ( - "testing" -) - -func TestClientSchemaPatch(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stopDefra := runDefraNode(t, conf) - defer stopDefra() - - fname := schemaFileFixture(t, "schema.graphql", ` - type User { - id: ID - name: String - }`) - stdout, _ := runDefraCommand(t, conf, []string{"client", "schema", "add", "-f", fname}) - assertContainsSubstring(t, stdout, "success") - - stdout, _ = runDefraCommand(t, conf, []string{"client", "schema", "patch", `[{ "op": "add", "path": "/User/Schema/Fields/-", "value": {"Name": "address", "Kind": "String"} }]`}) - assertContainsSubstring(t, stdout, "success") - - stdout, _ = runDefraCommand(t, conf, []string{"client", "query", `query IntrospectionQuery { __type (name: "User") { fields { name } }}`}) - assertContainsSubstring(t, stdout, "address") -} - -func TestClientSchemaPatch_InvalidJSONPatch(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stopDefra := runDefraNode(t, conf) - defer stopDefra() - - fname := schemaFileFixture(t, "schema.graphql", ` - type User { - id: ID - name: String - } - `) - stdout, _ := runDefraCommand(t, conf, []string{"client", "schema", "add", "-f", fname}) - assertContainsSubstring(t, stdout, "success") - - stdout, _ = runDefraCommand(t, conf, []string{"client", "schema", "patch", `[{ "op": "invalidOp" }]`}) - assertContainsSubstring(t, stdout, "Internal Server Error") -} diff --git a/tests/integration/cli/init_test.go b/tests/integration/cli/init_test.go deleted file mode 100644 index 7292d920c3..0000000000 --- a/tests/integration/cli/init_test.go +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2022 Democratized Data Foundation -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package clitest - -import ( - "path/filepath" - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/sourcenetwork/defradb/config" -) - -// Executing init command creates valid config file. -func TestCLIInitCommand(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - _, stderr := runDefraCommand(t, conf, []string{"init", "--rootdir", conf.rootDir}) - cfgfilePath := filepath.Join(conf.rootDir, config.DefaultConfigFileName) - assertContainsSubstring(t, stderr, "Created config file at "+cfgfilePath) - if !assert.FileExists(t, cfgfilePath) { - t.Fatal("Config file not created") - } -} - -func TestCLIInitCommandTwiceErrors(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - cfgfilePath := filepath.Join(conf.rootDir, config.DefaultConfigFileName) - _, stderr := runDefraCommand(t, conf, []string{"init", "--rootdir", conf.rootDir}) - assertContainsSubstring(t, stderr, "Created config file at "+cfgfilePath) - _, stderr = runDefraCommand(t, conf, []string{"init", "--rootdir", conf.rootDir}) - assertContainsSubstring(t, stderr, "Configuration file already exists at "+cfgfilePath) -} - -// Executing init command twice, but second time reinitializing. -func TestInitCommandTwiceReinitalize(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - cfgfilePath := filepath.Join(conf.rootDir, config.DefaultConfigFileName) - _, stderr := runDefraCommand(t, conf, []string{"init", "--rootdir", conf.rootDir}) - assertContainsSubstring(t, stderr, "Created config file at "+cfgfilePath) - _, stderr = runDefraCommand(t, conf, []string{"init", "--rootdir", conf.rootDir, "--reinitialize"}) - assertContainsSubstring(t, stderr, "Deleted config file at "+cfgfilePath) - assertContainsSubstring(t, stderr, "Created config file at "+cfgfilePath) -} diff --git a/tests/integration/cli/log_config_test.go b/tests/integration/cli/log_config_test.go deleted file mode 100644 index 55d1b18154..0000000000 --- a/tests/integration/cli/log_config_test.go +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright 2022 Democratized Data Foundation -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package clitest - -import ( - "bufio" - "bytes" - "context" - "fmt" - "io" - "os" - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/sourcenetwork/defradb/cli" - "github.com/sourcenetwork/defradb/config" - "github.com/sourcenetwork/defradb/logging" -) - -const ( - testLogger1 = "testLogger1" - testLogger2 = "testLogger2" - testLogger3 = "testLogger3" -) - -var ( - log1 = logging.MustNewLogger(testLogger1) - log2 = logging.MustNewLogger(testLogger2) - log3 = logging.MustNewLogger(testLogger3) -) - -func TestCLILogsToStderrGivenNamedLogLevel(t *testing.T) { - ctx := context.Background() - logLines := captureLogLines( - t, - func() { - // set the log levels - // general: error - // testLogger1: debug - // testLogger2: info - os.Args = append(os.Args, "--loglevel") - os.Args = append(os.Args, fmt.Sprintf("%s,%s=debug,%s=info", "error", testLogger1, testLogger2)) - }, - func() { - log1.Error(ctx, "error") - log1.Debug(ctx, "debug") - log2.Info(ctx, "info") - log3.Debug(ctx, "debug") // wont print, as logger3 will use global level defined above as 'error' - log3.Info(ctx, "info") // wont print, as logger3 will use global level defined above as 'error' - }, - ) - - assert.Len(t, logLines, 3) -} - -func captureLogLines(t *testing.T, setup func(), predicate func()) []string { - r, w, err := os.Pipe() - if err != nil { - t.Fatal(err) - } - stderr := os.Stderr - os.Stderr = w - defer func() { - os.Stderr = stderr - }() - - directory := t.TempDir() - - // Set the default logger output path to a file in the temp dir - // so that production logs don't polute and confuse the tests - // os.Args = append(os.Args, "--logoutput", directory+"/log.txt") - os.Args = append(os.Args, "init", "--rootdir", directory) - - setup() - cfg := config.DefaultConfig() - defraCmd := cli.NewDefraCommand(cfg) - if err := defraCmd.Execute(context.Background()); err != nil { - t.Fatal(err) - } - predicate() - log1.Flush() - log2.Flush() - log3.Flush() - - w.Close() - var buf bytes.Buffer - _, _ = io.Copy(&buf, r) - logLines, err := parseLines(&buf) - if err != nil { - t.Fatal(err) - } - - return logLines -} - -func parseLines(r io.Reader) ([]string, error) { - fileScanner := bufio.NewScanner(r) - - fileScanner.Split(bufio.ScanLines) - - logLines := []string{} - for fileScanner.Scan() { - logLines = append(logLines, fileScanner.Text()) - } - - return logLines, nil -} diff --git a/tests/integration/cli/root_test.go b/tests/integration/cli/root_test.go deleted file mode 100644 index 33df29fc4d..0000000000 --- a/tests/integration/cli/root_test.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2022 Democratized Data Foundation -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package clitest - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestRootCommandEmptyRootDir(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stdout, _ := runDefraCommand(t, conf, []string{}) - assert.Contains(t, stdout, "Usage:") -} - -func TestRootCommandRootDirWithDefaultConfig(t *testing.T) { - conf := DefraNodeConfig{ - logPath: t.TempDir(), - } - stdout, _ := runDefraCommand(t, conf, []string{}) - assert.Contains(t, stdout, "Usage:") -} - -func TestRootCommandRootDirFromEnv(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stdout, _ := runDefraCommand(t, conf, []string{}) - assert.Contains(t, stdout, "Usage:") -} - -func TestRootCommandRootWithNonexistentFlag(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stdout, _ := runDefraCommand(t, conf, []string{"--foo"}) - assert.Contains(t, stdout, "Usage:") -} diff --git a/tests/integration/cli/serverdump_test.go b/tests/integration/cli/serverdump_test.go deleted file mode 100644 index ed8fcd4d9f..0000000000 --- a/tests/integration/cli/serverdump_test.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2022 Democratized Data Foundation -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package clitest - -import ( - "testing" -) - -func TestServerDumpMemoryErrs(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - _, stderr := runDefraCommand(t, conf, []string{"server-dump", "--store", "memory"}) - assertContainsSubstring(t, stderr, "server-side dump is only supported for the Badger datastore") -} - -func TestServerDumpInvalidStoreErrs(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - _, stderr := runDefraCommand(t, conf, []string{"server-dump", "--store", "invalid"}) - // assertContainsSubstring(t, stderr, "invalid datastore type") - assertContainsSubstring(t, stderr, "server-side dump is only supported for the Badger datastore") -} diff --git a/tests/integration/cli/start_test.go b/tests/integration/cli/start_test.go deleted file mode 100644 index 1a6267f190..0000000000 --- a/tests/integration/cli/start_test.go +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright 2022 Democratized Data Foundation -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package clitest - -import ( - "fmt" - "testing" -) - -func TestStartCommandBasic(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - _, stderr := runDefraCommand(t, conf, []string{ - "start", - "--url", conf.APIURL, - "--tcpaddr", conf.GRPCAddr, - }) - assertContainsSubstring(t, stderr, "Starting DefraDB service...") - assertNotContainsSubstring(t, stderr, "Error") -} - -func TestStartCommandWithTLSIncomplete(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - _, stderr := runDefraCommand(t, conf, []string{ - "start", - "--tls", - "--url", conf.APIURL, - "--tcpaddr", conf.GRPCAddr, - }) - assertContainsSubstring(t, stderr, "Starting DefraDB service...") - assertContainsSubstring(t, stderr, "Error") -} - -func TestStartCommandWithStoreMemory(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - _, stderr := runDefraCommand(t, conf, []string{ - "start", "--store", "memory", - "--url", conf.APIURL, - "--tcpaddr", conf.GRPCAddr, - }) - assertContainsSubstring(t, stderr, "Starting DefraDB service...") - assertContainsSubstring(t, stderr, "Building new memory store") - assertNotContainsSubstring(t, stderr, "Error") -} - -func TestStartCommandWithP2PAddr(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - p2pport, err := findFreePortInRange(t, 49152, 65535) - if err != nil { - t.Fatal(err) - } - addr := fmt.Sprintf("/ip4/0.0.0.0/tcp/%d", p2pport) - _, stderr := runDefraCommand(t, conf, []string{ - "start", - "--p2paddr", addr, - "--url", conf.APIURL, - "--tcpaddr", conf.GRPCAddr, - }) - assertContainsSubstring(t, stderr, "Starting DefraDB service...") - logstring := fmt.Sprintf("Starting P2P node, {\"P2P address\": \"%s\"}", addr) - assertContainsSubstring(t, stderr, logstring) - assertNotContainsSubstring(t, stderr, "Error") -} - -func TestStartCommandWithNoP2P(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - _, stderr := runDefraCommand(t, conf, []string{ - "start", - "--no-p2p", - }) - assertContainsSubstring(t, stderr, "Starting DefraDB service...") - assertNotContainsSubstring(t, stderr, "Starting P2P node") - assertNotContainsSubstring(t, stderr, "Error") -} - -func TestStartCommandWithInvalidStoreType(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - _, stderr := runDefraCommand(t, conf, []string{ - "start", - "--store", "invalid", - }) - assertContainsSubstring(t, stderr, "failed to load config: failed to validate config: invalid store type") -} diff --git a/tests/integration/cli/utils.go b/tests/integration/cli/utils.go deleted file mode 100644 index c94ce222dc..0000000000 --- a/tests/integration/cli/utils.go +++ /dev/null @@ -1,263 +0,0 @@ -// Copyright 2022 Democratized Data Foundation -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -/* -Package clitest provides a testing framework for the Defra CLI, along with CLI integration tests. -*/ -package clitest - -import ( - "bufio" - "bytes" - "context" - "errors" - "fmt" - "io" - "math/rand" - "net" - "os" - "path/filepath" - "strings" - "sync" - "testing" - "time" - - "github.com/stretchr/testify/assert" - - "github.com/sourcenetwork/defradb/cli" - "github.com/sourcenetwork/defradb/config" -) - -const COMMAND_TIMEOUT_SECONDS = 2 * time.Second -const SUBCOMMAND_TIME_BUFFER_SECONDS = 200 * time.Millisecond - -type DefraNodeConfig struct { - rootDir string - logPath string - APIURL string - GRPCAddr string -} - -func NewDefraNodeDefaultConfig(t *testing.T) DefraNodeConfig { - t.Helper() - portAPI, err := findFreePortInRange(t, 49152, 65535) - if err != nil { - t.Fatal(err) - } - portGRPC, err := findFreePortInRange(t, 49152, 65535) - if err != nil { - t.Fatal(err) - } - - return DefraNodeConfig{ - rootDir: t.TempDir(), - logPath: "", - APIURL: fmt.Sprintf("localhost:%d", portAPI), - GRPCAddr: fmt.Sprintf("/ip4/0.0.0.0/tcp/%d", portGRPC), - } -} - -// runDefraNode runs a defra node in a separate goroutine and returns a stopping function -// which also returns the node's execution log lines. -func runDefraNode(t *testing.T, conf DefraNodeConfig) func() []string { - t.Helper() - - if conf.logPath == "" { - conf.logPath = filepath.Join(t.TempDir(), "defra.log") - } - - var args []string - if conf.rootDir != "" { - args = append(args, "--rootdir", conf.rootDir) - } - if conf.APIURL != "" { - args = append(args, "--url", conf.APIURL) - } - if conf.GRPCAddr != "" { - args = append(args, "--tcpaddr", conf.GRPCAddr) - } - args = append(args, "--logoutput", conf.logPath) - - cfg := config.DefaultConfig() - ctx, cancel := context.WithCancel(context.Background()) - ready := make(chan struct{}) - go func(ready chan struct{}) { - defraCmd := cli.NewDefraCommand(cfg) - defraCmd.RootCmd.SetArgs( - append([]string{"start"}, args...), - ) - ready <- struct{}{} - err := defraCmd.Execute(ctx) - assert.NoError(t, err) - }(ready) - <-ready - time.Sleep(SUBCOMMAND_TIME_BUFFER_SECONDS) - cancelAndOutput := func() []string { - cancel() - time.Sleep(SUBCOMMAND_TIME_BUFFER_SECONDS) - lines, err := readLoglines(t, conf.logPath) - assert.NoError(t, err) - return lines - } - return cancelAndOutput -} - -// Runs a defra command and returns the stdout and stderr output. -func runDefraCommand(t *testing.T, conf DefraNodeConfig, args []string) (stdout, stderr []string) { - t.Helper() - cfg := config.DefaultConfig() - args = append([]string{ - "--url", conf.APIURL, - }, args...) - if !contains(args, "--rootdir") { - args = append(args, "--rootdir", t.TempDir()) - } - - ctx, cancel := context.WithTimeout(context.Background(), COMMAND_TIMEOUT_SECONDS) - defer cancel() - - stdout, stderr = captureOutput(func() { - defraCmd := cli.NewDefraCommand(cfg) - t.Log("executing defra command with args", args) - defraCmd.RootCmd.SetArgs(args) - _ = defraCmd.Execute(ctx) - }) - return stdout, stderr -} - -func contains(args []string, arg string) bool { - for _, a := range args { - if a == arg { - return true - } - } - return false -} - -func readLoglines(t *testing.T, fpath string) ([]string, error) { - f, err := os.Open(fpath) - if err != nil { - return nil, err - } - defer f.Close() //nolint:errcheck - scanner := bufio.NewScanner(f) - lines := make([]string, 0) - for scanner.Scan() { - lines = append(lines, scanner.Text()) - } - err = scanner.Err() - assert.NoError(t, err) - return lines, nil -} - -func captureOutput(f func()) (stdout, stderr []string) { - oldStdout := os.Stdout - oldStderr := os.Stderr - rStdout, wStdout, err := os.Pipe() - if err != nil { - panic(err) - } - rStderr, wStderr, err := os.Pipe() - if err != nil { - panic(err) - } - os.Stdout = wStdout - os.Stderr = wStderr - - f() - - if err := wStdout.Close(); err != nil { - panic(err) - } - if err := wStderr.Close(); err != nil { - panic(err) - } - - os.Stdout = oldStdout - os.Stderr = oldStderr - - var stdoutBuf, stderrBuf bytes.Buffer - if _, err := io.Copy(&stdoutBuf, rStdout); err != nil { - panic(err) - } - if _, err := io.Copy(&stderrBuf, rStderr); err != nil { - panic(err) - } - - stdout = strings.Split(strings.TrimSuffix(stdoutBuf.String(), "\n"), "\n") - stderr = strings.Split(strings.TrimSuffix(stderrBuf.String(), "\n"), "\n") - - return -} - -var portsInUse = make(map[int]struct{}) -var portMutex = sync.Mutex{} - -// findFreePortInRange returns a free port in the range [minPort, maxPort]. -// The range of ports that are unfrequently used is [49152, 65535]. -func findFreePortInRange(t *testing.T, minPort, maxPort int) (int, error) { - if minPort < 1 || maxPort > 65535 || minPort > maxPort { - return 0, errors.New("invalid port range") - } - - const maxAttempts = 100 - for i := 0; i < maxAttempts; i++ { - port := rand.Intn(maxPort-minPort+1) + minPort - if _, ok := portsInUse[port]; ok { - continue - } - addr := fmt.Sprintf("127.0.0.1:%d", port) - listener, err := net.Listen("tcp", addr) - if err == nil { - portMutex.Lock() - portsInUse[port] = struct{}{} - portMutex.Unlock() - t.Cleanup(func() { - portMutex.Lock() - delete(portsInUse, port) - portMutex.Unlock() - }) - _ = listener.Close() - return port, nil - } - } - - return 0, errors.New("unable to find a free port") -} - -func assertContainsSubstring(t *testing.T, haystack []string, substring string) { - t.Helper() - if !containsSubstring(haystack, substring) { - t.Fatalf("expected %q to contain %q", haystack, substring) - } -} - -func assertNotContainsSubstring(t *testing.T, haystack []string, substring string) { - t.Helper() - if containsSubstring(haystack, substring) { - t.Fatalf("expected %q to not contain %q", haystack, substring) - } -} - -func containsSubstring(haystack []string, substring string) bool { - for _, s := range haystack { - if strings.Contains(s, substring) { - return true - } - } - return false -} - -func schemaFileFixture(t *testing.T, fname string, schema string) string { - absFname := filepath.Join(t.TempDir(), fname) - err := os.WriteFile(absFname, []byte(schema), 0644) - assert.NoError(t, err) - return absFname -} diff --git a/tests/integration/cli/version_test.go b/tests/integration/cli/version_test.go deleted file mode 100644 index bc9c2a7e25..0000000000 --- a/tests/integration/cli/version_test.go +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2022 Democratized Data Foundation -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -package clitest - -import ( - "encoding/json" - "strings" - "testing" - - "github.com/stretchr/testify/assert" -) - -// note: this assumes the version information *without* build-time info integrated. -func TestExecVersion(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stdout, stderr := runDefraCommand(t, conf, []string{"version"}) - for _, line := range stderr { - assert.NotContains(t, line, "ERROR") - } - output := strings.Join(stdout, " ") - assert.Contains(t, output, "defradb") - assert.Contains(t, output, "built with Go") -} - -func TestExecVersionJSON(t *testing.T) { - conf := NewDefraNodeDefaultConfig(t) - stdout, stderr := runDefraCommand(t, conf, []string{"version", "--format", "json"}) - for _, line := range stderr { - assert.NotContains(t, line, "ERROR") - } - output := strings.Join(stdout, " ") - assert.Contains(t, output, "go\":") - assert.Contains(t, output, "commit\":") - assert.Contains(t, output, "commitdate\":") - var data map[string]any - err := json.Unmarshal([]byte(output), &data) - assert.NoError(t, err) -} diff --git a/tests/integration/results.go b/tests/integration/results.go index 052de310c5..176b2e4cf2 100644 --- a/tests/integration/results.go +++ b/tests/integration/results.go @@ -28,7 +28,7 @@ type AnyOf []any // The comparison is relaxed when using client types other than goClientType. func assertResultsAnyOf(t *testing.T, client ClientType, expected AnyOf, actual any, msgAndArgs ...any) { switch client { - case httpClientType: + case httpClientType, cliClientType: if !areResultsAnyOf(expected, actual) { assert.Contains(t, expected, actual, msgAndArgs...) } @@ -42,7 +42,7 @@ func assertResultsAnyOf(t *testing.T, client ClientType, expected AnyOf, actual // The comparison is relaxed when using client types other than goClientType. func assertResultsEqual(t *testing.T, client ClientType, expected any, actual any, msgAndArgs ...any) { switch client { - case httpClientType: + case httpClientType, cliClientType: if !areResultsEqual(expected, actual) { assert.EqualValues(t, expected, actual, msgAndArgs...) } diff --git a/tests/integration/utils2.go b/tests/integration/utils2.go index f41e1a7485..420e7f4c9c 100644 --- a/tests/integration/utils2.go +++ b/tests/integration/utils2.go @@ -32,15 +32,17 @@ import ( "github.com/sourcenetwork/defradb/datastore/memory" "github.com/sourcenetwork/defradb/db" "github.com/sourcenetwork/defradb/errors" - "github.com/sourcenetwork/defradb/http" "github.com/sourcenetwork/defradb/logging" "github.com/sourcenetwork/defradb/net" changeDetector "github.com/sourcenetwork/defradb/tests/change_detector" + "github.com/sourcenetwork/defradb/tests/clients/cli" + "github.com/sourcenetwork/defradb/tests/clients/http" ) const ( clientGoEnvName = "DEFRA_CLIENT_GO" clientHttpEnvName = "DEFRA_CLIENT_HTTP" + clientCliEnvName = "DEFRA_CLIENT_CLI" memoryBadgerEnvName = "DEFRA_BADGER_MEMORY" fileBadgerEnvName = "DEFRA_BADGER_FILE" fileBadgerPathEnvName = "DEFRA_BADGER_FILE_PATH" @@ -65,6 +67,9 @@ const ( // httpClientType enables running the test suite using // the http implementation of the client.DB interface. httpClientType ClientType = "http" + // cliClientType enables running the test suite using + // the cli implementation of the client.DB interface. + cliClientType ClientType = "cli" ) // The MutationType that tests will run using. @@ -101,6 +106,7 @@ var ( inMemoryStore bool httpClient bool goClient bool + cliClient bool mutationType MutationType databaseDir string ) @@ -118,6 +124,7 @@ func init() { // that don't have the flag defined httpClient, _ = strconv.ParseBool(os.Getenv(clientHttpEnvName)) goClient, _ = strconv.ParseBool(os.Getenv(clientGoEnvName)) + cliClient, _ = strconv.ParseBool(os.Getenv(clientCliEnvName)) badgerFile, _ = strconv.ParseBool(os.Getenv(fileBadgerEnvName)) badgerInMemory, _ = strconv.ParseBool(os.Getenv(memoryBadgerEnvName)) inMemoryStore, _ = strconv.ParseBool(os.Getenv(inMemoryEnvName)) @@ -131,7 +138,7 @@ func init() { mutationType = CollectionSaveMutationType } - if !goClient && !httpClient { + if !goClient && !httpClient && !cliClient { // Default is to test go client type. goClient = true } @@ -162,8 +169,8 @@ func AssertPanic(t *testing.T, f assert.PanicTestFunc) bool { t.Skip("Assert panic with the change detector is not currently supported.") } - if httpClient { - // The http-client will return an error instead of panicing at the moment. + if httpClient || cliClient { + // The http / cli client will return an error instead of panicing at the moment. t.Skip("Assert panic with the http client is not currently supported.") } @@ -254,6 +261,9 @@ func GetDatabase(s *state) (cdb client.DB, path string, err error) { case httpClientType: cdb, err = http.NewWrapper(cdb) + case cliClientType: + cdb = cli.NewWrapper(cdb) + case goClientType: return @@ -288,6 +298,9 @@ func ExecuteTestCase( if goClient { clients = append(clients, goClientType) } + if cliClient { + clients = append(clients, cliClientType) + } var databases []DatabaseType if badgerInMemory { diff --git a/version/version.go b/version/version.go index a6fe7ea548..67538d302b 100644 --- a/version/version.go +++ b/version/version.go @@ -17,9 +17,9 @@ import ( "fmt" "strings" - "github.com/sourcenetwork/defradb/api/http" "github.com/sourcenetwork/defradb/client" "github.com/sourcenetwork/defradb/core/net" + "github.com/sourcenetwork/defradb/http" ) const commitHashMaxLength = 8