Browse Source

Add initial boost::leaf examples within cmake/vcpkg project

master
chodak166 1 month ago
parent
commit
3c47a629be
  1. 4
      .dockerignore
  2. 31
      CMakeLists.txt
  3. 39
      Dockerfile
  4. 78
      README.md
  5. 8
      docker-compose.yml
  6. 11
      scripts/run_all.sh
  7. 102
      src/leaf_mutable_block.cpp
  8. 130
      src/leaf_on_error_example.cpp
  9. 106
      src/leaf_universal_handler.cpp
  10. 8
      vcpkg.json

4
.dockerignore

@ -0,0 +1,4 @@
build/
.git/
answer.md
README.md

31
CMakeLists.txt

@ -0,0 +1,31 @@
cmake_minimum_required(VERSION 3.21)
project(leaf_examples LANGUAGES CXX)
if(NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 17)
endif()
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Boost CONFIG COMPONENTS leaf)
if(NOT TARGET Boost::leaf)
find_package(Boost REQUIRED)
set(LEAF_TARGET Boost::headers)
else()
set(LEAF_TARGET Boost::leaf)
endif()
set(EXAMPLES
leaf_on_error_example
leaf_mutable_block
leaf_universal_handler
)
foreach(example IN LISTS EXAMPLES)
add_executable(${example} src/${example}.cpp)
target_link_libraries(${example} PRIVATE ${LEAF_TARGET})
endforeach()
install(
TARGETS ${EXAMPLES}
RUNTIME DESTINATION bin
)

39
Dockerfile

@ -0,0 +1,39 @@
# syntax=docker/dockerfile:1
FROM debian:bookworm AS build
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
cmake \
ninja-build \
git \
ca-certificates \
curl \
zip \
unzip \
tar \
pkg-config \
&& rm -rf /var/lib/apt/lists/*
ENV VCPKG_ROOT=/opt/vcpkg
RUN git clone --depth 1 https://github.com/microsoft/vcpkg.git ${VCPKG_ROOT} \
&& ${VCPKG_ROOT}/bootstrap-vcpkg.sh -disableMetrics
WORKDIR /app
COPY vcpkg.json CMakeLists.txt ./
COPY src ./src
COPY scripts ./scripts
RUN cmake -S . -B build -G Ninja \
-DCMAKE_TOOLCHAIN_FILE=${VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake \
-DCMAKE_BUILD_TYPE=Release \
&& cmake --build build \
&& cmake --install build --prefix /out
FROM debian:bookworm-slim
WORKDIR /app
COPY --from=build /out/bin/ ./
COPY scripts/run_all.sh ./
ENTRYPOINT ["/app/run_all.sh"]

78
README.md

@ -0,0 +1,78 @@
# Boost.LEAF Diagnostic Propagation Examples
Three small C++ programs demonstrating how [Boost.LEAF](https://boostorg.github.io/leaf/)
propagates rich diagnostic information together with an error code, without throwing
exceptions and without exceptions needing to be enabled in the codebase.
- `src/leaf_on_error_example.cpp` — distinct context structs attached per scope with
`leaf::on_error`; handlers matched by the exact set of available types.
- `src/leaf_mutable_block.cpp` — a single mutable `diagnostic_context` struct, re-registered
with `leaf::on_error` as the operation progresses, so the handler receives the latest
snapshot of all fields.
- `src/leaf_universal_handler.cpp` — handler selection: specific handlers first, generic
"universal" handlers later, plus a final fallback. Shows how LEAF picks the best match.
## Core idea
LEAF returns errors as values of type `leaf::result<T>` instead of throwing exceptions.
At each point where an error can occur, arbitrary diagnostic values (file names, user info,
chunk numbers, timestamps, ...) are registered with `leaf::on_error(value)`. If
`leaf::new_error(...)` is returned, all registered values are captured into the current
error context, and the top level uses `leaf::try_handle_all(try_block, handlers...)` to let
the handlers inspect whichever context types were active when the error was created.
Handler matching is done purely on types: a handler requesting
`(write_error const&, file_context const&, user_context const&)` will only run if the error
carries exactly those registered values; a more general handler is used otherwise. This
gives exception-like separation between "raising" a failure and "dealing with" it, but with
explicit `result` values, no exceptions, and no RTTI dependency.
The examples intentionally return distinct exit codes from the handlers so each one
reports which handler actually matched.
## Local build
Requirements: CMake >= 3.21, a C++17 compiler, and [vcpkg](https://vcpkg.io) (git clone +
bootstrap). The dependency is header-only `boost-leaf`.
```bash
git clone https://github.com/microsoft/vcpkg.git
./vcpkg/bootstrap-vcpkg.sh
cmake -S . -B build \
-DCMAKE_TOOLCHAIN_FILE=$PWD/vcpkg/scripts/buildsystems/vcpkg.cmake \
-DCMAKE_BUILD_TYPE=Release
cmake --build build
```
Run one of the examples:
```bash
./build/leaf_on_error_example
```
If you already have Boost (>= 1.75) installed at the system level, plain
`cmake -S . -B build` also works; the CMake project picks `Boost::leaf` from a vcpkg
toolchain when available and falls back to the system header target otherwise.
## Docker
```bash
docker build -t leaf-examples .
docker run --rm leaf-examples
```
The first (build) stage installs the toolchain, clones vcpkg, downloads `boost-leaf`
through the `vcpkg.json` manifest and builds all three examples. The second (run)
stage copies the binaries into a slim image and executes them one after another,
printing the exit code of each.
## Docker Compose (v2)
```bash
docker compose up --build
```
runs the same build and then starts a container that executes all three examples.
`docker compose build` builds without running; `docker compose run --rm leaf-examples`
works too.

8
docker-compose.yml

@ -0,0 +1,8 @@
services:
leaf-examples:
build:
context: .
dockerfile: Dockerfile
network: host
image: local/leaf-examples
container_name: leaf-examples

11
scripts/run_all.sh

@ -0,0 +1,11 @@
#!/bin/sh
set -u
apps="leaf_on_error_example leaf_mutable_block leaf_universal_handler"
for app in ${apps}; do
printf '\n=== %s ===\n' "${app}"
"./${app}"
printf '(exit code: %s)\n' "$?"
done
exit 0

102
src/leaf_mutable_block.cpp

@ -0,0 +1,102 @@
// leaf_mutable_block.cpp
#include <boost/leaf.hpp>
#include <ctime>
#include <iostream>
#include <optional>
#include <string>
#include <utility>
namespace leaf = boost::leaf;
struct error_tag {};
struct open_error {};
struct write_error {};
struct diagnostic_context {
std::string file;
std::optional<std::string> user;
std::optional<int> privileges;
std::optional<int> chunk;
std::optional<std::size_t> chunk_size;
std::optional<std::time_t> timestamp;
};
leaf::result<void> write_chunk(int chunk, std::size_t size)
{
if (chunk == 3)
return leaf::new_error(write_error{});
return {};
}
leaf::result<void> process_file(std::string path, std::string user)
{
error_tag tag;
diagnostic_context diag;
diag.file = path;
diag.user = std::move(user);
diag.privileges = 0600;
[[maybe_unused]] auto tag_ctx = leaf::on_error(tag);
// on_error captures a snapshot of diag, not a reference, so any
// fields mutated later would be invisible unless re-registered.
[[maybe_unused]] auto diag_ctx = leaf::on_error(diag);
if (diag.file.empty())
return leaf::new_error(open_error{});
for (int i = 0; i != 5; ++i) {
diag.chunk = i;
diag.chunk_size = 4096;
diag.timestamp = std::time(nullptr);
// Re-registering publishes the updated snapshot; the newest
// registration wins for the handler.
[[maybe_unused]] auto refresh = leaf::on_error(diag);
auto r = write_chunk(i, 4096);
if (!r)
return r;
}
return {};
}
int main()
{
return leaf::try_handle_all(
[]() -> leaf::result<int> {
BOOST_LEAF_CHECK(process_file("data.bin", "alice"));
return 0;
},
[](error_tag const&, diagnostic_context const& d) {
// d is the newest snapshot taken before the error was raised,
// which is why chunk/chunk_size/timestamp are filled in here.
std::cerr << "operation failed: file=" << d.file;
if (d.user)
std::cerr << ", user=" << *d.user;
if (d.privileges)
std::cerr << ", privileges=" << *d.privileges;
if (d.chunk)
std::cerr << ", chunk=" << *d.chunk;
if (d.chunk_size)
std::cerr << ", chunk_size=" << *d.chunk_size;
if (d.timestamp)
std::cerr << ", time=" << *d.timestamp;
std::cerr << '\n';
return 1;
},
[] {
std::cerr << "operation failed before diagnostic context existed\n";
return 2;
}
);
}

130
src/leaf_on_error_example.cpp

@ -0,0 +1,130 @@
// leaf_on_error_example.cpp
#include <boost/leaf.hpp>
#include <ctime>
#include <iostream>
#include <string>
#include <utility>
namespace leaf = boost::leaf;
struct error_tag {};
struct open_error {};
struct write_error {};
struct file_context {
std::string path;
};
struct user_context {
std::string name;
int privileges;
};
struct chunk_context {
int chunk;
std::size_t size;
std::time_t timestamp;
};
leaf::result<void> write_chunk(int chunk, std::size_t size)
{
chunk_context cc{chunk, size, std::time(nullptr)};
// Registers cc to be captured only if an error unwinds this scope.
// On success the registration is discarded.
[[maybe_unused]] auto ctx = leaf::on_error(cc);
if (chunk == 3)
// The context registered above rides along with this error
// even though handlers live several frames up.
return leaf::new_error(write_error{});
return {};
}
leaf::result<void> process_file(std::string path, std::string user)
{
error_tag tag;
file_context fc{std::move(path)};
user_context uc{std::move(user), 0600};
[[maybe_unused]] auto tag_ctx = leaf::on_error(tag);
[[maybe_unused]] auto file_ctx = leaf::on_error(fc);
[[maybe_unused]] auto user_ctx = leaf::on_error(uc);
// write_chunk knows nothing about the contexts registered here;
// LEAF attaches them automatically to any error it propagates.
for (int i = 0; i != 5; ++i) {
auto r = write_chunk(i, 4096);
if (!r)
return r;
}
return {};
}
int main()
{
return leaf::try_handle_all(
[]() -> leaf::result<int> {
// Try block and handlers must return the same type.
BOOST_LEAF_CHECK(process_file("data.bin", "alice"));
return 0;
},
// A handler runs only if the error carries every type it
// requests; handlers below with fewer parameters match the
// same errors when richer contexts were never registered.
[](write_error const&,
error_tag const&,
file_context const& f,
user_context const& u,
chunk_context const& c) {
std::cerr << "write failed: "
<< "file=" << f.path
<< ", user=" << u.name
<< ", chunk=" << c.chunk
<< ", size=" << c.size
<< ", time=" << c.timestamp
<< '\n';
return 1;
},
[](open_error const&,
error_tag const&,
file_context const& f,
user_context const& u) {
std::cerr << "open failed: "
<< "file=" << f.path
<< ", user=" << u.name
<< '\n';
return 2;
},
[](error_tag const&,
file_context const& f,
user_context const& u,
chunk_context const& c) {
std::cerr << "unknown error: "
<< "file=" << f.path
<< ", user=" << u.name
<< ", chunk=" << c.chunk
<< '\n';
return 3;
},
[](error_tag const&,
file_context const& f,
user_context const& u) {
std::cerr << "unknown error: "
<< "file=" << f.path
<< ", user=" << u.name
<< '\n';
return 4;
},
[] {
std::cerr << "unknown error before file context\n";
return 5;
}
);
}

106
src/leaf_universal_handler.cpp

@ -0,0 +1,106 @@
// leaf_universal_handler.cpp
#include <boost/leaf.hpp>
#include <iostream>
#include <string>
#include <utility>
namespace leaf = boost::leaf;
struct error_tag {};
struct early_error {};
struct unknown_stage_error {};
struct operation_context {
std::string operation;
std::string user;
};
struct stage_context {
std::string stage;
};
leaf::result<void> stage_two()
{
// stage_context exists only in this scope; handlers above cannot
// see it unless an error escapes while this registration is active.
stage_context sc{"stage_two"};
[[maybe_unused]] auto ctx = leaf::on_error(sc);
return leaf::new_error(unknown_stage_error{});
}
leaf::result<void> run(bool fail_early)
{
error_tag tag;
operation_context oc{"universal_handler", "alice"};
[[maybe_unused]] auto tag_ctx = leaf::on_error(tag);
[[maybe_unused]] auto op_ctx = leaf::on_error(oc);
if (fail_early)
return leaf::new_error(early_error{});
return stage_two();
}
int execute(bool fail_early)
{
return leaf::try_handle_all(
[fail_early]() -> leaf::result<int> {
BOOST_LEAF_CHECK(run(fail_early));
return 0;
},
// Specific handler: runs only for early_error.
[](early_error const&,
error_tag const&,
operation_context const& op) {
std::cerr << "early error: "
<< "operation=" << op.operation
<< ", user=" << op.user
<< '\n';
return 1;
},
// Universal handler: no specific error type, so it matches any
// error that carries error_tag + operation_context. This overload
// is selected when the error also carries stage_context (i.e. it
// came from stage_two). LEAF picks the handler whose parameter
// list matches the most context available.
[](error_tag const&,
operation_context const& op,
stage_context const& st) {
std::cerr << "universal handler with stage: "
<< "operation=" << op.operation
<< ", user=" << op.user
<< ", stage=" << st.stage
<< '\n';
return 2;
},
// Same universal shape minus stage_context: matches errors that
// never passed through stage_two.
[](error_tag const&,
operation_context const& op) {
std::cerr << "universal handler without stage: "
<< "operation=" << op.operation
<< ", user=" << op.user
<< '\n';
return 3;
},
// Fallback for errors carrying nothing the other handlers accept
// (e.g. thrown exceptions not produced by new_error).
[] {
std::cerr << "fallback handler\n";
return 4;
}
);
}
int main()
{
execute(false);
execute(true);
return 0;
}

8
vcpkg.json

@ -0,0 +1,8 @@
{
"$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/vcpkg.schema.json",
"name": "leaf-examples",
"version": "1.0.0",
"dependencies": [
"boost-leaf"
]
}
Loading…
Cancel
Save