[
  {
    "path": ".github/ISSUE_TEMPLATE.md",
    "content": "Don't forget the label!\n\n - If you have a question, use the \"question\" tag.\n"
  },
  {
    "path": ".gitignore",
    "content": "*~\n*.swp\n*.orig\n*.kdev4\nbuild*\n.kdev*\n*.kdev4\npackages/\n.vs\nx64/\n*sdf\n*.pc\n\n# Compiled source #\n###################\n*.com\n*.class\n*.dll\n*.exe\n*.o\n*.so\n\n# Packages #\n############\n# it's better to unpack these files and commit the raw source\n# git has its own built in compression methods\n*.7z\n*.dmg\n*.gz\n*.iso\n*.jar\n*.rar\n*.tar\n*.zip\n\n# Logs and databases #\n######################\n*.log\n*.sql\n*.sqlite\n\n# OS generated files #\n######################\n.DS_Store\n.DS_Store?\n._*\n.Spotlight-V100\n.Trashes\nehthumbs.db\nThumbs.db\n## my files and cmake\nout\nout.*\n\n# IDE files #\n#############\nnbproject\n.~lock.*\n.buildpath\n.idea\n.project\n.settings\ncomposer.lock\n\n#Cmake\nCMakeCache.txt\nCMakeFiles\nCMakeScripts\nMakefile\ncmake_install.cmake\ninstall_manifest.txt\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: cpp\nsudo: required\ndist: trusty\nenv:\n  matrix:\n  - DISTRO=ubuntu-16.04 CONFIG=Debug\n  - DISTRO=ubuntu-16.04 CONFIG=Release\n  - DISTRO=ubuntu-18.04 CONFIG=Debug\n  - DISTRO=ubuntu-18.04 CONFIG=Release\n  - DISTRO=ubuntu-20.04 CONFIG=Debug\n  - DISTRO=ubuntu-20.04 CONFIG=Release\n  - DISTRO=debian-10    CONFIG=Debug\n  - DISTRO=debian-10    CONFIG=Release\n  - DISTRO=opensuse-15  CONFIG=Debug\nservices:\n- docker\nbefore_install:\n- docker build config/docker/${DISTRO} -t dev/zookeeper-cpp/${DISTRO}\nscript:\n- echo ${COVERALLS_REPO_TOKEN} > ${TRAVIS_BUILD_DIR}/coveralls-repo-token\n- if [[ ${CONFIG} == \"Debug\" ]]; then ./config/dev-env ${DISTRO} -- ./config/run-tests; fi\n- if [[ ${CONFIG} == \"Release\" ]]; then ./config/make-package --dockerize ${DISTRO}; fi\nafter_success:\n- if [[ ${DISTRO} != \"ubuntu-20.04\" ]]; then echo \"Skipping documentation publishing due to non-main build environment\"; exit 0; fi\n- if [[ ${CONFIG} != \"Debug\" ]]; then echo \"Skipp documentation publishing due to non-debug build\"; exit 0; fi\n- GIT_CURRENT_HASH=$(git rev-parse HEAD)\n- GIT_MASTER_HASH=$(git rev-parse master)\n- GIT_REMOTE_NAME=$(git remote)\n- GIT_REMOTE_FETCH_PATH=$(git remote --verbose | grep -P '^'${GIT_REMOTE_NAME}'.*\\(fetch\\)$'\n  | awk '{print $2}')\n- GIT_EXPECTED_PATH=https://github.com/tgockel/zookeeper-cpp.git\n- echo \"GIT_CURRENT_HASH=${GIT_CURRENT_HASH} GIT_REMOTE_NAME=${GIT_REMOTE_NAME} GIT_REMOTE_FETCH_PATH=${GIT_REMOTE_FETCH_PATH}\"\n- if [[ ${GIT_CURRENT_HASH} != ${GIT_MASTER_HASH} ]]; then echo \"Skipping documentation publishing due to non-master ${GIT_CURRENT_HASH} (master=${GIT_MASTER_HASH})\"; exit 0; fi\n- if [[ ${GIT_REMOTE_FETCH_PATH} != ${GIT_EXPECTED_PATH} ]]; then echo \"Skipping documentation publishing due to non-mainline remote ${GIT_REMOTE_FETCH_PATH}\"; exit 0; fi\n- sudo add-apt-repository --yes ppa:libreoffice/ppa\n- sudo apt-get update\n- sudo apt-get install --yes doxygen graphviz texlive-full\n- openssl aes-256-cbc -K $encrypted_513b1ad04072_key -iv $encrypted_513b1ad04072_iv -in config/travisci_rsa.enc -out config/travisci_rsa -d\n- chmod 0600 config/travisci_rsa\n- cp config/travisci_rsa ~/.ssh/id_rsa\n- \"./config/publish-doxygen\"\n"
  },
  {
    "path": "AUTHORS",
    "content": "Contributors\n============\n\nTravis Gockel <travis@gockelhut.com>\n"
  },
  {
    "path": "CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.5)\n\nfile(READ src/zk/config.hpp CONFIG_HPP_STR)\nstring(REGEX REPLACE \".*# *define +ZKPP_VERSION_MAJOR +([0-9]+).*\" \"\\\\1\" ZKPP_VERSION_MAJOR \"${CONFIG_HPP_STR}\")\nstring(REGEX REPLACE \".*# *define +ZKPP_VERSION_MINOR +([0-9]+).*\" \"\\\\1\" ZKPP_VERSION_MINOR \"${CONFIG_HPP_STR}\")\nstring(REGEX REPLACE \".*# *define +ZKPP_VERSION_PATCH +([0-9]+).*\" \"\\\\1\" ZKPP_VERSION_PATCH \"${CONFIG_HPP_STR}\")\n\nset(ZKPP_VERSION \"${ZKPP_VERSION_MAJOR}.${ZKPP_VERSION_MINOR}.${ZKPP_VERSION_PATCH}\")\nproject(zookeeper-cpp\n        LANGUAGES CXX\n        VERSION \"${ZKPP_VERSION}\"\n       )\nset(PROJECT_SO_VERSION \"${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}\")\nmessage(STATUS \"Software Version: ${ZKPP_VERSION}\")\n\n################################################################################\n# CMake                                                                        #\n################################################################################\n\ncmake_policy(VERSION 3.5)\ncmake_policy(SET CMP0037 OLD) # allow generation of \"test\" target\nset(CMAKE_REQUIRED_QUIET YES) # tell check_include_file_cxx to keep quiet\n\nlist(APPEND CMAKE_MODULE_PATH \"${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules/\")\n\ninclude(BuildFunctions)\ninclude(CheckIncludeFileCXX)\ninclude(ConfigurationSetting)\ninclude(ListSplit)\ninclude(ZooKeeper)\n\n################################################################################\n# Build Configuration                                                          #\n################################################################################\n\nif (NOT CMAKE_BUILD_TYPE)\n  set(CMAKE_BUILD_TYPE \"Debug\")\n  message(STATUS \"No build type selected, default to ${CMAKE_BUILD_TYPE}\")\nendif()\n\nset(VALID_BUILD_TYPES Debug Release)\nif(NOT ${CMAKE_BUILD_TYPE} IN_LIST VALID_BUILD_TYPES)\n  message(FATAL_ERROR \"Invalid CMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}\\nValid build types are: ${VALID_BUILD_TYPES}\")\nendif()\nmessage(STATUS \"Configuration: ${CMAKE_BUILD_TYPE}\")\n\nset(ZKPP_SERVER_VERSIONS \"3.5.5;3.4.14\"\n    CACHE STRING \"The ZooKeeper server versions to run tests against. The first in the list is the default.\"\n   )\n\nmessage(STATUS \"Features:\")\nbuild_option(NAME       CODE_COVERAGE\n             DOC        \"Enable code coverage (turns on the test-coverage target)\"\n             DEFAULT    OFF\n             CONFIGS_ON Debug\n            )\n\nconfiguration_setting(NAME    BUFFER\n                      DOC     \"Type to use for zk::buffer\"\n                      DEFAULT STD_VECTOR\n                      OPTIONS\n                        STD_VECTOR\n                        STD_STRING\n                        CUSTOM\n                     )\n\nconfiguration_setting(NAME    FUTURE\n                      DOC     \"Type to use for zk::future<T> and zk::promise<T>\"\n                      DEFAULT STD\n                      OPTIONS\n                        STD\n                        STD_EXPERIMENTAL\n                        BOOST\n                        CUSTOM\n                     )\n\nset(CXX_STANDARD c++17\n    CACHE STRING \"The language standard to target for C++.\"\n   )\n\nset(CXX_WARNINGS \"-Wall -Wextra -Wconversion -Werror\")\nset(CXX_EXTRA_FLAGS \"-Wl,--no-as-needed\")\n\nset(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} --std=${CXX_STANDARD} ${CXX_WARNINGS} -ggdb3 ${CXX_EXTRA_FLAGS}\")\nset(CMAKE_CXX_FLAGS_DEBUG   \"${CMAKE_CXX_FLAGS_DEBUG} -DZKPP_DEBUG=1\")\nset(CMAKE_CXX_FLAGS_RELEASE \"${CMAKE_CXX_FLAGS_RELEASE} -O3\")\n\n################################################################################\n# External Libraries                                                           #\n################################################################################\n\nfind_library(zookeeper_LIBRARIES zookeeper_mt)\nset(ZKPP_LIB_DEPENDENCIES ${ZKPP_LIB_DEPENDENCIES} ${zookeeper_LIBRARIES})\n\ninclude_directories(\"${PROJECT_SOURCE_DIR}/src\")\n\nif (ZKPP_BUILD_SETTING_FUTURE STREQUAL \"BOOST\")\n    find_package(Boost\n                 1.52.0\n                 REQUIRED\n                 thread)\n    set(ZKPP_LIB_DEPENDENCIES ${ZKPP_LIB_DEPENDENCIES} ${Boost_LIBRARIES})\nendif()\n\n\n\n################################################################################\n# GTest                                                                        #\n################################################################################\n\nfind_package(GTest)\n\nif(GTest_FOUND)\n  # Make it look like find_library\n  set(gtest_LIBRARIES GTest::GTest)\nelseif(EXISTS \"/usr/src/googletest/googletest/src\" OR EXISTS \"/usr/src/gtest/src/\")\n  # GTest's packaging on Ubuntu (googletest or libgtest-dev, depending on which version) contains all the source files\n  # instead of a library and headers. CMake has a package discovery for GTest, but it does not pick up on this for you,\n  # so we'll build it manually here.\n  message(STATUS \"GTest is not installed, but the sources were found...adding them to the build\")\n  if(EXISTS \"/usr/src/googletest/googletest/src\")\n    # Prefer the \"googletest\" version, as sometimes the \"gtest\" one is an empty directory (this seems to be the result\n    # of a packaging issue)\n    set(GTEST_SRC_ROOT \"/usr/src/googletest/googletest/src\")\n  else()\n    set(GTEST_SRC_ROOT \"/usr/src/gtest/src\")\n  endif()\n\n  if(EXISTS \"${GTEST_SRC_ROOT}/gtest-all.cc\")\n    set(gtest_lib_cpps \"${GTEST_SRC_ROOT}/gtest-all.cc\")\n    message(STATUS \"Building with ${GTEST_SRC_ROOT}/gtest-all.cc\")\n  else()\n    file(GLOB gtest_lib_cpps \"${GTEST_SRC_ROOT}/gtest-*.cc\")\n    message(STATUS \"Building with gtest_lib_cpps=${gtest_lib_cpps}\")\n  endif()\n  add_library(gtest SHARED ${gtest_lib_cpps})\n\n  # GTest uses relative imports incorrectly, so make sure to add it to the include path.\n  target_include_directories(gtest PRIVATE \"${GTEST_SRC_ROOT}/..\")\n  # Also disable -Werror\n  target_compile_options(gtest PRIVATE \"-Wno-error\")\n\n  set(gtest_LIBRARIES gtest)\nelse()\n  message(SEND_ERROR \"GTest was not found\")\nendif()\n\n################################################################################\n# Building                                                                     #\n################################################################################\n\nbuild_module(NAME zkpp-tests\n             PATH src/zk/tests\n             LINK_LIBRARIES\n               ${gtest_LIBRARIES}\n            )\n\nbuild_module(NAME zkpp\n             PATH src/zk\n             NO_RECURSE\n             LINK_LIBRARIES\n             ${ZKPP_LIB_DEPENDENCIES}\n             )\n\nbuild_module(NAME zkpp-server\n             PATH src/zk/server\n             LINK_LIBRARIES\n               zkpp\n            )\n\ntarget_link_libraries(zkpp_tests zkpp-server zkpp-server_tests)\n\n################################################################################\n# ZooKeeper Server Testing                                                     #\n################################################################################\n\nforeach(server_version IN LISTS ZKPP_SERVER_VERSIONS)\n  find_zookeeper_server(VERSION \"${server_version}\"\n                        OUTPUT_CLASSPATH server_classpath\n                       )\n  set(generated_cpp \"${CMAKE_CURRENT_BINARY_DIR}/generated/src/zk/server/classpath_registration_${server_version}.cpp\")\n  configure_file(src/zk/server/classpath_registration_template.cpp.in \"${generated_cpp}\" @ONLY)\n  target_sources(zkpp-server_tests PRIVATE \"${generated_cpp}\")\nendforeach()\n\n################################################################################\n# Targets                                                                      #\n################################################################################\n\nadd_custom_target(test\n                  COMMAND $<TARGET_FILE:zkpp-tests_prog>\n                          \"--gtest_output=xml:test-results.xml\"\n                          \"--gtest_death_test_style=threadsafe\"\n                  DEPENDS zkpp-tests_prog\n                  BYPRODUCTS test-results.xml\n                  USES_TERMINAL\n                 )\n\n# Similar to test, but run it inside of GDB with GTest options one would want when running in GDB.\nadd_custom_target(gdbtest\n                  COMMAND \"gdb\"\n                          \"-args\"\n                          $<TARGET_FILE:zkpp-tests_prog>\n                          \"--gtest_output=xml:test-results-gdb.xml\"\n                          \"--gtest_death_test_style=threadsafe\"\n                          \"--gtest_break_on_failure=1\"\n                          \"--gtest_catch_exceptions=0\"\n                  DEPENDS zkpp-tests_prog\n                  BYPRODUCTS test-results-gdb.xml\n                  USES_TERMINAL\n                 )\n\nif(ZKPP_BUILD_OPTION_CODE_COVERAGE)\n  include(CodeCoverage)\n\n  set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage\")\n  setup_target_for_coverage(test-coverage zkpp-tests_prog coverage \"--gtest_death_test_style=threadsafe\")\nendif()\n\n################################################################################\n# Packaging                                                                    #\n################################################################################\n\nset(ZKPP_PACKAGE_SYSTEM \"\"\n    CACHE STRING \"The packaging system to generate a package build file for (leave blank to not build a package)\"\n   )\nif(ZKPP_PACKAGE_SYSTEM)\n  message(STATUS \"Packaging system ${ZKPP_PACKAGE_SYSTEM}:\")\n  set(PROJECT_PACKAGE_VERSION \"${PROJECT_VERSION}-1\") # TODO: Allow arbitrary tags here.\n\n  if(CMAKE_SYSTEM_PROCESSOR STREQUAL \"x86_64\")\n    set(PROJECT_BUILD_ARCHITECTURE \"amd64\")\n  elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL \"x86\")\n    set(PROJECT_BUILD_ARCHITECTURE \"i386\")\n  else()\n    set(PROJECT_BUILD_ARCHITECTURE ${CMAKE_SYSTEM_PROCESSOR})\n  endif()\n\n  set(all_packages)\n  foreach(module zkpp zkpp-server)\n    message(STATUS \"  ${module}:\")\n    set(package_name ${module})\n    string(REGEX REPLACE \"-\"  \"/\" header_path \"${module}\")\n    string(REGEX REPLACE \"pp\" \"\"  header_path \"${header_path}\")\n\n    if(ZKPP_PACKAGE_SYSTEM STREQUAL \"DEB\")\n      if(EXISTS \"${CMAKE_CURRENT_SOURCE_DIR}/install/deb/lib${package_name}\")\n        message(STATUS \"    lib${package_name}\")\n        configure_file(\"${CMAKE_CURRENT_SOURCE_DIR}/install/deb/lib${package_name}/control.in\"\n                       \"${CMAKE_CURRENT_BINARY_DIR}/install/lib${package_name}${PROJECT_SO_VERSION}/DEBIAN/control\"\n                       @ONLY IMMEDIATE\n                      )\n        if(EXISTS \"${CMAKE_CURRENT_SOURCE_DIR}/install/deb/lib${package_name}/postinst.in\")\n          configure_file(\"${CMAKE_CURRENT_SOURCE_DIR}/install/deb/lib${package_name}/postinst.in\"\n                         \"${CMAKE_CURRENT_BINARY_DIR}/intermediate/lib${package_name}${PROJECT_SO_VERSION}/DEBIAN/postinst\"\n                         @ONLY IMMEDIATE\n                        )\n          file(COPY \"${CMAKE_CURRENT_BINARY_DIR}/intermediate/lib${package_name}${PROJECT_SO_VERSION}/DEBIAN/postinst\"\n               DESTINATION \"${CMAKE_CURRENT_BINARY_DIR}/install/lib${package_name}${PROJECT_SO_VERSION}/DEBIAN\"\n               FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE\n              )\n        endif()\n        if(EXISTS \"${CMAKE_CURRENT_SOURCE_DIR}/install/deb/lib${package_name}/shlibs.in\")\n          configure_file(\"${CMAKE_CURRENT_SOURCE_DIR}/install/deb/lib${package_name}/shlibs.in\"\n                         \"${CMAKE_CURRENT_BINARY_DIR}/install/lib${package_name}${PROJECT_SO_VERSION}/DEBIAN/shlibs\"\n                         @ONLY IMMEDIATE\n                        )\n        endif()\n\n        add_custom_target(\"lib${package_name}.deb\"\n                          BYPRODUCTS \"install/lib${package_name}${PROJECT_SO_VERSION}.deb\"\n                          COMMAND rm -rf \"install/lib${package_name}${PROJECT_SO_VERSION}${CMAKE_INSTALL_PREFIX}/lib\"\n                          COMMAND mkdir -p \"install/lib${package_name}${PROJECT_SO_VERSION}${CMAKE_INSTALL_PREFIX}/lib\"\n                          COMMAND cp \"$<TARGET_FILE:${module}>\" \"install/lib${package_name}${PROJECT_SO_VERSION}${CMAKE_INSTALL_PREFIX}/lib\"\n                          COMMAND dpkg --build \"install/lib${package_name}${PROJECT_SO_VERSION}\"\n                          DEPENDS\n                            \"${CMAKE_CURRENT_BINARY_DIR}/install/lib${package_name}${PROJECT_SO_VERSION}/DEBIAN/control\"\n                            ${module}\n                          )\n        list(APPEND all_packages \"lib${package_name}.deb\")\n      endif()\n\n      if(EXISTS \"${CMAKE_CURRENT_SOURCE_DIR}/install/deb/lib${package_name}-dev\")\n        message(STATUS \"    lib${package_name}-dev\")\n        set(staging_dir \"install/lib${package_name}${PROJECT_SO_VERSION}-dev${CMAKE_INSTALL_PREFIX}/include/${header_path}\")\n        configure_file(\"${CMAKE_CURRENT_SOURCE_DIR}/install/deb/lib${package_name}-dev/control.in\"\n                       \"${CMAKE_CURRENT_BINARY_DIR}/install/lib${package_name}${PROJECT_SO_VERSION}-dev/DEBIAN/control\"\n                       @ONLY IMMEDIATE\n                      )\n        add_custom_target(\"lib${package_name}-dev.deb\"\n                          BYPRODUCTS \"install/lib${package_name}${PROJECT_SO_VERSION}-dev.deb\"\n                          COMMAND rm -rf \"${staging_dir}\"\n                          COMMAND mkdir -p \"${staging_dir}\"\n                          COMMAND cp -t \"${staging_dir}\" ${${module}_LIBRARY_HEADERS}\n                          COMMAND dpkg --build \"install/lib${package_name}${PROJECT_SO_VERSION}-dev\"\n                          DEPENDS\n                            \"${CMAKE_CURRENT_BINARY_DIR}/install/lib${package_name}${PROJECT_SO_VERSION}-dev/DEBIAN/control\"\n                            ${${module}_LIBRARY_HEADERS}\n                         )\n        list(APPEND all_packages \"lib${package_name}-dev.deb\")\n      endif()\n    else()\n      message(FATAL_ERROR \"Unknown ZKPP_PACKAGE_SYSTEM=${ZKPP_PACKAGE_SYSTEM}\")\n    endif()\n  endforeach()\n  add_custom_target(package\n                    DEPENDS ${all_packages}\n                    COMMENT \"Built all packages\"\n                   )\nendif()\n"
  },
  {
    "path": "CONTRIBUTING.rst",
    "content": "Contributing Guide\n==================\n\nSo you want to contribute to the ZooKeeper C++ library?\nI'd love your contribution!\nPlease help me.\n\nBuilding\n--------\n\nBuilding the system only requires `CMake <https://cmake.org/>`_ and the standard-issue C++ compilation tools.\n\nDocker\n^^^^^^\n\nDocker is the official mechanism for supporting multiple Linux distributions (see the\n`TravisCI <https://travis-ci.org/tgockel/zookeeper-cpp>`_ build).\nIf you would like to do this at home, simply use the ``dev-env`` script::\n\n    $> cd /path/to/zookeeper-cpp\n    $> ./config/dev-env ubuntu-18.04\n\nThis will create a Docker image named something like ``dev/zookeeper-cpp/ubuntu-18.04`` and run that image with the\nproject's working directory mapped to ``~/zookeeper-cpp`` with you in control of a shell.\nInside Docker, you can now build::\n\n    root@0ae2f54b152b:~/zookeeper-cpp# mkdir build-debug\n    root@0ae2f54b152b:~/zookeeper-cpp# cd build-debug\n    root@0ae2f54b152b:~/zookeeper-cpp/build-debug# cmake -GNinja ..\n    ... output ...\n    root@0ae2f54b152b:~/zookeeper-cpp/build-debug# ninja test\n    ... output ...\n\nThis experience is pretty decent.\nThe biggest annoyance is editing within the Docker image makes files you touch owned by *root* (I suspect there is a way\nto prevent this, but I am far from competent at Docker).\nIf you use `KDevelop <https://www.kdevelop.org/>`_, you can use the IDE to build and debug inside of these images with\n`KDevelop Runtimes <http://www.proli.net/2017/05/23/kdevelop-runtimes-docker-and-flatpak-integration/>`_.\n\nProcess\n-------\n\nThis library follows the `GitHub Fork + Pull Model <https://help.github.com/articles/about-pull-requests/>`_.\nBelow are the more project-specific steps.\n\nIssue Tracker\n^^^^^^^^^^^^^\n\nAll work *must* be tracked in the `Issue Tracker <https://github.com/tgockel/zookeeper-cpp/issues>`_, otherwise the\nmaintainer will have no idea what is going on.\nTry to find an existing bug in the list of issues -- if you can't find it, open a new issue with a descriptive title and\ndescriptive description.\nIf you are unclear on if it should be a bug or not, mark it with a *Question* tag or just send me an\n`email <mailto:travis@gockelhut.com>`_.\nAssign the issue to yourself so I don't forget who is working on it.\n\nFor more granular tracking, the issue should move across the\n`GitHub Project board <https://github.com/tgockel/zookeeper-cpp/projects/1>`_.\nThe columns of the project should be somewhat intuitive:\n\n:Backlog:\n    Things we are planning on doing soon.\n\n:Design:\n    System is being designed.\n    What this usually means is the API is being written.\n    *Please* write your API first -- it can save a lot of time in the long run.\n\n:Implementation:\n    The component is currently being implemented.\n\n:Pull Request:\n    There is an open pull request.\n\n:Done:\n    Work is complete!\n\nDeveloping\n^^^^^^^^^^\n\n1. Fork the repository.\n2. Branch in your fork (not actually required, but generally considered a Good Idea).\n3. Write your code.\n4. If this is your first contribution, add yourself to ``AUTHORS`` (alphabetically).\n5. Commit your code (somewhere in the commit message, be sure to mention \"Issue #NN\", where \"NN\" is the issue number you\n   were working on).\n6. Watch your tests pass for all environments in TravisCI.\n7. Issue a pull request from your branch to the master branch of the main repository.\n8. Close the branch in your repository (not actually required, but clean repos are nice).\n\nSign Your Commits\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nWhen committing code, please `sign commits with GPG <https://help.github.com/articles/signing-commits-using-gpg/>`_.\nThis lets me know that work submitted by you was really created by you (security or something like that).\nIf you always want to sign commits instead of specifying ``-S`` on the command line every time, add it to your global\nconfiguration::\n\n    $> git config --global user.signingkey ${YOUR_KEY_ID}\n    $> git config --global commit.gpgsign true\n"
  },
  {
    "path": "COPYING",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "# ZooKeeper C++\n\nA ZooKeeper client for C++.\nIt is [hosted on GitHub](https://github.com/tgockel/zookeeper-cpp),\n[documented](https://tgockel.github.io/zookeeper-cpp/)\n(including [previous versions](https://tgockel.github.io/zookeeper-cpp/version/)\nof the software), and [tested](https://travis-ci.org/tgockel/zookeeper-cpp).\n\nFeatures include (but are not necessarily limited to):\n\n- Simple\n  - Connect with just a connection string\n  - Clients should not require factories\n  - Does not require knowledge of the Java or C APIs\n\n- Configurable\n  - Use the parts you need\n  - Change parts to fit in your application\n\n- Safe\n  - In the best case, illegal code should fail to compile\n  - An illegal action should throw an exception\n  - Utility functions have a [strong exception guarantee](http://www.gotw.ca/gotw/082.htm>)\n\n- Stable\n  - Worry less about upgrading -- the API and ABI will not change out from under you\n\n**NOTE**: This library is a work-in-progress.\nAll documentation you see here is subject to change and non-existence.\n\n[![Build Status](https://travis-ci.org/tgockel/zookeeper-cpp.svg?branch=master)](https://travis-ci.org/tgockel/zookeeper-cpp)\n\n## Usage\n\nUltimately, the usage looks like this (assuming you have a ZooKeeper server running on your local host):\n\n    #include <zk/client.hpp>\n    #include <zk/multi.hpp>\n    #include <zk/server/configuration.hpp>\n    #include <zk/server/server.hpp>\n\n    #include <exception>\n    #include <iostream>\n\n    /** All result types are printable for debugging purposes. **/\n    template <typename T>\n    void print_thing(const zk::future<T>& result)\n    {\n        try\n        {\n            // Unwrap the future value, which will not block (based on usage), but could throw.\n            T value(result.get());\n            std::cerr << value << std::endl;\n        }\n        catch (const std::exception& ex)\n        {\n            // Error \"handling\"\n            std::cerr << \"Exception: \" << ex.what() << std::endl;\n        }\n    }\n\n    int main()\n    {\n        // Start a ZK server running on localhost (not needed if you just want a client, but great for testing and\n        // demonstration purposes).\n        zk::server::server server(zk::server::configuration::make_minimal(\"zk-data\", 2181));\n\n        // zk::client::connect returns a future<zk::client>, which is delivered when the connection is established.\n        auto client = zk::client::connect(\"zk://127.0.0.1:2181\")\n                                 .get();\n\n        // get_result has a zk::buffer and zk::stat.\n        client.get(\"/foo/bar\")\n            .then(print_thing<zk::get_result>);\n\n        // get_children_result has a std::vector<std::string> for the path names and zk::stat for the parent stat.\n        client.get_children(\"/foo\")\n            .then(print_thing<zk::get_children_result>);\n\n        // set_result has a zk::stat for the modified ZNode.\n        client.set(\"/foo/bar\", \"some data\")\n            .then(print_thing<zk::set_result>);\n\n        // More explicit: client.create(\"/foo/baz\", \"more data\", zk::acls::open_unsafe(), zk::create_mode::normal);\n        client.create(\"/foo/baz\", \"more data\")\n            .then(print_thing<zk::create_result>);\n\n        client.get(\"/foo/bar\")\n            .then([client] (const auto& get_res)\n            {\n                zk::version foo_bar_version = get_res.get().stat().data_version;\n\n                zk::multi_op txn =\n                {\n                    zk::op::check(\"/foo\", zk::version::any()),\n                    zk::op::check(\"/foo/baz\", foo_bar_version),\n                    zk::op::create(\"/foo/bap\", \"hi\", nullopt, zk::create_mode::sequential),\n                    zk::op::erase(\"/foo/bzr\"),\n                };\n\n                // multi_res's type is zk::future<zk::multi_result>\n                client.commit(txn).then(print_thing<zk::multi_result>);\n            });\n\n        // This is not strictly needed -- a client falling out of scope will auto-trigger close\n        client.close();\n    }\n\n## Value-Added Features\n\nThe core library of `libzkpp` provides the primitives for connecting to and manipulating a ZooKeeper database.\nThis library also bundles a number of other features that are commonly required when working with a ZooKeeper cluster.\n\n### `zk/curator`\n\nThings in `zk/curator` have features found in the [Apache Curator](http://curator.apache.org/) project.\n\n* Elections\n  * [Leader Latch](https://github.com/tgockel/zookeeper-cpp/issues/1)\n  * [Leader Election](https://github.com/tgockel/zookeeper-cpp/issues/2)\n\n* Locks\n  * [Shared Reentrant Lock](https://github.com/tgockel/zookeeper-cpp/issues/3)\n  * [Shared Lock](https://github.com/tgockel/zookeeper-cpp/issues/4)\n  * [Shared Reentrant Read Write Lock](https://github.com/tgockel/zookeeper-cpp/issues/5)\n  * [Shared Semaphore](https://github.com/tgockel/zookeeper-cpp/issues/6)\n  * [Multi Shared Lock](https://github.com/tgockel/zookeeper-cpp/issues/7)\n\n* Barriers\n  * [Barrier](https://github.com/tgockel/zookeeper-cpp/issues/8)\n  * [Double Barrier](https://github.com/tgockel/zookeeper-cpp/issues/9)\n\n* Counters\n  * [Shared Counter](https://github.com/tgockel/zookeeper-cpp/issues/10)\n  * [Distributed Atomic Long](https://github.com/tgockel/zookeeper-cpp/issues/11)\n\n* Caches\n  * [Path Cache](https://github.com/tgockel/zookeeper-cpp/issues/12)\n  * [Node Cache](https://github.com/tgockel/zookeeper-cpp/issues/13)\n  * [Tree Cache](https://github.com/tgockel/zookeeper-cpp/issues/14)\n\n* Nodes\n  * [Persistent Node](https://github.com/tgockel/zookeeper-cpp/issues/15)\n  * [Persistent TTL Node](https://github.com/tgockel/zookeeper-cpp/issues/16)\n  * [Group Member](https://github.com/tgockel/zookeeper-cpp/issues/17)\n\nNone of the queue types are planned to be implemented.\nThe [Curator Documentation (TN4)](https://cwiki.apache.org/confluence/display/CURATOR/TN4) advises against their use,\nclaiming \"it is a bad idea to use ZooKeeper as a Queue.\"\nThe authors of this library agree with this claim.\n\n### `zk/fake`\n\nThis library also provides a fake version of ZooKeeper which operates in-memory.\nIt is meant to be used in your unit testing, when fine-grained control of behavior of ZooKeeper is needed.\nThis allows for the injection of arbitrary behavior into ZK, allowing you to simulate some of the hard-to-reproduce\nissues like `zk::event_type::not_watching`, `zk::marshalling_error`, or timing bugs.\nIt also allows for fast creation and teardown of entire databases, which is commonly done in unit testing.\n\nIt is connected to through using a connection string of the form:\n\n    fake://{name}\n\nTo use this in unit tests link to `libzkpp_fake` and use `zk::fake::server`:\n\n    TEST(my_test)\n    {\n        // The default constructor uses a randomly-generated unique name\n        zk::fake::server server;\n\n        // Fetch that name through the connection_string\n        zk::client client(server.connection_string());\n\n        // use client normally\n    }\n\n### `zk/server`\n\nThis library controls a ZooKeeper Java process on this machine.\nIt is meant to be used in applications that manage a ZooKeeper cluster from native code.\n\n## Unsupported Functionality\n\nIf you are used to using ZooKeeper via the Java or C APIs, there are a few things that are explicitly not supported in\nthis library.\n\n### Global Watches\n\nThere are two main ways to receive watch notifications: the global watch or through use a watcher objects.\nIn the Java API, the `ZooKeeper` client allows for a global\n[Watcher](https://zookeeper.apache.org/doc/r3.4.10/api/org/apache/zookeeper/Watcher.html).\nIn the C API, `zookeeper_init` can be provided with a global function with the signature\n`void (*)(zhandle_t* zh, int type, int state, const char* path, void* watcherCtx)` to achieve this same result.\nGlobal watches are somewhat of a \"legacy\" feature -- the dual interface of global and callbacks is somewhat confusing.\nAs such, global watches are *not* supported by this library.\n\n### Synchronous API\n\nThe C library offers both a synchronous and an asynchronous API.\nThis library offers only an asynchronous version.\nIf you prefer a synchronous API, call `get()` on the returned `future` to block until you receive the response.\n\n### Non-Linux\n\nCan you get this library working on platforms that are not Linux?\nMaybe.\nBut Linux is the primary development, testing, and deployment platform of people writing distributed applications, so\nthis library is targetted at Linux.\n\n## License\n\nLicensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\nthe License. You may obtain a copy of the License at\n[http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0).\n\nUnless required by applicable law or agreed to in writing, software distributed under the License is distributed on\nan \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\nspecific language governing permissions and limitations under the License.\n\n## F.A.Q.\n\n### Why `erase` instead of `delete`?\n\nIn the Java and C APIs, the act of removing a ZNode is called `delete` and `zoo_delete`, respectively.\nHowever, `delete` is a C++ keyword and cannot be used as a member function.\nSo, this library uses `erase`, which falls in line with standard C++ containers.\nAlternatives such as calling the operation `delete_` look a bit worse (in the author's opinion).\n\n### Why are watch calls separate?\n\nIn the Java and C APIs, adding a watch to a ZNode is an additional parameter to the `get`, `get_children`, or `exists`\ncalls while this library uses separate `watch`, `watch_children`, and `watch_exists` calls.\nThis is done because the return types are different between a simple fetch and setting a watch.\nWhile `get` returns a `future<get_result>`, `watch` returns the slightly more complicated `future<watch_result>`.\nThe `future` in `watch_result::next()` would be disabled in cases where a flag is not set, and it would be ignored with\nthe majority of use cases.\nThis leads to an awkward API for simple calls.\n\nAn alternative used by other libraries is to provide a `std::function`, implying to not watch when the function is not\npassed in.\nThis has a number of disadvantages:\n\n- There is no good way to cancel a watch without giving an extra parameter.\n  With a `future`, you simply let it fall out of scope.\n- Watches are delivered only once, which is obvious from a `future`-like API, but not obvious from a `function`-like\n  API.\n- It is not obvious what the behavior should be if the original call returns in error.\n  With a `future`, the behavior is obvious, since you never receive the mechanisms to perform the watch.\n\nIn Java, the method of choice is to use the\n[Watcher](https://zookeeper.apache.org/doc/r3.4.10/api/org/apache/zookeeper/Watcher.html) interface, but this feels\nextremely out of place in C++ code.\n\n### Where are all the `KeeperException`s?\n\nThis library uses an exception hierarchy with fewer exception codes than what are available in\n[`KeeperException`](https://zookeeper.apache.org/doc/r3.4.10/api/org/apache/zookeeper/KeeperException.html).\n\n![Exception hierarchy](https://tgockel.github.io/zookeeper-cpp/classzk_1_1error.png)\n\nSome exceptions are not present in this library because they are no longer used in the server implementation and will\nnot be used again; an example of this is `DataInconsistencyException`, which has not been used in ZooKeeper for a while.\nIn other cases, the error code would never be thrown by this library; examples of this are `NoWatcherException` (watch\nremoval happens implicitly in destructors) and `RuntimeInconsistencyException` (failed multi-ops throw a\n`transaction_failed` containing only the index of the failed operation instead).\nIn other cases, the error codes have been merged into a single exception type, as there was much logical overlap.\n\nAnother distinction that was dropped is the difference between \"system errors\" (`Code.SYSTEMERROR`/`ZSYSTEMERROR`) and\n\"API errors\" (`Code.APIERROR`/`ZAPIERROR`).\nThe general distinction is the origin of the error -- system errors are client-side (`invalid_arguments` -- other APIs:\n`Code.BADARGUMENTS`/`ZBADARGUMENTS`), while API errors are server-size (`no_entry` -- other APIs:\n`Code.NoNode`/`ZNONODE`).\nThis was dropped because this is not entirely meaningful from user's point of view.\nAs an example, `authentication_failed` is a subclass of `invalid_arguments`, even though the contents of the arguments\nhappen to be validated by the server instead of by the client.\n\n### How can I contribute?\n\nPick an [open issue](https://github.com/tgockel/zookeeper-cpp/issues) and start working on it!\nFor more details, read the [CONTRIBUTING](https://github.com/tgockel/zookeeper-cpp/blob/master/CONTRIBUTING.rst) guide.\n"
  },
  {
    "path": "cmake/modules/BuildFunctions.cmake",
    "content": "include(CMakeParseArguments)\ninclude(ListSplit)\n\n# build_option\n# Creates a build option, which is configurable via a CMake option. If the option is set to anything non-default, a\n# macro with the name `ZKPP_ENABLE_${NAME}` is exported with the value of `0` or `1`.\n#\n#  - NAME: The name of the build option.\n#  - DOC: Documentation to place in the configuration GUI.\n#  - DEFAULT: The default value of the configuration (ON or OFF).\n#  - CONFIGS_ON[]: List of build configurations this option should be ON for.\n#  - CONFIGS_OFF[]: List of build configurations this option should be OFF for.\nfunction(build_option)\n  set(options)\n  set(oneValueArgs   NAME DOC DEFAULT)\n  set(multiValueArgs CONFIGS_ON CONFIGS_OFF)\n  cmake_parse_arguments(OPT \"${options}\" \"${oneValueArgs}\" \"${multiValueArgs}\" ${ARGN})\n\n  if(NOT DEFINED VALID_BUILD_TYPES)\n    message(SEND_ERROR \"VALID_BUILD_TYPES not set -- future build_option errors will be inaccurrate\")\n  endif()\n\n  foreach(bt IN LISTS OPT_CONFIGS_ON OPT_CONFIGS_OFF)\n    if(NOT ${bt} IN_LIST VALID_BUILD_TYPES)\n      message(WARNING \"Specified configuration for invalid CMAKE_BUILD_TYPE=${bt}\")\n    endif()\n  endforeach()\n\n  if(${CMAKE_BUILD_TYPE} IN_LIST OPT_CONFIGS_ON)\n    set(ENABLED ON)\n  elseif(${CMAKE_BUILD_TYPE} IN_LIST OPT_CONFIGS_OFF)\n    set(ENABLED OFF)\n  else()\n    set(ENABLED ${OPT_DEFAULT})\n  endif()\n\n  set(ZKPP_BUILD_OPTION_${OPT_NAME} ${ENABLED}\n      CACHE BOOL \"${OPT_DOC}\"\n     )\n\n  if(ZKPP_BUILD_OPTION_${OPT_NAME})\n    set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -DZKPP_ENABLE_${OPT_NAME}=1\" PARENT_SCOPE)\n  else()\n    set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -DZKPP_ENABLE_${OPT_NAME}=0\" PARENT_SCOPE)\n  endif()\n\n  message(STATUS \"  ${OPT_NAME}: ${ENABLED}\")\nendfunction()\n\n# build_module\n# Adds a module to build.\n#\n#  NAME: The name of this module.\n#  PATH: The path to find the source files for this module. It is legal to specify more than one PATH in this list.\n#  LINK_LIBRARIES: A list of libraries to link to\n#  PROTOTYPE: If set, the module should be considered a \"prototype.\" It will not be built by default and does not\n#             consider warnings as errors.\n#  NO_RECURSE: Do not search recursively.\nfunction(build_module)\n  set(options        PROTOTYPE NO_RECURSE)\n  set(oneValueArgs   NAME)\n  set(multiValueArgs LINK_LIBRARIES PATH)\n  cmake_parse_arguments(MODULE \"${options}\" \"${oneValueArgs}\" \"${multiValueArgs}\" ${ARGN})\n\n  message(STATUS \"${MODULE_NAME} : ${MODULE_PATH}\")\n\n  if(MODULE_PROTOTYPE)\n    set(BUILD_PROTOTYPE_${MODULE_NAME} OFF\n        CACHE BOOL \"Build the '${MODULE_NAME}' program?\"\n       )\n    message(STATUS \"  ! prototype ${BUILD_PROTOTYPE_${MODULE_NAME}}\")\n    if(NOT BUILD_PROTOTYPE_${MODULE_NAME})\n      return()\n    endif()\n  endif()\n\n  if(MODULE_NO_RECURSE)\n    set(CPP_SEARCH GLOB)\n  else()\n    set(CPP_SEARCH GLOB_RECURSE)\n  endif()\n\n  set(all_library_cpps)\n  set(all_library_hpps)\n  set(main_name)\n  foreach(subpath ${MODULE_PATH})\n    file(${CPP_SEARCH} local_library_cpps RELATIVE_PATH \".\" \"${subpath}/*.cpp\")\n    file(${CPP_SEARCH} local_library_hpps RELATIVE_PATH \".\" \"${subpath}/*.hpp\")\n    file(GLOB          local_main_name    RELATIVE_PATH \".\" \"${subpath}/main.cpp\")\n\n    if(local_main_name)\n      if(main_name)\n        message(SEND_ERROR \"Found main.cpp in different paths for ${MODULE_NAME} (${main_name} and ${local_main_name})\")\n      endif()\n\n      set(main_name ${local_main_name})\n      list(REMOVE_ITEM local_library_cpps ${local_main_name})\n    endif()\n    list(APPEND all_library_cpps ${local_library_cpps})\n    list(APPEND all_library_hpps ${local_library_hpps})\n  endforeach()\n\n  list_split(library_test_cpps library_cpps \"${all_library_cpps}\" \"_tests.cpp\")\n  list_split(library_test_hpps library_notest_hpps \"${all_library_hpps}\" \"_tests.hpp\")\n  list_split(library_detail_hpps library_hpps \"${library_notest_hpps}\" \"detail\")\n  list(APPEND library_cpps ${library_detail_hpps})\n  set(MODULE_TARGETS)\n\n  if(main_name)\n    message(STATUS \"  + executable\")\n    list(APPEND MODULE_TARGETS ${MODULE_NAME}_prog)\n    add_executable(${MODULE_NAME}_prog ${main_name})\n    target_link_libraries(${MODULE_NAME}_prog ${MODULE_LINK_LIBRARIES})\n    set_target_properties(${MODULE_NAME}_prog\n                          PROPERTIES\n                            OUTPUT_NAME ${MODULE_NAME}\n                         )\n    set(${MODULE_NAME}_MAIN_SOURCES main_name PARENT_SCOPE)\n  endif()\n\n  if(library_cpps)\n    list(LENGTH library_cpps library_cpps_length)\n    message(STATUS \"  + library (${library_cpps_length})\")\n    list(APPEND MODULE_TARGETS ${MODULE_NAME})\n    add_library(${MODULE_NAME} SHARED ${library_cpps})\n    set_target_properties(${MODULE_NAME}\n                          PROPERTIES\n                              SOVERSION ${PROJECT_SO_VERSION}\n                              VERSION   ${PROJECT_SO_VERSION}\n                         )\n    target_link_libraries(${MODULE_NAME} ${MODULE_LINK_LIBRARIES})\n    if(main_name)\n      target_link_libraries(${MODULE_NAME}_prog ${MODULE_NAME})\n    endif()\n    set(${MODULE_NAME}_LIBRARY_SOURCES ${library_cpps} PARENT_SCOPE)\n    set(${MODULE_NAME}_LIBRARY_HEADERS ${library_hpps} PARENT_SCOPE)\n  endif()\n\n  if(library_test_cpps)\n    list(LENGTH library_test_cpps library_test_cpps_length)\n    message(STATUS \"  + test library (${library_test_cpps_length})\")\n    list(APPEND MODULE_TARGETS ${MODULE_NAME}_tests)\n    add_library(${MODULE_NAME}_tests SHARED ${library_test_cpps})\n    set_target_properties(${MODULE_NAME}_tests\n                          PROPERTIES\n                              SOVERSION ${PROJECT_SO_VERSION}\n                              VERSION   ${PROJECT_SO_VERSION}\n                         )\n    target_link_libraries(${MODULE_NAME}_tests zkpp-tests)\n    if(library_cpps)\n      target_link_libraries(${MODULE_NAME}_tests ${MODULE_NAME})\n    endif()\n    target_link_libraries(zkpp-tests_prog ${MODULE_NAME}_tests)\n  endif()\n\n  if(MODULE_PROTOTYPE)\n    foreach(target ${MODULE_TARGETS})\n      set_target_properties(${target}\n                            PROPERTIES\n                              COMPILE_FLAGS \"${CMAKE_CXX_FLAGS} -Wno-error\"\n                           )\n    endforeach()\n  endif()\nendfunction()\n"
  },
  {
    "path": "cmake/modules/CodeCoverage.cmake",
    "content": "# Copyright (c) 2012 - 2015, Lars Bilke\n# All rights reserved.\n#\n# Redistribution and use in source and binary forms, with or without modification,\n# are permitted provided that the following conditions are met:\n#\n# 1. Redistributions of source code must retain the above copyright notice, this\n#    list of conditions and the following disclaimer.\n#\n# 2. Redistributions in binary form must reproduce the above copyright notice,\n#    this list of conditions and the following disclaimer in the documentation\n#    and/or other materials provided with the distribution.\n#\n# 3. Neither the name of the copyright holder nor the names of its contributors\n#    may be used to endorse or promote products derived from this software without\n#    specific prior written permission.\n#\n# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR\n# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON\n# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n#\n#\n#\n# 2012-01-31, Lars Bilke\n# - Enable Code Coverage\n#\n# 2013-09-17, Joakim Söderberg\n# - Added support for Clang.\n# - Some additional usage instructions.\n#\n# USAGE:\n\n# 0. (Mac only) If you use Xcode 5.1 make sure to patch geninfo as described here:\n#      http://stackoverflow.com/a/22404544/80480\n#\n# 1. Copy this file into your cmake modules path.\n#\n# 2. Add the following line to your CMakeLists.txt:\n#      INCLUDE(CodeCoverage)\n#\n# 3. Set compiler flags to turn off optimization and enable coverage:\n#    SET(CMAKE_CXX_FLAGS \"-g -O0 -fprofile-arcs -ftest-coverage\")\n#    SET(CMAKE_C_FLAGS \"-g -O0 -fprofile-arcs -ftest-coverage\")\n#\n# 3. Use the function SETUP_TARGET_FOR_COVERAGE to create a custom make target\n#    which runs your test executable and produces a lcov code coverage report:\n#    Example:\n#    SETUP_TARGET_FOR_COVERAGE(\n#               my_coverage_target  # Name for custom target.\n#               test_driver         # Name of the test driver executable that runs the tests.\n#                                   # NOTE! This should always have a ZERO as exit code\n#                                   # otherwise the coverage generation will not complete.\n#               coverage            # Name of output directory.\n#               )\n#\n# 4. Build a Debug build:\n#    cmake -DCMAKE_BUILD_TYPE=Debug ..\n#    make\n#    make my_coverage_target\n#\n#\n\n# Check prereqs\nFIND_PROGRAM( GCOV_PATH gcov )\nFIND_PROGRAM( LCOV_PATH lcov )\nFIND_PROGRAM( GENHTML_PATH genhtml )\nFIND_PROGRAM( GCOVR_PATH gcovr PATHS ${CMAKE_SOURCE_DIR}/tests)\n\nIF(NOT GCOV_PATH)\n    MESSAGE(FATAL_ERROR \"gcov not found! Aborting...\")\nENDIF() # NOT GCOV_PATH\n\nIF(NOT CMAKE_COMPILER_IS_GNUCXX)\n    # Clang version 3.0.0 and greater now supports gcov as well.\n    MESSAGE(WARNING \"Compiler is not GNU gcc! Clang Version 3.0.0 and greater supports gcov as well, but older versions don't.\")\n\n    IF(NOT \"${CMAKE_CXX_COMPILER_ID}\" STREQUAL \"Clang\")\n        MESSAGE(FATAL_ERROR \"Compiler is not GNU gcc! Aborting...\")\n    ENDIF()\nENDIF() # NOT CMAKE_COMPILER_IS_GNUCXX\n\nSET(CMAKE_CXX_FLAGS_COVERAGE\n    \"-g -O0 --coverage -fprofile-arcs -ftest-coverage\"\n    CACHE STRING \"Flags used by the C++ compiler during coverage builds.\"\n    FORCE )\nSET(CMAKE_C_FLAGS_COVERAGE\n    \"-g -O0 --coverage -fprofile-arcs -ftest-coverage\"\n    CACHE STRING \"Flags used by the C compiler during coverage builds.\"\n    FORCE )\nSET(CMAKE_EXE_LINKER_FLAGS_COVERAGE\n    \"\"\n    CACHE STRING \"Flags used for linking binaries during coverage builds.\"\n    FORCE )\nSET(CMAKE_SHARED_LINKER_FLAGS_COVERAGE\n    \"\"\n    CACHE STRING \"Flags used by the shared libraries linker during coverage builds.\"\n    FORCE )\nMARK_AS_ADVANCED(\n    CMAKE_CXX_FLAGS_COVERAGE\n    CMAKE_C_FLAGS_COVERAGE\n    CMAKE_EXE_LINKER_FLAGS_COVERAGE\n    CMAKE_SHARED_LINKER_FLAGS_COVERAGE )\n\nIF ( NOT (CMAKE_BUILD_TYPE STREQUAL \"Debug\" OR CMAKE_BUILD_TYPE STREQUAL \"Coverage\"))\n  MESSAGE( WARNING \"Code coverage results with an optimized (non-Debug) build may be misleading\" )\nENDIF() # NOT CMAKE_BUILD_TYPE STREQUAL \"Debug\"\n\n\n# Param _targetname     The name of new the custom make target\n# Param _testrunner     The name of the target which runs the tests.\n#                       MUST return ZERO always, even on errors.\n#                       If not, no coverage report will be created!\n# Param _outputname     lcov output is generated as _outputname.info\n#                       HTML report is generated in _outputname/index.html\n# Optional fourth parameter is passed as arguments to _testrunner\n#   Pass them in list form, e.g.: \"-j;2\" for -j 2\nFUNCTION(SETUP_TARGET_FOR_COVERAGE _targetname _testrunner _outputname)\n\n    IF(NOT LCOV_PATH)\n        MESSAGE(FATAL_ERROR \"lcov not found! Aborting...\")\n    ENDIF() # NOT LCOV_PATH\n\n    IF(NOT GENHTML_PATH)\n        MESSAGE(FATAL_ERROR \"genhtml not found! Aborting...\")\n    ENDIF() # NOT GENHTML_PATH\n\n    # Setup target\n    ADD_CUSTOM_TARGET(${_targetname}\n\n        # Cleanup lcov\n        ${LCOV_PATH} --directory . --zerocounters\n\n        # Run tests\n        COMMAND ${_testrunner} ${ARGV3}\n\n        # Capturing lcov counters and generating report\n        COMMAND ${LCOV_PATH} --directory . --capture --output-file ${_outputname}.info\n        COMMAND ${LCOV_PATH} --remove ${_outputname}.info '*_tests.cpp' '*generated*' '/usr/*' --output-file ${_outputname}.info.cleaned\n        COMMAND ${GENHTML_PATH} -o ${_outputname} ${_outputname}.info.cleaned\n        COMMAND ${CMAKE_COMMAND} -E remove ${_outputname}.info ${_outputname}.info.cleaned\n\n        WORKING_DIRECTORY ${CMAKE_BINARY_DIR}\n        COMMENT \"Resetting code coverage counters to zero.\\nProcessing code coverage counters and generating report.\"\n    )\n\n    # Show info where to find the report\n    ADD_CUSTOM_COMMAND(TARGET ${_targetname} POST_BUILD\n        COMMAND ;\n        COMMENT \"Open ./${_outputname}/index.html in your browser to view the coverage report.\"\n    )\n\nENDFUNCTION() # SETUP_TARGET_FOR_COVERAGE\n"
  },
  {
    "path": "cmake/modules/ConfigurationSetting.cmake",
    "content": "include(CMakeParseArguments)\n\n# configuration_setting\n# Creates a configuration setting, which is configurable via a CMake option. If the option is set to anything\n# non-default, a macro with the name `ZKPP_${NAME}_USE_${OPTION_VALUE}` is exported as `1`.\n#\n#  - NAME: The name of the configuration setting.\n#  - DOC: Documentation to place in the CMake configuration GUI.\n#  - DEFUALT: The default value of the configuration setting (some value from OPTIONS). This value must be synced with\n#    the C++ code or the behavior will be nonsense.\n#  - SET: Set the value to this. If unspecified, this will simply be the same as DEFUALT. However, this can be useful in\n#    cases where you wish to specify a non-default based on system information.\n#  - OPTIONS[]: List of valid options to set.\nfunction(configuration_setting)\n  set(options)\n  set(oneValueArgs NAME DOC DEFAULT SET)\n  set(multiValueArgs OPTIONS)\n  cmake_parse_arguments(ARG \"${options}\" \"${oneValueArgs}\" \"${multiValueArgs}\" ${ARGN})\n\n  if(NOT ARG_SET)\n    set(ARG_SET \"${ARG_DEFAULT}\")\n  endif()\n\n  set(ZKPP_BUILD_SETTING_${ARG_NAME} \"${ARG_SET}\"\n      CACHE STRING \"${ARG_DOC}\"\n     )\n  if(NOT ${ZKPP_BUILD_SETTING_${ARG_NAME}} IN_LIST ARG_OPTIONS)\n    message(SEND_ERROR \"Invalid setting for ${ARG_NAME}: ${ZKPP_BUILD_SETTING_${ARG_NAME}}\")\n  endif()\n\n  if(NOT ${ZKPP_BUILD_SETTING_${ARG_NAME}} STREQUAL ${ARG_DEFAULT})\n    set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -DZKPP_${ARG_NAME}_USE_${ZKPP_BUILD_SETTING_${ARG_NAME}}=1\" PARENT_SCOPE)\n  endif()\n\n  message(STATUS \"  ${ARG_NAME}: ${ZKPP_BUILD_SETTING_${ARG_NAME}}\")\nendfunction()\n"
  },
  {
    "path": "cmake/modules/ListSplit.cmake",
    "content": "# list_split\n# Split a list into values matching or not matching a given expression.\nmacro(list_split matching non_matching orig expression)\n  foreach(val ${orig})\n    if(${val} MATCHES ${expression})\n      list(APPEND ${matching} ${val})\n    else()\n      list(APPEND ${non_matching} ${val})\n    endif()\n  endforeach()\nendmacro()\n\n"
  },
  {
    "path": "cmake/modules/ZooKeeper.cmake",
    "content": "# Utilities for configuring and running a ZooKeeper Server.\ncmake_minimum_required(VERSION 3.5)\n\ninclude(CMakeParseArguments)\n\nfind_package(Java COMPONENTS Runtime)\nif(NOT Java_Runtime_FOUND)\n  message(FATAL_ERROR \"Could not find Java Runtime\")\nendif()\n\n# execute_jar\n# Similar to execute_process, but drops \"java -jar\" in front for you so you can execute a JAR file in the same way you\n# would a process.\nmacro(execute_jar)\n  execute_process(COMMAND \"${Java_JAVA_EXECUTABLE}\" \"-jar\" ${ARGN})\nendmacro()\n\n# execute_java_cp\n# Similar to execute_process, but drops \"java -cp\" in front for you so you can execute a collection of Java locations in\n# the same way you would a process (albeit more annoyingly).\nmacro(execute_java_cp)\n  execute_process(COMMAND \"${Java_JAVA_EXECUTABLE}\" \"-cp\" ${ARGN})\nendmacro()\n\nfind_program(IVY_JAR\n             NAMES ivy.jar\n             PATHS \"/usr/share/java\"\n            )\nif(NOT IVY_JAR)\n  message(FATAL_ERROR \"Could not find Apache Ivy\")\nendif()\n\n# find_zookeeper_server\n# Get the ZooKeeper server JARs from Ivy.\n#\n#  VERSION: ZooKeeper version to fetch. This can be any Ivy pattern (for example, \"3.5+\").\n#  OUTPUT_CLASSPATH: A variable to output a classpath that can be used to run the server.\n#\n# Example:\n#\n#   find_zookeeper_server(VERSION \"3.5+\" OUTPUT_CLASSPATH ZOOKEEPER_SERVER_CLASSPATH)\n#   execute_java_cp(\"${ZOOKEEPER_SERVER_CLASSPATH}\" \"org.apache.zookeeper.server.quorum.QuorumPeerMain\" 2181 zk-data)\nfunction(find_zookeeper_server)\n  set(options)\n  set(oneValueArgs VERSION OUTPUT_CLASSPATH)\n  set(multiValueArgs)\n  cmake_parse_arguments(ARG \"${options}\" \"${oneValueArgs}\" \"${multiValueArgs}\" ${ARGN})\n  if(NOT ARG_VERSION)\n    message(FATAL_ERROR \"You must specify a VERSION to fetch\")\n  endif()\n  if(NOT ARG_OUTPUT_CLASSPATH)\n    message(FATAL_ERROR \"You must specify an OUTPUT_CLASSPATH\")\n  endif()\n\n  file(MAKE_DIRECTORY \"${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/ivy-cache\")\n  set(TEMP_CLASSPATH_FILE \"${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/ivy-cache/zookeeper-${ARG_VERSION}.txt\")\n\n  # There does not appear to be a good way to tell Ivy to be reasonable about progress reporting, so we'll silence it\n  # completely and hope the ellipsis in the status message will prevent people from thinking the system hung.\n  message(STATUS \"Fetching ZooKeeper Server ${ARG_VERSION}...\")\n  execute_jar(${IVY_JAR} \"-dependency\" \"org.apache.zookeeper\" \"zookeeper\" \"${ARG_VERSION}\"\n                         \"-cachepath\" \"${TEMP_CLASSPATH_FILE}\"\n              OUTPUT_VARIABLE IVY_FETCH_OUTPUT\n              ERROR_VARIABLE  IVY_FETCH_ERROR\n             )\n  if(EXISTS \"${TEMP_CLASSPATH_FILE}\")\n    file(READ \"${TEMP_CLASSPATH_FILE}\" CLASSPATH)\n    string(STRIP \"${CLASSPATH}\" CLASSPATH)\n    message(STATUS \" > SUCCESS!\")\n    set(${ARG_OUTPUT_CLASSPATH} \"${CLASSPATH}\" PARENT_SCOPE)\n  else()\n    message(SEND_ERROR \"Could not fetch ZooKeeper Server ${ARG_VERSION}\\n\"\n                       \"Ivy fetch output:\\n${IVY_FETCH_OUTPUT}\\n\"\n                       \"Ivy fetch errors:\\n${IVY_FETCH_ERROR}\"\n           )\n  endif()\nendfunction()\n"
  },
  {
    "path": "config/dev-env",
    "content": "#!/bin/bash -e\n# Create a build environment.\n\nPROJECT_ROOT=$(readlink -f $(dirname $0)/..)\n\nCOMMAND=/bin/bash\nDISTRO=\n\nfunction show_usage {\n  echo \"$0: Run a program in a Docker environment.\"\n  echo \"\"\n  echo \"Options:\"\n  echo \"\"\n  echo \" --distro DISTRO:\"\n  echo \"        The Linux distro to create the base container.\"\n  echo \"\"\n  echo \"        Available options:\"\n  printf \"         \"\n  printf \" %s\" $(ls $(dirname $0)/docker)\n  echo \"\"\n  echo \" --:\"\n  echo \"        Stop processing arguments and pass the remaining arguments to the\"\n  echo \"        container.\"\n  echo \"\"\n  echo \"Example:\"\n  echo \"\"\n  echo \"  Enter into a bash shell:\"\n  echo \"    $> $0 $(ls $(dirname $0)/docker | sort --random-sort | head -1)\"\n  echo \"\"\n  echo \"  Build the package for a given distro:\"\n  echo \"    $> $0 --distro=$(ls $(dirname $0)/docker | sort --random-sort | head -1) -- ./config/make-package\"\n}\n\nif [[ $# -lt 1 ]]; then\n  show_usage\n  exit 1\nelse\n  UNRECOGNIZED=0\n  while [[ $# -gt 0 ]]; do\n    key=\"$1\"\n\n    case $key in\n      --distro)\n        DISTRO=\"$2\"\n        shift 2\n        ;;\n      --distro=*)\n        DISTRO=\"${key/--distro=/}\"\n        shift\n        ;;\n      --help)\n        show_usage\n        exit 1\n        ;;\n      --)\n        shift\n        COMMAND=$@\n        break\n        ;;\n      *)\n        if [[ -z \"${DISTRO}\" ]]; then\n          DISTRO=\"$key\"\n        else\n          echo \"Unrecognized option: $key\"\n          UNRECOGNIZED=1\n        fi\n        shift\n        ;;\n    esac\n  done\nfi\n\nif [[ -z \"${DISTRO}\" ]]; then\n  echo \"Must set distro.\"\n  show_usage\n  exit 1\nfi\n\nIMAGE_NAME=dev/zookeeper-cpp/${DISTRO}\nDOCKER_DIR=${PROJECT_ROOT}/config/docker/${DISTRO}\n\nif ! [[ -e ${DOCKER_DIR}/Dockerfile ]]; then\n  echo \"Specified distro \\\"${DISTRO}\\\" does not have a Dockerfile\"\n  echo \"\"\n  show_usage\n  exit 1\nfi\n\ndocker build ${DOCKER_DIR} -t ${IMAGE_NAME}\nexec docker run                             \\\n    --rm                                    \\\n    -v ${PROJECT_ROOT}:/root/zookeeper-cpp  \\\n    -w /root/zookeeper-cpp                  \\\n    --security-opt seccomp=unconfined       \\\n    -it ${IMAGE_NAME}                       \\\n    ${COMMAND}\n"
  },
  {
    "path": "config/docker/debian-10/Dockerfile",
    "content": "FROM debian:10\nLABEL maintainer=\"Travis Gockel <travis@gockelhut.com>\"\n\nRUN apt-get update          \\\n && apt-get install --yes   \\\n    cmake                   \\\n    g++                     \\\n    grep                    \\\n    googletest              \\\n    ivy                     \\\n    lcov                    \\\n    libgtest-dev            \\\n    libzookeeper-mt-dev     \\\n    ninja-build\n\nCMD [\"/root/zookeeper-cpp/config/run-tests\"]\n"
  },
  {
    "path": "config/docker/opensuse-15/Dockerfile",
    "content": "FROM opensuse/leap:15\nLABEL maintainer=\"Travis Gockel <travis@gockelhut.com>\"\n\nRUN zypper addrepo -f \"https://download.opensuse.org/repositories/server:/database/openSUSE_Leap_15.2/\" \"serverdatabase\" \\\n && zypper --no-gpg-checks  \\\n    install -y              \\\n    apache-ivy              \\\n    cmake                   \\\n    grep                    \\\n    gcc-c++                 \\\n    git                     \\\n    googletest-devel        \\\n    java-1_8_0-openjdk      \\\n    lcov                    \\\n    libzookeeper2-devel     \\\n    ninja\n\nCMD [\"/root/zookeeper-cpp/config/run-tests\"]\n"
  },
  {
    "path": "config/docker/ubuntu-16.04/Dockerfile",
    "content": "FROM ubuntu:16.04\nLABEL maintainer=\"Travis Gockel <travis@gockelhut.com>\"\n\nRUN apt-get update\nRUN apt-get install --yes software-properties-common\nRUN add-apt-repository --yes ppa:ubuntu-toolchain-r/test\n\n# You might ask why g++-6 is installed, even when we intend to build with GCC 7. It turns out that Coveralls won't work\n# if it isn't installed due to...something. Instead of root causing the issue, we're going to ignore it and hope it gets\n# fixed in a future release.\nRUN apt-get update          \\\n && apt-get install --yes   \\\n    cmake                   \\\n    grep                    \\\n    g++-6                   \\\n    g++-7                   \\\n    ivy                     \\\n    libgtest-dev            \\\n    libzookeeper-mt-dev     \\\n    ninja-build\n\n# Code Coverage\nRUN apt-get install --yes   \\\n    git                     \\\n    lcov                    \\\n    python-pip\nRUN pip install --upgrade pip\nRUN pip install     \\\n    cpp-coveralls   \\\n    pyyaml\n\nRUN update-alternatives --install /usr/bin/c++ c++ /usr/bin/g++-7 99\n\nCMD [\"/root/zookeeper-cpp/config/run-tests\"]\n"
  },
  {
    "path": "config/docker/ubuntu-18.04/Dockerfile",
    "content": "FROM ubuntu:18.04\nLABEL maintainer=\"Travis Gockel <travis@gockelhut.com>\"\n\nRUN apt-get update          \\\n && apt-get install --yes   \\\n    cmake                   \\\n    grep                    \\\n    googletest              \\\n    g++-7                   \\\n    ivy                     \\\n    lcov                    \\\n    libgtest-dev            \\\n    libzookeeper-mt-dev     \\\n    ninja-build\n\nRUN update-alternatives --install /usr/bin/c++ c++ /usr/bin/g++-7 99\n\nCMD [\"/root/zookeeper-cpp/config/run-tests\"]\n"
  },
  {
    "path": "config/docker/ubuntu-20.04/Dockerfile",
    "content": "FROM ubuntu:20.04\nLABEL maintainer=\"Travis Gockel <travis@gockelhut.com>\"\n\nRUN apt-get update                  \\\n && DEBIAN_FRONTEND=noninteractive  \\\n    apt-get install --yes           \\\n    cmake                           \\\n    g++                             \\\n    grep                            \\\n    googletest                      \\\n    ivy                             \\\n    lcov                            \\\n    libgtest-dev                    \\\n    libzookeeper-mt-dev             \\\n    ninja-build\n\nCMD [\"/root/zookeeper-cpp/config/run-tests\"]\n"
  },
  {
    "path": "config/make-doxygen",
    "content": "#!/bin/bash -e\nmkdir -p build/doc\ndoxygen doc/Doxyfile\n"
  },
  {
    "path": "config/make-package",
    "content": "#!/bin/bash -e\n\nPROJECT_ROOT=$(readlink -f $(dirname $0)/..)\nBUILD_DIR=\"$BUILD_DIR\"\nCOPY_TO=\"\"\nDISTRO=\"$DISTRO\"\nPACKAGE_PREFIX=\"\"\n\nfunction show_usage {\n  echo \"$0: Create a package for this operating system.\"\n  echo \"\"\n  echo \"Usage: $0 [OPTION]...\"\n  echo \"\"\n  echo \"Options:\"\n  echo \"\"\n  echo \" --build-dir BUILD_DIR:\"\n  echo \"        Set the build output directory. By default, this is build-release or\"\n  echo \"        build-release-\\${DISTRO} if --distro was set.\"\n  echo \" --copy-to COPY_TO:\"\n  echo \"        Copy the generated packages to the specified folder.\"\n  echo \" --distro DISTRO:\"\n  echo \"        Set the distro name.\"\n  echo \" --dockerize DISTRO:\"\n  echo \"        Perform the build inside of a dev-env-created Docker container. If used,\"\n  echo \"        this must be the first option. The --distro flag is automatically set to\"\n  echo \"        the same value.\"\n  echo \" --package-prefix PREFIX:\"\n  echo \"        Generated packages will be renamed with this prefix. By default, this\"\n  echo \"        will be \\\"\\${DISTRO}-\\\" if --distro was set.\"\n  echo \"\"\n  echo \"Examples:\"\n  echo \"\"\n  echo \"  Create a package inside an Ubuntu 18.04 container, copying the DEBs to a\"\n  echo \"  packages folder.\"\n  echo \"    $> $0 --dockerize=ubuntu-18.04 --copy-to=packages\"\n}\n\nUNRECOGNIZED=0\nDOCKERIZE=0\nFIRST=1\nwhile [[ $# -gt 0 ]]; do\n  key=\"$1\"\n\n  case $key in\n    --build-dir)\n      BUILD_DIR=\"$2\"\n      shift 2\n      ;;\n    --build-dir=*)\n      BUILD_DIR=\"${key/--build-dir=/}\"\n      shift\n      ;;\n    --copy-to)\n      COPY_TO=\"$2\"\n      shift 2\n      ;;\n    --copy-to=*)\n      COPY_TO=\"${key/--copy-to=/}\"\n      shift\n      ;;\n    --distro)\n      DISTRO=\"$2\"\n      shift 2\n      ;;\n    --distro=*)\n      DISTRO=\"${key/--distro=/}\"\n      shift\n      ;;\n    --dockerize)\n      DISTRO=\"$2\"\n      DOCKERIZE=1\n      shift 2\n      break\n      ;;\n    --dockerize=*)\n      DISTRO=\"${key/--dockerize=/}\"\n      DOCKERIZE=1\n      shift\n      break\n      ;;\n    --help)\n      show_usage\n      exit 1\n      ;;\n    --package-prefix)\n      PACKAGE_PREFIX=\"$2\"\n      shift 2\n      ;;\n    --package-prefix=*)\n      PACKAGE_PREFIX=\"${key/--package-prefix=/}\"\n      shift\n      ;;\n    *)\n      echo \"Unrecognized option: $key\"\n      UNRECOGNIZED=1\n      shift\n      ;;\n  esac\n  FIRST=0\ndone\n\nif [[ ${UNRECOGNIZED} -ne 0 ]]; then\n  show_usage\n  exit 1\nfi\n\nif [[ ${DOCKERIZE} -eq 1 ]]; then\n  if [[ ${FIRST} -eq 0 ]]; then\n    echo \"If using --dockerize, it must be the first argument.\"\n    exit 1\n  fi\n\n  exec ${PROJECT_ROOT}/config/dev-env \"${DISTRO}\" -- \\\n        ./config/make-package                        \\\n            --distro=\"${DISTRO}\"                     \\\n            $@\nfi\n\nif [[ -z \"${BUILD_DIR}\" ]]; then\n  if [[ -z \"${DISTRO}\" ]]; then\n    BUILD_DIR=\"build-release\"\n  else\n    BUILD_DIR=\"build-release-${DISTRO}\"\n  fi\nfi\n\nif [[ -z \"${PACKAGE_PREFIX}\" ]]; then\n  if [[ -n \"${DISTRO}\" ]]; then\n    PACKAGE_PREFIX=\"${DISTRO}-\"\n  fi\nfi\n\nif $(hash rpm 2>/dev/null); then\n  PACKAGE_SYSTEM=RPM\n  PACKAGE_SUFFIX=rpm\n\n  function show_contents {\n    rpm -qlp \"$1\"\n  }\nelif $(hash dpkg 2>/dev/null); then\n  PACKAGE_SYSTEM=DEB\n  PACKAGE_SUFFIX=deb\n\n  function show_contents {\n    dpkg --contents \"${pkg_file}\"\n  }\nelse\n  echo \"Unknown packaging system for this operating system\"\n  exit 2\nfi\n\nif [[ -e ${BUILD_DIR} ]]; then\n  echo \"Build directory (${BUILD_DIR}) unclean -- deleting it\"\n  rm -rf ${BUILD_DIR}\nfi\nmkdir ${BUILD_DIR}\n\ncd ${BUILD_DIR}\ncmake .. -DZKPP_PACKAGE_SYSTEM=${PACKAGE_SYSTEM} -DCMAKE_BUILD_TYPE=Release -GNinja\nninja package\ncd -\n\nfor pkg_file in $(find \"${BUILD_DIR}/install\" -maxdepth 1 -name \"*.${PACKAGE_SUFFIX}\"); do\n  if [[ -n \"${PACKAGE_PREFIX}\" ]]; then\n    new_pkg_file=\"$(dirname -- ${pkg_file})/${PACKAGE_PREFIX}$(basename -- ${pkg_file})\"\n    mv \"${pkg_file}\" \"${new_pkg_file}\"\n    pkg_file=\"${new_pkg_file}\"\n  fi\n\n  echo \"PACKAGE ${pkg_file}\"\n  show_contents \"${pkg_file}\"\n\n  if [[ -n \"${COPY_TO}\" ]]; then\n    cp \"${pkg_file}\" \"${COPY_TO}\"\n  fi\ndone\n"
  },
  {
    "path": "config/make-packages",
    "content": "#!/bin/bash -e\n\nPROJECT_ROOT=$(readlink -f $(dirname $0)/..)\n\nfunction show_usage {\n  echo \"$0: Create multiple packages.\"\n  echo \"\"\n  echo \"Usage: $0 [OPTION]... [DISTRO]...\"\n  echo \"\"\n  echo \"Options:\"\n  echo \"\"\n  echo \" --all\"\n  echo \"        Create packages for all known distributions. If this is specified, you\"\n  echo \"        are not allowed to specify individual distributions.\"\n  echo \" --copy-to COPY_TO:\"\n  echo \"        Copy the generated packages to the specified folder.\"\n  echo \" --:\"\n  echo \"        Stop processing and pass the remaining arguments to make-package.\"\n}\n\nCOPY_TO=\nDISTROS=()\nALL=0\nwhile [[ $# -gt 0 ]]; do\n  key=\"$1\"\n\n  case $key in\n    --all)\n      ALL=1\n      shift\n      ;;\n    --copy-to)\n      COPY_TO=\"$2\"\n      shift 2\n      ;;\n    --copy-to=*)\n      COPY_TO=\"${key/--copy-to=/}\"\n      shift\n      ;;\n    --help)\n      show_usage\n      exit 1\n      ;;\n    --)\n      # No shift: want to use the -- in the call\n      break\n      ;;\n    *)\n      DISTROS+=(${key})\n      shift\n      ;;\n  esac\ndone\n\nif [[ ${ALL} -eq 1 ]]; then\n  if [[ ${#DISTROS[@]} -ne 0 ]]; then\n    echo \"Specified --all, but also distributions: ${DISTROS[@]}\"\n    echo \"\"\n    show_usage\n    exit 1\n  fi\n\n  # All distros with working package systems.\n  DISTROS=(ubuntu-16.04 ubuntu-17.10 ubuntu-18.04 ubuntu-18.10 debian-9)\nfi\n\nif [[ -n \"${COPY_TO}\" ]]; then\n  mkdir -p \"${COPY_TO}\"\n  COPY_TO=\"--copy-to=${COPY_TO}\"\nfi\n\nfor distro in ${DISTROS[@]}; do\n  echo \"BUILDING PACKAGE FOR $distro\"\n  ${PROJECT_ROOT}/config/make-package --dockerize=${distro} ${COPY_TO} $@\ndone\n"
  },
  {
    "path": "config/publish-doxygen",
    "content": "#!/bin/bash -e\n\n# Settings\nREPO_PATH=git@github.com:tgockel/zookeeper-cpp.git\nHTML_PATH=build/doc/html\nCOMMIT_USER=\"Documentation Builder\"\nCOMMIT_EMAIL=\"travis@gockelhut.com\"\nCHANGESET=$(git rev-parse --verify HEAD)\n\n# Get a clean version of the HTML documentation repo.\nrm -rf ${HTML_PATH}\nmkdir -p ${HTML_PATH}\ngit clone -b gh-pages \"${REPO_PATH}\" --single-branch ${HTML_PATH}\n\n# rm all the files through git to prevent stale files.\ncd ${HTML_PATH}\ngit rm -rf *.html *.js *.png *.css search\ncd -\n\n# Generate the HTML documentation.\n./config/make-doxygen\n\n# Create and commit the documentation repo.\ncd ${HTML_PATH}\ngit add .\ngit config user.name \"${COMMIT_USER}\"\ngit config user.email \"${COMMIT_EMAIL}\"\ngit commit -m \"Automated documentation build for changeset ${CHANGESET}.\"\ngit push origin gh-pages\ncd -\n"
  },
  {
    "path": "config/run-tests",
    "content": "#!/bin/bash -e\n\nPROJECT_ROOT=$(readlink -f $(dirname $0)/..)\nBUILD_DIR=${PROJECT_ROOT}/build-ci\necho \"${DISTRO} ${PROJECT_ROOT}\"\nc++ --version\n\nif [[ -e ${BUILD_DIR} ]]; then\n    echo \"Build directory (${BUILD_DIR}) unclean -- deleting it\"\n    rm -rf ${BUILD_DIR}\nfi\nmkdir ${BUILD_DIR}\ncd ${BUILD_DIR}\n\nif $(hash lcov 2>/dev/null); then\n  # TODO(https://github.com/tgockel/zookeeper-cpp/issues/42): Disabling Coveralls coverage for now.\n  COVERAGE=0\nelse\n  COVERAGE=0\nfi\n\ncmake .. -DZKPP_BUILD_OPTION_CODE_COVERAGE=${COVERAGE}\nmake VERBOSE=1\nmake test\n\nif [[ ${COVERAGE} -eq 1 ]]; then\n  ${PROJECT_ROOT}/config/upload-coverage ${BUILD_DIR}\nfi\n"
  },
  {
    "path": "config/upload-coverage",
    "content": "#!/bin/bash -e\n\n# TODO: 99% sure there is a better way to do this\nGCC_VERSION_MAJOR=$(c++ --version | grep -Po '\\d\\.\\d\\.\\d' | grep -Po '^\\d+' | head -1)\n\nls -l $1/../coveralls-repo-token\nCOVERALLS_REPO_TOKEN=$(cat $1/../coveralls-repo-token)\nTRAVIS=true\nCI=true\n\ncoveralls                                                       \\\n    --repo-token=\"${COVERALLS_REPO_TOKEN}\"                      \\\n    --build-root $1                                             \\\n    --exclude-pattern '_test\\.cpp$'                             \\\n    --exclude-pattern bits                                      \\\n    --gcov-options '\\-lp' --gcov \"gcov-${GCC_VERSION_MAJOR}\"\n"
  },
  {
    "path": "doc/Doxyfile",
    "content": "#---------------------------------------------------------------------------\n# Project related configuration options\n#---------------------------------------------------------------------------\n\nDOXYFILE_ENCODING      = UTF-8\nPROJECT_NAME           = \"zookeeper-cpp\"\nPROJECT_NUMBER         =\nPROJECT_BRIEF          = \"ZooKeeper Client for C++\"\nPROJECT_LOGO           =\nOUTPUT_DIRECTORY       = build/doc\nCREATE_SUBDIRS         = NO\nOUTPUT_LANGUAGE        = English\nBRIEF_MEMBER_DESC      = YES\nREPEAT_BRIEF           = YES\nABBREVIATE_BRIEF       =\nALWAYS_DETAILED_SEC    = YES\nINLINE_INHERITED_MEMB  = NO\nFULL_PATH_NAMES        = YES\nSTRIP_FROM_PATH        = src\nSTRIP_FROM_INC_PATH    = src\nSHORT_NAMES            = NO\nJAVADOC_AUTOBRIEF      = YES\nQT_AUTOBRIEF           = NO\nMULTILINE_CPP_IS_BRIEF = NO\nINHERIT_DOCS           = YES\nSEPARATE_MEMBER_PAGES  = NO\nTAB_SIZE               = 4\nALIASES                =\nTCL_SUBST              =\nOPTIMIZE_OUTPUT_FOR_C  = NO\nOPTIMIZE_OUTPUT_JAVA   = NO\nOPTIMIZE_FOR_FORTRAN   = NO\nOPTIMIZE_OUTPUT_VHDL   = NO\nEXTENSION_MAPPING      =\nMARKDOWN_SUPPORT       = YES\nAUTOLINK_SUPPORT       = YES\nBUILTIN_STL_SUPPORT    = YES\nCPP_CLI_SUPPORT        = NO\nSIP_SUPPORT            = NO\nIDL_PROPERTY_SUPPORT   = YES\nDISTRIBUTE_GROUP_DOC   = NO\nSUBGROUPING            = YES\nINLINE_GROUPED_CLASSES = NO\nINLINE_SIMPLE_STRUCTS  = NO\nTYPEDEF_HIDES_STRUCT   = NO\nLOOKUP_CACHE_SIZE      = 0\n\n#---------------------------------------------------------------------------\n# Build related configuration options\n#---------------------------------------------------------------------------\n\nEXTRACT_ALL            = NO\nEXTRACT_PRIVATE        = NO\nEXTRACT_STATIC         = NO\nEXTRACT_LOCAL_CLASSES  = YES\nEXTRACT_LOCAL_METHODS  = NO\nEXTRACT_ANON_NSPACES   = NO\nHIDE_UNDOC_MEMBERS     = NO\nHIDE_UNDOC_CLASSES     = NO\nHIDE_FRIEND_COMPOUNDS  = NO\nHIDE_IN_BODY_DOCS      = NO\nINTERNAL_DOCS          = NO\nCASE_SENSE_NAMES       = YES\nHIDE_SCOPE_NAMES       = NO\nSHOW_INCLUDE_FILES     = YES\nFORCE_LOCAL_INCLUDES   = NO\nINLINE_INFO            = YES\nSORT_MEMBER_DOCS       = YES\nSORT_BRIEF_DOCS        = NO\nSORT_MEMBERS_CTORS_1ST = YES\nSORT_GROUP_NAMES       = YES\nSORT_BY_SCOPE_NAME     = NO\nSTRICT_PROTO_MATCHING  = NO\nGENERATE_TODOLIST      = YES\nGENERATE_TESTLIST      = YES\nGENERATE_BUGLIST       = YES\nGENERATE_DEPRECATEDLIST= YES\nENABLED_SECTIONS       =\nMAX_INITIALIZER_LINES  = 30\nSHOW_USED_FILES        = YES\nSHOW_DIRECTORIES       = NO\nSHOW_FILES             = YES\nSHOW_NAMESPACES        = YES\nFILE_VERSION_FILTER    =\nLAYOUT_FILE            =\nCITE_BIB_FILES         =\n\n#---------------------------------------------------------------------------\n# Configuration options related to warning and progress messages\n#---------------------------------------------------------------------------\n\nQUIET                  = NO\nWARNINGS               = YES\nWARN_IF_UNDOCUMENTED   = YES\nWARN_IF_DOC_ERROR      = YES\nWARN_NO_PARAMDOC       = NO\nWARN_FORMAT            = \"$file:$line: $text\"\nWARN_LOGFILE           =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the input files\n#---------------------------------------------------------------------------\n\nINPUT                  = src README.md\nINPUT_ENCODING         = UTF-8\nFILE_PATTERNS          =\nRECURSIVE              = YES\nEXCLUDE                =\nEXCLUDE_SYMLINKS       = NO\nEXCLUDE_PATTERNS       = *_tests.cpp *_tests.hpp tests detail\nEXCLUDE_SYMBOLS        =\nEXAMPLE_PATH           =\nEXAMPLE_PATTERNS       =\nEXAMPLE_RECURSIVE      = NO\nIMAGE_PATH             =\nINPUT_FILTER           =\nFILTER_PATTERNS        =\nFILTER_SOURCE_FILES    = NO\nFILTER_SOURCE_PATTERNS =\nUSE_MDFILE_AS_MAINPAGE = README.md\n\n#---------------------------------------------------------------------------\n# Configuration options related to source browsing\n#---------------------------------------------------------------------------\n\nSOURCE_BROWSER         = YES\nINLINE_SOURCES         = NO\nSTRIP_CODE_COMMENTS    = YES\nREFERENCED_BY_RELATION = NO\nREFERENCES_RELATION    = NO\nREFERENCES_LINK_SOURCE = YES\nUSE_HTAGS              = NO\nVERBATIM_HEADERS       = YES\n\n#---------------------------------------------------------------------------\n# Configuration options related to the alphabetical class index\n#---------------------------------------------------------------------------\n\nALPHABETICAL_INDEX     = YES\nCOLS_IN_ALPHA_INDEX    = 5\nIGNORE_PREFIX          =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the HTML output\n#---------------------------------------------------------------------------\n\nGENERATE_HTML          = YES\nHTML_OUTPUT            = html\nHTML_FILE_EXTENSION    = .html\nHTML_HEADER            =\nHTML_FOOTER            =\nHTML_STYLESHEET        =\nHTML_EXTRA_STYLESHEET  =\nHTML_EXTRA_FILES       =\nHTML_COLORSTYLE_HUE    = 220\nHTML_COLORSTYLE_SAT    = 100\nHTML_COLORSTYLE_GAMMA  = 80\nHTML_TIMESTAMP         = NO\nHTML_DYNAMIC_SECTIONS  = YES\nHTML_INDEX_NUM_ENTRIES = 100\nGENERATE_DOCSET        = NO\nDOCSET_FEEDNAME        = \"Doxygen generated docs\"\nDOCSET_BUNDLE_ID       = com.gockelhut.zookeeper-cpp\nDOCSET_PUBLISHER_ID    = com.gockelhut\nDOCSET_PUBLISHER_NAME  = Gockel\nGENERATE_HTMLHELP      = NO\nCHM_FILE               =\nHHC_LOCATION           =\nGENERATE_CHI           = NO\nCHM_INDEX_ENCODING     =\nBINARY_TOC             = NO\nTOC_EXPAND             = NO\n\nGENERATE_QHP           = NO\nQCH_FILE               =\nQHP_NAMESPACE          = org.doxygen.Project\nQHP_VIRTUAL_FOLDER     = doc\nQHP_CUST_FILTER_NAME   =\nQHP_CUST_FILTER_ATTRS  =\nQHP_SECT_FILTER_ATTRS  =\nQHG_LOCATION           =\nGENERATE_ECLIPSEHELP   = NO\nECLIPSE_DOC_ID         = com.gockelhut.zookeeper-cpp\nDISABLE_INDEX          = NO\nGENERATE_TREEVIEW      = YES\nENUM_VALUES_PER_LINE   = 4\nUSE_INLINE_TREES       = NO\nTREEVIEW_WIDTH         = 250\nEXT_LINKS_IN_WINDOW    = NO\nFORMULA_FONTSIZE       = 10\nFORMULA_TRANSPARENT    = YES\nUSE_MATHJAX            = NO\nMATHJAX_RELPATH        = http://www.mathjax.org/mathjax\nMATHJAX_EXTENSIONS     =\nSEARCHENGINE           = YES\nSERVER_BASED_SEARCH    = NO\nEXTERNAL_SEARCH        = NO\nSEARCHENGINE_URL       =\nSEARCHDATA_FILE        = searchdata.xml\nEXTERNAL_SEARCH_ID     =\nEXTRA_SEARCH_MAPPINGS  =\n\n#---------------------------------------------------------------------------\n# configuration options related to the LaTeX output\n#---------------------------------------------------------------------------\n\nGENERATE_LATEX         = NO\nLATEX_OUTPUT           = latex\nLATEX_CMD_NAME         = latex\nMAKEINDEX_CMD_NAME     = makeindex\nCOMPACT_LATEX          = NO\nPAPER_TYPE             = a4\nEXTRA_PACKAGES         =\nLATEX_HEADER           =\nLATEX_FOOTER           =\nPDF_HYPERLINKS         = YES\nUSE_PDFLATEX           = YES\nLATEX_BATCHMODE        = NO\nLATEX_HIDE_INDICES     = NO\nLATEX_SOURCE_CODE      = NO\nLATEX_BIB_STYLE        = plain\n\n#---------------------------------------------------------------------------\n# configuration options related to the RTF output\n#---------------------------------------------------------------------------\n\nGENERATE_RTF           = NO\nRTF_OUTPUT             = rtf\nCOMPACT_RTF            = NO\nRTF_HYPERLINKS         = NO\nRTF_STYLESHEET_FILE    =\nRTF_EXTENSIONS_FILE    =\n\n#---------------------------------------------------------------------------\n# configuration options related to the man page output\n#---------------------------------------------------------------------------\n\nGENERATE_MAN           = NO\nMAN_OUTPUT             = man\nMAN_EXTENSION          = .3\nMAN_LINKS              = NO\n\n#---------------------------------------------------------------------------\n# configuration options related to the XML output\n#---------------------------------------------------------------------------\n\nGENERATE_XML           = NO\nXML_OUTPUT             = xml\nXML_SCHEMA             =\nXML_DTD                =\nXML_PROGRAMLISTING     = YES\n\n#---------------------------------------------------------------------------\n# configuration options for the AutoGen Definitions output\n#---------------------------------------------------------------------------\n\nGENERATE_AUTOGEN_DEF   = NO\n\n#---------------------------------------------------------------------------\n# configuration options related to the Perl module output\n#---------------------------------------------------------------------------\n\nGENERATE_PERLMOD       = NO\nPERLMOD_LATEX          = NO\nPERLMOD_PRETTY         = YES\nPERLMOD_MAKEVAR_PREFIX =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the preprocessor\n#---------------------------------------------------------------------------\n\nENABLE_PREPROCESSING   = YES\nMACRO_EXPANSION        = NO\nEXPAND_ONLY_PREDEF     = NO\nSEARCH_INCLUDES        = YES\nINCLUDE_PATH           =\nINCLUDE_FILE_PATTERNS  =\nPREDEFINED             =\nEXPAND_AS_DEFINED      =\nSKIP_FUNCTION_MACROS   = YES\n\n#---------------------------------------------------------------------------\n# Configuration::additions related to external references\n#---------------------------------------------------------------------------\n\nTAGFILES               =\nGENERATE_TAGFILE       =\nALLEXTERNALS           = NO\nEXTERNAL_GROUPS        = YES\nPERL_PATH              = /usr/bin/perl\n\n#---------------------------------------------------------------------------\n# Configuration options related to the dot tool\n#---------------------------------------------------------------------------\n\nCLASS_DIAGRAMS         = YES\nMSCGEN_PATH            =\nHIDE_UNDOC_RELATIONS   = YES\nHAVE_DOT               = NO\nDOT_NUM_THREADS        = 0\nDOT_FONTNAME           = Helvetica\nDOT_FONTSIZE           = 10\nDOT_FONTPATH           =\nCLASS_GRAPH            = YES\nCOLLABORATION_GRAPH    = YES\nGROUP_GRAPHS           = YES\nUML_LOOK               = NO\nTEMPLATE_RELATIONS     = NO\nINCLUDE_GRAPH          = YES\nINCLUDED_BY_GRAPH      = YES\nCALL_GRAPH             = YES\nCALLER_GRAPH           = NO\nGRAPHICAL_HIERARCHY    = YES\nDIRECTORY_GRAPH        = YES\nDOT_IMAGE_FORMAT       = svg\nINTERACTIVE_SVG        = YES\nDOT_PATH               =\nDOTFILE_DIRS           = .\nMSCFILE_DIRS           =\nDOT_GRAPH_MAX_NODES    = 50\nMAX_DOT_GRAPH_DEPTH    = 0\nDOT_TRANSPARENT        = NO\nDOT_MULTI_TARGETS      = YES\nGENERATE_LEGEND        = YES\nDOT_CLEANUP            = YES\n"
  },
  {
    "path": "install/deb/libzkpp/control.in",
    "content": "Package: libzkpp@PROJECT_SO_VERSION@\nVersion: @PROJECT_PACKAGE_VERSION@\nPriority: optional\nMaintainer: Travis Gockel <travis@gockelhut.com>\nArchitecture: @PROJECT_BUILD_ARCHITECTURE@\nSection: libs\nDepends: libzookeeper-mt2 (>= 3.4), libc6 (>= 2.18), libstdc++6 (>= 7-20170407)\nBuild-Depends: debhelper (>= 9), cmake (>= 3.5), libzookeeper-mt-dev (>= 3.4)\nHomepage: https://tgockel.github.io/zookeeper-cpp/\nDescription: A ZooKeeper client for C++.\n"
  },
  {
    "path": "install/deb/libzkpp/postinst.in",
    "content": "#!/bin/sh -e\n\nupdate-alternatives --install @CMAKE_INSTALL_PREFIX@/lib/libzkpp.so libzkpp @CMAKE_INSTALL_PREFIX@/lib/libzkpp.so.@PROJECT_SO_VERSION@ 10\n"
  },
  {
    "path": "install/deb/libzkpp/shlibs.in",
    "content": "libzkpp 0.2 libzkpp0.2 (>= 0.2.3)\n"
  },
  {
    "path": "install/deb/libzkpp-dev/control.in",
    "content": "Package: libzkpp@PROJECT_SO_VERSION@-dev\nVersion: @PROJECT_PACKAGE_VERSION@\nPriority: optional\nMaintainer: Travis Gockel <travis@gockelhut.com>\nArchitecture: all\nSection: libdevel\nDepends: libzkpp@PROJECT_SO_VERSION@ (= @PROJECT_PACKAGE_VERSION@)\nDescription: development files for zkpp\n A ZooKeeper client for C++.\n"
  },
  {
    "path": "install/deb/libzkpp-server/control.in",
    "content": "Package: libzkpp-server@PROJECT_SO_VERSION@\nVersion: @PROJECT_PACKAGE_VERSION@\nPriority: optional\nMaintainer: Travis Gockel <travis@gockelhut.com>\nArchitecture: @PROJECT_BUILD_ARCHITECTURE@\nSection: libs\nDepends: libzkpp@PROJECT_SO_VERSION@ (= @PROJECT_PACKAGE_VERSION@)\nBuild-Depends: debhelper (>= 9), cmake (>= 3.5)\nHomepage: https://tgockel.github.io/zookeeper-cpp/\nDescription: Control a ZooKeeper Java process on a single machine.\n"
  },
  {
    "path": "install/deb/libzkpp-server/postinst.in",
    "content": "#!/bin/sh -e\n\nupdate-alternatives --install @CMAKE_INSTALL_PREFIX@/lib/libzkpp-server.so libzkpp-server @CMAKE_INSTALL_PREFIX@/lib/libzkpp-server.so.@PROJECT_SO_VERSION@ 10\n"
  },
  {
    "path": "install/deb/libzkpp-server-dev/control.in",
    "content": "Package: libzkpp-server@PROJECT_SO_VERSION@-dev\nVersion: @PROJECT_PACKAGE_VERSION@\nPriority: optional\nMaintainer: Travis Gockel <travis@gockelhut.com>\nArchitecture: all\nSection: libdevel\nDepends: libzkpp@PROJECT_SO_VERSION@-dev (= @PROJECT_PACKAGE_VERSION@),\n         libzkpp-server@PROJECT_SO_VERSION@ (= @PROJECT_PACKAGE_VERSION@)\nDescription: development files for zkpp_server@PROJECT_SO_VERSION@\n Control a ZooKeeper Java process on a single machine.\n"
  },
  {
    "path": "src/zk/acl.cpp",
    "content": "#include \"acl.hpp\"\n\n#include <ostream>\n#include <sstream>\n#include <tuple>\n#include <utility>\n\nnamespace zk\n{\n\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n// permission                                                                                                         //\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\nstd::ostream& operator<<(std::ostream& os, const permission& self)\n{\n    if (self == permission::none)\n        return os << \"none\";\n    else if (self == permission::all)\n        return os << \"all\";\n\n    bool first = true;\n    auto tick = [&] { return std::exchange(first, false) ? \"\" : \"|\"; };\n    if (allows(self, permission::read))   os << tick() << \"read\";\n    if (allows(self, permission::write))  os << tick() << \"write\";\n    if (allows(self, permission::create)) os << tick() << \"create\";\n    if (allows(self, permission::erase))  os << tick() << \"erase\";\n    if (allows(self, permission::admin))  os << tick() << \"admin\";\n\n    return os;\n}\n\nstd::string to_string(const permission& self)\n{\n    std::ostringstream os;\n    os << self;\n    return os.str();\n}\n\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n// acl_rule                                                                                                           //\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\nacl_rule::acl_rule(std::string scheme, std::string id, permission permissions) :\n        _scheme(std::move(scheme)),\n        _id(std::move(id)),\n        _permissions(permissions)\n{ }\n\nacl_rule::~acl_rule() noexcept\n{ }\n\nstd::size_t hash(const acl_rule& self)\n{\n    return std::hash<std::string>{}(self.scheme())\n         ^ std::hash<std::string>{}(self.id())\n         ^ std::hash<unsigned int>{}(static_cast<unsigned int>(self.permissions()));\n}\n\nbool operator==(const acl_rule& lhs, const acl_rule& rhs)\n{\n    return lhs.scheme()      == rhs.scheme()\n        && lhs.id()          == rhs.id()\n        && lhs.permissions() == rhs.permissions();\n}\n\nbool operator!=(const acl_rule& lhs, const acl_rule& rhs)\n{\n    return !(lhs == rhs);\n}\n\nbool operator< (const acl_rule& lhs, const acl_rule& rhs)\n{\n    return std::tie(lhs.scheme(), lhs.id(), lhs.permissions()) < std::tie(rhs.scheme(), rhs.id(), rhs.permissions());\n}\n\nbool operator<=(const acl_rule& lhs, const acl_rule& rhs)\n{\n    return !(rhs < lhs);\n}\n\nbool operator> (const acl_rule& lhs, const acl_rule& rhs)\n{\n    return rhs < lhs;\n}\n\nbool operator>=(const acl_rule& lhs, const acl_rule& rhs)\n{\n    return !(lhs < rhs);\n}\n\nstd::ostream& operator<<(std::ostream& os, const acl_rule& self)\n{\n    os << '(' << self.scheme();\n    if (!self.id().empty())\n        os << ':' << self.id();\n    os << \", \" << self.permissions() << ')';\n    return os;\n}\n\nstd::string to_string(const acl_rule& self)\n{\n    std::ostringstream os;\n    os << self;\n    return os.str();\n}\n\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n// acl                                                                                                                //\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\nacl::acl(std::vector<acl_rule> rules) noexcept :\n        _impl(std::move(rules))\n{ }\n\nacl::~acl() noexcept\n{ }\n\nbool operator==(const acl& lhs, const acl& rhs)\n{\n    return std::equal(lhs.cbegin(), lhs.cend(), rhs.cbegin(), rhs.cend());\n}\n\nbool operator!=(const acl& lhs, const acl& rhs)\n{\n    return !(lhs == rhs);\n}\n\nstd::ostream& operator<<(std::ostream& os, const acl& self)\n{\n    os << '[';\n    bool first = true;\n    for (const auto& x : self)\n    {\n        if (first)\n            first = false;\n        else\n            os << \", \";\n        os << x;\n    }\n    return os << ']';\n}\n\nstd::string to_string(const acl& self)\n{\n    std::ostringstream os;\n    os << self;\n    return os.str();\n}\n\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n// acls                                                                                                               //\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\nconst acl& acls::creator_all()\n{\n    static acl instance = { { \"auth\", \"\", permission::all } };\n    return instance;\n}\n\nconst acl& acls::open_unsafe()\n{\n    static acl instance = { { \"world\", \"anyone\", permission::all } };\n    return instance;\n}\n\nconst acl& acls::read_unsafe()\n{\n    static acl instance = { { \"world\", \"anyone\", permission::read } };\n    return instance;\n}\n\n}\n"
  },
  {
    "path": "src/zk/acl.hpp",
    "content": "#pragma once\n\n#include <zk/config.hpp>\n\n#include <initializer_list>\n#include <iosfwd>\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"forwards.hpp\"\n\nnamespace zk\n{\n\n/// \\addtogroup Client\n/// \\{\n\n/// Describes the ability of a user to perform a certain action. Permissions can be mixed together like integers with\n/// \\c | and \\c &.\nenum class permission : unsigned int\n{\n    none    = 0b00000, //!< No permissions are set (server could have been configured without ACL support).\n    read    = 0b00001, //!< You can access the data of a node and can list its children.\n    write   = 0b00010, //!< You can set the data of a node.\n    create  = 0b00100, //!< You can create a child node.\n    erase   = 0b01000, //!< You can erase a child node (but not necessarily this one).\n    admin   = 0b10000, //!< You can alter permissions on this node.\n    all     = 0b11111, //!< You can do anything.\n};\n\n/// \\{\n/// Set union operation of \\ref permission.\ninline constexpr permission operator|(permission a, permission b)\n{\n    return permission(static_cast<unsigned int>(a) | static_cast<unsigned int>(b));\n}\n\ninline constexpr permission& operator|=(permission& self, permission mask)\n{\n    return self = self | mask;\n}\n/// \\}\n\n/// \\{\n/// Set intersection operation of \\ref permission.\ninline constexpr permission operator&(permission a, permission b)\n{\n    return permission(static_cast<unsigned int>(a) & static_cast<unsigned int>(b));\n}\n\ninline constexpr permission& operator&=(permission& self, permission mask)\n{\n    return self = self & mask;\n}\n/// \\}\n\n/// Set inverse operation of \\ref permission. This is not exactly the bitwise complement of \\a a, as the returned value\n/// will only include bits set that are valid in \\ref permission discriminants.\ninline constexpr permission operator~(permission a)\n{\n    return permission(~static_cast<unsigned int>(a)) & permission::all;\n}\n\n/// Check that \\a self allows it \\a perform all operations. For example,\n/// `allows(permission::read | permission::write, permission::read)` will be \\c true, as `read|write` is allowed to\n/// \\c read.\ninline constexpr bool allows(permission self, permission perform)\n{\n    return (self & perform) == perform;\n}\n\nstd::ostream& operator<<(std::ostream&, const permission&);\n\nstd::string to_string(const permission&);\n\n/// An individual rule in an \\ref acl. It consists of a \\ref scheme and \\ref id pair to identify the who and a\n/// \\ref permission set to determine what they are allowed to do.\n///\n/// See <a href=\"https://zookeeper.apache.org/doc/r3.4.10/zookeeperProgrammers.html#sc_ACLPermissions\">\"Builtin ACL\n/// Schemes\"</a> in the ZooKeeper Programmer's Guide for more information.\nclass acl_rule final\n{\npublic:\n    /// Create an ACL under the given \\a scheme and \\a id with the given \\a permissions.\n    acl_rule(std::string scheme, std::string id, permission permissions);\n\n    acl_rule(const acl_rule&)            = default;\n    acl_rule& operator=(const acl_rule&) = default;\n\n    acl_rule(acl_rule&&)            = default;\n    acl_rule& operator=(acl_rule&&) = default;\n\n    ~acl_rule() noexcept;\n\n    /// The authentication scheme this list is used for. The most common scheme is `\"auth\"`, which allows any\n    /// authenticated user to perform actions (see \\ref acls::creator_all).\n    ///\n    /// ZooKeeper's authentication system is extensible, but the majority of use cases are covered by the built-in\n    /// schemes:\n    ///\n    /// - \\c \"world\" -- This has a single ID \\c \"anyone\" that represents any user of the system. The ACLs\n    ///   \\ref acls::open_unsafe and \\ref acls::read_unsafe use the \\c \"world\" scheme.\n    /// - \\c \"auth\" -- This represents any authenticated user. The \\c id field is unused. The ACL \\ref acls::creator_all\n    ///   uses the \\c \"auth\" scheme.\n    /// - \\c \"digest\" -- This uses a \\c \"${username}:${password}\" string to generate MD5 hash which is then used as an\n    ///   identity. Authentication is done by sending the string in clear text. When used in the ACL, the expression\n    ///   will be the \\c \"${username}:${digest}\", where \\c digest is the base 64 encoded SHA1 digest of \\c password.\n    /// - \\c \"ip\" -- This uses the client host IP as an identity. The \\c id expression is an IP address or CIDR netmask,\n    ///   which will be matched against the client identity.\n    const std::string& scheme() const\n    {\n        return _scheme;\n    }\n\n    /// The ID of the user under the \\ref scheme. For example, with the \\c \"ip\" \\c scheme, this is an IP address or CIDR\n    /// netmask.\n    const std::string& id() const\n    {\n        return _id;\n    }\n\n    /// The permissions associated with this ACL.\n    const permission& permissions() const\n    {\n        return _permissions;\n    }\n\nprivate:\n    std::string _scheme;\n    std::string _id;\n    permission  _permissions;\n};\n\n/// Compute a hash for the given \\a rule.\nstd::size_t hash(const acl_rule& rule);\n\n[[gnu::pure]] bool operator==(const acl_rule& lhs, const acl_rule& rhs);\n[[gnu::pure]] bool operator!=(const acl_rule& lhs, const acl_rule& rhs);\n[[gnu::pure]] bool operator< (const acl_rule& lhs, const acl_rule& rhs);\n[[gnu::pure]] bool operator<=(const acl_rule& lhs, const acl_rule& rhs);\n[[gnu::pure]] bool operator> (const acl_rule& lhs, const acl_rule& rhs);\n[[gnu::pure]] bool operator>=(const acl_rule& lhs, const acl_rule& rhs);\n\nstd::ostream& operator<<(std::ostream&, const acl_rule&);\n\nstd::string to_string(const acl_rule&);\n\n/// An access control list is a wrapper around \\ref acl_rule instances. In general, the ACL system is similar to UNIX\n/// file access permissions, where znodes act as files. Unlike UNIX, each znode can have any number of ACLs to\n/// correspond with the potentially limitless (and pluggable) authentication schemes. A more surprising difference is\n/// that ACLs are not recursive: If \\c /path is only readable by a single user, but \\c /path/sub is world-readable, then\n/// anyone will be able to read \\c /path/sub.\n///\n/// See <a href=\"https://zookeeper.apache.org/doc/trunk/zookeeperProgrammers.html#sc_ZooKeeperAccessControl\">ZooKeeper\n/// Programmer's Guide</a> for more information.\n///\n/// \\see acls\nclass acl final\n{\npublic:\n    using iterator       = std::vector<acl_rule>::iterator;\n    using const_iterator = std::vector<acl_rule>::const_iterator;\n    using size_type      = std::size_t;\n\npublic:\n    /// Create an empty ACL. Keep in mind that an empty ACL is an illegal ACL.\n    acl() = default;\n\n    /// Create an instance with the provided \\a rules.\n    acl(std::vector<acl_rule> rules) noexcept;\n\n    /// Create an instance with the provided \\a rules.\n    acl(std::initializer_list<acl_rule> rules) :\n            acl(std::vector<acl_rule>(rules))\n    { }\n\n    acl(const acl&)            = default;\n    acl& operator=(const acl&) = default;\n\n    acl(acl&&)            = default;\n    acl& operator=(acl&&) = default;\n\n    ~acl() noexcept;\n\n    /// The number of rules in this ACL.\n    size_type size() const { return _impl.size(); }\n\n    /// \\{\n    /// Get the rule at the given \\a idx.\n    const acl_rule& operator[](size_type idx) const { return _impl[idx]; }\n    acl_rule&       operator[](size_type idx)       { return _impl[idx]; }\n    /// \\}\n\n    /// \\{\n    /// Get the rule at the given \\a idx.\n    ///\n    /// \\throws std::out_of_range if the \\a idx is larger than \\ref size.\n    const acl_rule& at(size_type idx) const { return _impl.at(idx); }\n    acl_rule&       at(size_type idx)       { return _impl.at(idx); }\n    /// \\}\n\n    /// \\{\n    /// Get an iterator to the beginning of the rule list.\n    iterator begin()              { return _impl.begin(); }\n    const_iterator begin() const  { return _impl.begin(); }\n    const_iterator cbegin() const { return _impl.begin(); }\n    /// \\}\n\n    /// \\{\n    /// Get an iterator to the end of the rule list.\n    iterator end()              { return _impl.end(); }\n    const_iterator end() const  { return _impl.end(); }\n    const_iterator cend() const { return _impl.end(); }\n    /// \\}\n\n    /// Increase the reserved memory block so it can store at least \\a capacity rules without reallocating.\n    void reserve(size_type capacity) { _impl.reserve(capacity); }\n\n    /// Construct a rule emplace on the end of the list using \\a args.\n    ///\n    /// \\see push_back\n    template <typename... TArgs>\n    void emplace_back(TArgs&&... args)\n    {\n        _impl.emplace_back(std::forward<TArgs>(args)...);\n    }\n\n    /// \\{\n    /// Add the rule \\a x to the end of this list.\n    void push_back(acl_rule&& x)      { emplace_back(std::move(x)); }\n    void push_back(const acl_rule& x) { emplace_back(x); }\n    /// \\}\n\nprivate:\n    std::vector<acl_rule> _impl;\n};\n\n[[gnu::pure]] bool operator==(const acl& lhs, const acl& rhs);\n[[gnu::pure]] bool operator!=(const acl& lhs, const acl& rhs);\n\nstd::ostream& operator<<(std::ostream&, const acl&);\n\nstd::string to_string(const acl& self);\n\n/// Commonly-used ACLs.\nclass acls\n{\npublic:\n    /// This ACL gives the creators authentication id's all permissions.\n    static const acl& creator_all();\n\n    /// This is a completely open ACL. It is also the ACL used in operations like \\ref client::create if no ACL is\n    /// specified.\n    static const acl& open_unsafe();\n\n    /// This ACL gives the world the ability to read.\n    static const acl& read_unsafe();\n};\n\n/// \\}\n\n}\n\nnamespace std\n{\n\ntemplate <>\nclass hash<zk::acl_rule>\n{\npublic:\n    using argument_type = zk::acl_rule;\n    using result_type   = std::size_t;\n\n    result_type operator()(const argument_type& x) const\n    {\n        return zk::hash(x);\n    }\n};\n\n}\n"
  },
  {
    "path": "src/zk/acl_tests.cpp",
    "content": "#include <zk/tests/test.hpp>\n\n#include \"acl.hpp\"\n\nnamespace zk\n{\n\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n// permission                                                                                                         //\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\nGTEST_TEST(permission_tests, all)\n{\n    auto all = permission::read\n             | permission::write\n             | permission::create\n             | permission::erase\n             | permission::admin;\n    CHECK_EQ(permission::all, all);\n    CHECK_EQ(\"all\", to_string(all));\n}\n\nGTEST_TEST(permission_tests, stringification)\n{\n    CHECK_EQ(\"none\", to_string(permission::none));\n    CHECK_EQ(\"read|create\", to_string(permission::read | permission::create));\n    CHECK_EQ(\"write|admin\", to_string(permission::write | permission::admin));\n    CHECK_EQ(\"read|create|erase\", to_string(permission::read | permission::create | permission::erase));\n    CHECK_EQ(\"admin\", to_string(permission::admin));\n}\n\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n// acl_rule                                                                                                           //\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\nGTEST_TEST(acl_rule_tests, stringification)\n{\n    CHECK_EQ(\"(ip:80.23.0.0/16, read|write)\",\n             to_string(acl_rule(\"ip\", \"80.23.0.0/16\", permission::read | permission::write))\n            );\n}\n\nGTEST_TEST(acl_rule_tests, comparisons)\n{\n    acl_rule creator_all = { \"auth\", \"\", permission::all };\n    acl_rule world_open  = { \"world\", \"anyone\", permission::all };\n\n    CHECK_EQ(creator_all, creator_all);\n    CHECK_NE(creator_all, world_open);\n    CHECK_LT(creator_all, world_open);\n    CHECK_LE(creator_all, world_open);\n    CHECK_GT(world_open, creator_all);\n    CHECK_GE(world_open, creator_all);\n}\n\nGTEST_TEST(acl_rule_tests, hashing)\n{\n    acl_rule creator_all = { \"auth\", \"\", permission::all };\n    acl_rule world_open  = { \"world\", \"anyone\", permission::all };\n\n    CHECK_EQ(hash(creator_all), hash(creator_all));\n}\n\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n// acl                                                                                                                //\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\nGTEST_TEST(acl_tests, stringification)\n{\n    CHECK_EQ(\"[(auth, all)]\",          to_string(acls::creator_all()));\n    CHECK_EQ(\"[(world:anyone, all)]\",  to_string(acls::open_unsafe()));\n    CHECK_EQ(\"[(world:anyone, read)]\", to_string(acls::read_unsafe()));\n\n    CHECK_EQ(\"[(auth, read), (ip:50.40.30.0/24, all)]\",\n             to_string(acl({ { \"auth\", \"\", permission::read }, { \"ip\", \"50.40.30.0/24\", permission::all } }))\n            );\n}\n\n}\n"
  },
  {
    "path": "src/zk/buffer.hpp",
    "content": "/// \\file\n/// Controls the \\c buffer type.\n#pragma once\n\n#include <zk/config.hpp>\n\n#include <type_traits>\n\n/// \\addtogroup Client\n/// \\{\n\n// \\def ZKPP_BUFFER_USE_STD_STRING\n//  Set this to 1 to use \\c std::string as the backing type for zk::buffer.\n//\n#ifndef ZKPP_BUFFER_USE_STD_STRING\n#   define ZKPP_BUFFER_USE_STD_STRING 0\n#endif\n\n/// \\def ZKPP_BUFFER_USE_CUSTOM\n/// Set this to 1 to use a custom definitions for \\c buffer. If this is set, you must also set\n/// \\ref ZKPP_BUFFER_INCLUDE and \\ref ZKPP_BUFFER_TYPE.\n///\n/// \\def ZKPP_BUFFER_INCLUDE\n/// The header file to use to find the \\c buffer type. This value is set automatically if using built-in configuration\n/// options (such as \\ref ZKPP_BUFFER_USE_STD_VECTOR) and must be set manually if using \\ref ZKPP_BUFFER_USE_CUSTOM.\n///\n/// \\def ZKPP_BUFFER_TYPE\n/// The type name to use for the \\c buffer type. This value is set automatically if using built-in configuration\n/// options (such as \\ref ZKPP_BUFFER_USE_STD_VECTOR) and must be set manually if using \\ref ZKPP_BUFFER_USE_CUSTOM.\n#ifndef ZKPP_BUFFER_USE_CUSTOM\n#   define ZKPP_BUFFER_USE_CUSTOM 0\n#endif\n\n/// \\def ZKPP_BUFFER_USE_STD_VECTOR\n/// Set this to 1 to use \\c std::vector<char> for the implementation of \\ref zk::buffer. This is the default behavior.\n#ifndef ZKPP_BUFFER_USE_STD_VECTOR\n#   if ZKPP_BUFFER_USE_STD_STRING || ZKPP_BUFFER_USE_CUSTOM\n#       define ZKPP_BUFFER_USE_STD_VECTOR 0\n#   else\n#       define ZKPP_BUFFER_USE_STD_VECTOR 1\n#   endif\n#endif\n\n#if ZKPP_BUFFER_USE_STD_VECTOR\n#   define ZKPP_BUFFER_INCLUDE <vector>\n#   define ZKPP_BUFFER_TYPE std::vector<char>\n#elif ZKPP_BUFFER_USE_STD_STRING\n#   define ZKPP_BUFFER_INCLUDE <string>\n#   define ZKPP_BUFFER_TYPE std::string\n#elif ZKPP_BUFFER_USE_CUSTOM\n#   if !defined ZKPP_BUFFER_INCLUDE || !defined ZKPP_BUFFER_TYPE\n#       error \"When ZKPP_BUFFER_USE_CUSTOM is set, you must also define ZKPP_BUFFER_INCLUDE and ZKPP_BUFFER_TYPE\"\n#   endif\n#else\n#   error \"Unknown type to use for zk::buffer\"\n#endif\n\n/// \\}\n\n#include ZKPP_BUFFER_INCLUDE\n\nnamespace zk\n{\n\n/// \\addtogroup Client\n/// \\{\n\n/// The \\c buffer type. By default, this is an \\c std::vector<char>, but this can be altered by compile-time flags such\n/// as \\ref ZKPP_BUFFER_USE_CUSTOM. The requirements for a custom buffer are minimal -- the type must fit this criteria:\n///\n/// | expression            | type                      | description                                                  |\n/// |:----------------------|:--------------------------|:-------------------------------------------------------------|\n/// | `buffer::value_type`  | `char`                    | Buffers must be made of single-byte elements                 |\n/// | `buffer::size_type`   | `std::size_t`             |                                                              |\n/// | `buffer(ib, ie)`      | `buffer`                  | Constructs a buffer with the range [`ib`, `ie`)              |\n/// | `buffer(buffer&&)`    | `buffer`                  | Move constructible (must be `noexcept`).                     |\n/// | `operator=(buffer&&)` | `buffer&`                 | Move assignable (must be `noexcept`).                        |\n/// | `size()`              | `size_type`               | Get the length of the buffer                                 |\n/// | `data()`              | `const value_type*`       | Get a pointer to the beginning of the contents               |\nusing buffer = ZKPP_BUFFER_TYPE;\n\n// Check through static_assert:\nstatic_assert(sizeof(buffer::value_type) == 1U, \"buffer::value_type must be single-byte elements\");\nstatic_assert(std::is_same<std::size_t, buffer::size_type>::value, \"buffer::size_type must be std::size_t\");\nstatic_assert(std::is_constructible<buffer, ptr<const buffer::value_type>, ptr<const buffer::value_type>>::value,\n              \"buffer must be constructible with two pointers\"\n             );\nstatic_assert(std::is_move_constructible<buffer>::value, \"buffer must be move-constructible\");\nstatic_assert(std::is_nothrow_move_constructible<buffer>::value, \"buffer must be nothrow move-constructible\");\nstatic_assert(std::is_nothrow_move_assignable<buffer>::value, \"buffer must be nothrow move-assignable\");\nstatic_assert(std::is_same<decltype(std::declval<const buffer&>().size()), buffer::size_type>::value,\n              \"buffer::size() must return buffer::size_type\"\n             );\nstatic_assert(std::is_constructible<ptr<const buffer::value_type>,\n                                    decltype(std::declval<const buffer&>().data())\n                                   >::value,\n              \"buffer::data() must return ptr<const buffer::value_type>\"\n             );\n\n/// \\}\n\n}\n"
  },
  {
    "path": "src/zk/client.cpp",
    "content": "#include \"client.hpp\"\n#include \"acl.hpp\"\n#include \"connection.hpp\"\n#include \"multi.hpp\"\n#include \"exceptions.hpp\"\n\n#include <sstream>\n#include <ostream>\n\nnamespace zk\n{\n\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n// client                                                                                                             //\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\nclient::client(const connection_params& params) :\n        client(connection::connect(params))\n{ }\n\nclient::client(string_view conn_string) :\n        client(connection::connect(conn_string))\n{ }\n\nclient::client(std::shared_ptr<connection> conn) noexcept :\n        _conn(std::move(conn))\n{ }\n\nfuture<client> client::connect(string_view conn_string)\n{\n    return connect(connection_params::parse(conn_string));\n}\n\nfuture<client> client::connect(connection_params conn_params)\n{\n    try\n    {\n        auto conn = connection::connect(conn_params);\n        auto state_change_fut = conn->watch_state();\n        if (conn->state() == state::connected)\n        {\n            promise<client> p;\n            p.set_value(client(std::move(conn)));\n            return p.get_future();\n        }\n        else\n        {\n            // TODO: Test if future::then can be relied on and use that instead of std::async\n            return zk::async\n                   (\n                       zk::launch::async,\n                       [state_change_fut = std::move(state_change_fut), conn = std::move(conn)] () mutable -> client\n                       {\n                         state s(state_change_fut.get());\n                         if (s == state::connected)\n                            return client(conn);\n                         else\n                            zk::throw_exception(std::runtime_error(std::string(\"Unexpected state: \") + to_string(s)));\n                       }\n                   );\n        }\n    }\n    catch (...)\n    {\n        promise<client> p;\n        p.set_exception(zk::current_exception());\n        return p.get_future();\n    }\n}\n\nclient::~client() noexcept = default;\n\nvoid client::close()\n{\n    _conn->close();\n}\n\nfuture<get_result> client::get(string_view path) const\n{\n    return _conn->get(path);\n}\n\nfuture<watch_result> client::watch(string_view path) const\n{\n    return _conn->watch(path);\n}\n\nfuture<get_children_result> client::get_children(string_view path) const\n{\n    return _conn->get_children(path);\n}\n\nfuture<watch_children_result> client::watch_children(string_view path) const\n{\n    return _conn->watch_children(path);\n}\n\nfuture<exists_result> client::exists(string_view path) const\n{\n    return _conn->exists(path);\n}\n\nfuture<watch_exists_result> client::watch_exists(string_view path) const\n{\n    return _conn->watch_exists(path);\n}\n\nfuture<create_result> client::create(string_view   path,\n                                     const buffer& data,\n                                     const acl&    rules,\n                                     create_mode   mode\n                                    )\n{\n    return _conn->create(path, data, rules, mode);\n}\n\nfuture<create_result> client::create(string_view   path,\n                                     const buffer& data,\n                                     create_mode   mode\n                                    )\n{\n    return create(path, data, acls::open_unsafe(), mode);\n}\n\nfuture<set_result> client::set(string_view path, const buffer& data, version check)\n{\n    return _conn->set(path, data, check);\n}\n\nfuture<get_acl_result> client::get_acl(string_view path) const\n{\n    return _conn->get_acl(path);\n}\n\nfuture<void> client::set_acl(string_view path, const acl& rules, acl_version check)\n{\n    return _conn->set_acl(path, rules, check);\n}\n\nfuture<void> client::erase(string_view path, version check)\n{\n    return _conn->erase(path, check);\n}\n\nfuture<void> client::load_fence() const\n{\n    return _conn->load_fence();\n}\n\nfuture<multi_result> client::commit(multi_op txn)\n{\n    return _conn->commit(std::move(txn));\n}\n\n}\n"
  },
  {
    "path": "src/zk/client.hpp",
    "content": "#pragma once\n\n#include <zk/config.hpp>\n\n#include <memory>\n#include <utility>\n\n#include \"buffer.hpp\"\n#include \"forwards.hpp\"\n#include \"future.hpp\"\n#include \"optional.hpp\"\n#include \"string_view.hpp\"\n#include \"results.hpp\"\n#include \"types.hpp\"\n\nnamespace zk\n{\n\n/// \\defgroup Client\n/// Interacting with ZooKeeper as a \\ref client.\n/// \\{\n\n/// A ZooKeeper client connection. This is the primary class for interacting with the ZooKeeper cluster. The best way to\n/// create a client is with the static \\ref connect function.\nclass client final\n{\npublic:\n    /// Create a client connected to the cluster specified by \\a params.\n    explicit client(const connection_params& params);\n\n    /// Create a client connected to the cluster specified by \\a conn_string.\n    ///\n    /// \\param conn_string A ZooKeeper \\ref ConnectionStrings \"connection string\".\n    explicit client(string_view conn_string);\n\n    /// Create a client connected with \\a conn.\n    explicit client(std::shared_ptr<connection> conn) noexcept;\n\n    /// \\{\n    /// Create a client connected to the cluster specified by \\c conn_string.\n    ///\n    /// \\param conn_string A ZooKeeper \\ref ConnectionStrings \"connection string\".\n    /// \\returns A future which will be filled when the conneciton is established. The future will be filled in error if\n    ///  the client will never be able to connect to the cluster (for example: a bad connection string).\n    static future<client> connect(string_view       conn_string);\n    static future<client> connect(connection_params conn_params);\n    /// \\}\n\n    client(const client&) noexcept = default;\n    client(client&&) noexcept = default;\n\n    client& operator=(const client&) noexcept = default;\n    client& operator=(client&&) noexcept = default;\n\n    ~client() noexcept;\n\n    /// Close the underlying \\ref connection. All outstanding operations will be cancelled and all watches will be\n    /// delivered with \\ref closed. You usually do not need to call this operation, as the destructor handles this\n    /// automatically.\n    void close();\n\n    /// Return the data and the \\ref stat of the entry of the given \\a path.\n    ///\n    /// \\throws no_entry If no entry exists at the given \\a path, the future will be delievered with \\ref no_entry.\n    future<get_result> get(string_view path) const;\n\n    /// Similar to \\ref get, but if the call is successful (no error is returned), a watch will be left on the entry\n    /// with the given \\a path. The watch will be triggered by a successful operation that sets data or erases the\n    /// entry.\n    ///\n    /// \\throws no_entry If no entry exists at the given \\a path, the future will be delievered with \\ref no_entry. To\n    ///  watch for the creation of an entry, use \\ref watch_exists.\n    future<watch_result> watch(string_view path) const;\n\n    /// Return the list of the children of the entry of the given \\a path. The returned values are not prefixed with the\n    /// provided \\a path; i.e. if the database contains \\c \"/path/a\" and \\c \"/path/b\", the result of \\c get_children for\n    /// \\c \"/path\" will be `[\"a\", \"b\"]`. The list of children returned is not sorted and no guarantee is provided as to\n    /// its natural or lexical order.\n    ///\n    /// \\throws no_entry If no entry exists at the given \\a path, the future will be delievered with \\ref no_entry.\n    future<get_children_result> get_children(string_view path) const;\n\n    /// Similar to \\ref get_children, but if the call is successful (no error is returned), a watch will be left on the\n    /// entry with the given \\a path. The watch will be triggered by a successful operation that erases the entry at the\n    /// given \\a path or creates or erases a child immediately under the path (it is not recursive).\n    future<watch_children_result> watch_children(string_view path) const;\n\n    /// Return the \\ref stat of the entry of the given \\a path or \\c nullopt if it does not exist.\n    future<exists_result> exists(string_view path) const;\n\n    /// Similar to \\ref watch, but if the call is successful (no error is returned), a watch will be left on the entry\n    /// with the given \\a path. The watch will be triggered by a successful operation that creates the entry, erases the\n    /// entry, or sets the data on the entry.\n    future<watch_exists_result> watch_exists(string_view path) const;\n\n    /// \\{\n    /// Create an entry at the given \\a path.\n    ///\n    /// This operation, if successful, will trigger all the watches left on the entry of the given path by \\ref watch\n    /// API calls, and the watches left on the parent entry by \\ref watch_children API calls.\n    ///\n    /// \\param path The path or path pattern (if using \\ref create_mode::sequential) to create.\n    /// \\param data The data to create for the entry.\n    /// \\param mode Specifies the behavior of the created entry (see \\ref create_mode for more information).\n    /// \\param rules The ACL for the created entry. If unspecified, it is equivalent to providing\n    ///  \\ref acls::open_unsafe.\n    /// \\returns A future which will be filled with the name of the created entry and its \\ref stat.\n    ///\n    /// \\throws entry_exists If an entry with the same actual \\a path already exists in the ZooKeeper, the future will\n    ///  be delivered with \\ref entry_exists. Note that since a different actual path is used for each invocation of\n    ///  creating sequential entry with the same \\a path argument, the call should never error in this manner.\n    /// \\throws no_entry If the parent of the given \\a path does not exist, the future will be delievered with\n    ///  \\ref no_entry.\n    /// \\throws no_children_for_ephemerals An ephemeral entry cannot have children. If the parent entry of the given\n    ///  \\a path is ephemeral, the future will be delivered with \\c no_children_for_ephemerals.\n    /// \\throws invalid_acl If the \\a acl is invalid or empty, the future will be delivered with \\ref invalid_acl.\n    /// \\throws invalid_arguments The maximum allowable size of the data array is 1 MiB (1,048,576 bytes). If \\a data\n    ///  is larger than this the future will be delivered with \\ref invalid_arguments.\n    future<create_result> create(string_view   path,\n                                 const buffer& data,\n                                 const acl&    rules,\n                                 create_mode   mode = create_mode::normal\n                                );\n    future<create_result> create(string_view   path,\n                                 const buffer& data,\n                                 create_mode   mode = create_mode::normal\n                                );\n    /// \\}\n\n    /// Set the data for the entry of the given \\a path if such an entry exists and the given version matches the\n    /// version of the entry (if the given version is \\ref version::any, there is no version check). This operation, if\n    /// successful, will trigger all the watches on the entry of the given \\c path left by \\ref watch calls.\n    ///\n    /// \\throws no_entry If no entry exists at the given \\a path, the future will be delievered with \\ref no_entry.\n    /// \\throws version_mismatch If the given version \\a check does not match the entry's version, the future will be\n    ///  delivered with \\ref version_mismatch.\n    /// \\throws invalid_arguments The maximum allowable size of the data array is 1 MiB (1,048,576 bytes). If \\a data\n    ///  is larger than this the future will be delivered with \\ref invalid_arguments.\n    future<set_result> set(string_view path, const buffer& data, version check = version::any());\n\n    /// Return the ACL and \\ref stat of the entry of the given path.\n    ///\n    /// \\throws no_entry If no entry exists at the given \\a path, the future will be delievered with \\ref no_entry.\n    future<get_acl_result> get_acl(string_view path) const;\n\n    /// Set the ACL for the entry of the given \\a path if such an entry exists and the given version \\a check matches\n    /// the version of the entry.\n    ///\n    /// \\param check If specified, check that the ACL version matches. Keep in mind this is the \\c acl_version, not the\n    ///  data \\ref version -- there is no way to have this operation fail on changes to \\ref stat::data_version without\n    ///  the use of a \\ref multi_op.\n    ///\n    /// \\throws no_entry If no entry exists at the given \\a path, the future will be delievered with \\ref no_entry.\n    /// \\throws version_mismatch If the given version \\a check does not match the entry's version, the future will be\n    ///  delivered with \\ref version_mismatch.\n    future<void> set_acl(string_view path, const acl& rules, acl_version check = acl_version::any());\n\n    /// Erase the entry at the given \\a path. The call will succeed if such an entry exists, and the given version\n    /// \\a check matches the entry's version (if the given version is \\ref version::any, it matches any entry's\n    /// versions). This operation, if successful, will trigger all the watches on the entry of the given \\a path left by\n    /// \\ref watch API calls, watches left by \\ref watch_exists API calls, and the watches on the parent entry left by\n    /// \\ref watch_children API calls.\n    ///\n    /// \\throws no_entry If no entry exists at the given \\a path, the future will be delievered with \\ref no_entry.\n    /// \\throws version_mismatch If the given version \\a check does not match the entry's version, the future will be\n    ///  delivered with \\ref version_mismatch.\n    /// \\throws not_empty You are only allowed to erase entries with no children. If the entry has children, the future\n    ///  will be delievered with \\ref not_empty.\n    future<void> erase(string_view path, version check = version::any());\n\n    /// Ensure that all subsequent reads observe the data at the transaction on the server at or past real-time \\e now.\n    /// If your application communicates only through reads and writes of ZooKeeper, this operation is never needed.\n    /// However, if your application communicates a change in ZooKeeper state through means outside of ZooKeeper (called\n    /// a \"hidden channel\" in ZooKeeper vernacular), then it is possible for a receiver to attempt to react to a change\n    /// before it can observe it through ZooKeeper state.\n    ///\n    /// \\warning\n    /// The internal pipeline for this operation is not the same as modifying operations (\\ref set, \\ref create, etc.).\n    /// In cases of leader failure, there is a chance that the leader does not have support from the quorum, as it has\n    /// switched to a new leader. While this is rare, it is still \\e possible that not all updates have been processed.\n    ///\n    /// \\note\n    /// Other APIs call this operation \\c sync and allow you to provide a \\c path parameter. There are a few issues with\n    /// this. First: that name conflicts with the commonly-used POSIX \\c sync command, leading to confusion that data in\n    /// ZooKeeper does not have integrity. Secondly, this operation has more in common with \\c std::atomic_thread_fence\n    /// or the x86 \\c lfence instruction than \\c sync (on the server, \"flush\" is an appropriate term -- just like fence\n    /// implementations in CPUs). Finally, the \\c path parameter is ignored by the server -- all future fetches are\n    /// fenced, no matter what path is specified. In the future, ZooKeeper might support partitioning, in which case the\n    /// \\c path parameter might become relevant.\n    ///\n    /// It is often not necessary to wait for the fence future to be returned, as future reads will be synced without\n    /// waiting. However, there is no guarantee on the ordering of the read if the future returned from \\c load_fence\n    /// is completed in error.\n    ///\n    /// \\code\n    /// auto fence_future = client.load_fence();\n    /// // data_future will be completed with the load_fence, even though we haven't waited to complete\n    /// auto data_future = client.get(\"/some/path\");\n    ///\n    /// // Useful to use when_all to concat and error check (C++ Extensions for Concurrency, ISO/IEC TS 19571:2016)\n    /// auto guaranteed_future = std::when_all(std::move(fence_future), std::move(data_future));\n    /// \\endcode\n    future<void> load_fence() const;\n\n    /// Commit the transaction specified by \\a txn. The operations are performed atomically: They will either all\n    /// succeed or all fail.\n    ///\n    /// \\throws transaction_failed If the transaction does not complete with an error in the transaction itself (any\n    ///  \\ref error_code that fits \\ref is_api_error), the future will be delivered with \\ref transaction_failed. Check\n    ///  the thrown \\ref transaction_failed::underlying_cause for more information.\n    /// \\throws system_error For the same reasons any other operation might fail, the future will be delivered with a\n    ///  specific \\ref system_error.\n    future<multi_result> commit(multi_op txn);\n\nprivate:\n    std::shared_ptr<connection> _conn;\n};\n\n/// \\}\n\n}\n"
  },
  {
    "path": "src/zk/client_tests.cpp",
    "content": "#include <zk/server/server_tests.hpp>\n\n#include <algorithm>\n#include <chrono>\n#include <cstring>\n#include <iostream>\n#include <thread>\n\n#include \"client.hpp\"\n#include \"error.hpp\"\n#include \"multi.hpp\"\n#include \"string_view.hpp\"\n\nnamespace zk\n{\n\nstatic buffer buffer_from(string_view str)\n{\n    return buffer(str.data(), str.data() + str.size());\n}\n\nclass client_tests :\n        public server::single_server_fixture\n{ };\n\nGTEST_TEST_F(client_tests, get_root)\n{\n    client c = get_connected_client();\n    auto res = c.get(\"/\").get();\n    std::cerr << res << std::endl;\n    c.close();\n}\n\nGTEST_TEST_F(client_tests, exists)\n{\n    client c = get_connected_client();\n    CHECK_TRUE(c.exists(\"/\").get());\n    CHECK_FALSE(c.exists(\"/some/bogus/path\").get());\n}\n\nGTEST_TEST_F(client_tests, create)\n{\n    client c = get_connected_client();\n    const char local_buf[10] = { 0 };\n    auto f_create = c.create(\"/test-node\", buffer(local_buf, local_buf + sizeof local_buf));\n    auto name = f_create.get().name();\n    CHECK_EQ(\"/test-node\", name);\n}\n\nGTEST_TEST_F(client_tests, create_seq_and_set)\n{\n    client c = get_connected_client();\n    auto f_create = c.create(\"/test-node-\", buffer_from(\"Hello!\"), create_mode::sequential);\n    auto name = f_create.get().name();\n    auto orig_stat = c.get(name).get().stat();\n    auto expected_version = orig_stat.data_version;\n    ++expected_version;\n\n    c.set(name, buffer_from(\"WORLD\")).get();\n    auto contents = c.get(name).get();\n    CHECK_EQ(contents.stat().data_version, expected_version);\n    CHECK_TRUE(contents.data() == buffer_from(\"WORLD\"));\n}\n\nGTEST_TEST_F(client_tests, create_seq_and_erase)\n{\n    client c = get_connected_client();\n    auto f_create = c.create(\"/test-node-\", buffer_from(\"Hello!\"), create_mode::sequential);\n    auto name = f_create.get().name();\n    auto orig_stat = c.get(name).get().stat();\n    c.erase(name, orig_stat.data_version).get();\n    CHECK_THROWS(no_entry)\n    {\n        c.get(name).get();\n    };\n}\n\nGTEST_TEST_F(client_tests, create_seq_and_get_children)\n{\n    client c = get_connected_client();\n    auto f_create = c.create(\"/test-node-\", buffer_from(\"Hello!\"), create_mode::sequential);\n    auto name = f_create.get().name();\n    auto orig_stat = c.get(name).get().stat();\n\n    c.create(name + \"/a\", buffer()).get();\n    c.create(name + \"/b\", buffer()).get();\n    c.create(name + \"/c\", buffer()).get();\n\n    auto result = c.get_children(name).get();\n    CHECK_EQ(orig_stat.data_version,  result.parent_stat().data_version);\n    CHECK_LT(orig_stat.child_version, result.parent_stat().child_version);\n    std::vector<std::string> expected_children = { \"a\", \"b\", \"c\" };\n    std::sort(result.children().begin(), result.children().end());\n    CHECK_TRUE(expected_children == result.children());\n}\n\nGTEST_TEST_F(client_tests, acl)\n{\n    client c = get_connected_client();\n    auto name = c.create(\"/test-node-\", buffer_from(\"Hello!\"), create_mode::sequential).get().name();\n\n    // set the data of the node a few times to make sure the data_version is different value from acl_version\n    for (std::size_t changes = 0; changes < 5; ++changes)\n        c.set(name, buffer_from(\"data change\")).get();\n\n    auto orig_result = c.get_acl(name).get();\n    CHECK_EQ(acls::open_unsafe(), orig_result.acl());\n    std::cerr << \"HEY: \" << orig_result << std::endl;\n\n    c.set_acl(name, acls::read_unsafe(), orig_result.stat().acl_version).get();\n    auto new_result = c.get_acl(name).get();\n    CHECK_EQ(acls::read_unsafe(), new_result.acl());\n}\n\nGTEST_TEST_F(client_tests, watch_change)\n{\n    client c = get_connected_client();\n    auto name = c.create(\"/test-node-\", buffer_from(\"Hello!\"), create_mode::sequential).get().name();\n\n    auto watch = c.watch(name).get();\n    CHECK_TRUE(watch.initial().data() == buffer_from(\"Hello!\"));\n\n    c.set(name, buffer_from(\"world\")); // don't wait -- the watch won't trigger until the operation completes\n    auto ev = watch.next().get();\n    CHECK_EQ(ev.type(), event_type::changed);\n    CHECK_EQ(ev.state(), state::connected);\n}\n\nGTEST_TEST_F(client_tests, watch_children)\n{\n    client c = get_connected_client();\n    auto root_name = c.create(\"/test-node-\", buffer_from(\"Hello!\"), create_mode::sequential).get().name();\n\n    c.commit({\n        op::create(root_name + \"/a\", buffer_from(\"a\")),\n        op::create(root_name + \"/b\", buffer_from(\"b\")),\n        op::create(root_name + \"/c\", buffer_from(\"c\")),\n        op::create(root_name + \"/d\", buffer_from(\"d\")),\n    }).get();\n\n    auto watch_child_creation = c.watch_children(root_name).get();\n    CHECK_EQ(4U, watch_child_creation.initial().children().size());\n\n    c.create(root_name + \"/e\", buffer_from(\"e\"));\n    auto ev = watch_child_creation.next().get();\n    CHECK_EQ(ev.type(), event_type::child);\n    CHECK_EQ(ev.state(), state::connected);\n\n    auto watch_child_erase = c.watch_children(root_name).get();\n    CHECK_EQ(5U, watch_child_erase.initial().children().size());\n\n    c.erase(root_name + \"/a\");\n    auto ev2 = watch_child_erase.next().get();\n    CHECK_EQ(ev2.type(), event_type::child);\n    CHECK_EQ(ev2.state(), state::connected);\n}\n\nGTEST_TEST_F(client_tests, watch_exists)\n{\n    client c = get_connected_client();\n    auto root_name = c.create(\"/test-node-\", buffer_from(\"Hello!\"), create_mode::sequential).get().name();\n\n    auto watch_creation = c.watch_exists(root_name + \"/sub\").get();\n    CHECK_FALSE(watch_creation.initial());\n\n    c.create(root_name + \"/sub\", buffer_from(\"Blah\"));\n    auto ev = watch_creation.next().get();\n    CHECK_EQ(ev.type(), event_type::created);\n    CHECK_EQ(ev.state(), state::connected);\n\n    auto watch_erase = c.watch_exists(root_name + \"/sub\").get();\n    CHECK_TRUE(watch_erase.initial());\n\n    c.erase(root_name + \"/sub\");\n    auto ev2 = watch_erase.next().get();\n    CHECK_EQ(ev2.type(), event_type::erased);\n    CHECK_EQ(ev2.state(), state::connected);\n}\n\nGTEST_TEST_F(client_tests, load_fence)\n{\n    client c = get_connected_client();\n    // There does not appear to be a good way to actually test this -- so just make sure we don't segfault\n    c.load_fence().get();\n}\n\nGTEST_TEST_F(client_tests, watch_close)\n{\n    client c   = get_connected_client();\n    auto watch = c.watch(\"/\").get();\n\n    c.close();\n\n    // watch should be triggered with session closed\n    auto ev = watch.next().get();\n    CHECK_EQ(ev.type(), event_type::session);\n    CHECK_EQ(ev.state(), state::closed);\n}\n\nclass stopping_client_tests :\n        public server::server_fixture\n{ };\n\nGTEST_TEST_F(stopping_client_tests, watch_server_stop)\n{\n    client c     = get_connected_client();\n    auto   watch = c.watch(\"/\").get();\n\n    this->stop_server(true);\n\n    auto ev = watch.next().get();\n    CHECK_EQ(ev.type(), event_type::session);\n}\n\n}\n"
  },
  {
    "path": "src/zk/config.hpp",
    "content": "#pragma once\n\n/// \\addtogroup Client\n/// \\{\n\n/// \\def ZKPP_USER_CONFIG\n/// A user-defined configuration file to be included before all other ZooKeeper C++ content.\n#ifdef ZKPP_USER_CONFIG\n#   include ZKPP_USER_CONFIG\n#endif\n\n#define ZKPP_VERSION_MAJOR 0\n#define ZKPP_VERSION_MINOR 2\n#define ZKPP_VERSION_PATCH 3\n\n/// \\def ZKPP_DEBUG\n/// Was ZooKeeper C++ compiled in debug mode? This value must be the same between when the SO was built and when you are\n/// compiling. In general, this is not useful outside of library maintainers.\n///\n/// \\warning\n/// Keep in mind this value is \\e always defined. Use `#if ZKPP_DEBUG`, \\e not `#ifdef ZKPP_DEBUG`.\n#ifndef ZKPP_DEBUG\n#   define ZKPP_DEBUG 0\n#endif\n\n/// \\}\n\nnamespace zk\n{\n\n/// \\addtogroup Client\n/// \\{\n\n/// A simple, unowned pointer. It operates exactly like using \\c *, but removes the question of \\c * associativity and\n/// is easier to read when \\c const qualifiers are involved.\ntemplate <typename T>\nusing ptr = T*;\n\n/// \\}\n\n}\n"
  },
  {
    "path": "src/zk/connection.cpp",
    "content": "#include \"connection.hpp\"\n#include \"connection_zk.hpp\"\n#include \"error.hpp\"\n#include \"types.hpp\"\n#include \"exceptions.hpp\"\n\n#include <algorithm>\n#include <regex>\n#include <ostream>\n#include <sstream>\n#include <stdexcept>\n#include <unordered_set>\n\n#include <zookeeper/zookeeper.h>\n\nnamespace zk\n{\n\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n// connection                                                                                                         //\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\nconnection::~connection() noexcept\n{ }\n\nstd::shared_ptr<connection> connection::connect(const connection_params& params)\n{\n    return std::make_shared<connection_zk>(params);\n}\n\nstd::shared_ptr<connection> connection::connect(string_view conn_string)\n{\n    return connect(connection_params::parse(conn_string));\n}\n\nfuture<zk::state> connection::watch_state()\n{\n    std::unique_lock<std::mutex> ax(_state_change_promises_protect);\n    _state_change_promises.emplace_back();\n    return _state_change_promises.rbegin()->get_future();\n}\n\nvoid connection::on_session_event(zk::state new_state)\n{\n    std::unique_lock<std::mutex> ax(_state_change_promises_protect);\n    auto l_state_change_promises = std::move(_state_change_promises);\n    ax.unlock();\n\n    auto ex = new_state == zk::state::expired_session       ? get_exception_ptr_of(error_code::session_expired)\n            : new_state == zk::state::authentication_failed ? get_exception_ptr_of(error_code::authentication_failed)\n            :                                                 zk::exception_ptr();\n\n    for (auto& p : l_state_change_promises)\n    {\n        if (ex)\n            p.set_exception(ex);\n        else\n            p.set_value(new_state);\n    }\n}\n\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n// connection_params                                                                                                  //\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\nconnection_params::connection_params() noexcept :\n        _connection_schema(\"zk\"),\n        _hosts({}),\n        _chroot(\"/\"),\n        _randomize_hosts(true),\n        _read_only(false),\n        _timeout(default_timeout)\n{ }\n\nconnection_params::~connection_params() noexcept\n{ }\n\ntemplate <typename TMatch>\nstatic string_view sv_from_match(const TMatch& src)\n{\n    return string_view(src.first, std::distance(src.first, src.second));\n}\n\ntemplate <typename FAction>\nvoid split_each_substr(string_view src, char delim, const FAction& action)\n{\n    while (!src.empty())\n    {\n        // if next_div is src.end, the logic still works\n        auto next_div = std::find(src.begin(), src.end(), delim);\n        action(string_view(src.data(), std::distance(src.begin(), next_div)));\n        src.remove_prefix(std::distance(src.begin(), next_div));\n        if (!src.empty())\n            src.remove_prefix(1U);\n    }\n}\n\nstatic connection_params::host_list extract_host_list(string_view src)\n{\n    connection_params::host_list out;\n    out.reserve(std::count(src.begin(), src.end(), ','));\n    split_each_substr(src, ',', [&] (string_view sub) { out.emplace_back(std::string(sub)); });\n    return out;\n}\n\nstatic bool extract_bool(string_view key, string_view val)\n{\n    if (val.empty())\n        zk::throw_exception(std::invalid_argument(std::string(\"Key \") + std::string(key) + \" has blank value\"));\n\n    switch (val[0])\n    {\n    case '1':\n    case 't':\n    case 'T':\n        return true;\n    case '0':\n    case 'f':\n    case 'F':\n        return false;\n    default:\n        zk::throw_exception(std::invalid_argument(std::string(\"Invalid value for \") + std::string(key) + std::string(\" \\\"\")\n                                    + std::string(val) + \"\\\" -- expected a boolean\"\n                                    ));\n    }\n}\n\nstatic std::chrono::milliseconds extract_millis(string_view key, string_view val)\n{\n    if (val.empty())\n        zk::throw_exception(std::invalid_argument(std::string(\"Key \") + std::string(key) + \" has blank value\"));\n\n    if (val[0] == 'P')\n    {\n        zk::throw_exception(std::invalid_argument(\"ISO 8601 duration is not supported (yet).\"));\n    }\n    else\n    {\n        double seconds = std::stod(std::string(val));\n        return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::duration<double>(seconds));\n    }\n}\n\nstatic void extract_advanced_options(string_view src, connection_params& out)\n{\n    if (src.empty() || src.size() == 1U)\n        return;\n    else\n        src.remove_prefix(1U);\n\n    std::string invalid_keys_msg;\n    auto invalid_key = [&] (string_view key)\n                       {\n                           if (invalid_keys_msg.empty())\n                               invalid_keys_msg = \"Invalid key in querystring: \";\n                           else\n                               invalid_keys_msg += \", \";\n\n                           invalid_keys_msg += std::string(key);\n                       };\n\n    split_each_substr(src, '&', [&] (string_view qp_part)\n    {\n        auto eq_it = std::find(qp_part.begin(), qp_part.end(), '=');\n        if (eq_it == qp_part.end())\n            zk::throw_exception(std::invalid_argument(\"Invalid connection string -- query string must be specified as \"\n                                        \"\\\"key1=value1&key2=value2...\\\"\"\n                                       ));\n\n        auto key = qp_part.substr(0, std::distance(qp_part.begin(), eq_it));\n        auto val = qp_part.substr(std::distance(qp_part.begin(), eq_it) + 1);\n\n        if (key == \"randomize_hosts\")\n            out.randomize_hosts() = extract_bool(key, val);\n        else if (key == \"read_only\")\n            out.read_only() = extract_bool(key, val);\n        else if (key == \"timeout\")\n            out.timeout() = extract_millis(key, val);\n        else\n            invalid_key(key);\n    });\n\n    if (!invalid_keys_msg.empty())\n        zk::throw_exception(std::invalid_argument(std::move(invalid_keys_msg)));\n}\n\nconnection_params connection_params::parse(string_view conn_string)\n{\n    static const std::regex expr(R\"(([^:]+)://([^/]+)((/[^\\?]*)(\\?.*)?)?)\",\n                                 std::regex_constants::ECMAScript | std::regex_constants::optimize\n                                );\n    constexpr auto schema_idx      = 1U;\n    constexpr auto hostaddr_idx    = 2U;\n    constexpr auto path_idx        = 4U;\n    constexpr auto querystring_idx = 5U;\n\n    std::cmatch match;\n    if (!std::regex_match(conn_string.begin(), conn_string.end(), match, expr))\n        zk::throw_exception(std::invalid_argument(std::string(\"Invalid connection string (\") + std::string(conn_string)\n                                    + \" -- format is \\\"schema://[auth@]${host_addrs}/[path][?options]\\\"\"\n                                    ));\n\n    connection_params out;\n    out.connection_schema() = match[schema_idx].str();\n    out.hosts()             = extract_host_list(sv_from_match(match[hostaddr_idx]));\n    out.chroot()            = match[path_idx].str();\n    if (out.chroot().empty())\n        out.chroot() = \"/\";\n\n    extract_advanced_options(sv_from_match(match[querystring_idx]), out);\n\n    return out;\n}\n\nbool operator==(const connection_params& lhs, const connection_params& rhs)\n{\n    return lhs.connection_schema() == rhs.connection_schema()\n        && lhs.hosts()             == rhs.hosts()\n        && lhs.chroot()            == rhs.chroot()\n        && lhs.randomize_hosts()   == rhs.randomize_hosts()\n        && lhs.read_only()         == rhs.read_only()\n        && lhs.timeout()           == rhs.timeout();\n}\n\nbool operator!=(const connection_params& lhs, const connection_params& rhs)\n{\n    return !(lhs == rhs);\n}\n\nstd::ostream& operator<<(std::ostream& os, const connection_params& x)\n{\n    os << x.connection_schema() << \"://\";\n    bool first = true;\n    for (const auto& host : x.hosts())\n    {\n        if (first)\n            first = false;\n        else\n            os << ',';\n\n        os << host;\n    }\n    os << x.chroot();\n\n    first = true;\n    auto query_string = [&] (ptr<const char> key, const auto& val)\n                        {\n                            if (first)\n                            {\n                                first = false;\n                                os << '?';\n                            }\n                            else\n                            {\n                                os << '&';\n                            }\n\n                            os << key << '=' << val;\n                        };\n    if (!x.randomize_hosts())\n        query_string(\"randomize_hosts\", \"false\");\n    if (x.read_only())\n        query_string(\"read_only\", \"true\");\n    if (x.timeout() != connection_params::default_timeout)\n        query_string(\"timeout\", std::chrono::duration<double>(x.timeout()).count());\n    return os;\n}\n\nstd::string to_string(const connection_params& x)\n{\n    std::ostringstream os;\n    os << x;\n    return os.str();\n}\n\n}\n"
  },
  {
    "path": "src/zk/connection.hpp",
    "content": "#pragma once\n\n#include <zk/config.hpp>\n\n#include <chrono>\n#include <iosfwd>\n#include <memory>\n#include <mutex>\n#include <string>\n#include <vector>\n\n#include \"buffer.hpp\"\n#include \"forwards.hpp\"\n#include \"future.hpp\"\n#include \"string_view.hpp\"\n\nnamespace zk\n{\n\n/// \\addtogroup Client\n/// \\{\n\n/// An actual connection to the server. The majority of methods have the same signature and meaning as \\ref client.\n///\n/// \\see connection_zk\nclass connection\n{\npublic:\n    static std::shared_ptr<connection> connect(const connection_params&);\n\n    static std::shared_ptr<connection> connect(string_view conn_string);\n\n    virtual ~connection() noexcept;\n\n    virtual void close() = 0;\n\n    virtual future<get_result> get(string_view path) = 0;\n\n    virtual future<watch_result> watch(string_view path) = 0;\n\n    virtual future<get_children_result> get_children(string_view path) = 0;\n\n    virtual future<watch_children_result> watch_children(string_view path) = 0;\n\n    virtual future<exists_result> exists(string_view path) = 0;\n\n    virtual future<watch_exists_result> watch_exists(string_view path) = 0;\n\n    virtual future<create_result> create(string_view   path,\n                                         const buffer& data,\n                                         const acl&    rules,\n                                         create_mode   mode\n                                        ) = 0;\n\n    virtual future<set_result> set(string_view path, const buffer& data, version check) = 0;\n\n    virtual future<void> erase(string_view path, version check) = 0;\n\n    virtual future<get_acl_result> get_acl(string_view path) const = 0;\n\n    virtual future<void> set_acl(string_view path, const acl& rules, acl_version check) = 0;\n\n    virtual future<multi_result> commit(multi_op&& txn) = 0;\n\n    virtual future<void> load_fence() = 0;\n\n    virtual zk::state state() const = 0;\n\n    /// Watch for a state change.\n    virtual future<zk::state> watch_state();\n\nprotected:\n    /// Call this from derived classes when a session event happens. This triggers the delivery of all promises of state\n    /// changes (issued through \\ref watch_state).\n    virtual void on_session_event(zk::state new_state);\n\nprivate:\n    mutable std::mutex              _state_change_promises_protect;\n    std::vector<promise<zk::state>> _state_change_promises;\n};\n\n/// Used to specify parameters for a \\c connection. This can either be created manually or through a\n/// \\ref ConnectionStrings \"connection string\".\nclass connection_params final\n{\npublic:\n    using host_list = std::vector<std::string>;\n\npublic:\n    static constexpr std::chrono::milliseconds default_timeout = std::chrono::seconds(10);\n\npublic:\n    /// Create an instance with default values.\n    connection_params() noexcept;\n\n    ~connection_params() noexcept;\n\n    /// Create an instance from a connection string.\n    ///\n    /// \\anchor ConnectionStrings\n    /// \\par Connection Strings\n    /// Connection strings follow the standard format for URLs (`schema://host_address/path?querystring`). The `schema`\n    /// is the type of connection (usually `\"zk\"`), `auth` is potential authentication, the `host_address` is the list\n    /// of servers, the `path` is the chroot to use for this connection and the `querystring` allows for configuring\n    /// advanced options. For example: `\"zk://server-a:2181,server-b:2181,server-c:2181/?timeout=5\"`.\n    ///\n    /// \\par\n    /// - \\e schema: \\ref connection_params::connection_schema\n    /// - \\e host_address: comma-separated list of \\ref connection_params::hosts\n    /// - \\e path: \\ref connection_params::chroot\n    /// - \\e querystring: Allows for an arbitrary amount of optional parameters to be specified. These are specified\n    ///   with the conventional HTTP-style (e.g. `\"zk://localhost/?timeout=10&read_only=true\"` specifies a timeout of 10\n    ///   seconds and sets a read-only client. Boolean values can be specified with \\c true, \\c t, or \\c 1 for \\c true\n    ///   or \\c false, \\c f, or \\c 0 for \\c false. It is important to note that, unlike regular HTTP URLs, query\n    ///   parameters which are not understood will result in an error.\n    ///   - `randomize_hosts`: \\ref connection_params::randomize_hosts\n    ///   - `read_only`: \\ref connection_params::read_only\n    ///   - `timeout`: \\ref connection_params::timeout\n    ///\n    /// \\throws std::invalid_argument if the string is malformed in some way.\n    static connection_params parse(string_view conn_string);\n\n    /// \\{\n    /// Determines the underlying \\ref zk::connection implementation to use. The valid values are \\c \"zk\" and\n    /// \\c \"fakezk\".\n    ///\n    /// - `zk`: The standard-issue ZooKeeper connection to a real ZooKeeper cluster. This schema uses\n    ///   \\ref zk::connection_zk as the underlying connection.\n    /// - `fakezk`: Create a client connected to a fake ZooKeeper server (\\ref zk::fake::server). Here, the\n    ///   `host_address` refers to the name of the in-memory DB created when the server instance was. This schema uses\n    ///   \\ref zk::fake::connection_fake as the underlying connection.\n    const std::string& connection_schema() const { return _connection_schema; }\n    std::string&       connection_schema()       { return _connection_schema; }\n    /// \\}\n\n    /// \\{\n    /// Addresses for the ensemble to connect to. This can be IP addresses (IPv4 or IPv6) or hostnames, but IP\n    /// addresses are the recommended method of specification. If the port is left unspecified, \\c 2181 is assumed (as\n    /// it is the de facto standard for ZooKeeper server). For IPv6 addresses, use the boxed format (e.g. `[::1]:2181`);\n    /// this is required even when the port is \\c 2181 to disambiguate between a host named in hexadecimal and an IPv6\n    /// address (e.g. `\"[fd2d:8413:d6c6::73b]\"` or `\"[::1]:2181\"`).\n    const host_list& hosts() const { return _hosts; }\n    host_list&       hosts()       { return _hosts; }\n    /// \\}\n\n    /// \\{\n    /// Specifying a value for \\c chroot as something aside from \\c \"\" or \\c \"/\" will run the client commands while\n    /// interpreting all paths relative to the specified path. For example, specifying `\"/app/a\"` will make requests for\n    /// `\"/foo/bar\"` sent to `\"/app/a/foo/bar\"` (from the server's perspective). If unspecified, the path will be\n    /// treated as `\"/\"`.\n    const std::string& chroot() const { return _chroot; }\n    std::string&       chroot()       { return _chroot; }\n    /// \\}\n\n    /// \\{\n    /// Connect to a host at random (as opposed to attempting connections in order)? The default is to randomize (the\n    /// use cases for sequential connections are usually limited to testing purposes).\n    bool  randomize_hosts() const { return _randomize_hosts; }\n    bool& randomize_hosts()       { return _randomize_hosts; }\n    /// \\}\n\n    /// \\{\n    /// Allow connections to read-only servers? The default (\\c false) is to disallow. **/\n    bool  read_only() const { return _read_only; }\n    bool& read_only()       { return _read_only; }\n    /// \\}\n\n    /// \\{\n    /// The session timeout between this client and the server. The server will attempt to respect this value, but will\n    /// automatically use a lower timeout value if this value is too large (see the ZooKeeper Programmer's Guide for\n    /// more information on maximum values). The default is 10 seconds.\n    ///\n    /// When specified in a query string, this value is specified either with floating-point seconds or as an ISO 8601\n    /// duration (`PT8.93S` for \\c 8.930 seconds).\n    std::chrono::milliseconds  timeout() const { return _timeout; }\n    std::chrono::milliseconds& timeout()       { return _timeout; }\n    /// \\}\n\nprivate:\n    std::string               _connection_schema;\n    host_list                 _hosts;\n    std::string               _chroot;\n    bool                      _randomize_hosts;\n    bool                      _read_only;\n    std::chrono::milliseconds _timeout;\n};\n\nbool operator==(const connection_params& lhs, const connection_params& rhs);\nbool operator!=(const connection_params& lhs, const connection_params& rhs);\n\nstd::string to_string(const connection_params&);\nstd::ostream& operator<<(std::ostream&, const connection_params&);\n\n/// \\}\n\n}\n"
  },
  {
    "path": "src/zk/connection_tests.cpp",
    "content": "#include <zk/tests/test.hpp>\n\n#include \"connection.hpp\"\n\nnamespace zk\n{\n\n// This test is mostly to check that we still use the defaults.\nGTEST_TEST(connection_params_tests, defaults)\n{\n    const auto res = connection_params::parse(\"zk://localhost/\");\n    CHECK_EQ(\"zk\", res.connection_schema());\n    CHECK_EQ(1U, res.hosts().size());\n    CHECK_EQ(\"localhost\", res.hosts()[0]);\n    CHECK_EQ(\"/\", res.chroot());\n    CHECK_TRUE(res.randomize_hosts());\n    CHECK_FALSE(res.read_only());\n    CHECK_TRUE(connection_params::default_timeout == res.timeout());\n\n    connection_params manual;\n    manual.hosts() = { \"localhost\" };\n    CHECK_EQ(manual, res);\n}\n\nGTEST_TEST(connection_params_tests, multi_hosts)\n{\n    const auto res = connection_params::parse(\"zk://server-a,server-b,server-c/\");\n    connection_params manual;\n    manual.hosts() = { \"server-a\", \"server-b\", \"server-c\" };\n    CHECK_EQ(manual, res);\n}\n\nGTEST_TEST(connection_params_tests, chroot)\n{\n    const auto res = connection_params::parse(\"zk://localhost/some/sub/path\");\n    connection_params manual;\n    manual.hosts()  = { \"localhost\" };\n    manual.chroot() = \"/some/sub/path\";\n    CHECK_EQ(manual, res);\n}\n\nGTEST_TEST(connection_params_tests, randomize_hosts)\n{\n    const auto res = connection_params::parse(\"zk://localhost/?randomize_hosts=false\");\n    connection_params manual;\n    manual.hosts()           = { \"localhost\" };\n    manual.randomize_hosts() = false;\n    CHECK_EQ(manual, res);\n}\n\nGTEST_TEST(connection_params_tests, read_only)\n{\n    const auto res = connection_params::parse(\"zk://localhost/?read_only=true\");\n    connection_params manual;\n    manual.hosts()     = { \"localhost\" };\n    manual.read_only() = true;\n    CHECK_EQ(manual, res);\n}\n\nGTEST_TEST(connection_params_tests, randomize_and_read_only)\n{\n    const auto res = connection_params::parse(\"zk://localhost/?randomize_hosts=false&read_only=1\");\n    connection_params manual;\n    manual.hosts()           = { \"localhost\" };\n    manual.randomize_hosts() = false;\n    manual.read_only()       = true;\n    CHECK_EQ(manual, res);\n}\n\nGTEST_TEST(connection_params_tests, timeout)\n{\n    const auto res = connection_params::parse(\"zk://localhost/?timeout=0.5\");\n    connection_params manual;\n    manual.hosts()   = { \"localhost\" };\n    manual.timeout() = std::chrono::milliseconds(500);\n    CHECK_EQ(manual, res);\n}\n\n}\n"
  },
  {
    "path": "src/zk/connection_zk.cpp",
    "content": "#include \"connection_zk.hpp\"\n#include \"exceptions.hpp\"\n\n#include <algorithm>\n#include <cassert>\n#include <cerrno>\n#include <cstring>\n#include <iostream>\n#include <map>\n#include <memory>\n#include <sstream>\n#include <stdexcept>\n#include <string>\n#include <system_error>\n#include <tuple>\n\n#include <zookeeper/zookeeper.h>\n\n#include \"acl.hpp\"\n#include \"error.hpp\"\n#include \"multi.hpp\"\n#include \"results.hpp\"\n#include \"types.hpp\"\n\nnamespace zk\n{\n\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n// Utility Functions                                                                                                  //\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\ntemplate <typename FAction>\nauto with_str(string_view src, FAction&& action) noexcept(noexcept(std::forward<FAction>(action)(ptr<const char>())))\n        -> decltype(std::forward<FAction>(action)(ptr<const char>()))\n{\n    char buffer[src.size() + 1];\n    buffer[src.size()] = '\\0';\n    std::memcpy(buffer, src.data(), src.size());\n    return std::forward<FAction>(action)(buffer);\n}\n\nstatic ACL encode_acl_part(const acl_rule& src)\n{\n    ACL out;\n    out.perms     = static_cast<int>(src.permissions());\n    out.id.scheme = const_cast<ptr<char>>(src.scheme().c_str());\n    out.id.id     = const_cast<ptr<char>>(src.id().c_str());\n    return out;\n}\n\ntemplate <typename FAction>\nauto with_acl(const acl& rules, FAction&& action) noexcept(noexcept(std::forward<FAction>(action)(ptr<ACL_vector>())))\n        -> decltype(std::forward<FAction>(action)(ptr<ACL_vector>()))\n{\n    ACL parts[rules.size()];\n    for (std::size_t idx = 0; idx < rules.size(); ++idx)\n        parts[idx] = encode_acl_part(rules[idx]);\n\n    ACL_vector vec;\n    vec.count = int(rules.size());\n    vec.data  = parts;\n    return std::forward<FAction>(action)(&vec);\n}\n\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n// Native Adaptors                                                                                                    //\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\nstatic error_code error_code_from_raw(int raw)\n{\n    switch (raw)\n    {\n    case ZOPERATIONTIMEOUT:\n        raw = ZCONNECTIONLOSS;\n        break;\n    case ZINVALIDCALLBACK:\n    case ZINVALIDACL:\n        raw = ZBADARGUMENTS;\n        break;\n    case ZSESSIONMOVED:\n        raw = ZCONNECTIONLOSS;\n        break;\n    default:\n        break;\n    }\n    return static_cast<error_code>(raw);\n}\n\nstatic event_type event_from_raw(int raw)\n{\n    return static_cast<event_type>(raw);\n}\n\n// ZooKeeper does not have this concept pre-3.5\n#if ZOO_MAJOR_VERSION <= 3 && ZOO_MINOR_VERSION <= 4\nstatic const int ZOO_NOTCONNECTED_STATE = 999;\n#endif\n\nstatic state state_from_raw(int raw)\n{\n    // The C client will put us into `ZOO_NOTCONNECTED_STATE` for two reasons:\n    //\n    // 1. This is the state of the initial connection (zookeeper_init_internal), which is then replaced when the adaptor\n    //    threads first call the interest function.\n    // 2. During a reconfiguration, the client disconnects and transitions to this state (update_addrs), which is then\n    //    updated the next time the I/O thread touches interest.\n    //\n    // In both cases, the state is still \"connecting\" from the point of view of a client.\n    if (raw == ZOO_NOTCONNECTED_STATE)\n    {\n        raw = ZOO_CONNECTING_STATE;\n    }\n    // `ZOO_ASSOCIATING_STATE` means we have connected to a server, but have not yet authenticated and created the\n    // session. We still can't perform any operations, so treat it as connecting -- the client does not care about the\n    // difference between establishing a TCP connection and negotiating credentials.\n    else if (raw == ZOO_ASSOCIATING_STATE)\n    {\n        raw = ZOO_CONNECTING_STATE;\n    }\n\n    return static_cast<state>(raw);\n}\n\nstatic stat stat_from_raw(const struct Stat& raw)\n{\n    stat out;\n    out.acl_version = acl_version(raw.aversion);\n    out.child_modified_transaction = transaction_id(raw.pzxid);\n    out.child_version = child_version(raw.cversion);\n    out.children_count = raw.numChildren;\n    out.create_time = stat::time_point() + std::chrono::milliseconds(raw.ctime);\n    out.create_transaction = transaction_id(raw.czxid);\n    out.data_size = raw.dataLength;\n    out.data_version = version(raw.version);\n    out.ephemeral_owner = raw.ephemeralOwner;\n    out.modified_time = stat::time_point() + std::chrono::milliseconds(raw.mtime);\n    out.modified_transaction = transaction_id(raw.mzxid);\n    return out;\n}\n\nstatic std::vector<std::string> string_vector_from_raw(const struct String_vector& raw)\n{\n    std::vector<std::string> out;\n    out.reserve(raw.count);\n    for (std::int32_t idx = 0; idx < raw.count; ++idx)\n        out.emplace_back(raw.data[idx]);\n    return out;\n}\n\nstatic acl acl_from_raw(const struct ACL_vector& raw)\n{\n    auto sz = std::size_t(raw.count);\n\n    acl out;\n    out.reserve(sz);\n    for (std::size_t idx = 0; idx < sz; ++idx)\n    {\n        const auto& item = raw.data[idx];\n        out.emplace_back(item.id.scheme, item.id.id, static_cast<permission>(item.perms));\n    }\n    return out;\n}\n\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n// connection_zk                                                                                                      //\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\nconnection_zk::connection_zk(const connection_params& params) :\n        _handle(nullptr)\n{\n    if (params.connection_schema() != \"zk\")\n        zk::throw_exception(std::invalid_argument(std::string(\"Invalid connection string \\\"\") + to_string(params) + \"\\\"\"));\n\n    auto conn_string = [&] ()\n                       {\n                           std::ostringstream os;\n                           bool first = true;\n                           for (const auto& host : params.hosts())\n                           {\n                               if (first)\n                                   first = false;\n                               else\n                                   os << ',';\n\n                               os << host;\n                           }\n                           return os.str();\n                       }();\n\n    _handle = ::zookeeper_init(conn_string.c_str(),\n                               on_session_event_raw,\n                               static_cast<int>(params.timeout().count()),\n                               nullptr,\n                               this,\n                               0\n                              );\n\n    if (!_handle)\n        std::system_error(errno, std::system_category(), \"Failed to create ZooKeeper client\");\n}\n\nconnection_zk::~connection_zk() noexcept\n{\n    close();\n}\n\nclass connection_zk::watcher\n{\npublic:\n    watcher() :\n            _event_delivered(false)\n    { }\n\n    virtual ~watcher() noexcept {}\n\n    virtual void deliver_event(event ev)\n    {\n        if (!_event_delivered.exchange(true, std::memory_order_relaxed))\n        {\n            _event_promise.set_value(std::move(ev));\n        }\n    }\n\n    future<event> get_event_future()\n    {\n        return _event_promise.get_future();\n    }\n\nprotected:\n    std::atomic<bool> _event_delivered;\n    promise<event>    _event_promise;\n};\n\ntemplate <typename TResult>\nclass connection_zk::basic_watcher :\n        public connection_zk::watcher\n{\npublic:\n    basic_watcher() :\n            _data_delivered(false)\n    { }\n\n    future<TResult> get_data_future()\n    {\n        return _data_promise.get_future();\n    }\n\n    virtual void deliver_event(event ev) override\n    {\n        if (!_data_delivered.load(std::memory_order_relaxed))\n        {\n            deliver_data(nullopt, get_exception_ptr_of(error_code::closed));\n        }\n\n        watcher::deliver_event(std::move(ev));\n    }\n\n    void deliver_data(optional<TResult> data, zk::exception_ptr ex_ptr)\n    {\n        if (!_data_delivered.exchange(true, std::memory_order_relaxed))\n        {\n            if (ex_ptr)\n            {\n                _data_promise.set_exception(std::move(ex_ptr));\n            }\n            else\n            {\n                _data_promise.set_value(std::move(*data));\n            }\n        }\n    }\n\nprivate:\n    std::atomic<bool> _data_delivered;\n    promise<TResult>  _data_promise;\n};\n\nstd::shared_ptr<connection_zk::watcher> connection_zk::try_extract_watch(ptr<const void> addr)\n{\n    std::unique_lock<std::mutex> ax(_watches_protect);\n    auto iter = _watches.find(addr);\n    if (iter != _watches.end())\n        return _watches.extract(iter).mapped();\n    else\n        return nullptr;\n}\n\nstatic ptr<connection_zk> connection_from_context(ptr<zhandle_t> zh)\n{\n    return (ptr<connection_zk>) zoo_get_context(zh);\n}\n\nvoid connection_zk::deliver_watch(ptr<zhandle_t>  zh,\n                                  int             type_in,\n                                  int             state_in,\n                                  ptr<const char> path     [[gnu::unused]],\n                                  ptr<void>       proms_in\n                                 )\n{\n    auto& self = *connection_from_context(zh);\n    if (auto watcher = self.try_extract_watch(proms_in))\n        watcher->deliver_event(event(event_from_raw(type_in), state_from_raw(state_in)));\n}\n\nvoid connection_zk::close()\n{\n    if (_handle)\n    {\n        auto err = error_code_from_raw(::zookeeper_close(_handle));\n        if (err != error_code::ok)\n            throw_error(err);\n\n        _handle = nullptr;\n\n        // Deliver a session event as if there was a close.\n        std::unique_lock<std::mutex> ax(_watches_protect);\n        auto l_watches = std::move(_watches);\n        ax.unlock();\n        for (const auto& pair : l_watches)\n            pair.second->deliver_event(event(event_type::session, zk::state::closed));\n    }\n}\n\nzk::state connection_zk::state() const\n{\n    if (_handle)\n        return state_from_raw(::zoo_state(_handle));\n    else\n        return zk::state::closed;\n}\n\nfuture<get_result> connection_zk::get(string_view path)\n{\n    ::data_completion_t callback =\n        [] (int rc_in, ptr<const char> data, int data_sz, ptr<const struct Stat> pstat, ptr<const void> prom_in) noexcept\n        {\n            std::unique_ptr<promise<get_result>> prom((ptr<promise<get_result>>) prom_in);\n            auto rc = error_code_from_raw(rc_in);\n            if (rc == error_code::ok)\n                prom->set_value(get_result(buffer(data, data + data_sz), stat_from_raw(*pstat)));\n            else\n                prom->set_exception(get_exception_ptr_of(rc));\n        };\n\n    return with_str(path, [&] (ptr<const char> path) noexcept\n    {\n        auto ppromise = std::make_unique<promise<get_result>>();\n        auto fut      = ppromise->get_future();\n        auto rc = error_code_from_raw(::zoo_aget(_handle, path, 0, callback, ppromise.get()));\n        if (rc == error_code::ok)\n        {\n            ppromise.release();\n            return fut;\n        }\n        else\n        {\n            ppromise->set_exception(get_exception_ptr_of(rc));\n            return fut;\n        }\n    });\n}\n\nclass connection_zk::data_watcher :\n        public connection_zk::basic_watcher<watch_result>\n{\npublic:\n    static void deliver_raw(int                    rc_in,\n                            ptr<const char>        data,\n                            int                    data_sz,\n                            ptr<const struct Stat> pstat,\n                            ptr<const void>        self_in\n                           ) noexcept\n    {\n        auto& self = *static_cast<ptr<data_watcher>>(const_cast<ptr<void>>(self_in));\n        auto  rc   = error_code_from_raw(rc_in);\n\n        if (rc == error_code::ok)\n        {\n            self.deliver_data(watch_result(get_result(buffer(data, data + data_sz), stat_from_raw(*pstat)),\n                                           self.get_event_future()\n                                          ),\n                              zk::exception_ptr()\n                             );\n        }\n        else\n        {\n            self.deliver_data(nullopt, get_exception_ptr_of(rc));\n        }\n    }\n};\n\nfuture<watch_result> connection_zk::watch(string_view path)\n{\n    return with_str(path, [&] (ptr<const char> path) noexcept\n    {\n        std::unique_lock<std::mutex> ax(_watches_protect);\n        auto watcher = std::make_shared<data_watcher>();\n        auto rc      = error_code_from_raw(::zoo_awget(_handle,\n                                                       path,\n                                                       deliver_watch,\n                                                       watcher.get(),\n                                                       data_watcher::deliver_raw,\n                                                       watcher.get()\n                                                      )\n                                          );\n        if (rc == error_code::ok)\n            _watches.emplace(watcher.get(), watcher);\n        else\n            watcher->deliver_data(nullopt, get_exception_ptr_of(rc));\n\n        return watcher->get_data_future();\n    });\n}\n\nfuture<get_children_result> connection_zk::get_children(string_view path)\n{\n    ::strings_stat_completion_t callback =\n        [] (int                             rc_in,\n            ptr<const struct String_vector> strings_in,\n            ptr<const struct Stat>          stat_in,\n            ptr<const void>                 prom_in\n           )\n        {\n            std::unique_ptr<promise<get_children_result>> prom((ptr<promise<get_children_result>>) prom_in);\n            auto rc = error_code_from_raw(rc_in);\n            try\n            {\n                if (rc != error_code::ok)\n                    throw_error(rc);\n\n                prom->set_value(get_children_result(string_vector_from_raw(*strings_in), stat_from_raw(*stat_in)));\n            }\n            catch (...)\n            {\n                prom->set_exception(zk::current_exception());\n            }\n        };\n\n    return with_str(path, [&] (ptr<const char> path) noexcept\n    {\n        auto ppromise = std::make_unique<promise<get_children_result>>();\n        auto fut      = ppromise->get_future();\n        auto rc       = error_code_from_raw(::zoo_aget_children2(_handle,\n                                                                 path,\n                                                                 0,\n                                                                 callback,\n                                                                 ppromise.get()\n                                                                )\n                                           );\n        if (rc == error_code::ok)\n        {\n            ppromise.release();\n            return fut;\n        }\n        else\n        {\n            ppromise->set_exception(get_exception_ptr_of(rc));\n            return fut;\n        }\n    });\n}\n\nclass connection_zk::child_watcher :\n        public connection_zk::basic_watcher<watch_children_result>\n{\npublic:\n    static void deliver_raw(int                             rc_in,\n                            ptr<const struct String_vector> strings_in,\n                            ptr<const struct Stat>          stat_in,\n                            ptr<const void>                 prom_in\n                           ) noexcept\n    {\n        auto& self = *static_cast<ptr<child_watcher>>(const_cast<ptr<void>>(prom_in));\n        auto  rc   = error_code_from_raw(rc_in);\n\n        try\n        {\n            if (rc != error_code::ok)\n                throw_error(rc);\n\n            self.deliver_data(watch_children_result(get_children_result(string_vector_from_raw(*strings_in),\n                                                                        stat_from_raw(*stat_in)\n                                                                       ),\n                                                    self.get_event_future()\n                                                   ),\n                              zk::exception_ptr()\n                             );\n        }\n        catch (...)\n        {\n            self.deliver_data(nullopt, zk::current_exception());\n        }\n\n    }\n};\n\nfuture<watch_children_result> connection_zk::watch_children(string_view path)\n{\n    return with_str(path, [&] (ptr<const char> path) noexcept\n    {\n        std::unique_lock<std::mutex> ax(_watches_protect);\n        auto watcher = std::make_shared<child_watcher>();\n        auto rc      = error_code_from_raw(::zoo_awget_children2(_handle,\n                                                                 path,\n                                                                 deliver_watch,\n                                                                 watcher.get(),\n                                                                 child_watcher::deliver_raw,\n                                                                 watcher.get()\n                                                                )\n                                          );\n        if (rc == error_code::ok)\n            _watches.emplace(watcher.get(), watcher);\n        else\n            watcher->deliver_data(nullopt, get_exception_ptr_of(rc));\n\n        return watcher->get_data_future();\n    });\n}\n\nfuture<exists_result> connection_zk::exists(string_view path)\n{\n    ::stat_completion_t callback =\n        [] (int rc_in, ptr<const struct Stat> stat_in, ptr<const void> prom_in)\n        {\n            std::unique_ptr<promise<exists_result>> prom((ptr<promise<exists_result>>) prom_in);\n            auto rc = error_code_from_raw(rc_in);\n            if (rc == error_code::ok)\n                prom->set_value(exists_result(stat_from_raw(*stat_in)));\n            else if (rc == error_code::no_entry)\n                prom->set_value(exists_result(nullopt));\n            else\n                prom->set_exception(get_exception_ptr_of(rc));\n        };\n\n    return with_str(path, [&] (ptr<const char> path) noexcept\n    {\n        auto ppromise = std::make_unique<promise<exists_result>>();\n        auto fut      = ppromise->get_future();\n        auto rc       = error_code_from_raw(::zoo_aexists(_handle, path, 0, callback, ppromise.get()));\n        if (rc == error_code::ok)\n        {\n            ppromise.release();\n            return fut;\n        }\n        else\n        {\n            ppromise->set_exception(get_exception_ptr_of(rc));\n            return fut;\n        }\n    });\n}\n\nclass connection_zk::exists_watcher :\n        public connection_zk::basic_watcher<watch_exists_result>\n{\npublic:\n    static void deliver_raw(int rc_in, ptr<const struct Stat> stat_in, ptr<const void> self_in)\n    {\n        auto& self = *static_cast<ptr<exists_watcher>>(const_cast<ptr<void>>(self_in));\n        auto  rc   = error_code_from_raw(rc_in);\n\n        if (rc == error_code::ok)\n        {\n            self.deliver_data(watch_exists_result(exists_result(stat_from_raw(*stat_in)), self.get_event_future()),\n                              zk::exception_ptr()\n                             );\n        }\n        else if (rc == error_code::no_entry)\n        {\n            self.deliver_data(watch_exists_result(exists_result(nullopt), self.get_event_future()),\n                              zk::exception_ptr()\n                             );\n        }\n        else\n        {\n            self.deliver_data(nullopt, get_exception_ptr_of(rc));\n        }\n    }\n};\n\nfuture<watch_exists_result> connection_zk::watch_exists(string_view path)\n{\n    return with_str(path, [&] (ptr<const char> path) noexcept\n    {\n        std::unique_lock<std::mutex> ax(_watches_protect);\n        auto watcher = std::make_shared<exists_watcher>();\n        auto rc      = error_code_from_raw(::zoo_awexists(_handle,\n                                                          path,\n                                                          deliver_watch,\n                                                          watcher.get(),\n                                                          exists_watcher::deliver_raw,\n                                                          watcher.get()\n                                                         )\n                                          );\n        if (rc == error_code::ok)\n            _watches.emplace(watcher.get(), watcher);\n        else\n            watcher->deliver_data(nullopt, get_exception_ptr_of(rc));\n\n        return watcher->get_data_future();\n    });\n}\n\nfuture<create_result> connection_zk::create(string_view   path,\n                                            const buffer& data,\n                                            const acl&    rules,\n                                            create_mode   mode\n                                           )\n{\n    ::string_completion_t callback =\n        [] (int rc_in, ptr<const char> name_in, ptr<const void> prom_in)\n        {\n            std::unique_ptr<promise<create_result>> prom((ptr<promise<create_result>>) prom_in);\n            auto rc = error_code_from_raw(rc_in);\n            if (rc == error_code::ok)\n                prom->set_value(create_result(std::string(name_in)));\n            else\n                prom->set_exception(get_exception_ptr_of(rc));\n        };\n\n        return with_str(path, [&] (ptr<const char> path) noexcept\n        {\n            auto ppromise = std::make_unique<promise<create_result>>();\n            auto fut      = ppromise->get_future();\n            auto rc       = with_acl(rules, [&] (ptr<const ACL_vector> rules) noexcept\n            {\n                return error_code_from_raw(::zoo_acreate(_handle,\n                                                         path,\n                                                         data.data(),\n                                                         int(data.size()),\n                                                         rules,\n                                                         static_cast<int>(mode),\n                                                         callback,\n                                                         ppromise.get()\n                                                        )\n                                          );\n            });\n\n            if (rc == error_code::ok)\n            {\n                ppromise.release();\n                return fut;\n            }\n            else\n            {\n                ppromise->set_exception(get_exception_ptr_of(rc));\n                return fut;\n            }\n        });\n}\n\nfuture<set_result> connection_zk::set(string_view path, const buffer& data, version check)\n{\n    ::stat_completion_t callback =\n        [] (int rc_in, ptr<const struct Stat> stat_raw, ptr<const void> prom_in)\n        {\n            std::unique_ptr<promise<set_result>> prom((ptr<promise<set_result>>) prom_in);\n            auto rc = error_code_from_raw(rc_in);\n            if (rc == error_code::ok)\n                prom->set_value(set_result(stat_from_raw(*stat_raw)));\n            else\n                prom->set_exception(get_exception_ptr_of(rc));\n        };\n\n    return with_str(path, [&] (ptr<const char> path) noexcept\n    {\n        auto ppromise = std::make_unique<promise<set_result>>();\n        auto fut      = ppromise->get_future();\n        auto rc       = error_code_from_raw(::zoo_aset(_handle,\n                                                       path,\n                                                       data.data(),\n                                                       int(data.size()),\n                                                       check.value,\n                                                       callback,\n                                                       ppromise.get()\n                                                      ));\n\n        if (rc == error_code::ok)\n        {\n            ppromise.release();\n            return fut;\n        }\n        else\n        {\n            ppromise->set_exception(get_exception_ptr_of(rc));\n            return fut;\n        }\n    });\n}\n\nfuture<void> connection_zk::erase(string_view path, version check)\n{\n    ::void_completion_t callback =\n        [] (int rc_in, ptr<const void> prom_in)\n        {\n            std::unique_ptr<promise<void>> prom((ptr<promise<void>>) prom_in);\n            auto rc = error_code_from_raw(rc_in);\n            if (rc == error_code::ok)\n                prom->set_value();\n            else\n                prom->set_exception(get_exception_ptr_of(rc));\n        };\n\n    return with_str(path, [&] (ptr<const char> path) noexcept\n    {\n        auto ppromise = std::make_unique<promise<void>>();\n        auto fut      = ppromise->get_future();\n        auto rc       = error_code_from_raw(::zoo_adelete(_handle, path, check.value, callback, ppromise.get()));\n        if (rc == error_code::ok)\n        {\n            ppromise.release();\n            return fut;\n        }\n        else\n        {\n            ppromise->set_exception(get_exception_ptr_of(rc));\n            return fut;\n        }\n    });\n}\n\nfuture<get_acl_result> connection_zk::get_acl(string_view path) const\n{\n    ::acl_completion_t callback =\n        [] (int rc_in, ptr<struct ACL_vector> acl_raw, ptr<struct Stat> stat_raw, ptr<const void> prom_in) noexcept\n        {\n            std::unique_ptr<promise<get_acl_result>> prom((ptr<promise<get_acl_result>>) prom_in);\n            auto rc = error_code_from_raw(rc_in);\n            if (rc == error_code::ok)\n                prom->set_value(get_acl_result(acl_from_raw(*acl_raw), stat_from_raw(*stat_raw)));\n            else\n                prom->set_exception(get_exception_ptr_of(rc));\n        };\n\n    return with_str(path, [&] (ptr<const char> path) noexcept\n    {\n        auto ppromise = std::make_unique<promise<get_acl_result>>();\n        auto fut      = ppromise->get_future();\n        auto rc       = error_code_from_raw(::zoo_aget_acl(_handle, path, callback, ppromise.get()));\n        if (rc == error_code::ok)\n        {\n            ppromise.release();\n            return fut;\n        }\n        else\n        {\n            ppromise->set_exception(get_exception_ptr_of(rc));\n            return fut;\n        }\n    });\n}\n\nfuture<void> connection_zk::set_acl(string_view path, const acl& rules, acl_version check)\n{\n    ::void_completion_t callback =\n        [] (int rc_in, ptr<const void> prom_in)\n        {\n            std::unique_ptr<promise<void>> prom((ptr<promise<void>>) prom_in);\n            auto rc = error_code_from_raw(rc_in);\n            if (rc == error_code::ok)\n                prom->set_value();\n            else\n                prom->set_exception(get_exception_ptr_of(rc));\n        };\n\n    return with_str(path, [&] (ptr<const char> path) noexcept\n    {\n        return with_acl(rules, [&] (ptr<struct ACL_vector> rules) noexcept\n        {\n            auto ppromise = std::make_unique<promise<void>>();\n            auto fut      = ppromise->get_future();\n            auto rc       = error_code_from_raw(::zoo_aset_acl(_handle,\n                                                               path,\n                                                               check.value,\n                                                               rules,\n                                                               callback,\n                                                               ppromise.get()\n                                                              )\n                                               );\n            if (rc == error_code::ok)\n            {\n                ppromise.release();\n                return fut;\n            }\n            else\n            {\n                ppromise->set_exception(get_exception_ptr_of(rc));\n                return fut;\n            }\n        });\n    });\n}\n\nstruct connection_zk_commit_completer\n{\n    multi_op                                 source_txn;\n    promise<multi_result>                    prom;\n    std::vector<zoo_op_result_t>             raw_results;\n    std::map<std::size_t, Stat>              raw_stats;\n    std::map<std::size_t, std::vector<char>> path_buffers;\n\n    explicit connection_zk_commit_completer(multi_op&& src) :\n            source_txn(std::move(src)),\n            raw_results(source_txn.size())\n    {\n        for (zoo_op_result_t& x : raw_results)\n            x.err = -42;\n    }\n\n    ptr<Stat> raw_stat_at(std::size_t idx)\n    {\n        return &raw_stats[idx];\n    }\n\n    ptr<std::vector<char>> path_buffer_for(std::size_t idx, const std::string& path, create_mode mode)\n    {\n        // If the creation is sequential, append 12 extra characters to store the digits\n        auto sz = path.size() + (is_set(mode, create_mode::sequential) ? 12 : 1);\n        path_buffers[idx] = std::vector<char>(sz);\n        return &path_buffers[idx];\n    }\n\n    void deliver(error_code rc)\n    {\n        try\n        {\n            if (rc == error_code::ok)\n            {\n                multi_result out;\n                out.reserve(raw_results.size());\n                for (std::size_t idx = 0; idx < source_txn.size(); ++idx)\n                {\n                    const auto& raw_res = raw_results[idx];\n\n                    switch (source_txn[idx].type())\n                    {\n                    case op_type::create:\n                        out.emplace_back(create_result(std::string(raw_res.value)));\n                        break;\n                    case op_type::set:\n                        out.emplace_back(set_result(stat_from_raw(*raw_res.stat)));\n                        break;\n                    default:\n                        out.emplace_back(source_txn[idx].type(), nullptr);\n                        break;\n                    }\n                }\n\n                prom.set_value(std::move(out));\n            }\n            else\n            {\n                // All results until the failure are 0, equal to rc where we care, and runtime_inconsistency after that.\n                auto iter = std::partition_point(raw_results.begin(), raw_results.end(),\n                                                 [] (auto res) { return res.err == 0; }\n                                                );\n                zk::throw_exception(transaction_failed(rc, std::size_t(std::distance(raw_results.begin(), iter))));\n            }\n        }\n        catch (...)\n        {\n            prom.set_exception(zk::current_exception());\n        }\n    }\n};\n\nfuture<multi_result> connection_zk::commit(multi_op&& txn_in)\n{\n    ::void_completion_t callback =\n        [] (int rc_in, ptr<const void> completer_in)\n        {\n            std::unique_ptr<connection_zk_commit_completer>\n                completer((ptr<connection_zk_commit_completer>) completer_in);\n            completer->deliver(error_code_from_raw(rc_in));\n        };\n\n    auto pcompleter = std::make_unique<connection_zk_commit_completer>(std::move(txn_in));\n    auto& txn = pcompleter->source_txn;\n    try\n    {\n        ::zoo_op raw_ops[txn.size()];\n        std::size_t create_op_count = 0;\n        std::size_t acl_piece_count = 0;\n        for (const auto& tx : txn)\n        {\n            if (tx.type() == op_type::create)\n            {\n                ++create_op_count;\n                acl_piece_count += tx.as_create().rules.size();\n            }\n        }\n        ACL_vector      encoded_acls[create_op_count];\n        ACL             acl_pieces[acl_piece_count];\n        ptr<ACL_vector> encoded_acl_iter = encoded_acls;\n        ptr<ACL>        acl_piece_iter   = acl_pieces;\n\n        for (std::size_t idx = 0; idx < txn.size(); ++idx)\n        {\n            auto& raw_op = raw_ops[idx];\n            auto& src_op = txn[idx];\n            switch (src_op.type())\n            {\n                case op_type::check:\n                    zoo_check_op_init(&raw_op, src_op.as_check().path.c_str(), src_op.as_check().check.value);\n                    break;\n                case op_type::create:\n                {\n                    const auto& cdata = src_op.as_create();\n                    encoded_acl_iter->count = int(cdata.rules.size());\n                    encoded_acl_iter->data  = acl_piece_iter;\n                    for (const auto& acl : cdata.rules)\n                    {\n                        *acl_piece_iter = encode_acl_part(acl);\n                        ++acl_piece_iter;\n                    }\n\n                    auto path_buf_ref = pcompleter->path_buffer_for(idx, cdata.path, cdata.mode);\n                    zoo_create_op_init(&raw_op,\n                                       cdata.path.c_str(),\n                                       cdata.data.data(),\n                                       int(cdata.data.size()),\n                                       encoded_acl_iter,\n                                       static_cast<int>(cdata.mode),\n                                       path_buf_ref->data(),\n                                       int(path_buf_ref->size())\n                                      );\n                    ++encoded_acl_iter;\n                    break;\n                }\n                case op_type::erase:\n                    zoo_delete_op_init(&raw_op, src_op.as_erase().path.c_str(), src_op.as_erase().check.value);\n                    break;\n                case op_type::set:\n                {\n                    const auto& setting = src_op.as_set();\n                    zoo_set_op_init(&raw_op,\n                                    setting.path.c_str(),\n                                    setting.data.data(),\n                                    int(setting.data.size()),\n                                    setting.check.value,\n                                    pcompleter->raw_stat_at(idx)\n                                   );\n                    break;\n                }\n                default:\n                {\n                    using std::to_string;\n                    zk::throw_exception(std::invalid_argument(\"Invalid op_type at index=\" + to_string(idx) + \": \"\n                                                + to_string(src_op.type())\n                                               ));\n                }\n            }\n        }\n        auto fut = pcompleter->prom.get_future();\n        auto rc = error_code_from_raw(::zoo_amulti(_handle,\n                                                   int(txn.size()),\n                                                   raw_ops,\n                                                   pcompleter->raw_results.data(),\n                                                   callback,\n                                                   pcompleter.get()\n                                                  )\n                                     );\n        if (rc == error_code::ok)\n        {\n            pcompleter.release();\n            return fut;\n        }\n        else\n        {\n            pcompleter->prom.set_exception(get_exception_ptr_of(rc));\n            return pcompleter->prom.get_future();\n        }\n    }\n    catch (...)\n    {\n        pcompleter->prom.set_exception(zk::current_exception());\n        return pcompleter->prom.get_future();\n    }\n}\n\nfuture<void> connection_zk::load_fence()\n{\n    ::string_completion_t callback =\n        [] (int rc_in, ptr<const char>, ptr<const void> prom_in)\n        {\n            std::unique_ptr<promise<void>> prom((ptr<promise<void>>) prom_in);\n            auto rc = error_code_from_raw(rc_in);\n            if (rc == error_code::ok)\n                prom->set_value();\n            else\n                prom->set_exception(get_exception_ptr_of(rc));\n        };\n\n    auto ppromise = std::make_unique<zk::promise<void>>();\n    auto rc = error_code_from_raw(::zoo_async(_handle, \"/\", callback, ppromise.get()));\n    if (rc == error_code::ok)\n    {\n        auto f = ppromise->get_future();\n        ppromise.release();\n        return f;\n    }\n    else\n    {\n        ppromise->set_exception(get_exception_ptr_of(rc));\n        return ppromise->get_future();\n    }\n}\n\nvoid connection_zk::on_session_event_raw(ptr<zhandle_t>  handle      [[gnu::unused]],\n                                         int             ev_type,\n                                         int             state,\n                                         ptr<const char> path_ptr,\n                                         ptr<void>       watcher_ctx\n                                        ) noexcept\n{\n    auto self = static_cast<ptr<connection_zk>>(watcher_ctx);\n    // Most of the time, self's _handle will be the same thing that ZK provides to us. However, if we connect very\n    // quickly, a session event will happen trigger *before* we set the _handle. This isn't a problem, just something to\n    // be aware of.\n    assert(self->_handle == nullptr || self->_handle == handle);\n    auto ev = event_from_raw(ev_type);\n    auto st = state_from_raw(state);\n    auto path = string_view(path_ptr);\n\n    if (ev != event_type::session)\n    {\n        // TODO: Remove this usage of std::cerr\n        std::cerr << \"WARNING: Got unexpected event \" << ev << \" in state=\" << st << \" with path=\" << path << std::endl;\n        return;\n    }\n    self->on_session_event(st);\n}\n\n}\n"
  },
  {
    "path": "src/zk/connection_zk.hpp",
    "content": "#pragma once\n\n#include <zk/config.hpp>\n\n#include <chrono>\n#include <memory>\n#include <mutex>\n#include <unordered_map>\n\n#include \"connection.hpp\"\n#include \"string_view.hpp\"\n\ntypedef struct _zhandle zhandle_t;\n\nnamespace zk\n{\n\n/// \\addtogroup Client\n/// \\{\n\nclass connection_zk final :\n        public connection\n{\npublic:\n    explicit connection_zk(const connection_params& params);\n\n    virtual ~connection_zk() noexcept;\n\n    virtual void close() override;\n\n    virtual zk::state state() const override;\n\n    virtual future<get_result> get(string_view path) override;\n\n    virtual future<watch_result> watch(string_view path) override;\n\n    virtual future<get_children_result> get_children(string_view path) override;\n\n    virtual future<watch_children_result> watch_children(string_view path) override;\n\n    virtual future<exists_result> exists(string_view path) override;\n\n    virtual future<watch_exists_result> watch_exists(string_view path) override;\n\n    virtual future<create_result> create(string_view   path,\n                                         const buffer& data,\n                                         const acl&    rules,\n                                         create_mode   mode\n                                        ) override;\n\n    virtual future<set_result> set(string_view path, const buffer& data, version check) override;\n\n    virtual future<void> erase(string_view path, version check) override;\n\n    virtual future<get_acl_result> get_acl(string_view path) const override;\n\n    virtual future<void> set_acl(string_view path, const acl& rules, acl_version check) override;\n\n    virtual future<multi_result> commit(multi_op&& txn) override;\n\n    virtual future<void> load_fence() override;\n\nprivate:\n    static void on_session_event_raw(ptr<zhandle_t>  handle,\n                                     int             ev_type,\n                                     int             state,\n                                     ptr<const char> path,\n                                     ptr<void>       watcher_ctx\n                                    ) noexcept;\n\n    using watch_function = void (*)(ptr<zhandle_t>, int type_in, int state_in, ptr<const char>, ptr<void>);\n\n    class watcher;\n\n    template <typename TResult>\n    class basic_watcher;\n\n    class data_watcher;\n\n    class child_watcher;\n\n    class exists_watcher;\n\n    /** Erase the watch tracker for the watch with the value \\a p.\n     *\n     *  \\returns \\c true if it was deleted (the watch should be delivered); \\c false if \\a p was not in the list.\n    **/\n    std::shared_ptr<watcher> try_extract_watch(ptr<const void> p);\n\n    static void deliver_watch(ptr<zhandle_t> zh, int type_in, int state_in, ptr<const char>, ptr<void> proms_in);\n\nprivate:\n    ptr<zhandle_t>                                                _handle;\n    std::unordered_map<ptr<const void>, std::shared_ptr<watcher>> _watches;\n    mutable std::mutex                                            _watches_protect;\n};\n\n/// \\}\n\n}\n"
  },
  {
    "path": "src/zk/error.cpp",
    "content": "#include \"error.hpp\"\n#include \"exceptions.hpp\"\n\n#include <sstream>\n#include <ostream>\n\n#include <zookeeper/zookeeper.h>\n\nnamespace zk\n{\n\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n// error_code                                                                                                         //\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\nstd::ostream& operator<<(std::ostream& os, const error_code& code)\n{\n    switch (code)\n    {\n    case error_code::ok: return os << \"ok\";\n    default:             return os << \"error_code(\" << static_cast<int>(code) << ')';\n    }\n}\n\nstd::string to_string(const error_code& code)\n{\n    std::ostringstream os;\n    os << code;\n    return os.str();\n}\n\nvoid throw_error(error_code code)\n{\n    switch (code)\n    {\n    case error_code::connection_loss:               zk::throw_exception( connection_loss() );\n    case error_code::marshalling_error:             zk::throw_exception( marshalling_error() );\n    case error_code::not_implemented:               zk::throw_exception( not_implemented(\"unspecified\") );\n    case error_code::invalid_arguments:             zk::throw_exception( invalid_arguments() );\n    case error_code::new_configuration_no_quorum:   zk::throw_exception( new_configuration_no_quorum() );\n    case error_code::reconfiguration_in_progress:   zk::throw_exception( reconfiguration_in_progress() );\n    case error_code::no_entry:                      zk::throw_exception( no_entry() );\n    case error_code::not_authorized:                zk::throw_exception( not_authorized() );\n    case error_code::version_mismatch:              zk::throw_exception( version_mismatch() );\n    case error_code::no_children_for_ephemerals:    zk::throw_exception( no_children_for_ephemerals() );\n    case error_code::entry_exists:                  zk::throw_exception( entry_exists() );\n    case error_code::not_empty:                     zk::throw_exception( not_empty() );\n    case error_code::session_expired:               zk::throw_exception( session_expired() );\n    case error_code::authentication_failed:         zk::throw_exception( authentication_failed() );\n    case error_code::closed:                        zk::throw_exception( closed() );\n    case error_code::read_only_connection:          zk::throw_exception( read_only_connection() );\n    case error_code::ephemeral_on_local_session:    zk::throw_exception( ephemeral_on_local_session() );\n    case error_code::reconfiguration_disabled:      zk::throw_exception( reconfiguration_disabled() );\n    case error_code::transaction_failed:            zk::throw_exception( transaction_failed(error_code::transaction_failed, 0U) );\n    default:                                        zk::throw_exception( error(code, \"unknown\") );\n    }\n}\n\nzk::exception_ptr get_exception_ptr_of(error_code code)\n{\n    try\n    {\n        throw_error(code);\n    }\n    catch (...)\n    {\n        return zk::current_exception();\n    }\n}\n\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n// error_category                                                                                                     //\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\nclass error_category_impl final : public std::error_category\n{\npublic:\n    virtual ptr<const char> name() const noexcept override { return \"zookeeper\"; }\n\n    virtual std::string message(int condition) const override;\n};\n\nstd::string error_category_impl::message(int condition) const\n{\n    return to_string(static_cast<error_code>(condition));\n}\n\nconst std::error_category& error_category()\n{\n    static error_category_impl instance;\n    return instance;\n}\n\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n// errors                                                                                                             //\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\nstatic std::string format_error(error_code code, const std::string& description)\n{\n    if (description.empty())\n        return to_string(code);\n    else\n        return to_string(code) + \": \" + description;\n}\n\nerror::error(error_code code, const std::string& description) :\n        std::system_error(static_cast<int>(code), error_category(), format_error(code, description)),\n        _code(code)\n{ }\n\nerror::~error() noexcept = default;\n\ntransport_error::transport_error(error_code code, const std::string& description) :\n        error(code, std::move(description))\n{ }\n\ntransport_error::~transport_error() noexcept = default;\n\nconnection_loss::connection_loss() :\n        transport_error(error_code::connection_loss, \"\")\n{ }\n\nconnection_loss::~connection_loss() noexcept = default;\n\nmarshalling_error::marshalling_error() :\n        transport_error(error_code::marshalling_error, \"\")\n{ }\n\nmarshalling_error::~marshalling_error() noexcept = default;\n\nnot_implemented::not_implemented(ptr<const char> op_name) :\n        error(error_code::not_implemented, std::string(\"Operation not implemented: \") + op_name)\n{ }\n\nnot_implemented::~not_implemented() noexcept = default;\n\ninvalid_arguments::invalid_arguments(error_code code, const std::string& description) :\n        error(code, description)\n{ }\n\ninvalid_arguments::invalid_arguments() :\n        invalid_arguments(error_code::invalid_arguments, \"\")\n{ }\n\ninvalid_arguments::~invalid_arguments() noexcept = default;\n\nauthentication_failed::authentication_failed() :\n        invalid_arguments(error_code::authentication_failed, \"\")\n{ }\n\nauthentication_failed::~authentication_failed() noexcept = default;\n\ninvalid_ensemble_state::invalid_ensemble_state(error_code code, const std::string& description) :\n        error(code, description)\n{ }\n\ninvalid_ensemble_state::~invalid_ensemble_state() noexcept = default;\n\nnew_configuration_no_quorum::new_configuration_no_quorum() :\n        invalid_ensemble_state(error_code::new_configuration_no_quorum, \"\")\n{ }\n\nnew_configuration_no_quorum::~new_configuration_no_quorum() noexcept = default;\n\nreconfiguration_in_progress::reconfiguration_in_progress() :\n        invalid_ensemble_state(error_code::reconfiguration_in_progress, \"\")\n{ }\n\nreconfiguration_in_progress::~reconfiguration_in_progress() noexcept = default;\n\nreconfiguration_disabled::reconfiguration_disabled() :\n        invalid_ensemble_state(error_code::reconfiguration_disabled, \"\")\n{ }\n\nreconfiguration_disabled::~reconfiguration_disabled() noexcept = default;\n\ninvalid_connection_state::invalid_connection_state(error_code code, const std::string& description) :\n        error(code, description)\n{ }\n\ninvalid_connection_state::~invalid_connection_state() noexcept = default;\n\nsession_expired::session_expired() :\n        invalid_connection_state(error_code::session_expired, \"\")\n{ }\n\nsession_expired::~session_expired() noexcept = default;\n\nnot_authorized::not_authorized() :\n        invalid_connection_state(error_code::not_authorized, \"\")\n{ }\n\nnot_authorized::~not_authorized() noexcept = default;\n\nclosed::closed() :\n        invalid_connection_state(error_code::closed, \"\")\n{ }\n\nclosed::~closed() noexcept = default;\n\nephemeral_on_local_session::ephemeral_on_local_session() :\n        invalid_connection_state(error_code::ephemeral_on_local_session, \"\")\n{ }\n\nephemeral_on_local_session::~ephemeral_on_local_session() noexcept = default;\n\nread_only_connection::read_only_connection() :\n        invalid_connection_state(error_code::read_only_connection, \"\")\n{ }\n\nread_only_connection::~read_only_connection() noexcept = default;\n\ncheck_failed::check_failed(error_code code, const std::string& description) :\n        error(code, description)\n{ }\n\ncheck_failed::~check_failed() noexcept = default;\n\nno_entry::no_entry() :\n        check_failed(error_code::no_entry, \"\")\n{ }\n\nno_entry::~no_entry() noexcept = default;\n\nentry_exists::entry_exists() :\n        check_failed(error_code::entry_exists, \"\")\n{ }\n\nentry_exists::~entry_exists() noexcept = default;\n\nnot_empty::not_empty() :\n        check_failed(error_code::not_empty, \"\")\n{ }\n\nnot_empty::~not_empty() noexcept = default;\n\nversion_mismatch::version_mismatch() :\n        check_failed(error_code::version_mismatch, \"\")\n{ }\n\nversion_mismatch::~version_mismatch() noexcept = default;\n\nno_children_for_ephemerals::no_children_for_ephemerals() :\n        check_failed(error_code::no_children_for_ephemerals, \"\")\n{ }\n\nno_children_for_ephemerals::~no_children_for_ephemerals() noexcept = default;\n\ntransaction_failed::transaction_failed(error_code underlying_cause, std::size_t op_index) :\n        check_failed(error_code::transaction_failed,\n                     std::string(\"Could not commit transaction due to \") + to_string(underlying_cause)\n                     + \" on operation \" + std::to_string(op_index)\n                    ),\n        _underlying_cause(underlying_cause),\n        _op_index(op_index)\n{ }\n\ntransaction_failed::~transaction_failed() noexcept = default;\n\n}\n"
  },
  {
    "path": "src/zk/error.hpp",
    "content": "#pragma once\n\n#include <zk/config.hpp>\n#include \"exceptions.hpp\"\n\n#include <iosfwd>\n#include <string>\n#include <system_error>\n\nnamespace zk\n{\n\n/// \\addtogroup Client\n/// \\{\n\n/// Code for all \\ref error types thrown by the client library.\n///\n/// \\see error\nenum class error_code : int\n{\n    ok                          =    0, //!< Never thrown.\n    connection_loss             =   -4, //!< Code for \\ref connection_loss.\n    marshalling_error           =   -5, //!< Code for \\ref marshalling_error.\n    not_implemented             =   -6, //!< Code for \\ref not_implemented.\n    invalid_arguments           =   -8, //!< Code for \\ref invalid_arguments.\n    new_configuration_no_quorum =  -13, //!< Code for \\ref new_configuration_no_quorum.\n    reconfiguration_in_progress =  -14, //!< Code for \\ref reconfiguration_in_progress.\n    no_entry                    = -101, //!< Code for \\ref no_entry.\n    not_authorized              = -102, //!< Code for \\ref not_authorized.\n    version_mismatch            = -103, //!< Code for \\ref version_mismatch.\n    no_children_for_ephemerals  = -108, //!< Code for \\ref no_children_for_ephemerals.\n    entry_exists                = -110, //!< Code for \\ref entry_exists.\n    not_empty                   = -111, //!< Code for \\ref not_empty.\n    session_expired             = -112, //!< Code for \\ref session_expired.\n    authentication_failed       = -115, //!< Code for \\ref authentication_failed.\n    closed                      = -116, //!< Code for \\ref closed.\n    read_only_connection        = -119, //!< Code for \\ref read_only_connection.\n    ephemeral_on_local_session  = -120, //!< Code for \\ref ephemeral_on_local_session.\n    reconfiguration_disabled    = -123, //!< Code for \\ref reconfiguration_disabled.\n    transaction_failed          = -199, //!< Code for \\ref transaction_failed.\n};\n\n/// Check if the provided \\a code is an exception code for a \\ref transport_error type of exception.\ninline constexpr bool is_transport_error(error_code code)\n{\n    return code == error_code::connection_loss\n        || code == error_code::marshalling_error;\n}\n\n/// Check if the provided \\a code is an exception code for a \\ref invalid_arguments type of exception.\ninline constexpr bool is_invalid_arguments(error_code code)\n{\n    return code == error_code::invalid_arguments\n        || code == error_code::authentication_failed;\n}\n\n/// Check if the provided \\a code is an exception code for a \\ref invalid_ensemble_state type of exception.\ninline constexpr bool is_invalid_ensemble_state(error_code code)\n{\n    return code == error_code::new_configuration_no_quorum\n        || code == error_code::reconfiguration_disabled\n        || code == error_code::reconfiguration_in_progress;\n}\n\n/// Check if the provided \\a code is an exception code for a \\ref invalid_connection_state type of exception.\ninline constexpr bool is_invalid_connection_state(error_code code)\n{\n    return code == error_code::closed\n        || code == error_code::ephemeral_on_local_session\n        || code == error_code::not_authorized\n        || code == error_code::read_only_connection\n        || code == error_code::session_expired;\n}\n\n/// Check if the provided \\a code is an exception code for a \\ref check_failed type of exception.\ninline constexpr bool is_check_failed(error_code code)\n{\n    return code == error_code::no_children_for_ephemerals\n        || code == error_code::no_entry\n        || code == error_code::entry_exists\n        || code == error_code::not_empty\n        || code == error_code::transaction_failed\n        || code == error_code::version_mismatch;\n}\n\nstd::ostream& operator<<(std::ostream&, const error_code&);\n\nstd::string to_string(const error_code&);\n\n/// Throw an exception for the given \\a code. This will use the proper refined exception type (such as \\ref no_entry) if\n/// one exists.\n[[noreturn]]\nvoid throw_error(error_code code);\n\n/// Get an \\c zk::exception_ptr containing an exception with the proper type for the given \\a code.\n///\n/// \\see throw_error\nzk::exception_ptr get_exception_ptr_of(error_code code);\n\n/// Get the \\c std::error_category capable of describing ZooKeeper-provided error codes.\n///\n/// \\see error\nconst std::error_category& error_category();\n\n/// Base error type for all errors raised by this library.\n///\n/// \\see error_code\nclass error :\n        public std::system_error\n{\npublic:\n    explicit error(error_code code, const std::string& description);\n\n    virtual ~error() noexcept;\n\n    /// The code representation of this error.\n    error_code code() const { return _code; }\n\nprivate:\n    error_code _code;\n};\n\n/// Base types for errors that occurred while transporting data across a network.\n///\n/// \\see is_transport_error\nclass transport_error :\n        public error\n{\npublic:\n    explicit transport_error(error_code code, const std::string& description);\n\n    virtual ~transport_error() noexcept;\n};\n\n/// Connection to the server has been lost before the attempted operation was verified as completed.\n///\n/// When thrown on an attempt to perform a modification, it is important to remember that it is possible to see this\n/// error and have the operation be a success. For example, a \\ref client::set operation can complete on the server, but\n/// the client can experience a \\ref connection_loss before the server replies with OK.\n///\n/// \\see session_expired\nclass connection_loss:\n        public transport_error\n{\npublic:\n    explicit connection_loss();\n\n    virtual ~connection_loss() noexcept;\n};\n\n/// An error occurred while marshalling data. The most common cause of this is exceeding the Jute buffer size -- meaning\n/// the transaction was too large (check the server logs for messages containing `\"Unreasonable length\"`). If that is\n/// the case, the solution is to change `jute.maxbuffer` on all servers (see the\n/// <a href=\"https://zookeeper.apache.org/doc/r3.4.10/zookeeperAdmin.html\">ZooKeeper Administrator's Guide</a> for more\n/// information and a stern warning). Another possible cause is the system running out of memory, but due to overcommit,\n/// OOM issues rarely manifest so cleanly.\nclass marshalling_error:\n        public transport_error\n{\npublic:\n    explicit marshalling_error();\n\n    virtual ~marshalling_error() noexcept;\n};\n\n/// Operation was attempted that was not implemented. If you happen to be writing a \\ref connection implementation, you\n/// are encouraged to raise this error in cases where you have not implemented an operation.\nclass not_implemented:\n        public error\n{\npublic:\n    /// \\param op_name the name of the attempted operation.\n    explicit not_implemented(ptr<const char> op_name);\n\n    virtual ~not_implemented() noexcept;\n};\n\n/// Arguments to an operation were invalid.\nclass invalid_arguments :\n        public error\n{\npublic:\n    explicit invalid_arguments(error_code code, const std::string& description);\n\n    explicit invalid_arguments();\n\n    virtual ~invalid_arguments() noexcept;\n};\n\n/// The server rejected the connection due to invalid authentication information. Depending on the authentication\n/// schemes enabled on the server, the authentication information sent might be explicit (in the case of the \\c \"digest\"\n/// scheme) or implicit (in the case of the \\c \"ip\" scheme). The connection must be recreated with the proper\n/// credentials to function.\n///\n/// \\see acl_rule\nclass authentication_failed:\n        public invalid_arguments\n{\npublic:\n    explicit authentication_failed();\n\n    virtual ~authentication_failed() noexcept;\n};\n\n/// Base exception for cases where the ensemble is in an invalid state to perform a given action. While errors such as\n/// \\ref connection_loss might also imply a bad ensemble (no quorum means no connection is possible), these errors are\n/// explicit rejections from the server.\nclass invalid_ensemble_state :\n        public error\n{\npublic:\n    explicit invalid_ensemble_state(error_code code, const std::string& description);\n\n    virtual ~invalid_ensemble_state() noexcept;\n};\n\n/// Raised when attempting an ensemble reconfiguration, but the proposed new ensemble would not be able to form quorum.\n/// This happens when not enough time has passed for potential new servers to sync with the leader. If the proposed new\n/// ensemble is up and running, the solution is usually to simply wait longer and attempt reconfiguration later.\nclass new_configuration_no_quorum:\n        public invalid_ensemble_state\n{\npublic:\n    explicit new_configuration_no_quorum();\n\n    virtual ~new_configuration_no_quorum() noexcept;\n};\n\n/// An attempt was made to reconfigure the ensemble, but there is already a reconfiguration in progress. Concurrent\n/// reconfiguration is not supported.\nclass reconfiguration_in_progress:\n        public invalid_ensemble_state\n{\npublic:\n    explicit reconfiguration_in_progress();\n\n    virtual ~reconfiguration_in_progress() noexcept;\n};\n\n/// The ensemble does not support reconfiguration.\nclass reconfiguration_disabled:\n        public invalid_ensemble_state\n{\npublic:\n    explicit reconfiguration_disabled();\n\n    virtual ~reconfiguration_disabled() noexcept;\n};\n\n/// Base type for errors generated because the connection is misconfigured.\nclass invalid_connection_state :\n        public error\n{\npublic:\n    explicit invalid_connection_state(error_code code, const std::string& description);\n\n    virtual ~invalid_connection_state() noexcept;\n};\n\n/// The client session has been ended by the server. When this occurs, all ephemerals associated with the session are\n/// deleted and standing watches are cancelled.\n///\n/// This error is somewhat easy to confuse with \\ref connection_loss, as they commonly happen around the same time. The\n/// key difference is a \\ref session_expired is an explicit error delivered from the server, whereas\n/// \\ref connection_loss is a client-related notification. A \\ref connection_loss is \\e usually followed by\n/// \\ref session_expired, but this is not guaranteed. If the client reconnects to a different server before the quorum\n/// removes the session, the connection can move back to \\ref state::connected without losing the session. The mechanism\n/// of resuming a session can happen even in cases of quorum loss, as session expiration requires a leader in order to\n/// proceed, so a client reconnecting soon enough after the ensemble forms quorum and elects a leader will resume the\n/// session, even if the quorum has been lost for days.\nclass session_expired:\n        public invalid_connection_state\n{\npublic:\n    explicit session_expired();\n\n    virtual ~session_expired() noexcept;\n};\n\n/// An attempt was made to read or write to a ZNode when the connection does not have permission to do.\nclass not_authorized:\n        public invalid_connection_state\n{\npublic:\n    explicit not_authorized();\n\n    virtual ~not_authorized() noexcept;\n};\n\n/// The connection is closed. This exception is delivered for all unfilled operations and watches when the connection is\n/// closing.\nclass closed:\n        public invalid_connection_state\n{\npublic:\n    explicit closed();\n\n    virtual ~closed() noexcept;\n};\n\n/// An attempt was made to create an ephemeral entry, but the connection has a local session.\n///\n/// \\see connection_params::local\nclass ephemeral_on_local_session:\n        public invalid_connection_state\n{\npublic:\n    explicit ephemeral_on_local_session();\n\n    virtual ~ephemeral_on_local_session() noexcept;\n};\n\n/// A write operation was attempted on a read-only connection.\n///\n/// \\see connection_params::read_only\nclass read_only_connection :\n        public invalid_connection_state\n{\npublic:\n    explicit read_only_connection();\n\n    virtual ~read_only_connection() noexcept;\n};\n\n/// Base exception for cases where a write operation was rolled back due to a failed check. There are more details in\n/// derived types such as \\ref no_entry or \\ref bad_version.\nclass check_failed :\n        public error\n{\npublic:\n    explicit check_failed(error_code code, const std::string& description);\n\n    virtual ~check_failed() noexcept;\n};\n\n/// Thrown from read operations when attempting to read a ZNode that does not exist.\nclass no_entry :\n        public check_failed\n{\npublic:\n    explicit no_entry();\n\n    virtual ~no_entry() noexcept;\n};\n\n/// Thrown when attempting to create a ZNode, but one already exists at the specified path.\nclass entry_exists :\n        public check_failed\n{\npublic:\n    explicit entry_exists();\n\n    virtual ~entry_exists() noexcept;\n};\n\n/// Thrown when attempting to erase a ZNode that has children.\nclass not_empty :\n        public check_failed\n{\npublic:\n    explicit not_empty();\n\n    virtual ~not_empty() noexcept;\n};\n\n/// Thrown from modification operations when a version check is specified and the value in the database does not match\n/// the expected.\nclass version_mismatch :\n        public check_failed\n{\npublic:\n    explicit version_mismatch();\n\n    virtual ~version_mismatch() noexcept;\n};\n\n/// Ephemeral ZNodes cannot have children.\nclass no_children_for_ephemerals :\n        public check_failed\n{\npublic:\n    explicit no_children_for_ephemerals();\n\n    virtual ~no_children_for_ephemerals() noexcept;\n};\n\n/// Thrown from \\ref client::commit when a transaction cannot be committed to the system. Check the\n/// \\ref underlying_cause to see the specific error and \\ref failed_op_index to see what operation failed.\nclass transaction_failed :\n        public check_failed\n{\npublic:\n    explicit transaction_failed(error_code code, std::size_t op_index);\n\n    virtual ~transaction_failed() noexcept;\n\n    /// The underlying cause that caused this transaction to be aborted. For example, if a \\ref op::set operation is\n    /// attempted on a node that does not exist, this will be \\ref error_code::no_entry.\n    error_code underlying_cause() const { return _underlying_cause; }\n\n    /// The transaction index which caused the error (0 indexed). If the 3rd operation in the \\ref multi_op could not be\n    /// committed, this will be 2.\n    std::size_t failed_op_index() const { return _op_index; }\n\nprivate:\n    error_code  _underlying_cause;\n    std::size_t _op_index;\n};\n\n/// \\}\n\n}\n\nnamespace std\n{\n\ntemplate <>\nstruct is_error_code_enum<zk::error_code> :\n        true_type\n{\n};\n\n}\n"
  },
  {
    "path": "src/zk/error_tests.cpp",
    "content": "#include <zk/tests/test.hpp>\n\n#include \"error.hpp\"\n\nnamespace zk\n{\n\nstatic error_code all_error_codes[] =\n    {\n        error_code::connection_loss,\n        error_code::marshalling_error,\n        error_code::not_implemented,\n        error_code::invalid_arguments,\n        error_code::new_configuration_no_quorum,\n        error_code::reconfiguration_in_progress,\n        error_code::no_entry,\n        error_code::not_authorized,\n        error_code::version_mismatch,\n        error_code::no_children_for_ephemerals,\n        error_code::entry_exists,\n        error_code::not_empty,\n        error_code::session_expired,\n        error_code::authentication_failed,\n        error_code::closed,\n        error_code::read_only_connection,\n        error_code::ephemeral_on_local_session,\n        error_code::reconfiguration_disabled,\n        error_code::transaction_failed,\n    };\n\nGTEST_TEST(error_code_tests, throwing)\n{\n    for (error_code code : all_error_codes)\n    {\n        try\n        {\n            try\n            {\n                throw_error(code);\n            }\n            catch (const check_failed& ex)\n            {\n                CHECK_EQ(code, ex.code());\n                CHECK_TRUE(is_check_failed(code)) << code;\n                throw;\n            }\n            catch (const invalid_arguments& ex)\n            {\n                CHECK_EQ(code, ex.code());\n                CHECK_TRUE(is_invalid_arguments(code)) << code;\n                throw;\n            }\n            catch (const invalid_connection_state& ex)\n            {\n                CHECK_EQ(code, ex.code());\n                CHECK_TRUE(is_invalid_connection_state(code)) << code;\n                throw;\n            }\n            catch (const invalid_ensemble_state& ex)\n            {\n                CHECK_EQ(code, ex.code());\n                CHECK_TRUE(is_invalid_ensemble_state(code)) << code;\n                throw;\n            }\n            catch (const not_implemented& ex)\n            {\n                CHECK_EQ(code, ex.code());\n                CHECK_EQ(error_code::not_implemented, ex.code());\n                throw;\n            }\n            catch (const transport_error& ex)\n            {\n                CHECK_EQ(code, ex.code());\n                throw;\n            }\n            catch (const error& ex)\n            {\n                // not a real error, so it will come across as unknown\n                CHECK_EQ(error_code::ok, ex.code());\n                throw;\n            }\n            CHECK_FAIL() << \"Should not have reached this point\";\n        }\n        catch (const error& ex)\n        {\n            CHECK_EQ(code, ex.code());\n        }\n    }\n}\n\nGTEST_TEST(error_code_tests, to_string_bogus_code)\n{\n    CHECK_EQ(\"error_code(19)\", to_string(static_cast<error_code>(19)));\n}\n\n}\n"
  },
  {
    "path": "src/zk/exceptions.cpp",
    "content": "#include \"exceptions.hpp\"\n\nnamespace zk\n{\n\nexception_ptr current_exception() noexcept\n{\n#if ZKPP_FUTURE_USE_BOOST\n    return boost::current_exception();\n#else\n    return std::current_exception();\n#endif\n}\n\n}\n"
  },
  {
    "path": "src/zk/exceptions.hpp",
    "content": "/** \\file\n *  Controls the throwing of exceptions and import of \\c exception_ptr types and \\c current_exception implementation.\n *  These are probably \\c std::exception_ptr and \\c std::current_exception(), but if boost future is used it will be\n *  boost's implementation.\n**/\n#pragma once\n\n#include <zk/future.hpp>\n\n#if ZKPP_FUTURE_USE_BOOST\n#include <boost/exception_ptr.hpp>\n#else\n#include <exception>\n#endif\n\nnamespace zk\n{\n\n/// \\addtogroup Client\n/// \\{\n\n\n#if ZKPP_FUTURE_USE_BOOST\n\nusing exception_ptr = boost::exception_ptr;\nexception_ptr current_exception() noexcept;\n\ntemplate<typename T>\n[[noreturn]] inline void throw_exception(T const & e)\n{\n\tthrow boost::enable_current_exception(e);\n}\n\n\n#else\n\nusing exception_ptr = std::exception_ptr;\nexception_ptr current_exception() noexcept;\n\ntemplate<typename T>\n[[noreturn]] inline void throw_exception(T const & e)\n{\n\tthrow e;\n}\n\n#endif\n\n/// \\}\n\n}\n\n"
  },
  {
    "path": "src/zk/forwards.hpp",
    "content": "#pragma once\n\n#include <zk/config.hpp>\n\nnamespace zk\n{\n\nclass acl;\nclass acl_rule;\nstruct acl_version;\nstruct child_version;\nclass client;\nclass connection;\nclass connection_params;\nenum class create_mode : unsigned int;\nclass create_result;\nclass error;\nclass event;\nclass exists_result;\nenum class error_code : int;\nenum class event_type : int;\nclass get_acl_result;\nclass get_children_result;\nclass get_result;\nclass multi_result;\nclass multi_op;\nclass op;\nenum class op_type : int;\nenum class permission : unsigned int;\nclass set_result;\nenum class state : int;\nstruct transaction_id;\nstruct version;\nclass watch_children_result;\nclass watch_exists_result;\nclass watch_result;\n\n}\n"
  },
  {
    "path": "src/zk/future.hpp",
    "content": "/** \\file\n *  Controls the import of \\c future and \\c promise types. These are probably \\c std::future and \\c std::promise, but\n *  can be your custom types (as long as they behave in a manner similar enough to \\c std::future and \\c std::promise).\n**/\n#pragma once\n\n#include <zk/config.hpp>\n\n/** \\addtogroup Client\n *  \\{\n**/\n\n/** \\def ZKPP_FUTURE_USE_STD_EXPERIMENTAL\n *  Set this to 1 to use \\c std::experimental::future and \\c std::experimental::promise as the backing types for\n *  \\c zk::future and \\c zk::promise.\n**/\n#ifndef ZKPP_FUTURE_USE_STD_EXPERIMENTAL\n#   define ZKPP_FUTURE_USE_STD_EXPERIMENTAL 0\n#endif\n\n/** \\def ZKPP_FUTURE_USE_BOOST\n *  Set this to 1 to use \\c boost::future and \\c boost::promise as the backing types for\n *  \\c zk::future and \\c zk::promise.\n**/\n#ifndef ZKPP_FUTURE_USE_BOOST\n#   define ZKPP_FUTURE_USE_BOOST 0\n#endif\n\n/** \\def ZKPP_FUTURE_USE_CUSTOM\n *  Set this to 1 to use custom definitions of \\c zk::future and \\c zk::promise. If this is set, you must also set\n *  \\c ZKPP_FUTURE_TEMPLATE, \\c ZKPP_PROMISE_TEMPLATE, and \\c ZKPP_FUTURE_INCLUDE.\n *\n *  \\def ZKPP_FUTURE_TEMPLATE\n *  The template to use for \\c zk::future. By default, this is \\c std::future.\n *\n *  \\def ZKPP_PROMISE_TEMPLATE\n *  The template to use for \\c zk::promise. This should be highly related to \\c ZKPP_FUTURE_TEMPLATE\n *\n *  \\def ZKPP_ASYNC_TEMPLATE\n *  The template to use for \\c zk::async. This should be highly related to \\c ZKPP_FUTURE_TEMPLATE and usually mapped\n *  to std::async or boost::async.\n *\n *  \\def ZKPP_LAUNCH_ENUM\n *  The enum to use for \\c zk::async launch policy. This should be highly related to \\c ZKPP_FUTURE_TEMPLATE and usually mapped\n *  to std::launch::async or boost::launch::async.\n *\n *  \\def ZKPP_FUTURE_INCLUDE\n *  The file to include to get the implementation for \\c future and \\c promise. If you define \\c ZKPP_FUTURE_TEMPLATE\n *  and \\c ZKPP_PROMISE_TEMPLATE, you must also define this.\n**/\n#ifndef ZKPP_FUTURE_USE_CUSTOM\n#   define ZKPP_FUTURE_USE_CUSTOM 0\n#endif\n\n/** \\def ZKPP_FUTURE_USE_STD\n *  Set this to 1 to use \\c std::future and \\c std::promise as the backing types for \\c zk::future and \\c zk::promise.\n *  This is the default behavior.\n**/\n#ifndef ZKPP_FUTURE_USE_STD\n#   if ZKPP_FUTURE_USE_BOOST || ZKPP_FUTURE_USE_STD_EXPERIMENTAL || ZKPP_FUTURE_USE_CUSTOM\n#       define ZKPP_FUTURE_USE_STD 0\n#   else\n#       define ZKPP_FUTURE_USE_STD 1\n#   endif\n#endif\n\n#if ZKPP_FUTURE_USE_STD\n#   define ZKPP_FUTURE_INCLUDE   <future>\n#   define ZKPP_FUTURE_TEMPLATE  std::future\n#   define ZKPP_PROMISE_TEMPLATE std::promise\n#   define ZKPP_ASYNC_TEMPLATE   std::async\n#   define ZKPP_LAUNCH_ENUM      std::launch\n#elif ZKPP_FUTURE_USE_STD_EXPERIMENTAL\n#   define ZKPP_FUTURE_INCLUDE   <experimental/future>\n#   define ZKPP_FUTURE_TEMPLATE  std::experimental::future\n#   define ZKPP_PROMISE_TEMPLATE std::experimental::promise\n#   define ZKPP_ASYNC_TEMPLATE   std::async\n#   define ZKPP_LAUNCH_ENUM      std::launch\n#elif ZKPP_FUTURE_USE_BOOST\n#   define BOOST_THREAD_PROVIDES_FUTURE\n#   define BOOST_THREAD_PROVIDES_FUTURE_CONTINUATION\n#   define BOOST_THREAD_PROVIDES_FUTURE_WHEN_ALL_WHEN_ANY\n#   define ZKPP_FUTURE_INCLUDE   <boost/thread/future.hpp>\n#   define ZKPP_FUTURE_TEMPLATE  boost::future\n#   define ZKPP_PROMISE_TEMPLATE boost::promise\n#   define ZKPP_ASYNC_TEMPLATE   boost::async\n#   define ZKPP_LAUNCH_ENUM      boost::launch\n#elif ZKPP_FUTURE_USE_CUSTOM\n#   if !defined ZKPP_FUTURE_TEMPLATE || !defined ZKPP_PROMISE_TEMPLATE || !defined ZKPP_FUTURE_INCLUDE\n#       error \"When ZKPP_FUTURE_USE_CUSTOM is set, you must also define ZKPP_FUTURE_TEMPLATE, ZKPP_PROMISE_TEMPLATE,\"\n#       error \"and ZKPP_FUTURE_INCLUDE.\"\n#   endif\n#else\n#   error \"Unknown type to use for zk::future and zk::promise\"\n#endif\n\n#include ZKPP_FUTURE_INCLUDE\n\n/** \\} **/\n\nnamespace zk\n{\n\n/** \\addtogroup Client\n *  \\{\n**/\n\ntemplate <typename T>\nusing future = ZKPP_FUTURE_TEMPLATE<T>;\n\ntemplate <typename T>\nusing promise = ZKPP_PROMISE_TEMPLATE<T>;\n\nusing ZKPP_LAUNCH_ENUM;\nusing ZKPP_ASYNC_TEMPLATE;\n\n/** \\} **/\n\n}\n"
  },
  {
    "path": "src/zk/multi.cpp",
    "content": "#include \"multi.hpp\"\n#include \"exceptions.hpp\"\n\n#include <new>\n#include <ostream>\n#include <sstream>\n#include <stdexcept>\n\nnamespace zk\n{\n\ntemplate <typename T>\nstatic std::string to_string_generic(const T& self)\n{\n    std::ostringstream os;\n    os << self;\n    return os.str();\n}\n\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n// op_type                                                                                                            //\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\nstd::ostream& operator<<(std::ostream& os, const op_type& self)\n{\n    switch (self)\n    {\n    case op_type::check:  return os << \"check\";\n    case op_type::create: return os << \"create\";\n    case op_type::erase:  return os << \"erase\";\n    case op_type::set:    return os << \"set\";\n    default:              return os << \"op_type(\" << static_cast<int>(self) << ')';\n    }\n}\n\nstd::string to_string(const op_type& self)\n{\n    return to_string_generic(self);\n}\n\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n// op                                                                                                                 //\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\nop::op(any_data&& src) noexcept :\n        _storage(std::move(src))\n{ }\n\nop::op(const op& src) = default;\n\nop::op(op&& src) noexcept :\n        _storage(std::move(src._storage))\n{ }\n\nop::~op() noexcept = default;\n\nop_type op::type() const\n{\n    return std::visit([] (const auto& x) { return x.type(); }, _storage);\n}\n\ntemplate <typename T>\nconst T& op::as(ptr<const char> operation) const\n{\n    try\n    {\n        return std::get<T>(_storage);\n    }\n    catch (const std::bad_variant_access&)\n    {\n        zk::throw_exception(std::logic_error( std::string(\"Invalid op type for op::\")\n                              + std::string(operation)\n                              + std::string(\": \")\n                              + to_string(type())\n                              ));\n    }\n}\n\n// check\n\nop::check_data::check_data(std::string path, version check_) :\n        path(std::move(path)),\n        check(check_)\n{ }\n\nstd::ostream& operator<<(std::ostream& os, const op::check_data& self)\n{\n    os << '{' << self.path;\n    os << ' ' << self.check;\n    return os << '}';\n}\n\nop op::check(std::string path, version check_)\n{\n    return op(check_data(std::move(path), check_));\n}\n\nconst op::check_data& op::as_check() const\n{\n    return as<check_data>(\"as_check\");\n}\n\n// create\n\nop::create_data::create_data(std::string path, buffer data, acl rules, create_mode mode) :\n        path(std::move(path)),\n        data(std::move(data)),\n        rules(std::move(rules)),\n        mode(mode)\n{ }\n\nstd::ostream& operator<<(std::ostream& os, const op::create_data& self)\n{\n    os << '{' << self.path;\n    os << ' ' << self.mode;\n    os << ' ' << self.rules;\n    return os << '}';\n}\n\nop op::create(std::string path, buffer data, acl rules, create_mode mode)\n{\n    return op(create_data(std::move(path), std::move(data), std::move(rules), mode));\n}\n\nop op::create(std::string path, buffer data, create_mode mode)\n{\n    return create(std::move(path), std::move(data), acls::open_unsafe(), mode);\n}\n\nconst op::create_data& op::as_create() const\n{\n    return as<create_data>(\"as_create\");\n}\n\n// erase\n\nop::erase_data::erase_data(std::string path, version check) :\n        path(std::move(path)),\n        check(check)\n{ }\n\nstd::ostream& operator<<(std::ostream& os, const op::erase_data& self)\n{\n    os << '{' << self.path;\n    os << ' ' << self.check;\n    return os << '}';\n}\n\nop op::erase(std::string path, version check)\n{\n    return op(erase_data(std::move(path), check));\n}\n\nconst op::erase_data& op::as_erase() const\n{\n    return as<erase_data>(\"as_erase\");\n}\n\n// set\n\nop::set_data::set_data(std::string path, buffer data, version check) :\n        path(std::move(path)),\n        data(std::move(data)),\n        check(check)\n{ }\n\nstd::ostream& operator<<(std::ostream& os, const op::set_data& self)\n{\n    os << '{' << self.path;\n    os << ' ' << self.check;\n    return os << '}';\n}\n\nop op::set(std::string path, buffer data, version check)\n{\n    return op(set_data(std::move(path), std::move(data), check));\n}\n\nconst op::set_data& op::as_set() const\n{\n    return as<set_data>(\"as_set\");\n}\n\n// generic\n\nstd::ostream& operator<<(std::ostream& os, const op& self)\n{\n    os << self.type();\n    std::visit([&] (const auto& x) { os << x; }, self._storage);\n    return os;\n}\n\nstd::string to_string(const op& self)\n{\n    std::ostringstream os;\n    os << self;\n    return os.str();\n}\n\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n// multi_op                                                                                                           //\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\nmulti_op::multi_op(std::vector<op> ops) noexcept :\n        _ops(std::move(ops))\n{ }\n\nmulti_op::~multi_op() noexcept\n{ }\n\nstd::ostream& operator<<(std::ostream& os, const multi_op& self)\n{\n    os << '[';\n    bool first = true;\n    for (const auto& x : self)\n    {\n        if (first)\n            first = false;\n        else\n            os << \", \";\n        os << x;\n    }\n    return os << ']';\n}\n\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n// multi_result                                                                                                       //\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\nmulti_result::part::part(op_type type, std::nullptr_t) noexcept :\n        _type(type),\n        _storage(std::monostate())\n{ }\n\nmulti_result::part::part(create_result res) noexcept :\n        _type(op_type::create),\n        _storage(std::move(res))\n{ }\n\nmulti_result::part::part(set_result res) noexcept :\n        _type(op_type::set),\n        _storage(std::move(res))\n{ }\n\nmulti_result::part::part(const part& src) = default;\n\nmulti_result::part::part(part&& src) noexcept :\n        _type(src._type),\n        _storage(std::move(src._storage))\n{ }\n\nmulti_result::part::~part() noexcept = default;\n\ntemplate <typename T>\nconst T& multi_result::part::as(ptr<const char> operation) const\n{\n    try\n    {\n        return std::get<T>(_storage);\n    }\n    catch (const std::bad_variant_access&)\n    {\n        zk::throw_exception(std::logic_error( std::string(\"Invalid op type for multi_result::\")\n                              + std::string(operation)\n                              + std::string(\": \")\n                              + to_string(type())\n                              ));\n    }\n}\n\nconst create_result& multi_result::part::as_create() const\n{\n    return as<create_result>(\"as_create\");\n}\n\nconst set_result& multi_result::part::as_set() const\n{\n    return as<set_result>(\"as_set\");\n}\n\nmulti_result::multi_result(std::vector<part> parts) noexcept :\n        _parts(std::move(parts))\n{ }\n\nmulti_result::~multi_result() noexcept\n{ }\n\nstd::ostream& operator<<(std::ostream& os, const multi_result::part& self)\n{\n    switch (self.type())\n    {\n        case op_type::create: return os << self.as_create();\n        case op_type::set:    return os << self.as_set();\n        default:              return os << self.type() << \"_result{}\";\n    }\n}\n\nstd::ostream& operator<<(std::ostream& os, const multi_result& self)\n{\n    os << '[';\n    bool first = true;\n    for (const auto& x : self)\n    {\n        if (first)\n            first = false;\n        else\n            os << \", \";\n        os << x;\n    }\n    return os << ']';\n}\n\nstd::string to_string(const multi_result::part& self)\n{\n    return to_string_generic(self);\n}\n\nstd::string to_string(const multi_result& self)\n{\n    return to_string_generic(self);\n}\n\n}\n"
  },
  {
    "path": "src/zk/multi.hpp",
    "content": "#pragma once\n\n#include <zk/config.hpp>\n\n#include <initializer_list>\n#include <iosfwd>\n#include <string>\n#include <variant>\n#include <vector>\n\n#include \"acl.hpp\"\n#include \"buffer.hpp\"\n#include \"forwards.hpp\"\n#include \"results.hpp\"\n#include \"types.hpp\"\n\nnamespace zk\n{\n\n/// \\addtogroup Client\n/// \\{\n\n/// Describes the type of an \\ref op.\nenum class op_type : int\n{\n    check,  //!< \\ref op::check\n    create, //!< \\ref op::create\n    erase,  //!< \\ref op::erase\n    set,    //!< \\ref op::set\n};\n\nstd::ostream& operator<<(std::ostream&, const op_type&);\n\nstd::string to_string(const op_type&);\n\n/// Represents a single operation of a \\ref multi_op.\nclass op final\n{\npublic:\n    /// Data for a \\ref op::check operation.\n    struct check_data\n    {\n        std::string path;\n        version     check;\n\n        explicit check_data(std::string path, version check);\n\n        op_type type() const { return op_type::check; }\n    };\n\n    /// Check that the given \\a path exists with the provided version \\a check (which can be \\c version::any).\n    static op check(std::string path, version check = version::any());\n\n    /// Data for a \\ref op::create operation.\n    struct create_data\n    {\n        std::string path;\n        buffer      data;\n        acl         rules;\n        create_mode mode;\n\n        explicit create_data(std::string path, buffer data, acl rules, create_mode mode);\n\n        op_type type() const { return op_type::create; }\n    };\n\n    /// \\{\n    /// Create a new entry at the given \\a path with the \\a data.\n    ///\n    /// \\see client::create\n    static op create(std::string path, buffer data, acl rules, create_mode mode = create_mode::normal);\n    static op create(std::string path, buffer data, create_mode mode = create_mode::normal);\n    /// \\}\n\n    /// Data for a \\ref op::erase operation.\n    struct erase_data\n    {\n        std::string path;\n        version     check;\n\n        explicit erase_data(std::string path, version check);\n\n        op_type type() const { return op_type::erase; }\n    };\n\n    /// Delete the entry at the given \\a path if it matches the version \\a check.\n    ///\n    /// \\see client::erase\n    static op erase(std::string path, version check = version::any());\n\n    /// Data for a \\ref op::set operation.\n    struct set_data\n    {\n        std::string path;\n        buffer      data;\n        version     check;\n\n        explicit set_data(std::string path, buffer data, version check);\n\n        op_type type() const { return op_type::set; }\n    };\n\n    /// Set the \\a data for the entry at \\a path if it matches the version \\a check.\n    ///\n    /// \\see client::set\n    static op set(std::string path, buffer data, version check = version::any());\n\npublic:\n    op(const op&);\n    op(op&&) noexcept;\n\n    op& operator=(const op&) = delete;\n    op& operator=(op&&) = delete;\n\n    ~op() noexcept;\n\n    /// Get the underlying type of this operation.\n    op_type type() const;\n\n    /// Get the check-specific data.\n    ///\n    /// \\throws std::logic_error if the \\ref type is not \\ref op_type::check.\n    const check_data& as_check() const;\n\n    /// Get the create-specific data.\n    ///\n    /// \\throws std::logic_error if the \\ref type is not \\ref op_type::create.\n    const create_data& as_create() const;\n\n    /// Get the erase-specific data.\n    ///\n    /// \\throws std::logic_error if the \\ref type is not \\ref op_type::erase.\n    const erase_data& as_erase() const;\n\n    /// Get the set-specific data.\n    ///\n    /// \\throws std::logic_error if the \\ref type is not \\ref op_type::set.\n    const set_data& as_set() const;\n\nprivate:\n    using any_data = std::variant<check_data, create_data, erase_data, set_data>;\n\n    explicit op(any_data&&) noexcept;\n\n    template <typename T>\n    const T& as(ptr<const char> operation) const;\n\n    friend std::ostream& operator<<(std::ostream&, const op&);\n\nprivate:\n    any_data _storage;\n};\n\nstd::ostream& operator<<(std::ostream&, const op&);\n\nstd::string to_string(const op&);\n\n/// A collection of operations that will be performed atomically.\n///\n/// \\see client::commit\n/// \\see op\nclass multi_op final\n{\npublic:\n    using iterator       = std::vector<op>::iterator;\n    using const_iterator = std::vector<op>::const_iterator;\n    using size_type      = std::vector<op>::size_type;\n\npublic:\n    /// Create an empty operation set.\n    multi_op() noexcept\n    { }\n\n    /// Create an instance from the provided \\a ops.\n    multi_op(std::vector<op> ops) noexcept;\n\n    /// Create an instance from the provided \\a ops.\n    multi_op(std::initializer_list<op> ops) :\n            multi_op(std::vector<op>(ops))\n    { }\n\n    ~multi_op() noexcept;\n\n    /// The number of operations in this transaction bundle.\n    size_type size() const { return _ops.size(); }\n\n    /// \\{\n    /// Get the operation at the given \\a idx.\n    const op& operator[](size_type idx) const { return _ops[idx]; }\n    op&       operator[](size_type idx)       { return _ops[idx]; }\n    /// \\}\n\n    /// \\{\n    /// Get the operation at the given \\a idx.\n    ///\n    /// \\throws std::out_of_range if \\a idx is larger than \\ref size.\n    const op& at(size_type idx) const { return _ops.at(idx); }\n    op&       at(size_type idx)       { return _ops.at(idx); }\n    /// \\}\n\n    /// \\{\n    /// Get an iterator to the beginning of the operation list.\n    iterator begin()              { return _ops.begin(); }\n    const_iterator begin() const  { return _ops.begin(); }\n    const_iterator cbegin() const { return _ops.begin(); }\n    /// \\}\n\n    /// \\{\n    /// Get an iterator to the end of the operation list.\n    iterator end()              { return _ops.end(); }\n    const_iterator end() const  { return _ops.end(); }\n    const_iterator cend() const { return _ops.end(); }\n    /// \\}\n\n    /// Increase the reserved memory block so it can store at least \\a capacity operations without reallocating.\n    void reserve(size_type capacity) { _ops.reserve(capacity); }\n\n    /// Construct an operation emplace on the end of the list using \\a args.\n    ///\n    /// \\see push_back\n    template <typename... TArgs>\n    void emplace_back(TArgs&&... args)\n    {\n        _ops.emplace_back(std::forward<TArgs>(args)...);\n    }\n\n    /// \\{\n    /// Add the operation \\a x to the end of this list.\n    void push_back(op&&      x) { emplace_back(std::move(x)); }\n    void push_back(const op& x) { emplace_back(x); }\n    /// \\}\n\nprivate:\n    std::vector<op> _ops;\n};\n\nstd::ostream& operator<<(std::ostream&, const multi_op&);\n\nstd::string to_string(const multi_op&);\n\n/// The result of a successful \\ref client::commit operation.\nclass multi_result final\n{\npublic:\n    /// A part of a result. The behavior depends on the type of \\ref op provided to the original transaction.\n    class part final\n    {\n    public:\n        explicit part(op_type, std::nullptr_t) noexcept;\n        explicit part(create_result) noexcept;\n        explicit part(set_result) noexcept;\n\n        part(const part&);\n        part(part&&) noexcept;\n\n        part& operator=(const part&) = delete;\n        part& operator=(part&&) = delete;\n\n        ~part() noexcept;\n\n        /// The \\ref op_type of the \\ref op that caused this result.\n        op_type type() const { return _type; }\n\n        /// Get the create-specific result data.\n        ///\n        /// \\throws std::logic_error if the \\ref type is not \\ref op_type::set.\n        const create_result& as_create() const;\n\n        /// Get the set-specific result data.\n        ///\n        /// \\throws std::logic_error if the \\ref type is not \\ref op_type::set.\n        const set_result& as_set() const;\n\n    private:\n        using any_result = std::variant<std::monostate, create_result, set_result>;\n\n        template <typename T>\n        const T& as(ptr<const char> operation) const;\n\n    private:\n        op_type    _type;\n        any_result _storage;\n    };\n\n    using iterator       = std::vector<part>::iterator;\n    using const_iterator = std::vector<part>::const_iterator;\n    using size_type      = std::vector<part>::size_type;\n\npublic:\n    multi_result() noexcept\n    { }\n\n    multi_result(std::vector<part> parts) noexcept;\n\n    multi_result(multi_result&&) = default;\n    multi_result & operator=(multi_result&&) = default;\n\n    ~multi_result() noexcept;\n\n    /// The number of results in this transaction bundle.\n    size_type size() const { return _parts.size(); }\n\n    /// \\{\n    /// Get the result at the given \\a idx.\n    ///\n    /// \\throws std::out_of_range if \\a idx is larger than \\a size.\n    part&       operator[](size_type idx)       { return _parts[idx]; }\n    const part& operator[](size_type idx) const { return _parts[idx]; }\n    /// \\}\n\n    /// \\{\n    /// Get the result at the given \\a idx.\n    ///\n    /// \\throws std::out_of_range if \\a idx is larger than \\ref size.\n    const part& at(size_type idx) const { return _parts.at(idx); }\n    part&       at(size_type idx)       { return _parts.at(idx); }\n    /// \\}\n\n    /// \\{\n    /// Get an iterator to the beginning of the result list.\n    iterator begin()              { return _parts.begin(); }\n    const_iterator begin() const  { return _parts.begin(); }\n    const_iterator cbegin() const { return _parts.begin(); }\n    /// \\}\n\n    /// \\{\n    /// Get an iterator to the end of the result list.\n    iterator end()              { return _parts.end(); }\n    const_iterator end() const  { return _parts.end(); }\n    const_iterator cend() const { return _parts.end(); }\n    /// \\}\n\n    /// Increase the reserved memory block so it can store at least \\a capacity results without reallocating.\n    void reserve(size_type capacity) { _parts.reserve(capacity); }\n\n    /// Construct a result emplace on the end of the list using \\a args.\n    ///\n    /// \\see push_back\n    template <typename... TArgs>\n    void emplace_back(TArgs&&... args)\n    {\n        _parts.emplace_back(std::forward<TArgs>(args)...);\n    }\n\n    /// \\{\n    /// Add the operation \\a x to the end of this list.\n    void push_back(part&& x)      { emplace_back(std::move(x)); }\n    void push_back(const part& x) { emplace_back(x); }\n    /// \\}\n\nprivate:\n    std::vector<part> _parts;\n};\n\nstd::ostream& operator<<(std::ostream&, const multi_result::part&);\nstd::ostream& operator<<(std::ostream&, const multi_result&);\n\nstd::string to_string(const multi_result::part&);\nstd::string to_string(const multi_result&);\n\n/** \\} **/\n\n}\n"
  },
  {
    "path": "src/zk/multi_tests.cpp",
    "content": "#include <zk/server/server_tests.hpp>\n\n#include <algorithm>\n#include <chrono>\n#include <cstring>\n#include <iostream>\n\n#include \"client.hpp\"\n#include \"error.hpp\"\n#include \"multi.hpp\"\n#include \"string_view.hpp\"\n\nnamespace zk\n{\n\nclass multi_tests :\n        public server::server_fixture\n{ };\n\nstatic buffer buffer_from(string_view str)\n{\n    return buffer(str.data(), str.data() + str.size());\n}\n\nGTEST_TEST_F(multi_tests, commit_no_fail)\n{\n    client c = get_connected_client();\n\n    auto node1 = c.create(\"/test-\", buffer_from(\"Going to delete\"), create_mode::sequential).get().name();\n    auto node2 = c.create(\"/test-\", buffer_from(\"First data\"), create_mode::sequential).get().name();\n\n    multi_op txn =\n    {\n        op::check(\"/\"),\n        op::create(\"/test-\", buffer_from(\"1\"), create_mode::sequential),\n        op::erase(node1),\n        op::set(node2, buffer_from(\"Second data\")),\n    };\n    multi_result res = c.commit(txn).get();\n    CHECK_EQ(4U, res.size());\n\n    // node1 should be erased\n    CHECK_FALSE(c.exists(node1).get());\n\n    auto node2_contents = c.get(node2).get().data();\n    CHECK_TRUE(node2_contents == buffer_from(\"Second data\"));\n\n    // Create a child of the node created in the multi\n    auto name_to_make = res[1].as_create().name() + \"/some-subnode\";\n    c.create(name_to_make, buffer_from(\"sub\")).get();\n}\n\nGTEST_TEST_F(multi_tests, commit_fail_check)\n{\n    client c = get_connected_client();\n\n    auto base = c.create(\"/test-\", buffer_from(\"Base\"), create_mode::sequential).get().name();\n    multi_op txn =\n    {\n        op::create(base + \"/a\", buffer_from(\"a\")),\n        op::check(base + \"/does-not-exist\"),\n        op::create(base + \"/b\", buffer_from(\"b\")),\n    };\n\n    try\n    {\n        c.commit(txn).get();\n    }\n    catch (const transaction_failed& ex)\n    {\n        CHECK_EQ(error_code::no_entry, ex.underlying_cause());\n        CHECK_EQ(1U, ex.failed_op_index());\n    }\n}\n\n}\n"
  },
  {
    "path": "src/zk/optional.hpp",
    "content": "/// \\file\n/// Imports of \\c optional and \\c nullopt_t types, as well as the \\c nullopt \\c constexpr. These are \\c std::optional,\n/// \\c std::nullopt_t, and \\c std::nullopt, respectively.\n#pragma once\n\n#include <zk/config.hpp>\n\n#include <optional>\n\nnamespace zk\n{\n\n/// \\addtogroup Client\n/// \\{\n\ntemplate <typename T>\nusing optional = std::optional<T>;\n\nusing nullopt_t = std::nullopt_t;\n\nusing std::nullopt;\n\n/// Apply \\a transform with the arguments in \\a x iff all of them have a value. Otherwise, \\c nullopt will be returned.\ntemplate <typename FUnary, typename... T>\nauto map(FUnary&& transform, const optional<T>&... x) -> optional<decltype(transform(x.value()...))>\n{\n    if ((x && ...))\n        return transform(x.value()...);\n    else\n        return nullopt;\n}\n\ntemplate <typename T>\noptional<T> some(T x)\n{\n    return optional<T>(std::move(x));\n}\n\n/// \\}\n\n}\n"
  },
  {
    "path": "src/zk/optional_tests.cpp",
    "content": "#include <zk/tests/test.hpp>\n\n#include \"optional.hpp\"\n\nnamespace zk\n{\n\nGTEST_TEST(optional_test, integer)\n{\n    optional<int> x = nullopt;\n    CHECK_FALSE(x);\n    x = 1;\n    CHECK_TRUE(x);\n    CHECK_EQ(1, x.value());\n}\n\n}\n"
  },
  {
    "path": "src/zk/results.cpp",
    "content": "#include \"results.hpp\"\n\n#include <ostream>\n#include <sstream>\n#include <utility>\n\nnamespace zk\n{\n\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n// Utilities                                                                                                          //\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\n// These tags are used during print_buffer overload resolution. If TBuffer type is not\n// applicable (ill-formed) for one of the overloads, then SFINAE selects the appropriate overload.\n// But there are cases (e.g. when TBuffer is std::string) when both of the print_buffer\n// are well-formed and therefore the call to print_buffer would be ambiguous. These tags\n// allows to select one of the overloads by using more derived tag (print_buffer_content_tag).\nstruct print_buffer_length_tag {};\nstruct print_buffer_content_tag  : public print_buffer_length_tag {};\n\ntemplate <typename TBuffer>\nauto print_buffer(std::ostream& os, const TBuffer& buf, struct print_buffer_content_tag)\n        -> decltype((os << buf), void())\n{\n    os << buf;\n}\n\ntemplate <typename TBuffer>\nvoid print_buffer(std::ostream& os, const TBuffer& buf, struct print_buffer_length_tag)\n{\n    os << \"size=\" << buf.size();\n}\n\ntemplate <typename TRange>\nvoid print_range(std::ostream& os, const TRange& range)\n{\n    os << '[';\n    bool first = true;\n    for (const auto& x : range)\n    {\n        if (first)\n            first = false;\n        else\n            os << \", \";\n        os << x;\n    }\n    os << ']';\n}\n\ntemplate <typename T>\nstd::string to_string_generic(const T& x)\n{\n    std::ostringstream os;\n    os << x;\n    return os.str();\n}\n\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n// get_result                                                                                                         //\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\nget_result::get_result(buffer data, const zk::stat& stat) noexcept :\n        _data(std::move(data)),\n        _stat(stat)\n{ }\n\nget_result::~get_result() noexcept\n{ }\n\nstd::ostream& operator<<(std::ostream& os, const get_result& self)\n{\n    os << \"get_result{\";\n    print_buffer(os, self.data(), print_buffer_content_tag {});\n    os << ' ' << self.stat();\n    return os << '}';\n}\n\nstd::string to_string(const get_result& self)\n{\n    return to_string_generic(self);\n}\n\nstatic_assert(std::is_copy_constructible_v<get_result>);\nstatic_assert(std::is_copy_assignable_v<get_result>);\nstatic_assert(std::is_nothrow_move_constructible_v<get_result>);\nstatic_assert(std::is_nothrow_move_assignable_v<get_result>);\n\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n// get_children_result                                                                                                //\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\nget_children_result::get_children_result(children_list_type children, const stat& parent_stat) noexcept :\n        _children(std::move(children)),\n        _parent_stat(parent_stat)\n{ }\n\nget_children_result::~get_children_result() noexcept\n{ }\n\nstd::ostream& operator<<(std::ostream& os, const get_children_result& self)\n{\n    os << \"get_children_result{\";\n    print_range(os, self.children());\n    os << \" parent=\" << self.parent_stat();\n    return os << '}';\n}\n\nstd::string to_string(const get_children_result& self)\n{\n    return to_string_generic(self);\n}\n\nstatic_assert(std::is_copy_constructible_v<get_children_result>);\nstatic_assert(std::is_copy_assignable_v<get_children_result>);\nstatic_assert(std::is_nothrow_move_constructible_v<get_children_result>);\nstatic_assert(std::is_nothrow_move_assignable_v<get_children_result>);\n\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n// exists_result                                                                                                      //\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\nexists_result::exists_result(const optional<zk::stat>& stat) noexcept :\n        _stat(stat)\n{ }\n\nexists_result::~exists_result() noexcept\n{ }\n\nstd::ostream& operator<<(std::ostream& os, const exists_result& self)\n{\n    os << \"exists_result{\";\n    if (self)\n        os << *self.stat();\n    else\n        os << \"(no)\";\n    return os << '}';\n}\n\nstd::string to_string(const exists_result& self)\n{\n    return to_string_generic(self);\n}\n\nstatic_assert(std::is_copy_constructible_v<exists_result>);\nstatic_assert(std::is_copy_assignable_v<exists_result>);\nstatic_assert(std::is_nothrow_move_constructible_v<exists_result>);\nstatic_assert(std::is_nothrow_move_assignable_v<exists_result>);\n\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n// create_result                                                                                                      //\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\ncreate_result::create_result(std::string name) noexcept :\n        _name(std::move(name))\n{ }\n\ncreate_result::~create_result() noexcept\n{ }\n\nstd::ostream& operator<<(std::ostream& os, const create_result& self)\n{\n    return os << \"create_result{name=\" << self.name() << '}';\n}\n\nstd::string to_string(const create_result& self)\n{\n    return to_string_generic(self);\n}\n\nstatic_assert(std::is_copy_constructible_v<create_result>);\nstatic_assert(std::is_copy_assignable_v<create_result>);\nstatic_assert(std::is_nothrow_move_constructible_v<create_result>);\nstatic_assert(std::is_nothrow_move_assignable_v<create_result>);\n\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n// set_result                                                                                                         //\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\nset_result::set_result(const zk::stat& stat) noexcept :\n        _stat(stat)\n{ }\n\nset_result::~set_result() noexcept\n{ }\n\nstd::ostream& operator<<(std::ostream& os, const set_result& self)\n{\n    return os << \"set_result{\" << self.stat() << '}';\n}\n\nstd::string to_string(const set_result& self)\n{\n    return to_string_generic(self);\n}\n\nstatic_assert(std::is_copy_constructible_v<set_result>);\nstatic_assert(std::is_copy_assignable_v<set_result>);\nstatic_assert(std::is_nothrow_move_constructible_v<set_result>);\nstatic_assert(std::is_nothrow_move_assignable_v<set_result>);\n\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n// get_acl_result                                                                                                     //\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\nget_acl_result::get_acl_result(zk::acl acl, const zk::stat& stat) noexcept :\n        _acl(std::move(acl)),\n        _stat(stat)\n{ }\n\nget_acl_result::~get_acl_result() noexcept\n{ }\n\nstd::ostream& operator<<(std::ostream& os, const get_acl_result& self)\n{\n    return os << \"get_acl_result{\" << self.acl() << ' ' << self.stat() << '}';\n}\n\nstd::string to_string(const get_acl_result& self)\n{\n    return to_string_generic(self);\n}\n\nstatic_assert(std::is_copy_constructible_v<get_acl_result>);\nstatic_assert(std::is_copy_assignable_v<get_acl_result>);\nstatic_assert(std::is_nothrow_move_constructible_v<get_acl_result>);\nstatic_assert(std::is_nothrow_move_assignable_v<get_acl_result>);\n\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n// event                                                                                                              //\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\nevent::event(event_type type, zk::state state) noexcept :\n        _type(type),\n        _state(state)\n{ }\n\nstd::ostream& operator<<(std::ostream& os, const event& self)\n{\n    return os << \"event{\" << self.type() << \" | \" << self.state() << '}';\n}\n\nstd::string to_string(const event& self)\n{\n    return to_string_generic(self);\n}\n\nstatic_assert(std::is_copy_constructible_v<event>);\nstatic_assert(std::is_copy_assignable_v<event>);\nstatic_assert(std::is_nothrow_move_constructible_v<event>);\nstatic_assert(std::is_nothrow_move_assignable_v<event>);\n\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n// watch_result                                                                                                       //\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\nwatch_result::watch_result(get_result initial, future<event> next) noexcept :\n        _initial(std::move(initial)),\n        _next(std::move(next))\n{ }\n\nwatch_result::~watch_result() noexcept\n{ }\n\nstd::ostream& operator<<(std::ostream& os, const watch_result& self)\n{\n    return os << \"watch_result{initial=\" << self.initial() << '}';\n}\n\nstd::string to_string(const watch_result& self)\n{\n    return to_string_generic(self);\n}\n\nstatic_assert(std::is_nothrow_move_constructible_v<watch_result>);\nstatic_assert(std::is_nothrow_move_assignable_v<watch_result>);\n\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n// watch_children_result                                                                                              //\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\nwatch_children_result::watch_children_result(get_children_result initial, future<event> next) noexcept :\n        _initial(std::move(initial)),\n        _next(std::move(next))\n{ }\n\nwatch_children_result::~watch_children_result() noexcept\n{ }\n\nstd::ostream& operator<<(std::ostream& os, const watch_children_result& self)\n{\n    return os << \"watch_children_result{initial=\" << self.initial() << '}';\n}\n\nstd::string to_string(const watch_children_result& self)\n{\n    return to_string_generic(self);\n}\n\nstatic_assert(std::is_nothrow_move_constructible_v<watch_children_result>);\nstatic_assert(std::is_nothrow_move_assignable_v<watch_children_result>);\n\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n// watch_exists_result                                                                                                //\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\nwatch_exists_result::watch_exists_result(exists_result initial, future<event> next) noexcept :\n        _initial(std::move(initial)),\n        _next(std::move(next))\n{ }\n\nwatch_exists_result::~watch_exists_result() noexcept\n{ }\n\nstd::ostream& operator<<(std::ostream& os, const watch_exists_result& self)\n{\n    return os << \"watch_exists_result{initial=\" << self.initial() << '}';\n}\n\nstd::string to_string(const watch_exists_result& self)\n{\n    return to_string_generic(self);\n}\n\nstatic_assert(std::is_nothrow_move_constructible_v<watch_exists_result>);\nstatic_assert(std::is_nothrow_move_assignable_v<watch_exists_result>);\n\n}\n"
  },
  {
    "path": "src/zk/results.hpp",
    "content": "/// \\file\n/// Describes the various result types of \\c client operations.\n#pragma once\n\n#include <zk/config.hpp>\n\n#include <iosfwd>\n#include <string>\n#include <vector>\n\n#include \"acl.hpp\"\n#include \"buffer.hpp\"\n#include \"future.hpp\"\n#include \"optional.hpp\"\n#include \"types.hpp\"\n\nnamespace zk\n{\n\n/// \\addtogroup Client\n/// \\{\n\n/// The result type of \\c client::get.\nclass get_result final\n{\npublic:\n    explicit get_result(buffer data, const zk::stat& stat) noexcept;\n\n    get_result(const get_result&)            = default;\n    get_result& operator=(const get_result&) = default;\n\n    get_result(get_result&&)            = default;\n    get_result& operator=(get_result&&) = default;\n\n    ~get_result() noexcept;\n\n    /// \\{\n    /// The data read from the entry.\n    const buffer& data() const & { return _data; }\n    buffer&       data() &       { return _data; }\n    buffer        data() &&      { return std::move(_data); }\n    /// \\}\n\n    /// \\{\n    /// The \\ref zk::stat of the entry at the time it was read. The most useful value of the returned value is\n    /// \\ref stat::data_version.\n    const zk::stat& stat() const { return _stat; }\n    zk::stat&       stat()       { return _stat; }\n    /// \\}\n\nprivate:\n    buffer   _data;\n    zk::stat _stat;\n};\n\nstd::ostream& operator<<(std::ostream&, const get_result&);\n\nstd::string to_string(const get_result&);\n\n/// The result type of \\c client::get_children.\nclass get_children_result final\n{\npublic:\n    using children_list_type = std::vector<std::string>;\n\npublic:\n    explicit get_children_result(children_list_type children, const stat& parent_stat) noexcept;\n\n    get_children_result(const get_children_result&)            = default;\n    get_children_result& operator=(const get_children_result&) = default;\n\n    get_children_result(get_children_result&&)            = default;\n    get_children_result& operator=(get_children_result&&) = default;\n\n    ~get_children_result() noexcept;\n\n    /// \\{\n    /// The list of children of the originally-queried node. Note that there is no guarantee on ordering of this list.\n    const children_list_type& children() const & { return _children; }\n    children_list_type&       children() &       { return _children; }\n    children_list_type        children() &&      { return std::move(_children); }\n    /// \\}\n\n    /// \\{\n    /// The \\ref zk::stat of the entry queried (the parent of the \\ref children).\n    const stat& parent_stat() const { return _parent_stat; }\n    stat&       parent_stat()       { return _parent_stat; }\n    /// \\}\n\nprivate:\n    children_list_type _children;\n    stat               _parent_stat;\n};\n\nstd::ostream& operator<<(std::ostream&, const get_children_result&);\n\nstd::string to_string(const get_children_result&);\n\n/// The result type of \\ref client::exists.\nclass exists_result final\n{\npublic:\n    explicit exists_result(const optional<zk::stat>& stat) noexcept;\n\n    exists_result(const exists_result&)            = default;\n    exists_result& operator=(const exists_result&) = default;\n\n    exists_result(exists_result&&)            = default;\n    exists_result& operator=(exists_result&&) = default;\n\n    ~exists_result() noexcept;\n\n    /// \\{\n    /// An \\c exists_result is \\c true if the entry exists.\n    explicit operator bool() const { return bool(_stat); }\n    bool operator!() const         { return !_stat; }\n    /// \\}\n\n    /// \\{\n    /// The \\ref zk::stat of the entry if it exists. If it does not exist, this value will be \\c nullopt.\n    const optional<zk::stat>& stat() const { return _stat; }\n    optional<zk::stat>&       stat()       { return _stat; }\n    /// \\}\n\nprivate:\n    optional<zk::stat> _stat;\n};\n\nstd::ostream& operator<<(std::ostream&, const exists_result&);\n\nstd::string to_string(const exists_result&);\n\n/// The result type of \\ref client::create.\nclass create_result final\n{\npublic:\n    explicit create_result(std::string name) noexcept;\n\n    create_result(const create_result&)            = default;\n    create_result& operator=(const create_result&) = default;\n\n    create_result(create_result&&)            = default;\n    create_result& operator=(create_result&&) = default;\n\n    ~create_result() noexcept;\n\n    /// \\{\n    /// The name of the created entry. How useful this is depends on the value of \\ref create_mode passed to the create\n    /// operation. If \\ref create_mode::sequential was set, this value must be used to see what was created. In all\n    /// other cases, the name is the same as the path which was passed in.\n    const std::string& name() const & { return _name; }\n    std::string&       name() &       { return _name; }\n    std::string        name() &&      { return std::move(_name); }\n    /// \\}\n\nprivate:\n    std::string _name;\n};\n\nstd::ostream& operator<<(std::ostream&, const create_result&);\n\nstd::string to_string(const create_result&);\n\n/// The result type of \\ref client::set.\nclass set_result final\n{\npublic:\n    explicit set_result(const zk::stat& stat) noexcept;\n\n    set_result(const set_result&)            = default;\n    set_result& operator=(const set_result&) = default;\n\n    set_result(set_result&&)            = default;\n    set_result& operator=(set_result&&) = default;\n\n    ~set_result() noexcept;\n\n    /// \\{\n    /// The \\ref zk::stat of the entry after the set operation.\n    const zk::stat& stat() const { return _stat; }\n    zk::stat&       stat()       { return _stat; }\n    /// \\}\n\nprivate:\n    zk::stat _stat;\n};\n\nstd::ostream& operator<<(std::ostream&, const set_result&);\n\nstd::string to_string(const set_result&);\n\n/// The result type of \\ref client::get_acl.\nclass get_acl_result final\n{\npublic:\n    explicit get_acl_result(zk::acl acl, const zk::stat& stat) noexcept;\n\n    get_acl_result(const get_acl_result&)            = default;\n    get_acl_result& operator=(const get_acl_result&) = default;\n\n    get_acl_result(get_acl_result&&)            = default;\n    get_acl_result& operator=(get_acl_result&&) = default;\n\n    ~get_acl_result() noexcept;\n\n    /// \\{\n    /// The \\ref zk::acl of the entry.\n    const zk::acl& acl() const & { return _acl; }\n    zk::acl&       acl() &       { return _acl; }\n    zk::acl        acl() &&      { return std::move(_acl); }\n    /// \\}\n\n    /// \\{\n    /// The \\ref zk::stat of the entry at the time it was read. The most useful value of the returned value is\n    /// \\ref stat::acl_version.\n    const zk::stat& stat() const { return _stat; }\n    zk::stat&       stat()       { return _stat; }\n    /// \\}\n\nprivate:\n    zk::acl  _acl;\n    zk::stat _stat;\n};\n\nstd::ostream& operator<<(std::ostream&, const get_acl_result&);\n\nstd::string to_string(const get_acl_result&);\n\n/// Data delivered when a watched event triggers.\n///\n/// \\note\n/// If you are familiar with the ZooKeeper C API, the limited information delivered might seem very simplistic. Since\n/// this API only supports non-global watches, the extra parameters are not helpful and generally unsafe. As an example,\n/// the \\c path parameter is not included. It is not helpful to include, since you already know the path you specified\n/// when you set the watch in the first place. Furthermore, it is unsafe, as the contents addressed by the pointer are\n/// only safe in the callback thread. While we could copy the path into an \\c std::string, this would require an\n/// allocation on every delivery, which is very intrusive.\nclass event final\n{\npublic:\n    explicit event(event_type type, zk::state state) noexcept;\n\n    event(const event&)            = default;\n    event& operator=(const event&) = default;\n\n    event(event&&)            = default;\n    event& operator=(event&&) = default;\n\n    /// The type of event that occurred.\n    const event_type& type() const { return _type; }\n\n    /// The state of the connection when the event occurred. Keep in mind that it might be different when the value is\n    /// delivered.\n    const zk::state& state() const { return _state; }\n\nprivate:\n    event_type _type;\n    zk::state  _state;\n};\n\nstd::ostream& operator<<(std::ostream&, const event&);\n\nstd::string to_string(const event&);\n\n/// The result type of \\ref client::watch.\nclass watch_result final\n{\npublic:\n    explicit watch_result(get_result initial, future<event> next) noexcept;\n\n    watch_result(const watch_result&)            = delete;\n    watch_result& operator=(const watch_result&) = delete;\n\n    watch_result(watch_result&&)            = default;\n    watch_result& operator=(watch_result&&) = default;\n\n    ~watch_result() noexcept;\n\n    /// \\{\n    /// The initial result of the fetch.\n    const get_result& initial() const & { return _initial; }\n    get_result&       initial() &       { return _initial; }\n    get_result        initial() &&      { return std::move(_initial); }\n    /// \\}\n\n    /// \\{\n    /// Future to be delivered when the watch is triggered.\n    const future<event>& next() const & { return _next; }\n    future<event>&       next() &       { return _next; }\n    future<event>        next() &&      { return std::move(_next); }\n    /// \\}\n\nprivate:\n    get_result    _initial;\n    future<event> _next;\n};\n\nstd::ostream& operator<<(std::ostream&, const watch_result&);\n\nstd::string to_string(const watch_result&);\n\n/// The result type of \\ref client::watch_children.\nclass watch_children_result final\n{\npublic:\n    explicit watch_children_result(get_children_result initial, future<event> next) noexcept;\n\n    watch_children_result(const watch_children_result&)            = delete;\n    watch_children_result& operator=(const watch_children_result&) = delete;\n\n    watch_children_result(watch_children_result&&)            = default;\n    watch_children_result& operator=(watch_children_result&&) = default;\n\n    ~watch_children_result() noexcept;\n\n    /// \\{\n    /// The initial result of the fetch.\n    const get_children_result& initial() const & { return _initial; }\n    get_children_result&       initial() &       { return _initial; }\n    get_children_result        initial() &&      { return std::move(_initial); }\n    /// \\}\n\n    /// \\{\n    /// Future to be delivered when the watch is triggered.\n    const future<event>& next() const & { return _next; }\n    future<event>&       next() &       { return _next; }\n    future<event>        next() &&      { return std::move(_next); }\n    /// \\}\n\nprivate:\n    get_children_result _initial;\n    future<event>       _next;\n};\n\nstd::ostream& operator<<(std::ostream&, const watch_children_result&);\n\nstd::string to_string(const watch_children_result&);\n\n/// The result type of \\ref client::watch_exists.\nclass watch_exists_result final\n{\npublic:\n    explicit watch_exists_result(exists_result initial, future<event> next) noexcept;\n\n    watch_exists_result(const watch_exists_result&)            = delete;\n    watch_exists_result& operator=(const watch_exists_result&) = delete;\n\n    watch_exists_result(watch_exists_result&&)            = default;\n    watch_exists_result& operator=(watch_exists_result&&) = default;\n\n    ~watch_exists_result() noexcept;\n\n    /// \\{\n    /// The initial result of the fetch.\n    const exists_result& initial() const & { return _initial; }\n    exists_result&       initial() &       { return _initial; }\n    exists_result        initial() &&      { return std::move(_initial); }\n    /// \\}\n\n    /// \\{\n    /// Future to be delivered when the watch is triggered.\n    const future<event>& next() const & { return _next; }\n    future<event>&       next() &       { return _next; }\n    future<event>        next() &&      { return std::move(_next); }\n    /// \\}\n\nprivate:\n    exists_result _initial;\n    future<event> _next;\n};\n\nstd::ostream& operator<<(std::ostream&, const watch_exists_result&);\n\nstd::string to_string(const watch_exists_result&);\n\n/// \\}\n\n}\n"
  },
  {
    "path": "src/zk/server/classpath.cpp",
    "content": "#include \"classpath.hpp\"\n\n#include <zk/string_view.hpp>\n\n#include <algorithm>\n#include <array>\n#include <cerrno>\n#include <cstdio>\n#include <ostream>\n#include <sstream>\n#include <system_error>\n\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <unistd.h>\n\nnamespace zk::server\n{\n\ntemplate <typename TContainer, typename TSep>\nvoid join(std::ostream& os, const TContainer& src, TSep sep)\n{\n    bool first = true;\n    for (const auto& x : src)\n    {\n        if (!std::exchange(first, false))\n            os << sep;\n        os << x;\n    }\n}\n\nstatic bool file_exists(ptr<const char> path)\n{\n    struct ::stat s;\n    if (::stat(path, &s))\n    {\n        if (errno == ENOENT)\n            return false;\n        else\n            throw std::system_error(errno, std::system_category());\n    }\n    else\n    {\n        return true;\n    }\n}\n\nstatic classpath find_system_default()\n{\n    string_view locations[] =\n        {\n            \"/usr/share/java\",\n            \"/usr/local/share/java\",\n        };\n    string_view requirements[] =\n        {\n            \"zookeeper.jar\",\n            \"slf4j-api.jar\",\n            \"slf4j-simple.jar\",\n        };\n\n    std::vector<std::string> components;\n    std::vector<string_view> unfound;\n    for (auto jar : requirements)\n    {\n        bool found = false;\n        for (auto base_loc : locations)\n        {\n            auto potential_sz = base_loc.size() + jar.size() + 4;\n            char potential[potential_sz];\n            std::snprintf(potential, potential_sz, \"%s/%s\", base_loc.data(), jar.data());\n\n            if (file_exists(potential))\n            {\n                found = true;\n                components.emplace_back(std::string(potential));\n                break;\n            }\n        }\n\n        if (!found)\n            unfound.emplace_back(jar);\n    }\n\n    if (unfound.empty())\n    {\n        return classpath(std::move(components));\n    }\n    else\n    {\n        std::ostringstream os;\n        os << \"Could not find requirement\" << ((unfound.size() == 1U) ? \"\" : \"s\") << \": \";\n        join(os, unfound, \", \");\n        os << \". Searched paths: \";\n        join(os, locations, \", \");\n        throw std::runtime_error(os.str());\n    }\n\n}\n\nclasspath classpath::system_default()\n{\n    static const auto instance = find_system_default();\n    return instance;\n}\n\nclasspath::classpath(std::vector<std::string> components) noexcept :\n        _components(std::move(components))\n{ }\n\nstd::string classpath::command_line() const\n{\n    std::ostringstream os;\n    os << *this;\n    return os.str();\n}\n\nstd::ostream& operator<<(std::ostream& os, const classpath& self)\n{\n    join(os, self._components, ':');\n    return os;\n}\n\n}\n"
  },
  {
    "path": "src/zk/server/classpath.hpp",
    "content": "#pragma once\n\n#include <zk/config.hpp>\n\n#include <iosfwd>\n#include <string>\n#include <vector>\n\nnamespace zk::server\n{\n\n/// \\addtogroup Server\n/// \\{\n\n/// Represents a collection of JARs or other Java entities that should be provided as the `--classpath` to the JVM. This\n/// is used to fetch both the ZooKeeper package (`zookeeper.jar`), but the required SLF JAR. If you do not know or care\n/// about what that second part means, just call \\ref system_default.\nclass classpath final\n{\npublic:\n    /// Create a classpath specification from the provided \\a components.\n    explicit classpath(std::vector<std::string> components) noexcept;\n\n    /// Load the system-default classpath for ZooKeeper. This searches for `zookeeper.jar` nad `slf4j-simple.jar` in\n    /// various standard locations.\n    static classpath system_default();\n\n    /// Get the command-line representation of this classpath. This puts \\c ':' characters between the components.\n    std::string command_line() const;\n\n    friend std::ostream& operator<<(std::ostream&, const classpath&);\n\nprivate:\n    std::vector<std::string> _components;\n};\n\n/// \\}\n\n}\n"
  },
  {
    "path": "src/zk/server/classpath_registration_template.cpp.in",
    "content": "#include <zk/config.hpp>\n#include <zk/server/classpath.hpp>\n#include <zk/server/package_registry.hpp>\n#include <zk/server/package_registry_tests.hpp>\n\nnamespace zk::server\n{\n\nnamespace\n{\n\nauto registration = test_package_registry::instance()\n                    .register_classpath_server\n                     (\n                        R\"(@server_version@)\",\n                        classpath({R\"(@server_classpath@)\"})\n                     );\n\n}\n\n}\n"
  },
  {
    "path": "src/zk/server/classpath_tests.cpp",
    "content": "#include <zk/tests/test.hpp>\n\n#include <iostream>\n#include <stdexcept>\n\n#include \"classpath.hpp\"\n\nnamespace zk::server\n{\n\nGTEST_TEST(classpath_tests, system_default)\n{\n    try\n    {\n        auto cp = classpath::system_default();\n        std::cout << \"Found system-default ZooKeeper: \" << cp.command_line() << std::endl;\n    }\n    catch (const std::runtime_error& ex)\n    {\n        std::cout << \"Could not find system-default ZooKeeper: \" << ex.what() << std::endl;\n    }\n}\n\n}\n"
  },
  {
    "path": "src/zk/server/configuration.cpp",
    "content": "#include \"configuration.hpp\"\n\n#include <cctype>\n#include <cstdlib>\n#include <fstream>\n#include <istream>\n#include <ostream>\n#include <regex>\n#include <sstream>\n#include <stdexcept>\n\nnamespace zk::server\n{\n\nvoid server_id::ensure_valid() const\n{\n    if (0U < value && value < 256U)\n        return;\n\n    std::ostringstream os;\n    os << \"Server ID value \" << value << \" is not in the valid range [1 .. 255]\";\n    throw std::out_of_range(os.str());\n}\n\nstd::ostream& operator<<(std::ostream& os, const server_id& self)\n{\n    return os << self.value;\n}\n\nnamespace\n{\n\nconstexpr std::size_t not_a_line = ~0UL;\n\nclass zero_copy_streambuf final :\n        public std::streambuf\n{\npublic:\n    zero_copy_streambuf(string_view input)\n    {\n        // We are just going to read from it, so this const_cast is okay\n        ptr<char> p = const_cast<ptr<char>>(input.data());\n        setg(p, p, p + input.size());\n    }\n};\n\n}\n\nconst std::uint16_t configuration::default_client_port = std::uint16_t(2181);\nconst std::uint16_t configuration::default_peer_port   = std::uint16_t(2888);\nconst std::uint16_t configuration::default_leader_port = std::uint16_t(3888);\n\nconst configuration::duration_type configuration::default_tick_time = std::chrono::milliseconds(2000);\n\nconst std::size_t configuration::default_init_limit = 10U;\nconst std::size_t configuration::default_sync_limit =  5U;\n\nconst std::set<std::string> configuration::default_four_letter_word_whitelist = { \"srvr\" };\nconst std::set<std::string> configuration::all_four_letter_word_whitelist     = { \"*\" };\n\ntemplate <typename T>\nconfiguration::setting<T>::setting() noexcept :\n        value(nullopt),\n        line(not_a_line)\n{ }\n\ntemplate <typename T>\nconfiguration::setting<T>::setting(T value, std::size_t line) noexcept :\n        value(std::move(value)),\n        line(line)\n{ }\n\nconfiguration::configuration() = default;\n\nconfiguration::~configuration() noexcept = default;\n\nconfiguration configuration::make_minimal(std::string data_directory, std::uint16_t client_port)\n{\n    configuration out;\n    out.data_directory(std::move(data_directory))\n       .client_port(client_port)\n       .init_limit(default_init_limit)\n       .sync_limit(default_sync_limit)\n       ;\n    return out;\n}\n\nstatic std::set<std::string> parse_whitelist(string_view source)\n{\n    std::set<std::string> out;\n\n    while (!source.empty())\n    {\n        auto idx = source.find_first_of(',');\n        if (idx == string_view::npos)\n            idx = source.size();\n\n        if (idx == 0)\n        {\n            source.remove_prefix(1);\n            continue;\n        }\n\n        auto sub = source.substr(0, idx);\n        source.remove_prefix(idx);\n        while (!sub.empty() && std::isspace(sub.front()))\n            sub.remove_prefix(1);\n\n        while (!sub.empty() && std::isspace(sub.back()))\n            sub.remove_suffix(1);\n\n        out.insert(std::string(sub));\n    }\n\n    return out;\n}\n\nconfiguration configuration::from_lines(std::vector<std::string> lines)\n{\n    static const std::regex line_expr(R\"(^([^=]+)=([^ #]+)[ #]*$)\",\n                                      std::regex_constants::ECMAScript | std::regex_constants::optimize\n                                     );\n    constexpr auto name_idx = 1U;\n    constexpr auto data_idx = 2U;\n\n    configuration out;\n\n    for (std::size_t line_no = 0U; line_no < lines.size(); ++line_no)\n    {\n        const auto& line = lines[line_no];\n\n        if (line.empty() || line[0] == '#')\n            continue;\n\n        std::smatch match;\n        if (std::regex_match(line, match, line_expr))\n        {\n            auto name = match[name_idx].str();\n            auto data = match[data_idx].str();\n\n            if (name == \"clientPort\")\n            {\n                out._client_port = { std::uint16_t(std::atoi(data.c_str())), line_no };\n            }\n            else if (name == \"dataDir\")\n            {\n                out._data_directory = { std::move(data), line_no };\n            }\n            else if (name == \"tickTime\")\n            {\n                out._tick_time = { std::chrono::milliseconds(std::atol(data.c_str())), line_no };\n            }\n            else if (name == \"initLimit\")\n            {\n                out._init_limit = { std::size_t(std::atol(data.c_str())), line_no };\n            }\n            else if (name == \"syncLimit\")\n            {\n                out._sync_limit = { std::size_t(std::atol(data.c_str())), line_no };\n            }\n            else if (name == \"leaderServes\")\n            {\n                out._leader_serves = { (data == \"yes\"), line_no };\n            }\n            else if (name == \"4lw.commands.whitelist\")\n            {\n                out._four_letter_word_whitelist = { parse_whitelist(data), line_no };\n            }\n            else if (name.find(\"server.\") == 0U)\n            {\n                auto id = std::size_t(std::atol(name.c_str() + 7));\n                out._server_paths.insert({ server_id(id), { std::move(data), line_no } });\n            }\n            else\n            {\n                out._unknown_settings.insert({ std::move(name), { std::move(data), line_no } });\n            }\n        }\n    }\n\n    out._lines = std::move(lines);\n    return out;\n}\n\nconfiguration configuration::from_stream(std::istream& stream)\n{\n    std::vector<std::string> lines;\n    std::string              line;\n\n    while(std::getline(stream, line))\n    {\n        lines.emplace_back(std::move(line));\n    }\n\n    if (stream.eof())\n        return from_lines(std::move(lines));\n    else\n        throw std::runtime_error(\"Loading configuration did not reach EOF\");\n}\n\nconfiguration configuration::from_file(std::string filename)\n{\n    std::ifstream inf(filename.c_str());\n    auto out = from_stream(inf);\n    out._source_file = std::move(filename);\n    return out;\n}\n\nconfiguration configuration::from_string(string_view value)\n{\n    zero_copy_streambuf buff(value);\n    std::istream        stream(&buff);\n    return from_stream(stream);\n}\n\nbool configuration::is_minimal() const\n{\n    return _data_directory.value\n        && _client_port.value\n        && _init_limit.value     && init_limit() == default_init_limit\n        && _sync_limit.value     && sync_limit() == default_sync_limit\n        && _lines.size() == 4U;\n}\n\ntemplate <typename T, typename FEncode>\nvoid configuration::set(setting<T>& target, optional<T> value, string_view key, const FEncode& encode)\n{\n    std::string target_line;\n    if (value)\n    {\n        std::ostringstream os;\n        os << key << '=' << encode(*value);\n        target_line = os.str();\n    }\n\n    if (target.line == not_a_line && value)\n    {\n        target.value = std::move(value);\n        target.line  = _lines.size();\n        _lines.emplace_back(std::move(target_line));\n    }\n    else if (target.line == not_a_line && !value)\n    {\n        // do nothing -- no line means no value\n    }\n    else\n    {\n        target.value        = std::move(value);\n        _lines[target.line] = std::move(target_line);\n    }\n}\n\ntemplate <typename T>\nvoid configuration::set(setting<T>& target, optional<T> value, string_view key)\n{\n    return set(target, std::move(value), key, [] (const T& x) -> const T& { return x; });\n}\n\nstd::uint16_t configuration::client_port() const\n{\n    return _client_port.value.value_or(default_client_port);\n}\n\nconfiguration& configuration::client_port(optional<std::uint16_t> port)\n{\n    set(_client_port, port, \"clientPort\");\n    return *this;\n}\n\nconst optional<std::string>& configuration::data_directory() const\n{\n    return _data_directory.value;\n}\n\nconfiguration& configuration::data_directory(optional<std::string> path)\n{\n    set(_data_directory, std::move(path), \"dataDir\");\n    return *this;\n}\n\nconfiguration::duration_type configuration::tick_time() const\n{\n    return _tick_time.value.value_or(default_tick_time);\n}\n\nconfiguration& configuration::tick_time(optional<duration_type> tick_time)\n{\n    set(_tick_time, tick_time, \"tickTime\", [] (duration_type x) { return x.count(); });\n    return *this;\n}\n\nstd::size_t configuration::init_limit() const\n{\n    return _init_limit.value.value_or(default_init_limit);\n}\n\nconfiguration& configuration::init_limit(optional<std::size_t> limit)\n{\n    set(_init_limit, limit, \"initLimit\");\n    return *this;\n}\n\nstd::size_t configuration::sync_limit() const\n{\n    return _sync_limit.value.value_or(default_sync_limit);\n}\n\nconfiguration& configuration::sync_limit(optional<std::size_t> limit)\n{\n    set(_sync_limit, limit, \"syncLimit\");\n    return *this;\n}\n\nbool configuration::leader_serves() const\n{\n    return _leader_serves.value.value_or(true);\n}\n\nconfiguration& configuration::leader_serves(optional<bool> serve)\n{\n    set(_leader_serves, serve, \"leaderServes\", [] (bool x) { return x ? \"yes\" : \"no\"; });\n    return *this;\n}\n\nconst std::set<std::string>& configuration::four_letter_word_whitelist() const\n{\n    if (_four_letter_word_whitelist.value)\n        return *_four_letter_word_whitelist.value;\n    else\n        return default_four_letter_word_whitelist;\n}\n\nconfiguration& configuration::four_letter_word_whitelist(optional<std::set<std::string>> words)\n{\n    if (words && words->size() > 1U && words->count(\"*\"))\n        throw std::invalid_argument(\"\");\n\n    set(_four_letter_word_whitelist,\n        std::move(words),\n        \"4lw.commands.whitelist\",\n        [] (const std::set<std::string>& words)\n        {\n            bool               first = true;\n            std::ostringstream os;\n\n            for (const auto& word : words)\n            {\n                if (!std::exchange(first, false))\n                    os << ',';\n                os << word;\n            }\n\n            return os.str();\n        }\n       );\n    return *this;\n}\n\nstd::map<server_id, std::string> configuration::servers() const\n{\n    std::map<server_id, std::string> out;\n    for (const auto& entry : _server_paths)\n        out.insert({ entry.first, *entry.second.value });\n\n    return out;\n}\n\nconfiguration& configuration::add_server(server_id     id,\n                                         std::string   hostname,\n                                         std::uint16_t peer_port,\n                                         std::uint16_t leader_port\n                                        )\n{\n    id.ensure_valid();\n\n    if (_server_paths.count(id))\n        throw std::runtime_error(std::string(\"Already a server with ID \") + std::to_string(id.value));\n\n    hostname += \":\";\n    hostname += std::to_string(peer_port);\n    hostname += \":\";\n    hostname += std::to_string(leader_port);\n\n    auto iter = _server_paths.emplace(id, setting<std::string>()).first;\n    set(iter->second, some(std::move(hostname)), std::string(\"server.\") + std::to_string(iter->first.value));\n    return *this;\n}\n\nstd::map<std::string, std::string> configuration::unknown_settings() const\n{\n    std::map<std::string, std::string> out;\n    for (const auto& entry : _unknown_settings)\n        out.insert({ entry.first, *entry.second.value });\n\n    return out;\n}\n\nconfiguration& configuration::add_setting(std::string key, std::string value)\n{\n    // This process is really inefficient, but people should not be using this very often. This is done this way because\n    // it is possible to specify a key that has a known setting (such as \"dataDir\"), which needs to be picked up\n    // correctly. `from_lines` has this logic, so just use it. Taking that matching logic out would be the best approach\n    // to take, but since this shouldn't be used, I haven't bothered.\n    auto source_file = _source_file;\n    auto lines       = _lines;\n    lines.emplace_back(key + \"=\" + value);\n\n    *this        = configuration::from_lines(std::move(lines));\n    _source_file = std::move(source_file);\n    return *this;\n}\n\nvoid configuration::save(std::ostream& os) const\n{\n    for (const auto& line : _lines)\n        os << line << std::endl;\n\n    os.flush();\n}\n\nvoid configuration::save_file(std::string filename)\n{\n    std::ofstream ofs(filename.c_str());\n    save(ofs);\n    if (ofs)\n        _source_file = std::move(filename);\n    else\n        throw std::runtime_error(\"Error saving file\");\n}\n\nbool operator==(const configuration& lhs, const configuration& rhs)\n{\n    if (&lhs == &rhs)\n        return true;\n\n    auto same_items = [] (const auto& a, const auto& b)\n                      {\n                          return a.first        == b.first\n                              && a.second.value == b.second.value;\n                      };\n\n    return lhs.client_port()                == rhs.client_port()\n        && lhs.data_directory()             == rhs.data_directory()\n        && lhs.tick_time()                  == rhs.tick_time()\n        && lhs.init_limit()                 == rhs.init_limit()\n        && lhs.sync_limit()                 == rhs.sync_limit()\n        && lhs.leader_serves()              == rhs.leader_serves()\n        && lhs.four_letter_word_whitelist() == rhs.four_letter_word_whitelist()\n        && lhs._server_paths.size()         == rhs._server_paths.size()\n        && lhs._server_paths.end()          == std::mismatch(lhs._server_paths.begin(), lhs._server_paths.end(),\n                                                             rhs._server_paths.begin(), rhs._server_paths.end(),\n                                                             same_items\n                                                            ).first\n        && lhs._unknown_settings.size()     == rhs._unknown_settings.size()\n        && lhs._unknown_settings.end()      == std::mismatch(lhs._unknown_settings.begin(), lhs._unknown_settings.end(),\n                                                             rhs._unknown_settings.begin(), rhs._unknown_settings.end(),\n                                                             same_items\n                                                            ).first\n        ;\n}\n\nbool operator!=(const configuration& lhs, const configuration& rhs)\n{\n    return !(lhs == rhs);\n}\n\n}\n"
  },
  {
    "path": "src/zk/server/configuration.hpp",
    "content": "#pragma once\n\n#include <zk/config.hpp>\n#include <zk/optional.hpp>\n#include <zk/string_view.hpp>\n#include <zk/types.hpp>\n\n#include <chrono>\n#include <cstdint>\n#include <iosfwd>\n#include <string>\n#include <map>\n#include <set>\n#include <vector>\n\nnamespace zk::server\n{\n\n/// \\addtogroup Server\n/// \\{\n\n/// Represents the ID of a server in the ensemble.\n///\n/// \\note\n/// The backing type for this ID is a \\c std::size_t, when a \\c std::uint8_t would cover all valid values of the ID.\n/// However, printing `unsigned char` types is somewhat odd, as the character value is usually printed. Beyond that,\n/// using a \\c std::uint8_t requires explicit casting when converting from a \\c std::size_t when \\c -Wconversion is\n/// enabled.\nstruct server_id :\n        strong_id<server_id, std::size_t>\n{\n    /// Create an instance from the given \\a value.\n    ///\n    /// \\throws std::out_of_range if \\a value is not in 1-255.\n    server_id(std::size_t value) :\n            strong_id<server_id, std::size_t>(value)\n    {\n        ensure_valid();\n    }\n\n    /// Check that this ID is a valid one.\n    ///\n    /// \\throws std::out_of_range if \\a value is not in 1-255.\n    void ensure_valid() const;\n\n    /// Debug print for this instance.\n    friend std::ostream& operator<<(std::ostream&, const server_id&);\n};\n\n/// Represents a configuration which should be run by \\ref server instance. This can also be used to modify an existing\n/// ZooKeeper server configuration file in a safer manner than the unfortunate operating practice of \\c sed, \\c awk, and\n/// \\c perl.\n///\n/// This can be used to quickly create a quorum peer, ready to connect to 3 servers.\n/// \\code\n/// auto config = server::configuration::make_minimal(\"zk-data\", 2181)\n///               .add_server(1, \"192.168.1.101\")\n///               .add_server(2, \"192.168.1.102\")\n///               .add_server(3, \"192.168.1.103\");\n/// config.save_file(\"settings.cfg\");\n/// {\n///     std::ofstream of(\"zk-data/myid\");\n///     // Assuming this is server 1\n///     of << 1 << std::endl;\n/// }\n/// server::server svr(config);\n/// \\endcode\n///\n/// \\see server For where this is used.\n/// \\see server_group An example of quickly creating multiple ZooKeeper servers on a single machine (for testing).\nclass configuration final\n{\npublic:\n    using duration_type = std::chrono::milliseconds;\n\npublic:\n    /// The default value for \\ref client_port.\n    static const std::uint16_t default_client_port;\n\n    /// The default value for \\ref peer_port.\n    static const std::uint16_t default_peer_port;\n\n    /// The default value for \\ref leader_port.\n    static const std::uint16_t default_leader_port;\n\n    /// The default value for \\ref tick_time.\n    static const duration_type default_tick_time;\n\n    /// The default value for \\ref init_limit.\n    static const std::size_t default_init_limit;\n\n    /// The default value for \\ref sync_limit.\n    static const std::size_t default_sync_limit;\n\n    /// The default value for \\ref four_letter_word_whitelist.\n    static const std::set<std::string> default_four_letter_word_whitelist;\n\n    /// A value for \\ref four_letter_word_whitelist that enables all commands. Note that this is not a list of all\n    /// allowed words, but simply the string \\c \"*\".\n    static const std::set<std::string> all_four_letter_word_whitelist;\n\n    /// All known values allowed in \\ref four_letter_word_whitelist. This set comes from what ZooKeeper server 3.5.3\n    /// supported, so it is possible the version of ZooKeeper you are running supports a different set.\n    static const std::set<std::string> known_four_letter_word_whitelist;\n\npublic:\n    /// Creates a minimal configuration, setting the four needed values. The resulting \\ref configuration can be run\n    /// through a file with \\c save or it can run directly from the command line.\n    static configuration make_minimal(std::string data_directory, std::uint16_t client_port = default_client_port);\n\n    /// Load the configuration from a file.\n    static configuration from_file(std::string filename);\n\n    /// Load configuration from the provided \\a stream.\n    static configuration from_stream(std::istream& stream);\n\n    /// Load configuration from the provided \\a lines.\n    static configuration from_lines(std::vector<std::string> lines);\n\n    /// Load configuration directly from the in-memory \\a value.\n    static configuration from_string(string_view value);\n\n    ~configuration() noexcept;\n\n    /// Get the source file. This will only have a value if this was created by \\ref from_file.\n    const optional<std::string>& source_file() const { return _source_file; }\n\n    /// Check if this is a \"minimal\" configuration -- meaning it only has a \\c data_directory and \\c client_port set.\n    /// Configurations which are minimal can be started directly from the command line.\n    bool is_minimal() const;\n\n    /// \\{\n    /// The port a client should use to connect to this server.\n    std::uint16_t  client_port() const;\n    configuration& client_port(optional<std::uint16_t> port);\n    /// \\}\n\n    /// \\{\n    /// The directory for \"myid\" file and \"version-2\" directory (containing the log, snapshot, and epoch files).\n    const optional<std::string>& data_directory() const;\n    configuration&               data_directory(optional<std::string> path);\n    /// \\}\n\n    /// \\{\n    /// The time between server \"ticks.\" This value is used to translate \\ref init_limit and \\ref sync_limit into clock\n    /// times.\n    duration_type  tick_time() const;\n    configuration& tick_time(optional<duration_type> time);\n    /// \\}\n\n    /// \\{\n    /// The number of ticks that the initial synchronization phase can take. This limits the length of time the\n    /// ZooKeeper servers in quorum have to connect to a leader.\n    std::size_t    init_limit() const;\n    configuration& init_limit(optional<std::size_t> limit);\n    /// \\}\n\n    /// \\{\n    /// Limits how far out of date a server can be from a leader.\n    std::size_t    sync_limit() const;\n    configuration& sync_limit(optional<std::size_t> limit);\n    /// \\}\n\n    /// \\{\n    /// Should an elected leader accepts client connections? For higher update throughput at the slight expense of read\n    /// latency, the leader can be configured to not accept clients and focus on coordination. The default to this value\n    /// is \\c true, which means that a leader will accept client connections.\n    bool           leader_serves() const;\n    configuration& leader_serves(optional<bool> serve);\n    /// \\}\n\n    /// \\{\n    /// A list of comma separated four letter words commands that user wants to use. A valid four letter words command\n    /// must be put in this list or the ZooKeeper server will not enable the command. If unspecified, the whitelist only\n    /// contains \"srvr\" command (\\ref default_four_letter_word_whitelist).\n    ///\n    /// \\note\n    /// It is planned that the ZooKeeper server will deprecate this whitelist in preference of using a JSON REST API for\n    /// health checks. It is unlikely to be deprecated any time in the near future and will likely remain in the product\n    /// for a very long time.\n    ///\n    /// \\param words is the list of four letter words to allow or \\c nullopt to clear the setting. If specified as an\n    ///  empty set, this explicitly disables all words, which is \\e different than setting this value to \\c nullopt.\n    ///\n    /// \\throws std::invalid_argument if \\a words contains the all value (\\c \"*\") but it is not the only value in the\n    ///  set.\n    ///\n    /// \\ref default_four_letter_word_whitelist\n    /// \\ref all_four_letter_word_whitelist\n    /// \\ref known_four_letter_word_whitelist\n    const std::set<std::string>& four_letter_word_whitelist() const;\n    configuration&               four_letter_word_whitelist(optional<std::set<std::string>> words);\n    /// \\}\n\n    /// Get the servers which are part of the ZooKeeper ensemble.\n    std::map<server_id, std::string> servers() const;\n\n    /// Add a new server to the configuration.\n    ///\n    /// \\param id The cluster unique ID of this server.\n    /// \\param hostname The address of the server to connect to.\n    /// \\param peer_port The port used to move ZooKeeper data on.\n    /// \\param leader_port The port used for leader election.\n    /// \\throws std::out_of_range if the \\a id is not in the valid range of IDs (see \\ref server_id).\n    /// \\throws std::runtime_error if there is already a server with the given \\a id.\n    configuration& add_server(server_id     id,\n                              std::string   hostname,\n                              std::uint16_t peer_port   = default_peer_port,\n                              std::uint16_t leader_port = default_leader_port\n                             );\n\n    /// Get settings that were in the configuration file (or manually added with \\ref add_setting) but unknown to this\n    /// library.\n    std::map<std::string, std::string> unknown_settings() const;\n\n    /// Add an arbitrary setting with the \\a key and \\a value.\n    ///\n    /// \\note\n    /// You should not use this frequently -- prefer the named settings.\n    configuration& add_setting(std::string key, std::string value);\n\n    /// Write this configuration to the provided \\a stream.\n    ///\n    /// \\see save_file\n    void save(std::ostream& stream) const;\n\n    /// Save this configuration to \\a filename. On successful save, \\c source_file will but updated to reflect the new\n    /// file.\n    void save_file(std::string filename);\n\n    /// \\{\n    /// Check for equality of configuration. This does not check the specification in lines, but the values of the\n    /// settings. In other words, two configurations with \\c tick_time set to 1 second are equal, even if the source\n    /// files are different and \\c \"tickTime=1000\" was set on different lines.\n    friend bool operator==(const configuration& lhs, const configuration& rhs);\n    friend bool operator!=(const configuration& lhs, const configuration& rhs);\n    /// \\}\n\nprivate:\n    using line_list = std::vector<std::string>;\n\n    template <typename T>\n    struct setting\n    {\n        setting() noexcept;\n        setting(T value, std::size_t line) noexcept;\n\n        optional<T> value;\n        std::size_t line;\n    };\n\n    template <typename T, typename FEncode>\n    void set(setting<T>& target, optional<T> value, string_view key, const FEncode& encode);\n\n    template <typename T>\n    void set(setting<T>& target, optional<T> value, string_view key);\n\nprivate:\n    explicit configuration();\n\nprivate:\n    optional<std::string>                       _source_file;\n    line_list                                   _lines;\n    setting<std::uint16_t>                      _client_port;\n    setting<std::string>                        _data_directory;\n    setting<duration_type>                      _tick_time;\n    setting<std::size_t>                        _init_limit;\n    setting<std::size_t>                        _sync_limit;\n    setting<bool>                               _leader_serves;\n    setting<std::set<std::string>>              _four_letter_word_whitelist;\n    std::map<server_id, setting<std::string>>   _server_paths;\n    std::map<std::string, setting<std::string>> _unknown_settings;\n};\n\n/// \\}\n\n}\n\nnamespace std\n{\n\ntemplate <>\nstruct hash<zk::server::server_id>\n{\n    using argument_type = zk::server::server_id;\n    using result_type   = std::size_t;\n\n    result_type operator()(const argument_type& x) const\n    {\n        return zk::hash(x);\n    }\n};\n\n}\n"
  },
  {
    "path": "src/zk/server/configuration_tests.cpp",
    "content": "#include <zk/string_view.hpp>\n#include <zk/tests/test.hpp>\n\n#include <iostream>\n#include <sstream>\n\n#include \"configuration.hpp\"\n\nnamespace zk::server\n{\n\nstatic string_view configuration_source_file_example =\nR\"(# http://hadoop.apache.org/zookeeper/docs/current/zookeeperAdmin.html\n\ntickTime=2500\ninitLimit=10\nsyncLimit=5\ndataDir=/var/lib/zookeeper\n\n# the port at which the clients will connect\nclientPort=2181\n\n# specify all zookeeper servers\nserver.1=zookeeper1:2888:3888\nserver.2=zookeeper2:2888:3888\nserver.3=zookeeper3:2888:3888\n\n#preAllocSize=65536\n#snapCount=1000\n\nleaderServes=yes\n\n# A server key that we do not understand\nrandomExtra=Value\n)\";\n\nGTEST_TEST(configuration_tests, from_example)\n{\n    auto parsed = configuration::from_string(configuration_source_file_example);\n    CHECK_EQ(2500U, parsed.tick_time().count());\n    CHECK_EQ(10U,   parsed.init_limit());\n    CHECK_EQ(5U,    parsed.sync_limit());\n    CHECK_EQ(2181U, parsed.client_port());\n    CHECK_TRUE(parsed.leader_serves());\n\n    auto servers = parsed.servers();\n    CHECK_EQ(3U, servers.size());\n    CHECK_EQ(\"zookeeper1:2888:3888\", servers.at(server_id(1)));\n    CHECK_EQ(\"zookeeper2:2888:3888\", servers.at(server_id(2)));\n    CHECK_EQ(\"zookeeper3:2888:3888\", servers.at(server_id(3)));\n\n    auto unrecognized = parsed.unknown_settings();\n    CHECK_EQ(1U, unrecognized.size());\n    CHECK_EQ(\"Value\", unrecognized.at(\"randomExtra\"));\n\n    auto configured = configuration::make_minimal(\"/var/lib/zookeeper\")\n                      .tick_time(std::chrono::milliseconds(2500))\n                      .init_limit(10)\n                      .sync_limit(5)\n                      .client_port(2181)\n                      .leader_serves(true)\n                      .add_server(server_id(1), \"zookeeper1\")\n                      .add_server(server_id(2), \"zookeeper2\")\n                      .add_server(server_id(3), \"zookeeper3\")\n                      .add_setting(\"randomExtra\", \"Value\")\n                      ;\n    CHECK_EQ(configured, parsed);\n\n    std::ostringstream os;\n    parsed.save(os);\n    auto reloaded = configuration::from_string(os.str());\n    CHECK_EQ(parsed, reloaded);\n}\n\nGTEST_TEST(configuration_tests, minimal)\n{\n    auto minimal = configuration::make_minimal(\"/some/path\", 2345);\n    CHECK_EQ(\"/some/path\", minimal.data_directory().value());\n    CHECK_EQ(2345,         minimal.client_port());\n    CHECK_TRUE(minimal.is_minimal());\n}\n\nstatic string_view configuration_source_with_four_letter_words_example =\nR\"(# http://hadoop.apache.org/zookeeper/docs/current/zookeeperAdmin.html\n\ntickTime=2500\ninitLimit=10\nsyncLimit=5\ndataDir=/var/lib/zookeeper\n4lw.commands.whitelist=stat,mntr,srvr,ruok\n)\";\n\nGTEST_TEST(configuration_tests, four_letter_words)\n{\n    auto parsed = configuration::from_string(configuration_source_with_four_letter_words_example);\n\n    CHECK_EQ(4U, parsed.four_letter_word_whitelist().size());\n    std::set<std::string> expected = { \"stat\", \"mntr\", \"srvr\", \"ruok\" };\n    CHECK_TRUE(expected == parsed.four_letter_word_whitelist());\n}\n\n}\n"
  },
  {
    "path": "src/zk/server/detail/close.cpp",
    "content": "#include \"close.hpp\"\n\n#include <string>\n#include <system_error>\n\n#include <cerrno>\n#include <unistd.h>\n\nnamespace zk::server::detail\n{\n\nvoid close(int fd)\n{\n    if (::close(fd) == -1)\n    {\n        // LCOV_EXCL_START: Close will always work -- push failures to the consumer\n        switch (errno)\n        {\n            case EINTR: return; // POSIX guarantees we're still closed in EINTR cases\n            case EIO:   throw std::system_error(errno, std::system_category(), \"I/O error on close()\");\n            default:    throw std::system_error(errno, std::system_category(), \"System error on close()\");\n        }\n        // LCOV_EXCL_STOP\n    }\n}\n\n}\n"
  },
  {
    "path": "src/zk/server/detail/close.hpp",
    "content": "#pragma once\n\n#include <zk/config.hpp>\n\nnamespace zk::server::detail\n{\n\n/** A safe wrapper around the Linux \\c close call. In particular, it transforms certain error codes that matter into\n *  exceptions and happily ignores error codes that still close the file descriptor (\\c EINTR).\n *\n *  \\throws system_error if an I/O error occurs (this can happen if the kernel defers writes -- although in general, you\n *   have made a grave error by not performing a \\c sync before \\c close).\n *  \\throws system_error if \\a fd is not a file descriptor.\n**/\nvoid close(int fd);\n\n}\n"
  },
  {
    "path": "src/zk/server/detail/event_handle.cpp",
    "content": "#include \"event_handle.hpp\"\n#include \"close.hpp\"\n\n#include <cerrno>\n#include <cstdint>\n#include <system_error>\n\n#include <sys/eventfd.h>\n#include <unistd.h>\n\nnamespace zk::server::detail\n{\n\nevent_handle::event_handle() :\n        _fd(::eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK))\n{ }\n\nevent_handle::~event_handle() noexcept\n{\n    close();\n}\n\nvoid event_handle::close() noexcept\n{\n    if (_fd != -1)\n    {\n        detail::close(_fd);\n        _fd = -1;\n    }\n}\n\nvoid event_handle::notify_one()\n{\n    std::uint64_t x = 1;\n    if (::write(_fd, &x, sizeof x) == -1 && errno != EAGAIN)\n        throw std::system_error(errno, std::system_category(), \"event_handle::notify_one()\");\n}\n\nbool event_handle::try_wait()\n{\n    std::uint64_t burn;\n    if (::read(_fd, &burn, sizeof burn) == -1)\n        return errno == EAGAIN ? false\n                               : throw std::system_error(errno, std::system_category(), \"event_handle::try_wait()\");\n    else\n        return true;\n}\n\n}\n"
  },
  {
    "path": "src/zk/server/detail/event_handle.hpp",
    "content": "#pragma once\n\n#include <zk/config.hpp>\n\nnamespace zk::server::detail\n{\n\nclass event_handle final\n{\npublic:\n    using native_handle_type = int;\n\npublic:\n    explicit event_handle();\n\n    event_handle(const event_handle&) = delete;\n\n    event_handle& operator=(const event_handle&) = delete;\n\n    ~event_handle() noexcept;\n\n    /// Close this event for future signalling. This is automatically called from the destructor.\n    void close() noexcept;\n\n    /// Signal this handle that something has happened.\n    void notify_one();\n\n    /// Attempt to wait for this handle to be signalled, but do not block.\n    ///\n    /// \\returns \\c true if we successfully waited for a signal (and consumed it); \\c false if this handle was not\n    ///  signalled.\n    bool try_wait();\n\n    /// Get the file descriptor backing this handle. This is generally only used when interacting with the kernel and\n    /// should be avoided in regular use.\n    native_handle_type native_handle() { return _fd; }\n\nprivate:\n    native_handle_type _fd;\n};\n\n}\n"
  },
  {
    "path": "src/zk/server/detail/pipe.cpp",
    "content": "#include \"close.hpp\"\n#include \"pipe.hpp\"\n\n#include <algorithm>\n#include <system_error>\n\n#include <fcntl.h>\n#include <unistd.h>\n\nnamespace zk::server::detail\n{\n\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n// pipe_closed                                                                                                        //\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\npipe_closed::pipe_closed() :\n        std::runtime_error(\"I/O operation on closed pipe\")\n{ }\n\npipe_closed::~pipe_closed() noexcept\n{ }\n\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n// pipe                                                                                                               //\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\nstatic int on_exec_flags(on_exec exec)\n{\n    switch (exec)\n    {\n        case on_exec::close:\n            return O_CLOEXEC;\n        case on_exec::keep_open:\n        default:\n            return 0;\n    }\n}\n\npipe::pipe(on_exec exec) :\n        _read_fd(-1),\n        _write_fd(-1)\n{\n    int fds[2];\n    if (::pipe2(fds, O_NONBLOCK | on_exec_flags(exec)))\n        throw std::system_error(errno, std::system_category(), \"Could not create pipe\");\n\n    _read_fd = fds[0];\n    _write_fd = fds[1];\n}\n\npipe::~pipe() noexcept\n{\n    close();\n}\n\nstatic void close_if_open(int& fd)\n{\n    if (fd != -1)\n    {\n        close(fd);\n        fd = -1;\n    }\n}\n\nvoid pipe::close()\n{\n    close_write();\n    close_read();\n}\n\nvoid pipe::close_read()\n{\n    close_if_open(_read_fd);\n}\n\nvoid pipe::close_write()\n{\n    close_if_open(_write_fd);\n}\n\nstd::string pipe::read(optional<std::size_t> max)\n{\n    if (_read_fd == -1)\n        throw pipe_closed();\n\n    std::string out;\n    if (max)\n        out.reserve(max.value());\n\n    while (true)\n    {\n        char read_buf[4096];\n\n        ssize_t rc = ::read(_read_fd, read_buf, sizeof read_buf);\n        if (rc < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))\n            rc = 0;\n\n        if (rc < 0 && errno == EINTR)\n        {\n            continue;\n        }\n        else if (rc < 0 && errno != EINTR)\n        {\n            if (out.empty())\n                throw std::system_error(errno, std::system_category(), \"Failed to read from pipe\");\n            else\n                return out;\n        }\n        else if (rc == 0)\n        {\n            return out;\n        }\n        else\n        {\n            out.append(read_buf, rc);\n        }\n    }\n}\n\nvoid pipe::write(const std::string& contents)\n{\n    if (_write_fd == -1)\n        throw pipe_closed();\n\n    ptr<const char> write_ptr = contents.data();\n    while (write_ptr < contents.data() + contents.size())\n    {\n        ssize_t goal_dist = std::distance(write_ptr, contents.data() + contents.size());\n        ssize_t rc = ::write(_write_fd, write_ptr, goal_dist);\n        if (rc < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))\n            rc = 0;\n\n        if (rc < 0 && errno == EINTR)\n        {\n            continue;\n        }\n        else if (rc < 0 && errno == EPIPE)\n        {\n            throw pipe_closed();\n        }\n        else if (rc < 0)\n        {\n            throw std::system_error(errno, std::system_category(), \"Failed to write to pipe\");\n        }\n        else\n        {\n            write_ptr += rc;\n        }\n    }\n}\n\nstatic int dup3(int src_fd, int redir_fd, on_exec exec)\n{\n    while (true)\n    {\n        int rc = ::dup3(src_fd, redir_fd, on_exec_flags(exec));\n        if (rc == -1 && errno == EINTR)\n            continue;\n        else if (rc == -1 && errno != EINTR)\n            throw std::system_error(errno, std::system_category());\n        else\n            return rc;\n    }\n}\n\nvoid pipe::subsume_read(handle fd, on_exec exec)\n{\n    dup3(_read_fd, fd, exec);\n    // close now unused read side of this pipe\n    close_read();\n}\n\nvoid pipe::subsume_write(handle fd, on_exec exec)\n{\n    dup3(_write_fd, fd, exec);\n    close_write();\n}\n\npipe::handle pipe::native_read_handle()\n{\n    return _read_fd;\n}\n\npipe::handle pipe::native_write_handle()\n{\n    return _write_fd;\n}\n\n}\n"
  },
  {
    "path": "src/zk/server/detail/pipe.hpp",
    "content": "#pragma once\n\n#include <zk/config.hpp>\n#include <zk/optional.hpp>\n\n#include <stdexcept>\n#include <string>\n\nnamespace zk::server::detail\n{\n\n/** Used to specify behavior of POSIX resources when \\c exec is called. **/\nenum class on_exec\n{\n    /** Enable the close-on-exec flag for an opened handle. This means that any subprocess created with \\c exec will not\n     *  be able to access the resource of the parent process.\n    **/\n    close,\n    /** Keep the file descriptor open for any opened subprocesses. You probably do not want to use this flag unless you\n     *  intend to share a pipe with the subprocess. In general, it is safer for the subprocess to open the same file\n     *  itself.\n    **/\n    keep_open,\n};\n\n/** Thrown when a read or write operation is attempted on a \\c pipe which is closed. **/\nclass pipe_closed :\n        public std::runtime_error\n{\npublic:\n    pipe_closed();\n\n    virtual ~pipe_closed() noexcept;\n};\n\n/** A unidirectional data channel that can be used for interprocess communication or as a signal-safe mechanism for\n *  in-process communication.\n**/\nclass pipe final\n{\npublic:\n    using handle = int;\n\npublic:\n    /** Create a pipe.\n     *\n     *  \\param exec Should this pipe stay open when \\c exec is called? See \\c on_exec for more details.\n     *  \\throws std::system_error if the pipe could not be created.\n    **/\n    explicit pipe(on_exec exec = on_exec::close);\n\n    pipe(const pipe&) = delete;\n    pipe& operator=(const pipe&) = delete;\n\n    ~pipe() noexcept;\n\n    /** Close the read and write ends of this pipe. It is safe to call this multiple times (subsequent calls to \\c close\n     *  will have no effect).\n    **/\n    void close();\n\n    /** Similar to \\c close, but only close the read end of the pipe. **/\n    void close_read();\n\n    /** Similar to \\c close, but only close the write end of the pipe. **/\n    void close_write();\n\n    /** Read from the pipe (up to \\a max). If there is nothing to read, an empty string is returned.\n     *\n     *  \\param max The maximum amount of bytes to read in a single pass. If unspecified, this will read until the pipe\n     *   appears to be empty.\n     *  \\throws pipe_closed if the pipe is already closed.\n    **/\n    std::string read(optional<std::size_t> max = nullopt);\n\n    /** Write the \\a contents into the pipe.\n     *\n     *  \\throws pipe_closed if the pipe is already closed (this typically happens when communicating with a subprocess\n     *   which has been terminated).\n    **/\n    void write(const std::string& contents);\n\n    /** Redirect the provided \\a fd to read from this pipe instead of what it was originally reading from.\n     *\n     *  \\example\n     *  This redirects standard input to be controlled by a pipe. See \\c subprocess for the use case.\n     *  \\code\n     *  pipe p;\n     *  p.subsume_read(STDIN_FILENO);\n     *  // Now p.write(...) calls will be picked up by reads to standard input\n     *  \\endcode\n    **/\n    void subsume_read(handle fd, on_exec exec = on_exec::close);\n\n    /** Redirect the provided \\a fd to write to this pipe instead of what it was originally writing to. **/\n    void subsume_write(handle fd, on_exec exec = on_exec::close);\n\n    /** Get the native handle for the read end of this pipe. **/\n    handle native_read_handle();\n\n    /** Get the native handle for the write end of this pipe. **/\n    handle native_write_handle();\n\nprivate:\n    handle _read_fd;\n    handle _write_fd;\n};\n\n}\n"
  },
  {
    "path": "src/zk/server/detail/pipe_tests.cpp",
    "content": "#include <zk/tests/test.hpp>\n\n#include \"pipe.hpp\"\n\n#include <unistd.h>\n\nnamespace zk::server::detail\n{\n\nGTEST_TEST(pipe_tests, read_write)\n{\n    std::string buff(8000, 'a');\n\n    pipe p;\n    p.write(buff);\n\n    std::string out;\n    out.reserve(buff.size());\n\n    while (out.size() < buff.size())\n        out += p.read();\n\n    CHECK_EQ(buff, out);\n}\n\n}\n\n"
  },
  {
    "path": "src/zk/server/detail/subprocess.cpp",
    "content": "#include \"subprocess.hpp\"\n\n#include <algorithm>\n#include <iterator>\n#include <numeric>\n#include <ostream>\n#include <system_error>\n\n#include <fcntl.h>\n#include <signal.h>\n#include <sys/types.h>\n#include <sys/wait.h>\n#include <unistd.h>\n\nnamespace zk::server::detail\n{\n\nstatic pid_t create_subproc(pipe&                     stdin_pipe,\n                            pipe&                     stdout_pipe,\n                            pipe&                     stderr_pipe,\n                            std::string               program_name,\n                            subprocess::argument_list args\n                           )\n{\n    std::vector<const char*> arg_ptrs;\n    arg_ptrs.reserve(args.size() + 2);\n    arg_ptrs.emplace_back(program_name.c_str());\n    std::transform(begin(args), end(args),\n                   std::back_inserter(arg_ptrs),\n                   [] (const std::string& s) { return s.c_str(); }\n                  );\n    arg_ptrs.emplace_back(nullptr);\n\n    pid_t pid = ::fork();\n    if (pid == 0) // child process\n    {\n        // LCOV_EXCL_START: No way to detect success at this point\n        stdin_pipe.subsume_read(STDIN_FILENO, on_exec::keep_open);\n        stdin_pipe.close();\n        stdout_pipe.subsume_write(STDOUT_FILENO, on_exec::keep_open);\n        stdout_pipe.close();\n        stderr_pipe.subsume_write(STDERR_FILENO, on_exec::keep_open);\n        stderr_pipe.close();\n\n        ::execvp(program_name.c_str(),\n                 const_cast<char**>(arg_ptrs.data())\n                );\n        // if we get here, exec failed\n        std::exit(1);\n        // LCOV_EXCL_STOP\n    }\n    else // parent process\n    {\n        stdin_pipe.close_read();\n        stdout_pipe.close_write();\n        stderr_pipe.close_write();\n\n        return pid;\n    }\n}\n\nstatic void dont_leak(pipe& p) noexcept\n{\n    auto fds = { p.native_read_handle(), p.native_write_handle() };\n    for (auto fd : fds)\n    {\n        if (fd == -1)\n            continue;\n\n        // ignore the return code -- the inability to set FD_CLOEXEC isn't fatal, just inconvenient\n        ::fcntl(fd, F_SETFD, FD_CLOEXEC);\n    }\n}\n\nsubprocess::subprocess(std::string program_name, argument_list args) :\n        _program_name(std::move(program_name)),\n        _proc_id(-1),\n        _stdin(on_exec::keep_open),\n        _stdout(on_exec::keep_open),\n        _stderr(on_exec::keep_open)\n{\n    _proc_id = create_subproc(_stdin, _stdout, _stderr, _program_name, std::move(args));\n\n    // Set the on_exec for the pipes to no longer keep_open for future subprocesses -- there is a race condition here if\n    // another thread creates a subprocess before we set FD_CLOEXEC. The worst thing that happens is we leak a couple of\n    // (unused) file descriptors to the subprocess. There is no easy way to prevent this.\n    dont_leak(_stdin);\n    dont_leak(_stdout);\n    dont_leak(_stderr);\n}\n\nsubprocess::~subprocess() noexcept\n{\n    terminate();\n}\n\nvoid subprocess::terminate(duration_type time_to_abort) noexcept\n{\n    auto alarm_time = [&] () -> unsigned int\n                      {\n                          if (time_to_abort.count() <= 0)\n                              return 1U;\n                          else if (time_to_abort.count() > 300)\n                              return 300U;\n                          else\n                              return static_cast<unsigned int>(time_to_abort.count());\n                      }();\n\n    for (unsigned attempt = 1U; _proc_id != -1; ++attempt)\n    {\n        auto old_sig_handler = ::signal(SIGALRM, [](int) { });\n        signal(attempt == 1U ? SIGTERM : SIGABRT);\n\n        int rc;\n        ::alarm(alarm_time);\n        if (::waitpid(_proc_id, &rc, 0) > 0)\n        {\n            _proc_id = -1;\n        }\n\n        ::alarm(0);\n        ::signal(SIGALRM, old_sig_handler);\n    }\n}\n\nbool subprocess::signal(int sig_val)\n{\n    if (_proc_id == -1)\n        return false;\n\n    pid_t pid = _proc_id;\n\n    int rc = ::kill(pid, sig_val);\n    if (rc == -1 && errno == ESRCH)\n        return false;\n    else if (rc == -1)\n        throw std::system_error(errno, std::system_category());\n    else\n        return true;\n}\n\n}\n"
  },
  {
    "path": "src/zk/server/detail/subprocess.hpp",
    "content": "#pragma once\n\n#include <zk/config.hpp>\n\n#include <chrono>\n#include <cstddef>\n#include <iosfwd>\n#include <string>\n#include <vector>\n\n#include \"pipe.hpp\"\n\nnamespace zk::server::detail\n{\n\n/// Represents an owned subprocess.\nclass subprocess\n{\npublic:\n    using handle        = int;\n    using argument_list = std::vector<std::string>;\n    using duration_type = std::chrono::seconds;\n\npublic:\n    explicit subprocess(std::string program_name, argument_list args = argument_list());\n\n    subprocess(const subprocess&) = delete;\n    subprocess& operator=(const subprocess&) = delete;\n\n    ~subprocess() noexcept;\n\n    /// Send a signal to this subprocess.\n    ///\n    /// \\returns \\c true if the signal likely reached the subprocess; \\c false if it might not have (this can happen if\n    ///  the subprocess has already terminated).\n    bool signal(int sig_val);\n\n    pipe& stdin()  noexcept { return _stdin; }\n    pipe& stdout() noexcept { return _stdout; }\n    pipe& stderr() noexcept { return _stderr; }\n\n    /// Terminate the process if it is still running. In the first attempt to terminate, \\c SIGTERM is used. If the\n    /// process has not terminated before \\a time_to_abort has passed, the process is signalled again with \\c SIGABRT.\n    void terminate(duration_type time_to_abort = std::chrono::seconds(1U)) noexcept;\n\nprivate:\n    std::string _program_name;\n    handle      _proc_id;\n    pipe        _stdin;\n    pipe        _stdout;\n    pipe        _stderr;\n};\n\n}\n"
  },
  {
    "path": "src/zk/server/detail/subprocess_tests.cpp",
    "content": "#include <zk/tests/test.hpp>\n\n#include <chrono>\n#include <thread>\n\n#include \"subprocess.hpp\"\n\nnamespace zk::server::detail\n{\n\nGTEST_TEST(subprocess_tests, echo)\n{\n    subprocess proc(\"echo\", { \"Hello, world!\" });\n\n    std::string read_str;\n    while (read_str.size() < 14)\n        read_str += proc.stdout().read(4096);\n    CHECK_EQ(\"Hello, world!\\n\", read_str);\n}\n\n// `cat` hangs on shutdown due to never getting EOF from stdin -- however, we should still be able to kill the process\n// with a signal on scope exit.\nGTEST_TEST(subprocess_tests, cat_kill)\n{\n    std::chrono::steady_clock::time_point scope_exit_time;\n    {\n        subprocess proc(\"cat\");\n        scope_exit_time = std::chrono::steady_clock::now();\n    }\n    auto elapsed = std::chrono::steady_clock::now() - scope_exit_time;\n    CHECK(elapsed < std::chrono::milliseconds(100));\n}\n\n}\n"
  },
  {
    "path": "src/zk/server/package_registry.cpp",
    "content": "#include \"package_registry.hpp\"\n\n#include <stdexcept>\n\nnamespace zk::server\n{\n\nstruct package_registry::registration_info final\n{\n    std::weak_ptr<package_registry> owner;\n    std::string                     name;\n\n    registration_info(std::shared_ptr<package_registry> owner, std::string name) :\n            owner(std::move(owner)),\n            name(std::move(name))\n    { }\n\n    ~registration_info() noexcept\n    {\n        if (auto strong_owner = owner.lock())\n            strong_owner->unregister_server(*this);\n    }\n};\n\npackage_registry::package_registry() :\n        _lifetime(std::make_shared<int>())\n{ }\n\npackage_registry::~package_registry() noexcept\n{\n    std::unique_lock<std::mutex> ax(_protect);\n    _lifetime.reset();\n}\n\npackage_registry::registration package_registry::register_classpath_server(std::string version, classpath packages)\n{\n    std::unique_lock<std::mutex> ax(_protect);\n\n    auto ret = _registrations.insert({ std::move(version), std::move(packages) });\n    if (!ret.second)\n        throw std::invalid_argument(version + \" is already registered\");\n\n    return std::make_shared<registration_info>(std::shared_ptr<package_registry>(_lifetime, this), ret.first->first);\n}\n\nbool package_registry::unregister_server(const registration_info& reg)\n{\n    std::unique_lock<std::mutex> ax(_protect);\n    return _registrations.erase(reg.name) > 0U;\n}\n\nbool package_registry::unregister_server(registration reg)\n{\n    if (reg)\n        return unregister_server(*reg);\n    else\n        return false;\n}\n\npackage_registry::size_type package_registry::size() const\n{\n    std::unique_lock<std::mutex> ax(_protect);\n    return _registrations.size();\n}\n\noptional<classpath> package_registry::find_newest_classpath() const\n{\n    std::unique_lock<std::mutex> ax(_protect);\n    if (_registrations.empty())\n        return nullopt;\n    else\n        return _registrations.rbegin()->second;\n}\n\n}\n"
  },
  {
    "path": "src/zk/server/package_registry.hpp",
    "content": "#pragma once\n\n#include <zk/config.hpp>\n#include <zk/optional.hpp>\n\n#include <cstddef>\n#include <map>\n#include <memory>\n#include <mutex>\n#include <string>\n\n#include \"classpath.hpp\"\n\nnamespace zk::server\n{\n\n/// \\addtogroup Server\n/// \\{\n\n/// The package registry tracks configuration of classpaths and JARs needed to run various ZooKeeper versions.\n///\n/// \\note{Thread Safety}\n/// Registering and unregistering configurations is thread-safe. However, it is \\e not safe when a \\c package_registry\n/// is being destroyed.\nclass package_registry final\n{\npublic:\n    using size_type = std::size_t;\n\n    struct registration_info;\n    using registration = std::shared_ptr<registration_info>;\n\npublic:\n    /// Create an empty registry.\n    package_registry();\n\n    ~package_registry() noexcept;\n\n    /// Register a server that can be created via the specified Java \\a classpath.\n    ///\n    /// \\param version A version string used to look up the server when creating them. While this can be a lie, it\n    ///  should not be.\n    /// \\param packages The Java classpath used to run the server. This will be the \\c cp argument to Java.\n    /// \\returns a registration that can be used to \\ref unregister_server.\n    /// \\throws std::invalid_argument if \\a version is already registered.\n    registration register_classpath_server(std::string version, classpath packages);\n\n    /// \\{\n    /// Attempt to unregister the server associated with the provided registration. Unregistering will prevent future\n    /// servers from being created with the particular setup, but will not teardown servers which might be running with\n    /// it.\n    ///\n    /// \\returns \\c true if this call removed anything; \\c false if otherwise.\n    bool unregister_server(registration reg);\n    bool unregister_server(const registration_info& reg);\n    /// \\}\n\n    /// How many registrations have been registered?\n    size_type size() const;\n\n    /// Is this registry empty?\n    bool empty() const\n    {\n        return size() == size_type(0);\n    }\n\n    /// Get the classpath for running the newest registered server version.\n    optional<classpath> find_newest_classpath() const;\n\nprivate:\n    mutable std::mutex               _protect;\n    std::shared_ptr<void>            _lifetime;\n    std::map<std::string, classpath> _registrations;\n};\n\n/// \\}\n\n}\n"
  },
  {
    "path": "src/zk/server/package_registry_tests.cpp",
    "content": "#include <zk/tests/test.hpp>\n\n#include \"classpath.hpp\"\n#include \"package_registry.hpp\"\n#include \"package_registry_tests.hpp\"\n\n#include <memory>\n#include <stdexcept>\n\nnamespace zk::server\n{\n\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n// test_package_registry                                                                                              //\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\npackage_registry& test_package_registry::instance()\n{\n    static auto instance_ptr = std::make_shared<package_registry>();\n    return *instance_ptr;\n}\n\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n// Unit Tests                                                                                                         //\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\nGTEST_TEST(package_registry_tests, test_package_registry_global_instance)\n{\n    CHECK_LT(0U, test_package_registry::instance().size());\n}\n\nGTEST_TEST(package_registry_tests, registration)\n{\n    package_registry registry;\n    CHECK_TRUE(registry.empty());\n\n    auto registration1 = registry.register_classpath_server(\"1.0\", classpath({ \"RANDOM\" }));\n    CHECK_EQ(1U, registry.size());\n    CHECK_THROWS(std::invalid_argument) { registry.register_classpath_server(\"1.0\", classpath({ \"RANDOM\" })); };\n\n    auto registration2 = registry.register_classpath_server(\"2.0\", classpath({ \"RANDOM\" }));\n    CHECK_EQ(2U, registry.size());\n    registration2.reset();\n    CHECK_EQ(1U, registry.size());\n    CHECK_FALSE(registry.unregister_server(registration2));\n\n    CHECK_TRUE(registry.unregister_server(std::move(registration1)));\n    CHECK_TRUE(registry.empty());\n}\n\n}\n"
  },
  {
    "path": "src/zk/server/package_registry_tests.hpp",
    "content": "#pragma once\n\n#include <zk/config.hpp>\n\nnamespace zk::server\n{\n\nclass package_registry;\n\n/** Global package registry for server unit tests. **/\nclass test_package_registry final\n{\npublic:\n    static package_registry& instance();\n};\n\n}\n"
  },
  {
    "path": "src/zk/server/server.cpp",
    "content": "#include \"server.hpp\"\n\n#include <zk/future.hpp>\n\n#include <cerrno>\n#include <exception>\n#include <iostream>\n#include <system_error>\n\n#include <signal.h>\n#include <sys/select.h>\n\n#include \"classpath.hpp\"\n#include \"configuration.hpp\"\n#include \"detail/event_handle.hpp\"\n#include \"detail/subprocess.hpp\"\n\nnamespace zk::server\n{\n\nstatic void validate_settings(const configuration& settings)\n{\n    if (settings.is_minimal())\n    {\n        return;\n    }\n    else if (!settings.source_file())\n    {\n        throw std::invalid_argument(\"Configuration has not been saved to a file\");\n    }\n}\n\nserver::server(classpath packages, configuration settings) :\n        _running(true),\n        _shutdown_event(std::make_unique<detail::event_handle>())\n{\n    validate_settings(settings);\n    _worker = std::thread([this, packages = std::move(packages), settings = std::move(settings)] ()\n                          {\n                              this->run_process(packages, settings);\n                          }\n                         );\n}\n\nserver::server(configuration settings) :\n        server(classpath::system_default(), std::move(settings))\n{ }\n\nserver::~server() noexcept\n{\n    shutdown(true);\n}\n\nvoid server::shutdown(bool wait_for_stop)\n{\n    _running.store(false, std::memory_order_release);\n    _shutdown_event->notify_one();\n\n    if (wait_for_stop && _worker.joinable())\n        _worker.join();\n}\n\nstatic void wait_for_event(int fd1, int fd2, int fd3)\n{\n    // This could be implemented with epoll instead of select, but since N=3, it doesn't really matter\n    ::fd_set read_fds;\n    FD_ZERO(&read_fds);\n    FD_SET(fd1, &read_fds);\n    FD_SET(fd2, &read_fds);\n    FD_SET(fd3, &read_fds);\n\n    int nfds = std::max(std::max(fd1, fd2), fd3) + 1;\n    int rc   = ::select(nfds, &read_fds, nullptr, nullptr, nullptr);\n    if (rc < 0)\n    {\n        if (errno == EINTR)\n            return;\n        else\n            throw std::system_error(errno, std::system_category(), \"select\");\n    }\n}\n\nvoid server::run_process(const classpath& packages, const configuration& settings)\n{\n    detail::subprocess::argument_list args = { \"-cp\", packages.command_line(),\n                                               \"org.apache.zookeeper.server.quorum.QuorumPeerMain\",\n                                             };\n    if (settings.is_minimal())\n    {\n        args.emplace_back(std::to_string(settings.client_port()));\n        args.emplace_back(settings.data_directory().value());\n    }\n    else\n    {\n        args.emplace_back(settings.source_file().value());\n    }\n\n    detail::subprocess proc(\"java\", std::move(args));\n\n    auto drain_pipes = [&] ()\n                       {\n                           bool read_anything = true;\n                           while (read_anything)\n                           {\n                               read_anything = false;\n\n                               auto out = proc.stdout().read();\n                               if (!out.empty())\n                               {\n                                   read_anything = true;\n                                   std::cout << out;\n                               }\n\n                               auto err = proc.stderr().read();\n                               if (!err.empty())\n                               {\n                                   read_anything = true;\n                                   std::cerr << out;\n                               }\n                           }\n                       };\n\n    while (_running.load(std::memory_order_acquire))\n    {\n        wait_for_event(proc.stdout().native_read_handle(),\n                       proc.stderr().native_read_handle(),\n                       _shutdown_event->native_handle()\n                      );\n\n        drain_pipes();\n    }\n    proc.terminate();\n    drain_pipes();\n}\n\n}\n"
  },
  {
    "path": "src/zk/server/server.hpp",
    "content": "#pragma once\n\n#include <zk/config.hpp>\n\n#include <atomic>\n#include <exception>\n#include <memory>\n#include <string>\n#include <thread>\n\nnamespace zk::server\n{\n\nnamespace detail\n{\n\nclass event_handle;\n\n}\n\n/// \\defgroup Server\n/// Control a ZooKeeper \\ref server process.\n/// \\{\n\nclass classpath;\nclass configuration;\n\n/// Controls a ZooKeeper server process on this local machine.\nclass server final\n{\npublic:\n    /// Create a running server process with the specified \\a packages and \\a settings.\n    ///\n    /// \\param packages The classpath to use to find ZooKeeper's \\c QuorumPeerMain class.\n    /// \\param settings The server settings to run with.\n    /// \\throws std::invalid_argument If `settings.is_minimal()` is \\c false and `settings.source_file()` is \\c nullopt.\n    ///  This is because non-minimal configurations require ZooKeeper to be launched with a file.\n    explicit server(classpath packages, configuration settings);\n\n    /// Create a running server with the specified \\a settings using the system-provided default packages for ZooKeeper\n    /// (see \\ref classpath::system_default).\n    ///\n    /// \\param settings The server settings to run with.\n    /// \\throws std::invalid_argument If `settings.is_minimal()` is \\c false and `settings.source_file()` is \\c nullopt.\n    ///  This is because non-minimal configurations require ZooKeeper to be launched with a file.\n    explicit server(configuration settings);\n\n    server(const server&) = delete;\n\n    ~server() noexcept;\n\n    /// Initiate shutting down the server process. For most usage, this is not needed, as it is called automatically\n    /// from the destructor.\n    ///\n    /// \\param wait_for_stop If \\c true, wait for the process to run until termination instead of simply initiating the\n    ///  termination.\n    void shutdown(bool wait_for_stop = false);\n\nprivate:\n    void run_process(const classpath&, const configuration&);\n\nprivate:\n    std::atomic<bool>                     _running;\n    std::unique_ptr<detail::event_handle> _shutdown_event;\n    std::thread                           _worker;\n\n    // NOTE: The configuration is NOT stored in the server object. This is because configuration can be changed by the\n    // ZK process in cases like ensemble reconfiguration. It is the job of run_process to deal with this.\n};\n\n/// \\}\n\n}\n"
  },
  {
    "path": "src/zk/server/server_group.cpp",
    "content": "#include \"server_group.hpp\"\n\n#include <algorithm>\n#include <cerrno>\n#include <fstream>\n#include <sstream>\n#include <stdexcept>\n#include <system_error>\n\n#include <sys/stat.h>\n#include <sys/types.h>\n\n#include \"classpath.hpp\"\n#include \"configuration.hpp\"\n#include \"server.hpp\"\n\nnamespace zk::server\n{\n\nstruct server_group::info\n{\n    configuration           settings;     //!< Settings for this server.\n    std::string             name;         //!< Name of this server (string version of its ID)\n    std::string             path;         //!< settings.data_directory\n    std::uint16_t           peer_port;    //!< settings.peer_port\n    std::uint16_t           leader_port;  //!< settings.leader_port\n    std::shared_ptr<server> instance;     //!< Instance(if this is running)\n\n    info(const configuration& base) :\n            settings(base)\n    { }\n};\n\nserver_group::server_group() noexcept\n{ }\n\nserver_group::server_group(server_group&&) noexcept = default;\n\nserver_group& server_group::operator=(server_group&& src) noexcept\n{\n    _servers     = std::move(src._servers);\n    _conn_string = std::move(src._conn_string);\n    return *this;\n}\n\nstatic void create_directory(const std::string& path)\n{\n    if (::mkdir(path.c_str(), 0755))\n    {\n        throw std::system_error(errno, std::system_category());\n    }\n}\n\nstatic void save_id_file(const std::string& path, const server_id& id)\n{\n    std::ofstream ofs(path.c_str());\n    if (!ofs)\n        throw std::runtime_error(\"IO failure\");\n    ofs << id << '\\n';\n    ofs.flush();\n}\n\nserver_group server_group::make_ensemble(std::size_t size, const configuration& base_settings_in)\n{\n    auto base_settings = base_settings_in;\n\n    if (!base_settings.data_directory())\n        throw std::invalid_argument(\"Settings must specify a base directory\");\n\n    auto base_directory = base_settings.data_directory().value();\n    auto base_port      = base_settings.client_port() == configuration::default_client_port ? std::uint16_t(18500)\n                                                                                            : base_settings.client_port();\n\n    server_group out;\n    for (std::size_t idx = 0U; idx < size; ++idx)\n    {\n        auto id    = server_id(idx + 1);\n        auto px    = std::make_shared<info>(base_settings);\n        auto& x    = *px;\n        x.name     = std::to_string(id.value);\n        x.path     = base_directory + \"/\" + x.name;\n        x.settings\n            .client_port(base_port++)\n            .data_directory(x.path + \"/data\")\n            ;\n        x.peer_port   = base_port++;\n        x.leader_port = base_port++;\n\n        out._servers.emplace(id, px);\n    }\n\n    std::ostringstream conn_str_os;\n    conn_str_os << \"zk://\";\n    bool first = true;\n\n    create_directory(base_directory);\n    for (auto& [id, srvr] : out._servers)\n    {\n        for (const auto& [id2, srvr2] : out._servers)\n        {\n            srvr->settings.add_server(id2, \"127.0.0.1\", srvr2->peer_port, srvr2->leader_port);\n        }\n\n        create_directory(srvr->path);\n        create_directory(srvr->path + \"/data\");\n        save_id_file(srvr->path + \"/data/myid\", id);\n        srvr->settings.save_file(srvr->path + \"/settings.cfg\");\n\n        if (!std::exchange(first, false))\n            conn_str_os << ',';\n        conn_str_os << \"127.0.0.1:\" << srvr->settings.client_port();\n    }\n    conn_str_os << '/';\n    out._conn_string = conn_str_os.str();\n\n    return out;\n}\n\nconst std::string& server_group::get_connection_string()\n{\n    return _conn_string;\n}\n\nvoid server_group::start_all_servers(const classpath& packages)\n{\n    for (auto& [name, srvr] : _servers)\n    {\n        static_cast<void>(name);\n\n        if (!srvr->instance)\n        {\n            srvr->instance = std::make_shared<server>(packages, srvr->settings);\n        }\n    }\n}\n\n}\n"
  },
  {
    "path": "src/zk/server/server_group.hpp",
    "content": "#pragma once\n\n#include <zk/config.hpp>\n\n#include <cstdint>\n#include <map>\n#include <memory>\n#include <string>\n#include <vector>\n\n#include \"configuration.hpp\"\n\nnamespace zk::server\n{\n\n/** \\addtogroup Server\n *  \\{\n**/\n\nclass classpath;\nclass server;\n\n/// Create and manage a group of \\ref server instances on this local machine (most likely in a single ensemble). This is\n/// exclusively for testing, as running multiple peers on a single machine is a very bad idea in production.\n///\n/// \\code\n/// auto servers = zk::server::server_group::make_ensemble(3U,\n///                                                        zk::server::configuration::make_minimal(\"test-data\")\n///                                                       );\n/// servers.start_all_servers();\n/// auto client = zk::client::connect(servers.get_connection_string()).get();\n/// // do things with client...\n/// \\endcode\nclass server_group final\n{\npublic:\n    /// Create an empty server group.\n    server_group() noexcept;\n\n    /// Move-construct a server group.\n    server_group(server_group&&) noexcept;\n\n    /// Move-assign a server group.\n    server_group& operator=(server_group&&) noexcept;\n\n    /// Create an ensemble of the given \\a size. None of the servers will be started.\n    ///\n    /// \\param size The size of the ensemble. This should be an odd number.\n    /// \\param base_settings The basic settings to use for every server.\n    static server_group make_ensemble(std::size_t size, const configuration& base_settings);\n\n    /// Get a connection string which can connect to any the servers in the group.\n    const std::string& get_connection_string();\n\n    /// Start all servers in the group. Note that this does not wait for the servers to be up-and-running. Use a\n    /// \\ref client instance to check for connectability.\n    void start_all_servers(const classpath& packages);\n\n    /// How many servers are in this group?\n    std::size_t size() const { return _servers.size(); }\n\nprivate:\n    struct info;\n\n    using server_map_type = std::map<server_id, std::shared_ptr<info>>;\n\nprivate:\n    server_map_type _servers;\n    std::string     _conn_string;\n};\n\n/// \\}\n\n}\n"
  },
  {
    "path": "src/zk/server/server_group_tests.cpp",
    "content": "#include <zk/client.hpp>\n#include <zk/tests/test.hpp>\n\n#include <chrono>\n#include <thread>\n\n#include \"configuration.hpp\"\n#include \"package_registry.hpp\"\n#include \"package_registry_tests.hpp\"\n#include \"server_group.hpp\"\n\nnamespace zk::server\n{\n\nvoid delete_directory(std::string path);\n\nGTEST_TEST(server_group_tests, ensemble)\n{\n    delete_directory(\"ensemble\");\n    auto group = server_group::make_ensemble(5U, configuration::make_minimal(\"ensemble\"));\n    group.start_all_servers(test_package_registry::instance().find_newest_classpath().value());\n\n    // connect and get data from the ensemble\n    auto c = client::connect(group.get_connection_string()).get();\n    CHECK_TRUE(c.exists(\"/\").get());\n}\n\n}\n"
  },
  {
    "path": "src/zk/server/server_tests.cpp",
    "content": "#include <zk/tests/test.hpp>\n#include <zk/client.hpp>\n\n#include <cerrno>\n#include <chrono>\n#include <iostream>\n#include <system_error>\n#include <thread>\n\n#include <ftw.h>\n#include <unistd.h>\n\n#include \"classpath.hpp\"\n#include \"configuration.hpp\"\n#include \"package_registry.hpp\"\n#include \"package_registry_tests.hpp\"\n#include \"server.hpp\"\n#include \"server_tests.hpp\"\n\nnamespace zk::server\n{\n\nvoid delete_directory(std::string path)\n{\n    auto unlink_cb = [] (ptr<const char> fpath, ptr<const struct ::stat>, int, ptr<struct FTW>) -> int\n                     {\n                         return std::remove(fpath);\n                     };\n\n    if (nftw(path.c_str(), unlink_cb, 64, FTW_DEPTH | FTW_PHYS))\n    {\n        if (errno == ENOENT)\n            return;\n        else\n            throw std::system_error(errno, std::system_category());\n    }\n}\n\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n// server_fixture                                                                                                     //\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\nvoid server_fixture::SetUp()\n{\n    delete_directory(\"zk-data\");\n    _server = std::make_shared<server>(test_package_registry::instance().find_newest_classpath().value(),\n                                       configuration::make_minimal(\"zk-data\")\n                                      );\n    _conn_string = \"zk://127.0.0.1:2181\";\n}\n\nvoid server_fixture::TearDown()\n{\n    _server->shutdown();\n    _server.reset();\n    _conn_string.clear();\n}\n\nconst std::string& server_fixture::get_connection_string() const\n{\n    return _conn_string;\n}\n\nclient server_fixture::get_connected_client() const\n{\n    return client(client::connect(get_connection_string()).get());\n}\n\nvoid server_fixture::stop_server(bool wait_for_stop)\n{\n    _server->shutdown(wait_for_stop);\n}\n\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n// single_server_fixture                                                                                              //\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\nstatic std::shared_ptr<server> single_server_server;\nstatic std::string             single_server_conn_string;\n\nvoid single_server_fixture::SetUpTestCase()\n{\n    delete_directory(\"zk-data\");\n    single_server_server = std::make_shared<server>(test_package_registry::instance().find_newest_classpath().value(),\n                                                    configuration::make_minimal(\"zk-data\")\n                                                   );\n    single_server_conn_string = \"zk://127.0.0.1:2181\";\n}\n\nvoid single_server_fixture::TearDownTestCase()\n{\n    single_server_server->shutdown();\n    single_server_server.reset();\n    single_server_conn_string.clear();\n}\n\nconst std::string& single_server_fixture::get_connection_string()\n{\n    return single_server_conn_string;\n}\n\nclient single_server_fixture::get_connected_client()\n{\n    return client(client::connect(get_connection_string()).get());\n}\n\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n// Unit Tests                                                                                                         //\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\nGTEST_TEST(server_tests, start_stop)\n{\n    server svr(test_package_registry::instance().find_newest_classpath().value(),\n               configuration::make_minimal(\"zk-data\")\n              );\n    std::this_thread::sleep_for(std::chrono::seconds(1));\n    svr.shutdown();\n}\n\nGTEST_TEST(server_tests, shutdown_and_wait)\n{\n    server svr(test_package_registry::instance().find_newest_classpath().value(),\n               configuration::make_minimal(\"zk-data\")\n              );\n    svr.shutdown(true);\n}\n\n}\n"
  },
  {
    "path": "src/zk/server/server_tests.hpp",
    "content": "#pragma once\n\n#include <zk/config.hpp>\n#include <zk/forwards.hpp>\n#include <zk/tests/test.hpp>\n\n#include <memory>\n\nnamespace zk::server\n{\n\nclass server;\n\nclass server_fixture :\n        public test::test_fixture\n{\npublic:\n    virtual void SetUp() override;\n\n    virtual void TearDown() override;\n\nprotected:\n    const std::string& get_connection_string() const;\n\n    client get_connected_client() const;\n\n    void stop_server(bool wait_for_stop = true);\n\nprivate:\n    std::shared_ptr<server> _server;\n    std::string             _conn_string;\n};\n\n/// Similar to \\ref server_fixture, but do not start up and tear down the server with each test. Instead, setup is run\n/// once at the start of a suite and torn down at the end of it.\nclass single_server_fixture :\n        public test::test_fixture\n{\npublic:\n    static void SetUpTestCase();\n\n    static void TearDownTestCase();\n\nprotected:\n    static const std::string& get_connection_string();\n\n    static client get_connected_client();\n};\n\n}\n"
  },
  {
    "path": "src/zk/string_view.hpp",
    "content": "/// \\file\n/// Imports the \\c string_view type as \\c std::string_view.\n#pragma once\n\n#include <zk/config.hpp>\n\n#include <string_view>\n\nnamespace zk\n{\n\n/// \\addtogroup Client\n/// \\{\n\nusing string_view = std::string_view;\n\n/// \\}\n\n}\n"
  },
  {
    "path": "src/zk/tests/main.cpp",
    "content": "#include \"test.hpp\"\n\nint main(int argc, char** argv)\n{\n    ::testing::InitGoogleTest(&argc, argv);\n    return ::RUN_ALL_TESTS();\n}\n"
  },
  {
    "path": "src/zk/tests/test.cpp",
    "content": "#include \"test.hpp\"\n"
  },
  {
    "path": "src/zk/tests/test.hpp",
    "content": "#pragma once\n\n#include <zk/config.hpp>\n\n#ifdef GTEST_API_\n#   error \"GTest was included externally -- you MUST include <zk/tests/test.hpp> first\"\n#endif\n\n// Before including GTest, tell it not to define the various generically-named macros (which clash with other common\n// names).\n#define GTEST_DONT_DEFINE_ASSERT_EQ 1\n#define GTEST_DONT_DEFINE_ASSERT_NE 1\n#define GTEST_DONT_DEFINE_ASSERT_LE 1\n#define GTEST_DONT_DEFINE_ASSERT_LT 1\n#define GTEST_DONT_DEFINE_ASSERT_GE 1\n#define GTEST_DONT_DEFINE_ASSERT_GT 1\n#define GTEST_DONT_DEFINE_TEST      1\n#define GTEST_DONT_DEFINE_FAIL      1\n#define GTEST_DONT_DEFINE_SUCCEED   1\n\n#include <gtest/gtest.h>\n\n// Maybe some day this will be defined\n#ifndef GTEST_TEST_F\n#   define GTEST_TEST_F TEST_F\n#endif\n\nnamespace zk::test\n{\n\n/** \\def CHECK\n *  Check a condition and abort the test in failure if it does not hold. When performing binary comparisons, prefer to\n *  use the check macros which accept two arguments (\\c CHECK_EQ, \\c CHECK_LT, etc), as these will print out the values\n *  in failure cases.\n *\n *  \\example\n *  \\code\n *  CHECK(something) << \"Something isn't right!\";\n *  \\endcode\n**/\n#define CHECK(cond)       GTEST_TEST_BOOLEAN_(cond,     #cond, false, true,  GTEST_FATAL_FAILURE_)\n#define CHECK_TRUE(cond)  GTEST_TEST_BOOLEAN_(!!(cond), #cond, false, true,  GTEST_FATAL_FAILURE_)\n#define CHECK_FALSE(cond) GTEST_TEST_BOOLEAN_(!(cond),  #cond, true,  false, GTEST_FATAL_FAILURE_)\n#define CHECK_EQ          GTEST_ASSERT_EQ\n#define CHECK_NE          GTEST_ASSERT_NE\n#define CHECK_LT          GTEST_ASSERT_LT\n#define CHECK_LE          GTEST_ASSERT_LE\n#define CHECK_GT          GTEST_ASSERT_GT\n#define CHECK_GE          GTEST_ASSERT_GE\n#define CHECK_SUCCESS     GTEST_SUCCEED\n#define CHECK_FAIL        GTEST_FAIL\n\nusing test_fixture = ::testing::Test;\n\nnamespace detail\n{\n\ntemplate <typename TException>\nstruct check_throws_info\n{\n    ptr<const char> filename;\n    std::size_t     line_no;\n\n    explicit check_throws_info(ptr<const char> filename, std::size_t line_no) :\n            filename(filename),\n            line_no(line_no)\n    { }\n\n    friend std::ostream& operator<<(std::ostream& os, const check_throws_info& info)\n    {\n        return os << info.filename << ':' << info.line_no;\n    }\n};\n\ntemplate <typename TException, typename FAction>\nvoid operator+(const check_throws_info<TException>& info, FAction&& action)\n{\n    try\n    {\n        std::forward<FAction>(action)();\n        CHECK_FAIL() << \"At \" << info << \": Action was supposed to throw, but it did not\"; // LCOV_EXCL_LINE\n    }\n    catch (const TException&)\n    {\n        CHECK_SUCCESS() << \"Successfully threw expected error\";\n    }\n}\n\n/** \\def CHECK_THROWS\n *  Ensure that a block of code throws an exception of type \\a ex.\n *\n *  \\code\n *  CHECK_THROWS(std::logic_error)\n *  {\n *      throw std::logic_error(\"Some issue\");\n *  }; // <- note the ';'\n *  \\endcode\n**/\n#define CHECK_THROWS(ex)                                                                                               \\\n    ::zk::test::detail::check_throws_info<ex>(__FILE__, __LINE__) + [&] ()\n\n}\n\n}\n"
  },
  {
    "path": "src/zk/types.cpp",
    "content": "#include \"types.hpp\"\n\n#include <ostream>\n#include <sstream>\n#include <utility>\n\nnamespace zk\n{\n\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n// event_type                                                                                                         //\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\nstd::ostream& operator<<(std::ostream& os, const event_type& self)\n{\n    switch (self)\n    {\n    case event_type::error:        return os << \"error\";\n    case event_type::created:      return os << \"created\";\n    case event_type::erased:       return os << \"erased\";\n    case event_type::changed:      return os << \"changed\";\n    case event_type::child:        return os << \"child\";\n    case event_type::session:      return os << \"session\";\n    case event_type::not_watching: return os << \"not_watching\";\n    default:                       return os << \"event_type(\" << static_cast<int>(self) << ')';\n    };\n}\n\nstd::string to_string(const event_type& self)\n{\n    std::ostringstream os;\n    os << self;\n    return os.str();\n}\n\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n// version                                                                                                            //\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\nstd::ostream& operator<<(std::ostream& os, const version& self)\n{\n    os << \"version(\";\n    if (self == version::any())\n        os << \"any\";\n    else if (self == version::invalid())\n        os << \"invalid\";\n    else\n        os << self.value;\n    return os << ')';\n}\n\nstd::string to_string(const version& self)\n{\n    std::ostringstream os;\n    os << self;\n    return os.str();\n}\n\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n// acl_version                                                                                                        //\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\nstd::ostream& operator<<(std::ostream& os, const acl_version& self)\n{\n    os << \"acl_version(\";\n    if (self == acl_version::any())\n        os << \"any\";\n    else if (self == acl_version::invalid())\n        os << \"invalid\";\n    else\n        os << self.value;\n    return os << ')';\n}\n\nstd::string to_string(const acl_version& self)\n{\n    std::ostringstream os;\n    os << self;\n    return os.str();\n}\n\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n// child_version                                                                                                      //\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\nstd::ostream& operator<<(std::ostream& os, const child_version& self)\n{\n    os << \"child_version(\";\n    if (self == child_version::any())\n        os << \"any\";\n    else if (self == child_version::invalid())\n        os << \"invalid\";\n    else\n        os << self.value;\n    return os << ')';\n}\n\nstd::string to_string(const child_version& self)\n{\n    std::ostringstream os;\n    os << self;\n    return os.str();\n}\n\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n// transaction_id                                                                                                     //\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\nstd::ostream& operator<<(std::ostream& os, const transaction_id& self)\n{\n    return os << \"transaction_id(\" << self.value << ')';\n}\n\nstd::string to_string(const transaction_id& self)\n{\n    std::ostringstream os;\n    os << self;\n    return os.str();\n}\n\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n// stat                                                                                                               //\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\nstd::ostream& operator<<(std::ostream& os, const stat& self)\n{\n    os << \"{data_version=\"   << self.data_version.value;\n    os << \" child_version=\"  << self.child_version.value;\n    os << \" acl_version=\"    << self.acl_version.value;\n    os << \" data_size=\"      << self.data_size;\n    os << \" children_count=\" << self.children_count;\n    os << \" ephemeral=\"      << (self.is_ephemeral() ? \"true\" : \"false\");\n    return os << '}';\n}\n\nstd::string to_string(const stat& self)\n{\n    std::ostringstream os;\n    os << self;\n    return os.str();\n}\n\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n// state                                                                                                              //\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\nstd::ostream& operator<<(std::ostream& os, const state& self)\n{\n    switch (self)\n    {\n    case state::closed:                return os << \"closed\";\n    case state::connecting:            return os << \"connecting\";\n    case state::connected:             return os << \"connected\";\n    case state::read_only:             return os << \"read_only\";\n    case state::expired_session:       return os << \"expired_session\";\n    case state::authentication_failed: return os << \"authentication_failed\";\n    default:                           return os << \"state(\" << static_cast<int>(self) << ')';\n    }\n}\n\nstd::string to_string(const state& self)\n{\n    std::ostringstream os;\n    os << self;\n    return os.str();\n}\n\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n// create_mode                                                                                                        //\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\nstd::ostream& operator<<(std::ostream& os, const create_mode& mode)\n{\n    if (mode == create_mode::normal)\n        return os << \"normal\";\n\n    bool first = true;\n    auto tick = [&] { return std::exchange(first, false) ? \"\" : \"|\"; };\n    if (is_set(mode, create_mode::ephemeral))  os << tick() << \"ephemeral\";\n    if (is_set(mode, create_mode::sequential)) os << tick() << \"sequential\";\n    if (is_set(mode, create_mode::container))  os << tick() << \"container\";\n\n    return os;\n}\n\nstd::string to_string(const create_mode& self)\n{\n    std::ostringstream os;\n    os << self;\n    return os.str();\n}\n\n}\n"
  },
  {
    "path": "src/zk/types.hpp",
    "content": "#pragma once\n\n#include <zk/config.hpp>\n\n#include <chrono>\n#include <iosfwd>\n#include <string>\n\nnamespace zk\n{\n\n/// \\addtogroup Client\n/// \\{\n\n/// Base type for creating strong ID types. These behave similar to a \\c typedef, but do not allow conversion between\n/// different \\c TReal types, even if they share the same \\c TId. This makes attempting to use a \\c version instead of\n/// an \\c acl_version in \\ref client::set_acl a compile-time failure instead of throwing a \\c bad_version at run-time.\n///\n/// \\tparam TReal The \"real\" leaf type of this ID.\n/// \\tparam TId The representation type of this ID. This must be some form of integer and is usually \\c std::int32_t (as\n///  in \\ref basic_version).\n///\n/// \\see version\n/// \\see child_version\n/// \\see acl_version\n/// \\see transaction_id\ntemplate <typename TReal, typename TId>\nstruct strong_id\n{\n    /// The representation type of this ID.\n    using value_type = TId;\n\n    /// Underlying value of this ID.\n    value_type value;\n\n    /// Default construct the ID. This probably leaves \\c value uninitialized (depending on the constructor of\n    /// \\ref value_type).\n    strong_id() noexcept = default;\n\n    /// Construct this instance with the given \\a value.\n    constexpr explicit strong_id(value_type value) noexcept :\n            value(value)\n    { }\n\n    /// Get the \\ref value of this ID.\n    constexpr value_type get() const noexcept { return value; }\n\n    /// Get the \\ref value of this ID.\n    ///\n    /// \\see get\n    explicit constexpr operator value_type() noexcept { return value; }\n\n    /// \\{\n    /// Increment the \\ref value of this ID by 1. This can be useful for maintaining caches after updates without\n    /// getting the result. For example, a transaction with a version-checked set operation knows the next version of\n    /// the entry will be only one more.\n    TReal& operator++()\n    {\n        ++this->value;\n        return *static_cast<TReal*>(this);\n    }\n\n    TReal operator++(int)\n    {\n        TReal copy(*this);\n        operator++();\n        return copy;\n    }\n    /// \\}\n\n    /// \\{\n    /// Decrement the \\ref value of this ID by 1. This operation is probably not useful, but is included for\n    /// completeness.\n    TReal& operator--()\n    {\n        --this->value;\n        return *static_cast<TReal*>(this);\n    }\n\n    TReal operator--(int)\n    {\n        TReal copy(*this);\n        operator--();\n        return copy;\n    }\n    /// \\}\n};\n\ntemplate <typename TReal, typename TId>\nconstexpr bool operator==(const strong_id<TReal, TId>& a, const strong_id<TReal, TId>& b)\n{\n    return a.value == b.value;\n}\n\ntemplate <typename TReal, typename TId>\nconstexpr bool operator!=(const strong_id<TReal, TId>& a, const strong_id<TReal, TId>& b)\n{\n    return a.value != b.value;\n}\n\ntemplate <typename TReal, typename TId>\nconstexpr bool operator<(const strong_id<TReal, TId>& a, const strong_id<TReal, TId>& b)\n{\n    return a.value < b.value;\n}\n\ntemplate <typename TReal, typename TId>\nconstexpr bool operator<=(const strong_id<TReal, TId>& a, const strong_id<TReal, TId>& b)\n{\n    return a.value <= b.value;\n}\n\ntemplate <typename TReal, typename TId>\nconstexpr bool operator>(const strong_id<TReal, TId>& a, const strong_id<TReal, TId>& b)\n{\n    return a.value > b.value;\n}\n\ntemplate <typename TReal, typename TId>\nconstexpr bool operator>=(const strong_id<TReal, TId>& a, const strong_id<TReal, TId>& b)\n{\n    return a.value >= b.value;\n}\n\n/// Compute the \\c std::hash of the given \\a x.\ntemplate <typename TReal, typename TId>\ninline std::size_t hash(const strong_id<TReal, TId>& x)\n{\n    return std::hash<TId>()(x.value);\n}\n\n/// Base type for version types. These are distinct so we can emit a compilation error on attempts to use the incorrect\n/// version type (for example, \\ref client::set_acl does a check on \\ref acl_version instead of the standard\n/// \\ref version).\n///\n/// \\see version\n/// \\see acl_version\n/// \\see child_version\ntemplate <typename TReal>\nstruct basic_version :\n        public strong_id<TReal, std::int32_t>\n{\n    /// An invalid version specifier. This will never be returned by the database and will always be rejected in commit\n    /// operations. This is a good value to use as a placeholder when you are searching for the proper \\ref version.\n    static constexpr TReal invalid() { return TReal(-42); };\n\n    /** When specified in an operation, this version specifier will always pass. It is the equivalent to not performing\n     *  a version check.\n    **/\n    static constexpr TReal any() { return TReal(-1); };\n\n    using strong_id<TReal, std::int32_t>::strong_id;\n};\n\n/// Represents a version of the data.\n///\n/// \\see stat::data_version\nstruct version final :\n        public basic_version<version>\n{\n    using basic_version<version>::basic_version;\n};\n\nstd::ostream& operator<<(std::ostream&, const version&);\n\nstd::string to_string(const version&);\n\n/// Represents a version of the ACL of an entry.\n///\n/// \\see stat::acl_version\nstruct acl_version final :\n        public basic_version<acl_version>\n{\n    using basic_version<acl_version>::basic_version;\n};\n\nstd::ostream& operator<<(std::ostream&, const acl_version&);\n\nstd::string to_string(const acl_version&);\n\n/// Represents a version of the children of an entry.\n///\n/// \\see stat::child_version\nstruct child_version final :\n        public basic_version<child_version>\n{\n    using basic_version<child_version>::basic_version;\n};\n\nstd::ostream& operator<<(std::ostream&, const child_version&);\n\nstd::string to_string(const child_version&);\n\n/// Represents the ZooKeeper transaction ID in which an event happened to an entry.\n///\n/// \\see stat::create_transaction\n/// \\see stat::modified_transaction\n/// \\see stat::child_modified_transaction\nstruct transaction_id final :\n        public strong_id<transaction_id, std::size_t>\n{\n    using strong_id::strong_id;\n};\n\nstd::ostream& operator<<(std::ostream&, const transaction_id&);\n\nstd::string to_string(const transaction_id&);\n\n/// Statistics about a ZooKeeper entry, similar to the UNIX `stat` structure.\n///\n/// \\note{Time in ZooKeeper}\n/// The concept of time is tricky in distributed systems. ZooKeeper keeps track of time in a number of ways.\n///\n/// - **zxid**: Every change to a ZooKeeper cluster receives a stamp in the form of a *zxid* (ZooKeeper Transaction ID).\n///   This exposes the total ordering of all changes to ZooKeeper. Each change will have a unique *zxid* -- if *zxid:a*\n///   is smaller than *zxid:b*, then the associated change to *zxid:a* happened before *zxid:b*.\n/// - **Version Numbers**: Every change to an entry will cause an increase to one of the version numbers of that entry.\n/// - **Clock Time**: ZooKeeper does not use clock time to make decisions, but it uses it to put timestamps into the\n///   \\c stat structure.\nstruct stat final\n{\npublic:\n    using time_point = std::chrono::system_clock::time_point;\n\npublic:\n    /// The transaction ID that created the entry.\n    transaction_id create_transaction;\n\n    /// The last transaction that modified the entry.\n    transaction_id modified_transaction;\n\n    /// The transaction ID that last modified the children of the entry.\n    transaction_id child_modified_transaction;\n\n    /// Time the entry was created.\n    ///\n    /// \\warning\n    /// This should \\e not be relied on for any logic. ZooKeeper sets this time based on the system clock of the master\n    /// server at the time the entry is created and performs no validity checking or synchronization with other servers\n    /// in the cluster. As such, there is no guarantee that this value is accurrate. There are many situations where a\n    /// entry with a higher \\c create_transaction (created after) will have a lower \\c create_time (appear to have been\n    /// created before).\n    time_point create_time;\n\n    /// Last time the entry was last modified. Like \\ref create_time, this is not a reliable source.\n    time_point modified_time;\n\n    /// The number of changes to the data of the entry. This value is used in operations like \\c client::set that take a\n    /// version check before modifying data.\n    zk::version data_version;\n\n    /// The number of changes to the children of the entry.\n    zk::child_version child_version;\n\n    /// The number of changes to the ACL of the entry.\n    zk::acl_version acl_version;\n\n    /// The session ID of the owner of this entry, if it is an ephemeral entry. In general, this is not useful beyond a\n    /// check for being \\c 0.\n    ///\n    /// \\see is_ephemeral\n    std::uint64_t ephemeral_owner;\n\n    /// The size of the data field of the entry.\n    std::size_t data_size;\n\n    /// The number of children this entry has.\n    std::size_t children_count;\n\n    /// Is the entry an ephemeral entry?\n    bool is_ephemeral() const\n    {\n        return ephemeral_owner == 0U;\n    }\n};\n\nstd::ostream& operator<<(std::ostream&, const stat&);\n\nstd::string to_string(const stat&);\n\n/// When used in \\c client::set, this value determines how the entry is created on the server. These values can be ORed\n/// together to create combinations.\nenum class create_mode : unsigned int\n{\n    /// Standard behavior of an entry -- the opposite of doing any of the options.\n    normal = 0b0000,\n    /// The entry will be deleted when the client session expires.\n    ephemeral = 0b0001,\n    /// The name of the entry will be appended with a monotonically increasing number. The actual path name of a\n    /// sequential entry will be the given path plus a suffix \\c \"i\" where \\c i is the current sequential number of the\n    /// entry. The sequence number is always fixed length of 10 digits, 0 padded. Once such a entry is created, the\n    /// sequential number will be incremented by one.\n    sequential = 0b0010,\n    /// Container entries are special purpose entries useful for recipes such as leader, lock, etc. When the last child\n    /// of a container is deleted, the container becomes a candidate to be deleted by the server at some point in the\n    /// future. Given this property, you should be prepared to get \\ref no_entry when creating children inside of this\n    /// container entry.\n    container = 0b0100,\n};\n\n/// Set union operation of \\ref create_mode.\nconstexpr create_mode operator|(create_mode a, create_mode b)\n{\n    return create_mode(static_cast<unsigned int>(a) | static_cast<unsigned int>(b));\n}\n\n/// Set intersection operation of \\ref create_mode.\nconstexpr create_mode operator&(create_mode a, create_mode b)\n{\n    return create_mode(static_cast<unsigned int>(a) & static_cast<unsigned int>(b));\n}\n\n/// Set inverse operation of \\ref create_mode. This is not exactly the bitwise complement of \\a a, as the returned value\n/// will only include bits set that are valid in \\ref create_mode discriminants.\nconstexpr create_mode operator~(create_mode a)\n{\n    return create_mode(~static_cast<unsigned int>(a) & 0b0111);\n}\n\n/// Check that \\a self has \\a flags set.\nconstexpr bool is_set(create_mode self, create_mode flags)\n{\n    return (self & flags) == flags;\n}\n\nstd::ostream& operator<<(std::ostream&, const create_mode&);\n\nstd::string to_string(const create_mode&);\n\n/// Enumeration of types of events that may occur.\nenum class event_type : int\n{\n    error           =  0, //!< Invalid event (this should never be issued).\n    created         =  1, //!< Issued when an entry for a given path is created.\n    erased          =  2, //!< Issued when an entry at a given path is erased.\n    changed         =  3, //!< Issued when the data of a watched entry is altered. This event value is issued whenever\n                          //!< a \\e set operation occurs without an actual contents check, so there is no guarantee the\n                          //!< data actually changed.\n    child           =  4, //!< Issued when the children of a watched entry are created or deleted. This event is not\n                          //!< issued when the data within children is altered.\n    session         = -1, //!< This value is issued as part of an event when the \\c state changes.\n    not_watching    = -2, //!< Watch has been forcefully removed. This is generated when the server for some reason\n                          //!< (probably a resource constraint), will no longer watch an entry for a client.\n};\n\nstd::ostream& operator<<(std::ostream&, const event_type&);\n\nstd::string to_string(const event_type&);\n\n/// Enumeration of states the client may be at when a watch triggers. It represents the state of the connection at the\n/// time the event was generated.\n///\n/// \\dot\n/// digraph G {\n///   rankdir = LR\n///\n///   connecting\n///   connected\n///   read_only\n///   authentication_failed\n///   expired_session\n///   closed\n///\n///   connecting -> connected             [label=\"Successful connection\"]\n///   connecting -> read_only             [label=\"Connection to read-only peer\"]\n///   connecting -> authentication_failed [label=\"Authentication failure\"]\n///   connecting -> expired_session       [label=\"Session lost\"]\n///   connecting -> closed                [label=\"close()\"]\n///   connected -> connecting             [label=\"Connection lost\" color=\"red\"]\n///   connected -> closed                 [label=\"close()\"]\n///   read_only -> connecting             [label=\"Connection lost\" color=\"red\"]\n///   read_only -> closed                 [label=\"close()\"]\n///   authentication_failed -> closed     [label=\"close()\"]\n///   expired_session -> closed           [label=\"close()\"]\n/// }\n/// \\enddot\n///\n/// \\note\n/// If you are familiar with the C API, notably missing from this list is a \\c ZOO_NOTCONNECTED_STATE equivalent. This\n/// state happens in cases where the client disconnects on purpose (either on initial connection or just after ensemble\n/// reconfiguration). However, the ability to see this state is limited to times when you call \\c zk_state at just the\n/// right moment. This state leads to a bit of confusion with \\c closed and \\c expired_session, so it is not in the\n/// list. Instead, these cases are presented as just \\c connecting, as the client is attempting to reconnect to the\n/// cluster.\nenum class state : int\n{\n    closed                =    0, //!< The client is not connected to any server in the ensemble.\n    connecting            =    1, //!< The client is connecting.\n    connected             =    3, //!< The client is in the connected state -- it is connected to a server in the\n                                  //!< ensemble (one of the servers specified in the host connection parameter during\n                                  //!< ZooKeeper client creation).\n    read_only             =    5, //!< The client is connected to a read-only server, that is the server which is not\n                                  //!< currently connected to the majority. The only operations allowed after receiving\n                                  //!< this state is read operations. This state is generated for read-only clients only\n                                  //!< since read/write clients aren't allowed to connect to read-only servers.\n    expired_session       = -112, //!< The serving cluster has expired this session. The ZooKeeper client connection\n                                  //!< (the session) is no longer valid. You must create a new client \\c connection if\n                                  //!< you wish to access the ensemble.\n    authentication_failed = -113, //!< Authentication has failed -- connection requires a new \\c connection instance\n                                  //!< with different credentials.\n};\n\nstd::ostream& operator<<(std::ostream&, const state&);\n\n/// Get the string representation of the provided \\a state.\nstd::string to_string(const state& state);\n\n/// \\}\n\n}\n\nnamespace std\n{\n\ntemplate <>\nstruct hash<zk::version>\n{\n    using argument_type = zk::version;\n    using result_type   = std::size_t;\n\n    result_type operator()(const argument_type& x) const\n    {\n        return zk::hash(x);\n    }\n};\n\ntemplate <>\nstruct hash<zk::acl_version>\n{\n    using argument_type = zk::acl_version;\n    using result_type   = std::size_t;\n\n    result_type operator()(const argument_type& x) const\n    {\n        return zk::hash(x);\n    }\n};\n\ntemplate <>\nstruct hash<zk::child_version>\n{\n    using argument_type = zk::child_version;\n    using result_type   = std::size_t;\n\n    result_type operator()(const argument_type& x) const\n    {\n        return zk::hash(x);\n    }\n};\n\ntemplate <>\nstruct hash<zk::transaction_id>\n{\n    using argument_type = zk::transaction_id;\n    using result_type   = std::size_t;\n\n    result_type operator()(const argument_type& x) const\n    {\n        return zk::hash(x);\n    }\n};\n\n}\n"
  },
  {
    "path": "src/zk/types_tests.cpp",
    "content": "#include <zk/tests/test.hpp>\n\n#include \"types.hpp\"\n\nnamespace zk\n{\n\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n// event_type                                                                                                         //\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\nGTEST_TEST(event_type_tests, stringification)\n{\n    CHECK_EQ(\"error\",              to_string(event_type::error));\n    CHECK_EQ(\"created\",            to_string(event_type::created));\n    CHECK_EQ(\"erased\",             to_string(event_type::erased));\n    CHECK_EQ(\"changed\",            to_string(event_type::changed));\n    CHECK_EQ(\"child\",              to_string(event_type::child));\n    CHECK_EQ(\"session\",            to_string(event_type::session));\n    CHECK_EQ(\"not_watching\",       to_string(event_type::not_watching));\n    CHECK_EQ(\"event_type(764532)\", to_string(static_cast<event_type>(764532)));\n}\n\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n// state                                                                                                              //\n////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\nGTEST_TEST(state_tests, stringification)\n{\n    CHECK_EQ(\"closed\",                to_string(state::closed));\n    CHECK_EQ(\"connecting\",            to_string(state::connecting));\n    CHECK_EQ(\"connected\",             to_string(state::connected));\n    CHECK_EQ(\"read_only\",             to_string(state::read_only));\n    CHECK_EQ(\"expired_session\",       to_string(state::expired_session));\n    CHECK_EQ(\"authentication_failed\", to_string(state::authentication_failed));\n    CHECK_EQ(\"state(605983)\",         to_string(static_cast<state>(605983)));\n}\n\n}\n"
  }
]