From daa6c939001a42fe8870f860b33817d8bcc564c3 Mon Sep 17 00:00:00 2001 From: parhelia512 <0011d3@gmail.com> Date: Sun, 13 Jul 2025 06:17:05 +0800 Subject: [PATCH] refactor: migrate from protobuf to simple-protobuf (#520) * refactor: migrate from protobuf to simple-protobuf * update go.mod * fix server * Update gRPC.cpp * Update gRPC.cpp --- .github/workflows/build.yml | 11 - 3rdparty/simple-protobuf/.clang-format | 46 + 3rdparty/simple-protobuf/.clang-tidy | 8 + 3rdparty/simple-protobuf/.clangd | 5 + 3rdparty/simple-protobuf/.github/FUNDING.yml | 15 + .../.github/workflows/ci-linux-coverage.yml | 103 + .../.github/workflows/ci-linux-tests.yml | 123 + .../.github/workflows/ci-macos-tests.yml | 94 + .../.github/workflows/ci-windows-tests.yml | 40 + 3rdparty/simple-protobuf/.gitignore | 38 + 3rdparty/simple-protobuf/CMakeLists.txt | 38 + 3rdparty/simple-protobuf/LICENSE | 21 + 3rdparty/simple-protobuf/README.md | 263 + .../cmake/spb_compile_options.cmake | 74 + .../simple-protobuf/cmake/spb_protobuf.cmake | 56 + 3rdparty/simple-protobuf/doc/extensions.md | 284 + .../simple-protobuf/example/CMakeLists.txt | 19 + .../simple-protobuf/example/addressbook.cpp | 132 + 3rdparty/simple-protobuf/example/etl.cpp | 22 + .../example/proto/addressbook.proto | 25 + .../simple-protobuf/example/proto/etl.proto | 43 + 3rdparty/simple-protobuf/include/spb/bits.h | 51 + .../simple-protobuf/include/spb/concepts.h | 97 + .../include/spb/io/buffer-io.hpp | 87 + .../include/spb/io/function_ref.hpp | 69 + .../simple-protobuf/include/spb/io/io.hpp | 39 + 3rdparty/simple-protobuf/include/spb/json.hpp | 153 + .../simple-protobuf/include/spb/json/base64.h | 217 + .../include/spb/json/deserialize.hpp | 845 ++ .../include/spb/json/serialize.hpp | 417 + 3rdparty/simple-protobuf/include/spb/pb.hpp | 208 + .../include/spb/pb/deserialize.hpp | 751 ++ .../include/spb/pb/serialize.hpp | 386 + .../include/spb/pb/wire-types.h | 99 + .../include/spb/to_from_chars.h | 217 + 3rdparty/simple-protobuf/include/spb/utf8.h | 128 + .../scripts/run-tests-coverage.sh | 11 + 3rdparty/simple-protobuf/src/CMakeLists.txt | 1 + .../src/spb-proto-compiler/CMakeLists.txt | 10 + .../ast/ast-messages-order.cpp | 365 + .../ast/ast-messages-order.h | 15 + .../src/spb-proto-compiler/ast/ast-types.cpp | 419 + .../src/spb-proto-compiler/ast/ast-types.h | 23 + .../src/spb-proto-compiler/ast/ast.cpp | 19 + .../src/spb-proto-compiler/ast/ast.h | 31 + .../spb-proto-compiler/ast/proto-comment.h | 20 + .../src/spb-proto-compiler/ast/proto-common.h | 47 + .../src/spb-proto-compiler/ast/proto-enum.h | 23 + .../src/spb-proto-compiler/ast/proto-field.h | 78 + .../src/spb-proto-compiler/ast/proto-file.h | 40 + .../src/spb-proto-compiler/ast/proto-import.h | 25 + .../src/spb-proto-compiler/ast/proto-map.h | 23 + .../spb-proto-compiler/ast/proto-message.h | 43 + .../src/spb-proto-compiler/ast/proto-oneof.h | 23 + .../spb-proto-compiler/ast/proto-service.h | 19 + .../src/spb-proto-compiler/ast/proto-syntax.h | 21 + .../src/spb-proto-compiler/dumper/dumper.cpp | 42 + .../src/spb-proto-compiler/dumper/dumper.h | 33 + .../src/spb-proto-compiler/dumper/header.cpp | 666 ++ .../src/spb-proto-compiler/dumper/header.h | 44 + .../spb-proto-compiler/dumper/json/dumper.cpp | 539 ++ .../spb-proto-compiler/dumper/json/dumper.h | 33 + .../dumper/json/template-h.h | 19 + .../spb-proto-compiler/dumper/pb/dumper.cpp | 310 + .../src/spb-proto-compiler/dumper/pb/dumper.h | 33 + .../spb-proto-compiler/dumper/pb/template-h.h | 19 + .../src/spb-proto-compiler/io/file.cpp | 43 + .../src/spb-proto-compiler/io/file.h | 31 + .../src/spb-proto-compiler/main.cpp | 142 + .../spb-proto-compiler/parser/char_stream.h | 174 + .../src/spb-proto-compiler/parser/options.h | 33 + .../src/spb-proto-compiler/parser/parser.cpp | 998 +++ .../src/spb-proto-compiler/parser/parser.h | 46 + 3rdparty/simple-protobuf/test/CMakeLists.txt | 195 + 3rdparty/simple-protobuf/test/base64.cpp | 155 + .../simple-protobuf/test/cmake/doctest.cmake | 189 + .../test/cmake/doctestAddTests.cmake | 120 + 3rdparty/simple-protobuf/test/doctest.h | 7134 +++++++++++++++++ .../test/etl-compatibility.cpp | 539 ++ .../test/fuzzer/spb-json-fuzzer.cpp | 28 + .../test/fuzzer/spb-pb-fuzzer.cpp | 28 + .../test/fuzzer/spb-protoc-fuzzer.cpp | 20 + .../test/gpb-compatibility.cpp | 1422 ++++ 3rdparty/simple-protobuf/test/json.cpp | 923 +++ 3rdparty/simple-protobuf/test/parser.cpp | 558 ++ 3rdparty/simple-protobuf/test/pb.cpp | 2109 +++++ .../test/proto/dependency.proto | 26 + .../simple-protobuf/test/proto/desc.proto | 1249 +++ .../simple-protobuf/test/proto/enum.proto | 14 + .../test/proto/etl/etl-person.proto.in | 31 + .../test/proto/etl/etl-scalar.proto.in | 283 + 3rdparty/simple-protobuf/test/proto/map.proto | 97 + .../simple-protobuf/test/proto/mesos.proto | 4062 ++++++++++ .../simple-protobuf/test/proto/message.proto | 46 + .../test/proto/name/name.proto.in | 22 + .../test/proto/person/person.proto.in | 22 + .../test/proto/playlist4_external.proto | 13 + .../test/proto/playlist_permission.proto | 12 + .../simple-protobuf/test/proto/proto3.proto | 199 + .../test/proto/scalar/scalar.proto.in | 1243 +++ 3rdparty/simple-protobuf/test/protos.cpp | 49 + CMakeLists.txt | 2 +- README.md | 2 +- README_zh.md | 2 +- cmake/myproto.cmake | 13 +- core/server/gen/libcore.pb.go | 713 +- core/server/gen/libcore.proto | 104 +- core/server/gen/libcore_grpc.pb.go | 2 +- core/server/go.mod | 3 - core/server/go.sum | 20 +- core/server/internal/distro/all/all.go | 1 - core/server/server.go | 139 +- core/server/server_windows.go | 2 +- include/api/gRPC.h | 4 +- include/ui/mainwindow.h | 4 +- script/build_deps_all.sh | 45 - src/api/gRPC.cpp | 110 +- .../connectionLister/connectionLister.cpp | 24 +- src/stats/traffic/TrafficLooper.cpp | 6 +- src/ui/mainwindow.cpp | 8 +- src/ui/mainwindow_grpc.cpp | 112 +- src/ui/setting/dialog_basic_settings.cpp | 2 +- src/ui/setting/dialog_manage_routes.cpp | 8 +- 123 files changed, 31065 insertions(+), 734 deletions(-) create mode 100644 3rdparty/simple-protobuf/.clang-format create mode 100644 3rdparty/simple-protobuf/.clang-tidy create mode 100644 3rdparty/simple-protobuf/.clangd create mode 100644 3rdparty/simple-protobuf/.github/FUNDING.yml create mode 100644 3rdparty/simple-protobuf/.github/workflows/ci-linux-coverage.yml create mode 100644 3rdparty/simple-protobuf/.github/workflows/ci-linux-tests.yml create mode 100644 3rdparty/simple-protobuf/.github/workflows/ci-macos-tests.yml create mode 100644 3rdparty/simple-protobuf/.github/workflows/ci-windows-tests.yml create mode 100644 3rdparty/simple-protobuf/.gitignore create mode 100644 3rdparty/simple-protobuf/CMakeLists.txt create mode 100644 3rdparty/simple-protobuf/LICENSE create mode 100644 3rdparty/simple-protobuf/README.md create mode 100644 3rdparty/simple-protobuf/cmake/spb_compile_options.cmake create mode 100644 3rdparty/simple-protobuf/cmake/spb_protobuf.cmake create mode 100644 3rdparty/simple-protobuf/doc/extensions.md create mode 100644 3rdparty/simple-protobuf/example/CMakeLists.txt create mode 100644 3rdparty/simple-protobuf/example/addressbook.cpp create mode 100644 3rdparty/simple-protobuf/example/etl.cpp create mode 100644 3rdparty/simple-protobuf/example/proto/addressbook.proto create mode 100644 3rdparty/simple-protobuf/example/proto/etl.proto create mode 100644 3rdparty/simple-protobuf/include/spb/bits.h create mode 100644 3rdparty/simple-protobuf/include/spb/concepts.h create mode 100644 3rdparty/simple-protobuf/include/spb/io/buffer-io.hpp create mode 100644 3rdparty/simple-protobuf/include/spb/io/function_ref.hpp create mode 100644 3rdparty/simple-protobuf/include/spb/io/io.hpp create mode 100644 3rdparty/simple-protobuf/include/spb/json.hpp create mode 100644 3rdparty/simple-protobuf/include/spb/json/base64.h create mode 100644 3rdparty/simple-protobuf/include/spb/json/deserialize.hpp create mode 100644 3rdparty/simple-protobuf/include/spb/json/serialize.hpp create mode 100644 3rdparty/simple-protobuf/include/spb/pb.hpp create mode 100644 3rdparty/simple-protobuf/include/spb/pb/deserialize.hpp create mode 100644 3rdparty/simple-protobuf/include/spb/pb/serialize.hpp create mode 100644 3rdparty/simple-protobuf/include/spb/pb/wire-types.h create mode 100644 3rdparty/simple-protobuf/include/spb/to_from_chars.h create mode 100644 3rdparty/simple-protobuf/include/spb/utf8.h create mode 100644 3rdparty/simple-protobuf/scripts/run-tests-coverage.sh create mode 100644 3rdparty/simple-protobuf/src/CMakeLists.txt create mode 100644 3rdparty/simple-protobuf/src/spb-proto-compiler/CMakeLists.txt create mode 100644 3rdparty/simple-protobuf/src/spb-proto-compiler/ast/ast-messages-order.cpp create mode 100644 3rdparty/simple-protobuf/src/spb-proto-compiler/ast/ast-messages-order.h create mode 100644 3rdparty/simple-protobuf/src/spb-proto-compiler/ast/ast-types.cpp create mode 100644 3rdparty/simple-protobuf/src/spb-proto-compiler/ast/ast-types.h create mode 100644 3rdparty/simple-protobuf/src/spb-proto-compiler/ast/ast.cpp create mode 100644 3rdparty/simple-protobuf/src/spb-proto-compiler/ast/ast.h create mode 100644 3rdparty/simple-protobuf/src/spb-proto-compiler/ast/proto-comment.h create mode 100644 3rdparty/simple-protobuf/src/spb-proto-compiler/ast/proto-common.h create mode 100644 3rdparty/simple-protobuf/src/spb-proto-compiler/ast/proto-enum.h create mode 100644 3rdparty/simple-protobuf/src/spb-proto-compiler/ast/proto-field.h create mode 100644 3rdparty/simple-protobuf/src/spb-proto-compiler/ast/proto-file.h create mode 100644 3rdparty/simple-protobuf/src/spb-proto-compiler/ast/proto-import.h create mode 100644 3rdparty/simple-protobuf/src/spb-proto-compiler/ast/proto-map.h create mode 100644 3rdparty/simple-protobuf/src/spb-proto-compiler/ast/proto-message.h create mode 100644 3rdparty/simple-protobuf/src/spb-proto-compiler/ast/proto-oneof.h create mode 100644 3rdparty/simple-protobuf/src/spb-proto-compiler/ast/proto-service.h create mode 100644 3rdparty/simple-protobuf/src/spb-proto-compiler/ast/proto-syntax.h create mode 100644 3rdparty/simple-protobuf/src/spb-proto-compiler/dumper/dumper.cpp create mode 100644 3rdparty/simple-protobuf/src/spb-proto-compiler/dumper/dumper.h create mode 100644 3rdparty/simple-protobuf/src/spb-proto-compiler/dumper/header.cpp create mode 100644 3rdparty/simple-protobuf/src/spb-proto-compiler/dumper/header.h create mode 100644 3rdparty/simple-protobuf/src/spb-proto-compiler/dumper/json/dumper.cpp create mode 100644 3rdparty/simple-protobuf/src/spb-proto-compiler/dumper/json/dumper.h create mode 100644 3rdparty/simple-protobuf/src/spb-proto-compiler/dumper/json/template-h.h create mode 100644 3rdparty/simple-protobuf/src/spb-proto-compiler/dumper/pb/dumper.cpp create mode 100644 3rdparty/simple-protobuf/src/spb-proto-compiler/dumper/pb/dumper.h create mode 100644 3rdparty/simple-protobuf/src/spb-proto-compiler/dumper/pb/template-h.h create mode 100644 3rdparty/simple-protobuf/src/spb-proto-compiler/io/file.cpp create mode 100644 3rdparty/simple-protobuf/src/spb-proto-compiler/io/file.h create mode 100644 3rdparty/simple-protobuf/src/spb-proto-compiler/main.cpp create mode 100644 3rdparty/simple-protobuf/src/spb-proto-compiler/parser/char_stream.h create mode 100644 3rdparty/simple-protobuf/src/spb-proto-compiler/parser/options.h create mode 100644 3rdparty/simple-protobuf/src/spb-proto-compiler/parser/parser.cpp create mode 100644 3rdparty/simple-protobuf/src/spb-proto-compiler/parser/parser.h create mode 100644 3rdparty/simple-protobuf/test/CMakeLists.txt create mode 100644 3rdparty/simple-protobuf/test/base64.cpp create mode 100644 3rdparty/simple-protobuf/test/cmake/doctest.cmake create mode 100644 3rdparty/simple-protobuf/test/cmake/doctestAddTests.cmake create mode 100644 3rdparty/simple-protobuf/test/doctest.h create mode 100644 3rdparty/simple-protobuf/test/etl-compatibility.cpp create mode 100644 3rdparty/simple-protobuf/test/fuzzer/spb-json-fuzzer.cpp create mode 100644 3rdparty/simple-protobuf/test/fuzzer/spb-pb-fuzzer.cpp create mode 100644 3rdparty/simple-protobuf/test/fuzzer/spb-protoc-fuzzer.cpp create mode 100644 3rdparty/simple-protobuf/test/gpb-compatibility.cpp create mode 100644 3rdparty/simple-protobuf/test/json.cpp create mode 100644 3rdparty/simple-protobuf/test/parser.cpp create mode 100644 3rdparty/simple-protobuf/test/pb.cpp create mode 100644 3rdparty/simple-protobuf/test/proto/dependency.proto create mode 100644 3rdparty/simple-protobuf/test/proto/desc.proto create mode 100644 3rdparty/simple-protobuf/test/proto/enum.proto create mode 100644 3rdparty/simple-protobuf/test/proto/etl/etl-person.proto.in create mode 100644 3rdparty/simple-protobuf/test/proto/etl/etl-scalar.proto.in create mode 100644 3rdparty/simple-protobuf/test/proto/map.proto create mode 100644 3rdparty/simple-protobuf/test/proto/mesos.proto create mode 100644 3rdparty/simple-protobuf/test/proto/message.proto create mode 100644 3rdparty/simple-protobuf/test/proto/name/name.proto.in create mode 100644 3rdparty/simple-protobuf/test/proto/person/person.proto.in create mode 100644 3rdparty/simple-protobuf/test/proto/playlist4_external.proto create mode 100644 3rdparty/simple-protobuf/test/proto/playlist_permission.proto create mode 100644 3rdparty/simple-protobuf/test/proto/proto3.proto create mode 100644 3rdparty/simple-protobuf/test/proto/scalar/scalar.proto.in create mode 100644 3rdparty/simple-protobuf/test/protos.cpp delete mode 100755 script/build_deps_all.sh diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ebd7e6d..106301b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -128,17 +128,6 @@ jobs: setup-python: true cache: true cache-key-prefix: QtCache-${{ matrix.platform }}-${{ matrix.target }} - # ========================================================================================================= Build deps - - name: Cache Download - id: cache-deps - uses: actions/cache@v4.2.3 - with: - path: libs/deps - key: DepsCache-${{ matrix.platform }}-${{ matrix.target }}-${{ hashFiles('script/build_deps_*.sh') }}-Qt${{ matrix.qt_version }} - - name: Build Dependencies - shell: bash - if: steps.cache-deps.outputs.cache-hit != 'true' - run: ./script/build_deps_all.sh ${{ matrix.target }} # ========================================================================================================= Generate MakeFile and Build - name: Windows - Generate MakeFile and Build shell: bash diff --git a/3rdparty/simple-protobuf/.clang-format b/3rdparty/simple-protobuf/.clang-format new file mode 100644 index 0000000..e0a34d3 --- /dev/null +++ b/3rdparty/simple-protobuf/.clang-format @@ -0,0 +1,46 @@ +--- +AccessModifierOffset: '-4' +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: 'true' +AlignConsecutiveDeclarations: 'false' +AlignOperands: 'false' +AllowShortBlocksOnASingleLine: 'false' +AllowShortCaseLabelsOnASingleLine: 'false' +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: 'false' +AllowShortLoopsOnASingleLine: 'false' +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakTemplateDeclarations: 'true' +BinPackArguments: 'true' +BinPackParameters: 'true' +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Allman +BreakConstructorInitializersBeforeComma: 'true' +ColumnLimit: 100 +ConstructorInitializerAllOnOneLineOrOnePerLine: 'false' +Cpp11BracedListStyle: 'false' +IndentWidth: '4' +Language: Cpp +NamespaceIndentation: None +PointerAlignment: Middle +SortIncludes: 'true' +SpaceAfterCStyleCast: 'true' +SpaceBeforeAssignmentOperators: 'true' +SpaceBeforeParens: Never +SpaceInEmptyParentheses: 'true' +SpacesBeforeTrailingComments: '0' +SpacesInAngles: 'true' +SpacesInCStyleCastParentheses: 'true' +SpacesInContainerLiterals: 'true' +SpacesInParentheses: 'true' +SpacesInSquareBrackets: 'true' +Standard: Cpp11 +TabWidth: '4' +UseTab: Never +... +Language: Proto +IndentWidth: '4' + +... + diff --git a/3rdparty/simple-protobuf/.clang-tidy b/3rdparty/simple-protobuf/.clang-tidy new file mode 100644 index 0000000..f61cecf --- /dev/null +++ b/3rdparty/simple-protobuf/.clang-tidy @@ -0,0 +1,8 @@ +--- +Checks: 'modernize-*,performance-*,readability-*,clang-abalyzer-*,bugprone-*,cppcoreguidelines-*,misc-*' +WarningsAsErrors: '' +HeaderFilterRegex: '' +AnalyzeTemporaryDtors: false +FormatStyle: file +... + diff --git a/3rdparty/simple-protobuf/.clangd b/3rdparty/simple-protobuf/.clangd new file mode 100644 index 0000000..630f500 --- /dev/null +++ b/3rdparty/simple-protobuf/.clangd @@ -0,0 +1,5 @@ +InlayHints: + Designators: No + Enabled: Yes + ParameterNames: No + DeducedTypes: No \ No newline at end of file diff --git a/3rdparty/simple-protobuf/.github/FUNDING.yml b/3rdparty/simple-protobuf/.github/FUNDING.yml new file mode 100644 index 0000000..e780d10 --- /dev/null +++ b/3rdparty/simple-protobuf/.github/FUNDING.yml @@ -0,0 +1,15 @@ +# These are supported funding model platforms + +github: [tonda-kriz] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry +polar: # Replace with a single Polar username +buy_me_a_coffee: # Replace with a single Buy Me a Coffee username +thanks_dev: # Replace with a single thanks.dev username +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/3rdparty/simple-protobuf/.github/workflows/ci-linux-coverage.yml b/3rdparty/simple-protobuf/.github/workflows/ci-linux-coverage.yml new file mode 100644 index 0000000..9bee3f3 --- /dev/null +++ b/3rdparty/simple-protobuf/.github/workflows/ci-linux-coverage.yml @@ -0,0 +1,103 @@ +name: Code coverage report + +on: + push: + branches: ['master'] + workflow_dispatch: + +permissions: write-all + +# Allow one concurrent deployment +concurrency: + group: 'pages' + cancel-in-progress: true + +jobs: + coverage: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-24.04 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set reusable strings + # Turn repeated input strings (such as the build output directory) into step outputs. These step outputs can be used throughout the workflow file. + id: strings + shell: bash + run: | + echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT" + + - name: Install Linux dependencies + run: | + sudo add-apt-repository ppa:ubuntu-toolchain-r/test + sudo apt-get install ninja-build protobuf-compiler lcov g++-14 gcc-14 bc + echo "CXX=g++-14" >> ${GITHUB_ENV} + echo "CC=gcc-14" >> ${GITHUB_ENV} + + - name: Configure CMake + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: > + cmake -B ${{ steps.strings.outputs.build-output-dir }} + -G Ninja . + -DCMAKE_C_COMPILER=gcc-14 + -DCMAKE_CXX_COMPILER=g++-14 + -DSPB_PROTO_BUILD_TESTS=ON + -DCMAKE_BUILD_TYPE=Debug + -DSPB_PROTO_USE_COVERAGE=ON + -S ${{ github.workspace }} + + - name: Build + run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --target unit_tests + + - name: Test + working-directory: ${{ steps.strings.outputs.build-output-dir }} + # Execute tests defined by the CMake configuration. Note that --build-config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: | + ctest --output-on-failure + mkdir report + + - name: coverage report + working-directory: ${{ steps.strings.outputs.build-output-dir }} + run: | + lcov --capture -o coverage.info --directory . --exclude '/usr/*' --exclude '*/test/*' -q --gcov-tool gcov-14 + coverage=$(genhtml coverage.info --include 'include/*' --output-directory report/library | grep -P 'lines\.+'| grep -oP '[0-9]+' | head -n1) + if (( $(echo "$coverage <= 50" | bc -l) )); then + color=red + elif (( $(echo "$coverage > 80" | bc -l) )); then + color=brightgreen + else + color=orange + fi + curl "https://img.shields.io/badge/library_coverage-$coverage%25-$color?logo=github" > report/library/coverage.svg + coverage=$(genhtml coverage.info --include 'compiler/*' --output-directory report/compiler | grep -P 'lines\.+'| grep -oP '[0-9]+' | head -n1) + if (( $(echo "$coverage <= 50" | bc -l) )); then + color=red + elif (( $(echo "$coverage > 80" | bc -l) )); then + color=brightgreen + else + color=orange + fi + curl "https://img.shields.io/badge/protoc_coverage-$coverage%25-$color?logo=github" > report/compiler/coverage.svg + + - name: Delete deployment + uses: strumwolf/delete-deployment-environment@v2 + with: + token: ${{ secrets.GITHUB_TOKEN }} + environment: github-pages + onlyRemoveDeployments: true + + - name: Setup Pages + uses: actions/configure-pages@v4 + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: ${{ steps.strings.outputs.build-output-dir }}/report + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/3rdparty/simple-protobuf/.github/workflows/ci-linux-tests.yml b/3rdparty/simple-protobuf/.github/workflows/ci-linux-tests.yml new file mode 100644 index 0000000..72b679e --- /dev/null +++ b/3rdparty/simple-protobuf/.github/workflows/ci-linux-tests.yml @@ -0,0 +1,123 @@ +name: Linux GPB compatibility + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +jobs: + build: + name: ${{ matrix.os }}, ${{ matrix.compiler }}-${{ matrix.version }}, ${{ matrix.build_type }}, sanitizer:${{ matrix.sanitizer }} + runs-on: ${{ matrix.os }} + + strategy: + # Set fail-fast to false to ensure that feedback is delivered for all matrix combinations. Consider changing this to true when your workflow is stable. + fail-fast: false + + matrix: + include: + - os: ubuntu-24.04 + compiler: gcc + build_type: Release + version: '13' + sanitizer: 'OFF' + - os: ubuntu-24.04 + compiler: gcc + build_type: Release + version: '14' + sanitizer: 'ON' + - os: ubuntu-24.04 + compiler: gcc + build_type: Release + version: '14' + sanitizer: 'OFF' + - os: ubuntu-24.04 + compiler: gcc + build_type: Debug + version: '14' + sanitizer: 'ON' + - os: ubuntu-24.04 + compiler: clang + build_type: Release + version: '14' + sanitizer: 'OFF' + - os: ubuntu-24.04 + compiler: clang + build_type: Release + version: '15' + sanitizer: 'OFF' + - os: ubuntu-24.04 + compiler: clang + build_type: Release + version: '16' + sanitizer: 'OFF' + - os: ubuntu-24.04 + compiler: clang + build_type: Release + version: '17' + sanitizer: 'OFF' + - os: ubuntu-24.04 + compiler: clang + build_type: Release + version: '18' + sanitizer: 'OFF' + - os: ubuntu-24.04 + compiler: clang + build_type: Release + version: '18' + sanitizer: 'ON' + - os: ubuntu-24.04 + compiler: clang + build_type: Debug + version: '18' + sanitizer: 'ON' + steps: + - uses: actions/checkout@v4 + + - name: Set reusable strings + # Turn repeated input strings (such as the build output directory) into step outputs. These step outputs can be used throughout the workflow file. + id: strings + shell: bash + run: | + echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT" + + - name: Install Linux dependencies + run: > + sudo apt-get install + protobuf-compiler + ninja-build + - name: Install gcc + if: matrix.compiler == 'gcc' + run: | + sudo add-apt-repository ppa:ubuntu-toolchain-r/test + sudo apt-get install g++-${{ matrix.version }} + sudo apt-get install gcc-${{ matrix.version }} + echo "CXX=g++-${{ matrix.version }}" >> ${GITHUB_ENV} + echo "CC=gcc-${{ matrix.version }}" >> ${GITHUB_ENV} + - name: Install clang + if: matrix.compiler == 'clang' + run: | + sudo apt-get install clang-${{ matrix.version }} + echo "CXX=clang++-${{ matrix.version }}" >> ${GITHUB_ENV} + echo "CC=clang-${{ matrix.version }}" >> ${GITHUB_ENV} + - name: Configure CMake + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: > + cmake -B ${{ steps.strings.outputs.build-output-dir }} + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} + -DSPB_PROTO_BUILD_TESTS=ON + -DSPB_PROTO_BUILD_EXAMPLES=ON + -DSPB_PROTO_BUILD_COMPATIBILITY_TESTS=ON + -DSPB_PROTO_BUILD_ETL_TESTS=ON + -DSPB_PROTO_USE_ADDRESS_SANITIZER=${{ matrix.sanitizer }} + -DSPB_PROTO_USE_UB_SANITIZER=${{ matrix.sanitizer }} + -S ${{ github.workspace }} + + - name: Build + run: cmake --build ${{ steps.strings.outputs.build-output-dir }} + + - name: Test + working-directory: ${{ steps.strings.outputs.build-output-dir }} + run: ctest diff --git a/3rdparty/simple-protobuf/.github/workflows/ci-macos-tests.yml b/3rdparty/simple-protobuf/.github/workflows/ci-macos-tests.yml new file mode 100644 index 0000000..146812d --- /dev/null +++ b/3rdparty/simple-protobuf/.github/workflows/ci-macos-tests.yml @@ -0,0 +1,94 @@ +name: MacOS build + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +jobs: + build: + name: ${{ matrix.os }}, ${{ matrix.compiler.name || matrix.compiler }}-${{ matrix.compiler.version || 'default' }} + runs-on: ${{ matrix.os }} + + strategy: + # Set fail-fast to false to ensure that feedback is delivered for all matrix combinations. Consider changing this to true when your workflow is stable. + fail-fast: false + + matrix: + os: [macos-13, macos-14] + compiler: + - appleclang # Default AppleClang per OS + - { name: clang, version: 14 } + - { name: clang, version: 15 } + - { name: clang, version: 16 } + - { name: clang, version: 17 } + - { name: clang, version: 18 } + - { name: clang, version: 19 } + - { name: gcc, version: 11 } + - { name: gcc, version: 12 } + #- { name: gcc, version: 13 } + - { name: gcc, version: 14 } + + steps: + - uses: actions/checkout@v4 + + - name: Set reusable strings + id: strings + shell: bash + run: | + echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT" + + - name: Install Clang (if selected) + if: matrix.compiler.name == 'clang' + run: | + brew install llvm@${{ matrix.compiler.version }} + CLANG_PATH=$(brew --prefix llvm@${{ matrix.compiler.version }}) + echo "CLANG_PATH=$CLANG_PATH/bin" >> $GITHUB_ENV + echo "$CLANG_PATH/bin" >> $GITHUB_PATH + + - name: Install GCC (if selected) + if: matrix.compiler.name == 'gcc' + run: | + brew install gcc@${{ matrix.compiler.version }} + GCC_PATH=$(brew --prefix gcc@${{ matrix.compiler.version }}) + echo "GCC_PATH=$GCC_PATH/bin" >> $GITHUB_ENV + echo "$GCC_PATH/bin" >> $GITHUB_PATH + + - name: Configure CMake + run: | + if [ "${{ matrix.compiler }}" == "appleclang" ]; then + cmake \ + -B ${{ steps.strings.outputs.build-output-dir }} \ + -DCMAKE_C_COMPILER=/Library/Developer/CommandLineTools/usr/bin/cc \ + -DCMAKE_CXX_COMPILER=/Library/Developer/CommandLineTools/usr/bin/c++ \ + -DCMAKE_BUILD_TYPE=Release \ + -DSPB_PROTO_BUILD_TESTS=ON \ + -DSPB_PROTO_BUILD_EXAMPLES=ON \ + -S ${{ github.workspace }} + elif [ "${{ matrix.compiler.name }}" == "clang" ]; then + cmake \ + -B ${{ steps.strings.outputs.build-output-dir }} \ + -DCMAKE_C_COMPILER=${{ env.CLANG_PATH }}/clang \ + -DCMAKE_CXX_COMPILER=${{ env.CLANG_PATH }}/clang++ \ + -DCMAKE_BUILD_TYPE=Release \ + -DSPB_PROTO_BUILD_TESTS=ON \ + -DSPB_PROTO_BUILD_EXAMPLES=ON \ + -S ${{ github.workspace }} + elif [ "${{ matrix.compiler.name }}" == "gcc" ]; then + cmake \ + -B ${{ steps.strings.outputs.build-output-dir }} \ + -DCMAKE_C_COMPILER=${{ env.GCC_PATH }}/gcc-${{ matrix.compiler.version }} \ + -DCMAKE_CXX_COMPILER=${{ env.GCC_PATH }}/g++-${{ matrix.compiler.version }} \ + -DCMAKE_BUILD_TYPE=Release \ + -DSPB_PROTO_BUILD_TESTS=ON \ + -DSPB_PROTO_BUILD_EXAMPLES=ON \ + -S ${{ github.workspace }} + fi + + - name: Build + run: cmake --build ${{ steps.strings.outputs.build-output-dir }} + + - name: Test + working-directory: ${{ steps.strings.outputs.build-output-dir }} + run: ctest diff --git a/3rdparty/simple-protobuf/.github/workflows/ci-windows-tests.yml b/3rdparty/simple-protobuf/.github/workflows/ci-windows-tests.yml new file mode 100644 index 0000000..0d191a7 --- /dev/null +++ b/3rdparty/simple-protobuf/.github/workflows/ci-windows-tests.yml @@ -0,0 +1,40 @@ +name: Windows build + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +jobs: + build_cmake_windows: + name: CMake on Windows 2022 + runs-on: windows-2022 + steps: + - uses: actions/checkout@v4 + + - name: Set reusable strings + # Turn repeated input strings (such as the build output directory) into step outputs. These step outputs can be used throughout the workflow file. + id: strings + shell: bash + run: | + echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT" + + - name: Configure CMake + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: > + cmake -B ${{ steps.strings.outputs.build-output-dir }} + -DSPB_PROTO_BUILD_TESTS=ON + -DSPB_PROTO_BUILD_EXAMPLES=ON + -S ${{ github.workspace }} + + - name: Build + # Build your program with the given configuration. Note that --config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). + run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config Release + + - name: Test + working-directory: ${{ steps.strings.outputs.build-output-dir }} + # Execute tests defined by the CMake configuration. Note that --build-config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest --build-config Release diff --git a/3rdparty/simple-protobuf/.gitignore b/3rdparty/simple-protobuf/.gitignore new file mode 100644 index 0000000..e9dca19 --- /dev/null +++ b/3rdparty/simple-protobuf/.gitignore @@ -0,0 +1,38 @@ +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +.cache/ +build/ +.vscode/ +coverage/ + diff --git a/3rdparty/simple-protobuf/CMakeLists.txt b/3rdparty/simple-protobuf/CMakeLists.txt new file mode 100644 index 0000000..da1824a --- /dev/null +++ b/3rdparty/simple-protobuf/CMakeLists.txt @@ -0,0 +1,38 @@ +cmake_minimum_required( VERSION 3.12 ) + +project(spb-proto VERSION 1.0.0 LANGUAGES CXX) + +# Set the C++ standard to C++20 +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +option(SPB_PROTO_BUILD_TESTS "Build tests" OFF) +option(SPB_PROTO_BUILD_COMPATIBILITY_TESTS "Build gpb compatibility tests" OFF) +option(SPB_PROTO_BUILD_COMPILER_TESTS "Build sprotoc tests" OFF) +option(SPB_PROTO_BUILD_ETL_TESTS "Build etl compatibility tests" OFF) +option(SPB_PROTO_BUILD_FUZZER_TESTS "Build fuzzers for spb-protoc, json-parser, gpb-parser" OFF) +option(SPB_PROTO_BUILD_EXAMPLES "Build examples" OFF) +option(SPB_PROTO_USE_CLANG_FORMAT "Enable clang format for generated code" ON) +option(SPB_PROTO_USE_COVERAGE "Enable code coverage" OFF) +option(SPB_PROTO_USE_ADDRESS_SANITIZER "Enable address sanitizer" OFF) +option(SPB_PROTO_USE_UB_SANITIZER "Enable undefined behavior sanitizer" OFF) + +include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/spb_protobuf.cmake) +include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/spb_compile_options.cmake) + +# the main serialization library is header only +add_library(spb-proto INTERFACE) +target_include_directories(spb-proto INTERFACE include) +target_compile_features(spb-proto INTERFACE cxx_std_20) + +add_subdirectory(src) + +if(SPB_PROTO_BUILD_EXAMPLES) + add_subdirectory(example) +endif() + +if(SPB_PROTO_BUILD_TESTS) + enable_testing() + add_subdirectory(test) +endif() \ No newline at end of file diff --git a/3rdparty/simple-protobuf/LICENSE b/3rdparty/simple-protobuf/LICENSE new file mode 100644 index 0000000..1798fc6 --- /dev/null +++ b/3rdparty/simple-protobuf/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Antonin Kriz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/3rdparty/simple-protobuf/README.md b/3rdparty/simple-protobuf/README.md new file mode 100644 index 0000000..5990118 --- /dev/null +++ b/3rdparty/simple-protobuf/README.md @@ -0,0 +1,263 @@ +# simple-protobuf + +[![Linux-build](https://github.com/tonda-kriz/simple-protobuf/actions/workflows/ci-linux-tests.yml/badge.svg)](https://github.com/tonda-kriz/simple-protobuf/actions/workflows/ci-linux-tests.yml) +[![Windows-build](https://github.com/tonda-kriz/simple-protobuf/actions/workflows/ci-windows-tests.yml/badge.svg)](https://github.com/tonda-kriz/simple-protobuf/actions/workflows/ci-windows-tests.yml) +[![Mac-build](https://github.com/tonda-kriz/simple-protobuf/actions/workflows/ci-macos-tests.yml/badge.svg)](https://github.com/tonda-kriz/simple-protobuf/actions/workflows/ci-macos-tests.yml) +[![Library-coverage](https://tonda-kriz.github.io/simple-protobuf/library/coverage.svg)](https://tonda-kriz.github.io/simple-protobuf/library) +[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://matrix.to/#/#simple-protobuf:gitter.im) + +**simple data struct** serialization library for C++. With this library you can serialize and deserialize *POD* C++ structs directly to [JSON](https://json.org) or [protobuf](https://github.com/protocolbuffers/protobuf). +When used together with [etl library](https://github.com/ETLCPP/etl) it doesn't need to allocate any memory, so its suitable for embedded environments (see [extensions](doc/extensions.md)). + +```CPP +namespace PhoneBook +{ +struct Person { + enum class PhoneType : int32_t { + MOBILE = 0, + HOME = 1, + WORK = 2, + }; + struct PhoneNumber { + // phone number is always required + etl::string<16> number; + std::optional< PhoneType > type; + }; + std::optional< std::string > name; + // Unique ID number for this person. + std::optional< int32_t > id; + std::optional< std::string > email; + // all registered phones + std::vector< PhoneNumber > phones; +}; +}// namespace PhoneBook + +auto john = PhoneBook::Person{ + .name = "John Doe", + .id = 1234, + .email = "jdoe@example.com", + }; +//- serialize john to json-string +auto json = spb::json::serialize< std::string >( john ); +//- deserialize john from json-string +auto person = spb::json::deserialize< PhoneBook::Person >( json ); +//- serialize john to protobuf-vector +auto pb = spb::pb::serialize< std::vector< std::byte > >( john ); +//- deserialize john from protobuf-vector +auto person2 = spb::pb::deserialize< PhoneBook::Person >( pb ); +//- john == person == person2 +``` + +## goal + +goal of this library is to make [JSON](https://json.org) and [protobuf](https://github.com/protocolbuffers/protobuf) *part* of the C++ language itself. + +## reason + +There are literally a tons of [JSON](https://json.org) C++ libraries but most of them are designed in a way that the user needs to construct the json *Object* via some API and for serialization and deserialization there is a lot of boilerplate code like type/schema checking, `to_json`, `from_json`, macros... All this is needed to be done by the user, and it usually ends up with a conversion to some C++ struct. + +spb works the other way around, from C++ struct to [JSON](https://json.org) or [protobuf](https://github.com/protocolbuffers/protobuf). With this approach user can focus only on the data, C++ struct, which is much more natural and spb will handle all the boring stuff like serialization/deserialization and type/schema checking. + +## about + +spb is an alternative implementation of [protobuf](https://github.com/protocolbuffers/protobuf) for C++. This is not an plugin for `protoc` but an **replacement** for `protoc`, so you don't need `protobuf` or `protoc` installed to use it. Serialization and deserialization to [JSON](https://json.org) or [protobuf](https://github.com/protocolbuffers/protobuf) is compatible with `protoc`, in other words, data serialized with code generated by `spb-protoc` can be deserialized by code generated by `protoc` and vice versa. + +## usage + +### dependencies + +* C++ compiler (at least C++20) +* cmake +* std library +* *(optional) clang-format for code formatting* + +### cheat sheet + +```cmake +# add this repo to your project +add_subdirectory(external/spb-proto) +# compile proto files to C++ +spb_protobuf_generate(PROTO_SRCS PROTO_HDRS ${CMAKE_SOURCE_DIR}/proto/person.proto) +# add generated files to your project +add_executable(myapp ${PROTO_SRCS} ${PROTO_HDRS}) +# `spb-proto` is an interface library +# the main purpose is to update include path of `myapp` +target_link_libraries(myapp PUBLIC spb-proto) +``` + +### how to use + +1. define a schema for you data in a `person.proto` file + +```proto +package PhoneBook; + +message Person { + optional string name = 1; + optional int32 id = 2; // Unique ID number for this person. + optional string email = 3; + + enum PhoneType { + MOBILE = 0; + HOME = 1; + WORK = 2; + } + + message PhoneNumber { + required string number = 1; // phone number is always required + optional PhoneType type = 2; + } + // all registered phones + repeated PhoneNumber phones = 4; +} +``` + +2. compile `person.proto` with `spb-protoc` into `person.pb.h` and `person.pb.cc` + +```cmake +spb_protobuf_generate(PROTO_SRCS PROTO_HDRS ${CMAKE_SOURCE_DIR}/proto/person.proto) +``` + +*observe the beautifully generated `person.pb.h` and `person.pb.cc`* + +```C++ +namespace PhoneBook +{ +struct Person { + enum class PhoneType : int32_t { + MOBILE = 0, + HOME = 1, + WORK = 2, + }; + struct PhoneNumber { + // phone number is always required + std::string number; + std::optional< PhoneType > type; + }; + std::optional< std::string > name; + // Unique ID number for this person. + std::optional< int32_t > id; + std::optional< std::string > email; + // all registered phones + std::vector< PhoneNumber > phones; +}; +}// namespace PhoneBook +``` + +3. use `Person` struct natively and de/serialize to/from json/pb + +```CPP +#include + +auto john = PhoneBook::Person{ + .name = "John Doe", + .id = 1234, + .email = "jdoe@example.com", + }; + +auto json = spb::json::serialize( john ); +auto person = spb::json::deserialize< PhoneBook::Person >( json ); +auto pb = spb::pb::serialize( john ); +auto person2 = spb::pb::deserialize< PhoneBook::Person >( pb ); +//- john == person == person2 +``` + +## API + +All generated messages (and enums) are using the following API [`include/spb/json.hpp`](include/spb/json.hpp) and [`include/spb/pb.hpp`](include/spb/pb.hpp) + +```CPP +//- serialize message via writer (all other `serialize` are just wrappers around this one) +//- example: `auto serialized_size = spb::pb::serialize( message, my_writer );` +auto serialize( const auto & message, spb::io::writer on_write ) -> size_t; + +//- return size in bytes of serialized message +//- example: `auto serialized_size = spb::pb::serialize_size( message );` +auto serialize_size( const auto & message ) -> size_t; + +//- serialize message into container like std::string, std::vector, ... +//- example: `auto serialized_size = spb::pb::serialize( message, my_string );` +template < typename Message, spb::resizable_container Container > +auto serialize( const Message & message, Container & result ) -> size_t; + +//- serialize message and return container like std::string, std::vector, ... +//- example: `auto my_string = spb::pb::serialize< std::string >( message );` +template < spb::resizable_container Container = std::string, typename Message > +auto serialize( const Message & message ) -> Container; + +``` + +```CPP +//- deserialize message from reader (all other `deserialize` are just wrappers around this one) +//- example: `spb::pb::deserialize( message, my_reader );` +void deserialize( auto & message, spb::io::reader on_read ); + +//- deserialize message from container like std::string, std::vector, ... +//- example: `spb::pb::deserialize( message, my_string );` +template < typename Message, spb::size_container Container > +void deserialize( Message & message, const Container & protobuf ); + + +//- return deserialized message from container like std::string, std::vector, ... +//- example: `auto message = spb::pb::deserialize< Message >( my_string );` +template < typename Message, spb::size_container Container > +auto deserialize( const Container & protobuf ) -> Message; + +//- return deserialized message from reader +//- example: `auto message = spb::pb::deserialize< Message >( my_reader );` +template < typename Message > +auto deserialize( spb::io::reader reader ) -> Message; +``` + +API is prefixed with `spb::json::` for **json** and `spb::pb::` for **protobuf**, +template concepts [`spb::size_container`](include/spb/concepts.h) and [`spb::resizable_container`](include/spb/concepts.h) are defined in [`include/spb/concepts.h`](include/spb/concepts.h), [`spb::io::reader`](include/spb/io/io.hpp) and [`spb::io::writer`](include/spb/io/io.hpp) are user specified *functions* for IO, more info at [`include/io/io.hpp`](include/spb/io/io.hpp) + +## type mapping + +| proto type | CPP type | GPB encoding | +|------------|-------------|-------------| +| `bool` | `bool` | varint | +| `float` | `float` | 4 bytes | +| `double` | `double` | 8 bytes | +| `int32` | `int32_t` | varint | +| `sint32` | `int32_t` | zig-zag varint | +| `uint32` | `uint32_t` | varint | +| `int64` | `int64_t` | varint | +| `sint64` | `int64_t` | zig-zag varint | +| `uint64` | `uint64_t` | varint | +| `fixed32` | `uint32_t` | 4 bytes | +| `sfixed32` | `int32_t` | 4 bytes | +| `fixed64` | `uint64_t` | 8 bytes | +| `sfixed64` | `int64_t` | 8 bytes | +| `string` | `std::string` | utf8 string | +| `bytes` | `std::vector< std::byte >` | base64 encoded in json | + +| proto type modifier | CPP type modifier | Notes | +|---------------------|-------------|-------------| +| `optional` | `std::optional` | | +| `optional` | `std::unique_ptr` | if there is cyclic dependency between messages ( A -> B, B -> A )| +| `repeated` | `std::vector` | | + +See also [extensions](doc/extensions.md) for user specific types and advanced usage. + +## example + +navigate to the [example](example/) directory. + +## status + +* [x] Make it work +* [x] Make it right +* [ ] Make it fast + +### roadmap + +* [x] parser for proto files (supported syntax: `proto2` and `proto3`) +* [x] compile proto message to C++ data struct +* [x] generate json de/serializer for generated C++ data struct (serialized json has to be compatible with GPB) +* [x] generate protobuf de/serializer for generated C++ data struct (serialized pb has to be compatible with GPB) + +### missing features + +* RPC is not implemented +* extend is not implemented diff --git a/3rdparty/simple-protobuf/cmake/spb_compile_options.cmake b/3rdparty/simple-protobuf/cmake/spb_compile_options.cmake new file mode 100644 index 0000000..0e569c4 --- /dev/null +++ b/3rdparty/simple-protobuf/cmake/spb_compile_options.cmake @@ -0,0 +1,74 @@ +if(SPB_PROTO_USE_ADDRESS_SANITIZER) + message("-- SPB: Enable address sanitizer") +endif() + +if(SPB_PROTO_USE_UB_SANITIZER) + message("-- SPB: Enable undefined behavior sanitizer") +endif() + +if(SPB_PROTO_USE_COVERAGE) + find_program(GCOV gcov) + if (NOT GCOV) + message(FATAL "SPB: program gcov not found") + endif() + + find_program(LCOV lcov) + if (NOT LCOV) + message(FATAL "SPB: program lcov not found") + endif() + + find_program(GENHTML genhtml) + if (NOT GENHTML) + message(FATAL "SPB: program genhtml not found") + endif() + + message("-- SPB: Enable code coverage") +endif() + +if(NOT CMAKE_BUILD_TYPE MATCHES Debug) + include(CheckIPOSupported) + check_ipo_supported(RESULT IPO_ENABLED) +endif() + +function(spb_enable_warnings TARGET) + if(MSVC) + if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + # clang-cl doesn't support /Zc:preprocessor + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd4996 /wd4244 /EHs -Wno-missing-field-initializers) + else() + # https://developercommunity.visualstudio.com/t/c2017-illegal-escape-sequence-when-using-in-a-raw/919371 + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd4996 /wd4244 /Zc:preprocessor) + endif() + target_compile_definitions(${TARGET} PRIVATE _CRT_SECURE_NO_WARNINGS) + else() + target_compile_options(${TARGET} PRIVATE -Wall -Wextra -Wpedantic -Werror -Wno-missing-field-initializers) + endif() +endfunction(spb_enable_warnings) + +function(spb_disable_warnings TARGET) + if(NOT MSVC) + target_compile_options(${TARGET} PRIVATE -Wno-deprecated-declarations -Wno-deprecated "-Wno-#warnings") + endif() +endfunction(spb_disable_warnings) + +function(spb_set_compile_options TARGET) + spb_enable_warnings(${TARGET}) + + set_target_properties(${TARGET} PROPERTIES INTERPROCEDURAL_OPTIMIZATION IPO_ENABLED) + + if(SPB_PROTO_USE_ADDRESS_SANITIZER) + target_compile_options(${TARGET} PRIVATE -fsanitize=address) + target_link_options(${TARGET} PRIVATE -fsanitize=address) + endif() + + if(SPB_PROTO_USE_UB_SANITIZER) + target_compile_options(${TARGET} PRIVATE -fsanitize=undefined) + target_link_options(${TARGET} PRIVATE -fsanitize=undefined) + endif() + + if(SPB_PROTO_USE_COVERAGE) + target_compile_options(${TARGET} PRIVATE -fprofile-arcs -ftest-coverage) + target_link_options(${TARGET} PRIVATE -fprofile-arcs -ftest-coverage) + endif() + +endfunction(spb_set_compile_options) diff --git a/3rdparty/simple-protobuf/cmake/spb_protobuf.cmake b/3rdparty/simple-protobuf/cmake/spb_protobuf.cmake new file mode 100644 index 0000000..5ad11ea --- /dev/null +++ b/3rdparty/simple-protobuf/cmake/spb_protobuf.cmake @@ -0,0 +1,56 @@ +include(CMakeParseArguments) + +if(SPB_PROTO_USE_CLANG_FORMAT) + find_program(CLANG_FORMAT clang-format) +endif() + +function(spb_protobuf_generate SRCS HDRS) + if (NOT ARGN) + message(FATAL_ERROR "Error: spb_protobuf_generate() called without any proto files") + return() + endif () + + set(${SRCS}) + set(${HDRS}) + + foreach (FIL ${ARGN}) + get_filename_component(FILE_NAME ${FIL} NAME_WE) + get_filename_component(FILE_ABS ${FIL} ABSOLUTE) + + if(TARGET spb-proto) + # add directory with generated files to the spb-proto includes + target_include_directories(spb-proto INTERFACE ${CMAKE_CURRENT_BINARY_DIR}) + endif() + + list(APPEND ${SRCS} "${CMAKE_CURRENT_BINARY_DIR}/${FILE_NAME}.pb.cc") + list(APPEND ${HDRS} "${CMAKE_CURRENT_BINARY_DIR}/${FILE_NAME}.pb.h") + + file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + + if(SPB_PROTO_USE_CLANG_FORMAT AND CLANG_FORMAT) + add_custom_command( + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${FILE_NAME}.pb.cc" + "${CMAKE_CURRENT_BINARY_DIR}/${FILE_NAME}.pb.h" + COMMAND $ ARGS "--cpp_out=${CMAKE_CURRENT_BINARY_DIR}" ${FILE_ABS} + COMMAND ${CLANG_FORMAT} ARGS -i "${CMAKE_CURRENT_BINARY_DIR}/${FILE_NAME}.pb.cc" + COMMAND ${CLANG_FORMAT} ARGS -i "${CMAKE_CURRENT_BINARY_DIR}/${FILE_NAME}.pb.h" + DEPENDS spb-protoc ${FILE_ABS} + COMMENT "Compiling protofile ${FIL}" + VERBATIM + ) + else() + add_custom_command( + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${FILE_NAME}.pb.cc" + "${CMAKE_CURRENT_BINARY_DIR}/${FILE_NAME}.pb.h" + COMMAND $ ARGS "--cpp_out=${CMAKE_CURRENT_BINARY_DIR}" ${FILE_ABS} + DEPENDS spb-protoc ${FILE_ABS} + COMMENT "Compiling protofile ${FIL}" + VERBATIM + ) + endif() + endforeach () + + set_source_files_properties(${${SRCS}} ${${HDRS}} PROPERTIES GENERATED TRUE) + set(${SRCS} ${${SRCS}} PARENT_SCOPE) + set(${HDRS} ${${HDRS}} PARENT_SCOPE) +endfunction(spb_protobuf_generate) diff --git a/3rdparty/simple-protobuf/doc/extensions.md b/3rdparty/simple-protobuf/doc/extensions.md new file mode 100644 index 0000000..1496c2d --- /dev/null +++ b/3rdparty/simple-protobuf/doc/extensions.md @@ -0,0 +1,284 @@ +# extensions + +All extensions to the .proto files are specified in the comments, so they are ignored by the GPB protoc and therefore compatible with GPB. Extensions are using GPB [options](https://protobuf.dev/programming-guides/proto2/#options) syntax inside of C++ [attribute](https://en.cppreference.com/w/cpp/language/attributes). + +Example: + +```proto +//[[ field.type = "int16" ]] +``` + +## int types + +You can use also **8** and **16** bit ints (`int8`, `uint8`, `int16`, `uint16`) for fields and for enums. Warning: due to compatibility with GPB, always use types with the same sign, like `int32` and `int8`, combinations like `int32` and `uint8` are invalid. + +### how to use int types + +Each field has an attribute `.type`. + +```proto +message Person{ + //[[ field.type = "int16" ]] + required int32 id = 2; +} +``` + +will be translated into... + +```CPP +struct Person{ + int16_t id; +} +``` + +## enum types + +You can specify type of an enum (`int8`, `uint8`, `int16`, `uint16`, `int32`, `uint32`, `int64` and `uint64`) + +### how to use enum types + +Each enum has an attribute `.type`. + +```proto +//[[ enum.type = "uint8" ]] +enum PhoneType { + MOBILE = 0; + HOME = 1; + WORK = 2; +} +``` + +will be translated into... + +```CPP +enum class PhoneType : uint8_t { + MOBILE = 0, + HOME = 1, + WORK = 2, +}; +``` + +Default is set to: + +```proto +//[[ enum.type = "int32" ]] +``` + +You can use this attribute ... + +- before `package`, means its set for all enums in the **whole .proto file** +- before `message`, means its set for all enums inside one **message only** +- before `enum`, means its set for a specific **enum only** + +you can combine them as you want, the more specific ones will be preferred. + +## bit-fields + +You can use bit fields (`int8:1`, `uint8:2` ...) for [ints](#int-types) + +### how to use bit-fields + +Bit fields are used similar as in C with [ints](#int-types). *Remember: always use types with the same sign* + +```proto +message Device{ + //[[ field.type = "uint8:4" ]] + required uint8 id_major = 2; + //[[ field.type = "uint8:4" ]] + required uint8 id_minor = 2; +} +``` + +will be translated into... + +```CPP +struct Device{ + uint8_t id_major : 4; + uint8_t id_minor : 4; +} +``` + +## container types + +You can use your own types for containers (`optionals`, `repeated`, `string`, `bytes`). + +### how to use user container types + +Each container has 2 attributes `.type` (user type) and `.include` (include header for the type). +Container needs to satisfy a [concept](../include/spb/concepts.h). + +| container | [concept](../include/spb/concepts.h) | notes | +|------------|------------------------------|-------------| +| `optional` | `proto_label_optional` | [`optional`](https://protobuf.dev/programming-guides/proto2/#field-labels) field label | +| `repeated` | `proto_label_repeated` | [`repeated`](https://protobuf.dev/programming-guides/proto2/#field-labels) field label | +| `string` | `proto_field_string` | fixed size [`string`](https://protobuf.dev/programming-guides/proto2/#scalar) field type | +| `string` | `proto_field_string_resizable` | resizable [`string`](https://protobuf.dev/programming-guides/proto2/#scalar) field type | +| `bytes` | `proto_field_bytes` | fixed size [`bytes`](https://protobuf.dev/programming-guides/proto2/#scalar) field type | +| `bytes` | `proto_field_bytes_resizable` | resizable [`bytes`](https://protobuf.dev/programming-guides/proto2/#scalar) field type | + +Defaults are set to: + +```proto +//[[ optional.type = "std::optional<$>" ]] +//[[ optional.include = "" ]] + +//[[ repeated.type = "std::vector<$>" ]] +//[[ repeated.include = "" ]] + +//[[ string.type = "std::string" ]] +//[[ string.include = "" ]] + +//[[ bytes.type = "std::vector<$>" ]] +//[[ bytes.include = "" ]] +``` + +`$` will be replaced with the `value_type` of a container. + +- for `string`, `$` (if specified) will be replaced by `char`. +- for `bytes`, `$` (if specified) will be replaced by `std::byte`. +- for `repeated` and `optional`, `$` will be replaced by a `field type` specified in .proto + +You can use those attributes ... + +- before `package`, means its set for the **whole .proto file** +- before `message`, means its set for one **message only** +- before `field`, means its set for one **field only** + +you can combine them as you want, the more specific ones will be preferred. + +#### fixed size bytes, string + +You can use **fixed size** containers for `bytes` and `string`. They needs to satisfy concept [proto_field_bytes](../include/spb/concepts.h) or [proto_field_string](../include/spb/concepts.h) + +```proto +message Person{ + //[[ string.type = "std::array<$,4>" ]] + //[[ string.include = "" ]] + optional string id = 1; +} +``` + +will be translated into... + +```CPP +struct Person{ + std::optional< std::array< char, 4 > > id; +} +``` + +### integration with [etl library](https://github.com/ETLCPP/etl) + +*the whole code is in [examples](../example/)* + +1. define a schema for you data in a `etl.proto` file + +```proto + +syntax = "proto2"; + +package ETL.Example; + +message DeviceStatus { + //[[ field.type = "uint32:31"]] + required uint32 device_id = 1; + //[[ field.type = "uint32:1"]] + required uint32 is_online = 2; + required uint64 last_heartbeat = 3; + //[[ string.type = "std::array<$,8>" ]] + //[[ string.include = "" ]] + required string firmware_version = 4; + //[[ string.type = "etl::string<16>" ]] + //[[ string.include = "" ]] + required string name = 5; +} + +message Command { + //[[ enum.type = "uint8"]] + enum CMD { + CMD_RST = 1; + CMD_READ = 2; + CMD_WRITE = 3; + CMD_TST = 4; + } + //[[ field.type = "uint8:4"]] + required uint32 command_id = 1; // CMD + //[[ field.type = "uint8:2"]] + required uint32 arg = 2; // argument for the command + //[[ field.type = "uint8:1"]] + required uint32 in_flag = 3; // input flag + //[[ field.type = "uint8:1"]] + required uint32 out_flag = 4; // output flag +} + +//[[ repeated.type = "etl::vector<$,16>" ]] +//[[ repeated.include = "" ]] +message CommandQueue { + repeated Command commands = 1; + repeated DeviceStatus statuses = 2; +} +``` + +2. compile `etl.proto` with `spb-protoc` into `etl.pb.h` and `etl.pb.cc` + +```cmake +spb_protobuf_generate(PROTO_SRCS PROTO_HDRS ${CMAKE_SOURCE_DIR}/proto/etl.proto) +``` + +You can combine different types for each container in a single .proto file. *In this example the `string` is represented as `etl::string< 16 >` and `std::array< char, 8 >`.* + +```C++ +namespace ETL::Example +{ +struct DeviceStatus +{ + uint32_t device_id : 31; + uint32_t is_online : 1; + uint64_t last_heartbeat; + std::array< char, 8 > firmware_version; + etl::string< 16 > name; +}; +struct Command +{ + enum class CMD : uint8_t + { + CMD_RST = 1, + CMD_READ = 2, + CMD_WRITE = 3, + CMD_TST = 4, + }; + // CMD + uint8_t command_id : 4; + // argument for the command + uint8_t arg : 2; + // input flag + uint8_t in_flag : 1; + // output flag + uint8_t out_flag : 1; +}; +struct CommandQueue +{ + etl::vector< Command, 16 > commands; + etl::vector< DeviceStatus, 16 > statuses; +}; +}// namespace ETL::Example +``` + +3. use generated structs natively and de/serialize to/from json/pb + +```CPP +#include + +auto command = ETL::Example::Command{ + .command_id = uint8_t( ETL::Example::Command::CMD::CMD_READ ), + .arg = 2, + .in_flag = 1, + .out_flag = 0, + }; + +auto json = spb::json::serialize( command ); +auto pb = spb::pb::serialize( command ); + +auto cmd = spb::json::deserialize< ETL::Example::Command >( json ); +auto cmd2 = spb::pb::deserialize< ETL::Example::Command >( pb ); +//- command == cmd == cmd2 +``` diff --git a/3rdparty/simple-protobuf/example/CMakeLists.txt b/3rdparty/simple-protobuf/example/CMakeLists.txt new file mode 100644 index 0000000..dc4798d --- /dev/null +++ b/3rdparty/simple-protobuf/example/CMakeLists.txt @@ -0,0 +1,19 @@ +spb_protobuf_generate(PROTO_SRCS PROTO_HDRS proto/addressbook.proto) +spb_protobuf_generate(PROTO_ETL_SRCS PROTO_ETL_HDRS proto/etl.proto) + +add_executable(addressbook addressbook.cpp ${PROTO_SRCS}) +target_link_libraries(addressbook PUBLIC spb-proto) + +Include(FetchContent) + +FetchContent_Declare( + etl + GIT_REPOSITORY https://github.com/ETLCPP/etl + GIT_TAG "20.39.4" +) + +FetchContent_MakeAvailable(etl) + +add_executable(etl-example etl.cpp ${PROTO_ETL_SRCS}) +target_link_libraries(etl-example PUBLIC spb-proto etl::etl) +spb_set_compile_options(etl-example) diff --git a/3rdparty/simple-protobuf/example/addressbook.cpp b/3rdparty/simple-protobuf/example/addressbook.cpp new file mode 100644 index 0000000..3ecdc4a --- /dev/null +++ b/3rdparty/simple-protobuf/example/addressbook.cpp @@ -0,0 +1,132 @@ +//- this example is based on https://protobuf.dev/getting-started/cpptutorial/ + +#include +#include +#include +#include +#include + +namespace +{ + +auto load_file( const std::filesystem::path & file_path ) -> std::string +{ + if( !std::filesystem::exists( file_path ) ) + { + return "{}"; + } + const auto file_size = std::filesystem::file_size( file_path ); + auto file_content = std::string( file_size, '\0' ); + + if( auto * p_file = fopen( file_path.string( ).c_str( ), "rb" ); p_file ) + { + const auto read = fread( file_content.data( ), 1, file_content.size( ), p_file ); + fclose( p_file ); + file_content.resize( read ); + return file_content; + } + + perror( file_path.string( ).c_str( ) ); + throw std::system_error( std::make_error_code( std::errc( errno ) ) ); +} + +void save_file( const std::filesystem::path & file_path, std::string_view file_content ) +{ + if( auto * p_file = fopen( file_path.string( ).c_str( ), "wb" ); p_file ) + { + const auto written = fwrite( file_content.data( ), 1, file_content.size( ), p_file ); + fclose( p_file ); + if( written == file_content.size( ) ) + { + return; + } + } + perror( file_path.string( ).c_str( ) ); + throw std::system_error( std::make_error_code( std::errc( errno ) ) ); +} +// This function fills in a Person message based on user input. +void PromptForAddress( tutorial::Person & person ) +{ + std::cout << "Enter person ID number: "; + int id; + std::cin >> id; + person.id = id; + std::cin.ignore( 256, '\n' ); + + std::cout << "Enter name: "; + getline( std::cin, person.name.emplace( ) ); + + std::cout << "Enter email address (blank for none): "; + std::string email; + getline( std::cin, email ); + if( !email.empty( ) ) + { + person.email = std::move( email ); + } + + while( true ) + { + std::cout << "Enter a phone number (or leave blank to finish): "; + std::string number; + getline( std::cin, number ); + if( number.empty( ) ) + { + break; + } + + auto & phone_number = person.phones.emplace_back( ); + phone_number.number = number; + + std::cout << "Is this a mobile, home, or work phone? "; + std::string type; + getline( std::cin, type ); + if( type == "mobile" ) + { + phone_number.type = tutorial::Person::PhoneType::PHONE_TYPE_MOBILE; + } + else if( type == "home" ) + { + phone_number.type = tutorial::Person::PhoneType::PHONE_TYPE_HOME; + } + else if( type == "work" ) + { + phone_number.type = tutorial::Person::PhoneType::PHONE_TYPE_WORK; + } + else + { + std::cout << "Unknown phone type. Using default." << std::endl; + } + } +} +}// namespace + +// Main function: Reads the entire address book from a file, +// adds one person based on user input, then writes it back out to the same +// file. +auto main( int argc, char * argv[] ) -> int +{ + if( argc != 2 ) + { + std::cerr << "Usage: " << argv[ 0 ] << " ADDRESS_BOOK_FILE" << std::endl; + return -1; + } + + try + { + auto address_book = + spb::json::deserialize< tutorial::AddressBook >( load_file( argv[ 1 ] ) ); + + // Add an address. + PromptForAddress( address_book.people.emplace_back( ) ); + + // Write the new address book back to disk. + save_file( argv[ 1 ], spb::json::serialize( address_book ) ); + } + catch( const std::exception & e ) + { + std::cerr << "Exception: " << e.what( ) << std::endl; + return 1; + } + + return 0; +} \ No newline at end of file diff --git a/3rdparty/simple-protobuf/example/etl.cpp b/3rdparty/simple-protobuf/example/etl.cpp new file mode 100644 index 0000000..38e65c0 --- /dev/null +++ b/3rdparty/simple-protobuf/example/etl.cpp @@ -0,0 +1,22 @@ +#include "spb/pb.hpp" +#include +#include + +auto create_command( uint8_t arg ) -> ETL::Example::Command +{ + return { + .command_id = uint8_t( ETL::Example::Command::CMD::CMD_READ ), + .arg = arg, + .in_flag = 1, + .out_flag = 0, + }; +} + +auto main( int, char *[] ) -> int +{ + std::cout << "sizeof(Command): " << sizeof( ETL::Example::Command ) << std::endl; + std::cout << "sizeof(DeviceStatus): " << sizeof( ETL::Example::DeviceStatus ) << std::endl; + std::cout << "sizeof(CommandQueue): " << sizeof( ETL::Example::CommandQueue ) << std::endl; + + return 0; +} \ No newline at end of file diff --git a/3rdparty/simple-protobuf/example/proto/addressbook.proto b/3rdparty/simple-protobuf/example/proto/addressbook.proto new file mode 100644 index 0000000..51a0d17 --- /dev/null +++ b/3rdparty/simple-protobuf/example/proto/addressbook.proto @@ -0,0 +1,25 @@ +syntax = "proto2"; + +package tutorial; + +message Person { + optional string name = 1; + optional int32 id = 2; + optional string email = 3; + + enum PhoneType { + PHONE_TYPE_UNSPECIFIED = 0; + PHONE_TYPE_MOBILE = 1; + PHONE_TYPE_HOME = 2; + PHONE_TYPE_WORK = 3; + } + + message PhoneNumber { + optional string number = 1; + optional PhoneType type = 2 [ default = PHONE_TYPE_HOME ]; + } + + repeated PhoneNumber phones = 4; +} + +message AddressBook { repeated Person people = 1; } \ No newline at end of file diff --git a/3rdparty/simple-protobuf/example/proto/etl.proto b/3rdparty/simple-protobuf/example/proto/etl.proto new file mode 100644 index 0000000..1e6f944 --- /dev/null +++ b/3rdparty/simple-protobuf/example/proto/etl.proto @@ -0,0 +1,43 @@ + +syntax = "proto2"; + +package ETL.Example; + +message DeviceStatus { + //[[ field.type = "uint32:31"]] + required uint32 device_id = 1; + //[[ field.type = "uint32:1"]] + required uint32 is_online = 2; + required uint64 last_heartbeat = 3; + //[[ string.type = "std::array<$,8>" ]] + //[[ string.include = "" ]] + required string firmware_version = 4; + //[[ string.type = "etl::string<16>" ]] + //[[ string.include = "" ]] + required string name = 5; +} + +message Command { + //[[ enum.type = "uint8"]] + enum CMD { + CMD_RST = 1; + CMD_READ = 2; + CMD_WRITE = 3; + CMD_TST = 4; + } + //[[ field.type = "uint8:4"]] + required uint32 command_id = 1; // CMD + //[[ field.type = "uint8:2"]] + required uint32 arg = 2; // argument for the command + //[[ field.type = "uint8:1"]] + required uint32 in_flag = 3; // input flag + //[[ field.type = "uint8:1"]] + required uint32 out_flag = 4; // output flag +} + +//[[ repeated.type = "etl::vector<$,16>" ]] +//[[ repeated.include = "" ]] +message CommandQueue { + repeated Command commands = 1; + repeated DeviceStatus statuses = 2; +} diff --git a/3rdparty/simple-protobuf/include/spb/bits.h b/3rdparty/simple-protobuf/include/spb/bits.h new file mode 100644 index 0000000..6896e38 --- /dev/null +++ b/3rdparty/simple-protobuf/include/spb/bits.h @@ -0,0 +1,51 @@ +/***************************************************************************\ +* Name : bitfields * +* Description : bitfields checks * +* Author : antonin.kriz@gmail.com * +* ------------------------------------------------------------------------- * +* This is free software; you can redistribute it and/or modify it under the * +* terms of the MIT license. A copy of the license can be found in the file * +* "LICENSE" at the root of this distribution. * +\***************************************************************************/ +#pragma once + +#include +#include +#include +#include + +namespace spb::detail +{ +template < typename T > +concept signed_int = std::is_signed_v< T > && std::is_integral_v< T >; + +template < typename T > +concept unsigned_int = !std::is_signed_v< T > && std::is_integral_v< T >; + +static inline void check_if_value_fit_in_bits( signed_int auto value, uint32_t bits ) +{ + assert( sizeof( value ) * CHAR_BIT >= bits ); + assert( bits > 0 ); + + decltype( value ) max = ( 1LL << ( bits - 1 ) ) - 1; + decltype( value ) min = -( 1LL << ( bits - 1 ) ); + + if( ( value < min ) | ( value > max ) ) [[unlikely]] + { + throw std::runtime_error( "bitfield overflow" ); + } +} + +static inline void check_if_value_fit_in_bits( unsigned_int auto value, uint32_t bits ) +{ + assert( sizeof( value ) * CHAR_BIT >= bits ); + + decltype( value ) max = ( 1LL << bits ) - 1; + + if( value > max ) [[unlikely]] + { + throw std::runtime_error( "bitfield overflow" ); + } +} + +}// namespace spb::detail \ No newline at end of file diff --git a/3rdparty/simple-protobuf/include/spb/concepts.h b/3rdparty/simple-protobuf/include/spb/concepts.h new file mode 100644 index 0000000..d5c614d --- /dev/null +++ b/3rdparty/simple-protobuf/include/spb/concepts.h @@ -0,0 +1,97 @@ +/***************************************************************************\ +* Name : template concepts * +* Description : general template concepts used by protobuf de/serializer * +* Author : antonin.kriz@gmail.com * +* ------------------------------------------------------------------------- * +* This is free software; you can redistribute it and/or modify it under the * +* terms of the MIT license. A copy of the license can be found in the file * +* "LICENSE" at the root of this distribution. * +\***************************************************************************/ +#pragma once + +#include +#include +#include + +namespace spb +{ +template < class T > +concept resizable_container = requires( T container ) { + { container.data( ) } -> std::same_as< typename std::decay_t< T >::value_type * >; + { container.resize( 1 ) }; + typename std::decay_t< T >::value_type; + { sizeof( typename std::decay_t< T >::value_type ) == sizeof( char ) }; +}; + +template < class T > +concept size_container = requires( T container ) { + { container.data( ) }; + { container.size( ) } -> std::convertible_to< std::size_t >; + typename std::decay_t< T >::value_type; + { sizeof( typename std::decay_t< T >::value_type ) == sizeof( char ) }; +}; + +namespace detail +{ +template < class T > +concept proto_enum = std::is_enum_v< T >; + +template < class T > +concept proto_field_int_or_float = std::is_integral_v< T > || std::is_floating_point_v< T >; + +template < class T > +concept proto_field_number = proto_enum< T > || proto_field_int_or_float< T >; + +template < class T > +concept container = requires( T container ) { + { container.data( ) } -> std::same_as< typename std::decay_t< T >::value_type * >; + { container.size( ) } -> std::convertible_to< std::size_t >; + { container.begin( ) }; + { container.end( ) }; + typename std::decay_t< T >::value_type; +}; + +template < class T > +concept proto_field_bytes = + container< T > && std::is_same_v< typename std::decay_t< T >::value_type, std::byte >; + +template < class T > +concept proto_field_bytes_resizable = proto_field_bytes< T > && requires( T obj ) { + { obj.resize( 1 ) }; + { obj.clear( ) }; +}; + +template < class T > +concept proto_field_string = + container< T > && std::is_same_v< typename std::decay_t< T >::value_type, char >; + +template < class T > +concept proto_field_string_resizable = proto_field_string< T > && requires( T obj ) { + { obj.append( "1", 1 ) }; + { obj.clear( ) }; +}; + +template < class T > +concept proto_label_repeated = requires( T container ) { + { container.emplace_back( ) }; + { container.begin( ) }; + { container.end( ) }; + { container.clear( ) }; + typename T::value_type; +} && !proto_field_string< T > && !proto_field_bytes< T >; + +template < class T > +concept proto_label_optional = requires( T container ) { + { container.has_value( ) } -> std::convertible_to< bool >; + { container.reset( ) }; + { *container } -> std::same_as< typename T::value_type & >; + { container.emplace( typename T::value_type( ) ) } -> std::same_as< typename T::value_type & >; + typename T::value_type; +}; + +template < class T > +concept proto_message = std::is_class_v< T > && !proto_field_string< T > && + !proto_field_bytes< T > && !proto_label_repeated< T > && !proto_label_optional< T >; + +}// namespace detail +}// namespace spb \ No newline at end of file diff --git a/3rdparty/simple-protobuf/include/spb/io/buffer-io.hpp b/3rdparty/simple-protobuf/include/spb/io/buffer-io.hpp new file mode 100644 index 0000000..964a782 --- /dev/null +++ b/3rdparty/simple-protobuf/include/spb/io/buffer-io.hpp @@ -0,0 +1,87 @@ +/***************************************************************************\ +* Name : buffered reader * +* Description : buffer between io::reader and detail::istream * +* Author : antonin.kriz@gmail.com * +* ------------------------------------------------------------------------- * +* This is free software; you can redistribute it and/or modify it under the * +* terms of the MIT license. A copy of the license can be found in the file * +* "LICENSE" at the root of this distribution. * +\***************************************************************************/ + +#pragma once +#include "io.hpp" +#include +#include +#include +#include + +namespace spb::io +{ +#ifndef SPB_READ_BUFFER_SIZE +#define SPB_READ_BUFFER_SIZE 256U +#endif + +class buffered_reader +{ +private: + io::reader on_read; + std::array< char, SPB_READ_BUFFER_SIZE > buffer; + size_t begin_index = 0; + size_t end_index = 0; + bool eof_reached = false; + + auto bytes_in_buffer( ) const noexcept -> size_t + { + return end_index - begin_index; + } + + auto space_left_in_buffer( ) const noexcept -> size_t + { + return SPB_READ_BUFFER_SIZE - end_index; + } + + void shift_data_to_start( ) noexcept + { + if( begin_index > 0 ) + { + memmove( buffer.data( ), buffer.data( ) + begin_index, bytes_in_buffer( ) ); + end_index -= begin_index; + begin_index = 0; + } + } + + void read_buffer( ) + { + shift_data_to_start( ); + + while( bytes_in_buffer( ) < SPB_READ_BUFFER_SIZE && !eof_reached ) + { + auto bytes_in = on_read( &buffer[ end_index ], space_left_in_buffer( ) ); + eof_reached |= bytes_in == 0; + end_index += bytes_in; + } + } + +public: + explicit buffered_reader( io::reader reader ) + : on_read( reader ) + { + } + + [[nodiscard]] auto view( size_t minimal_size ) -> std::string_view + { + minimal_size = std::max< size_t >( minimal_size, 1U ); + if( bytes_in_buffer( ) < minimal_size ) + { + read_buffer( ); + } + return std::string_view( &buffer[ begin_index ], bytes_in_buffer( ) ); + } + + void skip( size_t size ) noexcept + { + begin_index += std::min( size, bytes_in_buffer( ) ); + } +}; + +}// namespace spb::io diff --git a/3rdparty/simple-protobuf/include/spb/io/function_ref.hpp b/3rdparty/simple-protobuf/include/spb/io/function_ref.hpp new file mode 100644 index 0000000..f21226d --- /dev/null +++ b/3rdparty/simple-protobuf/include/spb/io/function_ref.hpp @@ -0,0 +1,69 @@ +/***************************************************************************\ +* Name : function_ref * +* Description : non-owning reference to a callable * +* Author : LLVM * +* Reference : https://llvm.org/doxygen/classllvm_1_1function__ref_3_01Ret_07Params_8_8_8_08_4.html +* +* ------------------------------------------------------------------------- * +* This is free software; you can redistribute it and/or modify it under the * +* terms of the MIT license. A copy of the license can be found in the file * +* "LICENSE" at the root of this distribution. * +\***************************************************************************/ +#pragma once +#pragma once + +#include +#include +#include + +namespace spb::detail +{ + +template < typename Fn > +class function_ref; + +template < typename Ret, typename... Params > +class function_ref< Ret( Params... ) > +{ + Ret ( *callback )( intptr_t callable, Params... params ) = nullptr; + intptr_t callable; + + template < typename Callable > + static Ret callback_fn( intptr_t callable, Params... params ) + { + return ( *reinterpret_cast< Callable * >( callable ) )( + std::forward< Params >( params )... ); + } + +public: + function_ref( ) = default; + function_ref( std::nullptr_t ) + { + } + + template < typename Callable > + function_ref( Callable && callable, + // This is not the copy-constructor. + std::enable_if_t< !std::is_same< std::remove_cvref_t< Callable >, + function_ref >::value > * = nullptr, + // Functor must be callable and return a suitable type. + std::enable_if_t< std::is_void< Ret >::value || + std::is_convertible< decltype( std::declval< Callable >( )( + std::declval< Params >( )... ) ), + Ret >::value > * = nullptr ) + : callback( callback_fn< std::remove_reference_t< Callable > > ) + , callable( reinterpret_cast< intptr_t >( &callable ) ) + { + } + + Ret operator( )( Params... params ) const + { + return callback( callable, std::forward< Params >( params )... ); + } + + explicit operator bool( ) const + { + return callback; + } +}; +}// namespace spb::detail diff --git a/3rdparty/simple-protobuf/include/spb/io/io.hpp b/3rdparty/simple-protobuf/include/spb/io/io.hpp new file mode 100644 index 0000000..fb6f1fc --- /dev/null +++ b/3rdparty/simple-protobuf/include/spb/io/io.hpp @@ -0,0 +1,39 @@ +/***************************************************************************\ +* Name : generic reader and writer * +* Description : user specific input/output used for de/serialization * +* Author : antonin.kriz@gmail.com * +* ------------------------------------------------------------------------- * +* This is free software; you can redistribute it and/or modify it under the * +* terms of the MIT license. A copy of the license can be found in the file * +* "LICENSE" at the root of this distribution. * +\***************************************************************************/ + +#pragma once +#include "function_ref.hpp" +#include + +namespace spb::io +{ +/** + * @brief generic writer used to write exactly `size` number of bytes from `p_data` + * + * @param[in] p_data input buffer + * @param[in] size input buffer size + * @throws any exception thrown will stop the `serialize` process and will be propagated to the + * caller of `spb::pb::serialize` or `spb::json::serialize` + */ +using writer = spb::detail::function_ref< void( const void * p_data, size_t size ) >; + +//- +/** + * @brief generic reader used to read up to `size` number of bytes into `p_data` + * + * @param p_data output buffer (this is never nullptr) + * @param[in] size number of bytes to read (this is always > 0) + * @return number of bytes copied into `p_data`, could be less than `size`. 0 indicates end-of-file + * @throws any exception thrown will stop the `deserialize` process and will be propagated to the + * caller of `spb::pb::deserialize` or `spb::json::deserialize` + */ +using reader = spb::detail::function_ref< size_t( void * p_data, size_t size ) >; + +}// namespace spb::io diff --git a/3rdparty/simple-protobuf/include/spb/json.hpp b/3rdparty/simple-protobuf/include/spb/json.hpp new file mode 100644 index 0000000..5fd1275 --- /dev/null +++ b/3rdparty/simple-protobuf/include/spb/json.hpp @@ -0,0 +1,153 @@ +/***************************************************************************\ +* Name : Public API for JSON * +* Description : all json serialize and deserialize functions * +* Author : antonin.kriz@gmail.com * +* ------------------------------------------------------------------------- * +* This is free software; you can redistribute it and/or modify it under the * +* terms of the MIT license. A copy of the license can be found in the file * +* "LICENSE" at the root of this distribution. * +\***************************************************************************/ +#pragma once + +#include "spb/io/io.hpp" +#include "json/deserialize.hpp" +#include "json/serialize.hpp" +#include + +namespace spb::json +{ + +/** + * @brief serialize message via writer + * + * @param message to be serialized + * @param on_write function for handling the writes + * @return serialized size in bytes + * @throws exceptions only from `on_write` + */ +static inline auto serialize( const auto & message, spb::io::writer on_write ) -> size_t +{ + return detail::serialize( message, on_write ); +} + +/** + * @brief return json-string serialized size in bytes + * + * @param message to be serialized + * @return serialized size in bytes + */ +[[nodiscard]] static inline auto serialize_size( const auto & message ) -> size_t +{ + return serialize( message, spb::io::writer( nullptr ) ); +} + +/** + * @brief serialize message into json-string + * + * @param message to be serialized + * @return serialized json + * @throws std::runtime_error on error + * @example `auto serialized = std::vector< std::byte >();` + * `spb::json::serialize( message, serialized );` + */ +template < typename Message, spb::resizable_container Container > +static inline auto serialize( const Message & message, Container & result ) -> size_t +{ + const auto size = serialize_size( message ); + result.resize( size ); + auto writer = [ ptr = result.data( ) ]( const void * data, size_t size ) mutable + { + memcpy( ptr, data, size ); + ptr += size; + }; + + serialize( message, writer ); + return size; +} + +/** + * @brief serialize message into json + * + * @param[in] message to be serialized + * @return serialized json + * @throws std::runtime_error on error + * @example `auto serialized_message = spb::json::serialize( message );` + */ +template < spb::resizable_container Container = std::string, typename Message > +[[nodiscard]] static inline auto serialize( const Message & message ) -> Container +{ + auto result = Container( ); + serialize< Message, Container >( message, result ); + return result; +} + +/** + * @brief deserialize json-string into variable + * + * @param on_read function for handling reads + * @param result deserialized json + * @throws std::runtime_error on error + */ +static inline void deserialize( auto & result, spb::io::reader on_read ) +{ + return detail::deserialize( result, on_read ); +} + +/** + * @brief deserialize json-string into variable + * + * @param json string with json + * @param message deserialized json + * @throws std::runtime_error on error + * @example `auto serialized = std::string( ... );` + * `auto message = Message();` + * `spb::json::deserialize( message, serialized );` + */ +template < typename Message, spb::size_container Container > +static inline void deserialize( Message & message, const Container & json ) +{ + auto reader = [ ptr = json.data( ), end = json.data( ) + json.size( ) ]( + void * data, size_t size ) mutable -> size_t + { + size_t bytes_left = end - ptr; + + size = std::min( size, bytes_left ); + memcpy( data, ptr, size ); + ptr += size; + return size; + }; + return deserialize( message, reader ); +} + +/** + * @brief deserialize json-string into variable + * + * @param json string with json + * @return deserialized json or throw an exception + * @example `auto serialized = std::string( ... );` + * `auto message = spb::json::deserialize< Message >( serialized );` + */ +template < typename Message, spb::size_container Container > +[[nodiscard]] static inline auto deserialize( const Container & json ) -> Message +{ + auto message = Message{ }; + deserialize( message, json ); + return message; +} + +/** + * @brief deserialize json-string into variable + * + * @param on_read function for handling reads + * @return deserialized json + * @throws std::runtime_error on error + * @example `auto message = spb::json::deserialize< Message >( reader )` + */ +template < typename Message > +[[nodiscard]] static inline auto deserialize( spb::io::reader on_read ) -> Message +{ + auto message = Message{ }; + return deserialize( message, on_read ); +} + +}// namespace spb::json diff --git a/3rdparty/simple-protobuf/include/spb/json/base64.h b/3rdparty/simple-protobuf/include/spb/json/base64.h new file mode 100644 index 0000000..4f161dd --- /dev/null +++ b/3rdparty/simple-protobuf/include/spb/json/base64.h @@ -0,0 +1,217 @@ +/***************************************************************************\ +* Name : base64 library for json * +* Description : RFC 4648 base64 decoder and encoder * +* Author : antonin.kriz@gmail.com * +* ------------------------------------------------------------------------- * +* This is free software; you can redistribute it and/or modify it under the * +* terms of the MIT license. A copy of the license can be found in the file * +* "LICENSE" at the root of this distribution. * +\***************************************************************************/ + +#pragma once + +#include "../concepts.h" +#include +#include +#include +#include + +namespace spb::json::detail +{ +template < typename ostream > +static inline void base64_encode( ostream & output, std::span< const std::byte > input ) +{ + static constexpr char encode_table[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + const auto * p_char = reinterpret_cast< const uint8_t * >( input.data( ) ); + // + //- +3 means 3 bytes are being processed in one iteration (3 * 8 = 24 bits) + // + for( size_t idx = 3; idx <= input.size( ); idx += 3 ) + { + auto temp = uint32_t( *p_char++ ) << 16U; + temp += uint32_t( *p_char++ ) << 8U; + temp += ( *p_char++ ); + output.write( encode_table[ ( temp & 0x00FC0000U ) >> 18U ] ); + output.write( encode_table[ ( temp & 0x0003F000U ) >> 12U ] ); + output.write( encode_table[ ( temp & 0x00000FC0U ) >> 6U ] ); + output.write( encode_table[ ( temp & 0x0000003FU ) ] ); + } + switch( input.size( ) % 3 ) + { + case 1: + { + auto temp = uint32_t( *p_char++ ) << 16U; + output.write( encode_table[ ( temp & 0x00FC0000U ) >> 18U ] ); + output.write( encode_table[ ( temp & 0x0003F000U ) >> 12U ] ); + output.write( '=' ); + output.write( '=' ); + } + break; + case 2: + { + auto temp = uint32_t( *p_char++ ) << 16U; + temp += uint32_t( *p_char++ ) << 8U; + output.write( encode_table[ ( temp & 0x00FC0000 ) >> 18 ] ); + output.write( encode_table[ ( temp & 0x0003F000 ) >> 12 ] ); + output.write( encode_table[ ( temp & 0x00000FC0 ) >> 6 ] ); + output.write( '=' ); + } + break; + } +} + +template < typename istream > +static inline void base64_decode_string( spb::detail::proto_field_bytes auto & output, + istream & stream ) +{ + static constexpr uint8_t decode_table[ 256 ] = { + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 62, 128, 128, 128, 63, 52, 53, 54, 55, 56, 57, + 58, 59, 60, 61, 128, 128, 128, 128, 128, 128, 128, 0, 1, 2, 3, 4, 5, 6, + 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 128, 128, 128, 128, 128, 128, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, + 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128 + }; + + /*static constexpr uint8_t decode_table2[] = { + 62, 128, 128, 128, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 128, 128, 128, 128, 128, 128, + 128, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 128, 128, 128, 128, 128, 128, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 + };*/ + + if constexpr( spb::detail::proto_field_bytes_resizable< decltype( output ) > ) + { + output.clear( ); + } + if( stream.current_char( ) != '"' ) [[unlikely]] + { + throw std::runtime_error( "expecting '\"'" ); + } + + stream.consume_current_char( false ); + if( stream.consume( '"' ) ) + { + return; + } + auto mask = uint8_t( 0 ); + + for( auto out_index = size_t( 0 );; ) + { + auto view = stream.view( UINT32_MAX ); + auto length = view.find( '"' ); + auto end_found = length < view.npos; + if( ( end_found && length % 4 != 0 ) || view.size( ) <= 4 ) [[unlikely]] + { + throw std::runtime_error( "invalid base64" ); + } + length = std::min( length, view.size( ) ); + + //- align to 4 bytes + auto aligned_length = length & ~3; + if( aligned_length > 4 ) [[likely]] + { + auto out_length = ( ( aligned_length - 4 ) / 4 ) * 3; + view = view.substr( 0, aligned_length ); + + if constexpr( spb::detail::proto_field_bytes_resizable< decltype( output ) > ) + { + output.resize( output.size( ) + out_length ); + } + else + { + if( out_length > ( output.size( ) - out_index ) ) + { + throw std::runtime_error( "too large base64" ); + } + } + + auto * p_out = output.data( ) + out_index; + const auto * p_in = reinterpret_cast< const uint8_t * >( view.data( ) ); + const auto * p_end = + p_in + aligned_length - 4;//- exclude the last 4 chars (possible padding) + + while( p_in < p_end ) [[likely]] + { + uint8_t v0 = decode_table[ *p_in++ ]; + uint8_t v1 = decode_table[ *p_in++ ]; + uint8_t v2 = decode_table[ *p_in++ ]; + uint8_t v3 = decode_table[ *p_in++ ]; + mask |= ( v0 | v1 | v2 | v3 ); + + *p_out++ = std::byte( ( v0 << 2 ) | ( v1 >> 4 ) ); + *p_out++ = std::byte( ( v1 << 4 ) | ( v2 >> 2 ) ); + *p_out++ = std::byte( ( v2 << 6 ) | ( v3 ) ); + + out_index += 3; + } + auto consumed_bytes = p_in - reinterpret_cast< const uint8_t * >( view.data( ) ); + view.remove_prefix( consumed_bytes ); + stream.skip( consumed_bytes ); + } + + if( end_found ) + { + //- handle padding + const auto * p_in = reinterpret_cast< const uint8_t * >( view.data( ) ); + + uint8_t v0 = decode_table[ *p_in++ ]; + uint8_t v1 = decode_table[ *p_in++ ]; + auto i1 = *p_in++; + uint8_t v2 = i1 == '=' ? 0 : decode_table[ i1 ]; + auto i2 = *p_in++; + uint8_t v3 = i2 == '=' ? 0 : decode_table[ i2 ]; + mask |= ( v0 | v1 | v2 | v3 ); + mask |= ( ( i1 == '=' ) & ( i2 != '=' ) ) ? 128 : 0; + if( mask & 128 ) [[unlikely]] + { + throw std::runtime_error( "invalid base64" ); + } + + auto padding_size = ( i1 == '=' ? 1 : 0 ) + ( i2 == '=' ? 1 : 0 ); + auto consumed_bytes = 3 - padding_size; + //- +1 is for " + stream.skip( 5 ); + if constexpr( spb::detail::proto_field_bytes_resizable< decltype( output ) > ) + { + output.resize( output.size( ) + consumed_bytes ); + } + else + { + if( output.size( ) != out_index + consumed_bytes ) + { + throw std::runtime_error( "too large base64" ); + } + } + auto * p_out = output.data( ) + out_index; + if( padding_size == 0 ) + { + *p_out++ = std::byte( ( v0 << 2 ) | ( v1 >> 4 ) ); + *p_out++ = std::byte( ( v1 << 4 ) | ( v2 >> 2 ) ); + *p_out++ = std::byte( ( v2 << 6 ) | ( v3 ) ); + } + else if( padding_size == 1 ) + { + *p_out++ = std::byte( ( v0 << 2 ) | ( v1 >> 4 ) ); + *p_out++ = std::byte( ( v1 << 4 ) | ( v2 >> 2 ) ); + } + else if( padding_size == 2 ) + { + *p_out++ = std::byte( ( v0 << 2 ) | ( v1 >> 4 ) ); + } + return; + } + } +} +}// namespace spb::json::detail diff --git a/3rdparty/simple-protobuf/include/spb/json/deserialize.hpp b/3rdparty/simple-protobuf/include/spb/json/deserialize.hpp new file mode 100644 index 0000000..3c0173c --- /dev/null +++ b/3rdparty/simple-protobuf/include/spb/json/deserialize.hpp @@ -0,0 +1,845 @@ +/***************************************************************************\ +* Name : deserialize library for json * +* Description : all json deserialization functions * +* Author : antonin.kriz@gmail.com * +* ------------------------------------------------------------------------- * +* This is free software; you can redistribute it and/or modify it under the * +* terms of the MIT license. A copy of the license can be found in the file * +* "LICENSE" at the root of this distribution. * +\***************************************************************************/ + +#pragma once + +#include "../bits.h" +#include "../concepts.h" +#include "../to_from_chars.h" +#include "../utf8.h" +#include "base64.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace spb::json::detail +{ +using namespace std::literals; + +static const auto escape = '\\'; + +/** + * @brief helper for std::variant visit + * https://en.cppreference.com/w/cpp/utility/variant/visit + * + */ +template < class... Ts > +struct overloaded : Ts... +{ + using Ts::operator( )...; +}; +// explicit deduction guide (not needed as of C++20) +template < class... Ts > +overloaded( Ts... ) -> overloaded< Ts... >; + +/** + * @brief djb2_hash for strings + * http://www.cse.yorku.ca/~oz/hash.html + * + * @param str + * @return uint32_t + */ +static constexpr inline auto djb2_hash( std::string_view str ) noexcept -> uint32_t +{ + uint32_t hash = 5381U; + + for( auto c : str ) + { + hash = ( ( hash << 5U ) + hash ) + uint8_t( c ); /* hash * 33 + c */ + } + + return hash; +} + +static constexpr inline auto fnv1a_hash( std::string_view str ) noexcept -> uint64_t +{ + uint64_t hash = 14695981039346656037ULL; + const uint64_t prime = 1099511628211ULL; + + for( auto c : str ) + { + hash *= prime; + hash ^= c; + } + + return hash; +} + +template < spb::detail::proto_field_bytes T > +void clear( T & container ) +{ + if constexpr( spb::detail::proto_field_bytes_resizable< T > ) + { + container.clear( ); + } + else + { + std::fill( container.begin( ), container.end( ), typename T::value_type( ) ); + } +} + +struct istream +{ +private: + spb::io::buffered_reader reader; + + //- current char + int m_current = -1; + + std::string_view m_current_key; + + /** + * @brief gets the next char from the stream + * + * @param skip_white_space if true, skip white spaces + */ + void update_current( bool skip_white_space ) + { + for( ;; ) + { + auto view = reader.view( 1 ); + if( view.empty( ) ) + { + m_current = 0; + return; + } + m_current = view[ 0 ]; + if( !skip_white_space ) + { + return; + } + size_t spaces = 0; + for( auto c : view ) + { + if( !isspace( c ) ) + { + m_current = c; + reader.skip( spaces ); + return; + } + spaces += 1; + } + reader.skip( spaces ); + } + } + + [[nodiscard]] auto eof( ) -> bool + { + return current_char( ) == 0; + } + +public: + istream( spb::io::reader reader ) + : reader( reader ) + { + } + + void deserialize( auto & value ); + template < size_t ordinal, typename T > + void deserialize_variant( T & variant ); + template < typename T > + [[nodiscard]] auto deserialize_bitfield( uint32_t bits ) -> T; + [[nodiscard]] auto deserialize_int( ) -> int32_t; + [[nodiscard]] auto deserialize_string_or_int( size_t min_size, size_t max_size ) + -> std::variant< std::string_view, int32_t >; + [[nodiscard]] auto deserialize_key( size_t min_size, size_t max_size ) -> std::string_view; + [[nodiscard]] auto current_key( ) const -> std::string_view; + + [[nodiscard]] auto current_char( ) -> char + { + if( m_current < 0 ) + { + update_current( true ); + } + + return m_current; + } + /** + * @brief consumes `current char` if its equal to c + * + * @param c consumed char + * @return true if char was consumed + */ + [[nodiscard]] auto consume( char c ) -> bool + { + if( current_char( ) == c ) + { + consume_current_char( true ); + return true; + } + return false; + } + + /** + * @brief consumes an `token` + * + * @param token consumed `token` (whole word) + * @return true if `token` was consumed + */ + [[nodiscard]] auto consume( std::string_view token ) -> bool + { + assert( !token.empty( ) ); + + if( current_char( ) != token[ 0 ] ) + { + return false; + } + + if( !reader.view( token.size( ) ).starts_with( token ) ) + { + return false; + } + auto token_view = reader.view( token.size( ) + 1 ).substr( 0, token.size( ) + 1 ); + if( token_view.size( ) == token.size( ) || isspace( token_view.back( ) ) || + ( !isalnum( token_view.back( ) ) && token_view.back( ) != '_' ) ) + { + reader.skip( token.size( ) ); + update_current( true ); + return true; + } + return false; + } + + [[nodiscard]] auto view( size_t size ) -> std::string_view + { + auto result = reader.view( size ); + if( result.empty( ) ) + { + throw std::runtime_error( "unexpected end of stream" ); + } + return result; + } + + void consume_current_char( bool skip_white_space ) noexcept + { + reader.skip( 1 ); + update_current( skip_white_space ); + } + + void skip( size_t size ) + { + reader.skip( size ); + m_current = -1; + } + void skip_value( ); +}; + +static inline void deserialize( istream & stream, spb::detail::proto_enum auto & value ) +{ + deserialize_value( stream, value ); +} + +static inline void ignore_string( istream & stream ) +{ + if( stream.current_char( ) != '"' ) + { + throw std::runtime_error( "expecting '\"'" ); + } + + auto last = escape; + for( ;; ) + { + auto view = stream.view( UINT32_MAX ); + auto length = 0U; + for( auto current : view ) + { + length += 1; + if( current == '"' && last != escape ) + { + stream.skip( length ); + return; + } + //- handle \\" + last = current != escape || last != escape ? current : ' '; + } + stream.skip( view.size( ) ); + } +} + +static inline auto deserialize_string_view( istream & stream, size_t min_size, size_t max_size ) + -> std::string_view +{ + if( stream.current_char( ) != '"' ) + { + throw std::runtime_error( "expecting '\"'" ); + } + + //- +2 for '"' + auto view = stream.view( max_size + 2 ); + auto last = escape; + auto length = size_t( 0 ); + for( auto current : view ) + { + length += 1; + + if( current == '"' && last != escape ) + { + stream.skip( length ); + + if( ( length - 2 ) >= min_size && ( length - 2 ) <= max_size ) + { + return view.substr( 1, length - 2 ); + } + + return { }; + } + //- handle \\" + last = current != escape || last != escape ? current : ' '; + } + + ignore_string( stream ); + return { }; +} + +static inline auto unicode_from_hex( istream & stream ) -> uint16_t +{ + const auto esc_size = 4U; + auto unicode_view = stream.view( esc_size ); + if( unicode_view.size( ) < esc_size ) + { + throw std::runtime_error( "invalid escape sequence" ); + } + auto value = uint16_t( 0 ); + auto result = + spb_std_emu::from_chars( unicode_view.data( ), unicode_view.data( ) + esc_size, value, 16 ); + if( result.ec != std::errc{ } || result.ptr != unicode_view.data( ) + esc_size ) + { + throw std::runtime_error( "invalid escape sequence" ); + } + stream.skip( esc_size ); + return value; +} + +static inline auto unescape_unicode( istream & stream, char utf8[ 4 ] ) -> uint32_t +{ + auto value = uint32_t( unicode_from_hex( stream ) ); + if( value >= 0xD800 && value <= 0xDBFF && stream.view( 2 ).starts_with( "\\u"sv ) ) + { + stream.skip( 2 ); + auto low = unicode_from_hex( stream ); + + if( low < 0xDC00 || low > 0xDFFF ) + { + throw std::invalid_argument( "invalid escape sequence" ); + } + value = ( ( value - 0xD800 ) << 10 ) + ( low - 0xDC00 ) + 0x10000; + } + if( auto result = spb::detail::utf8::encode_point( value, utf8 ); result != 0 ) + { + return result; + } + throw std::runtime_error( "invalid escape sequence" ); +} +static inline auto unescape( istream & stream, char utf8[ 4 ] ) -> uint32_t +{ + auto c = stream.current_char( ); + stream.consume_current_char( false ); + switch( c ) + { + case '"': + utf8[ 0 ] = '"'; + return 1; + case '\\': + utf8[ 0 ] = '\\'; + return 1; + case '/': + utf8[ 0 ] = '/'; + return 1; + case 'b': + utf8[ 0 ] = '\b'; + return 1; + case 'f': + utf8[ 0 ] = '\f'; + return 1; + case 'n': + utf8[ 0 ] = '\n'; + return 1; + case 'r': + utf8[ 0 ] = '\r'; + return 1; + case 't': + utf8[ 0 ] = '\t'; + return 1; + case 'u': + return unescape_unicode( stream, utf8 ); + default: + throw std::runtime_error( "invalid escape sequence" ); + } +} + +static inline void deserialize( istream & stream, spb::detail::proto_field_string auto & value ) +{ + if( stream.current_char( ) != '"' ) + { + throw std::runtime_error( "expecting '\"'" ); + } + + stream.consume_current_char( false ); + + if constexpr( spb::detail::proto_field_string_resizable< decltype( value ) > ) + { + value.clear( ); + } + auto index = size_t( 0 ); + auto append_to_value = [ & ]( const char * str, size_t size ) + { + if constexpr( spb::detail::proto_field_string_resizable< decltype( value ) > ) + { + value.append( str, size ); + } + else + { + if( auto space_left = value.size( ) - index; size <= space_left ) [[likely]] + { + memcpy( value.data( ) + index, str, size ); + index += size; + } + else + { + throw std::runtime_error( "invalid string size" ); + } + } + }; + + for( ;; ) + { + auto view = stream.view( UINT32_MAX ); + auto found = view.find_first_of( R"("\)" ); + if( found == view.npos ) [[unlikely]] + { + append_to_value( view.data( ), view.size( ) ); + stream.skip( view.size( ) ); + continue; + } + + append_to_value( view.data( ), found ); + // +1 for '"' or '\' + stream.skip( found + 1 ); + if( view[ found ] == '"' ) [[likely]] + { + if constexpr( !spb::detail::proto_field_string_resizable< decltype( value ) > ) + { + if( index != value.size( ) ) + { + throw std::runtime_error( "invalid string size" ); + } + } + return; + } + char utf8_buffer[ 4 ]; + auto utf8_size = unescape( stream, utf8_buffer ); + append_to_value( utf8_buffer, utf8_size ); + } + spb::detail::utf8::validate( std::string_view( value.data( ), value.size( ) ) ); +} + +static inline void deserialize( istream & stream, + spb::detail::proto_field_int_or_float auto & value ) +{ + if( stream.current_char( ) == '"' ) [[unlikely]] + { + //- https://protobuf.dev/programming-guides/proto2/#json + //- number can be a string + auto view = deserialize_string_view( stream, 1, UINT32_MAX ); + auto result = spb_std_emu::from_chars( view.data( ), view.data( ) + view.size( ), value ); + if( result.ec != std::errc{ } ) + { + throw std::runtime_error( "invalid number" ); + } + return; + } + auto view = stream.view( UINT32_MAX ); + auto result = spb_std_emu::from_chars( view.data( ), view.data( ) + view.size( ), value ); + if( result.ec != std::errc{ } ) + { + throw std::runtime_error( "invalid number" ); + } + stream.skip( result.ptr - view.data( ) ); +} + +static inline void deserialize( istream & stream, bool & value ) +{ + if( stream.consume( "true"sv ) ) + { + value = true; + } + else if( stream.consume( "false"sv ) ) + { + value = false; + } + else + { + throw std::runtime_error( "expecting 'true' or 'false'" ); + } +} + +static inline void deserialize( istream & stream, auto & value ); + +template < typename keyT, typename valueT > +static inline void deserialize( istream & stream, std::map< keyT, valueT > & value ); + +static inline void deserialize( istream & stream, spb::detail::proto_label_optional auto & value ); + +template < spb::detail::proto_label_repeated C > +static inline void deserialize( istream & stream, C & value ) +{ + if( stream.consume( "null"sv ) ) + { + value.clear( ); + return; + } + + if( !stream.consume( '[' ) ) + { + throw std::runtime_error( "expecting '['" ); + } + + if( stream.consume( ']' ) ) + { + return; + } + + do + { + if constexpr( std::is_same_v< typename C::value_type, bool > ) + { + auto b = false; + deserialize( stream, b ); + value.push_back( b ); + } + else + { + deserialize( stream, value.emplace_back( ) ); + } + } while( stream.consume( ',' ) ); + + if( !stream.consume( ']' ) ) + { + throw std::runtime_error( "expecting ']'" ); + } +} + +static inline void deserialize( istream & stream, spb::detail::proto_field_bytes auto & value ) +{ + if( stream.consume( "null"sv ) ) + { + clear( value ); + return; + } + + base64_decode_string( value, stream ); +} + +template < typename T > +void deserialize_map_key( istream & stream, T & map_key ) +{ + if constexpr( std::is_same_v< T, std::string > ) + { + return deserialize( stream, map_key ); + } + auto str_key_map = deserialize_string_view( stream, 1, UINT32_MAX ); + auto reader = [ ptr = str_key_map.data( ), end = str_key_map.data( ) + str_key_map.size( ) ]( + void * data, size_t size ) mutable -> size_t + { + size_t bytes_left = end - ptr; + size = std::min( size, bytes_left ); + memcpy( data, ptr, size ); + ptr += size; + return size; + }; + auto key_stream = istream( reader ); + deserialize( key_stream, map_key ); +} + +template < typename keyT, typename valueT > +static inline void deserialize( istream & stream, std::map< keyT, valueT > & value ) +{ + if( stream.consume( "null"sv ) ) + { + value.clear( ); + return; + } + if( !stream.consume( '{' ) ) + { + throw std::runtime_error( "expecting '{'" ); + } + + if( stream.consume( '}' ) ) + { + return; + } + + do + { + auto map_key = keyT( ); + deserialize_map_key( stream, map_key ); + if( !stream.consume( ':' ) ) + { + throw std::runtime_error( "expecting ':'" ); + } + auto map_value = valueT( ); + deserialize( stream, map_value ); + value.emplace( std::move( map_key ), std::move( map_value ) ); + } while( stream.consume( ',' ) ); + + if( !stream.consume( '}' ) ) + { + throw std::runtime_error( "expecting '}'" ); + } +} + +static inline void deserialize( istream & stream, spb::detail::proto_label_optional auto & p_value ) +{ + if( stream.consume( "null"sv ) ) + { + p_value.reset( ); + return; + } + + if( p_value.has_value( ) ) + { + deserialize( stream, *p_value ); + } + else + { + deserialize( + stream, + p_value.emplace( typename std::decay_t< decltype( p_value ) >::value_type( ) ) ); + } +} + +template < typename T > +static inline void deserialize( istream & stream, std::unique_ptr< T > & value ) +{ + if( stream.consume( "null"sv ) ) + { + value.reset( ); + return; + } + + if( value ) + { + deserialize( stream, *value ); + } + else + { + value = std::make_unique< T >( ); + deserialize( stream, *value ); + } +} + +static inline void ignore_value( istream & stream ); +static inline void ignore_key_and_value( istream & stream ) +{ + ignore_string( stream ); + if( !stream.consume( ':' ) ) + { + throw std::runtime_error( "expecting ':'" ); + } + ignore_value( stream ); +} + +static inline void ignore_object( istream & stream ) +{ + //- '{' was already checked by caller + stream.consume_current_char( true ); + + if( stream.consume( '}' ) ) + { + return; + } + + do + { + ignore_key_and_value( stream ); + } while( stream.consume( ',' ) ); + + if( !stream.consume( '}' ) ) + { + throw std::runtime_error( "expecting '}'" ); + } +} + +static inline void ignore_array( istream & stream ) +{ + //- '[' was already checked by caller + stream.consume_current_char( true ); + + if( stream.consume( ']' ) ) + { + return; + } + + do + { + ignore_value( stream ); + } while( stream.consume( ',' ) ); + + if( !stream.consume( ']' ) ) + { + throw std::runtime_error( "expecting ']" ); + } +} + +static inline void ignore_number( istream & stream ) +{ + auto value = double{ }; + deserialize( stream, value ); +} + +static inline void ignore_bool( istream & stream ) +{ + auto value = bool{ }; + deserialize( stream, value ); +} + +static inline void ignore_null( istream & stream ) +{ + if( !stream.consume( "null"sv ) ) + { + throw std::runtime_error( "expecting 'null'" ); + } +} + +static inline void ignore_value( istream & stream ) +{ + switch( stream.current_char( ) ) + { + case '{': + return ignore_object( stream ); + case '[': + return ignore_array( stream ); + case '"': + return ignore_string( stream ); + case 'n': + return ignore_null( stream ); + case 't': + case 'f': + return ignore_bool( stream ); + default: + return ignore_number( stream ); + } +} + +template < typename T > +inline auto deserialize_bitfield( istream & stream, uint32_t bits ) -> T +{ + auto value = T( ); + deserialize( stream, value ); + spb::detail::check_if_value_fit_in_bits( value, bits ); + return value; +} + +template < size_t ordinal, typename T > +static inline void deserialize_variant( istream & stream, T & variant ) +{ + deserialize( stream, variant.template emplace< ordinal >( ) ); +} + +static inline void deserialize( istream & stream, auto & value ) +{ + if( !stream.consume( '{' ) ) + { + throw std::runtime_error( "expecting '{'" ); + } + + if( stream.consume( '}' ) ) + { + return; + } + + for( ;; ) + { + // + //- deserialize_value is generated by the sprotoc + // + deserialize_value( stream, value ); + + if( stream.consume( ',' ) ) + { + continue; + } + + if( stream.consume( '}' ) ) + { + return; + } + + throw std::runtime_error( "expecting '}' or ','" ); + } +} + +inline auto istream::deserialize_key( size_t min_size, size_t max_size ) -> std::string_view +{ + m_current_key = deserialize_string_view( *this, min_size, max_size ); + if( !consume( ':' ) ) + { + throw std::runtime_error( "expecting ':'" ); + } + return m_current_key; +} + +inline void istream::deserialize( auto & value ) +{ + return detail::deserialize( *this, value ); +} + +template < size_t ordinal, typename T > +inline void istream::deserialize_variant( T & variant ) +{ + return detail::deserialize_variant< ordinal >( *this, variant ); +} + +template < typename T > +inline auto istream::deserialize_bitfield( uint32_t bits ) -> T +{ + return detail::deserialize_bitfield< T >( *this, bits ); +} + +inline auto istream::deserialize_string_or_int( size_t min_size, size_t max_size ) + -> std::variant< std::string_view, int32_t > +{ + if( current_char( ) == '"' ) + { + return deserialize_string_view( *this, min_size, max_size ); + } + return deserialize_int( ); +} + +inline auto istream::deserialize_int( ) -> int32_t +{ + auto result = int32_t{ }; + detail::deserialize( *this, result ); + return result; +} + +inline void istream::skip_value( ) +{ + return detail::ignore_value( *this ); +} + +static inline void deserialize( auto & value, spb::io::reader reader ) +{ + auto stream = detail::istream( reader ); + return detail::deserialize( stream, value ); +} + +}// namespace spb::json::detail diff --git a/3rdparty/simple-protobuf/include/spb/json/serialize.hpp b/3rdparty/simple-protobuf/include/spb/json/serialize.hpp new file mode 100644 index 0000000..69068c7 --- /dev/null +++ b/3rdparty/simple-protobuf/include/spb/json/serialize.hpp @@ -0,0 +1,417 @@ +/***************************************************************************\ +* Name : serialize library for json * +* Description : all json serialization functions * +* Author : antonin.kriz@gmail.com * +* ------------------------------------------------------------------------- * +* This is free software; you can redistribute it and/or modify it under the * +* terms of the MIT license. A copy of the license can be found in the file * +* "LICENSE" at the root of this distribution. * +\***************************************************************************/ + +#pragma once + +#include "../concepts.h" + +#include "../to_from_chars.h" +#include "base64.h" +#include "spb/json/deserialize.hpp" +#include "spb/utf8.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace spb::json::detail +{ +struct ostream +{ +private: + size_t bytes_written = 0; + spb::io::writer on_write; + +public: + //- flag if put ',' before value + bool put_comma = false; + + /** + * @brief Construct a new ostream object + * + * @param writer if null, stream will skip all writes but will still count number of written + * chars + */ + explicit ostream( spb::io::writer writer ) noexcept + : on_write( writer ) + { + } + + void write( char c ) noexcept + { + if( on_write ) + { + on_write( &c, sizeof( c ) ); + } + + bytes_written += sizeof( c ); + } + + void write_unicode( uint32_t codepoint ) + { + if( codepoint <= 0xffff ) + { + char buffer[ 8 ] = { }; + auto size = snprintf( buffer, sizeof( buffer ), "\\u%04x", codepoint ); + return write( std::string_view( buffer, size ) ); + } + if( codepoint <= 0x10FFFF ) + { + codepoint -= 0x10000; + + auto high = static_cast< uint16_t >( ( codepoint >> 10 ) + 0xD800 ); + auto low = static_cast< uint16_t >( ( codepoint & 0x3FF ) + 0xDC00 ); + char buffer[ 16 ] = { }; + auto size = snprintf( buffer, sizeof( buffer ), "\\u%04x\\u%04x", high, low ); + return write( std::string_view( buffer, size ) ); + } + throw std::invalid_argument( "invalid utf8" ); + } + + void write( std::string_view str ) + { + if( on_write ) + { + on_write( str.data( ), str.size( ) ); + } + + bytes_written += str.size( ); + } + + void write_escaped( std::string_view str ) + { + if( !has_escape_chars( str ) ) + { + write( str ); + return; + } + + using namespace std::literals; + uint32_t codepoint = 0; + uint32_t state = spb::detail::utf8::ok; + bool decoding_utf8 = false; + for( uint8_t c : str ) + { + if( decoding_utf8 ) + { + if( spb::detail::utf8::decode_point( &state, &codepoint, c ) == + spb::detail::utf8::ok ) + { + write_unicode( codepoint ); + decoding_utf8 = false; + } + continue; + } + if( is_escape( c ) ) + { + switch( c ) + { + case '"': + write( R"(\")"sv ); + break; + case '\\': + write( R"(\\)"sv ); + break; + case '\b': + write( R"(\b)"sv ); + break; + case '\f': + write( R"(\f)"sv ); + break; + case '\n': + write( R"(\n)"sv ); + break; + case '\r': + write( R"(\r)"sv ); + break; + case '\t': + write( R"(\t)"sv ); + break; + default: + decoding_utf8 = true; + if( spb::detail::utf8::decode_point( &state, &codepoint, c ) == + spb::detail::utf8::ok ) + { + write_unicode( codepoint ); + decoding_utf8 = false; + } + } + } + else + { + write( c ); + } + } + if( state != spb::detail::utf8::ok ) + { + throw std::runtime_error( "invalid utf8" ); + } + } + + void serialize( std::string_view key, const auto & value ); + void serialize( std::string_view value ); + + [[nodiscard]] auto size( ) const noexcept -> size_t + { + return bytes_written; + } + +private: + static auto is_escape( uint8_t c ) -> bool + { + static constexpr std::string_view escape_chars = "\\\"\b\f\n\r\t<>"; + return c <= 0x1f || c >= 0x7f || escape_chars.find( c ) != std::string_view::npos; + } + + static auto has_escape_chars( std::string_view str ) -> bool + { + return std::any_of( str.begin( ), str.end( ), is_escape ); + } +}; + +using namespace std::literals; + +static inline void serialize_key( ostream & stream, std::string_view key ) +{ + if( stream.put_comma ) + { + stream.write( ',' ); + } + stream.put_comma = true; + + if( !key.empty( ) ) + { + stream.write( '"' ); + stream.write_escaped( key ); + stream.write( R"(":)"sv ); + } +} + +static inline void serialize( ostream & stream, std::string_view key, const bool & value ); +static inline void serialize( ostream & stream, std::string_view key, + const spb::detail::proto_field_int_or_float auto & value ); +static inline void serialize( ostream & stream, std::string_view key, + const spb::detail::proto_message auto & value ); +static inline void serialize( ostream & stream, std::string_view key, + const spb::detail::proto_enum auto & value ); +static inline void serialize( ostream & stream, std::string_view key, + const spb::detail::proto_field_string auto & value ); +static inline void serialize( ostream & stream, std::string_view key, + const spb::detail::proto_field_bytes auto & value ); +static inline void serialize( ostream & stream, std::string_view key, + const spb::detail::proto_label_repeated auto & value ); +template < typename keyT, typename valueT > +static inline void serialize( ostream & stream, std::string_view key, + const std::map< keyT, valueT > & map ); + +static inline void serialize( ostream & stream, bool value ); +static inline void serialize( ostream & stream, spb::detail::proto_field_int_or_float auto value ); +static inline void serialize( ostream & stream, + const spb::detail::proto_field_string auto & value ); + +static inline void serialize( ostream & stream, bool value ) +{ + stream.write( value ? "true"sv : "false"sv ); +} + +static inline void serialize( ostream & stream, const std::string_view & value ) +{ + stream.write( '"' ); + stream.write_escaped( value ); + stream.write( '"' ); +} + +static inline void serialize( ostream & stream, const spb::detail::proto_field_string auto & value ) +{ + serialize( stream, std::string_view( value.data( ), value.size( ) ) ); +} + +static inline void serialize( ostream & stream, spb::detail::proto_field_int_or_float auto value ) +{ + auto buffer = std::array< char, 32 >( ); + + auto result = spb_std_emu::to_chars( buffer.data( ), buffer.data( ) + sizeof( buffer ), value ); + stream.write( + std::string_view( buffer.data( ), static_cast< size_t >( result.ptr - buffer.data( ) ) ) ); +} + +static inline void serialize( ostream & stream, std::string_view key, const bool & value ) +{ + serialize_key( stream, key ); + serialize( stream, value ); +} + +static inline void serialize( ostream & stream, std::string_view key, + const spb::detail::proto_field_int_or_float auto & value ) +{ + serialize_key( stream, key ); + serialize( stream, value ); +} + +static inline void serialize( ostream & stream, std::string_view key, + const spb::detail::proto_field_string auto & value ) +{ + if( !value.empty( ) ) + { + serialize_key( stream, key ); + serialize( stream, value ); + } +} + +static inline void serialize( ostream & stream, std::string_view key, + const spb::detail::proto_field_bytes auto & value ) +{ + if( !value.empty( ) ) + { + serialize_key( stream, key ); + stream.write( '"' ); + base64_encode( stream, value ); + stream.write( '"' ); + } +} +static inline void serialize( ostream & stream, std::string_view key, + const spb::detail::proto_label_repeated auto & value ) +{ + if( value.empty( ) ) + { + return; + } + + serialize_key( stream, key ); + stream.write( '[' ); + stream.put_comma = false; + for( const auto & v : value ) + { + if constexpr( std::is_same_v< typename std::decay_t< decltype( value ) >::value_type, + bool > ) + { + serialize( stream, { }, bool( v ) ); + } + else + { + serialize( stream, { }, v ); + } + } + stream.write( ']' ); + stream.put_comma = true; +} + +static constexpr std::string_view no_name = { }; + +template < typename keyT, typename valueT > +static inline void serialize( ostream & stream, std::string_view key, + const std::map< keyT, valueT > & map ) +{ + if( map.empty( ) ) + { + return; + } + serialize_key( stream, key ); + stream.write( '{' ); + stream.put_comma = false; + for( auto & [ map_key, map_value ] : map ) + { + if constexpr( std::is_same_v< keyT, std::string_view > || + std::is_same_v< keyT, std::string > ) + { + serialize_key( stream, map_key ); + } + else + { + if( stream.put_comma ) + { + stream.write( ',' ); + } + + stream.write( '"' ); + serialize( stream, map_key ); + stream.write( R"(":)"sv ); + } + stream.put_comma = false; + serialize( stream, no_name, map_value ); + stream.put_comma = true; + } + stream.write( '}' ); + stream.put_comma = true; +} + +static inline void serialize( ostream & stream, std::string_view key, + const spb::detail::proto_label_optional auto & p_value ) +{ + if( p_value.has_value( ) ) + { + return serialize( stream, key, *p_value ); + } +} + +template < typename T > +static inline void serialize( ostream & stream, std::string_view key, + const std::unique_ptr< T > & p_value ) +{ + if( p_value ) + { + return serialize( stream, key, *p_value ); + } +} + +static inline void serialize( ostream & stream, std::string_view key, + const spb::detail::proto_message auto & value ) +{ + serialize_key( stream, key ); + stream.write( '{' ); + stream.put_comma = false; + + // + //- serialize_value is generated by the spb-protoc + // + serialize_value( stream, value ); + stream.write( '}' ); + stream.put_comma = true; +} + +static inline void serialize( ostream & stream, std::string_view key, + const spb::detail::proto_enum auto & value ) +{ + serialize_key( stream, key ); + + // + //- serialize_value is generated by the spb-protoc + // + serialize_value( stream, value ); +} + +static inline auto serialize( const auto & value, spb::io::writer on_write ) -> size_t +{ + auto stream = ostream( on_write ); + serialize( stream, no_name, value ); + return stream.size( ); +} + +void ostream::serialize( std::string_view key, const auto & value ) +{ + detail::serialize( *this, key, value ); +} + +inline void ostream::serialize( std::string_view value ) +{ + detail::serialize( *this, value ); +} + +}// namespace spb::json::detail diff --git a/3rdparty/simple-protobuf/include/spb/pb.hpp b/3rdparty/simple-protobuf/include/spb/pb.hpp new file mode 100644 index 0000000..8c500d5 --- /dev/null +++ b/3rdparty/simple-protobuf/include/spb/pb.hpp @@ -0,0 +1,208 @@ +/***************************************************************************\ +* Name : Public API for protobuf * +* Description : all protobuf serialize and deserialize functions * +* Author : antonin.kriz@gmail.com * +* ------------------------------------------------------------------------- * +* This is free software; you can redistribute it and/or modify it under the * +* terms of the MIT license. A copy of the license can be found in the file * +* "LICENSE" at the root of this distribution. * +\***************************************************************************/ +#pragma once + +#include "concepts.h" +#include "pb/deserialize.hpp" +#include "pb/serialize.hpp" +#include "spb/io/io.hpp" +#include + +namespace spb::pb +{ + +struct serialize_options +{ + /** + * @brief Writes the size of the message (as a varint) before the message itself. + * Compatible with Google's `writeDelimitedTo` and NanoPb's PB_ENCODE_DELIMITED. + */ + bool delimited = false; +}; + +struct deserialize_options +{ + /** + * @brief Expect the size of the message (encoded as a varint) to come before the message + * itself. Compatible with Google's `parseDelimitedFrom` and NanoPb's PB_DECODE_DELIMITED. Will + * return after having read the specified length; the spb::io::reader object can then be read + * from again to get the next message (if any). + */ + bool delimited = false; +}; + +/** + * @brief serialize message via writer + * + * @param[in] message to be serialized + * @param[in] on_write function for handling the writes + * @param[in] options + * @return serialized size in bytes + * @throws exceptions only from `on_write` + */ +static inline auto serialize( const auto & message, spb::io::writer on_write, + const serialize_options & options = { } ) -> size_t +{ + auto stream = detail::ostream{ on_write }; + if( options.delimited ) + { + detail::serialize_varint( stream, detail::serialize_size( message ) ); + } + serialize( stream, message ); + return stream.size( ); +} + +/** + * @brief return protobuf serialized size in bytes + * + * @param[in] message to be serialized + * @param[in] options + * @return serialized size in bytes + */ +[[nodiscard]] static inline auto serialize_size( const auto & message, + const serialize_options & options = { } ) -> size_t +{ + return serialize( message, spb::io::writer( nullptr ), options ); +} + +/** + * @brief serialize message into protobuf + * + * @param[in] message to be serialized + * @param[in] options + * @param[out] result serialized protobuf + * @return serialized size in bytes + * @throws std::runtime_error on error + * @example `auto serialized = std::vector< std::byte >();` + * `spb::pb::serialize( message, serialized );` + */ +template < typename Message, spb::resizable_container Container > +static inline auto serialize( const Message & message, Container & result, + const serialize_options & options = { } ) -> size_t +{ + const auto size = serialize_size( message, options ); + result.resize( size ); + auto writer = [ ptr = result.data( ) ]( const void * data, size_t size ) mutable + { + memcpy( ptr, data, size ); + ptr += size; + }; + + serialize( message, writer, options ); + return size; +} + +/** + * @brief serialize message into protobuf + * + * @param[in] message to be serialized + * @param[in] options + * @return serialized protobuf + * @throws std::runtime_error on error + * @example `auto serialized_message = spb::pb::serialize< std::vector< std::byte > >( message );` + */ +template < spb::resizable_container Container = std::string, typename Message > +[[nodiscard]] static inline auto serialize( const Message & message, + const serialize_options & options = { } ) -> Container +{ + auto result = Container( ); + serialize< Message, Container >( message, result, options ); + return result; +} + +/** + * @brief deserialize message from protobuf + * + * @param[in] reader function for handling reads + * @param[in] options + * @param[out] message deserialized message + * @throws std::runtime_error on error + */ +static inline void deserialize( auto & message, spb::io::reader reader, + const deserialize_options & options = { } ) +{ + detail::istream stream{ reader }; + if( options.delimited ) + { + const auto substream_length = read_varint< uint32_t >( stream ); + auto substream = stream.sub_stream( substream_length ); + return deserialize_main( substream, message ); + } + else + { + return deserialize_main( stream, message ); + } +} + +/** + * @brief deserialize message from protobuf + * + * @param[in] protobuf string with protobuf + * @param[in] options + * @param[out] message deserialized message + * @throws std::runtime_error on error + * @example `auto serialized = std::vector< std::byte >( ... );` + * `auto message = Message();` + * `spb::pb::deserialize( message, serialized );` + */ +template < typename Message, spb::size_container Container > +static inline void deserialize( Message & message, const Container & protobuf, + const deserialize_options & options = { } ) +{ + auto reader = [ ptr = protobuf.data( ), end = protobuf.data( ) + protobuf.size( ) ]( + void * data, size_t size ) mutable -> size_t + { + size_t bytes_left = end - ptr; + + size = std::min( size, bytes_left ); + memcpy( data, ptr, size ); + ptr += size; + return size; + }; + return deserialize( message, reader, options ); +} + +/** + * @brief deserialize message from protobuf + * + * @param[in] protobuf serialized protobuf + * @param[in] options + * @return deserialized message + * @throws std::runtime_error on error + * @example `auto serialized = std::vector< std::byte >( ... );` + * `auto message = spb::pb::deserialize< Message >( serialized );` + */ +template < typename Message, spb::size_container Container > +[[nodiscard]] static inline auto deserialize( const Container & protobuf, + const deserialize_options & options = { } ) -> Message +{ + auto message = Message{ }; + deserialize( message, protobuf, options ); + return message; +} + +/** + * @brief deserialize message from reader + * + * @param[in] reader function for handling reads + * @param[in] options + * @return deserialized message + * @throws std::runtime_error on error + */ +template < typename Message > +[[nodiscard]] static inline auto deserialize( spb::io::reader reader, + const deserialize_options & options = { } ) -> Message +{ + auto message = Message{ }; + deserialize( message, reader, options ); + return message; +} + +}// namespace spb::pb diff --git a/3rdparty/simple-protobuf/include/spb/pb/deserialize.hpp b/3rdparty/simple-protobuf/include/spb/pb/deserialize.hpp new file mode 100644 index 0000000..ad5415b --- /dev/null +++ b/3rdparty/simple-protobuf/include/spb/pb/deserialize.hpp @@ -0,0 +1,751 @@ +/***************************************************************************\ +* Name : deserialize library for protobuf * +* Description : all protobuf deserialization functions * +* Author : antonin.kriz@gmail.com * +* ------------------------------------------------------------------------- * +* This is free software; you can redistribute it and/or modify it under the * +* terms of the MIT license. A copy of the license can be found in the file * +* "LICENSE" at the root of this distribution. * +\***************************************************************************/ + +#pragma once + +#include "../bits.h" +#include "../concepts.h" +#include "../utf8.h" +#include "wire-types.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace spb::pb::detail +{ + +struct istream +{ +private: + spb::io::reader on_read; + size_t m_size; + +public: + istream( spb::io::reader reader, size_t size = std::numeric_limits< size_t >::max( ) ) noexcept + : on_read( reader ) + , m_size( size ) + { + } + + void skip( uint32_t tag ); + void read_skip( size_t size ); + + void deserialize( auto & value, uint32_t tag ); + + template < scalar_encoder encoder > + void deserialize_as( auto & value, uint32_t tag ); + + template < size_t ordinal, typename T > + void deserialize_variant( T & variant, uint32_t tag ); + + template < size_t ordinal, scalar_encoder encoder, typename T > + void deserialize_variant_as( T & variant, uint32_t tag ); + + template < scalar_encoder encoder, typename T > + auto deserialize_bitfield_as( uint32_t bits, uint32_t tag ) -> T; + + [[nodiscard]] auto read_byte( ) -> uint8_t + { + uint8_t result = { }; + read_exact( &result, sizeof( result ) ); + return result; + } + + [[nodiscard]] auto read_byte_or_eof( ) -> int + { + uint8_t result = { }; + if( on_read( &result, sizeof( result ) ) == 0 ) + { + return -1; + } + return result; + } + + [[nodiscard]] auto size( ) const -> size_t + { + return m_size; + } + + void read_exact( void * data, size_t size ) + { + if( this->size( ) < size ) [[unlikely]] + { + throw std::runtime_error( "unexpected end of stream" ); + } + + while( size > 0 ) + { + auto chunk_size = on_read( data, size ); + if( chunk_size == 0 ) + { + throw std::runtime_error( "unexpected end of stream" ); + } + + size -= chunk_size; + m_size -= chunk_size; + } + } + + [[nodiscard]] auto empty( ) const -> bool + { + return size( ) == 0; + } + + [[nodiscard]] auto sub_stream( size_t sub_size ) -> istream + { + if( size( ) < sub_size ) + { + throw std::runtime_error( "unexpected end of stream" ); + } + m_size -= sub_size; + return istream( on_read, sub_size ); + } +}; + +[[nodiscard]] static inline auto wire_type_from_tag( uint32_t tag ) -> wire_type +{ + return wire_type( tag & 0x07 ); +} + +[[nodiscard]] static inline auto field_from_tag( uint32_t tag ) -> uint32_t +{ + return tag >> 3; +} + +static inline void check_tag( uint32_t tag ) +{ + if( field_from_tag( tag ) == 0 ) + { + throw std::runtime_error( "invalid field id" ); + } +} + +static inline void check_wire_type( wire_type type1, wire_type type2 ) +{ + if( type1 != type2 ) + { + throw std::runtime_error( "invalid wire type" ); + } +} + +static inline void check_if_empty( istream & stream ) +{ + if( !stream.empty( ) ) + { + throw std::runtime_error( "unexpected data in stream" ); + } +} + +[[nodiscard]] static inline auto read_tag_or_eof( istream & stream ) -> uint32_t +{ + auto byte_or_eof = stream.read_byte_or_eof( ); + if( byte_or_eof < 0 ) + { + return 0; + } + auto byte = uint8_t( byte_or_eof ); + auto tag = uint32_t( byte & 0x7F ); + + for( size_t shift = CHAR_BIT - 1; ( byte & 0x80 ) != 0; shift += CHAR_BIT - 1 ) + { + if( shift >= sizeof( tag ) * CHAR_BIT ) + { + throw std::runtime_error( "invalid tag" ); + } + + byte = stream.read_byte( ); + tag |= uint64_t( byte & 0x7F ) << shift; + } + + check_tag( tag ); + + return tag; +} + +template < typename T > +[[nodiscard]] static inline auto read_varint( istream & stream ) -> T +{ + if constexpr( std::is_same_v< T, bool > ) + { + switch( stream.read_byte( ) ) + { + case 0: + return false; + case 1: + return true; + default: + throw std::runtime_error( "invalid varint for bool" ); + } + } + else + { + auto value = uint64_t( 0 ); + + for( auto shift = 0U; shift < sizeof( value ) * CHAR_BIT; shift += CHAR_BIT - 1 ) + { + uint8_t byte = stream.read_byte( ); + value |= uint64_t( byte & 0x7F ) << shift; + if( ( byte & 0x80 ) == 0 ) + { + if constexpr( std::is_signed_v< T > && sizeof( T ) < sizeof( value ) ) + { + //- GPB encodes signed varints always as 64-bits + //- so int32_t(-2) is encoded as "\xfe\xff\xff\xff\xff\xff\xff\xff\xff\x01", + // same as int64_t(-2) + //- but it should be encoded as "\xfe\xff\xff\xff\x0f" + value = T( value ); + } + auto result = T( value ); + if constexpr( std::is_signed_v< T > ) + { + if( result == std::make_signed_t< T >( value ) ) + { + return result; + } + } + else + { + if( result == value ) + { + return result; + } + } + + break; + } + } + throw std::runtime_error( "invalid varint" ); + } +} + +static inline void deserialize( istream & stream, spb::detail::proto_message auto & value, + wire_type type ); + +template < scalar_encoder encoder > +static inline void deserialize_as( istream & stream, + spb::detail::proto_field_int_or_float auto & value, + wire_type type ); +static inline void deserialize( istream & stream, spb::detail::proto_field_bytes auto & value, + wire_type type ); +static inline void deserialize( istream & stream, spb::detail::proto_label_repeated auto & value, + wire_type type ); +static inline void deserialize( istream & stream, spb::detail::proto_field_string auto & value, + wire_type type ); + +template < scalar_encoder encoder, spb::detail::proto_label_repeated C > +static inline void deserialize_as( istream & stream, C & value, wire_type type ); + +template < scalar_encoder encoder, typename keyT, typename valueT > +static inline void deserialize_as( istream & stream, std::map< keyT, valueT > & value, + wire_type type ); + +static inline void deserialize( istream & stream, spb::detail::proto_label_optional auto & p_value, + wire_type type ); + +template < scalar_encoder encoder, spb::detail::proto_label_optional C > +static inline void deserialize_as( istream & stream, C & p_value, wire_type type ); + +template < typename T > +static inline void deserialize( istream & stream, std::unique_ptr< T > & value, wire_type type ); + +template < typename T, typename signedT, typename unsignedT > +static auto create_tmp_var( ) +{ + if constexpr( std::is_signed< T >::value ) + { + return signedT( ); + } + else + { + return unsignedT( ); + } +} + +template < scalar_encoder encoder, typename T > +static inline auto deserialize_bitfield_as( istream & stream, uint32_t bits, wire_type type ) -> T +{ + auto value = T( ); + if constexpr( scalar_encoder_type1( encoder ) == scalar_encoder::svarint ) + { + check_wire_type( type, wire_type::varint ); + + auto tmp = read_varint< std::make_unsigned_t< T > >( stream ); + value = T( ( tmp >> 1 ) ^ ( ~( tmp & 1 ) + 1 ) ); + } + else if constexpr( scalar_encoder_type1( encoder ) == scalar_encoder::varint ) + { + check_wire_type( type, wire_type::varint ); + value = read_varint< T >( stream ); + } + else if constexpr( scalar_encoder_type1( encoder ) == scalar_encoder::i32 ) + { + static_assert( sizeof( T ) <= sizeof( uint32_t ) ); + + check_wire_type( type, wire_type::fixed32 ); + + if constexpr( sizeof( value ) == sizeof( uint32_t ) ) + { + stream.read_exact( &value, sizeof( value ) ); + } + else + { + auto tmp = create_tmp_var< T, int32_t, uint32_t >( ); + stream.read_exact( &tmp, sizeof( tmp ) ); + spb::detail::check_if_value_fit_in_bits( tmp, bits ); + value = T( tmp ); + } + } + else if constexpr( scalar_encoder_type1( encoder ) == scalar_encoder::i64 ) + { + static_assert( sizeof( T ) <= sizeof( uint64_t ) ); + check_wire_type( type, wire_type::fixed64 ); + + if constexpr( sizeof( value ) == sizeof( uint64_t ) ) + { + stream.read_exact( &value, sizeof( value ) ); + } + else + { + auto tmp = create_tmp_var< T, int64_t, uint64_t >( ); + stream.read_exact( &tmp, sizeof( tmp ) ); + spb::detail::check_if_value_fit_in_bits( tmp, bits ); + value = T( tmp ); + } + } + spb::detail::check_if_value_fit_in_bits( value, bits ); + return value; +} + +template < scalar_encoder encoder > +static inline void deserialize_as( istream & stream, spb::detail::proto_enum auto & value, + wire_type type ) +{ + using T = std::remove_cvref_t< decltype( value ) >; + using int_type = std::underlying_type_t< T >; + + if constexpr( !scalar_encoder_is_packed( encoder ) ) + { + check_wire_type( type, wire_type::varint ); + } + + value = T( read_varint< int_type >( stream ) ); +} + +template < scalar_encoder encoder > +static inline void deserialize_as( istream & stream, + spb::detail::proto_field_int_or_float auto & value, + wire_type type ) +{ + using T = std::remove_cvref_t< decltype( value ) >; + + if constexpr( scalar_encoder_type1( encoder ) == scalar_encoder::svarint ) + { + if constexpr( !scalar_encoder_is_packed( encoder ) ) + { + check_wire_type( type, wire_type::varint ); + } + auto tmp = read_varint< std::make_unsigned_t< T > >( stream ); + value = T( ( tmp >> 1 ) ^ ( ~( tmp & 1 ) + 1 ) ); + } + else if constexpr( scalar_encoder_type1( encoder ) == scalar_encoder::varint ) + { + if constexpr( !scalar_encoder_is_packed( encoder ) ) + { + check_wire_type( type, wire_type::varint ); + } + value = read_varint< T >( stream ); + } + else if constexpr( scalar_encoder_type1( encoder ) == scalar_encoder::i32 ) + { + static_assert( sizeof( T ) <= sizeof( uint32_t ) ); + + if constexpr( !scalar_encoder_is_packed( encoder ) ) + { + check_wire_type( type, wire_type::fixed32 ); + } + if constexpr( sizeof( value ) == sizeof( uint32_t ) ) + { + stream.read_exact( &value, sizeof( value ) ); + } + else + { + if constexpr( std::is_signed_v< T > ) + { + auto tmp = int32_t( 0 ); + stream.read_exact( &tmp, sizeof( tmp ) ); + if( tmp > std::numeric_limits< T >::max( ) || + tmp < std::numeric_limits< T >::min( ) ) + { + throw std::runtime_error( "int overflow" ); + } + value = T( tmp ); + } + else + { + auto tmp = uint32_t( 0 ); + stream.read_exact( &tmp, sizeof( tmp ) ); + if( tmp > std::numeric_limits< T >::max( ) ) + { + throw std::runtime_error( "int overflow" ); + } + value = T( tmp ); + } + } + } + else if constexpr( scalar_encoder_type1( encoder ) == scalar_encoder::i64 ) + { + static_assert( sizeof( T ) <= sizeof( uint64_t ) ); + if constexpr( !scalar_encoder_is_packed( encoder ) ) + { + check_wire_type( type, wire_type::fixed64 ); + } + if constexpr( sizeof( value ) == sizeof( uint64_t ) ) + { + stream.read_exact( &value, sizeof( value ) ); + } + else + { + if constexpr( std::is_signed_v< T > ) + { + auto tmp = int64_t( 0 ); + stream.read_exact( &tmp, sizeof( tmp ) ); + if( tmp > std::numeric_limits< T >::max( ) || + tmp < std::numeric_limits< T >::min( ) ) + { + throw std::runtime_error( "int overflow" ); + } + value = T( tmp ); + } + else + { + auto tmp = uint64_t( 0 ); + stream.read_exact( &tmp, sizeof( tmp ) ); + if( tmp > std::numeric_limits< T >::max( ) ) + { + throw std::runtime_error( "int overflow" ); + } + value = T( tmp ); + } + } + } +} + +static inline void deserialize( istream & stream, spb::detail::proto_label_optional auto & p_value, + wire_type type ) +{ + auto & value = p_value.emplace( typename std::decay_t< decltype( p_value ) >::value_type( ) ); + deserialize( stream, value, type ); +} + +template < scalar_encoder encoder, spb::detail::proto_label_optional C > +static inline void deserialize_as( istream & stream, C & p_value, wire_type type ) +{ + auto & value = p_value.emplace( typename C::value_type( ) ); + deserialize_as< encoder >( stream, value, type ); +} + +static inline void deserialize( istream & stream, spb::detail::proto_field_string auto & value, + wire_type type ) +{ + check_wire_type( type, wire_type::length_delimited ); + if constexpr( spb::detail::proto_field_string_resizable< decltype( value ) > ) + { + value.resize( stream.size( ) ); + } + else + { + if( value.size( ) != stream.size( ) ) + { + throw std::runtime_error( "invalid string size" ); + } + } + stream.read_exact( value.data( ), stream.size( ) ); + spb::detail::utf8::validate( std::string_view( value.data( ), value.size( ) ) ); +} + +template < typename T > +static inline void deserialize( istream & stream, std::unique_ptr< T > & value, wire_type type ) +{ + value = std::make_unique< T >( ); + deserialize( stream, *value, type ); +} + +static inline void deserialize( istream & stream, spb::detail::proto_field_bytes auto & value, + wire_type type ) +{ + check_wire_type( type, wire_type::length_delimited ); + if constexpr( spb::detail::proto_field_bytes_resizable< decltype( value ) > ) + { + value.resize( stream.size( ) ); + } + else + { + if( stream.size( ) != value.size( ) ) + { + throw std::runtime_error( "invalid bytes size" ); + } + } + stream.read_exact( value.data( ), stream.size( ) ); +} + +static inline void deserialize( istream & stream, spb::detail::proto_label_repeated auto & value, + wire_type type ) +{ + deserialize( stream, value.emplace_back( ), type ); +} + +template < scalar_encoder encoder, spb::detail::proto_label_repeated C > +static inline void deserialize_packed_as( istream & stream, C & value, wire_type type ) +{ + while( !stream.empty( ) ) + { + if constexpr( std::is_same_v< typename C::value_type, bool > ) + { + value.emplace_back( read_varint< bool >( stream ) ); + } + else + { + deserialize_as< encoder >( stream, value.emplace_back( ), type ); + } + } +} + +template < scalar_encoder encoder, spb::detail::proto_label_repeated C > +static inline void deserialize_as( istream & stream, C & value, wire_type type ) +{ + if constexpr( scalar_encoder_is_packed( encoder ) ) + { + deserialize_packed_as< encoder >( stream, value, wire_type_from_scalar_encoder( encoder ) ); + } + else + { + if constexpr( std::is_same_v< typename C::value_type, bool > ) + { + value.emplace_back( read_varint< bool >( stream ) ); + } + else + { + deserialize_as< encoder >( stream, value.emplace_back( ), type ); + } + } +} + +template < scalar_encoder encoder, typename keyT, typename valueT > +static inline void deserialize_as( istream & stream, std::map< keyT, valueT > & value, + wire_type type ) +{ + const auto key_encoder = scalar_encoder_type1( encoder ); + const auto value_encoder = scalar_encoder_type2( encoder ); + + check_wire_type( type, wire_type::length_delimited ); + + auto pair = std::pair< keyT, valueT >( ); + auto key_defined = false; + auto value_defined = false; + while( !stream.empty( ) ) + { + const auto tag = read_varint< uint32_t >( stream ); + const auto field = field_from_tag( tag ); + const auto field_type = wire_type_from_tag( tag ); + + switch( field ) + { + case 1: + if constexpr( std::is_integral_v< keyT > ) + { + deserialize_as< key_encoder >( stream, pair.first, field_type ); + } + else + { + if( field_type == wire_type::length_delimited ) + { + const auto size = read_varint< uint32_t >( stream ); + auto substream = stream.sub_stream( size ); + deserialize( substream, pair.first, field_type ); + check_if_empty( substream ); + } + else + { + deserialize( stream, pair.first, field_type ); + } + } + key_defined = true; + break; + case 2: + if constexpr( spb::detail::proto_field_number< valueT > ) + { + deserialize_as< value_encoder >( stream, pair.second, field_type ); + } + else + { + if( field_type == wire_type::length_delimited ) + { + const auto size = read_varint< uint32_t >( stream ); + auto substream = stream.sub_stream( size ); + deserialize( substream, pair.second, field_type ); + check_if_empty( substream ); + } + else + { + throw std::runtime_error( "invalid field" ); + } + } + value_defined = true; + break; + default: + throw std::runtime_error( "invalid field" ); + } + } + if( key_defined && value_defined ) + { + value.insert( std::move( pair ) ); + } + else + { + throw std::runtime_error( "invalid map item" ); + } +} + +template < size_t ordinal, typename T > +static inline void deserialize_variant( istream & stream, T & variant, wire_type type ) +{ + deserialize( stream, variant.template emplace< ordinal >( ), type ); +} + +template < size_t ordinal, scalar_encoder encoder, typename T > +static inline void deserialize_variant_as( istream & stream, T & variant, wire_type type ) +{ + deserialize_as< encoder >( stream, variant.template emplace< ordinal >( ), type ); +} + +static inline void deserialize_main( istream & stream, spb::detail::proto_message auto & value ) +{ + for( ;; ) + { + const auto tag = read_tag_or_eof( stream ); + if( !tag ) + { + break; + } + const auto field_type = wire_type_from_tag( tag ); + + if( field_type == wire_type::length_delimited ) + { + const auto size = read_varint< uint32_t >( stream ); + auto substream = stream.sub_stream( size ); + deserialize_value( substream, value, tag ); + check_if_empty( substream ); + } + else + { + deserialize_value( stream, value, tag ); + } + } +} + +static inline void deserialize( istream & stream, spb::detail::proto_message auto & value, + wire_type type ) +{ + check_wire_type( type, wire_type::length_delimited ); + + while( !stream.empty( ) ) + { + const auto tag = read_varint< uint32_t >( stream ); + const auto field_type = wire_type_from_tag( tag ); + + if( field_type == wire_type::length_delimited ) + { + const auto size = read_varint< uint32_t >( stream ); + auto substream = stream.sub_stream( size ); + deserialize_value( substream, value, tag ); + check_if_empty( substream ); + } + else + { + deserialize_value( stream, value, tag ); + } + } +} + +inline void istream::deserialize( auto & value, uint32_t tag ) +{ + detail::deserialize( *this, value, wire_type_from_tag( tag ) ); +} + +inline void istream::read_skip( size_t size ) +{ + uint8_t buffer[ 64 ]; + while( size > 0 ) + { + auto chunk_size = std::min( size, sizeof( buffer ) ); + read_exact( buffer, chunk_size ); + size -= chunk_size; + } +} + +inline void istream::skip( uint32_t tag ) +{ + switch( wire_type_from_tag( tag ) ) + { + case wire_type::varint: + return ( void ) read_varint< uint64_t >( *this ); + case wire_type::length_delimited: + return read_skip( size( ) ); + case wire_type::fixed32: + return read_skip( sizeof( uint32_t ) ); + case wire_type::fixed64: + return read_skip( sizeof( uint64_t ) ); + default: + throw std::runtime_error( "invalid wire type" ); + } +} + +template < scalar_encoder encoder > +inline void istream::deserialize_as( auto & value, uint32_t tag ) +{ + detail::deserialize_as< encoder >( *this, value, wire_type_from_tag( tag ) ); +} + +template < size_t ordinal, typename T > +inline void istream::deserialize_variant( T & variant, uint32_t tag ) +{ + detail::deserialize_variant< ordinal >( *this, variant, wire_type_from_tag( tag ) ); +} + +template < size_t ordinal, scalar_encoder encoder, typename T > +inline void istream::deserialize_variant_as( T & variant, uint32_t tag ) +{ + detail::deserialize_variant_as< ordinal, encoder >( *this, variant, wire_type_from_tag( tag ) ); +} + +template < scalar_encoder encoder, typename T > +inline auto istream::deserialize_bitfield_as( uint32_t bits, uint32_t tag ) -> T +{ + return detail::deserialize_bitfield_as< encoder, T >( *this, bits, wire_type_from_tag( tag ) ); +} + +static inline void deserialize( auto & value, spb::io::reader on_read ) +{ + using T = std::remove_cvref_t< decltype( value ) >; + static_assert( spb::detail::proto_message< T > ); + + auto stream = istream( on_read ); + deserialize_main( stream, value ); +} + +}// namespace spb::pb::detail diff --git a/3rdparty/simple-protobuf/include/spb/pb/serialize.hpp b/3rdparty/simple-protobuf/include/spb/pb/serialize.hpp new file mode 100644 index 0000000..5a3679c --- /dev/null +++ b/3rdparty/simple-protobuf/include/spb/pb/serialize.hpp @@ -0,0 +1,386 @@ +/***************************************************************************\ +* Name : serialize library for protobuf * +* Description : all protobuf serialization functions * +* Author : antonin.kriz@gmail.com * +* ------------------------------------------------------------------------- * +* This is free software; you can redistribute it and/or modify it under the * +* terms of the MIT license. A copy of the license can be found in the file * +* "LICENSE" at the root of this distribution. * +\***************************************************************************/ + +#pragma once + +#include "../concepts.h" +#include "../utf8.h" +#include "wire-types.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace spb::pb::detail +{ +struct ostream +{ +private: + size_t bytes_written = 0; + spb::io::writer on_write; + +public: + /** + * @brief Construct a new ostream object + * + * @param writer if null, stream will skip all writes but will still count number of written + * bytes + */ + explicit ostream( spb::io::writer writer = nullptr ) noexcept + : on_write( writer ) + { + } + + void write( const void * p_data, size_t size ) noexcept + { + if( on_write ) + { + on_write( p_data, size ); + } + + bytes_written += size; + } + + [[nodiscard]] auto size( ) const noexcept -> size_t + { + return bytes_written; + } + + void serialize( uint32_t field_number, const auto & value ); + + template < scalar_encoder encoder > + void serialize_as( uint32_t field_number, const auto & value ); +}; + +static inline auto serialize_size( const auto & value ) -> size_t; + +using namespace std::literals; + +static inline void serialize_varint( ostream & stream, uint64_t value ) +{ + size_t i = 0; + uint8_t buffer[ 10 ]; + + do + { + uint8_t byte = value & 0x7F; + value >>= 7; + byte |= value > 0 ? 0x80U : 0; + buffer[ i++ ] = byte; + } while( value > 0 ); + + return stream.write( buffer, i ); +} +static inline void serialize_svarint( ostream & stream, int64_t value ) +{ + const auto tmp = uint64_t( ( value << 1 ) ^ ( value >> 63 ) ); + return serialize_varint( stream, tmp ); +} + +static inline void serialize_tag( ostream & stream, uint32_t field_number, wire_type type ) +{ + const auto tag = ( field_number << 3 ) | uint32_t( type ); + serialize_varint( stream, tag ); +} + +static inline void serialize( ostream & stream, uint32_t field_number, + const spb::detail::proto_message auto & value ); +static inline void serialize( ostream & stream, uint32_t field_number, + const spb::detail::proto_field_string auto & value ); +static inline void serialize( ostream & stream, uint32_t field_number, + const spb::detail::proto_field_bytes auto & value ); +static inline void serialize( ostream & stream, uint32_t field_number, + const spb::detail::proto_label_repeated auto & value ); + +template < scalar_encoder encoder, spb::detail::proto_label_repeated C > +static inline void serialize_as( ostream & stream, uint32_t field_number, const C & value ); + +template < scalar_encoder encoder, typename keyT, typename valueT > +static inline void serialize_as( ostream & stream, uint32_t field_number, + const std::map< keyT, valueT > & value ); + +template < scalar_encoder encoder > +static inline void serialize_as( ostream & stream, uint32_t field_number, + spb::detail::proto_field_number auto value ) +{ + serialize_tag( stream, field_number, wire_type_from_scalar_encoder( encoder ) ); + serialize_as< encoder >( stream, value ); +} + +template < scalar_encoder encoder > +static inline void serialize_as( ostream & stream, const spb::detail::proto_enum auto & value ) +{ + serialize_varint( stream, int32_t( value ) ); +} + +template < scalar_encoder encoder > +static inline void serialize_as( ostream & stream, + spb::detail::proto_field_int_or_float auto value ) +{ + using T = std::remove_cvref_t< decltype( value ) >; + + const auto type = scalar_encoder_type1( encoder ); + + if constexpr( type == scalar_encoder::varint ) + { + static_assert( std::is_integral_v< T > ); + + if constexpr( std::is_same_v< bool, T > ) + { + const uint8_t tmp = value ? 1 : 0; + return stream.write( &tmp, 1 ); + } + else if constexpr( std::is_signed_v< T > ) + { + //- GPB is serializing all negative ints always as int64_t + const auto u_value = uint64_t( int64_t( value ) ); + return serialize_varint( stream, u_value ); + } + else + { + return serialize_varint( stream, value ); + } + } + else if constexpr( type == scalar_encoder::svarint ) + { + static_assert( std::is_signed_v< T > && std::is_integral_v< T > ); + + return serialize_svarint( stream, value ); + } + else if constexpr( type == scalar_encoder::i32 ) + { + if constexpr( sizeof( value ) == sizeof( uint32_t ) ) + { + return stream.write( &value, sizeof( value ) ); + } + else + { + const auto tmp = uint32_t( value ); + return stream.write( &tmp, sizeof( tmp ) ); + } + } + else if constexpr( type == scalar_encoder::i64 ) + { + if constexpr( sizeof( value ) == sizeof( uint64_t ) ) + { + return stream.write( &value, sizeof( value ) ); + } + else + { + const auto tmp = uint64_t( value ); + return stream.write( &tmp, sizeof( tmp ) ); + } + } +} + +static inline void serialize( ostream & stream, uint32_t field_number, + const spb::detail::proto_field_string auto & value ) +{ + if( !value.empty( ) ) + { + spb::detail::utf8::validate( std::string_view( value.data( ), value.size( ) ) ); + serialize_tag( stream, field_number, wire_type::length_delimited ); + serialize_varint( stream, value.size( ) ); + stream.write( value.data( ), value.size( ) ); + } +} + +static inline void serialize( ostream & stream, uint32_t field_number, + const spb::detail::proto_field_bytes auto & value ) +{ + if( !value.empty( ) ) + { + serialize_tag( stream, field_number, wire_type::length_delimited ); + serialize_varint( stream, value.size( ) ); + stream.write( value.data( ), value.size( ) ); + } +} + +template < scalar_encoder encoder, typename keyT, typename valueT > +static inline void serialize_as( ostream & stream, const std::map< keyT, valueT > & value ) +{ + const auto key_encoder = scalar_encoder_type1( encoder ); + const auto value_encoder = scalar_encoder_type2( encoder ); + + for( const auto & [ k, v ] : value ) + { + if constexpr( std::is_integral_v< keyT > ) + { + serialize_as< key_encoder >( stream, 1, k ); + } + else + { + serialize( stream, 1, k ); + } + if constexpr( spb::detail::proto_field_number< valueT > ) + { + serialize_as< value_encoder >( stream, 2, v ); + } + else + { + serialize( stream, 2, v ); + } + } +} + +template < scalar_encoder encoder, typename keyT, typename valueT > +static inline void serialize_as( ostream & stream, uint32_t field_number, + const std::map< keyT, valueT > & value ) +{ + auto size_stream = ostream( ); + serialize_as< encoder >( size_stream, value ); + const auto size = size_stream.size( ); + + serialize_tag( stream, field_number, wire_type::length_delimited ); + serialize_varint( stream, size ); + serialize_as< encoder >( stream, value ); +} + +template < scalar_encoder encoder, spb::detail::proto_label_repeated C > +static inline void serialize_packed_as( ostream & stream, const C & container ) +{ + for( const auto & v : container ) + { + if constexpr( std::is_same_v< typename C::value_type, bool > ) + { + serialize_as< encoder >( stream, bool( v ) ); + } + else + { + serialize_as< encoder >( stream, v ); + } + } +} + +template < scalar_encoder encoder, spb::detail::proto_label_repeated C > +static inline void serialize_as( ostream & stream, uint32_t field_number, const C & value ) +{ + if constexpr( scalar_encoder_is_packed( encoder ) ) + { + if( value.empty( ) ) + { + return; + } + + auto size_stream = ostream( ); + serialize_packed_as< encoder >( size_stream, value ); + const auto size = size_stream.size( ); + + serialize_tag( stream, field_number, wire_type::length_delimited ); + serialize_varint( stream, size ); + serialize_packed_as< encoder >( stream, value ); + } + else + { + for( const auto & v : value ) + { + if constexpr( std::is_same_v< typename C::value_type, bool > ) + { + serialize_as< encoder >( stream, field_number, bool( v ) ); + } + else + { + serialize_as< encoder >( stream, field_number, v ); + } + } + } +} + +static inline void serialize( ostream & stream, uint32_t field_number, + const spb::detail::proto_label_repeated auto & value ) +{ + for( const auto & v : value ) + { + if constexpr( std::is_same_v< typename std::decay_t< decltype( value ) >::value_type, + bool > ) + { + serialize_as< scalar_encoder::varint >( stream, field_number, bool( v ) ); + } + else + { + serialize( stream, field_number, v ); + } + } +} + +static inline void serialize( ostream & stream, uint32_t field_number, + const spb::detail::proto_label_optional auto & p_value ) +{ + if( p_value.has_value( ) ) + { + return serialize( stream, field_number, *p_value ); + } +} + +template < scalar_encoder encoder, spb::detail::proto_label_optional C > +static inline void serialize_as( ostream & stream, uint32_t field_number, const C & p_value ) +{ + if( p_value.has_value( ) ) + { + return serialize_as< encoder >( stream, field_number, *p_value ); + } +} + +template < typename T > +static inline void serialize( ostream & stream, uint32_t field_number, + const std::unique_ptr< T > & p_value ) +{ + if( p_value ) + { + return serialize( stream, field_number, *p_value ); + } +} + +static inline void serialize( ostream & stream, uint32_t field_number, + const spb::detail::proto_message auto & value ) +{ + if( const auto size = serialize_size( value ); size > 0 ) + { + serialize_tag( stream, field_number, wire_type::length_delimited ); + serialize_varint( stream, size ); + // + //- serialize is generated by the spb-protoc + // + serialize( stream, value ); + } +} + +static inline auto serialize( const auto & value, spb::io::writer on_write ) -> size_t +{ + auto stream = ostream( on_write ); + serialize( stream, value ); + return stream.size( ); +} + +static inline auto serialize_size( const auto & value ) -> size_t +{ + auto stream = ostream( nullptr ); + + serialize( stream, value ); + return stream.size( ); +} + +void ostream::serialize( uint32_t field_number, const auto & value ) +{ + detail::serialize( *this, field_number, value ); +} + +template < scalar_encoder encoder > +void ostream::serialize_as( uint32_t field_number, const auto & value ) +{ + detail::serialize_as< encoder >( *this, field_number, value ); +} + +}// namespace spb::pb::detail \ No newline at end of file diff --git a/3rdparty/simple-protobuf/include/spb/pb/wire-types.h b/3rdparty/simple-protobuf/include/spb/pb/wire-types.h new file mode 100644 index 0000000..586c95e --- /dev/null +++ b/3rdparty/simple-protobuf/include/spb/pb/wire-types.h @@ -0,0 +1,99 @@ +/***************************************************************************\ +* Name : serialize library for protobuf * +* Description : encoding in protobuf * +* Author : antonin.kriz@gmail.com * +* ------------------------------------------------------------------------- * +* This is free software; you can redistribute it and/or modify it under the * +* terms of the MIT license. A copy of the license can be found in the file * +* "LICENSE" at the root of this distribution. * +\***************************************************************************/ + +#pragma once + +#include +#include + +namespace spb::pb::detail +{ + +//- https://protobuf.dev/programming-guides/encoding/ +enum class wire_type : uint8_t +{ + //- int32, int64, uint32, uint64, sint32, sint64, bool, enum + varint = 0, + //- fixed64, sfixed64, double + fixed64 = 1, + //- string, bytes, embedded messages, packed repeated fields + length_delimited = 2, + //- not used + StartGroup = 3, + //- not used + EndGroup = 4, + //- fixed32, sfixed32, float + fixed32 = 5 +}; + +//- type1, type2 and packed flag +enum class scalar_encoder : uint8_t +{ + //- int32, int64, uint32, uint64, bool + varint = 0x01, + //- zigzag int32 or int64 + svarint = 0x02, + //- 4 bytes + i32 = 0x03, + //- 8 bytes + i64 = 0x04, + //- packed array + packed = 0x08 +}; + +static constexpr scalar_encoder operator&( scalar_encoder lhs, scalar_encoder rhs ) noexcept +{ + return scalar_encoder( std::underlying_type_t< scalar_encoder >( lhs ) & + std::underlying_type_t< scalar_encoder >( rhs ) ); +} + +static constexpr scalar_encoder operator|( scalar_encoder lhs, scalar_encoder rhs ) noexcept +{ + return scalar_encoder( std::underlying_type_t< scalar_encoder >( lhs ) | + std::underlying_type_t< scalar_encoder >( rhs ) ); +} + +static constexpr auto scalar_encoder_combine( scalar_encoder type1, scalar_encoder type2 ) noexcept + -> scalar_encoder +{ + return scalar_encoder( ( std::underlying_type_t< scalar_encoder >( type1 ) & 0x0f ) | + ( ( std::underlying_type_t< scalar_encoder >( type2 ) & 0x0f ) << 4 ) ); +} + +static constexpr auto scalar_encoder_is_packed( scalar_encoder a ) noexcept -> bool +{ + return ( a & scalar_encoder::packed ) == scalar_encoder::packed; +} + +static constexpr auto scalar_encoder_type1( scalar_encoder a ) noexcept -> scalar_encoder +{ + return scalar_encoder( static_cast< std::underlying_type_t< scalar_encoder > >( a ) & 0x07 ); +} + +static constexpr auto scalar_encoder_type2( scalar_encoder a ) noexcept -> scalar_encoder +{ + return scalar_encoder( ( static_cast< std::underlying_type_t< scalar_encoder > >( a ) >> 4 ) & + 0x07 ); +} + +static constexpr auto wire_type_from_scalar_encoder( scalar_encoder a ) noexcept -> wire_type +{ + switch( scalar_encoder_type1( a ) ) + { + case scalar_encoder::i32: + return wire_type::fixed32; + case scalar_encoder::i64: + return wire_type::fixed64; + default: + return wire_type::varint; + } +} + +}// namespace spb::pb::detail diff --git a/3rdparty/simple-protobuf/include/spb/to_from_chars.h b/3rdparty/simple-protobuf/include/spb/to_from_chars.h new file mode 100644 index 0000000..bcd4741 --- /dev/null +++ b/3rdparty/simple-protobuf/include/spb/to_from_chars.h @@ -0,0 +1,217 @@ +/***************************************************************************\ +* Name : std::to/from_chars emulation, #if __cpp_lib_to_chars < 201611L * +* Description : string to number conversion, (std::from_chars or strtoX) * +* Author : antonin.kriz@gmail.com * +* ------------------------------------------------------------------------- * +* This is free software; you can redistribute it and/or modify it under the * +* terms of the MIT license. A copy of the license can be found in the file * +* "LICENSE" at the root of this distribution. * +\***************************************************************************/ + +#pragma once +#include +#include + +#if __cpp_lib_to_chars >= 201611L +namespace spb_std_emu = std; +#else +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace spb::std_emu +{ +struct from_chars_result +{ + const char * ptr; + std::errc ec; +}; + +struct to_chars_result +{ + const char * ptr; + std::errc ec; +}; + +static inline auto from_chars( const char * first, const char * last, std::integral auto & number, + int base = 10 ) -> from_chars_result +{ + assert( first <= last ); + char buffer[ 32 ]; + auto copy_size = std::min< size_t >( last - first, sizeof( buffer ) - 1 ); + memcpy( buffer, first, copy_size ); + buffer[ copy_size ] = '\0'; + const char * end = nullptr; + + using T = std::decay_t< decltype( number ) >; + + static_assert( sizeof( number ) >= 1 && sizeof( number ) <= 8, "unsupported size" ); + errno = 0; + auto result = T( 0 ); + + if constexpr( std::is_signed_v< T > ) + { + if constexpr( sizeof( number ) < 4 ) + { + auto tmp = strtol( buffer, ( char ** ) &end, base ); + if( tmp <= std::numeric_limits< T >::max( ) && tmp >= std::numeric_limits< T >::min( ) ) + [[likely]] + { + result = T( tmp ); + } + else + { + errno = ERANGE; + } + } + else if constexpr( sizeof( number ) == 4 ) + { + result = strtol( buffer, ( char ** ) &end, base ); + } + else if constexpr( sizeof( number ) == 8 ) + { + result = strtoll( buffer, ( char ** ) &end, base ); + } + } + else + { + if constexpr( sizeof( number ) < 4 ) + { + auto tmp = strtoul( buffer, ( char ** ) &end, base ); + if( tmp <= std::numeric_limits< T >::max( ) ) [[likely]] + { + result = T( tmp ); + } + else + { + errno = ERANGE; + } + } + else if constexpr( sizeof( number ) == 4 ) + { + result = strtoul( buffer, ( char ** ) &end, base ); + } + else if constexpr( sizeof( number ) == 8 ) + { + result = strtoull( buffer, ( char ** ) &end, base ); + } + } + end = first + ( end - buffer ); + if( end == first ) + { + return { first, std::errc::invalid_argument }; + } + if( errno == ERANGE ) + { + return { end, std::errc::result_out_of_range }; + } + number = result; + return { end, std::errc( ) }; +} + +static inline auto from_chars( const char * first, const char * last, + std::floating_point auto & number ) -> from_chars_result +{ + assert( first <= last ); + char buffer[ 256 ]; + auto copy_size = std::min< size_t >( last - first, sizeof( buffer ) - 1 ); + memcpy( buffer, first, copy_size ); + buffer[ copy_size ] = '\0'; + const char * end = nullptr; + + static_assert( sizeof( number ) == 4 || sizeof( number ) == 8, "unsupported size" ); + errno = 0; + auto result = std::decay_t< decltype( number ) >{ 0 }; + if constexpr( sizeof( number ) == 4 ) + { + result = strtof( buffer, ( char ** ) &end ); + } + else if constexpr( sizeof( number ) == 8 ) + { + result = strtod( buffer, ( char ** ) &end ); + } + end = first + ( end - buffer ); + if( end == first ) + { + return { end, std::errc::invalid_argument }; + } + if( errno == ERANGE ) + { + return { end, std::errc::result_out_of_range }; + } + number = result; + return { end, std::errc( ) }; +} + +static inline auto to_chars( char * first, char * last, const std::integral auto & number, + int base = 10 ) -> to_chars_result +{ + if( base != 10 ) + { + return { first, std::errc::invalid_argument }; + } + + if( last <= first ) + { + return { first, std::errc::value_too_large }; + } + + const auto result = std::to_string( number ); + const auto buffer_size = static_cast< size_t >( last - first ); + + if( result.size( ) > buffer_size ) + { + return { first, std::errc::value_too_large }; + } + + memcpy( first, result.data( ), result.size( ) ); + return { first + result.size( ), std::errc{} }; +} + +static inline auto to_chars( char * first, char * last, const std::floating_point auto & number ) + -> to_chars_result +{ + if( last <= first ) + { + return { first, std::errc::value_too_large }; + } + + const auto buffer_size = last - first; + + const char * format; + + if constexpr( std::is_same_v< std::remove_cvref_t< decltype( number ) >, double > ) + { + format = "%lg"; + } + else if constexpr( std::is_same_v< std::remove_cvref_t< decltype( number ) >, long double > ) + { + format = "%Lg"; + } + else + { + format = "%g"; + } + + const int written = snprintf( first, buffer_size, format, number ); + if( written < 0 ) + { + return { first, std::errc::invalid_argument }; + } + else if( written > buffer_size ) + { + return { first, std::errc::value_too_large }; + } + + return { first + written, std::errc{} }; +} + +}// namespace spb::std_emu +namespace spb_std_emu = spb::std_emu; +#endif diff --git a/3rdparty/simple-protobuf/include/spb/utf8.h b/3rdparty/simple-protobuf/include/spb/utf8.h new file mode 100644 index 0000000..6560cc4 --- /dev/null +++ b/3rdparty/simple-protobuf/include/spb/utf8.h @@ -0,0 +1,128 @@ + +/***************************************************************************\ +* Name : utf8 * +* Description : utf8 validation and utf8 to unicode convert * +* Author : antonin.kriz@gmail.com * +* reference : https://bjoern.hoehrmann.de/utf-8/decoder/dfa/ * +* ------------------------------------------------------------------------- * +* This is free software; you can redistribute it and/or modify it under the * +* terms of the MIT license. A copy of the license can be found in the file * +* "LICENSE" at the root of this distribution. * +\***************************************************************************/ +#pragma once + +#include +#include +#include +#include + +namespace spb::detail::utf8 +{ + +// Copyright (c) 2008-2009 Bjoern Hoehrmann +// See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. + +constexpr uint8_t ok = 0; + +static auto inline decode_point( uint32_t * state, uint32_t * codep, uint8_t byte ) -> uint32_t +{ + static const uint8_t utf8d[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,// 00..1f + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,// 20..3f + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,// 40..5f + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,// 60..7f + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,// 80..9f + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,// a0..bf + 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // c0..df + 0xa, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3,// e0..ef + 0xb, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8,// f0..ff + 0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1,// s0..s0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1,// s1..s2 + 1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1,// s3..s4 + 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1,// s5..s6 + 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, + 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,// s7..s8 + }; + + uint32_t type = utf8d[ byte ]; + + *codep = ( *state != ok ) ? ( byte & 0x3fu ) | ( *codep << 6 ) : ( 0xff >> type ) & ( byte ); + + *state = utf8d[ 256 + *state * 16 + type ]; + return *state; +} + +/** + * @brief encode codepoint to utf8 + * + * @param unicode codepoint + * @param utf8 output + * @return size of output in bytes, 0 on error + */ +static inline auto encode_point( uint32_t unicode, char utf8[ 4 ] ) -> uint32_t +{ + if( unicode <= 0x7F ) + { + utf8[ 0 ] = ( char ) unicode; + return 1; + } + if( unicode <= 0x7FF ) + { + utf8[ 0 ] = ( char ) ( ( unicode >> 6 ) | 0xC0 ); + utf8[ 1 ] = ( char ) ( ( unicode & 0x3F ) | 0x80 ); + return 2; + } + if( unicode >= 0xD800 && unicode < 0xE000 ) + { + return 0; + } + if( unicode <= 0xFFFF ) + { + utf8[ 0 ] = ( char ) ( ( unicode >> 12 ) | 0xE0 ); + utf8[ 1 ] = ( char ) ( ( ( unicode >> 6 ) & 0x3F ) | 0x80 ); + utf8[ 2 ] = ( char ) ( ( unicode & 0x3F ) | 0x80 ); + return 3; + } + if( unicode <= 0x10FFFF ) + { + utf8[ 0 ] = ( char ) ( ( unicode >> 18 ) | 0xF0 ); + utf8[ 1 ] = ( char ) ( ( ( unicode >> 12 ) & 0x3F ) | 0x80 ); + utf8[ 2 ] = ( char ) ( ( ( unicode >> 6 ) & 0x3F ) | 0x80 ); + utf8[ 3 ] = ( char ) ( ( unicode & 0x3F ) | 0x80 ); + return 4; + } + return 0; +} + +static inline auto is_valid( std::string_view str ) -> bool +{ + uint32_t codepoint; + uint32_t state = ok; + + for( uint8_t c : str ) + { + decode_point( &state, &codepoint, c ); + } + + return state == ok; +} + +static inline void validate( std::string_view value ) +{ + if( !spb::detail::utf8::is_valid( std::string_view( value.data( ), value.size( ) ) ) ) + { + throw std::runtime_error( "invalid utf8 string" ); + } +} + +}// namespace spb::detail::utf8 \ No newline at end of file diff --git a/3rdparty/simple-protobuf/scripts/run-tests-coverage.sh b/3rdparty/simple-protobuf/scripts/run-tests-coverage.sh new file mode 100644 index 0000000..222d265 --- /dev/null +++ b/3rdparty/simple-protobuf/scripts/run-tests-coverage.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +set -e + +rm -rf coverage +cmake -B coverage -G Ninja . -DCMAKE_BUILD_TYPE=Debug -DSPB_PROTO_BUILD_TESTS=ON -DSPB_PROTO_USE_COVERAGE=ON +cmake --build coverage --target unit_tests +cd coverage +ctest --output-on-failure +lcov --capture -o coverage.info --directory . --exclude '/usr/*' --exclude '*/test/*' -q --gcov-tool gcov +genhtml coverage.info --output-directory report diff --git a/3rdparty/simple-protobuf/src/CMakeLists.txt b/3rdparty/simple-protobuf/src/CMakeLists.txt new file mode 100644 index 0000000..f72d5c9 --- /dev/null +++ b/3rdparty/simple-protobuf/src/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(spb-proto-compiler) diff --git a/3rdparty/simple-protobuf/src/spb-proto-compiler/CMakeLists.txt b/3rdparty/simple-protobuf/src/spb-proto-compiler/CMakeLists.txt new file mode 100644 index 0000000..96d903c --- /dev/null +++ b/3rdparty/simple-protobuf/src/spb-proto-compiler/CMakeLists.txt @@ -0,0 +1,10 @@ +project(spb-proto-compiler VERSION 1.0.0 LANGUAGES CXX) + +add_executable(spb-protoc main.cpp parser/parser.cpp ast/ast-types.cpp + ast/ast-messages-order.cpp ast/ast.cpp io/file.cpp dumper/header.cpp + dumper/pb/dumper.cpp dumper/json/dumper.cpp dumper/dumper.cpp) + +target_include_directories(spb-protoc PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +target_link_libraries(spb-protoc PUBLIC spb-proto) +spb_set_compile_options(spb-protoc) +target_compile_features(spb-protoc PUBLIC cxx_std_20) diff --git a/3rdparty/simple-protobuf/src/spb-proto-compiler/ast/ast-messages-order.cpp b/3rdparty/simple-protobuf/src/spb-proto-compiler/ast/ast-messages-order.cpp new file mode 100644 index 0000000..a9f44ae --- /dev/null +++ b/3rdparty/simple-protobuf/src/spb-proto-compiler/ast/ast-messages-order.cpp @@ -0,0 +1,365 @@ +/***************************************************************************\ +* Name : ast render * +* Description : resolve types in ast tree * +* Author : antonin.kriz@gmail.com * +* ------------------------------------------------------------------------- * +* This is free software; you can redistribute it and/or modify it under the * +* terms of the MIT license. A copy of the license can be found in the file * +* "LICENSE" at the root of this distribution. * +\***************************************************************************/ + +#include "ast-types.h" +#include "dumper/header.h" +#include "proto-field.h" +#include "proto-file.h" +#include "proto-message.h" +#include +#include +#include +#include +#include +#include +#include + +namespace +{ +[[nodiscard]] auto is_imported( std::string_view full_type, const proto_files & imports ) -> bool +{ + return std::any_of( imports.begin( ), imports.end( ), + [ full_type ]( const auto & import ) -> bool + { + return full_type.size( ) > import.package.name.size( ) && + full_type[ import.package.name.size( ) ] == '.' && + full_type.starts_with( import.package.name ); + } ); +} + +[[nodiscard]] auto is_enum( std::string_view type, const proto_enums & enums ) -> bool +{ + return std::any_of( enums.begin( ), enums.end( ), [ type ]( const auto & enum_field ) -> bool + { return type == enum_field.name; } ); +} + +[[nodiscard]] auto is_sub_message( std::string_view type, const proto_messages & messages ) -> bool +{ + return std::any_of( messages.begin( ), messages.end( ), + [ type ]( const auto & message ) -> bool { return type == message.name; } ); +} + +[[nodiscard]] auto is_resolved_sub_message( std::string_view type, const proto_messages & messages ) + -> bool +{ + return std::any_of( messages.begin( ), messages.end( ), [ type ]( const auto & message ) -> bool + { return type == message.name && message.resolved > 0; } ); +} + +[[nodiscard]] auto is_sub_oneof( std::string_view type, const proto_oneofs & oneofs ) -> bool +{ + return std::any_of( oneofs.begin( ), oneofs.end( ), + [ type ]( const auto & oneof ) -> bool { return type == oneof.name; } ); +} + +struct search_state +{ + enum resolve_mode + { + //- only check if type is already defined + dependencies_only, + //- for optional fields create an std::unique_ptr< T > type and forward declare T + //- to break cyclic dependency + optional_pointers, + }; + + resolve_mode mode = dependencies_only; + size_t resolved_messages = 0; + const proto_files & imports; + const proto_file & file; +}; + +/** + * @brief search ctx to hold relation 'message -> parent_message' + * the relation is not stored in proto_message because its not needed until now + * and the messages get sorted (moved) later so the parent pointers will be invalid anyways + * the parent relation is used for type dependency check and for proper order of structs + * definition in the generated *.pb.h header file, because C++ needs proper order of type + * dependencies. The proper order is defined by the value of `.resolved` for every message + * + */ +struct search_ctx +{ + proto_message & message; + //- parent message (can be null for top level) + search_ctx * p_parent; + search_state & state; +}; + +/** + * @brief if the field is self-referencing and can be forward declared + * field label must be LABEL_OPTIONAL or LABEL_PTR or LABEL_REPEATED + * + * @param field filed + * @param ctx search context + * @return true if field can be forward declared + */ +[[nodiscard]] auto is_self( proto_field & field, const search_ctx & ctx ) -> bool +{ + if( field.type == proto_field::Type::MESSAGE && field.type_name == ctx.message.name ) + { + switch( field.label ) + { + case proto_field::Label::NONE: + throw_parse_error( ctx.state.file, field.name, + "Field '" + std::string( field.name ) + + "' cannot be self-referencing (make it optional)" ); + case proto_field::Label::OPTIONAL: + field.label = proto_field::Label::PTR; + return true; + case proto_field::Label::REPEATED: + case proto_field::Label::PTR: + return true; + } + } + return false; +} + +/** + * @brief if this dependency can be forward declared, do it + * field label must be LABEL_REPEATED or LABEL_PTR + * + * @param field field + * @param ctx search context + * @return true if field can be forward declared + */ +[[nodiscard]] auto is_forwarded( proto_field & field, search_ctx & ctx ) -> bool +{ + if( ctx.p_parent == nullptr ) + return false; + + for( auto & message : ctx.p_parent->message.messages ) + { + if( field.type_name == message.name ) + { + switch( field.label ) + { + case proto_field::Label::NONE: + return false; + case proto_field::Label::OPTIONAL: + switch( ctx.state.mode ) + { + case search_state::dependencies_only: + return false; + + case search_state::optional_pointers: + field.label = proto_field::Label::PTR; + // + //- revert the mode to dependencies_only and try again + //- reason is to create as little pointers as possible + // + ctx.state.mode = search_state::dependencies_only; + } + [[fallthrough]]; + case proto_field::Label::REPEATED: + case proto_field::Label::PTR: + ctx.p_parent->message.forwards.insert( message.name ); + return true; + } + } + } + return false; +} + +/** + * @brief if this dependency references the parent message and can be forward declared + * field label must be LABEL_OPTIONAL or LABEL_REPEATED or LABEL_PTR + * + * @param field field + * @param ctx search context + * @return true if field can be forward declared + */ +[[nodiscard]] auto is_parent( proto_field & field, const search_ctx & ctx ) -> bool +{ + if( ctx.p_parent == nullptr ) + return false; + + if( field.type_name == ctx.p_parent->message.name ) + { + switch( field.label ) + { + case proto_field::Label::NONE: + throw_parse_error( ctx.state.file, field.name, + "Field '" + std::string( field.name ) + + "' cannot reference parent (make it optional)" ); + case proto_field::Label::OPTIONAL: + field.label = proto_field::Label::PTR; + return true; + case proto_field::Label::REPEATED: + case proto_field::Label::PTR: + return true; + } + } + return false; +} + +[[nodiscard]] auto is_defined_in_parents( std::string_view type, const search_ctx & ctx ) -> bool +{ + if( ctx.p_parent == nullptr ) + return false; + + const auto & message = ctx.p_parent->message; + if( is_enum( type, message.enums ) || is_resolved_sub_message( type, message.messages ) || + is_sub_oneof( type, message.oneofs ) ) + { + return true; + } + + return is_defined_in_parents( type, *ctx.p_parent ); +} + +[[nodiscard]] auto all_types_are_resolved( const proto_messages & messages ) -> bool +{ + return std::all_of( messages.begin( ), messages.end( ), + []( const auto & message ) { return message.resolved > 0; } ); +} + +void mark_message_as_resolved( search_ctx & ctx ) +{ + assert( ctx.message.resolved == 0 ); + + ctx.message.resolved = ++ctx.state.resolved_messages; +} + +[[nodiscard]] auto resolve_message_field( search_ctx & ctx, proto_field & field ) -> bool +{ + //- check only the first type (before .) and leave the rest for the C++ compiler to check + //- TODO: check full type name + const auto type_name = field.type_name.substr( 0, field.type_name.find( '.' ) ); + + return is_scalar( field.type ) || is_self( field, ctx ) || + is_enum( field.type_name, ctx.message.enums ) || + is_sub_message( type_name, ctx.message.messages ) || + is_sub_oneof( type_name, ctx.message.oneofs ) || is_parent( field, ctx ) || + is_defined_in_parents( type_name, ctx ) || + is_imported( field.type_name, ctx.state.imports ) || is_forwarded( field, ctx ); +} + +[[nodiscard]] auto resolve_field( search_ctx & ctx, const proto_field & field ) -> bool +{ + const auto type_name = field.type_name.substr( 0, field.type_name.find( '.' ) ); + + return is_scalar( field.type ) || is_enum( field.type_name, ctx.message.enums ) || + is_sub_message( type_name, ctx.message.messages ) || + is_defined_in_parents( type_name, ctx ) || + is_imported( field.type_name, ctx.state.imports ); +} + +void resolve_message_fields( search_ctx & ctx ) +{ + if( ctx.message.resolved > 0 ) + return; + + for( auto & field : ctx.message.fields ) + { + if( !resolve_message_field( ctx, field ) ) + return; + } + + for( const auto & map : ctx.message.maps ) + { + if( !resolve_field( ctx, map.value ) ) + return; + } + + for( const auto & oneof : ctx.message.oneofs ) + { + for( const auto & field : oneof.fields ) + { + if( !resolve_field( ctx, field ) ) + return; + } + } + + if( all_types_are_resolved( ctx.message.messages ) ) + mark_message_as_resolved( ctx ); +} + +void resolve_message_dependencies( search_ctx & ctx ) +{ + if( ctx.message.resolved > 0 ) + return; + + for( auto & message : ctx.message.messages ) + { + auto sub_ctx = search_ctx{ + .message = message, + .p_parent = &ctx, + .state = ctx.state, + }; + + resolve_message_dependencies( sub_ctx ); + } + resolve_message_fields( ctx ); +} + +[[noreturn]] void dump_unresolved_message( const proto_messages & messages, + const proto_file & file ) +{ + for( const auto & message : messages ) + { + if( message.resolved == 0 ) + { + throw_parse_error( file, message.name, "type dependency can't be resolved" ); + } + } + throw_parse_error( file, file.content, "type dependency can't be resolved" ); +} + +void sort_messages( proto_messages & messages ) +{ + std::sort( messages.begin( ), messages.end( ), + []( const auto & a, const auto & b ) { return a.resolved < b.resolved; } ); + + for( auto & message : messages ) + { + sort_messages( message.messages ); + } +} + +}// namespace + +void resolve_messages_order( proto_file & file ) +{ + auto state = search_state{ + .mode = search_state::dependencies_only, + .resolved_messages = 0, + .imports = file.file_imports, + .file = file, + }; + + while( !all_types_are_resolved( file.package.messages ) ) + { + const auto resolved_messages = state.resolved_messages; + + auto top_level_ctx = search_ctx{ + .message = file.package, + .p_parent = nullptr, + .state = state, + }; + + resolve_message_dependencies( top_level_ctx ); + + if( resolved_messages == state.resolved_messages ) + { + if( state.mode == search_state::dependencies_only ) + { + //- no messages were resolved using only dependencies? + //- try to break cyclic dependency by using forward pointers declaration + state.mode = search_state::optional_pointers; + continue; + } + + dump_unresolved_message( file.package.messages, file ); + } + } + + sort_messages( file.package.messages ); +} diff --git a/3rdparty/simple-protobuf/src/spb-proto-compiler/ast/ast-messages-order.h b/3rdparty/simple-protobuf/src/spb-proto-compiler/ast/ast-messages-order.h new file mode 100644 index 0000000..8e213b9 --- /dev/null +++ b/3rdparty/simple-protobuf/src/spb-proto-compiler/ast/ast-messages-order.h @@ -0,0 +1,15 @@ +/***************************************************************************\ +* Name : ast types resolver * +* Description : resolve types in ast tree * +* Author : antonin.kriz@gmail.com * +* ------------------------------------------------------------------------- * +* This is free software; you can redistribute it and/or modify it under the * +* terms of the MIT license. A copy of the license can be found in the file * +* "LICENSE" at the root of this distribution. * +\***************************************************************************/ + +#pragma once + +#include "proto-file.h" + +void resolve_messages_order( proto_file & file ); diff --git a/3rdparty/simple-protobuf/src/spb-proto-compiler/ast/ast-types.cpp b/3rdparty/simple-protobuf/src/spb-proto-compiler/ast/ast-types.cpp new file mode 100644 index 0000000..7ab1c3a --- /dev/null +++ b/3rdparty/simple-protobuf/src/spb-proto-compiler/ast/ast-types.cpp @@ -0,0 +1,419 @@ +/***************************************************************************\ +* Name : ast render * +* Description : resolve types in ast tree * +* Author : antonin.kriz@gmail.com * +* ------------------------------------------------------------------------- * +* This is free software; you can redistribute it and/or modify it under the * +* terms of the MIT license. A copy of the license can be found in the file * +* "LICENSE" at the root of this distribution. * +\***************************************************************************/ + +#include "ast/proto-oneof.h" + +#include "../dumper/header.h" +#include "../parser/options.h" +#include "proto-field.h" +#include +#include "proto-file.h" +#include "proto-message.h" +#include "spb/pb/wire-types.h" +#include +#include +#include + +namespace +{ +using namespace std::literals; + +struct search_ctx +{ + //- `file` containing `message` + const proto_file & file; + //- `message` containing `field` + const proto_message & message; + //- parent message (null for top level) + const search_ctx * p_parent = nullptr; +}; + +using scalar_encoder = spb::pb::detail::scalar_encoder; + +[[nodiscard]] auto type_parts( std::string_view type_name ) -> size_t +{ + return size_t( std::count( type_name.cbegin( ), type_name.cend( ), '.' ) ); +} + +[[nodiscard]] auto is_last_part( const proto_field & field, size_t type_part ) -> bool +{ + return type_part == type_parts( field.type_name ); +} + +[[nodiscard]] auto get_type_part( const proto_field & field, size_t type_part ) -> std::string_view +{ + auto type_name = field.type_name; + for( ; type_part > 0; type_part-- ) + { + const auto dot_index = type_name.find( '.' ); + if( dot_index == type_name.npos ) + return { }; + + type_name.remove_prefix( dot_index + 1 ); + } + + return type_name.substr( 0, type_name.find( '.' ) ); +} + +[[nodiscard]] auto resolve_type( const search_ctx & self, const proto_field & field, + size_t type_part ) + -> std::pair< proto_field::Type, proto_field::BitType >; + +[[nodiscard]] auto remove_bitfield( std::string_view type ) -> std::string_view +{ + return type.substr( 0, type.find( ':' ) ); +} + +[[nodiscard]] auto convertible_types( proto_field::Type from, proto_field::BitType to ) -> bool +{ + static constexpr auto type_map = + std::array< std::pair< proto_field::Type, std::array< proto_field::BitType, 4 > >, 15 >{ { + { proto_field::Type::INT32, + { { proto_field::BitType::INT8, proto_field::BitType::INT16, + proto_field::BitType::INT32 } } }, + { proto_field::Type::INT64, + { { proto_field::BitType::INT8, proto_field::BitType::INT16, + proto_field::BitType::INT32, proto_field::BitType::INT64 } } }, + { proto_field::Type::SINT32, + { { proto_field::BitType::INT8, proto_field::BitType::INT16, + proto_field::BitType::INT32 } } }, + { proto_field::Type::SINT64, + { { proto_field::BitType::INT8, proto_field::BitType::INT16, + proto_field::BitType::INT32, proto_field::BitType::INT64 } } }, + { proto_field::Type::UINT32, + { { proto_field::BitType::UINT8, proto_field::BitType::UINT16, + proto_field::BitType::UINT32 } } }, + { proto_field::Type::UINT64, + { { proto_field::BitType::UINT8, proto_field::BitType::UINT16, + proto_field::BitType::UINT32, proto_field::BitType::UINT64 } } }, + { proto_field::Type::FIXED32, + { { proto_field::BitType::UINT8, proto_field::BitType::UINT16, + proto_field::BitType::UINT32 } } }, + { proto_field::Type::FIXED64, + { { proto_field::BitType::UINT8, proto_field::BitType::UINT16, + proto_field::BitType::UINT32, proto_field::BitType::UINT64 } } }, + { proto_field::Type::SFIXED32, + { { proto_field::BitType::INT8, proto_field::BitType::INT16, + proto_field::BitType::INT32 } } }, + { proto_field::Type::SFIXED64, + { { proto_field::BitType::INT8, proto_field::BitType::INT16, + proto_field::BitType::INT32, proto_field::BitType::INT64 } } }, + } }; + + for( auto [ proto_type, types ] : type_map ) + { + if( from == proto_type ) + return std::find( types.begin( ), types.end( ), to ) != types.end( ); + } + return false; +} + +[[nodiscard]] auto get_scalar_bit_type( std::string_view type_name ) -> proto_field::BitType +{ + struct type_table + { + std::string_view name; + proto_field::BitType type; + }; + + static constexpr auto scalar_types = std::array< type_table, 8 >{ { + { "int8"sv, proto_field::BitType::INT8 }, + { "int16"sv, proto_field::BitType::INT16 }, + { "int32"sv, proto_field::BitType::INT32 }, + { "int64"sv, proto_field::BitType::INT64 }, + { "uint8"sv, proto_field::BitType::UINT8 }, + { "uint16"sv, proto_field::BitType::UINT16 }, + { "uint32"sv, proto_field::BitType::UINT32 }, + { "uint64"sv, proto_field::BitType::UINT64 }, + } }; + + for( const auto item : scalar_types ) + { + if( item.name == type_name ) + return item.type; + } + + return proto_field::BitType::NONE; +} + +[[nodiscard]] auto get_scalar_proto_type( std::string_view type_name ) -> proto_field::Type +{ + struct type_table + { + std::string_view name; + proto_field::Type type; + }; + + static constexpr auto scalar_types = std::array< type_table, 15 >{ { + { "bool"sv, proto_field::Type::BOOL }, + { "bytes"sv, proto_field::Type::BYTES }, + { "double"sv, proto_field::Type::DOUBLE }, + { "float"sv, proto_field::Type::FLOAT }, + { "int32"sv, proto_field::Type::INT32 }, + { "int64"sv, proto_field::Type::INT64 }, + { "uint32"sv, proto_field::Type::UINT32 }, + { "uint64"sv, proto_field::Type::UINT64 }, + { "sint32"sv, proto_field::Type::SINT32 }, + { "sint64"sv, proto_field::Type::SINT64 }, + { "fixed32"sv, proto_field::Type::FIXED32 }, + { "fixed64"sv, proto_field::Type::FIXED64 }, + { "sfixed32"sv, proto_field::Type::SFIXED32 }, + { "sfixed64"sv, proto_field::Type::SFIXED64 }, + { "string"sv, proto_field::Type::STRING }, + } }; + + for( const auto item : scalar_types ) + { + if( item.name == type_name ) + return item.type; + } + + return proto_field::Type::NONE; +} + +[[nodiscard]] auto get_field_type( const proto_file & file, const proto_field & field ) + -> std::pair< proto_field::Type, proto_field::BitType > +{ + const auto type = get_scalar_proto_type( field.type_name ); + if( type == proto_field::Type::NONE ) + return { }; + + if( const auto p_name = field.options.find( option_field_type ); + p_name != field.options.end( ) ) + { + const auto field_type = get_scalar_bit_type( remove_bitfield( p_name->second ) ); + if( !convertible_types( type, field_type ) ) + { + throw_parse_error( file, p_name->second, + std::string( "incompatible int type: " ) + + std::string( field.type_name ) + " and " + + std::string( p_name->second ) ); + } + return { type, field_type }; + } + + return { type, proto_field::BitType::NONE }; +} + +[[nodiscard]] auto get_scalar_type( const proto_file & file, const proto_field & field, + size_t type_part ) + -> std::pair< proto_field::Type, proto_field::BitType > +{ + if( type_part != 0 ) + return { }; + + return get_field_type( file, field ); +} + +[[nodiscard]] auto get_sub_message( const proto_message & message, const proto_field & field, + size_t type_part ) -> const proto_message * +{ + const auto type_name = get_type_part( field, type_part ); + const auto index = std::find_if( message.messages.begin( ), message.messages.end( ), + [ type_name ]( const auto & sub_message ) -> bool + { return type_name == sub_message.name; } ); + return ( index != message.messages.end( ) ) ? &*index : nullptr; +} + +[[nodiscard]] auto resolve_enum( const proto_message & message, const proto_field & field, + size_t type_part ) -> proto_field::Type +{ + if( !is_last_part( field, type_part ) ) + return proto_field::Type::NONE; + + const auto type_name = get_type_part( field, type_part ); + + return std::any_of( message.enums.begin( ), message.enums.end( ), + [ type_name ]( const auto & enum_ ) -> bool + { return type_name == enum_.name; } ) + ? proto_field::Type::ENUM + : proto_field::Type::NONE; +} + +[[nodiscard]] auto resolve_from_message( const proto_message & message, const proto_field & field, + size_t type_part ) -> proto_field::Type +{ + if( const auto type = resolve_enum( message, field, type_part ); type ) + return type; + + if( const auto * sub_message = get_sub_message( message, field, type_part ); sub_message ) + { + if( is_last_part( field, type_part ) ) + return proto_field::Type::MESSAGE; + + return resolve_from_message( *sub_message, field, type_part + 1 ); + } + + return proto_field::Type::NONE; +} + +[[nodiscard]] auto resolve_from_parent( const search_ctx & self, const proto_field & field, + size_t type_part ) -> proto_field::Type +{ + if( !self.p_parent || type_part > 0 ) + return proto_field::Type::NONE; + + return resolve_type( *self.p_parent, field, type_part ).first; +} + +[[nodiscard]] auto resolve_from_import( const proto_file & import, const proto_field & field ) + -> proto_field::Type +{ + if( field.type_name.size( ) > import.package.name.size( ) && + field.type_name[ import.package.name.size( ) ] == '.' && + field.type_name.starts_with( import.package.name ) ) + { + return resolve_from_message( import.package, field, type_parts( import.package.name ) + 1 ); + } + + return proto_field::Type::NONE; +} + +[[nodiscard]] auto resolve_from_imports( const search_ctx & self, const proto_field & field, + size_t type_part ) -> proto_field::Type +{ + if( type_part > 0 ) + return proto_field::Type::NONE; + + for( const auto & import : self.file.file_imports ) + { + if( const auto type = resolve_from_import( import, field ); type ) + return type; + } + + return proto_field::Type::NONE; +} + +auto resolve_type( const search_ctx & self, const proto_field & field, size_t type_part ) + -> std::pair< proto_field::Type, proto_field::BitType > +{ + if( const auto type = get_scalar_type( self.file, field, type_part ); type.first ) + return type; + + if( const auto type = resolve_from_message( self.message, field, type_part ); type ) + return { type, proto_field::BitType::NONE }; + + if( const auto type = resolve_from_parent( self, field, type_part ); type ) + return { type, proto_field::BitType::NONE }; + + if( const auto type = resolve_from_imports( self, field, type_part ); type ) + return { type, proto_field::BitType::NONE }; + + throw_parse_error( self.file, field.type_name, "type can't be resolved" ); +} + +void resolve_types( const search_ctx & self, proto_field & field ) +{ + const auto [ type, bit_type ] = resolve_type( self, field, 0 ); + + field.type = type; + field.bit_type = bit_type; +} + +void resolve_types( const search_ctx & self, proto_map & map ) +{ + resolve_types( self, map.key ); + resolve_types( self, map.value ); +} + +void resolve_types( const search_ctx & self, proto_oneof & oneof ) +{ + for( auto & field : oneof.fields ) + { + resolve_types( self, field ); + } +} + +void resolve_types( const search_ctx & parent, proto_message & message ) +{ + auto ctx = search_ctx{ + .file = parent.file, + .message = message, + .p_parent = &parent, + }; + + for( auto & field : message.fields ) + { + resolve_types( ctx, field ); + } + + for( auto & map : message.maps ) + { + resolve_types( ctx, map ); + } + + for( auto & oneof : message.oneofs ) + { + resolve_types( ctx, oneof ); + } + + for( auto & sub_message : message.messages ) + { + resolve_types( ctx, sub_message ); + } +} +}// namespace + +auto is_packed_array( const proto_file & file, const proto_field & field ) -> bool +{ + if( field.label != proto_field::Label::REPEATED ) + return { }; + + if( file.syntax.version < 3 ) + { + const auto p_packed = field.options.find( "packed" ); + return p_packed != field.options.end( ) && p_packed->second == "true"; + } + else + { + const auto p_packed = field.options.find( "packed" ); + return p_packed == field.options.end( ) || p_packed->second != "false"; + } +} + +auto is_scalar( const proto_field::Type & type ) -> bool +{ + switch( type ) + { + case proto_field::Type::NONE: + case proto_field::Type::MESSAGE: + case proto_field::Type::ENUM: + return false; + + case proto_field::Type::BYTES: + case proto_field::Type::STRING: + case proto_field::Type::BOOL: + case proto_field::Type::INT32: + case proto_field::Type::UINT32: + case proto_field::Type::INT64: + case proto_field::Type::UINT64: + case proto_field::Type::SINT32: + case proto_field::Type::SINT64: + case proto_field::Type::FLOAT: + case proto_field::Type::FIXED32: + case proto_field::Type::SFIXED32: + case proto_field::Type::DOUBLE: + case proto_field::Type::FIXED64: + case proto_field::Type::SFIXED64: + return true; + } + + return false; +} + +void resolve_types( proto_file & file ) +{ + auto ctx = search_ctx{ + .file = file, + .message = file.package, + .p_parent = nullptr, + }; + resolve_types( ctx, file.package ); +} diff --git a/3rdparty/simple-protobuf/src/spb-proto-compiler/ast/ast-types.h b/3rdparty/simple-protobuf/src/spb-proto-compiler/ast/ast-types.h new file mode 100644 index 0000000..8223a2c --- /dev/null +++ b/3rdparty/simple-protobuf/src/spb-proto-compiler/ast/ast-types.h @@ -0,0 +1,23 @@ +/***************************************************************************\ +* Name : ast types resolver * +* Description : resolve types in ast tree * +* Author : antonin.kriz@gmail.com * +* ------------------------------------------------------------------------- * +* This is free software; you can redistribute it and/or modify it under the * +* terms of the MIT license. A copy of the license can be found in the file * +* "LICENSE" at the root of this distribution. * +\***************************************************************************/ + +#pragma once + +#include "ast/proto-field.h" +#include "proto-file.h" + +[[nodiscard]] auto is_scalar( const proto_field::Type & type ) -> bool; +[[nodiscard]] auto is_packed_array( const proto_file & file, const proto_field & field ) -> bool; + +/** + * @brief resolve types in a proto file + * + */ +void resolve_types( proto_file & file ); diff --git a/3rdparty/simple-protobuf/src/spb-proto-compiler/ast/ast.cpp b/3rdparty/simple-protobuf/src/spb-proto-compiler/ast/ast.cpp new file mode 100644 index 0000000..646b6a1 --- /dev/null +++ b/3rdparty/simple-protobuf/src/spb-proto-compiler/ast/ast.cpp @@ -0,0 +1,19 @@ +/***************************************************************************\ +* Name : ast render * +* Description : resolve types in ast tree * +* Author : antonin.kriz@gmail.com * +* ------------------------------------------------------------------------- * +* This is free software; you can redistribute it and/or modify it under the * +* terms of the MIT license. A copy of the license can be found in the file * +* "LICENSE" at the root of this distribution. * +\***************************************************************************/ + +#include "ast.h" +#include "ast-types.h" +#include "ast-messages-order.h" + +void resolve_messages( proto_file & file ) +{ + resolve_types( file ); + resolve_messages_order( file ); +} diff --git a/3rdparty/simple-protobuf/src/spb-proto-compiler/ast/ast.h b/3rdparty/simple-protobuf/src/spb-proto-compiler/ast/ast.h new file mode 100644 index 0000000..122b514 --- /dev/null +++ b/3rdparty/simple-protobuf/src/spb-proto-compiler/ast/ast.h @@ -0,0 +1,31 @@ +/***************************************************************************\ +* Name : ast render * +* Description : resolve types in ast tree * +* Author : antonin.kriz@gmail.com * +* ------------------------------------------------------------------------- * +* This is free software; you can redistribute it and/or modify it under the * +* terms of the MIT license. A copy of the license can be found in the file * +* "LICENSE" at the root of this distribution. * +\***************************************************************************/ + +#pragma once + +#include "ast/proto-field.h" +#include "proto-file.h" + +/** + * @brief return scalar type or no type + * https://protobuf.dev/programming-guides/proto3/#scalar + * + * @param type_name + * @return scalar type or no type + */ +[[nodiscard]] auto get_encoder( const proto_field & field ) -> proto_field::Type; + +/** + * @brief resolve types and sort all messages in a proto file + * so if message A depends on message B + * then message B must be defined before message A + * + */ +void resolve_messages( proto_file & file ); diff --git a/3rdparty/simple-protobuf/src/spb-proto-compiler/ast/proto-comment.h b/3rdparty/simple-protobuf/src/spb-proto-compiler/ast/proto-comment.h new file mode 100644 index 0000000..1b3c7ed --- /dev/null +++ b/3rdparty/simple-protobuf/src/spb-proto-compiler/ast/proto-comment.h @@ -0,0 +1,20 @@ +/***************************************************************************\ +* Name : proto ast * +* Description : data structure for an comment * +* Author : antonin.kriz@gmail.com * +* ------------------------------------------------------------------------- * +* This is free software; you can redistribute it and/or modify it under the * +* terms of the MIT license. A copy of the license can be found in the file * +* "LICENSE" at the root of this distribution. * +\***************************************************************************/ + +#pragma once + +#include +#include + +struct proto_comment +{ + //- points to .proto file + std::vector< std::string_view > comments; +}; diff --git a/3rdparty/simple-protobuf/src/spb-proto-compiler/ast/proto-common.h b/3rdparty/simple-protobuf/src/spb-proto-compiler/ast/proto-common.h new file mode 100644 index 0000000..b79990d --- /dev/null +++ b/3rdparty/simple-protobuf/src/spb-proto-compiler/ast/proto-common.h @@ -0,0 +1,47 @@ +/***************************************************************************\ +* Name : proto ast * +* Description : data structure for common definitions * +* Link : https://protobuf.dev/programming-guides/proto3/ * +* Author : antonin.kriz@gmail.com * +* ------------------------------------------------------------------------- * +* This is free software; you can redistribute it and/or modify it under the * +* terms of the MIT license. A copy of the license can be found in the file * +* "LICENSE" at the root of this distribution. * +\***************************************************************************/ + +#pragma once + +#include "proto-comment.h" +#include +#include +#include +#include +#include + +using proto_reserved_range = std::vector< std::pair< int32_t, int32_t > >; +using proto_reserved_name = std::unordered_set< std::string_view >; +using proto_options = std::unordered_map< std::string_view, std::string_view >; + +struct proto_reserved +{ + proto_reserved_range reserved_range; + proto_reserved_name reserved_name; +}; + +/** + * @brief base attributes for most proto types + * + */ +struct proto_base +{ + //- points to .proto file + std::string_view name; + + //- field number + int32_t number; + + proto_options options; + proto_comment comment; +}; + +using proto_bases = std::vector< proto_base >; diff --git a/3rdparty/simple-protobuf/src/spb-proto-compiler/ast/proto-enum.h b/3rdparty/simple-protobuf/src/spb-proto-compiler/ast/proto-enum.h new file mode 100644 index 0000000..fb858ff --- /dev/null +++ b/3rdparty/simple-protobuf/src/spb-proto-compiler/ast/proto-enum.h @@ -0,0 +1,23 @@ +/***************************************************************************\ +* Name : proto ast * +* Description : data structure for an enum * +* Link : https://protobuf.dev/reference/protobuf/proto3-spec/#enum_definition * +* Author : antonin.kriz@gmail.com * +* ------------------------------------------------------------------------- * +* This is free software; you can redistribute it and/or modify it under the * +* terms of the MIT license. A copy of the license can be found in the file * +* "LICENSE" at the root of this distribution. * +\***************************************************************************/ + +#pragma once + +#include "proto-common.h" +#include + +struct proto_enum : public proto_base +{ + proto_bases fields; + proto_reserved reserved; +}; + +using proto_enums = std::vector< proto_enum >; diff --git a/3rdparty/simple-protobuf/src/spb-proto-compiler/ast/proto-field.h b/3rdparty/simple-protobuf/src/spb-proto-compiler/ast/proto-field.h new file mode 100644 index 0000000..048c0e9 --- /dev/null +++ b/3rdparty/simple-protobuf/src/spb-proto-compiler/ast/proto-field.h @@ -0,0 +1,78 @@ +/***************************************************************************\ +* Name : proto ast * +* Description : data structure for a field * +* Link : https://protobuf.dev/reference/protobuf/proto3-spec/#normal_field * +* Author : antonin.kriz@gmail.com * +* ------------------------------------------------------------------------- * +* This is free software; you can redistribute it and/or modify it under the * +* terms of the MIT license. A copy of the license can be found in the file * +* "LICENSE" at the root of this distribution. * +\***************************************************************************/ + +#pragma once + +#include "proto-common.h" +#include +#include +#include + +struct proto_field : public proto_base +{ + enum Type + { + NONE, + BOOL, + BYTES, + DOUBLE, + ENUM, + FLOAT, + FIXED32, + FIXED64, + INT32, + INT64, + MESSAGE, + SFIXED32, + SFIXED64, + SINT32, + SINT64, + STRING, + UINT32, + UINT64, + }; + + enum class BitType + { + NONE, + INT8, + INT16, + INT32, + INT64, + UINT8, + UINT16, + UINT32, + UINT64, + }; + + enum class Label + { + //- no type modifier, only `type` + NONE = 0, + //- std::optional< type > + OPTIONAL = 1, + //- std::vector< type > + REPEATED = 2, + //- std::unique_ptr< type >, used to break up circular references + PTR = 3, + }; + + Type type = Type::NONE; + Label label = Label::OPTIONAL; + + //- points to .proto file + std::string_view type_name; + + BitType bit_type = BitType::NONE; + std::string_view bit_field; +}; + +using proto_fields = std::vector< proto_field >; diff --git a/3rdparty/simple-protobuf/src/spb-proto-compiler/ast/proto-file.h b/3rdparty/simple-protobuf/src/spb-proto-compiler/ast/proto-file.h new file mode 100644 index 0000000..aa59320 --- /dev/null +++ b/3rdparty/simple-protobuf/src/spb-proto-compiler/ast/proto-file.h @@ -0,0 +1,40 @@ +/***************************************************************************\ +* Name : proto ast * +* Description : data structure for the whole proto file * +* Link : https://protobuf.dev/reference/protobuf/proto3-spec * +* Author : antonin.kriz@gmail.com * +* ------------------------------------------------------------------------- * +* This is free software; you can redistribute it and/or modify it under the * +* terms of the MIT license. A copy of the license can be found in the file * +* "LICENSE" at the root of this distribution. * +\***************************************************************************/ + +#pragma once + +#include "proto-comment.h" +#include "proto-common.h" +#include "proto-import.h" +#include "proto-message.h" +#include "proto-service.h" +#include "proto-syntax.h" +#include +#include +#include + +struct proto_file; + +using proto_files = std::vector< proto_file >; + +struct proto_file +{ + std::filesystem::path path; + std::string content; + proto_syntax syntax; + proto_comment comment; + proto_imports imports; + proto_message package; + proto_options options; + + proto_services services; + proto_files file_imports; +}; diff --git a/3rdparty/simple-protobuf/src/spb-proto-compiler/ast/proto-import.h b/3rdparty/simple-protobuf/src/spb-proto-compiler/ast/proto-import.h new file mode 100644 index 0000000..6bbe578 --- /dev/null +++ b/3rdparty/simple-protobuf/src/spb-proto-compiler/ast/proto-import.h @@ -0,0 +1,25 @@ +/***************************************************************************\ +* Name : proto ast * +* Description : data structure for imports * +* Link : https://protobuf.dev/reference/protobuf/proto3-spec/#import_statement * +* Author : antonin.kriz@gmail.com * +* ------------------------------------------------------------------------- * +* This is free software; you can redistribute it and/or modify it under the * +* terms of the MIT license. A copy of the license can be found in the file * +* "LICENSE" at the root of this distribution. * +\***************************************************************************/ + +#pragma once + +#include "proto-comment.h" +#include + +struct proto_import +{ + //- points to .proto file + std::string_view file_name; + + proto_comment comments; +}; + +using proto_imports = std::vector< proto_import >; \ No newline at end of file diff --git a/3rdparty/simple-protobuf/src/spb-proto-compiler/ast/proto-map.h b/3rdparty/simple-protobuf/src/spb-proto-compiler/ast/proto-map.h new file mode 100644 index 0000000..6365c4c --- /dev/null +++ b/3rdparty/simple-protobuf/src/spb-proto-compiler/ast/proto-map.h @@ -0,0 +1,23 @@ +/***************************************************************************\ +* Name : proto ast * +* Description : data structure for map * +* Link : https://protobuf.dev/reference/protobuf/proto3-spec/#map_field * +* Author : antonin.kriz@gmail.com * +* ------------------------------------------------------------------------- * +* This is free software; you can redistribute it and/or modify it under the * +* terms of the MIT license. A copy of the license can be found in the file * +* "LICENSE" at the root of this distribution. * +\***************************************************************************/ + +#pragma once + +#include "ast/proto-field.h" +#include "proto-common.h" + +struct proto_map : public proto_base +{ + proto_field key; + proto_field value; +}; + +using proto_maps = std::vector< proto_map >; \ No newline at end of file diff --git a/3rdparty/simple-protobuf/src/spb-proto-compiler/ast/proto-message.h b/3rdparty/simple-protobuf/src/spb-proto-compiler/ast/proto-message.h new file mode 100644 index 0000000..01cce45 --- /dev/null +++ b/3rdparty/simple-protobuf/src/spb-proto-compiler/ast/proto-message.h @@ -0,0 +1,43 @@ +/***************************************************************************\ +* Name : proto ast * +* Description : data structure for message * +* Link : https://protobuf.dev/reference/protobuf/proto3-spec/#message_definition * +* Author : antonin.kriz@gmail.com * +* ------------------------------------------------------------------------- * +* This is free software; you can redistribute it and/or modify it under the * +* terms of the MIT license. A copy of the license can be found in the file * +* "LICENSE" at the root of this distribution. * +\***************************************************************************/ + +#pragma once + +#include "proto-common.h" +#include "proto-enum.h" +#include "proto-field.h" +#include "proto-map.h" +#include "proto-oneof.h" +#include +#include +#include + +struct proto_message; +using proto_messages = std::vector< proto_message >; +using forwarded_declarations = std::set< std::string_view >; + +struct proto_message : public proto_base +{ + proto_fields fields; + proto_reserved_range extensions; + proto_messages messages; + proto_maps maps; + proto_oneofs oneofs; + proto_enums enums; + proto_reserved reserved; + + //- this will be used for type dependency checking + //- its an "resolved order" of the message in the .proto file + //- so we can later call std::sort on `.messages` + size_t resolved = 0; + //- forwarded declarations needed for type dependency checking + forwarded_declarations forwards; +}; diff --git a/3rdparty/simple-protobuf/src/spb-proto-compiler/ast/proto-oneof.h b/3rdparty/simple-protobuf/src/spb-proto-compiler/ast/proto-oneof.h new file mode 100644 index 0000000..926fdab --- /dev/null +++ b/3rdparty/simple-protobuf/src/spb-proto-compiler/ast/proto-oneof.h @@ -0,0 +1,23 @@ +/***************************************************************************\ +* Name : proto ast * +* Description : data structure for oneof syntax * +* Link : https://protobuf.dev/reference/protobuf/proto3-spec/#oneof_and_oneof_field * +* Author : antonin.kriz@gmail.com * +* ------------------------------------------------------------------------- * +* This is free software; you can redistribute it and/or modify it under the * +* terms of the MIT license. A copy of the license can be found in the file * +* "LICENSE" at the root of this distribution. * +\***************************************************************************/ + +#pragma once + +#include "proto-common.h" +#include "proto-field.h" +#include + +struct proto_oneof : public proto_base +{ + proto_fields fields; +}; + +using proto_oneofs = std::vector< proto_oneof >; diff --git a/3rdparty/simple-protobuf/src/spb-proto-compiler/ast/proto-service.h b/3rdparty/simple-protobuf/src/spb-proto-compiler/ast/proto-service.h new file mode 100644 index 0000000..a1a667f --- /dev/null +++ b/3rdparty/simple-protobuf/src/spb-proto-compiler/ast/proto-service.h @@ -0,0 +1,19 @@ +/***************************************************************************\ +* Name : proto ast * +* Description : data structure for proto service (NOT implemented) * +* Link : https://protobuf.dev/reference/protobuf/proto3-spec/#service_definition * +* Author : antonin.kriz@gmail.com * +* ------------------------------------------------------------------------- * +* This is free software; you can redistribute it and/or modify it under the * +* terms of the MIT license. A copy of the license can be found in the file * +* "LICENSE" at the root of this distribution. * +\***************************************************************************/ +#pragma once + +#include + +struct proto_service +{ +}; + +using proto_services = std::vector< proto_service >; \ No newline at end of file diff --git a/3rdparty/simple-protobuf/src/spb-proto-compiler/ast/proto-syntax.h b/3rdparty/simple-protobuf/src/spb-proto-compiler/ast/proto-syntax.h new file mode 100644 index 0000000..bc04e71 --- /dev/null +++ b/3rdparty/simple-protobuf/src/spb-proto-compiler/ast/proto-syntax.h @@ -0,0 +1,21 @@ +/***************************************************************************\ +* Name : proto ast * +* Description : data structure for proto syntax * +* Link : https://protobuf.dev/reference/protobuf/proto3-spec/#syntax * +* Author : antonin.kriz@gmail.com * +* ------------------------------------------------------------------------- * +* This is free software; you can redistribute it and/or modify it under the * +* terms of the MIT license. A copy of the license can be found in the file * +* "LICENSE" at the root of this distribution. * +\***************************************************************************/ + +#pragma once + +#include "proto-comment.h" +#include + +struct proto_syntax +{ + uint32_t version = 2; + proto_comment comments; +}; diff --git a/3rdparty/simple-protobuf/src/spb-proto-compiler/dumper/dumper.cpp b/3rdparty/simple-protobuf/src/spb-proto-compiler/dumper/dumper.cpp new file mode 100644 index 0000000..ae686fc --- /dev/null +++ b/3rdparty/simple-protobuf/src/spb-proto-compiler/dumper/dumper.cpp @@ -0,0 +1,42 @@ +/***************************************************************************\ +* Name : CPP dumper * +* Description : generate C++ src files for de/serialization * +* Author : antonin.kriz@gmail.com * +* ------------------------------------------------------------------------- * +* This is free software; you can redistribute it and/or modify it under the * +* terms of the MIT license. A copy of the license can be found in the file * +* "LICENSE" at the root of this distribution. * +\***************************************************************************/ + +#include "dumper.h" +#include "header.h" +#include "pb/dumper.h" +#include "json/dumper.h" + +void dump_cpp_header( const proto_file & file, std::ostream & stream ) +{ + try + { + dump_cpp_definitions( file, stream ); + dump_json_header( file, stream ); + dump_pb_header( file, stream ); + } + catch( const std::exception & e ) + { + throw std::runtime_error( file.path.string( ) + ":" + e.what( ) ); + } +} + +void dump_cpp( const proto_file & file, const std::filesystem::path & header_file, + std::ostream & file_stream ) +{ + try + { + dump_json_cpp( file, header_file, file_stream ); + dump_pb_cpp( file, header_file, file_stream ); + } + catch( const std::exception & e ) + { + throw std::runtime_error( file.path.string( ) + ":" + e.what( ) ); + } +} diff --git a/3rdparty/simple-protobuf/src/spb-proto-compiler/dumper/dumper.h b/3rdparty/simple-protobuf/src/spb-proto-compiler/dumper/dumper.h new file mode 100644 index 0000000..d77c7c2 --- /dev/null +++ b/3rdparty/simple-protobuf/src/spb-proto-compiler/dumper/dumper.h @@ -0,0 +1,33 @@ +/***************************************************************************\ +* Name : CPP dumper * +* Description : generate C++ src files for de/serialization * +* Author : antonin.kriz@gmail.com * +* ------------------------------------------------------------------------- * +* This is free software; you can redistribute it and/or modify it under the * +* terms of the MIT license. A copy of the license can be found in the file * +* "LICENSE" at the root of this distribution. * +\***************************************************************************/ + +#pragma once + +#include "ast/proto-file.h" +#include + +/** + * @brief dump C++ header file for parsed proto + * + * @param file parsed proto + * @param header_file output file + * @return C++ header file for a ast + */ +void dump_cpp_header( const proto_file & file, std::ostream & header_file ); + +/** + * @brief dump C++ file for parsed proto + * + * @param file parsed proto + * @param header_file generated C++ header file (ex: my.pb.h) + * @param file_stream output file name (ex: my.pb.cpp) + */ +void dump_cpp( const proto_file & file, const std::filesystem::path & header_file, + std::ostream & file_stream ); diff --git a/3rdparty/simple-protobuf/src/spb-proto-compiler/dumper/header.cpp b/3rdparty/simple-protobuf/src/spb-proto-compiler/dumper/header.cpp new file mode 100644 index 0000000..35fb30c --- /dev/null +++ b/3rdparty/simple-protobuf/src/spb-proto-compiler/dumper/header.cpp @@ -0,0 +1,666 @@ +/***************************************************************************\ +* Name : C++ header dumper * +* Description : generate C++ header with all structs and enums * +* Author : antonin.kriz@gmail.com * +* ------------------------------------------------------------------------- * +* This is free software; you can redistribute it and/or modify it under the * +* terms of the MIT license. A copy of the license can be found in the file * +* "LICENSE" at the root of this distribution. * +\***************************************************************************/ + +#include "header.h" +#include "../parser/char_stream.h" +#include "ast/ast.h" +#include "ast/proto-common.h" +#include "ast/proto-field.h" +#include "ast/proto-file.h" +#include "ast/proto-import.h" +#include "ast/proto-message.h" +#include "io/file.h" +#include "parser/options.h" +#include "parser/parser.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std::literals; + +namespace +{ + +using cpp_includes = std::set< std::string >; + +void dump_comment( std::ostream & stream, const proto_comment & comment ) +{ + for( const auto & comm : comment.comments ) + { + if( comm.starts_with( "//[[" ) ) + { + //- ignore options in comments + continue; + } + + stream << comm; + if( comm.back( ) != '\n' ) + { + stream << '\n'; + } + } +} + +auto trim_include( std::string_view str ) -> std::string +{ + auto p_begin = str.data( ); + auto p_end = str.data( ) + str.size( ); + while( p_begin < p_end && isspace( *p_begin ) ) + { + p_begin++; + } + + while( p_begin < p_end && isspace( p_end[ -1 ] ) ) + { + p_end--; + } + + if( p_begin == p_end ) + { + return { }; + } + + auto add_prefix = *p_begin != '"' && *p_begin != '<'; + auto add_postfix = p_end[ -1 ] != '"' && p_end[ -1 ] != '>'; + + if( add_prefix || add_postfix ) + { + return '"' + std::string( str ) + '"'; + } + return std::string( str ); +} + +void dump_includes( std::ostream & stream, const cpp_includes & includes ) +{ + for( auto & include : includes ) + { + auto file = trim_include( include ); + if( !file.empty( ) ) + { + stream << "#include " << file << "\n"; + } + } + stream << "\n"; +} + +auto contains_map( const proto_messages & messages ) -> bool +{ + for( const auto & message : messages ) + { + if( !message.maps.empty( ) ) + { + return true; + } + + if( contains_map( message.messages ) ) + { + return true; + } + } + return false; +} + +auto contains_oneof( const proto_messages & messages ) -> bool +{ + for( const auto & message : messages ) + { + if( !message.oneofs.empty( ) ) + { + return true; + } + + if( contains_oneof( message.messages ) ) + { + return true; + } + } + return false; +} + +void get_std_includes( cpp_includes & includes, const proto_file & file ) +{ + includes.insert( "" ); + includes.insert( "" ); + includes.insert( "" ); + includes.insert( "" ); + + if( contains_map( file.package.messages ) ) + { + includes.insert( "" ); + } + if( contains_oneof( file.package.messages ) ) + { + includes.insert( "" ); + } +} + +void get_include_from_options( cpp_includes & includes, const proto_options & options, + const proto_options & message_options, + const proto_options & file_options, std::string_view option_include ) +{ + if( auto include = options.find( option_include ); include != options.end( ) ) + { + includes.insert( std::string( include->second ) ); + return; + } + if( auto include = message_options.find( option_include ); include != message_options.end( ) ) + { + includes.insert( std::string( include->second ) ); + return; + } + if( auto include = file_options.find( option_include ); include != file_options.end( ) ) + { + includes.insert( std::string( include->second ) ); + return; + } +} + +void get_includes_from_field( cpp_includes & includes, const proto_field & field, + const proto_message & message, const proto_file & file ) +{ + if( field.label == proto_field::Label::OPTIONAL ) + { + get_include_from_options( includes, field.options, message.options, file.options, + option_optional_include ); + } + if( field.label == proto_field::Label::REPEATED ) + { + get_include_from_options( includes, field.options, message.options, file.options, + option_repeated_include ); + } + if( field.label == proto_field::Label::PTR ) + { + get_include_from_options( includes, field.options, message.options, file.options, + option_pointer_include ); + } + if( field.type == proto_field::Type::STRING ) + { + get_include_from_options( includes, field.options, message.options, file.options, + option_string_include ); + } + if( field.type == proto_field::Type::BYTES ) + { + get_include_from_options( includes, field.options, message.options, file.options, + option_bytes_include ); + } +} + +void get_message_includes( cpp_includes & includes, const proto_message & message, + const proto_file & file ) +{ + for( const auto & field : message.fields ) + { + get_includes_from_field( includes, field, message, file ); + } + + for( const auto & oneof : message.oneofs ) + { + for( const auto & field : oneof.fields ) + { + get_includes_from_field( includes, field, message, file ); + } + } + + for( const auto & map : message.maps ) + { + get_includes_from_field( includes, map.key, message, file ); + get_includes_from_field( includes, map.value, message, file ); + } + + for( const auto & m : message.messages ) + { + get_message_includes( includes, m, file ); + } +} + +void get_user_includes( cpp_includes & includes, const proto_file & file ) +{ + get_message_includes( includes, file.package, file ); +} + +void get_imports( cpp_includes & includes, const proto_file & file ) +{ + for( const auto & import : file.file_imports ) + { + includes.insert( "\"" + cpp_file_name_from_proto( import.path, ".pb.h" ).string( ) + "\"" ); + } +} + +void dump_pragma( std::ostream & stream, const proto_file & ) +{ + stream << "#pragma once\n\n"; +} + +void dump_syntax( std::ostream & stream, const proto_file & file ) +{ + dump_comment( stream, file.syntax.comments ); +} + +auto type_literal_suffix( proto_field::Type type ) -> std::string_view +{ + static constexpr auto type_map = + std::array< std::pair< proto_field::Type, std::string_view >, 12 >{ { + { proto_field::Type::INT64, "LL" }, + { proto_field::Type::UINT32, "U" }, + { proto_field::Type::UINT64, "ULL" }, + { proto_field::Type::SINT64, "LL" }, + { proto_field::Type::FIXED32, "U" }, + { proto_field::Type::FIXED64, "ULL" }, + { proto_field::Type::SFIXED64, "LL" }, + { proto_field::Type::FLOAT, "F" }, + } }; + + for( auto [ proto_type, suffix ] : type_map ) + { + if( type == proto_type ) + { + return suffix; + } + } + + return { }; +} + +auto get_field_bits( const proto_field & field ) -> std::string_view +{ + if( auto p_name = field.options.find( option_field_type ); p_name != field.options.end( ) ) + { + auto bitfield = p_name->second; + if( auto index = bitfield.find( ':' ); index != std::string_view::npos ) + { + const_cast< proto_field & >( field ).bit_field = bitfield.substr( index + 1 ); + return bitfield.substr( index ); + } + } + return { }; +} + +auto get_container_type( const proto_options & options, const proto_options & message_options, + const proto_options & file_options, std::string_view option, + std::string_view ctype, std::string_view default_type = { } ) + -> std::string +{ + if( auto p_name = options.find( option ); p_name != options.end( ) ) + { + return replace( p_name->second, "$", ctype ); + } + + if( auto p_name = message_options.find( option ); p_name != message_options.end( ) ) + { + return replace( p_name->second, "$", ctype ); + } + + if( auto p_name = file_options.find( option ); p_name != file_options.end( ) ) + { + return replace( p_name->second, "$", ctype ); + } + + return replace( default_type, "$", ctype ); +} + +auto get_enum_type( const proto_file & file, const proto_options & options, + const proto_options & message_options, const proto_options & file_options, + std::string_view default_type ) -> std::string_view +{ + static constexpr auto type_map = + std::array< std::pair< std::string_view, std::string_view >, 6 >{ { + { "int8"sv, "int8_t"sv }, + { "uint8"sv, "uint8_t"sv }, + { "int16"sv, "int16_t"sv }, + { "uint16"sv, "uint16_t"sv }, + { "int32"sv, "int32_t"sv }, + } }; + + auto ctype_from_pb = [ & ]( std::string_view type ) + { + for( auto [ proto_type, c_type ] : type_map ) + { + if( type == proto_type ) + { + return c_type; + } + } + throw_parse_error( file, type, "invalid enum type: " + std::string( type ) ); + }; + + if( auto p_name = options.find( option_enum_type ); p_name != options.end( ) ) + { + return ctype_from_pb( p_name->second ); + } + + if( auto p_name = message_options.find( option_enum_type ); p_name != message_options.end( ) ) + { + return ctype_from_pb( p_name->second ); + } + + if( auto p_name = file_options.find( option_enum_type ); p_name != file_options.end( ) ) + { + return ctype_from_pb( p_name->second ); + } + + return default_type; +} + +auto convert_to_ctype( const proto_file & file, const proto_field & field, + const proto_message & message = { } ) -> std::string +{ + if( field.bit_type != proto_field::BitType::NONE ) + { + switch( field.bit_type ) + { + case proto_field::BitType::NONE: + throw_parse_error( file, field.type_name, "invalid type" ); + case proto_field::BitType::INT8: + return "int8_t"; + case proto_field::BitType::INT16: + return "int16_t"; + case proto_field::BitType::INT32: + return "int32_t"; + case proto_field::BitType::INT64: + return "int64_t"; + case proto_field::BitType::UINT8: + return "uint8_t"; + case proto_field::BitType::UINT16: + return "uint16_t"; + case proto_field::BitType::UINT32: + return "uint32_t"; + case proto_field::BitType::UINT64: + return "uint64_t"; + } + } + + switch( field.type ) + { + case proto_field::Type::NONE: + throw_parse_error( file, field.type_name, "invalid type" ); + + case proto_field::Type::STRING: + return get_container_type( field.options, message.options, file.options, option_string_type, + "char", "std::string" ); + case proto_field::Type::BYTES: + return get_container_type( field.options, message.options, file.options, option_bytes_type, + "std::byte", "std::vector<$>" ); + case proto_field::Type::ENUM: + case proto_field::Type::MESSAGE: + return replace( field.type_name, ".", "::" ); + + case proto_field::Type::FLOAT: + return "float"; + case proto_field::Type::DOUBLE: + return "double"; + case proto_field::Type::BOOL: + return "bool"; + case proto_field::Type::SFIXED32: + case proto_field::Type::INT32: + case proto_field::Type::SINT32: + return "int32_t"; + case proto_field::Type::FIXED32: + case proto_field::Type::UINT32: + return "uint32_t"; + case proto_field::Type::SFIXED64: + case proto_field::Type::INT64: + case proto_field::Type::SINT64: + return "int64_t"; + case proto_field::Type::UINT64: + case proto_field::Type::FIXED64: + return "uint64_t"; + } + + throw_parse_error( file, field.type_name, "invalid type" ); +} + +void dump_field_type_and_name( std::ostream & stream, const proto_field & field, + const proto_message & message, const proto_file & file ) +{ + const auto ctype = convert_to_ctype( file, field, message ); + + switch( field.label ) + { + case proto_field::Label::NONE: + stream << ctype << ' ' << field.name << get_field_bits( field ); + return; + case proto_field::Label::OPTIONAL: + stream << get_container_type( field.options, message.options, file.options, + option_optional_type, ctype, "std::optional<$>" ); + break; + case proto_field::Label::REPEATED: + stream << get_container_type( field.options, message.options, file.options, + option_repeated_type, ctype, "std::vector<$>" ); + break; + case proto_field::Label::PTR: + stream << get_container_type( field.options, message.options, file.options, + option_pointer_type, ctype, "std::unique_ptr<$>" ); + break; + } + if( auto bitfield = get_field_bits( field ); !bitfield.empty( ) ) + { + throw_parse_error( file, bitfield, "bitfield can be used only with `required` label" ); + } + stream << ' ' << field.name; +} + +void dump_enum_field( std::ostream & stream, const proto_base & field ) +{ + dump_comment( stream, field.comment ); + + stream << field.name << " = " << field.number << ",\n"; +} + +void dump_enum( std::ostream & stream, const proto_enum & my_enum, const proto_message & message, + const proto_file & file ) +{ + dump_comment( stream, my_enum.comment ); + + stream << "enum class " << my_enum.name << " : " + << get_enum_type( file, my_enum.options, message.options, file.options, "int32_t" ) + << "\n{\n"; + for( const auto & field : my_enum.fields ) + { + dump_enum_field( stream, field ); + } + stream << "};\n"; +} + +void dump_message_oneof( std::ostream & stream, const proto_oneof & oneof, const proto_file & file ) +{ + dump_comment( stream, oneof.comment ); + + auto put_comma = false; + stream << "std::variant< "; + for( const auto & field : oneof.fields ) + { + if( put_comma ) + { + stream << ", "; + } + + stream << convert_to_ctype( file, field ); + put_comma = true; + } + stream << " > " << oneof.name << ";\n"; +} + +void dump_message_map( std::ostream & stream, const proto_map & map, const proto_file & file ) +{ + dump_comment( stream, map.comment ); + + stream << "std::map< " << convert_to_ctype( file, map.key ) << ", " + << convert_to_ctype( file, map.value ) << " > "; + stream << map.name << ";\n"; +} + +void dump_default_value( std::ostream & stream, const proto_field & field ) +{ + if( auto p_index = field.options.find( "default" ); p_index != field.options.end( ) ) + { + if( field.type == proto_field::Type::ENUM ) + { + stream << " = " << replace( field.type_name, ".", "::" ) << "::" << p_index->second; + } + else if( field.type == proto_field::Type::STRING && + ( p_index->second.size( ) < 2 || p_index->second.front( ) != '"' || + p_index->second.back( ) != '"' ) ) + { + stream << " = \"" << p_index->second << "\""; + } + else + { + stream << " = " << p_index->second << type_literal_suffix( field.type ); + } + } +} + +void dump_deprecated_attribute( std::ostream & stream, const proto_field & field ) +{ + if( auto p_index = field.options.find( "deprecated" ); + p_index != field.options.end( ) && p_index->second == "true" ) + { + stream << "[[deprecated]] "; + } +} + +void dump_message_field( std::ostream & stream, const proto_field & field, + const proto_message & message, const proto_file & file ) +{ + dump_comment( stream, field.comment ); + dump_deprecated_attribute( stream, field ); + dump_field_type_and_name( stream, field, message, file ); + dump_default_value( stream, field ); + stream << ";\n"; +} + +void dump_forwards( std::ostream & stream, const forwarded_declarations & forwards ) +{ + for( const auto & forward : forwards ) + { + stream << "struct " << forward << ";\n"; + } + if( !forwards.empty( ) ) + { + stream << '\n'; + } +} + +void dump_message( std::ostream & stream, const proto_message & message, const proto_file & file ) +{ + dump_comment( stream, message.comment ); + + stream << "struct " << message.name << "\n{\n"; + + dump_forwards( stream, message.forwards ); + for( const auto & sub_enum : message.enums ) + { + dump_enum( stream, sub_enum, message, file ); + } + + for( const auto & sub_message : message.messages ) + { + dump_message( stream, sub_message, file ); + } + + for( const auto & field : message.fields ) + { + dump_message_field( stream, field, message, file ); + } + + for( const auto & map : message.maps ) + { + dump_message_map( stream, map, file ); + } + + for( const auto & oneof : message.oneofs ) + { + dump_message_oneof( stream, oneof, file ); + } + + //- TODO: is this used in any way? + // stream << "auto operator == ( const " << message.name << " & ) const noexcept -> + // bool = default;\n"; stream << "auto operator != ( const " << message.name << " & + // ) const noexcept -> bool = default;\n"; + stream << "};\n"; +} + +void dump_messages( std::ostream & stream, const proto_file & file ) +{ + dump_forwards( stream, file.package.forwards ); + for( const auto & message : file.package.messages ) + { + dump_message( stream, message, file ); + } +} + +void dump_enums( std::ostream & stream, const proto_file & file ) +{ + for( const auto & my_enum : file.package.enums ) + { + dump_enum( stream, my_enum, file.package, file ); + } +} + +void dump_package_begin( std::ostream & stream, const proto_file & file ) +{ + dump_comment( stream, file.package.comment ); + if( !file.package.name.empty( ) ) + { + stream << "namespace " << replace( file.package.name, ".", "::" ) << "\n{\n"; + } +} + +void dump_package_end( std::ostream & stream, const proto_file & file ) +{ + if( !file.package.name.empty( ) ) + { + stream << "}// namespace " << replace( file.package.name, ".", "::" ) << "\n\n"; + } +} +}// namespace + +void throw_parse_error( const proto_file & file, std::string_view at, std::string_view message ) +{ + auto stream = spb::char_stream( file.content ); + stream.skip_to( at.data( ) ); + stream.throw_parse_error( message ); +} + +void dump_cpp_definitions( const proto_file & file, std::ostream & stream ) +{ + auto includes = cpp_includes( ); + dump_pragma( stream, file ); + get_imports( includes, file ); + get_std_includes( includes, file ); + get_user_includes( includes, file ); + dump_includes( stream, includes ); + dump_syntax( stream, file ); + dump_package_begin( stream, file ); + dump_enums( stream, file ); + dump_messages( stream, file ); + dump_package_end( stream, file ); +} + +auto replace( std::string_view input, std::string_view what, std::string_view with ) -> std::string +{ + auto result = std::string( input ); + auto pos = size_t{ }; + + while( ( pos = result.find( what, pos ) ) != std::string::npos ) + { + result.replace( pos, what.size( ), with ); + pos += with.size( ); + } + + return result; +} diff --git a/3rdparty/simple-protobuf/src/spb-proto-compiler/dumper/header.h b/3rdparty/simple-protobuf/src/spb-proto-compiler/dumper/header.h new file mode 100644 index 0000000..c915802 --- /dev/null +++ b/3rdparty/simple-protobuf/src/spb-proto-compiler/dumper/header.h @@ -0,0 +1,44 @@ +/***************************************************************************\ +* Name : C++ header dumper * +* Description : generate C++ header with all structs and enums * +* Author : antonin.kriz@gmail.com * +* ------------------------------------------------------------------------- * +* This is free software; you can redistribute it and/or modify it under the * +* terms of the MIT license. A copy of the license can be found in the file * +* "LICENSE" at the root of this distribution. * +\***************************************************************************/ + +#pragma once + +#include "ast/proto-file.h" +#include + +/** + * @brief dump C++ header file for parsed proto + * dump all structs and enums + * + * @param file parsed proto + * @param stream output stream + */ +void dump_cpp_definitions( const proto_file & file, std::ostream & stream ); + +/** + * Replaces all occurrences of a substring in a given string with another substring. + * + * @param input the original string + * @param what the substring to be replaced + * @param with the substring to replace 'what' with + * + * @return the modified string after replacements + */ +auto replace( std::string_view input, std::string_view what, std::string_view with ) -> std::string; + +/** + * @brief throw parse error 'at' offset + * + * @param file parsed file + * @param at error offset + * @param message message to the user + */ +[[noreturn]] void throw_parse_error( const proto_file & file, std::string_view at, + std::string_view message ); diff --git a/3rdparty/simple-protobuf/src/spb-proto-compiler/dumper/json/dumper.cpp b/3rdparty/simple-protobuf/src/spb-proto-compiler/dumper/json/dumper.cpp new file mode 100644 index 0000000..cbbaa8d --- /dev/null +++ b/3rdparty/simple-protobuf/src/spb-proto-compiler/dumper/json/dumper.cpp @@ -0,0 +1,539 @@ +/***************************************************************************\ +* Name : json dumper * +* Description : generate C++ src files for json de/serialization * +* Author : antonin.kriz@gmail.com * +* ------------------------------------------------------------------------- * +* This is free software; you can redistribute it and/or modify it under the * +* terms of the MIT license. A copy of the license can be found in the file * +* "LICENSE" at the root of this distribution. * +\***************************************************************************/ + +#include "dumper.h" +#include "ast/ast.h" +#include "ast/proto-field.h" +#include "ast/proto-file.h" +#include "io/file.h" +#include "parser/parser.h" +#include "template-h.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std::literals; + +namespace +{ + +auto replace( std::string_view input, std::string_view what, std::string_view with ) -> std::string +{ + auto result = std::string( input ); + auto pos = size_t{ }; + + while( ( pos = result.find( what, pos ) ) != std::string::npos ) + { + result.replace( pos, what.size( ), with ); + pos += with.size( ); + } + + return result; +} + +void dump_prototypes( std::ostream & stream, std::string_view type ) +{ + stream << replace( file_json_header_template, "$", type ); +} + +auto json_name_from_options( const proto_options & options ) -> std::string_view +{ + if( auto p_option = options.find( "json_name" ); p_option != options.end( ) ) + { + return p_option->second; + } + + return ""sv; +} + +auto convert_to_camelCase( std::string_view input ) -> std::string +{ + auto result = std::string( input ); + if( !result.empty( ) ) + { + result[ 0 ] = char( std::tolower( result[ 0 ] ) ); + } + + if( input.find( '_' ) == std::string::npos ) + { + return result; + } + + auto index = 0U; + auto up = false; + for( auto c : input ) + { + if( c == '_' ) + { + up = true; + } + else + { + result[ index ] = char( ( up && index > 0 ) ? std::toupper( c ) : std::tolower( c ) ); + index += 1; + up = false; + } + } + result.resize( index ); + return result; +} + +auto json_field_name( const proto_base & field ) -> std::string +{ + if( const auto result = json_name_from_options( field.options ); !result.empty( ) ) + { + return std::string( result ); + } + + return std::string( field.name ); +} + +auto json_field_name_or_camelCase( const proto_base & field ) -> std::string +{ + if( const auto result = json_name_from_options( field.options ); !result.empty( ) ) + { + return std::string( result ); + } + + return convert_to_camelCase( field.name ); +} + +void dump_prototypes( std::ostream & stream, const proto_message & message, + std::string_view parent ) +{ + const auto message_with_parent = std::string( parent ) + "::" + std::string( message.name ); + dump_prototypes( stream, message_with_parent ); +} + +void dump_prototypes( std::ostream & stream, const proto_enum & my_enum, std::string_view parent ) +{ + const auto enum_with_parent = std::string( parent ) + "::" + std::string( my_enum.name ); + dump_prototypes( stream, enum_with_parent ); +} + +void dump_prototypes( std::ostream & stream, const proto_enums & enums, std::string_view parent ) +{ + for( const auto & my_enum : enums ) + { + dump_prototypes( stream, my_enum, parent ); + } +} + +void dump_prototypes( std::ostream & stream, const proto_messages & messages, + std::string_view parent ) +{ + for( const auto & message : messages ) + { + dump_prototypes( stream, message, parent ); + } + + for( const auto & message : messages ) + { + if( message.messages.empty( ) ) + { + continue; + } + const auto message_with_parent = std::string( parent ) + "::" + std::string( message.name ); + dump_prototypes( stream, message.messages, message_with_parent ); + } + + for( const auto & message : messages ) + { + if( message.enums.empty( ) ) + { + continue; + } + const auto message_with_parent = std::string( parent ) + "::" + std::string( message.name ); + dump_prototypes( stream, message.enums, message_with_parent ); + } +} + +void dump_prototypes( std::ostream & stream, const proto_file & file ) +{ + const auto package_name = replace( file.package.name, ".", "::" ); + dump_prototypes( stream, file.package.messages, package_name ); + dump_prototypes( stream, file.package.enums, package_name ); +} + +void dump_cpp_includes( std::ostream & stream, std::string_view header_file_path ) +{ + stream << "#include \"" << header_file_path << "\"\n" + << "#include \n" + "#include \n" + "#include \n\n"; +} + +void dump_cpp_close_namespace( std::ostream & stream, std::string_view name ) +{ + stream << "} // namespace " << name << "\n"; +} + +void dump_cpp_open_namespace( std::ostream & stream, std::string_view name ) +{ + stream << "namespace " << name << "\n{\n"; +} + +void dump_cpp_serialize_value( std::ostream & stream, const proto_oneof & oneof ) +{ + stream << "\t{\n\t\tconst auto index = value." << oneof.name << ".index( );\n"; + stream << "\t\tswitch( index )\n\t\t{\n"; + for( size_t i = 0; i < oneof.fields.size( ); ++i ) + { + stream << "\t\t\tcase " << i << ":\n\t\t\t\treturn stream.serialize( \"" + << json_field_name( oneof.fields[ i ] ) << "\"sv, std::get< " << i << " >( value." + << oneof.name << ") );\n"; + } + stream << "\t\t}\n\t}\n\n"; +} + +void dump_cpp_serialize_value( std::ostream & stream, const proto_enum & my_enum, + std::string_view full_name ) +{ + if( my_enum.fields.empty( ) ) + { + stream << "void serialize_value( detail::ostream &, const " << full_name << " & )\n{\n"; + stream << "\treturn ;\n}\n\n"; + return; + } + + stream << "void serialize_value( detail::ostream & stream, const " << full_name + << " & value )\n{\n"; + stream << "\tswitch( value )\n\t{\n"; + + std::set< int32_t > numbers_taken; + for( const auto & field : my_enum.fields ) + { + if( !numbers_taken.insert( field.number ).second ) + { + continue; + } + + stream << "\tcase " << full_name << "::" << field.name + << ":\n\t\treturn stream.serialize( \"" << field.name << "\"sv);\n"; + } + stream << "\tdefault:\n\t\tthrow std::system_error( std::make_error_code( " + "std::errc::invalid_argument ) );\n"; + stream << "\t}\n}\n\n"; +} + +void dump_cpp_deserialize_value( std::ostream & stream, const proto_enum & my_enum, + std::string_view full_name ) +{ + if( my_enum.fields.empty( ) ) + { + stream << "void deserialize_value( detail::istream &, " << full_name << " & )\n{\n"; + stream << "\n}\n\n"; + return; + } + + size_t key_size_min = UINT32_MAX; + size_t key_size_max = 0; + + auto name_map = std::multimap< uint32_t, std::string_view >( ); + for( const auto & field : my_enum.fields ) + { + name_map.emplace( spb::json::detail::djb2_hash( field.name ), field.name ); + key_size_min = std::min( key_size_min, field.name.size( ) ); + key_size_max = std::max( key_size_max, field.name.size( ) ); + } + + stream << "void deserialize_value( detail::istream & stream, " << full_name + << " & value )\n{\n"; + stream << "\tauto enum_value = stream.deserialize_string_or_int( " << key_size_min << ", " + << key_size_max << " );\n"; + stream << "\tstd::visit( detail::overloaded{\n\t\t[&]( std::string_view enum_str )\n\t\t{\n"; + stream << "\t\t\tconst auto enum_hash = djb2_hash( enum_str );\n"; + stream << "\t\t\tswitch( enum_hash )\n\t\t\t{\n"; + auto last_hash = name_map.begin( )->first + 1; + auto put_break = false; + for( const auto & [ hash, name ] : name_map ) + { + if( hash != last_hash ) + { + last_hash = hash; + if( put_break ) + { + stream << "\t\t\t\tbreak ;\n"; + } + put_break = true; + stream << "\t\t\tcase detail::djb2_hash( \"" << name << "\"sv ):\n"; + } + stream << "\t\t\t\tif( enum_str == \"" << name << "\"sv ){\n\t\t\t\t\tvalue = " << full_name + << "::" << name << ";\n\t\t\t\t\treturn ;\t\t\t\t}\n"; + } + stream << "\t\t\t\tbreak ;\n"; + stream << "\t\t\t}\n\t\t\tthrow std::system_error( std::make_error_code( " + "std::errc::invalid_argument ) );\n"; + stream << "\t\t},\n\t\t[&]( int32_t enum_int )\n\t\t{\n\t\t\tswitch( " << full_name + << "( enum_int ) )\n\t\t\t{\n"; + std::set< int32_t > numbers_taken; + for( const auto & field : my_enum.fields ) + { + if( !numbers_taken.insert( field.number ).second ) + { + continue; + } + + stream << "\t\t\tcase " << full_name << "::" << field.name << ":\n"; + } + stream << "\t\t\t\tvalue = " << full_name << "( enum_int );\n\t\t\t\treturn ;\n"; + stream << "\t\t\t}\n\t\t\tthrow std::system_error( std::make_error_code( " + "std::errc::invalid_argument ) );\n"; + stream << "\t\t}\n\t}, enum_value );\n}\n\n"; +} + +void dump_cpp_serialize_value( std::ostream & stream, const proto_message & message, + std::string_view full_name ) +{ + if( message.fields.empty( ) && message.maps.empty( ) && message.oneofs.empty( ) ) + { + stream << "void serialize_value( detail::ostream & , const " << full_name + << " & )\n{\n}\n\n"; + return; + } + + stream << "void serialize_value( detail::ostream & stream, const " << full_name + << " & value )\n{\n"; + for( const auto & field : message.fields ) + { + stream << "\tstream.serialize( \"" << json_field_name( field ) << "\"sv, value." + << field.name << " );\n"; + } + for( const auto & map : message.maps ) + { + stream << "\tstream.serialize( \"" << map.name << "\"sv, value." << map.name << " );\n"; + } + for( const auto & oneof : message.oneofs ) + { + dump_cpp_serialize_value( stream, oneof ); + } + stream << "}\n"; +} + +void dump_cpp_deserialize_value( std::ostream & stream, const proto_message & message, + std::string_view full_name ) +{ + if( message.fields.empty( ) && message.maps.empty( ) && message.oneofs.empty( ) ) + { + stream << "void deserialize_value( detail::istream &, " << full_name << " & )\n{\n"; + stream << "\n}\n\n"; + return; + } + + //- json deserializer needs to accept both camelCase (parsed_name) and the original field name + struct one_field + { + std::string parsed_name; + std::string_view name; + size_t oneof_index = SIZE_MAX; + std::string_view bitfield; + }; + + size_t key_size_min = UINT32_MAX; + size_t key_size_max = 0; + + auto name_map = std::multimap< uint32_t, one_field >( ); + for( const auto & field : message.fields ) + { + key_size_min = std::min( key_size_min, field.name.size( ) ); + key_size_max = std::max( key_size_max, field.name.size( ) ); + + const auto field_name = json_field_name_or_camelCase( field ); + name_map.emplace( spb::json::detail::djb2_hash( field_name ), + one_field{ + .parsed_name = field_name, + .name = field.name, + .bitfield = field.bit_field, + } ); + if( field_name != field.name ) + { + key_size_min = std::min( key_size_min, field_name.size( ) ); + key_size_max = std::max( key_size_max, field_name.size( ) ); + + name_map.emplace( spb::json::detail::djb2_hash( field.name ), + one_field{ + .parsed_name = std::string( field.name ), + .name = field.name, + .bitfield = field.bit_field, + } ); + } + } + for( const auto & field : message.maps ) + { + key_size_min = std::min( key_size_min, field.name.size( ) ); + key_size_max = std::max( key_size_max, field.name.size( ) ); + + const auto field_name = json_field_name_or_camelCase( field ); + name_map.emplace( spb::json::detail::djb2_hash( field_name ), + one_field{ + .parsed_name = field_name, + .name = field.name, + } ); + if( field_name != field.name ) + { + key_size_min = std::min( key_size_min, field_name.size( ) ); + key_size_max = std::max( key_size_max, field_name.size( ) ); + + name_map.emplace( + spb::json::detail::djb2_hash( field.name ), + one_field{ .parsed_name = std::string( field.name ), .name = field.name } ); + } + } + for( const auto & oneof : message.oneofs ) + { + for( size_t i = 0; i < oneof.fields.size( ); ++i ) + { + key_size_min = std::min( key_size_min, oneof.fields[ i ].name.size( ) ); + key_size_max = std::max( key_size_max, oneof.fields[ i ].name.size( ) ); + + const auto field_name = json_field_name_or_camelCase( oneof.fields[ i ] ); + name_map.emplace( spb::json::detail::djb2_hash( field_name ), + one_field{ + .parsed_name = field_name, + .name = oneof.name, + .oneof_index = i, + } ); + if( field_name != oneof.fields[ i ].name ) + { + key_size_min = std::min( key_size_min, field_name.size( ) ); + key_size_max = std::max( key_size_max, field_name.size( ) ); + + name_map.emplace( spb::json::detail::djb2_hash( oneof.fields[ i ].name ), + one_field{ + .parsed_name = std::string( oneof.fields[ i ].name ), + .name = oneof.name, + .oneof_index = i, + } ); + } + } + } + + stream << "void deserialize_value( detail::istream & stream, " << full_name + << " & value )\n{\n"; + stream << "\tauto key = stream.deserialize_key( " << key_size_min << ", " << key_size_max + << " );\n"; + stream << "\tswitch( djb2_hash( key ) )\n\t{\n"; + + auto last_hash = name_map.begin( )->first + 1; + auto put_break = false; + for( const auto & [ hash, field ] : name_map ) + { + if( hash != last_hash ) + { + if( put_break ) + { + stream << "\t\t\t\tbreak;\n"; + } + put_break = true; + last_hash = hash; + stream << "\t\tcase detail::djb2_hash( \"" << field.parsed_name << "\"sv ):\n"; + } + stream << "\t\t\tif( key == \"" << field.parsed_name << "\"sv )\n\t\t\t{\n"; + if( field.oneof_index == SIZE_MAX ) + { + if( !field.bitfield.empty( ) ) + { + + stream << "\t\t\t\tvalue." << field.name + << " = stream.deserialize_bitfield< decltype( value." << field.name + << " ) >( " << field.bitfield << " );\n\t\t\t\treturn ;\n"; + } + else + { + stream << "\t\t\t\treturn stream.deserialize( value." << field.name << " );\n"; + } + } + else + { + stream << "\t\t\t\treturn stream.deserialize_variant<" << field.oneof_index + << ">( value." << field.name << " );\n"; + } + stream << "\t\t\t}\n"; + } + stream << "\t\t\tbreak;\n\t}\n\treturn stream.skip_value( );\n}\n"; +} + +void dump_cpp_enum( std::ostream & stream, const proto_enum & my_enum, std::string_view parent ) +{ + const auto full_name = std::string( parent ) + "::" + std::string( my_enum.name ); + dump_cpp_open_namespace( stream, "detail" ); + dump_cpp_serialize_value( stream, my_enum, full_name ); + dump_cpp_deserialize_value( stream, my_enum, full_name ); + dump_cpp_close_namespace( stream, "detail" ); +} + +void dump_cpp_enums( std::ostream & stream, const proto_enums & enums, std::string_view parent ) +{ + for( const auto & my_enum : enums ) + { + dump_cpp_enum( stream, my_enum, parent ); + } +} + +void dump_cpp_messages( std::ostream & stream, const proto_messages & messages, + std::string_view parent ); + +void dump_cpp_message( std::ostream & stream, const proto_message & message, + std::string_view parent ) +{ + const auto full_name = std::string( parent ) + "::" + std::string( message.name ); + + dump_cpp_enums( stream, message.enums, full_name ); + + dump_cpp_open_namespace( stream, "detail" ); + dump_cpp_serialize_value( stream, message, full_name ); + dump_cpp_deserialize_value( stream, message, full_name ); + dump_cpp_close_namespace( stream, "detail" ); + + dump_cpp_messages( stream, message.messages, full_name ); +} + +void dump_cpp_messages( std::ostream & stream, const proto_messages & messages, + std::string_view parent ) +{ + for( const auto & message : messages ) + { + dump_cpp_message( stream, message, parent ); + } +} + +void dump_cpp( std::ostream & stream, const proto_file & file ) +{ + const auto str_namespace = "::" + replace( file.package.name, ".", "::" ); + dump_cpp_enums( stream, file.package.enums, str_namespace ); + dump_cpp_messages( stream, file.package.messages, str_namespace ); +} + +}// namespace + +void dump_json_header( const proto_file & file, std::ostream & stream ) +{ + dump_cpp_open_namespace( stream, "spb::json::detail" ); + stream << "struct ostream;\nstruct istream;\n"; + dump_prototypes( stream, file ); + dump_cpp_close_namespace( stream, "spb::json::detail" ); +} + +void dump_json_cpp( const proto_file & file, const std::filesystem::path & header_file, + std::ostream & stream ) +{ + dump_cpp_includes( stream, header_file.string( ) ); + dump_cpp_open_namespace( stream, "spb::json" ); + dump_cpp( stream, file ); + dump_cpp_close_namespace( stream, "spb::json" ); +} \ No newline at end of file diff --git a/3rdparty/simple-protobuf/src/spb-proto-compiler/dumper/json/dumper.h b/3rdparty/simple-protobuf/src/spb-proto-compiler/dumper/json/dumper.h new file mode 100644 index 0000000..fd4a1a4 --- /dev/null +++ b/3rdparty/simple-protobuf/src/spb-proto-compiler/dumper/json/dumper.h @@ -0,0 +1,33 @@ +/***************************************************************************\ +* Name : json dumper * +* Description : generate C++ src files for json de/serialization * +* Author : antonin.kriz@gmail.com * +* ------------------------------------------------------------------------- * +* This is free software; you can redistribute it and/or modify it under the * +* terms of the MIT license. A copy of the license can be found in the file * +* "LICENSE" at the root of this distribution. * +\***************************************************************************/ + +#pragma once + +#include "ast/proto-file.h" +#include + +/** + * @brief dump C++ header file for parsed proto + * + * @param file parsed proto + * @param stream output stream + * @return C++ header file for a ast + */ +void dump_json_header( const proto_file & file, std::ostream & stream ); + +/** + * @brief dump C++ file for parsed proto + * + * @param file parsed proto + * @param header_file generated C++ header file (ex: my.pb.h) + * @param stream output stream + */ +void dump_json_cpp( const proto_file & file, const std::filesystem::path & header_file, + std::ostream & stream ); diff --git a/3rdparty/simple-protobuf/src/spb-proto-compiler/dumper/json/template-h.h b/3rdparty/simple-protobuf/src/spb-proto-compiler/dumper/json/template-h.h new file mode 100644 index 0000000..e56d73a --- /dev/null +++ b/3rdparty/simple-protobuf/src/spb-proto-compiler/dumper/json/template-h.h @@ -0,0 +1,19 @@ +/***************************************************************************\ +* Name : json dumper * +* Description : C++ template used for json de/serialization * +* Author : antonin.kriz@gmail.com * +* ------------------------------------------------------------------------- * +* This is free software; you can redistribute it and/or modify it under the * +* terms of the MIT license. A copy of the license can be found in the file * +* "LICENSE" at the root of this distribution. * +\***************************************************************************/ + +#pragma once + +#include + +constexpr std::string_view file_json_header_template = R"( +void serialize_value( ostream & stream, const $ & value ); +void deserialize_value( istream & stream, $ & value ); + +)"; diff --git a/3rdparty/simple-protobuf/src/spb-proto-compiler/dumper/pb/dumper.cpp b/3rdparty/simple-protobuf/src/spb-proto-compiler/dumper/pb/dumper.cpp new file mode 100644 index 0000000..68ec8e4 --- /dev/null +++ b/3rdparty/simple-protobuf/src/spb-proto-compiler/dumper/pb/dumper.cpp @@ -0,0 +1,310 @@ +/***************************************************************************\ +* Name : pb dumper * +* Description : generate C++ src files for pb de/serialization * +* Author : antonin.kriz@gmail.com * +* ------------------------------------------------------------------------- * +* This is free software; you can redistribute it and/or modify it under the * +* terms of the MIT license. A copy of the license can be found in the file * +* "LICENSE" at the root of this distribution. * +\***************************************************************************/ + +#include "dumper.h" +#include "ast/ast-types.h" +#include "ast/proto-field.h" +#include "ast/proto-file.h" +#include "template-h.h" +#include +#include +#include +#include +#include + +using namespace std::literals; + +namespace +{ + +auto replace( std::string_view input, std::string_view what, std::string_view with ) -> std::string +{ + auto result = std::string( input ); + auto pos = size_t{ }; + + while( ( pos = result.find( what, pos ) ) != std::string::npos ) + { + result.replace( pos, what.size( ), with ); + pos += with.size( ); + } + + return result; +} + +void dump_prototypes( std::ostream & stream, std::string_view type ) +{ + stream << replace( file_pb_header_template, "$", type ); +} + +void dump_prototypes( std::ostream & stream, const proto_message & message, + std::string_view parent ) +{ + const auto message_with_parent = std::string( parent ) + "::" + std::string( message.name ); + dump_prototypes( stream, message_with_parent ); +} + +void dump_prototypes( std::ostream & stream, const proto_messages & messages, + std::string_view parent ) +{ + for( const auto & message : messages ) + { + dump_prototypes( stream, message, parent ); + } + + for( const auto & message : messages ) + { + if( message.messages.empty( ) ) + { + continue; + } + const auto message_with_parent = std::string( parent ) + "::" + std::string( message.name ); + dump_prototypes( stream, message.messages, message_with_parent ); + } +} + +void dump_prototypes( std::ostream & stream, const proto_file & file ) +{ + const auto package_name = replace( file.package.name, ".", "::" ); + dump_prototypes( stream, file.package.messages, package_name ); +} + +void dump_cpp_includes( std::ostream & stream, std::string_view header_file_path ) +{ + stream << "#include \"" << header_file_path << "\"\n" + << "#include \n" + "#include \n\n"; +} + +void dump_cpp_close_namespace( std::ostream & stream, std::string_view name ) +{ + stream << "} // namespace " << name << "\n"; +} + +void dump_cpp_open_namespace( std::ostream & stream, std::string_view name ) +{ + stream << "namespace " << name << "\n{\n"; +} + +auto encoder_type_str( const proto_file & file, const proto_field & field ) -> std::string +{ + switch( field.type ) + { + case proto_field::Type::NONE: + case proto_field::Type::BYTES: + case proto_field::Type::MESSAGE: + case proto_field::Type::STRING: + return { }; + + case proto_field::Type::BOOL: + case proto_field::Type::ENUM: + case proto_field::Type::INT32: + case proto_field::Type::UINT32: + case proto_field::Type::INT64: + case proto_field::Type::UINT64: + return is_packed_array( file, field ) ? "scalar_encoder::varint | scalar_encoder::packed" + : "scalar_encoder::varint"; + + case proto_field::Type::SINT32: + case proto_field::Type::SINT64: + return is_packed_array( file, field ) ? "scalar_encoder::svarint | scalar_encoder::packed" + : "scalar_encoder::svarint"; + + case proto_field::Type::FLOAT: + case proto_field::Type::FIXED32: + case proto_field::Type::SFIXED32: + return is_packed_array( file, field ) ? "scalar_encoder::i32 | scalar_encoder::packed" + : "scalar_encoder::i32"; + + case proto_field::Type::DOUBLE: + case proto_field::Type::FIXED64: + case proto_field::Type::SFIXED64: + return is_packed_array( file, field ) ? "scalar_encoder::i64 | scalar_encoder::packed" + : "scalar_encoder::i64"; + } + return { }; +} + +auto encoder_type( const proto_file & file, const proto_field & field ) -> std::string +{ + const auto encoder = encoder_type_str( file, field ); + if( encoder.empty( ) ) + return { }; + + return "_as<" + encoder + ">"; +} + +auto map_encoder_type( const proto_file & file, const proto_field & key, const proto_field & value ) + -> std::string +{ + const auto key_encoder = encoder_type_str( file, key ); + const auto value_encoder = encoder_type_str( file, value ); + + return "_as< scalar_encoder_combine( " + + ( key_encoder.empty( ) ? std::string( "{}" ) : key_encoder ) + ", " + + ( value_encoder.empty( ) ? std::string( "{}" ) : value_encoder ) + " ) >"; +} + +auto bitfield_encoder_type( const proto_file & file, const proto_field & field ) -> std::string +{ + const auto encoder = encoder_type_str( file, field ); + return "_as< " + encoder + ", decltype( value." + std::string( field.name ) + ") >"; +} + +void dump_cpp_serialize_value( std::ostream & stream, const proto_file & file, + const proto_oneof & oneof ) +{ + stream << "\t{\n\t\tconst auto index = value." << oneof.name << ".index( );\n"; + stream << "\t\tswitch( index )\n\t\t{\n"; + for( size_t i = 0; i < oneof.fields.size( ); ++i ) + { + stream << "\t\t\tcase " << i << ":\n\t\t\t\treturn stream.serialize" + << encoder_type( file, oneof.fields[ i ] ) << "( " << oneof.fields[ i ].number + << ", std::get< " << i << " >( value." << oneof.name << ") );\n"; + } + stream << "\t\t}\n\t}\n\n"; +} + +void dump_cpp_serialize_value( std::ostream & stream, const proto_file & file, + const proto_message & message, std::string_view full_name ) +{ + if( message.fields.empty( ) && message.maps.empty( ) && message.oneofs.empty( ) ) + { + stream << "void serialize( detail::ostream & , const " << full_name << " & )\n{\n}\n\n"; + return; + } + + stream << "void serialize( detail::ostream & stream, const " << full_name << " & value )\n{\n"; + for( const auto & field : message.fields ) + { + stream << "\tstream.serialize" << encoder_type( file, field ) << "( " << field.number + << ", value." << field.name << " );\n"; + } + for( const auto & map : message.maps ) + { + stream << "\tstream.serialize" << map_encoder_type( file, map.key, map.value ) << "( " + << map.number << ", value." << map.name << " );\n"; + } + for( const auto & oneof : message.oneofs ) + { + dump_cpp_serialize_value( stream, file, oneof ); + } + stream << "}\n"; +} + +void dump_cpp_deserialize_value( std::ostream & stream, const proto_file & file, + const proto_message & message, std::string_view full_name ) +{ + if( message.fields.empty( ) && message.maps.empty( ) && message.oneofs.empty( ) ) + { + stream << "void deserialize_value( detail::istream & stream, " << full_name + << " &, uint32_t tag )\n{\n"; + stream << "\tstream.skip( tag );\n}\n\n"; + return; + } + + stream << "void deserialize_value( detail::istream & stream, " << full_name + << " & value, uint32_t tag )\n{\n"; + stream << "\tswitch( field_from_tag( tag ) )\n\t{\n"; + + for( const auto & field : message.fields ) + { + + stream << "\t\tcase " << field.number << ":\n\t\t\t"; + if( !field.bit_field.empty( ) ) + { + stream << "value." << field.name << " = stream.deserialize_bitfield" + << bitfield_encoder_type( file, field ) << "( " << field.bit_field + << ", tag );\n"; + stream << "\t\t\treturn;\n"; + } + else + { + stream << "return stream.deserialize" << encoder_type( file, field ) << "( value." + << field.name << ", tag );\n"; + } + } + for( const auto & map : message.maps ) + { + stream << "\t\tcase " << map.number << ":\n\t\t\treturn "; + stream << "\tstream.deserialize" << map_encoder_type( file, map.key, map.value ) + << "( value." << map.name << ", tag );\n"; + } + for( const auto & oneof : message.oneofs ) + { + for( size_t i = 0; i < oneof.fields.size( ); ++i ) + { + stream << "\t\tcase " << oneof.fields[ i ].number << ":\n\t\t\treturn "; + auto type = encoder_type( file, oneof.fields[ i ] ); + if( type.empty( ) ) + { + stream << "\tstream.deserialize_variant< " << i << ">( value." << oneof.name + << ", tag );\n"; + } + else + { + type.erase( 0, 4 ); + stream << "\tstream.deserialize_variant_as< " << i << ", " << type << "( value." + << oneof.name << ", tag );\n"; + } + } + } + + stream << "\t\tdefault:\n\t\t\treturn stream.skip( tag );\t\n}\n}\n\n"; +} + +void dump_cpp_messages( std::ostream & stream, const proto_file & file, + const proto_messages & messages, std::string_view parent ); + +void dump_cpp_message( std::ostream & stream, const proto_file & file, + const proto_message & message, std::string_view parent ) +{ + const auto full_name = std::string( parent ) + "::" + std::string( message.name ); + + dump_cpp_open_namespace( stream, "detail" ); + dump_cpp_serialize_value( stream, file, message, full_name ); + dump_cpp_deserialize_value( stream, file, message, full_name ); + dump_cpp_close_namespace( stream, "detail" ); + + dump_cpp_messages( stream, file, message.messages, full_name ); +} + +void dump_cpp_messages( std::ostream & stream, const proto_file & file, + const proto_messages & messages, std::string_view parent ) +{ + for( const auto & message : messages ) + { + dump_cpp_message( stream, file, message, parent ); + } +} + +void dump_cpp( std::ostream & stream, const proto_file & file ) +{ + const auto str_namespace = "::" + replace( file.package.name, ".", "::" ); + dump_cpp_messages( stream, file, file.package.messages, str_namespace ); +} + +}// namespace + +void dump_pb_header( const proto_file & file, std::ostream & stream ) +{ + dump_cpp_open_namespace( stream, "spb::pb::detail" ); + stream << "struct ostream;\nstruct istream;\n"; + dump_prototypes( stream, file ); + dump_cpp_close_namespace( stream, "spb::pb::detail" ); +} + +void dump_pb_cpp( const proto_file & file, const std::filesystem::path & header_file, + std::ostream & stream ) +{ + dump_cpp_includes( stream, header_file.string( ) ); + dump_cpp_open_namespace( stream, "spb::pb" ); + dump_cpp( stream, file ); + dump_cpp_close_namespace( stream, "spb::pb" ); +} \ No newline at end of file diff --git a/3rdparty/simple-protobuf/src/spb-proto-compiler/dumper/pb/dumper.h b/3rdparty/simple-protobuf/src/spb-proto-compiler/dumper/pb/dumper.h new file mode 100644 index 0000000..06c9289 --- /dev/null +++ b/3rdparty/simple-protobuf/src/spb-proto-compiler/dumper/pb/dumper.h @@ -0,0 +1,33 @@ +/***************************************************************************\ +* Name : protobuf dumper * +* Description : generate C++ src files for protobuf de/serialization * +* Author : antonin.kriz@gmail.com * +* ------------------------------------------------------------------------- * +* This is free software; you can redistribute it and/or modify it under the * +* terms of the MIT license. A copy of the license can be found in the file * +* "LICENSE" at the root of this distribution. * +\***************************************************************************/ + +#pragma once + +#include "ast/proto-file.h" +#include + +/** + * @brief dump C++ header file for parsed proto + * + * @param file parsed proto + * @param stream output stream + * @return C++ header file for a ast + */ +void dump_pb_header( const proto_file & file, std::ostream & stream ); + +/** + * @brief dump C++ file for parsed proto + * + * @param file parsed proto + * @param header_file generated C++ header file (ex: my.pb.h) + * @param stream output stream + */ +void dump_pb_cpp( const proto_file & file, const std::filesystem::path & header_file, + std::ostream & stream ); diff --git a/3rdparty/simple-protobuf/src/spb-proto-compiler/dumper/pb/template-h.h b/3rdparty/simple-protobuf/src/spb-proto-compiler/dumper/pb/template-h.h new file mode 100644 index 0000000..9fbcb0d --- /dev/null +++ b/3rdparty/simple-protobuf/src/spb-proto-compiler/dumper/pb/template-h.h @@ -0,0 +1,19 @@ +/***************************************************************************\ +* Name : pb dumper * +* Description : C++ template used for pb de/serialization * +* Author : antonin.kriz@gmail.com * +* ------------------------------------------------------------------------- * +* This is free software; you can redistribute it and/or modify it under the * +* terms of the MIT license. A copy of the license can be found in the file * +* "LICENSE" at the root of this distribution. * +\***************************************************************************/ + +#pragma once + +#include + +constexpr std::string_view file_pb_header_template = R"( +void serialize( ostream & stream, const $ & value ); +void deserialize_value( istream & stream, $ & value, uint32_t tag ); + +)"; diff --git a/3rdparty/simple-protobuf/src/spb-proto-compiler/io/file.cpp b/3rdparty/simple-protobuf/src/spb-proto-compiler/io/file.cpp new file mode 100644 index 0000000..4782d19 --- /dev/null +++ b/3rdparty/simple-protobuf/src/spb-proto-compiler/io/file.cpp @@ -0,0 +1,43 @@ +/***************************************************************************\ +* Name : file io * +* Description : basic file io * +* Author : antonin.kriz@gmail.com * +* ------------------------------------------------------------------------- * +* This is free software; you can redistribute it and/or modify it under the * +* terms of the MIT license. A copy of the license can be found in the file * +* "LICENSE" at the root of this distribution. * +\***************************************************************************/ + +#include "file.h" +#include +#include +#include + +auto load_file( const std::filesystem::path & file_path ) -> std::string +{ + const auto file_size = std::filesystem::file_size( file_path ); + auto file_content = std::string( file_size, '\0' ); + + if( auto * p_file = fopen( file_path.string( ).c_str( ), "rb" ); p_file ) + { + const auto read = fread( file_content.data( ), 1, file_content.size( ), p_file ); + fclose( p_file ); + file_content.resize( read ); + return file_content; + } + throw std::runtime_error( std::string( " " ) + strerror( errno ) ); +} + +void save_file( const std::filesystem::path & file_path, std::string_view file_content ) +{ + if( auto * p_file = fopen( file_path.string( ).c_str( ), "wb" ); p_file ) + { + const auto written = fwrite( file_content.data( ), 1, file_content.size( ), p_file ); + fclose( p_file ); + if( written == file_content.size( ) ) + { + return; + } + } + throw std::runtime_error( std::string( " " ) + strerror( errno ) ); +} diff --git a/3rdparty/simple-protobuf/src/spb-proto-compiler/io/file.h b/3rdparty/simple-protobuf/src/spb-proto-compiler/io/file.h new file mode 100644 index 0000000..f5011d5 --- /dev/null +++ b/3rdparty/simple-protobuf/src/spb-proto-compiler/io/file.h @@ -0,0 +1,31 @@ +/***************************************************************************\ +* Name : file io * +* Description : basic file io * +* Author : antonin.kriz@gmail.com * +* ------------------------------------------------------------------------- * +* This is free software; you can redistribute it and/or modify it under the * +* terms of the MIT license. A copy of the license can be found in the file * +* "LICENSE" at the root of this distribution. * +\***************************************************************************/ + +#pragma once + +#include +#include +#include + +/** + * @brief loads file from file_path + * + * @param file_path files path + * @return file's content + */ +auto load_file( const std::filesystem::path & file_path ) -> std::string; + +/** + * @brief save file_content to a file_path + * + * @param file_path files path + * @param file_content files content + */ +void save_file( const std::filesystem::path & file_path, std::string_view file_content ); diff --git a/3rdparty/simple-protobuf/src/spb-proto-compiler/main.cpp b/3rdparty/simple-protobuf/src/spb-proto-compiler/main.cpp new file mode 100644 index 0000000..e5c7cb8 --- /dev/null +++ b/3rdparty/simple-protobuf/src/spb-proto-compiler/main.cpp @@ -0,0 +1,142 @@ +/***************************************************************************\ +* Name : spb-protoc * +* Description : proto file compiler * +* Author : antonin.kriz@gmail.com * +* ------------------------------------------------------------------------- * +* This is free software; you can redistribute it and/or modify it under the * +* terms of the MIT license. A copy of the license can be found in the file * +* "LICENSE" at the root of this distribution. * +\***************************************************************************/ + +#include "dumper/dumper.h" +#include "parser/parser.h" +#include +#include +#include +#include +#include +#include +#include + +namespace +{ +using namespace std::literals; +namespace fs = std::filesystem; + +constexpr auto opt_version = "--version"sv; +constexpr auto opt_v = "-v"sv; + +constexpr auto opt_help = "--help"sv; +constexpr auto opt_h = "-h"sv; + +constexpr auto opt_cpp_out = "--cpp_out="sv; + +constexpr auto opt_proto_path = "--proto_path="sv; +constexpr auto opt_ipath = "-IPATH="sv; + +void print_usage( ) +{ + std::cout + << "Usage: spb-protoc [OPTION] PROTO_FILES\n" + << "Parse PROTO_FILES and generate C++ source files.\n" + << " -IPATH=, --proto_path=PATH Specify the directory in which to search for imports.\n" + << "May be specified multiple times. directories will be searched in order.\n" + << " -v, --version Show version info and exit.\n" + << " -h, --help Show this text and exit.\n" + << " --cpp_out=OUT_DIR Generate C++ header and source.\n\n"; +} + +void process_file( const fs::path & input_file, std::span< const fs::path > import_paths, + const fs::path & output_dir ) +{ + const auto parsed_file = parse_proto_file( input_file, import_paths ); + const auto output_cpp_header = cpp_file_name_from_proto( input_file, ".pb.h" ); + const auto output_cpp = cpp_file_name_from_proto( input_file, ".pb.cc" ); + + auto cpp_header_stream = std::ofstream( output_dir / output_cpp_header ); + dump_cpp_header( parsed_file, cpp_header_stream ); + + auto cpp_stream = std::ofstream( output_dir / output_cpp ); + dump_cpp( parsed_file, output_cpp_header, cpp_stream ); +} + +}// namespace + +auto main( int argc, char * argv[] ) -> int +{ + if( argc < 2 ) + { + print_usage( ); + return 1; + } + + auto output_dir = fs::path( ); + auto import_paths = std::vector< fs::path >{ fs::current_path( ) }; + + for( ; argc > 1 && argv[ 1 ][ 0 ] == '-'; argc--, argv++ ) + { + if( opt_help == argv[ 1 ] || opt_h == argv[ 1 ] ) + { + print_usage( ); + return 0; + } + + if( opt_version == argv[ 1 ] || opt_v == argv[ 1 ] ) + { + std::cout << "spb-protoc version 0.1.0\n"; + return 0; + } + + if( std::string_view( argv[ 1 ] ).starts_with( opt_proto_path ) ) + { + import_paths.emplace_back( argv[ 1 ] + opt_proto_path.size( ) ); + } + else if( std::string_view( argv[ 1 ] ).starts_with( opt_ipath ) ) + { + import_paths.emplace_back( argv[ 1 ] + opt_ipath.size( ) ); + } + else if( std::string_view( argv[ 1 ] ).starts_with( opt_cpp_out ) ) + { + output_dir = argv[ 1 ] + opt_cpp_out.size( ); + } + else + { + std::cerr << "Unknown option: " << argv[ 1 ] << ", use -h or --help\n"; + return 1; + } + } + + auto input_files = std::vector< fs::path >( ); + for( auto i = 1; i < argc; i++ ) + { + input_files.emplace_back( argv[ i ] ); + import_paths.emplace_back( input_files.back( ).parent_path( ) ); + } + + if( output_dir.empty( ) ) + { + std::cerr << "Missing output directory, use --cpp_out=OUT_DIR:\n"; + return 1; + } + + if( input_files.empty( ) ) + { + std::cerr << "Missing input files, use PROTO_FILES:\n"; + return 1; + } + + try + { + for( const auto & input_file : input_files ) + { + process_file( input_file, import_paths, output_dir ); + } + } + catch( const std::exception & e ) + { + std::cerr << e.what( ) << '\n'; + return 1; + } + + return 0; +} \ No newline at end of file diff --git a/3rdparty/simple-protobuf/src/spb-proto-compiler/parser/char_stream.h b/3rdparty/simple-protobuf/src/spb-proto-compiler/parser/char_stream.h new file mode 100644 index 0000000..d4e921e --- /dev/null +++ b/3rdparty/simple-protobuf/src/spb-proto-compiler/parser/char_stream.h @@ -0,0 +1,174 @@ +/***************************************************************************\ +* Name : input stream * +* Description : char stream used for parsing proto files * +* Author : antonin.kriz@gmail.com * +* ------------------------------------------------------------------------- * +* This is free software; you can redistribute it and/or modify it under the * +* terms of the MIT license. A copy of the license can be found in the file * +* "LICENSE" at the root of this distribution. * +\***************************************************************************/ + +#pragma once +#include +#include +#include +#include +#include +#include + +namespace spb +{ + +struct char_stream +{ +private: + //- start of the stream + const char * p_start = nullptr; + //- current position in the stream + const char * p_begin = nullptr; + //- end of the stream + const char * p_end = nullptr; + //- current char + char m_current = { }; + + /** + * @brief gets the next char from the stream + * + * @param skip_white_space if true, skip white spaces + */ + void update_current( bool skip_white_space ) noexcept + { + while( p_begin < p_end ) + { + m_current = *p_begin; + if( !skip_white_space || isspace( m_current ) == 0 ) + { + return; + } + p_begin += 1; + } + m_current = { }; + } + +public: + explicit char_stream( std::string_view content ) noexcept + : p_start( content.data( ) ) + , p_begin( p_start ) + , p_end( p_begin + content.size( ) ) + { + update_current( true ); + } + + [[nodiscard]] auto begin( ) const noexcept -> const char * + { + return p_begin; + } + + [[nodiscard]] auto end( ) const noexcept -> const char * + { + return p_end; + } + + [[nodiscard]] auto empty( ) const noexcept -> bool + { + return p_end <= p_begin; + } + + [[nodiscard]] auto current_char( ) const noexcept -> char + { + return m_current; + } + + /** + * @brief consumes `current char` if its equal to c + * + * @param c consumed char + * @return true if char was consumed + */ + [[nodiscard]] auto consume( char c ) noexcept -> bool + { + if( auto current = current_char( ); current == c ) + { + consume_current_char( true ); + return true; + } + return false; + } + + /** + * @brief consumes an `token` + * + * @param token consumed `token` (whole word) + * @return true if `token` was consumed + */ + auto consume( std::string_view token ) noexcept -> bool + { + const auto state = *this; + + if( content( ).starts_with( token ) ) + { + p_begin += token.size( ); + update_current( false ); + auto next = current_char( ); + if( isspace( next ) || !isalnum( next ) ) + { + update_current( true ); + return true; + } + *this = state; + } + return false; + } + + void consume_current_char( bool skip_white_space ) noexcept + { + if( begin( ) < end( ) ) + { + p_begin += 1; + update_current( skip_white_space ); + } + } + + /** + * @brief trim current spaces + * + */ + void consume_space( ) noexcept + { + update_current( true ); + } + + void skip_to( const char * ptr ) noexcept + { + assert( ptr >= p_start && ptr <= end( ) ); + p_begin = ptr; + update_current( true ); + } + + [[nodiscard]] auto content( ) const noexcept -> std::string_view + { + return { begin( ), size_t( end( ) - begin( ) ) }; + } + + [[nodiscard]] auto current_line( ) const noexcept -> size_t + { + return std::count( p_start, p_begin, '\n' ) + 1; + } + [[nodiscard]] auto current_column( ) const noexcept -> size_t + { + auto parsed = std::string_view( p_start, p_begin - p_start ); + + if( auto p = parsed.rfind( '\n' ); p != std::string_view::npos ) + { + parsed.remove_prefix( p ); + } + return std::max< size_t >( parsed.size( ), 1 ); + } + [[noreturn]] void throw_parse_error( std::string_view message ) + { + throw std::runtime_error( std::to_string( current_line( ) ) + ":" + + std::to_string( current_column( ) ) + ": " + + std::string( message ) ); + } +}; +}// namespace spb diff --git a/3rdparty/simple-protobuf/src/spb-proto-compiler/parser/options.h b/3rdparty/simple-protobuf/src/spb-proto-compiler/parser/options.h new file mode 100644 index 0000000..79a5cc9 --- /dev/null +++ b/3rdparty/simple-protobuf/src/spb-proto-compiler/parser/options.h @@ -0,0 +1,33 @@ +/***************************************************************************\ +* Name : options * +* Description : option names used by parser/dumper * +* Author : antonin.kriz@gmail.com * +* ------------------------------------------------------------------------- * +* This is free software; you can redistribute it and/or modify it under the * +* terms of the MIT license. A copy of the license can be found in the file * +* "LICENSE" at the root of this distribution. * +\***************************************************************************/ + +#pragma once + +#include + +using namespace std::literals; + +const auto option_optional_type = "optional.type"sv; +const auto option_optional_include = "optional.include"sv; + +const auto option_repeated_type = "repeated.type"sv; +const auto option_repeated_include = "repeated.include"sv; + +const auto option_pointer_type = "pointer.type"sv; +const auto option_pointer_include = "pointer.include"sv; + +const auto option_bytes_type = "bytes.type"sv; +const auto option_bytes_include = "bytes.include"sv; + +const auto option_string_type = "string.type"sv; +const auto option_string_include = "string.include"sv; + +const auto option_field_type = "field.type"sv; +const auto option_enum_type = "enum.type"sv; diff --git a/3rdparty/simple-protobuf/src/spb-proto-compiler/parser/parser.cpp b/3rdparty/simple-protobuf/src/spb-proto-compiler/parser/parser.cpp new file mode 100644 index 0000000..410e81e --- /dev/null +++ b/3rdparty/simple-protobuf/src/spb-proto-compiler/parser/parser.cpp @@ -0,0 +1,998 @@ +/***************************************************************************\ +* Name : .proto parser * +* Description : parse proto file and constructs an ast tree * +* Author : antonin.kriz@gmail.com * +* ------------------------------------------------------------------------- * +* This is free software; you can redistribute it and/or modify it under the * +* terms of the MIT license. A copy of the license can be found in the file * +* "LICENSE" at the root of this distribution. * +\***************************************************************************/ + +#include "parser.h" +#include "ast/proto-field.h" +#include "ast/proto-file.h" +#include "dumper/header.h" +#include "options.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace +{ +namespace fs = std::filesystem; +using parsed_files = std::set< std::string >; + +[[nodiscard]] auto parse_proto_file( fs::path file, parsed_files &, + std::span< const fs::path > import_paths, + const fs::path & base_dir ) -> proto_file; + +auto find_file_in_paths( const fs::path & file_name, std::span< const fs::path > import_paths, + const fs::path & base_dir ) -> fs::path +{ + if( file_name.has_root_path( ) ) + { + if( fs::exists( file_name ) ) + { + return file_name; + } + } + else + { + if( fs::exists( base_dir / file_name ) ) + { + return base_dir / file_name; + } + + for( const auto & import_path : import_paths ) + { + auto file_path = import_path.has_root_path( ) ? import_path / file_name + : base_dir / import_path / file_name; + if( fs::exists( file_path ) ) + { + return file_path; + } + } + } + + throw std::runtime_error( strerror( ENOENT ) ); +} + +[[nodiscard]] auto parse_all_imports( const proto_file & file, parsed_files & already_parsed, + std::span< const fs::path > import_paths, + const fs::path & base_dir ) -> proto_files +{ + proto_files result; + result.reserve( file.imports.size( ) ); + for( const auto & import : file.imports ) + { + if( !already_parsed.contains( std::string( import.file_name ) ) ) + { + try + { + result.emplace_back( + parse_proto_file( import.file_name, already_parsed, import_paths, base_dir ) ); + } + catch( const std::runtime_error & error ) + { + throw_parse_error( file, import.file_name, error.what( ) ); + } + } + } + return result; +} + +void parse_or_throw( bool parsed, spb::char_stream & stream, std::string_view message ) +{ + if( !parsed ) + { + stream.throw_parse_error( message ); + } +} + +void consume_or_fail( spb::char_stream & stream, char c ) +{ + if( !stream.consume( c ) ) + { + return stream.throw_parse_error( "(expecting '" + std::string( 1, c ) + "')" ); + } +} + +void consume_or_fail( spb::char_stream & stream, std::string_view token ) +{ + if( !stream.consume( token ) ) + { + return stream.throw_parse_error( "(expecting '" + std::string( token ) + "')" ); + } +} + +void skip_white_space_until_new_line( spb::char_stream & stream ) +{ + while( ( isspace( stream.current_char( ) ) != 0 ) && stream.current_char( ) != '\n' ) + { + stream.consume_current_char( false ); + } +} + +template < typename T > +concept int_or_float = std::integral< T > || std::floating_point< T >; + +auto consume_number( spb::char_stream & stream, std::integral auto & number ) -> bool +{ + number = { }; + auto base = 10; + if( stream.consume( '0' ) ) + { + base = 8; + if( stream.consume( 'x' ) || stream.consume( 'X' ) ) + { + base = 16; + } + if( !::isdigit( stream.current_char( ) ) ) + { + return true; + } + } + auto result = spb_std_emu::from_chars( stream.begin( ), stream.end( ), number, base ); + if( result.ec == std::errc{ } ) [[likely]] + { + stream.skip_to( result.ptr ); + return true; + } + return false; +} + +auto consume_number( spb::char_stream & stream, std::floating_point auto & number ) -> bool +{ + auto result = spb_std_emu::from_chars( stream.begin( ), stream.end( ), number ); + if( result.ec == std::errc{ } ) [[likely]] + { + stream.skip_to( result.ptr ); + return true; + } + return false; +} + +auto consume_int( spb::char_stream & stream, std::integral auto & number ) -> bool +{ + return consume_number( stream, number ); +} + +auto consume_float( spb::char_stream & stream, std::floating_point auto & number ) -> bool +{ + return consume_number( stream, number ); +} + +template < int_or_float T > +auto parse_number( spb::char_stream & stream ) -> T +{ + auto result = T{ }; + if( consume_number( stream, result ) ) + { + return result; + } + stream.throw_parse_error( "expecting number" ); +} + +auto parse_int_or_float( spb::char_stream & stream ) -> std::string_view +{ + const auto * start = stream.begin( ); + auto number = double( ); + auto result = spb_std_emu::from_chars( stream.begin( ), stream.end( ), number ); + if( result.ec == std::errc{ } ) [[likely]] + { + stream.skip_to( result.ptr ); + return { start, static_cast< size_t >( result.ptr - start ) }; + } + stream.throw_parse_error( "expecting number" ); +} + +//- parse single line comment // \n +void parse_comment_line( spb::char_stream & stream, proto_comment & comment ) +{ + const auto * start = stream.begin( ); + const auto end = stream.content( ).find( '\n' ); + if( end == std::string_view::npos ) + { + stream.throw_parse_error( "expecting \\n" ); + } + + comment.comments.emplace_back( start - 2, end + 2 ); + + stream.skip_to( start + end + 1 ); +} + +//- parse multiline comment /* */ +void parse_comment_multiline( spb::char_stream & stream, proto_comment & comment ) +{ + const auto * start = stream.begin( ); + const auto end = stream.content( ).find( "*/" ); + if( end == std::string_view::npos ) + { + stream.throw_parse_error( "expecting */" ); + } + + comment.comments.emplace_back( start - 2, end + 4 ); + stream.skip_to( start + end + 2 ); +} + +//- parse // \n or /**/ +auto parse_comment( spb::char_stream & stream ) -> proto_comment +{ + auto result = proto_comment{ }; + + while( stream.current_char( ) == '/' ) + { + stream.consume_current_char( false ); + if( stream.current_char( ) == '/' ) + { + stream.consume_current_char( false ); + parse_comment_line( stream, result ); + } + else if( stream.current_char( ) == '*' ) + { + stream.consume_current_char( false ); + parse_comment_multiline( stream, result ); + } + else + { + stream.throw_parse_error( "expecting // or /*" ); + } + } + return result; +} + +//- parse ; +[[nodiscard]] auto parse_empty_statement( spb::char_stream & stream ) -> bool +{ + return stream.consume( ';' ); +} + +//- parse "string" | 'string' +[[nodiscard]] auto parse_string_literal( spb::char_stream & stream ) -> std::string_view +{ + const auto c = stream.current_char( ); + if( c != '"' && c != '\'' ) + { + stream.throw_parse_error( "expecting \" or '" ); + return { }; + } + + stream.consume_current_char( false ); + const auto * start = stream.begin( ); + auto current = stream.current_char( ); + while( ( current != 0 ) && current != c ) + { + stream.consume_current_char( false ); + current = stream.current_char( ); + } + + if( current != c ) + { + stream.throw_parse_error( "missing string end" ); + } + + auto result = std::string_view( start, static_cast< size_t >( stream.begin( ) - start ) ); + stream.consume_current_char( true ); + return result; +} + +[[nodiscard]] auto parse_ident( spb::char_stream & stream, bool skip_last_white_space = true ) + -> std::string_view +{ + const auto * start = stream.begin( ); + + if( isalpha( stream.current_char( ) ) == 0 ) + { + stream.throw_parse_error( "expecting identifier(a-zA-Z)" ); + return { }; + } + + stream.consume_current_char( false ); + auto current = stream.current_char( ); + while( ( current != 0 ) && ( isalnum( current ) != 0 || current == '_' ) ) + { + stream.consume_current_char( false ); + current = stream.current_char( ); + } + + auto result = std::string_view( start, static_cast< size_t >( stream.begin( ) - start ) ); + if( skip_last_white_space ) + { + stream.consume_space( ); + } + return result; +} + +[[nodiscard]] auto parse_full_ident( spb::char_stream & stream ) -> std::string_view +{ + const auto * start = stream.begin( ); + + for( ;; ) + { + ( void ) parse_ident( stream, false ); + if( stream.current_char( ) != '.' ) + { + break; + } + stream.consume_current_char( false ); + } + auto result = std::string_view{ start, static_cast< size_t >( stream.begin( ) - start ) }; + stream.consume_space( ); + return result; +} + +void parse_top_level_service_body( spb::char_stream & stream, proto_file &, proto_comment && ) +{ + while( !stream.consume( '}' ) ) + stream.consume_current_char( true ); +} + +void consume_statement_end( spb::char_stream & stream, proto_comment & comment ) +{ + if( stream.current_char( ) != ';' ) + { + return stream.throw_parse_error( R"(expecting ";")" ); + } + stream.consume_current_char( false ); + skip_white_space_until_new_line( stream ); + if( stream.current_char( ) == '/' ) + { + stream.consume_current_char( false ); + auto line_comment = proto_comment( ); + parse_comment_line( stream, line_comment ); + comment.comments.insert( comment.comments.end( ), line_comment.comments.begin( ), + line_comment.comments.end( ) ); + } + stream.consume_space( ); +} + +void parse_top_level_syntax_body( spb::char_stream & stream, proto_syntax & syntax, + proto_comment && comment ) +{ + //- syntax = ( "proto2" | "proto3" ); + + consume_or_fail( stream, '=' ); + + syntax.comments = std::move( comment ); + if( stream.consume( R"("proto2")" ) ) + { + syntax.version = 2; + return consume_statement_end( stream, syntax.comments ); + } + + if( stream.consume( R"("proto3")" ) ) + { + syntax.version = 3; + return consume_statement_end( stream, syntax.comments ); + } + + stream.throw_parse_error( "expecting proto2 or proto3" ); +} + +void parse_top_level_syntax_or_service( spb::char_stream & stream, proto_file & file, + proto_comment && comment ) +{ + if( stream.consume( "syntax" ) ) + { + return parse_top_level_syntax_body( stream, file.syntax, std::move( comment ) ); + } + + if( stream.consume( "service" ) ) + { + return parse_top_level_service_body( stream, file, std::move( comment ) ); + } + + stream.throw_parse_error( "expecting syntax or service" ); +} + +void parse_top_level_import( spb::char_stream & stream, proto_imports & imports, + proto_comment && comment ) +{ + // "import" [ "weak" | "public" ] strLit ";" + consume_or_fail( stream, "import" ); + stream.consume( "weak" ) || stream.consume( "public" ); + imports.emplace_back( proto_import{ .file_name = parse_string_literal( stream ), + .comments = std::move( comment ) } ); + consume_statement_end( stream, imports.back( ).comments ); +} + +void parse_top_level_package( spb::char_stream & stream, proto_base & package, + proto_comment && comment ) +{ + //- "package" fullIdent ";" + consume_or_fail( stream, "package" ); + package.name = parse_full_ident( stream ); + package.comment = std::move( comment ); + consume_statement_end( stream, package.comment ); +} + +[[nodiscard]] auto parse_option_name( spb::char_stream & stream ) -> std::string_view +{ + auto ident = std::string_view{ }; + + //- ( ident | "(" fullIdent ")" ) { "." ident } + parse_comment( stream ); + if( stream.consume( '(' ) ) + { + ident = parse_full_ident( stream ); + consume_or_fail( stream, ')' ); + } + else + { + ident = parse_ident( stream ); + } + auto ident2 = std::string_view{ }; + + while( stream.consume( '.' ) ) + { + ident2 = parse_ident( stream ); + } + + if( ident2.empty( ) ) + { + return ident; + } + + return { ident.data( ), + static_cast< size_t >( ident2.data( ) + ident2.size( ) - ident.data( ) ) }; +} + +[[nodiscard]] auto parse_constant( spb::char_stream & stream ) -> std::string_view +{ + //- fullIdent | ( [ "-" | "+" ] intLit ) | ( [ "-" | "+" ] floatLit ) | strLit | boolLit | + // MessageValue + if( stream.consume( "true" ) ) + { + return "true"; + } + if( stream.consume( "false" ) ) + { + return "false"; + } + const auto c = stream.current_char( ); + if( c == '"' || c == '\'' ) + { + return parse_string_literal( stream ); + } + if( isdigit( c ) || c == '+' || c == '-' ) + { + return parse_int_or_float( stream ); + } + return parse_full_ident( stream ); +} + +void parse_option_body( spb::char_stream & stream, proto_options & options ) +{ + const auto option_name = parse_option_name( stream ); + consume_or_fail( stream, '=' ); + options[ option_name ] = parse_constant( stream ); +} + +void parse_option_from_comment( const spb::char_stream & stream, proto_options & options, + std::string_view comment ) +{ + for( ;; ) + { + auto start = comment.find( "[[" ); + if( start == std::string_view::npos ) + { + return; + } + auto end = comment.find( "]]", start + 2 ); + if( end == std::string_view::npos ) + { + return; + } + auto option = comment.substr( start + 2, end - start - 2 ); + comment.remove_prefix( end + 2 ); + auto option_stream = stream; + option_stream.skip_to( option.data( ) ); + parse_option_body( option_stream, options ); + } +} + +void parse_options_from_comments( const spb::char_stream & stream, proto_options & options, + const proto_comment & comment ) +{ + for( auto & c : comment.comments ) + { + parse_option_from_comment( stream, options, c ); + } +} + +[[nodiscard]] auto parse_option( spb::char_stream & stream, proto_options & options, + proto_comment && comment ) -> bool +{ + //- "option" optionName "=" constant ";" + if( !stream.consume( "option" ) ) + { + return false; + } + parse_option_body( stream, options ); + consume_statement_end( stream, comment ); + parse_options_from_comments( stream, options, comment ); + return true; +} + +void parse_reserved_names( spb::char_stream & stream, proto_reserved_name & name, + proto_comment && comment ) +{ + //- strFieldNames = strFieldName { "," strFieldName } + //- strFieldName = "'" fieldName "'" | '"' fieldName '"' + do + { + name.insert( parse_string_literal( stream ) ); + } while( stream.consume( ',' ) ); + + consume_statement_end( stream, comment ); + comment.comments.clear( ); +} + +void parse_reserved_ranges( spb::char_stream & stream, proto_reserved_range & range, + proto_comment && comment ) +{ + //- ranges = range { "," range } + //- range = intLit [ "to" ( intLit | "max" ) ] + do + { + const auto number = parse_number< uint32_t >( stream ); + auto number2 = number; + + if( stream.consume( "to" ) ) + { + if( stream.consume( "max" ) ) + { + number2 = std::numeric_limits< decltype( number2 ) >::max( ); + } + else + { + number2 = parse_number< uint32_t >( stream ); + } + } + + range.emplace_back( number, number2 ); + } while( stream.consume( ',' ) ); + + consume_statement_end( stream, comment ); + comment.comments.clear( ); +} + +[[nodiscard]] auto parse_extensions( spb::char_stream & stream, proto_reserved_range & extensions, + proto_comment && comment ) -> bool +{ + //- extensions = "extensions" ranges ";" + if( !stream.consume( "extensions" ) ) + { + return false; + } + + parse_reserved_ranges( stream, extensions, std::move( comment ) ); + return true; +} + +[[nodiscard]] auto parse_reserved( spb::char_stream & stream, proto_reserved & reserved, + proto_comment && comment ) -> bool +{ + //- reserved = "reserved" ( ranges | strFieldNames ) ";" + if( !stream.consume( "reserved" ) ) + { + return false; + } + + auto c = stream.current_char( ); + if( c == '\'' || c == '"' ) + { + parse_reserved_names( stream, reserved.reserved_name, std::move( comment ) ); + return true; + } + parse_reserved_ranges( stream, reserved.reserved_range, std::move( comment ) ); + return true; +} + +[[nodiscard]] auto parse_field_options( spb::char_stream & stream ) -> proto_options +{ + auto options = proto_options{ }; + if( stream.consume( '[' ) ) + { + auto first = true; + while( !stream.consume( ']' ) ) + { + if( !first ) + { + consume_or_fail( stream, ',' ); + } + parse_option_body( stream, options ); + first = false; + } + } + return options; +} + +void parse_enum_field( spb::char_stream & stream, proto_enum & new_enum, proto_comment && comment ) +{ + //- enumField = ident "=" [ "-" ] intLit [ "[" enumValueOption { "," enumValueOption } "]" ]";" + //- enumValueOption = optionName "=" constant + auto field = + proto_base{ .name = parse_ident( stream ), + .number = ( consume_or_fail( stream, '=' ), + parse_number< decltype( proto_field::number ) >( stream ) ), + .options = parse_field_options( stream ), + .comment = std::move( comment ) }; + + consume_statement_end( stream, field.comment ); + new_enum.fields.push_back( field ); +} + +[[nodiscard]] auto parse_enum_body( spb::char_stream & stream, proto_comment && enum_comment ) + -> proto_enum +{ + //- enumBody = "{" { option | enumField | emptyStatement | reserved } "}" + + auto new_enum = proto_enum{ + proto_base{ + .name = parse_ident( stream ), + .comment = std::move( enum_comment ), + }, + }; + consume_or_fail( stream, '{' ); + + parse_options_from_comments( stream, new_enum.options, new_enum.comment ); + + while( !stream.consume( '}' ) ) + { + auto comment = parse_comment( stream ); + if( stream.consume( '}' ) ) + { + break; + } + + if( !parse_option( stream, new_enum.options, std::move( comment ) ) && + !parse_reserved( stream, new_enum.reserved, std::move( comment ) ) && + !parse_empty_statement( stream ) ) + { + parse_enum_field( stream, new_enum, std::move( comment ) ); + } + } + return new_enum; +} + +[[nodiscard]] auto parse_enum( spb::char_stream & stream, proto_enums & enums, + proto_comment && comment ) -> bool +{ + //- enum = "enum" enumName enumBody + if( !stream.consume( "enum" ) ) + { + return false; + } + enums.push_back( parse_enum_body( stream, std::move( comment ) ) ); + return true; +} + +[[nodiscard]] auto parse_field_label( spb::char_stream & stream ) -> proto_field::Label +{ + if( stream.consume( "optional" ) ) + { + return proto_field::Label::OPTIONAL; + } + if( stream.consume( "repeated" ) ) + { + return proto_field::Label::REPEATED; + } + if( stream.consume( "required" ) ) + { + return proto_field::Label::NONE; + } + + return proto_field::Label::OPTIONAL; + // stream.throw_parse_error( "expecting label" ); +} + +void parse_field( spb::char_stream & stream, proto_fields & fields, proto_comment && comment ) +{ + //- field = label type fieldName "=" fieldNumber [ "[" fieldOptions "]" ] ";" + //- fieldOptions = fieldOption { "," fieldOption } + //- fieldOption = optionName "=" constant + auto new_field = proto_field{ + .label = parse_field_label( stream ), + .type_name = parse_full_ident( stream ), + }; + + new_field.name = parse_ident( stream ); + new_field.number = ( consume_or_fail( stream, '=' ), + parse_number< decltype( proto_field::number ) >( stream ) ); + new_field.options = parse_field_options( stream ); + new_field.comment = std::move( comment ); + consume_statement_end( stream, new_field.comment ); + parse_options_from_comments( stream, new_field.options, new_field.comment ); + fields.push_back( new_field ); +} + +//[[nodiscard]] auto parse_extend( spb::char_stream & stream, proto_ast & ) -> bool; +//[[nodiscard]] auto parse_extensions( spb::char_stream & stream, proto_fields & ) -> bool; +//[[nodiscard]] auto parse_oneof( spb::char_stream & stream, proto_ast & ) -> bool; + +auto parse_map_key_type( spb::char_stream & stream ) -> std::string_view +{ + //- keyType = "int32" | "int64" | "uint32" | "uint64" | "sint32" | "sint64" | "fixed32" | + //"fixed64" | "sfixed32" | "sfixed64" | "bool" | "string" + constexpr auto key_types = + std::array< std::string_view, 12 >{ { "int32", "int64", "uint32", "uint64", "sint32", + "sint64", "fixed32", "fixed64", "sfixed32", + "sfixed64", "bool", "string" } }; + for( auto key_type : key_types ) + { + if( stream.consume( key_type ) ) + { + return key_type; + } + } + stream.throw_parse_error( "expecting map key type" ); +} + +auto parse_map_body( spb::char_stream & stream, proto_comment && comment ) -> proto_map +{ + //- "map" "<" keyType "," type ">" mapName "=" fieldNumber [ "[" fieldOptions "]" ] ";" + + auto new_map = proto_map{ + .key = proto_field{ .type_name = parse_map_key_type( stream ) }, + .value = proto_field{ .type_name = + ( consume_or_fail( stream, ',' ), parse_full_ident( stream ) ) }, + }; + new_map.name = ( consume_or_fail( stream, '>' ), parse_ident( stream ) ); + new_map.number = + ( consume_or_fail( stream, '=' ), parse_number< decltype( proto_map::number ) >( stream ) ); + new_map.options = parse_field_options( stream ); + new_map.comment = std::move( comment ); + consume_statement_end( stream, new_map.comment ); + return new_map; +} + +[[nodiscard]] auto parse_map_field( spb::char_stream & stream, proto_maps & maps, + proto_comment && comment ) -> bool +{ + //- "map" "<" + if( !stream.consume( "map" ) ) + { + return false; + } + consume_or_fail( stream, '<' ); + maps.push_back( parse_map_body( stream, std::move( comment ) ) ); + return true; +} + +void parse_oneof_field( spb::char_stream & stream, proto_fields & fields, proto_comment && comment ) +{ + //- oneofField = type fieldName "=" fieldNumber [ "[" fieldOptions "]" ] ";" + auto new_field = proto_field{ .type_name = parse_full_ident( stream ) }; + + new_field.name = parse_ident( stream ); + new_field.number = ( consume_or_fail( stream, '=' ), + parse_number< decltype( proto_field::number ) >( stream ) ); + new_field.options = parse_field_options( stream ); + new_field.comment = std::move( comment ); + consume_statement_end( stream, new_field.comment ); + fields.push_back( new_field ); +} + +[[nodiscard]] auto parse_oneof_body( spb::char_stream & stream, proto_comment && oneof_comment ) + -> proto_oneof +{ + //- oneof = "oneof" oneofName "{" { option | oneofField } "}" + auto new_oneof = proto_oneof{ proto_base{ + .name = parse_ident( stream ), + .comment = std::move( oneof_comment ), + } }; + consume_or_fail( stream, '{' ); + while( !stream.consume( '}' ) ) + { + auto comment = parse_comment( stream ); + if( stream.consume( '}' ) ) + { + break; + } + + if( !parse_option( stream, new_oneof.options, std::move( comment ) ) ) + { + parse_oneof_field( stream, new_oneof.fields, std::move( comment ) ); + } + } + return new_oneof; +} + +[[nodiscard]] auto parse_oneof( spb::char_stream & stream, proto_oneofs & oneofs, + proto_comment && comment ) -> bool +{ + //- oneof = "oneof" oneofName "{" { option | oneofField } "}" + if( !stream.consume( "oneof" ) ) + { + return false; + } + oneofs.push_back( parse_oneof_body( stream, std::move( comment ) ) ); + return true; +} + +[[nodiscard]] auto parse_message( spb::char_stream & stream, proto_messages & messages, + proto_comment && comment ) -> bool; + +void parse_message_body( spb::char_stream & stream, proto_messages & messages, + proto_comment && message_comment ) +{ + //- messageBody = messageName "{" { field | enum | message | extend | extensions | group | + // option | oneof | mapField | reserved | emptyStatement } "}" + auto new_message = proto_message{ proto_base{ + .name = parse_ident( stream ), + .comment = std::move( message_comment ), + } }; + + consume_or_fail( stream, '{' ); + parse_options_from_comments( stream, new_message.options, new_message.comment ); + + while( !stream.consume( '}' ) ) + { + auto comment = parse_comment( stream ); + if( stream.consume( '}' ) ) + { + break; + } + + if( !parse_empty_statement( stream ) && + !parse_enum( stream, new_message.enums, std::move( comment ) ) && + !parse_message( stream, new_message.messages, std::move( comment ) ) && + //! parse_extend( stream, new_message.extends ) && + !parse_extensions( stream, new_message.extensions, std::move( comment ) ) && + !parse_oneof( stream, new_message.oneofs, std::move( comment ) ) && + !parse_map_field( stream, new_message.maps, std::move( comment ) ) && + !parse_reserved( stream, new_message.reserved, std::move( comment ) ) && + !parse_option( stream, new_message.options, std::move( comment ) ) ) + { + parse_field( stream, new_message.fields, std::move( comment ) ); + } + } + messages.push_back( new_message ); +} + +[[nodiscard]] auto parse_message( spb::char_stream & stream, proto_messages & messages, + proto_comment && comment ) -> bool +{ + //- "message" messageName messageBody + if( !stream.consume( "message" ) ) + { + return false; + } + + parse_message_body( stream, messages, std::move( comment ) ); + return true; +} + +void parse_top_level_option( spb::char_stream & stream, proto_options & options, + proto_comment && comment ) +{ + parse_or_throw( parse_option( stream, options, std::move( comment ) ), stream, + "expecting option" ); +} + +void parse_top_level_message( spb::char_stream & stream, proto_messages & messages, + proto_comment && comment ) +{ + parse_or_throw( parse_message( stream, messages, std::move( comment ) ), stream, + "expecting message" ); +} + +void parse_top_level_enum( spb::char_stream & stream, proto_enums & enums, + proto_comment && comment ) +{ + parse_or_throw( parse_enum( stream, enums, std::move( comment ) ), stream, "expecting enum" ); +} + +void parse_top_level( spb::char_stream & stream, proto_file & file, proto_comment && comment ) +{ + switch( stream.current_char( ) ) + { + case '\0': + return; + case 's': + return parse_top_level_syntax_or_service( stream, file, std::move( comment ) ); + case 'i': + return parse_top_level_import( stream, file.imports, std::move( comment ) ); + case 'p': + return parse_top_level_package( stream, file.package, std::move( comment ) ); + case 'o': + return parse_top_level_option( stream, file.options, std::move( comment ) ); + case 'm': + return parse_top_level_message( stream, file.package.messages, std::move( comment ) ); + case 'e': + return parse_top_level_enum( stream, file.package.enums, std::move( comment ) ); + case ';': + return ( void ) parse_empty_statement( stream ); + + default: + return stream.throw_parse_error( "expecting top level definition" ); + } +} + +void set_default_options( proto_file & file ) +{ + file.options[ option_optional_type ] = "std::optional<$>"; + file.options[ option_optional_include ] = ""; + + file.options[ option_repeated_type ] = "std::vector<$>"; + file.options[ option_repeated_include ] = ""; + + file.options[ option_string_type ] = "std::string"; + file.options[ option_string_include ] = ""; + + file.options[ option_bytes_type ] = "std::vector<$>"; + file.options[ option_bytes_include ] = ""; + + file.options[ option_pointer_type ] = "std::unique_ptr<$>"; + file.options[ option_pointer_include ] = ""; + + file.options[ option_enum_type ] = "int32"; +} + +[[nodiscard]] auto parse_proto_file( fs::path file, parsed_files & already_parsed, + std::span< const fs::path > import_paths, + const fs::path & base_dir ) -> proto_file +{ + try + { + file = find_file_in_paths( file, import_paths, base_dir ); + + auto result = proto_file{ + .path = file, + .content = load_file( file ), + }; + + parse_proto_file_content( result ); + already_parsed.insert( file.string( ) ); + result.file_imports = + parse_all_imports( result, already_parsed, import_paths, file.parent_path( ) ); + resolve_messages( result ); + return result; + } + catch( const std::exception & e ) + { + throw std::runtime_error( file.string( ) + ":" + e.what( ) ); + } +} + +}// namespace + +void parse_proto_file_content( proto_file & file ) +{ + set_default_options( file ); + + auto stream = spb::char_stream( file.content ); + + while( !stream.empty( ) ) + { + auto comment = parse_comment( stream ); + parse_options_from_comments( stream, file.options, comment ); + parse_top_level( stream, file, std::move( comment ) ); + } +} + +auto parse_proto_file( const fs::path & file, std::span< const fs::path > import_paths, + const fs::path & base_dir ) -> proto_file +{ + auto already_parsed = parsed_files( ); + return parse_proto_file( file, already_parsed, import_paths, base_dir ); +} + +[[nodiscard]] auto cpp_file_name_from_proto( const fs::path & proto_file_path, + std::string_view extension ) -> fs::path +{ + return proto_file_path.stem( ).concat( extension ); +} diff --git a/3rdparty/simple-protobuf/src/spb-proto-compiler/parser/parser.h b/3rdparty/simple-protobuf/src/spb-proto-compiler/parser/parser.h new file mode 100644 index 0000000..5c87304 --- /dev/null +++ b/3rdparty/simple-protobuf/src/spb-proto-compiler/parser/parser.h @@ -0,0 +1,46 @@ +/***************************************************************************\ +* Name : .proto parser * +* Description : parse proto file and constructs an ast tree * +* Author : antonin.kriz@gmail.com * +* ------------------------------------------------------------------------- * +* This is free software; you can redistribute it and/or modify it under the * +* terms of the MIT license. A copy of the license can be found in the file * +* "LICENSE" at the root of this distribution. * +\***************************************************************************/ + +#pragma once + +#include "ast/proto-file.h" +#include +#include +#include + +/** + * @brief generate C++ file name from a .proto file name + * example: "foo.proto" -> "foo.pb.h" + * + * @param proto_file_path proto file name + * @param extension CPP file extension (example: ".pb.h") + * @return CPP file name + */ +[[nodiscard]] auto cpp_file_name_from_proto( const std::filesystem::path & proto_file_path, + std::string_view extension ) -> std::filesystem::path; + +/** + * @brief parse proto file + * + * @param file_path file path + * @param import_paths proto import paths (searched in order) + * @param base_dir working directory + * @return proto_file parsed proto + */ +auto parse_proto_file( + const std::filesystem::path & file_path, std::span< const std::filesystem::path > import_paths, + const std::filesystem::path & base_dir = std::filesystem::current_path( ) ) -> proto_file; + +/** + * @brief parse proto file content, used for fuzzing + * + * @param file proto file + */ +void parse_proto_file_content( proto_file & file ); diff --git a/3rdparty/simple-protobuf/test/CMakeLists.txt b/3rdparty/simple-protobuf/test/CMakeLists.txt new file mode 100644 index 0000000..5ea3781 --- /dev/null +++ b/3rdparty/simple-protobuf/test/CMakeLists.txt @@ -0,0 +1,195 @@ +include(cmake/doctest.cmake) + +FILE(GLOB protos ${CMAKE_CURRENT_SOURCE_DIR}/proto/*.proto) + +SET(PB_PACKAGE "") +configure_file(proto/scalar/scalar.proto.in ${CMAKE_CURRENT_BINARY_DIR}/scalar.proto @ONLY) +configure_file(proto/person/person.proto.in ${CMAKE_CURRENT_BINARY_DIR}/person.proto @ONLY) +configure_file(proto/name/name.proto.in ${CMAKE_CURRENT_BINARY_DIR}/name.proto @ONLY) + +if(SPB_PROTO_BUILD_ETL_TESTS) + configure_file(proto/etl/etl-scalar.proto.in ${CMAKE_CURRENT_BINARY_DIR}/etl-scalar.proto @ONLY) + configure_file(proto/etl/etl-person.proto.in ${CMAKE_CURRENT_BINARY_DIR}/etl-person.proto @ONLY) +endif() + +SET(PB_PACKAGE ".gpb") +configure_file(proto/scalar/scalar.proto.in ${CMAKE_CURRENT_BINARY_DIR}/gpb-scalar.proto @ONLY) +configure_file(proto/person/person.proto.in ${CMAKE_CURRENT_BINARY_DIR}/gpb-person.proto @ONLY) +configure_file(proto/name/name.proto.in ${CMAKE_CURRENT_BINARY_DIR}/gpb-name.proto @ONLY) +if(SPB_PROTO_BUILD_ETL_TESTS) + configure_file(proto/etl/etl-scalar.proto.in ${CMAKE_CURRENT_BINARY_DIR}/gpb-etl-scalar.proto @ONLY) + configure_file(proto/etl/etl-person.proto.in ${CMAKE_CURRENT_BINARY_DIR}/gpb-etl-person.proto @ONLY) +endif() + +spb_protobuf_generate(SPB_PROTO_SRCS SPB_PROTO_HDRS ${protos}) +spb_protobuf_generate(SPB_PROTO_PERSON_SRC SPB_PROTO_PERSON_HDR ${CMAKE_CURRENT_BINARY_DIR}/person.proto) +spb_protobuf_generate(SPB_PROTO_NAME_SRC SPB_PROTO_NAME_HDR ${CMAKE_CURRENT_BINARY_DIR}/name.proto) +spb_protobuf_generate(SPB_PROTO_SCALAR_SRC SPB_PROTO_SCALAR_HDR ${CMAKE_CURRENT_BINARY_DIR}/scalar.proto) +if(SPB_PROTO_BUILD_ETL_TESTS) + spb_protobuf_generate(SPB_PROTO_ETL_SCALAR_SRC SPB_PROTO_ETL_HDR ${CMAKE_CURRENT_BINARY_DIR}/etl-scalar.proto) + spb_protobuf_generate(SPB_PROTO_ETL_PERSON_SRC SPB_PROTO_ETL_HDR ${CMAKE_CURRENT_BINARY_DIR}/etl-person.proto) +endif() + +add_custom_target(unit_tests) + +add_executable(base64-test base64.cpp) +spb_set_compile_options(base64-test) +spb_disable_warnings(base64-test) +target_link_libraries(base64-test PUBLIC spb-proto) +add_dependencies(unit_tests base64-test) +doctest_discover_tests(base64-test) + +add_executable(json-test json.cpp ${SPB_PROTO_PERSON_SRC} ${SPB_PROTO_NAME_SRC} ${SPB_PROTO_SCALAR_SRC}) +spb_set_compile_options(json-test) +spb_disable_warnings(json-test) +target_link_libraries(json-test PUBLIC spb-proto) +add_dependencies(unit_tests json-test) +doctest_discover_tests(json-test) + +add_executable(pb-test pb.cpp ${SPB_PROTO_SRCS} ${SPB_PROTO_PERSON_SRC} ${SPB_PROTO_NAME_SRC} ${SPB_PROTO_SCALAR_SRC}) +spb_set_compile_options(pb-test) +spb_disable_warnings(pb-test) +target_link_libraries(pb-test PUBLIC spb-proto) +add_dependencies(unit_tests pb-test) +doctest_discover_tests(pb-test) + +if(NOT CMAKE_BUILD_TYPE MATCHES Debug) + include(CheckIPOSupported) + check_ipo_supported(RESULT IPO_ENABLED) +endif() + +add_executable(compiled-protos protos.cpp ${SPB_PROTO_SRCS} ${SPB_PROTO_HDRS}) +spb_set_compile_options(compiled-protos) +spb_disable_warnings(compiled-protos) +target_link_libraries(compiled-protos PUBLIC spb-proto) + +if(SPB_PROTO_BUILD_COMPATIBILITY_TESTS) + if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND EXISTS "/etc/arch-release") + set(ABSL_PROPAGATE_CXX_STD On) + set(ABSL_ENABLE_INSTALL Off) + set(BUILD_SHARED_LIBS Off) + + set(protobuf_INSTALL Off) + set(protobuf_BUILD_LIBPROTOC Off) + set(protobuf_BUILD_TESTS Off) + set(protobuf_BUILD_LIBUPB Off) + set(protobuf_BUILD_SHARED_LIBS Off) + set(protobuf_BUILD_LIBUPB Off) + set(protobuf_ABSL_PROVIDER "module") + + FetchContent_Declare( + protobuf + GIT_REPOSITORY https://github.com/protocolbuffers/protobuf.git + GIT_TAG v30.0 + ) + FetchContent_MakeAvailable(protobuf) + else() + find_package(Protobuf REQUIRED) + endif() + + function(PROTOBUF_GENERATE_CPP SRC_VAR HDR_VAR PROTO_FILE) + get_filename_component(PROTO_NAME ${PROTO_FILE} NAME_WE) + set(OUTPUT_CC "${CMAKE_CURRENT_BINARY_DIR}/${PROTO_NAME}.pb.cc") + set(OUTPUT_H "${CMAKE_CURRENT_BINARY_DIR}/${PROTO_NAME}.pb.h") + + add_custom_command( + OUTPUT ${OUTPUT_CC} ${OUTPUT_H} + COMMAND protobuf::protoc + ARGS --cpp_out=${CMAKE_CURRENT_BINARY_DIR} -I${CMAKE_CURRENT_BINARY_DIR} ${PROTO_FILE} + DEPENDS ${PROTO_FILE} + COMMENT "Generating Protobuf files for ${PROTO_FILE} as ${OUTPUT_CC} and ${OUTPUT_H}" + VERBATIM + ) + + set(${SRC_VAR} ${OUTPUT_CC} PARENT_SCOPE) + set(${HDR_VAR} ${OUTPUT_H} PARENT_SCOPE) + endfunction() + + PROTOBUF_GENERATE_CPP(PROTO_PERSON_SRC PROTO_PERSON_HDR ${CMAKE_CURRENT_BINARY_DIR}/gpb-person.proto) + PROTOBUF_GENERATE_CPP(PROTO_NAME_SRC PROTO_NAME_HDR ${CMAKE_CURRENT_BINARY_DIR}/gpb-name.proto) + PROTOBUF_GENERATE_CPP(PROTO_SCALAR_SRC PROTO_SCALAR_HDR ${CMAKE_CURRENT_BINARY_DIR}/gpb-scalar.proto) + if(SPB_PROTO_BUILD_ETL_TESTS) + PROTOBUF_GENERATE_CPP(PROTO_ETL_SCALAR_SRC PROTO_ETL_HDR ${CMAKE_CURRENT_BINARY_DIR}/gpb-etl-scalar.proto) + PROTOBUF_GENERATE_CPP(PROTO_ETL_PERSON_SRC PROTO_ETL_HDR ${CMAKE_CURRENT_BINARY_DIR}/gpb-etl-person.proto) + endif() + + add_library(gpb-proto STATIC ${PROTO_ETL_PERSON_SRC} ${PROTO_ETL_SCALAR_SRC} ${PROTO_SCALAR_SRC} ${PROTO_PERSON_SRC} ${PROTO_NAME_SRC}) + target_link_libraries(gpb-proto PUBLIC protobuf::libprotobuf) + target_compile_features(gpb-proto INTERFACE cxx_std_20) + + add_executable(gpb-compatibility-test gpb-compatibility.cpp ${SPB_PROTO_PERSON_SRC} ${SPB_PROTO_NAME_SRC} ${SPB_PROTO_SCALAR_SRC}) + spb_set_compile_options(gpb-compatibility-test) + target_link_libraries(gpb-compatibility-test PUBLIC spb-proto gpb-proto) + add_dependencies(unit_tests gpb-compatibility-test) + doctest_discover_tests(gpb-compatibility-test) + + if(SPB_PROTO_BUILD_ETL_TESTS) + Include(FetchContent) + + FetchContent_Declare( + etl + GIT_REPOSITORY https://github.com/ETLCPP/etl + GIT_TAG "20.39.4" + ) + + FetchContent_MakeAvailable(etl) + + add_executable(etl-compatibility-test etl-compatibility.cpp ${SPB_PROTO_ETL_SCALAR_SRC} ${SPB_PROTO_ETL_PERSON_SRC}) + spb_enable_warnings(etl-compatibility-test) + target_link_libraries(etl-compatibility-test PUBLIC spb-proto gpb-proto etl::etl) + add_dependencies(unit_tests etl-compatibility-test) + doctest_discover_tests(etl-compatibility-test) + endif() +endif() + +if(SPB_PROTO_BUILD_COMPILER_TESTS) + add_executable(spb-protoc-test parser.cpp + ../../src/spb-proto-compiler/parser/parser.cpp + ../../src/spb-proto-compiler/dumper/pb/dumper.cpp + ../../src/spb-proto-compiler/dumper/json/dumper.cpp + ../../src/spb-proto-compiler/dumper/dumper.cpp + ../../src/spb-proto-compiler/dumper/header.cpp + ../../src/spb-proto-compiler/io/file.cpp + ../../src/spb-proto-compiler/ast/ast.cpp + ../../src/spb-proto-compiler/ast/ast-types.cpp + ../../src/spb-proto-compiler/ast/ast-messages-order.cpp + ) + target_include_directories(spb-protoc-test PUBLIC ${CMAKE_SOURCE_DIR}/src/spb-proto-compiler) + target_link_libraries(spb-protoc-test PUBLIC spb-proto) + add_dependencies(unit_tests spb-protoc-test) + doctest_discover_tests(spb-protoc-test) +endif() + +if(SPB_PROTO_BUILD_FUZZER_TESTS) + if(SPB_PROTO_USE_ADDRESS_SANITIZER) + add_compile_options("-fsanitize=fuzzer,address" -O1 -g) + add_link_options("-fsanitize=fuzzer,address") +elseif(SPB_PROTO_USE_UB_SANITIZER) + message("-- Enable undefined behavior sanitizer" -O1 -g) + add_compile_options("-fsanitize=fuzzer,undefined") + add_link_options("-fsanitize=fuzzer,undefined") +endif() + + add_executable(spb-protoc-fuzzer fuzzer/spb-protoc-fuzzer.cpp + ../../src/spb-proto-compiler/parser/parser.cpp + ../../src/spb-proto-compiler/io/file.cpp + ../../src/spb-proto-compiler/ast/ast.cpp + ) + target_include_directories(spb-protoc-fuzzer PUBLIC ${CMAKE_SOURCE_DIR}/src/spb-proto-compiler) + target_link_libraries(spb-protoc-fuzzer PUBLIC spb-proto) + spb_disable_warnings(spb-protoc-fuzzer) + + add_executable(spb-pb-fuzzer fuzzer/spb-pb-fuzzer.cpp + ${SPB_PROTO_PERSON_SRC} ${SPB_PROTO_NAME_SRC} ${SPB_PROTO_SCALAR_SRC} ${SPB_PROTO_SRCS} + ) + target_link_libraries(spb-pb-fuzzer PUBLIC spb-proto) + spb_disable_warnings(spb-pb-fuzzer) + + add_executable(spb-json-fuzzer fuzzer/spb-json-fuzzer.cpp + ${SPB_PROTO_PERSON_SRC} ${SPB_PROTO_NAME_SRC} ${SPB_PROTO_SCALAR_SRC} ${SPB_PROTO_SRCS} + ) + target_link_libraries(spb-json-fuzzer PUBLIC spb-proto) + spb_disable_warnings(spb-json-fuzzer) +endif() + + diff --git a/3rdparty/simple-protobuf/test/base64.cpp b/3rdparty/simple-protobuf/test/base64.cpp new file mode 100644 index 0000000..c445c5a --- /dev/null +++ b/3rdparty/simple-protobuf/test/base64.cpp @@ -0,0 +1,155 @@ +#include +#include +#include +#include +#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN +#include "doctest.h" +#include + +namespace +{ +auto base64_encode_size( std::string_view value ) -> size_t +{ + auto stream = spb::json::detail::ostream( nullptr ); + spb::json::detail::base64_encode( stream, { reinterpret_cast< const std::byte * >( value.data( ) ), value.size( ) } ); + return stream.size( ); +} + +auto base64_encode( std::string_view value ) -> std::string +{ + auto encode_size = base64_encode_size( value ); + auto result = std::string( encode_size, '\0' ); + auto writer = [ ptr = result.data( ) ]( const void * data, size_t size ) mutable + { + memcpy( ptr, data, size ); + ptr += size; + }; + auto stream = spb::json::detail::ostream( writer ); + spb::json::detail::base64_encode( stream, { reinterpret_cast< const std::byte * >( value.data( ) ), value.size( ) } ); + return result; +} + +auto generate_random_bytes( size_t size ) -> std::string +{ + auto result = std::string( size, 0 ); + for( auto & c : result ) + { + c = ( char ) rand( ); + } + return result; +} + +auto base64_decode( std::string_view value ) -> std::string +{ + auto reader = [ ptr = value.data( ), end = value.data( ) + value.size( ) ]( void * data, size_t size ) mutable -> size_t + { + size_t bytes_left = end - ptr; + + size = std::min( size, bytes_left ); + memcpy( data, ptr, size ); + ptr += size; + return size; + }; + + auto stream = spb::json::detail::istream( reader ); + auto result = std::vector< std::byte >( ); + + spb::json::detail::base64_decode_string( result, stream ); + REQUIRE_THROWS( ( void ) stream.view( 1 ) ); + + return { std::string( reinterpret_cast< const char * >( result.data( ) ), reinterpret_cast< const char * >( result.data( ) + result.size( ) ) ) }; +} + +template < size_t N > +auto base64_decode_fixed( std::string_view value ) -> std::string +{ + auto reader = [ ptr = value.data( ), end = value.data( ) + value.size( ) ]( void * data, size_t size ) mutable -> size_t + { + size_t bytes_left = end - ptr; + + size = std::min( size, bytes_left ); + memcpy( data, ptr, size ); + ptr += size; + return size; + }; + + auto stream = spb::json::detail::istream( reader ); + auto result = std::array< std::byte, N >( ); + + spb::json::detail::base64_decode_string( result, stream ); + REQUIRE_THROWS( ( void ) stream.view( 1 ) ); + + return { std::string( reinterpret_cast< const char * >( result.data( ) ), reinterpret_cast< const char * >( result.data( ) + result.size( ) ) ) }; +} + +}// namespace + +TEST_CASE( "base64" ) +{ + SUBCASE( "base64_encode" ) + { + CHECK( base64_encode( "hello world" ) == "aGVsbG8gd29ybGQ=" ); + + CHECK( base64_encode( "" ) == "" ); + CHECK( base64_encode( "f" ) == "Zg==" ); + CHECK( base64_encode( "fo" ) == "Zm8=" ); + CHECK( base64_encode( "foo" ) == "Zm9v" ); + CHECK( base64_encode( "foob" ) == "Zm9vYg==" ); + CHECK( base64_encode( "fooba" ) == "Zm9vYmE=" ); + CHECK( base64_encode( "foobar" ) == "Zm9vYmFy" ); + } + SUBCASE( "base64_decode" ) + { + SUBCASE( "invalid" ) + { + CHECK_THROWS( base64_decode( R"(")" ) ); + CHECK_THROWS( base64_decode( R"("Zg=")" ) ); + CHECK_THROWS( base64_decode( R"("Zg")" ) ); + CHECK_THROWS( base64_decode( R"("Z")" ) ); + CHECK_THROWS( base64_decode( R"("Zg==)" ) ); + } + + CHECK( base64_decode( R"("")" ) == "" ); + CHECK( base64_decode( R"("Zg==")" ) == "f" ); + CHECK( base64_decode( R"("Zm8=")" ) == "fo" ); + CHECK( base64_decode( R"("Zm9v")" ) == "foo" ); + CHECK( base64_decode( R"("Zm9vYg==")" ) == "foob" ); + CHECK( base64_decode( R"("Zm9vYmE=")" ) == "fooba" ); + CHECK( base64_decode( R"("Zm9vYmFy")" ) == "foobar" ); + CHECK( base64_decode_fixed< 6 >( R"("Zm9vYmFy")" ) == "foobar" ); + CHECK_THROWS( base64_decode_fixed< 5 >( R"("Zm9vYmFy")" ) ); + CHECK_THROWS( base64_decode_fixed< 7 >( R"("Zm9vYmFy")" ) ); + CHECK_THROWS( base64_decode_fixed< 3 >( R"("Zm9vYmFy")" ) ); + CHECK_THROWS( base64_decode( R"("Zm9vY!Fy")" ) ); + CHECK_THROWS( base64_decode( R"("Zm9vY^Fy")" ) ); + CHECK_THROWS( base64_decode( R"("!m9vYmFy")" ) ); + CHECK_THROWS( base64_decode( R"("&m9vYmFy")" ) ); + } + SUBCASE( "encode/decode" ) + { + const auto buffer_max_size = SPB_READ_BUFFER_SIZE * 10; + for( auto i = 8U; i <= buffer_max_size; i++ ) + { + srand( i ); + + auto bytes = generate_random_bytes( i ); + auto encoded = base64_encode( bytes ); + CHECK( ( encoded.size( ) % 4 ) == 0 ); + CHECK( std::all_of( encoded.begin( ), encoded.end( ), isprint ) ); + { + auto decoded = base64_decode( '"' + encoded + '"' ); + CHECK( decoded == bytes ); + } + if( i == 8 ) + { + auto decoded = base64_decode_fixed< 8 >( '"' + encoded + '"' ); + CHECK( decoded == bytes ); + } + if( i == buffer_max_size ) + { + auto decoded = base64_decode_fixed< buffer_max_size >( '"' + encoded + '"' ); + CHECK( decoded == bytes ); + } + } + } +} \ No newline at end of file diff --git a/3rdparty/simple-protobuf/test/cmake/doctest.cmake b/3rdparty/simple-protobuf/test/cmake/doctest.cmake new file mode 100644 index 0000000..3c4929f --- /dev/null +++ b/3rdparty/simple-protobuf/test/cmake/doctest.cmake @@ -0,0 +1,189 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +#[=======================================================================[.rst: +doctest +----- + +This module defines a function to help use the doctest test framework. + +The :command:`doctest_discover_tests` discovers tests by asking the compiled test +executable to enumerate its tests. This does not require CMake to be re-run +when tests change. However, it may not work in a cross-compiling environment, +and setting test properties is less convenient. + +This command is intended to replace use of :command:`add_test` to register +tests, and will create a separate CTest test for each doctest test case. Note +that this is in some cases less efficient, as common set-up and tear-down logic +cannot be shared by multiple test cases executing in the same instance. +However, it provides more fine-grained pass/fail information to CTest, which is +usually considered as more beneficial. By default, the CTest test name is the +same as the doctest name; see also ``TEST_PREFIX`` and ``TEST_SUFFIX``. + +.. command:: doctest_discover_tests + + Automatically add tests with CTest by querying the compiled test executable + for available tests:: + + doctest_discover_tests(target + [TEST_SPEC arg1...] + [EXTRA_ARGS arg1...] + [WORKING_DIRECTORY dir] + [TEST_PREFIX prefix] + [TEST_SUFFIX suffix] + [PROPERTIES name1 value1...] + [ADD_LABELS value] + [TEST_LIST var] + [JUNIT_OUTPUT_DIR dir] + ) + + ``doctest_discover_tests`` sets up a post-build command on the test executable + that generates the list of tests by parsing the output from running the test + with the ``--list-test-cases`` argument. This ensures that the full + list of tests is obtained. Since test discovery occurs at build time, it is + not necessary to re-run CMake when the list of tests changes. + However, it requires that :prop_tgt:`CROSSCOMPILING_EMULATOR` is properly set + in order to function in a cross-compiling environment. + + Additionally, setting properties on tests is somewhat less convenient, since + the tests are not available at CMake time. Additional test properties may be + assigned to the set of tests as a whole using the ``PROPERTIES`` option. If + more fine-grained test control is needed, custom content may be provided + through an external CTest script using the :prop_dir:`TEST_INCLUDE_FILES` + directory property. The set of discovered tests is made accessible to such a + script via the ``_TESTS`` variable. + + The options are: + + ``target`` + Specifies the doctest executable, which must be a known CMake executable + target. CMake will substitute the location of the built executable when + running the test. + + ``TEST_SPEC arg1...`` + Specifies test cases, wildcarded test cases, tags and tag expressions to + pass to the doctest executable with the ``--list-test-cases`` argument. + + ``EXTRA_ARGS arg1...`` + Any extra arguments to pass on the command line to each test case. + + ``WORKING_DIRECTORY dir`` + Specifies the directory in which to run the discovered test cases. If this + option is not provided, the current binary directory is used. + + ``TEST_PREFIX prefix`` + Specifies a ``prefix`` to be prepended to the name of each discovered test + case. This can be useful when the same test executable is being used in + multiple calls to ``doctest_discover_tests()`` but with different + ``TEST_SPEC`` or ``EXTRA_ARGS``. + + ``TEST_SUFFIX suffix`` + Similar to ``TEST_PREFIX`` except the ``suffix`` is appended to the name of + every discovered test case. Both ``TEST_PREFIX`` and ``TEST_SUFFIX`` may + be specified. + + ``PROPERTIES name1 value1...`` + Specifies additional properties to be set on all tests discovered by this + invocation of ``doctest_discover_tests``. + + ``ADD_LABELS value`` + Specifies if the test labels should be set automatically. + + ``TEST_LIST var`` + Make the list of tests available in the variable ``var``, rather than the + default ``_TESTS``. This can be useful when the same test + executable is being used in multiple calls to ``doctest_discover_tests()``. + Note that this variable is only available in CTest. + + ``JUNIT_OUTPUT_DIR dir`` + If specified, the parameter is passed along with ``--reporters=junit`` + and ``--out=`` to the test executable. The actual file name is the same + as the test target, including prefix and suffix. This should be used + instead of EXTRA_ARGS to avoid race conditions writing the XML result + output when using parallel test execution. + +#]=======================================================================] + +#------------------------------------------------------------------------------ +function(doctest_discover_tests TARGET) + cmake_parse_arguments( + "" + "" + "TEST_PREFIX;TEST_SUFFIX;WORKING_DIRECTORY;TEST_LIST;JUNIT_OUTPUT_DIR" + "TEST_SPEC;EXTRA_ARGS;PROPERTIES;ADD_LABELS" + ${ARGN} + ) + + if(NOT _WORKING_DIRECTORY) + set(_WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}") + endif() + if(NOT _TEST_LIST) + set(_TEST_LIST ${TARGET}_TESTS) + endif() + + ## Generate a unique name based on the extra arguments + string(SHA1 args_hash "${_TEST_SPEC} ${_EXTRA_ARGS}") + string(SUBSTRING ${args_hash} 0 7 args_hash) + + # Define rule to generate test list for aforementioned test executable + set(ctest_include_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_include-${args_hash}.cmake") + set(ctest_tests_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_tests-${args_hash}.cmake") + get_property(crosscompiling_emulator + TARGET ${TARGET} + PROPERTY CROSSCOMPILING_EMULATOR + ) + add_custom_command( + TARGET ${TARGET} POST_BUILD + BYPRODUCTS "${ctest_tests_file}" + COMMAND "${CMAKE_COMMAND}" + -D "TEST_TARGET=${TARGET}" + -D "TEST_EXECUTABLE=$" + -D "TEST_EXECUTOR=${crosscompiling_emulator}" + -D "TEST_WORKING_DIR=${_WORKING_DIRECTORY}" + -D "TEST_SPEC=${_TEST_SPEC}" + -D "TEST_EXTRA_ARGS=${_EXTRA_ARGS}" + -D "TEST_PROPERTIES=${_PROPERTIES}" + -D "TEST_ADD_LABELS=${_ADD_LABELS}" + -D "TEST_PREFIX=${_TEST_PREFIX}" + -D "TEST_SUFFIX=${_TEST_SUFFIX}" + -D "TEST_LIST=${_TEST_LIST}" + -D "TEST_JUNIT_OUTPUT_DIR=${_JUNIT_OUTPUT_DIR}" + -D "CTEST_FILE=${ctest_tests_file}" + -P "${_DOCTEST_DISCOVER_TESTS_SCRIPT}" + VERBATIM + ) + + file(WRITE "${ctest_include_file}" + "if(EXISTS \"${ctest_tests_file}\")\n" + " include(\"${ctest_tests_file}\")\n" + "else()\n" + " add_test(${TARGET}_NOT_BUILT-${args_hash} ${TARGET}_NOT_BUILT-${args_hash})\n" + "endif()\n" + ) + + if(NOT CMAKE_VERSION VERSION_LESS 3.10) + # Add discovered tests to directory TEST_INCLUDE_FILES + set_property(DIRECTORY + APPEND PROPERTY TEST_INCLUDE_FILES "${ctest_include_file}" + ) + else() + # Add discovered tests as directory TEST_INCLUDE_FILE if possible + get_property(test_include_file_set DIRECTORY PROPERTY TEST_INCLUDE_FILE SET) + if(NOT ${test_include_file_set}) + set_property(DIRECTORY + PROPERTY TEST_INCLUDE_FILE "${ctest_include_file}" + ) + else() + message(FATAL_ERROR + "Cannot set more than one TEST_INCLUDE_FILE" + ) + endif() + endif() + +endfunction() + +############################################################################### + +set(_DOCTEST_DISCOVER_TESTS_SCRIPT + ${CMAKE_CURRENT_LIST_DIR}/doctestAddTests.cmake +) diff --git a/3rdparty/simple-protobuf/test/cmake/doctestAddTests.cmake b/3rdparty/simple-protobuf/test/cmake/doctestAddTests.cmake new file mode 100644 index 0000000..3b25485 --- /dev/null +++ b/3rdparty/simple-protobuf/test/cmake/doctestAddTests.cmake @@ -0,0 +1,120 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +set(prefix "${TEST_PREFIX}") +set(suffix "${TEST_SUFFIX}") +set(spec ${TEST_SPEC}) +set(extra_args ${TEST_EXTRA_ARGS}) +set(properties ${TEST_PROPERTIES}) +set(add_labels ${TEST_ADD_LABELS}) +set(junit_output_dir "${TEST_JUNIT_OUTPUT_DIR}") +set(script) +set(suite) +set(tests) + +function(add_command NAME) + set(_args "") + foreach(_arg ${ARGN}) + if(_arg MATCHES "[^-./:a-zA-Z0-9_]") + set(_args "${_args} [==[${_arg}]==]") # form a bracket_argument + else() + set(_args "${_args} ${_arg}") + endif() + endforeach() + set(script "${script}${NAME}(${_args})\n" PARENT_SCOPE) +endfunction() + +# Run test executable to get list of available tests +if(NOT EXISTS "${TEST_EXECUTABLE}") + message(FATAL_ERROR + "Specified test executable '${TEST_EXECUTABLE}' does not exist" + ) +endif() + +if("${spec}" MATCHES .) + set(spec "--test-case=${spec}") +endif() + +execute_process( + COMMAND ${TEST_EXECUTOR} "${TEST_EXECUTABLE}" ${spec} --list-test-cases + OUTPUT_VARIABLE output + RESULT_VARIABLE result + WORKING_DIRECTORY "${TEST_WORKING_DIR}" +) +if(NOT ${result} EQUAL 0) + message(FATAL_ERROR + "Error running test executable '${TEST_EXECUTABLE}':\n" + " Result: ${result}\n" + " Output: ${output}\n" + ) +endif() + +string(REPLACE "\n" ";" output "${output}") + +# Parse output +foreach(line ${output}) + if("${line}" STREQUAL "===============================================================================" OR "${line}" MATCHES [==[^\[doctest\] ]==]) + continue() + endif() + set(test ${line}) + set(labels "") + if(${add_labels}) + # get test suite that test belongs to + execute_process( + COMMAND ${TEST_EXECUTOR} "${TEST_EXECUTABLE}" --test-case=${test} --list-test-suites + OUTPUT_VARIABLE labeloutput + RESULT_VARIABLE labelresult + WORKING_DIRECTORY "${TEST_WORKING_DIR}" + ) + if(NOT ${labelresult} EQUAL 0) + message(FATAL_ERROR + "Error running test executable '${TEST_EXECUTABLE}':\n" + " Result: ${labelresult}\n" + " Output: ${labeloutput}\n" + ) + endif() + + string(REPLACE "\n" ";" labeloutput "${labeloutput}") + foreach(labelline ${labeloutput}) + if("${labelline}" STREQUAL "===============================================================================" OR "${labelline}" MATCHES [==[^\[doctest\] ]==]) + continue() + endif() + list(APPEND labels ${labelline}) + endforeach() + endif() + + if(NOT "${junit_output_dir}" STREQUAL "") + # turn testname into a valid filename by replacing all special characters with "-" + string(REGEX REPLACE "[/\\:\"|<>]" "-" test_filename "${test}") + set(TEST_JUNIT_OUTPUT_PARAM "--reporters=junit" "--out=${junit_output_dir}/${prefix}${test_filename}${suffix}.xml") + else() + unset(TEST_JUNIT_OUTPUT_PARAM) + endif() + # use escape commas to handle properly test cases with commas inside the name + string(REPLACE "," "\\," test_name ${test}) + # ...and add to script + add_command(add_test + "${prefix}${test}${suffix}" + ${TEST_EXECUTOR} + "${TEST_EXECUTABLE}" + "--test-case=${test_name}" + "${TEST_JUNIT_OUTPUT_PARAM}" + ${extra_args} + ) + add_command(set_tests_properties + "${prefix}${test}${suffix}" + PROPERTIES + WORKING_DIRECTORY "${TEST_WORKING_DIR}" + ${properties} + LABELS ${labels} + ) + unset(labels) + list(APPEND tests "${prefix}${test}${suffix}") +endforeach() + +# Create a list of all discovered tests, which users may use to e.g. set +# properties on the tests +add_command(set ${TEST_LIST} ${tests}) + +# Write CTest script +file(WRITE "${CTEST_FILE}" "${script}") diff --git a/3rdparty/simple-protobuf/test/doctest.h b/3rdparty/simple-protobuf/test/doctest.h new file mode 100644 index 0000000..52b4a4a --- /dev/null +++ b/3rdparty/simple-protobuf/test/doctest.h @@ -0,0 +1,7134 @@ +// ====================================================================== lgtm [cpp/missing-header-guard] +// == DO NOT MODIFY THIS FILE BY HAND - IT IS AUTO GENERATED BY CMAKE! == +// ====================================================================== +// +// doctest.h - the lightest feature-rich C++ single-header testing framework for unit tests and TDD +// +// Copyright (c) 2016-2023 Viktor Kirilov +// +// Distributed under the MIT Software License +// See accompanying file LICENSE.txt or copy at +// https://opensource.org/licenses/MIT +// +// The documentation can be found at the library's page: +// https://github.com/doctest/doctest/blob/master/doc/markdown/readme.md +// +// ================================================================================================= +// ================================================================================================= +// ================================================================================================= +// +// The library is heavily influenced by Catch - https://github.com/catchorg/Catch2 +// which uses the Boost Software License - Version 1.0 +// see here - https://github.com/catchorg/Catch2/blob/master/LICENSE.txt +// +// The concept of subcases (sections in Catch) and expression decomposition are from there. +// Some parts of the code are taken directly: +// - stringification - the detection of "ostream& operator<<(ostream&, const T&)" and StringMaker<> +// - the Approx() helper class for floating point comparison +// - colors in the console +// - breaking into a debugger +// - signal / SEH handling +// - timer +// - XmlWriter class - thanks to Phil Nash for allowing the direct reuse (AKA copy/paste) +// +// The expression decomposing templates are taken from lest - https://github.com/martinmoene/lest +// which uses the Boost Software License - Version 1.0 +// see here - https://github.com/martinmoene/lest/blob/master/LICENSE.txt +// +// ================================================================================================= +// ================================================================================================= +// ================================================================================================= + +#ifndef DOCTEST_LIBRARY_INCLUDED +#define DOCTEST_LIBRARY_INCLUDED + +// ================================================================================================= +// == VERSION ====================================================================================== +// ================================================================================================= + +#define DOCTEST_VERSION_MAJOR 2 +#define DOCTEST_VERSION_MINOR 4 +#define DOCTEST_VERSION_PATCH 12 + +// util we need here +#define DOCTEST_TOSTR_IMPL(x) #x +#define DOCTEST_TOSTR(x) DOCTEST_TOSTR_IMPL(x) + +#define DOCTEST_VERSION_STR \ + DOCTEST_TOSTR(DOCTEST_VERSION_MAJOR) "." \ + DOCTEST_TOSTR(DOCTEST_VERSION_MINOR) "." \ + DOCTEST_TOSTR(DOCTEST_VERSION_PATCH) + +#define DOCTEST_VERSION \ + (DOCTEST_VERSION_MAJOR * 10000 + DOCTEST_VERSION_MINOR * 100 + DOCTEST_VERSION_PATCH) + +// ================================================================================================= +// == COMPILER VERSION ============================================================================= +// ================================================================================================= + +// ideas for the version stuff are taken from here: https://github.com/cxxstuff/cxx_detect + +#ifdef _MSC_VER +#define DOCTEST_CPLUSPLUS _MSVC_LANG +#else +#define DOCTEST_CPLUSPLUS __cplusplus +#endif + +#define DOCTEST_COMPILER(MAJOR, MINOR, PATCH) ((MAJOR)*10000000 + (MINOR)*100000 + (PATCH)) + +// GCC/Clang and GCC/MSVC are mutually exclusive, but Clang/MSVC are not because of clang-cl... +#if defined(_MSC_VER) && defined(_MSC_FULL_VER) +#if _MSC_VER == _MSC_FULL_VER / 10000 +#define DOCTEST_MSVC DOCTEST_COMPILER(_MSC_VER / 100, _MSC_VER % 100, _MSC_FULL_VER % 10000) +#else // MSVC +#define DOCTEST_MSVC \ + DOCTEST_COMPILER(_MSC_VER / 100, (_MSC_FULL_VER / 100000) % 100, _MSC_FULL_VER % 100000) +#endif // MSVC +#endif // MSVC +#if defined(__clang__) && defined(__clang_minor__) && defined(__clang_patchlevel__) +#define DOCTEST_CLANG DOCTEST_COMPILER(__clang_major__, __clang_minor__, __clang_patchlevel__) +#elif defined(__GNUC__) && defined(__GNUC_MINOR__) && defined(__GNUC_PATCHLEVEL__) && \ + !defined(__INTEL_COMPILER) +#define DOCTEST_GCC DOCTEST_COMPILER(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) +#endif // GCC +#if defined(__INTEL_COMPILER) +#define DOCTEST_ICC DOCTEST_COMPILER(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, 0) +#endif // ICC + +#ifndef DOCTEST_MSVC +#define DOCTEST_MSVC 0 +#endif // DOCTEST_MSVC +#ifndef DOCTEST_CLANG +#define DOCTEST_CLANG 0 +#endif // DOCTEST_CLANG +#ifndef DOCTEST_GCC +#define DOCTEST_GCC 0 +#endif // DOCTEST_GCC +#ifndef DOCTEST_ICC +#define DOCTEST_ICC 0 +#endif // DOCTEST_ICC + +// ================================================================================================= +// == COMPILER WARNINGS HELPERS ==================================================================== +// ================================================================================================= + +#if DOCTEST_CLANG && !DOCTEST_ICC +#define DOCTEST_PRAGMA_TO_STR(x) _Pragma(#x) +#define DOCTEST_CLANG_SUPPRESS_WARNING_PUSH _Pragma("clang diagnostic push") +#define DOCTEST_CLANG_SUPPRESS_WARNING(w) DOCTEST_PRAGMA_TO_STR(clang diagnostic ignored w) +#define DOCTEST_CLANG_SUPPRESS_WARNING_POP _Pragma("clang diagnostic pop") +#define DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH(w) \ + DOCTEST_CLANG_SUPPRESS_WARNING_PUSH DOCTEST_CLANG_SUPPRESS_WARNING(w) +#else // DOCTEST_CLANG +#define DOCTEST_CLANG_SUPPRESS_WARNING_PUSH +#define DOCTEST_CLANG_SUPPRESS_WARNING(w) +#define DOCTEST_CLANG_SUPPRESS_WARNING_POP +#define DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH(w) +#endif // DOCTEST_CLANG + +#if DOCTEST_GCC +#define DOCTEST_PRAGMA_TO_STR(x) _Pragma(#x) +#define DOCTEST_GCC_SUPPRESS_WARNING_PUSH _Pragma("GCC diagnostic push") +#define DOCTEST_GCC_SUPPRESS_WARNING(w) DOCTEST_PRAGMA_TO_STR(GCC diagnostic ignored w) +#define DOCTEST_GCC_SUPPRESS_WARNING_POP _Pragma("GCC diagnostic pop") +#define DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH(w) \ + DOCTEST_GCC_SUPPRESS_WARNING_PUSH DOCTEST_GCC_SUPPRESS_WARNING(w) +#else // DOCTEST_GCC +#define DOCTEST_GCC_SUPPRESS_WARNING_PUSH +#define DOCTEST_GCC_SUPPRESS_WARNING(w) +#define DOCTEST_GCC_SUPPRESS_WARNING_POP +#define DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH(w) +#endif // DOCTEST_GCC + +#if DOCTEST_MSVC +#define DOCTEST_MSVC_SUPPRESS_WARNING_PUSH __pragma(warning(push)) +#define DOCTEST_MSVC_SUPPRESS_WARNING(w) __pragma(warning(disable : w)) +#define DOCTEST_MSVC_SUPPRESS_WARNING_POP __pragma(warning(pop)) +#define DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(w) \ + DOCTEST_MSVC_SUPPRESS_WARNING_PUSH DOCTEST_MSVC_SUPPRESS_WARNING(w) +#else // DOCTEST_MSVC +#define DOCTEST_MSVC_SUPPRESS_WARNING_PUSH +#define DOCTEST_MSVC_SUPPRESS_WARNING(w) +#define DOCTEST_MSVC_SUPPRESS_WARNING_POP +#define DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(w) +#endif // DOCTEST_MSVC + +// ================================================================================================= +// == COMPILER WARNINGS ============================================================================ +// ================================================================================================= + +// both the header and the implementation suppress all of these, +// so it only makes sense to aggregate them like so +#define DOCTEST_SUPPRESS_COMMON_WARNINGS_PUSH \ + DOCTEST_CLANG_SUPPRESS_WARNING_PUSH \ + DOCTEST_CLANG_SUPPRESS_WARNING("-Wunknown-pragmas") \ + DOCTEST_CLANG_SUPPRESS_WARNING("-Wweak-vtables") \ + DOCTEST_CLANG_SUPPRESS_WARNING("-Wpadded") \ + DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-prototypes") \ + DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat") \ + DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat-pedantic") \ + \ + DOCTEST_GCC_SUPPRESS_WARNING_PUSH \ + DOCTEST_GCC_SUPPRESS_WARNING("-Wunknown-pragmas") \ + DOCTEST_GCC_SUPPRESS_WARNING("-Wpragmas") \ + DOCTEST_GCC_SUPPRESS_WARNING("-Weffc++") \ + DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-overflow") \ + DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-aliasing") \ + DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-declarations") \ + DOCTEST_GCC_SUPPRESS_WARNING("-Wuseless-cast") \ + DOCTEST_GCC_SUPPRESS_WARNING("-Wnoexcept") \ + \ + DOCTEST_MSVC_SUPPRESS_WARNING_PUSH \ + /* these 4 also disabled globally via cmake: */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4514) /* unreferenced inline function has been removed */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4571) /* SEH related */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4710) /* function not inlined */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4711) /* function selected for inline expansion*/ \ + /* common ones */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4616) /* invalid compiler warning */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4619) /* invalid compiler warning */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4996) /* The compiler encountered a deprecated declaration */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4706) /* assignment within conditional expression */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4512) /* 'class' : assignment operator could not be generated */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4127) /* conditional expression is constant */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4820) /* padding */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4625) /* copy constructor was implicitly deleted */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4626) /* assignment operator was implicitly deleted */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5027) /* move assignment operator implicitly deleted */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5026) /* move constructor was implicitly deleted */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4640) /* construction of local static object not thread-safe */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5045) /* Spectre mitigation for memory load */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5264) /* 'variable-name': 'const' variable is not used */ \ + /* static analysis */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(26439) /* Function may not throw. Declare it 'noexcept' */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(26495) /* Always initialize a member variable */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(26451) /* Arithmetic overflow ... */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(26444) /* Avoid unnamed objects with custom ctor and dtor... */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(26812) /* Prefer 'enum class' over 'enum' */ + +#define DOCTEST_SUPPRESS_COMMON_WARNINGS_POP \ + DOCTEST_CLANG_SUPPRESS_WARNING_POP \ + DOCTEST_GCC_SUPPRESS_WARNING_POP \ + DOCTEST_MSVC_SUPPRESS_WARNING_POP + +DOCTEST_SUPPRESS_COMMON_WARNINGS_PUSH + +DOCTEST_CLANG_SUPPRESS_WARNING_PUSH +DOCTEST_CLANG_SUPPRESS_WARNING("-Wnon-virtual-dtor") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wdeprecated") + +DOCTEST_GCC_SUPPRESS_WARNING_PUSH +DOCTEST_GCC_SUPPRESS_WARNING("-Wctor-dtor-privacy") +DOCTEST_GCC_SUPPRESS_WARNING("-Wnon-virtual-dtor") +DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-promo") + +DOCTEST_MSVC_SUPPRESS_WARNING_PUSH +DOCTEST_MSVC_SUPPRESS_WARNING(4623) // default constructor was implicitly defined as deleted + +#define DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN \ + DOCTEST_MSVC_SUPPRESS_WARNING_PUSH \ + DOCTEST_MSVC_SUPPRESS_WARNING(4548) /* before comma no effect; expected side - effect */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4265) /* virtual functions, but destructor is not virtual */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4986) /* exception specification does not match previous */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4350) /* 'member1' called instead of 'member2' */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4668) /* not defined as a preprocessor macro */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4365) /* signed/unsigned mismatch */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4774) /* format string not a string literal */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4820) /* padding */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4625) /* copy constructor was implicitly deleted */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4626) /* assignment operator was implicitly deleted */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5027) /* move assignment operator implicitly deleted */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5026) /* move constructor was implicitly deleted */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4623) /* default constructor was implicitly deleted */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5039) /* pointer to pot. throwing function passed to extern C */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5045) /* Spectre mitigation for memory load */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5105) /* macro producing 'defined' has undefined behavior */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(4738) /* storing float result in memory, loss of performance */ \ + DOCTEST_MSVC_SUPPRESS_WARNING(5262) /* implicit fall-through */ + +#define DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END DOCTEST_MSVC_SUPPRESS_WARNING_POP + +// ================================================================================================= +// == FEATURE DETECTION ============================================================================ +// ================================================================================================= + +// general compiler feature support table: https://en.cppreference.com/w/cpp/compiler_support +// MSVC C++11 feature support table: https://msdn.microsoft.com/en-us/library/hh567368.aspx +// GCC C++11 feature support table: https://gcc.gnu.org/projects/cxx-status.html +// MSVC version table: +// https://en.wikipedia.org/wiki/Microsoft_Visual_C%2B%2B#Internal_version_numbering +// MSVC++ 14.3 (17) _MSC_VER == 1930 (Visual Studio 2022) +// MSVC++ 14.2 (16) _MSC_VER == 1920 (Visual Studio 2019) +// MSVC++ 14.1 (15) _MSC_VER == 1910 (Visual Studio 2017) +// MSVC++ 14.0 _MSC_VER == 1900 (Visual Studio 2015) +// MSVC++ 12.0 _MSC_VER == 1800 (Visual Studio 2013) +// MSVC++ 11.0 _MSC_VER == 1700 (Visual Studio 2012) +// MSVC++ 10.0 _MSC_VER == 1600 (Visual Studio 2010) +// MSVC++ 9.0 _MSC_VER == 1500 (Visual Studio 2008) +// MSVC++ 8.0 _MSC_VER == 1400 (Visual Studio 2005) + +// Universal Windows Platform support +#if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) +#define DOCTEST_CONFIG_NO_WINDOWS_SEH +#endif // WINAPI_FAMILY +#if DOCTEST_MSVC && !defined(DOCTEST_CONFIG_WINDOWS_SEH) +#define DOCTEST_CONFIG_WINDOWS_SEH +#endif // MSVC +#if defined(DOCTEST_CONFIG_NO_WINDOWS_SEH) && defined(DOCTEST_CONFIG_WINDOWS_SEH) +#undef DOCTEST_CONFIG_WINDOWS_SEH +#endif // DOCTEST_CONFIG_NO_WINDOWS_SEH + +#if !defined(_WIN32) && !defined(__QNX__) && !defined(DOCTEST_CONFIG_POSIX_SIGNALS) && \ + !defined(__EMSCRIPTEN__) && !defined(__wasi__) +#define DOCTEST_CONFIG_POSIX_SIGNALS +#endif // _WIN32 +#if defined(DOCTEST_CONFIG_NO_POSIX_SIGNALS) && defined(DOCTEST_CONFIG_POSIX_SIGNALS) +#undef DOCTEST_CONFIG_POSIX_SIGNALS +#endif // DOCTEST_CONFIG_NO_POSIX_SIGNALS + +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS +#if !defined(__cpp_exceptions) && !defined(__EXCEPTIONS) && !defined(_CPPUNWIND) \ + || defined(__wasi__) +#define DOCTEST_CONFIG_NO_EXCEPTIONS +#endif // no exceptions +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + +#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS +#define DOCTEST_CONFIG_NO_EXCEPTIONS +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS + +#if defined(DOCTEST_CONFIG_NO_EXCEPTIONS) && !defined(DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS) +#define DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS && !DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS + +#ifdef __wasi__ +#define DOCTEST_CONFIG_NO_MULTITHREADING +#endif + +#if defined(DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN) && !defined(DOCTEST_CONFIG_IMPLEMENT) +#define DOCTEST_CONFIG_IMPLEMENT +#endif // DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN + +#if defined(_WIN32) || defined(__CYGWIN__) +#if DOCTEST_MSVC +#define DOCTEST_SYMBOL_EXPORT __declspec(dllexport) +#define DOCTEST_SYMBOL_IMPORT __declspec(dllimport) +#else // MSVC +#define DOCTEST_SYMBOL_EXPORT __attribute__((dllexport)) +#define DOCTEST_SYMBOL_IMPORT __attribute__((dllimport)) +#endif // MSVC +#else // _WIN32 +#define DOCTEST_SYMBOL_EXPORT __attribute__((visibility("default"))) +#define DOCTEST_SYMBOL_IMPORT +#endif // _WIN32 + +#ifdef DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL +#ifdef DOCTEST_CONFIG_IMPLEMENT +#define DOCTEST_INTERFACE DOCTEST_SYMBOL_EXPORT +#else // DOCTEST_CONFIG_IMPLEMENT +#define DOCTEST_INTERFACE DOCTEST_SYMBOL_IMPORT +#endif // DOCTEST_CONFIG_IMPLEMENT +#else // DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL +#define DOCTEST_INTERFACE +#endif // DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL + +// needed for extern template instantiations +// see https://github.com/fmtlib/fmt/issues/2228 +#if DOCTEST_MSVC +#define DOCTEST_INTERFACE_DECL +#define DOCTEST_INTERFACE_DEF DOCTEST_INTERFACE +#else // DOCTEST_MSVC +#define DOCTEST_INTERFACE_DECL DOCTEST_INTERFACE +#define DOCTEST_INTERFACE_DEF +#endif // DOCTEST_MSVC + +#define DOCTEST_EMPTY + +#if DOCTEST_MSVC +#define DOCTEST_NOINLINE __declspec(noinline) +#define DOCTEST_UNUSED +#define DOCTEST_ALIGNMENT(x) +#elif DOCTEST_CLANG && DOCTEST_CLANG < DOCTEST_COMPILER(3, 5, 0) +#define DOCTEST_NOINLINE +#define DOCTEST_UNUSED +#define DOCTEST_ALIGNMENT(x) +#else +#define DOCTEST_NOINLINE __attribute__((noinline)) +#define DOCTEST_UNUSED __attribute__((unused)) +#define DOCTEST_ALIGNMENT(x) __attribute__((aligned(x))) +#endif + +#ifdef DOCTEST_CONFIG_NO_CONTRADICTING_INLINE +#define DOCTEST_INLINE_NOINLINE inline +#else +#define DOCTEST_INLINE_NOINLINE inline DOCTEST_NOINLINE +#endif + +#ifndef DOCTEST_NORETURN +#if DOCTEST_MSVC && (DOCTEST_MSVC < DOCTEST_COMPILER(19, 0, 0)) +#define DOCTEST_NORETURN +#else // DOCTEST_MSVC +#define DOCTEST_NORETURN [[noreturn]] +#endif // DOCTEST_MSVC +#endif // DOCTEST_NORETURN + +#ifndef DOCTEST_NOEXCEPT +#if DOCTEST_MSVC && (DOCTEST_MSVC < DOCTEST_COMPILER(19, 0, 0)) +#define DOCTEST_NOEXCEPT +#else // DOCTEST_MSVC +#define DOCTEST_NOEXCEPT noexcept +#endif // DOCTEST_MSVC +#endif // DOCTEST_NOEXCEPT + +#ifndef DOCTEST_CONSTEXPR +#if DOCTEST_MSVC && (DOCTEST_MSVC < DOCTEST_COMPILER(19, 0, 0)) +#define DOCTEST_CONSTEXPR const +#define DOCTEST_CONSTEXPR_FUNC inline +#else // DOCTEST_MSVC +#define DOCTEST_CONSTEXPR constexpr +#define DOCTEST_CONSTEXPR_FUNC constexpr +#endif // DOCTEST_MSVC +#endif // DOCTEST_CONSTEXPR + +#ifndef DOCTEST_NO_SANITIZE_INTEGER +#if DOCTEST_CLANG >= DOCTEST_COMPILER(3, 7, 0) +#define DOCTEST_NO_SANITIZE_INTEGER __attribute__((no_sanitize("integer"))) +#else +#define DOCTEST_NO_SANITIZE_INTEGER +#endif +#endif // DOCTEST_NO_SANITIZE_INTEGER + +// ================================================================================================= +// == FEATURE DETECTION END ======================================================================== +// ================================================================================================= + +#define DOCTEST_DECLARE_INTERFACE(name) \ + virtual ~name(); \ + name() = default; \ + name(const name&) = delete; \ + name(name&&) = delete; \ + name& operator=(const name&) = delete; \ + name& operator=(name&&) = delete; + +#define DOCTEST_DEFINE_INTERFACE(name) \ + name::~name() = default; + +// internal macros for string concatenation and anonymous variable name generation +#define DOCTEST_CAT_IMPL(s1, s2) s1##s2 +#define DOCTEST_CAT(s1, s2) DOCTEST_CAT_IMPL(s1, s2) +#ifdef __COUNTER__ // not standard and may be missing for some compilers +#define DOCTEST_ANONYMOUS(x) DOCTEST_CAT(x, __COUNTER__) +#else // __COUNTER__ +#define DOCTEST_ANONYMOUS(x) DOCTEST_CAT(x, __LINE__) +#endif // __COUNTER__ + +#ifndef DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE +#define DOCTEST_REF_WRAP(x) x& +#else // DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE +#define DOCTEST_REF_WRAP(x) x +#endif // DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE + +// not using __APPLE__ because... this is how Catch does it +#ifdef __MAC_OS_X_VERSION_MIN_REQUIRED +#define DOCTEST_PLATFORM_MAC +#elif defined(__IPHONE_OS_VERSION_MIN_REQUIRED) +#define DOCTEST_PLATFORM_IPHONE +#elif defined(_WIN32) +#define DOCTEST_PLATFORM_WINDOWS +#elif defined(__wasi__) +#define DOCTEST_PLATFORM_WASI +#else // DOCTEST_PLATFORM +#define DOCTEST_PLATFORM_LINUX +#endif // DOCTEST_PLATFORM + +namespace doctest { namespace detail { + static DOCTEST_CONSTEXPR int consume(const int*, int) noexcept { return 0; } +}} + +#define DOCTEST_GLOBAL_NO_WARNINGS(var, ...) \ + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wglobal-constructors") \ + static const int var = doctest::detail::consume(&var, __VA_ARGS__); \ + DOCTEST_CLANG_SUPPRESS_WARNING_POP + +#ifndef DOCTEST_BREAK_INTO_DEBUGGER +// should probably take a look at https://github.com/scottt/debugbreak +#ifdef DOCTEST_PLATFORM_LINUX +#if defined(__GNUC__) && (defined(__i386) || defined(__x86_64)) +// Break at the location of the failing check if possible +#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("int $3\n" : :) // NOLINT(hicpp-no-assembler) +#else +#include +#define DOCTEST_BREAK_INTO_DEBUGGER() raise(SIGTRAP) +#endif +#elif defined(DOCTEST_PLATFORM_MAC) +#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64__) || defined(__i386) +#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("int $3\n" : :) // NOLINT(hicpp-no-assembler) +#elif defined(__ppc__) || defined(__ppc64__) +// https://www.cocoawithlove.com/2008/03/break-into-debugger.html +#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("li r0, 20\nsc\nnop\nli r0, 37\nli r4, 2\nsc\nnop\n": : : "memory","r0","r3","r4") // NOLINT(hicpp-no-assembler) +#else +#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("brk #0"); // NOLINT(hicpp-no-assembler) +#endif +#elif DOCTEST_MSVC +#define DOCTEST_BREAK_INTO_DEBUGGER() __debugbreak() +#elif defined(__MINGW32__) +DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wredundant-decls") +extern "C" __declspec(dllimport) void __stdcall DebugBreak(); +DOCTEST_GCC_SUPPRESS_WARNING_POP +#define DOCTEST_BREAK_INTO_DEBUGGER() ::DebugBreak() +#else // linux +#define DOCTEST_BREAK_INTO_DEBUGGER() (static_cast(0)) +#endif // linux +#endif // DOCTEST_BREAK_INTO_DEBUGGER + +// this is kept here for backwards compatibility since the config option was changed +#ifdef DOCTEST_CONFIG_USE_IOSFWD +#ifndef DOCTEST_CONFIG_USE_STD_HEADERS +#define DOCTEST_CONFIG_USE_STD_HEADERS +#endif +#endif // DOCTEST_CONFIG_USE_IOSFWD + +// for clang - always include ciso646 (which drags some std stuff) because +// we want to check if we are using libc++ with the _LIBCPP_VERSION macro in +// which case we don't want to forward declare stuff from std - for reference: +// https://github.com/doctest/doctest/issues/126 +// https://github.com/doctest/doctest/issues/356 +#if DOCTEST_CLANG +#include +#endif // clang + +#ifdef _LIBCPP_VERSION +#ifndef DOCTEST_CONFIG_USE_STD_HEADERS +#define DOCTEST_CONFIG_USE_STD_HEADERS +#endif +#endif // _LIBCPP_VERSION + +#ifdef DOCTEST_CONFIG_USE_STD_HEADERS +#ifndef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS +#define DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS +#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS +DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN +#include +#include +#include +DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END +#else // DOCTEST_CONFIG_USE_STD_HEADERS + +// Forward declaring 'X' in namespace std is not permitted by the C++ Standard. +DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4643) + +namespace std { // NOLINT(cert-dcl58-cpp) +typedef decltype(nullptr) nullptr_t; // NOLINT(modernize-use-using) +typedef decltype(sizeof(void*)) size_t; // NOLINT(modernize-use-using) +template +struct char_traits; +template <> +struct char_traits; +template +class basic_ostream; // NOLINT(fuchsia-virtual-inheritance) +typedef basic_ostream> ostream; // NOLINT(modernize-use-using) +template +// NOLINTNEXTLINE +basic_ostream& operator<<(basic_ostream&, const char*); +template +class basic_istream; +typedef basic_istream> istream; // NOLINT(modernize-use-using) +template +class tuple; +#if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0) +// see this issue on why this is needed: https://github.com/doctest/doctest/issues/183 +template +class allocator; +template +class basic_string; +using string = basic_string, allocator>; +#endif // VS 2019 +} // namespace std + +DOCTEST_MSVC_SUPPRESS_WARNING_POP + +#endif // DOCTEST_CONFIG_USE_STD_HEADERS + +#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS +#include +#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + +namespace doctest { + +using std::size_t; + +DOCTEST_INTERFACE extern bool is_running_in_test; + +#ifndef DOCTEST_CONFIG_STRING_SIZE_TYPE +#define DOCTEST_CONFIG_STRING_SIZE_TYPE unsigned +#endif + +// A 24 byte string class (can be as small as 17 for x64 and 13 for x86) that can hold strings with length +// of up to 23 chars on the stack before going on the heap - the last byte of the buffer is used for: +// - "is small" bit - the highest bit - if "0" then it is small - otherwise its "1" (128) +// - if small - capacity left before going on the heap - using the lowest 5 bits +// - if small - 2 bits are left unused - the second and third highest ones +// - if small - acts as a null terminator if strlen() is 23 (24 including the null terminator) +// and the "is small" bit remains "0" ("as well as the capacity left") so its OK +// Idea taken from this lecture about the string implementation of facebook/folly - fbstring +// https://www.youtube.com/watch?v=kPR8h4-qZdk +// TODO: +// - optimizations - like not deleting memory unnecessarily in operator= and etc. +// - resize/reserve/clear +// - replace +// - back/front +// - iterator stuff +// - find & friends +// - push_back/pop_back +// - assign/insert/erase +// - relational operators as free functions - taking const char* as one of the params +class DOCTEST_INTERFACE String +{ +public: + using size_type = DOCTEST_CONFIG_STRING_SIZE_TYPE; + +private: + static DOCTEST_CONSTEXPR size_type len = 24; //!OCLINT avoid private static members + static DOCTEST_CONSTEXPR size_type last = len - 1; //!OCLINT avoid private static members + + struct view // len should be more than sizeof(view) - because of the final byte for flags + { + char* ptr; + size_type size; + size_type capacity; + }; + + union + { + char buf[len]; // NOLINT(*-avoid-c-arrays) + view data; + }; + + char* allocate(size_type sz); + + bool isOnStack() const noexcept { return (buf[last] & 128) == 0; } + void setOnHeap() noexcept; + void setLast(size_type in = last) noexcept; + void setSize(size_type sz) noexcept; + + void copy(const String& other); + +public: + static DOCTEST_CONSTEXPR size_type npos = static_cast(-1); + + String() noexcept; + ~String(); + + // cppcheck-suppress noExplicitConstructor + String(const char* in); + String(const char* in, size_type in_size); + + String(std::istream& in, size_type in_size); + + String(const String& other); + String& operator=(const String& other); + + String& operator+=(const String& other); + + String(String&& other) noexcept; + String& operator=(String&& other) noexcept; + + char operator[](size_type i) const; + char& operator[](size_type i); + + // the only functions I'm willing to leave in the interface - available for inlining + const char* c_str() const { return const_cast(this)->c_str(); } // NOLINT + char* c_str() { + if (isOnStack()) { + return reinterpret_cast(buf); + } + return data.ptr; + } + + size_type size() const; + size_type capacity() const; + + String substr(size_type pos, size_type cnt = npos) &&; + String substr(size_type pos, size_type cnt = npos) const &; + + size_type find(char ch, size_type pos = 0) const; + size_type rfind(char ch, size_type pos = npos) const; + + int compare(const char* other, bool no_case = false) const; + int compare(const String& other, bool no_case = false) const; + +friend DOCTEST_INTERFACE std::ostream& operator<<(std::ostream& s, const String& in); +}; + +DOCTEST_INTERFACE String operator+(const String& lhs, const String& rhs); + +DOCTEST_INTERFACE bool operator==(const String& lhs, const String& rhs); +DOCTEST_INTERFACE bool operator!=(const String& lhs, const String& rhs); +DOCTEST_INTERFACE bool operator<(const String& lhs, const String& rhs); +DOCTEST_INTERFACE bool operator>(const String& lhs, const String& rhs); +DOCTEST_INTERFACE bool operator<=(const String& lhs, const String& rhs); +DOCTEST_INTERFACE bool operator>=(const String& lhs, const String& rhs); + +class DOCTEST_INTERFACE Contains { +public: + explicit Contains(const String& string); + + bool checkWith(const String& other) const; + + String string; +}; + +DOCTEST_INTERFACE String toString(const Contains& in); + +DOCTEST_INTERFACE bool operator==(const String& lhs, const Contains& rhs); +DOCTEST_INTERFACE bool operator==(const Contains& lhs, const String& rhs); +DOCTEST_INTERFACE bool operator!=(const String& lhs, const Contains& rhs); +DOCTEST_INTERFACE bool operator!=(const Contains& lhs, const String& rhs); + +namespace Color { + enum Enum + { + None = 0, + White, + Red, + Green, + Blue, + Cyan, + Yellow, + Grey, + + Bright = 0x10, + + BrightRed = Bright | Red, + BrightGreen = Bright | Green, + LightGrey = Bright | Grey, + BrightWhite = Bright | White + }; + + DOCTEST_INTERFACE std::ostream& operator<<(std::ostream& s, Color::Enum code); +} // namespace Color + +namespace assertType { + enum Enum + { + // macro traits + + is_warn = 1, + is_check = 2 * is_warn, + is_require = 2 * is_check, + + is_normal = 2 * is_require, + is_throws = 2 * is_normal, + is_throws_as = 2 * is_throws, + is_throws_with = 2 * is_throws_as, + is_nothrow = 2 * is_throws_with, + + is_false = 2 * is_nothrow, + is_unary = 2 * is_false, // not checked anywhere - used just to distinguish the types + + is_eq = 2 * is_unary, + is_ne = 2 * is_eq, + + is_lt = 2 * is_ne, + is_gt = 2 * is_lt, + + is_ge = 2 * is_gt, + is_le = 2 * is_ge, + + // macro types + + DT_WARN = is_normal | is_warn, + DT_CHECK = is_normal | is_check, + DT_REQUIRE = is_normal | is_require, + + DT_WARN_FALSE = is_normal | is_false | is_warn, + DT_CHECK_FALSE = is_normal | is_false | is_check, + DT_REQUIRE_FALSE = is_normal | is_false | is_require, + + DT_WARN_THROWS = is_throws | is_warn, + DT_CHECK_THROWS = is_throws | is_check, + DT_REQUIRE_THROWS = is_throws | is_require, + + DT_WARN_THROWS_AS = is_throws_as | is_warn, + DT_CHECK_THROWS_AS = is_throws_as | is_check, + DT_REQUIRE_THROWS_AS = is_throws_as | is_require, + + DT_WARN_THROWS_WITH = is_throws_with | is_warn, + DT_CHECK_THROWS_WITH = is_throws_with | is_check, + DT_REQUIRE_THROWS_WITH = is_throws_with | is_require, + + DT_WARN_THROWS_WITH_AS = is_throws_with | is_throws_as | is_warn, + DT_CHECK_THROWS_WITH_AS = is_throws_with | is_throws_as | is_check, + DT_REQUIRE_THROWS_WITH_AS = is_throws_with | is_throws_as | is_require, + + DT_WARN_NOTHROW = is_nothrow | is_warn, + DT_CHECK_NOTHROW = is_nothrow | is_check, + DT_REQUIRE_NOTHROW = is_nothrow | is_require, + + DT_WARN_EQ = is_normal | is_eq | is_warn, + DT_CHECK_EQ = is_normal | is_eq | is_check, + DT_REQUIRE_EQ = is_normal | is_eq | is_require, + + DT_WARN_NE = is_normal | is_ne | is_warn, + DT_CHECK_NE = is_normal | is_ne | is_check, + DT_REQUIRE_NE = is_normal | is_ne | is_require, + + DT_WARN_GT = is_normal | is_gt | is_warn, + DT_CHECK_GT = is_normal | is_gt | is_check, + DT_REQUIRE_GT = is_normal | is_gt | is_require, + + DT_WARN_LT = is_normal | is_lt | is_warn, + DT_CHECK_LT = is_normal | is_lt | is_check, + DT_REQUIRE_LT = is_normal | is_lt | is_require, + + DT_WARN_GE = is_normal | is_ge | is_warn, + DT_CHECK_GE = is_normal | is_ge | is_check, + DT_REQUIRE_GE = is_normal | is_ge | is_require, + + DT_WARN_LE = is_normal | is_le | is_warn, + DT_CHECK_LE = is_normal | is_le | is_check, + DT_REQUIRE_LE = is_normal | is_le | is_require, + + DT_WARN_UNARY = is_normal | is_unary | is_warn, + DT_CHECK_UNARY = is_normal | is_unary | is_check, + DT_REQUIRE_UNARY = is_normal | is_unary | is_require, + + DT_WARN_UNARY_FALSE = is_normal | is_false | is_unary | is_warn, + DT_CHECK_UNARY_FALSE = is_normal | is_false | is_unary | is_check, + DT_REQUIRE_UNARY_FALSE = is_normal | is_false | is_unary | is_require, + }; +} // namespace assertType + +DOCTEST_INTERFACE const char* assertString(assertType::Enum at); +DOCTEST_INTERFACE const char* failureString(assertType::Enum at); +DOCTEST_INTERFACE const char* skipPathFromFilename(const char* file); + +struct DOCTEST_INTERFACE TestCaseData +{ + String m_file; // the file in which the test was registered (using String - see #350) + unsigned m_line; // the line where the test was registered + const char* m_name; // name of the test case + const char* m_test_suite; // the test suite in which the test was added + const char* m_description; + bool m_skip; + bool m_no_breaks; + bool m_no_output; + bool m_may_fail; + bool m_should_fail; + int m_expected_failures; + double m_timeout; +}; + +struct DOCTEST_INTERFACE AssertData +{ + // common - for all asserts + const TestCaseData* m_test_case; + assertType::Enum m_at; + const char* m_file; + int m_line; + const char* m_expr; + bool m_failed; + + // exception-related - for all asserts + bool m_threw; + String m_exception; + + // for normal asserts + String m_decomp; + + // for specific exception-related asserts + bool m_threw_as; + const char* m_exception_type; + + class DOCTEST_INTERFACE StringContains { + private: + Contains content; + bool isContains; + + public: + StringContains(const String& str) : content(str), isContains(false) { } + StringContains(Contains cntn) : content(static_cast(cntn)), isContains(true) { } + + bool check(const String& str) { return isContains ? (content == str) : (content.string == str); } + + operator const String&() const { return content.string; } + + const char* c_str() const { return content.string.c_str(); } + } m_exception_string; + + AssertData(assertType::Enum at, const char* file, int line, const char* expr, + const char* exception_type, const StringContains& exception_string); +}; + +struct DOCTEST_INTERFACE MessageData +{ + String m_string; + const char* m_file; + int m_line; + assertType::Enum m_severity; +}; + +struct DOCTEST_INTERFACE SubcaseSignature +{ + String m_name; + const char* m_file; + int m_line; + + bool operator==(const SubcaseSignature& other) const; + bool operator<(const SubcaseSignature& other) const; +}; + +struct DOCTEST_INTERFACE IContextScope +{ + DOCTEST_DECLARE_INTERFACE(IContextScope) + virtual void stringify(std::ostream*) const = 0; +}; + +namespace detail { + struct DOCTEST_INTERFACE TestCase; +} // namespace detail + +struct ContextOptions //!OCLINT too many fields +{ + std::ostream* cout = nullptr; // stdout stream + String binary_name; // the test binary name + + const detail::TestCase* currentTest = nullptr; + + // == parameters from the command line + String out; // output filename + String order_by; // how tests should be ordered + unsigned rand_seed; // the seed for rand ordering + + unsigned first; // the first (matching) test to be executed + unsigned last; // the last (matching) test to be executed + + int abort_after; // stop tests after this many failed assertions + int subcase_filter_levels; // apply the subcase filters for the first N levels + + bool success; // include successful assertions in output + bool case_sensitive; // if filtering should be case sensitive + bool exit; // if the program should be exited after the tests are ran/whatever + bool duration; // print the time duration of each test case + bool minimal; // minimal console output (only test failures) + bool quiet; // no console output + bool no_throw; // to skip exceptions-related assertion macros + bool no_exitcode; // if the framework should return 0 as the exitcode + bool no_run; // to not run the tests at all (can be done with an "*" exclude) + bool no_intro; // to not print the intro of the framework + bool no_version; // to not print the version of the framework + bool no_colors; // if output to the console should be colorized + bool force_colors; // forces the use of colors even when a tty cannot be detected + bool no_breaks; // to not break into the debugger + bool no_skip; // don't skip test cases which are marked to be skipped + bool gnu_file_line; // if line numbers should be surrounded with :x: and not (x): + bool no_path_in_filenames; // if the path to files should be removed from the output + String strip_file_prefixes;// remove the longest matching one of these prefixes from any file paths in the output + bool no_line_numbers; // if source code line numbers should be omitted from the output + bool no_debug_output; // no output in the debug console when a debugger is attached + bool no_skipped_summary; // don't print "skipped" in the summary !!! UNDOCUMENTED !!! + bool no_time_in_output; // omit any time/timestamps from output !!! UNDOCUMENTED !!! + + bool help; // to print the help + bool version; // to print the version + bool count; // if only the count of matching tests is to be retrieved + bool list_test_cases; // to list all tests matching the filters + bool list_test_suites; // to list all suites matching the filters + bool list_reporters; // lists all registered reporters +}; + +namespace detail { + namespace types { +#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + using namespace std; +#else + template + struct enable_if { }; + + template + struct enable_if { using type = T; }; + + struct true_type { static DOCTEST_CONSTEXPR bool value = true; }; + struct false_type { static DOCTEST_CONSTEXPR bool value = false; }; + + template struct remove_reference { using type = T; }; + template struct remove_reference { using type = T; }; + template struct remove_reference { using type = T; }; + + template struct is_rvalue_reference : false_type { }; + template struct is_rvalue_reference : true_type { }; + + template struct remove_const { using type = T; }; + template struct remove_const { using type = T; }; + + // Compiler intrinsics + template struct is_enum { static DOCTEST_CONSTEXPR bool value = __is_enum(T); }; + template struct underlying_type { using type = __underlying_type(T); }; + + template struct is_pointer : false_type { }; + template struct is_pointer : true_type { }; + + template struct is_array : false_type { }; + // NOLINTNEXTLINE(*-avoid-c-arrays) + template struct is_array : true_type { }; +#endif + } + + // + template + T&& declval(); + + template + DOCTEST_CONSTEXPR_FUNC T&& forward(typename types::remove_reference::type& t) DOCTEST_NOEXCEPT { + return static_cast(t); + } + + template + DOCTEST_CONSTEXPR_FUNC T&& forward(typename types::remove_reference::type&& t) DOCTEST_NOEXCEPT { + return static_cast(t); + } + + template + struct deferred_false : types::false_type { }; + +// MSVS 2015 :( +#if !DOCTEST_CLANG && defined(_MSC_VER) && _MSC_VER <= 1900 + template + struct has_global_insertion_operator : types::false_type { }; + + template + struct has_global_insertion_operator(), declval()), void())> : types::true_type { }; + + template + struct has_insertion_operator { static DOCTEST_CONSTEXPR bool value = has_global_insertion_operator::value; }; + + template + struct insert_hack; + + template + struct insert_hack { + static void insert(std::ostream& os, const T& t) { ::operator<<(os, t); } + }; + + template + struct insert_hack { + static void insert(std::ostream& os, const T& t) { operator<<(os, t); } + }; + + template + using insert_hack_t = insert_hack::value>; +#else + template + struct has_insertion_operator : types::false_type { }; +#endif + + template + struct has_insertion_operator(), declval()), void())> : types::true_type { }; + + template + struct should_stringify_as_underlying_type { + static DOCTEST_CONSTEXPR bool value = detail::types::is_enum::value && !doctest::detail::has_insertion_operator::value; + }; + + DOCTEST_INTERFACE std::ostream* tlssPush(); + DOCTEST_INTERFACE String tlssPop(); + + template + struct StringMakerBase { + template + static String convert(const DOCTEST_REF_WRAP(T)) { +#ifdef DOCTEST_CONFIG_REQUIRE_STRINGIFICATION_FOR_ALL_USED_TYPES + static_assert(deferred_false::value, "No stringification detected for type T. See string conversion manual"); +#endif + return "{?}"; + } + }; + + template + struct filldata; + + template + void filloss(std::ostream* stream, const T& in) { + filldata::fill(stream, in); + } + + template + void filloss(std::ostream* stream, const T (&in)[N]) { // NOLINT(*-avoid-c-arrays) + // T[N], T(&)[N], T(&&)[N] have same behaviour. + // Hence remove reference. + filloss::type>(stream, in); + } + + template + String toStream(const T& in) { + std::ostream* stream = tlssPush(); + filloss(stream, in); + return tlssPop(); + } + + template <> + struct StringMakerBase { + template + static String convert(const DOCTEST_REF_WRAP(T) in) { + return toStream(in); + } + }; +} // namespace detail + +template +struct StringMaker : public detail::StringMakerBase< + detail::has_insertion_operator::value || detail::types::is_pointer::value || detail::types::is_array::value> +{}; + +#ifndef DOCTEST_STRINGIFY +#ifdef DOCTEST_CONFIG_DOUBLE_STRINGIFY +#define DOCTEST_STRINGIFY(...) toString(toString(__VA_ARGS__)) +#else +#define DOCTEST_STRINGIFY(...) toString(__VA_ARGS__) +#endif +#endif + +template +String toString() { +#if DOCTEST_CLANG == 0 && DOCTEST_GCC == 0 && DOCTEST_ICC == 0 + String ret = __FUNCSIG__; // class doctest::String __cdecl doctest::toString(void) + String::size_type beginPos = ret.find('<'); + return ret.substr(beginPos + 1, ret.size() - beginPos - static_cast(sizeof(">(void)"))); +#else + String ret = __PRETTY_FUNCTION__; // doctest::String toString() [with T = TYPE] + String::size_type begin = ret.find('=') + 2; + return ret.substr(begin, ret.size() - begin - 1); +#endif +} + +template ::value, bool>::type = true> +String toString(const DOCTEST_REF_WRAP(T) value) { + return StringMaker::convert(value); +} + +#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +DOCTEST_INTERFACE String toString(const char* in); +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + +#if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0) +// see this issue on why this is needed: https://github.com/doctest/doctest/issues/183 +DOCTEST_INTERFACE String toString(const std::string& in); +#endif // VS 2019 + +DOCTEST_INTERFACE String toString(String in); + +DOCTEST_INTERFACE String toString(std::nullptr_t); + +DOCTEST_INTERFACE String toString(bool in); + +DOCTEST_INTERFACE String toString(float in); +DOCTEST_INTERFACE String toString(double in); +DOCTEST_INTERFACE String toString(double long in); + +DOCTEST_INTERFACE String toString(char in); +DOCTEST_INTERFACE String toString(char signed in); +DOCTEST_INTERFACE String toString(char unsigned in); +DOCTEST_INTERFACE String toString(short in); +DOCTEST_INTERFACE String toString(short unsigned in); +DOCTEST_INTERFACE String toString(signed in); +DOCTEST_INTERFACE String toString(unsigned in); +DOCTEST_INTERFACE String toString(long in); +DOCTEST_INTERFACE String toString(long unsigned in); +DOCTEST_INTERFACE String toString(long long in); +DOCTEST_INTERFACE String toString(long long unsigned in); + +template ::value, bool>::type = true> +String toString(const DOCTEST_REF_WRAP(T) value) { + using UT = typename detail::types::underlying_type::type; + return (DOCTEST_STRINGIFY(static_cast(value))); +} + +namespace detail { + template + struct filldata + { + static void fill(std::ostream* stream, const T& in) { +#if defined(_MSC_VER) && _MSC_VER <= 1900 + insert_hack_t::insert(*stream, in); +#else + operator<<(*stream, in); +#endif + } + }; + +DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4866) +// NOLINTBEGIN(*-avoid-c-arrays) + template + struct filldata { + static void fill(std::ostream* stream, const T(&in)[N]) { + *stream << "["; + for (size_t i = 0; i < N; i++) { + if (i != 0) { *stream << ", "; } + *stream << (DOCTEST_STRINGIFY(in[i])); + } + *stream << "]"; + } + }; +// NOLINTEND(*-avoid-c-arrays) +DOCTEST_MSVC_SUPPRESS_WARNING_POP + + // Specialized since we don't want the terminating null byte! +// NOLINTBEGIN(*-avoid-c-arrays) + template + struct filldata { + static void fill(std::ostream* stream, const char (&in)[N]) { + *stream << String(in, in[N - 1] ? N : N - 1); + } // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) + }; +// NOLINTEND(*-avoid-c-arrays) + + template <> + struct filldata { + static void fill(std::ostream* stream, const void* in); + }; + + template + struct filldata { +DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4180) + static void fill(std::ostream* stream, const T* in) { +DOCTEST_MSVC_SUPPRESS_WARNING_POP +DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wmicrosoft-cast") + filldata::fill(stream, +#if DOCTEST_GCC == 0 || DOCTEST_GCC >= DOCTEST_COMPILER(4, 9, 0) + reinterpret_cast(in) +#else + *reinterpret_cast(&in) +#endif + ); +DOCTEST_CLANG_SUPPRESS_WARNING_POP + } + }; +} + +struct DOCTEST_INTERFACE Approx +{ + Approx(double value); + + Approx operator()(double value) const; + +#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + template + explicit Approx(const T& value, + typename detail::types::enable_if::value>::type* = + static_cast(nullptr)) { + *this = static_cast(value); + } +#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + + Approx& epsilon(double newEpsilon); + +#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + template + typename std::enable_if::value, Approx&>::type epsilon( + const T& newEpsilon) { + m_epsilon = static_cast(newEpsilon); + return *this; + } +#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + + Approx& scale(double newScale); + +#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + template + typename std::enable_if::value, Approx&>::type scale( + const T& newScale) { + m_scale = static_cast(newScale); + return *this; + } +#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + + // clang-format off + DOCTEST_INTERFACE friend bool operator==(double lhs, const Approx & rhs); + DOCTEST_INTERFACE friend bool operator==(const Approx & lhs, double rhs); + DOCTEST_INTERFACE friend bool operator!=(double lhs, const Approx & rhs); + DOCTEST_INTERFACE friend bool operator!=(const Approx & lhs, double rhs); + DOCTEST_INTERFACE friend bool operator<=(double lhs, const Approx & rhs); + DOCTEST_INTERFACE friend bool operator<=(const Approx & lhs, double rhs); + DOCTEST_INTERFACE friend bool operator>=(double lhs, const Approx & rhs); + DOCTEST_INTERFACE friend bool operator>=(const Approx & lhs, double rhs); + DOCTEST_INTERFACE friend bool operator< (double lhs, const Approx & rhs); + DOCTEST_INTERFACE friend bool operator< (const Approx & lhs, double rhs); + DOCTEST_INTERFACE friend bool operator> (double lhs, const Approx & rhs); + DOCTEST_INTERFACE friend bool operator> (const Approx & lhs, double rhs); + +#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS +#define DOCTEST_APPROX_PREFIX \ + template friend typename std::enable_if::value, bool>::type + + DOCTEST_APPROX_PREFIX operator==(const T& lhs, const Approx& rhs) { return operator==(static_cast(lhs), rhs); } + DOCTEST_APPROX_PREFIX operator==(const Approx& lhs, const T& rhs) { return operator==(rhs, lhs); } + DOCTEST_APPROX_PREFIX operator!=(const T& lhs, const Approx& rhs) { return !operator==(lhs, rhs); } + DOCTEST_APPROX_PREFIX operator!=(const Approx& lhs, const T& rhs) { return !operator==(rhs, lhs); } + DOCTEST_APPROX_PREFIX operator<=(const T& lhs, const Approx& rhs) { return static_cast(lhs) < rhs.m_value || lhs == rhs; } + DOCTEST_APPROX_PREFIX operator<=(const Approx& lhs, const T& rhs) { return lhs.m_value < static_cast(rhs) || lhs == rhs; } + DOCTEST_APPROX_PREFIX operator>=(const T& lhs, const Approx& rhs) { return static_cast(lhs) > rhs.m_value || lhs == rhs; } + DOCTEST_APPROX_PREFIX operator>=(const Approx& lhs, const T& rhs) { return lhs.m_value > static_cast(rhs) || lhs == rhs; } + DOCTEST_APPROX_PREFIX operator< (const T& lhs, const Approx& rhs) { return static_cast(lhs) < rhs.m_value && lhs != rhs; } + DOCTEST_APPROX_PREFIX operator< (const Approx& lhs, const T& rhs) { return lhs.m_value < static_cast(rhs) && lhs != rhs; } + DOCTEST_APPROX_PREFIX operator> (const T& lhs, const Approx& rhs) { return static_cast(lhs) > rhs.m_value && lhs != rhs; } + DOCTEST_APPROX_PREFIX operator> (const Approx& lhs, const T& rhs) { return lhs.m_value > static_cast(rhs) && lhs != rhs; } +#undef DOCTEST_APPROX_PREFIX +#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + + // clang-format on + + double m_epsilon; + double m_scale; + double m_value; +}; + +DOCTEST_INTERFACE String toString(const Approx& in); + +DOCTEST_INTERFACE const ContextOptions* getContextOptions(); + +template +struct DOCTEST_INTERFACE_DECL IsNaN +{ + F value; bool flipped; + IsNaN(F f, bool flip = false) : value(f), flipped(flip) { } + IsNaN operator!() const { return { value, !flipped }; } + operator bool() const; +}; +#ifndef __MINGW32__ +extern template struct DOCTEST_INTERFACE_DECL IsNaN; +extern template struct DOCTEST_INTERFACE_DECL IsNaN; +extern template struct DOCTEST_INTERFACE_DECL IsNaN; +#endif +DOCTEST_INTERFACE String toString(IsNaN in); +DOCTEST_INTERFACE String toString(IsNaN in); +DOCTEST_INTERFACE String toString(IsNaN in); + +#ifndef DOCTEST_CONFIG_DISABLE + +namespace detail { + // clang-format off +#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + template struct decay_array { using type = T; }; + template struct decay_array { using type = T*; }; + template struct decay_array { using type = T*; }; + + template struct not_char_pointer { static DOCTEST_CONSTEXPR int value = 1; }; + template<> struct not_char_pointer { static DOCTEST_CONSTEXPR int value = 0; }; + template<> struct not_char_pointer { static DOCTEST_CONSTEXPR int value = 0; }; + + template struct can_use_op : public not_char_pointer::type> {}; +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + // clang-format on + + struct DOCTEST_INTERFACE TestFailureException + { + }; + + DOCTEST_INTERFACE bool checkIfShouldThrow(assertType::Enum at); + +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + DOCTEST_NORETURN +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + DOCTEST_INTERFACE void throwException(); + + struct DOCTEST_INTERFACE Subcase + { + SubcaseSignature m_signature; + bool m_entered = false; + + Subcase(const String& name, const char* file, int line); + Subcase(const Subcase&) = delete; + Subcase(Subcase&&) = delete; + Subcase& operator=(const Subcase&) = delete; + Subcase& operator=(Subcase&&) = delete; + ~Subcase(); + + operator bool() const; + + private: + bool checkFilters(); + }; + + template + String stringifyBinaryExpr(const DOCTEST_REF_WRAP(L) lhs, const char* op, + const DOCTEST_REF_WRAP(R) rhs) { + return (DOCTEST_STRINGIFY(lhs)) + op + (DOCTEST_STRINGIFY(rhs)); + } + +#if DOCTEST_CLANG && DOCTEST_CLANG < DOCTEST_COMPILER(3, 6, 0) +DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wunused-comparison") +#endif + +// This will check if there is any way it could find a operator like member or friend and uses it. +// If not it doesn't find the operator or if the operator at global scope is defined after +// this template, the template won't be instantiated due to SFINAE. Once the template is not +// instantiated it can look for global operator using normal conversions. +#ifdef __NVCC__ +#define SFINAE_OP(ret,op) ret +#else +#define SFINAE_OP(ret,op) decltype((void)(doctest::detail::declval() op doctest::detail::declval()),ret{}) +#endif + +#define DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(op, op_str, op_macro) \ + template \ + DOCTEST_NOINLINE SFINAE_OP(Result,op) operator op(R&& rhs) { \ + bool res = op_macro(doctest::detail::forward(lhs), doctest::detail::forward(rhs)); \ + if(m_at & assertType::is_false) \ + res = !res; \ + if(!res || doctest::getContextOptions()->success) \ + return Result(res, stringifyBinaryExpr(lhs, op_str, rhs)); \ + return Result(res); \ + } + + // more checks could be added - like in Catch: + // https://github.com/catchorg/Catch2/pull/1480/files + // https://github.com/catchorg/Catch2/pull/1481/files +#define DOCTEST_FORBIT_EXPRESSION(rt, op) \ + template \ + rt& operator op(const R&) { \ + static_assert(deferred_false::value, \ + "Expression Too Complex Please Rewrite As Binary Comparison!"); \ + return *this; \ + } + + struct DOCTEST_INTERFACE Result // NOLINT(*-member-init) + { + bool m_passed; + String m_decomp; + + Result() = default; // TODO: Why do we need this? (To remove NOLINT) + Result(bool passed, const String& decomposition = String()); + + // forbidding some expressions based on this table: https://en.cppreference.com/w/cpp/language/operator_precedence + DOCTEST_FORBIT_EXPRESSION(Result, &) + DOCTEST_FORBIT_EXPRESSION(Result, ^) + DOCTEST_FORBIT_EXPRESSION(Result, |) + DOCTEST_FORBIT_EXPRESSION(Result, &&) + DOCTEST_FORBIT_EXPRESSION(Result, ||) + DOCTEST_FORBIT_EXPRESSION(Result, ==) + DOCTEST_FORBIT_EXPRESSION(Result, !=) + DOCTEST_FORBIT_EXPRESSION(Result, <) + DOCTEST_FORBIT_EXPRESSION(Result, >) + DOCTEST_FORBIT_EXPRESSION(Result, <=) + DOCTEST_FORBIT_EXPRESSION(Result, >=) + DOCTEST_FORBIT_EXPRESSION(Result, =) + DOCTEST_FORBIT_EXPRESSION(Result, +=) + DOCTEST_FORBIT_EXPRESSION(Result, -=) + DOCTEST_FORBIT_EXPRESSION(Result, *=) + DOCTEST_FORBIT_EXPRESSION(Result, /=) + DOCTEST_FORBIT_EXPRESSION(Result, %=) + DOCTEST_FORBIT_EXPRESSION(Result, <<=) + DOCTEST_FORBIT_EXPRESSION(Result, >>=) + DOCTEST_FORBIT_EXPRESSION(Result, &=) + DOCTEST_FORBIT_EXPRESSION(Result, ^=) + DOCTEST_FORBIT_EXPRESSION(Result, |=) + }; + +#ifndef DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION + + DOCTEST_CLANG_SUPPRESS_WARNING_PUSH + DOCTEST_CLANG_SUPPRESS_WARNING("-Wsign-conversion") + DOCTEST_CLANG_SUPPRESS_WARNING("-Wsign-compare") + //DOCTEST_CLANG_SUPPRESS_WARNING("-Wdouble-promotion") + //DOCTEST_CLANG_SUPPRESS_WARNING("-Wconversion") + //DOCTEST_CLANG_SUPPRESS_WARNING("-Wfloat-equal") + + DOCTEST_GCC_SUPPRESS_WARNING_PUSH + DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-conversion") + DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-compare") + //DOCTEST_GCC_SUPPRESS_WARNING("-Wdouble-promotion") + //DOCTEST_GCC_SUPPRESS_WARNING("-Wconversion") + //DOCTEST_GCC_SUPPRESS_WARNING("-Wfloat-equal") + + DOCTEST_MSVC_SUPPRESS_WARNING_PUSH + // https://stackoverflow.com/questions/39479163 what's the difference between 4018 and 4389 + DOCTEST_MSVC_SUPPRESS_WARNING(4388) // signed/unsigned mismatch + DOCTEST_MSVC_SUPPRESS_WARNING(4389) // 'operator' : signed/unsigned mismatch + DOCTEST_MSVC_SUPPRESS_WARNING(4018) // 'expression' : signed/unsigned mismatch + //DOCTEST_MSVC_SUPPRESS_WARNING(4805) // 'operation' : unsafe mix of type 'type' and type 'type' in operation + +#endif // DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION + + // clang-format off +#ifndef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +#define DOCTEST_COMPARISON_RETURN_TYPE bool +#else // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +#define DOCTEST_COMPARISON_RETURN_TYPE typename types::enable_if::value || can_use_op::value, bool>::type + inline bool eq(const char* lhs, const char* rhs) { return String(lhs) == String(rhs); } + inline bool ne(const char* lhs, const char* rhs) { return String(lhs) != String(rhs); } + inline bool lt(const char* lhs, const char* rhs) { return String(lhs) < String(rhs); } + inline bool gt(const char* lhs, const char* rhs) { return String(lhs) > String(rhs); } + inline bool le(const char* lhs, const char* rhs) { return String(lhs) <= String(rhs); } + inline bool ge(const char* lhs, const char* rhs) { return String(lhs) >= String(rhs); } +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + // clang-format on + +#define DOCTEST_RELATIONAL_OP(name, op) \ + template \ + DOCTEST_COMPARISON_RETURN_TYPE name(const DOCTEST_REF_WRAP(L) lhs, \ + const DOCTEST_REF_WRAP(R) rhs) { \ + return lhs op rhs; \ + } + + DOCTEST_RELATIONAL_OP(eq, ==) + DOCTEST_RELATIONAL_OP(ne, !=) + DOCTEST_RELATIONAL_OP(lt, <) + DOCTEST_RELATIONAL_OP(gt, >) + DOCTEST_RELATIONAL_OP(le, <=) + DOCTEST_RELATIONAL_OP(ge, >=) + +#ifndef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +#define DOCTEST_CMP_EQ(l, r) l == r +#define DOCTEST_CMP_NE(l, r) l != r +#define DOCTEST_CMP_GT(l, r) l > r +#define DOCTEST_CMP_LT(l, r) l < r +#define DOCTEST_CMP_GE(l, r) l >= r +#define DOCTEST_CMP_LE(l, r) l <= r +#else // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +#define DOCTEST_CMP_EQ(l, r) eq(l, r) +#define DOCTEST_CMP_NE(l, r) ne(l, r) +#define DOCTEST_CMP_GT(l, r) gt(l, r) +#define DOCTEST_CMP_LT(l, r) lt(l, r) +#define DOCTEST_CMP_GE(l, r) ge(l, r) +#define DOCTEST_CMP_LE(l, r) le(l, r) +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + + template + // cppcheck-suppress copyCtorAndEqOperator + struct Expression_lhs + { + L lhs; + assertType::Enum m_at; + + explicit Expression_lhs(L&& in, assertType::Enum at) + : lhs(static_cast(in)) + , m_at(at) {} + + DOCTEST_NOINLINE operator Result() { +// this is needed only for MSVC 2015 +DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4800) // 'int': forcing value to bool + bool res = static_cast(lhs); +DOCTEST_MSVC_SUPPRESS_WARNING_POP + if(m_at & assertType::is_false) { //!OCLINT bitwise operator in conditional + res = !res; + } + + if(!res || getContextOptions()->success) { + return { res, (DOCTEST_STRINGIFY(lhs)) }; + } + return { res }; + } + + /* This is required for user-defined conversions from Expression_lhs to L */ + operator L() const { return lhs; } + + // clang-format off + DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(==, " == ", DOCTEST_CMP_EQ) //!OCLINT bitwise operator in conditional + DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(!=, " != ", DOCTEST_CMP_NE) //!OCLINT bitwise operator in conditional + DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(>, " > ", DOCTEST_CMP_GT) //!OCLINT bitwise operator in conditional + DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(<, " < ", DOCTEST_CMP_LT) //!OCLINT bitwise operator in conditional + DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(>=, " >= ", DOCTEST_CMP_GE) //!OCLINT bitwise operator in conditional + DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(<=, " <= ", DOCTEST_CMP_LE) //!OCLINT bitwise operator in conditional + // clang-format on + + // forbidding some expressions based on this table: https://en.cppreference.com/w/cpp/language/operator_precedence + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, &) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, ^) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, |) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, &&) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, ||) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, =) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, +=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, -=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, *=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, /=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, %=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, <<=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, >>=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, &=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, ^=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, |=) + // these 2 are unfortunate because they should be allowed - they have higher precedence over the comparisons, but the + // ExpressionDecomposer class uses the left shift operator to capture the left operand of the binary expression... + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, <<) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, >>) + }; + +#ifndef DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION + + DOCTEST_CLANG_SUPPRESS_WARNING_POP + DOCTEST_MSVC_SUPPRESS_WARNING_POP + DOCTEST_GCC_SUPPRESS_WARNING_POP + +#endif // DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION + +#if DOCTEST_CLANG && DOCTEST_CLANG < DOCTEST_COMPILER(3, 6, 0) +DOCTEST_CLANG_SUPPRESS_WARNING_POP +#endif + + struct DOCTEST_INTERFACE ExpressionDecomposer + { + assertType::Enum m_at; + + ExpressionDecomposer(assertType::Enum at); + + // The right operator for capturing expressions is "<=" instead of "<<" (based on the operator precedence table) + // but then there will be warnings from GCC about "-Wparentheses" and since "_Pragma()" is problematic this will stay for now... + // https://github.com/catchorg/Catch2/issues/870 + // https://github.com/catchorg/Catch2/issues/565 + template + Expression_lhs operator<<(const L&& operand) { //bitfields bind to universal ref but not const rvalue ref + return Expression_lhs(static_cast(operand), m_at); + } + + template ::value,void >::type* = nullptr> + Expression_lhs operator<<(const L &operand) { + return Expression_lhs(operand, m_at); + } + }; + + struct DOCTEST_INTERFACE TestSuite + { + const char* m_test_suite = nullptr; + const char* m_description = nullptr; + bool m_skip = false; + bool m_no_breaks = false; + bool m_no_output = false; + bool m_may_fail = false; + bool m_should_fail = false; + int m_expected_failures = 0; + double m_timeout = 0; + + TestSuite& operator*(const char* in); + + template + TestSuite& operator*(const T& in) { + in.fill(*this); + return *this; + } + }; + + using funcType = void (*)(); + + struct DOCTEST_INTERFACE TestCase : public TestCaseData + { + funcType m_test; // a function pointer to the test case + + String m_type; // for templated test cases - gets appended to the real name + int m_template_id; // an ID used to distinguish between the different versions of a templated test case + String m_full_name; // contains the name (only for templated test cases!) + the template type + + TestCase(funcType test, const char* file, unsigned line, const TestSuite& test_suite, + const String& type = String(), int template_id = -1); + + TestCase(const TestCase& other); + TestCase(TestCase&&) = delete; + + DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(26434) // hides a non-virtual function + TestCase& operator=(const TestCase& other); + DOCTEST_MSVC_SUPPRESS_WARNING_POP + + TestCase& operator=(TestCase&&) = delete; + + TestCase& operator*(const char* in); + + template + TestCase& operator*(const T& in) { + in.fill(*this); + return *this; + } + + bool operator<(const TestCase& other) const; + + ~TestCase() = default; + }; + + // forward declarations of functions used by the macros + DOCTEST_INTERFACE int regTest(const TestCase& tc); + DOCTEST_INTERFACE int setTestSuite(const TestSuite& ts); + DOCTEST_INTERFACE bool isDebuggerActive(); + + template + int instantiationHelper(const T&) { return 0; } + + namespace binaryAssertComparison { + enum Enum + { + eq = 0, + ne, + gt, + lt, + ge, + le + }; + } // namespace binaryAssertComparison + + // clang-format off + template struct RelationalComparator { bool operator()(const DOCTEST_REF_WRAP(L), const DOCTEST_REF_WRAP(R) ) const { return false; } }; + +#define DOCTEST_BINARY_RELATIONAL_OP(n, op) \ + template struct RelationalComparator { bool operator()(const DOCTEST_REF_WRAP(L) lhs, const DOCTEST_REF_WRAP(R) rhs) const { return op(lhs, rhs); } }; + // clang-format on + + DOCTEST_BINARY_RELATIONAL_OP(0, doctest::detail::eq) + DOCTEST_BINARY_RELATIONAL_OP(1, doctest::detail::ne) + DOCTEST_BINARY_RELATIONAL_OP(2, doctest::detail::gt) + DOCTEST_BINARY_RELATIONAL_OP(3, doctest::detail::lt) + DOCTEST_BINARY_RELATIONAL_OP(4, doctest::detail::ge) + DOCTEST_BINARY_RELATIONAL_OP(5, doctest::detail::le) + + struct DOCTEST_INTERFACE ResultBuilder : public AssertData + { + ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr, + const char* exception_type = "", const String& exception_string = ""); + + ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr, + const char* exception_type, const Contains& exception_string); + + void setResult(const Result& res); + + template + DOCTEST_NOINLINE bool binary_assert(const DOCTEST_REF_WRAP(L) lhs, + const DOCTEST_REF_WRAP(R) rhs) { + m_failed = !RelationalComparator()(lhs, rhs); + if (m_failed || getContextOptions()->success) { + m_decomp = stringifyBinaryExpr(lhs, ", ", rhs); + } + return !m_failed; + } + + template + DOCTEST_NOINLINE bool unary_assert(const DOCTEST_REF_WRAP(L) val) { + m_failed = !val; + + if (m_at & assertType::is_false) { //!OCLINT bitwise operator in conditional + m_failed = !m_failed; + } + + if (m_failed || getContextOptions()->success) { + m_decomp = (DOCTEST_STRINGIFY(val)); + } + + return !m_failed; + } + + void translateException(); + + bool log(); + void react() const; + }; + + namespace assertAction { + enum Enum + { + nothing = 0, + dbgbreak = 1, + shouldthrow = 2 + }; + } // namespace assertAction + + DOCTEST_INTERFACE void failed_out_of_a_testing_context(const AssertData& ad); + + DOCTEST_INTERFACE bool decomp_assert(assertType::Enum at, const char* file, int line, + const char* expr, const Result& result); + +#define DOCTEST_ASSERT_OUT_OF_TESTS(decomp) \ + do { \ + if(!is_running_in_test) { \ + if(failed) { \ + ResultBuilder rb(at, file, line, expr); \ + rb.m_failed = failed; \ + rb.m_decomp = decomp; \ + failed_out_of_a_testing_context(rb); \ + if(isDebuggerActive() && !getContextOptions()->no_breaks) \ + DOCTEST_BREAK_INTO_DEBUGGER(); \ + if(checkIfShouldThrow(at)) \ + throwException(); \ + } \ + return !failed; \ + } \ + } while(false) + +#define DOCTEST_ASSERT_IN_TESTS(decomp) \ + ResultBuilder rb(at, file, line, expr); \ + rb.m_failed = failed; \ + if(rb.m_failed || getContextOptions()->success) \ + rb.m_decomp = decomp; \ + if(rb.log()) \ + DOCTEST_BREAK_INTO_DEBUGGER(); \ + if(rb.m_failed && checkIfShouldThrow(at)) \ + throwException() + + template + DOCTEST_NOINLINE bool binary_assert(assertType::Enum at, const char* file, int line, + const char* expr, const DOCTEST_REF_WRAP(L) lhs, + const DOCTEST_REF_WRAP(R) rhs) { + bool failed = !RelationalComparator()(lhs, rhs); + + // ################################################################################### + // IF THE DEBUGGER BREAKS HERE - GO 1 LEVEL UP IN THE CALLSTACK FOR THE FAILING ASSERT + // THIS IS THE EFFECT OF HAVING 'DOCTEST_CONFIG_SUPER_FAST_ASSERTS' DEFINED + // ################################################################################### + DOCTEST_ASSERT_OUT_OF_TESTS(stringifyBinaryExpr(lhs, ", ", rhs)); + DOCTEST_ASSERT_IN_TESTS(stringifyBinaryExpr(lhs, ", ", rhs)); + return !failed; + } + + template + DOCTEST_NOINLINE bool unary_assert(assertType::Enum at, const char* file, int line, + const char* expr, const DOCTEST_REF_WRAP(L) val) { + bool failed = !val; + + if(at & assertType::is_false) //!OCLINT bitwise operator in conditional + failed = !failed; + + // ################################################################################### + // IF THE DEBUGGER BREAKS HERE - GO 1 LEVEL UP IN THE CALLSTACK FOR THE FAILING ASSERT + // THIS IS THE EFFECT OF HAVING 'DOCTEST_CONFIG_SUPER_FAST_ASSERTS' DEFINED + // ################################################################################### + DOCTEST_ASSERT_OUT_OF_TESTS((DOCTEST_STRINGIFY(val))); + DOCTEST_ASSERT_IN_TESTS((DOCTEST_STRINGIFY(val))); + return !failed; + } + + struct DOCTEST_INTERFACE IExceptionTranslator + { + DOCTEST_DECLARE_INTERFACE(IExceptionTranslator) + virtual bool translate(String&) const = 0; + }; + + template + class ExceptionTranslator : public IExceptionTranslator //!OCLINT destructor of virtual class + { + public: + explicit ExceptionTranslator(String (*translateFunction)(T)) + : m_translateFunction(translateFunction) {} + + bool translate(String& res) const override { +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + try { + throw; // lgtm [cpp/rethrow-no-exception] + // cppcheck-suppress catchExceptionByValue + } catch(const T& ex) { + res = m_translateFunction(ex); //!OCLINT parameter reassignment + return true; + } catch(...) {} //!OCLINT - empty catch statement +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + static_cast(res); // to silence -Wunused-parameter + return false; + } + + private: + String (*m_translateFunction)(T); + }; + + DOCTEST_INTERFACE void registerExceptionTranslatorImpl(const IExceptionTranslator* et); + + // ContextScope base class used to allow implementing methods of ContextScope + // that don't depend on the template parameter in doctest.cpp. + struct DOCTEST_INTERFACE ContextScopeBase : public IContextScope { + ContextScopeBase(const ContextScopeBase&) = delete; + + ContextScopeBase& operator=(const ContextScopeBase&) = delete; + ContextScopeBase& operator=(ContextScopeBase&&) = delete; + + ~ContextScopeBase() override = default; + + protected: + ContextScopeBase(); + ContextScopeBase(ContextScopeBase&& other) noexcept; + + void destroy(); + bool need_to_destroy{true}; + }; + + template class ContextScope : public ContextScopeBase + { + L lambda_; + + public: + explicit ContextScope(const L &lambda) : lambda_(lambda) {} + explicit ContextScope(L&& lambda) : lambda_(static_cast(lambda)) { } + + ContextScope(const ContextScope&) = delete; + ContextScope(ContextScope&&) noexcept = default; + + ContextScope& operator=(const ContextScope&) = delete; + ContextScope& operator=(ContextScope&&) = delete; + + void stringify(std::ostream* s) const override { lambda_(s); } + + ~ContextScope() override { + if (need_to_destroy) { + destroy(); + } + } + }; + + struct DOCTEST_INTERFACE MessageBuilder : public MessageData + { + std::ostream* m_stream; + bool logged = false; + + MessageBuilder(const char* file, int line, assertType::Enum severity); + + MessageBuilder(const MessageBuilder&) = delete; + MessageBuilder(MessageBuilder&&) = delete; + + MessageBuilder& operator=(const MessageBuilder&) = delete; + MessageBuilder& operator=(MessageBuilder&&) = delete; + + ~MessageBuilder(); + + // the preferred way of chaining parameters for stringification +DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4866) + template + MessageBuilder& operator,(const T& in) { + *m_stream << (DOCTEST_STRINGIFY(in)); + return *this; + } +DOCTEST_MSVC_SUPPRESS_WARNING_POP + + // kept here just for backwards-compatibility - the comma operator should be preferred now + template + MessageBuilder& operator<<(const T& in) { return this->operator,(in); } + + // the `,` operator has the lowest operator precedence - if `<<` is used by the user then + // the `,` operator will be called last which is not what we want and thus the `*` operator + // is used first (has higher operator precedence compared to `<<`) so that we guarantee that + // an operator of the MessageBuilder class is called first before the rest of the parameters + template + MessageBuilder& operator*(const T& in) { return this->operator,(in); } + + bool log(); + void react(); + }; + + template + ContextScope MakeContextScope(const L &lambda) { + return ContextScope(lambda); + } +} // namespace detail + +#define DOCTEST_DEFINE_DECORATOR(name, type, def) \ + struct name \ + { \ + type data; \ + name(type in = def) \ + : data(in) {} \ + void fill(detail::TestCase& state) const { state.DOCTEST_CAT(m_, name) = data; } \ + void fill(detail::TestSuite& state) const { state.DOCTEST_CAT(m_, name) = data; } \ + } + +DOCTEST_DEFINE_DECORATOR(test_suite, const char*, ""); +DOCTEST_DEFINE_DECORATOR(description, const char*, ""); +DOCTEST_DEFINE_DECORATOR(skip, bool, true); +DOCTEST_DEFINE_DECORATOR(no_breaks, bool, true); +DOCTEST_DEFINE_DECORATOR(no_output, bool, true); +DOCTEST_DEFINE_DECORATOR(timeout, double, 0); +DOCTEST_DEFINE_DECORATOR(may_fail, bool, true); +DOCTEST_DEFINE_DECORATOR(should_fail, bool, true); +DOCTEST_DEFINE_DECORATOR(expected_failures, int, 0); + +template +int registerExceptionTranslator(String (*translateFunction)(T)) { + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wexit-time-destructors") + static detail::ExceptionTranslator exceptionTranslator(translateFunction); + DOCTEST_CLANG_SUPPRESS_WARNING_POP + detail::registerExceptionTranslatorImpl(&exceptionTranslator); + return 0; +} + +} // namespace doctest + +// in a separate namespace outside of doctest because the DOCTEST_TEST_SUITE macro +// introduces an anonymous namespace in which getCurrentTestSuite gets overridden +namespace doctest_detail_test_suite_ns { +DOCTEST_INTERFACE doctest::detail::TestSuite& getCurrentTestSuite(); +} // namespace doctest_detail_test_suite_ns + +namespace doctest { +#else // DOCTEST_CONFIG_DISABLE +template +int registerExceptionTranslator(String (*)(T)) { + return 0; +} +#endif // DOCTEST_CONFIG_DISABLE + +namespace detail { + using assert_handler = void (*)(const AssertData&); + struct ContextState; +} // namespace detail + +class DOCTEST_INTERFACE Context +{ + detail::ContextState* p; + + void parseArgs(int argc, const char* const* argv, bool withDefaults = false); + +public: + explicit Context(int argc = 0, const char* const* argv = nullptr); + + Context(const Context&) = delete; + Context(Context&&) = delete; + + Context& operator=(const Context&) = delete; + Context& operator=(Context&&) = delete; + + ~Context(); // NOLINT(performance-trivially-destructible) + + void applyCommandLine(int argc, const char* const* argv); + + void addFilter(const char* filter, const char* value); + void clearFilters(); + void setOption(const char* option, bool value); + void setOption(const char* option, int value); + void setOption(const char* option, const char* value); + + bool shouldExit(); + + void setAsDefaultForAssertsOutOfTestCases(); + + void setAssertHandler(detail::assert_handler ah); + + void setCout(std::ostream* out); + + int run(); +}; + +namespace TestCaseFailureReason { + enum Enum + { + None = 0, + AssertFailure = 1, // an assertion has failed in the test case + Exception = 2, // test case threw an exception + Crash = 4, // a crash... + TooManyFailedAsserts = 8, // the abort-after option + Timeout = 16, // see the timeout decorator + ShouldHaveFailedButDidnt = 32, // see the should_fail decorator + ShouldHaveFailedAndDid = 64, // see the should_fail decorator + DidntFailExactlyNumTimes = 128, // see the expected_failures decorator + FailedExactlyNumTimes = 256, // see the expected_failures decorator + CouldHaveFailedAndDid = 512 // see the may_fail decorator + }; +} // namespace TestCaseFailureReason + +struct DOCTEST_INTERFACE CurrentTestCaseStats +{ + int numAssertsCurrentTest; + int numAssertsFailedCurrentTest; + double seconds; + int failure_flags; // use TestCaseFailureReason::Enum + bool testCaseSuccess; +}; + +struct DOCTEST_INTERFACE TestCaseException +{ + String error_string; + bool is_crash; +}; + +struct DOCTEST_INTERFACE TestRunStats +{ + unsigned numTestCases; + unsigned numTestCasesPassingFilters; + unsigned numTestSuitesPassingFilters; + unsigned numTestCasesFailed; + int numAsserts; + int numAssertsFailed; +}; + +struct QueryData +{ + const TestRunStats* run_stats = nullptr; + const TestCaseData** data = nullptr; + unsigned num_data = 0; +}; + +struct DOCTEST_INTERFACE IReporter +{ + // The constructor has to accept "const ContextOptions&" as a single argument + // which has most of the options for the run + a pointer to the stdout stream + // Reporter(const ContextOptions& in) + + // called when a query should be reported (listing test cases, printing the version, etc.) + virtual void report_query(const QueryData&) = 0; + + // called when the whole test run starts + virtual void test_run_start() = 0; + // called when the whole test run ends (caching a pointer to the input doesn't make sense here) + virtual void test_run_end(const TestRunStats&) = 0; + + // called when a test case is started (safe to cache a pointer to the input) + virtual void test_case_start(const TestCaseData&) = 0; + // called when a test case is reentered because of unfinished subcases (safe to cache a pointer to the input) + virtual void test_case_reenter(const TestCaseData&) = 0; + // called when a test case has ended + virtual void test_case_end(const CurrentTestCaseStats&) = 0; + + // called when an exception is thrown from the test case (or it crashes) + virtual void test_case_exception(const TestCaseException&) = 0; + + // called whenever a subcase is entered (don't cache pointers to the input) + virtual void subcase_start(const SubcaseSignature&) = 0; + // called whenever a subcase is exited (don't cache pointers to the input) + virtual void subcase_end() = 0; + + // called for each assert (don't cache pointers to the input) + virtual void log_assert(const AssertData&) = 0; + // called for each message (don't cache pointers to the input) + virtual void log_message(const MessageData&) = 0; + + // called when a test case is skipped either because it doesn't pass the filters, has a skip decorator + // or isn't in the execution range (between first and last) (safe to cache a pointer to the input) + virtual void test_case_skipped(const TestCaseData&) = 0; + + DOCTEST_DECLARE_INTERFACE(IReporter) + + // can obtain all currently active contexts and stringify them if one wishes to do so + static int get_num_active_contexts(); + static const IContextScope* const* get_active_contexts(); + + // can iterate through contexts which have been stringified automatically in their destructors when an exception has been thrown + static int get_num_stringified_contexts(); + static const String* get_stringified_contexts(); +}; + +namespace detail { + using reporterCreatorFunc = IReporter* (*)(const ContextOptions&); + + DOCTEST_INTERFACE void registerReporterImpl(const char* name, int prio, reporterCreatorFunc c, bool isReporter); + + template + IReporter* reporterCreator(const ContextOptions& o) { + return new Reporter(o); + } +} // namespace detail + +template +int registerReporter(const char* name, int priority, bool isReporter) { + detail::registerReporterImpl(name, priority, detail::reporterCreator, isReporter); + return 0; +} +} // namespace doctest + +#ifdef DOCTEST_CONFIG_ASSERTS_RETURN_VALUES +#define DOCTEST_FUNC_EMPTY [] { return false; }() +#else +#define DOCTEST_FUNC_EMPTY (void)0 +#endif + +// if registering is not disabled +#ifndef DOCTEST_CONFIG_DISABLE + +#ifdef DOCTEST_CONFIG_ASSERTS_RETURN_VALUES +#define DOCTEST_FUNC_SCOPE_BEGIN [&] +#define DOCTEST_FUNC_SCOPE_END () +#define DOCTEST_FUNC_SCOPE_RET(v) return v +#else +#define DOCTEST_FUNC_SCOPE_BEGIN do +#define DOCTEST_FUNC_SCOPE_END while(false) +#define DOCTEST_FUNC_SCOPE_RET(v) (void)0 +#endif + +// common code in asserts - for convenience +#define DOCTEST_ASSERT_LOG_REACT_RETURN(b) \ + if(b.log()) DOCTEST_BREAK_INTO_DEBUGGER(); \ + b.react(); \ + DOCTEST_FUNC_SCOPE_RET(!b.m_failed) + +#ifdef DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS +#define DOCTEST_WRAP_IN_TRY(x) x; +#else // DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS +#define DOCTEST_WRAP_IN_TRY(x) \ + try { \ + x; \ + } catch(...) { DOCTEST_RB.translateException(); } +#endif // DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS + +#ifdef DOCTEST_CONFIG_VOID_CAST_EXPRESSIONS +#define DOCTEST_CAST_TO_VOID(...) \ + DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wuseless-cast") \ + static_cast(__VA_ARGS__); \ + DOCTEST_GCC_SUPPRESS_WARNING_POP +#else // DOCTEST_CONFIG_VOID_CAST_EXPRESSIONS +#define DOCTEST_CAST_TO_VOID(...) __VA_ARGS__; +#endif // DOCTEST_CONFIG_VOID_CAST_EXPRESSIONS + +// registers the test by initializing a dummy var with a function +#define DOCTEST_REGISTER_FUNCTION(global_prefix, f, decorators) \ + global_prefix DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_VAR_), /* NOLINT */ \ + doctest::detail::regTest( \ + doctest::detail::TestCase( \ + f, __FILE__, __LINE__, \ + doctest_detail_test_suite_ns::getCurrentTestSuite()) * \ + decorators)) + +#define DOCTEST_IMPLEMENT_FIXTURE(der, base, func, decorators) \ + namespace { /* NOLINT */ \ + struct der : public base \ + { \ + void f(); \ + }; \ + static DOCTEST_INLINE_NOINLINE void func() { \ + der v; \ + v.f(); \ + } \ + DOCTEST_REGISTER_FUNCTION(DOCTEST_EMPTY, func, decorators) \ + } \ + DOCTEST_INLINE_NOINLINE void der::f() // NOLINT(misc-definitions-in-headers) + +#define DOCTEST_CREATE_AND_REGISTER_FUNCTION(f, decorators) \ + static void f(); \ + DOCTEST_REGISTER_FUNCTION(DOCTEST_EMPTY, f, decorators) \ + static void f() + +#define DOCTEST_CREATE_AND_REGISTER_FUNCTION_IN_CLASS(f, proxy, decorators) \ + static doctest::detail::funcType proxy() { return f; } \ + DOCTEST_REGISTER_FUNCTION(inline, proxy(), decorators) \ + static void f() + +// for registering tests +#define DOCTEST_TEST_CASE(decorators) \ + DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), decorators) + +// for registering tests in classes - requires C++17 for inline variables! +#if DOCTEST_CPLUSPLUS >= 201703L +#define DOCTEST_TEST_CASE_CLASS(decorators) \ + DOCTEST_CREATE_AND_REGISTER_FUNCTION_IN_CLASS(DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), \ + DOCTEST_ANONYMOUS(DOCTEST_ANON_PROXY_), \ + decorators) +#else // DOCTEST_TEST_CASE_CLASS +#define DOCTEST_TEST_CASE_CLASS(...) \ + TEST_CASES_CAN_BE_REGISTERED_IN_CLASSES_ONLY_IN_CPP17_MODE_OR_WITH_VS_2017_OR_NEWER +#endif // DOCTEST_TEST_CASE_CLASS + +// for registering tests with a fixture +#define DOCTEST_TEST_CASE_FIXTURE(c, decorators) \ + DOCTEST_IMPLEMENT_FIXTURE(DOCTEST_ANONYMOUS(DOCTEST_ANON_CLASS_), c, \ + DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), decorators) + +// for converting types to strings without the header and demangling +#define DOCTEST_TYPE_TO_STRING_AS(str, ...) \ + namespace doctest { \ + template <> \ + inline String toString<__VA_ARGS__>() { \ + return str; \ + } \ + } \ + static_assert(true, "") + +#define DOCTEST_TYPE_TO_STRING(...) DOCTEST_TYPE_TO_STRING_AS(#__VA_ARGS__, __VA_ARGS__) + +#define DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, iter, func) \ + template \ + static void func(); \ + namespace { /* NOLINT */ \ + template \ + struct iter; \ + template \ + struct iter> \ + { \ + iter(const char* file, unsigned line, int index) { \ + doctest::detail::regTest(doctest::detail::TestCase(func, file, line, \ + doctest_detail_test_suite_ns::getCurrentTestSuite(), \ + doctest::toString(), \ + int(line) * 1000 + index) \ + * dec); \ + iter>(file, line, index + 1); \ + } \ + }; \ + template <> \ + struct iter> \ + { \ + iter(const char*, unsigned, int) {} \ + }; \ + } \ + template \ + static void func() + +#define DOCTEST_TEST_CASE_TEMPLATE_DEFINE(dec, T, id) \ + DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, DOCTEST_CAT(id, ITERATOR), \ + DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_)) + +#define DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, anon, ...) \ + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_CAT(anon, DUMMY), /* NOLINT(cert-err58-cpp, fuchsia-statically-constructed-objects) */ \ + doctest::detail::instantiationHelper( \ + DOCTEST_CAT(id, ITERATOR)<__VA_ARGS__>(__FILE__, __LINE__, 0))) + +#define DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, ...) \ + DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_), std::tuple<__VA_ARGS__>) \ + static_assert(true, "") + +#define DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, ...) \ + DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_), __VA_ARGS__) \ + static_assert(true, "") + +#define DOCTEST_TEST_CASE_TEMPLATE_IMPL(dec, T, anon, ...) \ + DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, DOCTEST_CAT(anon, ITERATOR), anon); \ + DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(anon, anon, std::tuple<__VA_ARGS__>) \ + template \ + static void anon() + +#define DOCTEST_TEST_CASE_TEMPLATE(dec, T, ...) \ + DOCTEST_TEST_CASE_TEMPLATE_IMPL(dec, T, DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_), __VA_ARGS__) + +// for subcases +#define DOCTEST_SUBCASE(name) \ + if(const doctest::detail::Subcase & DOCTEST_ANONYMOUS(DOCTEST_ANON_SUBCASE_) DOCTEST_UNUSED = \ + doctest::detail::Subcase(name, __FILE__, __LINE__)) + +// for grouping tests in test suites by using code blocks +#define DOCTEST_TEST_SUITE_IMPL(decorators, ns_name) \ + namespace ns_name { namespace doctest_detail_test_suite_ns { \ + static DOCTEST_NOINLINE doctest::detail::TestSuite& getCurrentTestSuite() noexcept { \ + DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4640) \ + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wexit-time-destructors") \ + DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wmissing-field-initializers") \ + static doctest::detail::TestSuite data{}; \ + static bool inited = false; \ + DOCTEST_MSVC_SUPPRESS_WARNING_POP \ + DOCTEST_CLANG_SUPPRESS_WARNING_POP \ + DOCTEST_GCC_SUPPRESS_WARNING_POP \ + if(!inited) { \ + data* decorators; \ + inited = true; \ + } \ + return data; \ + } \ + } \ + } \ + namespace ns_name + +#define DOCTEST_TEST_SUITE(decorators) \ + DOCTEST_TEST_SUITE_IMPL(decorators, DOCTEST_ANONYMOUS(DOCTEST_ANON_SUITE_)) + +// for starting a testsuite block +#define DOCTEST_TEST_SUITE_BEGIN(decorators) \ + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_VAR_), /* NOLINT(cert-err58-cpp) */ \ + doctest::detail::setTestSuite(doctest::detail::TestSuite() * decorators)) \ + static_assert(true, "") + +// for ending a testsuite block +#define DOCTEST_TEST_SUITE_END \ + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_VAR_), /* NOLINT(cert-err58-cpp) */ \ + doctest::detail::setTestSuite(doctest::detail::TestSuite() * "")) \ + using DOCTEST_ANONYMOUS(DOCTEST_ANON_FOR_SEMICOLON_) = int + +// for registering exception translators +#define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR_IMPL(translatorName, signature) \ + inline doctest::String translatorName(signature); \ + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_TRANSLATOR_), /* NOLINT(cert-err58-cpp) */ \ + doctest::registerExceptionTranslator(translatorName)) \ + doctest::String translatorName(signature) + +#define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR(signature) \ + DOCTEST_REGISTER_EXCEPTION_TRANSLATOR_IMPL(DOCTEST_ANONYMOUS(DOCTEST_ANON_TRANSLATOR_), \ + signature) + +// for registering reporters +#define DOCTEST_REGISTER_REPORTER(name, priority, reporter) \ + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_REPORTER_), /* NOLINT(cert-err58-cpp) */ \ + doctest::registerReporter(name, priority, true)) \ + static_assert(true, "") + +// for registering listeners +#define DOCTEST_REGISTER_LISTENER(name, priority, reporter) \ + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_REPORTER_), /* NOLINT(cert-err58-cpp) */ \ + doctest::registerReporter(name, priority, false)) \ + static_assert(true, "") + +// clang-format off +// for logging - disabling formatting because it's important to have these on 2 separate lines - see PR #557 +#define DOCTEST_INFO(...) \ + DOCTEST_INFO_IMPL(DOCTEST_ANONYMOUS(DOCTEST_CAPTURE_), \ + DOCTEST_ANONYMOUS(DOCTEST_CAPTURE_OTHER_), \ + __VA_ARGS__) +// clang-format on + +#define DOCTEST_INFO_IMPL(mb_name, s_name, ...) \ + auto DOCTEST_ANONYMOUS(DOCTEST_CAPTURE_) = doctest::detail::MakeContextScope( \ + [&](std::ostream* s_name) { \ + doctest::detail::MessageBuilder mb_name(__FILE__, __LINE__, doctest::assertType::is_warn); \ + mb_name.m_stream = s_name; \ + mb_name * __VA_ARGS__; \ + }) + +#define DOCTEST_CAPTURE(x) DOCTEST_INFO(#x " := ", x) + +#define DOCTEST_ADD_AT_IMPL(type, file, line, mb, ...) \ + DOCTEST_FUNC_SCOPE_BEGIN { \ + doctest::detail::MessageBuilder mb(file, line, doctest::assertType::type); \ + mb * __VA_ARGS__; \ + if(mb.log()) \ + DOCTEST_BREAK_INTO_DEBUGGER(); \ + mb.react(); \ + } DOCTEST_FUNC_SCOPE_END + +// clang-format off +#define DOCTEST_ADD_MESSAGE_AT(file, line, ...) DOCTEST_ADD_AT_IMPL(is_warn, file, line, DOCTEST_ANONYMOUS(DOCTEST_MESSAGE_), __VA_ARGS__) +#define DOCTEST_ADD_FAIL_CHECK_AT(file, line, ...) DOCTEST_ADD_AT_IMPL(is_check, file, line, DOCTEST_ANONYMOUS(DOCTEST_MESSAGE_), __VA_ARGS__) +#define DOCTEST_ADD_FAIL_AT(file, line, ...) DOCTEST_ADD_AT_IMPL(is_require, file, line, DOCTEST_ANONYMOUS(DOCTEST_MESSAGE_), __VA_ARGS__) +// clang-format on + +#define DOCTEST_MESSAGE(...) DOCTEST_ADD_MESSAGE_AT(__FILE__, __LINE__, __VA_ARGS__) +#define DOCTEST_FAIL_CHECK(...) DOCTEST_ADD_FAIL_CHECK_AT(__FILE__, __LINE__, __VA_ARGS__) +#define DOCTEST_FAIL(...) DOCTEST_ADD_FAIL_AT(__FILE__, __LINE__, __VA_ARGS__) + +#define DOCTEST_TO_LVALUE(...) __VA_ARGS__ // Not removed to keep backwards compatibility. + +#ifndef DOCTEST_CONFIG_SUPER_FAST_ASSERTS + +#define DOCTEST_ASSERT_IMPLEMENT_2(assert_type, ...) \ + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Woverloaded-shift-op-parentheses") \ + /* NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) */ \ + doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + __LINE__, #__VA_ARGS__); \ + DOCTEST_WRAP_IN_TRY(DOCTEST_RB.setResult( \ + doctest::detail::ExpressionDecomposer(doctest::assertType::assert_type) \ + << __VA_ARGS__)) /* NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) */ \ + DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB) \ + DOCTEST_CLANG_SUPPRESS_WARNING_POP + +#define DOCTEST_ASSERT_IMPLEMENT_1(assert_type, ...) \ + DOCTEST_FUNC_SCOPE_BEGIN { \ + DOCTEST_ASSERT_IMPLEMENT_2(assert_type, __VA_ARGS__); \ + } DOCTEST_FUNC_SCOPE_END // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) + +#define DOCTEST_BINARY_ASSERT(assert_type, comp, ...) \ + DOCTEST_FUNC_SCOPE_BEGIN { \ + doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + __LINE__, #__VA_ARGS__); \ + DOCTEST_WRAP_IN_TRY( \ + DOCTEST_RB.binary_assert( \ + __VA_ARGS__)) \ + DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB); \ + } DOCTEST_FUNC_SCOPE_END + +#define DOCTEST_UNARY_ASSERT(assert_type, ...) \ + DOCTEST_FUNC_SCOPE_BEGIN { \ + doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + __LINE__, #__VA_ARGS__); \ + DOCTEST_WRAP_IN_TRY(DOCTEST_RB.unary_assert(__VA_ARGS__)) \ + DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB); \ + } DOCTEST_FUNC_SCOPE_END + +#else // DOCTEST_CONFIG_SUPER_FAST_ASSERTS + +// necessary for _MESSAGE +#define DOCTEST_ASSERT_IMPLEMENT_2 DOCTEST_ASSERT_IMPLEMENT_1 + +#define DOCTEST_ASSERT_IMPLEMENT_1(assert_type, ...) \ + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Woverloaded-shift-op-parentheses") \ + doctest::detail::decomp_assert( \ + doctest::assertType::assert_type, __FILE__, __LINE__, #__VA_ARGS__, \ + doctest::detail::ExpressionDecomposer(doctest::assertType::assert_type) \ + << __VA_ARGS__) DOCTEST_CLANG_SUPPRESS_WARNING_POP + +#define DOCTEST_BINARY_ASSERT(assert_type, comparison, ...) \ + doctest::detail::binary_assert( \ + doctest::assertType::assert_type, __FILE__, __LINE__, #__VA_ARGS__, __VA_ARGS__) + +#define DOCTEST_UNARY_ASSERT(assert_type, ...) \ + doctest::detail::unary_assert(doctest::assertType::assert_type, __FILE__, __LINE__, \ + #__VA_ARGS__, __VA_ARGS__) + +#endif // DOCTEST_CONFIG_SUPER_FAST_ASSERTS + +#define DOCTEST_WARN(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_WARN, __VA_ARGS__) +#define DOCTEST_CHECK(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_CHECK, __VA_ARGS__) +#define DOCTEST_REQUIRE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_REQUIRE, __VA_ARGS__) +#define DOCTEST_WARN_FALSE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_WARN_FALSE, __VA_ARGS__) +#define DOCTEST_CHECK_FALSE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_CHECK_FALSE, __VA_ARGS__) +#define DOCTEST_REQUIRE_FALSE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_REQUIRE_FALSE, __VA_ARGS__) + +// clang-format off +#define DOCTEST_WARN_MESSAGE(cond, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_WARN, cond); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_CHECK_MESSAGE(cond, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_CHECK, cond); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_REQUIRE_MESSAGE(cond, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_REQUIRE, cond); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_WARN_FALSE_MESSAGE(cond, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_WARN_FALSE, cond); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_CHECK_FALSE_MESSAGE(cond, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_CHECK_FALSE, cond); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_REQUIRE_FALSE, cond); } DOCTEST_FUNC_SCOPE_END +// clang-format on + +#define DOCTEST_WARN_EQ(...) DOCTEST_BINARY_ASSERT(DT_WARN_EQ, eq, __VA_ARGS__) +#define DOCTEST_CHECK_EQ(...) DOCTEST_BINARY_ASSERT(DT_CHECK_EQ, eq, __VA_ARGS__) +#define DOCTEST_REQUIRE_EQ(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_EQ, eq, __VA_ARGS__) +#define DOCTEST_WARN_NE(...) DOCTEST_BINARY_ASSERT(DT_WARN_NE, ne, __VA_ARGS__) +#define DOCTEST_CHECK_NE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_NE, ne, __VA_ARGS__) +#define DOCTEST_REQUIRE_NE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_NE, ne, __VA_ARGS__) +#define DOCTEST_WARN_GT(...) DOCTEST_BINARY_ASSERT(DT_WARN_GT, gt, __VA_ARGS__) +#define DOCTEST_CHECK_GT(...) DOCTEST_BINARY_ASSERT(DT_CHECK_GT, gt, __VA_ARGS__) +#define DOCTEST_REQUIRE_GT(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_GT, gt, __VA_ARGS__) +#define DOCTEST_WARN_LT(...) DOCTEST_BINARY_ASSERT(DT_WARN_LT, lt, __VA_ARGS__) +#define DOCTEST_CHECK_LT(...) DOCTEST_BINARY_ASSERT(DT_CHECK_LT, lt, __VA_ARGS__) +#define DOCTEST_REQUIRE_LT(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_LT, lt, __VA_ARGS__) +#define DOCTEST_WARN_GE(...) DOCTEST_BINARY_ASSERT(DT_WARN_GE, ge, __VA_ARGS__) +#define DOCTEST_CHECK_GE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_GE, ge, __VA_ARGS__) +#define DOCTEST_REQUIRE_GE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_GE, ge, __VA_ARGS__) +#define DOCTEST_WARN_LE(...) DOCTEST_BINARY_ASSERT(DT_WARN_LE, le, __VA_ARGS__) +#define DOCTEST_CHECK_LE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_LE, le, __VA_ARGS__) +#define DOCTEST_REQUIRE_LE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_LE, le, __VA_ARGS__) + +#define DOCTEST_WARN_UNARY(...) DOCTEST_UNARY_ASSERT(DT_WARN_UNARY, __VA_ARGS__) +#define DOCTEST_CHECK_UNARY(...) DOCTEST_UNARY_ASSERT(DT_CHECK_UNARY, __VA_ARGS__) +#define DOCTEST_REQUIRE_UNARY(...) DOCTEST_UNARY_ASSERT(DT_REQUIRE_UNARY, __VA_ARGS__) +#define DOCTEST_WARN_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_WARN_UNARY_FALSE, __VA_ARGS__) +#define DOCTEST_CHECK_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_CHECK_UNARY_FALSE, __VA_ARGS__) +#define DOCTEST_REQUIRE_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_REQUIRE_UNARY_FALSE, __VA_ARGS__) + +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + +#define DOCTEST_ASSERT_THROWS_AS(expr, assert_type, message, ...) \ + DOCTEST_FUNC_SCOPE_BEGIN { \ + if(!doctest::getContextOptions()->no_throw) { \ + doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + __LINE__, #expr, #__VA_ARGS__, message); \ + try { \ + DOCTEST_CAST_TO_VOID(expr) \ + } catch(const typename doctest::detail::types::remove_const< \ + typename doctest::detail::types::remove_reference<__VA_ARGS__>::type>::type&) {\ + DOCTEST_RB.translateException(); \ + DOCTEST_RB.m_threw_as = true; \ + } catch(...) { DOCTEST_RB.translateException(); } \ + DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB); \ + } else { /* NOLINT(*-else-after-return) */ \ + DOCTEST_FUNC_SCOPE_RET(false); \ + } \ + } DOCTEST_FUNC_SCOPE_END + +#define DOCTEST_ASSERT_THROWS_WITH(expr, expr_str, assert_type, ...) \ + DOCTEST_FUNC_SCOPE_BEGIN { \ + if(!doctest::getContextOptions()->no_throw) { \ + doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + __LINE__, expr_str, "", __VA_ARGS__); \ + try { \ + DOCTEST_CAST_TO_VOID(expr) \ + } catch(...) { DOCTEST_RB.translateException(); } \ + DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB); \ + } else { /* NOLINT(*-else-after-return) */ \ + DOCTEST_FUNC_SCOPE_RET(false); \ + } \ + } DOCTEST_FUNC_SCOPE_END + +#define DOCTEST_ASSERT_NOTHROW(assert_type, ...) \ + DOCTEST_FUNC_SCOPE_BEGIN { \ + doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + __LINE__, #__VA_ARGS__); \ + try { \ + DOCTEST_CAST_TO_VOID(__VA_ARGS__) \ + } catch(...) { DOCTEST_RB.translateException(); } \ + DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB); \ + } DOCTEST_FUNC_SCOPE_END + +// clang-format off +#define DOCTEST_WARN_THROWS(...) DOCTEST_ASSERT_THROWS_WITH((__VA_ARGS__), #__VA_ARGS__, DT_WARN_THROWS, "") +#define DOCTEST_CHECK_THROWS(...) DOCTEST_ASSERT_THROWS_WITH((__VA_ARGS__), #__VA_ARGS__, DT_CHECK_THROWS, "") +#define DOCTEST_REQUIRE_THROWS(...) DOCTEST_ASSERT_THROWS_WITH((__VA_ARGS__), #__VA_ARGS__, DT_REQUIRE_THROWS, "") + +#define DOCTEST_WARN_THROWS_AS(expr, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_WARN_THROWS_AS, "", __VA_ARGS__) +#define DOCTEST_CHECK_THROWS_AS(expr, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_CHECK_THROWS_AS, "", __VA_ARGS__) +#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_REQUIRE_THROWS_AS, "", __VA_ARGS__) + +#define DOCTEST_WARN_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, #expr, DT_WARN_THROWS_WITH, __VA_ARGS__) +#define DOCTEST_CHECK_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, #expr, DT_CHECK_THROWS_WITH, __VA_ARGS__) +#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, #expr, DT_REQUIRE_THROWS_WITH, __VA_ARGS__) + +#define DOCTEST_WARN_THROWS_WITH_AS(expr, message, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_WARN_THROWS_WITH_AS, message, __VA_ARGS__) +#define DOCTEST_CHECK_THROWS_WITH_AS(expr, message, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_CHECK_THROWS_WITH_AS, message, __VA_ARGS__) +#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, message, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_REQUIRE_THROWS_WITH_AS, message, __VA_ARGS__) + +#define DOCTEST_WARN_NOTHROW(...) DOCTEST_ASSERT_NOTHROW(DT_WARN_NOTHROW, __VA_ARGS__) +#define DOCTEST_CHECK_NOTHROW(...) DOCTEST_ASSERT_NOTHROW(DT_CHECK_NOTHROW, __VA_ARGS__) +#define DOCTEST_REQUIRE_NOTHROW(...) DOCTEST_ASSERT_NOTHROW(DT_REQUIRE_NOTHROW, __VA_ARGS__) + +#define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS(expr); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS(expr); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS(expr); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS_AS(expr, ex); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS_AS(expr, ex); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS_AS(expr, ex); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS_WITH(expr, with); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS_WITH(expr, with); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS_WITH(expr, with); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS_WITH_AS(expr, with, ex); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ex); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ex); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_NOTHROW(expr); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_NOTHROW(expr); } DOCTEST_FUNC_SCOPE_END +#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) DOCTEST_FUNC_SCOPE_BEGIN { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_NOTHROW(expr); } DOCTEST_FUNC_SCOPE_END +// clang-format on + +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + +// ================================================================================================= +// == WHAT FOLLOWS IS VERSIONS OF THE MACROS THAT DO NOT DO ANY REGISTERING! == +// == THIS CAN BE ENABLED BY DEFINING DOCTEST_CONFIG_DISABLE GLOBALLY! == +// ================================================================================================= +#else // DOCTEST_CONFIG_DISABLE + +#define DOCTEST_IMPLEMENT_FIXTURE(der, base, func, name) \ + namespace /* NOLINT */ { \ + template \ + struct der : public base \ + { void f(); }; \ + } \ + template \ + inline void der::f() + +#define DOCTEST_CREATE_AND_REGISTER_FUNCTION(f, name) \ + template \ + static inline void f() + +// for registering tests +#define DOCTEST_TEST_CASE(name) \ + DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), name) + +// for registering tests in classes +#define DOCTEST_TEST_CASE_CLASS(name) \ + DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), name) + +// for registering tests with a fixture +#define DOCTEST_TEST_CASE_FIXTURE(x, name) \ + DOCTEST_IMPLEMENT_FIXTURE(DOCTEST_ANONYMOUS(DOCTEST_ANON_CLASS_), x, \ + DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), name) + +// for converting types to strings without the header and demangling +#define DOCTEST_TYPE_TO_STRING_AS(str, ...) static_assert(true, "") +#define DOCTEST_TYPE_TO_STRING(...) static_assert(true, "") + +// for typed tests +#define DOCTEST_TEST_CASE_TEMPLATE(name, type, ...) \ + template \ + inline void DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_)() + +#define DOCTEST_TEST_CASE_TEMPLATE_DEFINE(name, type, id) \ + template \ + inline void DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_)() + +#define DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, ...) static_assert(true, "") +#define DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, ...) static_assert(true, "") + +// for subcases +#define DOCTEST_SUBCASE(name) + +// for a testsuite block +#define DOCTEST_TEST_SUITE(name) namespace // NOLINT + +// for starting a testsuite block +#define DOCTEST_TEST_SUITE_BEGIN(name) static_assert(true, "") + +// for ending a testsuite block +#define DOCTEST_TEST_SUITE_END using DOCTEST_ANONYMOUS(DOCTEST_ANON_FOR_SEMICOLON_) = int + +#define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR(signature) \ + template \ + static inline doctest::String DOCTEST_ANONYMOUS(DOCTEST_ANON_TRANSLATOR_)(signature) + +#define DOCTEST_REGISTER_REPORTER(name, priority, reporter) +#define DOCTEST_REGISTER_LISTENER(name, priority, reporter) + +#define DOCTEST_INFO(...) (static_cast(0)) +#define DOCTEST_CAPTURE(x) (static_cast(0)) +#define DOCTEST_ADD_MESSAGE_AT(file, line, ...) (static_cast(0)) +#define DOCTEST_ADD_FAIL_CHECK_AT(file, line, ...) (static_cast(0)) +#define DOCTEST_ADD_FAIL_AT(file, line, ...) (static_cast(0)) +#define DOCTEST_MESSAGE(...) (static_cast(0)) +#define DOCTEST_FAIL_CHECK(...) (static_cast(0)) +#define DOCTEST_FAIL(...) (static_cast(0)) + +#if defined(DOCTEST_CONFIG_EVALUATE_ASSERTS_EVEN_WHEN_DISABLED) \ + && defined(DOCTEST_CONFIG_ASSERTS_RETURN_VALUES) + +#define DOCTEST_WARN(...) [&] { return __VA_ARGS__; }() +#define DOCTEST_CHECK(...) [&] { return __VA_ARGS__; }() +#define DOCTEST_REQUIRE(...) [&] { return __VA_ARGS__; }() +#define DOCTEST_WARN_FALSE(...) [&] { return !(__VA_ARGS__); }() +#define DOCTEST_CHECK_FALSE(...) [&] { return !(__VA_ARGS__); }() +#define DOCTEST_REQUIRE_FALSE(...) [&] { return !(__VA_ARGS__); }() + +#define DOCTEST_WARN_MESSAGE(cond, ...) [&] { return cond; }() +#define DOCTEST_CHECK_MESSAGE(cond, ...) [&] { return cond; }() +#define DOCTEST_REQUIRE_MESSAGE(cond, ...) [&] { return cond; }() +#define DOCTEST_WARN_FALSE_MESSAGE(cond, ...) [&] { return !(cond); }() +#define DOCTEST_CHECK_FALSE_MESSAGE(cond, ...) [&] { return !(cond); }() +#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, ...) [&] { return !(cond); }() + +namespace doctest { +namespace detail { +#define DOCTEST_RELATIONAL_OP(name, op) \ + template \ + bool name(const DOCTEST_REF_WRAP(L) lhs, const DOCTEST_REF_WRAP(R) rhs) { return lhs op rhs; } + + DOCTEST_RELATIONAL_OP(eq, ==) + DOCTEST_RELATIONAL_OP(ne, !=) + DOCTEST_RELATIONAL_OP(lt, <) + DOCTEST_RELATIONAL_OP(gt, >) + DOCTEST_RELATIONAL_OP(le, <=) + DOCTEST_RELATIONAL_OP(ge, >=) +} // namespace detail +} // namespace doctest + +#define DOCTEST_WARN_EQ(...) [&] { return doctest::detail::eq(__VA_ARGS__); }() +#define DOCTEST_CHECK_EQ(...) [&] { return doctest::detail::eq(__VA_ARGS__); }() +#define DOCTEST_REQUIRE_EQ(...) [&] { return doctest::detail::eq(__VA_ARGS__); }() +#define DOCTEST_WARN_NE(...) [&] { return doctest::detail::ne(__VA_ARGS__); }() +#define DOCTEST_CHECK_NE(...) [&] { return doctest::detail::ne(__VA_ARGS__); }() +#define DOCTEST_REQUIRE_NE(...) [&] { return doctest::detail::ne(__VA_ARGS__); }() +#define DOCTEST_WARN_LT(...) [&] { return doctest::detail::lt(__VA_ARGS__); }() +#define DOCTEST_CHECK_LT(...) [&] { return doctest::detail::lt(__VA_ARGS__); }() +#define DOCTEST_REQUIRE_LT(...) [&] { return doctest::detail::lt(__VA_ARGS__); }() +#define DOCTEST_WARN_GT(...) [&] { return doctest::detail::gt(__VA_ARGS__); }() +#define DOCTEST_CHECK_GT(...) [&] { return doctest::detail::gt(__VA_ARGS__); }() +#define DOCTEST_REQUIRE_GT(...) [&] { return doctest::detail::gt(__VA_ARGS__); }() +#define DOCTEST_WARN_LE(...) [&] { return doctest::detail::le(__VA_ARGS__); }() +#define DOCTEST_CHECK_LE(...) [&] { return doctest::detail::le(__VA_ARGS__); }() +#define DOCTEST_REQUIRE_LE(...) [&] { return doctest::detail::le(__VA_ARGS__); }() +#define DOCTEST_WARN_GE(...) [&] { return doctest::detail::ge(__VA_ARGS__); }() +#define DOCTEST_CHECK_GE(...) [&] { return doctest::detail::ge(__VA_ARGS__); }() +#define DOCTEST_REQUIRE_GE(...) [&] { return doctest::detail::ge(__VA_ARGS__); }() +#define DOCTEST_WARN_UNARY(...) [&] { return __VA_ARGS__; }() +#define DOCTEST_CHECK_UNARY(...) [&] { return __VA_ARGS__; }() +#define DOCTEST_REQUIRE_UNARY(...) [&] { return __VA_ARGS__; }() +#define DOCTEST_WARN_UNARY_FALSE(...) [&] { return !(__VA_ARGS__); }() +#define DOCTEST_CHECK_UNARY_FALSE(...) [&] { return !(__VA_ARGS__); }() +#define DOCTEST_REQUIRE_UNARY_FALSE(...) [&] { return !(__VA_ARGS__); }() + +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + +#define DOCTEST_WARN_THROWS_WITH(expr, with, ...) [] { static_assert(false, "Exception translation is not available when doctest is disabled."); return false; }() +#define DOCTEST_CHECK_THROWS_WITH(expr, with, ...) DOCTEST_WARN_THROWS_WITH(,,) +#define DOCTEST_REQUIRE_THROWS_WITH(expr, with, ...) DOCTEST_WARN_THROWS_WITH(,,) +#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH(,,) +#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH(,,) +#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH(,,) + +#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_WARN_THROWS_WITH(,,) +#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_WARN_THROWS_WITH(,,) +#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_WARN_THROWS_WITH(,,) +#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH(,,) +#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH(,,) +#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH(,,) + +#define DOCTEST_WARN_THROWS(...) [&] { try { __VA_ARGS__; return false; } catch (...) { return true; } }() +#define DOCTEST_CHECK_THROWS(...) [&] { try { __VA_ARGS__; return false; } catch (...) { return true; } }() +#define DOCTEST_REQUIRE_THROWS(...) [&] { try { __VA_ARGS__; return false; } catch (...) { return true; } }() +#define DOCTEST_WARN_THROWS_AS(expr, ...) [&] { try { expr; } catch (__VA_ARGS__) { return true; } catch (...) { } return false; }() +#define DOCTEST_CHECK_THROWS_AS(expr, ...) [&] { try { expr; } catch (__VA_ARGS__) { return true; } catch (...) { } return false; }() +#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) [&] { try { expr; } catch (__VA_ARGS__) { return true; } catch (...) { } return false; }() +#define DOCTEST_WARN_NOTHROW(...) [&] { try { __VA_ARGS__; return true; } catch (...) { return false; } }() +#define DOCTEST_CHECK_NOTHROW(...) [&] { try { __VA_ARGS__; return true; } catch (...) { return false; } }() +#define DOCTEST_REQUIRE_NOTHROW(...) [&] { try { __VA_ARGS__; return true; } catch (...) { return false; } }() + +#define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) [&] { try { __VA_ARGS__; return false; } catch (...) { return true; } }() +#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) [&] { try { __VA_ARGS__; return false; } catch (...) { return true; } }() +#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) [&] { try { __VA_ARGS__; return false; } catch (...) { return true; } }() +#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) [&] { try { expr; } catch (__VA_ARGS__) { return true; } catch (...) { } return false; }() +#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) [&] { try { expr; } catch (__VA_ARGS__) { return true; } catch (...) { } return false; }() +#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) [&] { try { expr; } catch (__VA_ARGS__) { return true; } catch (...) { } return false; }() +#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) [&] { try { __VA_ARGS__; return true; } catch (...) { return false; } }() +#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) [&] { try { __VA_ARGS__; return true; } catch (...) { return false; } }() +#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) [&] { try { __VA_ARGS__; return true; } catch (...) { return false; } }() + +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + +#else // DOCTEST_CONFIG_EVALUATE_ASSERTS_EVEN_WHEN_DISABLED + +#define DOCTEST_WARN(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_FALSE(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_FALSE(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_FALSE(...) DOCTEST_FUNC_EMPTY + +#define DOCTEST_WARN_MESSAGE(cond, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_MESSAGE(cond, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_MESSAGE(cond, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_FALSE_MESSAGE(cond, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_FALSE_MESSAGE(cond, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, ...) DOCTEST_FUNC_EMPTY + +#define DOCTEST_WARN_EQ(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_EQ(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_EQ(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_NE(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_NE(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_NE(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_GT(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_GT(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_GT(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_LT(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_LT(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_LT(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_GE(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_GE(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_GE(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_LE(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_LE(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_LE(...) DOCTEST_FUNC_EMPTY + +#define DOCTEST_WARN_UNARY(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_UNARY(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_UNARY(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_UNARY_FALSE(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_UNARY_FALSE(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_UNARY_FALSE(...) DOCTEST_FUNC_EMPTY + +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + +#define DOCTEST_WARN_THROWS(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_THROWS(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_THROWS(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_THROWS_AS(expr, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_THROWS_AS(expr, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_THROWS_WITH(expr, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_THROWS_WITH(expr, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_NOTHROW(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_NOTHROW(...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_NOTHROW(...) DOCTEST_FUNC_EMPTY + +#define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) DOCTEST_FUNC_EMPTY +#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) DOCTEST_FUNC_EMPTY + +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + +#endif // DOCTEST_CONFIG_EVALUATE_ASSERTS_EVEN_WHEN_DISABLED + +#endif // DOCTEST_CONFIG_DISABLE + +#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS + +#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS +#define DOCTEST_EXCEPTION_EMPTY_FUNC DOCTEST_FUNC_EMPTY +#else // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS +#define DOCTEST_EXCEPTION_EMPTY_FUNC [] { static_assert(false, "Exceptions are disabled! " \ + "Use DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS if you want to compile with exceptions disabled."); return false; }() + +#undef DOCTEST_REQUIRE +#undef DOCTEST_REQUIRE_FALSE +#undef DOCTEST_REQUIRE_MESSAGE +#undef DOCTEST_REQUIRE_FALSE_MESSAGE +#undef DOCTEST_REQUIRE_EQ +#undef DOCTEST_REQUIRE_NE +#undef DOCTEST_REQUIRE_GT +#undef DOCTEST_REQUIRE_LT +#undef DOCTEST_REQUIRE_GE +#undef DOCTEST_REQUIRE_LE +#undef DOCTEST_REQUIRE_UNARY +#undef DOCTEST_REQUIRE_UNARY_FALSE + +#define DOCTEST_REQUIRE DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_FALSE DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_MESSAGE DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_FALSE_MESSAGE DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_EQ DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_NE DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_GT DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_LT DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_GE DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_LE DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_UNARY DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_UNARY_FALSE DOCTEST_EXCEPTION_EMPTY_FUNC + +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS + +#define DOCTEST_WARN_THROWS(...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_CHECK_THROWS(...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_THROWS(...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_WARN_THROWS_AS(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_CHECK_THROWS_AS(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_WARN_THROWS_WITH(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_CHECK_THROWS_WITH(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_WARN_NOTHROW(...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_CHECK_NOTHROW(...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_NOTHROW(...) DOCTEST_EXCEPTION_EMPTY_FUNC + +#define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC +#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) DOCTEST_EXCEPTION_EMPTY_FUNC + +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + +// clang-format off +// KEPT FOR BACKWARDS COMPATIBILITY - FORWARDING TO THE RIGHT MACROS +#define DOCTEST_FAST_WARN_EQ DOCTEST_WARN_EQ +#define DOCTEST_FAST_CHECK_EQ DOCTEST_CHECK_EQ +#define DOCTEST_FAST_REQUIRE_EQ DOCTEST_REQUIRE_EQ +#define DOCTEST_FAST_WARN_NE DOCTEST_WARN_NE +#define DOCTEST_FAST_CHECK_NE DOCTEST_CHECK_NE +#define DOCTEST_FAST_REQUIRE_NE DOCTEST_REQUIRE_NE +#define DOCTEST_FAST_WARN_GT DOCTEST_WARN_GT +#define DOCTEST_FAST_CHECK_GT DOCTEST_CHECK_GT +#define DOCTEST_FAST_REQUIRE_GT DOCTEST_REQUIRE_GT +#define DOCTEST_FAST_WARN_LT DOCTEST_WARN_LT +#define DOCTEST_FAST_CHECK_LT DOCTEST_CHECK_LT +#define DOCTEST_FAST_REQUIRE_LT DOCTEST_REQUIRE_LT +#define DOCTEST_FAST_WARN_GE DOCTEST_WARN_GE +#define DOCTEST_FAST_CHECK_GE DOCTEST_CHECK_GE +#define DOCTEST_FAST_REQUIRE_GE DOCTEST_REQUIRE_GE +#define DOCTEST_FAST_WARN_LE DOCTEST_WARN_LE +#define DOCTEST_FAST_CHECK_LE DOCTEST_CHECK_LE +#define DOCTEST_FAST_REQUIRE_LE DOCTEST_REQUIRE_LE + +#define DOCTEST_FAST_WARN_UNARY DOCTEST_WARN_UNARY +#define DOCTEST_FAST_CHECK_UNARY DOCTEST_CHECK_UNARY +#define DOCTEST_FAST_REQUIRE_UNARY DOCTEST_REQUIRE_UNARY +#define DOCTEST_FAST_WARN_UNARY_FALSE DOCTEST_WARN_UNARY_FALSE +#define DOCTEST_FAST_CHECK_UNARY_FALSE DOCTEST_CHECK_UNARY_FALSE +#define DOCTEST_FAST_REQUIRE_UNARY_FALSE DOCTEST_REQUIRE_UNARY_FALSE + +#define DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE(id, ...) DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id,__VA_ARGS__) +// clang-format on + +// BDD style macros +// clang-format off +#define DOCTEST_SCENARIO(name) DOCTEST_TEST_CASE(" Scenario: " name) +#define DOCTEST_SCENARIO_CLASS(name) DOCTEST_TEST_CASE_CLASS(" Scenario: " name) +#define DOCTEST_SCENARIO_TEMPLATE(name, T, ...) DOCTEST_TEST_CASE_TEMPLATE(" Scenario: " name, T, __VA_ARGS__) +#define DOCTEST_SCENARIO_TEMPLATE_DEFINE(name, T, id) DOCTEST_TEST_CASE_TEMPLATE_DEFINE(" Scenario: " name, T, id) + +#define DOCTEST_GIVEN(name) DOCTEST_SUBCASE(" Given: " name) +#define DOCTEST_WHEN(name) DOCTEST_SUBCASE(" When: " name) +#define DOCTEST_AND_WHEN(name) DOCTEST_SUBCASE("And when: " name) +#define DOCTEST_THEN(name) DOCTEST_SUBCASE(" Then: " name) +#define DOCTEST_AND_THEN(name) DOCTEST_SUBCASE(" And: " name) +// clang-format on + +// == SHORT VERSIONS OF THE MACROS +#ifndef DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES + +#define TEST_CASE(name) DOCTEST_TEST_CASE(name) +#define TEST_CASE_CLASS(name) DOCTEST_TEST_CASE_CLASS(name) +#define TEST_CASE_FIXTURE(x, name) DOCTEST_TEST_CASE_FIXTURE(x, name) +#define TYPE_TO_STRING_AS(str, ...) DOCTEST_TYPE_TO_STRING_AS(str, __VA_ARGS__) +#define TYPE_TO_STRING(...) DOCTEST_TYPE_TO_STRING(__VA_ARGS__) +#define TEST_CASE_TEMPLATE(name, T, ...) DOCTEST_TEST_CASE_TEMPLATE(name, T, __VA_ARGS__) +#define TEST_CASE_TEMPLATE_DEFINE(name, T, id) DOCTEST_TEST_CASE_TEMPLATE_DEFINE(name, T, id) +#define TEST_CASE_TEMPLATE_INVOKE(id, ...) DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, __VA_ARGS__) +#define TEST_CASE_TEMPLATE_APPLY(id, ...) DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, __VA_ARGS__) +#define SUBCASE(name) DOCTEST_SUBCASE(name) +#define TEST_SUITE(decorators) DOCTEST_TEST_SUITE(decorators) +#define TEST_SUITE_BEGIN(name) DOCTEST_TEST_SUITE_BEGIN(name) +#define TEST_SUITE_END DOCTEST_TEST_SUITE_END +#define REGISTER_EXCEPTION_TRANSLATOR(signature) DOCTEST_REGISTER_EXCEPTION_TRANSLATOR(signature) +#define REGISTER_REPORTER(name, priority, reporter) DOCTEST_REGISTER_REPORTER(name, priority, reporter) +#define REGISTER_LISTENER(name, priority, reporter) DOCTEST_REGISTER_LISTENER(name, priority, reporter) +#define INFO(...) DOCTEST_INFO(__VA_ARGS__) +#define CAPTURE(x) DOCTEST_CAPTURE(x) +#define ADD_MESSAGE_AT(file, line, ...) DOCTEST_ADD_MESSAGE_AT(file, line, __VA_ARGS__) +#define ADD_FAIL_CHECK_AT(file, line, ...) DOCTEST_ADD_FAIL_CHECK_AT(file, line, __VA_ARGS__) +#define ADD_FAIL_AT(file, line, ...) DOCTEST_ADD_FAIL_AT(file, line, __VA_ARGS__) +#define MESSAGE(...) DOCTEST_MESSAGE(__VA_ARGS__) +#define FAIL_CHECK(...) DOCTEST_FAIL_CHECK(__VA_ARGS__) +#define FAIL(...) DOCTEST_FAIL(__VA_ARGS__) +#define TO_LVALUE(...) DOCTEST_TO_LVALUE(__VA_ARGS__) + +#define WARN(...) DOCTEST_WARN(__VA_ARGS__) +#define WARN_FALSE(...) DOCTEST_WARN_FALSE(__VA_ARGS__) +#define WARN_THROWS(...) DOCTEST_WARN_THROWS(__VA_ARGS__) +#define WARN_THROWS_AS(expr, ...) DOCTEST_WARN_THROWS_AS(expr, __VA_ARGS__) +#define WARN_THROWS_WITH(expr, ...) DOCTEST_WARN_THROWS_WITH(expr, __VA_ARGS__) +#define WARN_THROWS_WITH_AS(expr, with, ...) DOCTEST_WARN_THROWS_WITH_AS(expr, with, __VA_ARGS__) +#define WARN_NOTHROW(...) DOCTEST_WARN_NOTHROW(__VA_ARGS__) +#define CHECK(...) DOCTEST_CHECK(__VA_ARGS__) +#define CHECK_FALSE(...) DOCTEST_CHECK_FALSE(__VA_ARGS__) +#define CHECK_THROWS(...) DOCTEST_CHECK_THROWS(__VA_ARGS__) +#define CHECK_THROWS_AS(expr, ...) DOCTEST_CHECK_THROWS_AS(expr, __VA_ARGS__) +#define CHECK_THROWS_WITH(expr, ...) DOCTEST_CHECK_THROWS_WITH(expr, __VA_ARGS__) +#define CHECK_THROWS_WITH_AS(expr, with, ...) DOCTEST_CHECK_THROWS_WITH_AS(expr, with, __VA_ARGS__) +#define CHECK_NOTHROW(...) DOCTEST_CHECK_NOTHROW(__VA_ARGS__) +#define REQUIRE(...) DOCTEST_REQUIRE(__VA_ARGS__) +#define REQUIRE_FALSE(...) DOCTEST_REQUIRE_FALSE(__VA_ARGS__) +#define REQUIRE_THROWS(...) DOCTEST_REQUIRE_THROWS(__VA_ARGS__) +#define REQUIRE_THROWS_AS(expr, ...) DOCTEST_REQUIRE_THROWS_AS(expr, __VA_ARGS__) +#define REQUIRE_THROWS_WITH(expr, ...) DOCTEST_REQUIRE_THROWS_WITH(expr, __VA_ARGS__) +#define REQUIRE_THROWS_WITH_AS(expr, with, ...) DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, __VA_ARGS__) +#define REQUIRE_NOTHROW(...) DOCTEST_REQUIRE_NOTHROW(__VA_ARGS__) + +#define WARN_MESSAGE(cond, ...) DOCTEST_WARN_MESSAGE(cond, __VA_ARGS__) +#define WARN_FALSE_MESSAGE(cond, ...) DOCTEST_WARN_FALSE_MESSAGE(cond, __VA_ARGS__) +#define WARN_THROWS_MESSAGE(expr, ...) DOCTEST_WARN_THROWS_MESSAGE(expr, __VA_ARGS__) +#define WARN_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, __VA_ARGS__) +#define WARN_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, __VA_ARGS__) +#define WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, __VA_ARGS__) +#define WARN_NOTHROW_MESSAGE(expr, ...) DOCTEST_WARN_NOTHROW_MESSAGE(expr, __VA_ARGS__) +#define CHECK_MESSAGE(cond, ...) DOCTEST_CHECK_MESSAGE(cond, __VA_ARGS__) +#define CHECK_FALSE_MESSAGE(cond, ...) DOCTEST_CHECK_FALSE_MESSAGE(cond, __VA_ARGS__) +#define CHECK_THROWS_MESSAGE(expr, ...) DOCTEST_CHECK_THROWS_MESSAGE(expr, __VA_ARGS__) +#define CHECK_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, __VA_ARGS__) +#define CHECK_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, __VA_ARGS__) +#define CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, __VA_ARGS__) +#define CHECK_NOTHROW_MESSAGE(expr, ...) DOCTEST_CHECK_NOTHROW_MESSAGE(expr, __VA_ARGS__) +#define REQUIRE_MESSAGE(cond, ...) DOCTEST_REQUIRE_MESSAGE(cond, __VA_ARGS__) +#define REQUIRE_FALSE_MESSAGE(cond, ...) DOCTEST_REQUIRE_FALSE_MESSAGE(cond, __VA_ARGS__) +#define REQUIRE_THROWS_MESSAGE(expr, ...) DOCTEST_REQUIRE_THROWS_MESSAGE(expr, __VA_ARGS__) +#define REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, __VA_ARGS__) +#define REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, __VA_ARGS__) +#define REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, __VA_ARGS__) +#define REQUIRE_NOTHROW_MESSAGE(expr, ...) DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, __VA_ARGS__) + +#define SCENARIO(name) DOCTEST_SCENARIO(name) +#define SCENARIO_CLASS(name) DOCTEST_SCENARIO_CLASS(name) +#define SCENARIO_TEMPLATE(name, T, ...) DOCTEST_SCENARIO_TEMPLATE(name, T, __VA_ARGS__) +#define SCENARIO_TEMPLATE_DEFINE(name, T, id) DOCTEST_SCENARIO_TEMPLATE_DEFINE(name, T, id) +#define GIVEN(name) DOCTEST_GIVEN(name) +#define WHEN(name) DOCTEST_WHEN(name) +#define AND_WHEN(name) DOCTEST_AND_WHEN(name) +#define THEN(name) DOCTEST_THEN(name) +#define AND_THEN(name) DOCTEST_AND_THEN(name) + +#define WARN_EQ(...) DOCTEST_WARN_EQ(__VA_ARGS__) +#define CHECK_EQ(...) DOCTEST_CHECK_EQ(__VA_ARGS__) +#define REQUIRE_EQ(...) DOCTEST_REQUIRE_EQ(__VA_ARGS__) +#define WARN_NE(...) DOCTEST_WARN_NE(__VA_ARGS__) +#define CHECK_NE(...) DOCTEST_CHECK_NE(__VA_ARGS__) +#define REQUIRE_NE(...) DOCTEST_REQUIRE_NE(__VA_ARGS__) +#define WARN_GT(...) DOCTEST_WARN_GT(__VA_ARGS__) +#define CHECK_GT(...) DOCTEST_CHECK_GT(__VA_ARGS__) +#define REQUIRE_GT(...) DOCTEST_REQUIRE_GT(__VA_ARGS__) +#define WARN_LT(...) DOCTEST_WARN_LT(__VA_ARGS__) +#define CHECK_LT(...) DOCTEST_CHECK_LT(__VA_ARGS__) +#define REQUIRE_LT(...) DOCTEST_REQUIRE_LT(__VA_ARGS__) +#define WARN_GE(...) DOCTEST_WARN_GE(__VA_ARGS__) +#define CHECK_GE(...) DOCTEST_CHECK_GE(__VA_ARGS__) +#define REQUIRE_GE(...) DOCTEST_REQUIRE_GE(__VA_ARGS__) +#define WARN_LE(...) DOCTEST_WARN_LE(__VA_ARGS__) +#define CHECK_LE(...) DOCTEST_CHECK_LE(__VA_ARGS__) +#define REQUIRE_LE(...) DOCTEST_REQUIRE_LE(__VA_ARGS__) +#define WARN_UNARY(...) DOCTEST_WARN_UNARY(__VA_ARGS__) +#define CHECK_UNARY(...) DOCTEST_CHECK_UNARY(__VA_ARGS__) +#define REQUIRE_UNARY(...) DOCTEST_REQUIRE_UNARY(__VA_ARGS__) +#define WARN_UNARY_FALSE(...) DOCTEST_WARN_UNARY_FALSE(__VA_ARGS__) +#define CHECK_UNARY_FALSE(...) DOCTEST_CHECK_UNARY_FALSE(__VA_ARGS__) +#define REQUIRE_UNARY_FALSE(...) DOCTEST_REQUIRE_UNARY_FALSE(__VA_ARGS__) + +// KEPT FOR BACKWARDS COMPATIBILITY +#define FAST_WARN_EQ(...) DOCTEST_FAST_WARN_EQ(__VA_ARGS__) +#define FAST_CHECK_EQ(...) DOCTEST_FAST_CHECK_EQ(__VA_ARGS__) +#define FAST_REQUIRE_EQ(...) DOCTEST_FAST_REQUIRE_EQ(__VA_ARGS__) +#define FAST_WARN_NE(...) DOCTEST_FAST_WARN_NE(__VA_ARGS__) +#define FAST_CHECK_NE(...) DOCTEST_FAST_CHECK_NE(__VA_ARGS__) +#define FAST_REQUIRE_NE(...) DOCTEST_FAST_REQUIRE_NE(__VA_ARGS__) +#define FAST_WARN_GT(...) DOCTEST_FAST_WARN_GT(__VA_ARGS__) +#define FAST_CHECK_GT(...) DOCTEST_FAST_CHECK_GT(__VA_ARGS__) +#define FAST_REQUIRE_GT(...) DOCTEST_FAST_REQUIRE_GT(__VA_ARGS__) +#define FAST_WARN_LT(...) DOCTEST_FAST_WARN_LT(__VA_ARGS__) +#define FAST_CHECK_LT(...) DOCTEST_FAST_CHECK_LT(__VA_ARGS__) +#define FAST_REQUIRE_LT(...) DOCTEST_FAST_REQUIRE_LT(__VA_ARGS__) +#define FAST_WARN_GE(...) DOCTEST_FAST_WARN_GE(__VA_ARGS__) +#define FAST_CHECK_GE(...) DOCTEST_FAST_CHECK_GE(__VA_ARGS__) +#define FAST_REQUIRE_GE(...) DOCTEST_FAST_REQUIRE_GE(__VA_ARGS__) +#define FAST_WARN_LE(...) DOCTEST_FAST_WARN_LE(__VA_ARGS__) +#define FAST_CHECK_LE(...) DOCTEST_FAST_CHECK_LE(__VA_ARGS__) +#define FAST_REQUIRE_LE(...) DOCTEST_FAST_REQUIRE_LE(__VA_ARGS__) + +#define FAST_WARN_UNARY(...) DOCTEST_FAST_WARN_UNARY(__VA_ARGS__) +#define FAST_CHECK_UNARY(...) DOCTEST_FAST_CHECK_UNARY(__VA_ARGS__) +#define FAST_REQUIRE_UNARY(...) DOCTEST_FAST_REQUIRE_UNARY(__VA_ARGS__) +#define FAST_WARN_UNARY_FALSE(...) DOCTEST_FAST_WARN_UNARY_FALSE(__VA_ARGS__) +#define FAST_CHECK_UNARY_FALSE(...) DOCTEST_FAST_CHECK_UNARY_FALSE(__VA_ARGS__) +#define FAST_REQUIRE_UNARY_FALSE(...) DOCTEST_FAST_REQUIRE_UNARY_FALSE(__VA_ARGS__) + +#define TEST_CASE_TEMPLATE_INSTANTIATE(id, ...) DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE(id, __VA_ARGS__) + +#endif // DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES + +#ifndef DOCTEST_CONFIG_DISABLE + +// this is here to clear the 'current test suite' for the current translation unit - at the top +DOCTEST_TEST_SUITE_END(); + +#endif // DOCTEST_CONFIG_DISABLE + +DOCTEST_CLANG_SUPPRESS_WARNING_POP +DOCTEST_MSVC_SUPPRESS_WARNING_POP +DOCTEST_GCC_SUPPRESS_WARNING_POP + +DOCTEST_SUPPRESS_COMMON_WARNINGS_POP + +#endif // DOCTEST_LIBRARY_INCLUDED + +#ifndef DOCTEST_SINGLE_HEADER +#define DOCTEST_SINGLE_HEADER +#endif // DOCTEST_SINGLE_HEADER + +#if defined(DOCTEST_CONFIG_IMPLEMENT) || !defined(DOCTEST_SINGLE_HEADER) + +#ifndef DOCTEST_SINGLE_HEADER +#include "doctest_fwd.h" +#endif // DOCTEST_SINGLE_HEADER + +DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wunused-macros") + +#ifndef DOCTEST_LIBRARY_IMPLEMENTATION +#define DOCTEST_LIBRARY_IMPLEMENTATION + +DOCTEST_CLANG_SUPPRESS_WARNING_POP + +DOCTEST_SUPPRESS_COMMON_WARNINGS_PUSH + +DOCTEST_CLANG_SUPPRESS_WARNING_PUSH +DOCTEST_CLANG_SUPPRESS_WARNING("-Wglobal-constructors") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wsign-conversion") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wshorten-64-to-32") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-variable-declarations") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wswitch") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wswitch-enum") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wcovered-switch-default") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-noreturn") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wdisabled-macro-expansion") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-braces") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-field-initializers") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-member-function") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wnonportable-system-include-path") + +DOCTEST_GCC_SUPPRESS_WARNING_PUSH +DOCTEST_GCC_SUPPRESS_WARNING("-Wconversion") +DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-conversion") +DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-field-initializers") +DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-braces") +DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch") +DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch-enum") +DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch-default") +DOCTEST_GCC_SUPPRESS_WARNING("-Wunsafe-loop-optimizations") +DOCTEST_GCC_SUPPRESS_WARNING("-Wold-style-cast") +DOCTEST_GCC_SUPPRESS_WARNING("-Wunused-function") +DOCTEST_GCC_SUPPRESS_WARNING("-Wmultiple-inheritance") +DOCTEST_GCC_SUPPRESS_WARNING("-Wsuggest-attribute") + +DOCTEST_MSVC_SUPPRESS_WARNING_PUSH +DOCTEST_MSVC_SUPPRESS_WARNING(4267) // 'var' : conversion from 'x' to 'y', possible loss of data +DOCTEST_MSVC_SUPPRESS_WARNING(4530) // C++ exception handler used, but unwind semantics not enabled +DOCTEST_MSVC_SUPPRESS_WARNING(4577) // 'noexcept' used with no exception handling mode specified +DOCTEST_MSVC_SUPPRESS_WARNING(4774) // format string expected in argument is not a string literal +DOCTEST_MSVC_SUPPRESS_WARNING(4365) // conversion from 'int' to 'unsigned', signed/unsigned mismatch +DOCTEST_MSVC_SUPPRESS_WARNING(5039) // pointer to potentially throwing function passed to extern C +DOCTEST_MSVC_SUPPRESS_WARNING(4800) // forcing value to bool 'true' or 'false' (performance warning) +DOCTEST_MSVC_SUPPRESS_WARNING(5245) // unreferenced function with internal linkage has been removed + +DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN + +// required includes - will go only in one translation unit! +#include +#include +#include +// borland (Embarcadero) compiler requires math.h and not cmath - https://github.com/doctest/doctest/pull/37 +#ifdef __BORLANDC__ +#include +#endif // __BORLANDC__ +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM +#include +#endif // DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM +#include +#include +#include +#ifndef DOCTEST_CONFIG_NO_MULTITHREADING +#include +#include +#define DOCTEST_DECLARE_MUTEX(name) std::mutex name; +#define DOCTEST_DECLARE_STATIC_MUTEX(name) static DOCTEST_DECLARE_MUTEX(name) +#define DOCTEST_LOCK_MUTEX(name) std::lock_guard DOCTEST_ANONYMOUS(DOCTEST_ANON_LOCK_)(name); +#else // DOCTEST_CONFIG_NO_MULTITHREADING +#define DOCTEST_DECLARE_MUTEX(name) +#define DOCTEST_DECLARE_STATIC_MUTEX(name) +#define DOCTEST_LOCK_MUTEX(name) +#endif // DOCTEST_CONFIG_NO_MULTITHREADING +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef DOCTEST_PLATFORM_MAC +#include +#include +#include +#endif // DOCTEST_PLATFORM_MAC + +#ifdef DOCTEST_PLATFORM_WINDOWS + +// defines for a leaner windows.h +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#define DOCTEST_UNDEF_WIN32_LEAN_AND_MEAN +#endif // WIN32_LEAN_AND_MEAN +#ifndef NOMINMAX +#define NOMINMAX +#define DOCTEST_UNDEF_NOMINMAX +#endif // NOMINMAX + +// not sure what AfxWin.h is for - here I do what Catch does +#ifdef __AFXDLL +#include +#else +#include +#endif +#include + +#else // DOCTEST_PLATFORM_WINDOWS + +#include +#include + +#endif // DOCTEST_PLATFORM_WINDOWS + +// this is a fix for https://github.com/doctest/doctest/issues/348 +// https://mail.gnome.org/archives/xml/2012-January/msg00000.html +#if !defined(HAVE_UNISTD_H) && !defined(STDOUT_FILENO) +#define STDOUT_FILENO fileno(stdout) +#endif // HAVE_UNISTD_H + +DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END + +// counts the number of elements in a C array +#define DOCTEST_COUNTOF(x) (sizeof(x) / sizeof(x[0])) + +#ifdef DOCTEST_CONFIG_DISABLE +#define DOCTEST_BRANCH_ON_DISABLED(if_disabled, if_not_disabled) if_disabled +#else // DOCTEST_CONFIG_DISABLE +#define DOCTEST_BRANCH_ON_DISABLED(if_disabled, if_not_disabled) if_not_disabled +#endif // DOCTEST_CONFIG_DISABLE + +#ifndef DOCTEST_CONFIG_OPTIONS_PREFIX +#define DOCTEST_CONFIG_OPTIONS_PREFIX "dt-" +#endif + +#ifndef DOCTEST_CONFIG_OPTIONS_FILE_PREFIX_SEPARATOR +#define DOCTEST_CONFIG_OPTIONS_FILE_PREFIX_SEPARATOR ':' +#endif + +#ifndef DOCTEST_THREAD_LOCAL +#if defined(DOCTEST_CONFIG_NO_MULTITHREADING) || DOCTEST_MSVC && (DOCTEST_MSVC < DOCTEST_COMPILER(19, 0, 0)) +#define DOCTEST_THREAD_LOCAL +#else // DOCTEST_MSVC +#define DOCTEST_THREAD_LOCAL thread_local +#endif // DOCTEST_MSVC +#endif // DOCTEST_THREAD_LOCAL + +#ifndef DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES +#define DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES 32 +#endif + +#ifndef DOCTEST_MULTI_LANE_ATOMICS_CACHE_LINE_SIZE +#define DOCTEST_MULTI_LANE_ATOMICS_CACHE_LINE_SIZE 64 +#endif + +#ifdef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS +#define DOCTEST_OPTIONS_PREFIX_DISPLAY DOCTEST_CONFIG_OPTIONS_PREFIX +#else +#define DOCTEST_OPTIONS_PREFIX_DISPLAY "" +#endif + +#if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) +#define DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS +#endif + +#ifndef DOCTEST_CDECL +#define DOCTEST_CDECL __cdecl +#endif + +namespace doctest { + +bool is_running_in_test = false; + +namespace { + using namespace detail; + + template + DOCTEST_NORETURN void throw_exception(Ex const& e) { +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + throw e; +#else // DOCTEST_CONFIG_NO_EXCEPTIONS +#ifdef DOCTEST_CONFIG_HANDLE_EXCEPTION + DOCTEST_CONFIG_HANDLE_EXCEPTION(e); +#else // DOCTEST_CONFIG_HANDLE_EXCEPTION +#ifndef DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM + std::cerr << "doctest will terminate because it needed to throw an exception.\n" + << "The message was: " << e.what() << '\n'; +#endif // DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM +#endif // DOCTEST_CONFIG_HANDLE_EXCEPTION + std::terminate(); +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + } + +#ifndef DOCTEST_INTERNAL_ERROR +#define DOCTEST_INTERNAL_ERROR(msg) \ + throw_exception(std::logic_error( \ + __FILE__ ":" DOCTEST_TOSTR(__LINE__) ": Internal doctest error: " msg)) +#endif // DOCTEST_INTERNAL_ERROR + + // case insensitive strcmp + int stricmp(const char* a, const char* b) { + for(;; a++, b++) { + const int d = tolower(*a) - tolower(*b); + if(d != 0 || !*a) + return d; + } + } + + struct Endianness + { + enum Arch + { + Big, + Little + }; + + static Arch which() { + int x = 1; + // casting any data pointer to char* is allowed + auto ptr = reinterpret_cast(&x); + if(*ptr) + return Little; + return Big; + } + }; +} // namespace + +namespace detail { + DOCTEST_THREAD_LOCAL class + { + std::vector stack; + std::stringstream ss; + + public: + std::ostream* push() { + stack.push_back(ss.tellp()); + return &ss; + } + + String pop() { + if (stack.empty()) + DOCTEST_INTERNAL_ERROR("TLSS was empty when trying to pop!"); + + std::streampos pos = stack.back(); + stack.pop_back(); + unsigned sz = static_cast(ss.tellp() - pos); + ss.rdbuf()->pubseekpos(pos, std::ios::in | std::ios::out); + return String(ss, sz); + } + } g_oss; + + std::ostream* tlssPush() { + return g_oss.push(); + } + + String tlssPop() { + return g_oss.pop(); + } + +#ifndef DOCTEST_CONFIG_DISABLE + +namespace timer_large_integer +{ + +#if defined(DOCTEST_PLATFORM_WINDOWS) + using type = ULONGLONG; +#else // DOCTEST_PLATFORM_WINDOWS + using type = std::uint64_t; +#endif // DOCTEST_PLATFORM_WINDOWS +} + +using ticks_t = timer_large_integer::type; + +#ifdef DOCTEST_CONFIG_GETCURRENTTICKS + ticks_t getCurrentTicks() { return DOCTEST_CONFIG_GETCURRENTTICKS(); } +#elif defined(DOCTEST_PLATFORM_WINDOWS) + ticks_t getCurrentTicks() { + static LARGE_INTEGER hz = { {0} }, hzo = { {0} }; + if(!hz.QuadPart) { + QueryPerformanceFrequency(&hz); + QueryPerformanceCounter(&hzo); + } + LARGE_INTEGER t; + QueryPerformanceCounter(&t); + return ((t.QuadPart - hzo.QuadPart) * LONGLONG(1000000)) / hz.QuadPart; + } +#else // DOCTEST_PLATFORM_WINDOWS + ticks_t getCurrentTicks() { + timeval t; + gettimeofday(&t, nullptr); + return static_cast(t.tv_sec) * 1000000 + static_cast(t.tv_usec); + } +#endif // DOCTEST_PLATFORM_WINDOWS + + struct Timer + { + void start() { m_ticks = getCurrentTicks(); } + unsigned int getElapsedMicroseconds() const { + return static_cast(getCurrentTicks() - m_ticks); + } + //unsigned int getElapsedMilliseconds() const { + // return static_cast(getElapsedMicroseconds() / 1000); + //} + double getElapsedSeconds() const { return static_cast(getCurrentTicks() - m_ticks) / 1000000.0; } + + private: + ticks_t m_ticks = 0; + }; + +#ifdef DOCTEST_CONFIG_NO_MULTITHREADING + template + using Atomic = T; +#else // DOCTEST_CONFIG_NO_MULTITHREADING + template + using Atomic = std::atomic; +#endif // DOCTEST_CONFIG_NO_MULTITHREADING + +#if defined(DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS) || defined(DOCTEST_CONFIG_NO_MULTITHREADING) + template + using MultiLaneAtomic = Atomic; +#else // DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS + // Provides a multilane implementation of an atomic variable that supports add, sub, load, + // store. Instead of using a single atomic variable, this splits up into multiple ones, + // each sitting on a separate cache line. The goal is to provide a speedup when most + // operations are modifying. It achieves this with two properties: + // + // * Multiple atomics are used, so chance of congestion from the same atomic is reduced. + // * Each atomic sits on a separate cache line, so false sharing is reduced. + // + // The disadvantage is that there is a small overhead due to the use of TLS, and load/store + // is slower because all atomics have to be accessed. + template + class MultiLaneAtomic + { + struct CacheLineAlignedAtomic + { + Atomic atomic{}; + char padding[DOCTEST_MULTI_LANE_ATOMICS_CACHE_LINE_SIZE - sizeof(Atomic)]; + }; + CacheLineAlignedAtomic m_atomics[DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES]; + + static_assert(sizeof(CacheLineAlignedAtomic) == DOCTEST_MULTI_LANE_ATOMICS_CACHE_LINE_SIZE, + "guarantee one atomic takes exactly one cache line"); + + public: + T operator++() DOCTEST_NOEXCEPT { return fetch_add(1) + 1; } + + T operator++(int) DOCTEST_NOEXCEPT { return fetch_add(1); } + + T fetch_add(T arg, std::memory_order order = std::memory_order_seq_cst) DOCTEST_NOEXCEPT { + return myAtomic().fetch_add(arg, order); + } + + T fetch_sub(T arg, std::memory_order order = std::memory_order_seq_cst) DOCTEST_NOEXCEPT { + return myAtomic().fetch_sub(arg, order); + } + + operator T() const DOCTEST_NOEXCEPT { return load(); } + + T load(std::memory_order order = std::memory_order_seq_cst) const DOCTEST_NOEXCEPT { + auto result = T(); + for(auto const& c : m_atomics) { + result += c.atomic.load(order); + } + return result; + } + + T operator=(T desired) DOCTEST_NOEXCEPT { // lgtm [cpp/assignment-does-not-return-this] + store(desired); + return desired; + } + + void store(T desired, std::memory_order order = std::memory_order_seq_cst) DOCTEST_NOEXCEPT { + // first value becomes desired", all others become 0. + for(auto& c : m_atomics) { + c.atomic.store(desired, order); + desired = {}; + } + } + + private: + // Each thread has a different atomic that it operates on. If more than NumLanes threads + // use this, some will use the same atomic. So performance will degrade a bit, but still + // everything will work. + // + // The logic here is a bit tricky. The call should be as fast as possible, so that there + // is minimal to no overhead in determining the correct atomic for the current thread. + // + // 1. A global static counter laneCounter counts continuously up. + // 2. Each successive thread will use modulo operation of that counter so it gets an atomic + // assigned in a round-robin fashion. + // 3. This tlsLaneIdx is stored in the thread local data, so it is directly available with + // little overhead. + Atomic& myAtomic() DOCTEST_NOEXCEPT { + static Atomic laneCounter; + DOCTEST_THREAD_LOCAL size_t tlsLaneIdx = + laneCounter++ % DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES; + + return m_atomics[tlsLaneIdx].atomic; + } + }; +#endif // DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS + + // this holds both parameters from the command line and runtime data for tests + struct ContextState : ContextOptions, TestRunStats, CurrentTestCaseStats + { + MultiLaneAtomic numAssertsCurrentTest_atomic; + MultiLaneAtomic numAssertsFailedCurrentTest_atomic; + + std::vector> filters = decltype(filters)(9); // 9 different filters + + std::vector reporters_currently_used; + + assert_handler ah = nullptr; + + Timer timer; + + std::vector stringifiedContexts; // logging from INFO() due to an exception + + // stuff for subcases + bool reachedLeaf; + std::vector subcaseStack; + std::vector nextSubcaseStack; + std::unordered_set fullyTraversedSubcases; + size_t currentSubcaseDepth; + Atomic shouldLogCurrentException; + + void resetRunData() { + numTestCases = 0; + numTestCasesPassingFilters = 0; + numTestSuitesPassingFilters = 0; + numTestCasesFailed = 0; + numAsserts = 0; + numAssertsFailed = 0; + numAssertsCurrentTest = 0; + numAssertsFailedCurrentTest = 0; + } + + void finalizeTestCaseData() { + seconds = timer.getElapsedSeconds(); + + // update the non-atomic counters + numAsserts += numAssertsCurrentTest_atomic; + numAssertsFailed += numAssertsFailedCurrentTest_atomic; + numAssertsCurrentTest = numAssertsCurrentTest_atomic; + numAssertsFailedCurrentTest = numAssertsFailedCurrentTest_atomic; + + if(numAssertsFailedCurrentTest) + failure_flags |= TestCaseFailureReason::AssertFailure; + + if(Approx(currentTest->m_timeout).epsilon(DBL_EPSILON) != 0 && + Approx(seconds).epsilon(DBL_EPSILON) > currentTest->m_timeout) + failure_flags |= TestCaseFailureReason::Timeout; + + if(currentTest->m_should_fail) { + if(failure_flags) { + failure_flags |= TestCaseFailureReason::ShouldHaveFailedAndDid; + } else { + failure_flags |= TestCaseFailureReason::ShouldHaveFailedButDidnt; + } + } else if(failure_flags && currentTest->m_may_fail) { + failure_flags |= TestCaseFailureReason::CouldHaveFailedAndDid; + } else if(currentTest->m_expected_failures > 0) { + if(numAssertsFailedCurrentTest == currentTest->m_expected_failures) { + failure_flags |= TestCaseFailureReason::FailedExactlyNumTimes; + } else { + failure_flags |= TestCaseFailureReason::DidntFailExactlyNumTimes; + } + } + + bool ok_to_fail = (TestCaseFailureReason::ShouldHaveFailedAndDid & failure_flags) || + (TestCaseFailureReason::CouldHaveFailedAndDid & failure_flags) || + (TestCaseFailureReason::FailedExactlyNumTimes & failure_flags); + + // if any subcase has failed - the whole test case has failed + testCaseSuccess = !(failure_flags && !ok_to_fail); + if(!testCaseSuccess) + numTestCasesFailed++; + } + }; + + ContextState* g_cs = nullptr; + + // used to avoid locks for the debug output + // TODO: figure out if this is indeed necessary/correct - seems like either there still + // could be a race or that there wouldn't be a race even if using the context directly + DOCTEST_THREAD_LOCAL bool g_no_colors; + +#endif // DOCTEST_CONFIG_DISABLE +} // namespace detail + +char* String::allocate(size_type sz) { + if (sz <= last) { + buf[sz] = '\0'; + setLast(last - sz); + return buf; + } else { + setOnHeap(); + data.size = sz; + data.capacity = data.size + 1; + data.ptr = new char[data.capacity]; + data.ptr[sz] = '\0'; + return data.ptr; + } +} + +void String::setOnHeap() noexcept { *reinterpret_cast(&buf[last]) = 128; } +void String::setLast(size_type in) noexcept { buf[last] = char(in); } +void String::setSize(size_type sz) noexcept { + if (isOnStack()) { buf[sz] = '\0'; setLast(last - sz); } + else { data.ptr[sz] = '\0'; data.size = sz; } +} + +void String::copy(const String& other) { + if(other.isOnStack()) { + memcpy(buf, other.buf, len); + } else { + memcpy(allocate(other.data.size), other.data.ptr, other.data.size); + } +} + +String::String() noexcept { + buf[0] = '\0'; + setLast(); +} + +String::~String() { + if(!isOnStack()) + delete[] data.ptr; +} // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) + +String::String(const char* in) + : String(in, strlen(in)) {} + +String::String(const char* in, size_type in_size) { + memcpy(allocate(in_size), in, in_size); +} + +String::String(std::istream& in, size_type in_size) { + in.read(allocate(in_size), in_size); +} + +String::String(const String& other) { copy(other); } + +String& String::operator=(const String& other) { + if(this != &other) { + if(!isOnStack()) + delete[] data.ptr; + + copy(other); + } + + return *this; +} + +String& String::operator+=(const String& other) { + const size_type my_old_size = size(); + const size_type other_size = other.size(); + const size_type total_size = my_old_size + other_size; + if(isOnStack()) { + if(total_size < len) { + // append to the current stack space + memcpy(buf + my_old_size, other.c_str(), other_size + 1); + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) + setLast(last - total_size); + } else { + // alloc new chunk + char* temp = new char[total_size + 1]; + // copy current data to new location before writing in the union + memcpy(temp, buf, my_old_size); // skip the +1 ('\0') for speed + // update data in union + setOnHeap(); + data.size = total_size; + data.capacity = data.size + 1; + data.ptr = temp; + // transfer the rest of the data + memcpy(data.ptr + my_old_size, other.c_str(), other_size + 1); + } + } else { + if(data.capacity > total_size) { + // append to the current heap block + data.size = total_size; + memcpy(data.ptr + my_old_size, other.c_str(), other_size + 1); + } else { + // resize + data.capacity *= 2; + if(data.capacity <= total_size) + data.capacity = total_size + 1; + // alloc new chunk + char* temp = new char[data.capacity]; + // copy current data to new location before releasing it + memcpy(temp, data.ptr, my_old_size); // skip the +1 ('\0') for speed + // release old chunk + delete[] data.ptr; + // update the rest of the union members + data.size = total_size; + data.ptr = temp; + // transfer the rest of the data + memcpy(data.ptr + my_old_size, other.c_str(), other_size + 1); + } + } + + return *this; +} + +String::String(String&& other) noexcept { + memcpy(buf, other.buf, len); + other.buf[0] = '\0'; + other.setLast(); +} + +String& String::operator=(String&& other) noexcept { + if(this != &other) { + if(!isOnStack()) + delete[] data.ptr; + memcpy(buf, other.buf, len); + other.buf[0] = '\0'; + other.setLast(); + } + return *this; +} + +char String::operator[](size_type i) const { + return const_cast(this)->operator[](i); +} + +char& String::operator[](size_type i) { + if(isOnStack()) + return reinterpret_cast(buf)[i]; + return data.ptr[i]; +} + +DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wmaybe-uninitialized") +String::size_type String::size() const { + if(isOnStack()) + return last - (size_type(buf[last]) & 31); // using "last" would work only if "len" is 32 + return data.size; +} +DOCTEST_GCC_SUPPRESS_WARNING_POP + +String::size_type String::capacity() const { + if(isOnStack()) + return len; + return data.capacity; +} + +String String::substr(size_type pos, size_type cnt) && { + cnt = std::min(cnt, size() - pos); + char* cptr = c_str(); + memmove(cptr, cptr + pos, cnt); + setSize(cnt); + return std::move(*this); +} + +String String::substr(size_type pos, size_type cnt) const & { + cnt = std::min(cnt, size() - pos); + return String{ c_str() + pos, cnt }; +} + +String::size_type String::find(char ch, size_type pos) const { + const char* begin = c_str(); + const char* end = begin + size(); + const char* it = begin + pos; + for (; it < end && *it != ch; it++); + if (it < end) { return static_cast(it - begin); } + else { return npos; } +} + +String::size_type String::rfind(char ch, size_type pos) const { + const char* begin = c_str(); + const char* it = begin + std::min(pos, size() - 1); + for (; it >= begin && *it != ch; it--); + if (it >= begin) { return static_cast(it - begin); } + else { return npos; } +} + +int String::compare(const char* other, bool no_case) const { + if(no_case) + return doctest::stricmp(c_str(), other); + return std::strcmp(c_str(), other); +} + +int String::compare(const String& other, bool no_case) const { + return compare(other.c_str(), no_case); +} + +String operator+(const String& lhs, const String& rhs) { return String(lhs) += rhs; } + +bool operator==(const String& lhs, const String& rhs) { return lhs.compare(rhs) == 0; } +bool operator!=(const String& lhs, const String& rhs) { return lhs.compare(rhs) != 0; } +bool operator< (const String& lhs, const String& rhs) { return lhs.compare(rhs) < 0; } +bool operator> (const String& lhs, const String& rhs) { return lhs.compare(rhs) > 0; } +bool operator<=(const String& lhs, const String& rhs) { return (lhs != rhs) ? lhs.compare(rhs) < 0 : true; } +bool operator>=(const String& lhs, const String& rhs) { return (lhs != rhs) ? lhs.compare(rhs) > 0 : true; } + +std::ostream& operator<<(std::ostream& s, const String& in) { return s << in.c_str(); } + +Contains::Contains(const String& str) : string(str) { } + +bool Contains::checkWith(const String& other) const { + return strstr(other.c_str(), string.c_str()) != nullptr; +} + +String toString(const Contains& in) { + return "Contains( " + in.string + " )"; +} + +bool operator==(const String& lhs, const Contains& rhs) { return rhs.checkWith(lhs); } +bool operator==(const Contains& lhs, const String& rhs) { return lhs.checkWith(rhs); } +bool operator!=(const String& lhs, const Contains& rhs) { return !rhs.checkWith(lhs); } +bool operator!=(const Contains& lhs, const String& rhs) { return !lhs.checkWith(rhs); } + +namespace { + void color_to_stream(std::ostream&, Color::Enum) DOCTEST_BRANCH_ON_DISABLED({}, ;) +} // namespace + +namespace Color { + std::ostream& operator<<(std::ostream& s, Color::Enum code) { + color_to_stream(s, code); + return s; + } +} // namespace Color + +// clang-format off +const char* assertString(assertType::Enum at) { + DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4061) // enum 'x' in switch of enum 'y' is not explicitly handled + #define DOCTEST_GENERATE_ASSERT_TYPE_CASE(assert_type) case assertType::DT_ ## assert_type: return #assert_type + #define DOCTEST_GENERATE_ASSERT_TYPE_CASES(assert_type) \ + DOCTEST_GENERATE_ASSERT_TYPE_CASE(WARN_ ## assert_type); \ + DOCTEST_GENERATE_ASSERT_TYPE_CASE(CHECK_ ## assert_type); \ + DOCTEST_GENERATE_ASSERT_TYPE_CASE(REQUIRE_ ## assert_type) + switch(at) { + DOCTEST_GENERATE_ASSERT_TYPE_CASE(WARN); + DOCTEST_GENERATE_ASSERT_TYPE_CASE(CHECK); + DOCTEST_GENERATE_ASSERT_TYPE_CASE(REQUIRE); + + DOCTEST_GENERATE_ASSERT_TYPE_CASES(FALSE); + + DOCTEST_GENERATE_ASSERT_TYPE_CASES(THROWS); + + DOCTEST_GENERATE_ASSERT_TYPE_CASES(THROWS_AS); + + DOCTEST_GENERATE_ASSERT_TYPE_CASES(THROWS_WITH); + + DOCTEST_GENERATE_ASSERT_TYPE_CASES(THROWS_WITH_AS); + + DOCTEST_GENERATE_ASSERT_TYPE_CASES(NOTHROW); + + DOCTEST_GENERATE_ASSERT_TYPE_CASES(EQ); + DOCTEST_GENERATE_ASSERT_TYPE_CASES(NE); + DOCTEST_GENERATE_ASSERT_TYPE_CASES(GT); + DOCTEST_GENERATE_ASSERT_TYPE_CASES(LT); + DOCTEST_GENERATE_ASSERT_TYPE_CASES(GE); + DOCTEST_GENERATE_ASSERT_TYPE_CASES(LE); + + DOCTEST_GENERATE_ASSERT_TYPE_CASES(UNARY); + DOCTEST_GENERATE_ASSERT_TYPE_CASES(UNARY_FALSE); + + default: DOCTEST_INTERNAL_ERROR("Tried stringifying invalid assert type!"); + } + DOCTEST_MSVC_SUPPRESS_WARNING_POP +} +// clang-format on + +const char* failureString(assertType::Enum at) { + if(at & assertType::is_warn) //!OCLINT bitwise operator in conditional + return "WARNING"; + if(at & assertType::is_check) //!OCLINT bitwise operator in conditional + return "ERROR"; + if(at & assertType::is_require) //!OCLINT bitwise operator in conditional + return "FATAL ERROR"; + return ""; +} + +DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wnull-dereference") +DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wnull-dereference") +// depending on the current options this will remove the path of filenames +const char* skipPathFromFilename(const char* file) { +#ifndef DOCTEST_CONFIG_DISABLE + if(getContextOptions()->no_path_in_filenames) { + auto back = std::strrchr(file, '\\'); + auto forward = std::strrchr(file, '/'); + if(back || forward) { + if(back > forward) + forward = back; + return forward + 1; + } + } else { + const auto prefixes = getContextOptions()->strip_file_prefixes; + const char separator = DOCTEST_CONFIG_OPTIONS_FILE_PREFIX_SEPARATOR; + String::size_type longest_match = 0U; + for(String::size_type pos = 0U; pos < prefixes.size(); ++pos) + { + const auto prefix_start = pos; + pos = std::min(prefixes.find(separator, prefix_start), prefixes.size()); + + const auto prefix_size = pos - prefix_start; + if(prefix_size > longest_match) + { + // TODO under DOCTEST_MSVC: does the comparison need strnicmp() to work with drive letter capitalization? + if(0 == std::strncmp(prefixes.c_str() + prefix_start, file, prefix_size)) + { + longest_match = prefix_size; + } + } + } + return &file[longest_match]; + } +#endif // DOCTEST_CONFIG_DISABLE + return file; +} +DOCTEST_CLANG_SUPPRESS_WARNING_POP +DOCTEST_GCC_SUPPRESS_WARNING_POP + +bool SubcaseSignature::operator==(const SubcaseSignature& other) const { + return m_line == other.m_line + && std::strcmp(m_file, other.m_file) == 0 + && m_name == other.m_name; +} + +bool SubcaseSignature::operator<(const SubcaseSignature& other) const { + if(m_line != other.m_line) + return m_line < other.m_line; + if(std::strcmp(m_file, other.m_file) != 0) + return std::strcmp(m_file, other.m_file) < 0; + return m_name.compare(other.m_name) < 0; +} + +DOCTEST_DEFINE_INTERFACE(IContextScope) + +namespace detail { + void filldata::fill(std::ostream* stream, const void* in) { + if (in) { *stream << in; } + else { *stream << "nullptr"; } + } + + template + String toStreamLit(T t) { + std::ostream* os = tlssPush(); + os->operator<<(t); + return tlssPop(); + } +} + +#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +String toString(const char* in) { return String("\"") + (in ? in : "{null string}") + "\""; } +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + +#if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0) +// see this issue on why this is needed: https://github.com/doctest/doctest/issues/183 +String toString(const std::string& in) { return in.c_str(); } +#endif // VS 2019 + +String toString(String in) { return in; } + +String toString(std::nullptr_t) { return "nullptr"; } + +String toString(bool in) { return in ? "true" : "false"; } + +String toString(float in) { return toStreamLit(in); } +String toString(double in) { return toStreamLit(in); } +String toString(double long in) { return toStreamLit(in); } + +String toString(char in) { return toStreamLit(static_cast(in)); } +String toString(char signed in) { return toStreamLit(static_cast(in)); } +String toString(char unsigned in) { return toStreamLit(static_cast(in)); } +String toString(short in) { return toStreamLit(in); } +String toString(short unsigned in) { return toStreamLit(in); } +String toString(signed in) { return toStreamLit(in); } +String toString(unsigned in) { return toStreamLit(in); } +String toString(long in) { return toStreamLit(in); } +String toString(long unsigned in) { return toStreamLit(in); } +String toString(long long in) { return toStreamLit(in); } +String toString(long long unsigned in) { return toStreamLit(in); } + +Approx::Approx(double value) + : m_epsilon(static_cast(std::numeric_limits::epsilon()) * 100) + , m_scale(1.0) + , m_value(value) {} + +Approx Approx::operator()(double value) const { + Approx approx(value); + approx.epsilon(m_epsilon); + approx.scale(m_scale); + return approx; +} + +Approx& Approx::epsilon(double newEpsilon) { + m_epsilon = newEpsilon; + return *this; +} +Approx& Approx::scale(double newScale) { + m_scale = newScale; + return *this; +} + +bool operator==(double lhs, const Approx& rhs) { + // Thanks to Richard Harris for his help refining this formula + return std::fabs(lhs - rhs.m_value) < + rhs.m_epsilon * (rhs.m_scale + std::max(std::fabs(lhs), std::fabs(rhs.m_value))); +} +bool operator==(const Approx& lhs, double rhs) { return operator==(rhs, lhs); } +bool operator!=(double lhs, const Approx& rhs) { return !operator==(lhs, rhs); } +bool operator!=(const Approx& lhs, double rhs) { return !operator==(rhs, lhs); } +bool operator<=(double lhs, const Approx& rhs) { return lhs < rhs.m_value || lhs == rhs; } +bool operator<=(const Approx& lhs, double rhs) { return lhs.m_value < rhs || lhs == rhs; } +bool operator>=(double lhs, const Approx& rhs) { return lhs > rhs.m_value || lhs == rhs; } +bool operator>=(const Approx& lhs, double rhs) { return lhs.m_value > rhs || lhs == rhs; } +bool operator<(double lhs, const Approx& rhs) { return lhs < rhs.m_value && lhs != rhs; } +bool operator<(const Approx& lhs, double rhs) { return lhs.m_value < rhs && lhs != rhs; } +bool operator>(double lhs, const Approx& rhs) { return lhs > rhs.m_value && lhs != rhs; } +bool operator>(const Approx& lhs, double rhs) { return lhs.m_value > rhs && lhs != rhs; } + +String toString(const Approx& in) { + return "Approx( " + doctest::toString(in.m_value) + " )"; +} +const ContextOptions* getContextOptions() { return DOCTEST_BRANCH_ON_DISABLED(nullptr, g_cs); } + +DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4738) +template +IsNaN::operator bool() const { + return std::isnan(value) ^ flipped; +} +DOCTEST_MSVC_SUPPRESS_WARNING_POP +template struct DOCTEST_INTERFACE_DEF IsNaN; +template struct DOCTEST_INTERFACE_DEF IsNaN; +template struct DOCTEST_INTERFACE_DEF IsNaN; +template +String toString(IsNaN in) { return String(in.flipped ? "! " : "") + "IsNaN( " + doctest::toString(in.value) + " )"; } +String toString(IsNaN in) { return toString(in); } +String toString(IsNaN in) { return toString(in); } +String toString(IsNaN in) { return toString(in); } + +} // namespace doctest + +#ifdef DOCTEST_CONFIG_DISABLE +namespace doctest { +Context::Context(int, const char* const*) {} +Context::~Context() = default; +void Context::applyCommandLine(int, const char* const*) {} +void Context::addFilter(const char*, const char*) {} +void Context::clearFilters() {} +void Context::setOption(const char*, bool) {} +void Context::setOption(const char*, int) {} +void Context::setOption(const char*, const char*) {} +bool Context::shouldExit() { return false; } +void Context::setAsDefaultForAssertsOutOfTestCases() {} +void Context::setAssertHandler(detail::assert_handler) {} +void Context::setCout(std::ostream*) {} +int Context::run() { return 0; } + +int IReporter::get_num_active_contexts() { return 0; } +const IContextScope* const* IReporter::get_active_contexts() { return nullptr; } +int IReporter::get_num_stringified_contexts() { return 0; } +const String* IReporter::get_stringified_contexts() { return nullptr; } + +int registerReporter(const char*, int, IReporter*) { return 0; } + +} // namespace doctest +#else // DOCTEST_CONFIG_DISABLE + +#if !defined(DOCTEST_CONFIG_COLORS_NONE) +#if !defined(DOCTEST_CONFIG_COLORS_WINDOWS) && !defined(DOCTEST_CONFIG_COLORS_ANSI) +#ifdef DOCTEST_PLATFORM_WINDOWS +#define DOCTEST_CONFIG_COLORS_WINDOWS +#else // linux +#define DOCTEST_CONFIG_COLORS_ANSI +#endif // platform +#endif // DOCTEST_CONFIG_COLORS_WINDOWS && DOCTEST_CONFIG_COLORS_ANSI +#endif // DOCTEST_CONFIG_COLORS_NONE + +namespace doctest_detail_test_suite_ns { +// holds the current test suite +doctest::detail::TestSuite& getCurrentTestSuite() { + static doctest::detail::TestSuite data{}; + return data; +} +} // namespace doctest_detail_test_suite_ns + +namespace doctest { +namespace { + // the int (priority) is part of the key for automatic sorting - sadly one can register a + // reporter with a duplicate name and a different priority but hopefully that won't happen often :| + using reporterMap = std::map, reporterCreatorFunc>; + + reporterMap& getReporters() { + static reporterMap data; + return data; + } + reporterMap& getListeners() { + static reporterMap data; + return data; + } +} // namespace +namespace detail { +#define DOCTEST_ITERATE_THROUGH_REPORTERS(function, ...) \ + for(auto& curr_rep : g_cs->reporters_currently_used) \ + curr_rep->function(__VA_ARGS__) + + bool checkIfShouldThrow(assertType::Enum at) { + if(at & assertType::is_require) //!OCLINT bitwise operator in conditional + return true; + + if((at & assertType::is_check) //!OCLINT bitwise operator in conditional + && getContextOptions()->abort_after > 0 && + (g_cs->numAssertsFailed + g_cs->numAssertsFailedCurrentTest_atomic) >= + getContextOptions()->abort_after) + return true; + + return false; + } + +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + DOCTEST_NORETURN void throwException() { + g_cs->shouldLogCurrentException = false; + throw TestFailureException(); // NOLINT(hicpp-exception-baseclass) + } +#else // DOCTEST_CONFIG_NO_EXCEPTIONS + void throwException() {} +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS +} // namespace detail + +namespace { + using namespace detail; + // matching of a string against a wildcard mask (case sensitivity configurable) taken from + // https://www.codeproject.com/Articles/1088/Wildcard-string-compare-globbing + int wildcmp(const char* str, const char* wild, bool caseSensitive) { + const char* cp = str; + const char* mp = wild; + + while((*str) && (*wild != '*')) { + if((caseSensitive ? (*wild != *str) : (tolower(*wild) != tolower(*str))) && + (*wild != '?')) { + return 0; + } + wild++; + str++; + } + + while(*str) { + if(*wild == '*') { + if(!*++wild) { + return 1; + } + mp = wild; + cp = str + 1; + } else if((caseSensitive ? (*wild == *str) : (tolower(*wild) == tolower(*str))) || + (*wild == '?')) { + wild++; + str++; + } else { + wild = mp; //!OCLINT parameter reassignment + str = cp++; //!OCLINT parameter reassignment + } + } + + while(*wild == '*') { + wild++; + } + return !*wild; + } + + // checks if the name matches any of the filters (and can be configured what to do when empty) + bool matchesAny(const char* name, const std::vector& filters, bool matchEmpty, + bool caseSensitive) { + if (filters.empty() && matchEmpty) + return true; + for (auto& curr : filters) + if (wildcmp(name, curr.c_str(), caseSensitive)) + return true; + return false; + } + + DOCTEST_NO_SANITIZE_INTEGER + unsigned long long hash(unsigned long long a, unsigned long long b) { + return (a << 5) + b; + } + + // C string hash function (djb2) - taken from http://www.cse.yorku.ca/~oz/hash.html + DOCTEST_NO_SANITIZE_INTEGER + unsigned long long hash(const char* str) { + unsigned long long hash = 5381; + char c; + while ((c = *str++)) + hash = ((hash << 5) + hash) + c; // hash * 33 + c + return hash; + } + + unsigned long long hash(const SubcaseSignature& sig) { + return hash(hash(hash(sig.m_file), hash(sig.m_name.c_str())), sig.m_line); + } + + unsigned long long hash(const std::vector& sigs, size_t count) { + unsigned long long running = 0; + auto end = sigs.begin() + count; + for (auto it = sigs.begin(); it != end; it++) { + running = hash(running, hash(*it)); + } + return running; + } + + unsigned long long hash(const std::vector& sigs) { + unsigned long long running = 0; + for (const SubcaseSignature& sig : sigs) { + running = hash(running, hash(sig)); + } + return running; + } +} // namespace +namespace detail { + bool Subcase::checkFilters() { + if (g_cs->subcaseStack.size() < size_t(g_cs->subcase_filter_levels)) { + if (!matchesAny(m_signature.m_name.c_str(), g_cs->filters[6], true, g_cs->case_sensitive)) + return true; + if (matchesAny(m_signature.m_name.c_str(), g_cs->filters[7], false, g_cs->case_sensitive)) + return true; + } + return false; + } + + Subcase::Subcase(const String& name, const char* file, int line) + : m_signature({name, file, line}) { + if (!g_cs->reachedLeaf) { + if (g_cs->nextSubcaseStack.size() <= g_cs->subcaseStack.size() + || g_cs->nextSubcaseStack[g_cs->subcaseStack.size()] == m_signature) { + // Going down. + if (checkFilters()) { return; } + + g_cs->subcaseStack.push_back(m_signature); + g_cs->currentSubcaseDepth++; + m_entered = true; + DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_start, m_signature); + } + } else { + if (g_cs->subcaseStack[g_cs->currentSubcaseDepth] == m_signature) { + // This subcase is reentered via control flow. + g_cs->currentSubcaseDepth++; + m_entered = true; + DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_start, m_signature); + } else if (g_cs->nextSubcaseStack.size() <= g_cs->currentSubcaseDepth + && g_cs->fullyTraversedSubcases.find(hash(hash(g_cs->subcaseStack, g_cs->currentSubcaseDepth), hash(m_signature))) + == g_cs->fullyTraversedSubcases.end()) { + if (checkFilters()) { return; } + // This subcase is part of the one to be executed next. + g_cs->nextSubcaseStack.clear(); + g_cs->nextSubcaseStack.insert(g_cs->nextSubcaseStack.end(), + g_cs->subcaseStack.begin(), g_cs->subcaseStack.begin() + g_cs->currentSubcaseDepth); + g_cs->nextSubcaseStack.push_back(m_signature); + } + } + } + + DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4996) // std::uncaught_exception is deprecated in C++17 + DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") + + Subcase::~Subcase() { + if (m_entered) { + g_cs->currentSubcaseDepth--; + + if (!g_cs->reachedLeaf) { + // Leaf. + g_cs->fullyTraversedSubcases.insert(hash(g_cs->subcaseStack)); + g_cs->nextSubcaseStack.clear(); + g_cs->reachedLeaf = true; + } else if (g_cs->nextSubcaseStack.empty()) { + // All children are finished. + g_cs->fullyTraversedSubcases.insert(hash(g_cs->subcaseStack)); + } + +#if defined(__cpp_lib_uncaught_exceptions) && __cpp_lib_uncaught_exceptions >= 201411L && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101200) + if(std::uncaught_exceptions() > 0 +#else + if(std::uncaught_exception() +#endif + && g_cs->shouldLogCurrentException) { + DOCTEST_ITERATE_THROUGH_REPORTERS( + test_case_exception, {"exception thrown in subcase - will translate later " + "when the whole test case has been exited (cannot " + "translate while there is an active exception)", + false}); + g_cs->shouldLogCurrentException = false; + } + + DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_end, DOCTEST_EMPTY); + } + } + + DOCTEST_CLANG_SUPPRESS_WARNING_POP + DOCTEST_GCC_SUPPRESS_WARNING_POP + DOCTEST_MSVC_SUPPRESS_WARNING_POP + + Subcase::operator bool() const { return m_entered; } + + Result::Result(bool passed, const String& decomposition) + : m_passed(passed) + , m_decomp(decomposition) {} + + ExpressionDecomposer::ExpressionDecomposer(assertType::Enum at) + : m_at(at) {} + + TestSuite& TestSuite::operator*(const char* in) { + m_test_suite = in; + return *this; + } + + TestCase::TestCase(funcType test, const char* file, unsigned line, const TestSuite& test_suite, + const String& type, int template_id) { + m_file = file; + m_line = line; + m_name = nullptr; // will be later overridden in operator* + m_test_suite = test_suite.m_test_suite; + m_description = test_suite.m_description; + m_skip = test_suite.m_skip; + m_no_breaks = test_suite.m_no_breaks; + m_no_output = test_suite.m_no_output; + m_may_fail = test_suite.m_may_fail; + m_should_fail = test_suite.m_should_fail; + m_expected_failures = test_suite.m_expected_failures; + m_timeout = test_suite.m_timeout; + + m_test = test; + m_type = type; + m_template_id = template_id; + } + + TestCase::TestCase(const TestCase& other) + : TestCaseData() { + *this = other; + } + + DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(26434) // hides a non-virtual function + TestCase& TestCase::operator=(const TestCase& other) { + TestCaseData::operator=(other); + m_test = other.m_test; + m_type = other.m_type; + m_template_id = other.m_template_id; + m_full_name = other.m_full_name; + + if(m_template_id != -1) + m_name = m_full_name.c_str(); + return *this; + } + DOCTEST_MSVC_SUPPRESS_WARNING_POP + + TestCase& TestCase::operator*(const char* in) { + m_name = in; + // make a new name with an appended type for templated test case + if(m_template_id != -1) { + m_full_name = String(m_name) + "<" + m_type + ">"; + // redirect the name to point to the newly constructed full name + m_name = m_full_name.c_str(); + } + return *this; + } + + bool TestCase::operator<(const TestCase& other) const { + // this will be used only to differentiate between test cases - not relevant for sorting + if(m_line != other.m_line) + return m_line < other.m_line; + const int name_cmp = strcmp(m_name, other.m_name); + if(name_cmp != 0) + return name_cmp < 0; + const int file_cmp = m_file.compare(other.m_file); + if(file_cmp != 0) + return file_cmp < 0; + return m_template_id < other.m_template_id; + } + + // all the registered tests + std::set& getRegisteredTests() { + static std::set data; + return data; + } +} // namespace detail +namespace { + using namespace detail; + // for sorting tests by file/line + bool fileOrderComparator(const TestCase* lhs, const TestCase* rhs) { + // this is needed because MSVC gives different case for drive letters + // for __FILE__ when evaluated in a header and a source file + const int res = lhs->m_file.compare(rhs->m_file, bool(DOCTEST_MSVC)); + if(res != 0) + return res < 0; + if(lhs->m_line != rhs->m_line) + return lhs->m_line < rhs->m_line; + return lhs->m_template_id < rhs->m_template_id; + } + + // for sorting tests by suite/file/line + bool suiteOrderComparator(const TestCase* lhs, const TestCase* rhs) { + const int res = std::strcmp(lhs->m_test_suite, rhs->m_test_suite); + if(res != 0) + return res < 0; + return fileOrderComparator(lhs, rhs); + } + + // for sorting tests by name/suite/file/line + bool nameOrderComparator(const TestCase* lhs, const TestCase* rhs) { + const int res = std::strcmp(lhs->m_name, rhs->m_name); + if(res != 0) + return res < 0; + return suiteOrderComparator(lhs, rhs); + } + + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") + void color_to_stream(std::ostream& s, Color::Enum code) { + static_cast(s); // for DOCTEST_CONFIG_COLORS_NONE or DOCTEST_CONFIG_COLORS_WINDOWS + static_cast(code); // for DOCTEST_CONFIG_COLORS_NONE +#ifdef DOCTEST_CONFIG_COLORS_ANSI + if(g_no_colors || + (isatty(STDOUT_FILENO) == false && getContextOptions()->force_colors == false)) + return; + + auto col = ""; + // clang-format off + switch(code) { //!OCLINT missing break in switch statement / unnecessary default statement in covered switch statement + case Color::Red: col = "[0;31m"; break; + case Color::Green: col = "[0;32m"; break; + case Color::Blue: col = "[0;34m"; break; + case Color::Cyan: col = "[0;36m"; break; + case Color::Yellow: col = "[0;33m"; break; + case Color::Grey: col = "[1;30m"; break; + case Color::LightGrey: col = "[0;37m"; break; + case Color::BrightRed: col = "[1;31m"; break; + case Color::BrightGreen: col = "[1;32m"; break; + case Color::BrightWhite: col = "[1;37m"; break; + case Color::Bright: // invalid + case Color::None: + case Color::White: + default: col = "[0m"; + } + // clang-format on + s << "\033" << col; +#endif // DOCTEST_CONFIG_COLORS_ANSI + +#ifdef DOCTEST_CONFIG_COLORS_WINDOWS + if(g_no_colors || + (_isatty(_fileno(stdout)) == false && getContextOptions()->force_colors == false)) + return; + + static struct ConsoleHelper { + HANDLE stdoutHandle; + WORD origFgAttrs; + WORD origBgAttrs; + + ConsoleHelper() { + stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE); + CONSOLE_SCREEN_BUFFER_INFO csbiInfo; + GetConsoleScreenBufferInfo(stdoutHandle, &csbiInfo); + origFgAttrs = csbiInfo.wAttributes & ~(BACKGROUND_GREEN | BACKGROUND_RED | + BACKGROUND_BLUE | BACKGROUND_INTENSITY); + origBgAttrs = csbiInfo.wAttributes & ~(FOREGROUND_GREEN | FOREGROUND_RED | + FOREGROUND_BLUE | FOREGROUND_INTENSITY); + } + } ch; + +#define DOCTEST_SET_ATTR(x) SetConsoleTextAttribute(ch.stdoutHandle, x | ch.origBgAttrs) + + // clang-format off + switch (code) { + case Color::White: DOCTEST_SET_ATTR(FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE); break; + case Color::Red: DOCTEST_SET_ATTR(FOREGROUND_RED); break; + case Color::Green: DOCTEST_SET_ATTR(FOREGROUND_GREEN); break; + case Color::Blue: DOCTEST_SET_ATTR(FOREGROUND_BLUE); break; + case Color::Cyan: DOCTEST_SET_ATTR(FOREGROUND_BLUE | FOREGROUND_GREEN); break; + case Color::Yellow: DOCTEST_SET_ATTR(FOREGROUND_RED | FOREGROUND_GREEN); break; + case Color::Grey: DOCTEST_SET_ATTR(0); break; + case Color::LightGrey: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY); break; + case Color::BrightRed: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_RED); break; + case Color::BrightGreen: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_GREEN); break; + case Color::BrightWhite: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE); break; + case Color::None: + case Color::Bright: // invalid + default: DOCTEST_SET_ATTR(ch.origFgAttrs); + } + // clang-format on +#endif // DOCTEST_CONFIG_COLORS_WINDOWS + } + DOCTEST_CLANG_SUPPRESS_WARNING_POP + + std::vector& getExceptionTranslators() { + static std::vector data; + return data; + } + + String translateActiveException() { +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + String res; + auto& translators = getExceptionTranslators(); + for(auto& curr : translators) + if(curr->translate(res)) + return res; + // clang-format off + DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wcatch-value") + try { + throw; + } catch(std::exception& ex) { + return ex.what(); + } catch(std::string& msg) { + return msg.c_str(); + } catch(const char* msg) { + return msg; + } catch(...) { + return "unknown exception"; + } + DOCTEST_GCC_SUPPRESS_WARNING_POP +// clang-format on +#else // DOCTEST_CONFIG_NO_EXCEPTIONS + return ""; +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + } +} // namespace + +namespace detail { + // used by the macros for registering tests + int regTest(const TestCase& tc) { + getRegisteredTests().insert(tc); + return 0; + } + + // sets the current test suite + int setTestSuite(const TestSuite& ts) { + doctest_detail_test_suite_ns::getCurrentTestSuite() = ts; + return 0; + } + +#ifdef DOCTEST_IS_DEBUGGER_ACTIVE + bool isDebuggerActive() { return DOCTEST_IS_DEBUGGER_ACTIVE(); } +#else // DOCTEST_IS_DEBUGGER_ACTIVE +#ifdef DOCTEST_PLATFORM_LINUX + class ErrnoGuard { + public: + ErrnoGuard() : m_oldErrno(errno) {} + ~ErrnoGuard() { errno = m_oldErrno; } + private: + int m_oldErrno; + }; + // See the comments in Catch2 for the reasoning behind this implementation: + // https://github.com/catchorg/Catch2/blob/v2.13.1/include/internal/catch_debugger.cpp#L79-L102 + bool isDebuggerActive() { + ErrnoGuard guard; + std::ifstream in("/proc/self/status"); + for(std::string line; std::getline(in, line);) { + static const int PREFIX_LEN = 11; + if(line.compare(0, PREFIX_LEN, "TracerPid:\t") == 0) { + return line.length() > PREFIX_LEN && line[PREFIX_LEN] != '0'; + } + } + return false; + } +#elif defined(DOCTEST_PLATFORM_MAC) + // The following function is taken directly from the following technical note: + // https://developer.apple.com/library/archive/qa/qa1361/_index.html + // Returns true if the current process is being debugged (either + // running under the debugger or has a debugger attached post facto). + bool isDebuggerActive() { + int mib[4]; + kinfo_proc info; + size_t size; + // Initialize the flags so that, if sysctl fails for some bizarre + // reason, we get a predictable result. + info.kp_proc.p_flag = 0; + // Initialize mib, which tells sysctl the info we want, in this case + // we're looking for information about a specific process ID. + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = getpid(); + // Call sysctl. + size = sizeof(info); + if(sysctl(mib, DOCTEST_COUNTOF(mib), &info, &size, 0, 0) != 0) { + std::cerr << "\nCall to sysctl failed - unable to determine if debugger is active **\n"; + return false; + } + // We're being debugged if the P_TRACED flag is set. + return ((info.kp_proc.p_flag & P_TRACED) != 0); + } +#elif DOCTEST_MSVC || defined(__MINGW32__) || defined(__MINGW64__) + bool isDebuggerActive() { return ::IsDebuggerPresent() != 0; } +#else + bool isDebuggerActive() { return false; } +#endif // Platform +#endif // DOCTEST_IS_DEBUGGER_ACTIVE + + void registerExceptionTranslatorImpl(const IExceptionTranslator* et) { + if(std::find(getExceptionTranslators().begin(), getExceptionTranslators().end(), et) == + getExceptionTranslators().end()) + getExceptionTranslators().push_back(et); + } + + DOCTEST_THREAD_LOCAL std::vector g_infoContexts; // for logging with INFO() + + ContextScopeBase::ContextScopeBase() { + g_infoContexts.push_back(this); + } + + ContextScopeBase::ContextScopeBase(ContextScopeBase&& other) noexcept { + if (other.need_to_destroy) { + other.destroy(); + } + other.need_to_destroy = false; + g_infoContexts.push_back(this); + } + + DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4996) // std::uncaught_exception is deprecated in C++17 + DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") + + // destroy cannot be inlined into the destructor because that would mean calling stringify after + // ContextScope has been destroyed (base class destructors run after derived class destructors). + // Instead, ContextScope calls this method directly from its destructor. + void ContextScopeBase::destroy() { +#if defined(__cpp_lib_uncaught_exceptions) && __cpp_lib_uncaught_exceptions >= 201411L && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101200) + if(std::uncaught_exceptions() > 0) { +#else + if(std::uncaught_exception()) { +#endif + std::ostringstream s; + this->stringify(&s); + g_cs->stringifiedContexts.push_back(s.str().c_str()); + } + g_infoContexts.pop_back(); + } + + DOCTEST_CLANG_SUPPRESS_WARNING_POP + DOCTEST_GCC_SUPPRESS_WARNING_POP + DOCTEST_MSVC_SUPPRESS_WARNING_POP +} // namespace detail +namespace { + using namespace detail; + +#if !defined(DOCTEST_CONFIG_POSIX_SIGNALS) && !defined(DOCTEST_CONFIG_WINDOWS_SEH) + struct FatalConditionHandler + { + static void reset() {} + static void allocateAltStackMem() {} + static void freeAltStackMem() {} + }; +#else // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH + + void reportFatal(const std::string&); + +#ifdef DOCTEST_PLATFORM_WINDOWS + + struct SignalDefs + { + DWORD id; + const char* name; + }; + // There is no 1-1 mapping between signals and windows exceptions. + // Windows can easily distinguish between SO and SigSegV, + // but SigInt, SigTerm, etc are handled differently. + SignalDefs signalDefs[] = { + {static_cast(EXCEPTION_ILLEGAL_INSTRUCTION), + "SIGILL - Illegal instruction signal"}, + {static_cast(EXCEPTION_STACK_OVERFLOW), "SIGSEGV - Stack overflow"}, + {static_cast(EXCEPTION_ACCESS_VIOLATION), + "SIGSEGV - Segmentation violation signal"}, + {static_cast(EXCEPTION_INT_DIVIDE_BY_ZERO), "Divide by zero error"}, + }; + + struct FatalConditionHandler + { + static LONG CALLBACK handleException(PEXCEPTION_POINTERS ExceptionInfo) { + // Multiple threads may enter this filter/handler at once. We want the error message to be printed on the + // console just once no matter how many threads have crashed. + DOCTEST_DECLARE_STATIC_MUTEX(mutex) + static bool execute = true; + { + DOCTEST_LOCK_MUTEX(mutex) + if(execute) { + bool reported = false; + for(size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) { + if(ExceptionInfo->ExceptionRecord->ExceptionCode == signalDefs[i].id) { + reportFatal(signalDefs[i].name); + reported = true; + break; + } + } + if(reported == false) + reportFatal("Unhandled SEH exception caught"); + if(isDebuggerActive() && !g_cs->no_breaks) + DOCTEST_BREAK_INTO_DEBUGGER(); + } + execute = false; + } + std::exit(EXIT_FAILURE); + } + + static void allocateAltStackMem() {} + static void freeAltStackMem() {} + + FatalConditionHandler() { + isSet = true; + // 32k seems enough for doctest to handle stack overflow, + // but the value was found experimentally, so there is no strong guarantee + guaranteeSize = 32 * 1024; + // Register an unhandled exception filter + previousTop = SetUnhandledExceptionFilter(handleException); + // Pass in guarantee size to be filled + SetThreadStackGuarantee(&guaranteeSize); + + // On Windows uncaught exceptions from another thread, exceptions from + // destructors, or calls to std::terminate are not a SEH exception + + // The terminal handler gets called when: + // - std::terminate is called FROM THE TEST RUNNER THREAD + // - an exception is thrown from a destructor FROM THE TEST RUNNER THREAD + original_terminate_handler = std::get_terminate(); + std::set_terminate([]() DOCTEST_NOEXCEPT { + reportFatal("Terminate handler called"); + if(isDebuggerActive() && !g_cs->no_breaks) + DOCTEST_BREAK_INTO_DEBUGGER(); + std::exit(EXIT_FAILURE); // explicitly exit - otherwise the SIGABRT handler may be called as well + }); + + // SIGABRT is raised when: + // - std::terminate is called FROM A DIFFERENT THREAD + // - an exception is thrown from a destructor FROM A DIFFERENT THREAD + // - an uncaught exception is thrown FROM A DIFFERENT THREAD + prev_sigabrt_handler = std::signal(SIGABRT, [](int signal) DOCTEST_NOEXCEPT { + if(signal == SIGABRT) { + reportFatal("SIGABRT - Abort (abnormal termination) signal"); + if(isDebuggerActive() && !g_cs->no_breaks) + DOCTEST_BREAK_INTO_DEBUGGER(); + std::exit(EXIT_FAILURE); + } + }); + + // The following settings are taken from google test, and more + // specifically from UnitTest::Run() inside of gtest.cc + + // the user does not want to see pop-up dialogs about crashes + prev_error_mode_1 = SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOALIGNMENTFAULTEXCEPT | + SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX); + // This forces the abort message to go to stderr in all circumstances. + prev_error_mode_2 = _set_error_mode(_OUT_TO_STDERR); + // In the debug version, Visual Studio pops up a separate dialog + // offering a choice to debug the aborted program - we want to disable that. + prev_abort_behavior = _set_abort_behavior(0x0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); + // In debug mode, the Windows CRT can crash with an assertion over invalid + // input (e.g. passing an invalid file descriptor). The default handling + // for these assertions is to pop up a dialog and wait for user input. + // Instead ask the CRT to dump such assertions to stderr non-interactively. + prev_report_mode = _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); + prev_report_file = _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR); + } + + static void reset() { + if(isSet) { + // Unregister handler and restore the old guarantee + SetUnhandledExceptionFilter(previousTop); + SetThreadStackGuarantee(&guaranteeSize); + std::set_terminate(original_terminate_handler); + std::signal(SIGABRT, prev_sigabrt_handler); + SetErrorMode(prev_error_mode_1); + _set_error_mode(prev_error_mode_2); + _set_abort_behavior(prev_abort_behavior, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); + static_cast(_CrtSetReportMode(_CRT_ASSERT, prev_report_mode)); + static_cast(_CrtSetReportFile(_CRT_ASSERT, prev_report_file)); + isSet = false; + } + } + + ~FatalConditionHandler() { reset(); } + + private: + static UINT prev_error_mode_1; + static int prev_error_mode_2; + static unsigned int prev_abort_behavior; + static int prev_report_mode; + static _HFILE prev_report_file; + static void (DOCTEST_CDECL *prev_sigabrt_handler)(int); + static std::terminate_handler original_terminate_handler; + static bool isSet; + static ULONG guaranteeSize; + static LPTOP_LEVEL_EXCEPTION_FILTER previousTop; + }; + + UINT FatalConditionHandler::prev_error_mode_1; + int FatalConditionHandler::prev_error_mode_2; + unsigned int FatalConditionHandler::prev_abort_behavior; + int FatalConditionHandler::prev_report_mode; + _HFILE FatalConditionHandler::prev_report_file; + void (DOCTEST_CDECL *FatalConditionHandler::prev_sigabrt_handler)(int); + std::terminate_handler FatalConditionHandler::original_terminate_handler; + bool FatalConditionHandler::isSet = false; + ULONG FatalConditionHandler::guaranteeSize = 0; + LPTOP_LEVEL_EXCEPTION_FILTER FatalConditionHandler::previousTop = nullptr; + +#else // DOCTEST_PLATFORM_WINDOWS + + struct SignalDefs + { + int id; + const char* name; + }; + SignalDefs signalDefs[] = {{SIGINT, "SIGINT - Terminal interrupt signal"}, + {SIGILL, "SIGILL - Illegal instruction signal"}, + {SIGFPE, "SIGFPE - Floating point error signal"}, + {SIGSEGV, "SIGSEGV - Segmentation violation signal"}, + {SIGTERM, "SIGTERM - Termination request signal"}, + {SIGABRT, "SIGABRT - Abort (abnormal termination) signal"}}; + + struct FatalConditionHandler + { + static bool isSet; + static struct sigaction oldSigActions[DOCTEST_COUNTOF(signalDefs)]; + static stack_t oldSigStack; + static size_t altStackSize; + static char* altStackMem; + + static void handleSignal(int sig) { + const char* name = ""; + for(std::size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) { + SignalDefs& def = signalDefs[i]; + if(sig == def.id) { + name = def.name; + break; + } + } + reset(); + reportFatal(name); + raise(sig); + } + + static void allocateAltStackMem() { + altStackMem = new char[altStackSize]; + } + + static void freeAltStackMem() { + delete[] altStackMem; + } + + FatalConditionHandler() { + isSet = true; + stack_t sigStack; + sigStack.ss_sp = altStackMem; + sigStack.ss_size = altStackSize; + sigStack.ss_flags = 0; + sigaltstack(&sigStack, &oldSigStack); + struct sigaction sa = {}; + sa.sa_handler = handleSignal; + sa.sa_flags = SA_ONSTACK; + for(std::size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) { + sigaction(signalDefs[i].id, &sa, &oldSigActions[i]); + } + } + + ~FatalConditionHandler() { reset(); } + static void reset() { + if(isSet) { + // Set signals back to previous values -- hopefully nobody overwrote them in the meantime + for(std::size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) { + sigaction(signalDefs[i].id, &oldSigActions[i], nullptr); + } + // Return the old stack + sigaltstack(&oldSigStack, nullptr); + isSet = false; + } + } + }; + + bool FatalConditionHandler::isSet = false; + struct sigaction FatalConditionHandler::oldSigActions[DOCTEST_COUNTOF(signalDefs)] = {}; + stack_t FatalConditionHandler::oldSigStack = {}; + size_t FatalConditionHandler::altStackSize = 4 * SIGSTKSZ; + char* FatalConditionHandler::altStackMem = nullptr; + +#endif // DOCTEST_PLATFORM_WINDOWS +#endif // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH + +} // namespace + +namespace { + using namespace detail; + +#ifdef DOCTEST_PLATFORM_WINDOWS +#define DOCTEST_OUTPUT_DEBUG_STRING(text) ::OutputDebugStringA(text) +#else + // TODO: integration with XCode and other IDEs +#define DOCTEST_OUTPUT_DEBUG_STRING(text) +#endif // Platform + + void addAssert(assertType::Enum at) { + if((at & assertType::is_warn) == 0) //!OCLINT bitwise operator in conditional + g_cs->numAssertsCurrentTest_atomic++; + } + + void addFailedAssert(assertType::Enum at) { + if((at & assertType::is_warn) == 0) //!OCLINT bitwise operator in conditional + g_cs->numAssertsFailedCurrentTest_atomic++; + } + +#if defined(DOCTEST_CONFIG_POSIX_SIGNALS) || defined(DOCTEST_CONFIG_WINDOWS_SEH) + void reportFatal(const std::string& message) { + g_cs->failure_flags |= TestCaseFailureReason::Crash; + + DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_exception, {message.c_str(), true}); + + while (g_cs->subcaseStack.size()) { + g_cs->subcaseStack.pop_back(); + DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_end, DOCTEST_EMPTY); + } + + g_cs->finalizeTestCaseData(); + + DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_end, *g_cs); + + DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_end, *g_cs); + } +#endif // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH +} // namespace + +AssertData::AssertData(assertType::Enum at, const char* file, int line, const char* expr, + const char* exception_type, const StringContains& exception_string) + : m_test_case(g_cs->currentTest), m_at(at), m_file(file), m_line(line), m_expr(expr), + m_failed(true), m_threw(false), m_threw_as(false), m_exception_type(exception_type), + m_exception_string(exception_string) { +#if DOCTEST_MSVC + if (m_expr[0] == ' ') // this happens when variadic macros are disabled under MSVC + ++m_expr; +#endif // MSVC +} + +namespace detail { + ResultBuilder::ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr, + const char* exception_type, const String& exception_string) + : AssertData(at, file, line, expr, exception_type, exception_string) { } + + ResultBuilder::ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr, + const char* exception_type, const Contains& exception_string) + : AssertData(at, file, line, expr, exception_type, exception_string) { } + + void ResultBuilder::setResult(const Result& res) { + m_decomp = res.m_decomp; + m_failed = !res.m_passed; + } + + void ResultBuilder::translateException() { + m_threw = true; + m_exception = translateActiveException(); + } + + bool ResultBuilder::log() { + if(m_at & assertType::is_throws) { //!OCLINT bitwise operator in conditional + m_failed = !m_threw; + } else if((m_at & assertType::is_throws_as) && (m_at & assertType::is_throws_with)) { //!OCLINT + m_failed = !m_threw_as || !m_exception_string.check(m_exception); + } else if(m_at & assertType::is_throws_as) { //!OCLINT bitwise operator in conditional + m_failed = !m_threw_as; + } else if(m_at & assertType::is_throws_with) { //!OCLINT bitwise operator in conditional + m_failed = !m_exception_string.check(m_exception); + } else if(m_at & assertType::is_nothrow) { //!OCLINT bitwise operator in conditional + m_failed = m_threw; + } + + if(m_exception.size()) + m_exception = "\"" + m_exception + "\""; + + if(is_running_in_test) { + addAssert(m_at); + DOCTEST_ITERATE_THROUGH_REPORTERS(log_assert, *this); + + if(m_failed) + addFailedAssert(m_at); + } else if(m_failed) { + failed_out_of_a_testing_context(*this); + } + + return m_failed && isDebuggerActive() && !getContextOptions()->no_breaks && + (g_cs->currentTest == nullptr || !g_cs->currentTest->m_no_breaks); // break into debugger + } + + void ResultBuilder::react() const { + if(m_failed && checkIfShouldThrow(m_at)) + throwException(); + } + + void failed_out_of_a_testing_context(const AssertData& ad) { + if(g_cs->ah) + g_cs->ah(ad); + else + std::abort(); + } + + bool decomp_assert(assertType::Enum at, const char* file, int line, const char* expr, + const Result& result) { + bool failed = !result.m_passed; + + // ################################################################################### + // IF THE DEBUGGER BREAKS HERE - GO 1 LEVEL UP IN THE CALLSTACK FOR THE FAILING ASSERT + // THIS IS THE EFFECT OF HAVING 'DOCTEST_CONFIG_SUPER_FAST_ASSERTS' DEFINED + // ################################################################################### + DOCTEST_ASSERT_OUT_OF_TESTS(result.m_decomp); + DOCTEST_ASSERT_IN_TESTS(result.m_decomp); + return !failed; + } + + MessageBuilder::MessageBuilder(const char* file, int line, assertType::Enum severity) { + m_stream = tlssPush(); + m_file = file; + m_line = line; + m_severity = severity; + } + + MessageBuilder::~MessageBuilder() { + if (!logged) + tlssPop(); + } + + DOCTEST_DEFINE_INTERFACE(IExceptionTranslator) + + bool MessageBuilder::log() { + if (!logged) { + m_string = tlssPop(); + logged = true; + } + + DOCTEST_ITERATE_THROUGH_REPORTERS(log_message, *this); + + const bool isWarn = m_severity & assertType::is_warn; + + // warn is just a message in this context so we don't treat it as an assert + if(!isWarn) { + addAssert(m_severity); + addFailedAssert(m_severity); + } + + return isDebuggerActive() && !getContextOptions()->no_breaks && !isWarn && + (g_cs->currentTest == nullptr || !g_cs->currentTest->m_no_breaks); // break into debugger + } + + void MessageBuilder::react() { + if(m_severity & assertType::is_require) //!OCLINT bitwise operator in conditional + throwException(); + } +} // namespace detail +namespace { + using namespace detail; + + // clang-format off + +// ================================================================================================= +// The following code has been taken verbatim from Catch2/include/internal/catch_xmlwriter.h/cpp +// This is done so cherry-picking bug fixes is trivial - even the style/formatting is untouched. +// ================================================================================================= + + class XmlEncode { + public: + enum ForWhat { ForTextNodes, ForAttributes }; + + XmlEncode( std::string const& str, ForWhat forWhat = ForTextNodes ); + + void encodeTo( std::ostream& os ) const; + + friend std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ); + + private: + std::string m_str; + ForWhat m_forWhat; + }; + + class XmlWriter { + public: + + class ScopedElement { + public: + ScopedElement( XmlWriter* writer ); + + ScopedElement( ScopedElement&& other ) DOCTEST_NOEXCEPT; + ScopedElement& operator=( ScopedElement&& other ) DOCTEST_NOEXCEPT; + + ~ScopedElement(); + + ScopedElement& writeText( std::string const& text, bool indent = true ); + + template + ScopedElement& writeAttribute( std::string const& name, T const& attribute ) { + m_writer->writeAttribute( name, attribute ); + return *this; + } + + private: + mutable XmlWriter* m_writer = nullptr; + }; + +#ifndef DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM + XmlWriter( std::ostream& os = std::cout ); +#else // DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM + XmlWriter( std::ostream& os ); +#endif // DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM + ~XmlWriter(); + + XmlWriter( XmlWriter const& ) = delete; + XmlWriter& operator=( XmlWriter const& ) = delete; + + XmlWriter& startElement( std::string const& name ); + + ScopedElement scopedElement( std::string const& name ); + + XmlWriter& endElement(); + + XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ); + + XmlWriter& writeAttribute( std::string const& name, const char* attribute ); + + XmlWriter& writeAttribute( std::string const& name, bool attribute ); + + template + XmlWriter& writeAttribute( std::string const& name, T const& attribute ) { + std::stringstream rss; + rss << attribute; + return writeAttribute( name, rss.str() ); + } + + XmlWriter& writeText( std::string const& text, bool indent = true ); + + //XmlWriter& writeComment( std::string const& text ); + + //void writeStylesheetRef( std::string const& url ); + + //XmlWriter& writeBlankLine(); + + void ensureTagClosed(); + + void writeDeclaration(); + + private: + + void newlineIfNecessary(); + + bool m_tagIsOpen = false; + bool m_needsNewline = false; + std::vector m_tags; + std::string m_indent; + std::ostream& m_os; + }; + +// ================================================================================================= +// The following code has been taken verbatim from Catch2/include/internal/catch_xmlwriter.h/cpp +// This is done so cherry-picking bug fixes is trivial - even the style/formatting is untouched. +// ================================================================================================= + +using uchar = unsigned char; + +namespace { + + size_t trailingBytes(unsigned char c) { + if ((c & 0xE0) == 0xC0) { + return 2; + } + if ((c & 0xF0) == 0xE0) { + return 3; + } + if ((c & 0xF8) == 0xF0) { + return 4; + } + DOCTEST_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered"); + } + + uint32_t headerValue(unsigned char c) { + if ((c & 0xE0) == 0xC0) { + return c & 0x1F; + } + if ((c & 0xF0) == 0xE0) { + return c & 0x0F; + } + if ((c & 0xF8) == 0xF0) { + return c & 0x07; + } + DOCTEST_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered"); + } + + void hexEscapeChar(std::ostream& os, unsigned char c) { + std::ios_base::fmtflags f(os.flags()); + os << "\\x" + << std::uppercase << std::hex << std::setfill('0') << std::setw(2) + << static_cast(c); + os.flags(f); + } + +} // anonymous namespace + + XmlEncode::XmlEncode( std::string const& str, ForWhat forWhat ) + : m_str( str ), + m_forWhat( forWhat ) + {} + + void XmlEncode::encodeTo( std::ostream& os ) const { + // Apostrophe escaping not necessary if we always use " to write attributes + // (see: https://www.w3.org/TR/xml/#syntax) + + for( std::size_t idx = 0; idx < m_str.size(); ++ idx ) { + uchar c = m_str[idx]; + switch (c) { + case '<': os << "<"; break; + case '&': os << "&"; break; + + case '>': + // See: https://www.w3.org/TR/xml/#syntax + if (idx > 2 && m_str[idx - 1] == ']' && m_str[idx - 2] == ']') + os << ">"; + else + os << c; + break; + + case '\"': + if (m_forWhat == ForAttributes) + os << """; + else + os << c; + break; + + default: + // Check for control characters and invalid utf-8 + + // Escape control characters in standard ascii + // see https://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0 + if (c < 0x09 || (c > 0x0D && c < 0x20) || c == 0x7F) { + hexEscapeChar(os, c); + break; + } + + // Plain ASCII: Write it to stream + if (c < 0x7F) { + os << c; + break; + } + + // UTF-8 territory + // Check if the encoding is valid and if it is not, hex escape bytes. + // Important: We do not check the exact decoded values for validity, only the encoding format + // First check that this bytes is a valid lead byte: + // This means that it is not encoded as 1111 1XXX + // Or as 10XX XXXX + if (c < 0xC0 || + c >= 0xF8) { + hexEscapeChar(os, c); + break; + } + + auto encBytes = trailingBytes(c); + // Are there enough bytes left to avoid accessing out-of-bounds memory? + if (idx + encBytes - 1 >= m_str.size()) { + hexEscapeChar(os, c); + break; + } + // The header is valid, check data + // The next encBytes bytes must together be a valid utf-8 + // This means: bitpattern 10XX XXXX and the extracted value is sane (ish) + bool valid = true; + uint32_t value = headerValue(c); + for (std::size_t n = 1; n < encBytes; ++n) { + uchar nc = m_str[idx + n]; + valid &= ((nc & 0xC0) == 0x80); + value = (value << 6) | (nc & 0x3F); + } + + if ( + // Wrong bit pattern of following bytes + (!valid) || + // Overlong encodings + (value < 0x80) || + ( value < 0x800 && encBytes > 2) || // removed "0x80 <= value &&" because redundant + (0x800 < value && value < 0x10000 && encBytes > 3) || + // Encoded value out of range + (value >= 0x110000) + ) { + hexEscapeChar(os, c); + break; + } + + // If we got here, this is in fact a valid(ish) utf-8 sequence + for (std::size_t n = 0; n < encBytes; ++n) { + os << m_str[idx + n]; + } + idx += encBytes - 1; + break; + } + } + } + + std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) { + xmlEncode.encodeTo( os ); + return os; + } + + XmlWriter::ScopedElement::ScopedElement( XmlWriter* writer ) + : m_writer( writer ) + {} + + XmlWriter::ScopedElement::ScopedElement( ScopedElement&& other ) DOCTEST_NOEXCEPT + : m_writer( other.m_writer ){ + other.m_writer = nullptr; + } + XmlWriter::ScopedElement& XmlWriter::ScopedElement::operator=( ScopedElement&& other ) DOCTEST_NOEXCEPT { + if ( m_writer ) { + m_writer->endElement(); + } + m_writer = other.m_writer; + other.m_writer = nullptr; + return *this; + } + + + XmlWriter::ScopedElement::~ScopedElement() { + if( m_writer ) + m_writer->endElement(); + } + + XmlWriter::ScopedElement& XmlWriter::ScopedElement::writeText( std::string const& text, bool indent ) { + m_writer->writeText( text, indent ); + return *this; + } + + XmlWriter::XmlWriter( std::ostream& os ) : m_os( os ) + { + // writeDeclaration(); // called explicitly by the reporters that use the writer class - see issue #627 + } + + XmlWriter::~XmlWriter() { + while( !m_tags.empty() ) + endElement(); + } + + XmlWriter& XmlWriter::startElement( std::string const& name ) { + ensureTagClosed(); + newlineIfNecessary(); + m_os << m_indent << '<' << name; + m_tags.push_back( name ); + m_indent += " "; + m_tagIsOpen = true; + return *this; + } + + XmlWriter::ScopedElement XmlWriter::scopedElement( std::string const& name ) { + ScopedElement scoped( this ); + startElement( name ); + return scoped; + } + + XmlWriter& XmlWriter::endElement() { + newlineIfNecessary(); + m_indent = m_indent.substr( 0, m_indent.size()-2 ); + if( m_tagIsOpen ) { + m_os << "/>"; + m_tagIsOpen = false; + } + else { + m_os << m_indent << ""; + } + m_os << std::endl; + m_tags.pop_back(); + return *this; + } + + XmlWriter& XmlWriter::writeAttribute( std::string const& name, std::string const& attribute ) { + if( !name.empty() && !attribute.empty() ) + m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"'; + return *this; + } + + XmlWriter& XmlWriter::writeAttribute( std::string const& name, const char* attribute ) { + if( !name.empty() && attribute && attribute[0] != '\0' ) + m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"'; + return *this; + } + + XmlWriter& XmlWriter::writeAttribute( std::string const& name, bool attribute ) { + m_os << ' ' << name << "=\"" << ( attribute ? "true" : "false" ) << '"'; + return *this; + } + + XmlWriter& XmlWriter::writeText( std::string const& text, bool indent ) { + if( !text.empty() ){ + bool tagWasOpen = m_tagIsOpen; + ensureTagClosed(); + if( tagWasOpen && indent ) + m_os << m_indent; + m_os << XmlEncode( text ); + m_needsNewline = true; + } + return *this; + } + + //XmlWriter& XmlWriter::writeComment( std::string const& text ) { + // ensureTagClosed(); + // m_os << m_indent << ""; + // m_needsNewline = true; + // return *this; + //} + + //void XmlWriter::writeStylesheetRef( std::string const& url ) { + // m_os << "\n"; + //} + + //XmlWriter& XmlWriter::writeBlankLine() { + // ensureTagClosed(); + // m_os << '\n'; + // return *this; + //} + + void XmlWriter::ensureTagClosed() { + if( m_tagIsOpen ) { + m_os << ">" << std::endl; + m_tagIsOpen = false; + } + } + + void XmlWriter::writeDeclaration() { + m_os << "\n"; + } + + void XmlWriter::newlineIfNecessary() { + if( m_needsNewline ) { + m_os << std::endl; + m_needsNewline = false; + } + } + +// ================================================================================================= +// End of copy-pasted code from Catch +// ================================================================================================= + + // clang-format on + + struct XmlReporter : public IReporter + { + XmlWriter xml; + DOCTEST_DECLARE_MUTEX(mutex) + + // caching pointers/references to objects of these types - safe to do + const ContextOptions& opt; + const TestCaseData* tc = nullptr; + + XmlReporter(const ContextOptions& co) + : xml(*co.cout) + , opt(co) {} + + void log_contexts() { + int num_contexts = get_num_active_contexts(); + if(num_contexts) { + auto contexts = get_active_contexts(); + std::stringstream ss; + for(int i = 0; i < num_contexts; ++i) { + contexts[i]->stringify(&ss); + xml.scopedElement("Info").writeText(ss.str()); + ss.str(""); + } + } + } + + unsigned line(unsigned l) const { return opt.no_line_numbers ? 0 : l; } + + void test_case_start_impl(const TestCaseData& in) { + bool open_ts_tag = false; + if(tc != nullptr) { // we have already opened a test suite + if(std::strcmp(tc->m_test_suite, in.m_test_suite) != 0) { + xml.endElement(); + open_ts_tag = true; + } + } + else { + open_ts_tag = true; // first test case ==> first test suite + } + + if(open_ts_tag) { + xml.startElement("TestSuite"); + xml.writeAttribute("name", in.m_test_suite); + } + + tc = ∈ + xml.startElement("TestCase") + .writeAttribute("name", in.m_name) + .writeAttribute("filename", skipPathFromFilename(in.m_file.c_str())) + .writeAttribute("line", line(in.m_line)) + .writeAttribute("description", in.m_description); + + if(Approx(in.m_timeout) != 0) + xml.writeAttribute("timeout", in.m_timeout); + if(in.m_may_fail) + xml.writeAttribute("may_fail", true); + if(in.m_should_fail) + xml.writeAttribute("should_fail", true); + } + + // ========================================================================================= + // WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE + // ========================================================================================= + + void report_query(const QueryData& in) override { + test_run_start(); + if(opt.list_reporters) { + for(auto& curr : getListeners()) + xml.scopedElement("Listener") + .writeAttribute("priority", curr.first.first) + .writeAttribute("name", curr.first.second); + for(auto& curr : getReporters()) + xml.scopedElement("Reporter") + .writeAttribute("priority", curr.first.first) + .writeAttribute("name", curr.first.second); + } else if(opt.count || opt.list_test_cases) { + for(unsigned i = 0; i < in.num_data; ++i) { + xml.scopedElement("TestCase").writeAttribute("name", in.data[i]->m_name) + .writeAttribute("testsuite", in.data[i]->m_test_suite) + .writeAttribute("filename", skipPathFromFilename(in.data[i]->m_file.c_str())) + .writeAttribute("line", line(in.data[i]->m_line)) + .writeAttribute("skipped", in.data[i]->m_skip); + } + xml.scopedElement("OverallResultsTestCases") + .writeAttribute("unskipped", in.run_stats->numTestCasesPassingFilters); + } else if(opt.list_test_suites) { + for(unsigned i = 0; i < in.num_data; ++i) + xml.scopedElement("TestSuite").writeAttribute("name", in.data[i]->m_test_suite); + xml.scopedElement("OverallResultsTestCases") + .writeAttribute("unskipped", in.run_stats->numTestCasesPassingFilters); + xml.scopedElement("OverallResultsTestSuites") + .writeAttribute("unskipped", in.run_stats->numTestSuitesPassingFilters); + } + xml.endElement(); + } + + void test_run_start() override { + xml.writeDeclaration(); + + // remove .exe extension - mainly to have the same output on UNIX and Windows + std::string binary_name = skipPathFromFilename(opt.binary_name.c_str()); +#ifdef DOCTEST_PLATFORM_WINDOWS + if(binary_name.rfind(".exe") != std::string::npos) + binary_name = binary_name.substr(0, binary_name.length() - 4); +#endif // DOCTEST_PLATFORM_WINDOWS + + xml.startElement("doctest").writeAttribute("binary", binary_name); + if(opt.no_version == false) + xml.writeAttribute("version", DOCTEST_VERSION_STR); + + // only the consequential ones (TODO: filters) + xml.scopedElement("Options") + .writeAttribute("order_by", opt.order_by.c_str()) + .writeAttribute("rand_seed", opt.rand_seed) + .writeAttribute("first", opt.first) + .writeAttribute("last", opt.last) + .writeAttribute("abort_after", opt.abort_after) + .writeAttribute("subcase_filter_levels", opt.subcase_filter_levels) + .writeAttribute("case_sensitive", opt.case_sensitive) + .writeAttribute("no_throw", opt.no_throw) + .writeAttribute("no_skip", opt.no_skip); + } + + void test_run_end(const TestRunStats& p) override { + if(tc) // the TestSuite tag - only if there has been at least 1 test case + xml.endElement(); + + xml.scopedElement("OverallResultsAsserts") + .writeAttribute("successes", p.numAsserts - p.numAssertsFailed) + .writeAttribute("failures", p.numAssertsFailed); + + xml.startElement("OverallResultsTestCases") + .writeAttribute("successes", + p.numTestCasesPassingFilters - p.numTestCasesFailed) + .writeAttribute("failures", p.numTestCasesFailed); + if(opt.no_skipped_summary == false) + xml.writeAttribute("skipped", p.numTestCases - p.numTestCasesPassingFilters); + xml.endElement(); + + xml.endElement(); + } + + void test_case_start(const TestCaseData& in) override { + test_case_start_impl(in); + xml.ensureTagClosed(); + } + + void test_case_reenter(const TestCaseData&) override {} + + void test_case_end(const CurrentTestCaseStats& st) override { + xml.startElement("OverallResultsAsserts") + .writeAttribute("successes", + st.numAssertsCurrentTest - st.numAssertsFailedCurrentTest) + .writeAttribute("failures", st.numAssertsFailedCurrentTest) + .writeAttribute("test_case_success", st.testCaseSuccess); + if(opt.duration) + xml.writeAttribute("duration", st.seconds); + if(tc->m_expected_failures) + xml.writeAttribute("expected_failures", tc->m_expected_failures); + xml.endElement(); + + xml.endElement(); + } + + void test_case_exception(const TestCaseException& e) override { + DOCTEST_LOCK_MUTEX(mutex) + + xml.scopedElement("Exception") + .writeAttribute("crash", e.is_crash) + .writeText(e.error_string.c_str()); + } + + void subcase_start(const SubcaseSignature& in) override { + xml.startElement("SubCase") + .writeAttribute("name", in.m_name) + .writeAttribute("filename", skipPathFromFilename(in.m_file)) + .writeAttribute("line", line(in.m_line)); + xml.ensureTagClosed(); + } + + void subcase_end() override { xml.endElement(); } + + void log_assert(const AssertData& rb) override { + if(!rb.m_failed && !opt.success) + return; + + DOCTEST_LOCK_MUTEX(mutex) + + xml.startElement("Expression") + .writeAttribute("success", !rb.m_failed) + .writeAttribute("type", assertString(rb.m_at)) + .writeAttribute("filename", skipPathFromFilename(rb.m_file)) + .writeAttribute("line", line(rb.m_line)); + + xml.scopedElement("Original").writeText(rb.m_expr); + + if(rb.m_threw) + xml.scopedElement("Exception").writeText(rb.m_exception.c_str()); + + if(rb.m_at & assertType::is_throws_as) + xml.scopedElement("ExpectedException").writeText(rb.m_exception_type); + if(rb.m_at & assertType::is_throws_with) + xml.scopedElement("ExpectedExceptionString").writeText(rb.m_exception_string.c_str()); + if((rb.m_at & assertType::is_normal) && !rb.m_threw) + xml.scopedElement("Expanded").writeText(rb.m_decomp.c_str()); + + log_contexts(); + + xml.endElement(); + } + + void log_message(const MessageData& mb) override { + DOCTEST_LOCK_MUTEX(mutex) + + xml.startElement("Message") + .writeAttribute("type", failureString(mb.m_severity)) + .writeAttribute("filename", skipPathFromFilename(mb.m_file)) + .writeAttribute("line", line(mb.m_line)); + + xml.scopedElement("Text").writeText(mb.m_string.c_str()); + + log_contexts(); + + xml.endElement(); + } + + void test_case_skipped(const TestCaseData& in) override { + if(opt.no_skipped_summary == false) { + test_case_start_impl(in); + xml.writeAttribute("skipped", "true"); + xml.endElement(); + } + } + }; + + DOCTEST_REGISTER_REPORTER("xml", 0, XmlReporter); + + void fulltext_log_assert_to_stream(std::ostream& s, const AssertData& rb) { + if((rb.m_at & (assertType::is_throws_as | assertType::is_throws_with)) == + 0) //!OCLINT bitwise operator in conditional + s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << " ) " + << Color::None; + + if(rb.m_at & assertType::is_throws) { //!OCLINT bitwise operator in conditional + s << (rb.m_threw ? "threw as expected!" : "did NOT throw at all!") << "\n"; + } else if((rb.m_at & assertType::is_throws_as) && + (rb.m_at & assertType::is_throws_with)) { //!OCLINT + s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", \"" + << rb.m_exception_string.c_str() + << "\", " << rb.m_exception_type << " ) " << Color::None; + if(rb.m_threw) { + if(!rb.m_failed) { + s << "threw as expected!\n"; + } else { + s << "threw a DIFFERENT exception! (contents: " << rb.m_exception << ")\n"; + } + } else { + s << "did NOT throw at all!\n"; + } + } else if(rb.m_at & + assertType::is_throws_as) { //!OCLINT bitwise operator in conditional + s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", " + << rb.m_exception_type << " ) " << Color::None + << (rb.m_threw ? (rb.m_threw_as ? "threw as expected!" : + "threw a DIFFERENT exception: ") : + "did NOT throw at all!") + << Color::Cyan << rb.m_exception << "\n"; + } else if(rb.m_at & + assertType::is_throws_with) { //!OCLINT bitwise operator in conditional + s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", \"" + << rb.m_exception_string.c_str() + << "\" ) " << Color::None + << (rb.m_threw ? (!rb.m_failed ? "threw as expected!" : + "threw a DIFFERENT exception: ") : + "did NOT throw at all!") + << Color::Cyan << rb.m_exception << "\n"; + } else if(rb.m_at & assertType::is_nothrow) { //!OCLINT bitwise operator in conditional + s << (rb.m_threw ? "THREW exception: " : "didn't throw!") << Color::Cyan + << rb.m_exception << "\n"; + } else { + s << (rb.m_threw ? "THREW exception: " : + (!rb.m_failed ? "is correct!\n" : "is NOT correct!\n")); + if(rb.m_threw) + s << rb.m_exception << "\n"; + else + s << " values: " << assertString(rb.m_at) << "( " << rb.m_decomp << " )\n"; + } + } + + // TODO: + // - log_message() + // - respond to queries + // - honor remaining options + // - more attributes in tags + struct JUnitReporter : public IReporter + { + XmlWriter xml; + DOCTEST_DECLARE_MUTEX(mutex) + Timer timer; + std::vector deepestSubcaseStackNames; + + struct JUnitTestCaseData + { + static std::string getCurrentTimestamp() { + // Beware, this is not reentrant because of backward compatibility issues + // Also, UTC only, again because of backward compatibility (%z is C++11) + time_t rawtime; + std::time(&rawtime); + auto const timeStampSize = sizeof("2017-01-16T17:06:45Z"); + + std::tm timeInfo; +#ifdef DOCTEST_PLATFORM_WINDOWS + gmtime_s(&timeInfo, &rawtime); +#else // DOCTEST_PLATFORM_WINDOWS + gmtime_r(&rawtime, &timeInfo); +#endif // DOCTEST_PLATFORM_WINDOWS + + char timeStamp[timeStampSize]; + const char* const fmt = "%Y-%m-%dT%H:%M:%SZ"; + + std::strftime(timeStamp, timeStampSize, fmt, &timeInfo); + return std::string(timeStamp); + } + + struct JUnitTestMessage + { + JUnitTestMessage(const std::string& _message, const std::string& _type, const std::string& _details) + : message(_message), type(_type), details(_details) {} + + JUnitTestMessage(const std::string& _message, const std::string& _details) + : message(_message), type(), details(_details) {} + + std::string message, type, details; + }; + + struct JUnitTestCase + { + JUnitTestCase(const std::string& _classname, const std::string& _name) + : classname(_classname), name(_name), time(0), failures() {} + + std::string classname, name; + double time; + std::vector failures, errors; + }; + + void add(const std::string& classname, const std::string& name) { + testcases.emplace_back(classname, name); + } + + void appendSubcaseNamesToLastTestcase(std::vector nameStack) { + for(auto& curr: nameStack) + if(curr.size()) + testcases.back().name += std::string("/") + curr.c_str(); + } + + void addTime(double time) { + if(time < 1e-4) + time = 0; + testcases.back().time = time; + totalSeconds += time; + } + + void addFailure(const std::string& message, const std::string& type, const std::string& details) { + testcases.back().failures.emplace_back(message, type, details); + ++totalFailures; + } + + void addError(const std::string& message, const std::string& details) { + testcases.back().errors.emplace_back(message, details); + ++totalErrors; + } + + std::vector testcases; + double totalSeconds = 0; + int totalErrors = 0, totalFailures = 0; + }; + + JUnitTestCaseData testCaseData; + + // caching pointers/references to objects of these types - safe to do + const ContextOptions& opt; + const TestCaseData* tc = nullptr; + + JUnitReporter(const ContextOptions& co) + : xml(*co.cout) + , opt(co) {} + + unsigned line(unsigned l) const { return opt.no_line_numbers ? 0 : l; } + + // ========================================================================================= + // WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE + // ========================================================================================= + + void report_query(const QueryData&) override { + xml.writeDeclaration(); + } + + void test_run_start() override { + xml.writeDeclaration(); + } + + void test_run_end(const TestRunStats& p) override { + // remove .exe extension - mainly to have the same output on UNIX and Windows + std::string binary_name = skipPathFromFilename(opt.binary_name.c_str()); +#ifdef DOCTEST_PLATFORM_WINDOWS + if(binary_name.rfind(".exe") != std::string::npos) + binary_name = binary_name.substr(0, binary_name.length() - 4); +#endif // DOCTEST_PLATFORM_WINDOWS + xml.startElement("testsuites"); + xml.startElement("testsuite").writeAttribute("name", binary_name) + .writeAttribute("errors", testCaseData.totalErrors) + .writeAttribute("failures", testCaseData.totalFailures) + .writeAttribute("tests", p.numAsserts); + if(opt.no_time_in_output == false) { + xml.writeAttribute("time", testCaseData.totalSeconds); + xml.writeAttribute("timestamp", JUnitTestCaseData::getCurrentTimestamp()); + } + if(opt.no_version == false) + xml.writeAttribute("doctest_version", DOCTEST_VERSION_STR); + + for(const auto& testCase : testCaseData.testcases) { + xml.startElement("testcase") + .writeAttribute("classname", testCase.classname) + .writeAttribute("name", testCase.name); + if(opt.no_time_in_output == false) + xml.writeAttribute("time", testCase.time); + // This is not ideal, but it should be enough to mimic gtest's junit output. + xml.writeAttribute("status", "run"); + + for(const auto& failure : testCase.failures) { + xml.scopedElement("failure") + .writeAttribute("message", failure.message) + .writeAttribute("type", failure.type) + .writeText(failure.details, false); + } + + for(const auto& error : testCase.errors) { + xml.scopedElement("error") + .writeAttribute("message", error.message) + .writeText(error.details); + } + + xml.endElement(); + } + xml.endElement(); + xml.endElement(); + } + + void test_case_start(const TestCaseData& in) override { + testCaseData.add(skipPathFromFilename(in.m_file.c_str()), in.m_name); + timer.start(); + } + + void test_case_reenter(const TestCaseData& in) override { + testCaseData.addTime(timer.getElapsedSeconds()); + testCaseData.appendSubcaseNamesToLastTestcase(deepestSubcaseStackNames); + deepestSubcaseStackNames.clear(); + + timer.start(); + testCaseData.add(skipPathFromFilename(in.m_file.c_str()), in.m_name); + } + + void test_case_end(const CurrentTestCaseStats&) override { + testCaseData.addTime(timer.getElapsedSeconds()); + testCaseData.appendSubcaseNamesToLastTestcase(deepestSubcaseStackNames); + deepestSubcaseStackNames.clear(); + } + + void test_case_exception(const TestCaseException& e) override { + DOCTEST_LOCK_MUTEX(mutex) + testCaseData.addError("exception", e.error_string.c_str()); + } + + void subcase_start(const SubcaseSignature& in) override { + deepestSubcaseStackNames.push_back(in.m_name); + } + + void subcase_end() override {} + + void log_assert(const AssertData& rb) override { + if(!rb.m_failed) // report only failures & ignore the `success` option + return; + + DOCTEST_LOCK_MUTEX(mutex) + + std::ostringstream os; + os << skipPathFromFilename(rb.m_file) << (opt.gnu_file_line ? ":" : "(") + << line(rb.m_line) << (opt.gnu_file_line ? ":" : "):") << std::endl; + + fulltext_log_assert_to_stream(os, rb); + log_contexts(os); + testCaseData.addFailure(rb.m_decomp.c_str(), assertString(rb.m_at), os.str()); + } + + void log_message(const MessageData& mb) override { + if(mb.m_severity & assertType::is_warn) // report only failures + return; + + DOCTEST_LOCK_MUTEX(mutex) + + std::ostringstream os; + os << skipPathFromFilename(mb.m_file) << (opt.gnu_file_line ? ":" : "(") + << line(mb.m_line) << (opt.gnu_file_line ? ":" : "):") << std::endl; + + os << mb.m_string.c_str() << "\n"; + log_contexts(os); + + testCaseData.addFailure(mb.m_string.c_str(), + mb.m_severity & assertType::is_check ? "FAIL_CHECK" : "FAIL", os.str()); + } + + void test_case_skipped(const TestCaseData&) override {} + + void log_contexts(std::ostringstream& s) { + int num_contexts = get_num_active_contexts(); + if(num_contexts) { + auto contexts = get_active_contexts(); + + s << " logged: "; + for(int i = 0; i < num_contexts; ++i) { + s << (i == 0 ? "" : " "); + contexts[i]->stringify(&s); + s << std::endl; + } + } + } + }; + + DOCTEST_REGISTER_REPORTER("junit", 0, JUnitReporter); + + struct Whitespace + { + int nrSpaces; + explicit Whitespace(int nr) + : nrSpaces(nr) {} + }; + + std::ostream& operator<<(std::ostream& out, const Whitespace& ws) { + if(ws.nrSpaces != 0) + out << std::setw(ws.nrSpaces) << ' '; + return out; + } + + struct ConsoleReporter : public IReporter + { + std::ostream& s; + bool hasLoggedCurrentTestStart; + std::vector subcasesStack; + size_t currentSubcaseLevel; + DOCTEST_DECLARE_MUTEX(mutex) + + // caching pointers/references to objects of these types - safe to do + const ContextOptions& opt; + const TestCaseData* tc; + + ConsoleReporter(const ContextOptions& co) + : s(*co.cout) + , opt(co) {} + + ConsoleReporter(const ContextOptions& co, std::ostream& ostr) + : s(ostr) + , opt(co) {} + + // ========================================================================================= + // WHAT FOLLOWS ARE HELPERS USED BY THE OVERRIDES OF THE VIRTUAL METHODS OF THE INTERFACE + // ========================================================================================= + + void separator_to_stream() { + s << Color::Yellow + << "===============================================================================" + "\n"; + } + + const char* getSuccessOrFailString(bool success, assertType::Enum at, + const char* success_str) { + if(success) + return success_str; + return failureString(at); + } + + Color::Enum getSuccessOrFailColor(bool success, assertType::Enum at) { + return success ? Color::BrightGreen : + (at & assertType::is_warn) ? Color::Yellow : Color::Red; + } + + void successOrFailColoredStringToStream(bool success, assertType::Enum at, + const char* success_str = "SUCCESS") { + s << getSuccessOrFailColor(success, at) + << getSuccessOrFailString(success, at, success_str) << ": "; + } + + void log_contexts() { + int num_contexts = get_num_active_contexts(); + if(num_contexts) { + auto contexts = get_active_contexts(); + + s << Color::None << " logged: "; + for(int i = 0; i < num_contexts; ++i) { + s << (i == 0 ? "" : " "); + contexts[i]->stringify(&s); + s << "\n"; + } + } + + s << "\n"; + } + + // this was requested to be made virtual so users could override it + virtual void file_line_to_stream(const char* file, int line, + const char* tail = "") { + s << Color::LightGrey << skipPathFromFilename(file) << (opt.gnu_file_line ? ":" : "(") + << (opt.no_line_numbers ? 0 : line) // 0 or the real num depending on the option + << (opt.gnu_file_line ? ":" : "):") << tail; + } + + void logTestStart() { + if(hasLoggedCurrentTestStart) + return; + + separator_to_stream(); + file_line_to_stream(tc->m_file.c_str(), tc->m_line, "\n"); + if(tc->m_description) + s << Color::Yellow << "DESCRIPTION: " << Color::None << tc->m_description << "\n"; + if(tc->m_test_suite && tc->m_test_suite[0] != '\0') + s << Color::Yellow << "TEST SUITE: " << Color::None << tc->m_test_suite << "\n"; + if(strncmp(tc->m_name, " Scenario:", 11) != 0) + s << Color::Yellow << "TEST CASE: "; + s << Color::None << tc->m_name << "\n"; + + for(size_t i = 0; i < currentSubcaseLevel; ++i) { + if(subcasesStack[i].m_name[0] != '\0') + s << " " << subcasesStack[i].m_name << "\n"; + } + + if(currentSubcaseLevel != subcasesStack.size()) { + s << Color::Yellow << "\nDEEPEST SUBCASE STACK REACHED (DIFFERENT FROM THE CURRENT ONE):\n" << Color::None; + for(size_t i = 0; i < subcasesStack.size(); ++i) { + if(subcasesStack[i].m_name[0] != '\0') + s << " " << subcasesStack[i].m_name << "\n"; + } + } + + s << "\n"; + + hasLoggedCurrentTestStart = true; + } + + void printVersion() { + if(opt.no_version == false) + s << Color::Cyan << "[doctest] " << Color::None << "doctest version is \"" + << DOCTEST_VERSION_STR << "\"\n"; + } + + void printIntro() { + if(opt.no_intro == false) { + printVersion(); + s << Color::Cyan << "[doctest] " << Color::None + << "run with \"--" DOCTEST_OPTIONS_PREFIX_DISPLAY "help\" for options\n"; + } + } + + void printHelp() { + int sizePrefixDisplay = static_cast(strlen(DOCTEST_OPTIONS_PREFIX_DISPLAY)); + printVersion(); + // clang-format off + s << Color::Cyan << "[doctest]\n" << Color::None; + s << Color::Cyan << "[doctest] " << Color::None; + s << "boolean values: \"1/on/yes/true\" or \"0/off/no/false\"\n"; + s << Color::Cyan << "[doctest] " << Color::None; + s << "filter values: \"str1,str2,str3\" (comma separated strings)\n"; + s << Color::Cyan << "[doctest]\n" << Color::None; + s << Color::Cyan << "[doctest] " << Color::None; + s << "filters use wildcards for matching strings\n"; + s << Color::Cyan << "[doctest] " << Color::None; + s << "something passes a filter if any of the strings in a filter matches\n"; +#ifndef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS + s << Color::Cyan << "[doctest]\n" << Color::None; + s << Color::Cyan << "[doctest] " << Color::None; + s << "ALL FLAGS, OPTIONS AND FILTERS ALSO AVAILABLE WITH A \"" DOCTEST_CONFIG_OPTIONS_PREFIX "\" PREFIX!!!\n"; +#endif + s << Color::Cyan << "[doctest]\n" << Color::None; + s << Color::Cyan << "[doctest] " << Color::None; + s << "Query flags - the program quits after them. Available:\n\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "?, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "help, -" DOCTEST_OPTIONS_PREFIX_DISPLAY "h " + << Whitespace(sizePrefixDisplay*0) << "prints this message\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "v, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "version " + << Whitespace(sizePrefixDisplay*1) << "prints the version\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "c, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "count " + << Whitespace(sizePrefixDisplay*1) << "prints the number of matching tests\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ltc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "list-test-cases " + << Whitespace(sizePrefixDisplay*1) << "lists all matching tests by name\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "lts, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "list-test-suites " + << Whitespace(sizePrefixDisplay*1) << "lists all matching test suites\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "lr, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "list-reporters " + << Whitespace(sizePrefixDisplay*1) << "lists all registered reporters\n\n"; + // ================================================================================== << 79 + s << Color::Cyan << "[doctest] " << Color::None; + s << "The available / options/filters are:\n\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "tc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-case= " + << Whitespace(sizePrefixDisplay*1) << "filters tests by their name\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "tce, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-case-exclude= " + << Whitespace(sizePrefixDisplay*1) << "filters OUT tests by their name\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sf, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "source-file= " + << Whitespace(sizePrefixDisplay*1) << "filters tests by their file\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sfe, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "source-file-exclude= " + << Whitespace(sizePrefixDisplay*1) << "filters OUT tests by their file\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ts, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-suite= " + << Whitespace(sizePrefixDisplay*1) << "filters tests by their test suite\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "tse, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-suite-exclude= " + << Whitespace(sizePrefixDisplay*1) << "filters OUT tests by their test suite\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "subcase= " + << Whitespace(sizePrefixDisplay*1) << "filters subcases by their name\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sce, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "subcase-exclude= " + << Whitespace(sizePrefixDisplay*1) << "filters OUT subcases by their name\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "r, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "reporters= " + << Whitespace(sizePrefixDisplay*1) << "reporters to use (console is default)\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "o, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "out= " + << Whitespace(sizePrefixDisplay*1) << "output filename\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ob, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "order-by= " + << Whitespace(sizePrefixDisplay*1) << "how the tests should be ordered\n"; + s << Whitespace(sizePrefixDisplay*3) << " - [file/suite/name/rand/none]\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "rs, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "rand-seed= " + << Whitespace(sizePrefixDisplay*1) << "seed for random ordering\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "f, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "first= " + << Whitespace(sizePrefixDisplay*1) << "the first test passing the filters to\n"; + s << Whitespace(sizePrefixDisplay*3) << " execute - for range-based execution\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "l, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "last= " + << Whitespace(sizePrefixDisplay*1) << "the last test passing the filters to\n"; + s << Whitespace(sizePrefixDisplay*3) << " execute - for range-based execution\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "aa, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "abort-after= " + << Whitespace(sizePrefixDisplay*1) << "stop after failed assertions\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "scfl,--" DOCTEST_OPTIONS_PREFIX_DISPLAY "subcase-filter-levels= " + << Whitespace(sizePrefixDisplay*1) << "apply filters for the first levels\n"; + s << Color::Cyan << "\n[doctest] " << Color::None; + s << "Bool options - can be used like flags and true is assumed. Available:\n\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "s, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "success= " + << Whitespace(sizePrefixDisplay*1) << "include successful assertions in output\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "cs, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "case-sensitive= " + << Whitespace(sizePrefixDisplay*1) << "filters being treated as case sensitive\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "e, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "exit= " + << Whitespace(sizePrefixDisplay*1) << "exits after the tests finish\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "d, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "duration= " + << Whitespace(sizePrefixDisplay*1) << "prints the time duration of each test\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "m, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "minimal= " + << Whitespace(sizePrefixDisplay*1) << "minimal console output (only failures)\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "q, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "quiet= " + << Whitespace(sizePrefixDisplay*1) << "no console output\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nt, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-throw= " + << Whitespace(sizePrefixDisplay*1) << "skips exceptions-related assert checks\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ne, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-exitcode= " + << Whitespace(sizePrefixDisplay*1) << "returns (or exits) always with success\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nr, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-run= " + << Whitespace(sizePrefixDisplay*1) << "skips all runtime doctest operations\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ni, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-intro= " + << Whitespace(sizePrefixDisplay*1) << "omit the framework intro in the output\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nv, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-version= " + << Whitespace(sizePrefixDisplay*1) << "omit the framework version in the output\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-colors= " + << Whitespace(sizePrefixDisplay*1) << "disables colors in output\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "fc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "force-colors= " + << Whitespace(sizePrefixDisplay*1) << "use colors even when not in a tty\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nb, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-breaks= " + << Whitespace(sizePrefixDisplay*1) << "disables breakpoints in debuggers\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ns, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-skip= " + << Whitespace(sizePrefixDisplay*1) << "don't skip test cases marked as skip\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "gfl, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "gnu-file-line= " + << Whitespace(sizePrefixDisplay*1) << ":n: vs (n): for line numbers in output\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "npf, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-path-filenames= " + << Whitespace(sizePrefixDisplay*1) << "only filenames and no paths in output\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "spp, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "skip-path-prefixes= " + << Whitespace(sizePrefixDisplay*1) << "whenever file paths start with this prefix, remove it from the output\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nln, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-line-numbers= " + << Whitespace(sizePrefixDisplay*1) << "0 instead of real line numbers in output\n"; + // ================================================================================== << 79 + // clang-format on + + s << Color::Cyan << "\n[doctest] " << Color::None; + s << "for more information visit the project documentation\n\n"; + } + + void printRegisteredReporters() { + printVersion(); + auto printReporters = [this] (const reporterMap& reporters, const char* type) { + if(reporters.size()) { + s << Color::Cyan << "[doctest] " << Color::None << "listing all registered " << type << "\n"; + for(auto& curr : reporters) + s << "priority: " << std::setw(5) << curr.first.first + << " name: " << curr.first.second << "\n"; + } + }; + printReporters(getListeners(), "listeners"); + printReporters(getReporters(), "reporters"); + } + + // ========================================================================================= + // WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE + // ========================================================================================= + + void report_query(const QueryData& in) override { + if(opt.version) { + printVersion(); + } else if(opt.help) { + printHelp(); + } else if(opt.list_reporters) { + printRegisteredReporters(); + } else if(opt.count || opt.list_test_cases) { + if(opt.list_test_cases) { + s << Color::Cyan << "[doctest] " << Color::None + << "listing all test case names\n"; + separator_to_stream(); + } + + for(unsigned i = 0; i < in.num_data; ++i) + s << Color::None << in.data[i]->m_name << "\n"; + + separator_to_stream(); + + s << Color::Cyan << "[doctest] " << Color::None + << "unskipped test cases passing the current filters: " + << g_cs->numTestCasesPassingFilters << "\n"; + + } else if(opt.list_test_suites) { + s << Color::Cyan << "[doctest] " << Color::None << "listing all test suites\n"; + separator_to_stream(); + + for(unsigned i = 0; i < in.num_data; ++i) + s << Color::None << in.data[i]->m_test_suite << "\n"; + + separator_to_stream(); + + s << Color::Cyan << "[doctest] " << Color::None + << "unskipped test cases passing the current filters: " + << g_cs->numTestCasesPassingFilters << "\n"; + s << Color::Cyan << "[doctest] " << Color::None + << "test suites with unskipped test cases passing the current filters: " + << g_cs->numTestSuitesPassingFilters << "\n"; + } + } + + void test_run_start() override { + if(!opt.minimal) + printIntro(); + } + + void test_run_end(const TestRunStats& p) override { + if(opt.minimal && p.numTestCasesFailed == 0) + return; + + separator_to_stream(); + s << std::dec; + + auto totwidth = int(std::ceil(log10(static_cast(std::max(p.numTestCasesPassingFilters, static_cast(p.numAsserts))) + 1))); + auto passwidth = int(std::ceil(log10(static_cast(std::max(p.numTestCasesPassingFilters - p.numTestCasesFailed, static_cast(p.numAsserts - p.numAssertsFailed))) + 1))); + auto failwidth = int(std::ceil(log10(static_cast(std::max(p.numTestCasesFailed, static_cast(p.numAssertsFailed))) + 1))); + const bool anythingFailed = p.numTestCasesFailed > 0 || p.numAssertsFailed > 0; + s << Color::Cyan << "[doctest] " << Color::None << "test cases: " << std::setw(totwidth) + << p.numTestCasesPassingFilters << " | " + << ((p.numTestCasesPassingFilters == 0 || anythingFailed) ? Color::None : + Color::Green) + << std::setw(passwidth) << p.numTestCasesPassingFilters - p.numTestCasesFailed << " passed" + << Color::None << " | " << (p.numTestCasesFailed > 0 ? Color::Red : Color::None) + << std::setw(failwidth) << p.numTestCasesFailed << " failed" << Color::None << " |"; + if(opt.no_skipped_summary == false) { + const int numSkipped = p.numTestCases - p.numTestCasesPassingFilters; + s << " " << (numSkipped == 0 ? Color::None : Color::Yellow) << numSkipped + << " skipped" << Color::None; + } + s << "\n"; + s << Color::Cyan << "[doctest] " << Color::None << "assertions: " << std::setw(totwidth) + << p.numAsserts << " | " + << ((p.numAsserts == 0 || anythingFailed) ? Color::None : Color::Green) + << std::setw(passwidth) << (p.numAsserts - p.numAssertsFailed) << " passed" << Color::None + << " | " << (p.numAssertsFailed > 0 ? Color::Red : Color::None) << std::setw(failwidth) + << p.numAssertsFailed << " failed" << Color::None << " |\n"; + s << Color::Cyan << "[doctest] " << Color::None + << "Status: " << (p.numTestCasesFailed > 0 ? Color::Red : Color::Green) + << ((p.numTestCasesFailed > 0) ? "FAILURE!" : "SUCCESS!") << Color::None << std::endl; + } + + void test_case_start(const TestCaseData& in) override { + hasLoggedCurrentTestStart = false; + tc = ∈ + subcasesStack.clear(); + currentSubcaseLevel = 0; + } + + void test_case_reenter(const TestCaseData&) override { + subcasesStack.clear(); + } + + void test_case_end(const CurrentTestCaseStats& st) override { + if(tc->m_no_output) + return; + + // log the preamble of the test case only if there is something + // else to print - something other than that an assert has failed + if(opt.duration || + (st.failure_flags && st.failure_flags != static_cast(TestCaseFailureReason::AssertFailure))) + logTestStart(); + + if(opt.duration) + s << Color::None << std::setprecision(6) << std::fixed << st.seconds + << " s: " << tc->m_name << "\n"; + + if(st.failure_flags & TestCaseFailureReason::Timeout) + s << Color::Red << "Test case exceeded time limit of " << std::setprecision(6) + << std::fixed << tc->m_timeout << "!\n"; + + if(st.failure_flags & TestCaseFailureReason::ShouldHaveFailedButDidnt) { + s << Color::Red << "Should have failed but didn't! Marking it as failed!\n"; + } else if(st.failure_flags & TestCaseFailureReason::ShouldHaveFailedAndDid) { + s << Color::Yellow << "Failed as expected so marking it as not failed\n"; + } else if(st.failure_flags & TestCaseFailureReason::CouldHaveFailedAndDid) { + s << Color::Yellow << "Allowed to fail so marking it as not failed\n"; + } else if(st.failure_flags & TestCaseFailureReason::DidntFailExactlyNumTimes) { + s << Color::Red << "Didn't fail exactly " << tc->m_expected_failures + << " times so marking it as failed!\n"; + } else if(st.failure_flags & TestCaseFailureReason::FailedExactlyNumTimes) { + s << Color::Yellow << "Failed exactly " << tc->m_expected_failures + << " times as expected so marking it as not failed!\n"; + } + if(st.failure_flags & TestCaseFailureReason::TooManyFailedAsserts) { + s << Color::Red << "Aborting - too many failed asserts!\n"; + } + s << Color::None; // lgtm [cpp/useless-expression] + } + + void test_case_exception(const TestCaseException& e) override { + DOCTEST_LOCK_MUTEX(mutex) + if(tc->m_no_output) + return; + + logTestStart(); + + file_line_to_stream(tc->m_file.c_str(), tc->m_line, " "); + successOrFailColoredStringToStream(false, e.is_crash ? assertType::is_require : + assertType::is_check); + s << Color::Red << (e.is_crash ? "test case CRASHED: " : "test case THREW exception: ") + << Color::Cyan << e.error_string << "\n"; + + int num_stringified_contexts = get_num_stringified_contexts(); + if(num_stringified_contexts) { + auto stringified_contexts = get_stringified_contexts(); + s << Color::None << " logged: "; + for(int i = num_stringified_contexts; i > 0; --i) { + s << (i == num_stringified_contexts ? "" : " ") + << stringified_contexts[i - 1] << "\n"; + } + } + s << "\n" << Color::None; + } + + void subcase_start(const SubcaseSignature& subc) override { + subcasesStack.push_back(subc); + ++currentSubcaseLevel; + hasLoggedCurrentTestStart = false; + } + + void subcase_end() override { + --currentSubcaseLevel; + hasLoggedCurrentTestStart = false; + } + + void log_assert(const AssertData& rb) override { + if((!rb.m_failed && !opt.success) || tc->m_no_output) + return; + + DOCTEST_LOCK_MUTEX(mutex) + + logTestStart(); + + file_line_to_stream(rb.m_file, rb.m_line, " "); + successOrFailColoredStringToStream(!rb.m_failed, rb.m_at); + + fulltext_log_assert_to_stream(s, rb); + + log_contexts(); + } + + void log_message(const MessageData& mb) override { + if(tc->m_no_output) + return; + + DOCTEST_LOCK_MUTEX(mutex) + + logTestStart(); + + file_line_to_stream(mb.m_file, mb.m_line, " "); + s << getSuccessOrFailColor(false, mb.m_severity) + << getSuccessOrFailString(mb.m_severity & assertType::is_warn, mb.m_severity, + "MESSAGE") << ": "; + s << Color::None << mb.m_string << "\n"; + log_contexts(); + } + + void test_case_skipped(const TestCaseData&) override {} + }; + + DOCTEST_REGISTER_REPORTER("console", 0, ConsoleReporter); + +#ifdef DOCTEST_PLATFORM_WINDOWS + struct DebugOutputWindowReporter : public ConsoleReporter + { + DOCTEST_THREAD_LOCAL static std::ostringstream oss; + + DebugOutputWindowReporter(const ContextOptions& co) + : ConsoleReporter(co, oss) {} + +#define DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(func, type, arg) \ + void func(type arg) override { \ + bool with_col = g_no_colors; \ + g_no_colors = false; \ + ConsoleReporter::func(arg); \ + if(oss.tellp() != std::streampos{}) { \ + DOCTEST_OUTPUT_DEBUG_STRING(oss.str().c_str()); \ + oss.str(""); \ + } \ + g_no_colors = with_col; \ + } + + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_run_start, DOCTEST_EMPTY, DOCTEST_EMPTY) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_run_end, const TestRunStats&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_start, const TestCaseData&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_reenter, const TestCaseData&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_end, const CurrentTestCaseStats&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_exception, const TestCaseException&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(subcase_start, const SubcaseSignature&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(subcase_end, DOCTEST_EMPTY, DOCTEST_EMPTY) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(log_assert, const AssertData&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(log_message, const MessageData&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_skipped, const TestCaseData&, in) + }; + + DOCTEST_THREAD_LOCAL std::ostringstream DebugOutputWindowReporter::oss; +#endif // DOCTEST_PLATFORM_WINDOWS + + // the implementation of parseOption() + bool parseOptionImpl(int argc, const char* const* argv, const char* pattern, String* value) { + // going from the end to the beginning and stopping on the first occurrence from the end + for(int i = argc; i > 0; --i) { + auto index = i - 1; + auto temp = std::strstr(argv[index], pattern); + if(temp && (value || strlen(temp) == strlen(pattern))) { //!OCLINT prefer early exits and continue + // eliminate matches in which the chars before the option are not '-' + bool noBadCharsFound = true; + auto curr = argv[index]; + while(curr != temp) { + if(*curr++ != '-') { + noBadCharsFound = false; + break; + } + } + if(noBadCharsFound && argv[index][0] == '-') { + if(value) { + // parsing the value of an option + temp += strlen(pattern); + const unsigned len = strlen(temp); + if(len) { + *value = temp; + return true; + } + } else { + // just a flag - no value + return true; + } + } + } + } + return false; + } + + // parses an option and returns the string after the '=' character + bool parseOption(int argc, const char* const* argv, const char* pattern, String* value = nullptr, + const String& defaultVal = String()) { + if(value) + *value = defaultVal; +#ifndef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS + // offset (normally 3 for "dt-") to skip prefix + if(parseOptionImpl(argc, argv, pattern + strlen(DOCTEST_CONFIG_OPTIONS_PREFIX), value)) + return true; +#endif // DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS + return parseOptionImpl(argc, argv, pattern, value); + } + + // locates a flag on the command line + bool parseFlag(int argc, const char* const* argv, const char* pattern) { + return parseOption(argc, argv, pattern); + } + + // parses a comma separated list of words after a pattern in one of the arguments in argv + bool parseCommaSepArgs(int argc, const char* const* argv, const char* pattern, + std::vector& res) { + String filtersString; + if(parseOption(argc, argv, pattern, &filtersString)) { + // tokenize with "," as a separator, unless escaped with backslash + std::ostringstream s; + auto flush = [&s, &res]() { + auto string = s.str(); + if(string.size() > 0) { + res.push_back(string.c_str()); + } + s.str(""); + }; + + bool seenBackslash = false; + const char* current = filtersString.c_str(); + const char* end = current + strlen(current); + while(current != end) { + char character = *current++; + if(seenBackslash) { + seenBackslash = false; + if(character == ',' || character == '\\') { + s.put(character); + continue; + } + s.put('\\'); + } + if(character == '\\') { + seenBackslash = true; + } else if(character == ',') { + flush(); + } else { + s.put(character); + } + } + + if(seenBackslash) { + s.put('\\'); + } + flush(); + return true; + } + return false; + } + + enum optionType + { + option_bool, + option_int + }; + + // parses an int/bool option from the command line + bool parseIntOption(int argc, const char* const* argv, const char* pattern, optionType type, + int& res) { + String parsedValue; + if(!parseOption(argc, argv, pattern, &parsedValue)) + return false; + + if(type) { + // integer + // TODO: change this to use std::stoi or something else! currently it uses undefined behavior - assumes '0' on failed parse... + int theInt = std::atoi(parsedValue.c_str()); + if (theInt != 0) { + res = theInt; //!OCLINT parameter reassignment + return true; + } + } else { + // boolean + const char positive[][5] = { "1", "true", "on", "yes" }; // 5 - strlen("true") + 1 + const char negative[][6] = { "0", "false", "off", "no" }; // 6 - strlen("false") + 1 + + // if the value matches any of the positive/negative possibilities + for (unsigned i = 0; i < 4; i++) { + if (parsedValue.compare(positive[i], true) == 0) { + res = 1; //!OCLINT parameter reassignment + return true; + } + if (parsedValue.compare(negative[i], true) == 0) { + res = 0; //!OCLINT parameter reassignment + return true; + } + } + } + return false; + } +} // namespace + +Context::Context(int argc, const char* const* argv) + : p(new detail::ContextState) { + parseArgs(argc, argv, true); + if(argc) + p->binary_name = argv[0]; +} + +Context::~Context() { + if(g_cs == p) + g_cs = nullptr; + delete p; +} + +void Context::applyCommandLine(int argc, const char* const* argv) { + parseArgs(argc, argv); + if(argc) + p->binary_name = argv[0]; +} + +// parses args +void Context::parseArgs(int argc, const char* const* argv, bool withDefaults) { + using namespace detail; + + // clang-format off + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "source-file=", p->filters[0]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sf=", p->filters[0]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "source-file-exclude=",p->filters[1]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sfe=", p->filters[1]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-suite=", p->filters[2]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "ts=", p->filters[2]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-suite-exclude=", p->filters[3]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "tse=", p->filters[3]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-case=", p->filters[4]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "tc=", p->filters[4]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-case-exclude=", p->filters[5]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "tce=", p->filters[5]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "subcase=", p->filters[6]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sc=", p->filters[6]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "subcase-exclude=", p->filters[7]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sce=", p->filters[7]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "reporters=", p->filters[8]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "r=", p->filters[8]); + // clang-format on + + int intRes = 0; + String strRes; + +#define DOCTEST_PARSE_AS_BOOL_OR_FLAG(name, sname, var, default) \ + if(parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name "=", option_bool, intRes) || \ + parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname "=", option_bool, intRes)) \ + p->var = static_cast(intRes); \ + else if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name) || \ + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname)) \ + p->var = true; \ + else if(withDefaults) \ + p->var = default + +#define DOCTEST_PARSE_INT_OPTION(name, sname, var, default) \ + if(parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name "=", option_int, intRes) || \ + parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname "=", option_int, intRes)) \ + p->var = intRes; \ + else if(withDefaults) \ + p->var = default + +#define DOCTEST_PARSE_STR_OPTION(name, sname, var, default) \ + if(parseOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name "=", &strRes, default) || \ + parseOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname "=", &strRes, default) || \ + withDefaults) \ + p->var = strRes + + // clang-format off + DOCTEST_PARSE_STR_OPTION("out", "o", out, ""); + DOCTEST_PARSE_STR_OPTION("order-by", "ob", order_by, "file"); + DOCTEST_PARSE_INT_OPTION("rand-seed", "rs", rand_seed, 0); + + DOCTEST_PARSE_INT_OPTION("first", "f", first, 0); + DOCTEST_PARSE_INT_OPTION("last", "l", last, UINT_MAX); + + DOCTEST_PARSE_INT_OPTION("abort-after", "aa", abort_after, 0); + DOCTEST_PARSE_INT_OPTION("subcase-filter-levels", "scfl", subcase_filter_levels, INT_MAX); + + DOCTEST_PARSE_AS_BOOL_OR_FLAG("success", "s", success, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("case-sensitive", "cs", case_sensitive, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("exit", "e", exit, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("duration", "d", duration, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("minimal", "m", minimal, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("quiet", "q", quiet, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-throw", "nt", no_throw, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-exitcode", "ne", no_exitcode, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-run", "nr", no_run, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-intro", "ni", no_intro, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-version", "nv", no_version, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-colors", "nc", no_colors, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("force-colors", "fc", force_colors, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-breaks", "nb", no_breaks, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-skip", "ns", no_skip, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("gnu-file-line", "gfl", gnu_file_line, !bool(DOCTEST_MSVC)); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-path-filenames", "npf", no_path_in_filenames, false); + DOCTEST_PARSE_STR_OPTION("strip-file-prefixes", "sfp", strip_file_prefixes, ""); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-line-numbers", "nln", no_line_numbers, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-debug-output", "ndo", no_debug_output, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-skipped-summary", "nss", no_skipped_summary, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-time-in-output", "ntio", no_time_in_output, false); + // clang-format on + + if(withDefaults) { + p->help = false; + p->version = false; + p->count = false; + p->list_test_cases = false; + p->list_test_suites = false; + p->list_reporters = false; + } + if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "help") || + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "h") || + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "?")) { + p->help = true; + p->exit = true; + } + if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "version") || + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "v")) { + p->version = true; + p->exit = true; + } + if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "count") || + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "c")) { + p->count = true; + p->exit = true; + } + if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "list-test-cases") || + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "ltc")) { + p->list_test_cases = true; + p->exit = true; + } + if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "list-test-suites") || + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "lts")) { + p->list_test_suites = true; + p->exit = true; + } + if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "list-reporters") || + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "lr")) { + p->list_reporters = true; + p->exit = true; + } +} + +// allows the user to add procedurally to the filters from the command line +void Context::addFilter(const char* filter, const char* value) { setOption(filter, value); } + +// allows the user to clear all filters from the command line +void Context::clearFilters() { + for(auto& curr : p->filters) + curr.clear(); +} + +// allows the user to override procedurally the bool options from the command line +void Context::setOption(const char* option, bool value) { + setOption(option, value ? "true" : "false"); +} + +// allows the user to override procedurally the int options from the command line +void Context::setOption(const char* option, int value) { + setOption(option, toString(value).c_str()); +} + +// allows the user to override procedurally the string options from the command line +void Context::setOption(const char* option, const char* value) { + auto argv = String("-") + option + "=" + value; + auto lvalue = argv.c_str(); + parseArgs(1, &lvalue); +} + +// users should query this in their main() and exit the program if true +bool Context::shouldExit() { return p->exit; } + +void Context::setAsDefaultForAssertsOutOfTestCases() { g_cs = p; } + +void Context::setAssertHandler(detail::assert_handler ah) { p->ah = ah; } + +void Context::setCout(std::ostream* out) { p->cout = out; } + +static class DiscardOStream : public std::ostream +{ +private: + class : public std::streambuf + { + private: + // allowing some buffering decreases the amount of calls to overflow + char buf[1024]; + + protected: + std::streamsize xsputn(const char_type*, std::streamsize count) override { return count; } + + int_type overflow(int_type ch) override { + setp(std::begin(buf), std::end(buf)); + return traits_type::not_eof(ch); + } + } discardBuf; + +public: + DiscardOStream() + : std::ostream(&discardBuf) {} +} discardOut; + +// the main function that does all the filtering and test running +int Context::run() { + using namespace detail; + + // save the old context state in case such was setup - for using asserts out of a testing context + auto old_cs = g_cs; + // this is the current contest + g_cs = p; + is_running_in_test = true; + + g_no_colors = p->no_colors; + p->resetRunData(); + + std::fstream fstr; + if(p->cout == nullptr) { + if(p->quiet) { + p->cout = &discardOut; + } else if(p->out.size()) { + // to a file if specified + fstr.open(p->out.c_str(), std::fstream::out); + p->cout = &fstr; + } else { +#ifndef DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM + // stdout by default + p->cout = &std::cout; +#else // DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM + return EXIT_FAILURE; +#endif // DOCTEST_CONFIG_NO_INCLUDE_IOSTREAM + } + } + + FatalConditionHandler::allocateAltStackMem(); + + auto cleanup_and_return = [&]() { + FatalConditionHandler::freeAltStackMem(); + + if(fstr.is_open()) + fstr.close(); + + // restore context + g_cs = old_cs; + is_running_in_test = false; + + // we have to free the reporters which were allocated when the run started + for(auto& curr : p->reporters_currently_used) + delete curr; + p->reporters_currently_used.clear(); + + if(p->numTestCasesFailed && !p->no_exitcode) + return EXIT_FAILURE; + return EXIT_SUCCESS; + }; + + // setup default reporter if none is given through the command line + if(p->filters[8].empty()) + p->filters[8].push_back("console"); + + // check to see if any of the registered reporters has been selected + for(auto& curr : getReporters()) { + if(matchesAny(curr.first.second.c_str(), p->filters[8], false, p->case_sensitive)) + p->reporters_currently_used.push_back(curr.second(*g_cs)); + } + + // TODO: check if there is nothing in reporters_currently_used + + // prepend all listeners + for(auto& curr : getListeners()) + p->reporters_currently_used.insert(p->reporters_currently_used.begin(), curr.second(*g_cs)); + +#ifdef DOCTEST_PLATFORM_WINDOWS + if(isDebuggerActive() && p->no_debug_output == false) + p->reporters_currently_used.push_back(new DebugOutputWindowReporter(*g_cs)); +#endif // DOCTEST_PLATFORM_WINDOWS + + // handle version, help and no_run + if(p->no_run || p->version || p->help || p->list_reporters) { + DOCTEST_ITERATE_THROUGH_REPORTERS(report_query, QueryData()); + + return cleanup_and_return(); + } + + std::vector testArray; + for(auto& curr : getRegisteredTests()) + testArray.push_back(&curr); + p->numTestCases = testArray.size(); + + // sort the collected records + if(!testArray.empty()) { + if(p->order_by.compare("file", true) == 0) { + std::sort(testArray.begin(), testArray.end(), fileOrderComparator); + } else if(p->order_by.compare("suite", true) == 0) { + std::sort(testArray.begin(), testArray.end(), suiteOrderComparator); + } else if(p->order_by.compare("name", true) == 0) { + std::sort(testArray.begin(), testArray.end(), nameOrderComparator); + } else if(p->order_by.compare("rand", true) == 0) { + std::srand(p->rand_seed); + + // random_shuffle implementation + const auto first = &testArray[0]; + for(size_t i = testArray.size() - 1; i > 0; --i) { + int idxToSwap = std::rand() % (i + 1); + + const auto temp = first[i]; + + first[i] = first[idxToSwap]; + first[idxToSwap] = temp; + } + } else if(p->order_by.compare("none", true) == 0) { + // means no sorting - beneficial for death tests which call into the executable + // with a specific test case in mind - we don't want to slow down the startup times + } + } + + std::set testSuitesPassingFilt; + + bool query_mode = p->count || p->list_test_cases || p->list_test_suites; + std::vector queryResults; + + if(!query_mode) + DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_start, DOCTEST_EMPTY); + + // invoke the registered functions if they match the filter criteria (or just count them) + for(auto& curr : testArray) { + const auto& tc = *curr; + + bool skip_me = false; + if(tc.m_skip && !p->no_skip) + skip_me = true; + + if(!matchesAny(tc.m_file.c_str(), p->filters[0], true, p->case_sensitive)) + skip_me = true; + if(matchesAny(tc.m_file.c_str(), p->filters[1], false, p->case_sensitive)) + skip_me = true; + if(!matchesAny(tc.m_test_suite, p->filters[2], true, p->case_sensitive)) + skip_me = true; + if(matchesAny(tc.m_test_suite, p->filters[3], false, p->case_sensitive)) + skip_me = true; + if(!matchesAny(tc.m_name, p->filters[4], true, p->case_sensitive)) + skip_me = true; + if(matchesAny(tc.m_name, p->filters[5], false, p->case_sensitive)) + skip_me = true; + + if(!skip_me) + p->numTestCasesPassingFilters++; + + // skip the test if it is not in the execution range + if((p->last < p->numTestCasesPassingFilters && p->first <= p->last) || + (p->first > p->numTestCasesPassingFilters)) + skip_me = true; + + if(skip_me) { + if(!query_mode) + DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_skipped, tc); + continue; + } + + // do not execute the test if we are to only count the number of filter passing tests + if(p->count) + continue; + + // print the name of the test and don't execute it + if(p->list_test_cases) { + queryResults.push_back(&tc); + continue; + } + + // print the name of the test suite if not done already and don't execute it + if(p->list_test_suites) { + if((testSuitesPassingFilt.count(tc.m_test_suite) == 0) && tc.m_test_suite[0] != '\0') { + queryResults.push_back(&tc); + testSuitesPassingFilt.insert(tc.m_test_suite); + p->numTestSuitesPassingFilters++; + } + continue; + } + + // execute the test if it passes all the filtering + { + p->currentTest = &tc; + + p->failure_flags = TestCaseFailureReason::None; + p->seconds = 0; + + // reset atomic counters + p->numAssertsFailedCurrentTest_atomic = 0; + p->numAssertsCurrentTest_atomic = 0; + + p->fullyTraversedSubcases.clear(); + + DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_start, tc); + + p->timer.start(); + + bool run_test = true; + + do { + // reset some of the fields for subcases (except for the set of fully passed ones) + p->reachedLeaf = false; + // May not be empty if previous subcase exited via exception. + p->subcaseStack.clear(); + p->currentSubcaseDepth = 0; + + p->shouldLogCurrentException = true; + + // reset stuff for logging with INFO() + p->stringifiedContexts.clear(); + +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + try { +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS +// MSVC 2015 diagnoses fatalConditionHandler as unused (because reset() is a static method) +DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4101) // unreferenced local variable + FatalConditionHandler fatalConditionHandler; // Handle signals + // execute the test + tc.m_test(); + fatalConditionHandler.reset(); +DOCTEST_MSVC_SUPPRESS_WARNING_POP +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + } catch(const TestFailureException&) { + p->failure_flags |= TestCaseFailureReason::AssertFailure; + } catch(...) { + DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_exception, + {translateActiveException(), false}); + p->failure_flags |= TestCaseFailureReason::Exception; + } +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + + // exit this loop if enough assertions have failed - even if there are more subcases + if(p->abort_after > 0 && + p->numAssertsFailed + p->numAssertsFailedCurrentTest_atomic >= p->abort_after) { + run_test = false; + p->failure_flags |= TestCaseFailureReason::TooManyFailedAsserts; + } + + if(!p->nextSubcaseStack.empty() && run_test) + DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_reenter, tc); + if(p->nextSubcaseStack.empty()) + run_test = false; + } while(run_test); + + p->finalizeTestCaseData(); + + DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_end, *g_cs); + + p->currentTest = nullptr; + + // stop executing tests if enough assertions have failed + if(p->abort_after > 0 && p->numAssertsFailed >= p->abort_after) + break; + } + } + + if(!query_mode) { + DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_end, *g_cs); + } else { + QueryData qdata; + qdata.run_stats = g_cs; + qdata.data = queryResults.data(); + qdata.num_data = unsigned(queryResults.size()); + DOCTEST_ITERATE_THROUGH_REPORTERS(report_query, qdata); + } + + return cleanup_and_return(); +} + +DOCTEST_DEFINE_INTERFACE(IReporter) + +int IReporter::get_num_active_contexts() { return detail::g_infoContexts.size(); } +const IContextScope* const* IReporter::get_active_contexts() { + return get_num_active_contexts() ? &detail::g_infoContexts[0] : nullptr; +} + +int IReporter::get_num_stringified_contexts() { return detail::g_cs->stringifiedContexts.size(); } +const String* IReporter::get_stringified_contexts() { + return get_num_stringified_contexts() ? &detail::g_cs->stringifiedContexts[0] : nullptr; +} + +namespace detail { + void registerReporterImpl(const char* name, int priority, reporterCreatorFunc c, bool isReporter) { + if(isReporter) + getReporters().insert(reporterMap::value_type(reporterMap::key_type(priority, name), c)); + else + getListeners().insert(reporterMap::value_type(reporterMap::key_type(priority, name), c)); + } +} // namespace detail + +} // namespace doctest + +#endif // DOCTEST_CONFIG_DISABLE + +#ifdef DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN +DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4007) // 'function' : must be 'attribute' - see issue #182 +int main(int argc, char** argv) { return doctest::Context(argc, argv).run(); } +DOCTEST_MSVC_SUPPRESS_WARNING_POP +#endif // DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN + +DOCTEST_CLANG_SUPPRESS_WARNING_POP +DOCTEST_MSVC_SUPPRESS_WARNING_POP +DOCTEST_GCC_SUPPRESS_WARNING_POP + +DOCTEST_SUPPRESS_COMMON_WARNINGS_POP + +#endif // DOCTEST_LIBRARY_IMPLEMENTATION +#endif // DOCTEST_CONFIG_IMPLEMENT + +#ifdef DOCTEST_UNDEF_WIN32_LEAN_AND_MEAN +#undef WIN32_LEAN_AND_MEAN +#undef DOCTEST_UNDEF_WIN32_LEAN_AND_MEAN +#endif // DOCTEST_UNDEF_WIN32_LEAN_AND_MEAN + +#ifdef DOCTEST_UNDEF_NOMINMAX +#undef NOMINMAX +#undef DOCTEST_UNDEF_NOMINMAX +#endif // DOCTEST_UNDEF_NOMINMAX diff --git a/3rdparty/simple-protobuf/test/etl-compatibility.cpp b/3rdparty/simple-protobuf/test/etl-compatibility.cpp new file mode 100644 index 0000000..22009ce --- /dev/null +++ b/3rdparty/simple-protobuf/test/etl-compatibility.cpp @@ -0,0 +1,539 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN +#include "doctest.h" + +namespace std +{ +template < size_t S > +auto operator==( const string & lhs, const ::etl::vector< byte, S > & rhs ) noexcept -> bool +{ + return lhs.size( ) == rhs.size( ) && ( memcmp( lhs.data( ), rhs.data( ), lhs.size( ) ) == 0 ); +} +template < size_t S > +auto operator==( const ::etl::string< S > & lhs, const string & rhs ) noexcept -> bool +{ + return lhs.size( ) == rhs.size( ) && ( memcmp( lhs.data( ), rhs.data( ), lhs.size( ) ) == 0 ); +} +}// namespace std + +namespace Test +{ + +namespace Etl::Scalar +{ + +template < typename T > +concept HasValueMember = requires( T t ) { + { + t.value + }; +}; + +template < typename T > + requires HasValueMember< T > +auto operator==( const T & lhs, const T & rhs ) noexcept -> bool +{ + return lhs.value == rhs.value; +} +}// namespace Etl::Scalar +}// namespace Test + +namespace PhoneBook::Etl +{ +auto operator==( const Person::PhoneNumber & lhs, const Person::PhoneNumber & rhs ) noexcept -> bool +{ + return lhs.number == rhs.number && lhs.type == rhs.type; +} + +auto operator==( const Person & lhs, const Person & rhs ) noexcept -> bool +{ + return lhs.name == rhs.name && lhs.id == rhs.id && lhs.email == rhs.email && lhs.phones == rhs.phones; +} +}// namespace PhoneBook::Etl + +namespace +{ +auto to_bytes( std::string_view str ) -> etl::vector< std::byte, 8 > +{ + auto span = std::span< std::byte >( ( std::byte * ) str.data( ), str.size( ) ); + return { span.data( ), span.data( ) + span.size( ) }; +} + +template < typename T > +concept is_gpb_repeated = requires( T t ) { + { + t.value( 0 ) + }; +}; + +template < typename T > +auto opt_size( const std::optional< T > & opt ) -> std::size_t +{ + if( opt.has_value( ) ) + { + return opt.value( ).size( ); + } + return 0; +} + +template < typename T, size_t S > +auto opt_size( const etl::vector< T, S > & opt ) -> std::size_t +{ + return opt.size( ); +} + +template < typename GPB, typename SPB > +void gpb_test( const SPB & spb ) +{ + auto gpb = GPB( ); + auto spb_serialized = spb::pb::serialize( spb ); + + REQUIRE( gpb.ParseFromString( spb_serialized ) ); + if constexpr( is_gpb_repeated< GPB > ) + { + REQUIRE( gpb.value( ).size( ) == opt_size( spb.value ) ); + for( size_t i = 0; i < opt_size( spb.value ); ++i ) + { + REQUIRE( gpb.value( i ) == spb.value[ i ] ); + } + } + else + { + REQUIRE( spb.value == gpb.value( ) ); + } + auto gpb_serialized = std::string( ); + REQUIRE( gpb.SerializeToString( &gpb_serialized ) ); + REQUIRE( spb::pb::deserialize< SPB >( gpb_serialized ) == spb ); +} + +template < typename GPB, typename SPB > +void gpb_json( const SPB & spb ) +{ + auto gpb = GPB( ); + auto spb_serialized = spb::json::serialize( spb ); + + auto parse_options = google::protobuf::util::JsonParseOptions{ }; + ( void ) JsonStringToMessage( spb_serialized, &gpb, parse_options ); + + if constexpr( is_gpb_repeated< GPB > ) + { + REQUIRE( gpb.value( ).size( ) == opt_size( spb.value ) ); + for( size_t i = 0; i < opt_size( spb.value ); ++i ) + { + REQUIRE( gpb.value( i ) == spb.value[ i ] ); + } + } + else + { + REQUIRE( spb.value == gpb.value( ) ); + } + auto json_string = std::string( ); + auto print_options = google::protobuf::util::JsonPrintOptions( ); + print_options.preserve_proto_field_names = true; + + ( void ) MessageToJsonString( gpb, &json_string, print_options ); + REQUIRE( spb::json::deserialize< SPB >( json_string ) == spb ); + + print_options.preserve_proto_field_names = false; + json_string.clear( ); + ( void ) MessageToJsonString( gpb, &json_string, print_options ); + REQUIRE( spb::json::deserialize< SPB >( json_string ) == spb ); +} + +template < typename GPB, typename SPB > +void gpb_compatibility( const SPB & spb ) +{ + SUBCASE( "gpb serialize/deserialize" ) + { + gpb_test< GPB, SPB >( spb ); + } + SUBCASE( "json serialize/deserialize" ) + { + gpb_json< GPB, SPB >( spb ); + } +} + +}// namespace + +using namespace std::literals; + +TEST_CASE( "string" ) +{ + SUBCASE( "required" ) + { + gpb_compatibility< Test::Etl::Scalar::gpb::ReqString >( Test::Etl::Scalar::ReqString{ .value = "hello" } ); + } + SUBCASE( "optional" ) + { + gpb_compatibility< Test::Etl::Scalar::gpb::OptString >( Test::Etl::Scalar::OptString{ .value = "hello" } ); + } + SUBCASE( "repeated" ) + { + gpb_compatibility< Test::Etl::Scalar::gpb::RepString >( Test::Etl::Scalar::RepString{ .value = { "hello" } } ); + gpb_compatibility< Test::Etl::Scalar::gpb::RepString >( Test::Etl::Scalar::RepString{ .value = { "hello", "world" } } ); + } +} +TEST_CASE( "bool" ) +{ + SUBCASE( "required" ) + { + gpb_compatibility< Test::Etl::Scalar::gpb::ReqBool >( Test::Etl::Scalar::ReqBool{ .value = true } ); + gpb_compatibility< Test::Etl::Scalar::gpb::ReqBool >( Test::Etl::Scalar::ReqBool{ .value = false } ); + } + SUBCASE( "optional" ) + { + gpb_compatibility< Test::Etl::Scalar::gpb::OptBool >( Test::Etl::Scalar::OptBool{ .value = true } ); + gpb_compatibility< Test::Etl::Scalar::gpb::OptBool >( Test::Etl::Scalar::OptBool{ .value = false } ); + } + SUBCASE( "repeated" ) + { + gpb_compatibility< Test::Etl::Scalar::gpb::RepBool >( Test::Etl::Scalar::RepBool{ .value = { true } } ); + gpb_compatibility< Test::Etl::Scalar::gpb::RepBool >( Test::Etl::Scalar::RepBool{ .value = { true, false } } ); + + SUBCASE( "packed" ) + { + gpb_compatibility< Test::Etl::Scalar::gpb::RepPackBool >( Test::Etl::Scalar::RepPackBool{ .value = { true } } ); + gpb_compatibility< Test::Etl::Scalar::gpb::RepPackBool >( Test::Etl::Scalar::RepPackBool{ .value = { true, false } } ); + } + } +} +TEST_CASE( "int" ) +{ + SUBCASE( "varint32" ) + { + SUBCASE( "required" ) + { + gpb_compatibility< Test::Etl::Scalar::gpb::ReqInt32 >( Test::Etl::Scalar::ReqInt32{ .value = 0x42 } ); + gpb_compatibility< Test::Etl::Scalar::gpb::ReqInt32 >( Test::Etl::Scalar::ReqInt32{ .value = 0xff } ); + gpb_compatibility< Test::Etl::Scalar::gpb::ReqInt32 >( Test::Etl::Scalar::ReqInt32{ .value = -2 } ); + } + SUBCASE( "optional" ) + { + gpb_compatibility< Test::Etl::Scalar::gpb::OptInt32 >( Test::Etl::Scalar::OptInt32{ .value = 0x42 } ); + gpb_compatibility< Test::Etl::Scalar::gpb::OptInt32 >( Test::Etl::Scalar::OptInt32{ .value = 0xff } ); + gpb_compatibility< Test::Etl::Scalar::gpb::OptInt32 >( Test::Etl::Scalar::OptInt32{ .value = -2 } ); + } + SUBCASE( "repeated" ) + { + gpb_compatibility< Test::Etl::Scalar::gpb::RepInt32 >( Test::Etl::Scalar::RepInt32{ .value = { 0x42 } } ); + gpb_compatibility< Test::Etl::Scalar::gpb::RepInt32 >( Test::Etl::Scalar::RepInt32{ .value = { 0x42, 0x3 } } ); + + SUBCASE( "packed" ) + { + gpb_compatibility< Test::Etl::Scalar::gpb::RepPackInt32 >( Test::Etl::Scalar::RepPackInt32{ .value = { 0x42 } } ); + gpb_compatibility< Test::Etl::Scalar::gpb::RepPackInt32 >( Test::Etl::Scalar::RepPackInt32{ .value = { 0x42, 0x3 } } ); + } + } + } + SUBCASE( "varint64" ) + { + SUBCASE( "required" ) + { + gpb_compatibility< Test::Etl::Scalar::gpb::ReqInt64 >( Test::Etl::Scalar::ReqInt64{ .value = 0x42 } ); + gpb_compatibility< Test::Etl::Scalar::gpb::ReqInt64 >( Test::Etl::Scalar::ReqInt64{ .value = 0xff } ); + gpb_compatibility< Test::Etl::Scalar::gpb::ReqInt64 >( Test::Etl::Scalar::ReqInt64{ .value = -2 } ); + } + SUBCASE( "optional" ) + { + gpb_compatibility< Test::Etl::Scalar::gpb::OptInt64 >( Test::Etl::Scalar::OptInt64{ .value = 0x42 } ); + gpb_compatibility< Test::Etl::Scalar::gpb::OptInt64 >( Test::Etl::Scalar::OptInt64{ .value = 0xff } ); + gpb_compatibility< Test::Etl::Scalar::gpb::OptInt64 >( Test::Etl::Scalar::OptInt64{ .value = -2 } ); + } + SUBCASE( "repeated" ) + { + gpb_compatibility< Test::Etl::Scalar::gpb::RepInt64 >( Test::Etl::Scalar::RepInt64{ .value = { 0x42 } } ); + gpb_compatibility< Test::Etl::Scalar::gpb::RepInt64 >( Test::Etl::Scalar::RepInt64{ .value = { 0x42, 0x3 } } ); + + SUBCASE( "packed" ) + { + gpb_compatibility< Test::Etl::Scalar::gpb::RepPackInt64 >( Test::Etl::Scalar::RepPackInt64{ .value = { 0x42 } } ); + gpb_compatibility< Test::Etl::Scalar::gpb::RepPackInt64 >( Test::Etl::Scalar::RepPackInt64{ .value = { 0x42, 0x3 } } ); + } + } + } + SUBCASE( "svarint32" ) + { + SUBCASE( "required" ) + { + gpb_compatibility< Test::Etl::Scalar::gpb::ReqSint32 >( Test::Etl::Scalar::ReqSint32{ .value = 0x42 } ); + gpb_compatibility< Test::Etl::Scalar::gpb::ReqSint32 >( Test::Etl::Scalar::ReqSint32{ .value = 0xff } ); + gpb_compatibility< Test::Etl::Scalar::gpb::ReqSint32 >( Test::Etl::Scalar::ReqSint32{ .value = -2 } ); + } + SUBCASE( "optional" ) + { + gpb_compatibility< Test::Etl::Scalar::gpb::OptSint32 >( Test::Etl::Scalar::OptSint32{ .value = 0x42 } ); + gpb_compatibility< Test::Etl::Scalar::gpb::OptSint32 >( Test::Etl::Scalar::OptSint32{ .value = 0xff } ); + gpb_compatibility< Test::Etl::Scalar::gpb::OptSint32 >( Test::Etl::Scalar::OptSint32{ .value = -2 } ); + } + SUBCASE( "repeated" ) + { + gpb_compatibility< Test::Etl::Scalar::gpb::RepSint32 >( Test::Etl::Scalar::RepSint32{ .value = { 0x42 } } ); + gpb_compatibility< Test::Etl::Scalar::gpb::RepSint32 >( Test::Etl::Scalar::RepSint32{ .value = { 0x42, -2 } } ); + + SUBCASE( "packed" ) + { + gpb_compatibility< Test::Etl::Scalar::gpb::RepPackSint32 >( Test::Etl::Scalar::RepPackSint32{ .value = { 0x42 } } ); + gpb_compatibility< Test::Etl::Scalar::gpb::RepPackSint32 >( Test::Etl::Scalar::RepPackSint32{ .value = { 0x42, -2 } } ); + } + } + } + SUBCASE( "svarint64" ) + { + SUBCASE( "required" ) + { + gpb_compatibility< Test::Etl::Scalar::gpb::ReqSint64 >( Test::Etl::Scalar::ReqSint64{ .value = 0x42 } ); + gpb_compatibility< Test::Etl::Scalar::gpb::ReqSint64 >( Test::Etl::Scalar::ReqSint64{ .value = 0xff } ); + gpb_compatibility< Test::Etl::Scalar::gpb::ReqSint64 >( Test::Etl::Scalar::ReqSint64{ .value = -2 } ); + } + SUBCASE( "optional" ) + { + gpb_compatibility< Test::Etl::Scalar::gpb::OptSint64 >( Test::Etl::Scalar::OptSint64{ .value = 0x42 } ); + gpb_compatibility< Test::Etl::Scalar::gpb::OptSint64 >( Test::Etl::Scalar::OptSint64{ .value = 0xff } ); + gpb_compatibility< Test::Etl::Scalar::gpb::OptSint64 >( Test::Etl::Scalar::OptSint64{ .value = -2 } ); + } + SUBCASE( "repeated" ) + { + gpb_compatibility< Test::Etl::Scalar::gpb::RepSint64 >( Test::Etl::Scalar::RepSint64{ .value = { 0x42 } } ); + gpb_compatibility< Test::Etl::Scalar::gpb::RepSint64 >( Test::Etl::Scalar::RepSint64{ .value = { 0x42, -2 } } ); + + SUBCASE( "packed" ) + { + gpb_compatibility< Test::Etl::Scalar::gpb::RepPackSint64 >( Test::Etl::Scalar::RepPackSint64{ .value = { 0x42 } } ); + gpb_compatibility< Test::Etl::Scalar::gpb::RepPackSint64 >( Test::Etl::Scalar::RepPackSint64{ .value = { 0x42, -2 } } ); + } + } + } + SUBCASE( "fixed32" ) + { + SUBCASE( "required" ) + { + gpb_compatibility< Test::Etl::Scalar::gpb::ReqFixed32 >( Test::Etl::Scalar::ReqFixed32{ .value = 0x42 } ); + gpb_compatibility< Test::Etl::Scalar::gpb::ReqFixed32 >( Test::Etl::Scalar::ReqFixed32{ .value = 0xff } ); + gpb_compatibility< Test::Etl::Scalar::gpb::ReqFixed32 >( Test::Etl::Scalar::ReqFixed32{ .value = uint32_t( -2 ) } ); + } + SUBCASE( "optional" ) + { + gpb_compatibility< Test::Etl::Scalar::gpb::OptFixed32 >( Test::Etl::Scalar::OptFixed32{ .value = 0x42 } ); + gpb_compatibility< Test::Etl::Scalar::gpb::OptFixed32 >( Test::Etl::Scalar::OptFixed32{ .value = 0xff } ); + gpb_compatibility< Test::Etl::Scalar::gpb::OptFixed32 >( Test::Etl::Scalar::OptFixed32{ .value = -2 } ); + } + SUBCASE( "repeated" ) + { + gpb_compatibility< Test::Etl::Scalar::gpb::RepFixed32 >( Test::Etl::Scalar::RepFixed32{ .value = { 0x42 } } ); + gpb_compatibility< Test::Etl::Scalar::gpb::RepFixed32 >( Test::Etl::Scalar::RepFixed32{ .value = { 0x42, 0x3 } } ); + + SUBCASE( "packed" ) + { + gpb_compatibility< Test::Etl::Scalar::gpb::RepPackFixed32 >( Test::Etl::Scalar::RepPackFixed32{ .value = { 0x42 } } ); + gpb_compatibility< Test::Etl::Scalar::gpb::RepPackFixed32 >( Test::Etl::Scalar::RepPackFixed32{ .value = { 0x42, 0x3 } } ); + } + } + } + SUBCASE( "fixed64" ) + { + SUBCASE( "required" ) + { + gpb_compatibility< Test::Etl::Scalar::gpb::ReqFixed64 >( Test::Etl::Scalar::ReqFixed64{ .value = 0x42 } ); + gpb_compatibility< Test::Etl::Scalar::gpb::ReqFixed64 >( Test::Etl::Scalar::ReqFixed64{ .value = 0xff } ); + gpb_compatibility< Test::Etl::Scalar::gpb::ReqFixed64 >( Test::Etl::Scalar::ReqFixed64{ .value = uint64_t( -2 ) } ); + } + SUBCASE( "optional" ) + { + gpb_compatibility< Test::Etl::Scalar::gpb::OptFixed64 >( Test::Etl::Scalar::OptFixed64{ .value = 0x42 } ); + gpb_compatibility< Test::Etl::Scalar::gpb::OptFixed64 >( Test::Etl::Scalar::OptFixed64{ .value = 0xff } ); + gpb_compatibility< Test::Etl::Scalar::gpb::OptFixed64 >( Test::Etl::Scalar::OptFixed64{ .value = -2 } ); + } + SUBCASE( "repeated" ) + { + gpb_compatibility< Test::Etl::Scalar::gpb::RepFixed64 >( Test::Etl::Scalar::RepFixed64{ .value = { 0x42 } } ); + gpb_compatibility< Test::Etl::Scalar::gpb::RepFixed64 >( Test::Etl::Scalar::RepFixed64{ .value = { 0x42, 0x3 } } ); + SUBCASE( "packed" ) + { + gpb_compatibility< Test::Etl::Scalar::gpb::RepPackFixed64 >( Test::Etl::Scalar::RepPackFixed64{ .value = { 0x42 } } ); + gpb_compatibility< Test::Etl::Scalar::gpb::RepPackFixed64 >( Test::Etl::Scalar::RepPackFixed64{ .value = { 0x42, 0x3 } } ); + } + } + } + SUBCASE( "sfixed32" ) + { + SUBCASE( "required" ) + { + gpb_compatibility< Test::Etl::Scalar::gpb::ReqSfixed32 >( Test::Etl::Scalar::ReqSfixed32{ .value = 0x42 } ); + gpb_compatibility< Test::Etl::Scalar::gpb::ReqSfixed32 >( Test::Etl::Scalar::ReqSfixed32{ .value = 0xff } ); + gpb_compatibility< Test::Etl::Scalar::gpb::ReqSfixed32 >( Test::Etl::Scalar::ReqSfixed32{ .value = -2 } ); + } + SUBCASE( "optional" ) + { + gpb_compatibility< Test::Etl::Scalar::gpb::OptSfixed32 >( Test::Etl::Scalar::OptSfixed32{ .value = 0x42 } ); + gpb_compatibility< Test::Etl::Scalar::gpb::OptSfixed32 >( Test::Etl::Scalar::OptSfixed32{ .value = 0xff } ); + gpb_compatibility< Test::Etl::Scalar::gpb::OptSfixed32 >( Test::Etl::Scalar::OptSfixed32{ .value = -2 } ); + } + SUBCASE( "repeated" ) + { + gpb_compatibility< Test::Etl::Scalar::gpb::RepSfixed32 >( Test::Etl::Scalar::RepSfixed32{ .value = { 0x42 } } ); + gpb_compatibility< Test::Etl::Scalar::gpb::RepSfixed32 >( Test::Etl::Scalar::RepSfixed32{ .value = { 0x42, 0x3 } } ); + + SUBCASE( "packed" ) + { + gpb_compatibility< Test::Etl::Scalar::gpb::RepPackSfixed32 >( Test::Etl::Scalar::RepPackSfixed32{ .value = { 0x42 } } ); + gpb_compatibility< Test::Etl::Scalar::gpb::RepPackSfixed32 >( Test::Etl::Scalar::RepPackSfixed32{ .value = { 0x42, 0x3 } } ); + } + } + } + SUBCASE( "sfixed64" ) + { + SUBCASE( "required" ) + { + gpb_compatibility< Test::Etl::Scalar::gpb::ReqSfixed64 >( Test::Etl::Scalar::ReqSfixed64{ .value = 0x42 } ); + gpb_compatibility< Test::Etl::Scalar::gpb::ReqSfixed64 >( Test::Etl::Scalar::ReqSfixed64{ .value = 0xff } ); + gpb_compatibility< Test::Etl::Scalar::gpb::ReqSfixed64 >( Test::Etl::Scalar::ReqSfixed64{ .value = -2 } ); + } + SUBCASE( "optional" ) + { + gpb_compatibility< Test::Etl::Scalar::gpb::OptSfixed64 >( Test::Etl::Scalar::OptSfixed64{ .value = 0x42 } ); + gpb_compatibility< Test::Etl::Scalar::gpb::OptSfixed64 >( Test::Etl::Scalar::OptSfixed64{ .value = 0xff } ); + gpb_compatibility< Test::Etl::Scalar::gpb::OptSfixed64 >( Test::Etl::Scalar::OptSfixed64{ .value = -2 } ); + } + SUBCASE( "repeated" ) + { + gpb_compatibility< Test::Etl::Scalar::gpb::RepSfixed64 >( Test::Etl::Scalar::RepSfixed64{ .value = { 0x42 } } ); + gpb_compatibility< Test::Etl::Scalar::gpb::RepSfixed64 >( Test::Etl::Scalar::RepSfixed64{ .value = { 0x42, 0x3 } } ); + + SUBCASE( "packed" ) + { + gpb_compatibility< Test::Etl::Scalar::gpb::RepPackSfixed64 >( Test::Etl::Scalar::RepPackSfixed64{ .value = { 0x42 } } ); + gpb_compatibility< Test::Etl::Scalar::gpb::RepPackSfixed64 >( Test::Etl::Scalar::RepPackSfixed64{ .value = { 0x42, 0x3 } } ); + } + } + } +} +TEST_CASE( "float" ) +{ + SUBCASE( "required" ) + { + gpb_compatibility< Test::Etl::Scalar::gpb::ReqFloat >( Test::Etl::Scalar::ReqFloat{ .value = 42.0 } ); + } + SUBCASE( "optional" ) + { + gpb_compatibility< Test::Etl::Scalar::gpb::OptFloat >( Test::Etl::Scalar::OptFloat{ .value = 42.3 } ); + } + SUBCASE( "repeated" ) + { + gpb_compatibility< Test::Etl::Scalar::gpb::RepFloat >( Test::Etl::Scalar::RepFloat{ .value = { 42.3 } } ); + gpb_compatibility< Test::Etl::Scalar::gpb::RepFloat >( Test::Etl::Scalar::RepFloat{ .value = { 42.0, 42.3 } } ); + } +} +TEST_CASE( "double" ) +{ + SUBCASE( "required" ) + { + gpb_compatibility< Test::Etl::Scalar::gpb::ReqDouble >( Test::Etl::Scalar::ReqDouble{ .value = 42.0 } ); + } + SUBCASE( "optional" ) + { + gpb_compatibility< Test::Etl::Scalar::gpb::OptDouble >( Test::Etl::Scalar::OptDouble{ .value = 42.3 } ); + } + SUBCASE( "repeated" ) + { + gpb_compatibility< Test::Etl::Scalar::gpb::RepDouble >( Test::Etl::Scalar::RepDouble{ .value = { 42.3 } } ); + gpb_compatibility< Test::Etl::Scalar::gpb::RepDouble >( Test::Etl::Scalar::RepDouble{ .value = { 42.3, 3.0 } } ); + } +} +TEST_CASE( "bytes" ) +{ + SUBCASE( "required" ) + { + gpb_compatibility< Test::Etl::Scalar::gpb::ReqBytes >( Test::Etl::Scalar::ReqBytes{ .value = to_bytes( "hello" ) } ); + gpb_compatibility< Test::Etl::Scalar::gpb::ReqBytes >( Test::Etl::Scalar::ReqBytes{ .value = to_bytes( "\x00\x01\x02"sv ) } ); + gpb_compatibility< Test::Etl::Scalar::gpb::ReqBytes >( Test::Etl::Scalar::ReqBytes{ .value = to_bytes( "\x00\x01\x02\x03\x04"sv ) } ); + } + SUBCASE( "optional" ) + { + gpb_compatibility< Test::Etl::Scalar::gpb::OptBytes >( Test::Etl::Scalar::OptBytes{ .value = to_bytes( "hello" ) } ); + gpb_compatibility< Test::Etl::Scalar::gpb::OptBytes >( Test::Etl::Scalar::OptBytes{ .value = to_bytes( "\x00\x01\x02"sv ) } ); + gpb_compatibility< Test::Etl::Scalar::gpb::OptBytes >( Test::Etl::Scalar::OptBytes{ .value = to_bytes( "\x00\x01\x02\x03\x04"sv ) } ); + } + SUBCASE( "repeated" ) + { + gpb_compatibility< Test::Etl::Scalar::gpb::RepBytes >( Test::Etl::Scalar::RepBytes{ .value = { to_bytes( "hello" ) } } ); + gpb_compatibility< Test::Etl::Scalar::gpb::RepBytes >( Test::Etl::Scalar::RepBytes{ .value = { to_bytes( "\x00\x01\x02"sv ) } } ); + gpb_compatibility< Test::Etl::Scalar::gpb::RepBytes >( Test::Etl::Scalar::RepBytes{ .value = { to_bytes( "\x00\x01\x02\x03\x04"sv ) } } ); + } +} +TEST_CASE( "person" ) +{ + auto gpb = PhoneBook::Etl::gpb::Person( ); + auto spb = PhoneBook::Etl::Person{ + .name = "John Doe", + .id = 123, + .email = "QXUeh@example.com", + .phones = { + PhoneBook::Etl::Person::PhoneNumber{ + .number = "555-4321", + .type = PhoneBook::Etl::Person::PhoneType::HOME, + }, + PhoneBook::Etl::Person::PhoneNumber{ + .number = "999-1234", + .type = PhoneBook::Etl::Person::PhoneType::MOBILE, + }, + } + }; + SUBCASE( "gpb serialize/deserialize" ) + { + auto spb_serialized = spb::pb::serialize( spb ); + + REQUIRE( gpb.ParseFromString( spb_serialized ) ); + REQUIRE( gpb.name( ) == spb.name ); + REQUIRE( gpb.id( ) == spb.id ); + REQUIRE( gpb.email( ) == spb.email ); + REQUIRE( gpb.phones_size( ) == 2 ); + for( auto i = 0; i < gpb.phones_size( ); i++ ) + { + REQUIRE( gpb.phones( i ).number( ) == spb.phones[ i ].number ); + REQUIRE( int( gpb.phones( i ).type( ) ) == int( spb.phones[ i ].type.value( ) ) ); + } + + auto gpb_serialized = std::string( ); + REQUIRE( gpb.SerializeToString( &gpb_serialized ) ); + REQUIRE( spb::pb::deserialize< PhoneBook::Etl::Person >( gpb_serialized ) == spb ); + } + SUBCASE( "json serialize/deserialize" ) + { + auto spb_json = spb::json::serialize( spb ); + + auto parse_options = google::protobuf::util::JsonParseOptions{ }; + ( void ) JsonStringToMessage( spb_json, &gpb, parse_options ); + + REQUIRE( gpb.name( ) == spb.name ); + REQUIRE( gpb.id( ) == spb.id ); + REQUIRE( gpb.email( ) == spb.email ); + REQUIRE( gpb.phones_size( ) == 2 ); + for( auto i = 0; i < gpb.phones_size( ); i++ ) + { + REQUIRE( gpb.phones( i ).number( ) == spb.phones[ i ].number ); + REQUIRE( int( gpb.phones( i ).type( ) ) == int( spb.phones[ i ].type.value( ) ) ); + } + + auto json_string = std::string( ); + auto print_options = google::protobuf::util::JsonPrintOptions( ); + print_options.preserve_proto_field_names = true; + + ( void ) MessageToJsonString( gpb, &json_string, print_options ); + REQUIRE( spb::json::deserialize< PhoneBook::Etl::Person >( json_string ) == spb ); + + print_options.preserve_proto_field_names = false; + json_string.clear( ); + ( void ) MessageToJsonString( gpb, &json_string, print_options ); + REQUIRE( spb::json::deserialize< PhoneBook::Etl::Person >( json_string ) == spb ); + } +} diff --git a/3rdparty/simple-protobuf/test/fuzzer/spb-json-fuzzer.cpp b/3rdparty/simple-protobuf/test/fuzzer/spb-json-fuzzer.cpp new file mode 100644 index 0000000..e45caea --- /dev/null +++ b/3rdparty/simple-protobuf/test/fuzzer/spb-json-fuzzer.cpp @@ -0,0 +1,28 @@ +#include +#include +#include +#include +#include +#include +#include + +template < typename T > +static void json_decode( const uint8_t * Data, size_t Size ) +{ + try + { + auto result = spb::json::deserialize< T >( { reinterpret_cast< const char * >( Data ), Size } ); + } + catch( ... ) + { + } +} + +extern "C" int LLVMFuzzerTestOneInput( const uint8_t * Data, size_t Size ) +{ + json_decode< PhoneBook::Person >( Data, Size ); + json_decode< proto3_unittest::TestAllTypes >( Data, Size ); + json_decode< proto3_unittest::TestEmptyMessage >( Data, Size ); + + return 0; +} \ No newline at end of file diff --git a/3rdparty/simple-protobuf/test/fuzzer/spb-pb-fuzzer.cpp b/3rdparty/simple-protobuf/test/fuzzer/spb-pb-fuzzer.cpp new file mode 100644 index 0000000..b8a05c7 --- /dev/null +++ b/3rdparty/simple-protobuf/test/fuzzer/spb-pb-fuzzer.cpp @@ -0,0 +1,28 @@ +#include +#include +#include +#include +#include +#include +#include + +template < typename T > +static void pb_decode( const uint8_t * Data, size_t Size ) +{ + try + { + auto result = spb::pb::deserialize< T >( { reinterpret_cast< const char * >( Data ), Size } ); + } + catch( ... ) + { + } +} + +extern "C" int LLVMFuzzerTestOneInput( const uint8_t * Data, size_t Size ) +{ + pb_decode< PhoneBook::Person >( Data, Size ); + pb_decode< proto3_unittest::TestAllTypes >( Data, Size ); + pb_decode< proto3_unittest::TestEmptyMessage >( Data, Size ); + + return 0; +} \ No newline at end of file diff --git a/3rdparty/simple-protobuf/test/fuzzer/spb-protoc-fuzzer.cpp b/3rdparty/simple-protobuf/test/fuzzer/spb-protoc-fuzzer.cpp new file mode 100644 index 0000000..a7d9adb --- /dev/null +++ b/3rdparty/simple-protobuf/test/fuzzer/spb-protoc-fuzzer.cpp @@ -0,0 +1,20 @@ +#include +#include +#include + +#include + +extern "C" int LLVMFuzzerTestOneInput( const uint8_t * Data, size_t Size ) +{ + auto file = proto_file{ + .content = std::string( reinterpret_cast< const char * >( Data ), Size ), + }; + try + { + parse_proto_file_content( file ); + } + catch( ... ) + { + } + return 0; +} \ No newline at end of file diff --git a/3rdparty/simple-protobuf/test/gpb-compatibility.cpp b/3rdparty/simple-protobuf/test/gpb-compatibility.cpp new file mode 100644 index 0000000..34d5a4f --- /dev/null +++ b/3rdparty/simple-protobuf/test/gpb-compatibility.cpp @@ -0,0 +1,1422 @@ +#include "spb/json.hpp" +#include "spb/utf8.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN +#include "doctest.h" + +namespace std +{ +auto operator==( span< byte > lhs, span< byte > rhs ) noexcept -> bool +{ + return lhs.size( ) == rhs.size( ) && ( memcmp( lhs.data( ), rhs.data( ), lhs.size( ) ) == 0 ); +} + +auto operator==( span< char > lhs, span< char > rhs ) noexcept -> bool +{ + return lhs.size( ) == rhs.size( ) && ( memcmp( lhs.data( ), rhs.data( ), lhs.size( ) ) == 0 ); +} + +auto operator==( const array< char, 6 > & lhs, const string & rhs ) noexcept -> bool +{ + return lhs.size( ) == rhs.size( ) && ( memcmp( lhs.data( ), rhs.data( ), lhs.size( ) ) == 0 ); +} + +auto operator==( const string & lhs, const vector< byte > & rhs ) noexcept -> bool +{ + return lhs.size( ) == rhs.size( ) && ( memcmp( lhs.data( ), rhs.data( ), lhs.size( ) ) == 0 ); +} + +template < size_t N > +auto operator==( const string & lhs, const array< byte, N > & rhs ) noexcept -> bool +{ + return lhs.size( ) == rhs.size( ) && ( memcmp( lhs.data( ), rhs.data( ), lhs.size( ) ) == 0 ); +} + +}// namespace std + +namespace Test +{ +auto operator==( const Test::Name & lhs, const Test::Name & rhs ) noexcept -> bool +{ + return lhs.name == rhs.name; +} +auto operator==( const Test::Variant & lhs, const Test::Variant & rhs ) noexcept -> bool +{ + return lhs.oneof_field == rhs.oneof_field; +} + +namespace Scalar +{ + +template < typename T > +concept HasValueMember = requires( T t ) { + { + t.value + }; +}; + +template < typename T > + requires HasValueMember< T > +auto operator==( const T & lhs, const T & rhs ) noexcept -> bool +{ + return lhs.value == rhs.value; +} +}// namespace Scalar +}// namespace Test + +namespace PhoneBook +{ +auto operator==( const Person::PhoneNumber & lhs, const Person::PhoneNumber & rhs ) noexcept -> bool +{ + return lhs.number == rhs.number && lhs.type == rhs.type; +} + +auto operator==( const Person & lhs, const Person & rhs ) noexcept -> bool +{ + return lhs.name == rhs.name && lhs.id == rhs.id && lhs.email == rhs.email && lhs.phones == rhs.phones; +} +}// namespace PhoneBook + +namespace +{ +template < size_t N > +auto to_string( const char ( &string )[ N ] ) +{ + auto result = std::array< char, N - 1 >( ); + memcpy( result.data( ), &string, result.size( ) ); + return result; +} + +auto to_bytes( std::string_view str ) -> std::vector< std::byte > +{ + auto span = std::span< std::byte >( ( std::byte * ) str.data( ), str.size( ) ); + return { span.data( ), span.data( ) + span.size( ) }; +} + +template < size_t N > +auto to_array( const char ( &string )[ N ] ) +{ + auto result = std::array< std::byte, N - 1 >( ); + memcpy( result.data( ), &string, result.size( ) ); + return result; +} + +template < typename T > +concept is_gpb_repeated = requires( T t ) { + { + t.value( 0 ) + }; +}; + +template < typename T > +auto opt_size( const std::optional< T > & opt ) -> std::size_t +{ + if( opt.has_value( ) ) + { + return opt.value( ).size( ); + } + return 0; +} + +template < typename T > +auto opt_size( const std::vector< T > & opt ) -> std::size_t +{ + return opt.size( ); +} + +template < typename T > +struct ExtractOptional +{ + using type = T; +}; + +template < typename T > +struct ExtractOptional< std::optional< T > > +{ + using type = T; +}; + +template < typename T > +auto enum_value( const T & value ) +{ + return std::underlying_type_t< T >( value ); +} + +template < typename T > +auto enum_value( const std::optional< T > & value ) +{ + return enum_value( value.value( ) ); +} + +template < typename GPB, typename SPB > +void gpb_test( const SPB & spb, const spb::pb::serialize_options & options = { } ) +{ + using T = typename ExtractOptional< std::decay_t< decltype( SPB::value ) > >::type; + + auto gpb = GPB( ); + auto spb_serialized = spb::pb::serialize( spb, options ); + + if( options.delimited ) + { + std::istringstream input_stream{ spb_serialized }; + auto raw_input_stream = google::protobuf::io::IstreamInputStream{ &input_stream }; + REQUIRE( google::protobuf::util::ParseDelimitedFromZeroCopyStream( &gpb, &raw_input_stream, + nullptr ) ); + } + else + { + REQUIRE( gpb.ParseFromString( spb_serialized ) ); + } + + if constexpr( is_gpb_repeated< GPB > ) + { + REQUIRE( gpb.value( ).size( ) == opt_size( spb.value ) ); + for( size_t i = 0; i < opt_size( spb.value ); ++i ) + { + using value_type = typename decltype( SPB::value )::value_type; + if constexpr( std::is_enum_v< value_type > ) + { + REQUIRE( enum_value( spb.value[ i ] ) == gpb.value( i ) ); + } + else + { + REQUIRE( gpb.value( i ) == spb.value[ i ] ); + } + } + } + else if constexpr( std::is_enum_v< T > ) + { + REQUIRE( enum_value( spb.value ) == gpb.value( ) ); + } + else + { + REQUIRE( spb.value == gpb.value( ) ); + } + + auto gpb_serialized = std::string( ); + if( options.delimited ) + { + std::ostringstream output_stream; + //- Wrapped so that the destructor of OstreamOutputStream is called, + //- which flushes the stream. + { + auto raw_output_stream = google::protobuf::io::OstreamOutputStream{ &output_stream }; + REQUIRE( google::protobuf::util::SerializeDelimitedToZeroCopyStream( + gpb, &raw_output_stream ) ); + } + gpb_serialized = output_stream.str( ); + } + else + { + REQUIRE( gpb.SerializeToString( &gpb_serialized ) ); + } + + REQUIRE( spb::pb::deserialize< SPB >( gpb_serialized, + { + .delimited = options.delimited, + } ) + .value == spb.value ); + REQUIRE( gpb_serialized == spb_serialized ); +} + +template < typename GPB, typename SPB > +void gpb_json( const SPB & spb ) +{ + using T = typename ExtractOptional< std::decay_t< decltype( SPB::value ) > >::type; + + auto gpb = GPB( ); + auto spb_serialized = spb::json::serialize( spb ); + + auto parse_options = google::protobuf::util::JsonParseOptions{ }; + REQUIRE( JsonStringToMessage( spb_serialized, &gpb, parse_options ).ok( ) ); + + if constexpr( is_gpb_repeated< GPB > ) + { + REQUIRE( gpb.value( ).size( ) == opt_size( spb.value ) ); + for( size_t i = 0; i < opt_size( spb.value ); ++i ) + { + using value_type = typename decltype( SPB::value )::value_type; + if constexpr( std::is_enum_v< value_type > ) + { + REQUIRE( enum_value( spb.value[ i ] ) == gpb.value( i ) ); + } + else + { + REQUIRE( gpb.value( i ) == spb.value[ i ] ); + } + } + } + else if constexpr( std::is_enum_v< T > ) + { + REQUIRE( enum_value( spb.value ) == gpb.value( ) ); + } + else + { + auto gpb_value = gpb.value( ); + REQUIRE( spb.value == gpb_value ); + } + auto json_string = std::string( ); + auto print_options = google::protobuf::util::JsonPrintOptions( ); + print_options.preserve_proto_field_names = true; + + REQUIRE( MessageToJsonString( gpb, &json_string, print_options ).ok( ) ); + REQUIRE( spb::json::deserialize< SPB >( json_string ).value == spb.value ); + + print_options.preserve_proto_field_names = false; + json_string.clear( ); + REQUIRE( MessageToJsonString( gpb, &json_string, print_options ).ok( ) ); + REQUIRE( spb::json::deserialize< SPB >( json_string ).value == spb.value ); + if constexpr( std::is_integral_v< T > && sizeof( T ) < sizeof( int64_t ) ) + { + auto gpb_value = gpb.value( ); + if( sizeof( gpb_value ) < sizeof( int64_t ) ) + { + REQUIRE( json_string == spb_serialized ); + } + } +} + +template < typename GPB, typename SPB > +void gpb_compatibility( const SPB & spb ) +{ + SUBCASE( "gpb serialize/deserialize" ) + { + gpb_test< GPB, SPB >( spb ); + gpb_test< GPB, SPB >( spb, { .delimited = true } ); + } + SUBCASE( "json serialize/deserialize" ) + { + gpb_json< GPB, SPB >( spb ); + } +} + +template < typename GPB, typename SPB, typename POD > +void gpb_compatibility_enum( ) +{ + using T = typename ExtractOptional< std::decay_t< decltype( SPB::value ) > >::type; + + gpb_compatibility< GPB >( SPB{ .value = SPB::Enum::Enum_min } ); + gpb_compatibility< GPB >( SPB{ .value = SPB::Enum::Enum_max } ); + gpb_compatibility< GPB >( SPB{ .value = SPB::Enum::Enum_value } ); + + CHECK( std::is_same_v< std::underlying_type_t< T >, POD > ); + + if constexpr( !std::is_same_v< std::optional< T >, std::decay_t< decltype( SPB::value ) > > ) + { + CHECK( sizeof( SPB ) == sizeof( POD ) ); + } +} + +template < typename GPB, typename SPB > +void gpb_compatibility_enum_array( ) +{ + gpb_compatibility< GPB >( SPB{ .value = { SPB::Enum::Enum_min } } ); + gpb_compatibility< GPB >( SPB{ .value = { SPB::Enum::Enum_max } } ); + gpb_compatibility< GPB >( SPB{ .value = { SPB::Enum::Enum_value } } ); + gpb_compatibility< GPB >( SPB{ .value = { + SPB::Enum::Enum_min, + SPB::Enum::Enum_max, + SPB::Enum::Enum_value, + } } ); +} + +template < typename GPB, typename SPB, typename POD > +void gpb_compatibility_value( ) +{ + using T = typename ExtractOptional< std::decay_t< decltype( SPB::value ) > >::type; + + gpb_compatibility< GPB >( SPB{ .value = 0 } ); + gpb_compatibility< GPB >( SPB{ .value = 0x42 } ); + gpb_compatibility< GPB >( SPB{ .value = 0x7f } ); + gpb_compatibility< GPB >( SPB{ .value = std::numeric_limits< T >::max( ) } ); + gpb_compatibility< GPB >( SPB{ .value = std::numeric_limits< T >::max( ) / 2 } ); + gpb_compatibility< GPB >( SPB{ .value = std::numeric_limits< T >::min( ) } ); + gpb_compatibility< GPB >( SPB{ .value = T( -2 ) } ); + CHECK( std::is_same_v< T, POD > ); + + if constexpr( !std::is_same_v< std::optional< T >, std::decay_t< decltype( SPB::value ) > > ) + { + CHECK( sizeof( SPB ) == sizeof( POD ) ); + } +} + +template < typename GPB, typename SPB, typename POD > +void gpb_compatibility_bitfield_value( std::initializer_list< POD > values ) +{ + using T = std::decay_t< decltype( SPB::value ) >; + static_assert( std::is_same_v< T, POD > ); + + for( auto value : values ) + { + gpb_compatibility< GPB >( SPB{ .value = value } ); + } +} + +template < typename GPB, typename SPB > +void gpb_compatibility_array( ) +{ + using T = typename decltype( SPB::value )::value_type; + + gpb_compatibility< GPB >( SPB{ .value = { 0 } } ); + gpb_compatibility< GPB >( SPB{ .value = { 0x42 } } ); + gpb_compatibility< GPB >( SPB{ .value = { 0, 0x42, 0x7f } } ); + gpb_compatibility< GPB >( SPB{ .value = { + 0, + 0x42, + 0x7f, + std::numeric_limits< T >::max( ), + std::numeric_limits< T >::max( ) / 2, + std::numeric_limits< T >::min( ), + T( -2 ), + } } ); +} + +}// namespace + +using namespace std::literals; + +TEST_CASE( "string" ) +{ + SUBCASE( "utf8" ) + { + for( auto i = 0U; i < 0x10ffff; i++ ) + { + char buffer[ 4 ]; + auto value = std::string( buffer, spb::detail::utf8::encode_point( i, buffer ) ); + if( value.empty( ) ) + { + continue; + } + + gpb_compatibility< Test::Scalar::gpb::ReqString >( Test::Scalar::ReqString{ .value = value } ); + } + } + SUBCASE( "required" ) + { + gpb_compatibility< Test::Scalar::gpb::ReqString >( Test::Scalar::ReqString{ .value = "hello" } ); + gpb_compatibility< Test::Scalar::gpb::ReqString >( Test::Scalar::ReqString{ .value = "\"\\/\b\f\n\r\t" } ); + } + SUBCASE( "optional" ) + { + gpb_compatibility< Test::Scalar::gpb::OptString >( Test::Scalar::OptString{ .value = "hello" } ); + } + SUBCASE( "repeated" ) + { + gpb_compatibility< Test::Scalar::gpb::RepString >( Test::Scalar::RepString{ .value = { "hello" } } ); + gpb_compatibility< Test::Scalar::gpb::RepString >( Test::Scalar::RepString{ .value = { "hello", "world" } } ); + } + SUBCASE( "fixed" ) + { + SUBCASE( "required" ) + { + gpb_compatibility< Test::Scalar::gpb::ReqStringFixed >( Test::Scalar::ReqStringFixed{ .value = to_string( "hello1" ) } ); + gpb_compatibility< Test::Scalar::gpb::ReqStringFixed >( Test::Scalar::ReqStringFixed{ .value = to_string( "\"\\/\n\r\t" ) } ); + } + SUBCASE( "optional" ) + { + gpb_compatibility< Test::Scalar::gpb::OptStringFixed >( Test::Scalar::OptStringFixed{ .value = to_string( "hello1" ) } ); + } + SUBCASE( "repeated" ) + { + gpb_compatibility< Test::Scalar::gpb::RepStringFixed >( Test::Scalar::RepStringFixed{ .value = { to_string( "hello1" ) } } ); + gpb_compatibility< Test::Scalar::gpb::RepStringFixed >( Test::Scalar::RepStringFixed{ .value = { to_string( "hello1" ), to_string( "world1" ) } } ); + } + } +} +TEST_CASE( "bool" ) +{ + SUBCASE( "required" ) + { + gpb_compatibility< Test::Scalar::gpb::ReqBool >( Test::Scalar::ReqBool{ .value = true } ); + gpb_compatibility< Test::Scalar::gpb::ReqBool >( Test::Scalar::ReqBool{ .value = false } ); + } + SUBCASE( "optional" ) + { + gpb_compatibility< Test::Scalar::gpb::OptBool >( Test::Scalar::OptBool{ .value = true } ); + gpb_compatibility< Test::Scalar::gpb::OptBool >( Test::Scalar::OptBool{ .value = false } ); + } + SUBCASE( "repeated" ) + { + gpb_compatibility< Test::Scalar::gpb::RepBool >( Test::Scalar::RepBool{ .value = { true } } ); + gpb_compatibility< Test::Scalar::gpb::RepBool >( Test::Scalar::RepBool{ .value = { true, false } } ); + + SUBCASE( "packed" ) + { + gpb_compatibility< Test::Scalar::gpb::RepPackBool >( Test::Scalar::RepPackBool{ .value = { true } } ); + gpb_compatibility< Test::Scalar::gpb::RepPackBool >( Test::Scalar::RepPackBool{ .value = { true, false } } ); + } + } +} +TEST_CASE( "int" ) +{ + SUBCASE( "varint8" ) + { + SUBCASE( "bitfield" ) + { + gpb_compatibility_bitfield_value< Test::Scalar::gpb::ReqInt8_1, Test::Scalar::ReqInt8_1, int8_t >( { -1, 0 } ); + gpb_compatibility_bitfield_value< Test::Scalar::gpb::ReqInt8_2, Test::Scalar::ReqInt8_2, int8_t >( { -2, -1, 0, 1 } ); + } + SUBCASE( "required" ) + { + gpb_compatibility_value< Test::Scalar::gpb::ReqInt8, Test::Scalar::ReqInt8, int8_t >( ); + } + SUBCASE( "optional" ) + { + gpb_compatibility_value< Test::Scalar::gpb::OptInt8, Test::Scalar::OptInt8, int8_t >( ); + } + SUBCASE( "repeated" ) + { + gpb_compatibility_array< Test::Scalar::gpb::RepInt8, Test::Scalar::RepInt8 >( ); + + SUBCASE( "packed" ) + { + gpb_compatibility_array< Test::Scalar::gpb::RepPackInt8, Test::Scalar::RepPackInt8 >( ); + } + } + } + SUBCASE( "varuint8" ) + { + SUBCASE( "bitfield" ) + { + gpb_compatibility_bitfield_value< Test::Scalar::gpb::ReqUint8_1, Test::Scalar::ReqUint8_1, uint8_t >( { 0, 1 } ); + gpb_compatibility_bitfield_value< Test::Scalar::gpb::ReqUint8_2, Test::Scalar::ReqUint8_2, uint8_t >( { 0, 1, 2, 3 } ); + } + SUBCASE( "required" ) + { + gpb_compatibility_value< Test::Scalar::gpb::ReqUint8, Test::Scalar::ReqUint8, uint8_t >( ); + } + SUBCASE( "optional" ) + { + gpb_compatibility_value< Test::Scalar::gpb::OptUint8, Test::Scalar::OptUint8, uint8_t >( ); + } + SUBCASE( "repeated" ) + { + gpb_compatibility_array< Test::Scalar::gpb::RepUint8, Test::Scalar::RepUint8 >( ); + + SUBCASE( "packed" ) + { + gpb_compatibility_array< Test::Scalar::gpb::RepPackUint8, Test::Scalar::RepPackUint8 >( ); + } + } + } + SUBCASE( "varint16" ) + { + SUBCASE( "bitfield" ) + { + gpb_compatibility_bitfield_value< Test::Scalar::gpb::ReqInt16_1, Test::Scalar::ReqInt16_1, int16_t >( { -1, 0 } ); + gpb_compatibility_bitfield_value< Test::Scalar::gpb::ReqInt16_2, Test::Scalar::ReqInt16_2, int16_t >( { -2, -1, 0, 1 } ); + } + SUBCASE( "required" ) + { + gpb_compatibility_value< Test::Scalar::gpb::ReqInt16, Test::Scalar::ReqInt16, int16_t >( ); + } + SUBCASE( "optional" ) + { + gpb_compatibility_value< Test::Scalar::gpb::OptInt16, Test::Scalar::OptInt16, int16_t >( ); + } + SUBCASE( "repeated" ) + { + gpb_compatibility_array< Test::Scalar::gpb::RepInt16, Test::Scalar::RepInt16 >( ); + + SUBCASE( "packed" ) + { + gpb_compatibility_array< Test::Scalar::gpb::RepPackInt16, Test::Scalar::RepPackInt16 >( ); + } + } + } + SUBCASE( "varuint16" ) + { + SUBCASE( "bitfield" ) + { + gpb_compatibility_bitfield_value< Test::Scalar::gpb::ReqUint16_1, Test::Scalar::ReqUint16_1, uint16_t >( { 0, 1 } ); + gpb_compatibility_bitfield_value< Test::Scalar::gpb::ReqUint16_2, Test::Scalar::ReqUint16_2, uint16_t >( { 0, 1, 2, 3 } ); + } + SUBCASE( "required" ) + { + gpb_compatibility_value< Test::Scalar::gpb::ReqUint16, Test::Scalar::ReqUint16, uint16_t >( ); + } + SUBCASE( "optional" ) + { + gpb_compatibility_value< Test::Scalar::gpb::OptUint16, Test::Scalar::OptUint16, uint16_t >( ); + } + SUBCASE( "repeated" ) + { + gpb_compatibility_array< Test::Scalar::gpb::RepUint16, Test::Scalar::RepUint16 >( ); + + SUBCASE( "packed" ) + { + gpb_compatibility_array< Test::Scalar::gpb::RepPackUint16, Test::Scalar::RepPackUint16 >( ); + } + } + } + SUBCASE( "varint32" ) + { + SUBCASE( "bitfield" ) + { + gpb_compatibility_bitfield_value< Test::Scalar::gpb::ReqInt32_1, Test::Scalar::ReqInt32_1, int32_t >( { -1, 0 } ); + gpb_compatibility_bitfield_value< Test::Scalar::gpb::ReqInt32_2, Test::Scalar::ReqInt32_2, int32_t >( { -2, -1, 0, 1 } ); + } + SUBCASE( "required" ) + { + gpb_compatibility_value< Test::Scalar::gpb::ReqInt32, Test::Scalar::ReqInt32, int32_t >( ); + } + SUBCASE( "optional" ) + { + gpb_compatibility_value< Test::Scalar::gpb::OptInt32, Test::Scalar::OptInt32, int32_t >( ); + } + SUBCASE( "repeated" ) + { + gpb_compatibility_array< Test::Scalar::gpb::RepInt32, Test::Scalar::RepInt32 >( ); + + SUBCASE( "packed" ) + { + gpb_compatibility_array< Test::Scalar::gpb::RepPackInt32, Test::Scalar::RepPackInt32 >( ); + } + } + } + SUBCASE( "varuint32" ) + { + SUBCASE( "bitfield" ) + { + gpb_compatibility_bitfield_value< Test::Scalar::gpb::ReqUint32_1, Test::Scalar::ReqUint32_1, uint32_t >( { 0, 1 } ); + gpb_compatibility_bitfield_value< Test::Scalar::gpb::ReqUint32_2, Test::Scalar::ReqUint32_2, uint32_t >( { 0, 1, 2, 3 } ); + } + SUBCASE( "required" ) + { + gpb_compatibility_value< Test::Scalar::gpb::ReqUint32, Test::Scalar::ReqUint32, uint32_t >( ); + } + SUBCASE( "optional" ) + { + gpb_compatibility_value< Test::Scalar::gpb::OptUint32, Test::Scalar::OptUint32, uint32_t >( ); + } + SUBCASE( "repeated" ) + { + gpb_compatibility_array< Test::Scalar::gpb::RepUint32, Test::Scalar::RepUint32 >( ); + + SUBCASE( "packed" ) + { + gpb_compatibility_array< Test::Scalar::gpb::RepPackUint32, Test::Scalar::RepPackUint32 >( ); + } + } + } + SUBCASE( "varint64" ) + { + SUBCASE( "bitfield" ) + { + gpb_compatibility_bitfield_value< Test::Scalar::gpb::ReqInt64_1, Test::Scalar::ReqInt64_1, int64_t >( { -1, 0 } ); + gpb_compatibility_bitfield_value< Test::Scalar::gpb::ReqInt64_2, Test::Scalar::ReqInt64_2, int64_t >( { -2, -1, 0, 1 } ); + } + SUBCASE( "required" ) + { + gpb_compatibility_value< Test::Scalar::gpb::ReqInt64, Test::Scalar::ReqInt64, int64_t >( ); + } + SUBCASE( "optional" ) + { + gpb_compatibility_value< Test::Scalar::gpb::OptInt64, Test::Scalar::OptInt64, int64_t >( ); + } + SUBCASE( "repeated" ) + { + gpb_compatibility_array< Test::Scalar::gpb::RepInt64, Test::Scalar::RepInt64 >( ); + + SUBCASE( "packed" ) + { + gpb_compatibility_array< Test::Scalar::gpb::RepPackInt64, Test::Scalar::RepPackInt64 >( ); + } + } + } + SUBCASE( "varuint64" ) + { + SUBCASE( "bitfield" ) + { + gpb_compatibility_bitfield_value< Test::Scalar::gpb::ReqUint8_1, Test::Scalar::ReqUint8_1, uint8_t >( { 0, 1 } ); + gpb_compatibility_bitfield_value< Test::Scalar::gpb::ReqUint8_2, Test::Scalar::ReqUint8_2, uint8_t >( { 0, 1, 2, 3 } ); + } + SUBCASE( "required" ) + { + gpb_compatibility_value< Test::Scalar::gpb::ReqUint64, Test::Scalar::ReqUint64, uint64_t >( ); + } + SUBCASE( "optional" ) + { + gpb_compatibility_value< Test::Scalar::gpb::OptUint64, Test::Scalar::OptUint64, uint64_t >( ); + } + SUBCASE( "repeated" ) + { + gpb_compatibility_array< Test::Scalar::gpb::RepUint64, Test::Scalar::RepUint64 >( ); + + SUBCASE( "packed" ) + { + gpb_compatibility_array< Test::Scalar::gpb::RepPackUint64, Test::Scalar::RepPackUint64 >( ); + } + } + } + SUBCASE( "svarint8" ) + { + SUBCASE( "bitfield" ) + { + gpb_compatibility_bitfield_value< Test::Scalar::gpb::ReqSint8_1, Test::Scalar::ReqSint8_1, int8_t >( { -1, 0 } ); + gpb_compatibility_bitfield_value< Test::Scalar::gpb::ReqSint8_2, Test::Scalar::ReqSint8_2, int8_t >( { -2, -1, 0, 1 } ); + } + SUBCASE( "required" ) + { + gpb_compatibility_value< Test::Scalar::gpb::ReqSint8, Test::Scalar::ReqSint8, int8_t >( ); + } + SUBCASE( "optional" ) + { + gpb_compatibility_value< Test::Scalar::gpb::OptSint8, Test::Scalar::OptSint8, int8_t >( ); + } + SUBCASE( "repeated" ) + { + gpb_compatibility_array< Test::Scalar::gpb::RepSint8, Test::Scalar::RepSint8 >( ); + + SUBCASE( "packed" ) + { + gpb_compatibility_array< Test::Scalar::gpb::RepPackSint8, Test::Scalar::RepPackSint8 >( ); + } + } + } + SUBCASE( "svarint16" ) + { + SUBCASE( "bitfield" ) + { + gpb_compatibility_bitfield_value< Test::Scalar::gpb::ReqSint16_1, Test::Scalar::ReqSint16_1, int16_t >( { -1, 0 } ); + gpb_compatibility_bitfield_value< Test::Scalar::gpb::ReqSint16_2, Test::Scalar::ReqSint16_2, int16_t >( { -2, -1, 0, 1 } ); + } + SUBCASE( "required" ) + { + gpb_compatibility_value< Test::Scalar::gpb::ReqSint16, Test::Scalar::ReqSint16, int16_t >( ); + } + SUBCASE( "optional" ) + { + gpb_compatibility_value< Test::Scalar::gpb::OptSint16, Test::Scalar::OptSint16, int16_t >( ); + } + SUBCASE( "repeated" ) + { + gpb_compatibility_array< Test::Scalar::gpb::RepSint16, Test::Scalar::RepSint16 >( ); + + SUBCASE( "packed" ) + { + gpb_compatibility_array< Test::Scalar::gpb::RepPackSint16, Test::Scalar::RepPackSint16 >( ); + } + } + } + SUBCASE( "svarint32" ) + { + SUBCASE( "bitfield" ) + { + gpb_compatibility_bitfield_value< Test::Scalar::gpb::ReqSint32_1, Test::Scalar::ReqSint32_1, int32_t >( { -1, 0 } ); + gpb_compatibility_bitfield_value< Test::Scalar::gpb::ReqSint32_2, Test::Scalar::ReqSint32_2, int32_t >( { -2, -1, 0, 1 } ); + } + SUBCASE( "required" ) + { + gpb_compatibility_value< Test::Scalar::gpb::ReqSint32, Test::Scalar::ReqSint32, int32_t >( ); + } + SUBCASE( "optional" ) + { + gpb_compatibility_value< Test::Scalar::gpb::OptSint32, Test::Scalar::OptSint32, int32_t >( ); + } + SUBCASE( "repeated" ) + { + gpb_compatibility_array< Test::Scalar::gpb::RepSint32, Test::Scalar::RepSint32 >( ); + + SUBCASE( "packed" ) + { + gpb_compatibility_array< Test::Scalar::gpb::RepPackSint32, Test::Scalar::RepPackSint32 >( ); + } + } + } + SUBCASE( "svarint64" ) + { + SUBCASE( "bitfield" ) + { + gpb_compatibility_bitfield_value< Test::Scalar::gpb::ReqSint64_1, Test::Scalar::ReqSint64_1, int64_t >( { -1, 0 } ); + gpb_compatibility_bitfield_value< Test::Scalar::gpb::ReqSint64_2, Test::Scalar::ReqSint64_2, int64_t >( { -2, -1, 0, 1 } ); + } + SUBCASE( "required" ) + { + gpb_compatibility_value< Test::Scalar::gpb::ReqSint64, Test::Scalar::ReqSint64, int64_t >( ); + } + SUBCASE( "optional" ) + { + gpb_compatibility_value< Test::Scalar::gpb::OptSint64, Test::Scalar::OptSint64, int64_t >( ); + } + SUBCASE( "repeated" ) + { + gpb_compatibility_array< Test::Scalar::gpb::RepSint64, Test::Scalar::RepSint64 >( ); + + SUBCASE( "packed" ) + { + gpb_compatibility_array< Test::Scalar::gpb::RepPackSint64, Test::Scalar::RepPackSint64 >( ); + } + } + } + SUBCASE( "fixed32" ) + { + SUBCASE( "bitfield" ) + { + gpb_compatibility_bitfield_value< Test::Scalar::gpb::ReqFixed32_32_1, Test::Scalar::ReqFixed32_32_1, uint32_t >( { 0, 1 } ); + gpb_compatibility_bitfield_value< Test::Scalar::gpb::ReqFixed32_32_2, Test::Scalar::ReqFixed32_32_2, uint32_t >( { 0, 1, 2, 3 } ); + } + SUBCASE( "required" ) + { + gpb_compatibility_value< Test::Scalar::gpb::ReqFixed32, Test::Scalar::ReqFixed32, uint32_t >( ); + } + SUBCASE( "optional" ) + { + gpb_compatibility_value< Test::Scalar::gpb::OptFixed32, Test::Scalar::OptFixed32, uint32_t >( ); + } + SUBCASE( "repeated" ) + { + gpb_compatibility_array< Test::Scalar::gpb::RepFixed32, Test::Scalar::RepFixed32 >( ); + + SUBCASE( "packed" ) + { + gpb_compatibility_array< Test::Scalar::gpb::RepPackFixed32, Test::Scalar::RepPackFixed32 >( ); + } + } + SUBCASE( "uint8" ) + { + SUBCASE( "bitfield" ) + { + gpb_compatibility_bitfield_value< Test::Scalar::gpb::ReqFixed32_8_1, Test::Scalar::ReqFixed32_8_1, uint8_t >( { 0, 1 } ); + gpb_compatibility_bitfield_value< Test::Scalar::gpb::ReqFixed32_8_2, Test::Scalar::ReqFixed32_8_2, uint8_t >( { 0, 1, 2, 3 } ); + } + SUBCASE( "required" ) + { + gpb_compatibility_value< Test::Scalar::gpb::ReqFixed32_8, Test::Scalar::ReqFixed32_8, uint8_t >( ); + } + SUBCASE( "optional" ) + { + gpb_compatibility_value< Test::Scalar::gpb::OptFixed32_8, Test::Scalar::OptFixed32_8, uint8_t >( ); + } + SUBCASE( "repeated" ) + { + gpb_compatibility_array< Test::Scalar::gpb::RepFixed32_8, Test::Scalar::RepFixed32_8 >( ); + + SUBCASE( "packed" ) + { + gpb_compatibility_array< Test::Scalar::gpb::RepPackFixed32_8, Test::Scalar::RepPackFixed32_8 >( ); + } + } + } + SUBCASE( "uint16" ) + { + SUBCASE( "bitfield" ) + { + gpb_compatibility_bitfield_value< Test::Scalar::gpb::ReqFixed32_16_1, Test::Scalar::ReqFixed32_16_1, uint16_t >( { 0, 1 } ); + gpb_compatibility_bitfield_value< Test::Scalar::gpb::ReqFixed32_16_2, Test::Scalar::ReqFixed32_16_2, uint16_t >( { 0, 1, 2, 3 } ); + } + SUBCASE( "required" ) + { + gpb_compatibility_value< Test::Scalar::gpb::ReqFixed32_16, Test::Scalar::ReqFixed32_16, uint16_t >( ); + } + SUBCASE( "optional" ) + { + gpb_compatibility_value< Test::Scalar::gpb::OptFixed32_16, Test::Scalar::OptFixed32_16, uint16_t >( ); + } + SUBCASE( "repeated" ) + { + gpb_compatibility_array< Test::Scalar::gpb::RepFixed32_16, Test::Scalar::RepFixed32_16 >( ); + + SUBCASE( "packed" ) + { + gpb_compatibility_array< Test::Scalar::gpb::RepPackFixed32_16, Test::Scalar::RepPackFixed32_16 >( ); + } + } + } + } + SUBCASE( "fixed64" ) + { + SUBCASE( "bitfield" ) + { + gpb_compatibility_bitfield_value< Test::Scalar::gpb::ReqFixed64_8_1, Test::Scalar::ReqFixed64_64_1, uint64_t >( { 0, 1 } ); + gpb_compatibility_bitfield_value< Test::Scalar::gpb::ReqFixed64_8_2, Test::Scalar::ReqFixed64_64_2, uint64_t >( { 0, 1, 2, 3 } ); + } + SUBCASE( "required" ) + { + gpb_compatibility_value< Test::Scalar::gpb::ReqFixed64, Test::Scalar::ReqFixed64, uint64_t >( ); + } + SUBCASE( "optional" ) + { + gpb_compatibility_value< Test::Scalar::gpb::OptFixed64, Test::Scalar::OptFixed64, uint64_t >( ); + } + SUBCASE( "repeated" ) + { + gpb_compatibility_array< Test::Scalar::gpb::RepFixed64, Test::Scalar::RepFixed64 >( ); + + SUBCASE( "packed" ) + { + gpb_compatibility_array< Test::Scalar::gpb::RepPackFixed64, Test::Scalar::RepPackFixed64 >( ); + } + } + SUBCASE( "uint8" ) + { + SUBCASE( "bitfield" ) + { + gpb_compatibility_bitfield_value< Test::Scalar::gpb::ReqFixed64_8_1, Test::Scalar::ReqFixed64_8_1, uint8_t >( { 0, 1 } ); + gpb_compatibility_bitfield_value< Test::Scalar::gpb::ReqFixed64_8_2, Test::Scalar::ReqFixed64_8_2, uint8_t >( { 0, 1, 2, 3 } ); + } + SUBCASE( "required" ) + { + gpb_compatibility_value< Test::Scalar::gpb::ReqFixed64_8, Test::Scalar::ReqFixed64_8, uint8_t >( ); + } + SUBCASE( "optional" ) + { + gpb_compatibility_value< Test::Scalar::gpb::OptFixed64_8, Test::Scalar::OptFixed64_8, uint8_t >( ); + } + SUBCASE( "repeated" ) + { + gpb_compatibility_array< Test::Scalar::gpb::RepFixed64_8, Test::Scalar::RepFixed64_8 >( ); + + SUBCASE( "packed" ) + { + gpb_compatibility_array< Test::Scalar::gpb::RepPackFixed64_8, Test::Scalar::RepPackFixed64_8 >( ); + } + } + } + SUBCASE( "uint16" ) + { + SUBCASE( "bitfield" ) + { + gpb_compatibility_bitfield_value< Test::Scalar::gpb::ReqFixed64_16_1, Test::Scalar::ReqFixed64_16_1, uint16_t >( { 0, 1 } ); + gpb_compatibility_bitfield_value< Test::Scalar::gpb::ReqFixed64_16_2, Test::Scalar::ReqFixed64_16_2, uint16_t >( { 0, 1, 2, 3 } ); + } + SUBCASE( "required" ) + { + gpb_compatibility_value< Test::Scalar::gpb::ReqFixed64_16, Test::Scalar::ReqFixed64_16, uint16_t >( ); + } + SUBCASE( "optional" ) + { + gpb_compatibility_value< Test::Scalar::gpb::OptFixed64_16, Test::Scalar::OptFixed64_16, uint16_t >( ); + } + SUBCASE( "repeated" ) + { + gpb_compatibility_array< Test::Scalar::gpb::RepFixed64_16, Test::Scalar::RepFixed64_16 >( ); + + SUBCASE( "packed" ) + { + gpb_compatibility_array< Test::Scalar::gpb::RepPackFixed64_16, Test::Scalar::RepPackFixed64_16 >( ); + } + } + } + SUBCASE( "uint32" ) + { + SUBCASE( "bitfield" ) + { + gpb_compatibility_bitfield_value< Test::Scalar::gpb::ReqFixed64_32_1, Test::Scalar::ReqFixed64_32_1, uint32_t >( { 0, 1 } ); + gpb_compatibility_bitfield_value< Test::Scalar::gpb::ReqFixed64_32_2, Test::Scalar::ReqFixed64_32_2, uint32_t >( { 0, 1, 2, 3 } ); + } + SUBCASE( "required" ) + { + gpb_compatibility_value< Test::Scalar::gpb::ReqFixed64_32, Test::Scalar::ReqFixed64_32, uint32_t >( ); + } + SUBCASE( "optional" ) + { + gpb_compatibility_value< Test::Scalar::gpb::OptFixed64_32, Test::Scalar::OptFixed64_32, uint32_t >( ); + } + SUBCASE( "repeated" ) + { + gpb_compatibility_array< Test::Scalar::gpb::RepFixed64_32, Test::Scalar::RepFixed64_32 >( ); + + SUBCASE( "packed" ) + { + gpb_compatibility_array< Test::Scalar::gpb::RepPackFixed64_32, Test::Scalar::RepPackFixed64_32 >( ); + } + } + } + } + SUBCASE( "sfixed32" ) + { + SUBCASE( "bitfield" ) + { + gpb_compatibility_bitfield_value< Test::Scalar::gpb::ReqSfixed32_32_1, Test::Scalar::ReqSfixed32_32_1, int32_t >( { 0, 1 } ); + gpb_compatibility_bitfield_value< Test::Scalar::gpb::ReqSfixed32_32_2, Test::Scalar::ReqSfixed32_32_2, int32_t >( { 0, 1, 2, 3 } ); + } + SUBCASE( "required" ) + { + gpb_compatibility_value< Test::Scalar::gpb::ReqSfixed32, Test::Scalar::ReqSfixed32, int32_t >( ); + } + SUBCASE( "optional" ) + { + gpb_compatibility_value< Test::Scalar::gpb::OptSfixed32, Test::Scalar::OptSfixed32, int32_t >( ); + } + SUBCASE( "repeated" ) + { + gpb_compatibility_array< Test::Scalar::gpb::RepSfixed32, Test::Scalar::RepSfixed32 >( ); + + SUBCASE( "packed" ) + { + gpb_compatibility_array< Test::Scalar::gpb::RepPackSfixed32, Test::Scalar::RepPackSfixed32 >( ); + } + } + SUBCASE( "int8" ) + { + SUBCASE( "bitfield" ) + { + gpb_compatibility_bitfield_value< Test::Scalar::gpb::ReqSfixed32_8_1, Test::Scalar::ReqSfixed32_8_1, int8_t >( { 0, 1 } ); + gpb_compatibility_bitfield_value< Test::Scalar::gpb::ReqSfixed32_8_2, Test::Scalar::ReqSfixed32_8_2, int8_t >( { 0, 1, 2, 3 } ); + } + SUBCASE( "required" ) + { + gpb_compatibility_value< Test::Scalar::gpb::ReqSfixed32_8, Test::Scalar::ReqSfixed32_8, int8_t >( ); + } + SUBCASE( "optional" ) + { + gpb_compatibility_value< Test::Scalar::gpb::OptSfixed32_8, Test::Scalar::OptSfixed32_8, int8_t >( ); + } + SUBCASE( "repeated" ) + { + gpb_compatibility_array< Test::Scalar::gpb::RepSfixed32_8, Test::Scalar::RepSfixed32_8 >( ); + + SUBCASE( "packed" ) + { + gpb_compatibility_array< Test::Scalar::gpb::RepPackSfixed32_8, Test::Scalar::RepPackSfixed32_8 >( ); + } + } + } + SUBCASE( "int16" ) + { + SUBCASE( "bitfield" ) + { + gpb_compatibility_bitfield_value< Test::Scalar::gpb::ReqSfixed32_16_1, Test::Scalar::ReqSfixed32_16_1, int16_t >( { 0, 1 } ); + gpb_compatibility_bitfield_value< Test::Scalar::gpb::ReqSfixed32_16_2, Test::Scalar::ReqSfixed32_16_2, int16_t >( { 0, 1, 2, 3 } ); + } + SUBCASE( "required" ) + { + gpb_compatibility_value< Test::Scalar::gpb::ReqSfixed32_16, Test::Scalar::ReqSfixed32_16, int16_t >( ); + } + SUBCASE( "optional" ) + { + gpb_compatibility_value< Test::Scalar::gpb::OptSfixed32_16, Test::Scalar::OptSfixed32_16, int16_t >( ); + } + SUBCASE( "repeated" ) + { + gpb_compatibility_array< Test::Scalar::gpb::RepSfixed32_16, Test::Scalar::RepSfixed32_16 >( ); + + SUBCASE( "packed" ) + { + gpb_compatibility_array< Test::Scalar::gpb::RepPackSfixed32_16, Test::Scalar::RepPackSfixed32_16 >( ); + } + } + } + } + SUBCASE( "sfixed64" ) + { + SUBCASE( "bitfield" ) + { + gpb_compatibility_bitfield_value< Test::Scalar::gpb::ReqSfixed64_64_1, Test::Scalar::ReqSfixed64_64_1, int64_t >( { 0, 1 } ); + gpb_compatibility_bitfield_value< Test::Scalar::gpb::ReqSfixed64_64_2, Test::Scalar::ReqSfixed64_64_2, int64_t >( { 0, 1, 2, 3 } ); + } + SUBCASE( "required" ) + { + gpb_compatibility_value< Test::Scalar::gpb::ReqSfixed64, Test::Scalar::ReqSfixed64, int64_t >( ); + } + SUBCASE( "optional" ) + { + gpb_compatibility_value< Test::Scalar::gpb::OptSfixed64, Test::Scalar::OptSfixed64, int64_t >( ); + } + SUBCASE( "repeated" ) + { + gpb_compatibility_array< Test::Scalar::gpb::RepSfixed64, Test::Scalar::RepSfixed64 >( ); + + SUBCASE( "packed" ) + { + gpb_compatibility_array< Test::Scalar::gpb::RepPackSfixed64, Test::Scalar::RepPackSfixed64 >( ); + } + } + SUBCASE( "int8" ) + { + SUBCASE( "bitfield" ) + { + gpb_compatibility_bitfield_value< Test::Scalar::gpb::ReqSfixed64_8_1, Test::Scalar::ReqSfixed64_8_1, int8_t >( { 0, 1 } ); + gpb_compatibility_bitfield_value< Test::Scalar::gpb::ReqSfixed64_8_2, Test::Scalar::ReqSfixed64_8_2, int8_t >( { 0, 1, 2, 3 } ); + } + SUBCASE( "required" ) + { + gpb_compatibility_value< Test::Scalar::gpb::ReqSfixed64_8, Test::Scalar::ReqSfixed64_8, int8_t >( ); + } + SUBCASE( "optional" ) + { + gpb_compatibility_value< Test::Scalar::gpb::OptSfixed64_8, Test::Scalar::OptSfixed64_8, int8_t >( ); + } + SUBCASE( "repeated" ) + { + gpb_compatibility_array< Test::Scalar::gpb::RepSfixed64_8, Test::Scalar::RepSfixed64_8 >( ); + + SUBCASE( "packed" ) + { + gpb_compatibility_array< Test::Scalar::gpb::RepPackSfixed64_8, Test::Scalar::RepPackSfixed64_8 >( ); + } + } + } + SUBCASE( "int16" ) + { + SUBCASE( "bitfield" ) + { + gpb_compatibility_bitfield_value< Test::Scalar::gpb::ReqSfixed64_16_1, Test::Scalar::ReqSfixed64_16_1, int16_t >( { 0, 1 } ); + gpb_compatibility_bitfield_value< Test::Scalar::gpb::ReqSfixed64_16_2, Test::Scalar::ReqSfixed64_16_2, int16_t >( { 0, 1, 2, 3 } ); + } + SUBCASE( "required" ) + { + gpb_compatibility_value< Test::Scalar::gpb::ReqSfixed64_16, Test::Scalar::ReqSfixed64_16, int16_t >( ); + } + SUBCASE( "optional" ) + { + gpb_compatibility_value< Test::Scalar::gpb::OptSfixed64_16, Test::Scalar::OptSfixed64_16, int16_t >( ); + } + SUBCASE( "repeated" ) + { + gpb_compatibility_array< Test::Scalar::gpb::RepSfixed64_16, Test::Scalar::RepSfixed64_16 >( ); + + SUBCASE( "packed" ) + { + gpb_compatibility_array< Test::Scalar::gpb::RepPackSfixed64_16, Test::Scalar::RepPackSfixed64_16 >( ); + } + } + } + SUBCASE( "int32" ) + { + SUBCASE( "bitfield" ) + { + gpb_compatibility_bitfield_value< Test::Scalar::gpb::ReqSfixed64_32_1, Test::Scalar::ReqSfixed64_32_1, int32_t >( { 0, 1 } ); + gpb_compatibility_bitfield_value< Test::Scalar::gpb::ReqSfixed64_32_2, Test::Scalar::ReqSfixed64_32_2, int32_t >( { 0, 1, 2, 3 } ); + } + SUBCASE( "required" ) + { + gpb_compatibility_value< Test::Scalar::gpb::ReqSfixed64_32, Test::Scalar::ReqSfixed64_32, int32_t >( ); + } + SUBCASE( "optional" ) + { + gpb_compatibility_value< Test::Scalar::gpb::OptSfixed64_32, Test::Scalar::OptSfixed64_32, int32_t >( ); + } + SUBCASE( "repeated" ) + { + gpb_compatibility_array< Test::Scalar::gpb::RepSfixed64_32, Test::Scalar::RepSfixed64_32 >( ); + + SUBCASE( "packed" ) + { + gpb_compatibility_array< Test::Scalar::gpb::RepPackSfixed64_32, Test::Scalar::RepPackSfixed64_32 >( ); + } + } + } + } +} +TEST_CASE( "float" ) +{ + SUBCASE( "required" ) + { + gpb_compatibility< Test::Scalar::gpb::ReqFloat >( Test::Scalar::ReqFloat{ .value = 42.0 } ); + } + SUBCASE( "optional" ) + { + gpb_compatibility< Test::Scalar::gpb::OptFloat >( Test::Scalar::OptFloat{ .value = 42.3 } ); + } + SUBCASE( "repeated" ) + { + gpb_compatibility< Test::Scalar::gpb::RepFloat >( Test::Scalar::RepFloat{ .value = { 42.3 } } ); + gpb_compatibility< Test::Scalar::gpb::RepFloat >( Test::Scalar::RepFloat{ .value = { 42.0, 42.3 } } ); + } +} +TEST_CASE( "double" ) +{ + SUBCASE( "required" ) + { + gpb_compatibility< Test::Scalar::gpb::ReqDouble >( Test::Scalar::ReqDouble{ .value = 42.0 } ); + } + SUBCASE( "optional" ) + { + gpb_compatibility< Test::Scalar::gpb::OptDouble >( Test::Scalar::OptDouble{ .value = 42.3 } ); + } + SUBCASE( "repeated" ) + { + gpb_compatibility< Test::Scalar::gpb::RepDouble >( Test::Scalar::RepDouble{ .value = { 42.3 } } ); + gpb_compatibility< Test::Scalar::gpb::RepDouble >( Test::Scalar::RepDouble{ .value = { 42.3, 3.0 } } ); + } +} +TEST_CASE( "bytes" ) +{ + SUBCASE( "required" ) + { + gpb_compatibility< Test::Scalar::gpb::ReqBytes >( Test::Scalar::ReqBytes{ .value = to_bytes( "hello" ) } ); + gpb_compatibility< Test::Scalar::gpb::ReqBytes >( Test::Scalar::ReqBytes{ .value = to_bytes( "\x00\x01\x02"sv ) } ); + gpb_compatibility< Test::Scalar::gpb::ReqBytes >( Test::Scalar::ReqBytes{ .value = to_bytes( "\x00\x01\x02\x03\x04"sv ) } ); + } + SUBCASE( "optional" ) + { + gpb_compatibility< Test::Scalar::gpb::OptBytes >( Test::Scalar::OptBytes{ .value = to_bytes( "hello" ) } ); + gpb_compatibility< Test::Scalar::gpb::OptBytes >( Test::Scalar::OptBytes{ .value = to_bytes( "\x00\x01\x02"sv ) } ); + gpb_compatibility< Test::Scalar::gpb::OptBytes >( Test::Scalar::OptBytes{ .value = to_bytes( "\x00\x01\x02\x03\x04"sv ) } ); + } + SUBCASE( "repeated" ) + { + gpb_compatibility< Test::Scalar::gpb::RepBytes >( Test::Scalar::RepBytes{ .value = { to_bytes( "hello" ) } } ); + gpb_compatibility< Test::Scalar::gpb::RepBytes >( Test::Scalar::RepBytes{ .value = { to_bytes( "\x00\x01\x02"sv ), to_bytes( "hello" ) } } ); + gpb_compatibility< Test::Scalar::gpb::RepBytes >( Test::Scalar::RepBytes{ .value = { to_bytes( "\x00\x01\x02\x03\x04"sv ), to_bytes( "\x00\x01\x02"sv ), to_bytes( "hello" ) } } ); + } + SUBCASE( "fixed" ) + { + SUBCASE( "required" ) + { + gpb_compatibility< Test::Scalar::gpb::ReqBytesFixed >( Test::Scalar::ReqBytesFixed{ .value = to_array( "12345678" ) } ); + } + SUBCASE( "optional" ) + { + gpb_compatibility< Test::Scalar::gpb::OptBytesFixed >( Test::Scalar::OptBytesFixed{ .value = to_array( "12345678" ) } ); + } + SUBCASE( "repeated" ) + { + gpb_compatibility< Test::Scalar::gpb::RepBytesFixed >( Test::Scalar::RepBytesFixed{ .value = { to_array( "12345678" ) } } ); + gpb_compatibility< Test::Scalar::gpb::RepBytesFixed >( Test::Scalar::RepBytesFixed{ .value = { to_array( "12345678" ), to_array( "87654321" ) } } ); + } + } +} +TEST_CASE( "enum" ) +{ + SUBCASE( "required" ) + { + gpb_compatibility_enum< Test::Scalar::gpb::ReqEnum, Test::Scalar::ReqEnum, int32_t >( ); + } + SUBCASE( "optional" ) + { + gpb_compatibility_enum< Test::Scalar::gpb::OptEnum, Test::Scalar::OptEnum, int32_t >( ); + } + SUBCASE( "repeated" ) + { + gpb_compatibility_enum_array< Test::Scalar::gpb::RepEnum, Test::Scalar::RepEnum >( ); + } + + SUBCASE( "int8" ) + { + SUBCASE( "required" ) + { + gpb_compatibility_enum< Test::Scalar::gpb::ReqEnumInt8, Test::Scalar::ReqEnumInt8, int8_t >( ); + } + SUBCASE( "optional" ) + { + gpb_compatibility_enum< Test::Scalar::gpb::OptEnumInt8, Test::Scalar::OptEnumInt8, int8_t >( ); + } + SUBCASE( "repeated" ) + { + gpb_compatibility_enum_array< Test::Scalar::gpb::RepEnumInt8, Test::Scalar::RepEnumInt8 >( ); + } + } + + SUBCASE( "uint8" ) + { + SUBCASE( "required" ) + { + gpb_compatibility_enum< Test::Scalar::gpb::ReqEnumUint8, Test::Scalar::ReqEnumUint8, uint8_t >( ); + } + SUBCASE( "optional" ) + { + gpb_compatibility_enum< Test::Scalar::gpb::OptEnumUint8, Test::Scalar::OptEnumUint8, uint8_t >( ); + } + SUBCASE( "repeated" ) + { + gpb_compatibility_enum_array< Test::Scalar::gpb::RepEnumUint8, Test::Scalar::RepEnumUint8 >( ); + } + } + + SUBCASE( "int16" ) + { + SUBCASE( "required" ) + { + gpb_compatibility_enum< Test::Scalar::gpb::ReqEnumInt16, Test::Scalar::ReqEnumInt16, int16_t >( ); + } + SUBCASE( "optional" ) + { + gpb_compatibility_enum< Test::Scalar::gpb::OptEnumInt16, Test::Scalar::OptEnumInt16, int16_t >( ); + } + SUBCASE( "repeated" ) + { + gpb_compatibility_enum_array< Test::Scalar::gpb::RepEnumInt16, Test::Scalar::RepEnumInt16 >( ); + } + } + + SUBCASE( "uint16" ) + { + SUBCASE( "required" ) + { + gpb_compatibility_enum< Test::Scalar::gpb::ReqEnumUint16, Test::Scalar::ReqEnumUint16, uint16_t >( ); + } + SUBCASE( "optional" ) + { + gpb_compatibility_enum< Test::Scalar::gpb::OptEnumUint16, Test::Scalar::OptEnumUint16, uint16_t >( ); + } + SUBCASE( "repeated" ) + { + gpb_compatibility_enum_array< Test::Scalar::gpb::RepEnumUint16, Test::Scalar::RepEnumUint16 >( ); + } + } + + SUBCASE( "int32" ) + { + SUBCASE( "required" ) + { + gpb_compatibility_enum< Test::Scalar::gpb::ReqEnumInt32, Test::Scalar::ReqEnumInt32, int32_t >( ); + } + SUBCASE( "optional" ) + { + gpb_compatibility_enum< Test::Scalar::gpb::OptEnumInt32, Test::Scalar::OptEnumInt32, int32_t >( ); + } + SUBCASE( "repeated" ) + { + gpb_compatibility_enum_array< Test::Scalar::gpb::RepEnumInt32, Test::Scalar::RepEnumInt32 >( ); + } + } +} +TEST_CASE( "variant" ) +{ + SUBCASE( "int" ) + { + auto gpb = Test::gpb::Variant( ); + auto spb = Test::Variant{ .oneof_field = 0x42U }; + auto spb_serialized = spb::pb::serialize( spb ); + + REQUIRE( gpb.ParseFromString( spb_serialized ) ); + REQUIRE( gpb.var_int( ) == std::get< 0 >( spb.oneof_field ) ); + + SUBCASE( "deserialize" ) + { + auto gpb_serialized = std::string( ); + REQUIRE( gpb.SerializeToString( &gpb_serialized ) ); + REQUIRE( spb::pb::deserialize< Test::Variant >( gpb_serialized ) == spb ); + } + } + SUBCASE( "string" ) + { + auto gpb = Test::gpb::Variant( ); + auto spb = Test::Variant{ .oneof_field = "hello" }; + auto spb_serialized = spb::pb::serialize( spb ); + + REQUIRE( gpb.ParseFromString( spb_serialized ) ); + REQUIRE( gpb.var_string( ) == std::get< 1 >( spb.oneof_field ) ); + + SUBCASE( "deserialize" ) + { + auto gpb_serialized = std::string( ); + REQUIRE( gpb.SerializeToString( &gpb_serialized ) ); + REQUIRE( spb::pb::deserialize< Test::Variant >( gpb_serialized ) == spb ); + } + } + SUBCASE( "bytes" ) + { + auto gpb = Test::gpb::Variant( ); + auto spb = Test::Variant{ .oneof_field = to_bytes( "hello" ) }; + auto spb_serialized = spb::pb::serialize( spb ); + + REQUIRE( gpb.ParseFromString( spb_serialized ) ); + REQUIRE( gpb.var_bytes( ) == std::get< 2 >( spb.oneof_field ) ); + + SUBCASE( "deserialize" ) + { + auto gpb_serialized = std::string( ); + REQUIRE( gpb.SerializeToString( &gpb_serialized ) ); + REQUIRE( spb::pb::deserialize< Test::Variant >( gpb_serialized ) == spb ); + } + } + SUBCASE( "name" ) + { + auto gpb = Test::gpb::Variant( ); + auto spb = Test::Variant{ .oneof_field = Test::Name{ .name = "John" } }; + auto spb_serialized = spb::pb::serialize( spb ); + + REQUIRE( gpb.ParseFromString( spb_serialized ) ); + REQUIRE( gpb.has_name( ) ); + + REQUIRE( gpb.name( ).name( ) == std::get< 3 >( spb.oneof_field ).name ); + + SUBCASE( "deserialize" ) + { + auto gpb_serialized = std::string( ); + REQUIRE( gpb.SerializeToString( &gpb_serialized ) ); + REQUIRE( spb::pb::deserialize< Test::Variant >( gpb_serialized ) == spb ); + } + } +} +TEST_CASE( "person" ) +{ + auto gpb = PhoneBook::gpb::Person( ); + auto spb = PhoneBook::Person{ + .name = "John Doe", + .id = 123, + .email = "QXUeh@example.com", + .phones = { + PhoneBook::Person::PhoneNumber{ + .number = "555-4321", + .type = PhoneBook::Person::PhoneType::HOME, + }, + PhoneBook::Person::PhoneNumber{ + .number = "999-1234", + .type = PhoneBook::Person::PhoneType::MOBILE, + }, + } + }; + SUBCASE( "gpb serialize/deserialize" ) + { + auto spb_serialized = spb::pb::serialize( spb ); + + REQUIRE( gpb.ParseFromString( spb_serialized ) ); + REQUIRE( gpb.name( ) == spb.name ); + REQUIRE( gpb.id( ) == spb.id ); + REQUIRE( gpb.email( ) == spb.email ); + REQUIRE( gpb.phones_size( ) == 2 ); + for( auto i = 0; i < gpb.phones_size( ); i++ ) + { + REQUIRE( gpb.phones( i ).number( ) == spb.phones[ i ].number ); + REQUIRE( int( gpb.phones( i ).type( ) ) == int( spb.phones[ i ].type.value( ) ) ); + } + + auto gpb_serialized = std::string( ); + REQUIRE( gpb.SerializeToString( &gpb_serialized ) ); + REQUIRE( spb::pb::deserialize< PhoneBook::Person >( gpb_serialized ) == spb ); + REQUIRE( gpb_serialized == spb_serialized ); + } + SUBCASE( "json serialize/deserialize" ) + { + auto spb_json = spb::json::serialize( spb ); + + auto parse_options = google::protobuf::util::JsonParseOptions{ }; + REQUIRE( JsonStringToMessage( spb_json, &gpb, parse_options ).ok( ) ); + + REQUIRE( gpb.name( ) == spb.name ); + REQUIRE( gpb.id( ) == spb.id ); + REQUIRE( gpb.email( ) == spb.email ); + REQUIRE( gpb.phones_size( ) == 2 ); + for( auto i = 0; i < gpb.phones_size( ); i++ ) + { + REQUIRE( gpb.phones( i ).number( ) == spb.phones[ i ].number ); + REQUIRE( int( gpb.phones( i ).type( ) ) == int( spb.phones[ i ].type.value( ) ) ); + } + + auto json_string = std::string( ); + auto print_options = google::protobuf::util::JsonPrintOptions( ); + print_options.preserve_proto_field_names = true; + + REQUIRE( MessageToJsonString( gpb, &json_string, print_options ).ok( ) ); + REQUIRE( spb::json::deserialize< PhoneBook::Person >( json_string ) == spb ); + + print_options.preserve_proto_field_names = false; + json_string.clear( ); + REQUIRE( MessageToJsonString( gpb, &json_string, print_options ).ok( ) ); + REQUIRE( spb::json::deserialize< PhoneBook::Person >( json_string ) == spb ); + REQUIRE( json_string == spb_json ); + } +} diff --git a/3rdparty/simple-protobuf/test/json.cpp b/3rdparty/simple-protobuf/test/json.cpp new file mode 100644 index 0000000..b21ea85 --- /dev/null +++ b/3rdparty/simple-protobuf/test/json.cpp @@ -0,0 +1,923 @@ +#include "spb/json.hpp" +#include +#include +#include +#include +#include +#include +#include + +#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN +#include "doctest.h" + +namespace std +{ +auto operator==( const std::span< const std::byte > & lhs, + const std::span< const std::byte > & rhs ) noexcept -> bool +{ + return lhs.size( ) == rhs.size( ) && memcmp( lhs.data( ), rhs.data( ), lhs.size( ) ) == 0; +} +}// namespace std + +namespace +{ +template < typename T > +concept HasValueMember = requires( T t ) { + { t.value }; +}; + +template < size_t N > +auto to_array( const char ( &string )[ N ] ) +{ + auto result = std::array< std::byte, N - 1 >( ); + memcpy( result.data( ), &string, result.size( ) ); + return result; +} + +auto to_bytes( std::string_view str ) -> std::vector< std::byte > +{ + auto span = std::span< std::byte >( ( std::byte * ) str.data( ), str.size( ) ); + return { span.data( ), span.data( ) + span.size( ) }; +} + +template < typename T > +void json_test( const T & value, std::string_view json ) +{ + { + auto serialized = spb::json::serialize( value ); + CHECK( serialized == json ); + auto size = spb::json::serialize_size( value ); + CHECK( size == json.size( ) ); + } + + { + auto deserialized = spb::json::deserialize< T >( json ); + if constexpr( HasValueMember< T > ) + { + using valueT = decltype( T::value ); + CHECK( valueT( deserialized.value ) == valueT( value.value ) ); + } + else + { + CHECK( deserialized == value ); + } + } + { + auto deserialized = T( ); + spb::json::deserialize( deserialized, json ); + if constexpr( HasValueMember< T > ) + { + using valueT = decltype( T::value ); + CHECK( valueT( deserialized.value ) == valueT( value.value ) ); + } + else + { + CHECK( deserialized == value ); + } + } +} + +}// namespace + +namespace Test +{ +auto operator==( const Test::Name & lhs, const Test::Name & rhs ) noexcept -> bool +{ + return lhs.name == rhs.name && lhs.bkfvdzz == rhs.bkfvdzz; +} +}// namespace Test + +using namespace std::literals; + +TEST_CASE( "json" ) +{ + SUBCASE( "dj2hash" ) + { + const auto hash = spb::json::detail::djb2_hash( "hello" ); + const auto collision = spb::json::detail::djb2_hash( "narpjy" ); + const auto hash2 = spb::json::detail::djb2_hash( "world" ); + const auto name_hash = spb::json::detail::djb2_hash( "name" ); + const auto name_collision = spb::json::detail::djb2_hash( "bkfvdzz" ); + const auto hash3 = spb::json::detail::djb2_hash( { } ); + CHECK( hash == collision ); + CHECK( name_hash == name_collision ); + CHECK( hash3 != 0 ); + CHECK( hash != 0 ); + CHECK( hash2 != 0 ); + CHECK( hash != hash2 ); + CHECK( hash3 != hash2 ); + CHECK( hash3 != hash ); + } + SUBCASE( "deserialize" ) + { + SUBCASE( "ignore" ) + { + SUBCASE( "empty" ) + { + CHECK( spb::json::deserialize< Test::Name >( R"({})"sv ) == Test::Name{ } ); + CHECK( + spb::json::deserialize< Test::Name >( + R"({ })"sv ) == + Test::Name{ } ); + CHECK_THROWS( ( void ) spb::json::deserialize< Test::Name >( ""sv ) ); + CHECK_THROWS( ( void ) spb::json::deserialize< Test::Name >( R"(})"sv ) ); + CHECK_THROWS( ( void ) spb::json::deserialize< Test::Name >( R"({)"sv ) ); + } + SUBCASE( "string" ) + { + CHECK( spb::json::deserialize< Test::Name >( R"({"string":"string"})"sv ) == + Test::Name{ } ); + CHECK( spb::json::deserialize< Test::Name >( R"({"bkfvdzz":"string"})"sv ) == + Test::Name{ .bkfvdzz = "string" } ); + CHECK( + spb::json::deserialize< Test::Name >( + R"({"string ":"string "})"sv ) == + Test::Name{ } ); + } + SUBCASE( "int" ) + { + CHECK( spb::json::deserialize< Test::Name >( R"({"integer":42})"sv ) == + Test::Name{ } ); + CHECK( spb::json::deserialize< Test::Name >( R"({"bkfvdzz1":42})"sv ) == + Test::Name{ } ); + } + SUBCASE( "float" ) + { + CHECK( spb::json::deserialize< Test::Name >( R"({"fl":42.0, "fl2":0})"sv ) == + Test::Name{ } ); + } + SUBCASE( "repeated" ) + { + CHECK( spb::json::deserialize< Test::Name >( R"({"repeated":[42,"hello"]})"sv ) == + Test::Name{ } ); + CHECK( spb::json::deserialize< Test::Name >( R"({"repeated":[]})"sv ) == + Test::Name{ } ); + CHECK( spb::json::deserialize< Test::Name >( R"({"name1":[]})"sv ) == + Test::Name{ } ); + CHECK_THROWS( + ( void ) spb::json::deserialize< Test::Name >( R"({"repeated":[})"sv ) ); + CHECK_THROWS( + ( void ) spb::json::deserialize< Test::Name >( R"({"repeated":[)"sv ) ); + CHECK_THROWS( + ( void ) spb::json::deserialize< Test::Name >( R"({"repeated":[42)"sv ) ); + CHECK_THROWS( + ( void ) spb::json::deserialize< Test::Name >( R"({"repeated":[42,)"sv ) ); + } + SUBCASE( "bool" ) + { + CHECK( spb::json::deserialize< Test::Name >( + R"({"value":true, "value2":false})"sv ) == Test::Name{ } ); + CHECK_THROWS( ( void ) spb::json::deserialize< Test::Name >( + R"({"value":tru, "value2":false})"sv ) ); + CHECK_THROWS( ( void ) spb::json::deserialize< Test::Name >( + R"({"value":true, "value2":fals})"sv ) ); + } + SUBCASE( "null" ) + { + CHECK( spb::json::deserialize< Test::Name >( R"({"value":null})"sv ) == + Test::Name{ } ); + CHECK_THROWS( + ( void ) spb::json::deserialize< Test::Name >( R"({"value":nul})"sv ) ); + } + SUBCASE( "object" ) + { + CHECK( spb::json::deserialize< Test::Name >( R"({"value":{}})"sv ) == + Test::Name{ } ); + CHECK( spb::json::deserialize< Test::Name >( + R"({"value":{"key":"value", "key2":[42]}})"sv ) == Test::Name{ } ); + CHECK_THROWS( ( void ) spb::json::deserialize< Test::Name >( R"({"value"})"sv ) ); + CHECK_THROWS( ( void ) spb::json::deserialize< Test::Name >( + R"({"value":{"key":"value", "key2":[42]})"sv ) ); + CHECK_THROWS( + ( void ) spb::json::deserialize< Test::Name >( R"({"value":{"key":}})"sv ) ); + CHECK_THROWS( + ( void ) spb::json::deserialize< Test::Name >( R"({"value":{"key"}})"sv ) ); + CHECK_THROWS( ( void ) spb::json::deserialize< Test::Name >( + R"({"value":{"key":"value")"sv ) ); + CHECK_THROWS( ( void ) spb::json::deserialize< Test::Name >( + R"({"value":{"key":"value",)"sv ) ); + } + } + SUBCASE( "person" ) + { + constexpr std::string_view jsons[] = { + R"({"name": "John Doe","id": 123,"email": "QXUeh@example.com", "phones": [ {"number": "555-4321","type": "HOME"}]})", + R"({"name": "John Doe","id": 123,"email": "QXUeh@example.com", "phones": [ {"number": "555-4321","type": 1}]})", + R"({"name2": "Jack","name": "John Doe","id": 123,"email": "QXUeh@example.com", "phones": [ {"number": "555-4321","type": "HOME"}]})", + }; + for( auto json : jsons ) + { + const auto person = spb::json::deserialize< PhoneBook::Person >( json ); + CHECK( person.name == "John Doe" ); + CHECK( person.id == 123 ); + CHECK( person.email == "QXUeh@example.com" ); + CHECK( person.phones.size( ) == 1 ); + CHECK( person.phones[ 0 ].number == "555-4321" ); + CHECK( person.phones[ 0 ].type == PhoneBook::Person::PhoneType::HOME ); + } + } + SUBCASE( "enum" ) + { + CHECK( spb::json::deserialize< PhoneBook::Person::PhoneType >( "\"HOME\""sv ) == + PhoneBook::Person::PhoneType::HOME ); + CHECK( Test::Scalar::ReqEnumAlias::Enum::EAA_STARTED == + Test::Scalar::ReqEnumAlias::Enum::EAA_RUNNING ); + CHECK( spb::json::deserialize< Test::Scalar::ReqEnumAlias::Enum >( + "\"EAA_STARTED\""sv ) == Test::Scalar::ReqEnumAlias::Enum::EAA_STARTED ); + CHECK( spb::json::deserialize< Test::Scalar::ReqEnumAlias::Enum >( + "\"EAA_STARTED\""sv ) == Test::Scalar::ReqEnumAlias::Enum::EAA_RUNNING ); + CHECK( spb::json::deserialize< Test::Scalar::ReqEnumAlias::Enum >( + "\"EAA_RUNNING\""sv ) == Test::Scalar::ReqEnumAlias::Enum::EAA_STARTED ); + CHECK( spb::json::deserialize< Test::Scalar::ReqEnumAlias::Enum >( + "\"EAA_RUNNING\""sv ) == Test::Scalar::ReqEnumAlias::Enum::EAA_RUNNING ); + REQUIRE_THROWS( + ( void ) spb::json::deserialize< PhoneBook::Person::PhoneType >( "3"sv ) ); + REQUIRE_THROWS( + ( void ) spb::json::deserialize< PhoneBook::Person::PhoneType >( "HME"sv ) ); + } + SUBCASE( "bool" ) + { + { + CHECK( spb::json::deserialize< bool >( "true"sv ) == true ); + CHECK( spb::json::deserialize< bool >( "false"sv ) == false ); + CHECK_THROWS( ( void ) spb::json::deserialize< bool >( "hello"sv ) ); + CHECK_THROWS( ( void ) spb::json::deserialize< bool >( "true1"sv ) ); + CHECK_THROWS( ( void ) spb::json::deserialize< bool >( "true_"sv ) ); + auto value = false; + CHECK_NOTHROW( spb::json::deserialize( value, "true"sv ) ); + CHECK( value ); + CHECK_NOTHROW( spb::json::deserialize( value, "false"sv ) ); + CHECK( value == false ); + } + SUBCASE( "repeated" ) + { + CHECK( spb::json::deserialize< std::vector< bool > >( R"([true,false])"sv ) == + std::vector< bool >{ true, false } ); + CHECK( spb::json::deserialize< std::vector< bool > >( R"([])"sv ) == + std::vector< bool >{ } ); + CHECK( spb::json::deserialize< std::vector< bool > >( R"(null)"sv ) == + std::vector< bool >{ } ); + CHECK_THROWS( ( void ) spb::json::deserialize< std::vector< bool > >( R"()"sv ) ); + CHECK_THROWS( + ( void ) spb::json::deserialize< std::vector< bool > >( R"(true)"sv ) ); + CHECK_THROWS( + ( void ) spb::json::deserialize< std::vector< bool > >( R"([null])"sv ) ); + CHECK_THROWS( + ( void ) spb::json::deserialize< std::vector< bool > >( R"("hello")"sv ) ); + CHECK_THROWS( ( void ) spb::json::deserialize< std::vector< bool > >( R"([)"sv ) ); + } + SUBCASE( "optional" ) + { + CHECK( spb::json::deserialize< std::optional< bool > >( "true"sv ) == + std::optional< bool >( true ) ); + CHECK( spb::json::deserialize< std::optional< bool > >( "false"sv ) == + std::optional< bool >( false ) ); + CHECK( spb::json::deserialize< std::optional< bool > >( "null"sv ) == + std::optional< bool >( ) ); + CHECK_THROWS( + ( void ) spb::json::deserialize< std::optional< bool > >( "hello"sv ) ); + CHECK_THROWS( ( void ) spb::json::deserialize< std::optional< bool > >( R"()"sv ) ); + + auto value = std::optional< bool >( ); + CHECK_NOTHROW( spb::json::deserialize( value, "true"sv ) ); + CHECK( value == true ); + CHECK_NOTHROW( spb::json::deserialize( value, "false"sv ) ); + CHECK( value == false ); + } + SUBCASE( "ptr" ) + { + CHECK( *spb::json::deserialize< std::unique_ptr< bool > >( "true"sv ) == true ); + CHECK( *spb::json::deserialize< std::unique_ptr< bool > >( "false"sv ) == false ); + CHECK( spb::json::deserialize< std::unique_ptr< bool > >( "null"sv ) == + std::unique_ptr< bool >( ) ); + CHECK_THROWS( + ( void ) spb::json::deserialize< std::unique_ptr< bool > >( "hello"sv ) ); + CHECK_THROWS( + ( void ) spb::json::deserialize< std::unique_ptr< bool > >( R"()"sv ) ); + + auto value = std::unique_ptr< bool >( ); + CHECK_NOTHROW( spb::json::deserialize( value, "true"sv ) ); + CHECK( *value == true ); + CHECK_NOTHROW( spb::json::deserialize( value, "false"sv ) ); + CHECK( *value == false ); + } + } + SUBCASE( "float" ) + { + CHECK( spb::json::deserialize< float >( "0"sv ) == 0 ); + CHECK( spb::json::deserialize< float >( "42"sv ) == 42 ); + CHECK( spb::json::deserialize< float >( "3.14"sv ) == 3.14f ); + CHECK( spb::json::deserialize< float >( "0.0"sv ) == 0.0f ); + CHECK( spb::json::deserialize< float >( "-0.0"sv ) == -0.0f ); + CHECK( spb::json::deserialize< float >( "-3.14"sv ) == -3.14f ); + CHECK( spb::json::deserialize< float >( "3.14e+10"sv ) == 3.14e+10f ); + CHECK( spb::json::deserialize< float >( "3.14e-10"sv ) == 3.14e-10f ); + CHECK( spb::json::deserialize< float >( "3.14E+10"sv ) == 3.14E+10f ); + CHECK( spb::json::deserialize< float >( "3.14E-10"sv ) == 3.14E-10f ); + CHECK_THROWS( ( void ) spb::json::deserialize< float >( "hello"sv ) ); + CHECK_THROWS( ( void ) spb::json::deserialize< float >( ""sv ) ); + CHECK_THROWS( ( void ) spb::json::deserialize< float >( "true"sv ) ); + CHECK_THROWS( ( void ) spb::json::deserialize< float >( R"("hello")"sv ) ); + SUBCASE( "repeated" ) + { + CHECK( spb::json::deserialize< std::vector< float > >( "[]"sv ) == + std::vector< float >{ } ); + CHECK( spb::json::deserialize< std::vector< float > >( "null"sv ) == + std::vector< float >{ } ); + CHECK( spb::json::deserialize< std::vector< float > >( "[42]"sv ) == + std::vector< float >{ 42 } ); + CHECK( spb::json::deserialize< std::vector< float > >( "[42,3.14]"sv ) == + std::vector< float >{ 42, 3.14f } ); + CHECK( spb::json::deserialize< std::vector< float > >( "[42,3.14,0.0]"sv ) == + std::vector< float >{ 42, 3.14f, 0.0f } ); + CHECK( spb::json::deserialize< std::vector< float > >( "[42,3.14,-0.0]"sv ) == + std::vector< float >{ 42, 3.14f, -0.0f } ); + CHECK_THROWS( ( void ) spb::json::deserialize< std::vector< float > >( "42"sv ) ); + CHECK_THROWS( ( void ) spb::json::deserialize< std::vector< float > >( "true"sv ) ); + CHECK_THROWS( + ( void ) spb::json::deserialize< std::vector< float > >( "hello"sv ) ); + CHECK_THROWS( ( void ) spb::json::deserialize< std::vector< float > >( R"([)"sv ) ); + } + SUBCASE( "optional" ) + { + auto value = std::optional< float >( ); + CHECK_NOTHROW( spb::json::deserialize( value, "42"sv ) ); + CHECK( *value == 42.0f ); + CHECK_NOTHROW( spb::json::deserialize( value, "3.14"sv ) ); + CHECK( *value == 3.14f ); + CHECK_NOTHROW( spb::json::deserialize( value, "0.0"sv ) ); + CHECK( *value == 0.0f ); + CHECK_NOTHROW( spb::json::deserialize( value, "-3.14"sv ) ); + CHECK( *value == -3.14f ); + CHECK( spb::json::deserialize< std::optional< float > >( "42"sv ) == 42 ); + CHECK( spb::json::deserialize< std::optional< float > >( "null"sv ) == + std::nullopt ); + CHECK_THROWS( + ( void ) spb::json::deserialize< std::optional< float > >( "hello"sv ) ); + CHECK_THROWS( ( void ) spb::json::deserialize< std::optional< float > >( ""sv ) ); + CHECK_THROWS( + ( void ) spb::json::deserialize< std::optional< float > >( "true"sv ) ); + CHECK_THROWS( + ( void ) spb::json::deserialize< std::optional< float > >( R"("hello")"sv ) ); + } + } + SUBCASE( "int32" ) + { + + CHECK( spb::json::deserialize< int32_t >( "42"sv ) == 42 ); + CHECK( spb::json::deserialize< int32_t >( "\"42\""sv ) == 42 ); + CHECK( spb::json::deserialize< int32_t >( "-42"sv ) == -42 ); + CHECK( spb::json::deserialize< int32_t >( "0"sv ) == 0 ); + CHECK( spb::json::deserialize< int32_t >( "\"-0\""sv ) == 0 ); + CHECK( spb::json::deserialize< int32_t >( "2147483647"sv ) == 2147483647 ); + CHECK( spb::json::deserialize< int32_t >( "-2147483648"sv ) == -2147483648 ); + CHECK_THROWS( ( void ) spb::json::deserialize< int32_t >( "hello"sv ) ); + CHECK_THROWS( ( void ) spb::json::deserialize< int32_t >( ""sv ) ); + CHECK_THROWS( ( void ) spb::json::deserialize< int32_t >( "true"sv ) ); + CHECK_THROWS( ( void ) spb::json::deserialize< int32_t >( R"("hello")"sv ) ); + SUBCASE( "repeated" ) + { + CHECK( spb::json::deserialize< std::vector< int32_t > >( R"([42,-42,0])"sv ) == + std::vector< int32_t >{ 42, -42, 0 } ); + CHECK( spb::json::deserialize< std::vector< int32_t > >( + R"([-2147483648,2147483647])"sv ) == + std::vector< int32_t >{ -2147483648, 2147483647 } ); + CHECK( spb::json::deserialize< std::vector< int32_t > >( R"([])"sv ) == + std::vector< int32_t >( ) ); + CHECK( spb::json::deserialize< std::vector< int32_t > >( R"(null)"sv ) == + std::vector< int32_t >( ) ); + CHECK_THROWS( + ( void ) spb::json::deserialize< std::vector< int32_t > >( R"([)"sv ) ); + CHECK_THROWS( + ( void ) spb::json::deserialize< std::vector< int32_t > >( R"(])"sv ) ); + CHECK_THROWS( + ( void ) spb::json::deserialize< std::vector< int32_t > >( R"([42,)"sv ) ); + } + SUBCASE( "optional" ) + { + CHECK( spb::json::deserialize< std::optional< int32_t > >( "42"sv ) == + std::optional< int32_t >( 42 ) ); + CHECK( spb::json::deserialize< std::optional< int32_t > >( "-42"sv ) == + std::optional< int32_t >( -42 ) ); + CHECK( spb::json::deserialize< std::optional< int32_t > >( "0"sv ) == + std::optional< int32_t >( 0 ) ); + CHECK( spb::json::deserialize< std::optional< int32_t > >( "2147483647"sv ) == + std::optional< int32_t >( 2147483647 ) ); + CHECK( spb::json::deserialize< std::optional< int32_t > >( "-2147483648"sv ) == + std::optional< int32_t >( -2147483648 ) ); + CHECK( spb::json::deserialize< std::optional< int32_t > >( "null"sv ) == + std::optional< int32_t >( ) ); + CHECK_THROWS( + ( void ) spb::json::deserialize< std::optional< int32_t > >( "hello"sv ) ); + CHECK_THROWS( + ( void ) spb::json::deserialize< std::optional< int32_t > >( R"()"sv ) ); + } + SUBCASE( "ptr" ) + { + auto value = std::unique_ptr< int32_t >( ); + CHECK_NOTHROW( spb::json::deserialize( value, "42"sv ) ); + CHECK( *value == 42 ); + CHECK_NOTHROW( spb::json::deserialize( value, "-42"sv ) ); + CHECK( *value == -42 ); + CHECK_NOTHROW( spb::json::deserialize( value, "0"sv ) ); + CHECK( *value == 0 ); + CHECK_NOTHROW( spb::json::deserialize( value, "2147483647"sv ) ); + CHECK( *value == 2147483647 ); + CHECK_NOTHROW( spb::json::deserialize( value, "-2147483648"sv ) ); + CHECK( *value == -2147483648 ); + CHECK_NOTHROW( spb::json::deserialize( value, "null"sv ) ); + CHECK( value == nullptr ); + } + SUBCASE( "bitfield" ) + { + CHECK( spb::json::deserialize< Test::Scalar::ReqUint8_1 >( R"({"value":0})"sv ) + .value == 0 ); + CHECK( spb::json::deserialize< Test::Scalar::ReqUint8_1 >( R"({"value":1})"sv ) + .value == 1 ); + CHECK_THROWS( ( void ) spb::json::deserialize< Test::Scalar::ReqUint8_1 >( + R"({"value":2})"sv ) ); + } + } + + SUBCASE( "repeated" ) + { + CHECK( + spb::json::deserialize< std::vector< std::string > >( R"(["hello","world"])"sv ) == + std::vector< std::string >{ "hello", "world" } ); + CHECK( spb::json::deserialize< std::vector< std::string > >( R"([])"sv ) == + std::vector< std::string >{ } ); + CHECK( spb::json::deserialize< std::vector< std::string > >( R"(null)"sv ) == + std::vector< std::string >{ } ); + CHECK_THROWS( + ( void ) spb::json::deserialize< std::vector< std::string > >( R"()"sv ) ); + CHECK_THROWS( + ( void ) spb::json::deserialize< std::vector< std::string > >( R"(true)"sv ) ); + CHECK_THROWS( + ( void ) spb::json::deserialize< std::vector< std::string > >( R"("hello"])"sv ) ); + CHECK_THROWS( + ( void ) spb::json::deserialize< std::vector< std::string > >( R"([)"sv ) ); + CHECK_THROWS( + ( void ) spb::json::deserialize< std::vector< std::string > >( R"(["hello")"sv ) ); + CHECK_THROWS( + ( void ) spb::json::deserialize< std::vector< std::string > >( R"(["hello",)"sv ) ); + CHECK_THROWS( ( void ) spb::json::deserialize< std::vector< std::string > >( + R"(["hello",])"sv ) ); + CHECK_THROWS( ( void ) spb::json::deserialize< std::vector< std::string > >( + R"(["hello","world")"sv ) ); + } + SUBCASE( "bytes" ) + { + CHECK( spb::json::deserialize< std::vector< std::byte > >( R"("AAECAwQ=")"sv ) == + to_bytes( "\x00\x01\x02\x03\x04"sv ) ); + CHECK( spb::json::deserialize< std::vector< std::byte > >( R"("aGVsbG8=")"sv ) == + to_bytes( "hello" ) ); + CHECK( spb::json::deserialize< std::vector< std::byte > >( R"(null)"sv ) == + std::vector< std::byte >{ } ); + CHECK( spb::json::deserialize< std::vector< std::byte > >( R"("")"sv ) == + std::vector< std::byte >{ } ); + CHECK_THROWS( + ( void ) spb::json::deserialize< std::vector< std::byte > >( R"(true)"sv ) ); + CHECK_THROWS( + ( void ) spb::json::deserialize< std::vector< std::byte > >( R"("AAECAwQ")"sv ) ); + CHECK_THROWS( + ( void ) spb::json::deserialize< std::vector< std::byte > >( R"([])"sv ) ); + CHECK_THROWS( ( void ) spb::json::deserialize< std::vector< std::byte > >( R"()"sv ) ); + SUBCASE( "repeated" ) + { + CHECK( spb::json::deserialize< std::vector< std::vector< std::byte > > >( + R"(["AAECAwQ="])"sv ) == + std::vector< std::vector< std::byte > >{ + to_bytes( "\x00\x01\x02\x03\x04"sv ) } ); + CHECK( spb::json::deserialize< std::vector< std::vector< std::byte > > >( + R"(["AAECAwQ=","aGVsbG8="])"sv ) == + std::vector< std::vector< std::byte > >{ + to_bytes( "\x00\x01\x02\x03\x04"sv ), to_bytes( "hello" ) } ); + CHECK( spb::json::deserialize< std::vector< std::vector< std::byte > > >( + R"([])"sv ) == std::vector< std::vector< std::byte > >{ } ); + CHECK( spb::json::deserialize< std::vector< std::vector< std::byte > > >( + R"(null)"sv ) == std::vector< std::vector< std::byte > >{ } ); + CHECK( spb::json::deserialize< std::vector< std::vector< std::byte > > >( + R"([""])"sv ) == + std::vector< std::vector< std::byte > >{ std::vector< std::byte >{} } ); + } + SUBCASE( "optional" ) + { + CHECK( spb::json::deserialize< std::optional< std::vector< std::byte > > >( + R"(null)"sv ) == std::nullopt ); + CHECK( spb::json::deserialize< std::optional< std::vector< std::byte > > >( + R"("AAECAwQ=")"sv ) == + std::vector< std::byte >{ std::byte( 0 ), std::byte( 1 ), std::byte( 2 ), + std::byte( 3 ), std::byte( 4 ) } ); + CHECK( spb::json::deserialize< std::optional< std::vector< std::byte > > >( + R"("aGVsbG8=")"sv ) == + std::vector< std::byte >{ std::byte( 'h' ), std::byte( 'e' ), + std::byte( 'l' ), std::byte( 'l' ), + std::byte( 'o' ) } ); + CHECK( spb::json::deserialize< std::optional< std::vector< std::byte > > >( + R"("")"sv ) == std::vector< std::byte >{ } ); + CHECK_THROWS( + ( void ) spb::json::deserialize< std::optional< std::vector< std::byte > > >( + R"(true)"sv ) ); + CHECK_THROWS( + ( void ) spb::json::deserialize< std::optional< std::vector< std::byte > > >( + R"("AAECAwQ")"sv ) ); + CHECK_THROWS( + ( void ) spb::json::deserialize< std::optional< std::vector< std::byte > > >( + R"([])"sv ) ); + CHECK_THROWS( + ( void ) spb::json::deserialize< std::optional< std::vector< std::byte > > >( + R"()"sv ) ); + } + } + SUBCASE( "map" ) + { + SUBCASE( "int32/int32" ) + { + CHECK( spb::json::deserialize< std::map< int32_t, int32_t > >( R"({"1":2})"sv ) == + std::map< int32_t, int32_t >{ { 1, 2 } } ); + CHECK( spb::json::deserialize< std::map< int32_t, int32_t > >( + R"({"1":2,"2":3})"sv ) == + std::map< int32_t, int32_t >{ { 1, 2 }, { 2, 3 } } ); + CHECK( spb::json::deserialize< std::map< int32_t, int32_t > >( R"({})"sv ) == + std::map< int32_t, int32_t >{ } ); + CHECK( spb::json::deserialize< std::map< int32_t, int32_t > >( R"(null)"sv ) == + std::map< int32_t, int32_t >{ } ); + CHECK_THROWS( + ( void ) spb::json::deserialize< std::map< int32_t, int32_t > >( R"()"sv ) ); + CHECK_THROWS( ( void ) spb::json::deserialize< std::map< int32_t, int32_t > >( + R"("hello")"sv ) ); + CHECK_THROWS( ( void ) spb::json::deserialize< std::map< int32_t, int32_t > >( + R"({"hello":2})"sv ) ); + CHECK_THROWS( ( void ) spb::json::deserialize< std::map< int32_t, int32_t > >( + R"({"1":"hello"})"sv ) ); + } + SUBCASE( "string/string" ) + { + CHECK( spb::json::deserialize< std::map< std::string, std::string > >( + R"({"hello":"world"})"sv ) == + std::map< std::string, std::string >{ { "hello", "world" } } ); + CHECK( spb::json::deserialize< std::map< std::string, std::string > >( + R"({})"sv ) == std::map< std::string, std::string >{ } ); + CHECK( spb::json::deserialize< std::map< std::string, std::string > >( + R"(null)"sv ) == std::map< std::string, std::string >{ } ); + CHECK_THROWS( + ( void ) spb::json::deserialize< std::map< std::string, std::string > >( + R"()"sv ) ); + CHECK_THROWS( + ( void ) spb::json::deserialize< std::map< std::string, std::string > >( + R"({"1":["hello"]})"sv ) ); + CHECK_THROWS( + ( void ) spb::json::deserialize< std::map< std::string, std::string > >( + R"({"hello":{"hello":"world"}]})"sv ) ); + } + SUBCASE( "int32/string" ) + { + CHECK( spb::json::deserialize< std::map< int32_t, std::string > >( + R"({"1":"hello"})"sv ) == + std::map< int32_t, std::string >{ { 1, "hello" } } ); + CHECK( spb::json::deserialize< std::map< int32_t, std::string > >( R"({})"sv ) == + std::map< int32_t, std::string >{ } ); + CHECK( spb::json::deserialize< std::map< int32_t, std::string > >( R"(null)"sv ) == + std::map< int32_t, std::string >{ } ); + CHECK_THROWS( ( void ) spb::json::deserialize< std::map< int32_t, std::string > >( + R"()"sv ) ); + CHECK_THROWS( ( void ) spb::json::deserialize< std::map< int32_t, std::string > >( + R"({"hello":"world"})"sv ) ); + CHECK_THROWS( ( void ) spb::json::deserialize< std::map< int32_t, std::string > >( + R"({"1":1})"sv ) ); + } + SUBCASE( "string/int32" ) + { + CHECK( spb::json::deserialize< std::map< std::string, int32_t > >( + R"({"hello":2})"sv ) == + std::map< std::string, int32_t >{ { "hello", 2 } } ); + CHECK( spb::json::deserialize< std::map< std::string, int32_t > >( R"({})"sv ) == + std::map< std::string, int32_t >{ } ); + CHECK( spb::json::deserialize< std::map< std::string, int32_t > >( R"(null)"sv ) == + std::map< std::string, int32_t >{ } ); + CHECK_THROWS( ( void ) spb::json::deserialize< std::map< std::string, int32_t > >( + R"()"sv ) ); + CHECK_THROWS( ( void ) spb::json::deserialize< std::map< std::string, int32_t > >( + R"({"2","hello"})"sv ) ); + CHECK_THROWS( ( void ) spb::json::deserialize< std::map< std::string, int32_t > >( + R"({"1":})"sv ) ); + CHECK_THROWS( ( void ) spb::json::deserialize< std::map< std::string, int32_t > >( + R"({"hello":2)"sv ) ); + } + SUBCASE( "string/name" ) + { + CHECK( spb::json::deserialize< std::map< std::string, Test::Name > >( + R"({"hello":{"name":"john"}})"sv ) == + std::map< std::string, Test::Name >{ { "hello", { .name = "john" } } } ); + } + } + SUBCASE( "string" ) + { + SUBCASE( "escape" ) + { + for( int c = 0; c <= 0xff; c++ ) + { + const auto esc = c == '"' || c == '\\' || c == '/' || c == 'b' || c == 'f' || + c == 'n' || c == 'r' || c == 't'; + + char buffer[] = { '"', '\\', char( c ), '"', 0 }; + if( !esc ) + { + CHECK_THROWS( ( void ) spb::json::deserialize< std::string >( + std::string_view( buffer ) ) ); + } + } + + CHECK( spb::json::deserialize< std::string >( R"("hello")"sv ) == "hello" ); + CHECK_THROWS( ( void ) spb::json::deserialize< std::string >( R"(hello")"sv ) ); + CHECK_THROWS( ( void ) spb::json::deserialize< std::string >( R"("hello)"sv ) ); + SUBCASE( "escaped" ) + { + CHECK( spb::json::deserialize< std::string >( R"("\"")"sv ) == "\"" ); + CHECK( spb::json::deserialize< std::string >( R"("\\")"sv ) == "\\" ); + CHECK( spb::json::deserialize< std::string >( R"("\/")"sv ) == "/" ); + CHECK( spb::json::deserialize< std::string >( R"("\b")"sv ) == "\b" ); + CHECK( spb::json::deserialize< std::string >( R"("\f")"sv ) == "\f" ); + CHECK( spb::json::deserialize< std::string >( R"("\n")"sv ) == "\n" ); + CHECK( spb::json::deserialize< std::string >( R"("\r")"sv ) == "\r" ); + CHECK( spb::json::deserialize< std::string >( R"("\t")"sv ) == "\t" ); + CHECK( spb::json::deserialize< std::string >( R"("\"\b\f\n\r\t\"")"sv ) == + "\"\b\f\n\r\t\"" ); + CHECK( spb::json::deserialize< std::string >( R"("\nhell\to")"sv ) == + "\nhell\to" ); + } + } + } + SUBCASE( "variant" ) + { + SUBCASE( "int" ) + { + auto variant = spb::json::deserialize< Test::Variant >( R"({"var_int":42})"sv ); + CHECK( variant.oneof_field.index( ) == 0 ); + CHECK( std::get< 0 >( variant.oneof_field ) == 42 ); + } + SUBCASE( "string" ) + { + auto variant = + spb::json::deserialize< Test::Variant >( R"({"var_string":"hello"})"sv ); + CHECK( variant.oneof_field.index( ) == 1 ); + CHECK( std::get< 1 >( variant.oneof_field ) == std::string( "hello" ) ); + } + SUBCASE( "bytes" ) + { + auto variant = + spb::json::deserialize< Test::Variant >( R"({"var_bytes":"aGVsbG8="})"sv ); + CHECK( variant.oneof_field.index( ) == 2 ); + CHECK( std::get< 2 >( variant.oneof_field ) == + std::vector< std::byte >{ std::byte( 'h' ), std::byte( 'e' ), + std::byte( 'l' ), std::byte( 'l' ), + std::byte( 'o' ) } ); + } + SUBCASE( "name" ) + { + auto variant = + spb::json::deserialize< Test::Variant >( R"({"name":{"name":"John"}})"sv ); + CHECK( variant.oneof_field.index( ) == 3 ); + CHECK( std::get< 3 >( variant.oneof_field ) == Test::Name{ .name = "John" } ); + } + SUBCASE( "collision" ) + { + auto variant = + spb::json::deserialize< Test::Variant >( R"({"bkfvdzz":{"name":"John"}})"sv ); + CHECK( variant.oneof_field.index( ) == 0 ); + } + } + } + SUBCASE( "serialize" ) + { + SUBCASE( "string" ) + { + CHECK( spb::json::serialize< std::string, std::string >( "john" ) == R"("john")" ); + SUBCASE( "escaped" ) + { + CHECK( spb::json::serialize< std::string, std::string >( "\"" ) == R"("\"")" ); + CHECK( spb::json::serialize< std::string, std::string >( "\\" ) == R"("\\")" ); + CHECK( spb::json::serialize< std::string, std::string >( "/" ) == R"("/")" ); + CHECK( spb::json::serialize< std::string, std::string >( "\b" ) == R"("\b")" ); + CHECK( spb::json::serialize< std::string, std::string >( "\f" ) == R"("\f")" ); + CHECK( spb::json::serialize< std::string, std::string >( "\n" ) == R"("\n")" ); + CHECK( spb::json::serialize< std::string, std::string >( "\r" ) == R"("\r")" ); + CHECK( spb::json::serialize< std::string, std::string >( "\t" ) == R"("\t")" ); + CHECK( spb::json::serialize< std::string, std::string >( "\"\\/\b\f\n\r\t" ) == + R"("\"\\/\b\f\n\r\t")" ); + CHECK( spb::json::serialize< std::string, std::string >( "\"hello\t" ) == + R"("\"hello\t")" ); + } + SUBCASE( "optional" ) + { + CHECK( spb::json::serialize< std::string, std::optional< std::string > >( + std::nullopt ) == "" ); + CHECK( spb::json::serialize< std::string, std::optional< std::string > >( + "hello" ) == R"("hello")" ); + } + SUBCASE( "repeated" ) + { + CHECK( spb::json::serialize< std::string, std::vector< std::string > >( + { "hello", "world" } ) == R"(["hello","world"])" ); + CHECK( spb::json::serialize< std::string, std::vector< std::string > >( { } ) == + "" ); + } + } + SUBCASE( "bool" ) + { + CHECK( spb::json::serialize( true ) == "true" ); + CHECK( spb::json::serialize( false ) == "false" ); + SUBCASE( "optional" ) + { + CHECK( spb::json::serialize< std::string, std::optional< bool > >( std::nullopt ) == + "" ); + CHECK( spb::json::serialize< std::string, std::optional< bool > >( true ) == + "true" ); + CHECK( spb::json::serialize< std::string, std::optional< bool > >( false ) == + "false" ); + } + SUBCASE( "repeated" ) + { + CHECK( spb::json::serialize< std::string, std::vector< bool > >( + { true, false } ) == R"([true,false])" ); + CHECK( spb::json::serialize< std::string, std::vector< bool > >( { } ) == "" ); + } + } + SUBCASE( "int" ) + { + CHECK( spb::json::serialize( 42 ) == "42" ); + SUBCASE( "optional" ) + { + CHECK( spb::json::serialize< std::string, std::optional< int > >( std::nullopt ) == + "" ); + CHECK( spb::json::serialize< std::string, std::optional< int > >( 42 ) == "42" ); + } + SUBCASE( "repeated" ) + { + CHECK( spb::json::serialize< std::string, std::vector< int > >( { 42 } ) == + R"([42])" ); + CHECK( spb::json::serialize< std::string, std::vector< int > >( { 42, 3 } ) == + R"([42,3])" ); + CHECK( spb::json::serialize< std::string, std::vector< int > >( { } ) == "" ); + } + } + SUBCASE( "double" ) + { + CHECK( spb::json::serialize( 42.0 ) == "42" ); + SUBCASE( "optional" ) + { + CHECK( spb::json::serialize< std::string, std::optional< double > >( + std::nullopt ) == "" ); + CHECK( spb::json::serialize< std::string, std::optional< double > >( 42.3 ) == + "42.3" ); + } + SUBCASE( "repeated" ) + { + CHECK( spb::json::serialize< std::string, std::vector< double > >( { 42.3 } ) == + R"([42.3])" ); + CHECK( spb::json::serialize< std::string, std::vector< double > >( + { 42.3, 3.0 } ) == R"([42.3,3])" ); + CHECK( spb::json::serialize< std::string, std::vector< double > >( { } ) == "" ); + } + } + SUBCASE( "bytes" ) + { + CHECK( spb::json::serialize( to_bytes( "\x00\x01\x02"sv ) ) == R"("AAEC")" ); + + CHECK( spb::json::serialize< std::string, std::vector< std::byte > >( + to_bytes( "\x00\x01\x02\x03\x04"sv ) ) == R"("AAECAwQ=")" ); + CHECK( spb::json::serialize< std::string, std::vector< std::byte > >( + to_bytes( "hello"sv ) ) == R"("aGVsbG8=")" ); + CHECK( spb::json::serialize< std::string, std::vector< std::byte > >( { } ) == "" ); + + SUBCASE( "repeated" ) + { + CHECK( spb::json::serialize< std::string, std::vector< std::vector< std::byte > > >( + std::vector< std::vector< std::byte > >{ + to_bytes( "\x00\x01\x02\x03\x04"sv ) } ) == R"(["AAECAwQ="])" ); + CHECK( spb::json::serialize< std::string, std::vector< std::vector< std::byte > > >( + std::vector< std::vector< std::byte > >{ + to_bytes( "\x00\x01\x02\x03\x04"sv ), to_bytes( "hello"sv ) } ) == + R"(["AAECAwQ=","aGVsbG8="])" ); + CHECK( spb::json::serialize< std::string, std::vector< std::vector< std::byte > > >( + std::vector< std::vector< std::byte > >{ } ) == "" ); + } + SUBCASE( "optional" ) + { + CHECK( + spb::json::serialize< std::string, std::optional< std::vector< std::byte > > >( + std::nullopt ) == "" ); + CHECK( + spb::json::serialize< std::string, std::optional< std::vector< std::byte > > >( + std::vector< std::byte >{ to_bytes( "\x00\x01\x02\x03\x04"sv ) } ) == + R"("AAECAwQ=")" ); + CHECK( + spb::json::serialize< std::string, std::optional< std::vector< std::byte > > >( + std::vector< std::byte >{ to_bytes( "hello"sv ) } ) == R"("aGVsbG8=")" ); + CHECK( + spb::json::serialize< std::string, std::optional< std::vector< std::byte > > >( + std::vector< std::byte >{ } ) == "" ); + } + SUBCASE( "fixed" ) + { + CHECK( + spb::json::serialize< std::string, std::optional< std::vector< std::byte > > >( + std::nullopt ) == "" ); + CHECK( + spb::json::serialize< std::string, std::optional< std::vector< std::byte > > >( + std::vector< std::byte >{ to_bytes( "\x00\x01\x02\x03\x04"sv ) } ) == + R"("AAECAwQ=")" ); + CHECK( + spb::json::serialize< std::string, std::optional< std::vector< std::byte > > >( + std::vector< std::byte >{ to_bytes( "hello"sv ) } ) == R"("aGVsbG8=")" ); + CHECK( + spb::json::serialize< std::string, std::optional< std::vector< std::byte > > >( + std::vector< std::byte >{ } ) == "" ); + } + } + SUBCASE( "variant" ) + { + SUBCASE( "int" ) + { + CHECK( spb::json::serialize( Test::Variant{ .oneof_field = 42U } ) == + R"({"var_int":42})" ); + } + SUBCASE( "string" ) + { + CHECK( spb::json::serialize( Test::Variant{ .oneof_field = "hello" } ) == + R"({"var_string":"hello"})" ); + } + SUBCASE( "bytes" ) + { + CHECK( + spb::json::serialize( Test::Variant{ + .oneof_field = std::vector< std::byte >{ + std::byte( 'h' ), std::byte( 'e' ), std::byte( 'l' ), std::byte( 'l' ), + std::byte( 'o' ) } } ) == R"({"var_bytes":"aGVsbG8="})" ); + } + SUBCASE( "name" ) + { + CHECK( spb::json::serialize( + Test::Variant{ .oneof_field = Test::Name{ .name = "John" } } ) == + R"({"name":{"name":"John"}})" ); + } + } + SUBCASE( "map" ) + { + SUBCASE( "int32/int32" ) + { + CHECK( spb::json::serialize( std::map< int32_t, int32_t >{ { 1, 2 } } ) == + R"({"1":2})" ); + CHECK( spb::json::serialize( std::map< int32_t, int32_t >{ { 1, 2 }, { 2, 3 } } ) == + R"({"1":2,"2":3})" ); + CHECK( spb::json::serialize( std::map< int32_t, int32_t >{ } ) == "" ); + } + SUBCASE( "string/string" ) + { + CHECK( spb::json::serialize( std::map< std::string, std::string >{ + { "hello", "world" } } ) == R"({"hello":"world"})" ); + CHECK( spb::json::serialize( std::map< std::string, std::string >{ } ) == "" ); + } + SUBCASE( "int32/string" ) + { + CHECK( spb::json::serialize( std::map< int32_t, std::string >{ { 1, "hello" } } ) == + ( R"({"1":"hello"})" ) ); + CHECK( spb::json::serialize( std::map< int32_t, std::string >{ } ) == "" ); + } + SUBCASE( "string/int32" ) + { + CHECK( spb::json::serialize( std::map< std::string, int32_t >{ { "hello", 2 } } ) == + R"({"hello":2})" ); + CHECK( spb::json::serialize( std::map< std::string, int32_t >{ } ) == "" ); + } + SUBCASE( "string/name" ) + { + CHECK( spb::json::serialize( std::map< std::string, Test::Name >{ + { "hello", { .name = "john" } } } ) == R"({"hello":{"name":"john"}})" ); + } + } + SUBCASE( "enum" ) + { + CHECK( spb::json::serialize( PhoneBook::Person::PhoneType::HOME ) == "\"HOME\"" ); + } + SUBCASE( "person" ) + { + CHECK( spb::json::serialize( PhoneBook::Person{ + .name = "John Doe", + .id = 123, + .email = "QXUeh@example.com", + .phones = { + PhoneBook::Person::PhoneNumber{ + .number = "555-4321", + .type = PhoneBook::Person::PhoneType::HOME, + }, + }, + } ) == R"({"name":"John Doe","id":123,"email":"QXUeh@example.com","phones":[{"number":"555-4321","type":"HOME"}]})" ); + } + SUBCASE( "name" ) + { + CHECK( spb::json::serialize( Test::Name{ } ) == R"({})" ); + CHECK( spb::json::serialize_size( Test::Name{ } ) == 2 ); + } + } +} diff --git a/3rdparty/simple-protobuf/test/parser.cpp b/3rdparty/simple-protobuf/test/parser.cpp new file mode 100644 index 0000000..cbfc955 --- /dev/null +++ b/3rdparty/simple-protobuf/test/parser.cpp @@ -0,0 +1,558 @@ +#include "ast/ast.h" +#include "io/file.h" +#include +#include +#include +#include +#include +#include +#include + +#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN +#include "doctest.h" + +namespace +{ +using namespace std::literals; + +struct proto_file_test +{ + const std::string_view file_content; + size_t error_line = 0; +}; + +void test_proto_file( const proto_file_test & test, + const std::string & test_file = "tmp_test.proto", + std::span< const std::filesystem::path > import_paths = { } ) +{ + REQUIRE_NOTHROW( save_file( test_file, test.file_content ) ); + try + { + auto file = parse_proto_file( test_file, import_paths ); + auto stream = std::stringstream( ); + dump_cpp_header( file, stream ); + dump_cpp( file, "header.pb.h", stream ); + REQUIRE( test.error_line == 0 ); + } + catch( const std::exception & ex ) + { + auto message = std::string_view( ex.what( ) ); + REQUIRE( message.find( test_file + ":" ) != message.npos ); + message.remove_prefix( message.find( test_file + ":" ) + 1 + test_file.size( ) ); + auto line = std::strtoul( message.data( ), nullptr, 10 ); + REQUIRE( test.error_line == line ); + } + REQUIRE_NOTHROW( std::filesystem::remove( test_file ) ); +} + +void test_files( std::span< const proto_file_test > files, + const std::string & test_file = "tmp_test.proto", + std::span< const std::filesystem::path > import_paths = { } ) +{ + for( const auto & file : files ) + { + test_proto_file( file, test_file, import_paths ); + } +} +}// namespace + +TEST_CASE( "protoc-parser" ) +{ + SUBCASE( "api" ) + { + REQUIRE_THROWS( parse_proto_file( "not_found.proto", { { "../", ".", "/home/" } } ) ); + REQUIRE( cpp_file_name_from_proto( "messages.proto", ".cpp" ) == "messages.cpp" ); + } + SUBCASE( "import" ) + { + REQUIRE_NOTHROW( std::filesystem::create_directories( "level1/level2/level3" ) ); + + REQUIRE_NOTHROW( save_file( "empty.proto", "" ) ); + REQUIRE_NOTHROW( save_file( "level1/empty1.proto", "import \"level2/empty2.proto\";" ) ); + REQUIRE_NOTHROW( + save_file( "level1/level2/empty2.proto", "import \"level3/empty3.proto\";" ) ); + REQUIRE_NOTHROW( save_file( "level1/level2/level3/empty3.proto", "" ) ); + REQUIRE_NOTHROW( save_file( "level1.proto", "import \"level1/empty1.proto\";" ) ); + REQUIRE_NOTHROW( save_file( "level2.proto", "import \"level1/level2/empty2.proto\";" ) ); + REQUIRE_NOTHROW( + save_file( "level3.proto", "import \"level1/level2/level3/empty3.proto\";" ) ); + REQUIRE_NOTHROW( save_file( "level2-from-1.proto", "import \"level2/empty2.proto\";" ) ); + + constexpr proto_file_test tests[] = { + { R"(package UnitTest; + import "empty.proto"; + )", + 0 }, + { R"(package UnitTest; + import "level1.proto"; + import "level2.proto"; + import "level3.proto"; + )", + 0 }, + { R"(package UnitTest; + import "empty_not_found.proto"; + )", + 2 }, + { R"(package UnitTest; + import "level2-from-1.proto"; + )", + 2 }, + { R"(package UnitTest; + importX "message.proto"; + )", + 2 }, + }; + test_files( tests ); + constexpr proto_file_test tests2[] = { + { R"(package UnitTest; + import "level2-from-1.proto"; + )", + 0 }, + }; + + test_files( tests2, "tmp_test.proto", { { "level2", "level1" } } ); + test_files( tests2, "tmp_test.proto", { { std::filesystem::current_path( ) / "level1" } } ); + test_files( tests2, ( std::filesystem::current_path( ) / "tmp_test.proto" ).string( ), + { { "level2", "level1" } } ); + + REQUIRE_NOTHROW( std::filesystem::remove( "empty.proto" ) ); + REQUIRE_NOTHROW( std::filesystem::remove( "level1.proto" ) ); + REQUIRE_NOTHROW( std::filesystem::remove( "level2.proto" ) ); + REQUIRE_NOTHROW( std::filesystem::remove( "level3.proto" ) ); + REQUIRE_NOTHROW( std::filesystem::remove_all( "level1" ) ); + } + SUBCASE( "comment" ) + { + constexpr proto_file_test tests[] = { { "//"sv, 1 }, + { "/*"sv, 1 }, + { "/-"sv, 1 }, + { R"(package UnitTest; + // comment + )", + 0 }, + { "/**/"sv, 0 }, + { R"(package UnitTest; + /* comment + */; + )", + 0 }, + { R"(syntax = "proto3"; + message Message + { + required uint32 value = 1; // comment + })"sv, + 0 }, + { R"(syntax = "proto3"; + message Message + { + // comment + required uint32 value = 1; + })"sv, + 0 }, + { R"(syntax = "proto3"; + message Message + { + // comment + required uint32 value = 1; // comment + })"sv, + 0 } }; + test_files( tests ); + } + SUBCASE( "syntax" ) + { + constexpr proto_file_test tests[] = { { ""sv, 0 }, + { "X"sv, 1 }, + { R"(synta = "proto2";)"sv, 1 }, + { R"(syntax = "proto1;")"sv, 1 }, + { R"(syntax = "proto2")"sv, 1 }, + { R"(syntax = "proto2";)"sv, 0 }, + { R"(syntax = "proto3";;)"sv, 0 }, + { R"(syntax = "proto3")"sv, 1 }, + { R"(syntax = "proto4;")"sv, 1 }, + { R"(syntax = "proto2"; + message Message + { + required uint32 value = 1; + })"sv, + 0 }, + { R"(syntax = "proto3"; + message Message + { + required uint32 value = 1; + })"sv, + 0 } }; + test_files( tests ); + } + SUBCASE( "scalar" ) + { + constexpr proto_file_test tests[] = { + { R"(message Message{ + bool b = 1; + float f = 2; + double d = 3; + int32 i32 = 4; + sint32 si32 = 5; + uint32 u32 = 6; + int64 i64 = 7; + sint64 si64 = 8; + uint64 u64 = 9; + fixed32 f32 = 10; + sfixed32 sf32 = 11; + fixed64 f64 = 12; + sfixed64 sf64 = 13; + string s = 14; + bytes by = 15; + })"sv, + 0 }, + { R"(message Message{ + repeated bool b = 1; + repeated float f = 2; + repeated double d = 3; + repeated int32 i32 = 4; + repeated sint32 si32 = 5; + repeated uint32 u32 = 6; + repeated int64 i64 = 7; + repeated sint64 si64 = 8; + repeated uint64 u64 = 9; + repeated fixed32 f32 = 10; + repeated sfixed32 sf32 = 11; + repeated fixed64 f64 = 12; + repeated sfixed64 sf64 = 13; + repeated string s = 14; + repeated bytes by = 15; + })"sv, + 0 }, + { R"(message Message{ + repeated bool b = 1 [packed = true]; + repeated float f = 2 [packed = true]; + repeated double d = 3 [packed = true]; + repeated int32 i32 = 4 [packed = true]; + repeated sint32 si32 = 5 [packed = true]; + repeated uint32 u32 = 6 [packed = true]; + repeated int64 i64 = 7 [packed = true]; + repeated sint64 si64 = 8 [packed = true]; + repeated uint64 u64 = 9 [packed = true]; + repeated fixed32 f32 = 10 [packed = true]; + repeated sfixed32 sf32 = 11 [packed = true]; + repeated fixed64 f64 = 12 [packed = true]; + repeated sfixed64 sf64 = 13 [packed = true]; + repeated string s = 14 [packed = true]; + repeated bytes by = 15 [packed = true]; + })"sv, + 0 }, + { R"(message Message{ + bool b = 1 [default = true]; + float f = 2 [default = 0.4]; + double d = 3 [default = 0.]; + int32 i32 = 4 [default = -2]; + sint32 si32 = 5 [default = -3]; + uint32 u32 = 6 [default = 5]; + int64 i64 = 7 [default = 7]; + sint64 si64 = 8 [default = 8]; + uint64 u64 = 9 [default = 6]; + fixed32 f32 = 10 [default = 3]; + sfixed32 sf32 = 11 [default = 6]; + fixed64 f64 = 12 [default = 3]; + sfixed64 sf64 = 13 [default = 4]; + string s = 14 [default = "true"]; + bytes by = 15; + })"sv, + 0 }, + { R"(message Message{ + bool b = X; + })"sv, + 2 }, + }; + test_files( tests ); + } + SUBCASE( "option" ) + { + constexpr proto_file_test tests[] = { { R"(package UnitTest; + option cc_enable_arenas true; + )", + 2 }, + { R"(package UnitTest; + option cc_enable_arenas = true; + )", + 0 }, + { R"(syntax = "proto3"; + message Message + { + required uint32 value = 1 [default = 2, deprecated = true]; + })"sv, + 0 } }; + test_files( tests ); + } + SUBCASE( "extensions" ) + { + SUBCASE( "enum type" ) + { + constexpr proto_file_test tests[] = { { R"(syntax = "proto2"; + //[[enum.type = "float"]] + enum Enum { + value = 1; + })"sv, + 2 } + + }; + test_files( tests ); + } + SUBCASE( "bitfield" ) + { + constexpr proto_file_test tests[] = { + { R"(syntax = "proto2"; + message ReqUint8_1{ + //[[ field.type = "uint8:1" ]] + required uint32 value = 1; + //[[ field.type = "int8:1" ]] + required sint32 value2 = 2; + //[[ field.type = "int8:1" ]] + required sint64 value3 = 3; + //[[ field.type = "uint8:1" ]] + required fixed32 value4 = 4; + //[[ field.type = "int8:1" ]] + required sfixed32 value5 = 5; + //[[ field.type = "int8:1" ]] + required sfixed64 value6 = 6; + //[[ field.type = "uint8:1" ]] + required fixed64 value7 = 7; + })"sv, + 0 }, + { R"(syntax = "proto2"; + message OptUint8_1{ + //[[ field.type = "uint8:1" ]] + optional uint32 value = 1; + })", + 3 }, + { R"(syntax = "proto2"; + message OptUint8_1{ + //[[ field.type = "uint8:1" ]] + required bool value = 1; + })", + 3 }, + { R"(syntax = "proto2"; + message OptUint8_1{ + //[[ field.type = "uint8:1" ]] + required float value = 1; + })", + 3 }, + { R"(syntax = "proto2"; + message OptUint8_1{ + //[[ field.type = "uint8:1" ]] + required double value = 1; + })", + 3 }, + + }; + test_files( tests ); + } + SUBCASE( "small int" ) + { + constexpr proto_file_test tests[] = { + { R"(package UnitTest; + message A { + // [[ field.type = "uint8" ]] + optional uint32 u32 = 1; + })", + 0 }, + { R"(package UnitTest; + message A { + // [[ field.type = "uint16" ]] + optional uint32 u32 = 1; + })", + 0 }, + { R"(package UnitTest; + message A { + // [[ field.type = "uint32" ]] + optional uint32 u32 = 1; + })", + 0 }, + { R"(package UnitTest; + message A { + // [[ field.type = "uint64" ]] + optional uint32 u32 = 1; + })", + 3 }, + { R"(syntax = "proto2"; + message ReqUint8_1{ + //[[ field.type = "int8" ]] + required uint32 value = 1; + })"sv, + 3 }, + }; + test_files( tests ); + } + } + SUBCASE( "dependency" ) + { + constexpr proto_file_test tests[] = { + { R"(package UnitTest; + message A { + optional A a = 1; + })", + 0 }, + { R"(package UnitTest; + message A { + repeated A a = 1; + })", + 0 }, + { R"(package UnitTest; + message A { + required A a = 1; + })", + 3 }, + { R"(package UnitTest; + message A { + required B b = 1; + } + message B { + required A a = 1; + })", + 2 }, + { R"(package UnitTest; + message A { + message B { + required A a = 1; + } + required B b = 1; + })", + 4 }, + { R"(package UnitTest; + option cc_enable_arenas = true; + message A { + message B { + repeated A a = 1; + required bool b = 2 [ default = true ]; + map map_field = 3; + } + required B b = 1; + })", + 0 }, + { R"(package UnitTest.dependency; + message A { + message B { optional C c = 1; } + message C { optional int32 b = 1; } + message D { optional A a = 1; } + message E { + repeated F f = 1; + optional int32 b = 2 [default = -2]; + } + message F { + repeated E e = 1; + optional int32 c = 2; + } + message G { + optional H h = 1; + optional int32 b = 2; + } + message H { + optional G g = 1; + optional int32 c = 2 [default = 2]; + } + optional int32 a = 1; + })", + 0 }, + { R"(package UnitTest; + message A { + oneof oneof_field { + uint32 oneof_uint32 = 1; + string oneof_string = 2; + bytes oneof_bytes = 3; + A a = 4; + } + })", + 2 }, + }; + test_files( tests ); + } + SUBCASE( "oneof" ) + { + constexpr proto_file_test tests[] = { + { R"(package UnitTest; + message A { + oneof oneof_field { + uint32 oneof_uint32 = 1; + string oneof_string = 2; + bytes oneof_bytes = 3; + } + })", + 0 }, + + }; + test_files( tests ); + } + SUBCASE( "reserved" ) + { + constexpr proto_file_test tests[] = { + { R"(package UnitTest; + message A { + reserved 4; + reserved 5,6,7; + reserved 8 to 10; + reserved 10 to max; + reserved "BB"; + })", + 0 }, + }; + test_files( tests ); + } + SUBCASE( "enum" ) + { + constexpr proto_file_test tests[] = { + { R"(package UnitTest; + message A { + enum PhoneType { + MOBILE = 0; + HOME = 1; + WORK = 2; + } + })", + 0 }, + + }; + test_files( tests ); + } + SUBCASE( "map" ) + { + constexpr proto_file_test tests[] = { + { R"(package UnitTest; + message A { + map m_int32 = 1; + map m_int64 = 2; + map m_uint32 = 3; + map m_uint64 = 4; + map m_sint32 = 5; + map m_sint64 = 6; + map m_fixed32 = 7; + map m_fixed64 = 8; + map m_sfixed32 = 9; + map m_sfixed64 = 10; + map m_bool = 11; + map m_string = 12; + })", + 0 }, + { R"(package UnitTest; + message A { + map m_int32 = 1; + })", + 3 }, + { R"(package UnitTest; + message A { + map m_int32 = 1; + })", + 3 }, + { R"(package UnitTest; + message A { + map m_int32 = 1; + })", + 3 }, + { R"(package UnitTest; + message A { + map m_int32 = 1; + })", + 3 }, + + }; + test_files( tests ); + } +} diff --git a/3rdparty/simple-protobuf/test/pb.cpp b/3rdparty/simple-protobuf/test/pb.cpp new file mode 100644 index 0000000..58d3a45 --- /dev/null +++ b/3rdparty/simple-protobuf/test/pb.cpp @@ -0,0 +1,2109 @@ +#include "spb/concepts.h" +#include "spb/pb/wire-types.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN +#include "doctest.h" + +namespace +{ +template < typename T > +concept HasValueMember = requires( T t ) { + { t.value }; +}; + +}// namespace + +static_assert( spb::detail::proto_field_bytes_resizable< std::vector< std::byte > > ); +static_assert( spb::detail::proto_field_string_resizable< std::string > ); +static_assert( !spb::detail::proto_field_bytes_resizable< std::array< std::byte, 4 > > ); +static_assert( !spb::detail::proto_field_string_resizable< std::array< char, 4 > > ); + +namespace std +{ +auto operator==( const std::span< const std::byte > & lhs, + const std::span< const std::byte > & rhs ) noexcept -> bool +{ + return lhs.size( ) == rhs.size( ) && memcmp( lhs.data( ), rhs.data( ), lhs.size( ) ) == 0; +} +}// namespace std + +namespace Test +{ +auto operator==( const Test::Name & lhs, const Test::Name & rhs ) noexcept -> bool +{ + return lhs.name == rhs.name; +} +auto operator==( const Test::Variant & lhs, const Test::Variant & rhs ) noexcept -> bool +{ + return lhs.oneof_field == rhs.oneof_field; +} + +}// namespace Test + +namespace example +{ +auto operator==( const SomeMessage & lhs, const SomeMessage & rhs ) noexcept -> bool +{ + return lhs.values == rhs.values; +} +}// namespace example + +namespace PhoneBook +{ +auto operator==( const Person::PhoneNumber & lhs, const Person::PhoneNumber & rhs ) noexcept -> bool +{ + return lhs.number == rhs.number && lhs.type == rhs.type; +} + +auto operator==( const Person & lhs, const Person & rhs ) noexcept -> bool +{ + return lhs.name == rhs.name && lhs.id == rhs.id && lhs.email == rhs.email && + lhs.phones == rhs.phones; +} +}// namespace PhoneBook + +namespace Test::Scalar +{ +auto operator==( const Simple & lhs, const Simple & rhs ) noexcept -> bool +{ + return lhs.value == rhs.value; +} +}// namespace Test::Scalar + +namespace +{ +template < size_t N > +auto to_array( const char ( &string )[ N ] ) +{ + auto result = std::array< std::byte, N - 1 >( ); + memcpy( result.data( ), &string, result.size( ) ); + return result; +} + +template < size_t N > +auto to_string( const char ( &string )[ N ] ) +{ + auto result = std::array< char, N - 1 >( ); + memcpy( result.data( ), &string, result.size( ) ); + return result; +} + +auto to_bytes( std::string_view str ) -> std::vector< std::byte > +{ + auto span = std::span< std::byte >( ( std::byte * ) str.data( ), str.size( ) ); + return { span.data( ), span.data( ) + span.size( ) }; +} + +template < spb::pb::detail::scalar_encoder encoder, typename T > +auto pb_serialize_as( const T & value ) -> std::string +{ + auto size_stream = spb::pb::detail::ostream( nullptr ); + spb::pb::detail::serialize_as< encoder >( size_stream, 1, value ); + const auto size = size_stream.size( ); + auto result = std::string( size, '\0' ); + auto writer = [ ptr = result.data( ) ]( const void * data, size_t size ) mutable + { + memcpy( ptr, data, size ); + ptr += size; + }; + auto stream = spb::pb::detail::ostream( writer ); + spb::pb::detail::serialize_as< encoder >( stream, 1, value ); + return result; +} + +template < typename T > +void pb_test( const T & value, std::string_view protobuf ) +{ + { + auto serialized = spb::pb::serialize< std::vector< std::byte > >( value ); + CHECK( serialized.size( ) == protobuf.size( ) ); + auto proto = std::string_view( ( char * ) serialized.data( ), serialized.size( ) ); + CHECK( proto == protobuf ); + } + + { + auto serialized = spb::pb::serialize( value ); + CHECK( serialized == protobuf ); + auto size = spb::pb::serialize_size( value ); + CHECK( size == protobuf.size( ) ); + } + + { + auto deserialized = spb::pb::deserialize< T >( protobuf ); + if constexpr( HasValueMember< T > ) + { + using valueT = decltype( T::value ); + CHECK( valueT( deserialized.value ) == valueT( value.value ) ); + } + else + { + CHECK( deserialized == value ); + } + } + { + auto deserialized = T( ); + spb::pb::deserialize( deserialized, protobuf ); + if constexpr( HasValueMember< T > ) + { + using valueT = decltype( T::value ); + CHECK( valueT( deserialized.value ) == valueT( value.value ) ); + } + else + { + CHECK( deserialized == value ); + } + } +} + +template < typename T > +void json_test( const T & value, std::string_view json ) +{ + { + auto serialized = spb::json::serialize< std::vector< std::byte > >( value ); + CHECK( serialized.size( ) == json.size( ) ); + auto js = std::string_view( ( char * ) serialized.data( ), serialized.size( ) ); + CHECK( js == json ); + } + + { + auto serialized = spb::json::serialize( value ); + CHECK( serialized == json ); + auto size = spb::json::serialize_size( value ); + CHECK( size == json.size( ) ); + } + + { + auto deserialized = spb::json::deserialize< T >( json ); + if constexpr( HasValueMember< T > ) + { + using valueT = decltype( T::value ); + CHECK( valueT( deserialized.value ) == valueT( value.value ) ); + } + else + { + CHECK( deserialized == value ); + } + } + { + auto deserialized = T( ); + spb::json::deserialize( deserialized, json ); + if constexpr( HasValueMember< T > ) + { + using valueT = decltype( T::value ); + CHECK( valueT( deserialized.value ) == valueT( value.value ) ); + } + else + { + CHECK( deserialized == value ); + } + } +} + +template < typename T > +void pb_json_test( const T & value, std::string_view protobuf, std::string_view json ) +{ + SUBCASE( "pb" ) + { + pb_test( value, protobuf ); + } + SUBCASE( "json" ) + { + json_test( value, json ); + } +} + +using spb::pb::detail::scalar_encoder; +using spb::pb::detail::wire_type; + +}// namespace +using namespace std::literals; + +TEST_CASE( "protobuf" ) +{ + SUBCASE( "tag" ) + { + SUBCASE( "large field numbers" ) + { + pb_json_test( Test::Scalar::LargeFieldNumber{ "hello" }, "\xa2\x06\x05hello", + R"({"value":"hello"})" ); + pb_json_test( Test::Scalar::VeryLargeFieldNumber{ "hello" }, + "\xfa\xff\xff\xff\x0f\x05hello", R"({"value":"hello"})" ); + } + } + SUBCASE( "enum" ) + { + SUBCASE( "alias" ) + { + SUBCASE( "required" ) + { + pb_json_test( + Test::Scalar::ReqEnumAlias{ .value = + Test::Scalar::ReqEnumAlias::Enum::EAA_STARTED }, + "\x08\x01", R"({"value":"EAA_STARTED"})" ); + } + SUBCASE( "optional" ) + { + pb_json_test( Test::Scalar::OptEnumAlias{ }, "", "{}" ); + pb_json_test( + Test::Scalar::OptEnumAlias{ .value = + Test::Scalar::OptEnumAlias::Enum::EAA_STARTED }, + "\x08\x01", R"({"value":"EAA_STARTED"})" ); + } + SUBCASE( "repeated" ) + { + pb_json_test( Test::Scalar::RepEnumAlias{ }, "", "{}" ); + pb_json_test( + Test::Scalar::RepEnumAlias{ + .value = { Test::Scalar::RepEnumAlias::Enum::EAA_STARTED } }, + "\x08\x01", R"({"value":["EAA_STARTED"]})" ); + pb_json_test( + Test::Scalar::RepEnumAlias{ + .value = { Test::Scalar::RepEnumAlias::Enum::EAA_STARTED, + Test::Scalar::RepEnumAlias::Enum::EAA_RUNNING } }, + "\x08\x01\x08\x01", R"({"value":["EAA_STARTED","EAA_STARTED"]})" ); + } + } + } + SUBCASE( "string" ) + { + SUBCASE( "required" ) + { + pb_json_test( Test::Scalar::ReqString{ }, "", "{}" ); + pb_json_test( Test::Scalar::ReqString{ .value = "hello" }, "\x0a\x05hello", + R"({"value":"hello"})" ); + CHECK_THROWS( + ( void ) spb::pb::deserialize< Test::Scalar::ReqString >( "\x0a\x05hell"sv ) ); + SUBCASE( "escaped" ) + { + pb_json_test( Test::Scalar::ReqString{ .value = "\"\\/\b\f\n\r\t" }, + "\x0a\x08\"\\/\b\f\n\r\t", R"({"value":"\"\\/\b\f\n\r\t"})" ); + pb_json_test( Test::Scalar::ReqString{ .value = "\"hello\t" }, "\x0a\x07\"hello\t", + R"({"value":"\"hello\t"})" ); + } + SUBCASE( "utf8" ) + { + pb_json_test( + Test::Scalar::ReqString{ .value = + "h\x16\xc3\x8c\xE3\x9B\x8B\xF0\x90\x87\xB3o" }, + "\x0a\x0ch\x16\xc3\x8c\xE3\x9B\x8B\xF0\x90\x87\xB3o", + R"({"value":"h\u0016\u00cc\u36cb\ud800\uddf3o"})" ); + SUBCASE( "invalid" ) + { + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqString >( + "\x0a\x02h\x80"sv ) ); + CHECK_THROWS( + ( void ) spb::json::serialize< std::string, std::string >( "h\x80" ) ); + CHECK_THROWS( + ( void ) spb::json::deserialize< std::string >( R"("h\u02w1")"sv ) ); + CHECK_THROWS( + ( void ) spb::json::deserialize< std::string >( R"("h\u02")"sv ) ); + CHECK_THROWS( + ( void ) spb::json::deserialize< std::string >( R"("h\ud800\u")"sv ) ); + CHECK_THROWS( + ( void ) spb::json::deserialize< std::string >( R"("h\ud800\u1")"sv ) ); + CHECK_THROWS( + ( void ) spb::json::deserialize< std::string >( R"("h\ud800\udbff")"sv ) ); + CHECK_THROWS( + ( void ) spb::json::deserialize< std::string >( R"("h\ud800\ue000")"sv ) ); + } + } + } + SUBCASE( "optional" ) + { + pb_json_test( Test::Scalar::OptString{ }, "", "{}" ); + pb_json_test( Test::Scalar::OptString{ .value = "hello" }, "\x0a\x05hello", + R"({"value":"hello"})" ); + CHECK_THROWS( + ( void ) spb::pb::deserialize< Test::Scalar::OptString >( "\x08\x05hello"sv ) ); + } + SUBCASE( "repeated" ) + { + pb_json_test( Test::Scalar::RepString{ }, "", "{}" ); + pb_json_test( Test::Scalar::RepString{ .value = { "hello" } }, "\x0a\x05hello", + R"({"value":["hello"]})" ); + pb_json_test( Test::Scalar::RepString{ .value = { "hello", "world" } }, + "\x0a\x05hello\x0a\x05world", R"({"value":["hello","world"]})" ); + } + SUBCASE( "fixed" ) + { + SUBCASE( "required" ) + { + // pb_json_test( Test::Scalar::ReqString{ }, "\x0a\x06\x00\x00\x00\x00\x00\x00"sv, + // R"({"value":"hello"})" ); + pb_json_test( Test::Scalar::ReqStringFixed{ .value = to_string( "hello1" ) }, + "\x0a\x06hello1", R"({"value":"hello1"})" ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqStringFixed >( + "\x0a\x05hello"sv ) ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqStringFixed >( + "\x0a\x07hello12"sv ) ); + SUBCASE( "escaped" ) + { + pb_json_test( + Test::Scalar::ReqStringFixed{ .value = to_string( "\"\\/\n\r\t" ) }, + "\x0a\x06\"\\/\n\r\t", R"({"value":"\"\\/\n\r\t"})" ); + pb_json_test( Test::Scalar::ReqStringFixed{ .value = to_string( "hello\t" ) }, + "\x0a\x06hello\t", R"({"value":"hello\t"})" ); + } + } + SUBCASE( "optional" ) + { + pb_json_test( Test::Scalar::OptStringFixed{ }, "", "{}" ); + pb_json_test( Test::Scalar::OptStringFixed{ .value = to_string( "hello1" ) }, + "\x0a\x06hello1", R"({"value":"hello1"})" ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::OptStringFixed >( + "\x0a\x05hello"sv ) ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::OptStringFixed >( + "\x0a\x07hello12"sv ) ); + CHECK_THROWS( ( void ) spb::json::deserialize< Test::Scalar::OptStringFixed >( + R"({"value":"hello12"})"sv ) ); + } + SUBCASE( "repeated" ) + { + pb_json_test( Test::Scalar::RepStringFixed{ }, "", "{}" ); + pb_json_test( Test::Scalar::RepStringFixed{ .value = { to_string( "hello1" ) } }, + "\x0a\x06hello1", R"({"value":["hello1"]})" ); + pb_json_test( Test::Scalar::RepStringFixed{ .value = { to_string( "hello1" ), + to_string( "world1" ) } }, + "\x0a\x06hello1\x0a\x06world1", R"({"value":["hello1","world1"]})" ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::RepStringFixed >( + "\x0a\x06hello1\x0a\x05world"sv ) ); + CHECK_THROWS( ( void ) spb::json::deserialize< Test::Scalar::RepStringFixed >( + R"({"value":["hello1","world"]})"sv ) ); + } + } + } + SUBCASE( "bool" ) + { + SUBCASE( "required" ) + { + pb_json_test( Test::Scalar::ReqBool{ }, "\x08\x00"sv, R"({"value":false})" ); + pb_json_test( Test::Scalar::ReqBool{ .value = true }, "\x08\x01", R"({"value":true})" ); + pb_json_test( Test::Scalar::ReqBool{ .value = false }, "\x08\x00"sv, + R"({"value":false})" ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqBool >( "\x08\x02"sv ) ); + CHECK_THROWS( + ( void ) spb::pb::deserialize< Test::Scalar::ReqBool >( "\x08\xff\x01"sv ) ); + } + SUBCASE( "optional" ) + { + pb_json_test( Test::Scalar::OptBool{ }, "", "{}" ); + pb_json_test( Test::Scalar::OptBool{ .value = true }, "\x08\x01", R"({"value":true})" ); + pb_json_test( Test::Scalar::OptBool{ .value = false }, "\x08\x00"sv, + R"({"value":false})" ); + } + SUBCASE( "repeated" ) + { + pb_json_test( Test::Scalar::RepBool{ }, "", "{}" ); + pb_json_test( Test::Scalar::RepBool{ .value = { true } }, "\x08\x01", + R"({"value":[true]})" ); + pb_json_test( Test::Scalar::RepBool{ .value = { true, false } }, "\x08\x01\x08\x00"sv, + R"({"value":[true,false]})" ); + pb_json_test( Test::Scalar::RepBool{ .value = {} }, "", "{}" ); + CHECK_THROWS( + ( void ) spb::pb::deserialize< Test::Scalar::RepBool >( "\x08\x01\x08"sv ) ); + + SUBCASE( "packed" ) + { + pb_json_test( Test::Scalar::RepPackBool{ }, "", "{}" ); + pb_json_test( Test::Scalar::RepPackBool{ .value = { true } }, "\x0a\x01\x01", + R"({"value":[true]})" ); + pb_json_test( Test::Scalar::RepPackBool{ .value = { true, false } }, + "\x0a\x02\x01\x00"sv, R"({"value":[true,false]})" ); + pb_json_test( Test::Scalar::RepPackBool{ .value = {} }, "", "{}" ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::RepPackBool >( + "\x0a\x02\x08"sv ) ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::RepPackBool >( + "\x0a\x01\x08"sv ) ); + } + } + } + SUBCASE( "enum" ) + { + SUBCASE( "required" ) + { + pb_json_test( Test::Scalar::ReqEnum{ .value = Test::Scalar::ReqEnum::Enum::Enum_value }, + "\x08\x01", R"({"value":"Enum_value"})" ); + } + SUBCASE( "optional" ) + { + pb_json_test( Test::Scalar::OptEnum{ }, "", "{}" ); + pb_json_test( Test::Scalar::OptEnum{ .value = Test::Scalar::OptEnum::Enum::Enum_value }, + "\x08\x01", R"({"value":"Enum_value"})" ); + } + SUBCASE( "repeated" ) + { + pb_json_test( Test::Scalar::RepEnum{ }, "", "{}" ); + pb_json_test( + Test::Scalar::RepEnum{ .value = { Test::Scalar::RepEnum::Enum::Enum_value } }, + "\x08\x01", R"({"value":["Enum_value"]})" ); + pb_json_test( + Test::Scalar::RepEnum{ .value = { Test::Scalar::RepEnum::Enum::Enum_value, + Test::Scalar::RepEnum::Enum::Enum_value2, + Test::Scalar::RepEnum::Enum::Enum_value3 } }, + "\x08\x01\x08\x02\x08\x03", + R"({"value":["Enum_value","Enum_value2","Enum_value3"]})" ); + pb_json_test( Test::Scalar::RepEnum{ .value = {} }, "", "{}" ); + + SUBCASE( "packed" ) + { + pb_json_test( Test::Scalar::RepPackEnum{ }, "", "{}" ); + pb_json_test( + Test::Scalar::RepPackEnum{ + .value = { Test::Scalar::RepPackEnum::Enum::Enum_value } }, + "\x0a\x01\x01", R"({"value":["Enum_value"]})" ); + pb_json_test( + Test::Scalar::RepPackEnum{ + .value = { Test::Scalar::RepPackEnum::Enum::Enum_value, + Test::Scalar::RepPackEnum::Enum::Enum_value3 } }, + "\x0a\x02\x01\x03", R"({"value":["Enum_value","Enum_value3"]})" ); + pb_json_test( Test::Scalar::RepPackEnum{ .value = {} }, "", "{}" ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::RepPackEnum >( + "\x0a\x02\x42"sv ) ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::RepPackEnum >( + "\x0a\x02\x42\xff"sv ) ); + + pb_json_test( + example::SomeMessage{ .values = { example::SomeEnum::SOME_ENUM_ONE, + example::SomeEnum::SOME_ENUM_TWO, + example::SomeEnum::SOME_ENUM_THREE, + example::SomeEnum::SOME_ENUM_FOUR, + example::SomeEnum::SOME_ENUM_FIVE } }, + "\x0A\x05\x01\x02\x03\x04\x05"sv, + R"({"values":["SOME_ENUM_ONE","SOME_ENUM_TWO","SOME_ENUM_THREE","SOME_ENUM_FOUR","SOME_ENUM_FIVE"]})" ); + } + } + } + SUBCASE( "int" ) + { + SUBCASE( "varint32" ) + { + SUBCASE( "bitfield" ) + { + pb_json_test( Test::Scalar::ReqInt8_1{ .value = -1 }, + "\x08\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01"sv, R"({"value":-1})" ); + pb_json_test( Test::Scalar::ReqInt8_1{ .value = 0 }, "\x08\x00"sv, + R"({"value":0})" ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqInt8_1 >( + "\x08\xfe\xff\xff\xff\xff\xff\xff\xff\xff\x01"sv ) ); + CHECK_THROWS( + ( void ) spb::pb::deserialize< Test::Scalar::ReqInt8_1 >( "\x08\x01"sv ) ); + + pb_json_test( Test::Scalar::ReqInt32_1{ .value = -1 }, + "\x08\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01"sv, R"({"value":-1})" ); + pb_json_test( Test::Scalar::ReqInt32_1{ .value = 0 }, "\x08\x00"sv, + R"({"value":0})" ); + + pb_json_test( Test::Scalar::ReqInt8_2{ .value = -2 }, + "\x08\xfe\xff\xff\xff\xff\xff\xff\xff\xff\x01"sv, R"({"value":-2})" ); + pb_json_test( Test::Scalar::ReqInt8_2{ .value = -1 }, + "\x08\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01"sv, R"({"value":-1})" ); + pb_json_test( Test::Scalar::ReqInt8_2{ .value = 0 }, "\x08\x00"sv, + R"({"value":0})" ); + pb_json_test( Test::Scalar::ReqInt8_2{ .value = 1 }, "\x08\x01"sv, + R"({"value":1})" ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqInt8_1 >( + "\x08\xfd\xff\xff\xff\xff\xff\xff\xff\xff\x01"sv ) ); + CHECK_THROWS( + ( void ) spb::pb::deserialize< Test::Scalar::ReqInt8_1 >( "\x08\x02"sv ) ); + } + SUBCASE( "required" ) + { + pb_json_test( Test::Scalar::ReqInt32{ .value = 0x42 }, "\x08\x42", + R"({"value":66})" ); + pb_json_test( Test::Scalar::ReqInt32{ .value = 0xff }, "\x08\xff\x01", + R"({"value":255})" ); + pb_json_test( Test::Scalar::ReqInt32{ .value = -2 }, + "\x08\xfe\xff\xff\xff\xff\xff\xff\xff\xff\x01"sv, R"({"value":-2})" ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqInt32 >( "\x08"sv ) ); + CHECK_THROWS( + ( void ) spb::pb::deserialize< Test::Scalar::ReqInt32 >( "\x08\xff"sv ) ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqInt32 >( + "\x08\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01"sv ) ); + } + SUBCASE( "optional" ) + { + pb_json_test( Test::Scalar::OptInt32{ }, "", "{}" ); + pb_json_test( Test::Scalar::OptInt32{ .value = 0x42 }, "\x08\x42", + R"({"value":66})" ); + pb_json_test( Test::Scalar::OptInt32{ .value = 0xff }, "\x08\xff\x01", + R"({"value":255})" ); + pb_json_test( Test::Scalar::OptInt32{ .value = -2 }, + "\x08\xfe\xff\xff\xff\xff\xff\xff\xff\xff\x01"sv, R"({"value":-2})" ); + } + SUBCASE( "repeated" ) + { + pb_json_test( Test::Scalar::RepInt32{ }, "", "{}" ); + pb_json_test( Test::Scalar::RepInt32{ .value = { 0x42 } }, "\x08\x42", + R"({"value":[66]})" ); + pb_json_test( Test::Scalar::RepInt32{ .value = { 0x42, 0x3 } }, "\x08\x42\x08\x03", + R"({"value":[66,3]})" ); + pb_json_test( Test::Scalar::RepInt32{ .value = {} }, "", "{}" ); + + SUBCASE( "packed" ) + { + pb_json_test( Test::Scalar::RepPackInt32{ }, "", "{}" ); + pb_json_test( Test::Scalar::RepPackInt32{ .value = { 0x42 } }, "\x0a\x01\x42", + R"({"value":[66]})" ); + pb_json_test( Test::Scalar::RepPackInt32{ .value = { 0x42, 0x3 } }, + "\x0a\x02\x42\x03", R"({"value":[66,3]})" ); + pb_json_test( Test::Scalar::RepPackInt32{ .value = {} }, "", "{}" ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::RepPackInt32 >( + "\x0a\x02\x42"sv ) ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::RepPackInt32 >( + "\x0a\x02\x42\xff"sv ) ); + } + } + } + SUBCASE( "varuint32" ) + { + SUBCASE( "bitfield" ) + { + pb_json_test( Test::Scalar::ReqUint8_1{ .value = 0 }, "\x08\x00"sv, + R"({"value":0})" ); + pb_json_test( Test::Scalar::ReqUint8_1{ .value = 1 }, "\x08\x01"sv, + R"({"value":1})" ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqUint8_1 >( + "\x08\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01"sv ) ); + CHECK_THROWS( + ( void ) spb::pb::deserialize< Test::Scalar::ReqUint8_1 >( "\x08\x02"sv ) ); + + pb_json_test( Test::Scalar::ReqUint32_1{ .value = 0 }, "\x08\x00"sv, + R"({"value":0})" ); + pb_json_test( Test::Scalar::ReqUint32_1{ .value = 1 }, "\x08\x01"sv, + R"({"value":1})" ); + + pb_json_test( Test::Scalar::ReqUint8_2{ .value = 0 }, "\x08\x00"sv, + R"({"value":0})" ); + pb_json_test( Test::Scalar::ReqUint8_2{ .value = 1 }, "\x08\x01"sv, + R"({"value":1})" ); + pb_json_test( Test::Scalar::ReqUint8_2{ .value = 2 }, "\x08\x02"sv, + R"({"value":2})" ); + pb_json_test( Test::Scalar::ReqUint8_2{ .value = 3 }, "\x08\x03"sv, + R"({"value":3})" ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqUint8_2 >( + "\x08\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01"sv ) ); + CHECK_THROWS( + ( void ) spb::pb::deserialize< Test::Scalar::ReqUint8_1 >( "\x08\x04"sv ) ); + } + + SUBCASE( "required" ) + { + pb_json_test( Test::Scalar::ReqUint32{ .value = 0x42 }, "\x08\x42", + R"({"value":66})" ); + pb_json_test( Test::Scalar::ReqUint32{ .value = 0xff }, "\x08\xff\x01", + R"({"value":255})" ); + CHECK_THROWS( + ( void ) spb::pb::deserialize< Test::Scalar::ReqUint32 >( "\x08"sv ) ); + CHECK_THROWS( + ( void ) spb::pb::deserialize< Test::Scalar::ReqUint32 >( "\x08\xff"sv ) ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqUint32 >( + "\x08\xff\xff\xff\xff\xff\x0f"sv ) ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqUint32 >( + "\x08\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01"sv ) ); + } + SUBCASE( "optional" ) + { + pb_json_test( Test::Scalar::OptUint32{ }, "", "{}" ); + pb_json_test( Test::Scalar::OptUint32{ .value = 0x42 }, "\x08\x42", + R"({"value":66})" ); + pb_json_test( Test::Scalar::OptUint32{ .value = 0xff }, "\x08\xff\x01", + R"({"value":255})" ); + } + SUBCASE( "repeated" ) + { + pb_json_test( Test::Scalar::RepUint32{ }, "", "{}" ); + pb_json_test( Test::Scalar::RepUint32{ .value = { 0x42 } }, "\x08\x42", + R"({"value":[66]})" ); + pb_json_test( Test::Scalar::RepUint32{ .value = { 0x42, 0x3 } }, "\x08\x42\x08\x03", + R"({"value":[66,3]})" ); + pb_json_test( Test::Scalar::RepUint32{ .value = {} }, "", "{}" ); + + SUBCASE( "packed" ) + { + pb_json_test( Test::Scalar::RepPackUint32{ }, "", "{}" ); + pb_json_test( Test::Scalar::RepPackUint32{ .value = { 0x42 } }, "\x0a\x01\x42", + R"({"value":[66]})" ); + pb_json_test( Test::Scalar::RepPackUint32{ .value = { 0x42, 0x3 } }, + "\x0a\x02\x42\x03", R"({"value":[66,3]})" ); + pb_json_test( Test::Scalar::RepPackUint32{ .value = {} }, "", "{}" ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::RepPackUint32 >( + "\x0a\x02\x42"sv ) ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::RepPackUint32 >( + "\x0a\x02\x42\xff"sv ) ); + } + } + } + SUBCASE( "varint64" ) + { + SUBCASE( "required" ) + { + pb_json_test( Test::Scalar::ReqInt64{ .value = 0x42 }, "\x08\x42", + R"({"value":66})" ); + pb_json_test( Test::Scalar::ReqInt64{ .value = 0xff }, "\x08\xff\x01", + R"({"value":255})" ); + pb_json_test( Test::Scalar::ReqInt64{ .value = -2 }, + "\x08\xfe\xff\xff\xff\xff\xff\xff\xff\xff\x01", R"({"value":-2})" ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqInt32 >( "\x08"sv ) ); + CHECK_THROWS( + ( void ) spb::pb::deserialize< Test::Scalar::ReqInt32 >( "\x08\xff"sv ) ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqInt32 >( + "\x08\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01"sv ) ); + } + SUBCASE( "optional" ) + { + pb_json_test( Test::Scalar::OptInt64{ }, "", "{}" ); + pb_json_test( Test::Scalar::OptInt64{ .value = 0x42 }, "\x08\x42", + R"({"value":66})" ); + pb_json_test( Test::Scalar::OptInt64{ .value = 0xff }, "\x08\xff\x01", + R"({"value":255})" ); + pb_json_test( Test::Scalar::OptInt64{ .value = -2 }, + "\x08\xfe\xff\xff\xff\xff\xff\xff\xff\xff\x01", R"({"value":-2})" ); + } + SUBCASE( "repeated" ) + { + pb_json_test( Test::Scalar::RepInt64{ }, "", "{}" ); + pb_json_test( Test::Scalar::RepInt64{ .value = { 0x42 } }, "\x08\x42", + R"({"value":[66]})" ); + pb_json_test( Test::Scalar::RepInt64{ .value = { 0x42, 0x3 } }, "\x08\x42\x08\x03", + R"({"value":[66,3]})" ); + pb_json_test( Test::Scalar::RepInt64{ .value = {} }, "", "{}" ); + + SUBCASE( "packed" ) + { + pb_json_test( Test::Scalar::RepPackInt64{ }, "", "{}" ); + pb_json_test( Test::Scalar::RepPackInt64{ .value = { 0x42 } }, "\x0a\x01\x42", + R"({"value":[66]})" ); + pb_json_test( Test::Scalar::RepPackInt64{ .value = { 0x42, 0x3 } }, + "\x0a\x02\x42\x03", R"({"value":[66,3]})" ); + pb_json_test( Test::Scalar::RepPackInt64{ .value = {} }, "", "{}" ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::RepPackInt64 >( + "\x0a\x02\x42"sv ) ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::RepPackInt64 >( + "\x0a\x02\x42\xff"sv ) ); + } + } + } + SUBCASE( "svarint32" ) + { + SUBCASE( "bitfield" ) + { + pb_json_test( Test::Scalar::ReqSint8_1{ .value = -1 }, "\x08\x01"sv, + R"({"value":-1})" ); + pb_json_test( Test::Scalar::ReqSint8_1{ .value = 0 }, "\x08\x00"sv, + R"({"value":0})" ); + CHECK_THROWS( + ( void ) spb::pb::deserialize< Test::Scalar::ReqSint8_1 >( "\x08\x03"sv ) ); + CHECK_THROWS( + ( void ) spb::pb::deserialize< Test::Scalar::ReqSint8_1 >( "\x08\x02"sv ) ); + + pb_json_test( Test::Scalar::ReqSint32_1{ .value = -1 }, "\x08\x01"sv, + R"({"value":-1})" ); + pb_json_test( Test::Scalar::ReqSint32_1{ .value = 0 }, "\x08\x00"sv, + R"({"value":0})" ); + + pb_json_test( Test::Scalar::ReqSint8_2{ .value = -2 }, "\x08\x03"sv, + R"({"value":-2})" ); + pb_json_test( Test::Scalar::ReqSint8_2{ .value = -1 }, "\x08\x01"sv, + R"({"value":-1})" ); + pb_json_test( Test::Scalar::ReqSint8_2{ .value = 0 }, "\x08\x00"sv, + R"({"value":0})" ); + pb_json_test( Test::Scalar::ReqSint8_2{ .value = 1 }, "\x08\x02"sv, + R"({"value":1})" ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqInt8_1 >( + "\x08\xfd\xff\xff\xff\xff\xff\xff\xff\xff\x01"sv ) ); + CHECK_THROWS( + ( void ) spb::pb::deserialize< Test::Scalar::ReqInt8_1 >( "\x08\x04"sv ) ); + } + SUBCASE( "required" ) + { + pb_json_test( Test::Scalar::ReqSint32{ .value = 0x42 }, "\x08\x84\x01"sv, + R"({"value":66})" ); + pb_json_test( Test::Scalar::ReqSint32{ .value = 0xff }, "\x08\xfe\x03"sv, + R"({"value":255})" ); + pb_json_test( Test::Scalar::ReqSint32{ .value = -2 }, "\x08\x03"sv, + R"({"value":-2})" ); + CHECK_THROWS( + ( void ) spb::pb::deserialize< Test::Scalar::ReqSint32 >( "\x08"sv ) ); + CHECK_THROWS( + ( void ) spb::pb::deserialize< Test::Scalar::ReqSint32 >( "\x08\xff"sv ) ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqSint32 >( + "\x08\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01"sv ) ); + } + SUBCASE( "optional" ) + { + pb_json_test( Test::Scalar::OptSint32{ }, "", "{}" ); + pb_json_test( Test::Scalar::OptSint32{ .value = 0x42 }, "\x08\x84\x01"sv, + R"({"value":66})" ); + pb_json_test( Test::Scalar::OptSint32{ .value = 0xff }, "\x08\xfe\x03"sv, + R"({"value":255})" ); + pb_json_test( Test::Scalar::OptSint32{ .value = -2 }, "\x08\x03"sv, + R"({"value":-2})" ); + } + SUBCASE( "repeated" ) + { + pb_json_test( Test::Scalar::RepSint32{ }, "", "{}" ); + pb_json_test( Test::Scalar::RepSint32{ .value = { 0x42 } }, "\x08\x84\x01"sv, + R"({"value":[66]})" ); + pb_json_test( Test::Scalar::RepSint32{ .value = { 0x42, -2 } }, + "\x08\x84\x01\x08\x03"sv, R"({"value":[66,-2]})" ); + pb_json_test( Test::Scalar::RepSint32{ .value = {} }, "", "{}" ); + + SUBCASE( "packed" ) + { + pb_json_test( Test::Scalar::RepPackSint32{ .value = { 0x42 } }, + "\x0a\x02\x84\x01"sv, R"({"value":[66]})" ); + pb_json_test( Test::Scalar::RepPackSint32{ .value = { 0x42, -2 } }, + "\x0a\x03\x84\x01\x03"sv, R"({"value":[66,-2]})" ); + pb_json_test( Test::Scalar::RepPackSint32{ .value = {} }, "", "{}" ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::RepPackSint32 >( + "\x0a\x02\x42"sv ) ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::RepPackSint32 >( + "\x0a\x02\x42\xff"sv ) ); + } + } + } + SUBCASE( "svarint64" ) + { + SUBCASE( "required" ) + { + pb_json_test( Test::Scalar::ReqSint64{ .value = 0x42 }, "\x08\x84\x01"sv, + R"({"value":66})" ); + pb_json_test( Test::Scalar::ReqSint64{ .value = 0xff }, "\x08\xfe\x03"sv, + R"({"value":255})" ); + pb_json_test( Test::Scalar::ReqSint64{ .value = -2 }, "\x08\x03"sv, + R"({"value":-2})" ); + CHECK_THROWS( + ( void ) spb::pb::deserialize< Test::Scalar::ReqSint64 >( "\x08"sv ) ); + CHECK_THROWS( + ( void ) spb::pb::deserialize< Test::Scalar::ReqSint64 >( "\x08\xff"sv ) ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqSint64 >( + "\x08\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01"sv ) ); + } + SUBCASE( "optional" ) + { + pb_json_test( Test::Scalar::OptSint64{ }, "", "{}" ); + pb_json_test( Test::Scalar::OptSint64{ .value = 0x42 }, "\x08\x84\x01"sv, + R"({"value":66})" ); + pb_json_test( Test::Scalar::OptSint64{ .value = 0xff }, "\x08\xfe\x03"sv, + R"({"value":255})" ); + pb_json_test( Test::Scalar::OptSint64{ .value = -2 }, "\x08\x03"sv, + R"({"value":-2})" ); + } + SUBCASE( "repeated" ) + { + pb_json_test( Test::Scalar::RepSint64{ }, "", "{}" ); + pb_json_test( Test::Scalar::RepSint64{ .value = { 0x42 } }, "\x08\x84\x01"sv, + R"({"value":[66]})" ); + pb_json_test( Test::Scalar::RepSint64{ .value = { 0x42, -2 } }, + "\x08\x84\x01\x08\x03"sv, R"({"value":[66,-2]})" ); + pb_json_test( Test::Scalar::RepSint64{ .value = {} }, "", "{}" ); + + SUBCASE( "packed" ) + { + pb_json_test( Test::Scalar::RepPackSint64{ .value = { 0x42 } }, + "\x0a\x02\x84\x01"sv, R"({"value":[66]})" ); + pb_json_test( Test::Scalar::RepPackSint64{ .value = { 0x42, -2 } }, + "\x0a\x03\x84\x01\x03"sv, R"({"value":[66,-2]})" ); + pb_json_test( Test::Scalar::RepPackSint64{ .value = {} }, "", "{}" ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::RepPackSint64 >( + "\x0a\x02\x84"sv ) ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::RepPackSint64 >( + "\x0a\x03\x84\x01"sv ) ); + } + } + } + SUBCASE( "fixed32" ) + { + SUBCASE( "bitfield" ) + { + pb_json_test( Test::Scalar::ReqFixed32_32_1{ .value = 0 }, "\x0d\x00\x00\x00\x00"sv, + R"({"value":0})" ); + pb_json_test( Test::Scalar::ReqFixed32_32_1{ .value = 1 }, "\x0d\x01\x00\x00\x00"sv, + R"({"value":1})" ); + + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqFixed32_32_1 >( + "\x0d\xff\xff\xff\xff"sv ) ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqFixed32_32_1 >( + "\x0d\x02\x00\x00\x00"sv ) ); + + pb_json_test( Test::Scalar::ReqFixed32_32_2{ .value = 0 }, "\x0d\x00\x00\x00\x00"sv, + R"({"value":0})" ); + pb_json_test( Test::Scalar::ReqFixed32_32_2{ .value = 1 }, "\x0d\x01\x00\x00\x00"sv, + R"({"value":1})" ); + pb_json_test( Test::Scalar::ReqFixed32_32_2{ .value = 2 }, "\x0d\x02\x00\x00\x00"sv, + R"({"value":2})" ); + pb_json_test( Test::Scalar::ReqFixed32_32_2{ .value = 3 }, "\x0d\x03\x00\x00\x00"sv, + R"({"value":3})" ); + + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqFixed32_32_2 >( + "\x0d\xff\xff\xff\xff"sv ) ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqFixed32_32_2 >( + "\x0d\x04\x00\x00\x00"sv ) ); + } + SUBCASE( "required" ) + { + pb_json_test( Test::Scalar::ReqFixed32{ .value = 0x42 }, "\x0d\x42\x00\x00\x00"sv, + R"({"value":66})" ); + pb_json_test( Test::Scalar::ReqFixed32{ .value = 0xff }, "\x0d\xff\x00\x00\x00"sv, + R"({"value":255})" ); + pb_json_test( Test::Scalar::ReqFixed32{ .value = uint32_t( -2 ) }, + "\x0d\xfe\xff\xff\xff"sv, R"({"value":4294967294})" ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqFixed32 >( + "\x0d\x42\x00\x00"sv ) ); + } + SUBCASE( "optional" ) + { + pb_json_test( Test::Scalar::OptFixed32{ .value = 0x42 }, "\x0d\x42\x00\x00\x00"sv, + R"({"value":66})" ); + pb_json_test( Test::Scalar::OptFixed32{ .value = 0xff }, "\x0d\xff\x00\x00\x00"sv, + R"({"value":255})" ); + pb_json_test( Test::Scalar::OptFixed32{ .value = -2 }, "\x0d\xfe\xff\xff\xff"sv, + R"({"value":4294967294})" ); + } + SUBCASE( "repeated" ) + { + pb_json_test( Test::Scalar::RepFixed32{ .value = { 0x42 } }, + "\x0d\x42\x00\x00\x00"sv, R"({"value":[66]})" ); + pb_json_test( Test::Scalar::RepFixed32{ .value = { 0x42, 0x3 } }, + "\x0d\x42\x00\x00\x00\x0d\x03\x00\x00\x00"sv, R"({"value":[66,3]})" ); + pb_json_test( Test::Scalar::RepFixed32{ .value = {} }, "", "{}" ); + + SUBCASE( "packed" ) + { + pb_json_test( Test::Scalar::RepPackFixed32{ .value = { 0x42 } }, + "\x0a\x04\x42\x00\x00\x00"sv, R"({"value":[66]})" ); + pb_json_test( Test::Scalar::RepPackFixed32{ .value = { 0x42, 0x3 } }, + "\x0a\x08\x42\x00\x00\x00\x03\x00\x00\x00"sv, + R"({"value":[66,3]})" ); + pb_json_test( Test::Scalar::RepPackFixed32{ .value = {} }, "", "{}" ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::RepPackFixed32 >( + "\x0a\x08\x42\x00\x00\x00\x03\x00\x00"sv ) ); + } + } + SUBCASE( "uint8" ) + { + SUBCASE( "bitfield" ) + { + pb_json_test( Test::Scalar::ReqFixed32_8_1{ .value = 0 }, + "\x0d\x00\x00\x00\x00"sv, R"({"value":0})" ); + pb_json_test( Test::Scalar::ReqFixed32_8_1{ .value = 1 }, + "\x0d\x01\x00\x00\x00"sv, R"({"value":1})" ); + + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqFixed32_8_1 >( + "\x0d\xff\xff\xff\xff"sv ) ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqFixed32_8_1 >( + "\x0d\x02\x00\x00\x00"sv ) ); + + pb_json_test( Test::Scalar::ReqFixed32_8_2{ .value = 0 }, + "\x0d\x00\x00\x00\x00"sv, R"({"value":0})" ); + pb_json_test( Test::Scalar::ReqFixed32_8_2{ .value = 1 }, + "\x0d\x01\x00\x00\x00"sv, R"({"value":1})" ); + pb_json_test( Test::Scalar::ReqFixed32_8_2{ .value = 2 }, + "\x0d\x02\x00\x00\x00"sv, R"({"value":2})" ); + pb_json_test( Test::Scalar::ReqFixed32_8_2{ .value = 3 }, + "\x0d\x03\x00\x00\x00"sv, R"({"value":3})" ); + + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqFixed32_8_2 >( + "\x0d\xff\xff\xff\xff"sv ) ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqFixed32_8_2 >( + "\x0d\x04\x00\x00\x00"sv ) ); + } + SUBCASE( "required" ) + { + CHECK( sizeof( Test::Scalar::ReqFixed32_8::value ) == sizeof( uint8_t ) ); + pb_json_test( Test::Scalar::ReqFixed32_8{ .value = 0x42 }, + "\x0d\x42\x00\x00\x00"sv, R"({"value":66})" ); + pb_json_test( Test::Scalar::ReqFixed32_8{ .value = 0xff }, + "\x0d\xff\x00\x00\x00"sv, R"({"value":255})" ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqFixed32_8 >( + "\x0d\xfe\xff\xff\xff"sv ) ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqFixed32_8 >( + "\x0d\x42\x00\x00"sv ) ); + } + SUBCASE( "optional" ) + { + pb_json_test( Test::Scalar::OptFixed32_8{ .value = 0x42 }, + "\x0d\x42\x00\x00\x00"sv, R"({"value":66})" ); + pb_json_test( Test::Scalar::OptFixed32_8{ .value = 0xff }, + "\x0d\xff\x00\x00\x00"sv, R"({"value":255})" ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqFixed32_8 >( + "\x0d\xfe\xff\xff\xff"sv ) ); + } + SUBCASE( "repeated" ) + { + pb_json_test( Test::Scalar::RepFixed32_8{ .value = { 0x42 } }, + "\x0d\x42\x00\x00\x00"sv, R"({"value":[66]})" ); + pb_json_test( Test::Scalar::RepFixed32_8{ .value = { 0x42, 0x3 } }, + "\x0d\x42\x00\x00\x00\x0d\x03\x00\x00\x00"sv, + R"({"value":[66,3]})" ); + pb_json_test( Test::Scalar::RepFixed32_8{ .value = {} }, "", "{}" ); + + SUBCASE( "packed" ) + { + pb_json_test( Test::Scalar::RepPackFixed32_8{ .value = { 0x42 } }, + "\x0a\x04\x42\x00\x00\x00"sv, R"({"value":[66]})" ); + pb_json_test( Test::Scalar::RepPackFixed32_8{ .value = { 0x42, 0x3 } }, + "\x0a\x08\x42\x00\x00\x00\x03\x00\x00\x00"sv, + R"({"value":[66,3]})" ); + pb_json_test( Test::Scalar::RepPackFixed32_8{ .value = {} }, "", "{}" ); + CHECK_THROWS( + ( void ) spb::pb::deserialize< Test::Scalar::RepPackFixed32_8 >( + "\x0a\x08\x42\x00\x00\x00\x03\x00\x00"sv ) ); + } + } + } + SUBCASE( "uint16" ) + { + SUBCASE( "bitfield" ) + { + pb_json_test( Test::Scalar::ReqFixed32_16_1{ .value = 0 }, + "\x0d\x00\x00\x00\x00"sv, R"({"value":0})" ); + pb_json_test( Test::Scalar::ReqFixed32_16_1{ .value = 1 }, + "\x0d\x01\x00\x00\x00"sv, R"({"value":1})" ); + + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqFixed32_16_1 >( + "\x0d\xff\xff\xff\xff"sv ) ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqFixed32_16_1 >( + "\x0d\x02\x00\x00\x00"sv ) ); + + pb_json_test( Test::Scalar::ReqFixed32_16_2{ .value = 0 }, + "\x0d\x00\x00\x00\x00"sv, R"({"value":0})" ); + pb_json_test( Test::Scalar::ReqFixed32_16_2{ .value = 1 }, + "\x0d\x01\x00\x00\x00"sv, R"({"value":1})" ); + pb_json_test( Test::Scalar::ReqFixed32_16_2{ .value = 2 }, + "\x0d\x02\x00\x00\x00"sv, R"({"value":2})" ); + pb_json_test( Test::Scalar::ReqFixed32_16_2{ .value = 3 }, + "\x0d\x03\x00\x00\x00"sv, R"({"value":3})" ); + + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqFixed32_16_2 >( + "\x0d\xff\xff\xff\xff"sv ) ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqFixed32_16_2 >( + "\x0d\x04\x00\x00\x00"sv ) ); + } + SUBCASE( "required" ) + { + CHECK( sizeof( Test::Scalar::ReqFixed32_16::value ) == sizeof( uint16_t ) ); + + pb_json_test( Test::Scalar::ReqFixed32_16{ .value = 0x42 }, + "\x0d\x42\x00\x00\x00"sv, R"({"value":66})" ); + pb_json_test( Test::Scalar::ReqFixed32_16{ .value = 0xff }, + "\x0d\xff\x00\x00\x00"sv, R"({"value":255})" ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqFixed32_16 >( + "\x0d\xfe\xff\xff\xff"sv ) ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqFixed32_16 >( + "\x0d\x42\x00\x00"sv ) ); + } + SUBCASE( "optional" ) + { + pb_json_test( Test::Scalar::OptFixed32_16{ .value = 0x42 }, + "\x0d\x42\x00\x00\x00"sv, R"({"value":66})" ); + pb_json_test( Test::Scalar::OptFixed32_16{ .value = 0xff }, + "\x0d\xff\x00\x00\x00"sv, R"({"value":255})" ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqFixed32_16 >( + "\x0d\xfe\xff\xff\xff"sv ) ); + } + SUBCASE( "repeated" ) + { + pb_json_test( Test::Scalar::RepFixed32_16{ .value = { 0x42 } }, + "\x0d\x42\x00\x00\x00"sv, R"({"value":[66]})" ); + pb_json_test( Test::Scalar::RepFixed32_16{ .value = { 0x42, 0x3 } }, + "\x0d\x42\x00\x00\x00\x0d\x03\x00\x00\x00"sv, + R"({"value":[66,3]})" ); + pb_json_test( Test::Scalar::RepFixed32_16{ .value = {} }, "", "{}" ); + + SUBCASE( "packed" ) + { + pb_json_test( Test::Scalar::RepPackFixed32_16{ .value = { 0x42 } }, + "\x0a\x04\x42\x00\x00\x00"sv, R"({"value":[66]})" ); + pb_json_test( Test::Scalar::RepPackFixed32_16{ .value = { 0x42, 0x3 } }, + "\x0a\x08\x42\x00\x00\x00\x03\x00\x00\x00"sv, + R"({"value":[66,3]})" ); + pb_json_test( Test::Scalar::RepPackFixed32_16{ .value = {} }, "", "{}" ); + CHECK_THROWS( + ( void ) spb::pb::deserialize< Test::Scalar::RepPackFixed32_16 >( + "\x0a\x08\x42\x00\x00\x00\x03\x00\x00"sv ) ); + } + } + } + } + SUBCASE( "fixed64" ) + { + SUBCASE( "bitfield" ) + { + pb_json_test( Test::Scalar::ReqSfixed64_64_1{ .value = -1 }, + "\x09\xff\xff\xff\xff\xff\xff\xff\xff"sv, R"({"value":-1})" ); + pb_json_test( Test::Scalar::ReqSfixed64_64_1{ .value = 0 }, + "\x09\x00\x00\x00\x00\x00\x00\x00\x00"sv, R"({"value":0})" ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqSfixed64_64_1 >( + "\x09\xfe\xff\xff\xff\xff\xff\xff\xff"sv ) ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqSfixed64_64_1 >( + "\x09\x01\x00\x00\x00\x00\x00\x00\x00"sv ) ); + + pb_json_test( Test::Scalar::ReqSfixed64_64_2{ .value = -2 }, + "\x09\xfe\xff\xff\xff\xff\xff\xff\xff"sv, R"({"value":-2})" ); + pb_json_test( Test::Scalar::ReqSfixed64_64_2{ .value = -1 }, + "\x09\xff\xff\xff\xff\xff\xff\xff\xff"sv, R"({"value":-1})" ); + pb_json_test( Test::Scalar::ReqSfixed64_64_2{ .value = 0 }, + "\x09\x00\x00\x00\x00\x00\x00\x00\x00"sv, R"({"value":0})" ); + pb_json_test( Test::Scalar::ReqSfixed64_64_2{ .value = 1 }, + "\x09\x01\x00\x00\x00\x00\x00\x00\x00"sv, R"({"value":1})" ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqSfixed64_64_2 >( + "\x09\xfd\xff\xff\xff\xff\xff\xff\xff"sv ) ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqSfixed64_64_2 >( + "\x09\x02\x00\x00\x00\x00\x00\x00\x00"sv ) ); + } + SUBCASE( "required" ) + { + pb_json_test( Test::Scalar::ReqFixed64{ .value = 0x42 }, + "\x09\x42\x00\x00\x00\x00\x00\x00\x00"sv, R"({"value":66})" ); + pb_json_test( Test::Scalar::ReqFixed64{ .value = 0xff }, + "\x09\xff\x00\x00\x00\x00\x00\x00\x00"sv, R"({"value":255})" ); + pb_json_test( Test::Scalar::ReqFixed64{ .value = uint64_t( -2 ) }, + "\x09\xfe\xff\xff\xff\xff\xff\xff\xff"sv, + R"({"value":18446744073709551614})" ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqFixed32 >( + "\x09\x42\x00\x00\x00\x00\x00\x00"sv ) ); + } + SUBCASE( "optional" ) + { + pb_json_test( Test::Scalar::OptFixed64{ .value = 0x42 }, + "\x09\x42\x00\x00\x00\x00\x00\x00\x00"sv, R"({"value":66})" ); + pb_json_test( Test::Scalar::OptFixed64{ .value = 0xff }, + "\x09\xff\x00\x00\x00\x00\x00\x00\x00"sv, R"({"value":255})" ); + pb_json_test( Test::Scalar::OptFixed64{ .value = -2 }, + "\x09\xfe\xff\xff\xff\xff\xff\xff\xff"sv, + R"({"value":18446744073709551614})" ); + } + SUBCASE( "repeated" ) + { + pb_json_test( Test::Scalar::RepFixed64{ .value = { 0x42 } }, + "\x09\x42\x00\x00\x00\x00\x00\x00\x00"sv, R"({"value":[66]})" ); + pb_json_test( + Test::Scalar::RepFixed64{ .value = { 0x42, 0x3 } }, + "\x09\x42\x00\x00\x00\x00\x00\x00\x00\x09\x03\x00\x00\x00\x00\x00\x00\x00"sv, + R"({"value":[66,3]})" ); + pb_json_test( Test::Scalar::RepFixed64{ .value = {} }, "", "{}" ); + SUBCASE( "packed" ) + { + pb_json_test( Test::Scalar::RepPackFixed64{ .value = { 0x42 } }, + "\x0a\x08\x42\x00\x00\x00\x00\x00\x00\x00"sv, + R"({"value":[66]})" ); + pb_json_test( + Test::Scalar::RepPackFixed64{ .value = { 0x42, 0x3 } }, + "\x0a\x10\x42\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00"sv, + R"({"value":[66,3]})" ); + pb_json_test( Test::Scalar::RepPackFixed64{ .value = {} }, "", "{}" ); + } + } + SUBCASE( "uint8" ) + { + SUBCASE( "bitfield" ) + { + pb_json_test( Test::Scalar::ReqFixed64_8_1{ .value = 0 }, + "\x09\x00\x00\x00\x00\x00\x00\x00\x00"sv, R"({"value":0})" ); + pb_json_test( Test::Scalar::ReqFixed64_8_1{ .value = 1 }, + "\x09\x01\x00\x00\x00\x00\x00\x00\x00"sv, R"({"value":1})" ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqFixed64_8_1 >( + "\x09\xfe\xff\xff\xff\xff\xff\xff\xff"sv ) ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqFixed64_8_1 >( + "\x09\x02\x00\x00\x00\x00\x00\x00\x00"sv ) ); + + pb_json_test( Test::Scalar::ReqFixed64_8_2{ .value = 0 }, + "\x09\x00\x00\x00\x00\x00\x00\x00\x00"sv, R"({"value":0})" ); + pb_json_test( Test::Scalar::ReqFixed64_8_2{ .value = 1 }, + "\x09\x01\x00\x00\x00\x00\x00\x00\x00"sv, R"({"value":1})" ); + pb_json_test( Test::Scalar::ReqFixed64_8_2{ .value = 2 }, + "\x09\x02\x00\x00\x00\x00\x00\x00\x00"sv, R"({"value":2})" ); + pb_json_test( Test::Scalar::ReqFixed64_8_2{ .value = 3 }, + "\x09\x03\x00\x00\x00\x00\x00\x00\x00"sv, R"({"value":3})" ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqFixed64_8_2 >( + "\x09\xfd\xff\xff\xff\xff\xff\xff\xff"sv ) ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqFixed64_8_2 >( + "\x09\x04\x00\x00\x00\x00\x00\x00\x00"sv ) ); + } + SUBCASE( "required" ) + { + CHECK( sizeof( Test::Scalar::ReqFixed64_8::value ) == sizeof( uint8_t ) ); + + pb_json_test( Test::Scalar::ReqFixed64_8{ .value = 0x42 }, + "\x09\x42\x00\x00\x00\x00\x00\x00\x00"sv, R"({"value":66})" ); + pb_json_test( Test::Scalar::ReqFixed64_8{ .value = 0xff }, + "\x09\xff\x00\x00\x00\x00\x00\x00\x00"sv, R"({"value":255})" ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqFixed64_8 >( + "\x09\xfe\xff\xff\xff\xff\xff\xff\xff"sv ) ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqFixed64_8 >( + "\x09\x42\x00\x00\x00\x00\x00\x00"sv ) ); + } + SUBCASE( "optional" ) + { + pb_json_test( Test::Scalar::OptFixed64_8{ .value = 0x42 }, + "\x09\x42\x00\x00\x00\x00\x00\x00\x00"sv, R"({"value":66})" ); + pb_json_test( Test::Scalar::OptFixed64_8{ .value = 0xff }, + "\x09\xff\x00\x00\x00\x00\x00\x00\x00"sv, R"({"value":255})" ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::OptFixed64_8 >( + "\x09\xfe\xff\xff\xff\xff\xff\xff\xff"sv ) ); + } + SUBCASE( "repeated" ) + { + pb_json_test( Test::Scalar::RepFixed64_8{ .value = { 0x42 } }, + "\x09\x42\x00\x00\x00\x00\x00\x00\x00"sv, R"({"value":[66]})" ); + pb_json_test( + Test::Scalar::RepFixed64_8{ .value = { 0x42, 0x3 } }, + "\x09\x42\x00\x00\x00\x00\x00\x00\x00\x09\x03\x00\x00\x00\x00\x00\x00\x00"sv, + R"({"value":[66,3]})" ); + pb_json_test( Test::Scalar::RepFixed64_8{ .value = {} }, "", "{}" ); + SUBCASE( "packed" ) + { + pb_json_test( Test::Scalar::RepPackFixed64_8{ .value = { 0x42 } }, + "\x0a\x08\x42\x00\x00\x00\x00\x00\x00\x00"sv, + R"({"value":[66]})" ); + pb_json_test( + Test::Scalar::RepPackFixed64_8{ .value = { 0x42, 0x3 } }, + "\x0a\x10\x42\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00"sv, + R"({"value":[66,3]})" ); + pb_json_test( Test::Scalar::RepPackFixed64_8{ .value = {} }, "", "{}" ); + } + } + } + } + SUBCASE( "sfixed32" ) + { + SUBCASE( "bitfield" ) + { + pb_json_test( Test::Scalar::ReqSfixed32_32_1{ .value = -1 }, + "\x0d\xff\xff\xff\xff"sv, R"({"value":-1})" ); + pb_json_test( Test::Scalar::ReqSfixed32_32_1{ .value = 0 }, + "\x0d\x00\x00\x00\x00"sv, R"({"value":0})" ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqSfixed32_32_1 >( + "\x0d\xfe\xff\xff\xff"sv ) ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqSfixed32_32_1 >( + "\x0d\x01\x00\x00\x00"sv ) ); + + pb_json_test( Test::Scalar::ReqSfixed32_32_2{ .value = -2 }, + "\x0d\xfe\xff\xff\xff"sv, R"({"value":-2})" ); + pb_json_test( Test::Scalar::ReqSfixed32_32_2{ .value = -1 }, + "\x0d\xff\xff\xff\xff"sv, R"({"value":-1})" ); + pb_json_test( Test::Scalar::ReqSfixed32_32_2{ .value = 0 }, + "\x0d\x00\x00\x00\x00"sv, R"({"value":0})" ); + pb_json_test( Test::Scalar::ReqSfixed32_32_2{ .value = 1 }, + "\x0d\x01\x00\x00\x00"sv, R"({"value":1})" ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqSfixed32_32_2 >( + "\x0d\xfd\xff\xff\xff"sv ) ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqSfixed32_32_2 >( + "\x0d\x02\x00\x00\x00"sv ) ); + } + SUBCASE( "required" ) + { + pb_json_test( Test::Scalar::ReqSfixed32{ .value = 0x42 }, "\x0d\x42\x00\x00\x00"sv, + R"({"value":66})" ); + pb_json_test( Test::Scalar::ReqSfixed32{ .value = 0xff }, "\x0d\xff\x00\x00\x00"sv, + R"({"value":255})" ); + pb_json_test( Test::Scalar::ReqSfixed32{ .value = -2 }, "\x0d\xfe\xff\xff\xff"sv, + R"({"value":-2})" ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqSfixed32 >( + "\x0d\x42\x00\x00"sv ) ); + } + SUBCASE( "optional" ) + { + pb_json_test( Test::Scalar::OptSfixed32{ .value = 0x42 }, "\x0d\x42\x00\x00\x00"sv, + R"({"value":66})" ); + pb_json_test( Test::Scalar::OptSfixed32{ .value = 0xff }, "\x0d\xff\x00\x00\x00"sv, + R"({"value":255})" ); + pb_json_test( Test::Scalar::OptSfixed32{ .value = -2 }, "\x0d\xfe\xff\xff\xff"sv, + R"({"value":-2})" ); + } + SUBCASE( "repeated" ) + { + pb_json_test( Test::Scalar::RepSfixed32{ .value = { 0x42 } }, + "\x0d\x42\x00\x00\x00"sv, R"({"value":[66]})" ); + pb_json_test( Test::Scalar::RepSfixed32{ .value = { 0x42, 0x3 } }, + "\x0d\x42\x00\x00\x00\x0d\x03\x00\x00\x00"sv, R"({"value":[66,3]})" ); + pb_json_test( Test::Scalar::RepSfixed32{ .value = {} }, "", "{}" ); + + SUBCASE( "packed" ) + { + pb_json_test( Test::Scalar::RepPackSfixed32{ .value = { 0x42 } }, + "\x0a\x04\x42\x00\x00\x00"sv, R"({"value":[66]})" ); + pb_json_test( Test::Scalar::RepPackSfixed32{ .value = { 0x42, 0x3 } }, + "\x0a\x08\x42\x00\x00\x00\x03\x00\x00\x00"sv, + R"({"value":[66,3]})" ); + pb_json_test( Test::Scalar::RepPackSfixed32{ .value = {} }, "", "{}" ); + } + } + SUBCASE( "int8" ) + { + SUBCASE( "bitfield" ) + { + pb_json_test( Test::Scalar::ReqSfixed32_8_1{ .value = -1 }, + "\x0d\xff\xff\xff\xff"sv, R"({"value":-1})" ); + pb_json_test( Test::Scalar::ReqSfixed32_8_1{ .value = 0 }, + "\x0d\x00\x00\x00\x00"sv, R"({"value":0})" ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqSfixed32_8_1 >( + "\x0d\xfe\xff\xff\xff"sv ) ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqSfixed32_8_1 >( + "\x0d\x01\x00\x00\x00"sv ) ); + + pb_json_test( Test::Scalar::ReqSfixed32_8_2{ .value = -2 }, + "\x0d\xfe\xff\xff\xff"sv, R"({"value":-2})" ); + pb_json_test( Test::Scalar::ReqSfixed32_8_2{ .value = -1 }, + "\x0d\xff\xff\xff\xff"sv, R"({"value":-1})" ); + pb_json_test( Test::Scalar::ReqSfixed32_8_2{ .value = 0 }, + "\x0d\x00\x00\x00\x00"sv, R"({"value":0})" ); + pb_json_test( Test::Scalar::ReqSfixed32_8_2{ .value = 1 }, + "\x0d\x01\x00\x00\x00"sv, R"({"value":1})" ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqSfixed32_8_2 >( + "\x0d\xfd\xff\xff\xff"sv ) ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqSfixed32_8_2 >( + "\x0d\x02\x00\x00\x00"sv ) ); + } + SUBCASE( "required" ) + { + CHECK( sizeof( Test::Scalar::ReqSfixed32_8::value ) == sizeof( uint8_t ) ); + + pb_json_test( Test::Scalar::ReqSfixed32_8{ .value = 0x42 }, + "\x0d\x42\x00\x00\x00"sv, R"({"value":66})" ); + pb_json_test( Test::Scalar::ReqSfixed32_8{ .value = -2 }, + "\x0d\xfe\xff\xff\xff"sv, R"({"value":-2})" ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqSfixed32_8 >( + "\x0d\xff\x00\x00\x00"sv ) ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqSfixed32_8 >( + "\x0d\x42\x00\x00"sv ) ); + } + SUBCASE( "optional" ) + { + pb_json_test( Test::Scalar::OptSfixed32_8{ .value = 0x42 }, + "\x0d\x42\x00\x00\x00"sv, R"({"value":66})" ); + pb_json_test( Test::Scalar::OptSfixed32_8{ .value = -2 }, + "\x0d\xfe\xff\xff\xff"sv, R"({"value":-2})" ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::OptSfixed32_8 >( + "\x0d\xff\x00\x00\x00"sv ) ); + } + SUBCASE( "repeated" ) + { + pb_json_test( Test::Scalar::RepSfixed32_8{ .value = { 0x42 } }, + "\x0d\x42\x00\x00\x00"sv, R"({"value":[66]})" ); + pb_json_test( Test::Scalar::RepSfixed32_8{ .value = { 0x42, 0x3 } }, + "\x0d\x42\x00\x00\x00\x0d\x03\x00\x00\x00"sv, + R"({"value":[66,3]})" ); + pb_json_test( Test::Scalar::RepSfixed32_8{ .value = {} }, "", "{}" ); + + SUBCASE( "packed" ) + { + pb_json_test( Test::Scalar::RepPackSfixed32_8{ .value = { 0x42 } }, + "\x0a\x04\x42\x00\x00\x00"sv, R"({"value":[66]})" ); + pb_json_test( Test::Scalar::RepPackSfixed32_8{ .value = { 0x42, 0x3 } }, + "\x0a\x08\x42\x00\x00\x00\x03\x00\x00\x00"sv, + R"({"value":[66,3]})" ); + pb_json_test( Test::Scalar::RepPackSfixed32_8{ .value = {} }, "", "{}" ); + } + } + } + } + SUBCASE( "sfixed64" ) + { + SUBCASE( "bitfield" ) + { + pb_json_test( Test::Scalar::ReqSfixed64_64_1{ .value = -1 }, + "\x09\xff\xff\xff\xff\xff\xff\xff\xff"sv, R"({"value":-1})" ); + pb_json_test( Test::Scalar::ReqSfixed64_64_1{ .value = 0 }, + "\x09\x00\x00\x00\x00\x00\x00\x00\x00"sv, R"({"value":0})" ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqSfixed64_64_1 >( + "\x09\xfe\xff\xff\xff\xff\xff\xff\xff"sv ) ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqSfixed64_64_1 >( + "\x09\x01\x00\x00\x00\x00\x00\x00\x00"sv ) ); + + pb_json_test( Test::Scalar::ReqSfixed64_64_2{ .value = -2 }, + "\x09\xfe\xff\xff\xff\xff\xff\xff\xff"sv, R"({"value":-2})" ); + pb_json_test( Test::Scalar::ReqSfixed64_64_2{ .value = -1 }, + "\x09\xff\xff\xff\xff\xff\xff\xff\xff"sv, R"({"value":-1})" ); + pb_json_test( Test::Scalar::ReqSfixed64_64_2{ .value = 0 }, + "\x09\x00\x00\x00\x00\x00\x00\x00\x00"sv, R"({"value":0})" ); + pb_json_test( Test::Scalar::ReqSfixed64_64_2{ .value = 1 }, + "\x09\x01\x00\x00\x00\x00\x00\x00\x00"sv, R"({"value":1})" ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqSfixed64_64_2 >( + "\x09\xfd\xff\xff\xff\xff\xff\xff\xff"sv ) ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqSfixed64_64_2 >( + "\x09\x02\x00\x00\x00\x00\x00\x00\x00"sv ) ); + } + SUBCASE( "required" ) + { + pb_json_test( Test::Scalar::ReqSfixed64{ .value = 0x42 }, + "\x09\x42\x00\x00\x00\x00\x00\x00\x00"sv, R"({"value":66})" ); + pb_json_test( Test::Scalar::ReqSfixed64{ .value = 0xff }, + "\x09\xff\x00\x00\x00\x00\x00\x00\x00"sv, R"({"value":255})" ); + pb_json_test( Test::Scalar::ReqSfixed64{ .value = -2 }, + "\x09\xfe\xff\xff\xff\xff\xff\xff\xff"sv, R"({"value":-2})" ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqSfixed64 >( + "\x09\x42\x00\x00\x00\x00\x00\x00"sv ) ); + } + SUBCASE( "optional" ) + { + pb_json_test( Test::Scalar::OptSfixed64{ .value = 0x42 }, + "\x09\x42\x00\x00\x00\x00\x00\x00\x00"sv, R"({"value":66})" ); + pb_json_test( Test::Scalar::OptSfixed64{ .value = 0xff }, + "\x09\xff\x00\x00\x00\x00\x00\x00\x00"sv, R"({"value":255})" ); + pb_json_test( Test::Scalar::OptSfixed64{ .value = -2 }, + "\x09\xfe\xff\xff\xff\xff\xff\xff\xff"sv, R"({"value":-2})" ); + } + SUBCASE( "repeated" ) + { + pb_json_test( Test::Scalar::RepSfixed64{ .value = { 0x42 } }, + "\x09\x42\x00\x00\x00\x00\x00\x00\x00"sv, R"({"value":[66]})" ); + pb_json_test( + Test::Scalar::RepSfixed64{ .value = { 0x42, 0x3 } }, + "\x09\x42\x00\x00\x00\x00\x00\x00\x00\x09\x03\x00\x00\x00\x00\x00\x00\x00"sv, + R"({"value":[66,3]})" ); + pb_json_test( Test::Scalar::RepSfixed64{ .value = {} }, "", "{}" ); + SUBCASE( "packed" ) + { + pb_json_test( Test::Scalar::RepPackSfixed64{ .value = { 0x42 } }, + "\x0a\x08\x42\x00\x00\x00\x00\x00\x00\x00"sv, + R"({"value":[66]})" ); + pb_json_test( + Test::Scalar::RepPackSfixed64{ .value = { 0x42, 0x3 } }, + "\x0a\x10\x42\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00"sv, + R"({"value":[66,3]})" ); + pb_json_test( Test::Scalar::RepPackSfixed64{ .value = {} }, "", "{}" ); + } + } + SUBCASE( "int8" ) + { + SUBCASE( "bitfield" ) + { + pb_json_test( Test::Scalar::ReqSfixed64_8_1{ .value = -1 }, + "\x09\xff\xff\xff\xff\xff\xff\xff\xff"sv, R"({"value":-1})" ); + pb_json_test( Test::Scalar::ReqSfixed64_8_1{ .value = 0 }, + "\x09\x00\x00\x00\x00\x00\x00\x00\x00"sv, R"({"value":0})" ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqSfixed64_8_1 >( + "\x09\xfe\xff\xff\xff\xff\xff\xff\xff"sv ) ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqSfixed64_8_1 >( + "\x09\x01\x00\x00\x00\x00\x00\x00\x00"sv ) ); + + pb_json_test( Test::Scalar::ReqSfixed64_8_2{ .value = -2 }, + "\x09\xfe\xff\xff\xff\xff\xff\xff\xff"sv, R"({"value":-2})" ); + pb_json_test( Test::Scalar::ReqSfixed64_8_2{ .value = -1 }, + "\x09\xff\xff\xff\xff\xff\xff\xff\xff"sv, R"({"value":-1})" ); + pb_json_test( Test::Scalar::ReqSfixed64_8_2{ .value = 0 }, + "\x09\x00\x00\x00\x00\x00\x00\x00\x00"sv, R"({"value":0})" ); + pb_json_test( Test::Scalar::ReqSfixed64_8_2{ .value = 1 }, + "\x09\x01\x00\x00\x00\x00\x00\x00\x00"sv, R"({"value":1})" ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqSfixed64_8_2 >( + "\x09\xfd\xff\xff\xff\xff\xff\xff\xff"sv ) ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqSfixed64_8_2 >( + "\x09\x02\x00\x00\x00\x00\x00\x00\x00"sv ) ); + } + SUBCASE( "required" ) + { + CHECK( sizeof( Test::Scalar::ReqSfixed64_8::value ) == sizeof( uint8_t ) ); + + pb_json_test( Test::Scalar::ReqSfixed64_8{ .value = 0x42 }, + "\x09\x42\x00\x00\x00\x00\x00\x00\x00"sv, R"({"value":66})" ); + pb_json_test( Test::Scalar::ReqSfixed64_8{ .value = -2 }, + "\x09\xfe\xff\xff\xff\xff\xff\xff\xff"sv, R"({"value":-2})" ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqSfixed64_8 >( + "\x09\xff\x00\x00\x00\x00\x00\x00\x00"sv ) ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqSfixed64_8 >( + "\x09\x42\x00\x00\x00\x00\x00\x00"sv ) ); + } + SUBCASE( "optional" ) + { + pb_json_test( Test::Scalar::OptSfixed64_8{ .value = 0x42 }, + "\x09\x42\x00\x00\x00\x00\x00\x00\x00"sv, R"({"value":66})" ); + pb_json_test( Test::Scalar::OptSfixed64_8{ .value = -2 }, + "\x09\xfe\xff\xff\xff\xff\xff\xff\xff"sv, R"({"value":-2})" ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::OptSfixed64_8 >( + "\x09\xff\x00\x00\x00\x00\x00\x00\x00"sv ) ); + } + SUBCASE( "repeated" ) + { + pb_json_test( Test::Scalar::RepSfixed64_8{ .value = { 0x42 } }, + "\x09\x42\x00\x00\x00\x00\x00\x00\x00"sv, R"({"value":[66]})" ); + pb_json_test( + Test::Scalar::RepSfixed64_8{ .value = { 0x42, 0x3 } }, + "\x09\x42\x00\x00\x00\x00\x00\x00\x00\x09\x03\x00\x00\x00\x00\x00\x00\x00"sv, + R"({"value":[66,3]})" ); + pb_json_test( Test::Scalar::RepSfixed64_8{ .value = {} }, "", "{}" ); + SUBCASE( "packed" ) + { + pb_json_test( Test::Scalar::RepPackSfixed64_8{ .value = { 0x42 } }, + "\x0a\x08\x42\x00\x00\x00\x00\x00\x00\x00"sv, + R"({"value":[66]})" ); + pb_json_test( + Test::Scalar::RepPackSfixed64_8{ .value = { 0x42, 0x3 } }, + "\x0a\x10\x42\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00"sv, + R"({"value":[66,3]})" ); + pb_json_test( Test::Scalar::RepPackSfixed64_8{ .value = {} }, "", "{}" ); + } + } + } + } + } + SUBCASE( "float" ) + { + SUBCASE( "required" ) + { + pb_json_test( Test::Scalar::ReqFloat{ .value = 42.0f }, "\x0d\x00\x00\x28\x42"sv, + R"({"value":42})" ); + CHECK_THROWS( + ( void ) spb::pb::deserialize< Test::Scalar::ReqFloat >( "\x0d\x00\x00\x28"sv ) ); + } + SUBCASE( "optional" ) + { + pb_json_test( Test::Scalar::OptFloat{ .value = 42.3f }, "\x0d\x33\x33\x29\x42"sv, + R"({"value":42.3})" ); + } + SUBCASE( "repeated" ) + { + pb_json_test( Test::Scalar::RepFloat{ .value = { 42.3f } }, "\x0d\x33\x33\x29\x42"sv, + R"({"value":[42.3]})" ); + pb_json_test( Test::Scalar::RepFloat{ .value = { 42.0f, 42.3f } }, + "\x0d\x00\x00\x28\x42\x0d\x33\x33\x29\x42"sv, R"({"value":[42,42.3]})" ); + pb_json_test( Test::Scalar::RepFloat{ .value = {} }, "", "{}" ); + } + } + SUBCASE( "double" ) + { + SUBCASE( "required" ) + { + pb_json_test( Test::Scalar::ReqDouble{ .value = 42.0 }, + "\x09\x00\x00\x00\x00\x00\x00\x45\x40"sv, R"({"value":42})" ); + CHECK_THROWS( + ( void ) spb::pb::deserialize< Test::Scalar::ReqDouble >( "\x0d\x00\x00\x28"sv ) ); + } + SUBCASE( "optional" ) + { + pb_json_test( Test::Scalar::OptDouble{ .value = 42.3 }, + "\x09\x66\x66\x66\x66\x66\x26\x45\x40"sv, R"({"value":42.3})" ); + } + SUBCASE( "repeated" ) + { + pb_json_test( Test::Scalar::RepDouble{ .value = { 42.3 } }, + "\x09\x66\x66\x66\x66\x66\x26\x45\x40"sv, R"({"value":[42.3]})" ); + pb_json_test( + Test::Scalar::RepDouble{ .value = { 42.3, 3.0 } }, + "\x09\x66\x66\x66\x66\x66\x26\x45\x40\x09\x00\x00\x00\x00\x00\x00\x08\x40"sv, + R"({"value":[42.3,3]})" ); + pb_json_test( Test::Scalar::RepDouble{ .value = {} }, "", "{}" ); + } + } + SUBCASE( "bytes" ) + { + SUBCASE( "required" ) + { + pb_json_test( Test::Scalar::ReqBytes{ }, "", "{}" ); + pb_json_test( Test::Scalar::ReqBytes{ .value = to_bytes( "hello" ) }, "\x0a\x05hello"sv, + R"({"value":"aGVsbG8="})" ); + pb_json_test( Test::Scalar::ReqBytes{ .value = to_bytes( "\x00\x01\x02"sv ) }, + "\x0a\x03\x00\x01\x02"sv, R"({"value":"AAEC"})" ); + pb_json_test( Test::Scalar::ReqBytes{ .value = to_bytes( "\x00\x01\x02\x03\x04"sv ) }, + "\x0a\x05\x00\x01\x02\x03\x04"sv, R"({"value":"AAECAwQ="})" ); + CHECK_THROWS( + ( void ) spb::pb::deserialize< Test::Scalar::ReqBytes >( "\x0a\x05hell"sv ) ); + } + SUBCASE( "optional" ) + { + pb_json_test( Test::Scalar::OptBytes{ }, "", "{}" ); + pb_json_test( Test::Scalar::OptBytes{ .value = to_bytes( "hello" ) }, "\x0a\x05hello"sv, + R"({"value":"aGVsbG8="})" ); + pb_json_test( Test::Scalar::OptBytes{ .value = to_bytes( "\x00\x01\x02"sv ) }, + "\x0a\x03\x00\x01\x02"sv, R"({"value":"AAEC"})" ); + pb_json_test( Test::Scalar::OptBytes{ .value = to_bytes( "\x00\x01\x02\x03\x04"sv ) }, + "\x0a\x05\x00\x01\x02\x03\x04"sv, R"({"value":"AAECAwQ="})" ); + } + SUBCASE( "repeated" ) + { + pb_json_test( Test::Scalar::RepBytes{ }, "", "{}" ); + pb_json_test( Test::Scalar::RepBytes{ .value = { to_bytes( "hello" ) } }, + "\x0a\x05hello"sv, R"({"value":["aGVsbG8="]})" ); + pb_json_test( Test::Scalar::RepBytes{ .value = { to_bytes( "\x00\x01\x02"sv ), + to_bytes( "hello" ) } }, + "\x0a\x03\x00\x01\x02\x0a\x05hello"sv, + R"({"value":["AAEC","aGVsbG8="]})" ); + pb_json_test( + Test::Scalar::RepBytes{ .value = { to_bytes( "\x00\x01\x02\x03\x04"sv ) } }, + "\x0a\x05\x00\x01\x02\x03\x04"sv, R"({"value":["AAECAwQ="]})" ); + } + SUBCASE( "fixed" ) + { + SUBCASE( "required" ) + { + pb_json_test( Test::Scalar::ReqBytesFixed{ }, + "\x0a\x08\x00\x00\x00\x00\x00\x00\x00\x00"sv, + R"({"value":"AAAAAAAAAAA="})" ); + pb_json_test( Test::Scalar::ReqBytesFixed{ .value = to_array( "hello123" ) }, + "\x0a\x08hello123"sv, R"({"value":"aGVsbG8xMjM="})" ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqBytesFixed >( + "\x0a\x05hello"sv ) ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::ReqBytesFixed >( + "\x0a\x09hello1234"sv ) ); + } + SUBCASE( "optional" ) + { + pb_json_test( Test::Scalar::OptBytesFixed{ }, "", "{}" ); + pb_json_test( Test::Scalar::OptBytesFixed{ .value = to_array( "hello123" ) }, + "\x0a\x08hello123"sv, R"({"value":"aGVsbG8xMjM="})" ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::OptBytesFixed >( + "\x0a\x05hello"sv ) ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::OptBytesFixed >( + "\x0a\x09hello1234"sv ) ); + } + SUBCASE( "repeated" ) + { + pb_json_test( Test::Scalar::RepBytesFixed{ }, "", "{}" ); + pb_json_test( Test::Scalar::RepBytesFixed{ .value = { to_array( "hello123" ) } }, + "\x0a\x08hello123"sv, R"({"value":["aGVsbG8xMjM="]})" ); + pb_json_test( Test::Scalar::RepBytesFixed{ .value = { to_array( "hello123" ), + to_array( "hello321" ) } }, + "\x0a\x08hello123\x0a\x08hello321"sv, + R"({"value":["aGVsbG8xMjM=","aGVsbG8zMjE="]})" ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::RepBytesFixed >( + "\x0a\x05hello"sv ) ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::RepBytesFixed >( + "\x0a\x09hello1234"sv ) ); + } + } + } + SUBCASE( "variant" ) + { + SUBCASE( "int" ) + { + pb_json_test( Test::Variant{ .oneof_field = 0x42U }, "\x08\x42", R"({"var_int":66})" ); + } + SUBCASE( "string" ) + { + pb_json_test( Test::Variant{ .oneof_field = "hello" }, "\x12\x05hello", + R"({"var_string":"hello"})" ); + } + SUBCASE( "bytes" ) + { + pb_json_test( Test::Variant{ .oneof_field = to_bytes( "hello" ) }, "\x1A\x05hello", + R"({"var_bytes":"aGVsbG8="})" ); + } + SUBCASE( "name" ) + { + pb_json_test( Test::Variant{ .oneof_field = Test::Name{ .name = "John" } }, + "\x22\x06\x0A\x04John", R"({"name":{"name":"John"}})" ); + } + } + SUBCASE( "map" ) + { + SUBCASE( "int32/int32" ) + { + CHECK( pb_serialize_as< scalar_encoder_combine( scalar_encoder::varint, + scalar_encoder::varint ) >( + std::map< int32_t, int32_t >{ { 1, 2 } } ) == "\x0a\x04\x08\x01\x10\x02" ); + CHECK( pb_serialize_as< scalar_encoder_combine( + spb::pb::detail::scalar_encoder::varint, + spb::pb::detail::scalar_encoder::varint ) >( std::map< int32_t, int32_t >{ + { 1, 2 }, { 2, 3 } } ) == "\x0a\x08\x08\x01\x10\x02\x08\x02\x10\x03" ); + } + SUBCASE( "string/string" ) + { + CHECK( pb_serialize_as< scalar_encoder_combine( + spb::pb::detail::scalar_encoder::varint, + spb::pb::detail::scalar_encoder::varint ) >( + std::map< std::string, std::string >{ { "hello", "world" } } ) == + "\x0a\x0e\x0a\x05hello\x12\x05world" ); + } + SUBCASE( "int32/string" ) + { + CHECK( pb_serialize_as< scalar_encoder_combine( + spb::pb::detail::scalar_encoder::varint, + spb::pb::detail::scalar_encoder::varint ) >( + std::map< int32_t, std::string >{ { 1, "hello" } } ) == + "\x0a\x09\x08\x01\x12\x05hello" ); + } + SUBCASE( "string/int32" ) + { + CHECK( pb_serialize_as< scalar_encoder_combine( + spb::pb::detail::scalar_encoder::varint, + spb::pb::detail::scalar_encoder::varint ) >( + std::map< std::string, int32_t >{ { "hello", 2 } } ) == + "\x0a\x09\x0a\x05hello\x10\x02" ); + } + SUBCASE( "string/name" ) + { + CHECK( pb_serialize_as< scalar_encoder_combine( + spb::pb::detail::scalar_encoder::varint, + spb::pb::detail::scalar_encoder::varint ) >( + std::map< std::string, Test::Name >{ { "hello", { .name = "john" } } } ) == + "\x0a\x0f\x0a\x05hello\x12\x06\x0A\x04john" ); + } + } + SUBCASE( "person" ) + { + pb_json_test( PhoneBook::Person{ + .name = "John Doe", + .id = 123, + .email = "QXUeh@example.com", + .phones = { + PhoneBook::Person::PhoneNumber{ + .number = "555-4321", + .type = PhoneBook::Person::PhoneType::HOME, + }, + }, + }, + "\x0a\x08John Doe\x10\x7b\x1a\x11QXUeh@example.com\x22\x0c\x0A\x08" + "555-4321\x10\x01", + R"({"name":"John Doe","id":123,"email":"QXUeh@example.com","phones":[{"number":"555-4321","type":"HOME"}]})" ); + CHECK_THROWS( ( void ) spb::pb::deserialize< PhoneBook::Person >( + "\x0a\x08John Doe\x10\x7b\x1a\x11QXUeh@example.com\x22\x0d\x0A\x08" + "555-4321\x10\x010\x00"sv ) ); + } + SUBCASE( "name" ) + { + pb_json_test( Test::Name{ }, "", "{}" ); + } + SUBCASE( "ignore" ) + { + SUBCASE( "string" ) + { + SUBCASE( "required" ) + { + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( "\x0a\x05hello"sv ) == + Test::Scalar::Simple{ } ); + CHECK_NOTHROW( + ( void ) spb::pb::deserialize< Test::Scalar::Empty >( "\x0a\x05hello"sv ) ); + CHECK_THROWS( + ( void ) spb::pb::deserialize< Test::Scalar::Empty >( "\x0a\x05hell"sv ) ); + } + SUBCASE( "repeated" ) + { + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( + "\x0a\x05hello\x0a\x05world"sv ) == Test::Scalar::Simple{ } ); + CHECK_NOTHROW( ( void ) spb::pb::deserialize< Test::Scalar::Empty >( + "\x0a\x05hello\x0a\x05world"sv ) ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::Empty >( + "\x0a\x05hello\x0a\x05worl"sv ) ); + } + } + SUBCASE( "bool" ) + { + SUBCASE( "required" ) + { + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( "\x08\x01"sv ) == + Test::Scalar::Simple{ } ); + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( "\x08\x00"sv ) == + Test::Scalar::Simple{ } ); + } + SUBCASE( "repeated" ) + { + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( "\x08\x01\x08\x00"sv ) == + Test::Scalar::Simple{ } ); + CHECK_THROWS( + ( void ) spb::pb::deserialize< Test::Scalar::Empty >( "\x08\x01\x08"sv ) ); + } + } + SUBCASE( "int" ) + { + SUBCASE( "varint32" ) + { + SUBCASE( "required" ) + { + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( "\x08\x42"sv ) == + Test::Scalar::Simple{ } ); + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( "\x08\xff\x01"sv ) == + Test::Scalar::Simple{ } ); + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( + "\x08\xfe\xff\xff\xff\x0f"sv ) == Test::Scalar::Simple{ } ); + CHECK_THROWS( + ( void ) spb::pb::deserialize< Test::Scalar::Empty >( "\x08"sv ) ); + CHECK_THROWS( + ( void ) spb::pb::deserialize< Test::Scalar::Empty >( "\x08\xff"sv ) ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::Empty >( + "\x08\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01"sv ) ); + } + SUBCASE( "repeated" ) + { + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( "\x08\x42\x08\x03"sv ) == + Test::Scalar::Simple{ } ); + + SUBCASE( "packed" ) + { + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( "\x0a\x01\x42"sv ) == + Test::Scalar::Simple{ } ); + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( + "\x0a\x02\x42\x03"sv ) == Test::Scalar::Simple{ } ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::Empty >( + "\x0a\x02\x42"sv ) ); + } + } + } + SUBCASE( "varint64" ) + { + SUBCASE( "required" ) + { + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( "\x08\x42"sv ) == + Test::Scalar::Simple{ } ); + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( "\x08\xff\x01"sv ) == + Test::Scalar::Simple{ } ); + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( + "\x08\xfe\xff\xff\xff\xff\xff\xff\xff\xff\x01"sv ) == + Test::Scalar::Simple{ } ); + CHECK_THROWS( + ( void ) spb::pb::deserialize< Test::Scalar::Empty >( "\x08"sv ) ); + CHECK_THROWS( + ( void ) spb::pb::deserialize< Test::Scalar::Empty >( "\x08\xff"sv ) ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::Empty >( + "\x08\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01"sv ) ); + } + SUBCASE( "repeated" ) + { + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( "\x08\x42\x08\x03"sv ) == + Test::Scalar::Simple{ } ); + + SUBCASE( "packed" ) + { + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( "\x0a\x01\x42"sv ) == + Test::Scalar::Simple{ } ); + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( + "\x0a\x02\x42\x03"sv ) == Test::Scalar::Simple{ } ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::Empty >( + "\x0a\x02\x42"sv ) ); + } + } + } + SUBCASE( "svarint32" ) + { + SUBCASE( "required" ) + { + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( "\x08\x84\x01"sv ) == + Test::Scalar::Simple{ } ); + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( "\x08\xfe\x03"sv ) == + Test::Scalar::Simple{ } ); + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( "\x08\x03"sv ) == + Test::Scalar::Simple{ } ); + CHECK_THROWS( + ( void ) spb::pb::deserialize< Test::Scalar::Empty >( "\x08"sv ) ); + CHECK_THROWS( + ( void ) spb::pb::deserialize< Test::Scalar::Empty >( "\x08\xff"sv ) ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::Empty >( + "\x08\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01"sv ) ); + } + SUBCASE( "repeated" ) + { + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( "\x08\x84\x01"sv ) == + Test::Scalar::Simple{ } ); + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( + "\x08\x84\x01\x08\x03"sv ) == Test::Scalar::Simple{ } ); + + SUBCASE( "packed" ) + { + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( + "\x0a\x02\x84\x01"sv ) == Test::Scalar::Simple{ } ); + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( + "\x0a\x03\x84\x01\x03"sv ) == Test::Scalar::Simple{ } ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::Empty >( + "\x0a\x02\x42"sv ) ); + } + } + } + SUBCASE( "svarint64" ) + { + SUBCASE( "required" ) + { + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( "\x08\x84\x01"sv ) == + Test::Scalar::Simple{ } ); + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( "\x08\xfe\x03"sv ) == + Test::Scalar::Simple{ } ); + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( "\x08\x03"sv ) == + Test::Scalar::Simple{ } ); + CHECK_THROWS( + ( void ) spb::pb::deserialize< Test::Scalar::Empty >( "\x08"sv ) ); + CHECK_THROWS( + ( void ) spb::pb::deserialize< Test::Scalar::Empty >( "\x08\xff"sv ) ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::Empty >( + "\x08\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01"sv ) ); + } + SUBCASE( "repeated" ) + { + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( "\x08\x84\x01"sv ) == + Test::Scalar::Simple{ } ); + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( + "\x08\x84\x01\x08\x03"sv ) == Test::Scalar::Simple{ } ); + + SUBCASE( "packed" ) + { + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( + "\x0a\x02\x84\x01"sv ) == Test::Scalar::Simple{ } ); + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( + "\x0a\x03\x84\x01\x03"sv ) == Test::Scalar::Simple{ } ); + + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::Empty >( + "\x0a\x02\x84"sv ) ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::Empty >( + "\x0a\x03\x84\x01"sv ) ); + } + } + } + SUBCASE( "fixed32" ) + { + SUBCASE( "required" ) + { + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( + "\x0d\x42\x00\x00\x00"sv ) == Test::Scalar::Simple{ } ); + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( + "\x0d\xff\x00\x00\x00"sv ) == Test::Scalar::Simple{ } ); + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( + "\x0d\xfe\xff\xff\xff"sv ) == Test::Scalar::Simple{ } ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::Empty >( + "\x0d\x42\x00\x00"sv ) ); + } + SUBCASE( "repeated" ) + { + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( + "\x0d\x42\x00\x00\x00"sv ) == Test::Scalar::Simple{ } ); + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( + "\x0d\x42\x00\x00\x00\x0d\x03\x00\x00\x00"sv ) == + Test::Scalar::Simple{ } ); + + SUBCASE( "packed" ) + { + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( + "\x0a\x04\x42\x00\x00\x00"sv ) == Test::Scalar::Simple{ } ); + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( + "\x0a\x08\x42\x00\x00\x00\x03\x00\x00\x00"sv ) == + Test::Scalar::Simple{ } ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::Empty >( + "\x0a\x08\x42\x00\x00\x00\x03\x00\x00"sv ) ); + } + } + } + SUBCASE( "fixed64" ) + { + SUBCASE( "required" ) + { + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( + "\x09\x42\x00\x00\x00\x00\x00\x00\x00"sv ) == + Test::Scalar::Simple{ } ); + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( + "\x09\xff\x00\x00\x00\x00\x00\x00\x00"sv ) == + Test::Scalar::Simple{ } ); + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( + "\x09\xfe\xff\xff\xff\xff\xff\xff\xff"sv ) == + Test::Scalar::Simple{ } ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::Empty >( + "\x09\x42\x00\x00\x00\x00\x00\x00"sv ) ); + } + SUBCASE( "repeated" ) + { + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( + "\x09\x42\x00\x00\x00\x00\x00\x00\x00"sv ) == + Test::Scalar::Simple{ } ); + CHECK( + spb::pb::deserialize< Test::Scalar::Simple >( + "\x09\x42\x00\x00\x00\x00\x00\x00\x00\x09\x03\x00\x00\x00\x00\x00\x00\x00"sv ) == + Test::Scalar::Simple{ } ); + + SUBCASE( "packed" ) + { + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( + "\x0a\x08\x42\x00\x00\x00\x00\x00\x00\x00"sv ) == + Test::Scalar::Simple{ } ); + CHECK( + spb::pb::deserialize< Test::Scalar::Simple >( + "\x0a\x10\x42\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00"sv ) == + Test::Scalar::Simple{ } ); + } + } + } + SUBCASE( "sfixed32" ) + { + SUBCASE( "required" ) + { + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( + "\x0d\x42\x00\x00\x00"sv ) == Test::Scalar::Simple{ } ); + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( + "\x0d\xff\x00\x00\x00"sv ) == Test::Scalar::Simple{ } ); + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( + "\x0d\xfe\xff\xff\xff"sv ) == Test::Scalar::Simple{ } ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::Empty >( + "\x0d\x42\x00\x00"sv ) ); + } + SUBCASE( "repeated" ) + { + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( + "\x0d\x42\x00\x00\x00"sv ) == Test::Scalar::Simple{ } ); + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( + "\x0d\x42\x00\x00\x00\x0d\x03\x00\x00\x00"sv ) == + Test::Scalar::Simple{ } ); + + SUBCASE( "packed" ) + { + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( + "\x0a\x04\x42\x00\x00\x00"sv ) == Test::Scalar::Simple{ } ); + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( + "\x0a\x08\x42\x00\x00\x00\x03\x00\x00\x00"sv ) == + Test::Scalar::Simple{ } ); + } + } + } + SUBCASE( "sfixed64" ) + { + SUBCASE( "required" ) + { + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( + "\x09\x42\x00\x00\x00\x00\x00\x00\x00"sv ) == + Test::Scalar::Simple{ } ); + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( + "\x09\xff\x00\x00\x00\x00\x00\x00\x00"sv ) == + Test::Scalar::Simple{ } ); + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( + "\x09\xfe\xff\xff\xff\xff\xff\xff\xff"sv ) == + Test::Scalar::Simple{ } ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::Empty >( + "\x09\x42\x00\x00\x00\x00\x00\x00"sv ) ); + } + SUBCASE( "repeated" ) + { + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( + "\x09\x42\x00\x00\x00\x00\x00\x00\x00"sv ) == + Test::Scalar::Simple{ } ); + CHECK( + spb::pb::deserialize< Test::Scalar::Simple >( + "\x09\x42\x00\x00\x00\x00\x00\x00\x00\x09\x03\x00\x00\x00\x00\x00\x00\x00"sv ) == + Test::Scalar::Simple{ } ); + + SUBCASE( "packed" ) + { + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( + "\x0a\x08\x42\x00\x00\x00\x00\x00\x00\x00"sv ) == + Test::Scalar::Simple{ } ); + CHECK( + spb::pb::deserialize< Test::Scalar::Simple >( + "\x0a\x10\x42\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00"sv ) == + Test::Scalar::Simple{ } ); + } + } + } + } + SUBCASE( "float" ) + { + SUBCASE( "required" ) + { + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( "\x0d\x00\x00\x28\x42"sv ) == + Test::Scalar::Simple{ } ); + CHECK_THROWS( + ( void ) spb::pb::deserialize< Test::Scalar::Empty >( "\x0d\x00\x00\x28"sv ) ); + } + SUBCASE( "repeated" ) + { + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( "\x0d\x33\x33\x29\x42"sv ) == + Test::Scalar::Simple{ } ); + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( + "\x0d\x00\x00\x28\x42\x0d\x33\x33\x29\x42"sv ) == + Test::Scalar::Simple{ } ); + } + } + SUBCASE( "double" ) + { + SUBCASE( "required" ) + { + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( + "\x09\x00\x00\x00\x00\x00\x00\x45\x40"sv ) == Test::Scalar::Simple{ } ); + CHECK_THROWS( + ( void ) spb::pb::deserialize< Test::Scalar::Empty >( "\x0d\x00\x00\x28"sv ) ); + } + SUBCASE( "optional" ) + { + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( + "\x09\x66\x66\x66\x66\x66\x26\x45\x40"sv ) == Test::Scalar::Simple{ } ); + } + SUBCASE( "repeated" ) + { + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( + "\x09\x66\x66\x66\x66\x66\x26\x45\x40"sv ) == Test::Scalar::Simple{ } ); + CHECK( + spb::pb::deserialize< Test::Scalar::Simple >( + "\x09\x66\x66\x66\x66\x66\x26\x45\x40\x09\x00\x00\x00\x00\x00\x00\x08\x40"sv ) == + Test::Scalar::Simple{ } ); + } + } + SUBCASE( "bytes" ) + { + SUBCASE( "required" ) + { + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( "\x0a\x05hello"sv ) == + Test::Scalar::Simple{ } ); + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( "\x0a\x03\x00\x01\x02"sv ) == + Test::Scalar::Simple{ } ); + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( + "\x0a\x05\x00\x01\x02\x03\x04"sv ) == Test::Scalar::Simple{ } ); + CHECK_THROWS( + ( void ) spb::pb::deserialize< Test::Scalar::Empty >( "\x0a\x05hell"sv ) ); + } + SUBCASE( "repeated" ) + { + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( "\x0a\x05hello"sv ) == + Test::Scalar::Simple{ } ); + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( "\x0a\x03\x00\x01\x02"sv ) == + Test::Scalar::Simple{ } ); + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( + "\x0a\x05\x00\x01\x02\x03\x04"sv ) == Test::Scalar::Simple{ } ); + } + } + SUBCASE( "struct" ) + { + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( + "\x0a\x08John Doe\x10\x7b\x1a\x11QXUeh@example.com\x22\x0c\x0A\x08" + "555-4321\x10\x01"sv ) == Test::Scalar::Simple{ } ); + CHECK_NOTHROW( ( void ) spb::pb::deserialize< Test::Scalar::Empty >( + "\x0a\x08John Doe\x10\x7b\x1a\x11QXUeh@example.com\x22\x0c\x0A\x08" + "555-4321\x10\x01"sv ) ); + } + SUBCASE( "invalid" ) + { + SUBCASE( "wire type" ) + { + CHECK_THROWS( + ( void ) spb::pb::deserialize< Test::Scalar::OptInt32 >( "\x0a\x05hello"sv ) ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::OptInt32 >( + "\x0d\x00\x00\x28\x42"sv ) ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::OptString >( + "\x0d\x00\x00\x28\x42"sv ) ); + CHECK_THROWS( + ( void ) spb::pb::deserialize< Test::Scalar::Empty >( "\x0f\x05hello"sv ) ); + } + SUBCASE( "stream" ) + { + CHECK_THROWS( + ( void ) spb::pb::deserialize< Test::Recursive >( "\x0a\x05\x0a\x05hello"sv ) ); + CHECK_THROWS( + ( void ) spb::pb::deserialize< Test::Recursive >( "\x0a\x08\x0a\x05hello"sv ) ); + } + SUBCASE( "tag" ) + { + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::Empty >( + "\xff\xff\xff\xff\xff\xff\xff\xff\x01"sv ) ); + CHECK_THROWS( + ( void ) spb::pb::deserialize< Test::Scalar::Empty >( "\x07\x05hello"sv ) ); + } + } + SUBCASE( "options" ) + { + SUBCASE( "delimited" ) + { + CHECK( spb::pb::serialize( Test::Scalar::ReqString{ .value = "hello" }, + { .delimited = true } ) == "\x07\x0a\x05hello" ); + CHECK( spb::pb::serialize( Test::Scalar::Simple{ .value = "hello" }, + { .delimited = true } ) == "\x08\xA2\x06\x05hello" ); + CHECK( spb::pb::deserialize< Test::Scalar::Simple >( "\x08\xA2\x06\x05hello"sv, + { .delimited = true } ) == + Test::Scalar::Simple{ .value = "hello" } ); + CHECK_THROWS( ( void ) spb::pb::deserialize< Test::Scalar::Simple >( + "\x0a\x05hello"sv, { .delimited = true } ) ); + } + } + } +} diff --git a/3rdparty/simple-protobuf/test/proto/dependency.proto b/3rdparty/simple-protobuf/test/proto/dependency.proto new file mode 100644 index 0000000..5fe9b97 --- /dev/null +++ b/3rdparty/simple-protobuf/test/proto/dependency.proto @@ -0,0 +1,26 @@ +syntax = "proto2"; + +package UnitTest.dependency; + +message A { + message B { optional C c = 1; } + message C { optional int32 b = 1; } + message D { optional A a = 1; } + message E { + repeated F f = 1; + optional int32 b = 2; + } + message F { + repeated E e = 1; + optional int32 c = 2; + } + message G { + optional H h = 1; + optional int32 b = 2; + } + message H { + optional G g = 1; + optional int32 c = 2; + } + optional int32 a = 1; +} diff --git a/3rdparty/simple-protobuf/test/proto/desc.proto b/3rdparty/simple-protobuf/test/proto/desc.proto new file mode 100644 index 0000000..4f5cfb4 --- /dev/null +++ b/3rdparty/simple-protobuf/test/proto/desc.proto @@ -0,0 +1,1249 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Author: kenton@google.com (Kenton Varda) +// Based on original Protocol Buffers design by +// Sanjay Ghemawat, Jeff Dean, and others. +// +// The messages in this file describe the definitions found in .proto files. +// A valid .proto file can be translated directly to a FileDescriptorProto +// without any other information (e.g. without reading its imports). + +syntax = "proto2"; + +package google.protobuf; + +option go_package = "google.golang.org/protobuf/types/descriptorpb"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "DescriptorProtos"; +option csharp_namespace = "Google.Protobuf.Reflection"; +option objc_class_prefix = "GPB"; +option cc_enable_arenas = true; + +// descriptor.proto must be optimized for speed because reflection-based +// algorithms don't work during bootstrapping. +option optimize_for = SPEED; + +// The protocol compiler can output a FileDescriptorSet containing the .proto +// files it parses. +message FileDescriptorSet { + repeated FileDescriptorProto file = 1; +} + +// The full set of known editions. +enum Edition { + // A placeholder for an unknown edition value. + EDITION_UNKNOWN = 0; + + // A placeholder edition for specifying default behaviors *before* a feature + // was first introduced. This is effectively an "infinite past". + EDITION_LEGACY = 900; + + // Legacy syntax "editions". These pre-date editions, but behave much like + // distinct editions. These can't be used to specify the edition of proto + // files, but feature definitions must supply proto2/proto3 defaults for + // backwards compatibility. + EDITION_PROTO2 = 998; + EDITION_PROTO3 = 999; + + // Editions that have been released. The specific values are arbitrary and + // should not be depended on, but they will always be time-ordered for easy + // comparison. + EDITION_2023 = 1000; + EDITION_2024 = 1001; + + // Placeholder editions for testing feature resolution. These should not be + // used or relyed on outside of tests. + EDITION_1_TEST_ONLY = 1; + EDITION_2_TEST_ONLY = 2; + EDITION_99997_TEST_ONLY = 99997; + EDITION_99998_TEST_ONLY = 99998; + EDITION_99999_TEST_ONLY = 99999; + + // Placeholder for specifying unbounded edition support. This should only + // ever be used by plugins that can expect to never require any changes to + // support a new edition. + EDITION_MAX = 0x7FFFFFFF; +} + +// Describes a complete .proto file. +message FileDescriptorProto { + optional string name = 1; // file name, relative to root of source tree + optional string package = 2; // e.g. "foo", "foo.bar", etc. + + // Names of files imported by this file. + repeated string dependency = 3; + // Indexes of the public imported files in the dependency list above. + repeated int32 public_dependency = 10; + // Indexes of the weak imported files in the dependency list. + // For Google-internal migration only. Do not use. + repeated int32 weak_dependency = 11; + + // All top-level definitions in this file. + repeated DescriptorProto message_type = 4; + repeated EnumDescriptorProto enum_type = 5; + repeated ServiceDescriptorProto service = 6; + repeated FieldDescriptorProto extension = 7; + + optional FileOptions options = 8; + + // This field contains optional information about the original source code. + // You may safely remove this entire field without harming runtime + // functionality of the descriptors -- the information is needed only by + // development tools. + optional SourceCodeInfo source_code_info = 9; + + // The syntax of the proto file. + // The supported values are "proto2", "proto3", and "editions". + // + // If `edition` is present, this value must be "editions". + optional string syntax = 12; + + // The edition of the proto file. + optional Edition edition = 14; +} + +// Describes a message type. +message DescriptorProto { + optional string name = 1; + + repeated FieldDescriptorProto field = 2; + repeated FieldDescriptorProto extension = 6; + + repeated DescriptorProto nested_type = 3; + repeated EnumDescriptorProto enum_type = 4; + + message ExtensionRange { + optional int32 start = 1; // Inclusive. + optional int32 end = 2; // Exclusive. + + optional ExtensionRangeOptions options = 3; + } + repeated ExtensionRange extension_range = 5; + + repeated OneofDescriptorProto oneof_decl = 8; + + optional MessageOptions options = 7; + + // Range of reserved tag numbers. Reserved tag numbers may not be used by + // fields or extension ranges in the same message. Reserved ranges may + // not overlap. + message ReservedRange { + optional int32 start = 1; // Inclusive. + optional int32 end = 2; // Exclusive. + } + repeated ReservedRange reserved_range = 9; + // Reserved field names, which may not be used by fields in the same message. + // A given name may only be reserved once. + repeated string reserved_name = 10; +} + +message ExtensionRangeOptions { + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + message Declaration { + // The extension number declared within the extension range. + optional int32 number = 1; + + // The fully-qualified name of the extension field. There must be a leading + // dot in front of the full name. + optional string full_name = 2; + + // The fully-qualified type name of the extension field. Unlike + // Metadata.type, Declaration.type must have a leading dot for messages + // and enums. + optional string type = 3; + + // If true, indicates that the number is reserved in the extension range, + // and any extension field with the number will fail to compile. Set this + // when a declared extension field is deleted. + optional bool reserved = 5; + + // If true, indicates that the extension must be defined as repeated. + // Otherwise the extension must be defined as optional. + optional bool repeated = 6; + + reserved 4; // removed is_repeated + } + + // For external users: DO NOT USE. We are in the process of open sourcing + // extension declaration and executing internal cleanups before it can be + // used externally. + repeated Declaration declaration = 2 [retention = RETENTION_SOURCE]; + + // Any features defined in the specific edition. + optional FeatureSet features = 50; + + // The verification state of the extension range. + enum VerificationState { + // All the extensions of the range must be declared. + DECLARATION = 0; + UNVERIFIED = 1; + } + + // The verification state of the range. + // TODO: flip the default to DECLARATION once all empty ranges + // are marked as UNVERIFIED. + optional VerificationState verification = 3 + [default = UNVERIFIED, retention = RETENTION_SOURCE]; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +// Describes a field within a message. +message FieldDescriptorProto { + enum Type { + // 0 is reserved for errors. + // Order is weird for historical reasons. + TYPE_DOUBLE = 1; + TYPE_FLOAT = 2; + // Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT64 if + // negative values are likely. + TYPE_INT64 = 3; + TYPE_UINT64 = 4; + // Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT32 if + // negative values are likely. + TYPE_INT32 = 5; + TYPE_FIXED64 = 6; + TYPE_FIXED32 = 7; + TYPE_BOOL = 8; + TYPE_STRING = 9; + // Tag-delimited aggregate. + // Group type is deprecated and not supported after google.protobuf. However, Proto3 + // implementations should still be able to parse the group wire format and + // treat group fields as unknown fields. In Editions, the group wire format + // can be enabled via the `message_encoding` feature. + TYPE_GROUP = 10; + TYPE_MESSAGE = 11; // Length-delimited aggregate. + + // New in version 2. + TYPE_BYTES = 12; + TYPE_UINT32 = 13; + TYPE_ENUM = 14; + TYPE_SFIXED32 = 15; + TYPE_SFIXED64 = 16; + TYPE_SINT32 = 17; // Uses ZigZag encoding. + TYPE_SINT64 = 18; // Uses ZigZag encoding. + } + + enum Label { + // 0 is reserved for errors + LABEL_OPTIONAL = 1; + LABEL_REPEATED = 3; + // The required label is only allowed in google.protobuf. In proto3 and Editions + // it's explicitly prohibited. In Editions, the `field_presence` feature + // can be used to get this behavior. + LABEL_REQUIRED = 2; + } + + optional string name = 1; + optional int32 number = 3; + optional Label label = 4; + + // If type_name is set, this need not be set. If both this and type_name + // are set, this must be one of TYPE_ENUM, TYPE_MESSAGE or TYPE_GROUP. + optional Type type = 5; + + // For message and enum types, this is the name of the type. If the name + // starts with a '.', it is fully-qualified. Otherwise, C++-like scoping + // rules are used to find the type (i.e. first the nested types within this + // message are searched, then within the parent, on up to the root + // namespace). + optional string type_name = 6; + + // For extensions, this is the name of the type being extended. It is + // resolved in the same manner as type_name. + optional string extendee = 2; + + // For numeric types, contains the original text representation of the value. + // For booleans, "true" or "false". + // For strings, contains the default text contents (not escaped in any way). + // For bytes, contains the C escaped value. All bytes >= 128 are escaped. + optional string default_value = 7; + + // If set, gives the index of a oneof in the containing type's oneof_decl + // list. This field is a member of that oneof. + optional int32 oneof_index = 9; + + // JSON name of this field. The value is set by protocol compiler. If the + // user has set a "json_name" option on this field, that option's value + // will be used. Otherwise, it's deduced from the field's name by converting + // it to camelCase. + optional string json_name = 10; + + optional FieldOptions options = 8; + + // If true, this is a proto3 "optional". When a proto3 field is optional, it + // tracks presence regardless of field type. + // + // When proto3_optional is true, this field must belong to a oneof to signal + // to old proto3 clients that presence is tracked for this field. This oneof + // is known as a "synthetic" oneof, and this field must be its sole member + // (each proto3 optional field gets its own synthetic oneof). Synthetic oneofs + // exist in the descriptor only, and do not generate any API. Synthetic oneofs + // must be ordered after all "real" oneofs. + // + // For message fields, proto3_optional doesn't create any semantic change, + // since non-repeated message fields always track presence. However it still + // indicates the semantic detail of whether the user wrote "optional" or not. + // This can be useful for round-tripping the .proto file. For consistency we + // give message fields a synthetic oneof also, even though it is not required + // to track presence. This is especially important because the parser can't + // tell if a field is a message or an enum, so it must always create a + // synthetic oneof. + // + // Proto2 optional fields do not set this flag, because they already indicate + // optional with `LABEL_OPTIONAL`. + optional bool proto3_optional = 17; +} + +// Describes a oneof. +message OneofDescriptorProto { + optional string name = 1; + optional OneofOptions options = 2; +} + +// Describes an enum type. +message EnumDescriptorProto { + optional string name = 1; + + repeated EnumValueDescriptorProto value = 2; + + optional EnumOptions options = 3; + + // Range of reserved numeric values. Reserved values may not be used by + // entries in the same enum. Reserved ranges may not overlap. + // + // Note that this is distinct from DescriptorProto.ReservedRange in that it + // is inclusive such that it can appropriately represent the entire int32 + // domain. + message EnumReservedRange { + optional int32 start = 1; // Inclusive. + optional int32 end = 2; // Inclusive. + } + + // Range of reserved numeric values. Reserved numeric values may not be used + // by enum values in the same enum declaration. Reserved ranges may not + // overlap. + repeated EnumReservedRange reserved_range = 4; + + // Reserved enum value names, which may not be reused. A given name may only + // be reserved once. + repeated string reserved_name = 5; +} + +// Describes a value within an enum. +message EnumValueDescriptorProto { + optional string name = 1; + optional int32 number = 2; + + optional EnumValueOptions options = 3; +} + +// Describes a service. +message ServiceDescriptorProto { + optional string name = 1; + repeated MethodDescriptorProto method = 2; + + optional ServiceOptions options = 3; +} + +// Describes a method of a service. +message MethodDescriptorProto { + optional string name = 1; + + // Input and output type names. These are resolved in the same way as + // FieldDescriptorProto.type_name, but must refer to a message type. + optional string input_type = 2; + optional string output_type = 3; + + optional MethodOptions options = 4; + + // Identifies if client streams multiple client messages + optional bool client_streaming = 5 [default = false]; + // Identifies if server streams multiple server messages + optional bool server_streaming = 6 [default = false]; +} + +// =================================================================== +// Options + +// Each of the definitions above may have "options" attached. These are +// just annotations which may cause code to be generated slightly differently +// or may contain hints for code that manipulates protocol messages. +// +// Clients may define custom options as extensions of the *Options messages. +// These extensions may not yet be known at parsing time, so the parser cannot +// store the values in them. Instead it stores them in a field in the *Options +// message called uninterpreted_option. This field must have the same name +// across all *Options messages. We then use this field to populate the +// extensions when we build a descriptor, at which point all protos have been +// parsed and so all extensions are known. +// +// Extension numbers for custom options may be chosen as follows: +// * For options which will only be used within a single application or +// organization, or for experimental options, use field numbers 50000 +// through 99999. It is up to you to ensure that you do not use the +// same number for multiple options. +// * For options which will be published and used publicly by multiple +// independent entities, e-mail protobuf-global-extension-registry@google.com +// to reserve extension numbers. Simply provide your project name (e.g. +// Objective-C plugin) and your project website (if available) -- there's no +// need to explain how you intend to use them. Usually you only need one +// extension number. You can declare multiple options with only one extension +// number by putting them in a sub-message. See the Custom Options section of +// the docs for examples: +// https://developers.google.com/protocol-buffers/docs/proto#options +// If this turns out to be popular, a web service will be set up +// to automatically assign option numbers. + +message FileOptions { + + // Sets the Java package where classes generated from this .proto will be + // placed. By default, the proto package is used, but this is often + // inappropriate because proto packages do not normally start with backwards + // domain names. + optional string java_package = 1; + + // Controls the name of the wrapper Java class generated for the .proto file. + // That class will always contain the .proto file's getDescriptor() method as + // well as any top-level extensions defined in the .proto file. + // If java_multiple_files is disabled, then all the other classes from the + // .proto file will be nested inside the single wrapper outer class. + optional string java_outer_classname = 8; + + // If enabled, then the Java code generator will generate a separate .java + // file for each top-level message, enum, and service defined in the .proto + // file. Thus, these types will *not* be nested inside the wrapper class + // named by java_outer_classname. However, the wrapper class will still be + // generated to contain the file's getDescriptor() method as well as any + // top-level extensions defined in the file. + optional bool java_multiple_files = 10 [default = false]; + + // This option does nothing. + optional bool java_generate_equals_and_hash = 20 [deprecated=true]; + + // A proto2 file can set this to true to opt in to UTF-8 checking for Java, + // which will throw an exception if invalid UTF-8 is parsed from the wire or + // assigned to a string field. + // + // TODO: clarify exactly what kinds of field types this option + // applies to, and update these docs accordingly. + // + // Proto3 files already perform these checks. Setting the option explicitly to + // false has no effect: it cannot be used to opt proto3 files out of UTF-8 + // checks. + optional bool java_string_check_utf8 = 27 [default = false]; + + // Generated classes can be optimized for speed or code size. + enum OptimizeMode { + SPEED = 1; // Generate complete code for parsing, serialization, + // etc. + CODE_SIZE = 2; // Use ReflectionOps to implement these methods. + LITE_RUNTIME = 3; // Generate code using MessageLite and the lite runtime. + } + optional OptimizeMode optimize_for = 9 [default = SPEED]; + + // Sets the Go package where structs generated from this .proto will be + // placed. If omitted, the Go package will be derived from the following: + // - The basename of the package import path, if provided. + // - Otherwise, the package statement in the .proto file, if present. + // - Otherwise, the basename of the .proto file, without extension. + optional string go_package = 11; + + // Should generic services be generated in each language? "Generic" services + // are not specific to any particular RPC system. They are generated by the + // main code generators in each language (without additional plugins). + // Generic services were the only kind of service generation supported by + // early versions of google.protobuf. + // + // Generic services are now considered deprecated in favor of using plugins + // that generate code specific to your particular RPC system. Therefore, + // these default to false. Old code which depends on generic services should + // explicitly set them to true. + optional bool cc_generic_services = 16 [default = false]; + optional bool java_generic_services = 17 [default = false]; + optional bool py_generic_services = 18 [default = false]; + reserved 42; // removed php_generic_services + + // Is this file deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for everything in the file, or it will be completely ignored; in the very + // least, this is a formalization for deprecating files. + optional bool deprecated = 23 [default = false]; + + // Enables the use of arenas for the proto messages in this file. This applies + // only to generated classes for C++. + optional bool cc_enable_arenas = 31 [default = true]; + + // Sets the objective c class prefix which is prepended to all objective c + // generated classes from this .proto. There is no default. + optional string objc_class_prefix = 36; + + // Namespace for generated classes; defaults to the package. + optional string csharp_namespace = 37; + + // By default Swift generators will take the proto package and CamelCase it + // replacing '.' with underscore and use that to prefix the types/symbols + // defined. When this options is provided, they will use this value instead + // to prefix the types/symbols defined. + optional string swift_prefix = 39; + + // Sets the php class prefix which is prepended to all php generated classes + // from this .proto. Default is empty. + optional string php_class_prefix = 40; + + // Use this option to change the namespace of php generated classes. Default + // is empty. When this option is empty, the package name will be used for + // determining the namespace. + optional string php_namespace = 41; + + // Use this option to change the namespace of php generated metadata classes. + // Default is empty. When this option is empty, the proto file name will be + // used for determining the namespace. + optional string php_metadata_namespace = 44; + + // Use this option to change the package of ruby generated classes. Default + // is empty. When this option is not set, the package name will be used for + // determining the ruby package. + optional string ruby_package = 45; + + // Any features defined in the specific edition. + optional FeatureSet features = 50; + + // The parser stores options it doesn't recognize here. + // See the documentation for the "Options" section above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. + // See the documentation for the "Options" section above. + extensions 1000 to max; + + reserved 38; +} + +message MessageOptions { + // Set true to use the old proto1 MessageSet wire format for extensions. + // This is provided for backwards-compatibility with the MessageSet wire + // format. You should not use this for any other reason: It's less + // efficient, has fewer features, and is more complicated. + // + // The message must be defined exactly as follows: + // message Foo { + // option message_set_wire_format = true; + // extensions 4 to max; + // } + // Note that the message cannot have any defined fields; MessageSets only + // have extensions. + // + // All extensions of your type must be singular messages; e.g. they cannot + // be int32s, enums, or repeated messages. + // + // Because this is an option, the above two restrictions are not enforced by + // the protocol compiler. + optional bool message_set_wire_format = 1 [default = false]; + + // Disables the generation of the standard "descriptor()" accessor, which can + // conflict with a field of the same name. This is meant to make migration + // from proto1 easier; new code should avoid fields named "descriptor". + optional bool no_standard_descriptor_accessor = 2 [default = false]; + + // Is this message deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the message, or it will be completely ignored; in the very least, + // this is a formalization for deprecating messages. + optional bool deprecated = 3 [default = false]; + + reserved 4, 5, 6; + + // Whether the message is an automatically generated map entry type for the + // maps field. + // + // For maps fields: + // map map_field = 1; + // The parsed descriptor looks like: + // message MapFieldEntry { + // option map_entry = true; + // optional KeyType key = 1; + // optional ValueType value = 2; + // } + // repeated MapFieldEntry map_field = 1; + // + // Implementations may choose not to generate the map_entry=true message, but + // use a native map in the target language to hold the keys and values. + // The reflection APIs in such implementations still need to work as + // if the field is a repeated message field. + // + // NOTE: Do not set the option in .proto files. Always use the maps syntax + // instead. The option should only be implicitly set by the proto compiler + // parser. + optional bool map_entry = 7; + + reserved 8; // javalite_serializable + reserved 9; // javanano_as_lite + + // Enable the legacy handling of JSON field name conflicts. This lowercases + // and strips underscored from the fields before comparison in proto3 only. + // The new behavior takes `json_name` into account and applies to proto2 as + // well. + // + // This should only be used as a temporary measure against broken builds due + // to the change in behavior for JSON field name conflicts. + // + // TODO This is legacy behavior we plan to remove once downstream + // teams have had time to migrate. + optional bool deprecated_legacy_json_field_conflicts = 11 [deprecated = true]; + + // Any features defined in the specific edition. + optional FeatureSet features = 12; + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +message FieldOptions { + // The ctype option instructs the C++ code generator to use a different + // representation of the field than it normally would. See the specific + // options below. This option is only implemented to support use of + // [ctype=CORD] and [ctype=STRING] (the default) on non-repeated fields of + // type "bytes" in the open source release -- sorry, we'll try to include + // other types in a future version! + optional CType ctype = 1 [default = STRING]; + enum CType { + // Default mode. + STRING = 0; + + // The option [ctype=CORD] may be applied to a non-repeated field of type + // "bytes". It indicates that in C++, the data should be stored in a Cord + // instead of a string. For very large strings, this may reduce memory + // fragmentation. It may also allow better performance when parsing from a + // Cord, or when parsing with aliasing enabled, as the parsed Cord may then + // alias the original buffer. + CORD = 1; + + STRING_PIECE = 2; + } + // The packed option can be enabled for repeated primitive fields to enable + // a more efficient representation on the wire. Rather than repeatedly + // writing the tag and type for each element, the entire array is encoded as + // a single length-delimited blob. In proto3, only explicit setting it to + // false will avoid using packed encoding. This option is prohibited in + // Editions, but the `repeated_field_encoding` feature can be used to control + // the behavior. + optional bool packed = 2; + + // The jstype option determines the JavaScript type used for values of the + // field. The option is permitted only for 64 bit integral and fixed types + // (int64, uint64, sint64, fixed64, sfixed64). A field with jstype JS_STRING + // is represented as JavaScript string, which avoids loss of precision that + // can happen when a large value is converted to a floating point JavaScript. + // Specifying JS_NUMBER for the jstype causes the generated JavaScript code to + // use the JavaScript "number" type. The behavior of the default option + // JS_NORMAL is implementation dependent. + // + // This option is an enum to permit additional types to be added, e.g. + // goog.math.Integer. + optional JSType jstype = 6 [default = JS_NORMAL]; + enum JSType { + // Use the default type. + JS_NORMAL = 0; + + // Use JavaScript strings. + JS_STRING = 1; + + // Use JavaScript numbers. + JS_NUMBER = 2; + } + + // Should this field be parsed lazily? Lazy applies only to message-type + // fields. It means that when the outer message is initially parsed, the + // inner message's contents will not be parsed but instead stored in encoded + // form. The inner message will actually be parsed when it is first accessed. + // + // This is only a hint. Implementations are free to choose whether to use + // eager or lazy parsing regardless of the value of this option. However, + // setting this option true suggests that the protocol author believes that + // using lazy parsing on this field is worth the additional bookkeeping + // overhead typically needed to implement it. + // + // This option does not affect the public interface of any generated code; + // all method signatures remain the same. Furthermore, thread-safety of the + // interface is not affected by this option; const methods remain safe to + // call from multiple threads concurrently, while non-const methods continue + // to require exclusive access. + // + // Note that lazy message fields are still eagerly verified to check + // ill-formed wireformat or missing required fields. Calling IsInitialized() + // on the outer message would fail if the inner message has missing required + // fields. Failed verification would result in parsing failure (except when + // uninitialized messages are acceptable). + optional bool lazy = 5 [default = false]; + + // unverified_lazy does no correctness checks on the byte stream. This should + // only be used where lazy with verification is prohibitive for performance + // reasons. + optional bool unverified_lazy = 15 [default = false]; + + // Is this field deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for accessors, or it will be completely ignored; in the very least, this + // is a formalization for deprecating fields. + optional bool deprecated = 3 [default = false]; + + // For Google-internal migration only. Do not use. + optional bool weak = 10 [default = false]; + + // Indicate that the field value should not be printed out when using debug + // formats, e.g. when the field contains sensitive credentials. + optional bool debug_redact = 16 [default = false]; + + // If set to RETENTION_SOURCE, the option will be omitted from the binary. + // Note: as of January 2023, support for this is in progress and does not yet + // have an effect (b/264593489). + enum OptionRetention { + RETENTION_UNKNOWN = 0; + RETENTION_RUNTIME = 1; + RETENTION_SOURCE = 2; + } + + optional OptionRetention retention = 17; + + // This indicates the types of entities that the field may apply to when used + // as an option. If it is unset, then the field may be freely used as an + // option on any kind of entity. Note: as of January 2023, support for this is + // in progress and does not yet have an effect (b/264593489). + enum OptionTargetType { + TARGET_TYPE_UNKNOWN = 0; + TARGET_TYPE_FILE = 1; + TARGET_TYPE_EXTENSION_RANGE = 2; + TARGET_TYPE_MESSAGE = 3; + TARGET_TYPE_FIELD = 4; + TARGET_TYPE_ONEOF = 5; + TARGET_TYPE_ENUM = 6; + TARGET_TYPE_ENUM_ENTRY = 7; + TARGET_TYPE_SERVICE = 8; + TARGET_TYPE_METHOD = 9; + } + + repeated OptionTargetType targets = 19; + + message EditionDefault { + optional Edition edition = 3; + optional string value = 2; // Textproto value. + } + repeated EditionDefault edition_defaults = 20; + + // Any features defined in the specific edition. + optional FeatureSet features = 21; + + // Information about the support window of a feature. + message FeatureSupport { + // The edition that this feature was first available in. In editions + // earlier than this one, the default assigned to EDITION_LEGACY will be + // used, and proto files will not be able to override it. + optional Edition edition_introduced = 1; + + // The edition this feature becomes deprecated in. Using this after this + // edition may trigger warnings. + optional Edition edition_deprecated = 2; + + // The deprecation warning text if this feature is used after the edition it + // was marked deprecated in. + optional string deprecation_warning = 3; + + // The edition this feature is no longer available in. In editions after + // this one, the last default assigned will be used, and proto files will + // not be able to override it. + optional Edition edition_removed = 4; + } + optional FeatureSupport feature_support = 22; + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; + + reserved 4; // removed jtype + reserved 18; // reserve target, target_obsolete_do_not_use +} + +message OneofOptions { + // Any features defined in the specific edition. + optional FeatureSet features = 1; + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +message EnumOptions { + + // Set this option to true to allow mapping different tag names to the same + // value. + optional bool allow_alias = 2; + + // Is this enum deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the enum, or it will be completely ignored; in the very least, this + // is a formalization for deprecating enums. + optional bool deprecated = 3 [default = false]; + + reserved 5; // javanano_as_lite + + // Enable the legacy handling of JSON field name conflicts. This lowercases + // and strips underscored from the fields before comparison in proto3 only. + // The new behavior takes `json_name` into account and applies to proto2 as + // well. + // TODO Remove this legacy behavior once downstream teams have + // had time to migrate. + optional bool deprecated_legacy_json_field_conflicts = 6 [deprecated = true]; + + // Any features defined in the specific edition. + optional FeatureSet features = 7; + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +message EnumValueOptions { + // Is this enum value deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the enum value, or it will be completely ignored; in the very least, + // this is a formalization for deprecating enum values. + optional bool deprecated = 1 [default = false]; + + // Any features defined in the specific edition. + optional FeatureSet features = 2; + + // Indicate that fields annotated with this enum value should not be printed + // out when using debug formats, e.g. when the field contains sensitive + // credentials. + optional bool debug_redact = 3 [default = false]; + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +message ServiceOptions { + + // Any features defined in the specific edition. + optional FeatureSet features = 34; + + // Note: Field numbers 1 through 32 are reserved for Google's internal RPC + // framework. We apologize for hoarding these numbers to ourselves, but + // we were already using them long before we decided to release Protocol + // Buffers. + + // Is this service deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the service, or it will be completely ignored; in the very least, + // this is a formalization for deprecating services. + optional bool deprecated = 33 [default = false]; + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +message MethodOptions { + + // Note: Field numbers 1 through 32 are reserved for Google's internal RPC + // framework. We apologize for hoarding these numbers to ourselves, but + // we were already using them long before we decided to release Protocol + // Buffers. + + // Is this method deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the method, or it will be completely ignored; in the very least, + // this is a formalization for deprecating methods. + optional bool deprecated = 33 [default = false]; + + // Is this method side-effect-free (or safe in HTTP parlance), or idempotent, + // or neither? HTTP based RPC implementation may choose GET verb for safe + // methods, and PUT verb for idempotent methods instead of the default POST. + enum IdempotencyLevel { + IDEMPOTENCY_UNKNOWN = 0; + NO_SIDE_EFFECTS = 1; // implies idempotent + IDEMPOTENT = 2; // idempotent, but may have side effects + } + optional IdempotencyLevel idempotency_level = 34 + [default = IDEMPOTENCY_UNKNOWN]; + + // Any features defined in the specific edition. + optional FeatureSet features = 35; + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +// A message representing a option the parser does not recognize. This only +// appears in options protos created by the compiler::Parser class. +// DescriptorPool resolves these when building Descriptor objects. Therefore, +// options protos in descriptor objects (e.g. returned by Descriptor::options(), +// or produced by Descriptor::CopyTo()) will never have UninterpretedOptions +// in them. +message UninterpretedOption { + // The name of the uninterpreted option. Each string represents a segment in + // a dot-separated name. is_extension is true iff a segment represents an + // extension (denoted with parentheses in options specs in .proto files). + // E.g.,{ ["foo", false], ["bar.baz", true], ["moo", false] } represents + // "foo.(bar.baz).moo". + message NamePart { + required string name_part = 1; + required bool is_extension = 2; + } + repeated NamePart name = 2; + + // The value of the uninterpreted option, in whatever type the tokenizer + // identified it as during parsing. Exactly one of these should be set. + optional string identifier_value = 3; + optional uint64 positive_int_value = 4; + optional int64 negative_int_value = 5; + optional double double_value = 6; + optional bytes string_value = 7; + optional string aggregate_value = 8; +} + +// =================================================================== +// Features + +// TODO Enums in C++ gencode (and potentially other languages) are +// not well scoped. This means that each of the feature enums below can clash +// with each other. The short names we've chosen maximize call-site +// readability, but leave us very open to this scenario. A future feature will +// be designed and implemented to handle this, hopefully before we ever hit a +// conflict here. +message FeatureSet { + enum FieldPresence { + FIELD_PRESENCE_UNKNOWN = 0; + EXPLICIT = 1; + IMPLICIT = 2; + LEGACY_REQUIRED = 3; + } + optional FieldPresence field_presence = 1 [ + retention = RETENTION_RUNTIME, + targets = TARGET_TYPE_FIELD, + targets = TARGET_TYPE_FILE + + ]; + + enum EnumType { + ENUM_TYPE_UNKNOWN = 0; + OPEN = 1; + CLOSED = 2; + } + optional EnumType enum_type = 2 [ + retention = RETENTION_RUNTIME, + targets = TARGET_TYPE_ENUM, + targets = TARGET_TYPE_FILE + ]; + + enum RepeatedFieldEncoding { + REPEATED_FIELD_ENCODING_UNKNOWN = 0; + PACKED = 1; + EXPANDED = 2; + } + optional RepeatedFieldEncoding repeated_field_encoding = 3 [ + retention = RETENTION_RUNTIME, + targets = TARGET_TYPE_FIELD, + targets = TARGET_TYPE_FILE + ]; + + enum Utf8Validation { + UTF8_VALIDATION_UNKNOWN = 0; + VERIFY = 2; + NONE = 3; + } + optional Utf8Validation utf8_validation = 4 [ + retention = RETENTION_RUNTIME, + targets = TARGET_TYPE_FIELD, + targets = TARGET_TYPE_FILE + ]; + + enum MessageEncoding { + MESSAGE_ENCODING_UNKNOWN = 0; + LENGTH_PREFIXED = 1; + DELIMITED = 2; + } + optional MessageEncoding message_encoding = 5 [ + retention = RETENTION_RUNTIME, + targets = TARGET_TYPE_FIELD, + targets = TARGET_TYPE_FILE + ]; + + enum JsonFormat { + JSON_FORMAT_UNKNOWN = 0; + ALLOW = 1; + LEGACY_BEST_EFFORT = 2; + } + optional JsonFormat json_format = 6 [ + retention = RETENTION_RUNTIME, + targets = TARGET_TYPE_MESSAGE, + targets = TARGET_TYPE_ENUM, + targets = TARGET_TYPE_FILE + ]; + + reserved 999; + + extensions 1000; // for Protobuf C++ + extensions 1001; // for Protobuf Java + extensions 1002; // for Protobuf Go + + extensions 9990; // for deprecated Java Proto1 + + extensions 9995 to 9999; // For internal testing + extensions 10000; // for https://github.com/bufbuild/protobuf-es +} + +// A compiled specification for the defaults of a set of features. These +// messages are generated from FeatureSet extensions and can be used to seed +// feature resolution. The resolution with this object becomes a simple search +// for the closest matching edition, followed by proto merges. +message FeatureSetDefaults { + // A map from every known edition with a unique set of defaults to its + // defaults. Not all editions may be contained here. For a given edition, + // the defaults at the closest matching edition ordered at or before it should + // be used. This field must be in strict ascending order by edition. + message FeatureSetEditionDefault { + optional Edition edition = 3; + + // Defaults of features that can be overridden in this edition. + optional FeatureSet overridable_features = 4; + + // Defaults of features that can't be overridden in this edition. + optional FeatureSet fixed_features = 5; + + // TODO Deprecate and remove this field, which is just the + // above two merged. + optional FeatureSet features = 2; + } + repeated FeatureSetEditionDefault defaults = 1; + + // The minimum supported edition (inclusive) when this was constructed. + // Editions before this will not have defaults. + optional Edition minimum_edition = 4; + + // The maximum known edition (inclusive) when this was constructed. Editions + // after this will not have reliable defaults. + optional Edition maximum_edition = 5; +} + +// =================================================================== +// Optional source code info + +// Encapsulates information about the original source file from which a +// FileDescriptorProto was generated. +message SourceCodeInfo { + // A Location identifies a piece of source code in a .proto file which + // corresponds to a particular definition. This information is intended + // to be useful to IDEs, code indexers, documentation generators, and similar + // tools. + // + // For example, say we have a file like: + // message Foo { + // optional string foo = 1; + // } + // Let's look at just the field definition: + // optional string foo = 1; + // ^ ^^ ^^ ^ ^^^ + // a bc de f ghi + // We have the following locations: + // span path represents + // [a,i) [ 4, 0, 2, 0 ] The whole field definition. + // [a,b) [ 4, 0, 2, 0, 4 ] The label (optional). + // [c,d) [ 4, 0, 2, 0, 5 ] The type (string). + // [e,f) [ 4, 0, 2, 0, 1 ] The name (foo). + // [g,h) [ 4, 0, 2, 0, 3 ] The number (1). + // + // Notes: + // - A location may refer to a repeated field itself (i.e. not to any + // particular index within it). This is used whenever a set of elements are + // logically enclosed in a single code segment. For example, an entire + // extend block (possibly containing multiple extension definitions) will + // have an outer location whose path refers to the "extensions" repeated + // field without an index. + // - Multiple locations may have the same path. This happens when a single + // logical declaration is spread out across multiple places. The most + // obvious example is the "extend" block again -- there may be multiple + // extend blocks in the same scope, each of which will have the same path. + // - A location's span is not always a subset of its parent's span. For + // example, the "extendee" of an extension declaration appears at the + // beginning of the "extend" block and is shared by all extensions within + // the block. + // - Just because a location's span is a subset of some other location's span + // does not mean that it is a descendant. For example, a "group" defines + // both a type and a field in a single declaration. Thus, the locations + // corresponding to the type and field and their components will overlap. + // - Code which tries to interpret locations should probably be designed to + // ignore those that it doesn't understand, as more types of locations could + // be recorded in the future. + repeated Location location = 1; + message Location { + // Identifies which part of the FileDescriptorProto was defined at this + // location. + // + // Each element is a field number or an index. They form a path from + // the root FileDescriptorProto to the place where the definition appears. + // For example, this path: + // [ 4, 3, 2, 7, 1 ] + // refers to: + // file.message_type(3) // 4, 3 + // .field(7) // 2, 7 + // .name() // 1 + // This is because FileDescriptorProto.message_type has field number 4: + // repeated DescriptorProto message_type = 4; + // and DescriptorProto.field has field number 2: + // repeated FieldDescriptorProto field = 2; + // and FieldDescriptorProto.name has field number 1: + // optional string name = 1; + // + // Thus, the above path gives the location of a field name. If we removed + // the last element: + // [ 4, 3, 2, 7 ] + // this path refers to the whole field declaration (from the beginning + // of the label to the terminating semicolon). + repeated int32 path = 1 [packed = true]; + + // Always has exactly three or four elements: start line, start column, + // end line (optional, otherwise assumed same as start line), end column. + // These are packed into a single field for efficiency. Note that line + // and column numbers are zero-based -- typically you will want to add + // 1 to each before displaying to a user. + repeated int32 span = 2 [packed = true]; + + // If this SourceCodeInfo represents a complete declaration, these are any + // comments appearing before and after the declaration which appear to be + // attached to the declaration. + // + // A series of line comments appearing on consecutive lines, with no other + // tokens appearing on those lines, will be treated as a single comment. + // + // leading_detached_comments will keep paragraphs of comments that appear + // before (but not connected to) the current element. Each paragraph, + // separated by empty lines, will be one comment element in the repeated + // field. + // + // Only the comment content is provided; comment markers (e.g. //) are + // stripped out. For block comments, leading whitespace and an asterisk + // will be stripped from the beginning of each line other than the first. + // Newlines are included in the output. + // + // Examples: + // + // optional int32 foo = 1; // Comment attached to foo. + // // Comment attached to bar. + // optional int32 bar = 2; + // + // optional string baz = 3; + // // Comment attached to baz. + // // Another line attached to baz. + // + // // Comment attached to moo. + // // + // // Another line attached to moo. + // optional double moo = 4; + // + // // Detached comment for corge. This is not leading or trailing comments + // // to moo or corge because there are blank lines separating it from + // // both. + // + // // Detached comment for corge paragraph 2. + // + // optional string corge = 5; + // /* Block comment attached + // * to corge. Leading asterisks + // * will be removed. */ + // /* Block comment attached to + // * grault. */ + // optional int32 grault = 6; + // + // // ignored detached comments. + optional string leading_comments = 3; + optional string trailing_comments = 4; + repeated string leading_detached_comments = 6; + } +} + +// Describes the relationship between generated code and its original source +// file. A GeneratedCodeInfo message is associated with only one generated +// source file, but may contain references to different source .proto files. +message GeneratedCodeInfo { + // An Annotation connects some span of text in generated code to an element + // of its generating .proto file. + repeated Annotation annotation = 1; + message Annotation { + // Identifies the element in the original source .proto file. This field + // is formatted the same as SourceCodeInfo.Location.path. + repeated int32 path = 1 [packed = true]; + + // Identifies the filesystem path to the original source .proto. + optional string source_file = 2; + + // Identifies the starting offset in bytes in the generated code + // that relates to the identified object. + optional int32 begin = 3; + + // Identifies the ending offset in bytes in the generated code that + // relates to the identified object. The end offset should be one past + // the last relevant byte (so the length of the text = end - begin). + optional int32 end = 4; + + // Represents the identified object's effect on the element in the original + // .proto file. + enum Semantic { + // There is no effect or the effect is indescribable. + NONE = 0; + // The element is set or otherwise mutated. + SET = 1; + // An alias to the element is returned. + ALIAS = 2; + } + optional Semantic semantic = 5; + } +} diff --git a/3rdparty/simple-protobuf/test/proto/enum.proto b/3rdparty/simple-protobuf/test/proto/enum.proto new file mode 100644 index 0000000..069f509 --- /dev/null +++ b/3rdparty/simple-protobuf/test/proto/enum.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; + +package example; + +enum SomeEnum { + SOME_ENUM_UNDEFINED = 0; + SOME_ENUM_ONE = 1; + SOME_ENUM_TWO = 2; + SOME_ENUM_THREE = 3; + SOME_ENUM_FOUR = 4; + SOME_ENUM_FIVE = 5; +}; + +message SomeMessage { repeated SomeEnum values = 1; }; \ No newline at end of file diff --git a/3rdparty/simple-protobuf/test/proto/etl/etl-person.proto.in b/3rdparty/simple-protobuf/test/proto/etl/etl-person.proto.in new file mode 100644 index 0000000..6a6e8c1 --- /dev/null +++ b/3rdparty/simple-protobuf/test/proto/etl/etl-person.proto.in @@ -0,0 +1,31 @@ +syntax = "proto2"; + +//[[ repeated.type = "etl::vector<$,60>"]] +//[[ repeated.include = ""]] + +//[[ string.type = "etl::string<32>"]] +//[[ string.include = ""]] + +package PhoneBook.Etl@PB_PACKAGE@; + +message Person { + //[[ string.type = "std::string"]] + optional string name = 1; + optional int32 id = 2; // Unique ID number for this person. + //[[ string.type = "etl::string<64>"]] + optional string email = 3; + + enum PhoneType { + MOBILE = 0; + HOME = 1; + WORK = 2; + } + + //[[ string.type = "etl::string<16>"]] + message PhoneNumber { + required string number = 1; // phone number is always required + optional PhoneType type = 2; + } + // all registered phones + repeated PhoneNumber phones = 4; +} diff --git a/3rdparty/simple-protobuf/test/proto/etl/etl-scalar.proto.in b/3rdparty/simple-protobuf/test/proto/etl/etl-scalar.proto.in new file mode 100644 index 0000000..f6840a5 --- /dev/null +++ b/3rdparty/simple-protobuf/test/proto/etl/etl-scalar.proto.in @@ -0,0 +1,283 @@ +syntax = "proto2"; + +//[[ repeated.type = "etl::vector<$,64>"]] +//[[ repeated.include = ""]] + +//[[ bytes.type = "etl::vector<$,8>"]] +//[[ bytes.include = ""]] + +//[[ string.type = "etl::string<16>"]] +//[[ string.include = ""]] + +package Test.Etl.Scalar@PB_PACKAGE@; + +message Simple +{ + optional string value = 100; +} + +message OptInt32 +{ + optional int32 value = 1; +} +message ReqInt32 +{ + required int32 value = 1; +} +message RepInt32 +{ + repeated int32 value = 1; +} +message RepPackInt32 +{ + repeated int32 value = 1 [ packed = true ]; +} + +message OptInt64 +{ + optional int64 value = 1; +} +message ReqInt64 +{ + required int64 value = 1; +} +message RepInt64 +{ + repeated int64 value = 1; +} +message RepPackInt64 +{ + repeated int64 value = 1 [ packed = true ]; +} + +message OptUint32 +{ + optional uint32 value = 1; +} +message ReqUint32 +{ + required uint32 value = 1; +} +message RepUint32 +{ + repeated uint32 value = 1; +} +message RepPackUint32 +{ + repeated uint32 value = 1 [ packed = true ]; +} + +message OptUint64 +{ + optional uint64 value = 1; +} +message ReqUint64 +{ + required uint64 value = 1; +} +message RepUint64 +{ + repeated uint64 value = 1; +} +message RepPackUint64 +{ + repeated uint64 value = 1 [ packed = true ]; +} + +message OptSint32 +{ + optional sint32 value = 1; +} +message ReqSint32 +{ + required sint32 value = 1; +} +message RepSint32 +{ + repeated sint32 value = 1; +} +message RepPackSint32 +{ + repeated sint32 value = 1 [ packed = true ]; +} + +message OptSint64 +{ + optional sint64 value = 1; +} +message ReqSint64 +{ + required sint64 value = 1; +} +message RepSint64 +{ + repeated sint64 value = 1; +} +message RepPackSint64 +{ + repeated sint64 value = 1 [ packed = true ]; +} + +message OptFixed32 +{ + optional fixed32 value = 1; +} +message ReqFixed32 +{ + required fixed32 value = 1; +} +message RepFixed32 +{ + repeated fixed32 value = 1; +} +message RepPackFixed32 +{ + repeated fixed32 value = 1 [ packed = true ]; +} + +message OptFixed64 +{ + optional fixed64 value = 1; +} +message ReqFixed64 +{ + required fixed64 value = 1; +} +message RepFixed64 +{ + repeated fixed64 value = 1; +} +message RepPackFixed64 +{ + repeated fixed64 value = 1 [ packed = true ]; +} + +message OptSfixed32 +{ + optional sfixed32 value = 1; +} +message ReqSfixed32 +{ + required sfixed32 value = 1; +} +message RepSfixed32 +{ + repeated sfixed32 value = 1; +} +message RepPackSfixed32 +{ + repeated sfixed32 value = 1 [ packed = true ]; +} + +message OptSfixed64 +{ + optional sfixed64 value = 1; +} +message ReqSfixed64 +{ + required sfixed64 value = 1; +} +message RepSfixed64 +{ + repeated sfixed64 value = 1; +} +message RepPackSfixed64 +{ + repeated sfixed64 value = 1 [ packed = true ]; +} + +message OptBool +{ + optional bool value = 1; +} +message ReqBool +{ + required bool value = 1; +} +message RepBool +{ + repeated bool value = 1; +} + +message RepPackBool +{ + repeated bool value = 1 [ packed = true ]; +} + +message OptFloat +{ + optional float value = 1; +} +message ReqFloat +{ + required float value = 1; +} +message RepFloat +{ + repeated float value = 1; +} + +message OptDouble +{ + optional double value = 1; +} +message ReqDouble +{ + required double value = 1; +} +message RepDouble +{ + repeated double value = 1; +} + +message OptString +{ + optional string value = 1; +} +message ReqString +{ + required string value = 1; +} +message RepString +{ + repeated string value = 1; +} + +message OptStringView +{ + optional string value = 1 [ctype = STRING_PIECE]; +} +message ReqStringView +{ + required string value = 1 [ctype = STRING_PIECE]; +} +message RepStringView +{ + repeated string value = 1 [ctype = STRING_PIECE]; +} + +message OptBytes +{ + optional bytes value = 1; +} +message ReqBytes +{ + required bytes value = 1; +} +message RepBytes +{ + repeated bytes value = 1; +} + +message OptBytesView +{ + optional bytes value = 1 [ctype = STRING_PIECE]; +} +message ReqBytesView +{ + required bytes value = 1 [ctype = STRING_PIECE]; +} +message RepBytesView +{ + repeated bytes value = 1 [ctype = STRING_PIECE]; +} diff --git a/3rdparty/simple-protobuf/test/proto/map.proto b/3rdparty/simple-protobuf/test/proto/map.proto new file mode 100644 index 0000000..3fee49a --- /dev/null +++ b/3rdparty/simple-protobuf/test/proto/map.proto @@ -0,0 +1,97 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +syntax = "proto2"; + +option cc_enable_arenas = true; + +// We don't put this in a package within proto2 because we need to make sure +// that the generated code doesn't depend on being in the proto2 namespace. +// In map_test_util.h we do "using namespace unittest = protobuf_unittest". +package protobuf_unittest; + +enum Proto2MapEnum { + PROTO2_MAP_ENUM_FOO = 0; + PROTO2_MAP_ENUM_BAR = 1; + PROTO2_MAP_ENUM_BAZ = 2; +} + +enum Proto2MapEnumPlusExtra { + E_PROTO2_MAP_ENUM_FOO = 0; + E_PROTO2_MAP_ENUM_BAR = 1; + E_PROTO2_MAP_ENUM_BAZ = 2; + E_PROTO2_MAP_ENUM_EXTRA = 3; +} + +message TestEnumMap { + map known_map_field = 101; + map unknown_map_field = 102; + + // Other maps with all key types to test the unknown entry serialization + map unknown_map_field_int64 = 200; + map unknown_map_field_uint64 = 201; + map unknown_map_field_int32 = 202; + map unknown_map_field_uint32 = 203; + map unknown_map_field_fixed32 = 204; + map unknown_map_field_fixed64 = 205; + map unknown_map_field_bool = 206; + map unknown_map_field_string = 207; + map unknown_map_field_sint32 = 208; + map unknown_map_field_sint64 = 209; + map unknown_map_field_sfixed32 = 210; + map unknown_map_field_sfixed64 = 211; +} + +message TestEnumMapPlusExtra { + map known_map_field = 101; + map unknown_map_field = 102; + + // Other maps with all key types to test the unknown entry serialization + map unknown_map_field_int64 = 200; + map unknown_map_field_uint64 = 201; + map unknown_map_field_int32 = 202; + map unknown_map_field_uint32 = 203; + map unknown_map_field_fixed32 = 204; + map unknown_map_field_fixed64 = 205; + map unknown_map_field_bool = 206; + map unknown_map_field_string = 207; + map unknown_map_field_sint32 = 208; + map unknown_map_field_sint64 = 209; + map unknown_map_field_sfixed32 = 210; + map unknown_map_field_sfixed64 = 211; +} + +message TestIntIntMap { map m = 1; } + +// Test all key types: string, plus the non-floating-point scalars. +message TestMaps { + map m_int32 = 1; + map m_int64 = 2; + map m_uint32 = 3; + map m_uint64 = 4; + map m_sint32 = 5; + map m_sint64 = 6; + map m_fixed32 = 7; + map m_fixed64 = 8; + map m_sfixed32 = 9; + map m_sfixed64 = 10; + map m_bool = 11; + map m_string = 12; +} + +// Test maps in submessages. +message TestSubmessageMaps { optional TestMaps m = 1; } + +message TestProto2BytesMap { + map map_bytes = 1; + map map_string = 2; +} + +message TestProto2FloatMap { + map map_floats = 1; + map map_doubles = 2; +} \ No newline at end of file diff --git a/3rdparty/simple-protobuf/test/proto/mesos.proto b/3rdparty/simple-protobuf/test/proto/mesos.proto new file mode 100644 index 0000000..2aad66d --- /dev/null +++ b/3rdparty/simple-protobuf/test/proto/mesos.proto @@ -0,0 +1,4062 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +syntax = "proto2"; + +package mesos; + +option cc_enable_arenas = true; + +option java_package = "org.apache.mesos"; +option java_outer_classname = "Protos"; + + +/** + * Status is used to indicate the state of the scheduler and executor + * driver after function calls. + */ +enum Status { + DRIVER_NOT_STARTED = 1; + DRIVER_RUNNING = 2; + DRIVER_ABORTED = 3; + DRIVER_STOPPED = 4; +} + + +/** + * A unique ID assigned to a framework. A framework can reuse this ID + * in order to do failover (see MesosSchedulerDriver). + */ +message FrameworkID { + required string value = 1; +} + + +/** + * A unique ID assigned to an offer. + */ +message OfferID { + required string value = 1; +} + + +/** + * A unique ID assigned to a slave. Currently, a slave gets a new ID + * whenever it (re)registers with Mesos. Framework writers shouldn't + * assume any binding between a slave ID and and a hostname. + */ +message SlaveID { + required string value = 1; +} + + +/** + * A framework-generated ID to distinguish a task. The ID must remain + * unique while the task is active. A framework can reuse an ID _only_ + * if the previous task with the same ID has reached a terminal state + * (e.g., TASK_FINISHED, TASK_KILLED, etc.). However, reusing task IDs + * is strongly discouraged (MESOS-2198). + */ +message TaskID { + required string value = 1; +} + + +/** + * A framework-generated ID to distinguish an executor. Only one + * executor with the same ID can be active on the same slave at a + * time. However, reusing executor IDs is discouraged. + */ +message ExecutorID { + required string value = 1; +} + + +/** + * ID used to uniquely identify a container. If the `parent` is not + * specified, the ID is a UUID generated by the agent to uniquely + * identify the container of an executor run. If the `parent` field is + * specified, it represents a nested container. + */ +message ContainerID { + required string value = 1; + optional ContainerID parent = 2; +} + + +/** + * A unique ID assigned to a resource provider. Currently, a resource + * provider gets a new ID whenever it (re)registers with Mesos. + */ +message ResourceProviderID { + required string value = 1; +} + + +/** + * A framework-generated ID to distinguish an operation. The ID + * must be unique within the framework. + */ +message OperationID { + required string value = 1; +} + + +/** + * Represents time since the epoch, in nanoseconds. + */ +message TimeInfo { + required int64 nanoseconds = 1; +} + + +/** + * Represents duration in nanoseconds. + */ +message DurationInfo { + required int64 nanoseconds = 1; +} + + +/** + * A network address. + * + * TODO(bmahler): Use this more widely. + */ +message Address { + // May contain a hostname, IP address, or both. + optional string hostname = 1; + optional string ip = 2; + + required int32 port = 3; +} + + +/** + * Represents a URL. + */ +message URL { + required string scheme = 1; + required Address address = 2; + optional string path = 3; + repeated Parameter query = 4; + optional string fragment = 5; +} + + +/** + * Represents an interval, from a given start time over a given duration. + * This interval pertains to an unavailability event, such as maintenance, + * and is not a generic interval. + */ +message Unavailability { + required TimeInfo start = 1; + + // When added to `start`, this represents the end of the interval. + // If unspecified, the duration is assumed to be infinite. + optional DurationInfo duration = 2; + + // TODO(josephw): Add additional fields for expressing the purpose and + // urgency of the unavailability event. +} + + +/** + * Represents a single machine, which may hold one or more slaves. + * + * NOTE: In order to match a slave to a machine, both the `hostname` and + * `ip` must match the values advertised by the slave to the master. + * Hostname is not case-sensitive. + */ +message MachineID { + optional string hostname = 1; + optional string ip = 2; +} + + +/** + * Holds information about a single machine, its `mode`, and any other + * relevant information which may affect the behavior of the machine. + */ +message MachineInfo { + // Describes the several states that a machine can be in. A `Mode` + // applies to a machine and to all associated slaves on the machine. + enum Mode { + // In this mode, a machine is behaving normally; + // offering resources, executing tasks, etc. + UP = 1; + + // In this mode, all slaves on the machine are expected to cooperate with + // frameworks to drain resources. In general, draining is done ahead of + // a pending `unavailability`. The resources should be drained so as to + // maximize utilization prior to the maintenance but without knowingly + // violating the frameworks' requirements. + DRAINING = 2; + + // In this mode, a machine is not running any tasks and will not offer + // any of its resources. Slaves on the machine will not be allowed to + // register with the master. + DOWN = 3; + } + + required MachineID id = 1; + optional Mode mode = 2; + + // Signifies that the machine may be unavailable during the given interval. + // See comments in `Unavailability` and for the `unavailability` fields + // in `Offer` and `InverseOffer` for more information. + optional Unavailability unavailability = 3; +} + + +/** + * Describes a framework. + */ +message FrameworkInfo { + // Used to determine the Unix user that an executor or task should be + // launched as. + // + // When using the MesosSchedulerDriver, if the field is set to an + // empty string, it will automagically set it to the current user. + // + // When using the HTTP Scheduler API, the user has to be set + // explicitly. + required string user = 1; + + // Name of the framework that shows up in the Mesos Web UI. + required string name = 2; + + // Used to uniquely identify the framework. + // + // This field must be unset when the framework subscribes for the + // first time upon which the master will assign a new ID. To + // resubscribe after scheduler failover the framework should set + // 'id' to the ID assigned by the master. Setting 'id' to values + // not assigned by Mesos masters is unsupported. + optional FrameworkID id = 3; + + // The amount of time (in seconds) that the master will wait for the + // scheduler to failover before it tears down the framework by + // killing all its tasks/executors. This should be non-zero if a + // framework expects to reconnect after a failure and not lose its + // tasks/executors. + // + // NOTE: To avoid accidental destruction of tasks, production + // frameworks typically set this to a large value (e.g., 1 week). + optional double failover_timeout = 4 [default = 0.0]; + + // If set, agents running tasks started by this framework will write + // the framework pid, executor pids and status updates to disk. If + // the agent exits (e.g., due to a crash or as part of upgrading + // Mesos), this checkpointed data allows the restarted agent to + // reconnect to executors that were started by the old instance of + // the agent. Enabling checkpointing improves fault tolerance, at + // the cost of a (usually small) increase in disk I/O. + optional bool checkpoint = 5 [default = false]; + + // Roles are the entities to which allocations are made. + // The framework must have at least one role in order to + // be offered resources. Note that `role` is deprecated + // in favor of `roles` and only one of these fields must + // be used. Since we cannot distinguish between empty + // `roles` and the default unset `role`, we require that + // frameworks set the `MULTI_ROLE` capability if + // setting the `roles` field. + optional string role = 6 [default = "*", deprecated=true]; + repeated string roles = 12; + + // Used to indicate the current host from which the scheduler is + // registered in the Mesos Web UI. If set to an empty string Mesos + // will automagically set it to the current hostname if one is + // available. + optional string hostname = 7; + + // This field should match the credential's principal the framework + // uses for authentication. This field is used for framework API + // rate limiting and dynamic reservations. It should be set even + // if authentication is not enabled if these features are desired. + optional string principal = 8; + + // This field allows a framework to advertise its web UI, so that + // the Mesos web UI can link to it. It is expected to be a full URL, + // for example http://my-scheduler.example.com:8080/. + optional string webui_url = 9; + + message Capability { + enum Type { + // This must be the first enum value in this list, to + // ensure that if 'type' is not set, the default value + // is UNKNOWN. This enables enum values to be added + // in a backwards-compatible way. See: MESOS-4997. + UNKNOWN = 0; + + // Receive offers with revocable resources. See 'Resource' + // message for details. + REVOCABLE_RESOURCES = 1; + + // Receive the TASK_KILLING TaskState when a task is being + // killed by an executor. The executor will examine this + // capability to determine whether it can send TASK_KILLING. + TASK_KILLING_STATE = 2; + + // Indicates whether the framework is aware of GPU resources. + // Frameworks that are aware of GPU resources are expected to + // avoid placing non-GPU workloads on GPU agents, in order + // to avoid occupying a GPU agent and preventing GPU workloads + // from running! Currently, if a framework is unaware of GPU + // resources, it will not be offered *any* of the resources on + // an agent with GPUs. This restriction is in place because we + // do not have a revocation mechanism that ensures GPU workloads + // can evict GPU agent occupants if necessary. + // + // TODO(bmahler): As we add revocation we can relax the + // restriction here. See MESOS-5634 for more information. + GPU_RESOURCES = 3; + + // Receive offers with resources that are shared. + SHARED_RESOURCES = 4; + + // Indicates that (1) the framework is prepared to handle the + // following TaskStates: TASK_UNREACHABLE, TASK_DROPPED, + // TASK_GONE, TASK_GONE_BY_OPERATOR, and TASK_UNKNOWN, and (2) + // the framework will assume responsibility for managing + // partitioned tasks that reregister with the master. + // + // Frameworks that enable this capability can define how they + // would like to handle partitioned tasks. Frameworks will + // receive TASK_UNREACHABLE for tasks on agents that are + // partitioned from the master. + // + // Without this capability, frameworks will receive TASK_LOST + // for tasks on partitioned agents. + // NOTE: Prior to Mesos 1.5, such tasks will be killed by Mesos + // when the agent reregisters (unless the master has failed over). + // However due to the lack of benefit in maintaining different + // behaviors depending on whether the master has failed over + // (see MESOS-7215), as of 1.5, Mesos will not kill these + // tasks in either case. + PARTITION_AWARE = 5; + + // This expresses the ability for the framework to be + // "multi-tenant" via using the newly introduced `roles` + // field, and examining `Offer.allocation_info` to determine + // which role the offers are being made to. We also + // expect that "single-tenant" schedulers eventually + // provide this and move away from the deprecated + // `role` field. + MULTI_ROLE = 6; + + // This capability has two effects for a framework. + // + // (1) The framework is offered resources in a new format. + // + // The offered resources have the `Resource.reservations` field set + // rather than `Resource.role` and `Resource.reservation`. In short, + // an empty `reservations` field denotes unreserved resources, and + // each `ReservationInfo` in the `reservations` field denotes a + // reservation that refines the previous one. + // + // See the 'Resource Format' section for more details. + // + // (2) The framework can create refined reservations. + // + // A framework can refine an existing reservation via the + // `Resource.reservations` field. For example, a reservation for role + // `eng` can be refined to `eng/front_end`. + // + // See `ReservationInfo.reservations` for more details. + // + // NOTE: Without this capability, a framework is not offered resources + // that have refined reservations. A resource is said to have refined + // reservations if it uses the `Resource.reservations` field, and + // `Resource.reservations_size() > 1`. + RESERVATION_REFINEMENT = 7; // EXPERIMENTAL. + + // Indicates that the framework is prepared to receive offers + // for agents whose region is different from the master's + // region. Network links between hosts in different regions + // typically have higher latency and lower bandwidth than + // network links within a region, so frameworks should be + // careful to only place suitable workloads in remote regions. + // Frameworks that are not region-aware will never receive + // offers for remote agents; region-aware frameworks are assumed + // to implement their own logic to decide which workloads (if + // any) are suitable for placement on remote agents. + REGION_AWARE = 8; + } + + // Enum fields should be optional, see: MESOS-4997. + optional Type type = 1; + } + + // This field allows a framework to advertise its set of + // capabilities (e.g., ability to receive offers for revocable + // resources). + repeated Capability capabilities = 10; + + // Labels are free-form key value pairs supplied by the framework + // scheduler (e.g., to describe additional functionality offered by + // the framework). These labels are not interpreted by Mesos itself. + // Labels should not contain duplicate key-value pairs. + optional Labels labels = 11; + + // Specifc resource requirements for each of the framework's roles. This field + // is used by e.g., the default allocator to decide whether a framework is + // interested in seeing a resource of a certain shape. + map offer_filters = 13; +} + + +/** + * Describes a general non-interpreting non-killing check for a task or + * executor (or any arbitrary process/command). A type is picked by + * specifying one of the optional fields. Specifying more than one type + * is an error. + * + * NOTE: This API is subject to change and the related feature is experimental. + */ +message CheckInfo { + enum Type { + UNKNOWN = 0; + COMMAND = 1; + HTTP = 2; + TCP = 3; + + // TODO(alexr): Consider supporting custom user checks. They should + // probably be paired with a `data` field and complemented by a + // `data` response in `CheckStatusInfo`. + } + + // Describes a command check. If applicable, enters mount and/or network + // namespaces of the task. + message Command { + required CommandInfo command = 1; + } + + // Describes an HTTP check. Sends a GET request to + // http://:port/path. Note that is not configurable and is + // resolved automatically to 127.0.0.1. + message Http { + // Port to send the HTTP request. + required uint32 port = 1; + + // HTTP request path. + optional string path = 2; + + // TODO(alexr): Add support for HTTP method. While adding POST + // and PUT is simple, supporting payload is more involved. + + // TODO(alexr): Add support for custom HTTP headers. + + // TODO(alexr): Consider adding an optional message to describe TLS + // options and thus enabling https. Such message might contain certificate + // validation, TLS version. + } + + // Describes a TCP check, i.e. based on establishing a TCP connection to + // the specified port. Note that is not configurable and is resolved + // automatically to 127.0.0.1. + message Tcp { + required uint32 port = 1; + } + + // The type of the check. + optional Type type = 1; + + // Command check. + optional Command command = 2; + + // HTTP check. + optional Http http = 3; + + // TCP check. + optional Tcp tcp = 7; + + // Amount of time to wait to start checking the task after it + // transitions to `TASK_RUNNING` or `TASK_STARTING` if the latter + // is used by the executor. + optional double delay_seconds = 4 [default = 15.0]; + + // Interval between check attempts, i.e., amount of time to wait after + // the previous check finished or timed out to start the next check. + optional double interval_seconds = 5 [default = 10.0]; + + // Amount of time to wait for the check to complete. Zero means infinite + // timeout. + // + // After this timeout, the check attempt is aborted and no result is + // reported. Note that this may be considered a state change and hence + // may trigger a check status change delivery to the corresponding + // scheduler. See `CheckStatusInfo` for more details. + optional double timeout_seconds = 6 [default = 20.0]; +} + + +/** + * Describes a health check for a task or executor (or any arbitrary + * process/command). A type is picked by specifying one of the + * optional fields. Specifying more than one type is an error. + */ +message HealthCheck { + enum Type { + UNKNOWN = 0; + COMMAND = 1; + HTTP = 2; + TCP = 3; + } + + // Describes an HTTP health check. Sends a GET request to + // scheme://:port/path. Note that is not configurable and is + // resolved automatically, in most cases to 127.0.0.1. Default executors + // treat return codes between 200 and 399 as success; custom executors + // may employ a different strategy, e.g. leveraging the `statuses` field. + message HTTPCheckInfo { + optional NetworkInfo.Protocol protocol = 5 [default = IPv4]; + + // Currently "http" and "https" are supported. + optional string scheme = 3; + + // Port to send the HTTP request. + required uint32 port = 1; + + // HTTP request path. + optional string path = 2; + + // TODO(alexr): Add support for HTTP method. While adding POST + // and PUT is simple, supporting payload is more involved. + + // TODO(alexr): Add support for custom HTTP headers. + + // TODO(alexr): Add support for success and possibly failure + // statuses. + + // NOTE: It is up to the custom executor to interpret and act on this + // field. Setting this field has no effect on the default executors. + // + // TODO(haosdent): Deprecate this field when we add better support for + // success and possibly failure statuses, e.g. ranges of success and + // failure statuses. + repeated uint32 statuses = 4; + + // TODO(haosdent): Consider adding a flag to enable task's certificate + // validation for HTTPS health checks, see MESOS-5997. + + // TODO(benh): Include an 'optional bytes data' field for checking + // for specific data in the response. + } + + // Describes a TCP health check, i.e. based on establishing + // a TCP connection to the specified port. + message TCPCheckInfo { + optional NetworkInfo.Protocol protocol = 2 [default = IPv4]; + + // Port expected to be open. + required uint32 port = 1; + } + + // TODO(benh): Consider adding a URL health check strategy which + // allows doing something similar to the HTTP strategy but + // encapsulates all the details in a single string field. + + // Amount of time to wait to start health checking the task after it + // transitions to `TASK_RUNNING` or `TASK_STATING` if the latter is + // used by the executor. + optional double delay_seconds = 2 [default = 15.0]; + + // Interval between health checks, i.e., amount of time to wait after + // the previous health check finished or timed out to start the next + // health check. + optional double interval_seconds = 3 [default = 10.0]; + + // Amount of time to wait for the health check to complete. After this + // timeout, the health check is aborted and treated as a failure. Zero + // means infinite timeout. + optional double timeout_seconds = 4 [default = 20.0]; + + // Number of consecutive failures until the task is killed by the executor. + optional uint32 consecutive_failures = 5 [default = 3]; + + // Amount of time after the task is launched during which health check + // failures are ignored. Once a check succeeds for the first time, + // the grace period does not apply anymore. Note that it includes + // `delay_seconds`, i.e., setting `grace_period_seconds` < `delay_seconds` + // has no effect. + optional double grace_period_seconds = 6 [default = 10.0]; + + // TODO(alexr): Add an optional `KillPolicy` that should be used + // if the task is killed because of a health check failure. + + // The type of health check. + optional Type type = 8; + + // Command health check. + optional CommandInfo command = 7; + + // HTTP health check. + optional HTTPCheckInfo http = 1; + + // TCP health check. + optional TCPCheckInfo tcp = 9; +} + + +/** + * Describes a kill policy for a task. Currently does not express + * different policies (e.g. hitting HTTP endpoints), only controls + * how long to wait between graceful and forcible task kill: + * + * graceful kill --------------> forcible kill + * grace_period + * + * Kill policies are best-effort, because machine failures / forcible + * terminations may occur. + * + * NOTE: For executor-less command-based tasks, the kill is performed + * via sending a signal to the task process: SIGTERM for the graceful + * kill and SIGKILL for the forcible kill. For the docker executor-less + * tasks the grace period is passed to 'docker stop --time'. + */ +message KillPolicy { + // The grace period specifies how long to wait before forcibly + // killing the task. It is recommended to attempt to gracefully + // kill the task (and send TASK_KILLING) to indicate that the + // graceful kill is in progress. Once the grace period elapses, + // if the task has not terminated, a forcible kill should occur. + // The task should not assume that it will always be allotted + // the full grace period. For example, the executor may be + // shutdown more quickly by the agent, or failures / forcible + // terminations may occur. + optional DurationInfo grace_period = 1; +} + + +/** + * Describes a command, executed via: '/bin/sh -c value'. Any URIs specified + * are fetched before executing the command. If the executable field for an + * uri is set, executable file permission is set on the downloaded file. + * Otherwise, if the downloaded file has a recognized archive extension + * (currently [compressed] tar and zip) it is extracted into the executor's + * working directory. This extraction can be disabled by setting `extract` to + * false. In addition, any environment variables are set before executing + * the command (so they can be used to "parameterize" your command). + */ +message CommandInfo { + message URI { + required string value = 1; + optional bool executable = 2; + + // In case the fetched file is recognized as an archive, extract + // its contents into the sandbox. Note that a cached archive is + // not copied from the cache to the sandbox in case extraction + // originates from an archive in the cache. + optional bool extract = 3 [default = true]; + + // If this field is "true", the fetcher cache will be used. If not, + // fetching bypasses the cache and downloads directly into the + // sandbox directory, no matter whether a suitable cache file is + // available or not. The former directs the fetcher to download to + // the file cache, then copy from there to the sandbox. Subsequent + // fetch attempts with the same URI will omit downloading and copy + // from the cache as long as the file is resident there. Cache files + // may get evicted at any time, which then leads to renewed + // downloading. See also "docs/fetcher.md" and + // "docs/fetcher-cache-internals.md". + optional bool cache = 4; + + // The fetcher's default behavior is to use the URI string's basename to + // name the local copy. If this field is provided, the local copy will be + // named with its value instead. If there is a directory component (which + // must be a relative path), the local copy will be stored in that + // subdirectory inside the sandbox. + optional string output_file = 5; + } + + repeated URI uris = 1; + + optional Environment environment = 2; + + // There are two ways to specify the command: + // 1) If 'shell == true', the command will be launched via shell + // (i.e., /bin/sh -c 'value'). The 'value' specified will be + // treated as the shell command. The 'arguments' will be ignored. + // 2) If 'shell == false', the command will be launched by passing + // arguments to an executable. The 'value' specified will be + // treated as the filename of the executable. The 'arguments' + // will be treated as the arguments to the executable. This is + // similar to how POSIX exec families launch processes (i.e., + // execlp(value, arguments(0), arguments(1), ...)). + // NOTE: The field 'value' is changed from 'required' to 'optional' + // in 0.20.0. It will only cause issues if a new framework is + // connecting to an old master. + optional bool shell = 6 [default = true]; + optional string value = 3; + repeated string arguments = 7; + + // Enables executor and tasks to run as a specific user. If the user + // field is present both in FrameworkInfo and here, the CommandInfo + // user value takes precedence. + optional string user = 5; +} + + +/** + * Describes information about an executor. + */ +message ExecutorInfo { + enum Type { + UNKNOWN = 0; + + // Mesos provides a simple built-in default executor that frameworks can + // leverage to run shell commands and containers. + // + // NOTES: + // + // 1) `command` must not be set when using a default executor. + // + // 2) Default executor only accepts a *single* `LAUNCH` or `LAUNCH_GROUP` + // operation. + // + // 3) If `container` is set, `container.type` must be `MESOS` + // and `container.mesos.image` must not be set. + DEFAULT = 1; + + // For frameworks that need custom functionality to run tasks, a `CUSTOM` + // executor can be used. Note that `command` must be set when using a + // `CUSTOM` executor. + CUSTOM = 2; + } + + // For backwards compatibility, if this field is not set when using `LAUNCH` + // operation, Mesos will infer the type by checking if `command` is set + // (`CUSTOM`) or unset (`DEFAULT`). `type` must be set when using + // `LAUNCH_GROUP` operation. + // + // TODO(vinod): Add support for explicitly setting `type` to `DEFAULT` in + // `LAUNCH` operation. + optional Type type = 15; + + required ExecutorID executor_id = 1; + optional FrameworkID framework_id = 8; // TODO(benh): Make this required. + optional CommandInfo command = 7; + + // Executor provided with a container will launch the container + // with the executor's CommandInfo and we expect the container to + // act as a Mesos executor. + optional ContainerInfo container = 11; + + repeated Resource resources = 5; + optional string name = 9; + + // 'source' is an identifier style string used by frameworks to + // track the source of an executor. This is useful when it's + // possible for different executor ids to be related semantically. + // + // NOTE: 'source' is exposed alongside the resource usage of the + // executor via JSON on the slave. This allows users to import usage + // information into a time series database for monitoring. + // + // This field is deprecated since 1.0. Please use labels for + // free-form metadata instead. + optional string source = 10 [deprecated = true]; // Since 1.0. + + // This field can be used to pass arbitrary bytes to an executor. + optional bytes data = 4; + + // Service discovery information for the executor. It is not + // interpreted or acted upon by Mesos. It is up to a service + // discovery system to use this information as needed and to handle + // executors without service discovery information. + optional DiscoveryInfo discovery = 12; + + // When shutting down an executor the agent will wait in a + // best-effort manner for the grace period specified here + // before forcibly destroying the container. The executor + // must not assume that it will always be allotted the full + // grace period, as the agent may decide to allot a shorter + // period and failures / forcible terminations may occur. + optional DurationInfo shutdown_grace_period = 13; + + // Labels are free-form key value pairs which are exposed through + // master and slave endpoints. Labels will not be interpreted or + // acted upon by Mesos itself. As opposed to the data field, labels + // will be kept in memory on master and slave processes. Therefore, + // labels should be used to tag executors with lightweight metadata. + // Labels should not contain duplicate key-value pairs. + optional Labels labels = 14; +} + + +/** + * Describes a domain. A domain is a collection of hosts that have + * similar characteristics. Mesos currently only supports "fault + * domains", which identify groups of hosts with similar failure + * characteristics. + * + * Frameworks can generally assume that network links between hosts in + * the same fault domain have lower latency, higher bandwidth, and better + * availability than network links between hosts in different domains. + * Schedulers may prefer to place network-intensive workloads in the + * same domain, as this may improve performance. Conversely, a single + * failure that affects a host in a domain may be more likely to + * affect other hosts in the same domain; hence, schedulers may prefer + * to place workloads that require high availability in multiple + * domains. (For example, all the hosts in a single rack might lose + * power or network connectivity simultaneously.) + * + * There are two kinds of fault domains: regions and zones. Regions + * offer the highest degree of fault isolation, but network latency + * between regions is typically high (typically >50 ms). Zones offer a + * modest degree of fault isolation along with reasonably low network + * latency (typically <10 ms). + * + * The mapping from fault domains to physical infrastructure is up to + * the operator to configure. In cloud environments, regions and zones + * can be mapped to the "region" and "availability zone" concepts + * exposed by most cloud providers, respectively. In on-premise + * deployments, regions and zones can be mapped to data centers and + * racks, respectively. + * + * Both masters and agents can be configured with domains. Frameworks + * can compare the domains of two hosts to determine if the hosts are + * in the same zone, in different zones in the same region, or in + * different regions. Note that all masters in a given Mesos cluster + * must be in the same region. + * + * Complex deployments may have additional levels of hierarchy: for example, + * multiple racks might be grouped together into "halls" and multiple DCs in + * the same geographical vicinity might be grouped together. As a convention, + * the recommended way to represent additional levels of hierarchy is via dot- + * separated labels in the existing zone and region fields. For example, the + * fact that racks "abc" and "def" are in the same hall might be represented + * using the zone names "rack-abc.hall-1" and "rack-def.hall-1", for example. + * Software that is not aware of this additional structure will compare the + * zone names for equality- hence, the two zones will be treated as different + * (unrelated) zones. Software that is "hall-aware" can inspect the zone names + * and make use of the additional hierarchy. + */ +message DomainInfo { + message FaultDomain { + message RegionInfo { + required string name = 1; + } + + message ZoneInfo { + required string name = 1; + } + + required RegionInfo region = 1; + required ZoneInfo zone = 2; + } + + optional FaultDomain fault_domain = 1; +} + + +/** + * Describes a master. This will probably have more fields in the + * future which might be used, for example, to link a framework webui + * to a master webui. + */ +message MasterInfo { + required string id = 1; + + // The IP address (only IPv4) as a packed 4-bytes integer, + // stored in network order. Deprecated, use `address.ip` instead. + required uint32 ip = 2; + + // The TCP port the Master is listening on for incoming + // HTTP requests; deprecated, use `address.port` instead. + required uint32 port = 3 [default = 5050]; + + // In the default implementation, this will contain information + // about both the IP address, port and Master name; it should really + // not be relied upon by external tooling/frameworks and be + // considered an "internal" implementation field. + optional string pid = 4; + + // The server's hostname, if available; it may be unreliable + // in environments where the DNS configuration does not resolve + // internal hostnames (eg, some public cloud providers). + // Deprecated, use `address.hostname` instead. + optional string hostname = 5; + + // The running Master version, as a string; taken from the + // generated "master/version.hpp". + optional string version = 6; + + // The full IP address (supports both IPv4 and IPv6 formats) + // and supersedes the use of `ip`, `port` and `hostname`. + // Since Mesos 0.24. + optional Address address = 7; + + // The domain that this master belongs to. All masters in a Mesos + // cluster should belong to the same region. + optional DomainInfo domain = 8; + + message Capability { + enum Type { + UNKNOWN = 0; + + // NOTE: When the master starts to use a new capability that + // may prevent compatible downgrade, remember to add the + // capability to `Registry::MinimumCapability`. Conversely, + // the added minimum capability should be removed if the capability + // is deemed to be no longer required for compatible downgrade. + // See MESOS-8878 for more details. + + // The master can handle slaves whose state + // changes after reregistering. + AGENT_UPDATE = 1; + + // The master can drain or deactivate agents when requested + // via operator APIs. + AGENT_DRAINING = 2; + + // The master can handle the new quota API, which supports setting + // limits separately from guarantees (introduced in Mesos 1.9). + QUOTA_V2 = 3; + } + optional Type type = 1; + } + + repeated Capability capabilities = 9; +} + + +/** + * Describes a slave. Note that the 'id' field is only available after + * a slave is registered with the master, and is made available here + * to facilitate re-registration. + */ +message SlaveInfo { + required string hostname = 1; + optional int32 port = 8 [default = 5051]; + + // The configured resources at the agent. This does not include any + // dynamic reservations or persistent volumes that may currently + // exist at the agent. + repeated Resource resources = 3; + + repeated Attribute attributes = 5; + optional SlaveID id = 6; + + // The domain that this slave belongs to. If the slave's region + // differs from the master's region, it will not appear in resource + // offers to frameworks that have not enabled the REGION_AWARE + // capability. + optional DomainInfo domain = 10; + + // Slave checkpointing is always enabled in recent Mesos versions; + // the value of this field is ignored. + // TODO(joerg84): Remove checkpoint field after deprecation cycle starting + // with 0.27 (MESOS-2317). + optional bool checkpoint = 7 [default = false]; + + message Capability { + enum Type { + // This must be the first enum value in this list, to + // ensure that if 'type' is not set, the default value + // is UNKNOWN. This enables enum values to be added + // in a backwards-compatible way. See: MESOS-4997. + UNKNOWN = 0; + + // This expresses the ability for the agent to be able + // to launch tasks of a 'multi-role' framework. + MULTI_ROLE = 1; + + // This expresses the ability for the agent to be able to launch + // tasks, reserve resources, and create volumes using resources + // allocated to a 'hierarchical-role'. + // NOTE: This capability is required specifically for creating + // volumes because a hierchical role includes '/' (slashes) in them. + // Agents with this capability know to transform the '/' (slashes) + // into ' ' (spaces). + HIERARCHICAL_ROLE = 2; + + // This capability has three effects for an agent. + // + // (1) The format of the checkpointed resources, and + // the resources reported to master. + // + // These resources are reported in the "pre-reservation-refinement" + // format if none of the resources have refined reservations. If any + // of the resources have refined reservations, they are reported in + // the "post-reservation-refinement" format. The purpose is to allow + // downgrading of an agent as well as communication with a pre-1.4.0 + // master until the reservation refinement feature is actually used. + // + // See the 'Resource Format' section for more details. + // + // (2) The format of the resources reported by the HTTP endpoints. + // + // For resources reported by agent endpoints, the + // "pre-reservation-refinement" format is "injected" if possible. + // That is, resources without refined reservations will have the + // `Resource.role` and `Resource.reservation` set, whereas + // resources with refined reservations will not. + // + // See the 'Resource Format' section for more details. + // + // (3) The ability for the agent to launch tasks, reserve resources, and + // create volumes using resources that have refined reservations. + // + // See `ReservationInfo.reservations` section for more details. + // + // NOTE: Resources are said to have refined reservations if it uses the + // `Resource.reservations` field, and `Resource.reservations_size() > 1`. + RESERVATION_REFINEMENT = 3; + + // This expresses the ability for the agent to handle resource + // provider related operations. This includes the following: + // + // (1) The ability to report resources that are provided by some + // local resource providers through the resource provider API. + // + // (2) The ability to provide operation feedback. This also means + // that this capability is a prerequisite for full support of + // feedback for operations on agent default resources. If an + // agent has the mandatory AGENT_OPERATION_FEEDBACK capability + // set but not the RESOURCE_PROVIDER capability, then + // operations on agent default resources which request feedback + // will not be allowed. + RESOURCE_PROVIDER = 4; + + // This expresses the capability for the agent to handle persistent volume + // resize operations safely. This capability is turned on by default. + RESIZE_VOLUME = 5; + + // This expresses the ability of the agent to handle operation feedback + // for operations on agent default resources. + // + // Note that full support for this feature also requires the + // RESOURCE_PROVIDER capability; if you would like the agent to + // handle feedback for operations on agent default resources, the + // RESOURCE_PROVIDER capability should be set as well. + AGENT_OPERATION_FEEDBACK = 6; + + // This expresses the ability for the agent to automatically drain tasks + // in preparation for operator maintenance. This capability is required. + AGENT_DRAINING = 7; + + // This expresses the ability for the agent to launch tasks which specify + // resource limits for CPU and/or memory. + TASK_RESOURCE_LIMITS = 8; + } + + // Enum fields should be optional, see: MESOS-4997. + optional Type type = 1; + } +} + + +/** + * Describes the container configuration to run a managed CSI plugin. + */ +message CSIPluginContainerInfo { + enum Service { + UNKNOWN = 0; + CONTROLLER_SERVICE = 1; + NODE_SERVICE = 2; + } + + repeated Service services = 1; + optional CommandInfo command = 2; + repeated Resource resources = 3; + optional ContainerInfo container = 4; +} + + +/** + * Describes the endpoint of an unmanaged CSI plugin service. + */ +message CSIPluginEndpoint { + required CSIPluginContainerInfo.Service csi_service = 1; + required string endpoint = 2; +} + + +/** + * Describes a CSI plugin. + */ +message CSIPluginInfo { + // The type of the CSI plugin. This uniquely identifies a CSI + // implementation. For instance: + // org.apache.mesos.csi.test + // + // Please follow to Java package naming convention + // (https://en.wikipedia.org/wiki/Java_package#Package_naming_conventions) + // to avoid conflicts on type names. + required string type = 1; + + // The name of the CSI plugin. There could be multiple instances of a + // type of CSI plugin within a Mesos cluster. The name field is used to + // distinguish these instances. It should be a legal Java identifier + // (https://docs.oracle.com/javase/tutorial/java/nutsandbolts/variables.html) + // to avoid conflicts on concatenation of type and name. + // + // The type and name together provide the means to uniquely identify a storage + // backend and its resources in the cluster, so the operator should ensure + // that the concatenation of type and name is unique in the cluster, and it + // remains the same if the instance is migrated to another agent (e.g., there + // is a change in the agent ID). + optional string name = 2 [default = "default"]; + + // We support two kinds of CSI plugins: + // 1. Managed CSI plugins: This is the plugin which will be launched by + // Mesos as standalone container, and Mesos will internally determine + // its endpoint when launching it and manage its whole lifecyle. For this + // kind of plugins, the `containers` field below must be specified. + // 2. Unmanaged CSI plugins: This is the plugin which is launched out of + // Mesos (e.g., manually launched by the operator). For this kind of + // plugins, the `endpoints` field below must be specified because Mesos + // needs it to call CSI gRPC methods. + // Please note that only one of the `containers` and `endpoints` fields should + // be specified. + + // A list of container configurations to run managed CSI plugin. + // The controller service will be served by the first configuration + // that contains `CONTROLLER_SERVICE`, and the node service will be + // served by the first configuration that contains `NODE_SERVICE`. + repeated CSIPluginContainerInfo containers = 3; + + // The service endpoints of the unmanaged CSI plugin. An endpoint is usually + // a path to a Unix domain socket. + repeated CSIPluginEndpoint endpoints = 4; + + // The root directory of all the target paths managed by the CSI plugin. + // Each volume will be published by the CSI plugin at a sub-directory + // under this path. + optional string target_path_root = 5; + + // For some CSI plugins which implement CSI v1 spec, they expect the target + // path is an existing path which is actually not CSI v1 spec compliant. In + // such case this field should be set to `true` as a work around for those + // plugins. For the CSI plugins which implement CSI v0 spec, this field will + // be just ignored. + optional bool target_path_exists = 6; +} + + +/** + * Describes a resource provider. Note that the 'id' field is only available + * after a resource provider is registered with the master, and is made + * available here to facilitate re-registration. + */ +message ResourceProviderInfo { + optional ResourceProviderID id = 1; + repeated Attribute attributes = 2; + + // The type of the resource provider. This uniquely identifies a + // resource provider implementation. For instance: + // org.apache.mesos.rp.local.storage + // + // Please follow to Java package naming convention + // (https://en.wikipedia.org/wiki/Java_package#Package_naming_conventions) + // to avoid conflicts on type names. + required string type = 3; + + // The name of the resource provider. There could be multiple + // instances of a type of resource provider. The name field is used + // to distinguish these instances. It should be a legal Java identifier + // (https://docs.oracle.com/javase/tutorial/java/nutsandbolts/variables.html) + // to avoid conflicts on concatenation of type and name. + required string name = 4; + + // The stack of default reservations. If this field is not empty, it + // indicates that resources from this resource provider are reserved + // by default, except for the resources that have been reserved or + // unreserved through operations. The first `ReservationInfo` + // may have type `STATIC` or `DYNAMIC`, but the rest must have + // `DYNAMIC`. One can create a new reservation on top of an existing + // one by pushing a new `ReservationInfo` to the back. The last + // `ReservationInfo` in this stack is the "current" reservation. The + // new reservation's role must be a child of the current one. + repeated Resource.ReservationInfo default_reservations = 5; // EXPERIMENTAL. + + // Storage resource provider related information. + message Storage { + required CSIPluginInfo plugin = 1; + + // Amount of time to wait after the resource provider finishes reconciling + // existing volumes and storage pools against the CSI plugin to start the + // next reconciliation. A non-positive value means that no reconciliation + // will happen after startup. + optional double reconciliation_interval_seconds = 2; + } + + optional Storage storage = 6; // EXPERIMENTAL. +} + + +/** + * Describes an Attribute or Resource "value". A value is described + * using the standard protocol buffer "union" trick. + */ +message Value { + enum Type { + SCALAR = 0; + RANGES = 1; + SET = 2; + TEXT = 3; + } + + message Scalar { + // Scalar values are represented using floating point. To reduce + // the chance of unpredictable floating point behavior due to + // roundoff error, Mesos only supports three decimal digits of + // precision for scalar resource values. That is, floating point + // values are converted to a fixed point format that supports + // three decimal digits of precision, and then converted back to + // floating point on output. Any additional precision in scalar + // resource values is discarded (via rounding). + required double value = 1; + } + + message Range { + required uint64 begin = 1; + required uint64 end = 2; + } + + message Ranges { + repeated Range range = 1; + } + + message Set { + repeated string item = 1; + } + + message Text { + required string value = 1; + } + + required Type type = 1; + optional Scalar scalar = 2; + optional Ranges ranges = 3; + optional Set set = 4; + optional Text text = 5; +} + + +/** + * Describes an attribute that can be set on a machine. For now, + * attributes and resources share the same "value" type, but this may + * change in the future and attributes may only be string based. + */ +message Attribute { + required string name = 1; + required Value.Type type = 2; + optional Value.Scalar scalar = 3; + optional Value.Ranges ranges = 4; + optional Value.Set set = 6; + optional Value.Text text = 5; +} + + +/** + * Describes a resource from a resource provider. The `name` field is + * a string like "cpus" or "mem" that indicates which kind of resource + * this is; the rest of the fields describe the properties of the + * resource. A resource can take on one of three types: scalar + * (double), a list of finite and discrete ranges (e.g., [1-10, + * 20-30]), or a set of items. A resource is described using the + * standard protocol buffer "union" trick. + * + * Note that "disk" and "mem" resources are scalar values expressed in + * megabytes. Fractional "cpus" values are allowed (e.g., "0.5"), + * which correspond to partial shares of a CPU. + */ +message Resource { + // Specified if the resource comes from a particular resource provider. + optional ResourceProviderID provider_id = 12; + + required string name = 1; + required Value.Type type = 2; + optional Value.Scalar scalar = 3; + optional Value.Ranges ranges = 4; + optional Value.Set set = 5; + + // The role that this resource is reserved for. If "*", this indicates + // that the resource is unreserved. Otherwise, the resource will only + // be offered to frameworks that belong to this role. + // + // NOTE: Frameworks must not set this field if `reservations` is set. + // See the 'Resource Format' section for more details. + // + // TODO(mpark): Deprecate once `reservations` is no longer experimental. + optional string role = 6 [default = "*", deprecated=true]; + + // This was initially introduced to support MULTI_ROLE capable + // frameworks. Frameworks that are not MULTI_ROLE capable can + // continue to assume that the offered resources are allocated + // to their role. + message AllocationInfo { + // If set, this resource is allocated to a role. Note that in the + // future, this may be unset and the scheduler may be responsible + // for allocating to one of its roles. + optional string role = 1; + + // In the future, we may add additional fields here, e.g. priority + // tier, type of allocation (quota / fair share). + } + + optional AllocationInfo allocation_info = 11; + + // Resource Format: + // + // Frameworks receive resource offers in one of two formats, depending on + // whether the RESERVATION_REFINEMENT capability is enabled. + // + // __WITHOUT__ the RESERVATION_REFINEMENT capability, the framework is offered + // resources in the "pre-reservation-refinement" format. In this format, the + // `Resource.role` and `Resource.reservation` fields are used in conjunction + // to describe the reservation state of a `Resource` message. + // + // The following is an overview of the possible reservation states: + // + // +------------+------------------------------------------------------------+ + // | unreserved | { | + // | | role: "*", | + // | | reservation: , | + // | | reservations: | + // | | } | + // +------------+------------------------------------------------------------+ + // | static | { | + // | | role: "eng", | + // | | reservation: , | + // | | reservations: | + // | | } | + // +------------+------------------------------------------------------------+ + // | dynamic | { | + // | | role: "eng", | + // | | reservation: { | + // | | type: , | + // | | role: , | + // | | principal: , | + // | | labels: | + // | | }, | + // | | reservations: | + // | | } | + // +------------+------------------------------------------------------------+ + // + // __WITH__ the RESERVATION_REFINEMENT capability, the framework is offered + // resources in the "post-reservation-refinement" format. In this format, the + // reservation state of a `Resource` message is expressed solely in + // `Resource.reservations` field. + // + // The following is an overview of the possible reservation states: + // + // +------------+------------------------------------------------------------+ + // | unreserved | { | + // | | role: , | + // | | reservation: , | + // | | reservations: [] | + // | | } | + // +------------+------------------------------------------------------------+ + // | static | { | + // | | role: , | + // | | reservation: , | + // | | reservations: [ | + // | | { | + // | | type: STATIC, | + // | | role: "eng", | + // | | principal: , | + // | | labels: | + // | | } | + // | | ] | + // | | } | + // +------------+------------------------------------------------------------+ + // | dynamic | { | + // | | role: , | + // | | reservation: , | + // | | reservations: [ | + // | | { | + // | | type: DYNAMIC, | + // | | role: "eng", | + // | | principal: , | + // | | labels: | + // | | } | + // | | ] | + // | | } | + // +------------+------------------------------------------------------------+ + // + // We can also __refine__ reservations with this capability like so: + // + // +------------+------------------------------------------------------------+ + // | refined | { | + // | | role: , | + // | | reservation: , | + // | | reservations: [ | + // | | { | + // | | type: STATIC or DYNAMIC, | + // | | role: "eng", | + // | | principal: , | + // | | labels: | + // | | }, | + // | | { | + // | | type: DYNAMIC, | + // | | role: "eng/front_end", | + // | | principal: , | + // | | labels: | + // | | } | + // | | ] | + // | | } | + // +------------+------------------------------------------------------------+ + // + // NOTE: Each `ReservationInfo` in the `reservations` field denotes + // a reservation that refines the previous `ReservationInfo`. + + message ReservationInfo { + // Describes a reservation. A static reservation is set by the operator on + // the command-line and they are immutable without agent restart. A dynamic + // reservation is made by an operator via the '/reserve' HTTP endpoint + // or by a framework via the offer cycle by sending back an + // 'Offer::Operation::Reserve' message. + // + // NOTE: We currently do not allow frameworks with role "*" to make dynamic + // reservations. + + enum Type { + UNKNOWN = 0; + STATIC = 1; + DYNAMIC = 2; + } + + // The type of this reservation. + // + // NOTE: This field must not be set for `Resource.reservation`. + // See the 'Resource Format' section for more details. + optional Type type = 4; + + // The role to which this reservation is made for. + // + // NOTE: This field must not be set for `Resource.reservation`. + // See the 'Resource Format' section for more details. + optional string role = 3; + + // Indicates the principal, if any, of the framework or operator + // that reserved this resource. If reserved by a framework, the + // field should match the `FrameworkInfo.principal`. It is used in + // conjunction with the `UnreserveResources` ACL to determine + // whether the entity attempting to unreserve this resource is + // permitted to do so. + optional string principal = 1; + + // Labels are free-form key value pairs that can be used to + // associate arbitrary metadata with a reserved resource. For + // example, frameworks can use labels to identify the intended + // purpose for a portion of the resources the framework has + // reserved at a given slave. Labels should not contain duplicate + // key-value pairs. + optional Labels labels = 2; + } + + // If this is set, this resource was dynamically reserved by an + // operator or a framework. Otherwise, this resource is either unreserved + // or statically reserved by an operator via the --resources flag. + // + // NOTE: Frameworks must not set this field if `reservations` is set. + // See the 'Resource Format' section for more details. + // + // TODO(mpark): Deprecate once `reservations` is no longer experimental. + optional ReservationInfo reservation = 8; + + // The stack of reservations. If this field is empty, it indicates that this + // resource is unreserved. Otherwise, the resource is reserved. The first + // `ReservationInfo` may have type `STATIC` or `DYNAMIC`, but the rest must + // have `DYNAMIC`. One can create a new reservation on top of an existing + // one by pushing a new `ReservationInfo` to the back. The last + // `ReservationInfo` in this stack is the "current" reservation. The new + // reservation's role must be a child of the current reservation's role. + // + // NOTE: Frameworks must not set this field if `reservation` is set. + // See the 'Resource Format' section for more details. + // + // TODO(mpark): Deprecate `role` and `reservation` once this is stable. + repeated ReservationInfo reservations = 13; // EXPERIMENTAL. + + message DiskInfo { + // Describes a persistent disk volume. + // + // A persistent disk volume will not be automatically garbage + // collected if the task/executor/slave terminates, but will be + // re-offered to the framework(s) belonging to the 'role'. + // + // NOTE: Currently, we do not allow persistent disk volumes + // without a reservation (i.e., 'role' cannot be '*'). + message Persistence { + // A unique ID for the persistent disk volume. This ID must be + // unique per role on each slave. Although it is possible to use + // the same ID on different slaves in the cluster and to reuse + // IDs after a volume with that ID has been destroyed, both + // practices are discouraged. + required string id = 1; + + // This field indicates the principal of the operator or + // framework that created this volume. It is used in conjunction + // with the "destroy" ACL to determine whether an entity + // attempting to destroy the volume is permitted to do so. + // + // NOTE: This field should match the FrameworkInfo.principal of + // the framework that created the volume. + optional string principal = 2; + } + + optional Persistence persistence = 1; + + // Describes how this disk resource will be mounted in the + // container. If not set, the disk resource will be used as the + // sandbox. Otherwise, it will be mounted according to the + // 'container_path' inside 'volume'. The 'host_path' inside + // 'volume' is ignored. + // NOTE: If 'volume' is set but 'persistence' is not set, the + // volume will be automatically garbage collected after + // task/executor terminates. Currently, if 'persistence' is set, + // 'volume' must be set. + optional Volume volume = 2; + + // Describes where a disk originates from. + message Source { + enum Type { + UNKNOWN = 0; + PATH = 1; + MOUNT = 2; + BLOCK = 3; + RAW = 4; + } + + // A folder that can be located on a separate disk device. This + // can be shared and carved up as necessary between frameworks. + message Path { + // Path to the folder (e.g., /mnt/raid/disk0). If the path is a + // relative path, it is relative to the agent work directory. + optional string root = 1; + } + + // A mounted file-system set up by the Agent administrator. This + // can only be used exclusively: a framework cannot accept a + // partial amount of this disk. + message Mount { + // Path to mount point (e.g., /mnt/raid/disk0). If the path is a + // relative path, it is relative to the agent work directory. + optional string root = 1; + } + + required Type type = 1; + optional Path path = 2; + optional Mount mount = 3; + + // The vendor of this source. If present, this field provides the means to + // uniquely identify the storage backend of this source in the cluster. + optional string vendor = 7; // EXPERIMENTAL. + + // The identifier of this source. This field maps onto CSI volume IDs and + // is not expected to be set by frameworks. If both `vendor` and `id` are + // present, these two fields together provide the means to uniquely + // identify this source in the cluster. + optional string id = 4; // EXPERIMENTAL. + + // Additional metadata for this source. This field maps onto CSI volume + // context. Frameworks should neither alter this field, nor expect this + // field to remain unchanged. + optional Labels metadata = 5; // EXPERIMENTAL. + + // This field serves as an indirection to a set of storage + // vendor specific disk parameters which describe the properties + // of the disk. The operator will setup mappings between a + // profile name to a set of vendor specific disk parameters. And + // the framework will do disk selection based on profile names, + // instead of vendor specific disk parameters. + // + // Also see the DiskProfileAdaptor module. + optional string profile = 6; // EXPERIMENTAL. + } + + optional Source source = 3; + } + + optional DiskInfo disk = 7; + + message RevocableInfo {} + + // If this is set, the resources are revocable, i.e., any tasks or + // executors launched using these resources could get preempted or + // throttled at any time. This could be used by frameworks to run + // best effort tasks that do not need strict uptime or performance + // guarantees. Note that if this is set, 'disk' or 'reservation' + // cannot be set. + optional RevocableInfo revocable = 9; + + // Allow the resource to be shared across tasks. + message SharedInfo {} + + // If this is set, the resources are shared, i.e. multiple tasks + // can be launched using this resource and all of them shall refer + // to the same physical resource on the cluster. Note that only + // persistent volumes can be shared currently. + // + // NOTE: Different shared resources must be uniquely identifiable. + // This currently holds as persistent volume should have unique `id` + // (this is not validated for enforced though). + optional SharedInfo shared = 10; +} + + +/** + * Represents filters that allow a framework to control the shape of + * offers that will be sent to its role(s). These filters apply + * globally to any agent (unlike the existing `DECLINE` filter which + * us a time-based resource subset filter that only applies to the + * agent that was declined). + * + * NOTE: Custom allocators might interpret these fields in a different + * way, or not at all. + */ +message OfferFilters { + message ResourceQuantities { + // Quantities are pairs of identifiers of scalar resources and + // an associated value, e.g., `{"disk": Scalar {"value": 30}}`. + map quantities = 1; + } + + message MinAllocatableResources { + // A set of resources is considered allocatable if contained in any of + // the following quantities. If no quantities are specified any resource + // is considered allocatable. + repeated ResourceQuantities quantities = 1; + } + + optional MinAllocatableResources min_allocatable_resources = 1; +} + + +/** + * When the network bandwidth caps are enabled and the container + * is over its limit, outbound packets may be either delayed or + * dropped completely either because it exceeds the maximum bandwidth + * allocation for a single container (the cap) or because the combined + * network traffic of multiple containers on the host exceeds the + * transmit capacity of the host (the share). We can report the + * following statistics for each of these conditions exported directly + * from the Linux Traffic Control Queueing Discipline. + * + * id : name of the limiter, e.g. 'tx_bw_cap' + * backlog : number of packets currently delayed + * bytes : total bytes seen + * drops : number of packets dropped in total + * overlimits : number of packets which exceeded allocation + * packets : total packets seen + * qlen : number of packets currently queued + * rate_bps : throughput in bytes/sec + * rate_pps : throughput in packets/sec + * requeues : number of times a packet has been delayed due to + * locking or device contention issues + * + * More information on the operation of Linux Traffic Control can be + * found at http://www.lartc.org/lartc.html. + */ +message TrafficControlStatistics { + required string id = 1; + optional uint64 backlog = 2; + optional uint64 bytes = 3; + optional uint64 drops = 4; + optional uint64 overlimits = 5; + optional uint64 packets = 6; + optional uint64 qlen = 7; + optional uint64 ratebps = 8; + optional uint64 ratepps = 9; + optional uint64 requeues = 10; +} + + +message IpStatistics { + optional int64 Forwarding = 1; + optional int64 DefaultTTL = 2; + optional int64 InReceives = 3; + optional int64 InHdrErrors = 4; + optional int64 InAddrErrors = 5; + optional int64 ForwDatagrams = 6; + optional int64 InUnknownProtos = 7; + optional int64 InDiscards = 8; + optional int64 InDelivers = 9; + optional int64 OutRequests = 10; + optional int64 OutDiscards = 11; + optional int64 OutNoRoutes = 12; + optional int64 ReasmTimeout = 13; + optional int64 ReasmReqds = 14; + optional int64 ReasmOKs = 15; + optional int64 ReasmFails = 16; + optional int64 FragOKs = 17; + optional int64 FragFails = 18; + optional int64 FragCreates = 19; +} + + +message IcmpStatistics { + optional int64 InMsgs = 1; + optional int64 InErrors = 2; + optional int64 InCsumErrors = 3; + optional int64 InDestUnreachs = 4; + optional int64 InTimeExcds = 5; + optional int64 InParmProbs = 6; + optional int64 InSrcQuenchs = 7; + optional int64 InRedirects = 8; + optional int64 InEchos = 9; + optional int64 InEchoReps = 10; + optional int64 InTimestamps = 11; + optional int64 InTimestampReps = 12; + optional int64 InAddrMasks = 13; + optional int64 InAddrMaskReps = 14; + optional int64 OutMsgs = 15; + optional int64 OutErrors = 16; + optional int64 OutDestUnreachs = 17; + optional int64 OutTimeExcds = 18; + optional int64 OutParmProbs = 19; + optional int64 OutSrcQuenchs = 20; + optional int64 OutRedirects = 21; + optional int64 OutEchos = 22; + optional int64 OutEchoReps = 23; + optional int64 OutTimestamps = 24; + optional int64 OutTimestampReps = 25; + optional int64 OutAddrMasks = 26; + optional int64 OutAddrMaskReps = 27; +} + + +message TcpStatistics { + optional int64 RtoAlgorithm = 1; + optional int64 RtoMin = 2; + optional int64 RtoMax = 3; + optional int64 MaxConn = 4; + optional int64 ActiveOpens = 5; + optional int64 PassiveOpens = 6; + optional int64 AttemptFails = 7; + optional int64 EstabResets = 8; + optional int64 CurrEstab = 9; + optional int64 InSegs = 10; + optional int64 OutSegs = 11; + optional int64 RetransSegs = 12; + optional int64 InErrs = 13; + optional int64 OutRsts = 14; + optional int64 InCsumErrors = 15; +} + + +message UdpStatistics { + optional int64 InDatagrams = 1; + optional int64 NoPorts = 2; + optional int64 InErrors = 3; + optional int64 OutDatagrams = 4; + optional int64 RcvbufErrors = 5; + optional int64 SndbufErrors = 6; + optional int64 InCsumErrors = 7; + optional int64 IgnoredMulti = 8; +} + + +message SNMPStatistics { + optional IpStatistics ip_stats = 1; + optional IcmpStatistics icmp_stats = 2; + optional TcpStatistics tcp_stats = 3; + optional UdpStatistics udp_stats = 4; +} + + +message DiskStatistics { + optional Resource.DiskInfo.Source source = 1; + optional Resource.DiskInfo.Persistence persistence = 2; + optional uint64 limit_bytes = 3; + optional uint64 used_bytes = 4; +} + + +/** + * A snapshot of resource usage statistics. + */ +message ResourceStatistics { + required double timestamp = 1; // Snapshot time, in seconds since the Epoch. + + optional uint32 processes = 30; + optional uint32 threads = 31; + + // CPU Usage Information: + // Total CPU time spent in user mode, and kernel mode. + optional double cpus_user_time_secs = 2; + optional double cpus_system_time_secs = 3; + + // Hard CPU limit. + optional double cpus_limit = 4; + + // Soft CPU limit. + optional double cpus_soft_limit = 45; + + // cpu.stat on process throttling (for contention issues). + optional uint32 cpus_nr_periods = 7; + optional uint32 cpus_nr_throttled = 8; + optional double cpus_throttled_time_secs = 9; + + // Memory Usage Information: + + // mem_total_bytes was added in 0.23.0 to represent the total memory + // of a process in RAM (as opposed to in Swap). This was previously + // reported as mem_rss_bytes, which was also changed in 0.23.0 to + // represent only the anonymous memory usage, to keep in sync with + // Linux kernel's (arguably erroneous) use of terminology. + optional uint64 mem_total_bytes = 36; + + // Total memory + swap usage. This is set if swap is enabled. + optional uint64 mem_total_memsw_bytes = 37; + + // Current kernel memory allocation. + optional uint64 mem_kmem_usage_bytes = 52; + + // Current TCP buf memory allocation. + optional uint64 mem_kmem_tcp_usage_bytes = 53; + + // Hard memory limit. + optional uint64 mem_limit_bytes = 6; + + // Soft memory limit. + optional uint64 mem_soft_limit_bytes = 38; + + // Broken out memory usage information: pagecache, rss (anonymous), + // mmaped files and swap. + + // TODO(chzhcn) mem_file_bytes and mem_anon_bytes are deprecated in + // 0.23.0 and will be removed in 0.24.0. + optional uint64 mem_file_bytes = 10; + optional uint64 mem_anon_bytes = 11; + + // mem_cache_bytes is added in 0.23.0 to represent page cache usage. + optional uint64 mem_cache_bytes = 39; + + // Since 0.23.0, mem_rss_bytes is changed to represent only + // anonymous memory usage. Note that neither its requiredness, type, + // name nor numeric tag has been changed. + optional uint64 mem_rss_bytes = 5; + + optional uint64 mem_mapped_file_bytes = 12; + // This is only set if swap is enabled. + optional uint64 mem_swap_bytes = 40; + optional uint64 mem_unevictable_bytes = 41; + + // Number of occurrences of different levels of memory pressure + // events reported by memory cgroup. Pressure listening (re)starts + // with these values set to 0 when slave (re)starts. See + // https://www.kernel.org/doc/Documentation/cgroups/memory.txt for + // more details. + optional uint64 mem_low_pressure_counter = 32; + optional uint64 mem_medium_pressure_counter = 33; + optional uint64 mem_critical_pressure_counter = 34; + + // Disk Usage Information for executor working directory. + optional uint64 disk_limit_bytes = 26; + optional uint64 disk_used_bytes = 27; + + // Per disk (resource) statistics. + repeated DiskStatistics disk_statistics = 43; + + // Cgroups blkio statistics. + optional CgroupInfo.Blkio.Statistics blkio_statistics = 44; + + // Perf statistics. + optional PerfStatistics perf = 13; + + // Network Usage Information: + optional uint64 net_rx_packets = 14; + optional uint64 net_rx_bytes = 15; + optional uint64 net_rx_errors = 16; + optional uint64 net_rx_dropped = 17; + optional uint64 net_tx_packets = 18; + optional uint64 net_tx_bytes = 19; + optional uint64 net_tx_errors = 20; + optional uint64 net_tx_dropped = 21; + + optional uint64 net_tx_rate_limit = 46; + optional uint64 net_tx_burst_rate_limit = 47; + optional uint64 net_tx_burst_size = 48; + optional uint64 net_rx_rate_limit = 49; + optional uint64 net_rx_burst_rate_limit = 50; + optional uint64 net_rx_burst_size = 51; + + message RatePercentiles { + optional uint64 min = 1; + optional uint64 max = 2; + optional uint64 p50 = 3; + optional uint64 p90 = 4; + optional uint64 p95 = 5; + optional uint64 p99 = 6; + optional uint64 p999 = 7; + optional uint64 p9999 = 8; + optional uint64 samples = 9; + } + + // Network rate statistics measured in bytes per second + // or packets per second. + // + // Rates are sampled every {sampling_interval_secs}. A + // time series is created out of the samples taken over + // a moving sampling window of {sampling_window_secs}. + // Percentiles for each time series are exposed through + // RatePercentiles. + // + // Linux documentation for more information: + // https://docs.kernel.org/networking/statistics.html#c.rtnl_link_stats64 + message RateStatistics { + // Bytes received per second. + optional RatePercentiles rx_rate = 1; + // Packets received per second. + optional RatePercentiles rx_packet_rate = 2; + // Received packets dropped per second. + optional RatePercentiles rx_drop_rate = 3; + // Receiving packet errors per second. + optional RatePercentiles rx_error_rate = 4; + // Bytes sent per second. + optional RatePercentiles tx_rate = 5; + // Packets sent per second. + optional RatePercentiles tx_packet_rate = 6; + // Send packets dropped per second. + optional RatePercentiles tx_drop_rate = 7; + // Sending packet errors per second. + optional RatePercentiles tx_error_rate = 8; + // Duration of the sliding time series window. + optional double sampling_window_secs = 9; + // The delay between rate samples. + optional double sampling_interval_secs = 10; + } + + optional RateStatistics net_rate_statistics = 54; + + // Inclusive ephemeral ports range of the container. + optional Value.Range net_ephemeral_ports = 55; + + // The kernel keeps track of RTT (round-trip time) for its TCP + // sockets. RTT is a way to tell the latency of a container. + optional double net_tcp_rtt_microsecs_p50 = 22; + optional double net_tcp_rtt_microsecs_p90 = 23; + optional double net_tcp_rtt_microsecs_p95 = 24; + optional double net_tcp_rtt_microsecs_p99 = 25; + + optional double net_tcp_active_connections = 28; + optional double net_tcp_time_wait_connections = 29; + + // Network traffic flowing into or out of a container can be delayed + // or dropped due to congestion or policy inside and outside the + // container. + repeated TrafficControlStatistics net_traffic_control_statistics = 35; + + // Network SNMP statistics for each container. + optional SNMPStatistics net_snmp_statistics = 42; +} + + +/** + * Describes a snapshot of the resource usage for executors. + */ +message ResourceUsage { + message Executor { + required ExecutorInfo executor_info = 1; + + // This includes resources used by the executor itself + // as well as its active tasks. + repeated Resource allocated = 2; + + // Current resource usage. If absent, the containerizer + // cannot provide resource usage. + optional ResourceStatistics statistics = 3; + + // The container id for the executor specified in the executor_info field. + required ContainerID container_id = 4; + + message Task { + required string name = 1; + required TaskID id = 2; + repeated Resource resources = 3; + optional Labels labels = 4; + } + + // Non-terminal tasks. + repeated Task tasks = 5; + } + + repeated Executor executors = 1; + + // Slave's total resources including checkpointed dynamic + // reservations and persistent volumes. + repeated Resource total = 2; +} + + +/** + * Describes a sample of events from "perf stat". Only available on + * Linux. + * + * NOTE: Each optional field matches the name of a perf event (see + * "perf list") with the following changes: + * 1. Names are downcased. + * 2. Hyphens ('-') are replaced with underscores ('_'). + * 3. Events with alternate names use the name "perf stat" returns, + * e.g., for the event "cycles OR cpu-cycles" perf always returns + * cycles. + */ +message PerfStatistics { + required double timestamp = 1; // Start of sample interval, in seconds since the Epoch. + required double duration = 2; // Duration of sample interval, in seconds. + + // Hardware event. + optional uint64 cycles = 3; + optional uint64 stalled_cycles_frontend = 4; + optional uint64 stalled_cycles_backend = 5; + optional uint64 instructions = 6; + optional uint64 cache_references = 7; + optional uint64 cache_misses = 8; + optional uint64 branches = 9; + optional uint64 branch_misses = 10; + optional uint64 bus_cycles = 11; + optional uint64 ref_cycles = 12; + + // Software event. + optional double cpu_clock = 13; + optional double task_clock = 14; + optional uint64 page_faults = 15; + optional uint64 minor_faults = 16; + optional uint64 major_faults = 17; + optional uint64 context_switches = 18; + optional uint64 cpu_migrations = 19; + optional uint64 alignment_faults = 20; + optional uint64 emulation_faults = 21; + + // Hardware cache event. + optional uint64 l1_dcache_loads = 22; + optional uint64 l1_dcache_load_misses = 23; + optional uint64 l1_dcache_stores = 24; + optional uint64 l1_dcache_store_misses = 25; + optional uint64 l1_dcache_prefetches = 26; + optional uint64 l1_dcache_prefetch_misses = 27; + optional uint64 l1_icache_loads = 28; + optional uint64 l1_icache_load_misses = 29; + optional uint64 l1_icache_prefetches = 30; + optional uint64 l1_icache_prefetch_misses = 31; + optional uint64 llc_loads = 32; + optional uint64 llc_load_misses = 33; + optional uint64 llc_stores = 34; + optional uint64 llc_store_misses = 35; + optional uint64 llc_prefetches = 36; + optional uint64 llc_prefetch_misses = 37; + optional uint64 dtlb_loads = 38; + optional uint64 dtlb_load_misses = 39; + optional uint64 dtlb_stores = 40; + optional uint64 dtlb_store_misses = 41; + optional uint64 dtlb_prefetches = 42; + optional uint64 dtlb_prefetch_misses = 43; + optional uint64 itlb_loads = 44; + optional uint64 itlb_load_misses = 45; + optional uint64 branch_loads = 46; + optional uint64 branch_load_misses = 47; + optional uint64 node_loads = 48; + optional uint64 node_load_misses = 49; + optional uint64 node_stores = 50; + optional uint64 node_store_misses = 51; + optional uint64 node_prefetches = 52; + optional uint64 node_prefetch_misses = 53; +} + + +/** + * Describes a request for resources that can be used by a framework + * to proactively influence the allocator. If 'slave_id' is provided + * then this request is assumed to only apply to resources on that + * slave. + */ +message Request { + optional SlaveID slave_id = 1; + repeated Resource resources = 2; +} + + +/** + * Describes some resources available on a slave. An offer only + * contains resources from a single slave. + */ +message Offer { + required OfferID id = 1; + required FrameworkID framework_id = 2; + required SlaveID slave_id = 3; + required string hostname = 4; + + // URL for reaching the slave running on the host. + optional URL url = 8; + + // The domain of the slave. + optional DomainInfo domain = 11; + + repeated Resource resources = 5; + repeated Attribute attributes = 7; + + // Executors of the same framework running on this agent. + repeated ExecutorID executor_ids = 6; + + // Signifies that the resources in this Offer may be unavailable during + // the given interval. Any tasks launched using these resources may be + // killed when the interval arrives. For example, these resources may be + // part of a planned maintenance schedule. + // + // This field only provides information about a planned unavailability. + // The unavailability interval may not necessarily start at exactly this + // interval, nor last for exactly the duration of this interval. + // The unavailability may also be forever! See comments in + // `Unavailability` for more details. + optional Unavailability unavailability = 9; + + // An offer represents resources allocated to *one* of the + // roles managed by the scheduler. (Therefore, each + // `Offer.resources[i].allocation_info` will match the + // top level `Offer.allocation_info`). + optional Resource.AllocationInfo allocation_info = 10; + + // Defines an operation that can be performed against offers. + message Operation { + enum Type { + UNKNOWN = 0; + LAUNCH = 1; + LAUNCH_GROUP = 6; + RESERVE = 2; + UNRESERVE = 3; + CREATE = 4; + DESTROY = 5; + GROW_VOLUME = 11; // EXPERIMENTAL. + SHRINK_VOLUME = 12; // EXPERIMENTAL. + CREATE_DISK = 13; // EXPERIMENTAL. + DESTROY_DISK = 14; // EXPERIMENTAL. + } + + // TODO(vinod): Deprecate this in favor of `LaunchGroup` below. + message Launch { + repeated TaskInfo task_infos = 1; + } + + // Unlike `Launch` above, all the tasks in a `task_group` are + // atomically delivered to an executor. + // + // `NetworkInfo` set on executor will be shared by all tasks in + // the task group. + // + // TODO(vinod): Any volumes set on executor could be used by a + // task by explicitly setting `Volume.source` in its resources. + message LaunchGroup { + required ExecutorInfo executor = 1; + required TaskGroupInfo task_group = 2; + } + + message Reserve { + repeated Resource source = 2; + repeated Resource resources = 1; + } + + message Unreserve { + repeated Resource resources = 1; + } + + message Create { + repeated Resource volumes = 1; + } + + message Destroy { + repeated Resource volumes = 1; + } + + // Grow a volume by an additional disk resource. + // NOTE: This is currently experimental and only for persistent volumes + // created on ROOT/PATH disk. + message GrowVolume { + required Resource volume = 1; + required Resource addition = 2; + } + + // Shrink a volume by the size specified in the `subtract` field. + // NOTE: This is currently experimental and only for persistent volumes + // created on ROOT/PATH disk. + message ShrinkVolume { + required Resource volume = 1; + + // See comments in `Value.Scalar` for maximum precision supported. + required Value.Scalar subtract = 2; + } + + // Create a `MOUNT` or `BLOCK` disk resource backed by a CSI volume from a + // `RAW` disk resource. + // + // In the typical case where the `RAW` disk resource has a profile and no + // source ID, a new CSI volume will be provisioned by Mesos to back the + // returned `MOUNT` or `BLOCK` disk resource. However, the `RAW` disk + // resource can instead have no profile but a source ID, indicating that + // it is already backed by a CSI volume in one of the following scenarios: + // + // (1) The CSI volume is preprovisioned out-of-band. + // + // (2) The CSI volume is provisioned by Mesos, but Mesos has lost the + // corresponding `MOUNT` or `BLOCK` resource metadata. This could + // happen if there has been a change in the agent ID or resource + // provider ID where the volume belongs. + // + // In the above cases, Mesos won't provision a new CSI volume, but instead + // will simply return a `MOUNT` or `BLOCK` disk resource backed by the same + // CSI volume, with the profile specified in this call. + // + // NOTE: For the time being, this API is subject to change and the related + // feature is experimental. + message CreateDisk { + required Resource source = 1; + + // NOTE: Only `MOUNT` or `BLOCK` is allowed in this field. + required Resource.DiskInfo.Source.Type target_type = 2; + + // Apply the specified profile to the created disk. This field must be set + // if `source` does not have a profile, and must not be set if it has one. + // + // NOTE: The operation will fail If the specified profile is unknown to + // Mesos, i.e., not reported by the disk profile adaptor. + optional string target_profile = 3; + } + + // Destroy a disk resource backed by a CSI volume. + // + // In the typical case where the CSI plugin of the volume supports volume + // deprovisioning and the disk resource is a `MOUNT` or `BLOCK` disk with a + // profile known to Mesos, the volume will be deprovisioned and a `RAW` disk + // resource with the same profile but no source ID will be returned. + // However, the following scenarios could lead to different outcomes: + // + // (1) If the CSI plugin supports volume deprovisioning but the profile of + // the disk resource is unknown to the disk profile adaptor, or the disk + // resource is a `RAW` disk with no profile but a source ID (see above + // for possible scenarios), the volume will be deprovisioned but no + // resource will be returned. + // + // (2) If the CSI plugin does not support volume deprovisioning, the volume + // won't be deprovisioned and a `RAW` disk resource with no profile but + // the same source ID will be returned. + // + // NOTE: For the time being, this API is subject to change and the related + // feature is experimental. + message DestroyDisk { + // NOTE: Only a `MOUNT`, `BLOCK` or `RAW` disk is allowed in this field. + required Resource source = 1; + } + + optional Type type = 1; + + // The `id` field allows frameworks to indicate that they wish to receive + // feedback about an operation via the UPDATE_OPERATION_STATUS event in the + // v1 scheduler API. + optional OperationID id = 12; // EXPERIMENTAL. + + optional Launch launch = 2; + optional LaunchGroup launch_group = 7; + optional Reserve reserve = 3; + optional Unreserve unreserve = 4; + optional Create create = 5; + optional Destroy destroy = 6; + optional GrowVolume grow_volume = 13; // EXPERIMENTAL. + optional ShrinkVolume shrink_volume = 14; // EXPERIMENTAL. + optional CreateDisk create_disk = 15; // EXPERIMENTAL. + optional DestroyDisk destroy_disk = 16; // EXPERIMENTAL. + } +} + + +/** + * A request to return some resources occupied by a framework. + */ +message InverseOffer { + // This is the same OfferID as found in normal offers, which allows + // re-use of some of the OfferID-only messages. + required OfferID id = 1; + + // URL for reaching the slave running on the host. This enables some + // optimizations as described in MESOS-3012, such as allowing the + // scheduler driver to bypass the master and talk directly with a slave. + optional URL url = 2; + + // The framework that should release its resources. + // If no specifics are provided (i.e. which slave), all the framework's + // resources are requested back. + required FrameworkID framework_id = 3; + + // Specified if the resources need to be released from a particular slave. + // All the framework's resources on this slave are requested back, + // unless further qualified by the `resources` field. + optional SlaveID slave_id = 4; + + // This InverseOffer represents a planned unavailability event in the + // specified interval. Any tasks running on the given framework or slave + // may be killed when the interval arrives. Therefore, frameworks should + // aim to gracefully terminate tasks prior to the arrival of the interval. + // + // For reserved resources, the resources are expected to be returned to the + // framework after the unavailability interval. This is an expectation, + // not a guarantee. For example, if the unavailability duration is not set, + // the resources may be removed permanently. + // + // For other resources, there is no guarantee that requested resources will + // be returned after the unavailability interval. The allocator has no + // obligation to re-offer these resources to the prior framework after + // the unavailability. + required Unavailability unavailability = 5; + + // A list of resources being requested back from the framework, + // on the slave identified by `slave_id`. If no resources are specified + // then all resources are being requested back. For the purpose of + // maintenance, this field is always empty (maintenance always requests + // all resources back). + repeated Resource resources = 6; + + // TODO(josephw): Add additional options for narrowing down the resources + // being requested back. Such as specific executors, tasks, etc. +} + + +/** + * Describes a task. Passed from the scheduler all the way to an + * executor (see SchedulerDriver::launchTasks and + * Executor::launchTask). Either ExecutorInfo or CommandInfo should be set. + * A different executor can be used to launch this task, and subsequent tasks + * meant for the same executor can reuse the same ExecutorInfo struct. + */ +message TaskInfo { + required string name = 1; + required TaskID task_id = 2; + required SlaveID slave_id = 3; + repeated Resource resources = 4; + optional ExecutorInfo executor = 5; + optional CommandInfo command = 7; + + // Task provided with a container will launch the container as part + // of this task paired with the task's CommandInfo. + optional ContainerInfo container = 9; + + // A health check for the task. Implemented for executor-less + // command-based tasks. For tasks that specify an executor, it is + // the executor's responsibility to implement the health checking. + optional HealthCheck health_check = 8; + + // A general check for the task. Implemented for all built-in executors. + // For tasks that specify an executor, it is the executor's responsibility + // to implement checking support. Executors should (all built-in executors + // will) neither interpret nor act on the check's result. + // + // NOTE: Check support in built-in executors is experimental. + // + // TODO(alexr): Consider supporting multiple checks per task. + optional CheckInfo check = 13; + + // A kill policy for the task. Implemented for executor-less + // command-based and docker tasks. For tasks that specify an + // executor, it is the executor's responsibility to implement + // the kill policy. + optional KillPolicy kill_policy = 12; + + optional bytes data = 6; + + // Labels are free-form key value pairs which are exposed through + // master and slave endpoints. Labels will not be interpreted or + // acted upon by Mesos itself. As opposed to the data field, labels + // will be kept in memory on master and slave processes. Therefore, + // labels should be used to tag tasks with light-weight meta-data. + // Labels should not contain duplicate key-value pairs. + optional Labels labels = 10; + + // Service discovery information for the task. It is not interpreted + // or acted upon by Mesos. It is up to a service discovery system + // to use this information as needed and to handle tasks without + // service discovery information. + optional DiscoveryInfo discovery = 11; + + // Maximum duration for task completion. If the task is non-terminal at the + // end of this duration, it will fail with the reason + // `REASON_MAX_COMPLETION_TIME_REACHED`. Mesos supports this field for + // executor-less tasks, and tasks that use Docker or default executors. + // It is the executor's responsibility to implement this, so it might not be + // supported by all custom executors. + optional DurationInfo max_completion_time = 14; + + // Resource limits associated with the task. + map limits = 15; +} + + +/** + * Describes a group of tasks that belong to an executor. The + * executor will receive the task group in a single message to + * allow the group to be launched "atomically". + * + * NOTES: + * 1) `NetworkInfo` must not be set inside task's `ContainerInfo`. + * 2) `TaskInfo.executor` doesn't need to set. If set, it should match + * `LaunchGroup.executor`. + */ +message TaskGroupInfo { + repeated TaskInfo tasks = 1; +} + + +// TODO(bmahler): Add executor_uuid here, and send it to the master. This will +// allow us to expose executor work directories for tasks in the webui when +// looking from the master level. Currently only the slave knows which run the +// task belongs to. +/** + * Describes a task, similar to `TaskInfo`. + * + * `Task` is used in some of the Mesos messages found below. + * `Task` is used instead of `TaskInfo` if: + * 1) we need additional IDs, such as a specific + * framework, executor, or agent; or + * 2) we do not need the additional data, such as the command run by the + * task. These additional fields may be large and unnecessary for some + * Mesos messages. + * + * `Task` is generally constructed from a `TaskInfo`. See protobuf::createTask. + */ +message Task { + required string name = 1; + required TaskID task_id = 2; + required FrameworkID framework_id = 3; + optional ExecutorID executor_id = 4; + required SlaveID slave_id = 5; + required TaskState state = 6; // Latest state of the task. + repeated Resource resources = 7; + repeated TaskStatus statuses = 8; + + // These fields correspond to the state and uuid of the latest + // status update forwarded to the master. + // NOTE: Either both the fields must be set or both must be unset. + optional TaskState status_update_state = 9; + optional bytes status_update_uuid = 10; + + optional Labels labels = 11; + + // Service discovery information for the task. It is not interpreted + // or acted upon by Mesos. It is up to a service discovery system + // to use this information as needed and to handle tasks without + // service discovery information. + optional DiscoveryInfo discovery = 12; + + // Container information for the task. + optional ContainerInfo container = 13; + + optional HealthCheck health_check = 15; + + // TODO(greggomann): Add the task's `CheckInfo`. See MESOS-8780. + + // The kill policy used for this task when it is killed. It's possible for + // this policy to be overridden by the scheduler when killing the task. + optional KillPolicy kill_policy = 16; + + // Specific user under which task is running. + optional string user = 14; + + // Resource limits associated with the task. + map limits = 17; +} + + +/** + * Describes possible task states. IMPORTANT: Mesos assumes tasks that + * enter terminal states (see below) imply the task is no longer + * running and thus clean up any thing associated with the task + * (ultimately offering any resources being consumed by that task to + * another task). + */ +enum TaskState { + TASK_STAGING = 6; // Initial state. Framework status updates should not use. + TASK_STARTING = 0; // The task is being launched by the executor. + TASK_RUNNING = 1; + + // NOTE: This should only be sent when the framework has + // the TASK_KILLING_STATE capability. + TASK_KILLING = 8; // The task is being killed by the executor. + + // The task finished successfully on its own without external interference. + TASK_FINISHED = 2; // TERMINAL. + + TASK_FAILED = 3; // TERMINAL: The task failed to finish successfully. + TASK_KILLED = 4; // TERMINAL: The task was killed by the executor. + TASK_ERROR = 7; // TERMINAL: The task description contains an error. + + // In Mesos 1.3, this will only be sent when the framework does NOT + // opt-in to the PARTITION_AWARE capability. + // + // NOTE: This state is not always terminal. For example, tasks might + // transition from TASK_LOST to TASK_RUNNING or other states when a + // partitioned agent reregisters. + TASK_LOST = 5; // The task failed but can be rescheduled. + + // The following task states are only sent when the framework + // opts-in to the PARTITION_AWARE capability. + + // The task failed to launch because of a transient error. The + // task's executor never started running. Unlike TASK_ERROR, the + // task description is valid -- attempting to launch the task again + // may be successful. + TASK_DROPPED = 9; // TERMINAL. + + // The task was running on an agent that has lost contact with the + // master, typically due to a network failure or partition. The task + // may or may not still be running. + TASK_UNREACHABLE = 10; + + // The task is no longer running. This can occur if the agent has + // been terminated along with all of its tasks (e.g., the host that + // was running the agent was rebooted). It might also occur if the + // task was terminated due to an agent or containerizer error, or if + // the task was preempted by the QoS controller in an + // oversubscription scenario. + TASK_GONE = 11; // TERMINAL. + + // The task was running on an agent that the master cannot contact; + // the operator has asserted that the agent has been shutdown, but + // this has not been directly confirmed by the master. If the + // operator is correct, the task is not running and this is a + // terminal state; if the operator is mistaken, the task may still + // be running and might return to RUNNING in the future. + TASK_GONE_BY_OPERATOR = 12; + + // The master has no knowledge of the task. This is typically + // because either (a) the master never had knowledge of the task, or + // (b) the master forgot about the task because it garbage collected + // its metadata about the task. The task may or may not still be + // running. + TASK_UNKNOWN = 13; +} + + +/** + * Describes a resource limitation that caused a task failure. + */ +message TaskResourceLimitation { + // This field contains the resource whose limits were violated. + // + // NOTE: 'Resources' is used here because the resource may span + // multiple roles (e.g. `"mem(*):1;mem(role):2"`). + repeated Resource resources = 1; +} + + +/** + * A 128 bit (16 byte) UUID, see RFC 4122. + */ +message UUID { + required bytes value = 1; +} + + +/** + * Describes an operation, similar to `Offer.Operation`, with + * some additional information. + */ +message Operation { + optional FrameworkID framework_id = 1; + optional SlaveID slave_id = 2; + required Offer.Operation info = 3; + required OperationStatus latest_status = 4; + + // All the statuses known to this operation. Some of the statuses in this + // list might not have been acknowledged yet. The statuses are ordered. + repeated OperationStatus statuses = 5; + + // This is the internal UUID for the operation, which is kept independently + // from the framework-specified operation ID, which is optional. + required UUID uuid = 6; +} + + +/** + * Describes possible operation states. + */ +enum OperationState { + // Default value if the enum is not set. See MESOS-4997. + OPERATION_UNSUPPORTED = 0; + + // Initial state. + OPERATION_PENDING = 1; + + // TERMINAL: The operation was successfully applied. + OPERATION_FINISHED = 2; + + // TERMINAL: The operation failed to apply. + OPERATION_FAILED = 3; + + // TERMINAL: The operation description contains an error. + OPERATION_ERROR = 4; + + // TERMINAL: The operation was dropped due to a transient error. + OPERATION_DROPPED = 5; + + // The operation affects an agent that has lost contact with the master, + // typically due to a network failure or partition. The operation may or may + // not still be pending. + OPERATION_UNREACHABLE = 6; + + // The operation affected an agent that the master cannot contact; + // the operator has asserted that the agent has been shutdown, but this has + // not been directly confirmed by the master. + // + // If the operator is correct, the operation is not pending and this is a + // terminal state; if the operator is mistaken, the operation may still be + // pending and might return to a different state in the future. + OPERATION_GONE_BY_OPERATOR = 7; + + // The operation affects an agent that the master recovered from its + // state, but that agent has not yet re-registered. + // + // The operation can transition to `OPERATION_UNREACHABLE` if the + // corresponding agent is marked as unreachable, and will transition to + // another status if the agent re-registers. + OPERATION_RECOVERING = 8; + + // The master has no knowledge of the operation. This is typically + // because either (a) the master never had knowledge of the operation, or + // (b) the master forgot about the operation because it garbage collected + // its metadata about the operation. The operation may or may not still be + // pending. + OPERATION_UNKNOWN = 9; +} + + +/** + * Describes the current status of an operation. + */ +message OperationStatus { + // While frameworks will only receive status updates for operations on which + // they have set an ID, this field is optional because this message is also + // used internally by Mesos components when the operation's ID has not been + // set. + optional OperationID operation_id = 1; + + required OperationState state = 2; + optional string message = 3; + + // Converted resources after applying the operation. This only + // applies if the `state` is `OPERATION_FINISHED`. + repeated Resource converted_resources = 4; + + // Statuses that are delivered reliably to the scheduler will + // include a `uuid`. The status is considered delivered once + // it is acknowledged by the scheduler. + optional UUID uuid = 5; + + // If the operation affects resources from a local resource provider, + // both `slave_id` and `resource_provider_id` will be set. + // + // If the operation affects resources that belong to an external + // resource provider, only `resource_provider_id` will be set. + // + // In certain cases, e.g., invalid operations, neither `uuid`, + // `slave_id` nor `resource_provider_id` will be set, and the + // scheduler does not need to acknowledge this status update. + optional SlaveID slave_id = 6; + optional ResourceProviderID resource_provider_id = 7; +} + + +/** +* Describes the status of a check. Type and the corresponding field, i.e., +* `command` or `http` must be set. If the result of the check is not available +* (e.g., the check timed out), these fields must contain empty messages, i.e., +* `exit_code` or `status_code` will be unset. +* +* NOTE: This API is subject to change and the related feature is experimental. +*/ +message CheckStatusInfo { + message Command { + // Exit code of a command check. It is the result of calling + // `WEXITSTATUS()` on `waitpid()` termination information on + // Posix and calling `GetExitCodeProcess()` on Windows. + optional int32 exit_code = 1; + } + + message Http { + // HTTP status code of an HTTP check. + optional uint32 status_code = 1; + } + + message Tcp { + // Whether a TCP connection succeeded. + optional bool succeeded = 1; + } + + // TODO(alexr): Consider adding a `data` field, which can contain, e.g., + // truncated stdout/stderr output for command checks or HTTP response body + // for HTTP checks. Alternatively, it can be an even shorter `message` field + // containing the last line of stdout or Reason-Phrase of the status line of + // the HTTP response. + + // The type of the check this status corresponds to. + optional CheckInfo.Type type = 1; + + // Status of a command check. + optional Command command = 2; + + // Status of an HTTP check. + optional Http http = 3; + + // Status of a TCP check. + optional Tcp tcp = 4; + + // TODO(alexr): Consider introducing a "last changed at" timestamp, since + // task status update's timestamp may not correspond to the last check's + // state, e.g., for reconciliation. + + // TODO(alexr): Consider introducing a `reason` enum here to explicitly + // distinguish between completed, delayed, and timed out checks. +} + + +/** + * Describes the current status of a task. + */ +message TaskStatus { + // Describes the source of the task status update. + enum Source { + SOURCE_MASTER = 0; + SOURCE_SLAVE = 1; + SOURCE_EXECUTOR = 2; + } + + // Detailed reason for the task status update. + // Refer to docs/task-state-reasons.md for additional explanation. + enum Reason { + // TODO(jieyu): The default value when a caller doesn't check for + // presence is 0 and so ideally the 0 reason is not a valid one. + // Since this is not used anywhere, consider removing this reason. + REASON_COMMAND_EXECUTOR_FAILED = 0; + + REASON_CONTAINER_LAUNCH_FAILED = 21; + REASON_CONTAINER_LIMITATION = 19; + REASON_CONTAINER_LIMITATION_DISK = 20; + REASON_CONTAINER_LIMITATION_MEMORY = 8; + REASON_CONTAINER_PREEMPTED = 17; + REASON_CONTAINER_UPDATE_FAILED = 22; + REASON_MAX_COMPLETION_TIME_REACHED = 33; + REASON_EXECUTOR_REGISTRATION_TIMEOUT = 23; + REASON_EXECUTOR_REREGISTRATION_TIMEOUT = 24; + REASON_EXECUTOR_TERMINATED = 1; + REASON_EXECUTOR_UNREGISTERED = 2; // No longer used. + REASON_FRAMEWORK_REMOVED = 3; + REASON_GC_ERROR = 4; + REASON_INVALID_FRAMEWORKID = 5; + REASON_INVALID_OFFERS = 6; + REASON_IO_SWITCHBOARD_EXITED = 27; + REASON_MASTER_DISCONNECTED = 7; + REASON_RECONCILIATION = 9; + REASON_RESOURCES_UNKNOWN = 18; + REASON_SLAVE_DISCONNECTED = 10; + REASON_SLAVE_DRAINING = 34; + REASON_SLAVE_REMOVED = 11; + REASON_SLAVE_REMOVED_BY_OPERATOR = 31; + REASON_SLAVE_REREGISTERED = 32; + REASON_SLAVE_RESTARTED = 12; + REASON_SLAVE_UNKNOWN = 13; + REASON_TASK_KILLED_DURING_LAUNCH = 30; + REASON_TASK_CHECK_STATUS_UPDATED = 28; + REASON_TASK_HEALTH_CHECK_STATUS_UPDATED = 29; + REASON_TASK_GROUP_INVALID = 25; + REASON_TASK_GROUP_UNAUTHORIZED = 26; + REASON_TASK_INVALID = 14; + REASON_TASK_UNAUTHORIZED = 15; + REASON_TASK_UNKNOWN = 16; + } + + required TaskID task_id = 1; + required TaskState state = 2; + optional string message = 4; // Possible message explaining state. + optional Source source = 9; + optional Reason reason = 10; + optional bytes data = 3; + optional SlaveID slave_id = 5; + optional ExecutorID executor_id = 7; // TODO(benh): Use in master/slave. + optional double timestamp = 6; + + // Statuses that are delivered reliably to the scheduler will + // include a 'uuid'. The status is considered delivered once + // it is acknowledged by the scheduler. Schedulers can choose + // to either explicitly acknowledge statuses or let the scheduler + // driver implicitly acknowledge (default). + // + // TODO(bmahler): This is currently overwritten in the scheduler + // driver and executor driver, but executors will need to set this + // to a valid RFC-4122 UUID if using the HTTP API. + optional bytes uuid = 11; + + // Describes whether the task has been determined to be healthy (true) or + // unhealthy (false) according to the `health_check` field in `TaskInfo`. + optional bool healthy = 8; + + // Contains check status for the check specified in the corresponding + // `TaskInfo`. If no check has been specified, this field must be + // absent, otherwise it must be present even if the check status is + // not available yet. If the status update is triggered for a different + // reason than `REASON_TASK_CHECK_STATUS_UPDATED`, this field will contain + // the last known value. + // + // NOTE: A check-related task status update is triggered if and only if + // the value or presence of any field in `CheckStatusInfo` changes. + // + // NOTE: Check support in built-in executors is experimental. + optional CheckStatusInfo check_status = 15; + + // Labels are free-form key value pairs which are exposed through + // master and slave endpoints. Labels will not be interpreted or + // acted upon by Mesos itself. As opposed to the data field, labels + // will be kept in memory on master and slave processes. Therefore, + // labels should be used to tag TaskStatus message with light-weight + // meta-data. Labels should not contain duplicate key-value pairs. + optional Labels labels = 12; + + // Container related information that is resolved dynamically such as + // network address. + optional ContainerStatus container_status = 13; + + // The time (according to the master's clock) when the agent where + // this task was running became unreachable. This is only set on + // status updates for tasks running on agents that are unreachable + // (e.g., partitioned away from the master). + optional TimeInfo unreachable_time = 14; + + // If the reason field indicates a container resource limitation, + // this field optionally contains additional information. + optional TaskResourceLimitation limitation = 16; +} + + +/** + * Describes possible filters that can be applied to unused resources + * (see SchedulerDriver::launchTasks) to influence the allocator. + */ +message Filters { + // Time to consider unused resources refused. Note that all unused + // resources will be considered refused and use the default value + // (below) regardless of whether Filters was passed to + // SchedulerDriver::launchTasks. You MUST pass Filters with this + // field set to change this behavior (i.e., get another offer which + // includes unused resources sooner or later than the default). + // + // If this field is set to a number of seconds greater than 31536000 + // (365 days), then the resources will be considered refused for 365 + // days. If it is set to a negative number, then the default value + // will be used. + optional double refuse_seconds = 1 [default = 5.0]; +} + + +/** +* Describes a collection of environment variables. This is used with +* CommandInfo in order to set environment variables before running a +* command. The contents of each variable may be specified as a string +* or a Secret; only one of `value` and `secret` must be set. +*/ +message Environment { + message Variable { + required string name = 1; + + enum Type { + UNKNOWN = 0; + VALUE = 1; + SECRET = 2; + } + + // In Mesos 1.2, the `Environment.variables.value` message was made + // optional. The default type for `Environment.variables.type` is now VALUE, + // which requires `value` to be set, maintaining backward compatibility. + // + // TODO(greggomann): The default can be removed in Mesos 2.1 (MESOS-7134). + optional Type type = 3 [default = VALUE]; + + // Only one of `value` and `secret` must be set. + optional string value = 2; + optional Secret secret = 4; + } + + repeated Variable variables = 1; +} + + +/** + * A generic (key, value) pair used in various places for parameters. + */ +message Parameter { + required string key = 1; + required string value = 2; +} + + +/** + * Collection of Parameter. + */ +message Parameters { + repeated Parameter parameter = 1; +} + + +/** + * Credential used in various places for authentication and + * authorization. + * + * NOTE: A 'principal' is different from 'FrameworkInfo.user'. The + * former is used for authentication and authorization while the + * latter is used to determine the default user under which the + * framework's executors/tasks are run. + */ +message Credential { + required string principal = 1; + optional string secret = 2; +} + + +/** + * Credentials used for framework authentication, HTTP authentication + * (where the common 'username' and 'password' are captured as + * 'principal' and 'secret' respectively), etc. + */ +message Credentials { + repeated Credential credentials = 1; +} + + +/** + * Secret used to pass privileged information. It is designed to provide + * pass-by-value or pass-by-reference semantics, where the REFERENCE type can be + * used by custom modules which interact with a secure back-end. + */ +message Secret +{ + enum Type { + UNKNOWN = 0; + REFERENCE = 1; + VALUE = 2; + } + + // Can be used by modules to refer to a secret stored in a secure back-end. + // The `key` field is provided to permit reference to a single value within a + // secret containing arbitrary key-value pairs. + // + // For example, given a back-end secret store with a secret named + // "my-secret" containing the following key-value pairs: + // + // { + // "username": "my-user", + // "password": "my-password + // } + // + // the username could be referred to in a `Secret` by specifying + // "my-secret" for the `name` and "username" for the `key`. + message Reference + { + required string name = 1; + optional string key = 2; + } + + // Used to pass the value of a secret. + message Value + { + required bytes data = 1; + } + + optional Type type = 1; + + // Only one of `reference` and `value` must be set. + optional Reference reference = 2; + optional Value value = 3; +} + + +/** + * Rate (queries per second, QPS) limit for messages from a framework to master. + * Strictly speaking they are the combined rate from all frameworks of the same + * principal. + */ +message RateLimit { + // Leaving QPS unset gives it unlimited rate (i.e., not throttled), + // which also implies unlimited capacity. + optional double qps = 1; + + // Principal of framework(s) to be throttled. Should match + // FrameworkInfo.principal and Credential.principal (if using authentication). + required string principal = 2; + + // Max number of outstanding messages from frameworks of this principal + // allowed by master before the next message is dropped and an error is sent + // back to the sender. Messages received before the capacity is reached are + // still going to be processed after the error is sent. + // If unspecified, this principal is assigned unlimited capacity. + // NOTE: This value is ignored if 'qps' is not set. + optional uint64 capacity = 3; +} + + +/** + * Collection of RateLimit. + * Frameworks without rate limits defined here are not throttled unless + * 'aggregate_default_qps' is specified. + */ +message RateLimits { + // Items should have unique principals. + repeated RateLimit limits = 1; + + // All the frameworks not specified in 'limits' get this default rate. + // This rate is an aggregate rate for all of them, i.e., their combined + // traffic is throttled together at this rate. + optional double aggregate_default_qps = 2; + + // All the frameworks not specified in 'limits' get this default capacity. + // This is an aggregate value similar to 'aggregate_default_qps'. + optional uint64 aggregate_default_capacity = 3; +} + + +/** + * Describe an image used by tasks or executors. Note that it's only + * for tasks or executors launched by MesosContainerizer currently. + */ +message Image { + enum Type { + APPC = 1; + DOCKER = 2; + } + + // Protobuf for specifying an Appc container image. See: + // https://github.com/appc/spec/blob/master/spec/aci.md + message Appc { + // The name of the image. + required string name = 1; + + // An image ID is a string of the format "hash-value", where + // "hash" is the hash algorithm used and "value" is the hex + // encoded string of the digest. Currently the only permitted + // hash algorithm is sha512. + optional string id = 2; + + // Optional labels. Suggested labels: "version", "os", and "arch". + optional Labels labels = 3; + } + + message Docker { + // The name of the image. Expected format: + // [REGISTRY_HOST[:REGISTRY_PORT]/]REPOSITORY[:TAG|@TYPE:DIGEST] + // + // See: https://docs.docker.com/reference/commandline/pull/ + required string name = 1; + + // Credential to authenticate with docker registry. + // NOTE: This is not encrypted, therefore framework and operators + // should enable SSL when passing this information. + // + // This field has never been used in Mesos before and is + // deprecated since Mesos 1.3. Please use `config` below + // (see MESOS-7088 for details). + optional Credential credential = 2 [deprecated = true]; // Since 1.3. + + // Docker config containing credentials to authenticate with + // docker registry. The secret is expected to be a docker + // config file in JSON format with UTF-8 character encoding. + optional Secret config = 3; + } + + required Type type = 1; + + // Only one of the following image messages should be set to match + // the type. + optional Appc appc = 2; + optional Docker docker = 3; + + // With this flag set to false, the mesos containerizer will pull + // the docker/appc image from the registry even if the image is + // already downloaded on the agent. + optional bool cached = 4 [default = true]; +} + + +/** + * Describes how the mount will be propagated for a volume. See the + * following doc for more details about mount propagation: + * https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt + */ +message MountPropagation { + enum Mode { + UNKNOWN = 0; + + // The volume in a container will receive new mounts from the host + // or other containers, but filesystems mounted inside the + // container won't be propagated to the host or other containers. + // This is currently the default behavior for all volumes. + HOST_TO_CONTAINER = 1; + + // The volume in a container will receive new mounts from the host + // or other containers, and its own mounts will be propagated from + // the container to the host or other containers. + BIDIRECTIONAL = 2; + } + + optional Mode mode = 1; +} + + +/** + * Describes a volume mapping either from host to container or vice + * versa. Both paths can either refer to a directory or a file. + */ +message Volume { + enum Mode { + RW = 1; // read-write. + RO = 2; // read-only. + } + + // TODO(gyliu513): Make this as `optional` after deprecation cycle of 1.0. + required Mode mode = 3; + + // Path pointing to a directory or file in the container. If the path + // is a relative path, it is relative to the container work directory. + // If the path is an absolute path and the container does not have its + // own rootfs, that path must already exist in the agent host rootfs. + required string container_path = 1; + + // The following specifies the source of this volume. At most one of + // the following should be set. + + // Absolute path pointing to a directory or file on the host or a + // path relative to the container work directory. + optional string host_path = 2; + + // The source of the volume is an Image which describes a root + // filesystem which will be provisioned by Mesos. + optional Image image = 4; + + // Describes where a volume originates from. + message Source { + enum Type { + // This must be the first enum value in this list, to + // ensure that if 'type' is not set, the default value + // is UNKNOWN. This enables enum values to be added + // in a backwards-compatible way. See: MESOS-4997. + UNKNOWN = 0; + + // TODO(gyliu513): Add IMAGE as volume source type. + DOCKER_VOLUME = 1; + HOST_PATH = 4; + SANDBOX_PATH = 2; + SECRET = 3; + CSI_VOLUME = 5; + } + + message DockerVolume { + // Driver of the volume, it can be flocker, convoy, raxrey etc. + optional string driver = 1; + + // Name of the volume. + required string name = 2; + + // Volume driver specific options. + optional Parameters driver_options = 3; + } + + // Absolute path pointing to a directory or file on the host. + message HostPath { + required string path = 1; + optional MountPropagation mount_propagation = 2; + } + + // Describe a path from a container's sandbox. The container can + // be the current container (SELF), or its parent container + // (PARENT). PARENT allows all child containers to share a volume + // from their parent container's sandbox. It'll be an error if + // the current container is a top level container. + message SandboxPath { + enum Type { + UNKNOWN = 0; + SELF = 1; + PARENT = 2; + } + + optional Type type = 1; + + // A path relative to the corresponding container's sandbox. + // Note that upwards traversal (i.e. ../../abc) is not allowed. + required string path = 2; + } + + // A volume which will be handled by the `volume/csi` isolator. + message CSIVolume { + // The name of the CSI plugin. + required string plugin_name = 1; + + // Specifies a capability of a volume. + // https://github.com/container-storage-interface/spec/blob/v1.3.0/csi.proto#L379:L438 + message VolumeCapability { + // Indicates that the volume will be accessed via the block device API. + message BlockVolume { + // Intentionally empty, for now. + } + + // Indicates that the volume will be accessed via the filesystem API. + message MountVolume { + // The filesystem type. An empty string is equal to an unspecified + // field value. + optional string fs_type = 1; + + // The mount options that can be used for the volume. This field is + // OPTIONAL. `mount_flags` MAY contain sensitive information. + // Therefore, Mesos and the Plugin MUST NOT leak this information + // to untrusted entities. The total size of this repeated field + // SHALL NOT exceed 4 KiB. + repeated string mount_flags = 2; + } + + // Specifies how a volume can be accessed. + message AccessMode { + enum Mode { + UNKNOWN = 0; + + // Can only be published once as read/write on a single node, at + // any given time. + SINGLE_NODE_WRITER = 1; + + // Can only be published once as readonly on a single node, at + // any given time. + SINGLE_NODE_READER_ONLY = 2; + + // Can be published as readonly at multiple nodes simultaneously. + MULTI_NODE_READER_ONLY = 3; + + // Can be published at multiple nodes simultaneously. Only one of + // the node can be used as read/write. The rest will be readonly. + MULTI_NODE_SINGLE_WRITER = 4; + + // Can be published as read/write at multiple nodes + // simultaneously. + MULTI_NODE_MULTI_WRITER = 5; + } + + required Mode mode = 1; + } + + // Specifies what API the volume will be accessed using. One of the + // following fields MUST be specified. + oneof access_type { + BlockVolume block = 1; + MountVolume mount = 2; + } + + required AccessMode access_mode = 3; + } + + // Specifies the parameters used to stage/publish a pre-provisioned volume + // on an agent host. The fields are merged from `NodeStageVolumeRequest` + // and `NodePublishVolumeRequest` protobuf messages defined in CSI spec + // except two fields `staging_target_path` and `target_path` which will be + // internally determined by Mesos when staging/publishing the volume. + message StaticProvisioning { + required string volume_id = 1; + required VolumeCapability volume_capability = 2; + + // The secrets needed for staging/publishing the volume, e.g.: + // { + // "username": {"type": REFERENCE, "reference": {"name": "U_SECRET"}}, + // "password": {"type": REFERENCE, "reference": {"name": "P_SECRET"}} + // } + map node_stage_secrets = 3; + map node_publish_secrets = 4; + map volume_context = 5; + } + + optional StaticProvisioning static_provisioning = 2; + } + + // Enum fields should be optional, see: MESOS-4997. + optional Type type = 1; + + // The following specifies the source of this volume. At most one of + // the following should be set. + + // The source of the volume created by docker volume driver. + optional DockerVolume docker_volume = 2; + + optional HostPath host_path = 5; + optional SandboxPath sandbox_path = 3; + + // The volume/secret isolator uses the secret-fetcher module (third-party or + // internal) downloads the secret and makes it available at container_path. + optional Secret secret = 4; + + optional CSIVolume csi_volume = 6; + } + + optional Source source = 5; +} + + +/** + * Describes a network request from a framework as well as network resolution + * provided by Mesos. + * + * A framework may request the network isolator on the Agent to isolate the + * container in a network namespace and create a virtual network interface. + * The `NetworkInfo` message describes the properties of that virtual + * interface, including the IP addresses and network isolation policy + * (network group membership). + * + * The NetworkInfo message is not interpreted by the Master or Agent and is + * intended to be used by Agent and Master modules implementing network + * isolation. If the modules are missing, the message is simply ignored. In + * future, the task launch will fail if there is no module providing the + * network isolation capabilities (MESOS-3390). + * + * An executor, Agent, or an Agent module may append NetworkInfos inside + * TaskStatus::container_status to provide information such as the container IP + * address and isolation groups. + */ +message NetworkInfo { + enum Protocol { + IPv4 = 1; + IPv6 = 2; + } + + // Specifies a request for an IP address, or reports the assigned container + // IP address. + // + // Users can request an automatically assigned IP (for example, via an + // IPAM service) or a specific IP by adding a NetworkInfo to the + // ContainerInfo for a task. On a request, specifying neither `protocol` + // nor `ip_address` means that any available address may be assigned. + message IPAddress { + // Specify IP address requirement. Set protocol to the desired value to + // request the network isolator on the Agent to assign an IP address to the + // container being launched. If a specific IP address is specified in + // ip_address, this field should not be set. + optional Protocol protocol = 1 [default = IPv4]; + + // Statically assigned IP provided by the Framework. This IP will be + // assigned to the container by the network isolator module on the Agent. + // This field should not be used with the protocol field above. + // + // If an explicit address is requested but is unavailable, the network + // isolator should fail the task. + optional string ip_address = 2; + } + + // When included in a ContainerInfo, each of these represent a + // request for an IP address. Each request can specify an explicit address + // or the IP protocol to use. + // + // When included in a TaskStatus message, these inform the framework + // scheduler about the IP addresses that are bound to the container + // interface. When there are no custom network isolator modules installed, + // this field is filled in automatically with the Agent IP address. + repeated IPAddress ip_addresses = 5; + + // Name of the network which will be used by network isolator to determine + // the network that the container joins. It's up to the network isolator + // to decide how to interpret this field. + optional string name = 6; + + // A group is the name given to a set of logically-related interfaces that + // are allowed to communicate among themselves. Network traffic is allowed + // between two container interfaces that share at least one network group. + // For example, one might want to create separate groups for isolating dev, + // testing, qa and prod deployment environments. + repeated string groups = 3; + + // To tag certain metadata to be used by Isolator/IPAM, e.g., rack, etc. + optional Labels labels = 4; + + // Specifies a port mapping request for the task on this network. + message PortMapping { + required uint32 host_port = 1; + required uint32 container_port = 2; + // Protocol to expose as (ie: tcp, udp). + optional string protocol = 3; + } + + repeated PortMapping port_mappings = 7; +}; + + +/** + * Encapsulation of `Capabilities` supported by Linux. + * Reference: http://linux.die.net/man/7/capabilities. + */ +message CapabilityInfo { + // We start the actual values at an offset(1000) because Protobuf 2 + // uses the first value as the default one. Separating the default + // value from the real first value helps to disambiguate them. This + // is especially valuable for backward compatibility. + // See: MESOS-4997. + enum Capability { + UNKNOWN = 0; + CHOWN = 1000; + DAC_OVERRIDE = 1001; + DAC_READ_SEARCH = 1002; + FOWNER = 1003; + FSETID = 1004; + KILL = 1005; + SETGID = 1006; + SETUID = 1007; + SETPCAP = 1008; + LINUX_IMMUTABLE = 1009; + NET_BIND_SERVICE = 1010; + NET_BROADCAST = 1011; + NET_ADMIN = 1012; + NET_RAW = 1013; + IPC_LOCK = 1014; + IPC_OWNER = 1015; + SYS_MODULE = 1016; + SYS_RAWIO = 1017; + SYS_CHROOT = 1018; + SYS_PTRACE = 1019; + SYS_PACCT = 1020; + SYS_ADMIN = 1021; + SYS_BOOT = 1022; + SYS_NICE = 1023; + SYS_RESOURCE = 1024; + SYS_TIME = 1025; + SYS_TTY_CONFIG = 1026; + MKNOD = 1027; + LEASE = 1028; + AUDIT_WRITE = 1029; + AUDIT_CONTROL = 1030; + SETFCAP = 1031; + MAC_OVERRIDE = 1032; + MAC_ADMIN = 1033; + SYSLOG = 1034; + WAKE_ALARM = 1035; + BLOCK_SUSPEND = 1036; + AUDIT_READ = 1037; + PERFMON = 1038; + BPF = 1039; + CHECKPOINT_RESTORE = 1040; + } + + repeated Capability capabilities = 1; +} + + +/** + * Encapsulation for Seccomp configuration, which is Linux specific. + */ +message SeccompInfo { + // A filename of the Seccomp profile. This should be a path + // relative to the directory containing Seccomp profiles, + // which is specified on the agent via the `--seccomp_config_dir` flag. + optional string profile_name = 1; + + // If set to `true`, Seccomp is not applied to the container. + // If not set or set to `false`, the container is launched with + // the profile specified in the `profile_name` field. + // + // NOTE: `profile_name` must not be specified if `unconfined` set to `true`. + // `profile_name` must be specified if `unconfined` is not set or + // is set to `false`. + optional bool unconfined = 2; +} + + +/** + * Encapsulation for Linux specific configuration. + * E.g, capabilities, limits etc. + */ +message LinuxInfo { + // Since 1.4.0, deprecated in favor of `effective_capabilities`. + optional CapabilityInfo capability_info = 1 [deprecated = true]; + + // The set of capabilities that are allowed but not initially + // granted to tasks. + optional CapabilityInfo bounding_capabilities = 2; + + // Represents the set of capabilities that the task will + // be executed with. + optional CapabilityInfo effective_capabilities = 3; + + // If set as 'true', the container shares the pid namespace with + // its parent. If the container is a top level container, it will + // share the pid namespace with the agent. If the container is a + // nested container, it will share the pid namespace with its + // parent container. This field will be ignored if 'namespaces/pid' + // isolator is not enabled. + optional bool share_pid_namespace = 4; + + // Represents Seccomp configuration, which is used for syscall filtering. + // This field is used to override the agent's default Seccomp configuration. + optional SeccompInfo seccomp = 5; + + enum IpcMode { + UNKNOWN = 0; + + // The container will have its own IPC namespace and /dev/shm, with a + // possibility to share them with its child containers. + PRIVATE = 1; + + // The container will share the IPC namespace and /dev/shm from its + // parent. If the container is a top level container, it will share + // the IPC namespace and /dev/shm from the agent host, if the container + // is a nested container, it will share the IPC namespace and /dev/shm + // from its parent container. The implication is if a nested container + // wants to share the IPC namespace and /dev/shm from the agent host, + // its parent container has to do it first. + SHARE_PARENT = 2; + } + + // There are two special cases that we need to handle for this field: + // 1. This field is not set: For backward compatibility we will keep the + // previous behavior: Top level container will have its own IPC namespace + // and nested container will share the IPC namespace from its parent + // container. If the container does not have its own rootfs, it will share + // agent's /dev/shm, otherwise it will have its own /dev/shm. + // 2. The `namespaces/ipc` isolator is not enabled: This field will be ignored + // in this case. For backward compatibility, in the `filesystem/linux` + // isolator we will keep the previous behavior: Any containers will share + // IPC namespace from agent, and if the container does not have its own + // rootfs, it will also share agent's /dev/shm, otherwise it will have its + // own /dev/shm. + // + // TODO(qianzhang): Remove the support for the above two cases after the + // deprecation cycle (started in 1.9). Eventually we want a single isolator + // (`namespaces/ipc`) to handle both IPC namespace and /dev/shm, and decouple + // /dev/shm from container's rootfs (i.e., whether a container will have its + // own /dev/shm depends on its `ipc_mode` instead of whether the container + // has its own rootfs). + optional IpcMode ipc_mode = 6; + + // Size of /dev/shm in MB. If not set, the size of the /dev/shm for container + // will be value of the `--default_container_shm_size` agent flag, if that + // flag is not set too, the size of the /dev/shm will be half of the host RAM + // which is the default behavior of Linux. This field will be ignored for the + // container which shares /dev/shm from its parent and it will be also ignored + // for any containers if the `namespaces/ipc` isolator is not enabled. Please + // note that we only support setting this field when the `ipc_mode` field is + // set to `PRIVATE` otherwise the container launch will be rejected. + optional uint32 shm_size = 7; + + // If set as 'true', the container will share the cgroups from its parent + // container, otherwise it will have its own cgroups created. Please note: + // 1. For tasks in a task group launched via the LAUNCH_GROUP operation, + // this field may be set to 'true' or 'false'. Resource limits may only be + // set for tasks in a task group when this field is set to 'false'. + // 2. For tasks launched via the LAUNCH operation, this field may only be set + // to 'true', and in this case resource limits may be set on these tasks. + // 3. For containers launched via the agent's LAUNCH_NESTED_CONTAINER_SESSION + // call, this field must be set to 'true'. + // 4. For executor containers, this field may only be set to 'false'. + // 5. All tasks under a single executor must share the same value of this + // field, if it is set. Note that this means that all tasks within a single + // task group must set this field to the same value. + optional bool share_cgroups = 8 [default = true]; +} + + +/** +* Encapsulation for POSIX rlimits, see +* http://pubs.opengroup.org/onlinepubs/009695399/functions/getrlimit.html. +* Note that some types might only be defined for Linux. +* We use a custom prefix to avoid conflict with existing system macros +* (e.g., `RLIMIT_CPU` or `NOFILE`). +*/ +message RLimitInfo { + message RLimit { + enum Type { + UNKNOWN = 0; + RLMT_AS = 1; + RLMT_CORE = 2; + RLMT_CPU = 3; + RLMT_DATA = 4; + RLMT_FSIZE = 5; + RLMT_LOCKS = 6; + RLMT_MEMLOCK = 7; + RLMT_MSGQUEUE = 8; + RLMT_NICE = 9; + RLMT_NOFILE = 10; + RLMT_NPROC = 11; + RLMT_RSS = 12; + RLMT_RTPRIO = 13; + RLMT_RTTIME = 14; + RLMT_SIGPENDING = 15; + RLMT_STACK = 16; + } + optional Type type = 1; + + // Either both are set or both are not set. + // If both are not set, it represents unlimited. + // If both are set, we require `soft` <= `hard`. + optional uint64 hard = 2; + optional uint64 soft = 3; + } + + repeated RLimit rlimits = 1; +} + + +/** + * Describes the information about (pseudo) TTY that can + * be attached to a process running in a container. + */ +message TTYInfo { + message WindowSize { + required uint32 rows = 1; + required uint32 columns = 2; + } + + optional WindowSize window_size = 1; +} + + +/** + * Describes a container configuration and allows extensible + * configurations for different container implementations. + * + * NOTE: `ContainerInfo` may be specified, e.g., by a task, even if no + * container image is provided. In this case neither `MesosInfo` nor + * `DockerInfo` is set, the required `type` must be `MESOS`. This is to + * address a case when a task without an image, e.g., a shell script + * with URIs, wants to use features originally designed for containers, + * for example custom network isolation via `NetworkInfo`. + */ +message ContainerInfo { + // All container implementation types. + // For each type there should be a field in the ContainerInfo itself + // with exactly matching name in lowercase. + enum Type { + DOCKER = 1; + MESOS = 2; + } + + message DockerInfo { + // The docker image that is going to be passed to the registry. + required string image = 1; + + // Network options. + enum Network { + HOST = 1; + BRIDGE = 2; + NONE = 3; + USER = 4; + } + + optional Network network = 2 [default = HOST]; + + message PortMapping { + required uint32 host_port = 1; + required uint32 container_port = 2; + // Protocol to expose as (ie: tcp, udp). + optional string protocol = 3; + } + + repeated PortMapping port_mappings = 3; + + optional bool privileged = 4 [default = false]; + + // Allowing arbitrary parameters to be passed to docker CLI. + // Note that anything passed to this field is not guaranteed + // to be supported moving forward, as we might move away from + // the docker CLI. + repeated Parameter parameters = 5; + + // With this flag set to true, the docker containerizer will + // pull the docker image from the registry even if the image + // is already downloaded on the slave. + optional bool force_pull_image = 6; + + // The name of volume driver plugin. + optional string volume_driver = 7 [deprecated = true]; // Since 1.0 + } + + message MesosInfo { + optional Image image = 1; + } + + required Type type = 1; + repeated Volume volumes = 2; + optional string hostname = 4; + + // At most one of the following *Info messages should be set to match + // the type, i.e. the "protobuf union" in ContainerInfo should be valid. + optional DockerInfo docker = 3; + optional MesosInfo mesos = 5; + + // A list of network requests. A framework can request multiple IP addresses + // for the container. + repeated NetworkInfo network_infos = 7; + + // Linux specific information for the container. + optional LinuxInfo linux_info = 8; + + // (POSIX only) rlimits of the container. + optional RLimitInfo rlimit_info = 9; + + // If specified a tty will be attached to the container entrypoint. + optional TTYInfo tty_info = 10; +} + + +/** + * Container related information that is resolved during container + * setup. The information is sent back to the framework as part of the + * TaskStatus message. + */ +message ContainerStatus { + optional ContainerID container_id = 4; + + // This field can be reliably used to identify the container IP address. + repeated NetworkInfo network_infos = 1; + + // Information about Linux control group (cgroup). + optional CgroupInfo cgroup_info = 2; + + // Information about Executor PID. + optional uint32 executor_pid = 3; +} + + +/** + * Linux control group (cgroup) information. + */ +message CgroupInfo { + // Configuration of a blkio cgroup subsystem. + message Blkio { + enum Operation { + UNKNOWN = 0; + TOTAL = 1; + READ = 2; + WRITE = 3; + SYNC = 4; + ASYNC = 5; + DISCARD = 6; + } + + // Describes a stat value without the device descriptor part. + message Value { + optional Operation op = 1; // Required. + optional uint64 value = 2; // Required. + } + + message CFQ { + message Statistics { + // Stats are grouped by block devices. If `device` is not + // set, it represents `Total`. + optional Device.Number device = 1; + // blkio.sectors + optional uint64 sectors = 2; + // blkio.time + optional uint64 time = 3; + // blkio.io_serviced + repeated Value io_serviced = 4; + // blkio.io_service_bytes + repeated Value io_service_bytes = 5; + // blkio.io_service_time + repeated Value io_service_time = 6; + // blkio.io_wait_time + repeated Value io_wait_time = 7; + // blkio.io_merged + repeated Value io_merged = 8; + // blkio.io_queued + repeated Value io_queued = 9; + } + + // TODO(jasonlai): Add fields for blkio weight and weight + // device. + } + + message Throttling { + message Statistics { + // Stats are grouped by block devices. If `device` is not + // set, it represents `Total`. + optional Device.Number device = 1; + // blkio.throttle.io_serviced + repeated Value io_serviced = 2; + // blkio.throttle.io_service_bytes + repeated Value io_service_bytes = 3; + } + + // TODO(jasonlai): Add fields for blkio.throttle.*_device. + } + + message Statistics { + repeated CFQ.Statistics cfq = 1; + repeated CFQ.Statistics cfq_recursive = 2; + repeated Throttling.Statistics throttling = 3; + } + } + + // Configuration of a net_cls cgroup subsystem. + message NetCls { + // The 32-bit classid consists of two parts, a 16 bit major handle + // and a 16-bit minor handle. The major and minor handle are + // represented using the format 0xAAAABBBB, where 0xAAAA is the + // 16-bit major handle and 0xBBBB is the 16-bit minor handle. + optional uint32 classid = 1; + } + + optional NetCls net_cls = 1; +} + + +/** + * Collection of labels. Labels should not contain duplicate key-value + * pairs. + */ +message Labels { + repeated Label labels = 1; +} + + +/** + * Key, value pair used to store free form user-data. + */ +message Label { + required string key = 1; + optional string value = 2; +} + + +/** + * Named port used for service discovery. + */ +message Port { + // Port number on which the framework exposes a service. + required uint32 number = 1; + + // Name of the service hosted on this port. + optional string name = 2; + + // Layer 4-7 protocol on which the framework exposes its services. + optional string protocol = 3; + + // This field restricts discovery within a framework (FRAMEWORK), + // within a Mesos cluster (CLUSTER), or places no restrictions (EXTERNAL). + // The visibility setting for a Port overrides the general visibility setting + // in the DiscoveryInfo. + optional DiscoveryInfo.Visibility visibility = 4; + + // This can be used to decorate the message with metadata to be + // interpreted by external applications such as firewalls. + optional Labels labels = 5; +} + + +/** + * Collection of ports. + */ +message Ports { + repeated Port ports = 1; +} + + +/** +* Service discovery information. +* The visibility field restricts discovery within a framework (FRAMEWORK), +* within a Mesos cluster (CLUSTER), or places no restrictions (EXTERNAL). +* Each port in the ports field also has an optional visibility field. +* If visibility is specified for a port, it overrides the default service-wide +* DiscoveryInfo.visibility for that port. +* The environment, location, and version fields provide first class support for +* common attributes used to differentiate between similar services. The +* environment may receive values such as PROD/QA/DEV, the location field may +* receive values like EAST-US/WEST-US/EUROPE/AMEA, and the version field may +* receive values like v2.0/v0.9. The exact use of these fields is up to each +* service discovery system. +*/ +message DiscoveryInfo { + enum Visibility { + FRAMEWORK = 0; + CLUSTER = 1; + EXTERNAL = 2; + } + + required Visibility visibility = 1; + optional string name = 2; + optional string environment = 3; + optional string location = 4; + optional string version = 5; + optional Ports ports = 6; + optional Labels labels = 7; +} + + +/** + * Named WeightInfo to indicate resource allocation + * priority between the different roles. + */ +message WeightInfo { + required double weight = 1; + + // Related role name. + optional string role = 2; +} + + +/** + * Version information of a component. + */ +message VersionInfo { + required string version = 1; + optional string build_date = 2; + optional double build_time = 3; + optional string build_user = 4; + optional string git_sha = 5; + optional string git_branch = 6; + optional string git_tag = 7; +} + + +/** + * Flag consists of a name and optionally its value. + */ +message Flag { + required string name = 1; + optional string value = 2; +} + + +/** + * Describes a Role. Roles can be used to specify that certain resources are + * reserved for the use of one or more frameworks. + */ +message Role { + required string name = 1; + required double weight = 2; + repeated FrameworkID frameworks = 3; + + // TODO(bmahler): Deprecate `resources` and introduce quota, + // consumed quota, allocated, offered, and reserved resource + // quantity fields. This is blocked by MESOS-9497 since the + // computation of these quantities is currently expensive. + repeated Resource resources = 4; +} + + +/** + * Metric consists of a name and optionally its value. + */ +message Metric { + required string name = 1; + optional double value = 2; +} + + +/** + * Describes a File. + */ +message FileInfo { + // Absolute path to the file. + required string path = 1; + + // Number of hard links. + optional int32 nlink = 2; + + // Total size in bytes. + optional uint64 size = 3; + + // Last modification time. + optional TimeInfo mtime = 4; + + // Represents a file's mode and permission bits. The bits have the same + // definition on all systems and is portable. + optional uint32 mode = 5; + + // User ID of owner. + optional string uid = 6; + + // Group ID of owner. + optional string gid = 7; +} + + +/** + * Describes information about a device. + */ +message Device { + message Number { + required uint64 major_number = 1; + required uint64 minor_number = 2; + } + + optional string path = 1; + optional Number number = 2; +} + + +/** + * Describes a device whitelist entry that expose from host to container. + */ +message DeviceAccess { + message Access { + optional bool read = 1; + optional bool write = 2; + optional bool mknod = 3; + } + required Device device = 1; + required Access access = 2; +} + + +message DeviceWhitelist { + repeated DeviceAccess allowed_devices = 1; +} + + +enum DrainState { + UNKNOWN = 0; + + // The agent is currently draining. + DRAINING = 1; + + // The agent has been drained: all tasks have terminated, all terminal + // task status updates have been acknowledged by the frameworks, and all + // operations have finished and had their terminal updates acknowledged. + DRAINED = 2; +} + + +message DrainConfig { + // An upper bound for tasks with a KillPolicy. + // If a task has a KillPolicy grace period greater than this value, this value + // will be used instead. This allows the operator to limit the maximum time it + // will take the agent to drain. If this field is unset, the task's KillPolicy + // or the executor's default grace period is used. + // + // NOTE: Grace periods start when the executor receives the associated kill. + // If, for example, the agent is unreachable when this call is made, + // tasks will still receive their full grace period to kill gracefully. + optional DurationInfo max_grace_period = 1; + + // Whether or not this agent will be removed permanently from the cluster when + // draining is complete. This transition is automatic and does **NOT** require + // a separate call to `MarkAgentGone`. If this field is unset, then the + // default value of `false` is used. + // + // Compared to `MarkAgentGone`, which is used for unreachable agents, + // marking agents gone after draining will respect kill policies. + // To notify frameworks, tasks terminated during draining will return + // a `TASK_GONE_BY_OPERATOR` status update instead of any other terminal + // status. Executors will not need to account for this case, because + // the terminal status update will be intercepted and modified by the agent. + optional bool mark_gone = 2 [default = false]; +} + + +message DrainInfo { + // The drain state of the agent. + required DrainState state = 1; + + // The configuration used to drain the agent. + required DrainConfig config = 2; +} diff --git a/3rdparty/simple-protobuf/test/proto/message.proto b/3rdparty/simple-protobuf/test/proto/message.proto new file mode 100644 index 0000000..9d16e6d --- /dev/null +++ b/3rdparty/simple-protobuf/test/proto/message.proto @@ -0,0 +1,46 @@ +// Specify whether you're using proto2 or proto3 at the top of the file +syntax = "proto2"; + +/***********/ +// PACKAGE // +/***********/ +// Your message will be packaged under "tutorial" now +// It's good practice to use packages to prevent naming conflicts +// between different projects. Fittingly, in C++ our generated +// classes will be placed in a namespace of the same name ("tutorial") +package tutorial; + +message Person { + // REQUIRED V.S. OPTIONAL FIELDS + // Be careful about setting fields as required; it can be a headache + // if the fields end up becoming optional later on. Some developers + // just stick to making all fields optional no matter what. + required string name = 1; + required int32 id = 2; + optional string email = 3; + + // ENUMS + // It's good practice to define enums if you have fields with a + // fixed set of values they could possibly take. Much more readable + enum PhoneType { + MOBILE = 0; + HOME = 1; + WORK = 2; + } + + // NESTED MESSAGE DEFINITIONS + // You can define messages within other message definitions + message PhoneNumber { + message PhoneNumber2 { + required string number = 1; + optional PhoneType type = 2 [ default = HOME ]; + } + + required string number = 1; + optional PhoneType type = 2 [ default = HOME ]; + } + + // REPEATED FIELDS + // The repeated keyword allows us to have multiple numbers + repeated PhoneNumber phones = 4; +} \ No newline at end of file diff --git a/3rdparty/simple-protobuf/test/proto/name/name.proto.in b/3rdparty/simple-protobuf/test/proto/name/name.proto.in new file mode 100644 index 0000000..d1ea5cf --- /dev/null +++ b/3rdparty/simple-protobuf/test/proto/name/name.proto.in @@ -0,0 +1,22 @@ +syntax = "proto3"; + +package Test@PB_PACKAGE@; + +//- message with collision values for djb2("name") == djb2("bkfvdzz") +message Name { + string name = 1; + string bkfvdzz = 2; +} + +message Recursive { + Name value = 1; +} + +message Variant { + oneof oneof_field { + uint32 var_int = 1; + string var_string = 2; + bytes var_bytes = 3; + Name name = 4; + } +} \ No newline at end of file diff --git a/3rdparty/simple-protobuf/test/proto/person/person.proto.in b/3rdparty/simple-protobuf/test/proto/person/person.proto.in new file mode 100644 index 0000000..736d638 --- /dev/null +++ b/3rdparty/simple-protobuf/test/proto/person/person.proto.in @@ -0,0 +1,22 @@ +syntax = "proto2"; + +package PhoneBook@PB_PACKAGE@; + +message Person { + optional string name = 1; + optional int32 id = 2; // Unique ID number for this person. + optional string email = 3; + + enum PhoneType { + MOBILE = 0; + HOME = 1; + WORK = 2; + } + + message PhoneNumber { + required string number = 1; // phone number is always required + optional PhoneType type = 2; + } + // all registered phones + repeated PhoneNumber phones = 4; +} diff --git a/3rdparty/simple-protobuf/test/proto/playlist4_external.proto b/3rdparty/simple-protobuf/test/proto/playlist4_external.proto new file mode 100644 index 0000000..d685d3e --- /dev/null +++ b/3rdparty/simple-protobuf/test/proto/playlist4_external.proto @@ -0,0 +1,13 @@ +syntax = "proto2"; + +package spotify.playlist4.proto; + +import "playlist_permission.proto"; + +message MetaItem { // its line 18 + optional ListAttributes attributes = 1; + optional int32 length = 2; + optional spotify.playlist_permission.proto.Capabilities capabilities = 3; +} + +message ListAttributes {} \ No newline at end of file diff --git a/3rdparty/simple-protobuf/test/proto/playlist_permission.proto b/3rdparty/simple-protobuf/test/proto/playlist_permission.proto new file mode 100644 index 0000000..7752d72 --- /dev/null +++ b/3rdparty/simple-protobuf/test/proto/playlist_permission.proto @@ -0,0 +1,12 @@ +syntax = "proto2"; + +package spotify.playlist_permission.proto; + +message Capabilities { repeated PermissionLevel grantable_level = 1; } + +enum PermissionLevel { + UNKNOWN = 0; + BLOCKED = 1; + VIEWER = 2; + CONTRIBUTOR = 3; +} \ No newline at end of file diff --git a/3rdparty/simple-protobuf/test/proto/proto3.proto b/3rdparty/simple-protobuf/test/proto/proto3.proto new file mode 100644 index 0000000..cf926fa --- /dev/null +++ b/3rdparty/simple-protobuf/test/proto/proto3.proto @@ -0,0 +1,199 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +syntax = "proto3"; + +package proto3_unittest; + +import "message.proto"; + +option optimize_for = SPEED; + +// This proto includes every type of field in both singular and repeated +// forms. +message TestAllTypes { + message NestedMessage { + // The field name "b" fails to compile in proto1 because it conflicts + // with a local variable named "b" in one of the generated methods. Doh. + // This file needs to compile in proto1 to test backwards-compatibility. + int32 bb = 1; + tutorial.Person person = 2; + } + + enum NestedEnum { + ZERO = 0; + FOO = 1; + BAR = 2; + BAZ = 3; + NEG = -1; // Intentionally negative. + } + + // Singular + int32 optional_int32 = 1; + int64 optional_int64 = 2; + uint32 optional_uint32 = 3; + uint64 optional_uint64 = 4; + sint32 optional_sint32 = 5; + sint64 optional_sint64 = 6; + fixed32 optional_fixed32 = 7; + fixed64 optional_fixed64 = 8; + sfixed32 optional_sfixed32 = 9; + sfixed64 optional_sfixed64 = 10; + float optional_float = 11; + double optional_double = 12; + bool optional_bool = 13; + string optional_string = 14; + bytes optional_bytes = 15; + + // Groups are not allowed in proto3. + // optional group OptionalGroup = 16 { + // optional int32 a = 17; + // } + + optional NestedMessage optional_nested_message = 18; + ForeignMessage optional_foreign_message = 19; + + NestedEnum optional_nested_enum = 21; + ForeignEnum optional_foreign_enum = 22; + + // Omitted (compared to unittest.proto) because proto2 enums are not allowed + // inside proto2 messages. + // + // optional protobuf_unittest_import.ImportEnum optional_import_enum = + // 23; + + string optional_string_piece = 24 [ ctype = STRING_PIECE ]; + string optional_cord = 25 [ ctype = CORD ]; + + NestedMessage optional_lazy_message = 27 [ lazy = true ]; + NestedMessage optional_unverified_lazy_message = 28 + [ unverified_lazy = true ]; + + // Repeated + repeated int32 repeated_int32 = 31; + repeated int64 repeated_int64 = 32; + repeated uint32 repeated_uint32 = 33; + repeated uint64 repeated_uint64 = 34; + repeated sint32 repeated_sint32 = 35; + repeated sint64 repeated_sint64 = 36; + repeated fixed32 repeated_fixed32 = 37; + repeated fixed64 repeated_fixed64 = 38; + repeated sfixed32 repeated_sfixed32 = 39; + repeated sfixed64 repeated_sfixed64 = 40; + repeated float repeated_float = 41; + repeated double repeated_double = 42; + repeated bool repeated_bool = 43; + repeated string repeated_string = 44; + repeated bytes repeated_bytes = 45; + + // Groups are not allowed in proto3. + // repeated group RepeatedGroup = 46 { + // optional int32 a = 47; + // } + + repeated NestedMessage repeated_nested_message = 48; + repeated ForeignMessage repeated_foreign_message = 49; + + repeated NestedEnum repeated_nested_enum = 51; + repeated ForeignEnum repeated_foreign_enum = 52; + + // Omitted (compared to unittest.proto) because proto2 enums are not allowed + // inside proto2 messages. + // + // repeated protobuf_unittest_import.ImportEnum repeated_import_enum = + // 53; + + repeated string repeated_string_piece = 54 [ ctype = STRING_PIECE ]; + repeated string repeated_cord = 55 [ ctype = CORD ]; + + repeated NestedMessage repeated_lazy_message = 57; + + oneof oneof_field { + uint32 oneof_uint32 = 111; + NestedMessage oneof_nested_message = 112; + string oneof_string = 113; + bytes oneof_bytes = 114; + } +} + +// Test messages for packed fields + +message TestPackedTypes { + repeated int32 packed_int32 = 90 [ packed = true ]; + repeated int64 packed_int64 = 91 [ packed = true ]; + repeated uint32 packed_uint32 = 92 [ packed = true ]; + repeated uint64 packed_uint64 = 93 [ packed = true ]; + repeated sint32 packed_sint32 = 94 [ packed = true ]; + repeated sint64 packed_sint64 = 95 [ packed = true ]; + repeated fixed32 packed_fixed32 = 96 [ packed = true ]; + repeated fixed64 packed_fixed64 = 97 [ packed = true ]; + repeated sfixed32 packed_sfixed32 = 98 [ packed = true ]; + repeated sfixed64 packed_sfixed64 = 99 [ packed = true ]; + repeated float packed_float = 100 [ packed = true ]; + repeated double packed_double = 101 [ packed = true ]; + repeated bool packed_bool = 102 [ packed = true ]; + repeated ForeignEnum packed_enum = 103 [ packed = true ]; +} + +// Explicitly set packed to false +message TestUnpackedTypes { + repeated int32 repeated_int32 = 1 [ packed = false ]; + repeated int64 repeated_int64 = 2 [ packed = false ]; + repeated uint32 repeated_uint32 = 3 [ packed = false ]; + repeated uint64 repeated_uint64 = 4 [ packed = false ]; + repeated sint32 repeated_sint32 = 5 [ packed = false ]; + repeated sint64 repeated_sint64 = 6 [ packed = false ]; + repeated fixed32 repeated_fixed32 = 7 [ packed = false ]; + repeated fixed64 repeated_fixed64 = 8 [ packed = false ]; + repeated sfixed32 repeated_sfixed32 = 9 [ packed = false ]; + repeated sfixed64 repeated_sfixed64 = 10 [ packed = false ]; + repeated float repeated_float = 11 [ packed = false ]; + repeated double repeated_double = 12 [ packed = false ]; + repeated bool repeated_bool = 13 [ packed = false ]; + repeated TestAllTypes.NestedEnum repeated_nested_enum = 14 + [ packed = false ]; +} + +// This proto includes a recursively nested message. +message NestedTestAllTypes { + NestedTestAllTypes child = 1; + TestAllTypes payload = 2; +} + +// Define these after TestAllTypes to make sure the compiler can handle +// that. +message ForeignMessage { int32 c = 1; } + +enum ForeignEnum { + FOREIGN_ZERO = 0; + FOREIGN_FOO = 4; + FOREIGN_BAR = 5; + FOREIGN_BAZ = 6; +} + +// TestEmptyMessage is used to test behavior of unknown fields. +message TestEmptyMessage {} + +// TestMessageWithDummy is also used to test behavior of unknown fields. +message TestMessageWithDummy { + // This field is only here for triggering copy-on-write; it's not intended + // to be serialized. + bool dummy = 536870911; +} + +// Same layout as TestOneof2 in unittest.proto to test unknown enum value +// parsing behavior in oneof. +message TestOneof2 { + oneof foo { NestedEnum foo_enum = 6; } + + enum NestedEnum { + UNKNOWN = 0; + FOO = 1; + BAR = 2; + BAZ = 3; + } +} \ No newline at end of file diff --git a/3rdparty/simple-protobuf/test/proto/scalar/scalar.proto.in b/3rdparty/simple-protobuf/test/proto/scalar/scalar.proto.in new file mode 100644 index 0000000..c8fe0b3 --- /dev/null +++ b/3rdparty/simple-protobuf/test/proto/scalar/scalar.proto.in @@ -0,0 +1,1243 @@ +syntax = "proto2"; + +package Test.Scalar@PB_PACKAGE@; + +message Empty +{ +} + +message Simple +{ + optional string value = 100; +} + +message OptInt8 +{ + //[[ field.type = "int8" ]] + optional int32 value = 1; +} +message ReqInt8 +{ + //[[ field.type = "int8" ]] + required int32 value = 1; +} +message RepInt8 +{ + //[[ field.type = "int8" ]] + repeated int32 value = 1; +} +message RepPackInt8 +{ + //[[ field.type = "int8" ]] + repeated int32 value = 1 [ packed = true ]; +} + +message OptInt16 +{ + //[[ field.type = "int16" ]] + optional int32 value = 1; +} +message ReqInt16 +{ + //[[ field.type = "int16" ]] + required int32 value = 1; +} +message RepInt16 +{ + //[[ field.type = "int16" ]] + repeated int32 value = 1; +} +message RepPackInt16 +{ + //[[ field.type = "int16" ]] + repeated int32 value = 1 [ packed = true ]; +} + +message OptInt32 +{ + optional int32 value = 1; +} +message ReqInt32 +{ + required int32 value = 1; +} +message RepInt32 +{ + repeated int32 value = 1; +} +message RepPackInt32 +{ + repeated int32 value = 1 [ packed = true ]; +} + +message OptInt64 +{ + optional int64 value = 1; +} +message ReqInt64 +{ + required int64 value = 1; +} +message RepInt64 +{ + repeated int64 value = 1; +} +message RepPackInt64 +{ + repeated int64 value = 1 [ packed = true ]; +} + +message OptUint8 +{ + //[[ field.type = "uint8" ]] + optional uint32 value = 1; +} +message ReqUint8 +{ + //[[ field.type = "uint8" ]] + required uint32 value = 1; +} +message RepUint8 +{ + //[[ field.type = "uint8" ]] + repeated uint32 value = 1; +} +message RepPackUint8 +{ + //[[ field.type = "uint8" ]] + repeated uint32 value = 1 [ packed = true ]; +} + +message OptUint16 +{ + //[[ field.type = "uint16" ]] + optional uint32 value = 1; +} +message ReqUint16 +{ + //[[ field.type = "uint16" ]] + required uint32 value = 1; +} +message RepUint16 +{ + //[[ field.type = "uint16" ]] + repeated uint32 value = 1; +} +message RepPackUint16 +{ + //[[ field.type = "uint16" ]] + repeated uint32 value = 1 [ packed = true ]; +} + +message OptUint32 +{ + optional uint32 value = 1; +} +message ReqUint32 +{ + required uint32 value = 1; +} +message RepUint32 +{ + repeated uint32 value = 1; +} +message RepPackUint32 +{ + repeated uint32 value = 1 [ packed = true ]; +} + +message OptUint64 +{ + optional uint64 value = 1; +} +message ReqUint64 +{ + required uint64 value = 1; +} +message RepUint64 +{ + repeated uint64 value = 1; +} +message RepPackUint64 +{ + repeated uint64 value = 1 [ packed = true ]; +} + +message OptSint8 +{ + //[[ field.type = "int8" ]] + optional sint32 value = 1; +} +message ReqSint8 +{ + //[[ field.type = "int8" ]] + required sint32 value = 1; +} +message RepSint8 +{ + //[[ field.type = "int8" ]] + repeated sint32 value = 1; +} +message RepPackSint8 +{ + //[[ field.type = "int8" ]] + repeated sint32 value = 1 [ packed = true ]; +} + +message OptSint16 +{ + //[[ field.type = "int16" ]] + optional sint32 value = 1; +} +message ReqSint16 +{ + //[[ field.type = "int16" ]] + required sint32 value = 1; +} +message RepSint16 +{ + //[[ field.type = "int16" ]] + repeated sint32 value = 1; +} +message RepPackSint16 +{ + //[[ field.type = "int16" ]] + repeated sint32 value = 1 [ packed = true ]; +} + +message OptSint32 +{ + optional sint32 value = 1; +} +message ReqSint32 +{ + required sint32 value = 1; +} +message RepSint32 +{ + repeated sint32 value = 1; +} +message RepPackSint32 +{ + repeated sint32 value = 1 [ packed = true ]; +} + +message OptSint64 +{ + optional sint64 value = 1; +} +message ReqSint64 +{ + required sint64 value = 1; +} +message RepSint64 +{ + repeated sint64 value = 1; +} +message RepPackSint64 +{ + repeated sint64 value = 1 [ packed = true ]; +} + +message OptFixed32_8 +{ + //[[ field.type = "uint8" ]] + optional fixed32 value = 1; +} +message ReqFixed32_8 +{ + //[[ field.type = "uint8" ]] + required fixed32 value = 1; +} +message RepFixed32_8 +{ + //[[ field.type = "uint8" ]] + repeated fixed32 value = 1; +} +message RepPackFixed32_8 +{ + //[[ field.type = "uint8" ]] + repeated fixed32 value = 1 [ packed = true ]; +} + +message OptFixed32_16 +{ + //[[ field.type = "uint16" ]] + optional fixed32 value = 1; +} +message ReqFixed32_16 +{ + //[[ field.type = "uint16" ]] + required fixed32 value = 1; +} +message RepFixed32_16 +{ + //[[ field.type = "uint16" ]] + repeated fixed32 value = 1; +} +message RepPackFixed32_16 +{ + //[[ field.type = "uint16" ]] + repeated fixed32 value = 1 [ packed = true ]; +} + +message OptFixed32 +{ + optional fixed32 value = 1; +} +message ReqFixed32 +{ + required fixed32 value = 1; +} +message RepFixed32 +{ + repeated fixed32 value = 1; +} +message RepPackFixed32 +{ + repeated fixed32 value = 1 [ packed = true ]; +} + +message OptFixed64 +{ + optional fixed64 value = 1; +} +message ReqFixed64 +{ + required fixed64 value = 1; +} +message RepFixed64 +{ + repeated fixed64 value = 1; +} +message RepPackFixed64 +{ + repeated fixed64 value = 1 [ packed = true ]; +} + +message OptFixed64_8 +{ + //[[ field.type = "uint8" ]] + optional fixed64 value = 1; +} +message ReqFixed64_8 +{ + //[[ field.type = "uint8" ]] + required fixed64 value = 1; +} +message RepFixed64_8 +{ + //[[ field.type = "uint8" ]] + repeated fixed64 value = 1; +} + +message RepPackFixed64_8 +{ + //[[ field.type = "uint8" ]] + repeated fixed64 value = 1 [ packed = true ]; +} + +message OptFixed64_16 +{ + //[[ field.type = "uint16" ]] + optional fixed64 value = 1; +} +message ReqFixed64_16 +{ + //[[ field.type = "uint16" ]] + required fixed64 value = 1; +} +message RepFixed64_16 +{ + //[[ field.type = "uint16" ]] + repeated fixed64 value = 1; +} +message RepPackFixed64_16 +{ + //[[ field.type = "uint16" ]] + repeated fixed64 value = 1 [ packed = true ]; +} + +message OptFixed64_32 +{ + //[[ field.type = "uint32" ]] + optional fixed64 value = 1; +} +message ReqFixed64_32 +{ + //[[ field.type = "uint32" ]] + required fixed64 value = 1; +} +message RepFixed64_32 +{ + //[[ field.type = "uint32" ]] + repeated fixed64 value = 1; +} +message RepPackFixed64_32 +{ + //[[ field.type = "uint32" ]] + repeated fixed64 value = 1 [ packed = true ]; +} + +message OptSfixed32_8 +{ + //[[ field.type = "int8" ]] + optional sfixed32 value = 1; +} +message ReqSfixed32_8 +{ + //[[ field.type = "int8" ]] + required sfixed32 value = 1; +} +message RepSfixed32_8 +{ + //[[ field.type = "int8" ]] + repeated sfixed32 value = 1; +} +message RepPackSfixed32_8 +{ + //[[ field.type = "int8" ]] + repeated sfixed32 value = 1 [ packed = true ]; +} + +message OptSfixed32_16 +{ + //[[ field.type = "int16" ]] + optional sfixed32 value = 1; +} +message ReqSfixed32_16 +{ + //[[ field.type = "int16" ]] + required sfixed32 value = 1; +} +message RepSfixed32_16 +{ + //[[ field.type = "int16" ]] + repeated sfixed32 value = 1; +} +message RepPackSfixed32_16 +{ + //[[ field.type = "int16" ]] + repeated sfixed32 value = 1 [ packed = true ]; +} + +message OptSfixed32 +{ + optional sfixed32 value = 1; +} +message ReqSfixed32 +{ + required sfixed32 value = 1; +} +message RepSfixed32 +{ + repeated sfixed32 value = 1; +} +message RepPackSfixed32 +{ + repeated sfixed32 value = 1 [ packed = true ]; +} + +message OptSfixed64_8 +{ + //[[ field.type = "int8" ]] + optional sfixed64 value = 1; +} +message ReqSfixed64_8 +{ + //[[ field.type = "int8" ]] + required sfixed64 value = 1; +} +message RepSfixed64_8 +{ + //[[ field.type = "int8" ]] + repeated sfixed64 value = 1; +} +message RepPackSfixed64_8 +{ + //[[ field.type = "int8" ]] + repeated sfixed64 value = 1 [ packed = true ]; +} + +message OptSfixed64_16 +{ + //[[ field.type = "int16" ]] + optional sfixed64 value = 1; +} +message ReqSfixed64_16 +{ + //[[ field.type = "int16" ]] + required sfixed64 value = 1; +} +message RepSfixed64_16 +{ + //[[ field.type = "int16" ]] + repeated sfixed64 value = 1; +} +message RepPackSfixed64_16 +{ + //[[ field.type = "int16" ]] + repeated sfixed64 value = 1 [ packed = true ]; +} + +message OptSfixed64_32 +{ + //[[ field.type = "int32" ]] + optional sfixed64 value = 1; +} +message ReqSfixed64_32 +{ + //[[ field.type = "int32" ]] + required sfixed64 value = 1; +} +message RepSfixed64_32 +{ + //[[ field.type = "int32" ]] + repeated sfixed64 value = 1; +} +message RepPackSfixed64_32 +{ + //[[ field.type = "int32" ]] + repeated sfixed64 value = 1 [ packed = true ]; +} + +message OptSfixed64 +{ + optional sfixed64 value = 1; +} +message ReqSfixed64 +{ + required sfixed64 value = 1; +} +message RepSfixed64 +{ + repeated sfixed64 value = 1; +} +message RepPackSfixed64 +{ + repeated sfixed64 value = 1 [ packed = true ]; +} + +message OptBool +{ + optional bool value = 1; +} +message ReqBool +{ + required bool value = 1; +} +message RepBool +{ + repeated bool value = 1; +} + +message RepPackBool +{ + repeated bool value = 1 [ packed = true ]; +} + +message OptFloat +{ + optional float value = 1; +} +message ReqFloat +{ + required float value = 1; +} +message RepFloat +{ + repeated float value = 1; +} + +message OptDouble +{ + optional double value = 1; +} +message ReqDouble +{ + required double value = 1; +} +message RepDouble +{ + repeated double value = 1; +} + +message OptString +{ + optional string value = 1; +} +message ReqString +{ + required string value = 1; +} +message RepString +{ + repeated string value = 1; +} + +message OptStringView +{ + optional string value = 1 [ctype = STRING_PIECE]; +} +message ReqStringView +{ + required string value = 1 [ctype = STRING_PIECE]; +} +message RepStringView +{ + repeated string value = 1 [ctype = STRING_PIECE]; +} + +message OptStringFixed { + //[[ string.type = "std::array<$, 6>"]] + //[[ string.include = ""]] + optional string value = 1; +} + +message ReqStringFixed { + //[[ string.type = "std::array<$, 6>"]] + required string value = 1; +} + +message RepStringFixed { + //[[ string.type = "std::array<$, 6>"]] + repeated string value = 1; +} + +message OptBytesFixed { + //[[ bytes.type = "std::array<$, 8>"]] + //[[ bytes.include = ""]] + optional bytes value = 1; +} + +message ReqBytesFixed { + //[[ bytes.type = "std::array<$, 8>"]] + required bytes value = 1; +} + +message RepBytesFixed { + //[[ bytes.type = "std::array<$, 8>"]] + repeated bytes value = 1; +} + +message OptBytes +{ + optional bytes value = 1; +} +message ReqBytes +{ + required bytes value = 1; +} +message RepBytes +{ + repeated bytes value = 1; +} + +message OptBytesView +{ + optional bytes value = 1 [ctype = STRING_PIECE]; +} +message ReqBytesView +{ + required bytes value = 1 [ctype = STRING_PIECE]; +} +message RepBytesView +{ + repeated bytes value = 1 [ctype = STRING_PIECE]; +} + +message OptEnum +{ + enum Enum { + Enum_min = -2147483648; + Enum_max = 2147483647; + Enum_value = 1; + Enum_value2 = 2; + Enum_value3 = 3; + } + + optional Enum value = 1; +} + +message ReqEnum +{ + enum Enum { + Enum_min = -2147483648; + Enum_max = 2147483647; + Enum_value = 1; + Enum_value2 = 2; + Enum_value3 = 3; + } + + required Enum value = 1; +} + +message RepEnum +{ + enum Enum { + Enum_min = -2147483648; + Enum_max = 2147483647; + Enum_value = 1; + Enum_value2 = 2; + Enum_value3 = 3; + } + + repeated Enum value = 1; +} + +message RepPackEnum +{ + enum Enum { + Enum_min = -2147483648; + Enum_max = 2147483647; + Enum_value = 1; + Enum_value2 = 2; + Enum_value3 = 3; + } + + repeated Enum value = 1 [packed = true]; +} + +message OptEnumInt8 +{ + //[[ enum.type = "int8" ]] + enum Enum { + Enum_min = -128; + Enum_max = 127; + Enum_value = 1; + } + + optional Enum value = 1; +} + +message ReqEnumInt8 +{ + //[[ enum.type = "int8" ]] + enum Enum { + Enum_min = -128; + Enum_max = 127; + Enum_value = 1; + } + + required Enum value = 1; +} + +message RepEnumInt8 +{ + //[[ enum.type = "int8" ]] + enum Enum { + Enum_min = -128; + Enum_max = 127; + Enum_value = 1; + } + + repeated Enum value = 1; +} + +message OptEnumUint8 +{ + //[[ enum.type = "uint8" ]] + enum Enum { + Enum_min = 0; + Enum_max = 255; + Enum_value = 1; + } + + optional Enum value = 1; +} + +message ReqEnumUint8 +{ + //[[ enum.type = "uint8" ]] + enum Enum { + Enum_min = 0; + Enum_max = 255; + Enum_value = 1; + } + + required Enum value = 1; +} + +message RepEnumUint8 +{ + //[[ enum.type = "uint8" ]] + enum Enum { + Enum_min = 0; + Enum_max = 255; + Enum_value = 1; + } + + repeated Enum value = 1; +} + +message OptEnumInt16 +{ + //[[ enum.type = "int16" ]] + enum Enum { + Enum_min = -32768; + Enum_max = 32767; + Enum_value = 1; + } + + optional Enum value = 1; +} + +message ReqEnumInt16 +{ + //[[ enum.type = "int16" ]] + enum Enum { + Enum_min = -32768; + Enum_max = 32767; + Enum_value = 1; + } + + required Enum value = 1; +} + +message RepEnumInt16 +{ + //[[ enum.type = "int16" ]] + enum Enum { + Enum_min = -32768; + Enum_max = 32767; + Enum_value = 1; + } + + repeated Enum value = 1; +} + +message OptEnumUint16 +{ + //[[ enum.type = "uint16" ]] + enum Enum { + Enum_min = 0; + Enum_max = 65535; + Enum_value = 1; + } + + optional Enum value = 1; +} + +message ReqEnumUint16 +{ + //[[ enum.type = "uint16" ]] + enum Enum { + Enum_min = 0; + Enum_max = 65535; + Enum_value = 1; + } + + required Enum value = 1; +} + +message RepEnumUint16 +{ + //[[ enum.type = "uint16" ]] + enum Enum { + Enum_min = 0; + Enum_max = 65535; + Enum_value = 1; + } + + repeated Enum value = 1; +} + +message OptEnumInt32 +{ + //[[ enum.type = "int32" ]] + enum Enum { + Enum_min = -2147483648; + Enum_max = 2147483647; + Enum_value = 1; + } + + optional Enum value = 1; +} + +message ReqEnumInt32 +{ + //[[ enum.type = "int32" ]] + enum Enum { + Enum_min = -2147483648; + Enum_max = 2147483647; + Enum_value = 1; + } + + required Enum value = 1; +} + +message RepEnumInt32 +{ + //[[ enum.type = "int32" ]] + enum Enum { + Enum_min = -2147483648; + Enum_max = 2147483647; + Enum_value = 1; + } + + repeated Enum value = 1; +} + +message LargeFieldNumber +{ + optional string value = 100; +} + +message VeryLargeFieldNumber +{ + optional string value = 536870911; // 2^29 - 1 +} + +message ReqUint8_1 +{ + //[[ field.type = "uint8:1" ]] + required uint32 value = 1; +} + +message ReqUint8_2 +{ + //[[ field.type = "uint8:2" ]] + required uint32 value = 1; +} + +message ReqUint16_1 +{ + //[[ field.type = "uint16:1" ]] + required uint32 value = 1; +} + +message ReqUint16_2 +{ + //[[ field.type = "uint16:2" ]] + required uint32 value = 1; +} + +message ReqUint32_1 +{ + //[[ field.type = "uint32:1" ]] + required uint32 value = 1; +} + +message ReqUint32_2 +{ + //[[ field.type = "uint32:2" ]] + required uint32 value = 1; +} + +message ReqInt8_1 +{ + //[[ field.type = "int8:1" ]] + required int32 value = 1; +} + +message ReqInt8_2 +{ + //[[ field.type = "int8:2" ]] + required int32 value = 1; +} + +message ReqInt16_1 +{ + //[[ field.type = "int16:1" ]] + required int32 value = 1; +} + +message ReqInt16_2 +{ + //[[ field.type = "int16:2" ]] + required int32 value = 1; +} + +message ReqInt32_1 +{ + //[[ field.type = "int32:1" ]] + required int32 value = 1; +} + +message ReqInt32_2 +{ + //[[ field.type = "int32:2" ]] + required int32 value = 1; +} + +message ReqInt64_1 +{ + //[[ field.type = "int64:1" ]] + required int64 value = 1; +} + +message ReqInt64_2 +{ + //[[ field.type = "int64:2" ]] + required int64 value = 1; +} + +message ReqSint8_1 +{ + //[[ field.type = "int8:1" ]] + required sint32 value = 1; +} + +message ReqSint8_2 +{ + //[[ field.type = "int8:2" ]] + required sint32 value = 1; +} + +message ReqSint16_1 +{ + //[[ field.type = "int16:1" ]] + required sint32 value = 1; +} + +message ReqSint16_2 +{ + //[[ field.type = "int16:2" ]] + required sint32 value = 1; +} + +message ReqSint32_1 +{ + //[[ field.type = "int32:1" ]] + required sint32 value = 1; +} + +message ReqSint32_2 +{ + //[[ field.type = "int32:2" ]] + required sint32 value = 1; +} + +message ReqSint64_1 +{ + //[[ field.type = "int64:1" ]] + required sint64 value = 1; +} + +message ReqSint64_2 +{ + //[[ field.type = "int64:2" ]] + required sint64 value = 1; +} + +message ReqFixed32_8_1 +{ + //[[ field.type = "uint8:1" ]] + required fixed32 value = 1; +} + +message ReqFixed32_8_2 +{ + //[[ field.type = "uint8:2" ]] + required fixed32 value = 1; +} + +message ReqFixed32_16_1 +{ + //[[ field.type = "uint16:1" ]] + required fixed32 value = 1; +} + +message ReqFixed32_16_2 +{ + //[[ field.type = "uint16:2" ]] + required fixed32 value = 1; +} + +message ReqFixed32_32_1 +{ + //[[ field.type = "uint32:1" ]] + required fixed32 value = 1; +} + +message ReqFixed32_32_2 +{ + //[[ field.type = "uint32:2" ]] + required fixed32 value = 1; +} + +message ReqSfixed32_8_1 +{ + //[[ field.type = "int8:1" ]] + required sfixed32 value = 1; +} + +message ReqSfixed32_8_2 +{ + //[[ field.type = "int8:2" ]] + required sfixed32 value = 1; +} + +message ReqSfixed32_16_1 +{ + //[[ field.type = "int16:1" ]] + required sfixed32 value = 1; +} + +message ReqSfixed32_16_2 +{ + //[[ field.type = "int16:2" ]] + required sfixed32 value = 1; +} + +message ReqSfixed32_32_1 +{ + //[[ field.type = "int32:1" ]] + required sfixed32 value = 1; +} + +message ReqSfixed32_32_2 +{ + //[[ field.type = "int32:2" ]] + required sfixed32 value = 1; +} + +message ReqFixed64_8_1 +{ + //[[ field.type = "uint8:1" ]] + required fixed64 value = 1; +} + +message ReqFixed64_8_2 +{ + //[[ field.type = "uint8:2" ]] + required fixed64 value = 1; +} + +message ReqFixed64_16_1 +{ + //[[ field.type = "uint16:1" ]] + required fixed64 value = 1; +} + +message ReqFixed64_16_2 +{ + //[[ field.type = "uint16:2" ]] + required fixed64 value = 1; +} + +message ReqFixed64_32_1 +{ + //[[ field.type = "uint32:1" ]] + required fixed64 value = 1; +} + +message ReqFixed64_32_2 +{ + //[[ field.type = "uint32:2" ]] + required fixed64 value = 1; +} + +message ReqFixed64_64_1 +{ + //[[ field.type = "uint64:1" ]] + required fixed64 value = 1; +} + +message ReqFixed64_64_2 +{ + //[[ field.type = "uint64:2" ]] + required fixed64 value = 1; +} + +message ReqSfixed64_8_1 +{ + //[[ field.type = "int8:1" ]] + required sfixed64 value = 1; +} + +message ReqSfixed64_8_2 +{ + //[[ field.type = "int8:2" ]] + required sfixed64 value = 1; +} + +message ReqSfixed64_16_1 +{ + //[[ field.type = "int16:1" ]] + required sfixed64 value = 1; +} + +message ReqSfixed64_16_2 +{ + //[[ field.type = "int16:2" ]] + required sfixed64 value = 1; +} + +message ReqSfixed64_32_1 +{ + //[[ field.type = "int32:1" ]] + required sfixed64 value = 1; +} + +message ReqSfixed64_32_2 +{ + //[[ field.type = "int32:2" ]] + required sfixed64 value = 1; +} + +message ReqSfixed64_64_1 +{ + //[[ field.type = "int64:1" ]] + required sfixed64 value = 1; +} + +message ReqSfixed64_64_2 +{ + //[[ field.type = "int64:2" ]] + required sfixed64 value = 1; +} + +message OptEnumAlias +{ + enum Enum + { + option allow_alias = true; + EAA_UNSPECIFIED = 0; + EAA_STARTED = 1; + EAA_RUNNING = 1; + EAA_FINISHED = 2; + } + + optional Enum value = 1; +} +message ReqEnumAlias +{ + enum Enum + { + option allow_alias = true; + EAA_UNSPECIFIED = 0; + EAA_STARTED = 1; + EAA_RUNNING = 1; + EAA_FINISHED = 2; + } + + required Enum value = 1; +} + +message RepEnumAlias +{ + enum Enum + { + option allow_alias = true; + EAA_UNSPECIFIED = 0; + EAA_STARTED = 1; + EAA_RUNNING = 1; + EAA_FINISHED = 2; + } + repeated Enum value = 1; +} + +message RepPackEnumAlias +{ + enum Enum + { + option allow_alias = true; + EAA_UNSPECIFIED = 0; + EAA_STARTED = 1; + EAA_RUNNING = 1; + EAA_FINISHED = 2; + } + + repeated Enum value = 1 [ packed = true ]; +} diff --git a/3rdparty/simple-protobuf/test/protos.cpp b/3rdparty/simple-protobuf/test/protos.cpp new file mode 100644 index 0000000..85b50a5 --- /dev/null +++ b/3rdparty/simple-protobuf/test/protos.cpp @@ -0,0 +1,49 @@ +#include "mesos.pb.h" +#include +#include +#include + +static auto load_file( const std::filesystem::path & file_path ) -> std::string +{ + const auto file_size = std::filesystem::file_size( file_path ); + auto file_content = std::string( file_size, '\0' ); + + if( auto * p_file = fopen( file_path.string( ).c_str( ), "rb" ); p_file ) + { + const auto read = fread( file_content.data( ), 1, file_content.size( ), p_file ); + fclose( p_file ); + file_content.resize( read ); + return file_content; + } + perror( file_path.string( ).c_str( ) ); + throw std::system_error( std::make_error_code( std::errc( errno ) ) ); +} + +static void save_file( const std::filesystem::path & file_path, std::string_view file_content ) +{ + if( auto * p_file = fopen( file_path.string( ).c_str( ), "wb" ); p_file ) + { + const auto written = fwrite( file_content.data( ), 1, file_content.size( ), p_file ); + fclose( p_file ); + if( written == file_content.size( ) ) + { + return; + } + } + perror( file_path.string( ).c_str( ) ); + throw std::system_error( std::make_error_code( std::errc( errno ) ) ); +} + +auto main( int argc, char * argv[] ) -> int +{ + if( argc != 3 ) + { + fprintf( stderr, "usage %s, \n", argv[ 0 ] ); + return 1; + } + + auto volume = spb::json::deserialize< mesos::Volume >( load_file( argv[ 1 ] ) ); + auto json = spb::json::serialize( volume ); + save_file( argv[ 2 ], json ); + return 0; +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index b2e821b..07ae8ad 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ project(nekoray VERSION 0.1) set(CMAKE_INCLUDE_CURRENT_DIR ON) -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_POLICY_DEFAULT_CMP0069 NEW) if (NOT CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND WIN32) diff --git a/README.md b/README.md index 4af1beb..f359bcd 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ Various formats are supported, including share links, JSON array of outbounds an - [SagerNet/sing-box](https://github.com/SagerNet/sing-box) - [Qv2ray](https://github.com/Qv2ray/Qv2ray) - [Qt](https://www.qt.io/) -- [protobuf](https://github.com/protocolbuffers/protobuf) +- [simple-protobuf](https://github.com/tonda-kriz/simple-protobuf) - [fkYAML](https://github.com/fktn-k/fkYAML) - [quirc](https://github.com/dlbeer/quirc) - [QHotkey](https://github.com/Skycoder42/QHotkey) diff --git a/README_zh.md b/README_zh.md index 1ec2774..3d757a4 100644 --- a/README_zh.md +++ b/README_zh.md @@ -40,7 +40,7 @@ - [SagerNet/sing-box](https://github.com/SagerNet/sing-box) - [Qv2ray](https://github.com/Qv2ray/Qv2ray) - [Qt](https://www.qt.io/) -- [protobuf](https://github.com/protocolbuffers/protobuf) +- [simple-protobuf](https://github.com/tonda-kriz/simple-protobuf) - [fkYAML](https://github.com/fktn-k/fkYAML) - [quirc](https://github.com/dlbeer/quirc) - [QHotkey](https://github.com/Skycoder42/QHotkey) diff --git a/cmake/myproto.cmake b/cmake/myproto.cmake index 43832bc..536376c 100644 --- a/cmake/myproto.cmake +++ b/cmake/myproto.cmake @@ -1,14 +1,9 @@ -find_package(Protobuf CONFIG REQUIRED) +add_subdirectory(3rdparty/simple-protobuf) -set(PROTO_FILES - core/server/gen/libcore.proto - ) +spb_protobuf_generate(PROTO_SRCS PROTO_HDRS core/server/gen/libcore.proto) -add_library(myproto STATIC ${PROTO_FILES}) +add_library(myproto STATIC ${PROTO_SRCS} ${PROTO_HDRS}) target_link_libraries(myproto PUBLIC - protobuf::libprotobuf + spb-proto ) -target_include_directories(myproto PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) - -protobuf_generate(TARGET myproto LANGUAGE cpp) diff --git a/core/server/gen/libcore.pb.go b/core/server/gen/libcore.pb.go index fb63a79..cb4f6bf 100644 --- a/core/server/gen/libcore.pb.go +++ b/core/server/gen/libcore.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.5 -// protoc v5.29.3 +// protoc-gen-go v1.36.6 +// protoc v6.31.0 // source: libcore.proto package gen @@ -95,11 +95,16 @@ func (*EmptyResp) Descriptor() ([]byte, []int) { type ErrorResp struct { state protoimpl.MessageState `protogen:"open.v1"` - Error string `protobuf:"bytes,1,opt,name=error,proto3" json:"error,omitempty"` + Error *string `protobuf:"bytes,1,opt,name=error,def=" json:"error,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } +// Default values for ErrorResp fields. +const ( + Default_ErrorResp_Error = string("") +) + func (x *ErrorResp) Reset() { *x = ErrorResp{} mi := &file_libcore_proto_msgTypes[2] @@ -131,26 +136,36 @@ func (*ErrorResp) Descriptor() ([]byte, []int) { } func (x *ErrorResp) GetError() string { - if x != nil { - return x.Error + if x != nil && x.Error != nil { + return *x.Error } - return "" + return Default_ErrorResp_Error } type LoadConfigReq struct { state protoimpl.MessageState `protogen:"open.v1"` - CoreConfig string `protobuf:"bytes,1,opt,name=core_config,json=coreConfig,proto3" json:"core_config,omitempty"` - DisableStats bool `protobuf:"varint,2,opt,name=disable_stats,json=disableStats,proto3" json:"disable_stats,omitempty"` - NeedExtraProcess bool `protobuf:"varint,3,opt,name=need_extra_process,json=needExtraProcess,proto3" json:"need_extra_process,omitempty"` - ExtraProcessPath string `protobuf:"bytes,4,opt,name=extra_process_path,json=extraProcessPath,proto3" json:"extra_process_path,omitempty"` - ExtraProcessArgs string `protobuf:"bytes,5,opt,name=extra_process_args,json=extraProcessArgs,proto3" json:"extra_process_args,omitempty"` - ExtraProcessConf string `protobuf:"bytes,6,opt,name=extra_process_conf,json=extraProcessConf,proto3" json:"extra_process_conf,omitempty"` - ExtraProcessConfDir string `protobuf:"bytes,7,opt,name=extra_process_conf_dir,json=extraProcessConfDir,proto3" json:"extra_process_conf_dir,omitempty"` - ExtraNoOut bool `protobuf:"varint,8,opt,name=extra_no_out,json=extraNoOut,proto3" json:"extra_no_out,omitempty"` + CoreConfig *string `protobuf:"bytes,1,req,name=core_config,json=coreConfig" json:"core_config,omitempty"` + DisableStats *bool `protobuf:"varint,2,req,name=disable_stats,json=disableStats" json:"disable_stats,omitempty"` + NeedExtraProcess *bool `protobuf:"varint,3,opt,name=need_extra_process,json=needExtraProcess,def=0" json:"need_extra_process,omitempty"` + ExtraProcessPath *string `protobuf:"bytes,4,opt,name=extra_process_path,json=extraProcessPath,def=" json:"extra_process_path,omitempty"` + ExtraProcessArgs *string `protobuf:"bytes,5,opt,name=extra_process_args,json=extraProcessArgs,def=" json:"extra_process_args,omitempty"` + ExtraProcessConf *string `protobuf:"bytes,6,opt,name=extra_process_conf,json=extraProcessConf,def=" json:"extra_process_conf,omitempty"` + ExtraProcessConfDir *string `protobuf:"bytes,7,opt,name=extra_process_conf_dir,json=extraProcessConfDir,def=" json:"extra_process_conf_dir,omitempty"` + ExtraNoOut *bool `protobuf:"varint,8,opt,name=extra_no_out,json=extraNoOut,def=0" json:"extra_no_out,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } +// Default values for LoadConfigReq fields. +const ( + Default_LoadConfigReq_NeedExtraProcess = bool(false) + Default_LoadConfigReq_ExtraProcessPath = string("") + Default_LoadConfigReq_ExtraProcessArgs = string("") + Default_LoadConfigReq_ExtraProcessConf = string("") + Default_LoadConfigReq_ExtraProcessConfDir = string("") + Default_LoadConfigReq_ExtraNoOut = bool(false) +) + func (x *LoadConfigReq) Reset() { *x = LoadConfigReq{} mi := &file_libcore_proto_msgTypes[3] @@ -182,66 +197,66 @@ func (*LoadConfigReq) Descriptor() ([]byte, []int) { } func (x *LoadConfigReq) GetCoreConfig() string { - if x != nil { - return x.CoreConfig + if x != nil && x.CoreConfig != nil { + return *x.CoreConfig } return "" } func (x *LoadConfigReq) GetDisableStats() bool { - if x != nil { - return x.DisableStats + if x != nil && x.DisableStats != nil { + return *x.DisableStats } return false } func (x *LoadConfigReq) GetNeedExtraProcess() bool { - if x != nil { - return x.NeedExtraProcess + if x != nil && x.NeedExtraProcess != nil { + return *x.NeedExtraProcess } - return false + return Default_LoadConfigReq_NeedExtraProcess } func (x *LoadConfigReq) GetExtraProcessPath() string { - if x != nil { - return x.ExtraProcessPath + if x != nil && x.ExtraProcessPath != nil { + return *x.ExtraProcessPath } - return "" + return Default_LoadConfigReq_ExtraProcessPath } func (x *LoadConfigReq) GetExtraProcessArgs() string { - if x != nil { - return x.ExtraProcessArgs + if x != nil && x.ExtraProcessArgs != nil { + return *x.ExtraProcessArgs } - return "" + return Default_LoadConfigReq_ExtraProcessArgs } func (x *LoadConfigReq) GetExtraProcessConf() string { - if x != nil { - return x.ExtraProcessConf + if x != nil && x.ExtraProcessConf != nil { + return *x.ExtraProcessConf } - return "" + return Default_LoadConfigReq_ExtraProcessConf } func (x *LoadConfigReq) GetExtraProcessConfDir() string { - if x != nil { - return x.ExtraProcessConfDir + if x != nil && x.ExtraProcessConfDir != nil { + return *x.ExtraProcessConfDir } - return "" + return Default_LoadConfigReq_ExtraProcessConfDir } func (x *LoadConfigReq) GetExtraNoOut() bool { - if x != nil { - return x.ExtraNoOut + if x != nil && x.ExtraNoOut != nil { + return *x.ExtraNoOut } - return false + return Default_LoadConfigReq_ExtraNoOut } type URLTestResp struct { state protoimpl.MessageState `protogen:"open.v1"` - OutboundTag string `protobuf:"bytes,1,opt,name=outbound_tag,json=outboundTag,proto3" json:"outbound_tag,omitempty"` - LatencyMs int32 `protobuf:"varint,2,opt,name=latency_ms,json=latencyMs,proto3" json:"latency_ms,omitempty"` - Error string `protobuf:"bytes,3,opt,name=error,proto3" json:"error,omitempty"` + OutboundTag *string `protobuf:"bytes,1,req,name=outbound_tag,json=outboundTag" json:"outbound_tag,omitempty"` + LatencyMs *int32 `protobuf:"varint,2,req,name=latency_ms,json=latencyMs" json:"latency_ms,omitempty"` + Error *string `protobuf:"bytes,3,req,name=error" json:"error,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -277,34 +292,34 @@ func (*URLTestResp) Descriptor() ([]byte, []int) { } func (x *URLTestResp) GetOutboundTag() string { - if x != nil { - return x.OutboundTag + if x != nil && x.OutboundTag != nil { + return *x.OutboundTag } return "" } func (x *URLTestResp) GetLatencyMs() int32 { - if x != nil { - return x.LatencyMs + if x != nil && x.LatencyMs != nil { + return *x.LatencyMs } return 0 } func (x *URLTestResp) GetError() string { - if x != nil { - return x.Error + if x != nil && x.Error != nil { + return *x.Error } return "" } type TestReq struct { state protoimpl.MessageState `protogen:"open.v1"` - Config string `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"` - OutboundTags []string `protobuf:"bytes,2,rep,name=outbound_tags,json=outboundTags,proto3" json:"outbound_tags,omitempty"` - UseDefaultOutbound bool `protobuf:"varint,3,opt,name=use_default_outbound,json=useDefaultOutbound,proto3" json:"use_default_outbound,omitempty"` - Url string `protobuf:"bytes,4,opt,name=url,proto3" json:"url,omitempty"` - TestCurrent bool `protobuf:"varint,5,opt,name=test_current,json=testCurrent,proto3" json:"test_current,omitempty"` - MaxConcurrency int32 `protobuf:"varint,6,opt,name=max_concurrency,json=maxConcurrency,proto3" json:"max_concurrency,omitempty"` + Config *string `protobuf:"bytes,1,req,name=config" json:"config,omitempty"` + OutboundTags []string `protobuf:"bytes,2,rep,name=outbound_tags,json=outboundTags" json:"outbound_tags,omitempty"` + UseDefaultOutbound *bool `protobuf:"varint,3,req,name=use_default_outbound,json=useDefaultOutbound" json:"use_default_outbound,omitempty"` + Url *string `protobuf:"bytes,4,req,name=url" json:"url,omitempty"` + TestCurrent *bool `protobuf:"varint,5,req,name=test_current,json=testCurrent" json:"test_current,omitempty"` + MaxConcurrency *int32 `protobuf:"varint,6,req,name=max_concurrency,json=maxConcurrency" json:"max_concurrency,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -340,8 +355,8 @@ func (*TestReq) Descriptor() ([]byte, []int) { } func (x *TestReq) GetConfig() string { - if x != nil { - return x.Config + if x != nil && x.Config != nil { + return *x.Config } return "" } @@ -354,36 +369,36 @@ func (x *TestReq) GetOutboundTags() []string { } func (x *TestReq) GetUseDefaultOutbound() bool { - if x != nil { - return x.UseDefaultOutbound + if x != nil && x.UseDefaultOutbound != nil { + return *x.UseDefaultOutbound } return false } func (x *TestReq) GetUrl() string { - if x != nil { - return x.Url + if x != nil && x.Url != nil { + return *x.Url } return "" } func (x *TestReq) GetTestCurrent() bool { - if x != nil { - return x.TestCurrent + if x != nil && x.TestCurrent != nil { + return *x.TestCurrent } return false } func (x *TestReq) GetMaxConcurrency() int32 { - if x != nil { - return x.MaxConcurrency + if x != nil && x.MaxConcurrency != nil { + return *x.MaxConcurrency } return 0 } type TestResp struct { state protoimpl.MessageState `protogen:"open.v1"` - Results []*URLTestResp `protobuf:"bytes,1,rep,name=results,proto3" json:"results,omitempty"` + Results []*URLTestResp `protobuf:"bytes,1,rep,name=results" json:"results,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -427,8 +442,8 @@ func (x *TestResp) GetResults() []*URLTestResp { type QueryStatsResp struct { state protoimpl.MessageState `protogen:"open.v1"` - Ups map[string]int64 `protobuf:"bytes,1,rep,name=ups,proto3" json:"ups,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"` - Downs map[string]int64 `protobuf:"bytes,2,rep,name=downs,proto3" json:"downs,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"` + Ups map[string]int64 `protobuf:"bytes,1,rep,name=ups" json:"ups,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"` + Downs map[string]int64 `protobuf:"bytes,2,rep,name=downs" json:"downs,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -479,7 +494,7 @@ func (x *QueryStatsResp) GetDowns() map[string]int64 { type ListConnectionsResp struct { state protoimpl.MessageState `protogen:"open.v1"` - Connections []*ConnectionMetaData `protobuf:"bytes,1,rep,name=connections,proto3" json:"connections,omitempty"` + Connections []*ConnectionMetaData `protobuf:"bytes,1,rep,name=connections" json:"connections,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -523,16 +538,16 @@ func (x *ListConnectionsResp) GetConnections() []*ConnectionMetaData { type ConnectionMetaData struct { state protoimpl.MessageState `protogen:"open.v1"` - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - CreatedAt int64 `protobuf:"varint,2,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` - Upload int64 `protobuf:"varint,3,opt,name=upload,proto3" json:"upload,omitempty"` - Download int64 `protobuf:"varint,4,opt,name=download,proto3" json:"download,omitempty"` - Outbound string `protobuf:"bytes,5,opt,name=outbound,proto3" json:"outbound,omitempty"` - Network string `protobuf:"bytes,6,opt,name=network,proto3" json:"network,omitempty"` - Dest string `protobuf:"bytes,7,opt,name=dest,proto3" json:"dest,omitempty"` - Protocol string `protobuf:"bytes,8,opt,name=protocol,proto3" json:"protocol,omitempty"` - Domain string `protobuf:"bytes,9,opt,name=domain,proto3" json:"domain,omitempty"` - Process string `protobuf:"bytes,10,opt,name=process,proto3" json:"process,omitempty"` + Id *string `protobuf:"bytes,1,req,name=id" json:"id,omitempty"` + CreatedAt *int64 `protobuf:"varint,2,req,name=created_at,json=createdAt" json:"created_at,omitempty"` + Upload *int64 `protobuf:"varint,3,req,name=upload" json:"upload,omitempty"` + Download *int64 `protobuf:"varint,4,req,name=download" json:"download,omitempty"` + Outbound *string `protobuf:"bytes,5,req,name=outbound" json:"outbound,omitempty"` + Network *string `protobuf:"bytes,6,req,name=network" json:"network,omitempty"` + Dest *string `protobuf:"bytes,7,req,name=dest" json:"dest,omitempty"` + Protocol *string `protobuf:"bytes,8,req,name=protocol" json:"protocol,omitempty"` + Domain *string `protobuf:"bytes,9,req,name=domain" json:"domain,omitempty"` + Process *string `protobuf:"bytes,10,req,name=process" json:"process,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -568,78 +583,78 @@ func (*ConnectionMetaData) Descriptor() ([]byte, []int) { } func (x *ConnectionMetaData) GetId() string { - if x != nil { - return x.Id + if x != nil && x.Id != nil { + return *x.Id } return "" } func (x *ConnectionMetaData) GetCreatedAt() int64 { - if x != nil { - return x.CreatedAt + if x != nil && x.CreatedAt != nil { + return *x.CreatedAt } return 0 } func (x *ConnectionMetaData) GetUpload() int64 { - if x != nil { - return x.Upload + if x != nil && x.Upload != nil { + return *x.Upload } return 0 } func (x *ConnectionMetaData) GetDownload() int64 { - if x != nil { - return x.Download + if x != nil && x.Download != nil { + return *x.Download } return 0 } func (x *ConnectionMetaData) GetOutbound() string { - if x != nil { - return x.Outbound + if x != nil && x.Outbound != nil { + return *x.Outbound } return "" } func (x *ConnectionMetaData) GetNetwork() string { - if x != nil { - return x.Network + if x != nil && x.Network != nil { + return *x.Network } return "" } func (x *ConnectionMetaData) GetDest() string { - if x != nil { - return x.Dest + if x != nil && x.Dest != nil { + return *x.Dest } return "" } func (x *ConnectionMetaData) GetProtocol() string { - if x != nil { - return x.Protocol + if x != nil && x.Protocol != nil { + return *x.Protocol } return "" } func (x *ConnectionMetaData) GetDomain() string { - if x != nil { - return x.Domain + if x != nil && x.Domain != nil { + return *x.Domain } return "" } func (x *ConnectionMetaData) GetProcess() string { - if x != nil { - return x.Process + if x != nil && x.Process != nil { + return *x.Process } return "" } type GetGeoIPListResponse struct { state protoimpl.MessageState `protogen:"open.v1"` - Items []string `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"` + Items []string `protobuf:"bytes,1,rep,name=items" json:"items,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -683,7 +698,7 @@ func (x *GetGeoIPListResponse) GetItems() []string { type GetGeoSiteListResponse struct { state protoimpl.MessageState `protogen:"open.v1"` - Items []string `protobuf:"bytes,2,rep,name=items,proto3" json:"items,omitempty"` + Items []string `protobuf:"bytes,2,rep,name=items" json:"items,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -727,7 +742,7 @@ func (x *GetGeoSiteListResponse) GetItems() []string { type GeoListRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - Path string `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"` + Path *string `protobuf:"bytes,1,req,name=path" json:"path,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -763,16 +778,16 @@ func (*GeoListRequest) Descriptor() ([]byte, []int) { } func (x *GeoListRequest) GetPath() string { - if x != nil { - return x.Path + if x != nil && x.Path != nil { + return *x.Path } return "" } type CompileGeoIPToSrsRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - Item string `protobuf:"bytes,1,opt,name=item,proto3" json:"item,omitempty"` - Path string `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"` + Item *string `protobuf:"bytes,1,req,name=item" json:"item,omitempty"` + Path *string `protobuf:"bytes,2,req,name=path" json:"path,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -808,23 +823,23 @@ func (*CompileGeoIPToSrsRequest) Descriptor() ([]byte, []int) { } func (x *CompileGeoIPToSrsRequest) GetItem() string { - if x != nil { - return x.Item + if x != nil && x.Item != nil { + return *x.Item } return "" } func (x *CompileGeoIPToSrsRequest) GetPath() string { - if x != nil { - return x.Path + if x != nil && x.Path != nil { + return *x.Path } return "" } type CompileGeoSiteToSrsRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - Item string `protobuf:"bytes,1,opt,name=item,proto3" json:"item,omitempty"` - Path string `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"` + Item *string `protobuf:"bytes,1,req,name=item" json:"item,omitempty"` + Path *string `protobuf:"bytes,2,req,name=path" json:"path,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -860,22 +875,22 @@ func (*CompileGeoSiteToSrsRequest) Descriptor() ([]byte, []int) { } func (x *CompileGeoSiteToSrsRequest) GetItem() string { - if x != nil { - return x.Item + if x != nil && x.Item != nil { + return *x.Item } return "" } func (x *CompileGeoSiteToSrsRequest) GetPath() string { - if x != nil { - return x.Path + if x != nil && x.Path != nil { + return *x.Path } return "" } type SetSystemDNSRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - Clear bool `protobuf:"varint,1,opt,name=clear,proto3" json:"clear,omitempty"` + Clear *bool `protobuf:"varint,1,req,name=clear" json:"clear,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -911,15 +926,15 @@ func (*SetSystemDNSRequest) Descriptor() ([]byte, []int) { } func (x *SetSystemDNSRequest) GetClear() bool { - if x != nil { - return x.Clear + if x != nil && x.Clear != nil { + return *x.Clear } return false } type IsPrivilegedResponse struct { state protoimpl.MessageState `protogen:"open.v1"` - HasPrivilege bool `protobuf:"varint,1,opt,name=has_privilege,json=hasPrivilege,proto3" json:"has_privilege,omitempty"` + HasPrivilege *bool `protobuf:"varint,1,req,name=has_privilege,json=hasPrivilege" json:"has_privilege,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -955,22 +970,22 @@ func (*IsPrivilegedResponse) Descriptor() ([]byte, []int) { } func (x *IsPrivilegedResponse) GetHasPrivilege() bool { - if x != nil { - return x.HasPrivilege + if x != nil && x.HasPrivilege != nil { + return *x.HasPrivilege } return false } type SpeedTestRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - Config string `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"` - OutboundTags []string `protobuf:"bytes,2,rep,name=outbound_tags,json=outboundTags,proto3" json:"outbound_tags,omitempty"` - TestCurrent bool `protobuf:"varint,3,opt,name=test_current,json=testCurrent,proto3" json:"test_current,omitempty"` - UseDefaultOutbound bool `protobuf:"varint,4,opt,name=use_default_outbound,json=useDefaultOutbound,proto3" json:"use_default_outbound,omitempty"` - TestDownload bool `protobuf:"varint,5,opt,name=test_download,json=testDownload,proto3" json:"test_download,omitempty"` - TestUpload bool `protobuf:"varint,6,opt,name=test_upload,json=testUpload,proto3" json:"test_upload,omitempty"` - SimpleDownload bool `protobuf:"varint,7,opt,name=simple_download,json=simpleDownload,proto3" json:"simple_download,omitempty"` - SimpleDownloadAddr string `protobuf:"bytes,8,opt,name=simple_download_addr,json=simpleDownloadAddr,proto3" json:"simple_download_addr,omitempty"` + Config *string `protobuf:"bytes,1,req,name=config" json:"config,omitempty"` + OutboundTags []string `protobuf:"bytes,2,rep,name=outbound_tags,json=outboundTags" json:"outbound_tags,omitempty"` + TestCurrent *bool `protobuf:"varint,3,req,name=test_current,json=testCurrent" json:"test_current,omitempty"` + UseDefaultOutbound *bool `protobuf:"varint,4,req,name=use_default_outbound,json=useDefaultOutbound" json:"use_default_outbound,omitempty"` + TestDownload *bool `protobuf:"varint,5,req,name=test_download,json=testDownload" json:"test_download,omitempty"` + TestUpload *bool `protobuf:"varint,6,req,name=test_upload,json=testUpload" json:"test_upload,omitempty"` + SimpleDownload *bool `protobuf:"varint,7,req,name=simple_download,json=simpleDownload" json:"simple_download,omitempty"` + SimpleDownloadAddr *string `protobuf:"bytes,8,req,name=simple_download_addr,json=simpleDownloadAddr" json:"simple_download_addr,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1006,8 +1021,8 @@ func (*SpeedTestRequest) Descriptor() ([]byte, []int) { } func (x *SpeedTestRequest) GetConfig() string { - if x != nil { - return x.Config + if x != nil && x.Config != nil { + return *x.Config } return "" } @@ -1020,57 +1035,57 @@ func (x *SpeedTestRequest) GetOutboundTags() []string { } func (x *SpeedTestRequest) GetTestCurrent() bool { - if x != nil { - return x.TestCurrent + if x != nil && x.TestCurrent != nil { + return *x.TestCurrent } return false } func (x *SpeedTestRequest) GetUseDefaultOutbound() bool { - if x != nil { - return x.UseDefaultOutbound + if x != nil && x.UseDefaultOutbound != nil { + return *x.UseDefaultOutbound } return false } func (x *SpeedTestRequest) GetTestDownload() bool { - if x != nil { - return x.TestDownload + if x != nil && x.TestDownload != nil { + return *x.TestDownload } return false } func (x *SpeedTestRequest) GetTestUpload() bool { - if x != nil { - return x.TestUpload + if x != nil && x.TestUpload != nil { + return *x.TestUpload } return false } func (x *SpeedTestRequest) GetSimpleDownload() bool { - if x != nil { - return x.SimpleDownload + if x != nil && x.SimpleDownload != nil { + return *x.SimpleDownload } return false } func (x *SpeedTestRequest) GetSimpleDownloadAddr() string { - if x != nil { - return x.SimpleDownloadAddr + if x != nil && x.SimpleDownloadAddr != nil { + return *x.SimpleDownloadAddr } return "" } type SpeedTestResult struct { state protoimpl.MessageState `protogen:"open.v1"` - DlSpeed string `protobuf:"bytes,1,opt,name=dl_speed,json=dlSpeed,proto3" json:"dl_speed,omitempty"` - UlSpeed string `protobuf:"bytes,2,opt,name=ul_speed,json=ulSpeed,proto3" json:"ul_speed,omitempty"` - Latency int32 `protobuf:"varint,3,opt,name=latency,proto3" json:"latency,omitempty"` - OutboundTag string `protobuf:"bytes,4,opt,name=outbound_tag,json=outboundTag,proto3" json:"outbound_tag,omitempty"` - Error string `protobuf:"bytes,5,opt,name=error,proto3" json:"error,omitempty"` - ServerName string `protobuf:"bytes,6,opt,name=server_name,json=serverName,proto3" json:"server_name,omitempty"` - ServerCountry string `protobuf:"bytes,7,opt,name=server_country,json=serverCountry,proto3" json:"server_country,omitempty"` - Cancelled bool `protobuf:"varint,8,opt,name=cancelled,proto3" json:"cancelled,omitempty"` + DlSpeed *string `protobuf:"bytes,1,req,name=dl_speed,json=dlSpeed" json:"dl_speed,omitempty"` + UlSpeed *string `protobuf:"bytes,2,req,name=ul_speed,json=ulSpeed" json:"ul_speed,omitempty"` + Latency *int32 `protobuf:"varint,3,req,name=latency" json:"latency,omitempty"` + OutboundTag *string `protobuf:"bytes,4,req,name=outbound_tag,json=outboundTag" json:"outbound_tag,omitempty"` + Error *string `protobuf:"bytes,5,req,name=error" json:"error,omitempty"` + ServerName *string `protobuf:"bytes,6,req,name=server_name,json=serverName" json:"server_name,omitempty"` + ServerCountry *string `protobuf:"bytes,7,req,name=server_country,json=serverCountry" json:"server_country,omitempty"` + Cancelled *bool `protobuf:"varint,8,req,name=cancelled" json:"cancelled,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1106,64 +1121,64 @@ func (*SpeedTestResult) Descriptor() ([]byte, []int) { } func (x *SpeedTestResult) GetDlSpeed() string { - if x != nil { - return x.DlSpeed + if x != nil && x.DlSpeed != nil { + return *x.DlSpeed } return "" } func (x *SpeedTestResult) GetUlSpeed() string { - if x != nil { - return x.UlSpeed + if x != nil && x.UlSpeed != nil { + return *x.UlSpeed } return "" } func (x *SpeedTestResult) GetLatency() int32 { - if x != nil { - return x.Latency + if x != nil && x.Latency != nil { + return *x.Latency } return 0 } func (x *SpeedTestResult) GetOutboundTag() string { - if x != nil { - return x.OutboundTag + if x != nil && x.OutboundTag != nil { + return *x.OutboundTag } return "" } func (x *SpeedTestResult) GetError() string { - if x != nil { - return x.Error + if x != nil && x.Error != nil { + return *x.Error } return "" } func (x *SpeedTestResult) GetServerName() string { - if x != nil { - return x.ServerName + if x != nil && x.ServerName != nil { + return *x.ServerName } return "" } func (x *SpeedTestResult) GetServerCountry() string { - if x != nil { - return x.ServerCountry + if x != nil && x.ServerCountry != nil { + return *x.ServerCountry } return "" } func (x *SpeedTestResult) GetCancelled() bool { - if x != nil { - return x.Cancelled + if x != nil && x.Cancelled != nil { + return *x.Cancelled } return false } type SpeedTestResponse struct { state protoimpl.MessageState `protogen:"open.v1"` - Results []*SpeedTestResult `protobuf:"bytes,1,rep,name=results,proto3" json:"results,omitempty"` + Results []*SpeedTestResult `protobuf:"bytes,1,rep,name=results" json:"results,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1207,8 +1222,8 @@ func (x *SpeedTestResponse) GetResults() []*SpeedTestResult { type QuerySpeedTestResponse struct { state protoimpl.MessageState `protogen:"open.v1"` - Result *SpeedTestResult `protobuf:"bytes,1,opt,name=result,proto3" json:"result,omitempty"` - IsRunning bool `protobuf:"varint,2,opt,name=is_running,json=isRunning,proto3" json:"is_running,omitempty"` + Result *SpeedTestResult `protobuf:"bytes,1,req,name=result" json:"result,omitempty"` + IsRunning *bool `protobuf:"varint,2,req,name=is_running,json=isRunning" json:"is_running,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1251,15 +1266,15 @@ func (x *QuerySpeedTestResponse) GetResult() *SpeedTestResult { } func (x *QuerySpeedTestResponse) GetIsRunning() bool { - if x != nil { - return x.IsRunning + if x != nil && x.IsRunning != nil { + return *x.IsRunning } return false } type QueryURLTestResponse struct { state protoimpl.MessageState `protogen:"open.v1"` - Results []*URLTestResp `protobuf:"bytes,1,rep,name=results,proto3" json:"results,omitempty"` + Results []*URLTestResp `protobuf:"bytes,1,rep,name=results" json:"results,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1303,243 +1318,127 @@ func (x *QueryURLTestResponse) GetResults() []*URLTestResp { var File_libcore_proto protoreflect.FileDescriptor -var file_libcore_proto_rawDesc = string([]byte{ - 0x0a, 0x0d, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, - 0x07, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x22, 0x0a, 0x0a, 0x08, 0x45, 0x6d, 0x70, 0x74, - 0x79, 0x52, 0x65, 0x71, 0x22, 0x0b, 0x0a, 0x09, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, - 0x70, 0x22, 0x21, 0x0a, 0x09, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x12, 0x14, - 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, - 0x72, 0x72, 0x6f, 0x72, 0x22, 0xe4, 0x02, 0x0a, 0x0d, 0x4c, 0x6f, 0x61, 0x64, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6f, 0x72, 0x65, 0x5f, 0x63, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x72, - 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x23, 0x0a, 0x0d, 0x64, 0x69, 0x73, 0x61, 0x62, - 0x6c, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, - 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x2c, 0x0a, 0x12, - 0x6e, 0x65, 0x65, 0x64, 0x5f, 0x65, 0x78, 0x74, 0x72, 0x61, 0x5f, 0x70, 0x72, 0x6f, 0x63, 0x65, - 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x6e, 0x65, 0x65, 0x64, 0x45, 0x78, - 0x74, 0x72, 0x61, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x65, 0x78, - 0x74, 0x72, 0x61, 0x5f, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x70, 0x61, 0x74, 0x68, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x65, 0x78, 0x74, 0x72, 0x61, 0x50, 0x72, 0x6f, - 0x63, 0x65, 0x73, 0x73, 0x50, 0x61, 0x74, 0x68, 0x12, 0x2c, 0x0a, 0x12, 0x65, 0x78, 0x74, 0x72, - 0x61, 0x5f, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x61, 0x72, 0x67, 0x73, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x65, 0x78, 0x74, 0x72, 0x61, 0x50, 0x72, 0x6f, 0x63, 0x65, - 0x73, 0x73, 0x41, 0x72, 0x67, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x65, 0x78, 0x74, 0x72, 0x61, 0x5f, - 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x10, 0x65, 0x78, 0x74, 0x72, 0x61, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, - 0x43, 0x6f, 0x6e, 0x66, 0x12, 0x33, 0x0a, 0x16, 0x65, 0x78, 0x74, 0x72, 0x61, 0x5f, 0x70, 0x72, - 0x6f, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x5f, 0x64, 0x69, 0x72, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x65, 0x78, 0x74, 0x72, 0x61, 0x50, 0x72, 0x6f, 0x63, 0x65, - 0x73, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x44, 0x69, 0x72, 0x12, 0x20, 0x0a, 0x0c, 0x65, 0x78, 0x74, - 0x72, 0x61, 0x5f, 0x6e, 0x6f, 0x5f, 0x6f, 0x75, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x0a, 0x65, 0x78, 0x74, 0x72, 0x61, 0x4e, 0x6f, 0x4f, 0x75, 0x74, 0x22, 0x65, 0x0a, 0x0b, 0x55, - 0x52, 0x4c, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x12, 0x21, 0x0a, 0x0c, 0x6f, 0x75, - 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0b, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x61, 0x67, 0x12, 0x1d, 0x0a, - 0x0a, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x09, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x4d, 0x73, 0x12, 0x14, 0x0a, 0x05, - 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, - 0x6f, 0x72, 0x22, 0xd6, 0x01, 0x0a, 0x07, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x12, 0x16, - 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x23, 0x0a, 0x0d, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, - 0x6e, 0x64, 0x5f, 0x74, 0x61, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x6f, - 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x61, 0x67, 0x73, 0x12, 0x30, 0x0a, 0x14, 0x75, - 0x73, 0x65, 0x5f, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x6f, 0x75, 0x74, 0x62, 0x6f, - 0x75, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x75, 0x73, 0x65, 0x44, 0x65, - 0x66, 0x61, 0x75, 0x6c, 0x74, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x10, 0x0a, - 0x03, 0x75, 0x72, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, - 0x21, 0x0a, 0x0c, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x74, 0x65, 0x73, 0x74, 0x43, 0x75, 0x72, 0x72, 0x65, - 0x6e, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x6d, 0x61, 0x78, 0x5f, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, - 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x6d, 0x61, 0x78, - 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x22, 0x3a, 0x0a, 0x08, 0x54, - 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x12, 0x2e, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, - 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, - 0x72, 0x65, 0x2e, 0x55, 0x52, 0x4c, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x52, 0x07, - 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x22, 0xf0, 0x01, 0x0a, 0x0e, 0x51, 0x75, 0x65, 0x72, - 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x12, 0x32, 0x0a, 0x03, 0x75, 0x70, - 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, - 0x65, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x2e, 0x55, 0x70, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x03, 0x75, 0x70, 0x73, 0x12, 0x38, - 0x0a, 0x05, 0x64, 0x6f, 0x77, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, - 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, - 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x2e, 0x44, 0x6f, 0x77, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, - 0x79, 0x52, 0x05, 0x64, 0x6f, 0x77, 0x6e, 0x73, 0x1a, 0x36, 0x0a, 0x08, 0x55, 0x70, 0x73, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, - 0x1a, 0x38, 0x0a, 0x0a, 0x44, 0x6f, 0x77, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, - 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, - 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x54, 0x0a, 0x13, 0x4c, 0x69, - 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x12, 0x3d, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, - 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x74, 0x61, 0x44, - 0x61, 0x74, 0x61, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x22, 0x8f, 0x02, 0x0a, 0x12, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4d, - 0x65, 0x74, 0x61, 0x44, 0x61, 0x74, 0x61, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x1a, - 0x0a, 0x08, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x08, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x6f, 0x75, - 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6f, 0x75, - 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, - 0x6b, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, - 0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, 0x73, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, - 0x64, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, - 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, - 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x63, - 0x65, 0x73, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x63, 0x65, - 0x73, 0x73, 0x22, 0x2c, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x4c, 0x69, - 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x74, - 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, - 0x22, 0x2e, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x47, 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x4c, 0x69, - 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x74, - 0x65, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, - 0x22, 0x24, 0x0a, 0x0e, 0x47, 0x65, 0x6f, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x22, 0x42, 0x0a, 0x18, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, - 0x65, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x54, 0x6f, 0x53, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x22, 0x44, 0x0a, 0x1a, 0x43, 0x6f, - 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x47, 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x54, 0x6f, 0x53, 0x72, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x12, 0x12, 0x0a, 0x04, - 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, - 0x22, 0x2b, 0x0a, 0x13, 0x53, 0x65, 0x74, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x44, 0x4e, 0x53, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6c, 0x65, 0x61, 0x72, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x63, 0x6c, 0x65, 0x61, 0x72, 0x22, 0x3b, 0x0a, - 0x14, 0x49, 0x73, 0x50, 0x72, 0x69, 0x76, 0x69, 0x6c, 0x65, 0x67, 0x65, 0x64, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x68, 0x61, 0x73, 0x5f, 0x70, 0x72, 0x69, - 0x76, 0x69, 0x6c, 0x65, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x68, 0x61, - 0x73, 0x50, 0x72, 0x69, 0x76, 0x69, 0x6c, 0x65, 0x67, 0x65, 0x22, 0xc5, 0x02, 0x0a, 0x10, 0x53, - 0x70, 0x65, 0x65, 0x64, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x16, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x23, 0x0a, 0x0d, 0x6f, 0x75, 0x74, 0x62, 0x6f, - 0x75, 0x6e, 0x64, 0x5f, 0x74, 0x61, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, - 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x61, 0x67, 0x73, 0x12, 0x21, 0x0a, 0x0c, - 0x74, 0x65, 0x73, 0x74, 0x5f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x0b, 0x74, 0x65, 0x73, 0x74, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x12, - 0x30, 0x0a, 0x14, 0x75, 0x73, 0x65, 0x5f, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x6f, - 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x75, - 0x73, 0x65, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, - 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, - 0x61, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x74, 0x65, 0x73, 0x74, 0x44, 0x6f, - 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x75, - 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x74, 0x65, 0x73, - 0x74, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x69, 0x6d, 0x70, 0x6c, - 0x65, 0x5f, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x0e, 0x73, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, - 0x12, 0x30, 0x0a, 0x14, 0x73, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x5f, 0x64, 0x6f, 0x77, 0x6e, 0x6c, - 0x6f, 0x61, 0x64, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, - 0x73, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x41, 0x64, - 0x64, 0x72, 0x22, 0x80, 0x02, 0x0a, 0x0f, 0x53, 0x70, 0x65, 0x65, 0x64, 0x54, 0x65, 0x73, 0x74, - 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x64, 0x6c, 0x5f, 0x73, 0x70, 0x65, - 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x64, 0x6c, 0x53, 0x70, 0x65, 0x65, - 0x64, 0x12, 0x19, 0x0a, 0x08, 0x75, 0x6c, 0x5f, 0x73, 0x70, 0x65, 0x65, 0x64, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x75, 0x6c, 0x53, 0x70, 0x65, 0x65, 0x64, 0x12, 0x18, 0x0a, 0x07, - 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x6c, - 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, - 0x6e, 0x64, 0x5f, 0x74, 0x61, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6f, 0x75, - 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x61, 0x67, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, - 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, - 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, - 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x72, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x61, 0x6e, 0x63, 0x65, - 0x6c, 0x6c, 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x63, 0x61, 0x6e, 0x63, - 0x65, 0x6c, 0x6c, 0x65, 0x64, 0x22, 0x47, 0x0a, 0x11, 0x53, 0x70, 0x65, 0x65, 0x64, 0x54, 0x65, - 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x32, 0x0a, 0x07, 0x72, 0x65, - 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x69, - 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x53, 0x70, 0x65, 0x65, 0x64, 0x54, 0x65, 0x73, 0x74, 0x52, - 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x22, 0x69, - 0x0a, 0x16, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x70, 0x65, 0x65, 0x64, 0x54, 0x65, 0x73, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x30, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, - 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, - 0x72, 0x65, 0x2e, 0x53, 0x70, 0x65, 0x65, 0x64, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x75, - 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x73, - 0x5f, 0x72, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, - 0x69, 0x73, 0x52, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x22, 0x46, 0x0a, 0x14, 0x51, 0x75, 0x65, - 0x72, 0x79, 0x55, 0x52, 0x4c, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x2e, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x55, 0x52, 0x4c, - 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, - 0x73, 0x32, 0xbc, 0x08, 0x0a, 0x0e, 0x4c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x53, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x12, 0x2d, 0x0a, 0x04, 0x45, 0x78, 0x69, 0x74, 0x12, 0x11, 0x2e, 0x6c, - 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x71, 0x1a, - 0x12, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, - 0x65, 0x73, 0x70, 0x12, 0x33, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x72, 0x74, 0x12, 0x16, 0x2e, 0x6c, - 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4c, 0x6f, 0x61, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x52, 0x65, 0x71, 0x1a, 0x12, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, - 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x12, 0x2d, 0x0a, 0x04, 0x53, 0x74, 0x6f, 0x70, - 0x12, 0x11, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, - 0x52, 0x65, 0x71, 0x1a, 0x12, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x72, - 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x12, 0x39, 0x0a, 0x0b, 0x43, 0x68, 0x65, 0x63, 0x6b, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x16, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, - 0x2e, 0x4c, 0x6f, 0x61, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x1a, 0x12, - 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, - 0x73, 0x70, 0x12, 0x2b, 0x0a, 0x04, 0x54, 0x65, 0x73, 0x74, 0x12, 0x10, 0x2e, 0x6c, 0x69, 0x62, - 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x1a, 0x11, 0x2e, 0x6c, - 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x12, - 0x31, 0x0a, 0x08, 0x53, 0x74, 0x6f, 0x70, 0x54, 0x65, 0x73, 0x74, 0x12, 0x11, 0x2e, 0x6c, 0x69, - 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x12, - 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, - 0x73, 0x70, 0x12, 0x40, 0x0a, 0x0c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x55, 0x52, 0x4c, 0x54, 0x65, - 0x73, 0x74, 0x12, 0x11, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, - 0x74, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x1d, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, - 0x51, 0x75, 0x65, 0x72, 0x79, 0x55, 0x52, 0x4c, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x38, 0x0a, 0x0a, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, - 0x74, 0x73, 0x12, 0x11, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, - 0x74, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x17, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, - 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x12, 0x42, - 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x12, 0x11, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, - 0x79, 0x52, 0x65, 0x71, 0x1a, 0x1c, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4c, - 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x12, 0x46, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x4c, 0x69, - 0x73, 0x74, 0x12, 0x17, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x47, 0x65, 0x6f, - 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x69, - 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x4c, 0x69, - 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x0e, 0x47, 0x65, - 0x74, 0x47, 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x17, 0x2e, 0x6c, - 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x47, 0x65, 0x6f, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, - 0x47, 0x65, 0x74, 0x47, 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x11, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, - 0x65, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x54, 0x6f, 0x53, 0x72, 0x73, 0x12, 0x21, 0x2e, 0x6c, 0x69, - 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x47, 0x65, 0x6f, - 0x49, 0x50, 0x54, 0x6f, 0x53, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, - 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, - 0x73, 0x70, 0x12, 0x4e, 0x0a, 0x13, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x47, 0x65, 0x6f, - 0x53, 0x69, 0x74, 0x65, 0x54, 0x6f, 0x53, 0x72, 0x73, 0x12, 0x23, 0x2e, 0x6c, 0x69, 0x62, 0x63, - 0x6f, 0x72, 0x65, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x47, 0x65, 0x6f, 0x53, 0x69, - 0x74, 0x65, 0x54, 0x6f, 0x53, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, - 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, - 0x73, 0x70, 0x12, 0x40, 0x0a, 0x0c, 0x53, 0x65, 0x74, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x44, - 0x4e, 0x53, 0x12, 0x1c, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x53, 0x65, 0x74, - 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x44, 0x4e, 0x53, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x12, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, - 0x52, 0x65, 0x73, 0x70, 0x12, 0x40, 0x0a, 0x0c, 0x49, 0x73, 0x50, 0x72, 0x69, 0x76, 0x69, 0x6c, - 0x65, 0x67, 0x65, 0x64, 0x12, 0x11, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, - 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x1d, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, - 0x65, 0x2e, 0x49, 0x73, 0x50, 0x72, 0x69, 0x76, 0x69, 0x6c, 0x65, 0x67, 0x65, 0x64, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x42, 0x0a, 0x09, 0x53, 0x70, 0x65, 0x65, 0x64, 0x54, - 0x65, 0x73, 0x74, 0x12, 0x19, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x53, 0x70, - 0x65, 0x65, 0x64, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, - 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x53, 0x70, 0x65, 0x65, 0x64, 0x54, 0x65, - 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x0e, 0x51, 0x75, - 0x65, 0x72, 0x79, 0x53, 0x70, 0x65, 0x65, 0x64, 0x54, 0x65, 0x73, 0x74, 0x12, 0x11, 0x2e, 0x6c, - 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x71, 0x1a, - 0x1f, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, - 0x70, 0x65, 0x65, 0x64, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x42, 0x13, 0x48, 0x03, 0x5a, 0x0f, 0x67, 0x72, 0x70, 0x63, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, - 0x72, 0x2f, 0x67, 0x65, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -}) +const file_libcore_proto_rawDesc = "" + + "\n" + + "\rlibcore.proto\x12\alibcore\"\n" + + "\n" + + "\bEmptyReq\"\v\n" + + "\tEmptyResp\"#\n" + + "\tErrorResp\x12\x16\n" + + "\x05error\x18\x01 \x01(\t:\x00R\x05error\"\xfa\x02\n" + + "\rLoadConfigReq\x12\x1f\n" + + "\vcore_config\x18\x01 \x02(\tR\n" + + "coreConfig\x12#\n" + + "\rdisable_stats\x18\x02 \x02(\bR\fdisableStats\x123\n" + + "\x12need_extra_process\x18\x03 \x01(\b:\x05falseR\x10needExtraProcess\x12.\n" + + "\x12extra_process_path\x18\x04 \x01(\t:\x00R\x10extraProcessPath\x12.\n" + + "\x12extra_process_args\x18\x05 \x01(\t:\x00R\x10extraProcessArgs\x12.\n" + + "\x12extra_process_conf\x18\x06 \x01(\t:\x00R\x10extraProcessConf\x125\n" + + "\x16extra_process_conf_dir\x18\a \x01(\t:\x00R\x13extraProcessConfDir\x12'\n" + + "\fextra_no_out\x18\b \x01(\b:\x05falseR\n" + + "extraNoOut\"e\n" + + "\vURLTestResp\x12!\n" + + "\foutbound_tag\x18\x01 \x02(\tR\voutboundTag\x12\x1d\n" + + "\n" + + "latency_ms\x18\x02 \x02(\x05R\tlatencyMs\x12\x14\n" + + "\x05error\x18\x03 \x02(\tR\x05error\"\xd6\x01\n" + + "\aTestReq\x12\x16\n" + + "\x06config\x18\x01 \x02(\tR\x06config\x12#\n" + + "\routbound_tags\x18\x02 \x03(\tR\foutboundTags\x120\n" + + "\x14use_default_outbound\x18\x03 \x02(\bR\x12useDefaultOutbound\x12\x10\n" + + "\x03url\x18\x04 \x02(\tR\x03url\x12!\n" + + "\ftest_current\x18\x05 \x02(\bR\vtestCurrent\x12'\n" + + "\x0fmax_concurrency\x18\x06 \x02(\x05R\x0emaxConcurrency\":\n" + + "\bTestResp\x12.\n" + + "\aresults\x18\x01 \x03(\v2\x14.libcore.URLTestRespR\aresults\"\xf0\x01\n" + + "\x0eQueryStatsResp\x122\n" + + "\x03ups\x18\x01 \x03(\v2 .libcore.QueryStatsResp.UpsEntryR\x03ups\x128\n" + + "\x05downs\x18\x02 \x03(\v2\".libcore.QueryStatsResp.DownsEntryR\x05downs\x1a6\n" + + "\bUpsEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\x03R\x05value:\x028\x01\x1a8\n" + + "\n" + + "DownsEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\x03R\x05value:\x028\x01\"T\n" + + "\x13ListConnectionsResp\x12=\n" + + "\vconnections\x18\x01 \x03(\v2\x1b.libcore.ConnectionMetaDataR\vconnections\"\x8f\x02\n" + + "\x12ConnectionMetaData\x12\x0e\n" + + "\x02id\x18\x01 \x02(\tR\x02id\x12\x1d\n" + + "\n" + + "created_at\x18\x02 \x02(\x03R\tcreatedAt\x12\x16\n" + + "\x06upload\x18\x03 \x02(\x03R\x06upload\x12\x1a\n" + + "\bdownload\x18\x04 \x02(\x03R\bdownload\x12\x1a\n" + + "\boutbound\x18\x05 \x02(\tR\boutbound\x12\x18\n" + + "\anetwork\x18\x06 \x02(\tR\anetwork\x12\x12\n" + + "\x04dest\x18\a \x02(\tR\x04dest\x12\x1a\n" + + "\bprotocol\x18\b \x02(\tR\bprotocol\x12\x16\n" + + "\x06domain\x18\t \x02(\tR\x06domain\x12\x18\n" + + "\aprocess\x18\n" + + " \x02(\tR\aprocess\",\n" + + "\x14GetGeoIPListResponse\x12\x14\n" + + "\x05items\x18\x01 \x03(\tR\x05items\".\n" + + "\x16GetGeoSiteListResponse\x12\x14\n" + + "\x05items\x18\x02 \x03(\tR\x05items\"$\n" + + "\x0eGeoListRequest\x12\x12\n" + + "\x04path\x18\x01 \x02(\tR\x04path\"B\n" + + "\x18CompileGeoIPToSrsRequest\x12\x12\n" + + "\x04item\x18\x01 \x02(\tR\x04item\x12\x12\n" + + "\x04path\x18\x02 \x02(\tR\x04path\"D\n" + + "\x1aCompileGeoSiteToSrsRequest\x12\x12\n" + + "\x04item\x18\x01 \x02(\tR\x04item\x12\x12\n" + + "\x04path\x18\x02 \x02(\tR\x04path\"+\n" + + "\x13SetSystemDNSRequest\x12\x14\n" + + "\x05clear\x18\x01 \x02(\bR\x05clear\";\n" + + "\x14IsPrivilegedResponse\x12#\n" + + "\rhas_privilege\x18\x01 \x02(\bR\fhasPrivilege\"\xc5\x02\n" + + "\x10SpeedTestRequest\x12\x16\n" + + "\x06config\x18\x01 \x02(\tR\x06config\x12#\n" + + "\routbound_tags\x18\x02 \x03(\tR\foutboundTags\x12!\n" + + "\ftest_current\x18\x03 \x02(\bR\vtestCurrent\x120\n" + + "\x14use_default_outbound\x18\x04 \x02(\bR\x12useDefaultOutbound\x12#\n" + + "\rtest_download\x18\x05 \x02(\bR\ftestDownload\x12\x1f\n" + + "\vtest_upload\x18\x06 \x02(\bR\n" + + "testUpload\x12'\n" + + "\x0fsimple_download\x18\a \x02(\bR\x0esimpleDownload\x120\n" + + "\x14simple_download_addr\x18\b \x02(\tR\x12simpleDownloadAddr\"\x80\x02\n" + + "\x0fSpeedTestResult\x12\x19\n" + + "\bdl_speed\x18\x01 \x02(\tR\adlSpeed\x12\x19\n" + + "\bul_speed\x18\x02 \x02(\tR\aulSpeed\x12\x18\n" + + "\alatency\x18\x03 \x02(\x05R\alatency\x12!\n" + + "\foutbound_tag\x18\x04 \x02(\tR\voutboundTag\x12\x14\n" + + "\x05error\x18\x05 \x02(\tR\x05error\x12\x1f\n" + + "\vserver_name\x18\x06 \x02(\tR\n" + + "serverName\x12%\n" + + "\x0eserver_country\x18\a \x02(\tR\rserverCountry\x12\x1c\n" + + "\tcancelled\x18\b \x02(\bR\tcancelled\"G\n" + + "\x11SpeedTestResponse\x122\n" + + "\aresults\x18\x01 \x03(\v2\x18.libcore.SpeedTestResultR\aresults\"i\n" + + "\x16QuerySpeedTestResponse\x120\n" + + "\x06result\x18\x01 \x02(\v2\x18.libcore.SpeedTestResultR\x06result\x12\x1d\n" + + "\n" + + "is_running\x18\x02 \x02(\bR\tisRunning\"F\n" + + "\x14QueryURLTestResponse\x12.\n" + + "\aresults\x18\x01 \x03(\v2\x14.libcore.URLTestRespR\aresults2\xbc\b\n" + + "\x0eLibcoreService\x12-\n" + + "\x04Exit\x12\x11.libcore.EmptyReq\x1a\x12.libcore.EmptyResp\x123\n" + + "\x05Start\x12\x16.libcore.LoadConfigReq\x1a\x12.libcore.ErrorResp\x12-\n" + + "\x04Stop\x12\x11.libcore.EmptyReq\x1a\x12.libcore.ErrorResp\x129\n" + + "\vCheckConfig\x12\x16.libcore.LoadConfigReq\x1a\x12.libcore.ErrorResp\x12+\n" + + "\x04Test\x12\x10.libcore.TestReq\x1a\x11.libcore.TestResp\x121\n" + + "\bStopTest\x12\x11.libcore.EmptyReq\x1a\x12.libcore.EmptyResp\x12@\n" + + "\fQueryURLTest\x12\x11.libcore.EmptyReq\x1a\x1d.libcore.QueryURLTestResponse\x128\n" + + "\n" + + "QueryStats\x12\x11.libcore.EmptyReq\x1a\x17.libcore.QueryStatsResp\x12B\n" + + "\x0fListConnections\x12\x11.libcore.EmptyReq\x1a\x1c.libcore.ListConnectionsResp\x12F\n" + + "\fGetGeoIPList\x12\x17.libcore.GeoListRequest\x1a\x1d.libcore.GetGeoIPListResponse\x12J\n" + + "\x0eGetGeoSiteList\x12\x17.libcore.GeoListRequest\x1a\x1f.libcore.GetGeoSiteListResponse\x12J\n" + + "\x11CompileGeoIPToSrs\x12!.libcore.CompileGeoIPToSrsRequest\x1a\x12.libcore.EmptyResp\x12N\n" + + "\x13CompileGeoSiteToSrs\x12#.libcore.CompileGeoSiteToSrsRequest\x1a\x12.libcore.EmptyResp\x12@\n" + + "\fSetSystemDNS\x12\x1c.libcore.SetSystemDNSRequest\x1a\x12.libcore.EmptyResp\x12@\n" + + "\fIsPrivileged\x12\x11.libcore.EmptyReq\x1a\x1d.libcore.IsPrivilegedResponse\x12B\n" + + "\tSpeedTest\x12\x19.libcore.SpeedTestRequest\x1a\x1a.libcore.SpeedTestResponse\x12D\n" + + "\x0eQuerySpeedTest\x12\x11.libcore.EmptyReq\x1a\x1f.libcore.QuerySpeedTestResponseB\x13H\x03Z\x0fgrpc_server/gen" var ( file_libcore_proto_rawDescOnce sync.Once diff --git a/core/server/gen/libcore.proto b/core/server/gen/libcore.proto index 53289a8..9014b1c 100644 --- a/core/server/gen/libcore.proto +++ b/core/server/gen/libcore.proto @@ -1,4 +1,4 @@ -syntax = "proto3"; +syntax = "proto2"; package libcore; option go_package = "grpc_server/gen"; @@ -34,33 +34,33 @@ message EmptyReq {} message EmptyResp {} message ErrorResp { - string error = 1; + optional string error = 1 [default = ""]; } message LoadConfigReq { - string core_config = 1; - bool disable_stats = 2; - bool need_extra_process = 3; - string extra_process_path = 4; - string extra_process_args = 5; - string extra_process_conf = 6; - string extra_process_conf_dir = 7; - bool extra_no_out = 8; + required string core_config = 1; + required bool disable_stats = 2; + optional bool need_extra_process = 3 [default = false]; + optional string extra_process_path = 4 [default = ""]; + optional string extra_process_args = 5 [default = ""]; + optional string extra_process_conf = 6 [default = ""]; + optional string extra_process_conf_dir = 7 [default = ""]; + optional bool extra_no_out = 8 [default = false]; } message URLTestResp { - string outbound_tag = 1; - int32 latency_ms = 2; - string error = 3; + required string outbound_tag = 1; + required int32 latency_ms = 2; + required string error = 3; } message TestReq { - string config = 1; + required string config = 1; repeated string outbound_tags = 2; - bool use_default_outbound = 3; - string url = 4; - bool test_current = 5; - int32 max_concurrency = 6; + required bool use_default_outbound = 3; + required string url = 4; + required bool test_current = 5; + required int32 max_concurrency = 6; } message TestResp { @@ -77,16 +77,16 @@ message ListConnectionsResp { } message ConnectionMetaData { - string id = 1; - int64 created_at = 2; - int64 upload = 3; - int64 download = 4; - string outbound = 5; - string network = 6; - string dest = 7; - string protocol = 8; - string domain = 9; - string process = 10; + required string id = 1; + required int64 created_at = 2; + required int64 upload = 3; + required int64 download = 4; + required string outbound = 5; + required string network = 6; + required string dest = 7; + required string protocol = 8; + required string domain = 9; + required string process = 10; } message GetGeoIPListResponse { @@ -98,47 +98,47 @@ message GetGeoSiteListResponse { } message GeoListRequest { - string path = 1; + required string path = 1; } message CompileGeoIPToSrsRequest { - string item = 1; - string path = 2; + required string item = 1; + required string path = 2; } message CompileGeoSiteToSrsRequest { - string item = 1; - string path = 2; + required string item = 1; + required string path = 2; } message SetSystemDNSRequest { - bool clear = 1; + required bool clear = 1; } message IsPrivilegedResponse { - bool has_privilege = 1; + required bool has_privilege = 1; } message SpeedTestRequest { - string config = 1; + required string config = 1; repeated string outbound_tags = 2; - bool test_current = 3; - bool use_default_outbound = 4; - bool test_download = 5; - bool test_upload = 6; - bool simple_download = 7; - string simple_download_addr = 8; + required bool test_current = 3; + required bool use_default_outbound = 4; + required bool test_download = 5; + required bool test_upload = 6; + required bool simple_download = 7; + required string simple_download_addr = 8; } message SpeedTestResult { - string dl_speed = 1; - string ul_speed = 2; - int32 latency = 3; - string outbound_tag = 4; - string error = 5; - string server_name = 6; - string server_country = 7; - bool cancelled = 8; + required string dl_speed = 1; + required string ul_speed = 2; + required int32 latency = 3; + required string outbound_tag = 4; + required string error = 5; + required string server_name = 6; + required string server_country = 7; + required bool cancelled = 8; } message SpeedTestResponse { @@ -146,8 +146,8 @@ message SpeedTestResponse { } message QuerySpeedTestResponse { - SpeedTestResult result = 1; - bool is_running = 2; + required SpeedTestResult result = 1; + required bool is_running = 2; } message QueryURLTestResponse { diff --git a/core/server/gen/libcore_grpc.pb.go b/core/server/gen/libcore_grpc.pb.go index d83c3b7..2c15743 100644 --- a/core/server/gen/libcore_grpc.pb.go +++ b/core/server/gen/libcore_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.5.1 -// - protoc v5.29.3 +// - protoc v6.31.0 // source: libcore.proto package gen diff --git a/core/server/go.mod b/core/server/go.mod index 1a78265..6f3f748 100644 --- a/core/server/go.mod +++ b/core/server/go.mod @@ -2,8 +2,6 @@ module nekobox_core go 1.23.0 -toolchain go1.24.4 - require ( github.com/Mahdi-zarei/speedtest-go v1.7.12 github.com/dustin/go-humanize v1.0.1 @@ -14,7 +12,6 @@ require ( github.com/sagernet/sing-box v1.11.14 github.com/sagernet/sing-dns v0.4.5 github.com/sagernet/sing-tun v0.6.9 - github.com/sagernet/sing-box v1.11.14 github.com/spf13/cobra v1.9.1 golang.org/x/sys v0.33.0 google.golang.org/grpc v1.73.0 diff --git a/core/server/go.sum b/core/server/go.sum index 7ceabb4..74479d5 100644 --- a/core/server/go.sum +++ b/core/server/go.sum @@ -171,16 +171,16 @@ github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= -go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= -go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= -go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= -go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= -go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= -go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= -go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= -go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= -go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= +go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= +go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= +go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= +go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= +go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= +go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= +go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= +go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= +go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= diff --git a/core/server/internal/distro/all/all.go b/core/server/internal/distro/all/all.go index 202785f..6e13a28 100644 --- a/core/server/internal/distro/all/all.go +++ b/core/server/internal/distro/all/all.go @@ -1,6 +1,5 @@ package all import ( - _ "nekobox_core/internal/boxapi" _ "nekobox_core/internal/boxdns" ) diff --git a/core/server/server.go b/core/server/server.go index d435968..aee8e19 100644 --- a/core/server/server.go +++ b/core/server/server.go @@ -35,6 +35,11 @@ type server struct { gen.UnimplementedLibcoreServiceServer } +// To returns a pointer to the given value. +func To[T any](v T) *T { + return &v +} + func (s *server) Exit(ctx context.Context, in *gen.EmptyReq) (out *gen.EmptyResp, _ error) { out = &gen.EmptyResp{} @@ -57,7 +62,7 @@ func (s *server) Start(ctx context.Context, in *gen.LoadConfigReq) (out *gen.Err defer func() { out = &gen.ErrorResp{} if err != nil { - out.Error = err.Error() + out.Error = To(err.Error()) boxInstance = nil } }() @@ -71,20 +76,20 @@ func (s *server) Start(ctx context.Context, in *gen.LoadConfigReq) (out *gen.Err return } - if in.NeedExtraProcess { - extraConfPath := in.ExtraProcessConfDir + string(os.PathSeparator) + "extra.conf" + if *in.NeedExtraProcess { + extraConfPath := *in.ExtraProcessConfDir + string(os.PathSeparator) + "extra.conf" f, e := os.OpenFile(extraConfPath, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 700) if e != nil { err = E.Cause(e, "Failed to open extra.conf") return } - _, e = f.WriteString(in.ExtraProcessConf) + _, e = f.WriteString(*in.ExtraProcessConf) if e != nil { err = E.Cause(e, "Failed to write extra.conf") return } _ = f.Close() - args, e := shlex.Split(in.ExtraProcessArgs) + args, e := shlex.Split(*in.ExtraProcessArgs) if e != nil { err = E.Cause(e, "Failed to parse args") return @@ -96,18 +101,18 @@ func (s *server) Start(ctx context.Context, in *gen.LoadConfigReq) (out *gen.Err } } - extraProcess = process.NewProcess(in.ExtraProcessPath, args, in.ExtraNoOut) + extraProcess = process.NewProcess(*in.ExtraProcessPath, args, *in.ExtraNoOut) err = extraProcess.Start() if err != nil { return } } - boxInstance, instanceCancel, err = boxmain.Create([]byte(in.CoreConfig)) + boxInstance, instanceCancel, err = boxmain.Create([]byte(*in.CoreConfig)) if err != nil { return } - if runtime.GOOS == "darwin" && strings.Contains(in.CoreConfig, "utun") { + if runtime.GOOS == "darwin" && strings.Contains(*in.CoreConfig, "utun") { err := sys.SetSystemDNS("172.19.0.2", boxInstance.Network().InterfaceMonitor()) if err != nil { log.Println("Failed to set system DNS:", err) @@ -124,7 +129,7 @@ func (s *server) Stop(ctx context.Context, in *gen.EmptyReq) (out *gen.ErrorResp defer func() { out = &gen.ErrorResp{} if err != nil { - out.Error = err.Error() + out.Error = To(err.Error()) } }() @@ -152,10 +157,10 @@ func (s *server) Stop(ctx context.Context, in *gen.EmptyReq) (out *gen.ErrorResp } func (s *server) CheckConfig(ctx context.Context, in *gen.LoadConfigReq) (*gen.ErrorResp, error) { - err := boxmain.Check([]byte(in.CoreConfig)) + err := boxmain.Check([]byte(*in.CoreConfig)) if err != nil { return &gen.ErrorResp{ - Error: err.Error(), + Error: To(err.Error()), }, nil } return &gen.ErrorResp{}, nil @@ -166,18 +171,18 @@ func (s *server) Test(ctx context.Context, in *gen.TestReq) (*gen.TestResp, erro var cancel context.CancelFunc var err error var twice = true - if in.TestCurrent { + if *in.TestCurrent { if boxInstance == nil { return &gen.TestResp{Results: []*gen.URLTestResp{{ - OutboundTag: "proxy", - LatencyMs: 0, - Error: "Instance is not running", + OutboundTag: To("proxy"), + LatencyMs: To(int32(0)), + Error: To("Instance is not running"), }}}, nil } testInstance = boxInstance twice = false } else { - testInstance, cancel, err = boxmain.Create([]byte(in.Config)) + testInstance, cancel, err = boxmain.Create([]byte(*in.Config)) if err != nil { return nil, err } @@ -186,16 +191,16 @@ func (s *server) Test(ctx context.Context, in *gen.TestReq) (*gen.TestResp, erro } outboundTags := in.OutboundTags - if in.UseDefaultOutbound || in.TestCurrent { + if *in.UseDefaultOutbound || *in.TestCurrent { outbound := testInstance.Outbound().Default() outboundTags = []string{outbound.Tag()} } - var maxConcurrency = in.MaxConcurrency + var maxConcurrency = *in.MaxConcurrency if maxConcurrency >= 500 || maxConcurrency == 0 { maxConcurrency = MaxConcurrentTests } - results := BatchURLTest(testCtx, testInstance, outboundTags, in.Url, int(maxConcurrency), twice) + results := BatchURLTest(testCtx, testInstance, outboundTags, *in.Url, int(maxConcurrency), twice) res := make([]*gen.URLTestResp, 0) for idx, data := range results { @@ -204,9 +209,9 @@ func (s *server) Test(ctx context.Context, in *gen.TestReq) (*gen.TestResp, erro errStr = data.Error.Error() } res = append(res, &gen.URLTestResp{ - OutboundTag: outboundTags[idx], - LatencyMs: int32(data.Duration.Milliseconds()), - Error: errStr, + OutboundTag: To(outboundTags[idx]), + LatencyMs: To(int32(data.Duration.Milliseconds())), + Error: To(errStr), }) } @@ -229,9 +234,9 @@ func (s *server) QueryURLTest(ctx context.Context, in *gen.EmptyReq) (*gen.Query errStr = r.Error.Error() } resp.Results = append(resp.Results, &gen.URLTestResp{ - OutboundTag: r.Tag, - LatencyMs: int32(r.Duration.Milliseconds()), - Error: errStr, + OutboundTag: To(r.Tag), + LatencyMs: To(int32(r.Duration.Milliseconds())), + Error: To(errStr), }) } return resp, nil @@ -297,16 +302,16 @@ func (s *server) ListConnections(ctx context.Context, in *gen.EmptyReq) (*gen.Li process = spl[len(spl)-1] } r := &gen.ConnectionMetaData{ - Id: c.ID.String(), - CreatedAt: c.CreatedAt.UnixMilli(), - Upload: c.Upload.Load(), - Download: c.Download.Load(), - Outbound: c.Outbound, - Network: c.Metadata.Network, - Dest: c.Metadata.Destination.String(), - Protocol: c.Metadata.Protocol, - Domain: c.Metadata.Domain, - Process: process, + Id: To(c.ID.String()), + CreatedAt: To(c.CreatedAt.UnixMilli()), + Upload: To(c.Upload.Load()), + Download: To(c.Download.Load()), + Outbound: To(c.Outbound), + Network: To(c.Metadata.Network), + Dest: To(c.Metadata.Destination.String()), + Protocol: To(c.Metadata.Protocol), + Domain: To(c.Metadata.Domain), + Process: To(process), } res = append(res, r) } @@ -317,7 +322,7 @@ func (s *server) ListConnections(ctx context.Context, in *gen.EmptyReq) (*gen.Li } func (s *server) GetGeoIPList(ctx context.Context, in *gen.GeoListRequest) (*gen.GetGeoIPListResponse, error) { - resp, err := boxmain.ListGeoip(in.Path + string(os.PathSeparator) + "geoip.db") + resp, err := boxmain.ListGeoip(*in.Path + string(os.PathSeparator) + "geoip.db") if err != nil { return nil, err } @@ -332,7 +337,7 @@ func (s *server) GetGeoIPList(ctx context.Context, in *gen.GeoListRequest) (*gen } func (s *server) GetGeoSiteList(ctx context.Context, in *gen.GeoListRequest) (*gen.GetGeoSiteListResponse, error) { - resp, err := boxmain.GeositeList(in.Path + string(os.PathSeparator) + "geosite.db") + resp, err := boxmain.GeositeList(*in.Path + string(os.PathSeparator) + "geosite.db") if err != nil { return nil, err } @@ -347,8 +352,8 @@ func (s *server) GetGeoSiteList(ctx context.Context, in *gen.GeoListRequest) (*g } func (s *server) CompileGeoIPToSrs(ctx context.Context, in *gen.CompileGeoIPToSrsRequest) (*gen.EmptyResp, error) { - category := strings.TrimSuffix(in.Item, "_IP") - err := boxmain.CompileRuleSet(in.Path+string(os.PathSeparator)+"geoip.db", category, boxmain.IpRuleSet, "./rule_sets/"+in.Item+".srs") + category := strings.TrimSuffix(*in.Item, "_IP") + err := boxmain.CompileRuleSet(*in.Path+string(os.PathSeparator)+"geoip.db", category, boxmain.IpRuleSet, "./rule_sets/"+*in.Item+".srs") if err != nil { return nil, err } @@ -357,8 +362,8 @@ func (s *server) CompileGeoIPToSrs(ctx context.Context, in *gen.CompileGeoIPToSr } func (s *server) CompileGeoSiteToSrs(ctx context.Context, in *gen.CompileGeoSiteToSrsRequest) (*gen.EmptyResp, error) { - category := strings.TrimSuffix(in.Item, "_SITE") - err := boxmain.CompileRuleSet(in.Path+string(os.PathSeparator)+"geosite.db", category, boxmain.SiteRuleSet, "./rule_sets/"+in.Item+".srs") + category := strings.TrimSuffix(*in.Item, "_SITE") + err := boxmain.CompileRuleSet(*in.Path+string(os.PathSeparator)+"geosite.db", category, boxmain.SiteRuleSet, "./rule_sets/"+*in.Item+".srs") if err != nil { return nil, err } @@ -369,31 +374,31 @@ func (s *server) CompileGeoSiteToSrs(ctx context.Context, in *gen.CompileGeoSite func (s *server) IsPrivileged(ctx context.Context, _ *gen.EmptyReq) (*gen.IsPrivilegedResponse, error) { if runtime.GOOS == "windows" { return &gen.IsPrivilegedResponse{ - HasPrivilege: false, + HasPrivilege: To(false), }, nil } - return &gen.IsPrivilegedResponse{HasPrivilege: os.Geteuid() == 0}, nil + return &gen.IsPrivilegedResponse{HasPrivilege: To(os.Geteuid() == 0)}, nil } func (s *server) SpeedTest(ctx context.Context, in *gen.SpeedTestRequest) (*gen.SpeedTestResponse, error) { - if !in.TestDownload && !in.TestUpload && !in.SimpleDownload { + if !*in.TestDownload && !*in.TestUpload && !*in.SimpleDownload { return nil, errors.New("cannot run empty test") } var testInstance *boxbox.Box var cancel context.CancelFunc outboundTags := in.OutboundTags var err error - if in.TestCurrent { + if *in.TestCurrent { if boxInstance == nil { return &gen.SpeedTestResponse{Results: []*gen.SpeedTestResult{{ - OutboundTag: "proxy", - Error: "Instance is not running", + OutboundTag: To("proxy"), + Error: To("Instance is not running"), }}}, nil } testInstance = boxInstance } else { - testInstance, cancel, err = boxmain.Create([]byte(in.Config)) + testInstance, cancel, err = boxmain.Create([]byte(*in.Config)) if err != nil { return nil, err } @@ -401,12 +406,12 @@ func (s *server) SpeedTest(ctx context.Context, in *gen.SpeedTestRequest) (*gen. defer testInstance.Close() } - if in.UseDefaultOutbound || in.TestCurrent { + if *in.UseDefaultOutbound || *in.TestCurrent { outbound := testInstance.Outbound().Default() outboundTags = []string{outbound.Tag()} } - results := BatchSpeedTest(testCtx, testInstance, outboundTags, in.TestDownload, in.TestUpload, in.SimpleDownload, in.SimpleDownloadAddr) + results := BatchSpeedTest(testCtx, testInstance, outboundTags, *in.TestDownload, *in.TestUpload, *in.SimpleDownload, *in.SimpleDownloadAddr) res := make([]*gen.SpeedTestResult, 0) for _, data := range results { @@ -415,14 +420,14 @@ func (s *server) SpeedTest(ctx context.Context, in *gen.SpeedTestRequest) (*gen. errStr = data.Error.Error() } res = append(res, &gen.SpeedTestResult{ - DlSpeed: data.DlSpeed, - UlSpeed: data.UlSpeed, - Latency: data.Latency, - OutboundTag: data.Tag, - Error: errStr, - ServerName: data.ServerName, - ServerCountry: data.ServerCountry, - Cancelled: data.Cancelled, + DlSpeed: To(data.DlSpeed), + UlSpeed: To(data.UlSpeed), + Latency: To(data.Latency), + OutboundTag: To(data.Tag), + Error: To(errStr), + ServerName: To(data.ServerName), + ServerCountry: To(data.ServerCountry), + Cancelled: To(data.Cancelled), }) } @@ -437,15 +442,15 @@ func (s *server) QuerySpeedTest(context.Context, *gen.EmptyReq) (*gen.QuerySpeed } return &gen.QuerySpeedTestResponse{ Result: &gen.SpeedTestResult{ - DlSpeed: res.DlSpeed, - UlSpeed: res.UlSpeed, - Latency: res.Latency, - OutboundTag: res.Tag, - Error: errStr, - ServerName: res.ServerName, - ServerCountry: res.ServerCountry, - Cancelled: res.Cancelled, + DlSpeed: To(res.DlSpeed), + UlSpeed: To(res.UlSpeed), + Latency: To(res.Latency), + OutboundTag: To(res.Tag), + Error: To(errStr), + ServerName: To(res.ServerName), + ServerCountry: To(res.ServerCountry), + Cancelled: To(res.Cancelled), }, - IsRunning: isRunning, + IsRunning: To(isRunning), }, nil } diff --git a/core/server/server_windows.go b/core/server/server_windows.go index 49071fd..27f6a5b 100644 --- a/core/server/server_windows.go +++ b/core/server/server_windows.go @@ -7,7 +7,7 @@ import ( ) func (s *server) SetSystemDNS(ctx context.Context, in *gen.SetSystemDNSRequest) (*gen.EmptyResp, error) { - err := boxdns.DnsManagerInstance.SetSystemDNS(nil, in.Clear) + err := boxdns.DnsManagerInstance.SetSystemDNS(nil, *in.Clear) if err != nil { return nil, err } diff --git a/include/api/gRPC.h b/include/api/gRPC.h index 555ebab..8d9c393 100644 --- a/include/api/gRPC.h +++ b/include/api/gRPC.h @@ -1,6 +1,8 @@ #pragma once -#include "core/server/gen/libcore.pb.h" +#ifndef Q_MOC_RUN +#include "libcore.pb.h" +#endif #include namespace QtGrpc { diff --git a/include/ui/mainwindow.h b/include/ui/mainwindow.h index 1ee7451..06bae11 100644 --- a/include/ui/mainwindow.h +++ b/include/ui/mainwindow.h @@ -1,8 +1,10 @@ #pragma once #include -#include #include +#ifndef Q_MOC_RUN +#include +#endif #include "include/global/NekoGui.hpp" #include "include/stats/connections/connectionLister.hpp" diff --git a/script/build_deps_all.sh b/script/build_deps_all.sh deleted file mode 100755 index 809afbe..0000000 --- a/script/build_deps_all.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/bin/bash -set -e - -mkdir -p libs -cd libs - -if [ -z $cmake ]; then - cmake="cmake" -fi -if [ -z $deps ]; then - deps="deps" -fi - -mkdir -p $deps -cd $deps -INSTALL_PREFIX=$PWD/built -rm -rf $INSTALL_PREFIX -mkdir -p $INSTALL_PREFIX - -#### clean #### -clean() { - rm -rf protobuf -} - -#### protobuf #### -git clone --recurse-submodules -b v31.0 --depth 1 --shallow-submodules https://github.com/protocolbuffers/protobuf - -mkdir -p protobuf/build -cd protobuf/build - -$cmake .. -GNinja \ - -DCMAKE_BUILD_TYPE=Release \ - -DBUILD_SHARED_LIBS=OFF \ - -Dprotobuf_BUILD_TESTS=OFF \ - -DCMAKE_INSTALL_PREFIX=$INSTALL_PREFIX \ - -Dprotobuf_BUILD_PROTOBUF_BINARIES=ON \ - -DCMAKE_OSX_ARCHITECTURES=$1 \ - -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 \ - -DCMAKE_CXX_STANDARD=17 -ninja && ninja install - -cd ../.. - -#### -clean diff --git a/src/api/gRPC.cpp b/src/api/gRPC.cpp index 2af9f14..3df8dfd 100644 --- a/src/api/gRPC.cpp +++ b/src/api/gRPC.cpp @@ -144,13 +144,11 @@ namespace QtGrpc { } QNetworkReply::NetworkError Call(const QString &methodName, - const google::protobuf::MessageLite &req, google::protobuf::MessageLite *rsp, + const std::string req, std::vector &rsp, int timeout_ms = 0) { if (!NekoGui::dataStore->core_running) return QNetworkReply::NetworkError(-1919); - std::string reqStr; - req.SerializeToString(&reqStr); - auto requestArray = QByteArray::fromStdString(reqStr); + auto requestArray = QByteArray::fromStdString(req); QByteArray responseArray; QNetworkReply::NetworkError err; @@ -172,9 +170,7 @@ namespace QtGrpc { if (err != QNetworkReply::NetworkError::NoError) { return err; } - if (!rsp->ParseFromArray(responseArray.data(), responseArray.size())) { - return QNetworkReply::NetworkError(-114514); - } + rsp.assign(responseArray.begin(), responseArray.end()); return QNetworkReply::NetworkError::NoError; } }; @@ -195,30 +191,35 @@ namespace NekoGui_rpc { void Client::Exit() { libcore::EmptyReq request; libcore::EmptyResp reply; - default_grpc_channel->Call("Exit", request, &reply, 100); + std::vector rsp; + default_grpc_channel->Call("Exit", spb::pb::serialize< std::string >( request ), rsp, 100); } QString Client::Start(bool *rpcOK, const libcore::LoadConfigReq &request) { libcore::ErrorResp reply; - auto status = default_grpc_channel->Call("Start", request, &reply); + std::vector rsp; + auto status = default_grpc_channel->Call("Start", spb::pb::serialize< std::string >( request ), rsp); if (status == QNetworkReply::NoError) { + reply = spb::pb::deserialize< libcore::ErrorResp >( rsp ); *rpcOK = true; - return {reply.error().c_str()}; + return {reply.error.value().c_str()}; } else { NOT_OK - return reply.error().c_str(); + return reply.error.value().c_str(); } } QString Client::Stop(bool *rpcOK) { libcore::EmptyReq request; libcore::ErrorResp reply; - auto status = default_grpc_channel->Call("Stop", request, &reply); + std::vector rsp; + auto status = default_grpc_channel->Call("Stop", spb::pb::serialize< std::string >( request ), rsp); if (status == QNetworkReply::NoError) { + reply = spb::pb::deserialize< libcore::ErrorResp >( rsp ); *rpcOK = true; - return {reply.error().c_str()}; + return {reply.error.value().c_str()}; } else { NOT_OK return ""; @@ -227,11 +228,12 @@ namespace NekoGui_rpc { libcore::QueryStatsResp Client::QueryStats() { libcore::EmptyReq request; - libcore::QueryStatsResp reply; - auto status = default_grpc_channel->Call("QueryStats", request, &reply, 500); + std::vector rsp; + auto status = default_grpc_channel->Call("QueryStats", spb::pb::serialize< std::string >( request ), rsp, 500); if (status == QNetworkReply::NoError) { + reply = spb::pb::deserialize< libcore::QueryStatsResp >( rsp ); return reply; } else { return {}; @@ -240,9 +242,11 @@ namespace NekoGui_rpc { libcore::TestResp Client::Test(bool *rpcOK, const libcore::TestReq &request) { libcore::TestResp reply; - auto status = make_grpc_channel()->Call("Test", request, &reply); + std::vector rsp; + auto status = make_grpc_channel()->Call("Test", spb::pb::serialize< std::string >( request ), rsp); if (status == QNetworkReply::NoError) { + reply = spb::pb::deserialize< libcore::TestResp >( rsp ); *rpcOK = true; return reply; } else { @@ -254,8 +258,8 @@ namespace NekoGui_rpc { void Client::StopTests(bool *rpcOK) { const libcore::EmptyReq req; libcore::EmptyResp resp; - - auto status = make_grpc_channel()->Call("StopTest", req, &resp); + std::vector rsp; + auto status = make_grpc_channel()->Call("StopTest", spb::pb::serialize< std::string >( req ), rsp); if (status == QNetworkReply::NoError) { *rpcOK = true; @@ -268,9 +272,11 @@ namespace NekoGui_rpc { { libcore::EmptyReq request; libcore::QueryURLTestResponse resp; + std::vector rsp; + auto status = make_grpc_channel()->Call("QueryURLTest", spb::pb::serialize< std::string >( request ), rsp); - auto status = make_grpc_channel()->Call("QueryURLTest", request, &resp); if (status == QNetworkReply::NoError) { + resp = spb::pb::deserialize< libcore::QueryURLTestResponse >( rsp ); *rpcOK = true; return resp; } else { @@ -284,12 +290,15 @@ namespace NekoGui_rpc { case GeoRuleSetType::ip: { libcore::GeoListRequest req; libcore::GetGeoIPListResponse resp; - req.set_path(basePath.toStdString()); + std::vector rsp; + req.path = basePath.toStdString(); + + auto status = default_grpc_channel->Call("GetGeoIPList", spb::pb::serialize< std::string >( req ), rsp); - auto status = default_grpc_channel->Call("GetGeoIPList", req, &resp); if (status == QNetworkReply::NoError) { QStringList res; - for (const auto & i : resp.items()) { + resp = spb::pb::deserialize< libcore::GetGeoIPListResponse >( rsp ); + for (const auto & i : resp.items) { res.append(QString::fromStdString(i)); } *rpcOK = true; @@ -302,12 +311,15 @@ namespace NekoGui_rpc { case GeoRuleSetType::site: { libcore::GeoListRequest req; libcore::GetGeoSiteListResponse resp; - req.set_path(basePath.toStdString()); + std::vector rsp; + req.path = basePath.toStdString(); + + auto status = default_grpc_channel->Call("GetGeoSiteList", spb::pb::serialize< std::string >( req ), rsp); - auto status = default_grpc_channel->Call("GetGeoSiteList", req, &resp); if (status == QNetworkReply::NoError) { QStringList res; - for (const auto & i : resp.items()) { + resp = spb::pb::deserialize< libcore::GetGeoSiteListResponse >( rsp ); + for (const auto & i : resp.items) { res.append(QString::fromStdString(i)); } *rpcOK = true; @@ -326,10 +338,11 @@ namespace NekoGui_rpc { case ip: { libcore::CompileGeoIPToSrsRequest req; libcore::EmptyResp resp; - req.set_item(category); - req.set_path(basePath.toStdString()); + std::vector rsp; + req.item = category; + req.path = basePath.toStdString(); - auto status = default_grpc_channel->Call("CompileGeoIPToSrs", req, &resp); + auto status = default_grpc_channel->Call("CompileGeoIPToSrs", spb::pb::serialize< std::string >( req ), rsp); if (status == QNetworkReply::NoError) { *rpcOK = true; return ""; @@ -341,10 +354,11 @@ namespace NekoGui_rpc { case site: { libcore::CompileGeoSiteToSrsRequest req; libcore::EmptyResp resp; - req.set_item(category); - req.set_path(basePath.toStdString()); + std::vector rsp; + req.item = category; + req.path = basePath.toStdString(); - auto status = default_grpc_channel->Call("CompileGeoSiteToSrs", req, &resp); + auto status = default_grpc_channel->Call("CompileGeoSiteToSrs", spb::pb::serialize< std::string >( req ), rsp); if (status == QNetworkReply::NoError) { *rpcOK = true; return ""; @@ -360,10 +374,10 @@ namespace NekoGui_rpc { QString Client::SetSystemDNS(bool *rpcOK, const bool clear) const { libcore::SetSystemDNSRequest req; libcore::EmptyResp resp; + std::vector rsp; + req.clear = clear; - req.set_clear(clear); - - auto status = default_grpc_channel->Call("SetSystemDNS", req, &resp); + auto status = default_grpc_channel->Call("SetSystemDNS", spb::pb::serialize< std::string >( req ), rsp); if (status == QNetworkReply::NoError) { *rpcOK = true; return ""; @@ -377,8 +391,10 @@ namespace NekoGui_rpc { { libcore::EmptyReq req; libcore::ListConnectionsResp resp; - auto status = default_grpc_channel->Call("ListConnections", req, &resp); + std::vector rsp; + auto status = default_grpc_channel->Call("ListConnections", spb::pb::serialize< std::string >( req ), rsp); if (status == QNetworkReply::NoError) { + resp = spb::pb::deserialize< libcore::ListConnectionsResp >( rsp ); *rpcOK = true; return resp; } else { @@ -392,12 +408,15 @@ namespace NekoGui_rpc { { libcore::LoadConfigReq req; libcore::ErrorResp resp; - req.set_core_config(config.toStdString()); - auto status = default_grpc_channel->Call("CheckConfig", req, &resp); + std::vector rsp; + req.core_config = config.toStdString(); + auto status = default_grpc_channel->Call("CheckConfig", spb::pb::serialize< std::string >( req ), rsp); + if (status == QNetworkReply::NoError) { + resp = spb::pb::deserialize< libcore::ErrorResp >( rsp ); *rpcOK = true; - return {resp.error().c_str()}; + return {resp.error.value().c_str()}; } else { NOT_OK @@ -410,11 +429,14 @@ namespace NekoGui_rpc { { auto req = libcore::EmptyReq(); auto resp = libcore::IsPrivilegedResponse(); - auto status = default_grpc_channel->Call("IsPrivileged", req, &resp); + std::vector rsp; + auto status = default_grpc_channel->Call("IsPrivileged", spb::pb::serialize< std::string >( req ), rsp); + if (status == QNetworkReply::NoError) { + resp = spb::pb::deserialize< libcore::IsPrivilegedResponse >( rsp ); *rpcOK = true; - return resp.has_privilege(); + return resp.has_privilege; } else { NOT_OK @@ -425,9 +447,11 @@ namespace NekoGui_rpc { libcore::SpeedTestResponse Client::SpeedTest(bool *rpcOK, const libcore::SpeedTestRequest &request) { libcore::SpeedTestResponse reply; - auto status = make_grpc_channel()->Call("SpeedTest", request, &reply); + std::vector rsp; + auto status = make_grpc_channel()->Call("SpeedTest", spb::pb::serialize< std::string >( request ), rsp); if (status == QNetworkReply::NoError) { + reply = spb::pb::deserialize< libcore::SpeedTestResponse >( rsp ); *rpcOK = true; return reply; } else { @@ -440,9 +464,11 @@ namespace NekoGui_rpc { { const libcore::EmptyReq req; libcore::QuerySpeedTestResponse reply; - auto status = make_grpc_channel()->Call("QuerySpeedTest", req, &reply); + std::vector rsp; + auto status = make_grpc_channel()->Call("QuerySpeedTest", spb::pb::serialize< std::string >( req ), rsp); if (status == QNetworkReply::NoError) { + reply = spb::pb::deserialize< libcore::QuerySpeedTestResponse >( rsp ); *rpcOK = true; return reply; } else { diff --git a/src/stats/connectionLister/connectionLister.cpp b/src/stats/connectionLister/connectionLister.cpp index 0705a79..3b95701 100644 --- a/src/stats/connectionLister/connectionLister.cpp +++ b/src/stats/connectionLister/connectionLister.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include #include "include/ui/mainwindow_interface.h" #include @@ -49,20 +49,20 @@ namespace NekoGui_traffic QMap toAdd; QSet newState; QList sorted; - auto conns = resp.connections(); + auto conns = resp.connections; for (auto conn : conns) { auto c = ConnectionMetadata(); - c.id = QString(conn.mutable_id()->c_str()); - c.createdAtMs = conn.created_at(); - c.dest = QString(conn.mutable_dest()->c_str()); - c.upload = conn.upload(); - c.download = conn.download(); - c.domain = QString(conn.mutable_domain()->c_str()); - c.network = QString(conn.mutable_network()->c_str()); - c.outbound = QString(conn.mutable_outbound()->c_str()); - c.process = QString(conn.mutable_process()->c_str()); - c.protocol = QString(conn.mutable_protocol()->c_str()); + c.id = QString(conn.id.c_str()); + c.createdAtMs = conn.created_at; + c.dest = QString(conn.dest.c_str()); + c.upload = conn.upload; + c.download = conn.download; + c.domain = QString(conn.domain.c_str()); + c.network = QString(conn.network.c_str()); + c.outbound = QString(conn.outbound.c_str()); + c.process = QString(conn.process.c_str()); + c.protocol = QString(conn.protocol.c_str()); if (sort == Default) { if (state->contains(c.id)) diff --git a/src/stats/traffic/TrafficLooper.cpp b/src/stats/traffic/TrafficLooper.cpp index 5f28ccb..400bf92 100644 --- a/src/stats/traffic/TrafficLooper.cpp +++ b/src/stats/traffic/TrafficLooper.cpp @@ -24,13 +24,13 @@ namespace NekoGui_traffic { int proxyUp = 0, proxyDown = 0; for (const auto &item: this->items) { - if (!resp.ups().contains(item->tag)) continue; + if (!resp.ups.contains(item->tag)) continue; auto now = elapsedTimer.elapsed(); auto interval = now - item->last_update; item->last_update = now; if (interval <= 0) continue; - auto up = resp.ups().at(item->tag); - auto down = resp.downs().at(item->tag); + auto up = resp.ups.at(item->tag); + auto down = resp.downs.at(item->tag); if (item->tag == "proxy") { proxyUp = up; diff --git a/src/ui/mainwindow.cpp b/src/ui/mainwindow.cpp index d738d48..a4fad4f 100644 --- a/src/ui/mainwindow.cpp +++ b/src/ui/mainwindow.cpp @@ -959,10 +959,10 @@ void MainWindow::UpdateDataView(bool force) "" "

Server: %4, %5

" ).arg(currentSptProfileName, - currentTestResult.dl_speed().c_str(), - currentTestResult.ul_speed().c_str(), - currentTestResult.server_country().c_str(), - currentTestResult.server_name().c_str()); + currentTestResult.dl_speed.c_str(), + currentTestResult.ul_speed.c_str(), + currentTestResult.server_country.c_str(), + currentTestResult.server_name.c_str()); } ui->data_view->setHtml(html); lastUpdated = QDateTime::currentDateTime(); diff --git a/src/ui/mainwindow_grpc.cpp b/src/ui/mainwindow_grpc.cpp index ea441b1..e999b58 100644 --- a/src/ui/mainwindow_grpc.cpp +++ b/src/ui/mainwindow_grpc.cpp @@ -37,12 +37,12 @@ void MainWindow::runURLTest(const QString& config, bool useDefault, const QStrin libcore::TestReq req; for (const auto &item: outboundTags) { - req.add_outbound_tags(item.toStdString()); + req.outbound_tags.push_back(item.toStdString()); } - req.set_config(config.toStdString()); - req.set_url(NekoGui::dataStore->test_latency_url.toStdString()); - req.set_use_default_outbound(useDefault); - req.set_max_concurrency(NekoGui::dataStore->test_concurrent); + req.config = config.toStdString(); + req.url = NekoGui::dataStore->test_latency_url.toStdString(); + req.use_default_outbound = useDefault; + req.max_concurrency = NekoGui::dataStore->test_concurrent; auto done = new QMutex; done->lock(); @@ -54,17 +54,17 @@ void MainWindow::runURLTest(const QString& config, bool useDefault, const QStrin QThread::msleep(1500); if (done->try_lock()) break; auto resp = defaultClient->QueryURLTest(&ok); - if (!ok || resp.results().empty()) + if (!ok || resp.results.empty()) { continue; } bool needRefresh = false; - for (const auto& res : resp.results()) + for (const auto& res : resp.results) { int entid = -1; if (!tag2entID.empty()) { - entid = tag2entID.count(QString(res.outbound_tag().c_str())) == 0 ? -1 : tag2entID[QString(res.outbound_tag().c_str())]; + entid = tag2entID.count(QString(res.outbound_tag.c_str())) == 0 ? -1 : tag2entID[QString(res.outbound_tag.c_str())]; } if (entid == -1) { continue; @@ -73,14 +73,14 @@ void MainWindow::runURLTest(const QString& config, bool useDefault, const QStrin if (ent == nullptr) { continue; } - if (res.error().empty()) { - ent->latency = res.latency_ms(); + if (res.error.empty()) { + ent->latency = res.latency_ms; } else { - if (QString(res.error().c_str()).contains("test aborted") || - QString(res.error().c_str()).contains("context canceled")) ent->latency=0; + if (QString(res.error.c_str()).contains("test aborted") || + QString(res.error.c_str()).contains("context canceled")) ent->latency=0; else { ent->latency = -1; - MW_show_log(tr("[%1] test error: %2").arg(ent->bean->DisplayTypeAndName(), res.error().c_str())); + MW_show_log(tr("[%1] test error: %2").arg(ent->bean->DisplayTypeAndName(), res.error.c_str())); } } ent->Save(); @@ -102,9 +102,9 @@ void MainWindow::runURLTest(const QString& config, bool useDefault, const QStrin // if (!rpcOK) return; - for (const auto &res: result.results()) { + for (const auto &res: result.results) { if (!tag2entID.empty()) { - entID = tag2entID.count(QString(res.outbound_tag().c_str())) == 0 ? -1 : tag2entID[QString(res.outbound_tag().c_str())]; + entID = tag2entID.count(QString(res.outbound_tag.c_str())) == 0 ? -1 : tag2entID[QString(res.outbound_tag.c_str())]; } if (entID == -1) { MW_show_log(tr("Something is very wrong, the subject ent cannot be found!")); @@ -117,14 +117,14 @@ void MainWindow::runURLTest(const QString& config, bool useDefault, const QStrin continue; } - if (res.error().empty()) { - ent->latency = res.latency_ms(); + if (res.error.empty()) { + ent->latency = res.latency_ms; } else { - if (QString(res.error().c_str()).contains("test aborted") || - QString(res.error().c_str()).contains("context canceled")) ent->latency=0; + if (QString(res.error.c_str()).contains("test aborted") || + QString(res.error.c_str()).contains("context canceled")) ent->latency=0; else { ent->latency = -1; - MW_show_log(tr("[%1] test error: %2").arg(ent->bean->DisplayTypeAndName(), res.error().c_str())); + MW_show_log(tr("[%1] test error: %2").arg(ent->bean->DisplayTypeAndName(), res.error.c_str())); } } ent->Save(); @@ -200,19 +200,19 @@ void MainWindow::url_test_current() { runOnNewThread([=] { libcore::TestReq req; - req.set_test_current(true); - req.set_url(NekoGui::dataStore->test_latency_url.toStdString()); + req.test_current = true; + req.url = NekoGui::dataStore->test_latency_url.toStdString(); bool rpcOK; auto result = defaultClient->Test(&rpcOK, req); if (!rpcOK) return; - auto latency = result.results()[0].latency_ms(); + auto latency = result.results[0].latency_ms; last_test_time = QTime::currentTime(); runOnUiThread([=] { - if (!result.results()[0].error().empty()) { - MW_show_log(QString("UrlTest error: %1").arg(result.results()[0].error().c_str())); + if (!result.results[0].error.empty()) { + MW_show_log(QString("UrlTest error: %1").arg(result.results[0].error.c_str())); } if (latency <= 0) { ui->label_running->setText(tr("Test Result") + ": " + tr("Unavailable")); @@ -276,15 +276,15 @@ void MainWindow::runSpeedTest(const QString& config, bool useDefault, bool testC libcore::SpeedTestRequest req; auto speedtestConf = NekoGui::dataStore->speed_test_mode; for (const auto &item: outboundTags) { - req.add_outbound_tags(item.toStdString()); + req.outbound_tags.push_back(item.toStdString()); } - req.set_config(config.toStdString()); - req.set_use_default_outbound(useDefault); - req.set_test_download(speedtestConf == NekoGui::TestConfig::FULL || speedtestConf == NekoGui::TestConfig::DL); - req.set_test_upload(speedtestConf == NekoGui::TestConfig::FULL || speedtestConf == NekoGui::TestConfig::UL); - req.set_simple_download(speedtestConf == NekoGui::TestConfig::SIMPLEDL); - req.set_simple_download_addr(NekoGui::dataStore->simple_dl_url.toStdString()); - req.set_test_current(testCurrent); + req.config = config.toStdString(); + req.use_default_outbound = useDefault; + req.test_download = speedtestConf == NekoGui::TestConfig::FULL || speedtestConf == NekoGui::TestConfig::DL; + req.test_upload = speedtestConf == NekoGui::TestConfig::FULL || speedtestConf == NekoGui::TestConfig::UL; + req.simple_download = speedtestConf == NekoGui::TestConfig::SIMPLEDL; + req.simple_download_addr = NekoGui::dataStore->simple_dl_url.toStdString(); + req.test_current = testCurrent; // loop query result auto doneMu = new QMutex; @@ -300,11 +300,11 @@ void MainWindow::runSpeedTest(const QString& config, bool useDefault, bool testC break; } auto res = defaultClient->QueryCurrentSpeedTests(&ok); - if (!ok || !res.is_running()) + if (!ok || !res.is_running) { continue; } - auto profile = testCurrent ? running : NekoGui::profileManager->GetProfile(tag2entID[res.result().outbound_tag().c_str()]); + auto profile = testCurrent ? running : NekoGui::profileManager->GetProfile(tag2entID[res.result.outbound_tag.c_str()]); if (profile == nullptr) { continue; @@ -313,14 +313,14 @@ void MainWindow::runSpeedTest(const QString& config, bool useDefault, bool testC { showSpeedtestData = true; currentSptProfileName = profile->bean->name; - currentTestResult = res.result(); + currentTestResult = res.result; UpdateDataView(); - if (res.result().error().empty() && !res.result().cancelled() && lastProxyListUpdate.msecsTo(QDateTime::currentDateTime()) >= 500) + if (res.result.error.empty() && !res.result.cancelled && lastProxyListUpdate.msecsTo(QDateTime::currentDateTime()) >= 500) { - if (!res.result().dl_speed().empty()) profile->dl_speed = res.result().dl_speed().c_str(); - if (!res.result().ul_speed().empty()) profile->ul_speed = res.result().ul_speed().c_str(); - if (profile->latency <= 0 && res.result().latency() > 0) profile->latency = res.result().latency(); + if (!res.result.dl_speed.empty()) profile->dl_speed = res.result.dl_speed.c_str(); + if (!res.result.ul_speed.empty()) profile->ul_speed = res.result.ul_speed.c_str(); + if (profile->latency <= 0 && res.result.latency > 0) profile->latency = res.result.latency; refresh_proxy_list(profile->id); lastProxyListUpdate = QDateTime::currentDateTime(); } @@ -340,10 +340,10 @@ void MainWindow::runSpeedTest(const QString& config, bool useDefault, bool testC // if (!rpcOK) return; - for (const auto &res: result.results()) { + for (const auto &res: result.results) { if (testCurrent) entID = running ? running->id : -1; else { - entID = tag2entID.count(QString(res.outbound_tag().c_str())) == 0 ? -1 : tag2entID[QString(res.outbound_tag().c_str())]; + entID = tag2entID.count(QString(res.outbound_tag.c_str())) == 0 ? -1 : tag2entID[QString(res.outbound_tag.c_str())]; } if (entID == -1) { MW_show_log(tr("Something is very wrong, the subject ent cannot be found!")); @@ -356,17 +356,17 @@ void MainWindow::runSpeedTest(const QString& config, bool useDefault, bool testC continue; } - if (res.cancelled()) continue; + if (res.cancelled) continue; - if (res.error().empty()) { - ent->dl_speed = res.dl_speed().c_str(); - ent->ul_speed = res.ul_speed().c_str(); - if (ent->latency <= 0 && res.latency() > 0) ent->latency = res.latency(); + if (res.error.empty()) { + ent->dl_speed = res.dl_speed.c_str(); + ent->ul_speed = res.ul_speed.c_str(); + if (ent->latency <= 0 && res.latency > 0) ent->latency = res.latency; } else { ent->dl_speed = "N/A"; ent->ul_speed = "N/A"; ent->latency = -1; - MW_show_log(tr("[%1] speed test error: %2").arg(ent->bean->DisplayTypeAndName(), res.error().c_str())); + MW_show_log(tr("[%1] speed test error: %2").arg(ent->bean->DisplayTypeAndName(), res.error.c_str())); } ent->Save(); } @@ -428,16 +428,16 @@ void MainWindow::neko_start(int _id) { auto neko_start_stage2 = [=] { libcore::LoadConfigReq req; - req.set_core_config(QJsonObject2QString(result->coreConfig, true).toStdString()); - req.set_disable_stats(NekoGui::dataStore->disable_traffic_stats); + req.core_config = QJsonObject2QString(result->coreConfig, true).toStdString(); + req.disable_stats = NekoGui::dataStore->disable_traffic_stats; if (ent->type == "extracore") { - req.set_need_extra_process(true); - req.set_extra_process_path(result->extraCoreData->path.toStdString()); - req.set_extra_process_args(result->extraCoreData->args.toStdString()); - req.set_extra_process_conf(result->extraCoreData->config.toStdString()); - req.set_extra_process_conf_dir(result->extraCoreData->configDir.toStdString()); - req.set_extra_no_out(result->extraCoreData->noLog); + req.need_extra_process = true; + req.extra_process_path = result->extraCoreData->path.toStdString(); + req.extra_process_args = result->extraCoreData->args.toStdString(); + req.extra_process_conf = result->extraCoreData->config.toStdString(); + req.extra_process_conf_dir = result->extraCoreData->configDir.toStdString(); + req.extra_no_out = result->extraCoreData->noLog; } // bool rpcOK; diff --git a/src/ui/setting/dialog_basic_settings.cpp b/src/ui/setting/dialog_basic_settings.cpp index 0ff5e1e..1f5803f 100644 --- a/src/ui/setting/dialog_basic_settings.cpp +++ b/src/ui/setting/dialog_basic_settings.cpp @@ -145,7 +145,7 @@ DialogBasicSettings::DialogBasicSettings(QWidget *parent) ui->ntp_server->setText(NekoGui::dataStore->ntp_server_address); ui->ntp_port->setText(Int2String(NekoGui::dataStore->ntp_server_port)); ui->ntp_interval->setCurrentText(NekoGui::dataStore->ntp_interval); - connect(ui->ntp_enable, &QCheckBox::checkStateChanged, this, [=](const bool &state) { + connect(ui->ntp_enable, &QCheckBox::stateChanged, this, [=](const bool &state) { ui->ntp_server->setEnabled(state); ui->ntp_port->setEnabled(state); ui->ntp_interval->setEnabled(state); diff --git a/src/ui/setting/dialog_manage_routes.cpp b/src/ui/setting/dialog_manage_routes.cpp index 341b134..39d9e34 100644 --- a/src/ui/setting/dialog_manage_routes.cpp +++ b/src/ui/setting/dialog_manage_routes.cpp @@ -86,12 +86,12 @@ DialogManageRoutes::DialogManageRoutes(QWidget *parent) : QDialog(parent), ui(ne ui->remote_dns_strategy->addItems(qsValue); ui->enable_fakeip->setChecked(NekoGui::dataStore->fake_dns); // - connect(ui->use_dns_object, &QCheckBox::checkStateChanged, this, [=](int state) { + connect(ui->use_dns_object, &QCheckBox::stateChanged, this, [=](int state) { auto useDNSObject = state == Qt::Checked; ui->simple_dns_box->setDisabled(useDNSObject); ui->dns_object->setDisabled(!useDNSObject); }); - ui->use_dns_object->checkStateChanged(Qt::Unchecked); // uncheck to uncheck + ui->use_dns_object->stateChanged(Qt::Unchecked); // uncheck to uncheck connect(ui->dns_document, &QPushButton::clicked, this, [=] { MessageBoxInfo("DNS", dnsHelpDocumentUrl); }); @@ -165,10 +165,10 @@ DialogManageRoutes::DialogManageRoutes(QWidget *parent) : QDialog(parent), ui(ne ui->redirect_listenport->setValidator(QRegExpValidator_Number); ui->redirect_listenport->setText(Int2String(NekoGui::dataStore->redirect_listen_port)); - connect(ui->dnshijack_enable, &QCheckBox::checkStateChanged, this, [=](bool state) { + connect(ui->dnshijack_enable, &QCheckBox::stateChanged, this, [=](bool state) { set_dns_hijack_enability(state); }); - connect(ui->redirect_enable, &QCheckBox::checkStateChanged, this, [=](bool state) { + connect(ui->redirect_enable, &QCheckBox::stateChanged, this, [=](bool state) { ui->redirect_listenaddr->setEnabled(state); ui->redirect_listenport->setEnabled(state); });