diff --git a/.github/actions/cmake-build/action.yml b/.github/actions/cmake-build/action.yml deleted file mode 100644 index 4b54027..0000000 --- a/.github/actions/cmake-build/action.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: Run CMake -description: "Runs CMake" - -inputs: - args: - description: "Other arguments" - required: false - default: -D_7BIT_CONF_BUILD_TESTS:BOOL=true -D_7BIT_CONF_BUILD_EXAMPLES:BOOL=true - library-type: - description: "Library type" - required: false - default: Static - -runs: - using: composite - steps: - - name: Set Proper Conan Profile - uses: kanga333/variable-mapper@v0.3.0 - with: - key: "${{ runner.os }}" - map: | - { - "Windows":{ - "initPreset": "conan-default" - }, - ".*":{ - "initPreset": "conan-release" - } - } - - - name: Configure CMake - run: cmake --preset ${{ env.initPreset }} -D_7BIT_CONF_LIBRARY_TYPE=${{ inputs.library-type }} ${{ inputs.args }} - shell: pwsh - - - name: Build - run: cmake --build --preset conan-release - shell: pwsh diff --git a/.github/actions/conan-install/action.yml b/.github/actions/conan-install/action.yml deleted file mode 100644 index 073c398..0000000 --- a/.github/actions/conan-install/action.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: Run CMake -description: "Runs CMake" -inputs: - install-dir: - description: "Installation directory" - required: true - -runs: - using: composite - steps: - - name: Install Conan - uses: turtlebrowser/get-conan@v1.2 - - - name: Set Proper Conan Profile - uses: kanga333/variable-mapper@v0.3.0 - with: - key: "${{ runner.os }}" - map: | - { - "Linux":{ - "use_conan_profile":"linux-gcc-x86_64" - }, - "macOS":{ - "use_conan_profile":"mac-appple-clang-x86_64" - }, - "Windows":{ - "use_conan_profile":"windows-msvc-x86_64" - } - } - - name: Configure Conan - run: conan config install https://github.com/7bitcoder/conan-config.git - shell: pwsh - - - name: Install Conan Packages in ${{ inputs.install-dir }} - run: conan install . --output-folder=${{ inputs.install-dir }} --build=missing -pr=${{ env.use_conan_profile }} -pr:b=${{ env.use_conan_profile }} - shell: pwsh diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml deleted file mode 100644 index f33a56e..0000000 --- a/.github/workflows/CI.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: CI - -on: - push: - branches: [ "dev", "main" ] - paths-ignore: - - ".readthedocs.yaml" - - "README.md" - -env: - BUILD_TYPE: Release - BUILD_DIR: ${{github.workspace}}/build - -jobs: - test: - strategy: - fail-fast: false - matrix: - libraryType: [ HeaderOnly, Static, Shared ] - os: [ ubuntu-22.04, macos-12, windows-2022 ] - - runs-on: ${{matrix.os}} - - steps: - - uses: actions/checkout@v3 - - - name: Install Conan Packages - id: conan - uses: ./.github/actions/conan-install - with: - install-dir: ${{ env.BUILD_DIR }} - - - name: CMake Build - uses: ./.github/actions/cmake-build - with: - library-type: ${{ matrix.libraryType }} - - - name: Test - run: ctest -j 10 --preset conan-release diff --git a/.github/workflows/DevCI.yml b/.github/workflows/DevCI.yml new file mode 100644 index 0000000..4e2b0e6 --- /dev/null +++ b/.github/workflows/DevCI.yml @@ -0,0 +1,38 @@ +name: DevCI + +on: + push: + branches: [ "dev" ] + paths-ignore: + - ".readthedocs.yaml" + - "README.md" + +jobs: + test: + strategy: + fail-fast: false + matrix: + os: [ ubuntu-22.04, macos-12, windows-2022 ] + library_type: [ HeaderOnly, Static, Shared ] + std: [ 17, 20 ] + build_type: [ Release ] + + runs-on: ${{matrix.os}} + + steps: + - uses: actions/checkout@v4 + + - name: Create Build Environment + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure + run: cmake -B ${{runner.workspace}}/build -DCMAKE_BUILD_TYPE=${{matrix.build_type}} -DCMAKE_CXX_STANDARD=${{matrix.std}} -D_7BIT_CONF_LIBRARY_TYPE=${{matrix.library_type}} -D_7BIT_CONF_BUILD_ALL_TESTS=ON -D_7BIT_CONF_BUILD_EXAMPLES=ON + + - name: Build + run: cmake --build ${{runner.workspace}}/build --config ${{matrix.build_type}} -j + + - name: Test + working-directory: ${{runner.workspace}}/build + run: ctest -C ${{matrix.build_type}} + env: + CTEST_OUTPUT_ON_FAILURE: True diff --git a/.github/workflows/Linux.yml b/.github/workflows/Linux.yml new file mode 100644 index 0000000..b558ab7 --- /dev/null +++ b/.github/workflows/Linux.yml @@ -0,0 +1,80 @@ +name: Linux + +on: + pull_request: + branches: [ "main" ] + paths-ignore: + - ".readthedocs.yaml" + - "README.md" + +jobs: + test: + strategy: + fail-fast: false + matrix: + compiler: + - { tool: gcc, ver: 8 } + - { tool: gcc, ver: 9 } + - { tool: gcc, ver: 10 } + - { tool: gcc, ver: 11 } + - { tool: gcc, ver: 12 } + - { tool: gcc, ver: 13 } + - { tool: clang, ver: 8 } + - { tool: clang, ver: 9 } + - { tool: clang, ver: 10 } + - { tool: clang, ver: 11 } + - { tool: clang, ver: 12 } + - { tool: clang, ver: 13 } + - { tool: clang, ver: 14 } + - { tool: clang, ver: 15 } + build_type: [ Release ] + os: [ ubuntu-20.04, ubuntu-22.04 ] + std: [ 17 ] + library_type: [ Static ] + include: + - compiler: { tool: gcc } + cxx: g++ + cc: gcc + generator: Ninja + - compiler: { tool: clang } + cxx: clang++ + cc: clang + generator: Ninja + exclude: + - { os: ubuntu-20.04, compiler: { tool: gcc, ver: 12 } } + - { os: ubuntu-20.04, compiler: { tool: gcc, ver: 13 } } + - { os: ubuntu-20.04, compiler: { tool: clang, ver: 13 } } + - { os: ubuntu-20.04, compiler: { tool: clang, ver: 14 } } + - { os: ubuntu-20.04, compiler: { tool: clang, ver: 15 } } + - { os: ubuntu-22.04, compiler: { tool: gcc, ver: 8 } } + - { os: ubuntu-22.04, compiler: { tool: clang, ver: 8 } } + - { os: ubuntu-22.04, compiler: { tool: clang, ver: 9 } } + - { os: ubuntu-22.04, compiler: { tool: clang, ver: 10 } } + + runs-on: ${{matrix.os}} + steps: + - uses: actions/checkout@v4 + + - name: Create Build Environment + env: + PACKAGES: ${{ matrix.compiler.tool == 'gcc' && format('gcc-{0} g++-{0}', matrix.compiler.ver) || format('{0}-{1}', matrix.compiler.tool, matrix.compiler.ver) }} + run: | + sudo apt update + sudo apt install ${{env.PACKAGES}} ninja-build -y + sudo apt install locales-all + cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure + env: + CXX: ${{matrix.cxx}}-${{matrix.compiler.ver}} + CC: ${{matrix.cc}}-${{matrix.compiler.ver}} + run: cmake -B ${{runner.workspace}}/build -G "${{matrix.generator}}" -DCMAKE_BUILD_TYPE=${{matrix.build_type}} -DCMAKE_CXX_STANDARD=${{matrix.std}} -D_7BIT_CONF_LIBRARY_TYPE=${{matrix.library_type}} -D_7BIT_CONF_BUILD_ALL_TESTS=ON + + - name: Build + run: cmake --build ${{runner.workspace}}/build --config ${{matrix.build_type}} -j + + - name: Test + working-directory: ${{runner.workspace}}/build + run: ctest -C ${{matrix.build_type}} + env: + CTEST_OUTPUT_ON_FAILURE: True diff --git a/.github/workflows/MacOs.yml b/.github/workflows/MacOs.yml new file mode 100644 index 0000000..a23a0e6 --- /dev/null +++ b/.github/workflows/MacOs.yml @@ -0,0 +1,62 @@ +name: MacOs + +on: + pull_request: + branches: [ "main" ] + paths-ignore: + - ".readthedocs.yaml" + - "README.md" + +jobs: + test: + strategy: + fail-fast: false + matrix: + compiler: + [ + { tool: apple-clang }, + { tool: gcc, ver: 10 }, + { tool: gcc, ver: 11 }, + { tool: gcc, ver: 12 }, + { tool: gcc, ver: 13 }, + ] + build_type: [ Release ] + os: [ macos-12, macos-13 ] + std: [ 17 ] + library_type: [ Static ] + include: + - compiler: { tool: gcc } + cxx: g++ + cc: gcc + - compiler: { tool: apple-clang } + cxx: "" + cc: "" + exclude: + - { os: macos-13, compiler: { tool: gcc } } + + runs-on: ${{matrix.os}} + steps: + - uses: actions/checkout@v4 + + - name: Create Build Environment + if: matrix.compiler.tool != 'apple-clang' + run: | + brew update && + brew install ${{matrix.compiler.tool}}@${{matrix.compiler.ver}} ninja + cmake -E make_directory ${{runner.workspace}}/build + echo "CXX=${{matrix.cxx}}-${{matrix.compiler.ver}}" >> $GITHUB_ENV + echo "CC=${{matrix.cc}}-${{matrix.compiler.ver}}" >> $GITHUB_ENV + + - name: Configure + env: + MACOSX_DEPLOYMENT_TARGET: 11 + run: cmake -B ${{runner.workspace}}/build -DCMAKE_BUILD_TYPE=${{matrix.build_type}} -DCMAKE_CXX_STANDARD=${{matrix.std}} -D_7BIT_CONF_LIBRARY_TYPE=${{matrix.library_type}} -D_7BIT_CONF_BUILD_ALL_TESTS=ON + + - name: Build + run: cmake --build ${{runner.workspace}}/build --config ${{matrix.build_type}} -j + + - name: Test + working-directory: ${{runner.workspace}}/build + run: ctest -C ${{matrix.build_type}} + env: + CTEST_OUTPUT_ON_FAILURE: True diff --git a/.github/workflows/Windows.yml b/.github/workflows/Windows.yml new file mode 100644 index 0000000..f92d04f --- /dev/null +++ b/.github/workflows/Windows.yml @@ -0,0 +1,68 @@ +name: Windows + +on: + pull_request: + branches: [ "main" ] + paths-ignore: + - ".readthedocs.yaml" + - "README.md" + +jobs: + test: + strategy: + fail-fast: false + matrix: + compiler: + [ + { tool: msvc, ver: 141 }, + { tool: msvc, ver: 142 }, + { tool: msvc, ver: 143 }, + { tool: LLVM, ver: 11.1.0 }, + { tool: LLVM, ver: 12.0.1 }, + { tool: LLVM, ver: 13.0.1 }, + { tool: LLVM, ver: 14.0.6 }, + { tool: LLVM, ver: 15.0.7 }, + { tool: LLVM, ver: 16.0.6 }, + { tool: LLVM, ver: 17.0.6 }, + ] + build_type: [ Release ] + os: [ windows-2019, windows-2022 ] + std: [ 17 ] + library_type: [ Static ] + include: + - compiler: { tool: LLVM } + cxx: clang++ + cc: clang + generator: Ninja + - compiler: { tool: msvc } + cxx: "" + cc: "" + generator: "" + exclude: + - { os: windows-2022, compiler: { tool: LLVM } } + - { os: windows-2019, compiler: { tool: msvc, ver: 143 } } + + runs-on: ${{matrix.os}} + steps: + - uses: actions/checkout@v4 + + - name: Create Build Environment + if: matrix.compiler.tool != 'msvc' + shell: bash + run: choco install ${{matrix.compiler.tool}} --version ${{matrix.compiler.ver}} --allow-downgrade -y && choco install ninja && cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure + env: + CXX: ${{matrix.cxx}} + CC: ${{matrix.cc}} + PARAMETERS: ${{ matrix.compiler.tool == 'msvc' && format('-A x64 -T v{0}', matrix.compiler.ver) || format('-G "{0}"', matrix.generator) }} + run: cmake -B ${{runner.workspace}}/build ${{env.PARAMETERS}} -DCMAKE_BUILD_TYPE=${{matrix.build_type}} -DCMAKE_CXX_STANDARD=${{matrix.std}} -D_7BIT_CONF_LIBRARY_TYPE=${{matrix.library_type}} -D_7BIT_CONF_BUILD_ALL_TESTS=ON + + - name: Build + run: cmake --build ${{runner.workspace}}/build --config ${{matrix.build_type}} -j + + - name: Test + working-directory: ${{runner.workspace}}/build + run: ctest -C ${{matrix.build_type}} + env: + CTEST_OUTPUT_ON_FAILURE: True diff --git a/CMakeLists.txt b/CMakeLists.txt index 490a760..24c5ad8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,27 +1,32 @@ cmake_minimum_required(VERSION 3.15.0) +set(_7BIT_CONF_LIBRARY 7bitConf) + set(_7BIT_CONF_VERSION_MAJOR 1) -set(_7BIT_CONF_VERSION_MINOR 1) +set(_7BIT_CONF_VERSION_MINOR 2) set(_7BIT_CONF_VERSION_PATCH 0) set(_7BIT_CONF_VERSION ${_7BIT_CONF_VERSION_MAJOR}.${_7BIT_CONF_VERSION_MINOR}.${_7BIT_CONF_VERSION_PATCH}) -project(7bitConf LANGUAGES CXX VERSION ${_7BIT_CONF_VERSION}) +project(${_7BIT_CONF_LIBRARY} LANGUAGES CXX VERSION ${_7BIT_CONF_VERSION}) -list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/Cmake") +list(PREPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/Cmake") -include(Setup) -include(GNUInstallDirs) +if (NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 17) +endif () -set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) -include_directories(${CMAKE_SOURCE_DIR}/Include) +include(Setup) +include(CMakeDependentOption) +include(GNUInstallDirs) + +include_directories(${_7BIT_CONF_INCLUDE_DIR}) add_subdirectory(Source) -if (_7BIT_CONF_BUILD_TESTS) - include(GoogleTest) +if (_7BIT_CONF_BUILD_UNIT_TESTS OR _7BIT_CONF_BUILD_INTEGRATION_TESTS OR _7BIT_CONF_BUILD_E2E_TESTS) enable_testing() add_subdirectory(Tests) @@ -36,13 +41,13 @@ if (_7BIT_CONF_BUILD_SINGLE_HEADER) endif () if (_7BIT_CONF_INSTALL) - set(PROJECT_CONFIG_IN ${CMAKE_SOURCE_DIR}/Cmake/7bitConfConfig.cmake.in) - set(PROJECT_CONFIG_OUT ${CMAKE_BINARY_DIR}/7bitConfConfig.cmake) + set(PROJECT_CONFIG_IN ${CMAKE_CURRENT_SOURCE_DIR}/Cmake/7bitConfConfig.cmake.in) + set(PROJECT_CONFIG_OUT ${CMAKE_CURRENT_BINARY_DIR}/7bitConfConfig.cmake) set(CONFIG_TARGETS_FILE 7bitConfConfigTargets.cmake) set(VERSIONS_CONFIG_FILE ${CMAKE_CURRENT_BINARY_DIR}/7bitConfConfigVersion.cmake) set(EXPORT_DEST_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/7bitConf) - install(DIRECTORY ${_7BIT_CONF_HEADERS_DIR}/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + install(DIRECTORY ${_7BIT_CONF_INCLUDE_DIR}/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) install( TARGETS 7bitConf diff --git a/Cmake/Setup.cmake b/Cmake/Setup.cmake index c7e3dc2..b3f443a 100644 --- a/Cmake/Setup.cmake +++ b/Cmake/Setup.cmake @@ -22,21 +22,27 @@ set(CPACK_PACKAGE_VERSION_MAJOR ${_7BIT_CONF_VERSION_MAJOR}) set(CPACK_PACKAGE_VERSION_MINOR ${_7BIT_CONF_VERSION_MINOR}) set(CPACK_PACKAGE_VERSION_PATCH ${_7BIT_CONF_VERSION_PATCH}) set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "7bitInjector is a simple C++ dependency injection library") -set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/LICENSE") -set(CPACK_RESOURCE_FILE_README "${CMAKE_SOURCE_DIR}/README.md") +set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") +set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md") set(CPACK_SOURCE_GENERATOR "TGZ;ZIP") -set(_7BIT_CONF_HEADERS_DIR "${CMAKE_SOURCE_DIR}/Include") +set(_7BIT_CONF_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/Include") +set(_7BIT_CONF_CONF_DIR "${_7BIT_CONF_INCLUDE_DIR}/SevenBit/Conf") +set(_7BIT_CONF_SOURCES_DIR "${_7BIT_CONF_CONF_DIR}/Sources") +set(_7BIT_CONF_DETAILS_DIR "${_7BIT_CONF_CONF_DIR}/Details") -set(_7BIT_CONF_MAIN_HEADER "${_7BIT_CONF_HEADERS_DIR}/SevenBit/Conf.hpp") -file(GLOB _7BIT_CONF_TOP_HEADERS "${_7BIT_CONF_HEADERS_DIR}/SevenBit/Conf/*.hpp") -file(GLOB _7BIT_CONF_DETAILS_HEADERS "${_7BIT_CONF_HEADERS_DIR}/SevenBit/Conf/Details/*.hpp") -file(GLOB _7BIT_CONF_IMPL_HEADERS "${_7BIT_CONF_HEADERS_DIR}/SevenBit/Conf/Impl/*.hpp") -set(_7BIT_CONF_ALL_HEADERS ${_7BIT_CONF_MAIN_HEADER} ${_7BIT_CONF_TOP_HEADERS} ${_7BIT_CONF_DETAILS_HEADERS} ${_7BIT_CONF_IMPL_HEADERS}) - -source_group("Header Files\\SevenBit" FILES ${_7BIT_CONF_TOP_HEADERS}) -source_group("Header Files\\SevenBit\\Details" FILES ${_7BIT_CONF_DETAILS_HEADERS}) -source_group("Header Files\\SevenBit\\Details\\Impl" FILES ${_7BIT_CONF_IMPL_HEADERS}) +set(_7BIT_CONF_MAIN_HEADER "${_7BIT_CONF_INCLUDE_DIR}/SevenBit/Conf.hpp") +file(GLOB _7BIT_CONF_PUBLIC_HEADERS + "${_7BIT_CONF_CONF_DIR}/*.hpp" + "${_7BIT_CONF_SOURCES_DIR}/*.hpp" +) +file(GLOB _7BIT_CONF_DETAILS_HEADERS "${_7BIT_CONF_DETAILS_DIR}/*.hpp") +file(GLOB _7BIT_CONF_IMPL_HEADERS + "${_7BIT_CONF_CONF_DIR}/Impl/*.hpp" + "${_7BIT_CONF_SOURCES_DIR}/Impl/*.hpp" + "${_7BIT_CONF_DETAILS_DIR}/Impl/*.hpp" +) +set(_7BIT_CONF_ALL_HEADERS ${_7BIT_CONF_MAIN_HEADER} ${_7BIT_CONF_PUBLIC_HEADERS} ${_7BIT_CONF_DETAILS_HEADERS} ${_7BIT_CONF_IMPL_HEADERS}) set(_7BIT_CONF_LIBRARY_TYPE "Static" CACHE STRING "Library build type: Shared;Static;HeaderOnly") set(_7BIT_CONF_LIBRARY_TYPE_VALUES "Shared;Static;HeaderOnly" CACHE STRING "List of possible _7BIT_CONF_LIBRARY_TYPE values") @@ -45,10 +51,19 @@ set_property(CACHE _7BIT_CONF_LIBRARY_TYPE PROPERTY STRINGS Shared Static Header option(_7BIT_CONF_BUILD_PIC "Build position independent code (-fPIC)" OFF) option(_7BIT_CONF_BUILD_EXAMPLES "Build example" OFF) -option(_7BIT_CONF_BUILD_TESTS "Build tests" OFF) +option(_7BIT_CONF_BUILD_ALL_TESTS "Build all tests" OFF) +option(_7BIT_CONF_BUILD_UNIT_TESTS "Build unit tests" OFF) +option(_7BIT_CONF_BUILD_INTEGRATION_TESTS "Build integration tests" OFF) +option(_7BIT_CONF_BUILD_E2E_TESTS "Build e2e tests" OFF) option(_7BIT_CONF_INSTALL "Installs 7bitConf" OFF) option(_7BIT_CONF_BUILD_SINGLE_HEADER "Builds single header SevenBitConf.hpp" OFF) +if (_7BIT_CONF_BUILD_ALL_TESTS) + set(_7BIT_CONF_BUILD_UNIT_TESTS ${_7BIT_CONF_BUILD_ALL_TESTS}) + set(_7BIT_CONF_BUILD_INTEGRATION_TESTS ${_7BIT_CONF_BUILD_ALL_TESTS}) + set(_7BIT_CONF_BUILD_E2E_TESTS ${_7BIT_CONF_BUILD_ALL_TESTS}) +endif () + if (_7BIT_CONF_BUILD_PIC) set(CMAKE_POSITION_INDEPENDENT_CODE ON) endif () @@ -64,14 +79,26 @@ else () set(_7BIT_CONF_STATIC_LIB true) endif () -configure_file(${CMAKE_SOURCE_DIR}/Include/SevenBit/Conf/CmakeDef.hpp.input ${CMAKE_SOURCE_DIR}/Include/SevenBit/Conf/CmakeDef.hpp) +configure_file(${_7BIT_CONF_CONF_DIR}/CmakeDef.hpp.input ${_7BIT_CONF_CONF_DIR}/CmakeDef.hpp) + +set(BYTE_SIZE 8) +math(EXPR MEMORY_SIZE "${CMAKE_SIZEOF_VOID_P} * ${BYTE_SIZE}") set(INFOS - "${CMAKE_PROJECT_NAME} version: ${_7BIT_CONF_VERSION}" - "${CMAKE_PROJECT_NAME} build type: ${CMAKE_BUILD_TYPE} " - "${CMAKE_PROJECT_NAME} build as ${_7BIT_CONF_BUILD_LIBRARY_TYPE} library" + "${_7BIT_CONF_LIBRARY} ${_7BIT_CONF_VERSION}" + "Build type: ${CMAKE_BUILD_TYPE}" + "Library type: ${_7BIT_CONF_BUILD_LIBRARY_TYPE}" + "==================================================" + "Cmake version: ${CMAKE_VERSION}" + "Os: ${CMAKE_SYSTEM_NAME} ${CMAKE_SYSTEM_VERSION}" + "Architecture: ${CMAKE_SYSTEM_PROCESSOR} ${MEMORY_SIZE}bit" + "CXX compiler: ${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION}" + "CXX standard: ${CMAKE_CXX_STANDARD}" + "Generator: ${CMAKE_GENERATOR}" "==================================================" - "Build tests: ${_7BIT_CONF_BUILD_TESTS}" + "Build unit tests: ${_7BIT_CONF_BUILD_UNIT_TESTS}" + "Build integration tests: ${_7BIT_CONF_BUILD_INTEGRATION_TESTS}" + "Build e2e tests: ${_7BIT_CONF_BUILD_E2E_TESTS}" "Build examples: ${_7BIT_CONF_BUILD_EXAMPLES}" "Build single header: ${_7BIT_CONF_BUILD_SINGLE_HEADER}" "Install project: ${_7BIT_CONF_INSTALL}" diff --git a/Examples/Basic.cpp b/Examples/Basic.cpp index 930f343..25654aa 100644 --- a/Examples/Basic.cpp +++ b/Examples/Basic.cpp @@ -3,17 +3,17 @@ using namespace sb::cf; -int main(int argc, char **argv) +int main(const int argc, char **argv) { - IConfiguration::Ptr configuration = ConfigurationBuilder{} // - .addAppSettings() - .addEnvironmentVariables() - .addCommandLine(argc, argv) - .build(); + const IConfiguration::Ptr configuration = ConfigurationBuilder{} // + .addAppSettings() + .addEnvironmentVariables() + .addCommandLine(argc, argv) + .build(); - std::string value = configuration->at("MySetting").get_string(); - std::string defaultLogLevel = configuration->deepAt("Logging:LogLevel:Default").get_string(); - std::uint64_t secondArrayElement = configuration->deepAt("Array:1").get_unsigned(); + const std::string value = configuration->at("MySetting").get_string(); + const std::string defaultLogLevel = configuration->deepAt("Logging:LogLevel:Default").get_string(); + const std::uint64_t secondArrayElement = configuration->deepAt("Array:1").get_unsigned(); std::cout << "MySetting: " << value << std::endl; std::cout << "Default LogLevel: " << defaultLogLevel << std::endl; diff --git a/Examples/CommandLineParserConfig.cpp b/Examples/CommandLineParserConfig.cpp new file mode 100644 index 0000000..7ae4a1c --- /dev/null +++ b/Examples/CommandLineParserConfig.cpp @@ -0,0 +1,20 @@ +#include +#include + +using namespace sb::cf; + +int main(const int argc, char **argv) +{ + CommandLineParserConfig parserConfig; + parserConfig.optionPrefixes = {"//"}; + parserConfig.typeMarkers.clear(); + + const IConfiguration::Ptr configuration = ConfigurationBuilder{} // + .addAppSettings() + .addCommandLine(argc, argv, std::move(parserConfig)) + .build(); + + std::cout << "Configuration json:" << std::endl << std::setw(2) << *configuration; + + return 0; +} diff --git a/Examples/CustomCommandLineParserConfig.cpp b/Examples/CustomCommandLineParserConfig.cpp new file mode 100644 index 0000000..068f2bd --- /dev/null +++ b/Examples/CustomCommandLineParserConfig.cpp @@ -0,0 +1,37 @@ +#include +#include + +using namespace sb::cf; + +struct MyTypeDeserializer final : IDeserializer +{ + [[nodiscard]] JsonValue deserialize(std::optional value) const override + { + return value ? value : "emptyValue"; + } +}; + +int main(const int argc, char **argv) +{ + auto builderFunc = [](CommandLineParserBuilder &builder) { + CommandLineParserConfig parserConfig; + parserConfig.keySplitters.clear(); + parserConfig.optionPrefixes.emplace_back("//"); + parserConfig.defaultType = "myType"; + parserConfig.throwOnUnknownType = false; + + builder.useConfig(std::move(parserConfig)) + .useDefaultValueDeserializers() + .useValueDeserializer("myType", std::make_unique()); + }; + + const IConfiguration::Ptr configuration = ConfigurationBuilder{} // + .addAppSettings() + .addEnvironmentVariables() + .addCommandLine(argc, argv, builderFunc) + .build(); + + std::cout << "Configuration json:" << std::endl << std::setw(2) << *configuration; + + return 0; +} diff --git a/Examples/CustomConfirugationSource.cpp b/Examples/CustomConfirugationSource.cpp index 5d55650..1edf492 100644 --- a/Examples/CustomConfirugationSource.cpp +++ b/Examples/CustomConfirugationSource.cpp @@ -6,15 +6,12 @@ using namespace sb::cf; class CustomConfigurationProvider : public IConfigurationProvider { - private: JsonObject _configuration; public: void load() override { _configuration = {{"mysettingOne", "value1"}, {"mysettingTwo", "value2"}}; } - JsonObject &getConfiguration() override { return _configuration; } - - const JsonObject &getConfiguration() const override { return _configuration; } + [[nodiscard]] const JsonObject &getConfiguration() const override { return _configuration; } }; class CustomConfigurationSource : public IConfigurationSource @@ -28,7 +25,7 @@ class CustomConfigurationSource : public IConfigurationSource int main(int argc, char **argv) { - IConfiguration::Ptr configuration = + const IConfiguration::Ptr configuration = ConfigurationBuilder{}.add(std::make_unique()).build(); std::cout << "Configuration json:" << std::endl << std::setw(2) << *configuration; diff --git a/Examples/CustomSettingParser.cpp b/Examples/CustomSettingParser.cpp deleted file mode 100644 index 9c122e9..0000000 --- a/Examples/CustomSettingParser.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include -#include - -using namespace sb::cf; - -struct MyTypeDeserializer final : IDeserializer -{ - JsonValue deserialize(std::optional value) const final { return value ? value : "emptyValue"; } -}; - -int main(int argc, char **argv) -{ - SettingParserConfig envParserConfig; - envParserConfig.keySplitters.clear(); - envParserConfig.settingPrefixes.emplace_back("//"); - envParserConfig.defaultType = "myType"; - envParserConfig.throwOnUnknownType = false; - - ISettingParser::Ptr settingParser = SettingParserBuilder{} // - .useConfig(std::move(envParserConfig)) - .useDefaultValueDeserializers() - .useValueDeserializer("myType", std::make_unique()) - .build(); - - IConfiguration::Ptr configuration = ConfigurationBuilder{} // - .addAppSettings() - .addEnvironmentVariables() - .addCommandLine(argc, argv, std::move(settingParser)) - .build(); - - std::cout << "Configuration json:" << std::endl << std::setw(2) << *configuration; - - return 0; -} diff --git a/Examples/EnvironmentVarsParserConfig.cpp b/Examples/EnvironmentVarsParserConfig.cpp new file mode 100644 index 0000000..2f7f683 --- /dev/null +++ b/Examples/EnvironmentVarsParserConfig.cpp @@ -0,0 +1,21 @@ +#include +#include + +using namespace sb::cf; + +int main(const int argc, char **argv) +{ + EnvironmentVarsParserConfig parserConfig; + parserConfig.keySplitters.clear(); + parserConfig.typeMarkers.clear(); + + const IConfiguration::Ptr configuration = ConfigurationBuilder{} // + .addAppSettings() + .addEnvironmentVariables("", std::move(parserConfig)) + .addCommandLine(argc, argv) + .build(); + + std::cout << "Configuration json:" << std::endl << std::setw(2) << *configuration; + + return 0; +} diff --git a/Examples/SettingParserConfig.cpp b/Examples/SettingParserConfig.cpp deleted file mode 100644 index 143e45f..0000000 --- a/Examples/SettingParserConfig.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include -#include - -using namespace sb::cf; - -int main(int argc, char **argv) -{ - SettingParserConfig envParserConfig; - envParserConfig.keySplitters.clear(); - envParserConfig.typeMarkers.clear(); - - IConfiguration::Ptr configuration = ConfigurationBuilder{} // - .addAppSettings() - .addEnvironmentVariables("", std::move(envParserConfig)) - .addCommandLine(argc, argv) - .build(); - - std::cout << "Configuration json:" << std::endl << std::setw(2) << *configuration; - - return 0; -} diff --git a/Include/SevenBit/Conf.hpp b/Include/SevenBit/Conf.hpp index 45d0f71..1d5d549 100644 --- a/Include/SevenBit/Conf.hpp +++ b/Include/SevenBit/Conf.hpp @@ -3,6 +3,5 @@ #include "SevenBit/Conf/LibraryConfig.hpp" #include "SevenBit/Conf/ConfigurationBuilder.hpp" -#include "SevenBit/Conf/ConfigurationManager.hpp" #include "SevenBit/Conf/Exceptions.hpp" #include "SevenBit/Conf/ObjectHolder.hpp" diff --git a/Include/SevenBit/Conf/CmakeDef.hpp b/Include/SevenBit/Conf/CmakeDef.hpp index b4115c4..4e8f8d7 100644 --- a/Include/SevenBit/Conf/CmakeDef.hpp +++ b/Include/SevenBit/Conf/CmakeDef.hpp @@ -13,22 +13,5 @@ #endif #define _7BIT_CONF_VERSION_MAJOR 1 -#define _7BIT_CONF_VERSION_MINOR 1 +#define _7BIT_CONF_VERSION_MINOR 2 /* #undef _7BIT_CONF_VERSION_PATCH */ - -#define _7BIT_CONF_VERSION "1.1.0" - -#ifndef _7BIT_CONF_VERSION_MAJOR -#define _7BIT_CONF_VERSION_MAJOR 0 -#endif - -#ifndef _7BIT_CONF_VERSION_MINOR -#define _7BIT_CONF_VERSION_MINOR 0 -#endif - -#ifndef _7BIT_CONF_VERSION_PATCH -#define _7BIT_CONF_VERSION_PATCH 0 -#endif - -#define _7BIT_CONF_VERSION_AS_NUMBER \ - (_7BIT_CONF_VERSION_MAJOR * 10000 + _7BIT_CONF_VERSION_MINOR * 100 + _7BIT_CONF_VERSION_PATCH) diff --git a/Include/SevenBit/Conf/CmakeDef.hpp.input b/Include/SevenBit/Conf/CmakeDef.hpp.input index 0a33fcc..c75c51a 100644 --- a/Include/SevenBit/Conf/CmakeDef.hpp.input +++ b/Include/SevenBit/Conf/CmakeDef.hpp.input @@ -15,20 +15,3 @@ #cmakedefine _7BIT_CONF_VERSION_MAJOR @_7BIT_CONF_VERSION_MAJOR@ #cmakedefine _7BIT_CONF_VERSION_MINOR @_7BIT_CONF_VERSION_MINOR@ #cmakedefine _7BIT_CONF_VERSION_PATCH @_7BIT_CONF_VERSION_PATCH@ - -#cmakedefine _7BIT_CONF_VERSION "@_7BIT_CONF_VERSION@" - -#ifndef _7BIT_CONF_VERSION_MAJOR -#define _7BIT_CONF_VERSION_MAJOR 0 -#endif - -#ifndef _7BIT_CONF_VERSION_MINOR -#define _7BIT_CONF_VERSION_MINOR 0 -#endif - -#ifndef _7BIT_CONF_VERSION_PATCH -#define _7BIT_CONF_VERSION_PATCH 0 -#endif - -#define _7BIT_CONF_VERSION_AS_NUMBER \ - (_7BIT_CONF_VERSION_MAJOR * 10000 + _7BIT_CONF_VERSION_MINOR * 100 + _7BIT_CONF_VERSION_PATCH) diff --git a/Include/SevenBit/Conf/CommandLineParserBuilder.hpp b/Include/SevenBit/Conf/CommandLineParserBuilder.hpp new file mode 100644 index 0000000..df08fbf --- /dev/null +++ b/Include/SevenBit/Conf/CommandLineParserBuilder.hpp @@ -0,0 +1,53 @@ +#pragma once + +#include +#include +#include +#include + +#include "SevenBit/Conf/LibraryConfig.hpp" + +#include "SevenBit/Conf/CommandLineParserConfig.hpp" +#include "SevenBit/Conf/ISettingSplitter.hpp" +#include "SevenBit/Conf/ISettingsParser.hpp" +#include "SevenBit/Conf/IValueDeserializersMap.hpp" + +namespace sb::cf +{ + + class EXPORT CommandLineParserBuilder + { + private: + ISettingSplitter::Ptr _splitter; + IValueDeserializersMap::Ptr _valueDeserializersMap; + std::vector> _valueDeserializers; + std::optional _config; + + public: + CommandLineParserBuilder &useSplitter(ISettingSplitter::Ptr splitter); + + CommandLineParserBuilder &useValueDeserializersMap(IValueDeserializersMap::Ptr valueDeserializersMap); + + CommandLineParserBuilder &useValueDeserializer(std::string_view type, IDeserializer::Ptr valueDeserializer); + + CommandLineParserBuilder &useConfig(CommandLineParserConfig config); + + CommandLineParserBuilder &useDefaultValueDeserializers(); + + ISettingsParser::Ptr build(); + + private: + ISettingSplitter::Ptr getSplitter(); + + IValueDeserializersMap::Ptr getValueDeserializersMap(); + + std::vector> getValueDeserializers(); + + CommandLineParserConfig &getConfig(); + }; + +} // namespace sb::cf + +#ifdef _7BIT_CONF_ADD_IMPL +#include "SevenBit/Conf/Impl/CommandLineParserBuilder.hpp" +#endif diff --git a/Include/SevenBit/Conf/CommandLineParserConfig.hpp b/Include/SevenBit/Conf/CommandLineParserConfig.hpp new file mode 100644 index 0000000..c8cbbd4 --- /dev/null +++ b/Include/SevenBit/Conf/CommandLineParserConfig.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include +#include + +#include "SevenBit/Conf/LibraryConfig.hpp" + +namespace sb::cf +{ + struct CommandLineParserConfig + { + std::vector optionPrefixes = {"--", "/"}; + std::vector optionSplitters = {"=", " "}; + std::vector keySplitters = {":"}; + std::vector typeMarkers = {"!"}; + std::string_view defaultType = "string"; + bool throwOnUnknownType = true; + bool allowEmptyKeys = false; + }; +} // namespace sb::cf diff --git a/Include/SevenBit/Conf/ConfigurationBuilder.hpp b/Include/SevenBit/Conf/ConfigurationBuilder.hpp index 6cbb20e..8a43d6a 100644 --- a/Include/SevenBit/Conf/ConfigurationBuilder.hpp +++ b/Include/SevenBit/Conf/ConfigurationBuilder.hpp @@ -45,5 +45,5 @@ namespace sb::cf } // namespace sb::cf #ifdef _7BIT_CONF_ADD_IMPL -#include "SevenBit/Conf/Details/Impl/ConfigurationBuilder.hpp" +#include "SevenBit/Conf/Impl/ConfigurationBuilder.hpp" #endif diff --git a/Include/SevenBit/Conf/ConfigurationManager.hpp b/Include/SevenBit/Conf/ConfigurationManager.hpp deleted file mode 100644 index b869687..0000000 --- a/Include/SevenBit/Conf/ConfigurationManager.hpp +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -#include "SevenBit/Conf/LibraryConfig.hpp" - -#include "SevenBit/Conf/Configuration.hpp" -#include "SevenBit/Conf/ConfigurationBuilder.hpp" - -namespace sb::cf -{ - class EXPORT ConfigurationManager : public ConfigurationBuilder, public Configuration - { - public: - IConfigurationBuilder &add(IConfigurationSource::SPtr source) override; - }; -} // namespace sb::cf - -#ifdef _7BIT_CONF_ADD_IMPL -#include "SevenBit/Conf/Details/Impl/ConfigurationManager.hpp" -#endif diff --git a/Include/SevenBit/Conf/ConfigurationProviderBase.hpp b/Include/SevenBit/Conf/ConfigurationProviderBase.hpp deleted file mode 100644 index 93ada2b..0000000 --- a/Include/SevenBit/Conf/ConfigurationProviderBase.hpp +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -#include -#include -#include - -#include "SevenBit/Conf/LibraryConfig.hpp" - -#include "SevenBit/Conf/IConfigurationProvider.hpp" - -namespace sb::cf -{ - class EXPORT ConfigurationProviderBase : public IConfigurationProvider - { - protected: - JsonObject _configuration; - - public: - [[nodiscard]] const JsonObject &getConfiguration() const override; - - [[nodiscard]] JsonObject &getConfiguration() override; - - protected: - void clear(); - - void set(JsonObject configuration); - - void update(JsonObject configuration); - - void update(const std::vector &keys, JsonValue value); - }; -} // namespace sb::cf - -#ifdef _7BIT_CONF_ADD_IMPL -#include "SevenBit/Conf/Details/Impl/ConfigurationProviderBase.hpp" -#endif diff --git a/Include/SevenBit/Conf/Details/CommandLineParser.hpp b/Include/SevenBit/Conf/Details/CommandLineParser.hpp new file mode 100644 index 0000000..9d1b28e --- /dev/null +++ b/Include/SevenBit/Conf/Details/CommandLineParser.hpp @@ -0,0 +1,56 @@ +#pragma once + +#include +#include +#include + +#include "SevenBit/Conf/LibraryConfig.hpp" + +#include "SevenBit/Conf/ISettingSplitter.hpp" +#include "SevenBit/Conf/ISettingsParser.hpp" +#include "SevenBit/Conf/IValueDeserializersMap.hpp" + +namespace sb::cf::details +{ + class EXPORT CommandLineParser : public ISettingsParser + { + struct ArgumentParseResult + { + std::vector keys; + JsonValue value; + }; + + const ISettingSplitter::Ptr _optionSplitter; + const IValueDeserializersMap::Ptr _valueDeserializersMap; + const std::vector _optionPrefixes; + const bool _considerSeparated; + + public: + using Ptr = std::unique_ptr; + + CommandLineParser(ISettingSplitter::Ptr optionSplitter, IValueDeserializersMap::Ptr valueDeserializersMap, + std::vector optionPrefixes, bool considerSeparated = true); + + [[nodiscard]] JsonObject parse(const std::vector &arguments) const override; + + [[nodiscard]] const ISettingSplitter &getOptionsSplitter() const; + + [[nodiscard]] const IValueDeserializersMap &getValueDeserializersMap() const; + + [[nodiscard]] const std::vector &getOptionPrefixes() const; + + [[nodiscard]] bool getConsiderSeparated() const; + + private: + [[nodiscard]] ArgumentParseResult parseArgument(const std::vector &arguments, + size_t &index) const; + + bool tryRemoveOptionPrefix(std::string_view &argument) const; + + [[nodiscard]] std::optional tryGetOptionPrefix(std::string_view argument) const; + }; +} // namespace sb::cf::details + +#ifdef _7BIT_CONF_ADD_IMPL +#include "SevenBit/Conf/Details/Impl/CommandLineParser.hpp" +#endif diff --git a/Include/SevenBit/Conf/Configuration.hpp b/Include/SevenBit/Conf/Details/Configuration.hpp similarity index 89% rename from Include/SevenBit/Conf/Configuration.hpp rename to Include/SevenBit/Conf/Details/Configuration.hpp index c992b4a..aa320e4 100644 --- a/Include/SevenBit/Conf/Configuration.hpp +++ b/Include/SevenBit/Conf/Details/Configuration.hpp @@ -11,7 +11,7 @@ #include "SevenBit/Conf/IConfigurationProvider.hpp" #include "SevenBit/Conf/Json.hpp" -namespace sb::cf +namespace sb::cf::details { class EXPORT Configuration : public IConfiguration { @@ -31,13 +31,15 @@ namespace sb::cf Configuration &operator=(Configuration &&) = delete; Configuration &operator=(const Configuration &) = delete; - void reload(); + void load(); + + void buildConfig(bool withLoad = false); [[nodiscard]] const std::vector &getProviders() const; [[nodiscard]] std::vector &getProviders(); - [[nodiscard]] std::string toString(std::size_t indent = 1, std::string newLineMark = "\n") const override; + [[nodiscard]] std::string toString(std::size_t indent, std::string newLineMark) const override; [[nodiscard]] JsonValue &root(); @@ -104,11 +106,11 @@ namespace sb::cf [[nodiscard]] auto crend() const { return rootAsObject().crend(); } private: - [[nodiscard]] JsonValue &throwNotFoundException(const std::vector &key) const; + [[nodiscard]] static JsonValue &throwNotFoundException(const std::vector &key); - [[nodiscard]] JsonValue &throwNotFoundException(std::string_view key) const; + [[nodiscard]] static JsonValue &throwNotFoundException(std::string_view key); }; -} // namespace sb::cf +} // namespace sb::cf::details #ifdef _7BIT_CONF_ADD_IMPL #include "SevenBit/Conf/Details/Impl/Configuration.hpp" diff --git a/Include/SevenBit/Conf/Details/ContainerUtils.hpp b/Include/SevenBit/Conf/Details/ContainerUtils.hpp new file mode 100644 index 0000000..4e63e3a --- /dev/null +++ b/Include/SevenBit/Conf/Details/ContainerUtils.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include +#include + +#include "SevenBit/Conf/LibraryConfig.hpp" + +namespace sb::cf::details +{ + struct ContainerUtils + { + template static TIt removeIf(TIt first, TIt last, TPred &&p) + { + first = std::find_if(first, last, p); + if (first != last) + { + for (auto i = first; ++i != last;) + { + if (!p(*i)) + { + *first++ = std::move(*i); + } + } + } + return first; + } + + template static size_t eraseIf(std::vector &vec, TPred &&p) + { + const auto it = removeIf(vec.begin(), vec.end(), std::forward(p)); + const size_t removedCnt = vec.end() - it; + vec.erase(it, vec.end()); + return removedCnt; + } + }; +} // namespace sb::cf::details diff --git a/Include/SevenBit/Conf/Details/DefaultDeserializers.hpp b/Include/SevenBit/Conf/Details/DefaultDeserializers.hpp new file mode 100644 index 0000000..74b17ef --- /dev/null +++ b/Include/SevenBit/Conf/Details/DefaultDeserializers.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include "SevenBit/Conf/LibraryConfig.hpp" + +#include "SevenBit/Conf/Details/Deserializers.hpp" +#include "SevenBit/Conf/Details/ValueDeserializersMap.hpp" + +namespace sb::cf::details +{ + struct EXPORT DefaultDeserializers + { + static void add(std::vector> &deserializers); + + static void add(ValueDeserializersMap &deserializersMap); + }; +} // namespace sb::cf::details + +#ifdef _7BIT_CONF_ADD_IMPL +#include "SevenBit/Conf/Details/Impl/DefaultDeserializers.hpp" +#endif diff --git a/Include/SevenBit/Conf/Details/EnvironmentVarsParser.hpp b/Include/SevenBit/Conf/Details/EnvironmentVarsParser.hpp new file mode 100644 index 0000000..94b608e --- /dev/null +++ b/Include/SevenBit/Conf/Details/EnvironmentVarsParser.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include +#include + +#include "SevenBit/Conf/LibraryConfig.hpp" + +#include "SevenBit/Conf/ISettingSplitter.hpp" +#include "SevenBit/Conf/ISettingsParser.hpp" +#include "SevenBit/Conf/IValueDeserializersMap.hpp" + +namespace sb::cf::details +{ + class EXPORT EnvironmentVarsParser : public ISettingsParser + { + private: + const ISettingSplitter::Ptr _settingSplitter; + const IValueDeserializersMap::Ptr _valueDeserializersMap; + + public: + using Ptr = std::unique_ptr; + + EnvironmentVarsParser(ISettingSplitter::Ptr settingSplitter, IValueDeserializersMap::Ptr valueDeserializersMap); + + [[nodiscard]] JsonObject parse(const std::vector &envVariables) const override; + + [[nodiscard]] const ISettingSplitter &getSettingSplitter() const; + + [[nodiscard]] const IValueDeserializersMap &getValueDeserializersMap() const; + }; +} // namespace sb::cf::details + +#ifdef _7BIT_CONF_ADD_IMPL +#include "SevenBit/Conf/Details/Impl/EnvironmentVarsParser.hpp" +#endif diff --git a/Include/SevenBit/Conf/Details/Impl/CommandLineParser.hpp b/Include/SevenBit/Conf/Details/Impl/CommandLineParser.hpp new file mode 100644 index 0000000..6eec0da --- /dev/null +++ b/Include/SevenBit/Conf/Details/Impl/CommandLineParser.hpp @@ -0,0 +1,96 @@ +#pragma once + +#include + +#include "SevenBit/Conf/Details/CommandLineParser.hpp" +#include "SevenBit/Conf/Details/Require.hpp" +#include "SevenBit/Conf/Details/StringUtils.hpp" +#include "SevenBit/Conf/Exceptions.hpp" + +#include + +namespace sb::cf::details +{ + INLINE CommandLineParser::CommandLineParser(ISettingSplitter::Ptr optionSplitter, + IValueDeserializersMap::Ptr valueDeserializersMap, + std::vector optionPrefixes, + const bool considerSeparated) + : _optionSplitter(std::move(optionSplitter)), _valueDeserializersMap(std::move(valueDeserializersMap)), + _optionPrefixes(std::move(optionPrefixes)), _considerSeparated(considerSeparated) + { + Require::notNull(_optionSplitter); + Require::notNull(_valueDeserializersMap); + } + + INLINE JsonObject CommandLineParser::parse(const std::vector &arguments) const + { + JsonObject result; + for (size_t index = 0; index < arguments.size(); ++index) + { + try + { + auto [keys, value] = parseArgument(arguments, index); + JsonExt::updateWith(result, keys, std::move(value)); + } + catch (const std::exception &e) + { + throw ConfigException("Parsing error for argument '" + std::string{arguments[index]} + + "' error: " + e.what()); + } + } + return result; + } + + INLINE const ISettingSplitter &CommandLineParser::getOptionsSplitter() const { return *_optionSplitter; } + + INLINE const IValueDeserializersMap &CommandLineParser::getValueDeserializersMap() const + { + return *_valueDeserializersMap; + } + + INLINE const std::vector &CommandLineParser::getOptionPrefixes() const { return _optionPrefixes; } + + INLINE bool CommandLineParser::getConsiderSeparated() const { return _considerSeparated; } + + INLINE CommandLineParser::ArgumentParseResult CommandLineParser::parseArgument( + const std::vector &arguments, size_t &index) const + { + auto argument = arguments[index]; + const auto hadOptionPrefix = tryRemoveOptionPrefix(argument); + + auto [keys, type, value] = _optionSplitter->split(argument); + + if (_considerSeparated && hadOptionPrefix && !value && index + 1 < arguments.size()) + { + if (auto nextArgument = arguments[index + 1]; !tryGetOptionPrefix(nextArgument)) + { + value = nextArgument; + ++index; + } + } + return {keys, _valueDeserializersMap->getDeserializerFor(type).deserialize(value)}; + } + + INLINE bool CommandLineParser::tryRemoveOptionPrefix(std::string_view &argument) const + { + if (const auto optionPrefix = tryGetOptionPrefix(argument)) + { + argument.remove_prefix(optionPrefix->size()); + return true; + } + return false; + } + + INLINE std::optional CommandLineParser::tryGetOptionPrefix(std::string_view argument) const + { + for (auto &optionPrefix : _optionPrefixes) + { + if (StringUtils::startsWith(argument, optionPrefix)) + { + return optionPrefix; + } + } + return std::nullopt; + } + +} // namespace sb::cf::details diff --git a/Include/SevenBit/Conf/Details/Impl/Configuration.hpp b/Include/SevenBit/Conf/Details/Impl/Configuration.hpp index 26795e8..bf31048 100644 --- a/Include/SevenBit/Conf/Details/Impl/Configuration.hpp +++ b/Include/SevenBit/Conf/Details/Impl/Configuration.hpp @@ -3,18 +3,18 @@ #include #include -#include "SevenBit/Conf/Configuration.hpp" +#include "SevenBit/Conf/Details/Configuration.hpp" #include "SevenBit/Conf/Details/JsonExt.hpp" -#include "SevenBit/Conf/Details/Utils.hpp" +#include "SevenBit/Conf/Details/StringUtils.hpp" #include "SevenBit/Conf/Exceptions.hpp" -namespace sb::cf +namespace sb::cf::details { INLINE Configuration::Configuration(std::vector providers) : _providers(std::move(providers)) { - reload(); + buildConfig(true); } INLINE std::string Configuration::toString(std::size_t indent, std::string newLineMark) const @@ -34,31 +34,25 @@ namespace sb::cf INLINE JsonValue &Configuration::at(const std::string &key) { return rootAsObject().at(key); } - INLINE const JsonValue *Configuration::find(std::string_view key) const - { - return details::JsonExt::find(root(), key); - } + INLINE const JsonValue *Configuration::find(std::string_view key) const { return JsonExt::find(root(), key); } - INLINE JsonValue *Configuration::find(std::string_view key) { return details::JsonExt::find(root(), key); } + INLINE JsonValue *Configuration::find(std::string_view key) { return JsonExt::find(root(), key); } INLINE const JsonValue *Configuration::deepFind(std::string_view key) const { - return details::JsonExt::deepFind(rootAsObject(), key); + return JsonExt::deepFind(rootAsObject(), key); } - INLINE JsonValue *Configuration::deepFind(std::string_view key) - { - return details::JsonExt::deepFind(rootAsObject(), key); - } + INLINE JsonValue *Configuration::deepFind(std::string_view key) { return JsonExt::deepFind(rootAsObject(), key); } INLINE const JsonValue *Configuration::deepFind(const std::vector &key) const { - return details::JsonExt::deepFind(rootAsObject(), key); + return JsonExt::deepFind(rootAsObject(), key); } INLINE JsonValue *Configuration::deepFind(const std::vector &key) { - return details::JsonExt::deepFind(rootAsObject(), key); + return JsonExt::deepFind(rootAsObject(), key); } INLINE JsonValue &Configuration::deepAt(std::string_view key) @@ -87,14 +81,14 @@ namespace sb::cf INLINE JsonValue &Configuration::operator[](std::string_view key) { - return details::JsonExt::deepGetOrOverride(rootAsObject(), key); + return JsonExt::deepGetOrOverride(rootAsObject(), key); } INLINE const JsonValue &Configuration::operator[](std::string_view key) const { return deepAt(key); } INLINE JsonValue &Configuration::operator[](const std::vector &key) { - return details::JsonExt::deepGetOrOverride(rootAsObject(), key); + return JsonExt::deepGetOrOverride(rootAsObject(), key); } INLINE const JsonValue &Configuration::operator[](const std::vector &key) const @@ -102,15 +96,27 @@ namespace sb::cf return deepAt(key); } - INLINE void Configuration::reload() + INLINE void Configuration::load() + { + for (auto &provider : _providers) + { + Require::notNull(provider); + provider->load(); + } + } + + INLINE void Configuration::buildConfig(const bool withLoad) { auto &configRoot = rootAsObject(); configRoot.clear(); for (auto &provider : _providers) { - details::utils::assertPtr(provider); - provider->load(); - details::JsonExt::deepMerge(configRoot, std::move(provider->getConfiguration())); + Require::notNull(provider); + if (withLoad) + { + provider->load(); + } + JsonExt::deepMerge(configRoot, provider->getConfiguration()); } } @@ -118,13 +124,13 @@ namespace sb::cf INLINE std::vector &Configuration::getProviders() { return _providers; } - INLINE JsonValue &Configuration::throwNotFoundException(const std::vector &key) const + INLINE JsonValue &Configuration::throwNotFoundException(const std::vector &key) { - throw ValueNotFoundException{"Value was not found for key: " + details::utils::joinViews(key, ":")}; + throw ValueNotFoundException{"Value was not found for key: " + StringUtils::join(key, ":")}; } - INLINE JsonValue &Configuration::throwNotFoundException(std::string_view key) const + INLINE JsonValue &Configuration::throwNotFoundException(std::string_view key) { throw ValueNotFoundException{"Value was not found for key: " + std::string{key}}; } -} // namespace sb::cf +} // namespace sb::cf::details diff --git a/Include/SevenBit/Conf/Details/Impl/ConfigurationManager.hpp b/Include/SevenBit/Conf/Details/Impl/ConfigurationManager.hpp deleted file mode 100644 index 0efdc65..0000000 --- a/Include/SevenBit/Conf/Details/Impl/ConfigurationManager.hpp +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#include "SevenBit/Conf/ConfigurationManager.hpp" -#include "SevenBit/Conf/Details/JsonExt.hpp" -#include "SevenBit/Conf/Details/Utils.hpp" - -namespace sb::cf -{ - INLINE IConfigurationBuilder &ConfigurationManager::add(IConfigurationSource::SPtr source) - { - ConfigurationBuilder::add(source); - auto &provider = getProviders().emplace_back(source->build(*this)); - details::utils::assertPtr(provider); - provider->load(); - details::JsonExt::deepMerge(rootAsObject(), std::move(provider->getConfiguration())); - return *this; - } -} // namespace sb::cf diff --git a/Include/SevenBit/Conf/Details/Impl/ConfigurationProviderBase.hpp b/Include/SevenBit/Conf/Details/Impl/ConfigurationProviderBase.hpp deleted file mode 100644 index 09a8927..0000000 --- a/Include/SevenBit/Conf/Details/Impl/ConfigurationProviderBase.hpp +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -#include - -#include "SevenBit/Conf/ConfigurationProviderBase.hpp" -#include "SevenBit/Conf/Details/JsonExt.hpp" -#include "SevenBit/Conf/Exceptions.hpp" - -namespace sb::cf -{ - INLINE const JsonObject &ConfigurationProviderBase::getConfiguration() const { return _configuration; } - - INLINE JsonObject &ConfigurationProviderBase::getConfiguration() { return _configuration; } - - INLINE void ConfigurationProviderBase::clear() { _configuration.clear(); } - - INLINE void ConfigurationProviderBase::set(JsonObject configuration) { _configuration = std::move(configuration); } - - INLINE void ConfigurationProviderBase::update(JsonObject configuration) - { - details::JsonExt::deepMerge(_configuration, std::move(configuration)); - } - - INLINE void ConfigurationProviderBase::update(const std::vector &keys, JsonValue value) - { - details::JsonExt::updateWith(_configuration, keys, std::move(value)); - } -} // namespace sb::cf diff --git a/Include/SevenBit/Conf/Details/Impl/DefaultDeserializers.hpp b/Include/SevenBit/Conf/Details/Impl/DefaultDeserializers.hpp new file mode 100644 index 0000000..5d20f3e --- /dev/null +++ b/Include/SevenBit/Conf/Details/Impl/DefaultDeserializers.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include "SevenBit/Conf/Details/DefaultDeserializers.hpp" + +namespace sb::cf::details +{ + INLINE void DefaultDeserializers::add(std::vector> &deserializers) + { + deserializers.emplace_back("string", std::make_unique()); + deserializers.emplace_back("bool", std::make_unique()); + deserializers.emplace_back("int", std::make_unique()); + deserializers.emplace_back("double", std::make_unique()); + deserializers.emplace_back("uint", std::make_unique()); + deserializers.emplace_back("json", std::make_unique()); + deserializers.emplace_back("null", std::make_unique()); + } + + INLINE void DefaultDeserializers::add(ValueDeserializersMap &deserializersMap) + { + std::vector> deserializers; + add(deserializers); + for (auto &[type, deserializer] : deserializers) + { + deserializersMap.set(type, std::move(deserializer)); + } + } + +} // namespace sb::cf::details diff --git a/Include/SevenBit/Conf/Details/Impl/Deserializers.hpp b/Include/SevenBit/Conf/Details/Impl/Deserializers.hpp index 6596008..14d28e5 100644 --- a/Include/SevenBit/Conf/Details/Impl/Deserializers.hpp +++ b/Include/SevenBit/Conf/Details/Impl/Deserializers.hpp @@ -4,7 +4,7 @@ #include #include "SevenBit/Conf/Details/Deserializers.hpp" -#include "SevenBit/Conf/Details/Utils.hpp" +#include "SevenBit/Conf/Details/StringUtils.hpp" namespace sb::cf::details { @@ -15,25 +15,25 @@ namespace sb::cf::details INLINE JsonValue BoolDeserializer::deserialize(std::optional value) const { - return value && details::utils::stringTo(*value); + return value && StringUtils::convertTo(*value); } INLINE JsonValue IntDeserializer::deserialize(std::optional value) const { - return value ? details::utils::stringTo(*value) : 0; + return value ? StringUtils::convertTo(*value) : 0; } INLINE JsonValue UIntDeserializer::deserialize(std::optional value) const { - return value ? details::utils::stringTo(*value) : 0; + return value ? StringUtils::convertTo(*value) : 0; } INLINE JsonValue DoubleDeserializer::deserialize(std::optional value) const { - return value ? details::utils::stringTo(*value) : 0.0; + return value ? StringUtils::convertTo(*value) : 0.0; } - INLINE JsonValue NullDeserializer::deserialize(std::optional value) const { return json::null; } + INLINE JsonValue NullDeserializer::deserialize(std::optional) const { return json::null; } INLINE JsonValue JsonDeserializer::deserialize(std::optional value) const { diff --git a/Include/SevenBit/Conf/Details/Impl/EnvironmentVarsParser.hpp b/Include/SevenBit/Conf/Details/Impl/EnvironmentVarsParser.hpp new file mode 100644 index 0000000..0aa59e2 --- /dev/null +++ b/Include/SevenBit/Conf/Details/Impl/EnvironmentVarsParser.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include + +#include "SevenBit/Conf/Details/EnvironmentVarsParser.hpp" +#include "SevenBit/Conf/Details/JsonExt.hpp" +#include "SevenBit/Conf/Details/Require.hpp" +#include "SevenBit/Conf/Exceptions.hpp" + +namespace sb::cf::details +{ + INLINE EnvironmentVarsParser::EnvironmentVarsParser(ISettingSplitter::Ptr settingSplitter, + IValueDeserializersMap::Ptr valueDeserializersMap) + : _settingSplitter(std::move(settingSplitter)), _valueDeserializersMap(std::move(valueDeserializersMap)) + { + Require::notNull(_settingSplitter); + Require::notNull(_valueDeserializersMap); + } + + INLINE JsonObject EnvironmentVarsParser::parse(const std::vector &envVariables) const + { + JsonObject result; + for (const auto variable : envVariables) + { + try + { + auto [keys, type, value] = getSettingSplitter().split(variable); + JsonExt::updateWith(result, keys, + getValueDeserializersMap().getDeserializerFor(type).deserialize(value)); + } + catch (const std::exception &e) + { + throw SettingParserException("Parsing error for environment variable '" + std::string{variable} + + "' error: " + e.what()); + } + } + return result; + } + + INLINE const ISettingSplitter &EnvironmentVarsParser::getSettingSplitter() const { return *_settingSplitter; } + + INLINE const IValueDeserializersMap &EnvironmentVarsParser::getValueDeserializersMap() const + { + return *_valueDeserializersMap; + } +} // namespace sb::cf::details diff --git a/Include/SevenBit/Conf/Details/Impl/JsonObjectExt.hpp b/Include/SevenBit/Conf/Details/Impl/JsonExt.hpp similarity index 65% rename from Include/SevenBit/Conf/Details/Impl/JsonObjectExt.hpp rename to Include/SevenBit/Conf/Details/Impl/JsonExt.hpp index c289b65..15fd6d0 100644 --- a/Include/SevenBit/Conf/Details/Impl/JsonObjectExt.hpp +++ b/Include/SevenBit/Conf/Details/Impl/JsonExt.hpp @@ -3,7 +3,7 @@ #include #include "SevenBit/Conf/Details/JsonExt.hpp" -#include "SevenBit/Conf/Details/Utils.hpp" +#include "SevenBit/Conf/Details/StringUtils.hpp" #include "SevenBit/Conf/Exceptions.hpp" #include "SevenBit/Conf/Json.hpp" @@ -59,13 +59,13 @@ namespace sb::cf::details INLINE JsonValue *JsonExt::find(JsonArray &json, std::string_view key) { - auto [success, index] = details::utils::tryStringTo(key); + auto [success, index] = StringUtils::tryConvertTo(key); return success ? find(json, index) : nullptr; } INLINE const JsonValue *JsonExt::find(const JsonArray &json, std::string_view key) { - auto [success, index] = details::utils::tryStringTo(key); + auto [success, index] = StringUtils::tryConvertTo(key); return success ? find(json, index) : nullptr; } @@ -82,12 +82,12 @@ namespace sb::cf::details INLINE JsonValue *JsonExt::deepFind(JsonValue &json, std::string_view key) { - return deepFind(json, details::utils::split(key, ":")); + return deepFind(json, StringUtils::split(key, ":")); } INLINE const JsonValue *JsonExt::deepFind(const JsonValue &json, std::string_view key) { - return deepFind(json, details::utils::split(key, ":")); + return deepFind(json, StringUtils::split(key, ":")); } INLINE JsonValue *JsonExt::deepFind(JsonValue &json, const std::vector &key) @@ -102,12 +102,12 @@ namespace sb::cf::details INLINE JsonValue *JsonExt::deepFind(JsonObject &json, std::string_view key) { - return deepFind(json, details::utils::split(key, ":")); + return deepFind(json, StringUtils::split(key, ":")); } INLINE const JsonValue *JsonExt::deepFind(const JsonObject &json, std::string_view key) { - return deepFind(json, details::utils::split(key, ":")); + return deepFind(json, StringUtils::split(key, ":")); } INLINE JsonValue *JsonExt::deepFind(JsonObject &json, const std::vector &key) @@ -122,12 +122,12 @@ namespace sb::cf::details INLINE JsonValue *JsonExt::deepFind(JsonArray &json, std::string_view key) { - return deepFind(json, details::utils::split(key, ":")); + return deepFind(json, StringUtils::split(key, ":")); } INLINE const JsonValue *JsonExt::deepFind(const JsonArray &json, std::string_view key) { - return deepFind(json, details::utils::split(key, ":")); + return deepFind(json, StringUtils::split(key, ":")); } INLINE JsonValue *JsonExt::deepFind(JsonArray &json, const std::vector &key) @@ -142,7 +142,7 @@ namespace sb::cf::details INLINE JsonValue &JsonExt::getOrOverride(JsonValue &json, std::string_view key) { - auto isNumber = utils::isNumberString(key); + auto isNumber = StringUtils::isNumber(key); if (!json.is_object() && !json.is_array()) { json = isNumber ? JsonValue(JsonArray{}) : JsonValue(JsonObject{}); @@ -151,7 +151,7 @@ namespace sb::cf::details { if (isNumber) { - return getOrOverride(json.get_array(), utils::stringTo(key)); + return getOrOverride(json.get_array(), StringUtils::convertTo(key)); } json.set_object({}); } @@ -180,7 +180,7 @@ namespace sb::cf::details INLINE JsonValue &JsonExt::deepGetOrOverride(JsonObject &json, std::string_view key) { - return deepGetOrOverride(json, details::utils::split(key, ":")); + return deepGetOrOverride(json, StringUtils::split(key, ":")); } INLINE JsonValue &JsonExt::deepGetOrOverride(JsonObject &json, const std::vector &keys) @@ -194,7 +194,57 @@ namespace sb::cf::details return *current; } - INLINE void JsonExt::deepMerge(JsonValue &json, JsonValue override) + INLINE void JsonExt::deepMerge(JsonValue &json, const JsonValue &override) + { + if (override.is_uninitialized()) // undefined is mark for skip + { + return; + } + if (json.is_object() && override.is_object()) + { + deepMerge(json.get_object(), override.get_object()); + } + else if (json.is_array() && override.is_array()) + { + deepMerge(json.get_array(), override.get_array()); + } + else + { + json = override; + } + } + + INLINE void JsonExt::deepMerge(JsonArray &json, const JsonArray &override) + { + if (json.empty()) + { + json = override; + return; + } + for (size_t i = 0; i < override.size(); ++i) + { + if (i >= json.size()) + { + json.emplace_back(); + } + deepMerge(json[i], override[i]); + } + } + + INLINE void JsonExt::deepMerge(JsonObject &json, const JsonObject &override) + { + if (json.empty()) + { + json = override; + return; + } + for (auto &[key, value] : override) + { + deepMerge(json[key], value); + } + } + + INLINE void JsonExt::deepMerge(JsonValue &json, JsonValue &&override) { if (override.is_uninitialized()) // undefined is mark for skip { @@ -214,7 +264,7 @@ namespace sb::cf::details } } - INLINE void JsonExt::deepMerge(JsonArray &json, JsonArray override) + INLINE void JsonExt::deepMerge(JsonArray &json, JsonArray &&override) { if (json.empty()) { @@ -231,7 +281,7 @@ namespace sb::cf::details } } - INLINE void JsonExt::deepMerge(JsonObject &json, JsonObject override) + INLINE void JsonExt::deepMerge(JsonObject &json, JsonObject &&override) { if (json.empty()) { @@ -244,7 +294,43 @@ namespace sb::cf::details } } - INLINE void JsonExt::updateWith(JsonObject &json, const std::vector &keys, JsonValue value) + INLINE void JsonExt::updateWith(JsonObject &json, const std::vector &keys, const JsonValue &value) + { + deepGetOrOverride(json, keys) = value; + } + + INLINE void JsonExt::updateWith(JsonObject &json, const std::vector &keys, + const JsonObject &value) + { + deepGetOrOverride(json, keys) = value; + } + + INLINE void JsonExt::updateWith(JsonObject &json, std::string_view key, const JsonValue &value) + { + deepGetOrOverride(json, key) = value; + } + + INLINE void JsonExt::updateWith(JsonObject &json, std::string_view key, const JsonObject &value) + { + deepGetOrOverride(json, key) = value; + } + + INLINE void JsonExt::updateWith(JsonObject &json, std::string_view key, JsonValue &&value) + { + deepGetOrOverride(json, key) = std::move(value); + } + + INLINE void JsonExt::updateWith(JsonObject &json, std::string_view key, JsonObject &&value) + { + deepGetOrOverride(json, key) = std::move(value); + } + + INLINE void JsonExt::updateWith(JsonObject &json, const std::vector &keys, JsonValue &&value) + { + deepGetOrOverride(json, keys) = std::move(value); + } + + INLINE void JsonExt::updateWith(JsonObject &json, const std::vector &keys, JsonObject &&value) { deepGetOrOverride(json, keys) = std::move(value); } diff --git a/Include/SevenBit/Conf/Details/Impl/SettingParser.hpp b/Include/SevenBit/Conf/Details/Impl/SettingParser.hpp deleted file mode 100644 index 84011b6..0000000 --- a/Include/SevenBit/Conf/Details/Impl/SettingParser.hpp +++ /dev/null @@ -1,80 +0,0 @@ -#pragma once - -#include - -#include "SevenBit/Conf/Details/SettingParser.hpp" -#include "SevenBit/Conf/Details/Utils.hpp" -#include "SevenBit/Conf/Exceptions.hpp" - -namespace sb::cf::details -{ - INLINE SettingParser::SettingParser(ISettingSplitter::Ptr settingSplitter, - IValueDeserializersMap::Ptr valueDeserializersMap, std::string_view defaultType, - bool allowEmptyKeys, bool throwOnUnknownType) - : _settingSplitter(std::move(settingSplitter)), _valueDeserializersMap(std::move(valueDeserializersMap)), - _defaultType(defaultType), _allowEmptyKeys(allowEmptyKeys), _throwOnUnknownType(throwOnUnknownType) - { - details::utils::assertPtr(_settingSplitter); - details::utils::assertPtr(_valueDeserializersMap); - } - - INLINE ISettingParser::Result SettingParser::parse(std::string_view setting) const - { - try - { - auto [keys, type, value] = getSettingSplitter().split(setting); - - checkKeys(keys); - - return {std::move(keys), getDeserializerFor(type ? *type : getDefaultType()).deserialize(value)}; - } - catch (const std::exception &e) - { - throw SettingParserException("Parsing error for setting '" + std::string{setting} + "' error: " + e.what()); - } - } - - INLINE const IDeserializer &SettingParser::getDeserializerFor(std::string_view type) const - { - auto deserializer = getValueDeserializersMap().getDeserializerFor(type); - if (!deserializer) - { - if (getThrowOnUnknownType()) - { - throw ConfigException("Unknown setting type: " + std::string{type}); - } - deserializer = getValueDeserializersMap().getDeserializerFor(getDefaultType()); - if (!deserializer) - { - throw ConfigException("Unknown default setting type: " + std::string{getDefaultType()}); - } - } - return *deserializer; - } - - INLINE void SettingParser::checkKeys(const std::vector &keys) const - { - if (getAllowEmptyKeys()) - { - return; - } - if (keys.empty() || std::any_of(keys.begin(), keys.end(), [](auto key) { return key.empty(); })) - { - throw ConfigException("Setting key cannot be empty"); - } - } - - INLINE const ISettingSplitter &SettingParser::getSettingSplitter() const { return *_settingSplitter; } - - INLINE const IValueDeserializersMap &SettingParser::getValueDeserializersMap() const - { - return *_valueDeserializersMap; - } - - INLINE std::string_view SettingParser::getDefaultType() const { return _defaultType; } - - INLINE bool SettingParser::getAllowEmptyKeys() const { return _allowEmptyKeys; } - - INLINE bool SettingParser::getThrowOnUnknownType() const { return _throwOnUnknownType; } - -} // namespace sb::cf::details diff --git a/Include/SevenBit/Conf/Details/Impl/SettingParserBuilder.hpp b/Include/SevenBit/Conf/Details/Impl/SettingParserBuilder.hpp deleted file mode 100644 index 7bbc664..0000000 --- a/Include/SevenBit/Conf/Details/Impl/SettingParserBuilder.hpp +++ /dev/null @@ -1,95 +0,0 @@ -#pragma once - -#include "SevenBit/Conf/Details/Deserializers.hpp" -#include "SevenBit/Conf/Details/SettingParser.hpp" -#include "SevenBit/Conf/Details/SettingSplitter.hpp" -#include "SevenBit/Conf/Details/Utils.hpp" -#include "SevenBit/Conf/Details/ValueDeserializersMap.hpp" -#include "SevenBit/Conf/SettingParserBuilder.hpp" - -namespace sb::cf -{ - INLINE SettingParserBuilder &SettingParserBuilder::useSplitter(ISettingSplitter::Ptr splitter) - { - _splitter = std::move(splitter); - return *this; - } - - INLINE SettingParserBuilder &SettingParserBuilder::useValueDeserializersMap( - IValueDeserializersMap::Ptr valueDeserializersMap) - { - _valueDeserializersMap = std::move(valueDeserializersMap); - return *this; - } - - INLINE SettingParserBuilder &SettingParserBuilder::useValueDeserializer(std::string_view type, - IDeserializer::Ptr valueDeserializer) - { - _deserializersMap.emplace_back(type, std::move(valueDeserializer)); - return *this; - } - - INLINE SettingParserBuilder &SettingParserBuilder::useConfig(SettingParserConfig config) - { - _config = std::move(config); - return *this; - } - - INLINE SettingParserBuilder &SettingParserBuilder::useDefaultValueDeserializers() - { - useValueDeserializer("string", std::make_unique()); - useValueDeserializer("bool", std::make_unique()); - useValueDeserializer("int", std::make_unique()); - useValueDeserializer("double", std::make_unique()); - useValueDeserializer("uint", std::make_unique()); - useValueDeserializer("json", std::make_unique()); - useValueDeserializer("null", std::make_unique()); - return *this; - } - - INLINE ISettingParser::Ptr SettingParserBuilder::build() - { - auto &config = getConfig(); - return std::make_unique(getSplitter(), getValueDeserializersMap(), config.defaultType, - config.allowEmptyKeys, config.throwOnUnknownType); - } - - INLINE ISettingSplitter::Ptr SettingParserBuilder::getSplitter() - { - if (!_splitter) - { - auto &config = getConfig(); - useSplitter(std::make_unique( - std::move(config.settingPrefixes), std::move(config.settingSplitters), std::move(config.typeMarkers), - std::move(config.keySplitters))); - } - return std::move(_splitter); - } - - INLINE IValueDeserializersMap::Ptr SettingParserBuilder::getValueDeserializersMap() - { - if (!_valueDeserializersMap) - { - if (_deserializersMap.empty()) - { - useDefaultValueDeserializers(); - } - auto valueDeserializers = std::make_unique(); - for (auto &[type, deserializer] : _deserializersMap) - { - valueDeserializers->set(type, std::move(deserializer)); - } - useValueDeserializersMap(std::move(valueDeserializers)); - } - return std::move(_valueDeserializersMap); - } - - INLINE SettingParserConfig &SettingParserBuilder::getConfig() - { - if (!_config) - { - useConfig({}); - } - return *_config; - } -} // namespace sb::cf diff --git a/Include/SevenBit/Conf/Details/Impl/SettingSplitter.hpp b/Include/SevenBit/Conf/Details/Impl/SettingSplitter.hpp index 469a458..5f52d6f 100644 --- a/Include/SevenBit/Conf/Details/Impl/SettingSplitter.hpp +++ b/Include/SevenBit/Conf/Details/Impl/SettingSplitter.hpp @@ -1,62 +1,74 @@ #pragma once #include "SevenBit/Conf/Details/SettingSplitter.hpp" -#include "SevenBit/Conf/Details/Utils.hpp" +#include "SevenBit/Conf/Details/StringUtils.hpp" namespace sb::cf::details { - INLINE SettingSplitter::SettingSplitter(std::vector settingPrefixes, - std::vector settingSplitters, + INLINE SettingSplitter::SettingSplitter(std::vector settingSplitters, std::vector typeMarkers, - std::vector keySplitters) - : _settingPrefixes(std::move(settingPrefixes)), _settingSplitters(std::move(settingSplitters)), - _typeMarkers(std::move(typeMarkers)), _keySplitters(std::move(keySplitters)) + std::vector keySplitters, const bool allowEmptyKeys) + : _settingSplitters(std::move(settingSplitters)), _typeMarkers(std::move(typeMarkers)), + _keySplitters(std::move(keySplitters)), _allowEmptyKeys(allowEmptyKeys) { } - INLINE ISettingSplitter::Result SettingSplitter::split(std::string_view setting) const + INLINE ISettingSplitter::Result SettingSplitter::split(const std::string_view setting) const { - auto [key, value] = splitSetting(tryRemovePrefix(setting)); - auto [rawKey, type] = splitType(key); + auto [rawKey, value] = splitSetting(setting); + const auto type = tryExtractType(rawKey); return {splitKey(rawKey), type, value}; } - INLINE std::string_view SettingSplitter::tryRemovePrefix(std::string_view setting) const + INLINE const std::vector &SettingSplitter::getSettingSplitters() const { - for (auto &prefix : _settingPrefixes) - { - if (details::utils::startsWith(setting, prefix)) - { - setting.remove_prefix(prefix.size()); - break; - } - } - return setting; + return _settingSplitters; } + INLINE const std::vector &SettingSplitter::getTypeMarkers() const { return _typeMarkers; } + + INLINE const std::vector &SettingSplitter::getKeySplitters() const { return _keySplitters; } + + INLINE bool SettingSplitter::getAllowEmptyKeys() const { return _allowEmptyKeys; } + INLINE std::pair> SettingSplitter::splitSetting( - std::string_view setting) const + const std::string_view setting) const { - if (auto breakResult = details::utils::tryBreak(setting, _settingSplitters)) + if (auto breakResult = StringUtils::tryBreak(setting, getSettingSplitters())) { return {breakResult->first, breakResult->second}; } return {setting, std::nullopt}; } - INLINE std::pair> SettingSplitter::splitType( - std::string_view key) const + INLINE std::optional SettingSplitter::tryExtractType(std::string_view &key) const { - if (auto breakResult = details::utils::tryBreakFromEnd(key, _typeMarkers)) + if (auto breakResult = StringUtils::tryBreakFromEnd(key, getTypeMarkers())) { - return {breakResult->first, breakResult->second}; + key = breakResult->first; + return breakResult->second; } - return {key, std::nullopt}; + return std::nullopt; } - INLINE std::vector SettingSplitter::splitKey(std::string_view key) const + INLINE std::vector SettingSplitter::splitKey(const std::string_view key) const { - return details::utils::split(key, _keySplitters); + auto keys = StringUtils::split(key, getKeySplitters()); + checkKeys(keys); + return keys; + } + + INLINE void SettingSplitter::checkKeys(const std::vector &keys) const + { + if (getAllowEmptyKeys()) + { + return; + } + if (keys.empty() || + std::any_of(keys.begin(), keys.end(), [](const std::string_view &key) { return key.empty(); })) + { + throw ConfigException("Setting key cannot be empty"); + } } } // namespace sb::cf::details diff --git a/Include/SevenBit/Conf/Details/Impl/StringUtils.hpp b/Include/SevenBit/Conf/Details/Impl/StringUtils.hpp new file mode 100644 index 0000000..4def691 --- /dev/null +++ b/Include/SevenBit/Conf/Details/Impl/StringUtils.hpp @@ -0,0 +1,165 @@ +#pragma once + +#include +#include + +#include "SevenBit/Conf/Details/StringUtils.hpp" + +namespace sb::cf::details +{ + INLINE bool StringUtils::isNumber(std::string_view str) + { + return !str.empty() && std::all_of(str.begin(), str.end(), [](unsigned char ch) { return std::isdigit(ch); }); + } + + INLINE bool StringUtils::ignoreCaseLess(std::string_view str, std::string_view search) + { + return std::lexicographical_compare( + str.begin(), str.end(), search.begin(), search.end(), + [](unsigned char cha, unsigned char chb) { return std::tolower(cha) < std::tolower(chb); }); + } + + INLINE bool StringUtils::ignoreCaseEqual(std::string_view str, std::string_view search) + { + return str.size() == search.size() && + std::equal(str.begin(), str.end(), search.begin(), search.end(), + [](unsigned char cha, unsigned char chb) { return std::tolower(cha) == std::tolower(chb); }); + } + + INLINE bool StringUtils::containsAt(std::string_view str, size_t index, std::string_view search) + { + if (index >= str.size() || search.empty()) + { + return false; + } + return str.compare(index, search.size(), search) == 0; + } + + INLINE std::optional StringUtils::containsAt(std::string_view str, size_t index, + const std::vector &searches) + { + for (auto &search : searches) + { + if (containsAt(str, index, search)) + { + return search; + } + } + return std::nullopt; + } + + INLINE bool StringUtils::containsAtFromEnd(std::string_view str, size_t index, std::string_view search) + { + if (index + 1 < search.size()) + { + return false; + } + return containsAt(str, index + 1 - search.size(), search); + } + + INLINE std::optional StringUtils::containsAtFromEnd(std::string_view str, size_t index, + const std::vector &searches) + { + for (auto &search : searches) + { + if (containsAtFromEnd(str, index, search)) + { + return search; + } + } + return std::nullopt; + } + + INLINE bool StringUtils::startsWith(std::string_view str, std::string_view search) + { + auto searchIt = search.begin(); + for (auto it = str.begin(); it != str.end() && searchIt != search.end(); ++it, ++searchIt) + { + if (*it != *searchIt) + { + return false; + } + } + return searchIt == search.end(); + } + + INLINE size_t StringUtils::startsWithWhiteSpace(std::string_view str) + { + const auto it = std::find_if(str.begin(), str.end(), [](const unsigned char ch) { return !std::isspace(ch); }); + return it - str.begin(); + } + + INLINE bool StringUtils::isWhiteSpace(std::string_view str) + { + return !str.empty() && startsWithWhiteSpace(str) == str.size(); + } + + INLINE std::vector StringUtils::split(std::string_view str, std::string_view separator) + { + std::vector result; + std::string::size_type begin = 0, pos = 0; + for (; std::string_view::npos != (pos = str.find_first_of(separator, pos)); begin = (pos += separator.size())) + { + result.push_back(str.substr(begin, pos - begin)); + } + result.push_back(str.substr(begin)); + return result; + } + + INLINE std::vector StringUtils::split(std::string_view str, + const std::vector &separators) + { + std::vector result; + for (int i = 0; i < str.size(); ++i) + { + if (const auto separator = containsAt(str, i, separators)) + { + result.emplace_back(str.substr(0, i)); + str.remove_prefix(separator->size() + result.back().size()); + i = -1; + } + } + result.emplace_back(str); + return result; + } + + INLINE std::optional> StringUtils::tryBreak( + std::string_view str, const std::vector &separators) + { + for (size_t i = 0; i < str.size(); ++i) + { + if (const auto separator = containsAt(str, i, separators)) + { + return std::make_pair(str.substr(0, i), str.substr(i + separator->size())); + } + } + return std::nullopt; + } + + INLINE std::optional> StringUtils::tryBreakFromEnd( + std::string_view str, const std::vector &separators) + { + for (int i = static_cast(str.size()) - 1; i >= 0; --i) + { + if (const auto separator = containsAtFromEnd(str, i, separators)) + { + return std::make_pair(str.substr(0, i + 1 - separator->size()), str.substr(i + 1)); + } + } + return std::nullopt; + } + + INLINE std::string StringUtils::join(const std::vector &strs, const std::string &separator) + { + std::string res; + if (!strs.empty()) + { + for (size_t i = 0; i < strs.size() - 1; ++i) + { + res += std::string{strs[i]} + separator; + } + res += strs.back(); + } + return res; + } +} // namespace sb::cf::details diff --git a/Include/SevenBit/Conf/Details/Impl/Utils.hpp b/Include/SevenBit/Conf/Details/Impl/Utils.hpp deleted file mode 100644 index a9df582..0000000 --- a/Include/SevenBit/Conf/Details/Impl/Utils.hpp +++ /dev/null @@ -1,154 +0,0 @@ -#pragma once - -#include -#include - -#include "SevenBit/Conf/Details/Utils.hpp" -#include "SevenBit/Conf/Exceptions.hpp" - -namespace sb::cf::details::utils -{ - INLINE bool isNumberString(std::string_view str) - { - return !str.empty() && std::all_of(str.begin(), str.end(), [](char ch) { return std::isdigit(ch); }); - } - - INLINE bool ignoreCaseLess(std::string_view str, std::string_view search) - { - return std::lexicographical_compare(str.begin(), str.end(), search.begin(), search.end(), - [](char cha, char chb) { return std::tolower(cha) < std::tolower(chb); }); - } - - INLINE bool ignoreCaseEqual(std::string_view str, std::string_view search) - { - return str.size() == search.size() && - std::equal(str.begin(), str.end(), search.begin(), search.end(), - [](char cha, char chb) { return std::tolower(cha) == std::tolower(chb); }); - } - - INLINE bool containsAt(std::string_view str, size_t index, std::string_view search) - { - if (index >= str.size() || search.empty()) - { - return false; - } - return str.compare(index, search.size(), search) == 0; - } - - INLINE std::optional containsAt(std::string_view str, size_t index, - const std::vector &searches) - { - for (auto &search : searches) - { - if (containsAt(str, index, search)) - { - return search; - } - } - return std::nullopt; - } - - INLINE bool containsAtFromEnd(std::string_view str, size_t index, std::string_view search) - { - if (index + 1 < search.size()) - { - return false; - } - return containsAt(str, index + 1 - search.size(), search); - } - - INLINE std::optional containsAtFromEnd(std::string_view str, size_t index, - const std::vector &searches) - { - for (auto &search : searches) - { - if (containsAtFromEnd(str, index, search)) - { - return search; - } - } - return std::nullopt; - } - - INLINE bool startsWith(std::string_view str, std::string_view search) - { - auto searchIt = search.begin(); - for (auto it = str.begin(); it != str.end() && searchIt != search.end(); ++it, ++searchIt) - { - if (*it != *searchIt) - { - return false; - } - } - return searchIt == search.end(); - } - - INLINE std::vector split(std::string_view str, std::string_view divider) - { - std::vector result; - std::string::size_type begin = 0, pos = 0; - for (; std::string_view::npos != (pos = str.find_first_of(divider, pos)); begin = (pos += divider.size())) - { - result.push_back(str.substr(begin, pos - begin)); - } - result.push_back(str.substr(begin)); - return result; - } - - INLINE std::vector split(std::string_view str, const std::vector ÷rs) - { - std::vector result; - for (int i = 0; i < str.size(); ++i) - { - if (auto foundDelim = containsAt(str, i, dividers)) - { - result.emplace_back(str.substr(0, i)); - str.remove_prefix(foundDelim->size() + result.back().size()); - i = -1; - } - } - result.emplace_back(str); - return result; - } - - INLINE std::optional> tryBreak( - std::string_view str, const std::vector ÷rs) - { - for (size_t i = 0; i < str.size(); ++i) - { - if (auto foundDelim = containsAt(str, i, dividers)) - { - return std::make_pair(str.substr(0, i), str.substr(i + foundDelim->size())); - } - } - return std::nullopt; - } - - INLINE std::optional> tryBreakFromEnd( - std::string_view str, const std::vector ÷rs) - { - for (int i = static_cast(str.size()) - 1; i >= 0; --i) - { - if (auto foundDelim = containsAtFromEnd(str, i, dividers)) - { - return std::make_pair(str.substr(0, i + 1 - foundDelim->size()), str.substr(i + 1)); - } - } - return std::nullopt; - } - - INLINE std::string joinViews(const std::vector &strings, const std::string ÷r) - { - std::string res; - if (strings.empty()) - { - return res; - } - for (size_t i = 0; i < strings.size() - 1; ++i) - { - res += std::string{strings[i]} + divider; - } - res += strings.back(); - return res; - } -} // namespace sb::cf::details::utils diff --git a/Include/SevenBit/Conf/Details/Impl/ValueDeserializersMap.hpp b/Include/SevenBit/Conf/Details/Impl/ValueDeserializersMap.hpp index aeeba45..81b3e4e 100644 --- a/Include/SevenBit/Conf/Details/Impl/ValueDeserializersMap.hpp +++ b/Include/SevenBit/Conf/Details/Impl/ValueDeserializersMap.hpp @@ -1,25 +1,62 @@ #pragma once -#include "SevenBit/Conf/Details/Utils.hpp" #include "SevenBit/Conf/Details/ValueDeserializersMap.hpp" #include "SevenBit/Conf/Exceptions.hpp" namespace sb::cf::details { - INLINE std::map & - ValueDeserializersMap::getDeserializersMap() + INLINE ValueDeserializersMap::ValueDeserializersMap(const std::string_view defaultType, + const bool throwOnUnknownType, + Deserializers &&valueDeserializers) + : _defaultType(defaultType), _throwOnUnknownType(throwOnUnknownType) { - return _deserializersLookup; + for (auto &[type, deserializer] : valueDeserializers) + { + set(type, std::move(deserializer)); + } } - INLINE void ValueDeserializersMap::set(std::string_view type, IDeserializer::Ptr deserializer) + INLINE void ValueDeserializersMap::set(const std::string_view type, IDeserializer::Ptr deserializer) { - _deserializersLookup[std::string{type}] = std::move(deserializer); + _deserializersMap[std::string{type}] = std::move(deserializer); } - INLINE const IDeserializer *ValueDeserializersMap::getDeserializerFor(std::string_view typeStr) const + INLINE const IDeserializer &ValueDeserializersMap::getDeserializerFor( + const std::optional type) const { - auto it = _deserializersLookup.find(typeStr); - return it != _deserializersLookup.end() ? it->second.get() : nullptr; + if (const auto deserializer = type ? findDeserializerFor(*type) : &getDefaultDeserializer()) + { + return *deserializer; + } + if (getThrowOnUnknownType()) + { + throw ConfigException("Unknown type: '" + std::string{*type} + "' deserializer for type was not found"); + } + return getDefaultDeserializer(); + } + + INLINE const ValueDeserializersMap::DeserializersMap &ValueDeserializersMap::getDeserializersMap() const + { + return _deserializersMap; + } + + INLINE std::string_view ValueDeserializersMap::getDefaultType() const { return _defaultType; } + + INLINE bool ValueDeserializersMap::getThrowOnUnknownType() const { return _throwOnUnknownType; } + + INLINE const IDeserializer &ValueDeserializersMap::getDefaultDeserializer() const + { + if (const auto deserializer = findDeserializerFor(getDefaultType())) + { + return *deserializer; + } + throw ConfigException("Unknown default type: '" + std::string{getDefaultType()} + + "' deserializer for type was not found"); + } + + INLINE const IDeserializer *ValueDeserializersMap::findDeserializerFor(const std::string_view type) const + { + const auto it = _deserializersMap.find(type); + return it != _deserializersMap.end() ? it->second.get() : nullptr; } } // namespace sb::cf::details diff --git a/Include/SevenBit/Conf/Details/JsonExt.hpp b/Include/SevenBit/Conf/Details/JsonExt.hpp index 6b317d2..cfe4c66 100644 --- a/Include/SevenBit/Conf/Details/JsonExt.hpp +++ b/Include/SevenBit/Conf/Details/JsonExt.hpp @@ -43,22 +43,31 @@ namespace sb::cf::details [[nodiscard]] static const JsonValue *deepFind(const JsonArray &json, const std::vector &key); static JsonValue &getOrOverride(JsonValue &json, std::string_view key); - static JsonValue &getOrOverride(JsonObject &json, std::string_view key); - static JsonValue &getOrOverride(JsonArray &array, size_t index); static JsonValue &deepGetOrOverride(JsonObject &json, std::string_view key); - static JsonValue &deepGetOrOverride(JsonObject &json, const std::vector &keys); - static void deepMerge(JsonValue &json, JsonValue override); + static void deepMerge(JsonValue &json, const JsonValue &override); + static void deepMerge(JsonArray &json, const JsonArray &override); + static void deepMerge(JsonObject &json, const JsonObject &override); + + static void deepMerge(JsonValue &json, JsonValue &&override); + static void deepMerge(JsonArray &json, JsonArray &&override); + static void deepMerge(JsonObject &json, JsonObject &&override); + + static void updateWith(JsonObject &json, std::string_view key, const JsonValue &value); + static void updateWith(JsonObject &json, std::string_view key, const JsonObject &value); - static void deepMerge(JsonArray &json, JsonArray override); + static void updateWith(JsonObject &json, const std::vector &keys, const JsonValue &value); + static void updateWith(JsonObject &json, const std::vector &keys, const JsonObject &value); - static void deepMerge(JsonObject &json, JsonObject override); + static void updateWith(JsonObject &json, std::string_view key, JsonValue &&value); + static void updateWith(JsonObject &json, std::string_view key, JsonObject &&value); - static void updateWith(JsonObject &json, const std::vector &keys, JsonValue value); + static void updateWith(JsonObject &json, const std::vector &keys, JsonValue &&value); + static void updateWith(JsonObject &json, const std::vector &keys, JsonObject &&value); static void checkSegmentSize(const std::vector &key); }; @@ -66,5 +75,5 @@ namespace sb::cf::details } // namespace sb::cf::details #ifdef _7BIT_CONF_ADD_IMPL -#include "SevenBit/Conf/Details/Impl/JsonObjectExt.hpp" +#include "SevenBit/Conf/Details/Impl/JsonExt.hpp" #endif diff --git a/Include/SevenBit/Conf/Details/Require.hpp b/Include/SevenBit/Conf/Details/Require.hpp new file mode 100644 index 0000000..68e8b29 --- /dev/null +++ b/Include/SevenBit/Conf/Details/Require.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include +#include +#include + +#include "SevenBit/Conf/LibraryConfig.hpp" + +#include "SevenBit/Conf/Exceptions.hpp" + +namespace sb::cf::details +{ + struct Require + { + template static void notNull(const T *ptr) + { + if (!ptr) + { + throw NullPointerException(std::string{"Pointer of type: '"} + typeid(T).name() + "' cannot be null"); + } + } + + template static void notNull(const std::unique_ptr &ptr) { notNull(ptr.get()); } + + template static void notNull(const std::shared_ptr &ptr) { notNull(ptr.get()); } + }; +} // namespace sb::cf::details diff --git a/Include/SevenBit/Conf/Details/SettingParser.hpp b/Include/SevenBit/Conf/Details/SettingParser.hpp deleted file mode 100644 index 904b401..0000000 --- a/Include/SevenBit/Conf/Details/SettingParser.hpp +++ /dev/null @@ -1,53 +0,0 @@ -#pragma once - -#include -#include -#include - -#include "SevenBit/Conf/LibraryConfig.hpp" - -#include "SevenBit/Conf/ISettingParser.hpp" -#include "SevenBit/Conf/ISettingSplitter.hpp" -#include "SevenBit/Conf/IValueDeserializersMap.hpp" -#include "SevenBit/Conf/SettingParserConfig.hpp" - -namespace sb::cf::details -{ - class EXPORT SettingParser : public ISettingParser - { - private: - const ISettingSplitter::Ptr _settingSplitter; - const IValueDeserializersMap::Ptr _valueDeserializersMap; - - const std::string_view _defaultType; - const bool _allowEmptyKeys; - const bool _throwOnUnknownType; - - public: - using Ptr = std::unique_ptr; - - SettingParser(ISettingSplitter::Ptr settingSplitter, IValueDeserializersMap::Ptr valueDeserializersMap, - std::string_view defaultType, bool allowEmptyKeys, bool throwOnUnknownType); - - [[nodiscard]] ISettingParser::Result parse(std::string_view setting) const override; - - [[nodiscard]] const ISettingSplitter &getSettingSplitter() const; - - [[nodiscard]] const IValueDeserializersMap &getValueDeserializersMap() const; - - [[nodiscard]] std::string_view getDefaultType() const; - - [[nodiscard]] bool getAllowEmptyKeys() const; - - [[nodiscard]] bool getThrowOnUnknownType() const; - - private: - [[nodiscard]] const IDeserializer &getDeserializerFor(std::string_view type) const; - - void checkKeys(const std::vector &keys) const; - }; -} // namespace sb::cf::details - -#ifdef _7BIT_CONF_ADD_IMPL -#include "SevenBit/Conf/Details/Impl/SettingParser.hpp" -#endif diff --git a/Include/SevenBit/Conf/Details/SettingSplitter.hpp b/Include/SevenBit/Conf/Details/SettingSplitter.hpp index 6ddba9f..b6c47aa 100644 --- a/Include/SevenBit/Conf/Details/SettingSplitter.hpp +++ b/Include/SevenBit/Conf/Details/SettingSplitter.hpp @@ -14,28 +14,34 @@ namespace sb::cf::details class EXPORT SettingSplitter : public ISettingSplitter { - private: - const std::vector _settingPrefixes; const std::vector _settingSplitters; const std::vector _typeMarkers; const std::vector _keySplitters; + const bool _allowEmptyKeys; public: - SettingSplitter(std::vector settingPrefixes, std::vector settingSplitters, - std::vector typeMarkers, std::vector keySplitters); + SettingSplitter(std::vector settingSplitters, std::vector typeMarkers, + std::vector keySplitters, bool allowEmptyKeys = false); - [[nodiscard]] ISettingSplitter::Result split(std::string_view setting) const override; + [[nodiscard]] Result split(std::string_view setting) const override; - private: - [[nodiscard]] std::string_view tryRemovePrefix(std::string_view setting) const; + [[nodiscard]] const std::vector &getSettingSplitters() const; + + [[nodiscard]] const std::vector &getTypeMarkers() const; + + [[nodiscard]] const std::vector &getKeySplitters() const; + + [[nodiscard]] bool getAllowEmptyKeys() const; + private: [[nodiscard]] std::pair> splitSetting( std::string_view setting) const; - [[nodiscard]] std::pair> splitType( - std::string_view key) const; - [[nodiscard]] std::vector splitKey(std::string_view key) const; + + [[nodiscard]] std::optional tryExtractType(std::string_view &key) const; + + void checkKeys(const std::vector &keys) const; }; } // namespace sb::cf::details diff --git a/Include/SevenBit/Conf/Details/StringUtils.hpp b/Include/SevenBit/Conf/Details/StringUtils.hpp new file mode 100644 index 0000000..df55a8d --- /dev/null +++ b/Include/SevenBit/Conf/Details/StringUtils.hpp @@ -0,0 +1,110 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "SevenBit/Conf/LibraryConfig.hpp" + +#include "SevenBit/Conf/Exceptions.hpp" + +namespace sb::cf::details +{ + struct EXPORT StringUtils + { + static bool isNumber(std::string_view str); + + static bool ignoreCaseLess(std::string_view str, std::string_view search); + + static bool ignoreCaseEqual(std::string_view str, std::string_view search); + + static bool containsAt(std::string_view str, size_t index, std::string_view search); + + static std::optional containsAt(std::string_view str, size_t index, + const std::vector &searches); + + static bool containsAtFromEnd(std::string_view str, size_t index, std::string_view search); + + static std::optional containsAtFromEnd(std::string_view str, size_t index, + const std::vector &searches); + + static bool startsWith(std::string_view str, std::string_view search); + + static size_t startsWithWhiteSpace(std::string_view str); + + static bool isWhiteSpace(std::string_view str); + + static std::vector split(std::string_view str, std::string_view separator); + + static std::vector split(std::string_view str, + const std::vector &separators); + + static std::optional> tryBreak( + std::string_view str, const std::vector &separators); + + static std::optional> tryBreakFromEnd( + std::string_view str, const std::vector &separators); + + static std::string join(const std::vector &strs, const std::string &separator); + + template + static std::pair tryConvertTo(std::string_view str, const bool full = true) + { + TNumber number = 0; + str.remove_prefix(startsWithWhiteSpace(str)); + auto last = str.data() + str.size(); + auto res = std::from_chars(str.data(), last, number); + auto success = res.ec == std::errc{} && (!full || res.ptr == last); + return {success, number}; + } + + template static TNumber convertTo(std::string_view str, const bool full = true) + { + if (auto [success, number] = tryConvertTo(str, full); success) + { + return number; + } + throw ConfigException{"Cannot convert string to number: " + std::string{str}}; + } + }; + + template <> inline std::pair StringUtils::tryConvertTo(std::string_view str, const bool full) + { + try + { + std::string string{str}; + size_t processed = 0; + auto number = std::stod(string, &processed); + auto success = !full || processed == string.size(); + return {success, number}; + } + catch (std::exception &e) + { + return {false, 0.0}; + } + } + + template <> inline std::pair StringUtils::tryConvertTo(std::string_view str, const bool full) + { + if (ignoreCaseEqual(str, "true")) + { + return {true, true}; + } + else if (ignoreCaseEqual(str, "false")) + { + return {true, false}; + } + else if (auto [success, number] = tryConvertTo(str, full); success) + { + return {true, number}; + } + return {false, false}; + } +} // namespace sb::cf::details + +#ifdef _7BIT_CONF_ADD_IMPL +#include "SevenBit/Conf/Details/Impl/StringUtils.hpp" +#endif diff --git a/Include/SevenBit/Conf/Details/Utils.hpp b/Include/SevenBit/Conf/Details/Utils.hpp deleted file mode 100644 index f51559b..0000000 --- a/Include/SevenBit/Conf/Details/Utils.hpp +++ /dev/null @@ -1,118 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "SevenBit/Conf/LibraryConfig.hpp" - -#include "SevenBit/Conf/Exceptions.hpp" - -namespace sb::cf::details::utils -{ - EXPORT bool isNumberString(std::string_view str); - - EXPORT bool ignoreCaseLess(std::string_view str, std::string_view search); - - EXPORT bool ignoreCaseEqual(std::string_view str, std::string_view search); - - EXPORT bool containsAt(std::string_view str, size_t index, std::string_view search); - - EXPORT std::optional containsAt(std::string_view str, size_t index, - const std::vector &searches); - - EXPORT bool containsAtFromEnd(std::string_view str, size_t index, std::string_view search); - - EXPORT std::optional containsAtFromEnd(std::string_view str, size_t index, - const std::vector &searches); - - EXPORT bool startsWith(std::string_view str, std::string_view search); - - EXPORT std::vector split(std::string_view str, std::string_view divider); - - EXPORT std::vector split(std::string_view str, const std::vector ÷rs); - - EXPORT std::optional> tryBreak( - std::string_view str, const std::vector ÷rs); - - EXPORT std::optional> tryBreakFromEnd( - std::string_view str, const std::vector ÷rs); - - EXPORT std::string joinViews(const std::vector &strings, const std::string ÷r); - - template void assertPtr(const T *ptr) - { - if (!ptr) - { - throw NullPointerException(std::string{"Pointer of type: '"} + typeid(T).name() + "' cannot be null"); - } - } - - template void assertPtr(const std::unique_ptr &ptr) { assertPtr(ptr.get()); } - - template void assertPtr(const std::shared_ptr &ptr) { assertPtr(ptr.get()); } - - template std::pair tryStringTo(std::string_view str, bool full = true) - { - TNumber number = 0; - while (!str.empty() && std::isspace(str.front())) - { - str.remove_prefix(1); - } - auto last = str.data() + str.size(); - auto res = std::from_chars(str.data(), last, number); - auto success = res.ec == std::errc{} && (!full || res.ptr == last); - return {success, number}; - } - - template <> inline std::pair tryStringTo(std::string_view str, bool full) - { - try - { - std::string string{str}; - size_t processed = 0; - auto number = std::stod(string, &processed); - auto success = !full || processed == string.size(); - return {success, number}; - } - catch (std::exception &e) - { - return {false, 0.0}; - } - } - - template <> inline std::pair tryStringTo(std::string_view str, bool full) - { - if (ignoreCaseEqual(str, "true")) - { - return {true, true}; - } - else if (ignoreCaseEqual(str, "false")) - { - return {true, false}; - } - else if (auto [success, number] = tryStringTo(str, full); success) - { - return {true, number}; - } - return {false, false}; - } - - template TNumber stringTo(std::string_view str, bool full = true) - { - if (auto [success, number] = tryStringTo(str, full); success) - { - return number; - } - throw ConfigException{"Cannot convert string to number: " + std::string{str}}; - } -} // namespace sb::cf::details::utils - -#ifdef _7BIT_CONF_ADD_IMPL -#include "SevenBit/Conf/Details/Impl/Utils.hpp" -#endif diff --git a/Include/SevenBit/Conf/Details/ValueDeserializersMap.hpp b/Include/SevenBit/Conf/Details/ValueDeserializersMap.hpp index 315f3e5..e775d8a 100644 --- a/Include/SevenBit/Conf/Details/ValueDeserializersMap.hpp +++ b/Include/SevenBit/Conf/Details/ValueDeserializersMap.hpp @@ -7,42 +7,58 @@ #include "SevenBit/Conf/LibraryConfig.hpp" -#include "SevenBit/Conf/Details/Utils.hpp" +#include "SevenBit/Conf/Details/StringUtils.hpp" #include "SevenBit/Conf/IValueDeserializersMap.hpp" namespace sb::cf::details { class EXPORT ValueDeserializersMap : public IValueDeserializersMap { - private: + public: struct CaseInsensitiveLess { using is_transparent = int; template bool operator()(const T1 &s1, const T2 &s2) const { - return utils::ignoreCaseLess(s1, s2); + return StringUtils::ignoreCaseLess(s1, s2); } }; - std::map _deserializersLookup; + using Deserializers = std::vector>; + using DeserializersMap = std::map; + + private: + DeserializersMap _deserializersMap; + const std::string_view _defaultType; + const bool _throwOnUnknownType; public: using Ptr = std::unique_ptr; - ValueDeserializersMap() = default; + explicit ValueDeserializersMap(std::string_view defaultType, bool throwOnUnknownType = true, + Deserializers &&valueDeserializers = {}); ValueDeserializersMap(const ValueDeserializersMap &) = delete; ValueDeserializersMap(ValueDeserializersMap &&) noexcept = default; ValueDeserializersMap &operator=(const ValueDeserializersMap &) = delete; - ValueDeserializersMap &operator=(ValueDeserializersMap &&) = default; - - [[nodiscard]] std::map &getDeserializersMap(); + ValueDeserializersMap &operator=(ValueDeserializersMap &&) = delete; void set(std::string_view type, IDeserializer::Ptr deserializer); - [[nodiscard]] const IDeserializer *getDeserializerFor(std::string_view type) const override; + [[nodiscard]] const IDeserializer &getDeserializerFor(std::optional type) const override; + + [[nodiscard]] const DeserializersMap &getDeserializersMap() const; + + [[nodiscard]] std::string_view getDefaultType() const; + + [[nodiscard]] bool getThrowOnUnknownType() const; + + private: + [[nodiscard]] const IDeserializer &getDefaultDeserializer() const; + + [[nodiscard]] const IDeserializer *findDeserializerFor(std::string_view type) const; }; } // namespace sb::cf::details diff --git a/Include/SevenBit/Conf/EnvironmentVarsParserBuilder.hpp b/Include/SevenBit/Conf/EnvironmentVarsParserBuilder.hpp new file mode 100644 index 0000000..fbb590e --- /dev/null +++ b/Include/SevenBit/Conf/EnvironmentVarsParserBuilder.hpp @@ -0,0 +1,53 @@ +#pragma once + +#include +#include +#include +#include + +#include "SevenBit/Conf/LibraryConfig.hpp" + +#include "SevenBit/Conf/EnvironmentVarsParserConfig.hpp" +#include "SevenBit/Conf/ISettingSplitter.hpp" +#include "SevenBit/Conf/ISettingsParser.hpp" +#include "SevenBit/Conf/IValueDeserializersMap.hpp" + +namespace sb::cf +{ + + class EXPORT EnvironmentVarsParserBuilder + { + private: + ISettingSplitter::Ptr _splitter; + IValueDeserializersMap::Ptr _valueDeserializersMap; + std::vector> _valueDeserializers; + std::optional _config; + + public: + EnvironmentVarsParserBuilder &useSplitter(ISettingSplitter::Ptr splitter); + + EnvironmentVarsParserBuilder &useValueDeserializersMap(IValueDeserializersMap::Ptr valueDeserializersMap); + + EnvironmentVarsParserBuilder &useValueDeserializer(std::string_view type, IDeserializer::Ptr valueDeserializer); + + EnvironmentVarsParserBuilder &useConfig(EnvironmentVarsParserConfig config); + + EnvironmentVarsParserBuilder &useDefaultValueDeserializers(); + + ISettingsParser::Ptr build(); + + private: + ISettingSplitter::Ptr getSplitter(); + + IValueDeserializersMap::Ptr getValueDeserializersMap(); + + std::vector> getValueDeserializers(); + + EnvironmentVarsParserConfig &getConfig(); + }; + +} // namespace sb::cf + +#ifdef _7BIT_CONF_ADD_IMPL +#include "SevenBit/Conf/Impl/EnvironmentVarsParserBuilder.hpp" +#endif diff --git a/Include/SevenBit/Conf/SettingParserConfig.hpp b/Include/SevenBit/Conf/EnvironmentVarsParserConfig.hpp similarity index 72% rename from Include/SevenBit/Conf/SettingParserConfig.hpp rename to Include/SevenBit/Conf/EnvironmentVarsParserConfig.hpp index 3e8db7c..f91267d 100644 --- a/Include/SevenBit/Conf/SettingParserConfig.hpp +++ b/Include/SevenBit/Conf/EnvironmentVarsParserConfig.hpp @@ -7,10 +7,9 @@ namespace sb::cf { - struct SettingParserConfig + struct EnvironmentVarsParserConfig { - std::vector settingPrefixes = {"--"}; - std::vector settingSplitters = {"="}; + std::vector variableSplitters = {"="}; std::vector keySplitters = {":", "__"}; std::vector typeMarkers = {"!", "___"}; std::string_view defaultType = "string"; diff --git a/Include/SevenBit/Conf/Exceptions.hpp b/Include/SevenBit/Conf/Exceptions.hpp index 2e3359e..ad105b2 100644 --- a/Include/SevenBit/Conf/Exceptions.hpp +++ b/Include/SevenBit/Conf/Exceptions.hpp @@ -49,5 +49,5 @@ namespace sb::cf } // namespace sb::cf #ifdef _7BIT_CONF_ADD_IMPL -#include "SevenBit/Conf/Details/Impl/Exceptions.hpp" +#include "SevenBit/Conf/Impl/Exceptions.hpp" #endif diff --git a/Include/SevenBit/Conf/IConfigurationBuilder.hpp b/Include/SevenBit/Conf/IConfigurationBuilder.hpp index 8e8c5dd..e6c1922 100644 --- a/Include/SevenBit/Conf/IConfigurationBuilder.hpp +++ b/Include/SevenBit/Conf/IConfigurationBuilder.hpp @@ -3,7 +3,6 @@ #include #include #include -#include #include #include #include @@ -12,18 +11,18 @@ #include "SevenBit/Conf/LibraryConfig.hpp" -#include "SevenBit/Conf/AppSettingsConfiguration.hpp" -#include "SevenBit/Conf/CommandLineConfiguration.hpp" -#include "SevenBit/Conf/EnvironmentVarsConfiguration.hpp" +#include "SevenBit/Conf/CommandLineParserBuilder.hpp" +#include "SevenBit/Conf/EnvironmentVarsParserBuilder.hpp" #include "SevenBit/Conf/IConfiguration.hpp" #include "SevenBit/Conf/IObject.hpp" -#include "SevenBit/Conf/InMemoryConfiguration.hpp" -#include "SevenBit/Conf/JsonConfiguration.hpp" -#include "SevenBit/Conf/JsonFileConfiguration.hpp" -#include "SevenBit/Conf/JsonStreamConfiguration.hpp" -#include "SevenBit/Conf/KeyPerFileConfiguration.hpp" -#include "SevenBit/Conf/SettingParserBuilder.hpp" -#include "SevenBit/Conf/SettingParserConfig.hpp" +#include "SevenBit/Conf/Sources/AppSettingsConfiguration.hpp" +#include "SevenBit/Conf/Sources/CommandLineConfiguration.hpp" +#include "SevenBit/Conf/Sources/EnvironmentVarsConfiguration.hpp" +#include "SevenBit/Conf/Sources/InMemoryConfiguration.hpp" +#include "SevenBit/Conf/Sources/JsonConfiguration.hpp" +#include "SevenBit/Conf/Sources/JsonFileConfiguration.hpp" +#include "SevenBit/Conf/Sources/JsonStreamConfiguration.hpp" +#include "SevenBit/Conf/Sources/KeyPerFileConfiguration.hpp" namespace sb::cf { @@ -45,7 +44,7 @@ namespace sb::cf virtual void clear() = 0; - template IConfigurationBuilder &addFrom(TFactory factory) { return add(factory(*this)); } + template IConfigurationBuilder &addFrom(TFactory &&factory) { return add(factory(*this)); } IConfigurationBuilder &addJsonFile(std::filesystem::path filePath) { @@ -69,50 +68,77 @@ namespace sb::cf return add(AppSettingsConfigurationSource::create(std::move(environmentName))); } - IConfigurationBuilder &addEnvironmentVariables() { return addEnvironmentVariables("", SettingParserConfig{}); } + IConfigurationBuilder &addEnvironmentVariables() { return addEnvironmentVariables(""); } IConfigurationBuilder &addEnvironmentVariables(std::string prefix) { - return addEnvironmentVariables(std::move(prefix), SettingParserConfig{}); + return addEnvironmentVariables(std::move(prefix), EnvironmentVarsParserConfig{}); } - IConfigurationBuilder &addEnvironmentVariables(std::string prefix, SettingParserConfig config) + IConfigurationBuilder &addEnvironmentVariables(std::string prefix, EnvironmentVarsParserConfig config) { - return addEnvironmentVariables(std::move(prefix), - SettingParserBuilder{}.useConfig(std::move(config)).build()); + return addEnvironmentVariables(std::move(prefix), [&](EnvironmentVarsParserBuilder &builder) { + builder.useConfig(std::move(config)); + }); } - IConfigurationBuilder &addEnvironmentVariables(std::string prefix, ISettingParser::Ptr parser) + template + IConfigurationBuilder &addEnvironmentVariables(std::string prefix, TBuilderFunc &&parserBuilderFunctor) + { + EnvironmentVarsParserBuilder builder; + parserBuilderFunctor(builder); + return addEnvironmentVariables(std::move(prefix), builder.build()); + } + + IConfigurationBuilder &addEnvironmentVariables(std::string prefix, ISettingsParser::Ptr parser) { return add(EnvironmentVarsConfigurationSource::create(std::move(prefix), std::move(parser))); } IConfigurationBuilder &addCommandLine(int argc, char *const *const argv) { - return addCommandLine(argc, argv, SettingParserConfig{}); + return addCommandLine(argc, argv, CommandLineParserConfig{}); } IConfigurationBuilder &addCommandLine(std::vector args) { - return addCommandLine(std::move(args), SettingParserConfig{}); + return addCommandLine(std::move(args), CommandLineParserConfig{}); + } + + IConfigurationBuilder &addCommandLine(int argc, char *const *const argv, CommandLineParserConfig config) + { + return addCommandLine(argc, argv, + [&](CommandLineParserBuilder &builder) { builder.useConfig(std::move(config)); }); + } + + IConfigurationBuilder &addCommandLine(std::vector args, CommandLineParserConfig config) + { + return addCommandLine(std::move(args), + [&](CommandLineParserBuilder &builder) { builder.useConfig(std::move(config)); }); } - IConfigurationBuilder &addCommandLine(int argc, char *const *const argv, SettingParserConfig config) + template + IConfigurationBuilder &addCommandLine(int argc, char *const *const argv, TBuilderFunc &&parserBuilderFunctor) { - return addCommandLine(argc, argv, SettingParserBuilder{}.useConfig(std::move(config)).build()); + CommandLineParserBuilder builder; + parserBuilderFunctor(builder); + return addCommandLine(argc, argv, builder.build()); } - IConfigurationBuilder &addCommandLine(std::vector args, SettingParserConfig config) + template + IConfigurationBuilder &addCommandLine(std::vector args, TBuilderFunc &&parserBuilderFunctor) { - return addCommandLine(std::move(args), SettingParserBuilder{}.useConfig(std::move(config)).build()); + CommandLineParserBuilder builder; + parserBuilderFunctor(builder); + return addCommandLine(std::move(args), builder.build()); } - IConfigurationBuilder &addCommandLine(int argc, char *const *const argv, ISettingParser::Ptr parser) + IConfigurationBuilder &addCommandLine(int argc, char *const *const argv, ISettingsParser::Ptr parser) { return add(CommandLineConfigurationSource::create(argc, argv, std::move(parser))); } - IConfigurationBuilder &addCommandLine(std::vector args, ISettingParser::Ptr parser) + IConfigurationBuilder &addCommandLine(std::vector args, ISettingsParser::Ptr parser) { return add(CommandLineConfigurationSource::create(std::move(args), std::move(parser))); } @@ -124,7 +150,7 @@ namespace sb::cf IConfigurationBuilder &addKeyPerFile(std::filesystem::path directoryPath) { - return addKeyPerFile(std::move(directoryPath), false, ""); + return addKeyPerFile(std::move(directoryPath), false); } IConfigurationBuilder &addKeyPerFile(std::filesystem::path directoryPath, bool isOptional) diff --git a/Include/SevenBit/Conf/IConfigurationProvider.hpp b/Include/SevenBit/Conf/IConfigurationProvider.hpp index d070288..83e4ec8 100644 --- a/Include/SevenBit/Conf/IConfigurationProvider.hpp +++ b/Include/SevenBit/Conf/IConfigurationProvider.hpp @@ -16,8 +16,6 @@ namespace sb::cf [[nodiscard]] virtual const JsonObject &getConfiguration() const = 0; - virtual JsonObject &getConfiguration() = 0; - virtual ~IConfigurationProvider() = default; }; } // namespace sb::cf diff --git a/Include/SevenBit/Conf/IDeserializer.hpp b/Include/SevenBit/Conf/IDeserializer.hpp index daee04d..03ebdb4 100644 --- a/Include/SevenBit/Conf/IDeserializer.hpp +++ b/Include/SevenBit/Conf/IDeserializer.hpp @@ -2,7 +2,6 @@ #include #include -#include #include "SevenBit/Conf/LibraryConfig.hpp" diff --git a/Include/SevenBit/Conf/ISettingParser.hpp b/Include/SevenBit/Conf/ISettingParser.hpp deleted file mode 100644 index 8f7f8d1..0000000 --- a/Include/SevenBit/Conf/ISettingParser.hpp +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -#include -#include -#include - -#include "SevenBit/Conf/LibraryConfig.hpp" - -#include "SevenBit/Conf/Json.hpp" - -namespace sb::cf -{ - struct ISettingParser - { - using Ptr = std::unique_ptr; - - struct Result - { - std::vector keys; - JsonValue value; - }; - - [[nodiscard]] virtual Result parse(std::string_view setting) const = 0; - - virtual ~ISettingParser() = default; - }; - - inline bool operator==(const ISettingParser::Result &lhs, const ISettingParser::Result &rhs) - { - return lhs.keys == rhs.keys && lhs.value == rhs.value; - } -} // namespace sb::cf diff --git a/Include/SevenBit/Conf/ISettingsParser.hpp b/Include/SevenBit/Conf/ISettingsParser.hpp new file mode 100644 index 0000000..5a3ca8d --- /dev/null +++ b/Include/SevenBit/Conf/ISettingsParser.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include +#include +#include + +#include "SevenBit/Conf/LibraryConfig.hpp" + +#include "SevenBit/Conf/Json.hpp" + +namespace sb::cf +{ + struct ISettingsParser + { + using Ptr = std::unique_ptr; + + [[nodiscard]] virtual JsonObject parse(const std::vector &settings) const = 0; + + virtual ~ISettingsParser() = default; + }; +} // namespace sb::cf diff --git a/Include/SevenBit/Conf/IValueDeserializersMap.hpp b/Include/SevenBit/Conf/IValueDeserializersMap.hpp index 44ed9de..d719139 100644 --- a/Include/SevenBit/Conf/IValueDeserializersMap.hpp +++ b/Include/SevenBit/Conf/IValueDeserializersMap.hpp @@ -13,7 +13,7 @@ namespace sb::cf { using Ptr = std::unique_ptr; - [[nodiscard]] virtual const IDeserializer *getDeserializerFor(std::string_view type) const = 0; + [[nodiscard]] virtual const IDeserializer &getDeserializerFor(std::optional type) const = 0; virtual ~IValueDeserializersMap() = default; }; diff --git a/Include/SevenBit/Conf/Impl/CommandLineParserBuilder.hpp b/Include/SevenBit/Conf/Impl/CommandLineParserBuilder.hpp new file mode 100644 index 0000000..fa38dfb --- /dev/null +++ b/Include/SevenBit/Conf/Impl/CommandLineParserBuilder.hpp @@ -0,0 +1,99 @@ +#pragma once + +#include + +#include "SevenBit/Conf/CommandLineParserBuilder.hpp" +#include "SevenBit/Conf/Details/CommandLineParser.hpp" +#include "SevenBit/Conf/Details/ContainerUtils.hpp" +#include "SevenBit/Conf/Details/DefaultDeserializers.hpp" +#include "SevenBit/Conf/Details/Deserializers.hpp" +#include "SevenBit/Conf/Details/SettingSplitter.hpp" +#include "SevenBit/Conf/Details/StringUtils.hpp" +#include "SevenBit/Conf/Details/ValueDeserializersMap.hpp" + +namespace sb::cf +{ + INLINE CommandLineParserBuilder &CommandLineParserBuilder::useSplitter(ISettingSplitter::Ptr splitter) + { + _splitter = std::move(splitter); + return *this; + } + + INLINE CommandLineParserBuilder &CommandLineParserBuilder::useValueDeserializersMap( + IValueDeserializersMap::Ptr valueDeserializersMap) + { + _valueDeserializersMap = std::move(valueDeserializersMap); + return *this; + } + + INLINE CommandLineParserBuilder &CommandLineParserBuilder::useValueDeserializer( + std::string_view type, IDeserializer::Ptr valueDeserializer) + { + _valueDeserializers.emplace_back(type, std::move(valueDeserializer)); + return *this; + } + + INLINE CommandLineParserBuilder &CommandLineParserBuilder::useConfig(CommandLineParserConfig config) + { + _config = std::move(config); + return *this; + } + + INLINE CommandLineParserBuilder &CommandLineParserBuilder::useDefaultValueDeserializers() + { + details::DefaultDeserializers::add(_valueDeserializers); + return *this; + } + + INLINE ISettingsParser::Ptr CommandLineParserBuilder::build() + { + auto &config = getConfig(); + auto hadWhiteSpaceSplitters = details::ContainerUtils::eraseIf( + config.optionSplitters, [](const auto splitter) { return details::StringUtils::isWhiteSpace(splitter); }); + + return std::make_unique(getSplitter(), getValueDeserializersMap(), + std::move(config.optionPrefixes), hadWhiteSpaceSplitters); + } + + INLINE ISettingSplitter::Ptr CommandLineParserBuilder::getSplitter() + { + if (!_splitter) + { + auto &config = getConfig(); + useSplitter(std::make_unique( + std::move(config.optionSplitters), std::move(config.typeMarkers), std::move(config.keySplitters), + config.allowEmptyKeys)); + } + return std::move(_splitter); + } + + INLINE IValueDeserializersMap::Ptr CommandLineParserBuilder::getValueDeserializersMap() + { + if (!_valueDeserializersMap) + { + auto &config = getConfig(); + useValueDeserializersMap(std::make_unique( + config.defaultType, config.throwOnUnknownType, getValueDeserializers())); + } + return std::move(_valueDeserializersMap); + } + + INLINE std::vector> CommandLineParserBuilder:: + getValueDeserializers() + { + if (_valueDeserializers.empty()) + { + useDefaultValueDeserializers(); + } + return std::move(_valueDeserializers); + } + + INLINE CommandLineParserConfig &CommandLineParserBuilder::getConfig() + { + if (!_config) + { + useConfig({}); + } + return *_config; + } +} // namespace sb::cf diff --git a/Include/SevenBit/Conf/Details/Impl/ConfigurationBuilder.hpp b/Include/SevenBit/Conf/Impl/ConfigurationBuilder.hpp similarity index 81% rename from Include/SevenBit/Conf/Details/Impl/ConfigurationBuilder.hpp rename to Include/SevenBit/Conf/Impl/ConfigurationBuilder.hpp index 1457464..47093e7 100644 --- a/Include/SevenBit/Conf/Details/Impl/ConfigurationBuilder.hpp +++ b/Include/SevenBit/Conf/Impl/ConfigurationBuilder.hpp @@ -1,8 +1,8 @@ #pragma once -#include "SevenBit/Conf/Configuration.hpp" #include "SevenBit/Conf/ConfigurationBuilder.hpp" -#include "SevenBit/Conf/Details/Utils.hpp" +#include "SevenBit/Conf/Details/Configuration.hpp" +#include "SevenBit/Conf/Details/Require.hpp" namespace sb::cf { @@ -11,13 +11,13 @@ namespace sb::cf { for (auto &source : _sources) { - details::utils::assertPtr(source); + details::Require::notNull(source); } } INLINE IConfigurationBuilder &ConfigurationBuilder::add(IConfigurationSource::SPtr source) { - details::utils::assertPtr(source); + details::Require::notNull(source); _sources.push_back(std::move(source)); return *this; } @@ -28,10 +28,10 @@ namespace sb::cf providers.reserve(_sources.size()); for (auto &source : _sources) { - details::utils::assertPtr(source); + details::Require::notNull(source); providers.emplace_back(source->build(*this)); } - return std::make_unique(std::move(providers)); + return std::make_unique(std::move(providers)); } INLINE void ConfigurationBuilder::clear() { _sources.clear(); } diff --git a/Include/SevenBit/Conf/Impl/EnvironmentVarsParserBuilder.hpp b/Include/SevenBit/Conf/Impl/EnvironmentVarsParserBuilder.hpp new file mode 100644 index 0000000..efb8ee2 --- /dev/null +++ b/Include/SevenBit/Conf/Impl/EnvironmentVarsParserBuilder.hpp @@ -0,0 +1,89 @@ +#pragma once + +#include "SevenBit/Conf/Details/DefaultDeserializers.hpp" +#include "SevenBit/Conf/Details/EnvironmentVarsParser.hpp" +#include "SevenBit/Conf/Details/SettingSplitter.hpp" +#include "SevenBit/Conf/Details/ValueDeserializersMap.hpp" +#include "SevenBit/Conf/EnvironmentVarsParserBuilder.hpp" + +namespace sb::cf +{ + INLINE EnvironmentVarsParserBuilder &EnvironmentVarsParserBuilder::useSplitter(ISettingSplitter::Ptr splitter) + { + _splitter = std::move(splitter); + return *this; + } + + INLINE EnvironmentVarsParserBuilder &EnvironmentVarsParserBuilder::useValueDeserializersMap( + IValueDeserializersMap::Ptr valueDeserializersMap) + { + _valueDeserializersMap = std::move(valueDeserializersMap); + return *this; + } + + INLINE EnvironmentVarsParserBuilder &EnvironmentVarsParserBuilder::useValueDeserializer( + std::string_view type, IDeserializer::Ptr valueDeserializer) + { + _valueDeserializers.emplace_back(type, std::move(valueDeserializer)); + return *this; + } + + INLINE EnvironmentVarsParserBuilder &EnvironmentVarsParserBuilder::useConfig(EnvironmentVarsParserConfig config) + { + _config = std::move(config); + return *this; + } + + INLINE EnvironmentVarsParserBuilder &EnvironmentVarsParserBuilder::useDefaultValueDeserializers() + { + details::DefaultDeserializers::add(_valueDeserializers); + return *this; + } + + INLINE ISettingsParser::Ptr EnvironmentVarsParserBuilder::build() + { + return std::make_unique(getSplitter(), getValueDeserializersMap()); + } + + INLINE ISettingSplitter::Ptr EnvironmentVarsParserBuilder::getSplitter() + { + if (!_splitter) + { + auto &config = getConfig(); + useSplitter(std::make_unique( + std::move(config.variableSplitters), std::move(config.typeMarkers), std::move(config.keySplitters), + config.allowEmptyKeys)); + } + return std::move(_splitter); + } + + INLINE IValueDeserializersMap::Ptr EnvironmentVarsParserBuilder::getValueDeserializersMap() + { + if (!_valueDeserializersMap) + { + auto &config = getConfig(); + useValueDeserializersMap(std::make_unique( + config.defaultType, config.throwOnUnknownType, getValueDeserializers())); + } + return std::move(_valueDeserializersMap); + } + + INLINE std::vector> EnvironmentVarsParserBuilder:: + getValueDeserializers() + { + if (_valueDeserializers.empty()) + { + useDefaultValueDeserializers(); + } + return std::move(_valueDeserializers); + } + + INLINE EnvironmentVarsParserConfig &EnvironmentVarsParserBuilder::getConfig() + { + if (!_config) + { + useConfig({}); + } + return *_config; + } +} // namespace sb::cf diff --git a/Include/SevenBit/Conf/Details/Impl/Exceptions.hpp b/Include/SevenBit/Conf/Impl/Exceptions.hpp similarity index 100% rename from Include/SevenBit/Conf/Details/Impl/Exceptions.hpp rename to Include/SevenBit/Conf/Impl/Exceptions.hpp diff --git a/Include/SevenBit/Conf/LibraryConfig.hpp b/Include/SevenBit/Conf/LibraryConfig.hpp index 1ba4b0d..44d3d03 100644 --- a/Include/SevenBit/Conf/LibraryConfig.hpp +++ b/Include/SevenBit/Conf/LibraryConfig.hpp @@ -2,29 +2,53 @@ #include "SevenBit/Conf/CmakeDef.hpp" -#ifndef _7BIT_CONF_VERSION +#ifndef _7BIT_CONF_VERSION_MAJOR +#define _7BIT_CONF_VERSION_MAJOR 0 +#endif -#define _7BIT_CONF_VERSION "0.0.0" +#ifndef _7BIT_CONF_VERSION_MINOR +#define _7BIT_CONF_VERSION_MINOR 0 +#endif +#ifndef _7BIT_CONF_VERSION_PATCH +#define _7BIT_CONF_VERSION_PATCH 0 #endif -#if !defined _7BIT_CONF_SHARED_LIB && !defined _7BIT_CONF_STATIC_LIB && !defined _7BIT_CONF_HEADER_ONLY_LIB +#define _7BIT_CONF_VERSION_AS_NUMBER \ + (_7BIT_CONF_VERSION_MAJOR * 10000 + _7BIT_CONF_VERSION_MINOR * 100 + _7BIT_CONF_VERSION_PATCH) -#define _7BIT_CONF_HEADER_ONLY_LIB +#define _7BIT_CONF_MACRO_TO_STRING(macro) _7BIT_CONF_TO_STRING(macro) +#define _7BIT_CONF_TO_STRING(value) #value -#endif +#define _7BIT_CONF_VERSION \ + _7BIT_CONF_MACRO_TO_STRING(_7BIT_CONF_VERSION_MAJOR) \ + "." _7BIT_CONF_MACRO_TO_STRING(_7BIT_CONF_VERSION_MINOR) "." _7BIT_CONF_MACRO_TO_STRING(_7BIT_CONF_VERSION_PATCH) -#ifdef _7BIT_CONF_HEADER_ONLY_LIB +#if defined _7BIT_CONF_STATIC_LIB #undef _7BIT_CONF_SHARED_LIB +#undef _7BIT_CONF_HEADER_ONLY_LIB + +#define INLINE + +#elif defined _7BIT_CONF_SHARED_LIB + #undef _7BIT_CONF_STATIC_LIB +#undef _7BIT_CONF_HEADER_ONLY_LIB -#define _7BIT_CONF_ADD_IMPL -#define INLINE inline +#define INLINE #else -#define INLINE +#ifndef _7BIT_CONF_HEADER_ONLY_LIB +#define _7BIT_CONF_HEADER_ONLY_LIB +#endif + +#undef _7BIT_CONF_SHARED_LIB +#undef _7BIT_CONF_STATIC_LIB + +#define _7BIT_CONF_ADD_IMPL +#define INLINE inline #endif diff --git a/Include/SevenBit/Conf/ObjectHolder.hpp b/Include/SevenBit/Conf/ObjectHolder.hpp index 454d7a7..13df43f 100644 --- a/Include/SevenBit/Conf/ObjectHolder.hpp +++ b/Include/SevenBit/Conf/ObjectHolder.hpp @@ -10,7 +10,6 @@ namespace sb::cf { template class ObjectHolder : public IObject { - private: T _object; explicit ObjectHolder(const T &object) : _object(object) {} @@ -18,36 +17,27 @@ namespace sb::cf explicit ObjectHolder(T &&object) : _object(std::move(object)) {} public: - using Ptr = std::unique_ptr>; + using Ptr = std::unique_ptr; - [[nodiscard]] static ObjectHolder::Ptr from(const T &object) - { - return ObjectHolder::Ptr{new ObjectHolder{object}}; - } + [[nodiscard]] static Ptr from(const T &object) { return Ptr{new ObjectHolder{object}}; } - [[nodiscard]] static ObjectHolder::Ptr from(T &&object) - { - return ObjectHolder::Ptr{new ObjectHolder{object}}; - } + [[nodiscard]] static Ptr from(T &&object) { return Ptr{new ObjectHolder{object}}; } - [[nodiscard]] static ObjectHolder &castFrom(IObject &object) - { - return static_cast &>(object); - } + [[nodiscard]] static ObjectHolder &castFrom(IObject &object) { return static_cast(object); } - [[nodiscard]] static const ObjectHolder &castFrom(const IObject &object) + [[nodiscard]] static const ObjectHolder &castFrom(const IObject &object) { - return static_cast &>(object); + return static_cast(object); } - [[nodiscard]] static ObjectHolder &safeCastFrom(IObject &object) + [[nodiscard]] static ObjectHolder &safeCastFrom(IObject &object) { - return dynamic_cast &>(object); + return dynamic_cast(object); } - [[nodiscard]] static const ObjectHolder &safeCastFrom(const IObject &object) + [[nodiscard]] static const ObjectHolder &safeCastFrom(const IObject &object) { - return dynamic_cast &>(object); + return dynamic_cast(object); } [[nodiscard]] T &get() { return _object; } diff --git a/Include/SevenBit/Conf/SettingParserBuilder.hpp b/Include/SevenBit/Conf/SettingParserBuilder.hpp deleted file mode 100644 index 3b3e7cd..0000000 --- a/Include/SevenBit/Conf/SettingParserBuilder.hpp +++ /dev/null @@ -1,51 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -#include "SevenBit/Conf/LibraryConfig.hpp" - -#include "SevenBit/Conf/ISettingParser.hpp" -#include "SevenBit/Conf/ISettingSplitter.hpp" -#include "SevenBit/Conf/IValueDeserializersMap.hpp" -#include "SevenBit/Conf/SettingParserConfig.hpp" - -namespace sb::cf -{ - - class EXPORT SettingParserBuilder - { - private: - ISettingSplitter::Ptr _splitter; - IValueDeserializersMap::Ptr _valueDeserializersMap; - std::vector> _deserializersMap; - std::optional _config; - - public: - SettingParserBuilder &useSplitter(ISettingSplitter::Ptr splitter); - - SettingParserBuilder &useValueDeserializersMap(IValueDeserializersMap::Ptr valueDeserializersMap); - - SettingParserBuilder &useValueDeserializer(std::string_view type, IDeserializer::Ptr valueDeserializer); - - SettingParserBuilder &useConfig(SettingParserConfig config); - - SettingParserBuilder &useDefaultValueDeserializers(); - - ISettingParser::Ptr build(); - - private: - ISettingSplitter::Ptr getSplitter(); - - IValueDeserializersMap::Ptr getValueDeserializersMap(); - - SettingParserConfig &getConfig(); - }; - -} // namespace sb::cf - -#ifdef _7BIT_CONF_ADD_IMPL -#include "SevenBit/Conf/Details/Impl/SettingParserBuilder.hpp" -#endif diff --git a/Include/SevenBit/Conf/AppSettingsConfiguration.hpp b/Include/SevenBit/Conf/Sources/AppSettingsConfiguration.hpp similarity index 92% rename from Include/SevenBit/Conf/AppSettingsConfiguration.hpp rename to Include/SevenBit/Conf/Sources/AppSettingsConfiguration.hpp index b8df519..5b243f8 100644 --- a/Include/SevenBit/Conf/AppSettingsConfiguration.hpp +++ b/Include/SevenBit/Conf/Sources/AppSettingsConfiguration.hpp @@ -29,5 +29,5 @@ namespace sb::cf } // namespace sb::cf #ifdef _7BIT_CONF_ADD_IMPL -#include "SevenBit/Conf/Details/Impl/AppSettingsConfiguration.hpp" +#include "SevenBit/Conf/Sources/Impl/AppSettingsConfiguration.hpp" #endif diff --git a/Include/SevenBit/Conf/ChainedConfiguration.hpp b/Include/SevenBit/Conf/Sources/ChainedConfiguration.hpp similarity index 92% rename from Include/SevenBit/Conf/ChainedConfiguration.hpp rename to Include/SevenBit/Conf/Sources/ChainedConfiguration.hpp index c9ed933..e120261 100644 --- a/Include/SevenBit/Conf/ChainedConfiguration.hpp +++ b/Include/SevenBit/Conf/Sources/ChainedConfiguration.hpp @@ -5,8 +5,8 @@ #include "SevenBit/Conf/LibraryConfig.hpp" -#include "SevenBit/Conf/ConfigurationProviderBase.hpp" #include "SevenBit/Conf/IConfigurationSource.hpp" +#include "SevenBit/Conf/Sources/ConfigurationProviderBase.hpp" namespace sb::cf { @@ -49,5 +49,5 @@ namespace sb::cf } // namespace sb::cf #ifdef _7BIT_CONF_ADD_IMPL -#include "SevenBit/Conf/Details/Impl/ChainedConfiguration.hpp" +#include "SevenBit/Conf/Sources/Impl/ChainedConfiguration.hpp" #endif diff --git a/Include/SevenBit/Conf/CommandLineConfiguration.hpp b/Include/SevenBit/Conf/Sources/CommandLineConfiguration.hpp similarity index 70% rename from Include/SevenBit/Conf/CommandLineConfiguration.hpp rename to Include/SevenBit/Conf/Sources/CommandLineConfiguration.hpp index 2c09e51..1941287 100644 --- a/Include/SevenBit/Conf/CommandLineConfiguration.hpp +++ b/Include/SevenBit/Conf/Sources/CommandLineConfiguration.hpp @@ -4,9 +4,8 @@ #include "SevenBit/Conf/LibraryConfig.hpp" -#include "SevenBit/Conf/ConfigurationProviderBase.hpp" #include "SevenBit/Conf/IConfigurationSource.hpp" -#include "SevenBit/Conf/SettingParserBuilder.hpp" +#include "SevenBit/Conf/Sources/ConfigurationProviderBase.hpp" namespace sb::cf { @@ -15,23 +14,21 @@ namespace sb::cf { private: std::vector _args; - ISettingParser::Ptr _parser; + ISettingsParser::Ptr _parser; - CommandLineConfigurationSource(std::vector args, ISettingParser::Ptr parser); + CommandLineConfigurationSource(std::vector args, ISettingsParser::Ptr parser); public: using Ptr = std::unique_ptr; using SPtr = std::shared_ptr; - [[nodiscard]] static SPtr create(int argc, const char *const *argv, - ISettingParser::Ptr parser = SettingParserBuilder{}.build()); + [[nodiscard]] static SPtr create(int argc, const char *const *argv, ISettingsParser::Ptr parser); - [[nodiscard]] static SPtr create(std::vector args, - ISettingParser::Ptr parser = SettingParserBuilder{}.build()); + [[nodiscard]] static SPtr create(std::vector args, ISettingsParser::Ptr parser); [[nodiscard]] const std::vector &getArgs() const; - [[nodiscard]] const ISettingParser &getSettingParser() const; + [[nodiscard]] const ISettingsParser &getSettingsParser() const; IConfigurationProvider::Ptr build(IConfigurationBuilder &builder) override; }; @@ -49,5 +46,5 @@ namespace sb::cf } // namespace sb::cf #ifdef _7BIT_CONF_ADD_IMPL -#include "SevenBit/Conf/Details/Impl/CommandLineConfiguration.hpp" +#include "SevenBit/Conf/Sources/Impl/CommandLineConfiguration.hpp" #endif diff --git a/Include/SevenBit/Conf/Sources/ConfigurationProviderBase.hpp b/Include/SevenBit/Conf/Sources/ConfigurationProviderBase.hpp new file mode 100644 index 0000000..c8b715f --- /dev/null +++ b/Include/SevenBit/Conf/Sources/ConfigurationProviderBase.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include +#include +#include + +#include "SevenBit/Conf/LibraryConfig.hpp" + +#include "SevenBit/Conf/IConfigurationProvider.hpp" + +namespace sb::cf +{ + class EXPORT ConfigurationProviderBase : public IConfigurationProvider + { + protected: + JsonObject _configuration; + + public: + [[nodiscard]] const JsonObject &getConfiguration() const override; + + [[nodiscard]] JsonObject &getConfiguration(); + + protected: + void clear(); + + void set(const JsonObject &configuration); + void set(JsonObject &&configuration); + + void update(const JsonObject &configuration); + void update(JsonObject &&configuration); + + void updateWith(std::string_view key, const JsonValue &value); + void updateWith(std::string_view key, JsonValue &&value); + + void updateWith(const std::vector &keys, const JsonValue &value); + void updateWith(const std::vector &keys, JsonValue &&value); + }; +} // namespace sb::cf + +#ifdef _7BIT_CONF_ADD_IMPL +#include "SevenBit/Conf/Sources/Impl/ConfigurationProviderBase.hpp" +#endif diff --git a/Include/SevenBit/Conf/EnvironmentVarsConfiguration.hpp b/Include/SevenBit/Conf/Sources/EnvironmentVarsConfiguration.hpp similarity index 67% rename from Include/SevenBit/Conf/EnvironmentVarsConfiguration.hpp rename to Include/SevenBit/Conf/Sources/EnvironmentVarsConfiguration.hpp index 494ecc8..f6207a3 100644 --- a/Include/SevenBit/Conf/EnvironmentVarsConfiguration.hpp +++ b/Include/SevenBit/Conf/Sources/EnvironmentVarsConfiguration.hpp @@ -5,10 +5,9 @@ #include "SevenBit/Conf/LibraryConfig.hpp" -#include "SevenBit/Conf/ConfigurationProviderBase.hpp" +#include "SevenBit/Conf/EnvironmentVarsParserBuilder.hpp" #include "SevenBit/Conf/IConfigurationSource.hpp" -#include "SevenBit/Conf/ISettingParser.hpp" -#include "SevenBit/Conf/SettingParserBuilder.hpp" +#include "SevenBit/Conf/Sources/ConfigurationProviderBase.hpp" namespace sb::cf { @@ -18,20 +17,19 @@ namespace sb::cf { private: std::string _prefix; - ISettingParser::Ptr _parser; + ISettingsParser::Ptr _parser; - EnvironmentVarsConfigurationSource(std::string prefix, ISettingParser::Ptr parser); + EnvironmentVarsConfigurationSource(std::string prefix, ISettingsParser::Ptr parser); public: using Ptr = std::unique_ptr; using SPtr = std::shared_ptr; - [[nodiscard]] static SPtr create(std::string prefix, - ISettingParser::Ptr parser = SettingParserBuilder{}.build()); + [[nodiscard]] static SPtr create(std::string prefix, ISettingsParser::Ptr parser); - [[nodiscard]] const std::string &getPrefix(); + [[nodiscard]] const std::string &getPrefix() const; - [[nodiscard]] const ISettingParser &getSettingParser(); + [[nodiscard]] const ISettingsParser &getSettingParser() const; IConfigurationProvider::Ptr build(IConfigurationBuilder &builder) override; }; @@ -47,11 +45,11 @@ namespace sb::cf void load() override; private: - [[nodiscard]] std::vector getEnvVars(); + [[nodiscard]] std::vector getEnvVars() const; }; } // namespace sb::cf #ifdef _7BIT_CONF_ADD_IMPL -#include "SevenBit/Conf/Details/Impl/EnvironmentVarsConfiguration.hpp" +#include "SevenBit/Conf/Sources/Impl/EnvironmentVarsConfiguration.hpp" #endif diff --git a/Include/SevenBit/Conf/Details/Impl/AppSettingsConfiguration.hpp b/Include/SevenBit/Conf/Sources/Impl/AppSettingsConfiguration.hpp similarity index 84% rename from Include/SevenBit/Conf/Details/Impl/AppSettingsConfiguration.hpp rename to Include/SevenBit/Conf/Sources/Impl/AppSettingsConfiguration.hpp index a435641..07c586e 100644 --- a/Include/SevenBit/Conf/Details/Impl/AppSettingsConfiguration.hpp +++ b/Include/SevenBit/Conf/Sources/Impl/AppSettingsConfiguration.hpp @@ -2,9 +2,10 @@ #include -#include "SevenBit/Conf/AppSettingsConfiguration.hpp" -#include "SevenBit/Conf/ChainedConfiguration.hpp" -#include "SevenBit/Conf/JsonFileConfiguration.hpp" +#include "SevenBit/Conf/Sources/AppSettingsConfiguration.hpp" +#include "SevenBit/Conf/Sources/ChainedConfiguration.hpp" +#include "SevenBit/Conf/Sources/JsonFileConfiguration.hpp" + namespace sb::cf { diff --git a/Include/SevenBit/Conf/Details/Impl/ChainedConfiguration.hpp b/Include/SevenBit/Conf/Sources/Impl/ChainedConfiguration.hpp similarity index 76% rename from Include/SevenBit/Conf/Details/Impl/ChainedConfiguration.hpp rename to Include/SevenBit/Conf/Sources/Impl/ChainedConfiguration.hpp index 3b9b801..43bd24f 100644 --- a/Include/SevenBit/Conf/Details/Impl/ChainedConfiguration.hpp +++ b/Include/SevenBit/Conf/Sources/Impl/ChainedConfiguration.hpp @@ -2,9 +2,8 @@ #include -#include "SevenBit/Conf/ChainedConfiguration.hpp" -#include "SevenBit/Conf/Details/Utils.hpp" -#include "SevenBit/Conf/Exceptions.hpp" +#include "SevenBit/Conf/Details/Require.hpp" +#include "SevenBit/Conf/Sources/ChainedConfiguration.hpp" namespace sb::cf { @@ -13,7 +12,7 @@ namespace sb::cf { for (auto &source : _sources) { - details::utils::assertPtr(source); + details::Require::notNull(source); } } @@ -25,7 +24,7 @@ namespace sb::cf INLINE void ChainedConfigurationSource::add(IConfigurationSource::SPtr source) { - details::utils::assertPtr(source); + details::Require::notNull(source); _sources.push_back(std::move(source)); } @@ -35,7 +34,7 @@ namespace sb::cf providers.reserve(_sources.size()); for (auto &source : _sources) { - details::utils::assertPtr(source); + details::Require::notNull(source); providers.emplace_back(source->build(builder)); } return std::make_unique(std::move(providers)); @@ -47,7 +46,7 @@ namespace sb::cf { for (auto &provider : _providers) { - details::utils::assertPtr(provider); + details::Require::notNull(provider); } } @@ -56,9 +55,10 @@ namespace sb::cf clear(); for (auto &provider : _providers) { - details::utils::assertPtr(provider); + details::Require::notNull(provider); provider->load(); - update(std::move(provider->getConfiguration())); + auto &optimized = const_cast(provider->getConfiguration()); + update(std::move(optimized)); } } diff --git a/Include/SevenBit/Conf/Details/Impl/CommandLineConfiguration.hpp b/Include/SevenBit/Conf/Sources/Impl/CommandLineConfiguration.hpp similarity index 69% rename from Include/SevenBit/Conf/Details/Impl/CommandLineConfiguration.hpp rename to Include/SevenBit/Conf/Sources/Impl/CommandLineConfiguration.hpp index 413ff2e..05e766a 100644 --- a/Include/SevenBit/Conf/Details/Impl/CommandLineConfiguration.hpp +++ b/Include/SevenBit/Conf/Sources/Impl/CommandLineConfiguration.hpp @@ -1,20 +1,20 @@ #pragma once -#include "SevenBit/Conf/CommandLineConfiguration.hpp" -#include "SevenBit/Conf/Details/Utils.hpp" +#include "SevenBit/Conf/Details/Require.hpp" +#include "SevenBit/Conf/Sources/CommandLineConfiguration.hpp" namespace sb::cf { INLINE CommandLineConfigurationSource::CommandLineConfigurationSource(std::vector args, - ISettingParser::Ptr parser) + ISettingsParser::Ptr parser) : _args(std::move(args)), _parser(std::move(parser)) { - details::utils::assertPtr(_parser); + details::Require::notNull(_parser); } - INLINE CommandLineConfigurationSource::SPtr CommandLineConfigurationSource::create(int argc, + INLINE CommandLineConfigurationSource::SPtr CommandLineConfigurationSource::create(const int argc, const char *const *argv, - ISettingParser::Ptr parser) + ISettingsParser::Ptr parser) { std::vector args; if (argc > 1) @@ -29,7 +29,7 @@ namespace sb::cf } INLINE CommandLineConfigurationSource::SPtr CommandLineConfigurationSource::create( - std::vector args, ISettingParser::Ptr parser) + std::vector args, ISettingsParser::Ptr parser) { return CommandLineConfigurationSource::SPtr( new CommandLineConfigurationSource{std::move(args), std::move(parser)}); @@ -37,7 +37,7 @@ namespace sb::cf INLINE const std::vector &CommandLineConfigurationSource::getArgs() const { return _args; } - INLINE const ISettingParser &CommandLineConfigurationSource::getSettingParser() const { return *_parser; } + INLINE const ISettingsParser &CommandLineConfigurationSource::getSettingsParser() const { return *_parser; } INLINE IConfigurationProvider::Ptr CommandLineConfigurationSource::build(IConfigurationBuilder &builder) { @@ -48,17 +48,11 @@ namespace sb::cf CommandLineConfigurationSource::SPtr source) : _source(std::move(source)) { - details::utils::assertPtr(_source); + details::Require::notNull(_source); } INLINE void CommandLineConfigurationProvider::load() { - clear(); - auto &parser = _source->getSettingParser(); - for (auto &setting : _source->getArgs()) - { - auto [keys, value] = parser.parse(setting); - update(keys, std::move(value)); - } + set(_source->getSettingsParser().parse(_source->getArgs())); } } // namespace sb::cf diff --git a/Include/SevenBit/Conf/Sources/Impl/ConfigurationProviderBase.hpp b/Include/SevenBit/Conf/Sources/Impl/ConfigurationProviderBase.hpp new file mode 100644 index 0000000..e2be6a1 --- /dev/null +++ b/Include/SevenBit/Conf/Sources/Impl/ConfigurationProviderBase.hpp @@ -0,0 +1,53 @@ +#pragma once + +#include + +#include "SevenBit/Conf/Details/JsonExt.hpp" +#include "SevenBit/Conf/Exceptions.hpp" +#include "SevenBit/Conf/Sources/ConfigurationProviderBase.hpp" + +namespace sb::cf +{ + INLINE const JsonObject &ConfigurationProviderBase::getConfiguration() const { return _configuration; } + + INLINE JsonObject &ConfigurationProviderBase::getConfiguration() { return _configuration; } + + INLINE void ConfigurationProviderBase::clear() { _configuration.clear(); } + + INLINE void ConfigurationProviderBase::set(const JsonObject &configuration) { _configuration = configuration; } + + INLINE void ConfigurationProviderBase::set(JsonObject &&configuration) + { + _configuration = std::move(configuration); + } + + INLINE void ConfigurationProviderBase::update(const JsonObject &configuration) + { + details::JsonExt::deepMerge(_configuration, configuration); + } + + INLINE void ConfigurationProviderBase::update(JsonObject &&configuration) + { + details::JsonExt::deepMerge(_configuration, std::move(configuration)); + } + + INLINE void ConfigurationProviderBase::updateWith(std::string_view key, const JsonValue &value) + { + details::JsonExt::updateWith(_configuration, key, value); + } + + INLINE void ConfigurationProviderBase::updateWith(std::string_view key, JsonValue &&value) + { + details::JsonExt::updateWith(_configuration, key, std::move(value)); + } + + INLINE void ConfigurationProviderBase::updateWith(const std::vector &keys, const JsonValue &value) + { + details::JsonExt::updateWith(_configuration, keys, value); + } + + INLINE void ConfigurationProviderBase::updateWith(const std::vector &keys, JsonValue &&value) + { + details::JsonExt::updateWith(_configuration, keys, std::move(value)); + } +} // namespace sb::cf diff --git a/Include/SevenBit/Conf/Details/Impl/EnvironmentVarsConfiguration.hpp b/Include/SevenBit/Conf/Sources/Impl/EnvironmentVarsConfiguration.hpp similarity index 60% rename from Include/SevenBit/Conf/Details/Impl/EnvironmentVarsConfiguration.hpp rename to Include/SevenBit/Conf/Sources/Impl/EnvironmentVarsConfiguration.hpp index b2b49cf..c8d5b18 100644 --- a/Include/SevenBit/Conf/Details/Impl/EnvironmentVarsConfiguration.hpp +++ b/Include/SevenBit/Conf/Sources/Impl/EnvironmentVarsConfiguration.hpp @@ -2,9 +2,9 @@ #include -#include "SevenBit/Conf/Details/Utils.hpp" -#include "SevenBit/Conf/EnvironmentVarsConfiguration.hpp" -#include "SevenBit/Conf/Exceptions.hpp" +#include "SevenBit/Conf/Details/Require.hpp" +#include "SevenBit/Conf/Details/StringUtils.hpp" +#include "SevenBit/Conf/Sources/EnvironmentVarsConfiguration.hpp" #ifdef _WIN32 extern "C" __declspec(dllimport) char **_environ; @@ -17,22 +17,22 @@ extern "C" char **environ; namespace sb::cf { INLINE EnvironmentVarsConfigurationSource::EnvironmentVarsConfigurationSource(std::string prefix, - ISettingParser::Ptr parser) + ISettingsParser::Ptr parser) : _prefix(std::move(prefix)), _parser(std::move(parser)) { - details::utils::assertPtr(_parser); + details::Require::notNull(_parser); } INLINE EnvironmentVarsConfigurationSource::SPtr EnvironmentVarsConfigurationSource::create( - std::string prefix, ISettingParser::Ptr parser) + std::string prefix, ISettingsParser::Ptr parser) { return EnvironmentVarsConfigurationSource::SPtr( new EnvironmentVarsConfigurationSource{std::move(prefix), std::move(parser)}); } - INLINE const std::string &EnvironmentVarsConfigurationSource::getPrefix() { return _prefix; } + INLINE const std::string &EnvironmentVarsConfigurationSource::getPrefix() const { return _prefix; } - INLINE const ISettingParser &EnvironmentVarsConfigurationSource::getSettingParser() { return *_parser; } + INLINE const ISettingsParser &EnvironmentVarsConfigurationSource::getSettingParser() const { return *_parser; } INLINE IConfigurationProvider::Ptr EnvironmentVarsConfigurationSource::build(IConfigurationBuilder &builder) { @@ -43,32 +43,18 @@ namespace sb::cf EnvironmentVarsConfigurationSource::SPtr source) : _source(std::move(source)) { - details::utils::assertPtr(_source); + details::Require::notNull(_source); } - INLINE void EnvironmentVarsConfigurationProvider::load() - { - clear(); - auto &parser = _source->getSettingParser(); - for (auto &setting : getEnvVars()) - { - auto [keys, value] = parser.parse(setting); - update(keys, std::move(value)); - } - } + INLINE void EnvironmentVarsConfigurationProvider::load() { set(_source->getSettingParser().parse(getEnvVars())); } - INLINE std::vector EnvironmentVarsConfigurationProvider::getEnvVars() + INLINE std::vector EnvironmentVarsConfigurationProvider::getEnvVars() const { std::vector result; auto &prefix = _source->getPrefix(); for (auto env = _7BIT_CONF_ENV_PTR; *env; env++) { - std::string_view envStr = *env; - if (prefix.empty()) - { - result.push_back(envStr); - } - else if (details::utils::startsWith(envStr, prefix)) + if (std::string_view envStr = *env; details::StringUtils::startsWith(envStr, prefix)) { result.push_back(envStr.substr(prefix.size())); } diff --git a/Include/SevenBit/Conf/Details/Impl/InMemoryConfiguration.hpp b/Include/SevenBit/Conf/Sources/Impl/InMemoryConfiguration.hpp similarity index 78% rename from Include/SevenBit/Conf/Details/Impl/InMemoryConfiguration.hpp rename to Include/SevenBit/Conf/Sources/Impl/InMemoryConfiguration.hpp index 5268d5e..0155b03 100644 --- a/Include/SevenBit/Conf/Details/Impl/InMemoryConfiguration.hpp +++ b/Include/SevenBit/Conf/Sources/Impl/InMemoryConfiguration.hpp @@ -1,8 +1,8 @@ #pragma once #include "SevenBit/Conf/Details/JsonExt.hpp" -#include "SevenBit/Conf/Details/Utils.hpp" -#include "SevenBit/Conf/InMemoryConfiguration.hpp" +#include "SevenBit/Conf/Details/Require.hpp" +#include "SevenBit/Conf/Sources/InMemoryConfiguration.hpp" namespace sb::cf { @@ -26,7 +26,7 @@ namespace sb::cf INLINE InMemoryConfigurationProvider::InMemoryConfigurationProvider(InMemoryConfigurationSource::SPtr source) : _source(std::move(source)) { - details::utils::assertPtr(_source); + details::Require::notNull(_source); } INLINE void InMemoryConfigurationProvider::load() @@ -34,9 +34,7 @@ namespace sb::cf clear(); for (auto &[key, value] : *_source) { - JsonObject result{}; - details::JsonExt::deepGetOrOverride(result, key) = std::move(value); - update(std::move(result)); + updateWith(key, value); } } } // namespace sb::cf diff --git a/Include/SevenBit/Conf/Details/Impl/JsonConfiguration.hpp b/Include/SevenBit/Conf/Sources/Impl/JsonConfiguration.hpp similarity index 86% rename from Include/SevenBit/Conf/Details/Impl/JsonConfiguration.hpp rename to Include/SevenBit/Conf/Sources/Impl/JsonConfiguration.hpp index 1d32be7..f59bb94 100644 --- a/Include/SevenBit/Conf/Details/Impl/JsonConfiguration.hpp +++ b/Include/SevenBit/Conf/Sources/Impl/JsonConfiguration.hpp @@ -1,7 +1,7 @@ #pragma once -#include "SevenBit/Conf/Details/Utils.hpp" -#include "SevenBit/Conf/JsonConfiguration.hpp" +#include "SevenBit/Conf/Details/Require.hpp" +#include "SevenBit/Conf/Sources/JsonConfiguration.hpp" namespace sb::cf { @@ -25,7 +25,7 @@ namespace sb::cf INLINE JsonConfigurationProvider::JsonConfigurationProvider(JsonConfigurationSource::SPtr source) : _source(std::move(source)) { - details::utils::assertPtr(_source); + details::Require::notNull(_source); } INLINE void JsonConfigurationProvider::load() { set(_source->getJson()); } diff --git a/Include/SevenBit/Conf/Details/Impl/JsonFileConfiguration.hpp b/Include/SevenBit/Conf/Sources/Impl/JsonFileConfiguration.hpp similarity index 78% rename from Include/SevenBit/Conf/Details/Impl/JsonFileConfiguration.hpp rename to Include/SevenBit/Conf/Sources/Impl/JsonFileConfiguration.hpp index 28271ab..8135ec1 100644 --- a/Include/SevenBit/Conf/Details/Impl/JsonFileConfiguration.hpp +++ b/Include/SevenBit/Conf/Sources/Impl/JsonFileConfiguration.hpp @@ -3,9 +3,9 @@ #include #include -#include "SevenBit/Conf/Details/Utils.hpp" +#include "SevenBit/Conf/Details/Require.hpp" #include "SevenBit/Conf/Exceptions.hpp" -#include "SevenBit/Conf/JsonFileConfiguration.hpp" +#include "SevenBit/Conf/Sources/JsonFileConfiguration.hpp" namespace sb::cf { @@ -32,25 +32,26 @@ namespace sb::cf INLINE JsonFileConfigurationProvider::JsonFileConfigurationProvider(JsonFileConfigurationSource::SPtr source) : _source(std::move(source)) { - details::utils::assertPtr(_source); + details::Require::notNull(_source); } INLINE void JsonFileConfigurationProvider::load() { set(getJsonFromFile()); } INLINE JsonObject JsonFileConfigurationProvider::getJsonFromFile() { - if (!std::filesystem::exists(_source->getFilePath())) + auto &filePath = _source->getFilePath(); + if (!std::filesystem::exists(filePath)) { if (_source->getIsOptional()) { return JsonObject{}; } - throw ConfigFileNotFoundException(_source->getFilePath()); + throw ConfigFileNotFoundException(filePath); } - auto json = tao::json::basic_from_file(_source->getFilePath()); + auto json = tao::json::basic_from_file(filePath); if (!json.is_object()) { - throw BadConfigFileException(_source->getFilePath(), "file does not contain json object"); + throw BadConfigFileException(filePath, "file does not contain json object"); } return json.get_object(); } diff --git a/Include/SevenBit/Conf/Details/Impl/JsonStreamConfiguration.hpp b/Include/SevenBit/Conf/Sources/Impl/JsonStreamConfiguration.hpp similarity index 90% rename from Include/SevenBit/Conf/Details/Impl/JsonStreamConfiguration.hpp rename to Include/SevenBit/Conf/Sources/Impl/JsonStreamConfiguration.hpp index b738163..918edd7 100644 --- a/Include/SevenBit/Conf/Details/Impl/JsonStreamConfiguration.hpp +++ b/Include/SevenBit/Conf/Sources/Impl/JsonStreamConfiguration.hpp @@ -2,9 +2,9 @@ #include -#include "SevenBit/Conf/Details/Utils.hpp" +#include "SevenBit/Conf/Details/Require.hpp" #include "SevenBit/Conf/Exceptions.hpp" -#include "SevenBit/Conf/JsonStreamConfiguration.hpp" +#include "SevenBit/Conf/Sources/JsonStreamConfiguration.hpp" namespace sb::cf { @@ -25,7 +25,7 @@ namespace sb::cf INLINE JsonStreamConfigurationProvider::JsonStreamConfigurationProvider(JsonStreamConfigurationSource::SPtr source) : _source(std::move(source)) { - details::utils::assertPtr(_source); + details::Require::notNull(_source); } INLINE void JsonStreamConfigurationProvider::load() { set(getJsonFromStream()); } diff --git a/Include/SevenBit/Conf/Details/Impl/KeyPerFileConfiguration.hpp b/Include/SevenBit/Conf/Sources/Impl/KeyPerFileConfiguration.hpp similarity index 59% rename from Include/SevenBit/Conf/Details/Impl/KeyPerFileConfiguration.hpp rename to Include/SevenBit/Conf/Sources/Impl/KeyPerFileConfiguration.hpp index fe11e54..1ed5047 100644 --- a/Include/SevenBit/Conf/Details/Impl/KeyPerFileConfiguration.hpp +++ b/Include/SevenBit/Conf/Sources/Impl/KeyPerFileConfiguration.hpp @@ -1,25 +1,25 @@ #pragma once -#include "SevenBit/Conf/ChainedConfiguration.hpp" #include "SevenBit/Conf/Details/JsonExt.hpp" -#include "SevenBit/Conf/Details/Utils.hpp" -#include "SevenBit/Conf/JsonFileConfiguration.hpp" -#include "SevenBit/Conf/KeyPerFileConfiguration.hpp" -#include "SevenBit/Conf/MapConfiguration.hpp" +#include "SevenBit/Conf/Details/StringUtils.hpp" +#include "SevenBit/Conf/Sources/ChainedConfiguration.hpp" +#include "SevenBit/Conf/Sources/JsonFileConfiguration.hpp" +#include "SevenBit/Conf/Sources/KeyPerFileConfiguration.hpp" +#include "SevenBit/Conf/Sources/MapConfiguration.hpp" namespace sb::cf { INLINE KeyPerFileConfigurationSource::KeyPerFileConfigurationSource(std::filesystem::path directoryPath, bool isOptional, std::string ignorePrefix) - : _directoryPath(std::move(directoryPath)), _isOptional(isOptional), _ignorePrefix(std::move(ignorePrefix)) + : _directoryPath(std::move(directoryPath)), _ignorePrefix(std::move(ignorePrefix)), _isOptional(isOptional) { } INLINE KeyPerFileConfigurationSource::KeyPerFileConfigurationSource( std::filesystem::path directoryPath, bool isOptional, std::function ignoreCondition) - : _directoryPath(std::move(directoryPath)), _isOptional(isOptional), - _ignoreCondition(std::move(ignoreCondition)) + : _directoryPath(std::move(directoryPath)), _ignoreCondition(std::move(ignoreCondition)), + _isOptional(isOptional) { } @@ -65,31 +65,53 @@ namespace sb::cf } for (auto const &entry : std::filesystem::directory_iterator{directoryPath}) { - const auto &filePath = entry.path(); - if (!entry.is_regular_file() || canIgnore(filePath)) + if (auto source = tryGetMappedFileSource(entry)) { - continue; + sources.add(source); } - auto extension = filePath.extension(); + } + return sources.build(builder); + } + + INLINE IConfigurationSource::SPtr KeyPerFileConfigurationSource::tryGetMappedFileSource( + const std::filesystem::directory_entry &entry) const + { + if (const auto &filePath = entry.path(); entry.is_regular_file()) + { + if (auto fileSource = tryGetFileSource(filePath)) + { + auto mapFcn = [name = filePath.stem().generic_string()](const JsonObject &config) -> JsonObject { + auto res = JsonObject{}; + const auto keys = details::StringUtils::split(name, "__"); + auto &optimized = const_cast(config); + details::JsonExt::updateWith(res, keys, std::move(optimized)); + return res; + }; + return MapConfigurationSource::create(std::move(fileSource), std::move(mapFcn)); + } + } + return nullptr; + } + + INLINE IConfigurationSource::SPtr KeyPerFileConfigurationSource::tryGetFileSource( + const std::filesystem::path &filePath) const + { + if (!canIgnore(filePath)) + { + const auto &extension = filePath.extension(); if (extension == ".json") { - auto fileSource = JsonFileConfigurationSource::create(filePath); - auto mapSource = MapConfigurationSource::create( - std::move(fileSource), [name = filePath.stem().generic_string()](JsonObject config) -> JsonObject { - auto res = JsonObject{}; - details::JsonExt::deepGetOrOverride(res, details::utils::split(name, "__")) = std::move(config); - return res; - }); - sources.add(mapSource); + return JsonFileConfigurationSource::create(filePath); } } - return sources.build(builder); + return nullptr; } INLINE bool KeyPerFileConfigurationSource::canIgnore(const std::filesystem::path &filePath) const { auto &ignorePrefix = getIgnorePrefix(); - if (!ignorePrefix.empty() && details::utils::startsWith(filePath.filename().generic_string(), ignorePrefix)) + if (!ignorePrefix.empty() && + details::StringUtils::startsWith(filePath.filename().generic_string(), ignorePrefix)) { return true; } @@ -100,5 +122,4 @@ namespace sb::cf } return false; } - } // namespace sb::cf diff --git a/Include/SevenBit/Conf/Details/Impl/MapConfiguration.hpp b/Include/SevenBit/Conf/Sources/Impl/MapConfiguration.hpp similarity index 60% rename from Include/SevenBit/Conf/Details/Impl/MapConfiguration.hpp rename to Include/SevenBit/Conf/Sources/Impl/MapConfiguration.hpp index 09f9b7d..a100490 100644 --- a/Include/SevenBit/Conf/Details/Impl/MapConfiguration.hpp +++ b/Include/SevenBit/Conf/Sources/Impl/MapConfiguration.hpp @@ -1,24 +1,27 @@ #pragma once -#include "SevenBit/Conf/Details/Utils.hpp" -#include "SevenBit/Conf/MapConfiguration.hpp" +#include "SevenBit/Conf/Details/Require.hpp" +#include "SevenBit/Conf/Sources/MapConfiguration.hpp" namespace sb::cf { INLINE MapConfigurationSource::MapConfigurationSource(IConfigurationSource::SPtr source, - std::function mapFcn) + std::function mapFcn) : _source(std::move(source)), _mapFcn(std::move(mapFcn)) { - details::utils::assertPtr(_source); + details::Require::notNull(_source); } - INLINE MapConfigurationSource::SPtr MapConfigurationSource::create(IConfigurationSource::SPtr source, - std::function mapFcn) + INLINE MapConfigurationSource::SPtr MapConfigurationSource::create( + IConfigurationSource::SPtr source, std::function mapFcn) { return MapConfigurationSource::SPtr(new MapConfigurationSource{std::move(source), std::move(mapFcn)}); } - INLINE const std::function &MapConfigurationSource::getMapFcn() const { return _mapFcn; } + INLINE const std::function &MapConfigurationSource::getMapFcn() const + { + return _mapFcn; + } INLINE IConfigurationProvider::Ptr MapConfigurationSource::build(IConfigurationBuilder &builder) { @@ -29,15 +32,15 @@ namespace sb::cf IConfigurationProvider::Ptr innerProvider) : _source(std::move(source)), _innerProvider(std::move(innerProvider)) { - details::utils::assertPtr(_source); - details::utils::assertPtr(_innerProvider); + details::Require::notNull(_source); + details::Require::notNull(_innerProvider); } INLINE void MapConfigurationProvider::load() { - details::utils::assertPtr(_source); - details::utils::assertPtr(_innerProvider); + details::Require::notNull(_source); + details::Require::notNull(_innerProvider); _innerProvider->load(); - set(_source->getMapFcn()(std::move(_innerProvider->getConfiguration()))); + set(_source->getMapFcn()(_innerProvider->getConfiguration())); } } // namespace sb::cf diff --git a/Include/SevenBit/Conf/InMemoryConfiguration.hpp b/Include/SevenBit/Conf/Sources/InMemoryConfiguration.hpp similarity index 83% rename from Include/SevenBit/Conf/InMemoryConfiguration.hpp rename to Include/SevenBit/Conf/Sources/InMemoryConfiguration.hpp index 7773bfd..b933867 100644 --- a/Include/SevenBit/Conf/InMemoryConfiguration.hpp +++ b/Include/SevenBit/Conf/Sources/InMemoryConfiguration.hpp @@ -7,8 +7,8 @@ #include "SevenBit/Conf/LibraryConfig.hpp" -#include "SevenBit/Conf/ConfigurationProviderBase.hpp" #include "SevenBit/Conf/IConfigurationSource.hpp" +#include "SevenBit/Conf/Sources/ConfigurationProviderBase.hpp" namespace sb::cf { @@ -28,9 +28,9 @@ namespace sb::cf IConfigurationProvider::Ptr build(IConfigurationBuilder &builder) override; - [[nodiscard]] auto begin() { return _settings.begin(); } + [[nodiscard]] auto begin() const { return _settings.begin(); } - [[nodiscard]] auto end() { return _settings.end(); } + [[nodiscard]] auto end() const { return _settings.end(); } }; class EXPORT InMemoryConfigurationProvider : public ConfigurationProviderBase @@ -46,5 +46,5 @@ namespace sb::cf } // namespace sb::cf #ifdef _7BIT_CONF_ADD_IMPL -#include "SevenBit/Conf/Details/Impl/InMemoryConfiguration.hpp" +#include "SevenBit/Conf/Sources/Impl/InMemoryConfiguration.hpp" #endif diff --git a/Include/SevenBit/Conf/JsonConfiguration.hpp b/Include/SevenBit/Conf/Sources/JsonConfiguration.hpp similarity index 90% rename from Include/SevenBit/Conf/JsonConfiguration.hpp rename to Include/SevenBit/Conf/Sources/JsonConfiguration.hpp index e7f667c..df676b9 100644 --- a/Include/SevenBit/Conf/JsonConfiguration.hpp +++ b/Include/SevenBit/Conf/Sources/JsonConfiguration.hpp @@ -4,8 +4,8 @@ #include "SevenBit/Conf/LibraryConfig.hpp" -#include "SevenBit/Conf/ConfigurationProviderBase.hpp" #include "SevenBit/Conf/IConfigurationSource.hpp" +#include "SevenBit/Conf/Sources/ConfigurationProviderBase.hpp" namespace sb::cf { @@ -41,5 +41,5 @@ namespace sb::cf } // namespace sb::cf #ifdef _7BIT_CONF_ADD_IMPL -#include "SevenBit/Conf/Details/Impl/JsonConfiguration.hpp" +#include "SevenBit/Conf/Sources/Impl/JsonConfiguration.hpp" #endif diff --git a/Include/SevenBit/Conf/JsonFileConfiguration.hpp b/Include/SevenBit/Conf/Sources/JsonFileConfiguration.hpp similarity index 91% rename from Include/SevenBit/Conf/JsonFileConfiguration.hpp rename to Include/SevenBit/Conf/Sources/JsonFileConfiguration.hpp index 274483c..0316cc7 100644 --- a/Include/SevenBit/Conf/JsonFileConfiguration.hpp +++ b/Include/SevenBit/Conf/Sources/JsonFileConfiguration.hpp @@ -5,8 +5,8 @@ #include "SevenBit/Conf/LibraryConfig.hpp" -#include "SevenBit/Conf/ConfigurationProviderBase.hpp" #include "SevenBit/Conf/IConfigurationSource.hpp" +#include "SevenBit/Conf/Sources/ConfigurationProviderBase.hpp" namespace sb::cf { @@ -48,5 +48,5 @@ namespace sb::cf } // namespace sb::cf #ifdef _7BIT_CONF_ADD_IMPL -#include "SevenBit/Conf/Details/Impl/JsonFileConfiguration.hpp" +#include "SevenBit/Conf/Sources/Impl/JsonFileConfiguration.hpp" #endif diff --git a/Include/SevenBit/Conf/JsonStreamConfiguration.hpp b/Include/SevenBit/Conf/Sources/JsonStreamConfiguration.hpp similarity index 90% rename from Include/SevenBit/Conf/JsonStreamConfiguration.hpp rename to Include/SevenBit/Conf/Sources/JsonStreamConfiguration.hpp index 38540dd..7369502 100644 --- a/Include/SevenBit/Conf/JsonStreamConfiguration.hpp +++ b/Include/SevenBit/Conf/Sources/JsonStreamConfiguration.hpp @@ -4,8 +4,8 @@ #include "SevenBit/Conf/LibraryConfig.hpp" -#include "SevenBit/Conf/ConfigurationProviderBase.hpp" #include "SevenBit/Conf/IConfigurationSource.hpp" +#include "SevenBit/Conf/Sources/ConfigurationProviderBase.hpp" namespace sb::cf { @@ -44,5 +44,5 @@ namespace sb::cf } // namespace sb::cf #ifdef _7BIT_CONF_ADD_IMPL -#include "SevenBit/Conf/Details/Impl/JsonStreamConfiguration.hpp" +#include "SevenBit/Conf/Sources/Impl/JsonStreamConfiguration.hpp" #endif diff --git a/Include/SevenBit/Conf/KeyPerFileConfiguration.hpp b/Include/SevenBit/Conf/Sources/KeyPerFileConfiguration.hpp similarity index 83% rename from Include/SevenBit/Conf/KeyPerFileConfiguration.hpp rename to Include/SevenBit/Conf/Sources/KeyPerFileConfiguration.hpp index 7ffe796..96910f8 100644 --- a/Include/SevenBit/Conf/KeyPerFileConfiguration.hpp +++ b/Include/SevenBit/Conf/Sources/KeyPerFileConfiguration.hpp @@ -7,8 +7,8 @@ #include "SevenBit/Conf/LibraryConfig.hpp" -#include "SevenBit/Conf/ConfigurationProviderBase.hpp" #include "SevenBit/Conf/IConfigurationSource.hpp" +#include "SevenBit/Conf/Sources/ConfigurationProviderBase.hpp" namespace sb::cf { @@ -47,10 +47,15 @@ namespace sb::cf IConfigurationProvider::Ptr build(IConfigurationBuilder &builder) override; private: + [[nodiscard]] IConfigurationSource::SPtr tryGetMappedFileSource( + std::filesystem::directory_entry const &entry) const; + + [[nodiscard]] IConfigurationSource::SPtr tryGetFileSource(const std::filesystem::path &filePath) const; + [[nodiscard]] bool canIgnore(const std::filesystem::path &filePath) const; }; } // namespace sb::cf #ifdef _7BIT_CONF_ADD_IMPL -#include "SevenBit/Conf/Details/Impl/KeyPerFileConfiguration.hpp" +#include "SevenBit/Conf/Sources/Impl/KeyPerFileConfiguration.hpp" #endif diff --git a/Include/SevenBit/Conf/MapConfiguration.hpp b/Include/SevenBit/Conf/Sources/MapConfiguration.hpp similarity index 77% rename from Include/SevenBit/Conf/MapConfiguration.hpp rename to Include/SevenBit/Conf/Sources/MapConfiguration.hpp index 736090f..582949d 100644 --- a/Include/SevenBit/Conf/MapConfiguration.hpp +++ b/Include/SevenBit/Conf/Sources/MapConfiguration.hpp @@ -5,9 +5,9 @@ #include "SevenBit/Conf/LibraryConfig.hpp" -#include "SevenBit/Conf/ConfigurationProviderBase.hpp" #include "SevenBit/Conf/IConfigurationSource.hpp" #include "SevenBit/Conf/Json.hpp" +#include "SevenBit/Conf/Sources/ConfigurationProviderBase.hpp" namespace sb::cf { @@ -16,18 +16,18 @@ namespace sb::cf { private: IConfigurationSource::SPtr _source; - std::function _mapFcn; + std::function _mapFcn; - MapConfigurationSource(IConfigurationSource::SPtr source, std::function mapFcn); + MapConfigurationSource(IConfigurationSource::SPtr source, std::function mapFcn); public: using Ptr = std::unique_ptr; using SPtr = std::shared_ptr; [[nodiscard]] static SPtr create(IConfigurationSource::SPtr source, - std::function mapFcn); + std::function mapFcn); - [[nodiscard]] const std::function &getMapFcn() const; + [[nodiscard]] const std::function &getMapFcn() const; IConfigurationProvider::Ptr build(IConfigurationBuilder &builder) override; }; @@ -46,5 +46,5 @@ namespace sb::cf } // namespace sb::cf #ifdef _7BIT_CONF_ADD_IMPL -#include "SevenBit/Conf/Details/Impl/MapConfiguration.hpp" +#include "SevenBit/Conf/Sources/Impl/MapConfiguration.hpp" #endif diff --git a/README.md b/README.md index 1105b98..889d216 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,13 @@ -[![CI](https://github.com/7bitcoder/7bitConf/actions/workflows/CI.yml/badge.svg?branch=main)](https://github.com/7bitcoder/7bitConf/actions/workflows/CI.yml) ![Conan Center](https://img.shields.io/conan/v/7bitconf) +[![DevCI](https://github.com/7bitcoder/7bitConf/actions/workflows/DevCI.yml/badge.svg?branch=dev)](https://github.com/7bitcoder/7bitConf/actions/workflows/DevCI.yml) +[![Windows](https://github.com/7bitcoder/7bitConf/actions/workflows/Windows.yml/badge.svg?branch=main)](https://github.com/7bitcoder/7bitConf/actions/workflows/Windows.yml) +[![Linux](https://github.com/7bitcoder/7bitConf/actions/workflows/Linux.yml/badge.svg?branch=main)](https://github.com/7bitcoder/7bitConf/actions/workflows/Linux.yml) +[![MacOs](https://github.com/7bitcoder/7bitConf/actions/workflows/MacOs.yml/badge.svg?branch=main)](https://github.com/7bitcoder/7bitConf/actions/workflows/MacOs.yml) +[![Conan Center](https://img.shields.io/conan/v/7bitconf)](https://conan.io/center/recipes/7bitconf)
logo -

C++17 configuration provider library!

+

C++17 centralized configuration provider library!

@@ -16,6 +20,7 @@ - [Built With](#built-with) - [Supported Platforms](#supported-platforms) - [Installation](#installation) + - [Using Cmake Fetch Content Api](#using-cmake-fetch-content-api---recommended) - [Using Conan Package Manager](#using-conan-package-manager) - [Header Only](#header-only) - [Header Only Single File](#header-only-single-file) @@ -33,9 +38,11 @@ - [Json Object](#json-object) - [In Memory](#in-memory) - [Custom Configuration Source](#custom-configuration-source) -- [Setting Parser Config](#setting-parser-config) - - [Usage Scenario](#usage-scenario) -- [Custom Settings Parser](#custom-setting-parser) +- [Command Line Parser Config](#command-line-parser-config) + - [Cmd Config Usage Scenario](#cmd-config-usage-scenario) +- [Environment Variables Parser Config](#environment-variables-parser-config) + - [Config Usage Scenario](#env-config-usage-scenario) +- [Custom Parsers](#custom-parsers) - [Advanced Usage Scenario](#advanced-usage-scenario) - [Build Library](#build-library) - [Build Library With Conan](#build-library-with-conan) @@ -69,20 +76,33 @@ The library is officially supported on the following platforms: **Compilers:** - gcc 8.0+ -- clang 7.0+ +- clang 8.0+ - MSVC 2015+ ## Installation -All options, except Conan, require [taocpp json](https://github.com/taocpp/json) version 1.0.0-beta.14 to be already -installed. +### Using Cmake Fetch Content Api - Recommended -#### Using Conan Package Manager - Recommended +Update CMakeLists.txt file with the following code + +```cmake +include(FetchContent) +FetchContent_Declare( + 7bitConf + GIT_REPOSITORY https://github.com/7bitcoder/7bitConf.git + GIT_TAG v1.2.0 +) +FetchContent_MakeAvailable(7bitConf) + +target_link_libraries(Target 7bitConf::7bitConf) +``` + +### Using Conan Package Manager Download and install [Conan.io](https://conan.io/downloads.html) then install [package](https://conan.io/center/recipes/7bitconf), see Conan documentation for the package installation guide. -#### Header Only +### Header Only Download source code from the most recent release and copy the include folder into your project location, for example, copy into the '/SevenBitConf' folder. Include this folder into the project, example with [CMake](https://cmake.org/): @@ -91,14 +111,14 @@ copy into the '/SevenBitConf' folder. Include this folder into the project, exam include_directories(SevenBitConf/Include) ``` -#### Header Only Single File +### Header Only Single File -Download SevenBitConf.hpp header file from the most recent release, copy this file into desired project location and -include it. +Download SevenBitConf.hpp header file from the most recent release, copy this file into the desired project location +and include it. -#### Building Library Locally +### Building Library Locally -Download source code from the most recent release, build or install the project using [CMake](https://cmake.org/), +Download source code from the most recent release, and build or install the project using [CMake](https://cmake.org/), for more details, see the [Building Library](#build-library) guide. ## Usage @@ -131,17 +151,17 @@ Create the appsettings.json file in the compiled executable directory: using namespace sb::cf; -int main(int argc, char **argv) +int main(const int argc, char **argv) { - IConfiguration::Ptr configuration = ConfigurationBuilder{} // - .addAppSettings() - .addEnvironmentVariables() - .addCommandLine(argc, argv) - .build(); + const IConfiguration::Ptr configuration = ConfigurationBuilder{} // + .addAppSettings() + .addEnvironmentVariables() + .addCommandLine(argc, argv) + .build(); - std::string value = configuration->at("MySetting").get_string(); - std::string defaultLogLevel = configuration->deepAt("Logging:LogLevel:Default").get_string(); - std::uint64_t secondArrayElement = configuration->deepAt("Array:1").get_unsigned(); + const std::string value = configuration->at("MySetting").get_string(); + const std::string defaultLogLevel = configuration->deepAt("Logging:LogLevel:Default").get_string(); + const std::uint64_t secondArrayElement = configuration->deepAt("Array:1").get_unsigned(); std::cout << "MySetting: " << value << std::endl; std::cout << "Default LogLevel: " << defaultLogLevel << std::endl; @@ -169,12 +189,16 @@ The command line configuration source is added using the addCommandLine(argc, ar auto configuration = ConfigurationBuilder{}.addCommandLine(argc, argv).build(); ``` -**Argument pattern:** [--]setting[:nestedSetting|arrayIndex...][!type]=[value] +**Argument patterns:** + +- option[:nestedOption|arrayIndex...][!type]=[value] +- --option[:nestedOption|arrayIndex...][!type] [value] +- /option[:nestedOption|arrayIndex...][!type] [value] -Setting the prefix '--' is optional. Nested settings are supported using the ':' separator. If the object is an array, -numbers can be used to address the proper element. By default, setting values are saved as strings, but other types are -also supported using the '!' mark. If a value is not provided, the default one will be used for the specified type, -see [supported types](#supported-types). +Prefix ('--' or '/') is optional when the value is separated using '='. Nested settings are supported using the ':' +separator. If the object is an array, numbers can be used to address the proper element. By default, setting values are +saved as strings, but other types are also supported using the '!' mark. If a value is not provided, the default one +will be used for the specified type, see [supported types](#supported-types). Some arguments might be filtered using an overloaded method that accepts std::vector\ This example shows how to pass only arguments that start with "--SETTING": @@ -192,11 +216,11 @@ auto configuration = ConfigurationBuilder{}.addCommandLine(configArgs).build(); ``` The command line configuration source can be more customized with the additional addCommandLine method -arguments: [SettingParserConfig](#setting-parser-config) or [SettingsParser](#custom-setting-parser). +arguments: [CommandLineParserConfig](#command-line-parser-config) or [SettingParser](#custom-parsers). #### Supported Types -type (default value) - Description +type (default value) - description - string ("") - default type, could be specified explicitly. - uint (0) - unsigned 64-bit integer. @@ -208,13 +232,15 @@ type (default value) - Description #### Example Command Line Arguments -- --MySetting="hello" - will override or create a MySetting setting with the "hello" string value. -- --Switch!bool=true - will override or create a Switch setting with a true bool value. +- MySetting - will override or create a MySetting setting with the default "" string value. +- MySetting="hello" - will override or create a MySetting setting with the "hello" string value. +- Switch!bool=true - will override or create a Switch setting with a true bool value. +- --MySetting hello - will override or create a MySetting setting with the "hello" string value. - --Offset!double - will override or create an Offset setting with the default 0.0 double value. -- --Logging:LogLevel:Default=Warning - will override or create a nested setting with the string "Warning" value. -- --Strings:2=hello - will override or create a third element in the Strings array setting with the string "hello" +- --Logging:LogLevel:Default Warning - will override or create a nested setting with the string "Warning" value. +- /Strings:2 hello - will override or create a third element in the Strings array setting with the string "hello" value. -- --Array:1!uint=123 will override the second element in Array with the unsigned integer 123 value. +- /Array:1!uint 123 will override the second element in Array with the unsigned integer 123 value. ### Environment Variables @@ -241,9 +267,9 @@ is '\_\_' (double underscore) and for '!' is '\_\_\_' (triple underscore). Setting Array:2!uint=123 would be rewritten as Array\_\_2\_\_\_uint=123 -Same as command line source, environment variables configuration source can be more customized with -additional addEnvironmentVariables method arguments: [SettingParserConfig](#setting-parser-config) -or [SettingsParser](#custom-setting-parser). +Same as the command line source, the environment variables configuration source can be more customized with +additional addEnvironmentVariables method arguments: [EnvironmentVarsParserConfig](#environment-variables-parser-config) +or [SettingParser](#custom-parsers). ### Json File @@ -373,15 +399,12 @@ using namespace sb::cf; class CustomConfigurationProvider : public IConfigurationProvider { - private: JsonObject _configuration; public: void load() override { _configuration = {{"mysettingOne", "value1"}, {"mysettingTwo", "value2"}}; } - JsonObject &getConfiguration() override { return _configuration; } - - const JsonObject &getConfiguration() const override { return _configuration; } + [[nodiscard]] const JsonObject &getConfiguration() const override { return _configuration; } }; class CustomConfigurationSource : public IConfigurationSource @@ -395,7 +418,7 @@ class CustomConfigurationSource : public IConfigurationSource int main(int argc, char **argv) { - IConfiguration::Ptr configuration = + const IConfiguration::Ptr configuration = ConfigurationBuilder{}.add(std::make_unique()).build(); std::cout << "Configuration json:" << std::endl << std::setw(2) << *configuration; @@ -404,16 +427,73 @@ int main(int argc, char **argv) } ``` -## Setting Parser Config +## Command Line Parser Config + +CommandLineParserConfig is a simple struct that contains the data used to configure the command line +parser, by default it is initialized with these values: + +```cpp +struct CommandLineParserConfig +{ + std::vector optionPrefixes = {"--", "/"}; + std::vector optionSplitters = {"=", " "}; + std::vector keySplitters = {":"}; + std::vector typeMarkers = {"!"}; + std::string_view defaultType = "string"; + bool throwOnUnknownType = true; + bool allowEmptyKeys = false; +}; +``` + +This configuration allows specifying different behaviors of the command line parser. + +Example option option:values:2!int=123 each part is marked as affected with () + +- optionPrefixes - list of possible option prefixes: (--)option:values:2!int=123 +- optionSplitters - list of possible option splitters: --option:values:2!int(=)123 +- keySplitters - list of possible key splitters: --option(:)values(:)2!int=123 +- typeMarkers - list of possible type markers: --option:values:2(!)int=123 +- defaultType - is the type that is used if the type was not specified explicitly in a variable: --option:values:2=123 +- throwOnUnknownType - if the type was not recognized in the parsing phase then an exception will be thrown if in this + case this setting is set to false default type will be used: --option:values:2!nonExistingType=123 +- allowEmptyKeys - if set to true and empty keys are detected, an exception will be thrown: --option::2!int=123 + +### Cmd Config Usage Scenario -SettingParserConfig is a simple struct that contains the data used to configure the setting parser, by default it is -initialized with these values: +All options should be loaded without considering the type and with the custom option prefix '//', solution: ```cpp -struct SettingParserConfig +#include +#include + +using namespace sb::cf; + +int main(const int argc, char **argv) { - std::vector settingPrefixes = {"--"}; - std::vector settingSplitters = {"="}; + CommandLineParserConfig parserConfig; + parserConfig.optionPrefixes = {"//"}; + parserConfig.typeMarkers.clear(); + + const IConfiguration::Ptr configuration = ConfigurationBuilder{} // + .addAppSettings() + .addCommandLine(argc, argv, std::move(parserConfig)) + .build(); + + std::cout << "Configuration json:" << std::endl << std::setw(2) << *configuration; + + return 0; +} +``` + +## Environment Variables Parser Config + +EnvironmentVarsParserConfig is a similar struct to the command line parser config with one difference there is no way to +set variable prefixes: + +```cpp +struct EnvironmentVarsParserConfig +{ + std::vector variableSplitters = {"="}; std::vector keySplitters = {":", "__"}; std::vector typeMarkers = {"!", "___"}; std::string_view defaultType = "string"; @@ -422,20 +502,25 @@ struct SettingParserConfig }; ``` -This configuration allows specifying different behaviors of the parser. +This configuration allows specifying different behaviors of the environment variable parser. -Example setting --option:values:1!int=123 each part is marked as affected with () +Example environment variable option\_\_values\_\_2\_\_\_int=123 each part is marked as affected with () -- settingPrefixes - list of possible setting prefixes (--)option:values:1!int=123 -- settingSplitters - list of possible setting splitters --option:values:1!int(=)123 -- keySplitters - list of possible key splitters --option(:)values(:)1!int=123 -- typeMarkers - list of possible type markers --option:values:1(!)int=123 -- defaultType - is the type that is used if the type was not specified in setting --option:values:1=123 +- variableSplitters - list of possible variable splitters: option\_\_values\_\_2\_\_\_int(=)123 +- keySplitters - list of possible key splitters: option(\_\_)values(\_\_)2\_\_\_int=123 +- typeMarkers - list of possible type markers: option\_\_values\_\_2(\_\_\_)int=123 +- defaultType - is the type that is used if the type was not specified explicitly in a variable: optionvalues2=123 - throwOnUnknownType - if the type was not recognized in the parsing phase then an exception will be thrown if in this - case this setting is set to false default type will be used --option:values:1!nonExistingType=123 -- allowEmptyKeys - if set to true and empty keys are detected, an exception will be thrown --option::1!int=123 + case this setting is set to false default type will be used: option\_\_values\_\_2\_\_\_nonExistingType=123 +- allowEmptyKeys - if set to true and empty keys are detected, an exception will be thrown: option\_\_\_\_2\_\_\_int=123 -### Usage Scenario +Some system environment variables are prefixed with double underscore \_\_, in that case, the environment parser by +default will throw an exception because this prefix will be treated as a key splitter resulting in the empty first part +of keys, example \_\_SOME_SYSTEM_ENV=value will be parsed to "" and "SOME_SYSTEM_ENV" keys, in that case, set +allowEmptyKeys to true or disable keys splitting with clearing keySplitters config value, or filter out unwanted +variables with prefix passed to addEnvironmentVariables method + +### Env Config Usage Scenario All environment variables should be loaded as not nested objects (no key splitting) and without considering the type, solution: @@ -448,15 +533,15 @@ using namespace sb::cf; int main(int argc, char **argv) { - SettingParserConfig envParserConfig; - envParserConfig.keySplitters.clear(); - envParserConfig.typeMarkers.clear(); + EnvironmentVarsParserConfig parserConfig; + parserConfig.keySplitters.clear(); + parserConfig.typeMarkers.clear(); - IConfiguration::Ptr configuration = ConfigurationBuilder{} // - .addAppSettings() - .addEnvironmentVariables("", std::move(envParserConfig)) - .addCommandLine(argc, argv) - .build(); + const IConfiguration::Ptr configuration = ConfigurationBuilder{} // + .addAppSettings() + .addEnvironmentVariables("", std::move(parserConfig)) + .addCommandLine(argc, argv) + .build(); std::cout << "Configuration json:" << std::endl << std::setw(2) << *configuration; @@ -464,18 +549,16 @@ int main(int argc, char **argv) } ``` -In this case, custom SettingParserConfig is used in addEnvironmentVariables method, keySplitters is cleared to prevent -extracting nested keys and typeMarkers is cleared to prevent type extraction. - -## Custom Setting Parser +## Custom Parsers -The library provides a SettingParserBuilder to create customized SettingParser, builder allows using custom value -deserializer, config, settingSplitter, and valueDeserializersMap. +The library provides a CommandLineParserBuilder and EnvironmentVarsParserBuilder builder classes to create customized +parsers for command line and environment variables, builder allows using custom value deserializer, config, +settingSplitter, and valueDeserializersMap. ### Advanced Usage Scenario -All command line options should be loaded as not nested objects (no key splitting) with consideration of new type -"myType" will set the option value to "emptyValue" string if no value was provided, myType should be used as the default +All command line options should be loaded as not nested objects (no key splitting) with consideration of the new type " +myType" will set the option value to "emptyValue" string if no value was provided, myType should be used as the default type, additional setting prefix should be considered '//' and default type should be also used if the type was not recognized, solution: @@ -487,28 +570,31 @@ using namespace sb::cf; struct MyTypeDeserializer final : IDeserializer { - JsonValue deserialize(std::optional value) const final { return value ? value : "emptyValue"; } + [[nodiscard]] JsonValue deserialize(std::optional value) const override + { + return value ? value : "emptyValue"; + } }; -int main(int argc, char **argv) +int main(const int argc, char **argv) { - SettingParserConfig envParserConfig; - envParserConfig.keySplitters.clear(); - envParserConfig.settingPrefixes.emplace_back("//"); - envParserConfig.defaultType = "myType"; - envParserConfig.throwOnUnknownType = false; - - ISettingParser::Ptr settingParser = SettingParserBuilder{} // - .useConfig(std::move(envParserConfig)) - .useDefaultValueDeserializers() - .useValueDeserializer("myType", std::make_unique()) - .build(); - - IConfiguration::Ptr configuration = ConfigurationBuilder{} // - .addAppSettings() - .addEnvironmentVariables() - .addCommandLine(argc, argv, std::move(settingParser)) - .build(); + auto builderFunc = [](CommandLineParserBuilder &builder) { + CommandLineParserConfig parserConfig; + parserConfig.keySplitters.clear(); + parserConfig.optionPrefixes.emplace_back("//"); + parserConfig.defaultType = "myType"; + parserConfig.throwOnUnknownType = false; + + builder.useConfig(std::move(parserConfig)) + .useDefaultValueDeserializers() + .useValueDeserializer("myType", std::make_unique()); + }; + + const IConfiguration::Ptr configuration = ConfigurationBuilder{} // + .addAppSettings() + .addEnvironmentVariables() + .addCommandLine(argc, argv, builderFunc) + .build(); std::cout << "Configuration json:" << std::endl << std::setw(2) << *configuration; @@ -517,16 +603,16 @@ int main(int argc, char **argv) ``` In this case, keySplitters is cleared to prevent extracting nested keys, the additional setting prefix is added '//', -and default type is changed to custom type "myType", and with throwOnUnknownType set to false instead of throwing an -exception when type is not recognized, default will be used. SettingParserBuilder is being used to create custom -settingParser with new config and custom valueDeserializer for type "myType", useDefaultValueDeserializers is used to -add predefined value deserializers (string, int, json ...). +and the default type is changed to custom type "myType", and with throwOnUnknownType set to false instead of throwing an +exception when type is not recognized, default will be used. CommandLineParserBuilder is being used to create a custom +command line parser with new config and custom valueDeserializer for type "myType", useDefaultValueDeserializers is used +to add predefined value deserializers (string, int, json ...). ## Build Library The library can be built locally using [Cmake](https://cmake.org/), library -requires [Taocpp JSON](https://github.com/taocpp/json) to be already installed, the easiest way is -to [build a library with Conan](#build-library-with-conan). +uses [Taocpp JSON](https://github.com/taocpp/json) if the library is not found it will be downloaded using Cmake fetch +api Create a build directory and navigate to it: @@ -544,16 +630,17 @@ Using this command, several cache variables can be set: - \: [possible values] (default value) - Description - \_7BIT_CONF_LIBRARY_TYPE: ["Shared", "Static", "HeaderOnly"] ("Static") - Library build type -- \_7BIT_CONF_BUILD_TESTS: ["ON", "OFF"] ("OFF") - Turn on to build tests ( - requires [Gtest](https://google.github.io/googletest/) to be installed, - see [Build Library With Conan](#build-library-with-conan)) +- \_7BIT_CONF_BUILD_UNIT_TESTS: ["ON", "OFF"] ("OFF") - Turn on to build unit tests +- \_7BIT_CONF_BUILD_INTEGRATION_TESTS: ["ON", "OFF"] ("OFF") - Turn on to build integration tests +- \_7BIT_CONF_BUILD_E2E_TESTS: ["ON", "OFF"] ("OFF") - Turn on to build e2e tests +- \_7BIT_CONF_BUILD_ALL_TESTS: ["ON", "OFF"] ("OFF") - Turn on to build all tests (unit, integration and e2e) - \_7BIT_CONF_BUILD_EXAMPLES: ["ON", "OFF"] ("OFF") - Turn on to build examples - \_7BIT_CONF_BUILD_SINGLE_HEADER: ["ON", "OFF"] ("OFF") - Turn on to build single header SevenBitConf.hpp (requires Quom to be installed) - \_7BIT_CONF_INSTALL: ["ON", "OFF"] ("OFF") - Turn on to install the library -To set cache variable, pass additional option: -D\=[value], -for example, this command will set the library type to Static and will force examples built +To set the cache variable, pass the additional option: -D\=[value], for example, this command will +set the library type to Static and will force examples built ```sh cmake .. -DCMAKE_BUILD_TYPE=Release -D_7BIT_CONF_LIBRARY_TYPE=Static -D_7BIT_CONF_BUILD_EXAMPLES=true @@ -565,40 +652,6 @@ Build the library using the command: cmake --build . ``` -### Build Library With Conan - -Gtest library is added to the project using the Conan package -manager ([Conan Installation](https://conan.io/downloads.html)), -If Conan was freshly installed, run the detect command: - -```sh -conan profile detect -``` - -To install Conan packages, run this command in the library root folder: - -```sh -conan install . --output-folder=build --build=missing -``` - -Navigate to the build directory: - -```sh -cd build -``` - -Configure the CMake project and add the toolchain file as a CMAKE_TOOLCHAIN_FILE cache variable: - -```sh -cmake .. -DCMAKE_TOOLCHAIN_FILE:PATH="./conan_toolchain.cmake" -DCMAKE_BUILD_TYPE=Release -D_7BIT_CONF_BUILD_TESTS=ON -``` - -Build the library using the command: - -```sh -cmake --build . -``` - ### Install Library To install the library, set the additional cache variables \_7BIT_CONF_INSTALL=ON and specify the installation directory diff --git a/SingleHeader/CMakeLists.txt b/SingleHeader/CMakeLists.txt index b1cf051..8da7d25 100644 --- a/SingleHeader/CMakeLists.txt +++ b/SingleHeader/CMakeLists.txt @@ -5,7 +5,7 @@ set(_7BIT_CONF_SINGLE_OUT ${CMAKE_CURRENT_BINARY_DIR}/SevenBitConf.hpp) add_custom_command(OUTPUT ${_7BIT_CONF_SINGLE_OUT} COMMAND - ${QUOM_EXECUTABLE} ${_7BIT_CONF_SINGLE_IN} ${_7BIT_CONF_SINGLE_OUT} -I ${_7BIT_CONF_HEADERS_DIR} + ${QUOM_EXECUTABLE} ${_7BIT_CONF_SINGLE_IN} ${_7BIT_CONF_SINGLE_OUT} -I ${_7BIT_CONF_INCLUDE_DIR} DEPENDS ${_7BIT_CONF_ALL_HEADERS} COMMENT "Generating single header with Quom") diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index 0957235..6cdab6c 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -1,36 +1,43 @@ include(GenerateExportHeader) +set(PEGTL_USE_BOOST_FILESYSTEM OFF) +find_package(taocpp-json QUIET) + +if (NOT taocpp-json_FOUND) + include(FetchContent) + + FetchContent_Declare( + taocpp-json + GIT_REPOSITORY https://github.com/taocpp/json.git + GIT_TAG 1.0.0-beta.14 + OVERRIDE_FIND_PACKAGE + ) + FetchContent_MakeAvailable(taocpp-json) +endif () + find_package(taocpp-json REQUIRED) if (_7BIT_CONF_SHARED_LIB) - add_library(7bitConf SHARED - Source.cpp ${_7BIT_CONF_ALL_HEADERS} - ) + add_library(7bitConf SHARED Source.cpp) elseif (_7BIT_CONF_STATIC_LIB) - add_library(7bitConf STATIC - Source.cpp ${_7BIT_CONF_ALL_HEADERS} - ) + add_library(7bitConf STATIC Source.cpp) elseif (_7BIT_CONF_HEADER_ONLY_LIB) add_library(7bitConf INTERFACE) endif () if (_7BIT_CONF_HEADER_ONLY_LIB) - target_link_libraries(7bitConf INTERFACE - taocpp::json - ) + target_link_libraries(7bitConf INTERFACE taocpp-json) else () - target_link_libraries(7bitConf - taocpp::json - ) + target_link_libraries(7bitConf taocpp-json) endif () add_library(7bitConf::7bitConf ALIAS 7bitConf) -target_include_directories( - 7bitConf INTERFACE - $ - $) - set_target_properties(7bitConf PROPERTIES VERSION ${_7BIT_CONF_VERSION}) set_target_properties(7bitConf PROPERTIES DEBUG_POSTFIX d) + +string(REPLACE ";" "$" dirs "${_7BIT_CONF_INCLUDE_DIR}") +target_include_directories(7bitConf INTERFACE + $ + "$/${CMAKE_INSTALL_INCLUDEDIR}>") diff --git a/Source/Source.cpp b/Source/Source.cpp index 9600b44..c23ee7b 100644 --- a/Source/Source.cpp +++ b/Source/Source.cpp @@ -1,25 +1,27 @@ #include "ConfigCheck.hpp" -#include "SevenBit/Conf/Details/Impl/AppSettingsConfiguration.hpp" -#include "SevenBit/Conf/Details/Impl/ChainedConfiguration.hpp" -#include "SevenBit/Conf/Details/Impl/CommandLineConfiguration.hpp" +#include "SevenBit/Conf/Details/Impl/CommandLineParser.hpp" #include "SevenBit/Conf/Details/Impl/Configuration.hpp" -#include "SevenBit/Conf/Details/Impl/ConfigurationBuilder.hpp" -#include "SevenBit/Conf/Details/Impl/ConfigurationManager.hpp" -#include "SevenBit/Conf/Details/Impl/ConfigurationProviderBase.hpp" +#include "SevenBit/Conf/Details/Impl/DefaultDeserializers.hpp" #include "SevenBit/Conf/Details/Impl/Deserializers.hpp" -#include "SevenBit/Conf/Details/Impl/EnvironmentVarsConfiguration.hpp" -#include "SevenBit/Conf/Details/Impl/Exceptions.hpp" -#include "SevenBit/Conf/Details/Impl/InMemoryConfiguration.hpp" -#include "SevenBit/Conf/Details/Impl/JsonConfiguration.hpp" -#include "SevenBit/Conf/Details/Impl/JsonFileConfiguration.hpp" -#include "SevenBit/Conf/Details/Impl/JsonObjectExt.hpp" -#include "SevenBit/Conf/Details/Impl/JsonStreamConfiguration.hpp" -#include "SevenBit/Conf/Details/Impl/KeyPerFileConfiguration.hpp" -#include "SevenBit/Conf/Details/Impl/MapConfiguration.hpp" -#include "SevenBit/Conf/Details/Impl/SettingParser.hpp" -#include "SevenBit/Conf/Details/Impl/SettingParserBuilder.hpp" +#include "SevenBit/Conf/Details/Impl/EnvironmentVarsParser.hpp" +#include "SevenBit/Conf/Details/Impl/JsonExt.hpp" #include "SevenBit/Conf/Details/Impl/SettingSplitter.hpp" -#include "SevenBit/Conf/Details/Impl/Utils.hpp" +#include "SevenBit/Conf/Details/Impl/StringUtils.hpp" #include "SevenBit/Conf/Details/Impl/ValueDeserializersMap.hpp" +#include "SevenBit/Conf/Impl/CommandLineParserBuilder.hpp" +#include "SevenBit/Conf/Impl/ConfigurationBuilder.hpp" +#include "SevenBit/Conf/Impl/EnvironmentVarsParserBuilder.hpp" +#include "SevenBit/Conf/Impl/Exceptions.hpp" +#include "SevenBit/Conf/Sources/Impl/AppSettingsConfiguration.hpp" +#include "SevenBit/Conf/Sources/Impl/ChainedConfiguration.hpp" +#include "SevenBit/Conf/Sources/Impl/CommandLineConfiguration.hpp" +#include "SevenBit/Conf/Sources/Impl/ConfigurationProviderBase.hpp" +#include "SevenBit/Conf/Sources/Impl/EnvironmentVarsConfiguration.hpp" +#include "SevenBit/Conf/Sources/Impl/InMemoryConfiguration.hpp" +#include "SevenBit/Conf/Sources/Impl/JsonConfiguration.hpp" +#include "SevenBit/Conf/Sources/Impl/JsonFileConfiguration.hpp" +#include "SevenBit/Conf/Sources/Impl/JsonStreamConfiguration.hpp" +#include "SevenBit/Conf/Sources/Impl/KeyPerFileConfiguration.hpp" +#include "SevenBit/Conf/Sources/Impl/MapConfiguration.hpp" diff --git a/Tests/AppSettingsConfigurationTest.cpp b/Tests/AppSettingsConfigurationTest.cpp deleted file mode 100644 index a68fcdc..0000000 --- a/Tests/AppSettingsConfigurationTest.cpp +++ /dev/null @@ -1,48 +0,0 @@ -#include -#include - -#include "Mocks/ConfigurationBuilderMock.hpp" -#include "SevenBit/Conf/AppSettingsConfiguration.hpp" - -class AppSettingsConfiguration : public testing::Test -{ - protected: - ConfigurationBuilderMock mock; - - static void TearUpTestSuite() {} - - AppSettingsConfiguration() {} - - void SetUp() override {} - - void TearDown() override {} - - static void TearDownTestSuite() {} -}; - -TEST_F(AppSettingsConfiguration, ShouldLoadAppSettings) -{ - auto provider = sb::cf::AppSettingsConfigurationSource::create()->build(mock); - - provider->load(); - - sb::cf::JsonObject expected = {{"Array", sb::cf::JsonArray{1, 2, 3, 4, 5}}, - {"MySetting", "appsettings.json Value"}, - {"Logging", {{"LogLevel", {{"Default", "Information"}}}}}}; - - EXPECT_EQ(provider->getConfiguration(), expected); -} - -TEST_F(AppSettingsConfiguration, ShouldLoadDevAppSettings) -{ - auto provider = sb::cf::AppSettingsConfigurationSource::create("dev")->build(mock); - - provider->load(); - - sb::cf::JsonObject expected = {{"Array", sb::cf::JsonArray{11, 2, 3, 4, 5}}, - {"MySetting", "appsettings.dev.json Value"}, - {"ExtraSetting", "extra appsettings.dev.json Value"}, - {"Logging", {{"LogLevel", {{"Default", "Warning"}}}}}}; - - EXPECT_EQ(provider->getConfiguration(), expected); -} diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 6208239..00b51cc 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -1,25 +1,29 @@ -find_package(GTest REQUIRED) +include(FetchContent) +FetchContent_Declare( + googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG v1.14.0 +) +set(gtest_build_tests OFF) +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +FetchContent_MakeAvailable(googletest) -file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS *.cpp) +include_directories(./Helpers) -add_executable(Tests - ${SOURCES} -) +if (_7BIT_CONF_BUILD_UNIT_TESTS) + add_subdirectory(Unit) +endif () -target_link_libraries(Tests PUBLIC - GTest::gtest - GTest::gmock - 7bitConf -) +if (_7BIT_CONF_BUILD_INTEGRATION_TESTS) + add_subdirectory(Integration) +endif () -file(GLOB_RECURSE FILES CONFIGURE_DEPENDS Files/*) -file(GLOB_RECURSE FILES_DIR CONFIGURE_DEPENDS Files/Directory/*) +if (_7BIT_CONF_BUILD_E2E_TESTS) + add_subdirectory(E2E) +endif () + +file(GLOB_RECURSE FILES CONFIGURE_DEPENDS ./Helpers/Files/*) +file(GLOB_RECURSE FILES_DIR CONFIGURE_DEPENDS ./Helpers/Files/Directory/*) file(COPY ${FILES} DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) file(COPY ${FILES_DIR} DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Directory) - -file(COPY ${FILES} DESTINATION ${CMAKE_BINARY_DIR}) -file(COPY ${FILES_DIR} DESTINATION ${CMAKE_BINARY_DIR}/Directory) - -gtest_discover_tests(Tests - WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) diff --git a/Tests/CommandLineConfigurationTest.cpp b/Tests/CommandLineConfigurationTest.cpp deleted file mode 100644 index e726e67..0000000 --- a/Tests/CommandLineConfigurationTest.cpp +++ /dev/null @@ -1,93 +0,0 @@ -#include -#include - -#include "Mocks/ConfigurationBuilderMock.hpp" -#include "SevenBit/Conf/CommandLineConfiguration.hpp" -#include "SevenBit/Conf/Exceptions.hpp" - -class CommandLineConfigurationTest : public testing::Test -{ - protected: - ConfigurationBuilderMock mock; - - static void TearUpTestSuite() {} - - CommandLineConfigurationTest() {} - - void SetUp() override {} - - void TearDown() override {} - - static void TearDownTestSuite() {} -}; - -TEST_F(CommandLineConfigurationTest, ShouldFailCreationDueToNullSource) -{ - std::vector args; - EXPECT_THROW(auto result = sb::cf::CommandLineConfigurationSource::create(args, nullptr), - sb::cf::NullPointerException); -} - -TEST_F(CommandLineConfigurationTest, ShouldLoadConfFromArgs) -{ - const char *const argv[] = { - "program/path", "--string___string=test", "--list:0!string=string", "double!double=1.22", - "true_bool!bool=-1", "false_bool!bool=0", "false_bool2!bool=false", "true_bool2!bool=true", - "--list:1=string1", "list__2!string=string2", "--object__inner:object=string", "--int_list:0___int=33", - "--int_list:1___int=22", "int_list__2!int=11", - }; - int size = sizeof(argv) / sizeof(char *); - auto provider = sb::cf::CommandLineConfigurationSource::create(size, argv)->build(mock); - - provider->load(); - - sb::cf::JsonObject expected = {{"string", "test"}, - {"double", 1.22}, - {"false_bool", false}, - {"false_bool2", false}, - {"true_bool", true}, - {"true_bool2", true}, - {"list", sb::cf::JsonArray{"string", "string1", "string2"}}, - {"int_list", sb::cf::JsonArray{33, 22, 11}}, - {"object", {{"inner", {{"object", "string"}}}}}}; - - EXPECT_EQ(provider->getConfiguration(), expected); -} - -TEST_F(CommandLineConfigurationTest, ShouldLoadConfFromArgsVector) -{ - std::vector args = { - "--string___string=test", "--list:0!string=string", "double!double=1.22", "true_bool!bool=-1", - "false_bool!bool=0", "false_bool2!bool=false", "true_bool2!bool=true", "--list:1=string1", - "list__2!string=string2", "--object__inner:object=string", "--int_list:0___int=33", "--int_list:1___int=22", - "int_list__2!int=11", - }; - auto provider = sb::cf::CommandLineConfigurationSource::create(args)->build(mock); - - provider->load(); - - sb::cf::JsonObject expected = {{"string", "test"}, - {"double", 1.22}, - {"false_bool", false}, - {"false_bool2", false}, - {"true_bool", true}, - {"true_bool2", true}, - {"list", sb::cf::JsonArray{"string", "string1", "string2"}}, - {"int_list", sb::cf::JsonArray{33, 22, 11}}, - {"object", {{"inner", {{"object", "string"}}}}}}; - - EXPECT_EQ(provider->getConfiguration(), expected); -} - -TEST_F(CommandLineConfigurationTest, ShouldLoadEmptyConfFromArgs) -{ - const char *argv[] = { - "exec/path", - }; - int size = sizeof(argv) / sizeof(char *); - auto provider = sb::cf::CommandLineConfigurationSource::create(size, argv)->build(mock); - - provider->load(); - - EXPECT_TRUE(provider->getConfiguration().empty()); -} diff --git a/Tests/ConfigurationTest.cpp b/Tests/ConfigurationTest.cpp deleted file mode 100644 index 3b9989c..0000000 --- a/Tests/ConfigurationTest.cpp +++ /dev/null @@ -1,94 +0,0 @@ -#include -#include - -#include "SevenBit/Conf/ConfigurationBuilder.hpp" - -class ConfigurationTest : public testing::Test -{ - protected: - static void TearUpTestSuite() {} - - ConfigurationTest() {} - - void SetUp() override {} - - void TearDown() override {} - - static void TearDownTestSuite() {} -}; - -TEST_F(ConfigurationTest, ShouldLoadConfig) -{ - auto conf = sb::cf::ConfigurationBuilder{} - .addAppSettings("dev") - .addJson({{"string", 1}}) - .addCommandLine({"--string=2", "Array:0!int=33"}) - .AddInMemory("set:set", 44444) - .addKeyPerFile("Directory") - .build(); - - sb::cf::JsonObject expected = {{"Array", sb::cf::JsonArray{33, 2, 3, 4, 5}}, - {"MySetting", "appsettings.dev.json Value"}, - {"ExtraSetting", "extra appsettings.dev.json Value"}, - {"Logging", {{"LogLevel", {{"Default", "Warning"}}}}}, - {"set", {{"set", 44444}}}, - {"string", "2"}, - {"settingOne", - {{"number", 12345}, - {"array", sb::cf::JsonArray{1, 2, 3, 4, 5, 6}}, - {"string", "string"}, - {"object", {{"num", 134}, {"string", "string"}}}}}, - {"settingTwo", - {{"array", sb::cf::JsonArray{1}}, - {"string", "stringdev"}, - {"object", {{"inner", {{"num", 12345}}}, {"string", "stringdev"}}}}}}; - - EXPECT_EQ(conf->root(), expected); -} - -TEST_F(ConfigurationTest, ShouldFindConfgValues) -{ - auto conf = sb::cf::ConfigurationBuilder{} - .addAppSettings("dev") - .addJson({{"string", 1}}) - .addCommandLine({"--string=2", "Array:0!int=33"}) - .AddInMemory("set:set", 44444) - .addKeyPerFile("Directory") - .build(); - - EXPECT_TRUE(conf->find("Array")); - EXPECT_TRUE(conf->find("MySetting")); - EXPECT_TRUE(conf->deepFind("settingOne:number")); - EXPECT_TRUE(conf->deepFind({"settingTwo", "array", "0"})); - EXPECT_TRUE(conf->deepFind("settingTwo:array:0")); - EXPECT_EQ(conf->at("Array"), (sb::cf::JsonArray{33, 2, 3, 4, 5})); - EXPECT_EQ(conf->deepAt("settingOne:number"), (std::int64_t{12345})); - EXPECT_EQ(conf->deepAt("settingTwo:array:0"), (std::int64_t{1})); - EXPECT_EQ(conf->operator[]("settingTwo:array:0"), (std::int64_t{1})); - EXPECT_EQ(conf->operator[]({"settingTwo", "array", "0"}), (std::int64_t{1})); -} - -TEST_F(ConfigurationTest, ShouldSerializeValues) -{ - auto conf = sb::cf::ConfigurationBuilder{} - .addJson({{"string", 1}}) - .addCommandLine({"--string=2", "Array:0!int=33"}) - .AddInMemory("set:set", 44444) - .build(); - - std::string expected = R"({"Array":[33,2,3,4,5],"MySetting":""})"; - std::ostringstream stream; - - EXPECT_EQ(conf->toString(), R"({ - "Array": [ - 33 - ], - "set": { - "set": 44444 - }, - "string": "2" -})"); - EXPECT_EQ(conf->toString(0, ""), R"({"Array": [33],"set": {"set": 44444},"string": "2"})"); - stream << *conf; - EXPECT_EQ(stream.str(), R"({"Array":[33],"set":{"set":44444},"string":"2"})"); -} diff --git a/Tests/E2E/CMakeLists.txt b/Tests/E2E/CMakeLists.txt new file mode 100644 index 0000000..2935de4 --- /dev/null +++ b/Tests/E2E/CMakeLists.txt @@ -0,0 +1,9 @@ +add_executable(Echo echo.cpp) + +target_link_libraries(Echo 7bitConf) + +find_package(Python REQUIRED) + +add_test(NAME E2E.Echo + COMMAND ${Python_EXECUTABLE} echo_test.py $ + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/Tests/E2E/echo.cpp b/Tests/E2E/echo.cpp new file mode 100644 index 0000000..ef478d5 --- /dev/null +++ b/Tests/E2E/echo.cpp @@ -0,0 +1,15 @@ +#include + +#include + +using namespace sb::cf; + +int main(const int argc, char **argv) +{ + const IConfiguration::Ptr configuration = ConfigurationBuilder{} // + .addCommandLine(argc, argv) + .addEnvironmentVariables() + .build(); + std::cout << *configuration; + return 0; +} diff --git a/Tests/E2E/echoTestData.json b/Tests/E2E/echoTestData.json new file mode 100644 index 0000000..9c5aa49 --- /dev/null +++ b/Tests/E2E/echoTestData.json @@ -0,0 +1,87 @@ +[ + { + "args": [ + "arg=123" + ], + "env": {}, + "expected": { + "arg": "123" + } + }, + { + "args": [ + "--arg", + "123" + ], + "env": {}, + "expected": { + "arg": "123" + } + }, + { + "args": [ + "--arg", + "123", + "/arg2", + "12", + "arg3=321", + "arg4=2", + "arg5", + "--arg6" + ], + "env": {}, + "expected": { + "arg": "123", + "arg2": "12", + "arg3": "321", + "arg4": "2", + "arg5": "", + "arg6": "" + } + }, + { + "args": [ + "--str", + "123", + "/number:nested!int", + "12", + "unsignedNumber!uint=321", + "flag!bool=true", + "--json!json", + "[1,2,3,4,5,6]", + "double=12.345", + "--null!null" + ], + "env": { + "number2___int": "321", + "json__1___int": "321", + "flag": "false", + "newOption:0:master!string": "mystring", + "number__nested___int": "3333" + }, + "expected": { + "double": "12.345", + "flag": "false", + "json": [ + 1, + 321, + 3, + 4, + 5, + 6 + ], + "newOption": [ + { + "master": "mystring" + } + ], + "null": null, + "number": { + "nested": 3333 + }, + "number2": 321, + "str": "123", + "unsignedNumber": 321 + } + } +] diff --git a/Tests/E2E/echo_test.py b/Tests/E2E/echo_test.py new file mode 100644 index 0000000..a74fe50 --- /dev/null +++ b/Tests/E2E/echo_test.py @@ -0,0 +1,60 @@ +import json +import os +import subprocess +import sys + + +def get_echo_exec_path(): + if len(sys.argv) != 2: + raise Exception("Echo executable not provided") + echo_exec = sys.argv[1] + if not os.path.exists(echo_exec): + raise Exception("Echo executable does not exist") + return echo_exec + + +class EchoTest: + def __init__(self, echo_exec_path): + self.echoExecPath = echo_exec_path + self.tests_data = self.__get_tests_data() + + @staticmethod + def __get_tests_data(): + with open('echoTestData.json') as data: + return json.load(data) + + def __run_test(self, args, env, expected_json): + result = subprocess.run([self.echoExecPath, *args], env=env, capture_output=True, text=True) + if result.returncode: + raise Exception(f"test returned non zero code {result.returncode}") + output = json.dumps(json.loads(result.stdout)) + expected = json.dumps(expected_json) + if expected != output: + raise Exception(f"result of running test: '{output}' does not match expected: '{expected}'") + + def __run_test_and_summarize(self, test_data): + args = test_data["args"] + env = test_data["env"] + expected_json = test_data["expected"] + try: + self.__run_test(args, env, expected_json) + print(f"Test for args: {args}, env: {env} succeeded") + return True + except Exception as e: + print(f"Test for args: {args}, env: {env} failed: {e}") + return False + + def run(self): + all_tests = len(self.tests_data) + succeeded_tests = 0 + for count, testData in enumerate(self.tests_data, start=1): + print(f"Test {count}/{all_tests}") + succeeded_tests += self.__run_test_and_summarize(testData) + if succeeded_tests == all_tests: + print(f"All test succeeded: {succeeded_tests}/{all_tests}") + else: + raise Exception(f"Some tests failed: {all_tests - succeeded_tests}/{all_tests}") + + +if __name__ == "__main__": + EchoTest(get_echo_exec_path()).run() diff --git a/Tests/Files/Directory/settingTwo.json b/Tests/Files/Directory/settingTwo.json deleted file mode 100644 index cf81edc..0000000 --- a/Tests/Files/Directory/settingTwo.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "array": [1], - "string": "stringdev", - "object": { - "string": "stringdev", - "inner": { - "num": 12345 - } - } -} diff --git a/Tests/Classes/CustomConfigSource.hpp b/Tests/Helpers/Classes/CustomConfigSource.hpp similarity index 85% rename from Tests/Classes/CustomConfigSource.hpp rename to Tests/Helpers/Classes/CustomConfigSource.hpp index c581dcf..77289ee 100644 --- a/Tests/Classes/CustomConfigSource.hpp +++ b/Tests/Helpers/Classes/CustomConfigSource.hpp @@ -3,12 +3,13 @@ #include "SevenBit/Conf/IConfigurationBuilder.hpp" #include "SevenBit/Conf/IConfigurationProvider.hpp" #include "SevenBit/Conf/IConfigurationSource.hpp" -#include "SevenBit/Conf/JsonConfiguration.hpp" #include "SevenBit/Conf/ObjectHolder.hpp" +#include "SevenBit/Conf/Sources/JsonConfiguration.hpp" + class CustomConfigSource : public sb::cf::IConfigurationSource { - sb::cf::IConfigurationProvider::Ptr build(sb::cf::IConfigurationBuilder &builder) + sb::cf::IConfigurationProvider::Ptr build(sb::cf::IConfigurationBuilder &builder) override { sb::cf::ObjectHolder::safeCastFrom(*builder.getProperties()["counter"]).get()++; diff --git a/Tests/Files/Directory/settingOne.json b/Tests/Helpers/Files/Directory/settingOne.json similarity index 100% rename from Tests/Files/Directory/settingOne.json rename to Tests/Helpers/Files/Directory/settingOne.json diff --git a/Tests/Helpers/Files/Directory/settingTwo.json b/Tests/Helpers/Files/Directory/settingTwo.json new file mode 100644 index 0000000..06f63f3 --- /dev/null +++ b/Tests/Helpers/Files/Directory/settingTwo.json @@ -0,0 +1,12 @@ +{ + "array": [ + 1 + ], + "string": "dev", + "object": { + "string": "dev", + "inner": { + "num": 12345 + } + } +} diff --git a/Tests/Files/appsettings.dev.json b/Tests/Helpers/Files/appsettings.dev.json similarity index 100% rename from Tests/Files/appsettings.dev.json rename to Tests/Helpers/Files/appsettings.dev.json diff --git a/Tests/Files/appsettings.json b/Tests/Helpers/Files/appsettings.json similarity index 100% rename from Tests/Files/appsettings.json rename to Tests/Helpers/Files/appsettings.json diff --git a/Tests/Files/bad.json b/Tests/Helpers/Files/bad.json similarity index 100% rename from Tests/Files/bad.json rename to Tests/Helpers/Files/bad.json diff --git a/Tests/Mocks/ConfigurationBuilderMock.hpp b/Tests/Helpers/Mocks/ConfigurationBuilderMock.hpp similarity index 100% rename from Tests/Mocks/ConfigurationBuilderMock.hpp rename to Tests/Helpers/Mocks/ConfigurationBuilderMock.hpp diff --git a/Tests/Mocks/DeserializerMock.hpp b/Tests/Helpers/Mocks/DeserializerMock.hpp similarity index 100% rename from Tests/Mocks/DeserializerMock.hpp rename to Tests/Helpers/Mocks/DeserializerMock.hpp diff --git a/Tests/Mocks/SettingSplitterMock.hpp b/Tests/Helpers/Mocks/SettingSplitterMock.hpp similarity index 100% rename from Tests/Mocks/SettingSplitterMock.hpp rename to Tests/Helpers/Mocks/SettingSplitterMock.hpp diff --git a/Tests/Mocks/ValueDeserializersMapMock.hpp b/Tests/Helpers/Mocks/ValueDeserializersMapMock.hpp similarity index 55% rename from Tests/Mocks/ValueDeserializersMapMock.hpp rename to Tests/Helpers/Mocks/ValueDeserializersMapMock.hpp index 1fe4350..4c9f0fe 100644 --- a/Tests/Mocks/ValueDeserializersMapMock.hpp +++ b/Tests/Helpers/Mocks/ValueDeserializersMapMock.hpp @@ -6,5 +6,6 @@ struct ValueDeserializersMapMock : public sb::cf::IValueDeserializersMap { - MOCK_METHOD((const sb::cf::IDeserializer *), getDeserializerFor, (std::string_view), (const override)); + MOCK_METHOD((const sb::cf::IDeserializer &), getDeserializerFor, (std::optional), + (const override)); }; diff --git a/Tests/Utilities/ParamsTest.hpp b/Tests/Helpers/Utilities/ParamsTest.hpp similarity index 100% rename from Tests/Utilities/ParamsTest.hpp rename to Tests/Helpers/Utilities/ParamsTest.hpp diff --git a/Tests/Integration/CMakeLists.txt b/Tests/Integration/CMakeLists.txt new file mode 100644 index 0000000..ecfed9a --- /dev/null +++ b/Tests/Integration/CMakeLists.txt @@ -0,0 +1,15 @@ +file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS *.cpp) + +add_executable(IntegrationTests + ${SOURCES} +) + +target_link_libraries(IntegrationTests PUBLIC + GTest::gtest + GTest::gmock + 7bitConf +) + +include(GoogleTest) +gtest_discover_tests(IntegrationTests + WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) diff --git a/Tests/Integration/ConfigurationTest.cpp b/Tests/Integration/ConfigurationTest.cpp new file mode 100644 index 0000000..7fc9f99 --- /dev/null +++ b/Tests/Integration/ConfigurationTest.cpp @@ -0,0 +1,93 @@ +#include + +#include "SevenBit/Conf/ConfigurationBuilder.hpp" + +class ConfigurationTest : public testing::Test +{ + protected: + static void TearUpTestSuite() {} + + ConfigurationTest() {} + + void SetUp() override {} + + void TearDown() override {} + + static void TearDownTestSuite() {} +}; + +TEST_F(ConfigurationTest, ShouldLoadConfig) +{ + const auto conf = sb::cf::ConfigurationBuilder{} + .addAppSettings("dev") + .addJson({{"string", 1}}) + .addCommandLine({"--string", "2", "Array:0!int=33"}) + .AddInMemory("set:set", 44444) + .addKeyPerFile("Directory") + .build(); + + const sb::cf::JsonObject expected = {{"Array", sb::cf::JsonArray{33, 2, 3, 4, 5}}, + {"MySetting", "appsettings.dev.json Value"}, + {"ExtraSetting", "extra appsettings.dev.json Value"}, + {"Logging", {{"LogLevel", {{"Default", "Warning"}}}}}, + {"set", {{"set", 44444}}}, + {"string", "2"}, + {"settingOne", + {{"number", 12345}, + {"array", sb::cf::JsonArray{1, 2, 3, 4, 5, 6}}, + {"string", "string"}, + {"object", {{"num", 134}, {"string", "string"}}}}}, + {"settingTwo", + {{"array", sb::cf::JsonArray{1}}, + {"string", "dev"}, + {"object", {{"inner", {{"num", 12345}}}, {"string", "dev"}}}}}}; + + EXPECT_EQ(conf->root(), expected); +} + +TEST_F(ConfigurationTest, ShouldFindConfgValues) +{ + const auto conf = sb::cf::ConfigurationBuilder{} + .addAppSettings("dev") + .addJson({{"string", 1}}) + .addCommandLine({"--string", "2", "Array:0!int=33"}) + .AddInMemory("set:set", 44444) + .addKeyPerFile("Directory") + .build(); + + EXPECT_TRUE(conf->find("Array")); + EXPECT_TRUE(conf->find("MySetting")); + EXPECT_TRUE(conf->deepFind("settingOne:number")); + EXPECT_TRUE(conf->deepFind({"settingTwo", "array", "0"})); + EXPECT_TRUE(conf->deepFind("settingTwo:array:0")); + EXPECT_EQ(conf->at("Array"), (sb::cf::JsonArray{33, 2, 3, 4, 5})); + EXPECT_EQ(conf->deepAt("settingOne:number"), std::int64_t{12345}); + EXPECT_EQ(conf->deepAt("settingTwo:array:0"), std::int64_t{1}); + EXPECT_EQ(conf->operator[]("settingTwo:array:0"), std::int64_t{1}); + EXPECT_EQ(conf->operator[]({"settingTwo", "array", "0"}), std::int64_t{1}); +} + +TEST_F(ConfigurationTest, ShouldSerializeValues) +{ + const auto conf = sb::cf::ConfigurationBuilder{} + .addJson({{"string", 1}}) + .addCommandLine({"--string", "2", "Array:0!int=33"}) + .AddInMemory("set:set", 44444) + .build(); + + std::string expected = R"({"Array":[33,2,3,4,5],"MySetting":""})"; + std::ostringstream stream; + + EXPECT_EQ(conf->toString(), R"({ + "Array": [ + 33 + ], + "set": { + "set": 44444 + }, + "string": "2" +})"); + EXPECT_EQ(conf->toString(0, ""), R"({"Array": [33],"set": {"set": 44444},"string": "2"})"); + stream << *conf; + EXPECT_EQ(stream.str(), R"({"Array":[33],"set":{"set":44444},"string":"2"})"); +} diff --git a/Tests/Integration/RunTests.cpp b/Tests/Integration/RunTests.cpp new file mode 100644 index 0000000..245b9f5 --- /dev/null +++ b/Tests/Integration/RunTests.cpp @@ -0,0 +1,10 @@ +#include + +int main(int argc, char *argv[]) +{ + // Run a specific test only + // testing::GTEST_FLAG(filter) = "Template.*"; + + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} \ No newline at end of file diff --git a/Tests/TestTemplate.cpp b/Tests/Integration/TestTemplate.cpp similarity index 73% rename from Tests/TestTemplate.cpp rename to Tests/Integration/TestTemplate.cpp index a6fc38a..c8f294e 100644 --- a/Tests/TestTemplate.cpp +++ b/Tests/Integration/TestTemplate.cpp @@ -1,5 +1,4 @@ #include -#include class Template : public testing::Test { @@ -12,9 +11,9 @@ class Template : public testing::Test void TearDown() override {} - ~Template() {} + ~Template() override = default; static void TearDownTestSuite() {} }; -TEST_F(Template, ExampleTest) {} +TEST_F(Template, ExampleTest) { EXPECT_TRUE(true); } diff --git a/Tests/KeyPerFileConfigurationTest.cpp b/Tests/KeyPerFileConfigurationTest.cpp deleted file mode 100644 index ca3249b..0000000 --- a/Tests/KeyPerFileConfigurationTest.cpp +++ /dev/null @@ -1,87 +0,0 @@ -#include -#include - -#include "Mocks/ConfigurationBuilderMock.hpp" -#include "SevenBit/Conf/KeyPerFileConfiguration.hpp" - -class KeyPerFileConfigurationTest : public testing::Test -{ - protected: - ConfigurationBuilderMock mock; - - static void TearUpTestSuite() {} - - KeyPerFileConfigurationTest() {} - - void SetUp() override {} - - void TearDown() override {} - - static void TearDownTestSuite() {} -}; - -TEST_F(KeyPerFileConfigurationTest, ShouldFailLoadingNonExistingDirectory) -{ - auto source = sb::cf::KeyPerFileConfigurationSource::create("nonexisting"); - - EXPECT_ANY_THROW(source->build(mock)); -} - -TEST_F(KeyPerFileConfigurationTest, ShouldLoadNonExistingDirectory) -{ - auto provider = sb::cf::KeyPerFileConfigurationSource::create("nonexisting", true)->build(mock); - - provider->load(); - - EXPECT_TRUE(provider->getConfiguration().empty()); -} - -TEST_F(KeyPerFileConfigurationTest, ShouldLoadDirectoryConfig) -{ - auto provider = sb::cf::KeyPerFileConfigurationSource::create("Directory")->build(mock); - - provider->load(); - - sb::cf::JsonObject expected = {{"settingOne", - {{"number", 12345}, - {"array", sb::cf::JsonArray{1, 2, 3, 4, 5, 6}}, - {"string", "string"}, - {"object", {{"num", 134}, {"string", "string"}}}}}, - {"settingTwo", - {{"array", sb::cf::JsonArray{1}}, - {"string", "stringdev"}, - {"object", {{"inner", {{"num", 12345}}}, {"string", "stringdev"}}}}}}; - - EXPECT_EQ(provider->getConfiguration(), expected); -} - -TEST_F(KeyPerFileConfigurationTest, ShloudLoadFilteredConfigFiles) -{ - auto provider = sb::cf::KeyPerFileConfigurationSource::create("Directory", false, "settingOne")->build(mock); - - provider->load(); - - sb::cf::JsonObject expected = {{"settingTwo", - {{"array", sb::cf::JsonArray{1}}, - {"string", "stringdev"}, - {"object", {{"inner", {{"num", 12345}}}, {"string", "stringdev"}}}}}}; - - EXPECT_EQ(provider->getConfiguration(), expected); -} - -TEST_F(KeyPerFileConfigurationTest, ShloudLoadFilteredConditionConfigFiles) -{ - auto provider = - sb::cf::KeyPerFileConfigurationSource::create("Directory", false, [](const std::filesystem::path &path) { - return path.filename() == "settingOne.json"; - })->build(mock); - - provider->load(); - - sb::cf::JsonObject expected = {{"settingTwo", - {{"array", sb::cf::JsonArray{1}}, - {"string", "stringdev"}, - {"object", {{"inner", {{"num", 12345}}}, {"string", "stringdev"}}}}}}; - - EXPECT_EQ(provider->getConfiguration(), expected); -} diff --git a/Tests/SettingParserBuilderTest.cpp b/Tests/SettingParserBuilderTest.cpp deleted file mode 100644 index 52a39dc..0000000 --- a/Tests/SettingParserBuilderTest.cpp +++ /dev/null @@ -1,108 +0,0 @@ -#include -#include - -#include "Mocks/DeserializerMock.hpp" -#include "Mocks/SettingSplitterMock.hpp" -#include "Mocks/ValueDeserializersMapMock.hpp" -#include "SevenBit/Conf/Details/SettingParser.hpp" -#include "SevenBit/Conf/Details/SettingSplitter.hpp" -#include "SevenBit/Conf/Details/ValueDeserializersMap.hpp" -#include "SevenBit/Conf/SettingParserBuilder.hpp" - -class SettingParserBuilderTest : public testing::Test -{ - protected: - static void TearUpTestSuite() {} - - SettingParserBuilderTest() {} - - void SetUp() override {} - - void TearDown() override {} - - static void TearDownTestSuite() {} -}; - -TEST_F(SettingParserBuilderTest, ShouldBuildDefault) -{ - sb::cf::SettingParserBuilder builder; - - auto parser = builder.build(); - - auto &casted = dynamic_cast(*parser); - - EXPECT_TRUE(dynamic_cast(&casted.getSettingSplitter())); - EXPECT_TRUE(dynamic_cast(&casted.getValueDeserializersMap())); - EXPECT_EQ(casted.getDefaultType(), "string"); - EXPECT_FALSE(casted.getAllowEmptyKeys()); - EXPECT_TRUE(casted.getThrowOnUnknownType()); -} - -TEST_F(SettingParserBuilderTest, ShouldUseCustomConfig) -{ - sb::cf::SettingParserBuilder builder; - - sb::cf::SettingParserConfig config; - config.defaultType = "int"; - config.throwOnUnknownType = false; - config.allowEmptyKeys = true; - auto parser = builder.useConfig(config).build(); - - auto &casted = dynamic_cast(*parser); - - EXPECT_TRUE(dynamic_cast(&casted.getSettingSplitter())); - EXPECT_TRUE(dynamic_cast(&casted.getValueDeserializersMap())); - EXPECT_EQ(casted.getDefaultType(), "int"); - EXPECT_TRUE(casted.getAllowEmptyKeys()); - EXPECT_FALSE(casted.getThrowOnUnknownType()); -} - -TEST_F(SettingParserBuilderTest, ShouldUseValueDeserializer) -{ - sb::cf::SettingParserBuilder builder; - - auto parser = builder.useDefaultValueDeserializers() - .useValueDeserializer("newType", std::make_unique()) - .build(); - - auto &casted = dynamic_cast(*parser); - - EXPECT_TRUE(dynamic_cast(&casted.getSettingSplitter())); - EXPECT_TRUE(dynamic_cast(&casted.getValueDeserializersMap())); - EXPECT_EQ(casted.getDefaultType(), "string"); - EXPECT_FALSE(casted.getAllowEmptyKeys()); - EXPECT_TRUE(casted.getThrowOnUnknownType()); - EXPECT_TRUE(casted.getValueDeserializersMap().getDeserializerFor("newType")); - EXPECT_TRUE(casted.getValueDeserializersMap().getDeserializerFor("int")); - EXPECT_TRUE(casted.getValueDeserializersMap().getDeserializerFor("bool")); -} - -TEST_F(SettingParserBuilderTest, ShouldUseCustomValueDeserializerMap) -{ - sb::cf::SettingParserBuilder builder; - - auto parser = builder.useValueDeserializersMap(std::make_unique()).build(); - - auto &casted = dynamic_cast(*parser); - - EXPECT_TRUE(dynamic_cast(&casted.getSettingSplitter())); - EXPECT_TRUE(dynamic_cast(&casted.getValueDeserializersMap())); - EXPECT_EQ(casted.getDefaultType(), "string"); - EXPECT_FALSE(casted.getAllowEmptyKeys()); - EXPECT_TRUE(casted.getThrowOnUnknownType()); -} - -TEST_F(SettingParserBuilderTest, ShouldUseCustomSplitter) -{ - sb::cf::SettingParserBuilder builder; - - auto parser = builder.useSplitter(std::make_unique()).build(); - - auto &casted = dynamic_cast(*parser); - - EXPECT_TRUE(dynamic_cast(&casted.getSettingSplitter())); - EXPECT_TRUE(dynamic_cast(&casted.getValueDeserializersMap())); - EXPECT_EQ(casted.getDefaultType(), "string"); - EXPECT_FALSE(casted.getAllowEmptyKeys()); - EXPECT_TRUE(casted.getThrowOnUnknownType()); -} diff --git a/Tests/SettingParserTest.cpp b/Tests/SettingParserTest.cpp deleted file mode 100644 index c4eba41..0000000 --- a/Tests/SettingParserTest.cpp +++ /dev/null @@ -1,182 +0,0 @@ -#include -#include -#include - -#include "Mocks/DeserializerMock.hpp" -#include "Mocks/SettingSplitterMock.hpp" -#include "Mocks/ValueDeserializersMapMock.hpp" -#include "SevenBit/Conf/Details/SettingParser.hpp" -#include "SevenBit/Conf/Exceptions.hpp" - -class SettingParserTest : public testing::Test -{ - protected: - static void TearUpTestSuite() {} - - SettingParserTest() {} - - void SetUp() override {} - - void TearDown() override {} - - static void TearDownTestSuite() {} -}; - -TEST_F(SettingParserTest, ShouldParseSetting) -{ - DeserializerMock deserializer; - auto deserializers = std::make_unique(); - auto splitter = std::make_unique(); - - std::string_view setting = "--option:deep:deep!int=123"; - sb::cf::ISettingSplitter::Result returned = {{"option", "deep", "deep"}, "int", "123"}; - EXPECT_CALL(*splitter, split).WillOnce(testing::Return(returned)); - EXPECT_CALL(*deserializers, getDeserializerFor).WillOnce(testing::Return(&deserializer)); - sb::cf::JsonValue returnedValue = 123; - EXPECT_CALL(deserializer, deserialize).WillOnce(testing::Return(returnedValue)); - - sb::cf::details::SettingParser parser{std::move(splitter), std::move(deserializers), "string", false, true}; - - EXPECT_EQ(parser.parse(setting), (sb::cf::ISettingParser::Result{{"option", "deep", "deep"}, 123})); - EXPECT_EQ(parser.getDefaultType(), "string"); - EXPECT_TRUE(parser.getThrowOnUnknownType()); - EXPECT_FALSE(parser.getAllowEmptyKeys()); -} - -TEST_F(SettingParserTest, ShouldFailCreateSettingParserDueNullSplitter) -{ - sb::cf::ISettingSplitter::Ptr splitter; - auto deserializers = std::make_unique(); - - EXPECT_THROW((sb::cf::details::SettingParser{std::move(splitter), std::move(deserializers), "string", false, true}), - sb::cf::ConfigException); -} - -TEST_F(SettingParserTest, ShouldFailCreateSettingParserDueNullDeserializers) -{ - sb::cf::IValueDeserializersMap::Ptr deserializers; - auto splitter = std::make_unique(); - - EXPECT_THROW((sb::cf::details::SettingParser{std::move(splitter), std::move(deserializers), "string", false, true}), - sb::cf::ConfigException); -} - -TEST_F(SettingParserTest, ShouldUseDefaultType) -{ - DeserializerMock deserializer; - auto deserializers = std::make_unique(); - auto splitter = std::make_unique(); - - std::string_view setting = "--option:deep:deep=value"; - sb::cf::ISettingSplitter::Result returned = {{"option", "deep", "deep"}, std::nullopt, "value"}; - EXPECT_CALL(*splitter, split).WillOnce(testing::Return(returned)); - EXPECT_CALL(*deserializers, getDeserializerFor).WillOnce(testing::Return(&deserializer)); - sb::cf::JsonValue returnedValue = "value"; - EXPECT_CALL(deserializer, deserialize).WillOnce(testing::Return(returnedValue)); - - sb::cf::details::SettingParser parser{std::move(splitter), std::move(deserializers), "string", false, true}; - - EXPECT_EQ(parser.parse(setting), (sb::cf::ISettingParser::Result{{"option", "deep", "deep"}, "value"})); - EXPECT_EQ(parser.getDefaultType(), std::string_view{"string"}); -} - -TEST_F(SettingParserTest, ShouldNotFailCreateSettingParserDueEmptyKey) -{ - DeserializerMock deserializer; - auto deserializers = std::make_unique(); - auto splitter = std::make_unique(); - - std::string_view setting = "--!string=value"; - sb::cf::ISettingSplitter::Result returned = {{""}, "string", "value"}; - EXPECT_CALL(*splitter, split).WillOnce(testing::Return(returned)); - EXPECT_CALL(*deserializers, getDeserializerFor).WillOnce(testing::Return(&deserializer)); - sb::cf::JsonValue returnedValue = "value"; - EXPECT_CALL(deserializer, deserialize).WillOnce(testing::Return(returnedValue)); - - sb::cf::details::SettingParser parser{std::move(splitter), std::move(deserializers), "string", true, true}; - - EXPECT_EQ(parser.parse(setting), (sb::cf::ISettingParser::Result{{""}, "value"})); - EXPECT_EQ(parser.getDefaultType(), "string"); - EXPECT_TRUE(parser.getThrowOnUnknownType()); - EXPECT_TRUE(parser.getAllowEmptyKeys()); -} - -TEST_F(SettingParserTest, ShouldFailCreateSettingParserDueEmptyKey) -{ - DeserializerMock deserializer; - auto deserializers = std::make_unique(); - auto splitter = std::make_unique(); - - std::string_view setting = "--!string=value"; - sb::cf::ISettingSplitter::Result returned = {{""}, "string", "value"}; - EXPECT_CALL(*splitter, split).WillOnce(testing::Return(returned)); - - sb::cf::details::SettingParser parser{std::move(splitter), std::move(deserializers), "string", false, true}; - - EXPECT_THROW(auto result = parser.parse(setting), sb::cf::ConfigException); - EXPECT_EQ(parser.getDefaultType(), "string"); - EXPECT_TRUE(parser.getThrowOnUnknownType()); - EXPECT_FALSE(parser.getAllowEmptyKeys()); -} - -TEST_F(SettingParserTest, ShouldUseDefaultTypeForUnknownType) -{ - DeserializerMock deserializer; - auto deserializers = std::make_unique(); - auto splitter = std::make_unique(); - - std::string_view setting = "--option:deep:deep!unknown=value"; - sb::cf::ISettingSplitter::Result returned = {{"option", "deep", "deep"}, "unknown", "value"}; - EXPECT_CALL(*splitter, split).WillOnce(testing::Return(returned)); - EXPECT_CALL(*deserializers, getDeserializerFor) - .WillOnce(testing::Return(nullptr)) - .WillOnce(testing::Return(&deserializer)); - sb::cf::JsonValue returnedValue = "value"; - EXPECT_CALL(deserializer, deserialize).WillOnce(testing::Return(returnedValue)); - - sb::cf::details::SettingParser parser{std::move(splitter), std::move(deserializers), "string", true, false}; - - EXPECT_EQ(parser.parse(setting), (sb::cf::ISettingParser::Result{{"option", "deep", "deep"}, "value"})); - EXPECT_EQ(parser.getDefaultType(), "string"); - EXPECT_FALSE(parser.getThrowOnUnknownType()); - EXPECT_TRUE(parser.getAllowEmptyKeys()); -} - -TEST_F(SettingParserTest, ShouldFailDueToForUnknownType) -{ - DeserializerMock deserializer; - auto deserializers = std::make_unique(); - auto splitter = std::make_unique(); - - std::string_view setting = "--option:deep:deep!unknown=value"; - sb::cf::ISettingSplitter::Result returned = {{"option", "deep", "deep"}, "unknown", "value"}; - EXPECT_CALL(*splitter, split).WillOnce(testing::Return(returned)); - EXPECT_CALL(*deserializers, getDeserializerFor).WillOnce(testing::Return(nullptr)); - - sb::cf::details::SettingParser parser{std::move(splitter), std::move(deserializers), "string", true, true}; - - EXPECT_THROW(auto result = parser.parse(setting), sb::cf::ConfigException); - EXPECT_EQ(parser.getDefaultType(), "string"); - EXPECT_TRUE(parser.getThrowOnUnknownType()); - EXPECT_TRUE(parser.getAllowEmptyKeys()); -} - -TEST_F(SettingParserTest, ShouldFailDueToForUnknownDefaultType) -{ - DeserializerMock deserializer; - auto deserializers = std::make_unique(); - auto splitter = std::make_unique(); - - std::string_view setting = "--option:deep:deep!unknown=value"; - sb::cf::ISettingSplitter::Result returned = {{"option", "deep", "deep"}, "unknown", "value"}; - EXPECT_CALL(*splitter, split).WillOnce(testing::Return(returned)); - EXPECT_CALL(*deserializers, getDeserializerFor).WillRepeatedly(testing::Return(nullptr)); - sb::cf::JsonValue returnedValue = "value"; - - sb::cf::details::SettingParser parser{std::move(splitter), std::move(deserializers), "string", true, false}; - - EXPECT_THROW(auto result = parser.parse(setting), sb::cf::ConfigException); - EXPECT_EQ(parser.getDefaultType(), "string"); - EXPECT_FALSE(parser.getThrowOnUnknownType()); - EXPECT_TRUE(parser.getAllowEmptyKeys()); -} diff --git a/Tests/SettingSplitterTest.cpp b/Tests/SettingSplitterTest.cpp deleted file mode 100644 index 7c3887c..0000000 --- a/Tests/SettingSplitterTest.cpp +++ /dev/null @@ -1,113 +0,0 @@ -#include -#include - -#include "SevenBit/Conf/Details/SettingSplitter.hpp" -#include "SevenBit/Conf/Details/Utils.hpp" -#include "Utilities/ParamsTest.hpp" - -class SettingSplitterTest : public testing::Test -{ - protected: - static void TearUpTestSuite() {} - - SettingSplitterTest() {} - - void SetUp() override {} - - void TearDown() override {} - - static void TearDownTestSuite() {} -}; - -static Params SplitSettingData = { - {"--", {{""}}}, - {"one", {{"one"}}}, - {"--====", {{""}, std::nullopt, "==="}}, - {"--option!!type=val", {{"option!"}, "type", "val"}}, - {"--option!type=value", {{"option"}, "type", "value"}}, - {"//option___type;value", {{"option"}, "type", "value"}}, - {"--option=value", {{"option"}, std::nullopt, "value"}}, - {"--option=", {{"option"}, std::nullopt, ""}}, - {"--option!!type=", {{"option!"}, "type", ""}}, - {"--option!!type", {{"option!"}, "type"}}, - {"--option!!=type=val", {{"option!"}, "", "type=val"}}, - {"--option!!:inner!=type=val", {{"option!!", "inner"}, "", "type=val"}}, - {"--option!!:inner:::!=type=val", {{"option!!", "inner", "", "", ""}, "", "type=val"}}, - {":option:inner=type=val", {{"", "option", "inner"}, std::nullopt, "type=val"}}, - {"!!option:inner=type=val", {{"!"}, "option:inner", "type=val"}}, - {":option:inner=value", {{"", "option", "inner"}, std::nullopt, "value"}}, - {":option:inner=value::!", {{"", "option", "inner"}, std::nullopt, "value::!"}}, - {":option!!:inner=value::!", {{"", "option!"}, ":inner", "value::!"}}, - {":::!=value::!", {{"", "", "", ""}, "", "value::!"}}, - {"::!:=value::!", {{"", "", ""}, ":", "value::!"}}, - {":=:!:=value::!", {{"", ""}, std::nullopt, ":!:=value::!"}}, - {"__=:!___=value::!", {{"", ""}, std::nullopt, ":!___=value::!"}}, - {"_______=value", {{"", "", ""}, "", "value"}}, - {"__hello_____type=value", {{"", "hello", ""}, "type", "value"}}, - {"__hello__type=value", {{"", "hello", "type"}, std::nullopt, "value"}}, -}; -PARAMS_TEST(SettingSplitterTest, ShouldSplitSetting, SplitSettingData) -{ - const auto &[setting, expected] = GetParam(); - sb::cf::details::SettingSplitter splitter{{"--", "//"}, {"=", ";"}, {"!", "___"}, {":", "__"}}; - - EXPECT_EQ(splitter.split(setting), expected); -} - -static OneParams SettingSplittersData = {"=", ">", "===", "<<<<", "////"}; - -static OneParams SettingPrefixesData = {":", ";", "%%$", "***", "++"}; - -static OneParams KeySplittersData = {":", ";", "__", "{}", "\\"}; - -static OneParams TypeMarkersData = {"!", "@", ":", "[]", "{}"}; - -static Params, std::string, std::string> SplitSettingSimpleData = { - {{"key1", "key2", "key3"}, "string", "value"}, - {{"key1"}, "string", "value"}, - {{""}, "", ""}, - {{""}, "type", ""}, - {{"key"}, "", ""}, - {{""}, "", "value"}, -}; -PARAMS_TEST_COMBINED_5(SettingSplitterTest, ShouldSplitWithDifferentSplitters, SettingPrefixesData, - SettingSplittersData, TypeMarkersData, KeySplittersData, SplitSettingSimpleData) -{ - const auto &[prefix, settingSplitter, typeMarker, keySplitter, setting] = GetParam(); - const auto &[keys, type, value] = setting; - - sb::cf::details::SettingSplitter splitter{{prefix}, {settingSplitter}, {typeMarker}, {keySplitter}}; - - auto key = sb::cf::details::utils::joinViews(keys, keySplitter); - auto fullSetting = prefix + key + typeMarker + type + settingSplitter + value; - EXPECT_EQ(splitter.split(fullSetting), (sb::cf::ISettingSplitter::Result{keys, type, value})); -} - -static Params, std::vector, std::vector, - std::vector, sb::cf::ISettingSplitter::Result> - EmptySplittersData = { - {{}, {"=", ";"}, {"!", "___"}, {":", "__"}, {{"--key", "deep"}, "int", "value"}}, - {{"--", "//"}, {}, {"!", "___"}, {":", "__"}, {{"key", "deep"}, "int=value"}}, - {{"--", "//"}, {"=", ";"}, {}, {":", "__"}, {{"key", "deep!int"}, std::nullopt, "value"}}, - {{"--", "//"}, {"=", ";"}, {"!", "___"}, {}, {{"key:deep"}, "int", "value"}}, - {{}, {}, {"!", "___"}, {":", "__"}, {{"--key", "deep"}, "int=value"}}, - {{}, {"=", ";"}, {}, {":", "__"}, {{"--key", "deep!int"}, std::nullopt, "value"}}, - {{}, {"=", ";"}, {"!", "___"}, {}, {{"--key:deep"}, "int", "value"}}, - {{"--", "//"}, {}, {}, {":", "__"}, {{"key", "deep!int=value"}}}, - {{"--", "//"}, {}, {"!", "___"}, {}, {{"key:deep"}, "int=value"}}, - {{"--", "//"}, {"=", ";"}, {}, {}, {{"key:deep!int"}, std::nullopt, "value"}}, - {{}, {}, {}, {":", "__"}, {{"--key", "deep!int=value"}}}, - {{}, {}, {"!", "___"}, {}, {{"--key:deep"}, "int=value"}}, - {{}, {"=", ";"}, {}, {}, {{"--key:deep!int"}, std::nullopt, "value"}}, - {{"--", "//"}, {}, {}, {}, {{"key:deep!int=value"}}}, - {{}, {}, {}, {}, {{"--key:deep!int=value"}}}, - -}; -PARAMS_TEST(SettingSplitterTest, ShouldSplitWithEmptySplitters, EmptySplittersData) -{ - const auto &[prefixes, settingSplitters, typeMarkers, keySplitters, expected] = GetParam(); - - sb::cf::details::SettingSplitter splitter{prefixes, settingSplitters, typeMarkers, keySplitters}; - - EXPECT_EQ(splitter.split("--key:deep!int=value"), expected); -} diff --git a/Tests/Unit/CMakeLists.txt b/Tests/Unit/CMakeLists.txt new file mode 100644 index 0000000..c7642aa --- /dev/null +++ b/Tests/Unit/CMakeLists.txt @@ -0,0 +1,15 @@ +file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS *.cpp) + +add_executable(UnitTests + ${SOURCES} +) + +target_link_libraries(UnitTests PUBLIC + GTest::gtest + GTest::gmock + 7bitConf +) + +include(GoogleTest) +gtest_discover_tests(UnitTests + WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) diff --git a/Tests/Unit/CommandLineParserBuilderTest.cpp b/Tests/Unit/CommandLineParserBuilderTest.cpp new file mode 100644 index 0000000..986e6d0 --- /dev/null +++ b/Tests/Unit/CommandLineParserBuilderTest.cpp @@ -0,0 +1,132 @@ +#include + +#include "Mocks/DeserializerMock.hpp" +#include "Mocks/SettingSplitterMock.hpp" +#include "Mocks/ValueDeserializersMapMock.hpp" +#include "SevenBit/Conf/CommandLineParserBuilder.hpp" +#include "SevenBit/Conf/Details/CommandLineParser.hpp" +#include "SevenBit/Conf/Details/SettingSplitter.hpp" +#include "SevenBit/Conf/Details/ValueDeserializersMap.hpp" + +class CommandLineParserBuilderTest : public testing::Test +{ + protected: + static void TearUpTestSuite() {} + + CommandLineParserBuilderTest() {} + + void SetUp() override {} + + void TearDown() override {} + + static void TearDownTestSuite() {} +}; + +TEST_F(CommandLineParserBuilderTest, ShouldBuildDefault) +{ + const auto parser = sb::cf::CommandLineParserBuilder{}.build(); + + const auto &casted = dynamic_cast(*parser); + + auto &splitter = dynamic_cast(casted.getOptionsSplitter()); + auto &deserializers = + dynamic_cast(casted.getValueDeserializersMap()); + + EXPECT_EQ(casted.getOptionPrefixes(), (std::vector{"--", "/"})); + EXPECT_TRUE(casted.getConsiderSeparated()); + EXPECT_EQ(splitter.getKeySplitters(), std::vector{":"}); + EXPECT_EQ(splitter.getSettingSplitters(), std::vector{"="}); + EXPECT_EQ(splitter.getTypeMarkers(), std::vector{"!"}); + EXPECT_FALSE(splitter.getAllowEmptyKeys()); + + std::vector types; + for (auto &[type, _] : deserializers.getDeserializersMap()) + { + types.push_back(type); + } + std::sort(types.begin(), types.end()); + EXPECT_EQ(types, (std::vector{"bool", "double", "int", "json", "null", "string", "uint"})); + EXPECT_EQ(deserializers.getDefaultType(), "string"); + EXPECT_TRUE(deserializers.getThrowOnUnknownType()); +} + +TEST_F(CommandLineParserBuilderTest, ShouldUseCustomConfig) +{ + sb::cf::CommandLineParserBuilder builder; + + sb::cf::CommandLineParserConfig config; + config.defaultType = "int"; + config.throwOnUnknownType = false; + config.allowEmptyKeys = true; + config.keySplitters = {"/", "++"}; + config.optionSplitters = {"--", "%"}; + config.optionPrefixes = {"@@", "**"}; + config.typeMarkers = {"$", "##"}; + auto parser = builder.useConfig(config).build(); + + auto &casted = dynamic_cast(*parser); + + auto &splitter = dynamic_cast(casted.getOptionsSplitter()); + auto &deserializers = + dynamic_cast(casted.getValueDeserializersMap()); + + EXPECT_EQ(casted.getOptionPrefixes(), (std::vector{"@@", "**"})); + EXPECT_FALSE(casted.getConsiderSeparated()); + EXPECT_EQ(splitter.getKeySplitters(), (std::vector{"/", "++"})); + EXPECT_EQ(splitter.getSettingSplitters(), (std::vector{"--", "%"})); + EXPECT_EQ(splitter.getTypeMarkers(), (std::vector{"$", "##"})); + EXPECT_TRUE(splitter.getAllowEmptyKeys()); + + std::vector types; + for (auto &[type, _] : deserializers.getDeserializersMap()) + { + types.push_back(type); + } + std::sort(types.begin(), types.end()); + EXPECT_EQ(types, (std::vector{"bool", "double", "int", "json", "null", "string", "uint"})); + EXPECT_EQ(deserializers.getDefaultType(), "int"); + EXPECT_FALSE(deserializers.getThrowOnUnknownType()); +} + +TEST_F(CommandLineParserBuilderTest, ShouldUseValueDeserializer) +{ + sb::cf::CommandLineParserBuilder builder; + + auto parser = builder.useDefaultValueDeserializers() + .useValueDeserializer("newType", std::make_unique()) + .build(); + + auto &casted = dynamic_cast(*parser); + + auto &deserializers = casted.getValueDeserializersMap(); + auto get = [&](std::string_view type) { auto &_ = deserializers.getDeserializerFor(type); }; + EXPECT_TRUE(dynamic_cast(&casted.getOptionsSplitter())); + EXPECT_TRUE(dynamic_cast(&casted.getValueDeserializersMap())); + EXPECT_NO_THROW(get("newType")); + EXPECT_NO_THROW(get("int")); + EXPECT_THROW(get("unknown"), sb::cf::ConfigException); +} + +TEST_F(CommandLineParserBuilderTest, ShouldUseCustomValueDeserializerMap) +{ + sb::cf::CommandLineParserBuilder builder; + + const auto parser = builder.useValueDeserializersMap(std::make_unique()).build(); + + const auto &casted = dynamic_cast(*parser); + + EXPECT_TRUE(dynamic_cast(&casted.getOptionsSplitter())); + EXPECT_TRUE(dynamic_cast(&casted.getValueDeserializersMap())); +} + +TEST_F(CommandLineParserBuilderTest, ShouldUseCustomSplitter) +{ + sb::cf::CommandLineParserBuilder builder; + + const auto parser = builder.useSplitter(std::make_unique()).build(); + + const auto &casted = dynamic_cast(*parser); + + EXPECT_TRUE(dynamic_cast(&casted.getOptionsSplitter())); + EXPECT_TRUE(dynamic_cast(&casted.getValueDeserializersMap())); +} diff --git a/Tests/ConfigurationBuilderTest.cpp b/Tests/Unit/ConfigurationBuilderTest.cpp similarity index 95% rename from Tests/ConfigurationBuilderTest.cpp rename to Tests/Unit/ConfigurationBuilderTest.cpp index 838b1f0..e4faafc 100644 --- a/Tests/ConfigurationBuilderTest.cpp +++ b/Tests/Unit/ConfigurationBuilderTest.cpp @@ -1,6 +1,5 @@ #include #include -#include #include "Classes/CustomConfigSource.hpp" #include "SevenBit/Conf/ConfigurationBuilder.hpp" @@ -85,7 +84,7 @@ TEST_F(ConfigurationBuilderTest, ShouldBuildConfigWithProperties) .add(std::make_unique()) .build(); - auto cnt = sb::cf::ObjectHolder::safeCastFrom(*builder.getProperties()["counter"]).get(); + const auto cnt = sb::cf::ObjectHolder::safeCastFrom(*builder.getProperties()["counter"]).get(); EXPECT_EQ(cnt, 4); } diff --git a/Tests/Unit/Details/CommandLineParserTest.cpp b/Tests/Unit/Details/CommandLineParserTest.cpp new file mode 100644 index 0000000..d88275b --- /dev/null +++ b/Tests/Unit/Details/CommandLineParserTest.cpp @@ -0,0 +1,97 @@ +#include +#include + +#include "Mocks/DeserializerMock.hpp" +#include "Mocks/SettingSplitterMock.hpp" +#include "Mocks/ValueDeserializersMapMock.hpp" +#include "SevenBit/Conf/Details/CommandLineParser.hpp" +#include "SevenBit/Conf/Exceptions.hpp" + +class CommandLineParserTest : public testing::Test +{ + protected: + static void TearUpTestSuite() {} + + CommandLineParserTest() {} + + void SetUp() override {} + + void TearDown() override {} + + static void TearDownTestSuite() {} +}; + +TEST_F(CommandLineParserTest, ShouldParseSetting) +{ + DeserializerMock deserializer; + auto deserializers = std::make_unique(); + auto splitter = std::make_unique(); + + std::vector settings = {"--option:deep:deep!string=123", "--option2=123"}; + sb::cf::ISettingSplitter::Result returned1 = {{"option", "deep", "deep"}, "string", "123"}; + sb::cf::ISettingSplitter::Result returned2 = {{"option2"}, std::nullopt, "123"}; + EXPECT_CALL(*splitter, split(std::string_view{"option:deep:deep!string=123"})).WillOnce(testing::Return(returned1)); + EXPECT_CALL(*splitter, split(std::string_view{"option2=123"})).WillOnce(testing::Return(returned2)); + EXPECT_CALL(*deserializers, getDeserializerFor).WillRepeatedly(testing::ReturnRef(deserializer)); + sb::cf::JsonValue returnedValue1 = "123"; + EXPECT_CALL(deserializer, deserialize).WillRepeatedly(testing::Return(returnedValue1)); + + sb::cf::details::CommandLineParser parser{std::move(splitter), std::move(deserializers), {"--"}, false}; + + EXPECT_EQ(parser.parse(settings), + (sb::cf::JsonObject{{"option2", "123"}, {"option", {{"deep", {{"deep", "123"}}}}}})); +} + +TEST_F(CommandLineParserTest, ShouldParseSeparatedSetting) +{ + DeserializerMock deserializer; + auto deserializers = std::make_unique(); + auto splitter = std::make_unique(); + + std::vector settings = {"--option:deep:deep!int", "123"}; + sb::cf::ISettingSplitter::Result returned1 = {{"option", "deep", "deep"}, "int", std::nullopt}; + EXPECT_CALL(*splitter, split).WillOnce(testing::Return(returned1)); + EXPECT_CALL(*deserializers, getDeserializerFor).WillOnce(testing::ReturnRef(deserializer)); + sb::cf::JsonValue returnedValue1 = 123; + EXPECT_CALL(deserializer, deserialize).WillOnce(testing::Return(returnedValue1)); + + sb::cf::details::CommandLineParser parser{std::move(splitter), std::move(deserializers), {"--"}, true}; + + EXPECT_EQ(parser.parse(settings), (sb::cf::JsonObject{{"option", {{"deep", {{"deep", 123}}}}}})); +} + +TEST_F(CommandLineParserTest, ShouldParseSeparatedEndSetting) +{ + DeserializerMock deserializer; + auto deserializers = std::make_unique(); + auto splitter = std::make_unique(); + + std::vector settings = {"--option:deep:deep!int"}; + sb::cf::ISettingSplitter::Result returned1 = {{"option", "deep", "deep"}, "int", std::nullopt}; + EXPECT_CALL(*splitter, split).WillOnce(testing::Return(returned1)); + EXPECT_CALL(*deserializers, getDeserializerFor).WillOnce(testing::ReturnRef(deserializer)); + sb::cf::JsonValue returnedValue1 = 0; + EXPECT_CALL(deserializer, deserialize).WillOnce(testing::Return(returnedValue1)); + + sb::cf::details::CommandLineParser parser{std::move(splitter), std::move(deserializers), {"--"}, true}; + + EXPECT_EQ(parser.parse(settings), (sb::cf::JsonObject{{"option", {{"deep", {{"deep", 0}}}}}})); +} + +TEST_F(CommandLineParserTest, ShouldFailCreateSettingParserDueNullSplitter) +{ + sb::cf::ISettingSplitter::Ptr splitter; + auto deserializers = std::make_unique(); + + EXPECT_THROW((sb::cf::details::CommandLineParser{std::move(splitter), std::move(deserializers), {"--"}}), + sb::cf::ConfigException); +} + +TEST_F(CommandLineParserTest, ShouldFailCreateSettingParserDueNullDeserializers) +{ + sb::cf::IValueDeserializersMap::Ptr deserializers; + auto splitter = std::make_unique(); + + EXPECT_THROW((sb::cf::details::CommandLineParser{std::move(splitter), std::move(deserializers), {"--"}}), + sb::cf::ConfigException); +} diff --git a/Tests/Unit/Details/ContainerUtilsTest.cpp b/Tests/Unit/Details/ContainerUtilsTest.cpp new file mode 100644 index 0000000..7c2eb6a --- /dev/null +++ b/Tests/Unit/Details/ContainerUtilsTest.cpp @@ -0,0 +1,29 @@ +#include + +#include "SevenBit/Conf/Details/ContainerUtils.hpp" + +class ContainerUtilsTest : public testing::Test +{ + protected: + static void TearUpTestSuite() {} + + ContainerUtilsTest() {} + + void SetUp() override {} + + void TearDown() override {} + + ~ContainerUtilsTest() override = default; + + static void TearDownTestSuite() {} +}; + +TEST_F(ContainerUtilsTest, ShouldEraseElements) +{ + std::vector vec = {1, 2, 3, 4, 5, 6}; + + const auto removed = sb::cf::details::ContainerUtils::eraseIf(vec, [](const int val) { return val % 2; }); + + EXPECT_EQ(removed, 3); + EXPECT_EQ(vec, (std::vector{2, 4, 6})); +} diff --git a/Tests/Unit/Details/EnvironmentVarsParserTest.cpp b/Tests/Unit/Details/EnvironmentVarsParserTest.cpp new file mode 100644 index 0000000..ab0f5eb --- /dev/null +++ b/Tests/Unit/Details/EnvironmentVarsParserTest.cpp @@ -0,0 +1,58 @@ +#include +#include + +#include "Mocks/DeserializerMock.hpp" +#include "Mocks/SettingSplitterMock.hpp" +#include "Mocks/ValueDeserializersMapMock.hpp" +#include "SevenBit/Conf/Details/EnvironmentVarsParser.hpp" +#include "SevenBit/Conf/Exceptions.hpp" + +class EnvironmentVarsParserTest : public testing::Test +{ + protected: + static void TearUpTestSuite() {} + + EnvironmentVarsParserTest() {} + + void SetUp() override {} + + void TearDown() override {} + + static void TearDownTestSuite() {} +}; + +TEST_F(EnvironmentVarsParserTest, ShouldParseSetting) +{ + DeserializerMock deserializer; + auto deserializers = std::make_unique(); + auto splitter = std::make_unique(); + + std::vector settings = {"--option:deep:deep!int=123"}; + sb::cf::ISettingSplitter::Result returned = {{"option", "deep", "deep"}, "int", "123"}; + EXPECT_CALL(*splitter, split).WillOnce(testing::Return(returned)); + EXPECT_CALL(*deserializers, getDeserializerFor).WillOnce(testing::ReturnRef(deserializer)); + sb::cf::JsonValue returnedValue = 123; + EXPECT_CALL(deserializer, deserialize).WillOnce(testing::Return(returnedValue)); + + sb::cf::details::EnvironmentVarsParser parser{std::move(splitter), std::move(deserializers)}; + + EXPECT_EQ(parser.parse(settings), (sb::cf::JsonObject{{"option", {{"deep", {{"deep", 123}}}}}})); +} + +TEST_F(EnvironmentVarsParserTest, ShouldFailCreateSettingParserDueNullSplitter) +{ + sb::cf::ISettingSplitter::Ptr splitter; + auto deserializers = std::make_unique(); + + EXPECT_THROW((sb::cf::details::EnvironmentVarsParser{std::move(splitter), std::move(deserializers)}), + sb::cf::ConfigException); +} + +TEST_F(EnvironmentVarsParserTest, ShouldFailCreateSettingParserDueNullDeserializers) +{ + sb::cf::IValueDeserializersMap::Ptr deserializers; + auto splitter = std::make_unique(); + + EXPECT_THROW((sb::cf::details::EnvironmentVarsParser{std::move(splitter), std::move(deserializers)}), + sb::cf::ConfigException); +} diff --git a/Tests/JsonObjectExtTest.cpp b/Tests/Unit/Details/JsonExtTest.cpp similarity index 65% rename from Tests/JsonObjectExtTest.cpp rename to Tests/Unit/Details/JsonExtTest.cpp index e65eae1..2cde0cb 100644 --- a/Tests/JsonObjectExtTest.cpp +++ b/Tests/Unit/Details/JsonExtTest.cpp @@ -1,18 +1,17 @@ #include -#include -#include "SevenBit/Conf/Details/JsonExt.hpp" -#include "SevenBit/Conf/Exceptions.hpp" -#include "Utilities/ParamsTest.hpp" +#include "../../../Include/SevenBit/Conf/Details/JsonExt.hpp" +#include "../../../Include/SevenBit/Conf/Exceptions.hpp" +#include "../../Helpers/Utilities/ParamsTest.hpp" using namespace sb::cf::json; -class JsonObjectExtTest : public testing::Test +class JsonExtTest : public testing::Test { protected: static void TearUpTestSuite() {} - JsonObjectExtTest() {} + JsonExtTest() {} void SetUp() override {} @@ -28,13 +27,13 @@ Params FindData{ {"inner", true, -123}, {"nonExisting", false, tao::json::null}, }; -PARAMS_TEST(JsonObjectExtTest, ShouldFing, FindData) +PARAMS_TEST(JsonExtTest, ShouldFing, FindData) { sb::cf::JsonValue json = { {"str", "hello"}, {"number", 123}, {"array", sb::cf::JsonArray{{{"key", "value"}}}}, {"inner", -123}}; auto &[keys, expectedFound, expectedValue] = GetParam(); - auto valuePtr = sb::cf::details::JsonExt::find(json, keys); + const auto valuePtr = sb::cf::details::JsonExt::find(json, keys); EXPECT_EQ(!!valuePtr, expectedFound); if (valuePtr) { @@ -59,7 +58,7 @@ Params DeepFindData{ {"inner::inner:str", false, tao::json::null}, }; -PARAMS_TEST(JsonObjectExtTest, ShouldDeepFing, DeepFindData) +PARAMS_TEST(JsonExtTest, ShouldDeepFing, DeepFindData) { sb::cf::JsonValue json = {{"str", "hello"}, {"number", 123}, @@ -74,7 +73,7 @@ PARAMS_TEST(JsonObjectExtTest, ShouldDeepFing, DeepFindData) }}}}}; auto &[keys, expectedFound, expectedValue] = GetParam(); - auto valuePtr = sb::cf::details::JsonExt::deepFind(json, keys); + const auto valuePtr = sb::cf::details::JsonExt::deepFind(json, keys); EXPECT_EQ(!!valuePtr, expectedFound); if (valuePtr) { @@ -82,7 +81,7 @@ PARAMS_TEST(JsonObjectExtTest, ShouldDeepFing, DeepFindData) } } -TEST_F(JsonObjectExtTest, ShouldDeepGetOrOverride) +TEST_F(JsonExtTest, ShouldDeepGetOrOverride) { sb::cf::JsonObject json = {{"str", "hello"}, {"number", 123}, @@ -98,29 +97,29 @@ TEST_F(JsonObjectExtTest, ShouldDeepGetOrOverride) sb::cf::details::JsonExt::deepGetOrOverride(json, "inner:inner:str") = "hello3"; sb::cf::details::JsonExt::deepGetOrOverride(json, "inner:inner:inner:str") = "hello5"; - sb::cf::JsonObject expectedJson = {{"str", "hello"}, - {"number", 123}, - {"inner", - {{"str", "hello1"}, - {"number", 1231}, - {"inner", - {{"str", "hello3"}, - {"number", 1232}, - {"inner", - { - {"str", "hello5"}, - }}}}}}}; + const sb::cf::JsonObject expectedJson = {{"str", "hello"}, + {"number", 123}, + {"inner", + {{"str", "hello1"}, + {"number", 1231}, + {"inner", + {{"str", "hello3"}, + {"number", 1232}, + {"inner", + { + {"str", "hello5"}, + }}}}}}}; EXPECT_EQ(json, expectedJson); } -TEST_F(JsonObjectExtTest, ShouldDeepGetOrOverrideArrayElement) +TEST_F(JsonExtTest, ShouldDeepGetOrOverrideArrayElement) { sb::cf::JsonObject json = {{"str", "hello"}}; sb::cf::details::JsonExt::deepGetOrOverride(json, "array:3:object") = "value"; - sb::cf::JsonObject expected = { + const sb::cf::JsonObject expected = { {"str", "hello"}, {"array", sb::cf::JsonArray{sb::cf::JsonValue{}, sb::cf::JsonValue{}, sb::cf::JsonValue{}, {{"object", "value"}}}}, @@ -129,13 +128,13 @@ TEST_F(JsonObjectExtTest, ShouldDeepGetOrOverrideArrayElement) EXPECT_EQ(json, expected); } -TEST_F(JsonObjectExtTest, ShouldDeepGetOrOverrideExistingArrayElement) +TEST_F(JsonExtTest, ShouldDeepGetOrOverrideExistingArrayElement) { sb::cf::JsonObject json = {{"str", "hello"}, {"array", sb::cf::JsonArray{1234, {{"second", "element"}}}}}; sb::cf::details::JsonExt::deepGetOrOverride(json, "array:3:object") = "value"; - sb::cf::JsonObject expected = { + const sb::cf::JsonObject expected = { {"str", "hello"}, {"array", sb::cf::JsonArray{1234, {{"second", "element"}}, sb::cf::JsonValue{}, {{"object", "value"}}}}, }; @@ -143,18 +142,18 @@ TEST_F(JsonObjectExtTest, ShouldDeepGetOrOverrideExistingArrayElement) EXPECT_EQ(json, expected); } -TEST_F(JsonObjectExtTest, ShouldDeepGetOrOverrideWrongArrayElement) +TEST_F(JsonExtTest, ShouldDeepGetOrOverrideWrongArrayElement) { sb::cf::JsonObject json = {{"str", "hello"}}; sb::cf::details::JsonExt::deepGetOrOverride(json, "array:-3:object") = "value"; - sb::cf::JsonObject expected = {{"str", "hello"}, {"array", {{"-3", {{"object", "value"}}}}}}; + const sb::cf::JsonObject expected = {{"str", "hello"}, {"array", {{"-3", {{"object", "value"}}}}}}; EXPECT_EQ(json, expected); } -TEST_F(JsonObjectExtTest, ShouldDeepGetOrOverrideDestroy) +TEST_F(JsonExtTest, ShouldDeepGetOrOverrideDestroy) { sb::cf::JsonObject json = {{"str", "hello"}, {"number", 123}, @@ -169,28 +168,28 @@ TEST_F(JsonObjectExtTest, ShouldDeepGetOrOverrideDestroy) sb::cf::details::JsonExt::deepGetOrOverride(json, "inner:inner:str:fail") = "value"; - sb::cf::JsonObject expected = {{"str", "hello"}, - {"number", 123}, - {"inner", - {{"str", "hello1"}, - {"number", 1231}, - {"inner", - { - {"str", {{"fail", "value"}}}, - {"number", 1232}, - }}}}}; + const sb::cf::JsonObject expected = {{"str", "hello"}, + {"number", 123}, + {"inner", + {{"str", "hello1"}, + {"number", 1231}, + {"inner", + { + {"str", {{"fail", "value"}}}, + {"number", 1232}, + }}}}}; EXPECT_EQ(json, expected); } -TEST_F(JsonObjectExtTest, ShouldFaildDeepGetOrOverrideEmpty) +TEST_F(JsonExtTest, ShouldFaildDeepGetOrOverrideEmpty) { sb::cf::JsonObject json = {{"str", "hello"}}; EXPECT_ANY_THROW(sb::cf::details::JsonExt::deepGetOrOverride(json, std::vector{})); } -TEST_F(JsonObjectExtTest, SouldDeepMergeEmptyJsonValue) +TEST_F(JsonExtTest, SouldDeepMergeEmptyJsonValue) { sb::cf::JsonValue json; @@ -203,7 +202,7 @@ TEST_F(JsonObjectExtTest, SouldDeepMergeEmptyJsonValue) EXPECT_EQ(json, expected); } -TEST_F(JsonObjectExtTest, SouldDeepMergeJsonValue) +TEST_F(JsonExtTest, SouldDeepMergeJsonValue) { sb::cf::JsonValue json = {{"str", "hello"}, @@ -230,23 +229,23 @@ TEST_F(JsonObjectExtTest, SouldDeepMergeJsonValue) sb::cf::details::JsonExt::deepMerge(json, std::move(jsonOverride)); - sb::cf::JsonObject expectedJson = {{"str", "helloOv"}, - {"number", 123}, - {"array", sb::cf::JsonArray{3, {{"value", "value"}}, 1, 4, 5, 6}}, - {"default", "default"}, - {"inner", - {{"str", "hello1"}, - {"number", 12313}, - {"inner", - { - {"str", "hello2Ov"}, - {"number", 12323}, - }}}}}; + const sb::cf::JsonObject expectedJson = {{"str", "helloOv"}, + {"number", 123}, + {"array", sb::cf::JsonArray{3, {{"value", "value"}}, 1, 4, 5, 6}}, + {"default", "default"}, + {"inner", + {{"str", "hello1"}, + {"number", 12313}, + {"inner", + { + {"str", "hello2Ov"}, + {"number", 12323}, + }}}}}; EXPECT_EQ(json, expectedJson); } -TEST_F(JsonObjectExtTest, SouldDeepMergeJsonArray) +TEST_F(JsonExtTest, SouldDeepMergeJsonArray) { sb::cf::JsonArray json{{{"str", "hello"}}, @@ -267,15 +266,15 @@ TEST_F(JsonObjectExtTest, SouldDeepMergeJsonArray) sb::cf::details::JsonExt::deepMerge(json, std::move(jsonOverride)); - sb::cf::JsonArray expectedJson{{{"str", "hello22"}}, - {{"number", 123}}, - {{"number", 123}}, - {{"inner", - { - {"str", "hello1"}, - {"number", 1231}, + const sb::cf::JsonArray expectedJson{{{"str", "hello22"}}, + {{"number", 123}}, + {{"number", 123}}, + {{"inner", + { + {"str", "hello1"}, + {"number", 1231}, - }}}}; + }}}}; EXPECT_EQ(json, expectedJson); } diff --git a/Tests/Unit/Details/RequireTest.cpp b/Tests/Unit/Details/RequireTest.cpp new file mode 100644 index 0000000..d4859e5 --- /dev/null +++ b/Tests/Unit/Details/RequireTest.cpp @@ -0,0 +1,30 @@ +#include + +#include "SevenBit/Conf/Details/Require.hpp" + +class RequireTest : public testing::Test +{ + protected: + static void TearUpTestSuite() {} + + RequireTest() {} + + void SetUp() override {} + + void TearDown() override {} + + ~RequireTest() override = default; + + static void TearDownTestSuite() {} +}; + +TEST_F(RequireTest, ShouldRequireNotNull) +{ + int test = 123; + EXPECT_THROW(sb::cf::details::Require::notNull(nullptr), sb::cf::NullPointerException); + EXPECT_THROW(sb::cf::details::Require::notNull(std::unique_ptr{}), sb::cf::NullPointerException); + EXPECT_THROW(sb::cf::details::Require::notNull(std::shared_ptr{}), sb::cf::NullPointerException); + EXPECT_NO_THROW(sb::cf::details::Require::notNull(&test)); + EXPECT_NO_THROW(sb::cf::details::Require::notNull(std::shared_ptr{new int})); + EXPECT_NO_THROW(sb::cf::details::Require::notNull(std::unique_ptr{new int})); +} diff --git a/Tests/Unit/Details/SettingSplitterTest.cpp b/Tests/Unit/Details/SettingSplitterTest.cpp new file mode 100644 index 0000000..aab8c77 --- /dev/null +++ b/Tests/Unit/Details/SettingSplitterTest.cpp @@ -0,0 +1,123 @@ +#include + +#include "../../../Include/SevenBit/Conf/Details/SettingSplitter.hpp" +#include "../../../Include/SevenBit/Conf/Details/StringUtils.hpp" +#include "../../Helpers/Utilities/ParamsTest.hpp" + +class SettingSplitterTest : public testing::Test +{ + protected: + static void TearUpTestSuite() {} + + SettingSplitterTest() {} + + void SetUp() override {} + + void TearDown() override {} + + static void TearDownTestSuite() {} +}; + +static Params SplitSettingData = { + {"--", {{"--"}}}, + {"one", {{"one"}}}, + {"====", {{""}, std::nullopt, "==="}}, + {"option!!type=val", {{"option!"}, "type", "val"}}, + {"option!type=value", {{"option"}, "type", "value"}}, + {"option___type;value", {{"option"}, "type", "value"}}, + {"option=value", {{"option"}, std::nullopt, "value"}}, + {"option=", {{"option"}, std::nullopt, ""}}, + {"option!!type=", {{"option!"}, "type", ""}}, + {"option!!type", {{"option!"}, "type"}}, + {"option!!=type=val", {{"option!"}, "", "type=val"}}, + {"option!!:inner!=type=val", {{"option!!", "inner"}, "", "type=val"}}, + {"option!!:inner:::!=type=val", {{"option!!", "inner", "", "", ""}, "", "type=val"}}, + {":option:inner=type=val", {{"", "option", "inner"}, std::nullopt, "type=val"}}, + {"!!option:inner=type=val", {{"!"}, "option:inner", "type=val"}}, + {":option:inner=value", {{"", "option", "inner"}, std::nullopt, "value"}}, + {":option:inner=value::!", {{"", "option", "inner"}, std::nullopt, "value::!"}}, + {":option!!:inner=value::!", {{"", "option!"}, ":inner", "value::!"}}, + {":::!=value::!", {{"", "", "", ""}, "", "value::!"}}, + {"::!:=value::!", {{"", "", ""}, ":", "value::!"}}, + {":=:!:=value::!", {{"", ""}, std::nullopt, ":!:=value::!"}}, + {"__=:!___=value::!", {{"", ""}, std::nullopt, ":!___=value::!"}}, + {"_______=value", {{"", "", ""}, "", "value"}}, + {"__hello_____type=value", {{"", "hello", ""}, "type", "value"}}, + {"__hello__type=value", {{"", "hello", "type"}, std::nullopt, "value"}}, +}; +PARAMS_TEST(SettingSplitterTest, ShouldSplitSetting, SplitSettingData) +{ + const auto &[setting, expected] = GetParam(); + const sb::cf::details::SettingSplitter splitter{{"=", ";"}, {"!", "___"}, {":", "__"}, true}; + + EXPECT_EQ(splitter.split(setting), expected); +} + +static OneParams SettingSplittersData = {"=", ">", "===", "<<<<", "////"}; + +static OneParams KeySplittersData = {":", ";", "__", "{}", "\\"}; + +static OneParams TypeMarkersData = {"!", "@", ":", "[]", "{}"}; + +static Params, std::string, std::string> SplitSettingSimpleData = { + {{"key1", "key2", "key3"}, "string", "value"}, + {{"key1"}, "string", "value"}, + {{""}, "", ""}, + {{""}, "type", ""}, + {{"key"}, "", ""}, + {{""}, "", "value"}, +}; +PARAMS_TEST_COMBINED_4(SettingSplitterTest, ShouldSplitWithDifferentSplitters, SettingSplittersData, TypeMarkersData, + KeySplittersData, SplitSettingSimpleData) +{ + const auto &[settingSplitter, typeMarker, keySplitter, setting] = GetParam(); + const auto &[keys, type, value] = setting; + + const sb::cf::details::SettingSplitter splitter{{settingSplitter}, {typeMarker}, {keySplitter}, true}; + + const auto key = sb::cf::details::StringUtils::join(keys, keySplitter); + const auto fullSetting = key + typeMarker + type + settingSplitter + value; + EXPECT_EQ(splitter.split(fullSetting), (sb::cf::ISettingSplitter::Result{keys, type, value})); +} + +PARAMS_TEST_COMBINED_3(SettingSplitterTest, ShouldFailOnEmptyKeys, SettingSplittersData, TypeMarkersData, + KeySplittersData) +{ + const auto &[settingSplitter, typeMarker, keySplitter] = GetParam(); + + const sb::cf::details::SettingSplitter splitter{{settingSplitter}, {typeMarker}, {keySplitter}, false}; + + const auto key = sb::cf::details::StringUtils::join({"", "", ""}, keySplitter); + const auto fullSetting = key + typeMarker + "type" + settingSplitter + "value"; + auto act = [&] { auto _ = splitter.split(fullSetting); }; + EXPECT_THROW(act(), sb::cf::ConfigException); +} + +static Params, std::vector, std::vector, + sb::cf::ISettingSplitter::Result> + EmptySplittersData = { + {{"=", ";"}, {"!", "___"}, {":", "__"}, {{"key", "deep"}, "int", "value"}}, + {{}, {"!", "___"}, {":", "__"}, {{"key", "deep"}, "int=value"}}, + {{"=", ";"}, {}, {":", "__"}, {{"key", "deep!int"}, std::nullopt, "value"}}, + {{"=", ";"}, {"!", "___"}, {}, {{"key:deep"}, "int", "value"}}, + {{}, {"!", "___"}, {":", "__"}, {{"key", "deep"}, "int=value"}}, + {{"=", ";"}, {}, {":", "__"}, {{"key", "deep!int"}, std::nullopt, "value"}}, + {{"=", ";"}, {"!", "___"}, {}, {{"key:deep"}, "int", "value"}}, + {{}, {}, {":", "__"}, {{"key", "deep!int=value"}}}, + {{}, {"!", "___"}, {}, {{"key:deep"}, "int=value"}}, + {{"=", ";"}, {}, {}, {{"key:deep!int"}, std::nullopt, "value"}}, + {{}, {}, {":", "__"}, {{"key", "deep!int=value"}}}, + {{}, {"!", "___"}, {}, {{"key:deep"}, "int=value"}}, + {{"=", ";"}, {}, {}, {{"key:deep!int"}, std::nullopt, "value"}}, + {{}, {}, {}, {{"key:deep!int=value"}}}, + {{}, {}, {}, {{"key:deep!int=value"}}}, + +}; +PARAMS_TEST(SettingSplitterTest, ShouldSplitWithEmptySplitters, EmptySplittersData) +{ + const auto &[settingSplitters, typeMarkers, keySplitters, expected] = GetParam(); + + const sb::cf::details::SettingSplitter splitter{settingSplitters, typeMarkers, keySplitters, true}; + + EXPECT_EQ(splitter.split("key:deep!int=value"), expected); +} diff --git a/Tests/UtilsTest.cpp b/Tests/Unit/Details/StringUtilsTest.cpp similarity index 75% rename from Tests/UtilsTest.cpp rename to Tests/Unit/Details/StringUtilsTest.cpp index 6cfc816..5e6343e 100644 --- a/Tests/UtilsTest.cpp +++ b/Tests/Unit/Details/StringUtilsTest.cpp @@ -1,15 +1,15 @@ #include #include -#include "SevenBit/Conf/Details/Utils.hpp" -#include "Utilities/ParamsTest.hpp" +#include "../../../Include/SevenBit/Conf/Details/StringUtils.hpp" +#include "../../Helpers/Utilities/ParamsTest.hpp" -class UtilsTest : public testing::Test +class StringUtilsTest : public testing::Test { protected: static void TearUpTestSuite() {} - UtilsTest() {} + StringUtilsTest() {} void SetUp() override {} @@ -23,10 +23,10 @@ Params CheckNumberStringsData{ {"", false}, {"alk1", false}, {"1223-", false}, {"1223#", false}, {"1223+=", false}, {"1223.123", false}, {"1223.123", false}, }; -PARAMS_TEST(UtilsTest, ShouldCheckNumberStrings, CheckNumberStringsData) +PARAMS_TEST(StringUtilsTest, ShouldCheckNumberStrings, CheckNumberStringsData) { auto &[string, expected] = GetParam(); - EXPECT_EQ(sb::cf::details::utils::isNumberString(string), expected); + EXPECT_EQ(sb::cf::details::StringUtils::isNumber(string), expected); } Params IgnoreCaseLessData{ @@ -43,10 +43,10 @@ Params IgnoreCaseLessData{ {"ab", "ab\n", true}, {"1222", "12", false}, }; -PARAMS_TEST(UtilsTest, ShouldIgnoreCaseLessCompareStrings, IgnoreCaseLessData) +PARAMS_TEST(StringUtilsTest, ShouldIgnoreCaseLessCompareStrings, IgnoreCaseLessData) { auto &[string, search, expected] = GetParam(); - EXPECT_EQ(sb::cf::details::utils::ignoreCaseLess(string, search), expected); + EXPECT_EQ(sb::cf::details::StringUtils::ignoreCaseLess(string, search), expected); } Params IgnoreCaseEqualsData{ @@ -63,10 +63,10 @@ Params IgnoreCaseEqualsData{ {"ab", "ab\n", false}, {"1222", "12", false}, }; -PARAMS_TEST(UtilsTest, ShouldIgnoreCaseCompareStrings, IgnoreCaseEqualsData) +PARAMS_TEST(StringUtilsTest, ShouldIgnoreCaseCompareStrings, IgnoreCaseEqualsData) { auto &[string, search, expected] = GetParam(); - EXPECT_EQ(sb::cf::details::utils::ignoreCaseEqual(string, search), expected); + EXPECT_EQ(sb::cf::details::StringUtils::ignoreCaseEqual(string, search), expected); } Params ContainsAtData{ @@ -78,10 +78,10 @@ Params ContainsAtData{ {"", 0, "abcded", false}, {"BA", 0, "ab", false}, {"ab", 0, "ab\n", false}, {"1222", 0, "12", true}, }; -PARAMS_TEST(UtilsTest, ShouldContainsAt, ContainsAtData) +PARAMS_TEST(StringUtilsTest, ShouldContainsAt, ContainsAtData) { auto &[string, index, search, expected] = GetParam(); - EXPECT_EQ(sb::cf::details::utils::containsAt(string, index, search), expected); + EXPECT_EQ(sb::cf::details::StringUtils::containsAt(string, index, search), expected); } Params, std::optional> ContainsAtMultitData{ @@ -104,10 +104,10 @@ Params, std::optional ContainsAtFromEndData{ @@ -118,10 +118,10 @@ Params ContainsAtFromEndData{ {"123@#", 2, "@", false}, {"abcdef", 5, "abcdef", true}, {"", 0, "abcded", false}, {"BA", 2, "ab", false}, {"ab", 2, "ab\n", false}, {"1222", 1, "12", true}, }; -PARAMS_TEST(UtilsTest, ShouldContainsAtFromEnd, ContainsAtFromEndData) +PARAMS_TEST(StringUtilsTest, ShouldContainsAtFromEnd, ContainsAtFromEndData) { auto &[string, index, search, expected] = GetParam(); - EXPECT_EQ(sb::cf::details::utils::containsAtFromEnd(string, index, search), expected); + EXPECT_EQ(sb::cf::details::StringUtils::containsAtFromEnd(string, index, search), expected); } Params, std::optional> @@ -147,10 +147,10 @@ Params, std::optional StartsWithData{ @@ -159,10 +159,38 @@ Params StartsWithData{ {"123", "890", false}, {"123", "1245", false}, {"1234567", "234", false}, {"", "234", false}, {"123", "234", false}, }; -PARAMS_TEST(UtilsTest, ShouldStartsWith, StartsWithData) +PARAMS_TEST(StringUtilsTest, ShouldStartsWith, StartsWithData) { auto &[string, search, expected] = GetParam(); - EXPECT_EQ(sb::cf::details::utils::startsWith(string, search), expected); + EXPECT_EQ(sb::cf::details::StringUtils::startsWith(string, search), expected); +} + +Params StartsWithWhiteSpaceData{ + {"", 0}, + {"asddwq", 0}, + {" 1234567", 1}, + {" 1234567", 2}, + {"\n\t\nasdwd", 3}, + {"\n \nasdwd", 4}, + {" \n 1234567", 6}, + {" \n1234567", 6}, + {" \t 1234567", 6}, + {" asd", 6}, +}; +PARAMS_TEST(StringUtilsTest, ShouldStartsWithWhiteSpace, StartsWithWhiteSpaceData) +{ + auto &[string, expectedCnt] = GetParam(); + EXPECT_EQ(sb::cf::details::StringUtils::startsWithWhiteSpace(string), expectedCnt); +} + +Params CheckWhiteSpaceData{ + {"", false}, {"asddwq", false}, {" ", true}, {"\n\t\n", true}, {"\n \nasdw", false}, + {" \n ", true}, {" \n asdadd", false}, {" \n", true}, {" \t ", true}, {" asd", false}, +}; +PARAMS_TEST(StringUtilsTest, ShouldCheckWhiteSpace, CheckWhiteSpaceData) +{ + auto &[string, expected] = GetParam(); + EXPECT_EQ(sb::cf::details::StringUtils::isWhiteSpace(string), expected); } Params> SplitStrData{ @@ -178,10 +206,10 @@ Params> SplitStrData{ {"first__sec__for__5__6__", "__", {"first", "sec", "for", "5", "6", ""}}, {":::", ":", {"", "", "", ""}}, }; -PARAMS_TEST(UtilsTest, ShouldSplitString, SplitStrData) +PARAMS_TEST(StringUtilsTest, ShouldSplitString, SplitStrData) { auto &[string, delim, expected] = GetParam(); - EXPECT_EQ(sb::cf::details::utils::split(string, delim), expected); + EXPECT_EQ(sb::cf::details::StringUtils::split(string, delim), expected); } Params, std::vector> SplitStrMultiData{ @@ -201,10 +229,10 @@ Params, std::vector {"first:sec:for:5:6:", {":"}, {"first", "sec", "for", "5", "6", ""}}, {":::", {":"}, {"", "", "", ""}}, }; -PARAMS_TEST(UtilsTest, ShouldSplitStringMulti, SplitStrMultiData) +PARAMS_TEST(StringUtilsTest, ShouldSplitStringMulti, SplitStrMultiData) { auto &[string, delims, expected] = GetParam(); - EXPECT_EQ(sb::cf::details::utils::split(string, delims), expected); + EXPECT_EQ(sb::cf::details::StringUtils::split(string, delims), expected); } Params, std::optional>> @@ -222,10 +250,10 @@ Params, std::optional{"first", "sec:for:5:6"}}, {"first__sec__for__5__6__", {"__"}, std::pair{"first", "sec__for__5__6__"}}, }; -PARAMS_TEST(UtilsTest, ShouldBreakString, BreakStrData) +PARAMS_TEST(StringUtilsTest, ShouldBreakString, BreakStrData) { auto &[string, delim, expected] = GetParam(); - EXPECT_EQ(sb::cf::details::utils::tryBreak(string, delim), expected); + EXPECT_EQ(sb::cf::details::StringUtils::tryBreak(string, delim), expected); } Params, std::optional>> @@ -243,11 +271,10 @@ Params, std::optional{"first:sec:for:5", "6"}}, {"first__sec__for__5__6", {"__"}, std::pair{"first__sec__for__5", "6"}}, }; -PARAMS_TEST(UtilsTest, ShouldBreakFromEndString, BreakFromEndStrData) +PARAMS_TEST(StringUtilsTest, ShouldBreakFromEndString, BreakFromEndStrData) { auto &[string, delim, expected] = GetParam(); - auto res = sb::cf::details::utils::tryBreakFromEnd(string, delim); - EXPECT_EQ(sb::cf::details::utils::tryBreakFromEnd(string, delim), expected); + EXPECT_EQ(sb::cf::details::StringUtils::tryBreakFromEnd(string, delim), expected); } Params, std::string, std::string> JoinStrData{ @@ -262,27 +289,20 @@ Params, std::string, std::string> JoinStrData{ {{"first", "sec", "for:5:6:"}, ":", "first:sec:for:5:6:"}, {{"first", "sec", "for", "5", "6"}, ":", "first:sec:for:5:6"}, }; -PARAMS_TEST(UtilsTest, ShouldJoinStrings, JoinStrData) +PARAMS_TEST(StringUtilsTest, ShouldJoinStrings, JoinStrData) { auto &[strings, delim, expected] = GetParam(); - EXPECT_EQ(sb::cf::details::utils::joinViews(strings, delim), expected); -} - -TEST_F(UtilsTest, ShouldAssertPtr) -{ - int i = 1; - EXPECT_NO_THROW(sb::cf::details::utils::assertPtr(&i)); - EXPECT_THROW(sb::cf::details::utils::assertPtr(nullptr), sb::cf::NullPointerException); + EXPECT_EQ(sb::cf::details::StringUtils::join(strings, delim), expected); } Params> ConvertToNumberIntData{ {"123", true, {true, 123}}, {"-123", true, {true, -123}}, {"00123", true, {true, 123}}, {"123 23", false, {true, 123}}, {"123.23", false, {true, 123}}, {"123adawadwa", false, {true, 123}}, }; -PARAMS_TEST(UtilsTest, ShouldConvertToIntNumber, ConvertToNumberIntData) +PARAMS_TEST(StringUtilsTest, ShouldConvertToIntNumber, ConvertToNumberIntData) { auto &[string, full, expected] = GetParam(); - auto [success, result] = sb::cf::details::utils::tryStringTo(string, full); + auto [success, result] = sb::cf::details::StringUtils::tryConvertTo(string, full); EXPECT_EQ(success, expected.first); if (success) @@ -296,10 +316,10 @@ Params> ConvertToNumberDoubleDat {"00123.2", true, {true, 123.2}}, {" 123.23asdw", false, {true, 123.23}}, {"123.23", false, {true, 123.23}}, {"123.1adawadwa", false, {true, 123.1}}, }; -PARAMS_TEST(UtilsTest, ShouldConvertToDoubleNumber, ConvertToNumberDoubleData) +PARAMS_TEST(StringUtilsTest, ShouldConvertToDoubleNumber, ConvertToNumberDoubleData) { auto &[string, full, expected] = GetParam(); - auto [success, result] = sb::cf::details::utils::tryStringTo(string, full); + auto [success, result] = sb::cf::details::StringUtils::tryConvertTo(string, full); EXPECT_EQ(success, expected.first); if (success) @@ -314,10 +334,10 @@ Params> ConvertToBoolData{ {"0 asdad", false, {true, false}}, {"12 asdad", false, {true, true}}, {" 12", true, {true, true}}, {"ttrue", true, {false, true}}, }; -PARAMS_TEST(UtilsTest, ShouldConvertToBool, ConvertToBoolData) +PARAMS_TEST(StringUtilsTest, ShouldConvertToBool, ConvertToBoolData) { auto &[string, full, expected] = GetParam(); - auto [success, result] = sb::cf::details::utils::tryStringTo(string, full); + auto [success, result] = sb::cf::details::StringUtils::tryConvertTo(string, full); EXPECT_EQ(success, expected.first); if (success) diff --git a/Tests/DeserializersTest.cpp b/Tests/Unit/Details/ValueDeserializersMapTest.cpp similarity index 71% rename from Tests/DeserializersTest.cpp rename to Tests/Unit/Details/ValueDeserializersMapTest.cpp index 28dc776..68eace6 100644 --- a/Tests/DeserializersTest.cpp +++ b/Tests/Unit/Details/ValueDeserializersMapTest.cpp @@ -2,19 +2,20 @@ #include #include #include -#include #include -#include "SevenBit/Conf/Details/Deserializers.hpp" -#include "SevenBit/Conf/Details/ValueDeserializersMap.hpp" -#include "Utilities/ParamsTest.hpp" +#include "../../../Include/SevenBit/Conf/Details/Deserializers.hpp" +#include "../../../Include/SevenBit/Conf/Details/ValueDeserializersMap.hpp" +#include "../../Helpers/Utilities/ParamsTest.hpp" -class DeserializersTest : public testing::Test +#include + +class ValueDeserializersMapTest : public testing::Test { protected: static void TearUpTestSuite() {} - DeserializersTest() {} + ValueDeserializersMapTest() {} void SetUp() override {} @@ -23,17 +24,12 @@ class DeserializersTest : public testing::Test static void TearDownTestSuite() {} }; -sb::cf::details::ValueDeserializersMap makeDefaultDeserializersMap() +sb::cf::details::ValueDeserializersMap::Ptr makeDefaultDeserializersMap(const std::string_view defaultType = "string", + const bool throwOnUnknownType = true) { - sb::cf::details::ValueDeserializersMap deserializers; - deserializers.set("string", std::make_unique()); - deserializers.set("bool", std::make_unique()); - deserializers.set("int", std::make_unique()); - deserializers.set("double", std::make_unique()); - deserializers.set("uint", std::make_unique()); - deserializers.set("json", std::make_unique()); - deserializers.set("null", std::make_unique()); - return std::move(deserializers); + auto deserializers = std::make_unique(defaultType, throwOnUnknownType); + sb::cf::details::DefaultDeserializers::add(*deserializers); + return deserializers; } static Params, sb::cf::JsonValue> DeserializeData = { @@ -142,35 +138,44 @@ static Params, sb::cf::JsonVal {"JsOn", R"({"hello": 123456})", sb::cf::JsonObject{{"hello", 123456}}}, {"json", R"({"hello": [1]})", sb::cf::JsonObject{{"hello", sb::cf::JsonArray{1}}}}, {"json", R"({"hello": null})", sb::cf::JsonObject{{"hello", sb::cf::json::null}}}, - + // Unknown + {"Unknown", "hello", std::string{"hello"}}, + {"sad", "hello", std::string{"hello"}}, + {"ddd", "hello", std::string{"hello"}}, + {"as", "hello", std::string{"hello"}}, + {"qwed", "hello", std::string{"hello"}}, + {"few", "hell\to", std::string{"hell\to"}}, + {"dda", "hello", std::string{"hello"}}, + {"qwdv", "", std::string{""}}, + {"das", std::nullopt, std::string{""}}, }; -PARAMS_TEST(DeserializersTest, ShouldDeserializeValue, DeserializeData) +PARAMS_TEST(ValueDeserializersMapTest, ShouldDeserializeValue, DeserializeData) { const auto &[type, value, expected] = GetParam(); - auto deserializers = makeDefaultDeserializersMap(); + const auto deserializers = makeDefaultDeserializersMap("string", false); - auto deserializer = deserializers.getDeserializerFor(type); - EXPECT_TRUE(deserializer); - EXPECT_EQ(deserializer->deserialize(value), expected); + auto &deserializer = deserializers->getDeserializerFor(type); + EXPECT_EQ(deserializer.deserialize(value), expected); } -TEST_F(DeserializersTest, ShouldDeserializeEmptyJsonOption) +TEST_F(ValueDeserializersMapTest, ShouldDeserializeEmptyJsonOption) { - auto deserializers = makeDefaultDeserializersMap(); + const auto deserializers = makeDefaultDeserializersMap(); - auto deserializer = deserializers.getDeserializerFor("json"); - EXPECT_EQ((deserializer->deserialize(std::nullopt)), (sb::cf::JsonValue{})); + auto &deserializer = deserializers->getDeserializerFor("json"); + EXPECT_EQ(deserializer.deserialize(std::nullopt), sb::cf::JsonValue{}); } -TEST_F(DeserializersTest, ShouldNotFoundDeserializer) +TEST_F(ValueDeserializersMapTest, ShouldNotFoundDeserializer) { auto deserializers = makeDefaultDeserializersMap(); - deserializers.set("unknown2", nullptr); + deserializers->set("unknown2", nullptr); - EXPECT_EQ(deserializers.getDeserializersMap().size(), 8); - EXPECT_FALSE(deserializers.getDeserializerFor("unknown")); - EXPECT_FALSE(deserializers.getDeserializerFor("unknown2")); + auto get = [&](std::string_view type) { auto &_ = deserializers->getDeserializerFor(type); }; + EXPECT_EQ(deserializers->getDeserializersMap().size(), 8); + EXPECT_THROW(get("unknown"), sb::cf::ConfigException); + EXPECT_THROW(get("unknown2"), sb::cf::ConfigException); } static Params> FailDeserializeValues = { @@ -225,13 +230,12 @@ static Params> FailDeserialize {"json", ""}, {"json", "\n"}, }; -PARAMS_TEST(DeserializersTest, ShouldFailDeserialize, FailDeserializeValues) +PARAMS_TEST(ValueDeserializersMapTest, ShouldFailDeserialize, FailDeserializeValues) { const auto &[type, value] = GetParam(); - auto deserializers = makeDefaultDeserializersMap(); + const auto deserializers = makeDefaultDeserializersMap(); - auto deserializer = deserializers.getDeserializerFor(type); + auto &deserializer = deserializers->getDeserializerFor(type); - EXPECT_TRUE(deserializer); - EXPECT_ANY_THROW(auto result = deserializer->deserialize(value)); + EXPECT_ANY_THROW(auto result = deserializer.deserialize(value)); } diff --git a/Tests/Unit/EnvironmentVarsParserBuilderTest.cpp b/Tests/Unit/EnvironmentVarsParserBuilderTest.cpp new file mode 100644 index 0000000..e72c851 --- /dev/null +++ b/Tests/Unit/EnvironmentVarsParserBuilderTest.cpp @@ -0,0 +1,127 @@ +#include + +#include "Mocks/DeserializerMock.hpp" +#include "Mocks/SettingSplitterMock.hpp" +#include "Mocks/ValueDeserializersMapMock.hpp" +#include "SevenBit/Conf/Details/EnvironmentVarsParser.hpp" +#include "SevenBit/Conf/Details/SettingSplitter.hpp" +#include "SevenBit/Conf/Details/ValueDeserializersMap.hpp" +#include "SevenBit/Conf/EnvironmentVarsParserBuilder.hpp" + +class EnvironmentVarsParserBuilderTest : public testing::Test +{ + protected: + static void TearUpTestSuite() {} + + EnvironmentVarsParserBuilderTest() {} + + void SetUp() override {} + + void TearDown() override {} + + static void TearDownTestSuite() {} +}; + +TEST_F(EnvironmentVarsParserBuilderTest, ShouldBuildDefault) +{ + const auto parser = sb::cf::EnvironmentVarsParserBuilder{}.build(); + + const auto &casted = dynamic_cast(*parser); + + auto &splitter = dynamic_cast(casted.getSettingSplitter()); + auto &deserializers = + dynamic_cast(casted.getValueDeserializersMap()); + + EXPECT_EQ(splitter.getKeySplitters(), (std::vector{":", "__"})); + EXPECT_EQ(splitter.getSettingSplitters(), std::vector{"="}); + EXPECT_EQ(splitter.getTypeMarkers(), (std::vector{"!", "___"})); + EXPECT_FALSE(splitter.getAllowEmptyKeys()); + + std::vector types; + for (auto &[type, _] : deserializers.getDeserializersMap()) + { + types.push_back(type); + } + std::sort(types.begin(), types.end()); + EXPECT_EQ(types, (std::vector{"bool", "double", "int", "json", "null", "string", "uint"})); + EXPECT_EQ(deserializers.getDefaultType(), "string"); + EXPECT_TRUE(deserializers.getThrowOnUnknownType()); +} + +TEST_F(EnvironmentVarsParserBuilderTest, ShouldUseCustomConfig) +{ + sb::cf::EnvironmentVarsParserBuilder builder; + + sb::cf::EnvironmentVarsParserConfig config; + config.defaultType = "int"; + config.throwOnUnknownType = false; + config.allowEmptyKeys = true; + config.keySplitters = {"/", "++"}; + config.variableSplitters = {"--", "%"}; + config.typeMarkers = {"$", "##"}; + auto parser = builder.useConfig(config).build(); + + auto &casted = dynamic_cast(*parser); + + auto &splitter = dynamic_cast(casted.getSettingSplitter()); + auto &deserializers = + dynamic_cast(casted.getValueDeserializersMap()); + + EXPECT_EQ(splitter.getKeySplitters(), (std::vector{"/", "++"})); + EXPECT_EQ(splitter.getSettingSplitters(), (std::vector{"--", "%"})); + EXPECT_EQ(splitter.getTypeMarkers(), (std::vector{"$", "##"})); + EXPECT_TRUE(splitter.getAllowEmptyKeys()); + + std::vector types; + for (auto &[type, _] : deserializers.getDeserializersMap()) + { + types.push_back(type); + } + std::sort(types.begin(), types.end()); + EXPECT_EQ(types, (std::vector{"bool", "double", "int", "json", "null", "string", "uint"})); + EXPECT_EQ(deserializers.getDefaultType(), "int"); + EXPECT_FALSE(deserializers.getThrowOnUnknownType()); +} + +TEST_F(EnvironmentVarsParserBuilderTest, ShouldUseValueDeserializer) +{ + sb::cf::EnvironmentVarsParserBuilder builder; + + auto parser = builder.useDefaultValueDeserializers() + .useValueDeserializer("newType", std::make_unique()) + .build(); + + auto &casted = dynamic_cast(*parser); + + auto &deserializers = casted.getValueDeserializersMap(); + auto get = [&](std::string_view type) { auto &_ = deserializers.getDeserializerFor(type); }; + EXPECT_TRUE(dynamic_cast(&casted.getSettingSplitter())); + EXPECT_TRUE(dynamic_cast(&casted.getValueDeserializersMap())); + EXPECT_NO_THROW(get("newType")); + EXPECT_NO_THROW(get("int")); + EXPECT_THROW(get("unknown"), sb::cf::ConfigException); +} + +TEST_F(EnvironmentVarsParserBuilderTest, ShouldUseCustomValueDeserializerMap) +{ + sb::cf::EnvironmentVarsParserBuilder builder; + + const auto parser = builder.useValueDeserializersMap(std::make_unique()).build(); + + const auto &casted = dynamic_cast(*parser); + + EXPECT_TRUE(dynamic_cast(&casted.getSettingSplitter())); + EXPECT_TRUE(dynamic_cast(&casted.getValueDeserializersMap())); +} + +TEST_F(EnvironmentVarsParserBuilderTest, ShouldUseCustomSplitter) +{ + sb::cf::EnvironmentVarsParserBuilder builder; + + const auto parser = builder.useSplitter(std::make_unique()).build(); + + const auto &casted = dynamic_cast(*parser); + + EXPECT_TRUE(dynamic_cast(&casted.getSettingSplitter())); + EXPECT_TRUE(dynamic_cast(&casted.getValueDeserializersMap())); +} diff --git a/Tests/Unit/ObjectHolderTest.cpp b/Tests/Unit/ObjectHolderTest.cpp new file mode 100644 index 0000000..9ea8dca --- /dev/null +++ b/Tests/Unit/ObjectHolderTest.cpp @@ -0,0 +1,33 @@ +#include + +#include "SevenBit/Conf/ObjectHolder.hpp" + +class ObjectHolderTest : public testing::Test +{ + protected: + static void TearUpTestSuite() {} + + ObjectHolderTest() {} + + void SetUp() override {} + + void TearDown() override {} + + ~ObjectHolderTest() override = default; + + static void TearDownTestSuite() {} +}; + +TEST_F(ObjectHolderTest, ShouldCastHolder) +{ + const sb::cf::IObject::Ptr holder = sb::cf::ObjectHolder::from(1); + + auto &casted = sb::cf::ObjectHolder::castFrom(*holder); + auto &safeCasted = sb::cf::ObjectHolder::safeCastFrom(*holder); + + EXPECT_EQ(casted.get(), 1); + EXPECT_EQ(&casted, &safeCasted); + + auto act = [&] { auto &_ = sb::cf::ObjectHolder::safeCastFrom(*holder); }; + EXPECT_THROW(act(), std::bad_cast); +} diff --git a/Tests/RunTests.cpp b/Tests/Unit/RunTests.cpp similarity index 100% rename from Tests/RunTests.cpp rename to Tests/Unit/RunTests.cpp diff --git a/Tests/Unit/Sources/AppSettingsConfigurationTest.cpp b/Tests/Unit/Sources/AppSettingsConfigurationTest.cpp new file mode 100644 index 0000000..707b22d --- /dev/null +++ b/Tests/Unit/Sources/AppSettingsConfigurationTest.cpp @@ -0,0 +1,48 @@ +#include +#include + +#include "Mocks/ConfigurationBuilderMock.hpp" +#include "SevenBit/Conf/Sources/AppSettingsConfiguration.hpp" + +class AppSettingsConfiguration : public testing::Test +{ + protected: + ConfigurationBuilderMock mock; + + static void TearUpTestSuite() {} + + AppSettingsConfiguration() {} + + void SetUp() override {} + + void TearDown() override {} + + static void TearDownTestSuite() {} +}; + +TEST_F(AppSettingsConfiguration, ShouldLoadAppSettings) +{ + const auto provider = sb::cf::AppSettingsConfigurationSource::create()->build(mock); + + provider->load(); + + const sb::cf::JsonObject expected = {{"Array", sb::cf::JsonArray{1, 2, 3, 4, 5}}, + {"MySetting", "appsettings.json Value"}, + {"Logging", {{"LogLevel", {{"Default", "Information"}}}}}}; + + EXPECT_EQ(provider->getConfiguration(), expected); +} + +TEST_F(AppSettingsConfiguration, ShouldLoadDevAppSettings) +{ + const auto provider = sb::cf::AppSettingsConfigurationSource::create("dev")->build(mock); + + provider->load(); + + const sb::cf::JsonObject expected = {{"Array", sb::cf::JsonArray{11, 2, 3, 4, 5}}, + {"MySetting", "appsettings.dev.json Value"}, + {"ExtraSetting", "extra appsettings.dev.json Value"}, + {"Logging", {{"LogLevel", {{"Default", "Warning"}}}}}}; + + EXPECT_EQ(provider->getConfiguration(), expected); +} diff --git a/Tests/ChainedConfigurationTest.cpp b/Tests/Unit/Sources/ChainedConfigurationTest.cpp similarity index 64% rename from Tests/ChainedConfigurationTest.cpp rename to Tests/Unit/Sources/ChainedConfigurationTest.cpp index cc123b8..d4ab505 100644 --- a/Tests/ChainedConfigurationTest.cpp +++ b/Tests/Unit/Sources/ChainedConfigurationTest.cpp @@ -2,10 +2,10 @@ #include #include "Mocks/ConfigurationBuilderMock.hpp" -#include "SevenBit/Conf/ChainedConfiguration.hpp" #include "SevenBit/Conf/Exceptions.hpp" -#include "SevenBit/Conf/JsonConfiguration.hpp" -#include "SevenBit/Conf/JsonFileConfiguration.hpp" +#include "SevenBit/Conf/Sources/ChainedConfiguration.hpp" +#include "SevenBit/Conf/Sources/JsonConfiguration.hpp" +#include "SevenBit/Conf/Sources/JsonFileConfiguration.hpp" class ChainedConfigurationTest : public testing::Test { @@ -25,7 +25,7 @@ class ChainedConfigurationTest : public testing::Test TEST_F(ChainedConfigurationTest, ShouldLoadEmptyChainConfig) { - auto provider = sb::cf::ChainedConfigurationSource::create()->build(mock); + const auto provider = sb::cf::ChainedConfigurationSource::create()->build(mock); provider->load(); @@ -39,7 +39,7 @@ TEST_F(ChainedConfigurationTest, ShouldFailCreationDueToNullSource) TEST_F(ChainedConfigurationTest, ShouldFailAddDueToNullSource) { - auto source = + const auto source = sb::cf::ChainedConfigurationSource::create({sb::cf::JsonFileConfigurationSource::create("appsettings.json")}); EXPECT_THROW(source->add(nullptr), sb::cf::NullPointerException); @@ -47,22 +47,22 @@ TEST_F(ChainedConfigurationTest, ShouldFailAddDueToNullSource) TEST_F(ChainedConfigurationTest, ShouldLoadSimpleChainedConfig) { - auto provider = + const auto provider = sb::cf::ChainedConfigurationSource::create({sb::cf::JsonFileConfigurationSource::create("appsettings.json")}) ->build(mock); provider->load(); - sb::cf::JsonObject expected = {{"Array", sb::cf::JsonArray{1, 2, 3, 4, 5}}, - {"MySetting", "appsettings.json Value"}, - {"Logging", {{"LogLevel", {{"Default", "Information"}}}}}}; + const sb::cf::JsonObject expected = {{"Array", sb::cf::JsonArray{1, 2, 3, 4, 5}}, + {"MySetting", "appsettings.json Value"}, + {"Logging", {{"LogLevel", {{"Default", "Information"}}}}}}; EXPECT_EQ(provider->getConfiguration(), expected); } TEST_F(ChainedConfigurationTest, ShouldLoadComplexChainedConfig) { - auto provider = + const auto provider = sb::cf::ChainedConfigurationSource::create({sb::cf::JsonFileConfigurationSource::create("appsettings.json"), sb::cf::JsonFileConfigurationSource::create("appsettings.dev.json"), sb::cf::JsonConfigurationSource::create({{"number", 1}})}) @@ -70,11 +70,11 @@ TEST_F(ChainedConfigurationTest, ShouldLoadComplexChainedConfig) provider->load(); - sb::cf::JsonObject expected = {{"Array", sb::cf::JsonArray{11, 2, 3, 4, 5}}, - {"number", 1}, - {"MySetting", "appsettings.dev.json Value"}, - {"ExtraSetting", "extra appsettings.dev.json Value"}, - {"Logging", {{"LogLevel", {{"Default", "Warning"}}}}}}; + const sb::cf::JsonObject expected = {{"Array", sb::cf::JsonArray{11, 2, 3, 4, 5}}, + {"number", 1}, + {"MySetting", "appsettings.dev.json Value"}, + {"ExtraSetting", "extra appsettings.dev.json Value"}, + {"Logging", {{"LogLevel", {{"Default", "Warning"}}}}}}; EXPECT_EQ(provider->getConfiguration(), expected); } diff --git a/Tests/Unit/Sources/CommandLineConfigurationTest.cpp b/Tests/Unit/Sources/CommandLineConfigurationTest.cpp new file mode 100644 index 0000000..050dab9 --- /dev/null +++ b/Tests/Unit/Sources/CommandLineConfigurationTest.cpp @@ -0,0 +1,122 @@ +#include + +#include "Mocks/ConfigurationBuilderMock.hpp" +#include "SevenBit/Conf/Exceptions.hpp" +#include "SevenBit/Conf/Sources/CommandLineConfiguration.hpp" + +class CommandLineConfigurationTest : public testing::Test +{ + protected: + ConfigurationBuilderMock mock; + + static void TearUpTestSuite() {} + + CommandLineConfigurationTest() {} + + void SetUp() override {} + + void TearDown() override {} + + static void TearDownTestSuite() {} +}; + +TEST_F(CommandLineConfigurationTest, ShouldFailCreationDueToNullSource) +{ + const std::vector args; + EXPECT_THROW(auto result = sb::cf::CommandLineConfigurationSource::create(args, nullptr), + sb::cf::NullPointerException); +} + +TEST_F(CommandLineConfigurationTest, ShouldLoadConfFromArgs) +{ + constexpr const char *const argv[] = {"program/path", + "--string!string=test", + "--string_sep!string", + "sep_value", + "--list:0!string=string", + "double!double=1.22", + "true_bool!bool=-1", + "false_bool!bool=0", + "false_bool2!bool=false", + "true_bool2!bool=true", + "--list:1=string1", + "list:2!string=string2", + "--object:inner:object=string", + "--int_list:0!int=33", + "--int_list:1!int=22", + "int_list:2!int=11", + "--last!int"}; + constexpr int size = sizeof(argv) / sizeof(char *); + const auto provider = + sb::cf::CommandLineConfigurationSource::create(size, argv, sb::cf::CommandLineParserBuilder{}.build()) + ->build(mock); + + provider->load(); + + const sb::cf::JsonObject expected = {{"string", "test"}, + {"string_sep", "sep_value"}, + {"double", 1.22}, + {"false_bool", false}, + {"false_bool2", false}, + {"true_bool", true}, + {"true_bool2", true}, + {"last", 0}, + {"list", sb::cf::JsonArray{"string", "string1", "string2"}}, + {"int_list", sb::cf::JsonArray{33, 22, 11}}, + {"object", {{"inner", {{"object", "string"}}}}}}; + + EXPECT_EQ(provider->getConfiguration(), expected); +} + +TEST_F(CommandLineConfigurationTest, ShouldLoadConfFromArgsVector) +{ + const std::vector args = {"--string!string=test", + "--string_sep!string", + "sep_value", + "--list:0!string=string", + "double!double=1.22", + "true_bool!bool=-1", + "false_bool!bool=0", + "false_bool2!bool=false", + "true_bool2!bool=true", + "--list:1=string1", + "list:2!string=string2", + "--object:inner:object=string", + "--int_list:0!int=33", + "--int_list:1!int=22", + "int_list:2!int=11", + "--last!int"}; + const auto provider = + sb::cf::CommandLineConfigurationSource::create(args, sb::cf::CommandLineParserBuilder{}.build())->build(mock); + + provider->load(); + + const sb::cf::JsonObject expected = {{"string", "test"}, + {"double", 1.22}, + {"string_sep", "sep_value"}, + {"false_bool", false}, + {"false_bool2", false}, + {"true_bool", true}, + {"true_bool2", true}, + {"last", 0}, + {"list", sb::cf::JsonArray{"string", "string1", "string2"}}, + {"int_list", sb::cf::JsonArray{33, 22, 11}}, + {"object", {{"inner", {{"object", "string"}}}}}}; + + EXPECT_EQ(provider->getConfiguration(), expected); +} + +TEST_F(CommandLineConfigurationTest, ShouldLoadEmptyConfFromArgs) +{ + constexpr const char *argv[] = { + "exec/path", + }; + constexpr int size = sizeof(argv) / sizeof(char *); + const auto provider = + sb::cf::CommandLineConfigurationSource::create(size, argv, sb::cf::CommandLineParserBuilder{}.build()) + ->build(mock); + + provider->load(); + + EXPECT_TRUE(provider->getConfiguration().empty()); +} diff --git a/Tests/EnvironmentVarsConfigurationTest.cpp b/Tests/Unit/Sources/EnvironmentVarsConfigurationTest.cpp similarity index 50% rename from Tests/EnvironmentVarsConfigurationTest.cpp rename to Tests/Unit/Sources/EnvironmentVarsConfigurationTest.cpp index 8a3d39d..207e623 100644 --- a/Tests/EnvironmentVarsConfigurationTest.cpp +++ b/Tests/Unit/Sources/EnvironmentVarsConfigurationTest.cpp @@ -1,9 +1,9 @@ #include #include -#include "Mocks/ConfigurationBuilderMock.hpp" -#include "SevenBit/Conf/EnvironmentVarsConfiguration.hpp" -#include "SevenBit/Conf/Exceptions.hpp" +#include "../../../Include/SevenBit/Conf/Exceptions.hpp" +#include "../../../Include/SevenBit/Conf/Sources/EnvironmentVarsConfiguration.hpp" +#include "../../Helpers/Mocks/ConfigurationBuilderMock.hpp" #ifdef _WIN32 #define _7BIT_CONF_PUT_ENV _putenv @@ -29,7 +29,7 @@ class EnvironmentVarsConfigurationTest : public testing::Test _7BIT_CONF_PUT_ENV((char *)"7BIT_CONFIG_TEST_NUMBER_LIST__0___int=1"); _7BIT_CONF_PUT_ENV((char *)"7BIT_CONFIG_TEST_NUMBER_LIST__1___int=3"); _7BIT_CONF_PUT_ENV((char *)"7BIT_CONFIG_TEST_NUMBER_LIST__2___int=22"); - _7BIT_CONF_PUT_ENV((char *)"7BIT_CONFIG_TEST_JSON___json={\"key\": \"value\"}"); + _7BIT_CONF_PUT_ENV((char *)R"(7BIT_CONFIG_TEST_JSON___json={"key": "value"})"); _7BIT_CONF_PUT_ENV((char *)"7BIT_CONFIG_TEST_OBJECT__INNER__OBJECT=string"); _7BIT_CONF_PUT_ENV((char *)"7BIT_OTHER_CONFIG_TEST_STRING=string2"); } @@ -54,42 +54,48 @@ TEST_F(EnvironmentVarsConfigurationTest, ShouldFailProviderCreationDueToNullSour TEST_F(EnvironmentVarsConfigurationTest, ShouldLoadConfFromEnvVars) { - auto provider = sb::cf::EnvironmentVarsConfigurationSource::create("7BIT_CONFIG_")->build(mock); + const auto provider = sb::cf::EnvironmentVarsConfigurationSource::create( + "7BIT_CONFIG_", sb::cf::EnvironmentVarsParserBuilder{}.build()) + ->build(mock); provider->load(); - sb::cf::JsonObject expected = {{"TEST_STRING", "test"}, - {"TEST_DOUBLE", 1.4}, - {"TEST_BOOL", true}, - {"TEST_NUMBER_LIST", sb::cf::JsonArray{1, 3, 22}}, - {"TEST_STRING_LIST", sb::cf::JsonArray{"string", "string1", "string2"}}, - {"TEST_OBJECT", {{"INNER", {{"OBJECT", "string"}}}}}, - {"TEST_JSON", {{"key", "value"}}}}; + const sb::cf::JsonObject expected = {{"TEST_STRING", "test"}, + {"TEST_DOUBLE", 1.4}, + {"TEST_BOOL", true}, + {"TEST_NUMBER_LIST", sb::cf::JsonArray{1, 3, 22}}, + {"TEST_STRING_LIST", sb::cf::JsonArray{"string", "string1", "string2"}}, + {"TEST_OBJECT", {{"INNER", {{"OBJECT", "string"}}}}}, + {"TEST_JSON", {{"key", "value"}}}}; EXPECT_EQ(provider->getConfiguration(), expected); } TEST_F(EnvironmentVarsConfigurationTest, ShouldLoadConfFromEnvVarsWithPrefix) { - auto provider = sb::cf::EnvironmentVarsConfigurationSource::create("7BIT_")->build(mock); + const auto provider = + sb::cf::EnvironmentVarsConfigurationSource::create("7BIT_", sb::cf::EnvironmentVarsParserBuilder{}.build()) + ->build(mock); provider->load(); - sb::cf::JsonObject expected = {{"CONFIG_TEST_STRING", "test"}, - {"CONFIG_TEST_DOUBLE", 1.4}, - {"CONFIG_TEST_BOOL", true}, - {"OTHER_CONFIG_TEST_STRING", "string2"}, - {"CONFIG_TEST_NUMBER_LIST", sb::cf::JsonArray{1, 3, 22}}, - {"CONFIG_TEST_STRING_LIST", sb::cf::JsonArray{"string", "string1", "string2"}}, - {"CONFIG_TEST_OBJECT", {{"INNER", {{"OBJECT", "string"}}}}}, - {"CONFIG_TEST_JSON", {{"key", "value"}}}}; + const sb::cf::JsonObject expected = {{"CONFIG_TEST_STRING", "test"}, + {"CONFIG_TEST_DOUBLE", 1.4}, + {"CONFIG_TEST_BOOL", true}, + {"OTHER_CONFIG_TEST_STRING", "string2"}, + {"CONFIG_TEST_NUMBER_LIST", sb::cf::JsonArray{1, 3, 22}}, + {"CONFIG_TEST_STRING_LIST", sb::cf::JsonArray{"string", "string1", "string2"}}, + {"CONFIG_TEST_OBJECT", {{"INNER", {{"OBJECT", "string"}}}}}, + {"CONFIG_TEST_JSON", {{"key", "value"}}}}; EXPECT_EQ(provider->getConfiguration(), expected); } TEST_F(EnvironmentVarsConfigurationTest, ShouldNotLoadConfFromEnvVars) { - auto provider = sb::cf::EnvironmentVarsConfigurationSource::create("7BITCONFIGURATION_")->build(mock); + const auto provider = sb::cf::EnvironmentVarsConfigurationSource::create( + "7BITCONFIGURATION_", sb::cf::EnvironmentVarsParserBuilder{}.build()) + ->build(mock); provider->load(); diff --git a/Tests/InMemoryConfigurationTest.cpp b/Tests/Unit/Sources/InMemoryConfigurationTest.cpp similarity index 81% rename from Tests/InMemoryConfigurationTest.cpp rename to Tests/Unit/Sources/InMemoryConfigurationTest.cpp index 6840938..73e16ed 100644 --- a/Tests/InMemoryConfigurationTest.cpp +++ b/Tests/Unit/Sources/InMemoryConfigurationTest.cpp @@ -1,9 +1,8 @@ #include -#include #include "Mocks/ConfigurationBuilderMock.hpp" #include "SevenBit/Conf/Exceptions.hpp" -#include "SevenBit/Conf/InMemoryConfiguration.hpp" +#include "SevenBit/Conf/Sources/InMemoryConfiguration.hpp" class InMemoryConfigurationTest : public testing::Test { @@ -28,12 +27,12 @@ TEST_F(InMemoryConfigurationTest, ShouldFailProviderCreationDueToNullSource) TEST_F(InMemoryConfigurationTest, ShouldLoadSimpleSettingConfiguration) { - auto provider = + const auto provider = sb::cf::InMemoryConfigurationSource::create({{"yes:yes:inner", sb::cf::JsonArray{1, 2, 3, 4, 5}}})->build(mock); provider->load(); - sb::cf::JsonObject expected = {{"yes", {{"yes", {{"inner", sb::cf::JsonArray{1, 2, 3, 4, 5}}}}}}}; + const sb::cf::JsonObject expected = {{"yes", {{"yes", {{"inner", sb::cf::JsonArray{1, 2, 3, 4, 5}}}}}}}; EXPECT_EQ(provider->getConfiguration(), expected); } diff --git a/Tests/JsonConfigurationTest.cpp b/Tests/Unit/Sources/JsonConfigurationTest.cpp similarity index 76% rename from Tests/JsonConfigurationTest.cpp rename to Tests/Unit/Sources/JsonConfigurationTest.cpp index 6132fcd..2b3f382 100644 --- a/Tests/JsonConfigurationTest.cpp +++ b/Tests/Unit/Sources/JsonConfigurationTest.cpp @@ -2,7 +2,7 @@ #include "Mocks/ConfigurationBuilderMock.hpp" #include "SevenBit/Conf/Exceptions.hpp" -#include "SevenBit/Conf/JsonConfiguration.hpp" +#include "SevenBit/Conf/Sources/JsonConfiguration.hpp" class JsonConfigurationTest : public testing::Test { @@ -27,11 +27,11 @@ TEST_F(JsonConfigurationTest, ShouldFailProviderCreationDueToNullSource) TEST_F(JsonConfigurationTest, ShouldLoadSimpleJsonConfig) { - auto provider = sb::cf::JsonConfigurationSource::create({{"hello", 12345}})->build(mock); + const auto provider = sb::cf::JsonConfigurationSource::create({{"hello", 12345}})->build(mock); provider->load(); - sb::cf::JsonObject expected = {{"hello", 12345}}; + const sb::cf::JsonObject expected = {{"hello", 12345}}; EXPECT_EQ(provider->getConfiguration(), expected); } diff --git a/Tests/JsonFileConfigurationTest.cpp b/Tests/Unit/Sources/JsonFileConfigurationTest.cpp similarity index 61% rename from Tests/JsonFileConfigurationTest.cpp rename to Tests/Unit/Sources/JsonFileConfigurationTest.cpp index 8f25e2f..288f326 100644 --- a/Tests/JsonFileConfigurationTest.cpp +++ b/Tests/Unit/Sources/JsonFileConfigurationTest.cpp @@ -2,7 +2,7 @@ #include "Mocks/ConfigurationBuilderMock.hpp" #include "SevenBit/Conf/Exceptions.hpp" -#include "SevenBit/Conf/JsonFileConfiguration.hpp" +#include "SevenBit/Conf/Sources/JsonFileConfiguration.hpp" class JsonFileConfigurationTest : public testing::Test { @@ -27,20 +27,20 @@ TEST_F(JsonFileConfigurationTest, ShouldFailProviderCreationDueToNullSource) TEST_F(JsonFileConfigurationTest, ShouldLoadSimpleJsonConfigFile) { - auto provider = sb::cf::JsonFileConfigurationSource::create("appsettings.json")->build(mock); + const auto provider = sb::cf::JsonFileConfigurationSource::create("appsettings.json")->build(mock); provider->load(); - sb::cf::JsonObject expected = {{"Array", sb::cf::JsonArray{1, 2, 3, 4, 5}}, - {"MySetting", "appsettings.json Value"}, - {"Logging", {{"LogLevel", {{"Default", "Information"}}}}}}; + const sb::cf::JsonObject expected = {{"Array", sb::cf::JsonArray{1, 2, 3, 4, 5}}, + {"MySetting", "appsettings.json Value"}, + {"Logging", {{"LogLevel", {{"Default", "Information"}}}}}}; EXPECT_EQ(provider->getConfiguration(), expected); } TEST_F(JsonFileConfigurationTest, ShouldNotLoadNonExistingJsonConfigFile) { - auto provider = sb::cf::JsonFileConfigurationSource::create("nonExisting.json", true)->build(mock); + const auto provider = sb::cf::JsonFileConfigurationSource::create("nonExisting.json", true)->build(mock); provider->load(); @@ -49,14 +49,14 @@ TEST_F(JsonFileConfigurationTest, ShouldNotLoadNonExistingJsonConfigFile) TEST_F(JsonFileConfigurationTest, ShouldFailLoadingNonExistingJsonConfigFile) { - auto provider = sb::cf::JsonFileConfigurationSource ::create("nonExisting.json")->build(mock); + const auto provider = sb::cf::JsonFileConfigurationSource ::create("nonExisting.json")->build(mock); EXPECT_THROW(provider->load(), sb::cf::ConfigFileNotFoundException); } TEST_F(JsonFileConfigurationTest, ShouldFailLoadingBadJsonConfigFile) { - auto provider = sb::cf::JsonFileConfigurationSource ::create("bad.json")->build(mock); + const auto provider = sb::cf::JsonFileConfigurationSource ::create("bad.json")->build(mock); EXPECT_THROW(provider->load(), sb::cf::BadConfigFileException); } diff --git a/Tests/JsonStreamConfigurationTest.cpp b/Tests/Unit/Sources/JsonStreamConfigurationTest.cpp similarity index 68% rename from Tests/JsonStreamConfigurationTest.cpp rename to Tests/Unit/Sources/JsonStreamConfigurationTest.cpp index 0ce74a6..d2d5ff4 100644 --- a/Tests/JsonStreamConfigurationTest.cpp +++ b/Tests/Unit/Sources/JsonStreamConfigurationTest.cpp @@ -3,7 +3,7 @@ #include "Mocks/ConfigurationBuilderMock.hpp" #include "SevenBit/Conf/Exceptions.hpp" -#include "SevenBit/Conf/JsonStreamConfiguration.hpp" +#include "SevenBit/Conf/Sources/JsonStreamConfiguration.hpp" class JsonStreamConfigurationTest : public testing::Test { @@ -30,13 +30,13 @@ TEST_F(JsonStreamConfigurationTest, ShouldLoadConfigFromStream) { std::stringstream stream; - stream << "{\"hello\": 12345, \"string\": \"asdf\"}"; + stream << R"({"hello": 12345, "string": "asdf"})"; - auto provider = sb::cf::JsonStreamConfigurationSource::create(stream)->build(mock); + const auto provider = sb::cf::JsonStreamConfigurationSource::create(stream)->build(mock); provider->load(); - sb::cf::JsonObject expected = {{"hello", 12345}, {"string", "asdf"}}; + const sb::cf::JsonObject expected = {{"hello", 12345}, {"string", "asdf"}}; EXPECT_EQ(provider->getConfiguration(), expected); } @@ -47,7 +47,7 @@ TEST_F(JsonStreamConfigurationTest, ShouldFailLoadingConfigFromBadStream) stream << "\"hello\""; - auto provider = sb::cf::JsonStreamConfigurationSource::create(stream)->build(mock); + const auto provider = sb::cf::JsonStreamConfigurationSource::create(stream)->build(mock); EXPECT_THROW(provider->load(), sb::cf::BadStreamException); } @@ -56,9 +56,9 @@ TEST_F(JsonStreamConfigurationTest, ShouldFailLoadingDueToDoubleStreamRead) { std::stringstream stream; - stream << "{\"hello\": 12345, \"string\": \"asdf\"}"; + stream << R"({"hello": 12345, "string": "asdf"})"; - auto provider = sb::cf::JsonStreamConfigurationSource::create(stream)->build(mock); + const auto provider = sb::cf::JsonStreamConfigurationSource::create(stream)->build(mock); provider->load(); diff --git a/Tests/Unit/Sources/KeyPerFileConfigurationTest.cpp b/Tests/Unit/Sources/KeyPerFileConfigurationTest.cpp new file mode 100644 index 0000000..9288ea1 --- /dev/null +++ b/Tests/Unit/Sources/KeyPerFileConfigurationTest.cpp @@ -0,0 +1,87 @@ +#include +#include + +#include "Mocks/ConfigurationBuilderMock.hpp" +#include "SevenBit/Conf/Sources/KeyPerFileConfiguration.hpp" + +class KeyPerFileConfigurationTest : public testing::Test +{ + protected: + ConfigurationBuilderMock mock; + + static void TearUpTestSuite() {} + + KeyPerFileConfigurationTest() {} + + void SetUp() override {} + + void TearDown() override {} + + static void TearDownTestSuite() {} +}; + +TEST_F(KeyPerFileConfigurationTest, ShouldFailLoadingNonExistingDirectory) +{ + const auto source = sb::cf::KeyPerFileConfigurationSource::create("nonexisting"); + + EXPECT_ANY_THROW(source->build(mock)); +} + +TEST_F(KeyPerFileConfigurationTest, ShouldLoadNonExistingDirectory) +{ + const auto provider = sb::cf::KeyPerFileConfigurationSource::create("nonexisting", true)->build(mock); + + provider->load(); + + EXPECT_TRUE(provider->getConfiguration().empty()); +} + +TEST_F(KeyPerFileConfigurationTest, ShouldLoadDirectoryConfig) +{ + const auto provider = sb::cf::KeyPerFileConfigurationSource::create("Directory")->build(mock); + + provider->load(); + + const sb::cf::JsonObject expected = {{"settingOne", + {{"number", 12345}, + {"array", sb::cf::JsonArray{1, 2, 3, 4, 5, 6}}, + {"string", "string"}, + {"object", {{"num", 134}, {"string", "string"}}}}}, + {"settingTwo", + {{"array", sb::cf::JsonArray{1}}, + {"string", "dev"}, + {"object", {{"inner", {{"num", 12345}}}, {"string", "dev"}}}}}}; + + EXPECT_EQ(provider->getConfiguration(), expected); +} + +TEST_F(KeyPerFileConfigurationTest, ShloudLoadFilteredConfigFiles) +{ + const auto provider = sb::cf::KeyPerFileConfigurationSource::create("Directory", false, "settingOne")->build(mock); + + provider->load(); + + const sb::cf::JsonObject expected = {{"settingTwo", + {{"array", sb::cf::JsonArray{1}}, + {"string", "dev"}, + {"object", {{"inner", {{"num", 12345}}}, {"string", "dev"}}}}}}; + + EXPECT_EQ(provider->getConfiguration(), expected); +} + +TEST_F(KeyPerFileConfigurationTest, ShloudLoadFilteredConditionConfigFiles) +{ + const auto provider = + sb::cf::KeyPerFileConfigurationSource::create("Directory", false, [](const std::filesystem::path &path) { + return path.filename() == "settingOne.json"; + })->build(mock); + + provider->load(); + + const sb::cf::JsonObject expected = {{"settingTwo", + {{"array", sb::cf::JsonArray{1}}, + {"string", "dev"}, + {"object", {{"inner", {{"num", 12345}}}, {"string", "dev"}}}}}}; + + EXPECT_EQ(provider->getConfiguration(), expected); +} diff --git a/Tests/MapConfigutationTest.cpp b/Tests/Unit/Sources/MapConfigutationTest.cpp similarity index 51% rename from Tests/MapConfigutationTest.cpp rename to Tests/Unit/Sources/MapConfigutationTest.cpp index 8d646e3..4af1a90 100644 --- a/Tests/MapConfigutationTest.cpp +++ b/Tests/Unit/Sources/MapConfigutationTest.cpp @@ -1,10 +1,9 @@ #include -#include #include "Mocks/ConfigurationBuilderMock.hpp" #include "SevenBit/Conf/Exceptions.hpp" -#include "SevenBit/Conf/JsonConfiguration.hpp" -#include "SevenBit/Conf/MapConfiguration.hpp" +#include "SevenBit/Conf/Sources/JsonConfiguration.hpp" +#include "SevenBit/Conf/Sources/MapConfiguration.hpp" class MapConfigurationTest : public testing::Test { @@ -29,16 +28,17 @@ TEST_F(MapConfigurationTest, ShouldFailProviderCreationDueToNullSource) TEST_F(MapConfigurationTest, ShouldMapSimpleConfiguration) { - auto provider = sb::cf::MapConfigurationSource::create(sb::cf::JsonConfigurationSource::create({{"yes", 12345}}), - [](sb::cf::JsonObject ob) { - ob["yes2"] = "yes"; - return std::move(ob); - }) - ->build(mock); + const auto provider = + sb::cf::MapConfigurationSource::create(sb::cf::JsonConfigurationSource::create({{"yes", 12345}}), + [](sb::cf::JsonObject ob) { + ob["yes2"] = "yes"; + return std::move(ob); + }) + ->build(mock); provider->load(); - sb::cf::JsonObject expected = {{"yes", 12345}, {"yes2", "yes"}}; + const sb::cf::JsonObject expected = {{"yes", 12345}, {"yes2", "yes"}}; EXPECT_EQ(provider->getConfiguration(), expected); } diff --git a/Tests/Unit/TestTemplate.cpp b/Tests/Unit/TestTemplate.cpp new file mode 100644 index 0000000..c8f294e --- /dev/null +++ b/Tests/Unit/TestTemplate.cpp @@ -0,0 +1,19 @@ +#include + +class Template : public testing::Test +{ + protected: + static void TearUpTestSuite() {} + + Template() {} + + void SetUp() override {} + + void TearDown() override {} + + ~Template() override = default; + + static void TearDownTestSuite() {} +}; + +TEST_F(Template, ExampleTest) { EXPECT_TRUE(true); } diff --git a/conanfile.txt b/conanfile.txt deleted file mode 100644 index 40bae2c..0000000 --- a/conanfile.txt +++ /dev/null @@ -1,9 +0,0 @@ -[requires] -gtest/1.14.0 -taocpp-json/1.0.0-beta.14 - -[generators] -CMakeDeps -CMakeToolchain -[layout] -cmake_layout