diff --git a/README.md b/README.md index 2533a5a..1766f79 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # Vix.cpp

- Vix.cpp Banner @@ -45,7 +45,7 @@ but engineered **from day one** for: > **Run applications like Node/Deno/Bun > with C++ speed, control, and predictability.** -Vix is not just a backend framework. +Vix is not just a backend framework. It is a **runtime layer** for real-world distributed systems. --- @@ -106,7 +106,7 @@ Vix.cpp is designed to remove overhead, unpredictability, and GC pauses. int main() { Vix::App app; - app.get("/", [](auto&, auto& res){ + app.get("/", [](Request&, Response& res){ res.send("Hello from Vix.cpp πŸš€"); }); diff --git a/docs/examples/delete_user.md b/docs/examples/delete_user.md index 2bad24c..9dec9fe 100644 --- a/docs/examples/delete_user.md +++ b/docs/examples/delete_user.md @@ -5,17 +5,17 @@ #include #include -using namespace Vix; -namespace J = Vix::json; +using namespace vix; +namespace J = vix::json; int main() { App app; // DELETE /users/{id} - app.del("/users/{id}", [](auto &, auto &res, auto ¶ms) + app.del("/users/{id}", [](Request &req, Response &res) { - const std::string id = params["id"]; + const std::string id = req.param("id"); // In a real app you'd remove the resource from DB or memory here res.json({ @@ -25,5 +25,6 @@ int main() }); }); app.run(8080); + return 0; } ``` diff --git a/docs/examples/hello_routes.md b/docs/examples/hello_routes.md index a39003f..607fbfc 100644 --- a/docs/examples/hello_routes.md +++ b/docs/examples/hello_routes.md @@ -4,13 +4,13 @@ Minimal GET routes and path params. ```cpp #include -using namespace Vix; +using namespace vix; int main() { App app; - app.get("/hello", [](auto &, auto &res) + app.get("/hello", [](Request &, Response &res) { res.json({"message", "Hello, Vix!"}); }); app.run(8080); diff --git a/docs/examples/json_builders_routes.md b/docs/examples/json_builders_routes.md index bdce868..a4eae2a 100644 --- a/docs/examples/json_builders_routes.md +++ b/docs/examples/json_builders_routes.md @@ -4,33 +4,34 @@ #include #include -using namespace Vix; -namespace J = Vix::json; +using namespace vix; +namespace J = vix::json; int main() { App app; // GET /hello -> {"message": "Hello, World!"} - app.get("/hello", [](auto &, auto &res) - { res.json({"message", "Hello, World!"}); }); + app.get("/hello", [](Request &, Response &res){ + res.json({"message", "Hello, World!"}); + }); // GET /users/{id} -> {"user": {"id": "...", "active": true}} - app.get("/users/{id}", [](auto &, auto &res, auto ¶ms) - { - const std::string id = params["id"]; - res.json({ - "user", J::obj({ - "id", id, - "active", true - }) - }); }); + app.get("/users/{id}", [](Request &req, Response &res){ + const std::string id = req.param("id"); + res.json({ + "user", J::obj({ + "id", id, + "active", true + }) + }); + }); // GET /roles -> {"roles": ["admin", "editor", "viewer"]} - app.get("/roles", [](auto &, auto &res) - { res.json({"roles", J::array({"admin", "editor", "viewer"})}); }); + app.get("/roles", [](Request &, Response &res){ + res.json({"roles", J::array({"admin", "editor", "viewer"})}); + }); app.run(8080); - return 0; } ``` diff --git a/docs/examples/logger_context_and_uuid.md b/docs/examples/logger_context_and_uuid.md deleted file mode 100644 index f198ff9..0000000 --- a/docs/examples/logger_context_and_uuid.md +++ /dev/null @@ -1,57 +0,0 @@ -# Example β€” json_builders_routes - -```cpp -#include -#include -#include - -using namespace Vix::orm; - -int main(int argc, char **argv) -{ - std::string host = (argc > 1 ? argv[1] : "tcp://127.0.0.1:3306"); - std::string user = (argc > 2 ? argv[2] : "root"); - std::string pass = (argc > 3 ? argv[3] : ""); - std::string db = (argc > 4 ? argv[4] : "vixdb"); - - try - { - ConnectionPool pool{host, user, pass, db}; - - Transaction tx(pool); - auto &c = tx.conn(); - - auto st = c.prepare("INSERT INTO users(name,email,age) VALUES(?,?,?)"); - - struct Row - { - const char *name; - const char *email; - int age; - }; - std::vector rows = { - {"Zoe", "zoe@example.com", 23}, - {"Mina", "mina@example.com", 31}, - {"Omar", "omar@example.com", 35}, - }; - - std::uint64_t total = 0; - for (const auto &r : rows) - { - st->bind(1, r.name); - st->bind(2, r.email); - st->bind(3, r.age); - total += st->exec(); - } - - tx.commit(); - std::cout << "[OK] inserted rows = " << total << "\n"; - return 0; - } - catch (const std::exception &e) - { - std::cerr << "[ERR] " << e.what() << "\n"; - return 1; - } -} -``` diff --git a/docs/examples/overview.md b/docs/examples/overview.md index 92d2a1f..da37134 100644 --- a/docs/examples/overview.md +++ b/docs/examples/overview.md @@ -180,7 +180,7 @@ int main() // Basic JSON response (auto send) app.get("/", [](Request req, Response res) { - return vix::json::o("message", "Hello from Vix"); + res.send("message", "Hello from Vix"); }); // Path params + return {status, payload} app.get("/users/{id}", [](Request req, Response res) { diff --git a/docs/examples/post_create_user.md b/docs/examples/post_create_user.md index b110b3e..d9c633f 100644 --- a/docs/examples/post_create_user.md +++ b/docs/examples/post_create_user.md @@ -5,25 +5,24 @@ #include #include -using namespace Vix; -namespace J = Vix::json; +using namespace vix; +namespace J = vix::json; int main() { App app; // POST /users - app.post("/users", [](auto &req, auto &res) + app.post("/users", [](Request &req, Response &res) { try { - // Parse body as nlohmann::json for simplicity (still supported) - auto body = nlohmann::json::parse(req.body()); + auto body = json::Json::parse(req.body()); const std::string name = body.value("name", ""); const std::string email = body.value("email", ""); const int age = body.value("age", 0); - res.status(http::status::created).json({ + res.status(200).json({ "action", "create", "status", "created", "user", J::obj({ @@ -34,7 +33,7 @@ int main() }); } catch (...) { - res.status(http::status::bad_request).json({ + res.status(400).json({ "error", "Invalid JSON" }); } }); diff --git a/docs/examples/put_update_user.md b/docs/examples/put_update_user.md index 6e076e1..06c2a7d 100644 --- a/docs/examples/put_update_user.md +++ b/docs/examples/put_update_user.md @@ -5,21 +5,20 @@ #include #include -using namespace Vix; -namespace J = Vix::json; +using namespace vix; +namespace J = vix::json; int main() { App app; // PUT /users/{id} - app.put("/users/{id}", [](auto &req, auto &res, auto ¶ms) + app.put("/users/{id}", [](Request &req, Response &res) { - const std::string id = params["id"]; + const std::string id = req.param("id"); try { - // Parsing with nlohmann::json for input is fine (Vix supports it internally) - auto body = nlohmann::json::parse(req.body()); + auto body = json::Json::parse(req.body()); const std::string name = body.value("name", ""); const std::string email = body.value("email", ""); @@ -37,7 +36,7 @@ int main() }); } catch (...) { - res.status(http::status::bad_request).json({ + res.status(400).json({ "error", "Invalid JSON" }); } }); diff --git a/docs/examples/user_crud_with_validation.md b/docs/examples/user_crud_with_validation.md index 4e967af..ecc62f2 100644 --- a/docs/examples/user_crud_with_validation.md +++ b/docs/examples/user_crud_with_validation.md @@ -23,10 +23,10 @@ #include #include -using namespace Vix; -namespace J = Vix::json; +using namespace vix; +namespace J = vix::json; using njson = nlohmann::json; -using namespace Vix::utils; +using namespace vix::utils; // --------------------------- Data Model ------------------------------------- struct User @@ -110,7 +110,7 @@ int main() App app; // CREATE (POST /users) - app.post("/users", [](auto &req, auto &res) + app.post("/users", [](Request &req, Response &res) { njson body; try { @@ -172,9 +172,9 @@ int main() }); }); // READ (GET /users/{id}) - app.get("/users/{id}", [](auto & /*req*/, auto &res, auto ¶ms) + app.get("/users/{id}", [](Request &req, Response &res) { - const std::string id = params["id"]; + const std::string id = req.param("id"); std::lock_guard lock(g_mtx); auto it = g_users.find(id); if (it == g_users.end()) { @@ -188,9 +188,9 @@ int main() }); }); // UPDATE (PUT /users/{id}) - app.put("/users/{id}", [](auto &req, auto &res, auto ¶ms) + app.put("/users/{id}", [](Request &req, Response &res) { - const std::string id = params["id"]; + const std::string id = req.param("id"); njson body; try { @@ -226,9 +226,9 @@ int main() }); }); // DELETE (DELETE /users/{id}) - app.del("/users/{id}", [](auto & /*req*/, auto &res, auto ¶ms) + app.del("/users/{id}", [](Request &req, Response &res) { - const std::string id = params["id"]; + const std::string id = req.param("id"); std::lock_guard lock(g_mtx); const auto n = g_users.erase(id); if (!n) { @@ -244,8 +244,5 @@ int main() // Lancement app.run(8080); - return 0; } - - ``` diff --git a/docs/introduction.md b/docs/introduction.md index 9949f47..5d5df9c 100644 --- a/docs/introduction.md +++ b/docs/introduction.md @@ -1,6 +1,6 @@ # Introduction to Vix.cpp -Vix.cpp is a next‑generation **C++20** web backend framework focused on **speed**, **modularity**, and **developer ergonomics**. +Vix.cpp is a next‑generation **C++20** web backend framework focused on **speed**, **modularity**, and **developer ergonomics**. Inspired by ideas from **FastAPI**, **Vue.js**, and **React**, it brings a clean, expressive style to native C++ while retaining zero‑overhead abstractions and low‑level control. --- @@ -19,19 +19,19 @@ Inspired by ideas from **FastAPI**, **Vue.js**, and **React**, it brings a clean ## Core Ideas -1. **Small, sharp core** +1. **Small, sharp core** The core (`App`, router, request/response, HTTP server) stays tiny and predictable. Everything else is opt‑in. -2. **Simple routing** +2. **Simple routing** Declarative routes with path parameters: `app.get("/users/{id}", handler);` -3. **JSON‑first** +3. **JSON‑first** Seamless helpers around _nlohmann/json_ via `Vix::json` (builders, small utilities, safe conversions). -4. **Composability** +4. **Composability** Middleware, utilities (Logger, UUID, Time, Env), and an optional ORM layer (MySQL / SQLite) integrate without tight coupling. -5. **Pragmatism** +5. **Pragmatism** Clean, incremental APIs; clear error messages; predictable defaults; portable builds (CMake). --- @@ -44,8 +44,8 @@ using namespace Vix; int main() { App app; - app.get("/", [](auto&, auto& res) { - res.json({ "message", "Hello world" }); + app.get("/", [](Request&, Response& res) { + res.send("message", "Hello world"); }); app.run(8080); } @@ -107,7 +107,7 @@ cmake --build build-rel -j ./build-rel/hello_routes ``` -For platform‑specific setup (Linux/macOS/Windows), see **[Installation](./installation.md)**. +For platform‑specific setup (Linux/macOS/Windows), see **[Installation](./installation.md)**. For packaging, sanitizers, and compile_commands.json, see **[Build & Packaging](./build.md)**. --- @@ -149,5 +149,5 @@ For details and status, see **[Architecture](./architecture.md)** and module pag ## Contributing & License -Contributions are welcome! Please read **CONTRIBUTING.md**. +Contributions are welcome! Please read **CONTRIBUTING.md**. Licensed under **MIT** β€” see **LICENSE**. diff --git a/docs/modules/core.md b/docs/modules/core.md index 238fd5a..c1fc3a4 100644 --- a/docs/modules/core.md +++ b/docs/modules/core.md @@ -5,8 +5,8 @@ ![Status](https://img.shields.io/badge/Status-Stable-success) ![Performance](https://img.shields.io/badge/Throughput-80k%2B%20req%2Fs-orange) -> **vix.cpp/core** β€” The foundational module of the [**Vix.cpp**](https://github.com/vixcpp/vix) framework. -> Provides the high-performance HTTP server, router, middleware system, and base runtime. +> **vix.cpp/core** β€” The foundational module of the [**Vix.cpp**](https://github.com/vixcpp/vix) framework. +> Provides the high-performance HTTP server, router, middleware system, and base runtime. > Every other Vix module builds on top of this layer. --- @@ -79,22 +79,22 @@ cmake --build build -j$(nproc) #include int main() { - Vix::App app; + vix::App app; - app.get("/hello", [](auto&, auto& res) { - res.json({{"message", "Hello, World!"}}); + app.get("/hello", [](Request&, Response& res) { + res.send("message", "Hello, World!"); }); - app.get("/users/{id}", [](auto&, auto& res, auto& params) { - res.json({{"user_id", params["id"]}}); + app.get("/users/{id}", [](Request& req, Response& res) { + res.json({{"user_id", req.param("id")}}); }); app.run(8080); } ``` -βœ… Supports `GET`, `POST`, `PUT`, `DELETE` -βœ… Automatic path parameter extraction +βœ… Supports `GET`, `POST`, `PUT`, `DELETE` +βœ… Automatic path parameter extraction βœ… Helper methods: `res.json()`, `res.text()`, `res.status()` --- @@ -148,5 +148,5 @@ Transfer/sec: 18.25MB ## 🧾 License -**MIT License** Β© [Gaspard Kirira](https://github.com/gkirira) +**MIT License** Β© [Gaspard Kirira](https://github.com/gkirira) See [LICENSE](../../LICENSE) for details. diff --git a/docs/modules/json.md b/docs/modules/json.md index fd2cb50..133ad02 100644 --- a/docs/modules/json.md +++ b/docs/modules/json.md @@ -5,14 +5,14 @@ ![Status](https://img.shields.io/badge/Status-Stable-success) ![JSON](https://img.shields.io/badge/JSON-nlohmann%2Fjson-orange) -> **vix.cpp/json** β€” A high-level JSON utility library built on top of [nlohmann/json](https://github.com/nlohmann/json). +> **vix.cpp/json** β€” A high-level JSON utility library built on top of [nlohmann/json](https://github.com/nlohmann/json). > Provides expressive, concise, and safe helpers for working with JSON in C++. --- ## πŸš€ Overview -The **Vix JSON module** offers a lightweight abstraction over `nlohmann::json`, +The **Vix JSON module** offers a lightweight abstraction over `nlohmann::json`, designed for simplicity and expressiveness. It adds helpers for: - Fast JSON object/array construction (`o()`, `a()`, `kv()`) @@ -37,7 +37,7 @@ designed for simplicity and expressiveness. It adds helpers for: #include #include -using namespace Vix::json; +using namespace vix::json; int main() { auto user = o( @@ -65,11 +65,20 @@ int main() { ## 🧱 JSON Builders ```cpp -auto obj1 = o("id", 1, "name", "Vix", "active", true); -auto arr1 = a(1, 2, 3, 4); +#include +#include +using namespace vix::json; + +int main() +{ + Json obj1 = o("id", 1, "name", "Vix", "active", true); + Json arr1 = a(1, 2, 3, 4); -auto obj2 = kv({{"key1", "val1"}, {"key2", 2}}); -std::cout << dumps(obj1, 2) << "\n" << dumps(arr1, 2); + Json obj2 = kv({{"key1", "val1"}, {"key2", 2}}); + + std::cout << dumps(obj1, 2) << "\n" + << dumps(arr1, 2) << std::endl; +} ``` **Example Output** @@ -88,10 +97,28 @@ std::cout << dumps(obj1, 2) << "\n" << dumps(arr1, 2); ## πŸ’Ύ JSON File Operations ```cpp -auto j = loads(R"({"a":1,"b":[10,20]})"); -dump_file("out.json", j, 2); -auto j2 = load_file("out.json"); -std::cout << dumps(j2, 2) << "\n"; +#include +#include +using namespace vix::json; + +int main() +{ + Json j = loads(R"({"a":1,"b":[10,20]})"); + dump_file("out.json", j, 2); + Json j2 = load_file("out.json"); + std::cout << dumps(j2, 2) << std::endl; +} +``` +**Output** + +```json +{ + "a": 1, + "b": [ + 10, + 20 + ] +} ``` **Features** @@ -105,15 +132,24 @@ std::cout << dumps(j2, 2) << "\n"; ## 🧭 JPath Access ```cpp -Json j = obj(); -jset(j, "user.langs[2]", "cpp"); -jset(j, "user.profile.name", "Gaspard"); -jset(j, R"(user["display.name"])", "Ada L."); +#include +#include + +using namespace vix::json; -if (auto v = jget(j, "user.langs[2]")) { - std::cout << v->get() << "\n"; // cpp +int main() +{ + Json j = obj(); + jset(j, "user.langs[2]", "cpp"); + jset(j, "user.profile.name", "Gaspard"); + jset(j, R"(user["display.name"])", "Ada L."); + + if (auto v = jget(j, "user.langs[2]")) + { + std::cout << v->get() << "\n"; + } + std::cout << dumps(j, 2) << "\n"; } -std::cout << dumps(j, 2) << "\n"; ``` **Output** @@ -160,14 +196,22 @@ This module is automatically included when you build the umbrella **Vix.cpp** pr ```cpp #include -using namespace Vix::json; +using namespace vix::json; auto j = o("framework", "Vix.cpp", "version", "1.7.0"); ``` +**Output** + +```json +{ + "framework": "Vix.cpp", + "version": "1.7.0", +} +``` --- ## 🧾 License -**MIT License** Β© [Gaspard Kirira](https://github.com/gkirira) +**MIT License** Β© [Gaspard Kirira](https://github.com/gkirira) See [LICENSE](../../LICENSE) for details. diff --git a/docs/modules/orm.md b/docs/modules/orm.md index a762914..86006e8 100644 --- a/docs/modules/orm.md +++ b/docs/modules/orm.md @@ -109,35 +109,91 @@ Example: Simple CRUD (examples/users_crud.cpp) ```cpp #include +#include +#include + #include +#include +#include -struct User { +struct User +{ std::int64_t id{}; std::string name; std::string email; int age{}; }; -namespace Vix::orm { -template<> struct Mapper { - static std::vector> toInsertParams(const User& u) { - return {{"name", u.name}, {"email", u.email}, {"age", u.age}}; - } - static std::vector> toUpdateParams(const User& u) { - return {{"name", u.name}, {"email", u.email}, {"age", u.age}}; +namespace vix::orm +{ + template <> + struct Mapper + { + static User fromRow(const ResultRow &) { return {}; } // pending + + static std::vector> + toInsertParams(const User &u) + { + return { + {"name", u.name}, + {"email", u.email}, + {"age", u.age}, + }; + } + + static std::vector> + toUpdateParams(const User &u) + { + return { + {"name", u.name}, + {"email", u.email}, + {"age", u.age}, + }; + } + }; +} // namespace vix::orm + +int main(int argc, char **argv) +{ + using namespace vix::orm; + + std::string host = (argc > 1 ? argv[1] : "tcp://127.0.0.1:3306"); + std::string user = (argc > 2 ? argv[2] : "root"); + std::string pass = (argc > 3 ? argv[3] : ""); + std::string db = (argc > 4 ? argv[4] : "vixdb"); + + try + { + auto factory = make_mysql_factory(host, user, pass, db); + + PoolConfig cfg; + cfg.min = 1; + cfg.max = 8; + + ConnectionPool pool{factory, cfg}; + pool.warmup(); + + BaseRepository repo{pool, "users"}; + + // Create + std::int64_t id = static_cast( + repo.create(User{0, "Bob", "gaspardkirira@example.com", 30})); + std::cout << "[OK] create β†’ id=" << id << "\n"; + + // Update + repo.updateById(id, User{id, "Adastra", "adastra@example.com", 31}); + std::cout << "[OK] update β†’ id=" << id << "\n"; + + // Delete + repo.removeById(id); + std::cout << "[OK] delete β†’ id=" << id << "\n"; + + return 0; } -}; -} - -int main() { - using namespace Vix::orm; - try { - ConnectionPool pool{"tcp://127.0.0.1:3306", "root", "", "vixdb"}; - BaseRepository users{pool, "users"}; - auto id = users.create(User{0, "Alice", "alice@example.com", 28}); - std::cout << "[OK] Insert user β†’ id=" << id << std::endl; - } catch (const std::exception& e) { - std::cerr << "[ERR] " << e.what() << std::endl; + catch (const std::exception &e) + { + std::cerr << "[ERR] " << e.what() << "\n"; + return 1; } } ``` @@ -209,23 +265,11 @@ CREATE TABLE IF NOT EXISTS users ( # 🧩 Example Migration -```cpp -#include - -class CreateProductsTable : public Vix::orm::Migration { -public: - std::string id() const override { return "2025_10_01_create_products"; } - - void up(Vix::orm::Connection& c) override { - auto st = c.prepare("CREATE TABLE products (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255))"); - st->exec(); - } - - void down(Vix::orm::Connection& c) override { - auto st = c.prepare("DROP TABLE IF EXISTS products"); - st->exec(); - } -}; +```bash +vix orm migrate --db blog_db --dir ./migrations +vix orm rollback --steps 1 --db blog_db --dir ./migrations +vix orm status --db blog_db +VIX_ORM_DB=blog_db vix orm migrate --dir ./migrations ``` # 🧰 Installation & Integration (for Vix.cpp) diff --git a/docs/modules/utils.md b/docs/modules/utils.md index b61881a..d716bd2 100644 --- a/docs/modules/utils.md +++ b/docs/modules/utils.md @@ -5,14 +5,14 @@ ![Status](https://img.shields.io/badge/Status-Stable-success) ![spdlog](https://img.shields.io/badge/Logging-spdlog-orange) -> **vix.cpp/utils** β€” Foundational utility layer for the **Vix.cpp** framework. +> **vix.cpp/utils** β€” Foundational utility layer for the **Vix.cpp** framework. > Includes environment management, logging, validation, UUIDs, timestamps, and build metadata. --- ## πŸš€ Overview -The **Utils module** provides lightweight, reusable helpers used throughout Vix.cpp and standalone C++ apps. +The **Utils module** provides lightweight, reusable helpers used throughout Vix.cpp and standalone C++ apps. It is designed to simplify configuration, logging, and validation with zero runtime dependencies beyond `spdlog`. --- @@ -36,7 +36,7 @@ It is designed to simplify configuration, logging, and validation with zero runt #include #include #include -using namespace Vix::utils; +using namespace vix::utils; int main() { std::cout << "version=" << version() << "\n"; @@ -63,7 +63,7 @@ uuid4=550e8400-e29b-41d4-a716-446655440000 #include #include #include -using namespace Vix::utils; +using namespace vix::utils; using Vix::Logger; int main() { @@ -99,7 +99,7 @@ int main() { #include #include #include -using namespace Vix::utils; +using namespace vix::utils; using Vix::Logger; int main() { @@ -168,5 +168,5 @@ cmake --build build -j$(nproc) ## 🧾 License -**MIT License** Β© [Gaspard Kirira](https://github.com/gkirira) +**MIT License** Β© [Gaspard Kirira](https://github.com/gkirira) See [LICENSE](../../LICENSE) for details. diff --git a/docs/orm/error_handling.md b/docs/orm/error_handling.md index d1f8d65..18a56d1 100644 --- a/docs/orm/error_handling.md +++ b/docs/orm/error_handling.md @@ -14,9 +14,6 @@ int main(int argc, char **argv) { // Intentionally wrong DB name to show error auto raw = make_mysql_conn("tcp://127.0.0.1:3306", "root", "", "db_does_not_exist"); - MySQLConnection c{raw}; - auto st = c.prepare("SELECT 1"); - st->exec(); std::cout << "[INFO] This message may not be reached if connection fails.\n"; } catch (const DBError &e) diff --git a/examples/auth/api_key_app_simple.cpp b/examples/auth/api_key_app_simple.cpp index ff7e2bb..3d93b59 100644 --- a/examples/auth/api_key_app_simple.cpp +++ b/examples/auth/api_key_app_simple.cpp @@ -1,6 +1,16 @@ -// ============================================================================ -// api_key_app_simple.cpp β€” API Key auth example (Vix.cpp) -// ---------------------------------------------------------------------------- +/** + * + * @file api_key_app_simple.cpp β€” API Key auth example (Vix.cpp) + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ // Run: // vix run api_key_app_simple.cpp // @@ -27,49 +37,49 @@ using namespace vix; static void print_help() { - std::cout - << "Vix API Key example running:\n" - << " http://localhost:8080/\n" - << " http://localhost:8080/secure\n\n" - << "Valid key:\n" - << " secret\n\n" - << "Try:\n" - << " curl -i http://localhost:8080/secure\n" - << " curl -i -H \"x-api-key: wrong\" http://localhost:8080/secure\n" - << " curl -i -H \"x-api-key: secret\" http://localhost:8080/secure\n" - << " curl -i \"http://localhost:8080/secure?api_key=wrong\"\n" - << " curl -i \"http://localhost:8080/secure?api_key=secret\"\n\n"; + std::cout + << "Vix API Key example running:\n" + << " http://localhost:8080/\n" + << " http://localhost:8080/secure\n\n" + << "Valid key:\n" + << " secret\n\n" + << "Try:\n" + << " curl -i http://localhost:8080/secure\n" + << " curl -i -H \"x-api-key: wrong\" http://localhost:8080/secure\n" + << " curl -i -H \"x-api-key: secret\" http://localhost:8080/secure\n" + << " curl -i \"http://localhost:8080/secure?api_key=wrong\"\n" + << " curl -i \"http://localhost:8080/secure?api_key=secret\"\n\n"; } int main() { - App app; + App app; - // --------------------------------------------------------------------- - // API key protection (preset) - // - Header: x-api-key - // - Query : ?api_key= - // - Allowed key: "secret" - // --------------------------------------------------------------------- - app.use("/secure", middleware::app::api_key_dev("secret")); - // app.use( - // "/secure", - // middleware::app::api_key_auth({ - // .header = "x-api-key", - // .query_param = "api_key", - // .allowed_keys = {"secret"}, - // })); + // --------------------------------------------------------------------- + // API key protection (preset) + // - Header: x-api-key + // - Query : ?api_key= + // - Allowed key: "secret" + // --------------------------------------------------------------------- + app.use("/secure", middleware::app::api_key_dev("secret")); + // app.use( + // "/secure", + // middleware::app::api_key_auth({ + // .header = "x-api-key", + // .query_param = "api_key", + // .allowed_keys = {"secret"}, + // })); - // --------------------------------------------------------------------- - // Routes - // --------------------------------------------------------------------- - app.get("/", [](Request &, Response &res) - { res.send( - "API Key example:\n" - " /secure requires x-api-key: secret OR ?api_key=secret\n"); }); + // --------------------------------------------------------------------- + // Routes + // --------------------------------------------------------------------- + app.get("/", [](Request &, Response &res) + { res.send( + "API Key example:\n" + " /secure requires x-api-key: secret OR ?api_key=secret\n"); }); - app.get("/secure", [](Request &req, Response &res) - { + app.get("/secure", [](Request &req, Response &res) + { auto &key = req.state(); res.json({ @@ -77,7 +87,7 @@ int main() "api_key", key.value }); }); - print_help(); - app.run(8080); - return 0; + print_help(); + app.run(8080); + return 0; } diff --git a/examples/auth/generate_token/jwt_gen.cpp b/examples/auth/generate_token/jwt_gen.cpp index e740191..bb635b4 100644 --- a/examples/auth/generate_token/jwt_gen.cpp +++ b/examples/auth/generate_token/jwt_gen.cpp @@ -1,6 +1,16 @@ -// ============================================================================ -// jwt_gen.cpp β€” generate HS256 JWT tokens for rbac_app_simple.cpp (Vix.cpp) -// ---------------------------------------------------------------------------- +/** + * + * @file jwt_gen.cpp β€” generate HS256 JWT tokens for rbac_app_simple.cpp (Vix.cpp) + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ // Build: // g++ -std=c++20 jwt_gen.cpp -lssl -lcrypto -O2 && ./a.out // @@ -21,78 +31,78 @@ static std::string b64url_encode(const unsigned char *data, size_t len) { - std::string b64; - b64.resize(4 * ((len + 2) / 3)); - - int out_len = EVP_EncodeBlock( - reinterpret_cast(&b64[0]), - data, - static_cast(len)); - - b64.resize(static_cast(out_len)); - - for (char &c : b64) - { - if (c == '+') - c = '-'; - else if (c == '/') - c = '_'; - } - while (!b64.empty() && b64.back() == '=') - b64.pop_back(); - - return b64; + std::string b64; + b64.resize(4 * ((len + 2) / 3)); + + int out_len = EVP_EncodeBlock( + reinterpret_cast(&b64[0]), + data, + static_cast(len)); + + b64.resize(static_cast(out_len)); + + for (char &c : b64) + { + if (c == '+') + c = '-'; + else if (c == '/') + c = '_'; + } + while (!b64.empty() && b64.back() == '=') + b64.pop_back(); + + return b64; } static std::string hmac_sha256_b64url(std::string_view msg, std::string_view secret) { - unsigned int out_len = 0; - unsigned char out[EVP_MAX_MD_SIZE]; - - HMAC(EVP_sha256(), - secret.data(), - static_cast(secret.size()), - reinterpret_cast(msg.data()), - msg.size(), - out, - &out_len); - - return b64url_encode(out, static_cast(out_len)); + unsigned int out_len = 0; + unsigned char out[EVP_MAX_MD_SIZE]; + + HMAC(EVP_sha256(), + secret.data(), + static_cast(secret.size()), + reinterpret_cast(msg.data()), + msg.size(), + out, + &out_len); + + return b64url_encode(out, static_cast(out_len)); } static std::string make_jwt_hs256(const nlohmann::json &payload, const std::string &secret) { - nlohmann::json header = {{"alg", "HS256"}, {"typ", "JWT"}}; + nlohmann::json header = {{"alg", "HS256"}, {"typ", "JWT"}}; - const std::string h = header.dump(); - const std::string p = payload.dump(); + const std::string h = header.dump(); + const std::string p = payload.dump(); - const std::string h64 = b64url_encode(reinterpret_cast(h.data()), h.size()); - const std::string p64 = b64url_encode(reinterpret_cast(p.data()), p.size()); + const std::string h64 = b64url_encode(reinterpret_cast(h.data()), h.size()); + const std::string p64 = b64url_encode(reinterpret_cast(p.data()), p.size()); - const std::string signing = h64 + "." + p64; - const std::string sig = hmac_sha256_b64url(signing, secret); + const std::string signing = h64 + "." + p64; + const std::string sig = hmac_sha256_b64url(signing, secret); - return signing + "." + sig; + return signing + "." + sig; } int main() { - const std::string secret = "dev_secret"; - - nlohmann::json ok = { - {"sub", "user123"}, - {"roles", {"admin"}}, - {"perms", {"products:write", "orders:read"}}}; - - nlohmann::json no_perm = { - {"sub", "user123"}, - {"roles", {"admin"}}, - {"perms", {"orders:read"}}}; - - std::cout << "TOKEN_OK:\n" - << make_jwt_hs256(ok, secret) << "\n\n"; - std::cout << "TOKEN_NO_PERM:\n" - << make_jwt_hs256(no_perm, secret) << "\n"; - return 0; + const std::string secret = "dev_secret"; + + nlohmann::json ok = { + {"sub", "user123"}, + {"roles", {"admin"}}, + {"perms", {"products:write", "orders:read"}}}; + + nlohmann::json no_perm = { + {"sub", "user123"}, + {"roles", {"admin"}}, + {"perms", {"orders:read"}}}; + + std::cout << "TOKEN_OK:\n" + << make_jwt_hs256(ok, secret) << "\n\n"; + std::cout << "TOKEN_NO_PERM:\n" + << make_jwt_hs256(no_perm, secret) << "\n"; + return 0; } diff --git a/examples/auth/jwt/jwt_app_simple.cpp b/examples/auth/jwt/jwt_app_simple.cpp index e6d58d0..428d671 100644 --- a/examples/auth/jwt/jwt_app_simple.cpp +++ b/examples/auth/jwt/jwt_app_simple.cpp @@ -1,6 +1,16 @@ -// ============================================================================ -// jwt_app_simple.cpp β€” JWT middleware (App) super simple -// ---------------------------------------------------------------------------- +/** + * + * @file jwt_app_simple.cpp β€” JWT middleware (App) super simple + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ // Run: // vix run jwt_app_simple.cpp // @@ -28,22 +38,22 @@ static const std::string kToken = int main() { - App app; + App app; - // πŸ” Protect ONLY /secure (dev preset: verify_exp = false) - app.use("/secure", middleware::app::jwt_dev("dev_secret")); + // πŸ” Protect ONLY /secure (dev preset: verify_exp = false) + app.use("/secure", middleware::app::jwt_dev("dev_secret")); - app.get("/", [](Request &, Response &res) - { res.send( - "JWT example:\n" - " GET /secure requires Bearer token.\n" - "\n" - "Try:\n" - " curl -i http://localhost:8080/secure\n" - " curl -i -H \"Authorization: Bearer \" http://localhost:8080/secure\n"); }); + app.get("/", [](Request &, Response &res) + { res.send( + "JWT example:\n" + " GET /secure requires Bearer token.\n" + "\n" + "Try:\n" + " curl -i http://localhost:8080/secure\n" + " curl -i -H \"Authorization: Bearer \" http://localhost:8080/secure\n"); }); - app.get("/secure", [](Request &req, Response &res) - { + app.get("/secure", [](Request &req, Response &res) + { auto &claims = req.state(); res.json({"ok", true, "sub", claims.subject, @@ -51,16 +61,16 @@ int main() res.status(501).json({"ok", false, "error", "JWT middleware not enabled (VIX_ENABLE_JWT)"}); }); - std::cout - << "Vix JWT example running:\n" - << " http://localhost:8080/\n" - << " http://localhost:8080/secure\n\n" - << "Use this token:\n" - << " " << kToken << "\n\n" - << "Test:\n" - << " curl -i -H \"Authorization: Bearer " << kToken - << "\" http://localhost:8080/secure\n"; + std::cout + << "Vix JWT example running:\n" + << " http://localhost:8080/\n" + << " http://localhost:8080/secure\n\n" + << "Use this token:\n" + << " " << kToken << "\n\n" + << "Test:\n" + << " curl -i -H \"Authorization: Bearer " << kToken + << "\" http://localhost:8080/secure\n"; - app.run(8080); - return 0; + app.run(8080); + return 0; } diff --git a/examples/auth/rbac/rbac_app_simple.cpp b/examples/auth/rbac/rbac_app_simple.cpp index d1e70ac..dffd39d 100644 --- a/examples/auth/rbac/rbac_app_simple.cpp +++ b/examples/auth/rbac/rbac_app_simple.cpp @@ -1,6 +1,16 @@ -// ============================================================================ -// rbac_app_simple.cpp β€” RBAC (roles + perms) example (Vix.cpp) -// ---------------------------------------------------------------------------- +/** + * + * @file rbac_app_simple.cpp β€” RBAC (roles + perms) example (Vix.cpp) + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ // Run: // vix run rbac_app_simple.cpp // @@ -42,48 +52,48 @@ static const std::string TOKEN_NO_PERM = int main() { - App app; - - // 1) JWT auth (puts JwtClaims into request state) - vix::middleware::auth::JwtOptions jwt_opt{}; - jwt_opt.secret = "dev_secret"; - jwt_opt.verify_exp = false; - - // 2) RBAC: build Authz from JwtClaims, then enforce rules - vix::middleware::auth::RbacOptions rbac_opt{}; - rbac_opt.require_auth = true; - rbac_opt.use_resolver = false; // keep the example simple - - auto jwt_mw = vix::middleware::app::adapt_ctx(vix::middleware::auth::jwt(jwt_opt)); - auto ctx_mw = vix::middleware::app::adapt_ctx(vix::middleware::auth::rbac_context(rbac_opt)); - auto role_mw = vix::middleware::app::adapt_ctx(vix::middleware::auth::require_role("admin")); - auto perm_mw = vix::middleware::app::adapt_ctx(vix::middleware::auth::require_perm("products:write")); - - // Protect only /admin - app.use(vix::middleware::app::when( - [](const Request &req) - { return req.path() == "/admin"; }, - std::move(jwt_mw))); - app.use(vix::middleware::app::when( - [](const Request &req) - { return req.path() == "/admin"; }, - std::move(ctx_mw))); - app.use(vix::middleware::app::when( - [](const Request &req) - { return req.path() == "/admin"; }, - std::move(role_mw))); - app.use(vix::middleware::app::when( - [](const Request &req) - { return req.path() == "/admin"; }, - std::move(perm_mw))); - - // Public - app.get("/", [](Request &, Response &res) - { res.send("RBAC example: /admin requires role=admin + perm=products:write"); }); - - // Protected - app.get("/admin", [](Request &req, Response &res) - { + App app; + + // 1) JWT auth (puts JwtClaims into request state) + vix::middleware::auth::JwtOptions jwt_opt{}; + jwt_opt.secret = "dev_secret"; + jwt_opt.verify_exp = false; + + // 2) RBAC: build Authz from JwtClaims, then enforce rules + vix::middleware::auth::RbacOptions rbac_opt{}; + rbac_opt.require_auth = true; + rbac_opt.use_resolver = false; // keep the example simple + + auto jwt_mw = vix::middleware::app::adapt_ctx(vix::middleware::auth::jwt(jwt_opt)); + auto ctx_mw = vix::middleware::app::adapt_ctx(vix::middleware::auth::rbac_context(rbac_opt)); + auto role_mw = vix::middleware::app::adapt_ctx(vix::middleware::auth::require_role("admin")); + auto perm_mw = vix::middleware::app::adapt_ctx(vix::middleware::auth::require_perm("products:write")); + + // Protect only /admin + app.use(vix::middleware::app::when( + [](const Request &req) + { return req.path() == "/admin"; }, + std::move(jwt_mw))); + app.use(vix::middleware::app::when( + [](const Request &req) + { return req.path() == "/admin"; }, + std::move(ctx_mw))); + app.use(vix::middleware::app::when( + [](const Request &req) + { return req.path() == "/admin"; }, + std::move(role_mw))); + app.use(vix::middleware::app::when( + [](const Request &req) + { return req.path() == "/admin"; }, + std::move(perm_mw))); + + // Public + app.get("/", [](Request &, Response &res) + { res.send("RBAC example: /admin requires role=admin + perm=products:write"); }); + + // Protected + app.get("/admin", [](Request &req, Response &res) + { auto& authz = req.state(); res.json({ @@ -93,17 +103,17 @@ int main() "has_products_write", authz.has_perm("products:write") }); }); - std::cout - << "Vix RBAC example running:\n" - << " http://localhost:8080/\n" - << " http://localhost:8080/admin\n\n" - << "TOKEN_OK:\n " << TOKEN_OK << "\n\n" - << "TOKEN_NO_PERM:\n " << TOKEN_NO_PERM << "\n\n" - << "Try:\n" - << " curl -i http://localhost:8080/admin\n" - << " curl -i -H \"Authorization: Bearer " << TOKEN_OK << "\" http://localhost:8080/admin\n" - << " curl -i -H \"Authorization: Bearer " << TOKEN_NO_PERM << "\" http://localhost:8080/admin\n"; - - app.run(8080); - return 0; + std::cout + << "Vix RBAC example running:\n" + << " http://localhost:8080/\n" + << " http://localhost:8080/admin\n\n" + << "TOKEN_OK:\n " << TOKEN_OK << "\n\n" + << "TOKEN_NO_PERM:\n " << TOKEN_NO_PERM << "\n\n" + << "Try:\n" + << " curl -i http://localhost:8080/admin\n" + << " curl -i -H \"Authorization: Bearer " << TOKEN_OK << "\" http://localhost:8080/admin\n" + << " curl -i -H \"Authorization: Bearer " << TOKEN_NO_PERM << "\" http://localhost:8080/admin\n"; + + app.run(8080); + return 0; } diff --git a/examples/body_limit/body_limit_app.cpp b/examples/body_limit/body_limit_app.cpp index a75237d..5e57357 100644 --- a/examples/body_limit/body_limit_app.cpp +++ b/examples/body_limit/body_limit_app.cpp @@ -1,6 +1,16 @@ -// ============================================================================ -// body_limit_app.cpp β€” Body limit middleware example (Vix.cpp) -// +/** + * + * @file body_limit_app.cpp β€” Body limit middleware example (Vix.cpp) + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ // Run: // vix run vix/examples/body_limit_app.cpp // @@ -35,43 +45,43 @@ using namespace vix; static void register_routes(App &app) { - app.get("/", [](Request &, Response &res) - { res.send("body_limit example: /api/ping, /api/echo, /api/strict"); }); + app.get("/", [](Request &, Response &res) + { res.send("body_limit example: /api/ping, /api/echo, /api/strict"); }); - app.get("/api/ping", [](Request &, Response &res) - { res.json({"ok", true, "msg", "pong"}); }); + app.get("/api/ping", [](Request &, Response &res) + { res.json({"ok", true, "msg", "pong"}); }); - app.post("/api/echo", [](Request &req, Response &res) - { res.json({"ok", true, - "bytes", static_cast(req.body().size()), - "content_type", req.header("content-type")}); }); + app.post("/api/echo", [](Request &req, Response &res) + { res.json({"ok", true, + "bytes", static_cast(req.body().size()), + "content_type", req.header("content-type")}); }); - app.post("/api/strict", [](Request &req, Response &res) - { res.json({"ok", true, - "msg", "strict accepted", - "bytes", static_cast(req.body().size())}); }); + app.post("/api/strict", [](Request &req, Response &res) + { res.json({"ok", true, + "msg", "strict accepted", + "bytes", static_cast(req.body().size())}); }); } int main() { - App app; + App app; - // /api: max 32 bytes (demo), chunked allowed - app.use("/api", middleware::app::body_limit_dev( - 32, // max_bytes - false, // apply_to_get - true // allow_chunked - )); + // /api: max 32 bytes (demo), chunked allowed + app.use("/api", middleware::app::body_limit_dev( + 32, // max_bytes + false, // apply_to_get + true // allow_chunked + )); - // /api/strict: max 32 bytes, chunked NOT allowed => 411 if missing Content-Length - app.use("/api/strict", middleware::app::body_limit_dev( - 32, // max_bytes - false, // apply_to_get - false // allow_chunked (strict) - )); + // /api/strict: max 32 bytes, chunked NOT allowed => 411 if missing Content-Length + app.use("/api/strict", middleware::app::body_limit_dev( + 32, // max_bytes + false, // apply_to_get + false // allow_chunked (strict) + )); - register_routes(app); + register_routes(app); - app.run(8080); - return 0; + app.run(8080); + return 0; } diff --git a/examples/body_limit/body_limit_should_apply.cpp b/examples/body_limit/body_limit_should_apply.cpp index ea057af..570b17c 100644 --- a/examples/body_limit/body_limit_should_apply.cpp +++ b/examples/body_limit/body_limit_should_apply.cpp @@ -1,6 +1,16 @@ -// ============================================================================ -// body_limit_should_apply.cpp β€” CORS + conditional body limit via should_apply() -// +/** + * + * @file body_limit_should_apply.cpp β€” CORS + conditional body limit via should_apply() + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ // Run: // vix run vix/examples/body_limit_should_apply.cpp // @@ -31,19 +41,19 @@ using namespace vix; // ------------------------------------------------------------ static void install_cors(App &app) { - app.use("/", middleware::app::cors_ip_demo({"http://localhost:5173", - "http://127.0.0.1:5173", - "http://0.0.0.0:5173"})); + app.use("/", middleware::app::cors_ip_demo({"http://localhost:5173", + "http://127.0.0.1:5173", + "http://0.0.0.0:5173"})); - auto options_noop = [](Request &, Response &res) - { - res.status(204).send(); - }; + auto options_noop = [](Request &, Response &res) + { + res.status(204).send(); + }; - app.options("/api/ping", options_noop); - app.options("/api/echo", options_noop); - app.options("/api/strict", options_noop); - app.options("/upload", options_noop); + app.options("/api/ping", options_noop); + app.options("/api/echo", options_noop); + app.options("/api/strict", options_noop); + app.options("/upload", options_noop); } // ------------------------------------------------------------ @@ -51,8 +61,8 @@ static void install_cors(App &app) // ------------------------------------------------------------ static void install_body_limit(App &app) { - // Applies only to POST/PUT/PATCH (alias) - app.use("/", middleware::app::body_limit_write_dev(16)); + // Applies only to POST/PUT/PATCH (alias) + app.use("/", middleware::app::body_limit_write_dev(16)); } // ------------------------------------------------------------ @@ -60,16 +70,16 @@ static void install_body_limit(App &app) // ------------------------------------------------------------ static void install_routes(App &app) { - app.get("/health", [](Request &, Response &res) - { res.json({"ok", true}); }); + app.get("/health", [](Request &, Response &res) + { res.json({"ok", true}); }); - app.get("/api/ping", [](Request &, Response &res) - { + app.get("/api/ping", [](Request &, Response &res) + { res.header("X-Request-Id", "req_ping_1"); res.json({"ok", true, "msg", "pong"}); }); - app.post("/api/echo", [](Request &req, Response &res) - { + app.post("/api/echo", [](Request &req, Response &res) + { res.header("X-Request-Id", "req_echo_1"); res.json({ "ok", true, @@ -78,26 +88,26 @@ static void install_routes(App &app) "body", req.body() }); }); - app.post("/api/strict", [](Request &req, Response &res) - { res.json({"ok", true, "bytes", static_cast(req.body().size())}); }); + app.post("/api/strict", [](Request &req, Response &res) + { res.json({"ok", true, "bytes", static_cast(req.body().size())}); }); - app.post("/upload", [](Request &req, Response &res) - { res.json({"ok", true, "bytes", static_cast(req.body().size())}); }); + app.post("/upload", [](Request &req, Response &res) + { res.json({"ok", true, "bytes", static_cast(req.body().size())}); }); } static void run_app() { - App app; + App app; - install_cors(app); - install_body_limit(app); - install_routes(app); + install_cors(app); + install_body_limit(app); + install_routes(app); - app.run(8080); + app.run(8080); } int main() { - run_app(); - return 0; + run_app(); + return 0; } diff --git a/examples/cache/http_cache_app_custom_cache.cpp b/examples/cache/http_cache_app_custom_cache.cpp index 886bd61..6764796 100644 --- a/examples/cache/http_cache_app_custom_cache.cpp +++ b/examples/cache/http_cache_app_custom_cache.cpp @@ -1,6 +1,16 @@ -// ============================================================================ -// http_cache_app_custom_cache.cpp β€” HTTP Cache (Custom Cache Injection) -// ---------------------------------------------------------------------------- +/** + * + * @file http_cache_app_custom_cache.cpp β€” HTTP Cache (Custom Cache Injection) + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ // Run: // vix run examples/http_cache_app_custom_cache.cpp // @@ -23,43 +33,43 @@ using namespace vix; static void register_routes(App &app) { - app.get("/api/slow", [](Request &, Response &res) - { + app.get("/api/slow", [](Request &, Response &res) + { std::this_thread::sleep_for(std::chrono::milliseconds(250)); res.text("slow response (origin)"); }); - app.get("/", [](Request &, Response &res) - { res.text("home (not cached)"); }); + app.get("/", [](Request &, Response &res) + { res.text("home (not cached)"); }); } int main() { - App app; + App app; - // Build a default cache instance (MemoryStore + policy) with ttl - auto cache = middleware::app::make_default_cache({ - .ttl_ms = 30'000, - }); + // Build a default cache instance (MemoryStore + policy) with ttl + auto cache = middleware::app::make_default_cache({ + .ttl_ms = 30'000, + }); - // Install middleware using injected cache - app.use("/api/", middleware::app::http_cache_mw({ - .prefix = "/api/", - .only_get = true, - .ttl_ms = 30'000, + // Install middleware using injected cache + app.use("/api/", middleware::app::http_cache_mw({ + .prefix = "/api/", + .only_get = true, + .ttl_ms = 30'000, - .allow_bypass = true, - .bypass_header = "x-vix-cache", - .bypass_value = "bypass", + .allow_bypass = true, + .bypass_header = "x-vix-cache", + .bypass_value = "bypass", - .vary_headers = {}, - .cache = cache, + .vary_headers = {}, + .cache = cache, - .add_debug_header = true, - .debug_header = "x-vix-cache-status", - })); + .add_debug_header = true, + .debug_header = "x-vix-cache-status", + })); - register_routes(app); + register_routes(app); - app.run(8080); - return 0; + app.run(8080); + return 0; } diff --git a/examples/cache/http_cache_app_debug.cpp b/examples/cache/http_cache_app_debug.cpp index 9756b60..ae51698 100644 --- a/examples/cache/http_cache_app_debug.cpp +++ b/examples/cache/http_cache_app_debug.cpp @@ -1,6 +1,16 @@ -// ============================================================================ -// http_cache_app_debug.cpp β€” HTTP Cache (Debug + Vary) -// ---------------------------------------------------------------------------- +/** + * + * @file http_cache_app_debug.cpp β€” HTTP Cache (Debug + Vary) + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ // Run: // vix run examples/http_cache_app_debug.cpp // @@ -30,11 +40,11 @@ using namespace vix; static void register_routes(App &app) { - app.get("/", [](Request &, Response &res) - { res.text("home (not cached)"); }); + app.get("/", [](Request &, Response &res) + { res.text("home (not cached)"); }); - app.get("/api/users", [](Request &req, Response &res) - { + app.get("/api/users", [](Request &req, Response &res) + { // βœ… Request API you actually have: // - req.has_header(name) // - req.header(name) -> std::string @@ -51,24 +61,24 @@ static void register_routes(App &app) int main() { - App app; + App app; - app.use("/api/", middleware::app::http_cache({ - .ttl_ms = 30'000, - .allow_bypass = true, - .bypass_header = "x-vix-cache", - .bypass_value = "bypass", + app.use("/api/", middleware::app::http_cache({ + .ttl_ms = 30'000, + .allow_bypass = true, + .bypass_header = "x-vix-cache", + .bypass_value = "bypass", - // Create different cache entries per language header - .vary_headers = {"accept-language"}, + // Create different cache entries per language header + .vary_headers = {"accept-language"}, - // Useful for demo/learning - .add_debug_header = true, - .debug_header = "x-vix-cache-status", - })); + // Useful for demo/learning + .add_debug_header = true, + .debug_header = "x-vix-cache-status", + })); - register_routes(app); + register_routes(app); - app.run(8080); - return 0; + app.run(8080); + return 0; } diff --git a/examples/cache/http_cache_app_simple.cpp b/examples/cache/http_cache_app_simple.cpp index 928759c..fdfac59 100644 --- a/examples/cache/http_cache_app_simple.cpp +++ b/examples/cache/http_cache_app_simple.cpp @@ -1,6 +1,16 @@ -// ============================================================================ -// http_cache_app_simple.cpp β€” HTTP Cache (Simple) -// ---------------------------------------------------------------------------- +/** + * + * @file http_cache_app_simple.cpp β€” HTTP Cache (Simple) + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ // Run: // vix run examples/http_cache_app_simple.cpp // @@ -21,27 +31,27 @@ using namespace vix; static void register_routes(App &app) { - app.get("/", [](Request &, Response &res) - { res.text("home (not cached)"); }); + app.get("/", [](Request &, Response &res) + { res.text("home (not cached)"); }); - app.get("/api/users", [](Request &, Response &res) - { res.text("users from origin"); }); + app.get("/api/users", [](Request &, Response &res) + { res.text("users from origin"); }); } int main() { - App app; + App app; - // Cache GET requests under /api/* - app.use("/api/", middleware::app::http_cache({ - .ttl_ms = 30'000, - .allow_bypass = true, - .bypass_header = "x-vix-cache", - .bypass_value = "bypass", - })); + // Cache GET requests under /api/* + app.use("/api/", middleware::app::http_cache({ + .ttl_ms = 30'000, + .allow_bypass = true, + .bypass_header = "x-vix-cache", + .bypass_value = "bypass", + })); - register_routes(app); + register_routes(app); - app.run(8080); - return 0; + app.run(8080); + return 0; } diff --git a/examples/compression/compression_app_simple.cpp b/examples/compression/compression_app_simple.cpp index cf0dc05..b2aa265 100644 --- a/examples/compression/compression_app_simple.cpp +++ b/examples/compression/compression_app_simple.cpp @@ -1,6 +1,16 @@ -// ============================================================================ -// compression_app_simple.cpp β€” Compression middleware (App) example (Vix.cpp) -// ---------------------------------------------------------------------------- +/** + * + * @file compression_app_simple.cpp β€” Compression middleware (App) example (Vix.cpp) + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ // Run: // vix run compression_app_simple.cpp // @@ -29,45 +39,45 @@ using namespace vix; static void print_help() { - std::cout - << "Vix Compression example running:\n" - << " http://localhost:8080/\n" - << " http://localhost:8080/x\n" - << " http://localhost:8080/small\n\n" - << "Try:\n" - << " curl -i http://localhost:8080/x\n" - << " curl -i -H \"Accept-Encoding: gzip, br\" http://localhost:8080/x\n" - << " curl -i -H \"Accept-Encoding: gzip\" http://localhost:8080/small\n"; + std::cout + << "Vix Compression example running:\n" + << " http://localhost:8080/\n" + << " http://localhost:8080/x\n" + << " http://localhost:8080/small\n\n" + << "Try:\n" + << " curl -i http://localhost:8080/x\n" + << " curl -i -H \"Accept-Encoding: gzip, br\" http://localhost:8080/x\n" + << " curl -i -H \"Accept-Encoding: gzip\" http://localhost:8080/small\n"; } int main() { - App app; + App app; - // Install compression middleware globally - auto mw = vix::middleware::app::adapt_ctx( - vix::middleware::performance::compression({ - .min_size = 8, // same as smoke test - .add_vary = true, - .enabled = true, - })); + // Install compression middleware globally + auto mw = vix::middleware::app::adapt_ctx( + vix::middleware::performance::compression({ + .min_size = 8, // same as smoke test + .add_vary = true, + .enabled = true, + })); - app.use(std::move(mw)); + app.use(std::move(mw)); - app.get("/", [](Request &, Response &res) - { res.send("Compression middleware installed. Try /x with Accept-Encoding."); }); + app.get("/", [](Request &, Response &res) + { res.send("Compression middleware installed. Try /x with Accept-Encoding."); }); - // Big body => should trigger "planned" (debug) if Accept-Encoding asks gzip/br - app.get("/x", [](Request &, Response &res) - { res.status(200).send(std::string(20, 'a')); }); + // Big body => should trigger "planned" (debug) if Accept-Encoding asks gzip/br + app.get("/x", [](Request &, Response &res) + { res.status(200).send(std::string(20, 'a')); }); - // Small body => should NOT trigger "planned" - app.get("/small", [](Request &, Response &res) - { - res.status(200).send("aaaa"); // 4 bytes - }); + // Small body => should NOT trigger "planned" + app.get("/small", [](Request &, Response &res) + { + res.status(200).send("aaaa"); // 4 bytes + }); - print_help(); - app.run(8080); - return 0; + print_help(); + app.run(8080); + return 0; } diff --git a/examples/cors/cors_app_basic.cpp b/examples/cors/cors_app_basic.cpp index b5e455d..e839a82 100644 --- a/examples/cors/cors_app_basic.cpp +++ b/examples/cors/cors_app_basic.cpp @@ -1,5 +1,16 @@ -// ============================================================================ -// cors_app_basic.cpp β€” Basic CORS example (Vix.cpp) +/** + * + * @file cors_app_basic.cpp β€” Basic CORS example (Vix.cpp) + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ // curl -i http://localhost:8080/api -H "Origin: https://example.com" // ============================================================================ @@ -10,14 +21,14 @@ using namespace vix; int main() { - App app; + App app; - app.use("/api", middleware::app::cors_dev({"https://example.com"})); + app.use("/api", middleware::app::cors_dev({"https://example.com"})); - app.get("/api", [](Request &, Response &res) - { + app.get("/api", [](Request &, Response &res) + { res.header("X-Request-Id", "req_123"); res.json({ "ok", true }); }); - app.run(8080); + app.run(8080); } diff --git a/examples/cors/cors_app_strict.cpp b/examples/cors/cors_app_strict.cpp index b2f743f..bf1e72a 100644 --- a/examples/cors/cors_app_strict.cpp +++ b/examples/cors/cors_app_strict.cpp @@ -1,5 +1,16 @@ -// ============================================================================ -// cors_app_strict.cpp β€” Strict CORS + controlled preflight (Vix.cpp) +/** + * + * @file cors_app_strict.cpp β€” Strict CORS + controlled preflight (Vix.cpp) + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ // # allowed // curl -i -X OPTIONS http://localhost:8080/api \ // -H "Origin: https://example.com" \ @@ -18,22 +29,22 @@ using namespace vix; int main() { - App app; + App app; - // πŸ”’ Apply CORS only on /api prefix - app.use("/api", middleware::app::cors_dev({"https://example.com"})); + // πŸ”’ Apply CORS only on /api prefix + app.use("/api", middleware::app::cors_dev({"https://example.com"})); - // βœ… Explicit OPTIONS route (lets middleware answer preflight) - app.options("/api", [](Request &, Response &res) - { + // βœ… Explicit OPTIONS route (lets middleware answer preflight) + app.options("/api", [](Request &, Response &res) + { // Optional debug marker (only if this handler executes) res.header("X-OPTIONS-HIT", "1"); res.status(204).send(); }); - app.get("/api", [](Request &, Response &res) - { + app.get("/api", [](Request &, Response &res) + { res.header("X-Request-Id", "req_123"); res.json({ "ok", true }); }); - app.run(8080); + app.run(8080); } diff --git a/examples/csrf/csrf_pipeline_demo.cpp b/examples/csrf/csrf_pipeline_demo.cpp index 206110b..15c45c3 100644 --- a/examples/csrf/csrf_pipeline_demo.cpp +++ b/examples/csrf/csrf_pipeline_demo.cpp @@ -1,6 +1,16 @@ -// ============================================================================ -// csrf_pipeline_demo.cpp β€” CSRF pipeline demo (Vix.cpp) -// ---------------------------------------------------------------------------- +/** + * + * @file csrf_pipeline_demo.cpp β€” CSRF pipeline demo (Vix.cpp) + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ // Run: // vix run csrf_pipeline_demo.cpp // ============================================================================ @@ -17,66 +27,66 @@ using namespace vix::middleware; static vix::vhttp::RawRequest make_post(bool ok) { - namespace http = boost::beast::http; + namespace http = boost::beast::http; - vix::vhttp::RawRequest req{http::verb::post, "/api/update", 11}; - req.set(http::field::host, "localhost"); + vix::vhttp::RawRequest req{http::verb::post, "/api/update", 11}; + req.set(http::field::host, "localhost"); - // Cookie + header must match - req.set("Cookie", "csrf_token=abc"); - req.set("x-csrf-token", ok ? "abc" : "wrong"); + // Cookie + header must match + req.set("Cookie", "csrf_token=abc"); + req.set("x-csrf-token", ok ? "abc" : "wrong"); - req.body() = "x=1"; - req.prepare_payload(); - return req; + req.body() = "x=1"; + req.prepare_payload(); + return req; } int main() { - namespace http = boost::beast::http; + namespace http = boost::beast::http; - // FAIL - { - auto raw = make_post(false); - http::response res; + // FAIL + { + auto raw = make_post(false); + http::response res; - vix::vhttp::Request req(raw, {}); - vix::vhttp::ResponseWrapper w(res); + vix::vhttp::Request req(raw, {}); + vix::vhttp::ResponseWrapper w(res); - HttpPipeline p; - p.use(vix::middleware::security::csrf()); // MiddlewareFn(Context&, Next) + HttpPipeline p; + p.use(vix::middleware::security::csrf()); // MiddlewareFn(Context&, Next) - int final_calls = 0; - p.run(req, w, [&](Request &, Response &) - { + int final_calls = 0; + p.run(req, w, [&](Request &, Response &) + { final_calls++; w.ok().text("OK"); }); - assert(final_calls == 0); - assert(res.result_int() == 403); - } + assert(final_calls == 0); + assert(res.result_int() == 403); + } - // OK - { - auto raw = make_post(true); - http::response res; + // OK + { + auto raw = make_post(true); + http::response res; - vix::vhttp::Request req(raw, {}); - vix::vhttp::ResponseWrapper w(res); + vix::vhttp::Request req(raw, {}); + vix::vhttp::ResponseWrapper w(res); - HttpPipeline p; - p.use(vix::middleware::security::csrf()); + HttpPipeline p; + p.use(vix::middleware::security::csrf()); - int final_calls = 0; - p.run(req, w, [&](Request &, Response &) - { + int final_calls = 0; + p.run(req, w, [&](Request &, Response &) + { final_calls++; w.ok().text("OK"); }); - assert(final_calls == 1); - assert(res.result_int() == 200); - } + assert(final_calls == 1); + assert(res.result_int() == 200); + } - std::cout << "[OK] csrf pipeline demo\n"; - return 0; + std::cout << "[OK] csrf pipeline demo\n"; + return 0; } diff --git a/examples/csrf/csrf_strict_server.cpp b/examples/csrf/csrf_strict_server.cpp index 21f78d3..917cd4d 100644 --- a/examples/csrf/csrf_strict_server.cpp +++ b/examples/csrf/csrf_strict_server.cpp @@ -1,6 +1,16 @@ -// ============================================================================ -// csrf_strict_server.cpp β€” CSRF middleware example (Vix.cpp) -// ---------------------------------------------------------------------------- +/** + * + * @file csrf_strict_server.cpp β€” CSRF middleware example (Vix.cpp) + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ // Run: // vix run csrf_strict_server.cpp // @@ -27,17 +37,17 @@ using namespace vix; int main() { - App app; + App app; - app.use("/api", middleware::app::csrf_dev()); + app.use("/api", middleware::app::csrf_dev()); - app.get("/api/csrf", [](Request &, Response &res) - { + app.get("/api/csrf", [](Request &, Response &res) + { res.header("Set-Cookie", "csrf_token=abc; Path=/; SameSite=Lax"); res.json({ "csrf_token", "abc" }); }); - app.post("/api/update", [](Request &, Response &res) - { res.json({"ok", true, "message", "CSRF passed βœ…"}); }); + app.post("/api/update", [](Request &, Response &res) + { res.json({"ok", true, "message", "CSRF passed βœ…"}); }); - app.run(8080); + app.run(8080); } diff --git a/examples/csrf/security_cors_csrf_server.cpp b/examples/csrf/security_cors_csrf_server.cpp index 8b04f59..565b398 100644 --- a/examples/csrf/security_cors_csrf_server.cpp +++ b/examples/csrf/security_cors_csrf_server.cpp @@ -1,5 +1,16 @@ -// ============================================================================ -// security_cors_csrf_server.cpp β€” CORS + CSRF (Vix.cpp) +/** + * + * @file security_cors_csrf_server.cpp β€” CORS + CSRF (Vix.cpp) + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ // ---------------------------------------------------------------------------- // Goal: // - OPTIONS handled by CORS middleware (preflight) @@ -51,33 +62,33 @@ using namespace vix; int main() { - App app; + App app; - // Apply on /api (order matters) - middleware::app::protect_prefix(app, "/api", - middleware::app::cors_dev({"https://example.com"})); + // Apply on /api (order matters) + middleware::app::protect_prefix(app, "/api", + middleware::app::cors_dev({"https://example.com"})); - // CSRF expects: cookie "csrf_token" and header "x-csrf-token" by default - middleware::app::protect_prefix(app, "/api", - middleware::app::csrf_dev("csrf_token", "x-csrf-token", false)); - // ou strict: - // middleware::app::protect_prefix(app, "/api", - // middleware::app::csrf_strict_dev("csrf_token", "x-csrf-token")); + // CSRF expects: cookie "csrf_token" and header "x-csrf-token" by default + middleware::app::protect_prefix(app, "/api", + middleware::app::csrf_dev("csrf_token", "x-csrf-token", false)); + // ou strict: + // middleware::app::protect_prefix(app, "/api", + // middleware::app::csrf_strict_dev("csrf_token", "x-csrf-token")); - // Routes - app.get("/api/csrf", [](Request &, Response &res) - { + // Routes + app.get("/api/csrf", [](Request &, Response &res) + { res.header("Set-Cookie", "csrf_token=abc; Path=/; SameSite=Lax"); res.header("X-Request-Id", "req_123"); res.json({ "csrf_token", "abc" }); }); - app.post("/api/update", [](Request &, Response &res) - { + app.post("/api/update", [](Request &, Response &res) + { res.header("X-Request-Id", "req_456"); res.json({ "ok", true, "message", "CORS βœ… + CSRF βœ…" }); }); - app.get("/", [](Request &, Response &res) - { res.send("Welcome"); }); + app.get("/", [](Request &, Response &res) + { res.send("Welcome"); }); - app.run(8080); + app.run(8080); } diff --git a/examples/etag/etag_app_simple.cpp b/examples/etag/etag_app_simple.cpp index f811109..a33d1c9 100644 --- a/examples/etag/etag_app_simple.cpp +++ b/examples/etag/etag_app_simple.cpp @@ -1,5 +1,16 @@ -// ============================================================================ -// etag_app_simple.cpp β€” ETag middleware example (Vix.cpp) +/** + * + * @file etag_app_simple.cpp β€” ETag middleware example (Vix.cpp) + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ // ---------------------------------------------------------------------------- // Run: // vix run etag_app_simple.cpp @@ -21,30 +32,30 @@ using namespace vix; static void print_help() { - std::cout - << "Vix ETag example running:\n" - << " http://localhost:8080/x\n\n" - << "Try:\n" - << " curl -i http://localhost:8080/x\n" - << " curl -i -H 'If-None-Match: ' http://localhost:8080/x\n" - << " curl -I http://localhost:8080/x\n"; + std::cout + << "Vix ETag example running:\n" + << " http://localhost:8080/x\n\n" + << "Try:\n" + << " curl -i http://localhost:8080/x\n" + << " curl -i -H 'If-None-Match: ' http://localhost:8080/x\n" + << " curl -I http://localhost:8080/x\n"; } int main() { - App app; + App app; - // Install ETag middleware globally - auto mw = vix::middleware::app::adapt_ctx( - vix::middleware::performance::etag({.weak = true, - .add_cache_control_if_missing = false, - .min_body_size = 1})); - app.use(std::move(mw)); + // Install ETag middleware globally + auto mw = vix::middleware::app::adapt_ctx( + vix::middleware::performance::etag({.weak = true, + .add_cache_control_if_missing = false, + .min_body_size = 1})); + app.use(std::move(mw)); - app.head("/x", [](Request &, Response &res) - { res.status(200); }); + app.head("/x", [](Request &, Response &res) + { res.status(200); }); - print_help(); - app.run(8080); - return 0; + print_help(); + app.run(8080); + return 0; } diff --git a/examples/form/form_app_simple.cpp b/examples/form/form_app_simple.cpp index df086da..ddd9fee 100644 --- a/examples/form/form_app_simple.cpp +++ b/examples/form/form_app_simple.cpp @@ -1,5 +1,16 @@ -// ============================================================================ -// form_app_simple.cpp β€” Form parser (App) simple example (Vix.cpp) +/** + * + * @file form_app_simple.cpp β€” Form parser (App) simple example (Vix.cpp) + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ // ---------------------------------------------------------------------------- // Run: // vix run form_app_simple.cpp @@ -28,20 +39,20 @@ using namespace vix; int main() { - App app; + App app; - app.use("/form", middleware::app::form_dev(128)); + app.use("/form", middleware::app::form_dev(128)); - app.get("/", [](Request &, Response &res) - { res.send("POST /form (application/x-www-form-urlencoded)"); }); + app.get("/", [](Request &, Response &res) + { res.send("POST /form (application/x-www-form-urlencoded)"); }); - app.post("/form", [](Request &req, Response &res) - { + app.post("/form", [](Request &req, Response &res) + { auto& fb = req.state(); auto it = fb.fields.find("b"); res.send(it == fb.fields.end() ? "" : it->second); }); - app.run(8080); - return 0; + app.run(8080); + return 0; } diff --git a/examples/form/json_app_simple.cpp b/examples/form/json_app_simple.cpp index 7b2d7ce..0c7b6ee 100644 --- a/examples/form/json_app_simple.cpp +++ b/examples/form/json_app_simple.cpp @@ -1,5 +1,16 @@ -// ============================================================================ -// json_app_simple.cpp β€” JSON parser (App) simple example (Vix.cpp) +/** + * + * @file json_app_simple.cpp β€” JSON parser (App) simple example (Vix.cpp) + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ // ---------------------------------------------------------------------------- // Run: // vix run json_app_simple.cpp @@ -39,19 +50,19 @@ using namespace vix; int main() { - App app; + App app; - // 1-liner like Node/FastAPI - app.use("/json", middleware::app::json_dev( - /*max_bytes=*/256, - /*allow_empty=*/true, - /*require_content_type=*/true)); + // 1-liner like Node/FastAPI + app.use("/json", middleware::app::json_dev( + /*max_bytes=*/256, + /*allow_empty=*/true, + /*require_content_type=*/true)); - app.get("/", [](Request &, Response &res) - { res.send("POST /json with application/json"); }); + app.get("/", [](Request &, Response &res) + { res.send("POST /json with application/json"); }); - app.post("/json", [](Request &req, Response &res) - { + app.post("/json", [](Request &req, Response &res) + { auto &jb = req.state(); // keep it simple: just echo the parsed JSON @@ -60,6 +71,6 @@ int main() "raw", jb.value.dump() }); }); - app.run(8080); - return 0; + app.run(8080); + return 0; } diff --git a/examples/form/json_app_strict.cpp b/examples/form/json_app_strict.cpp index b04384d..1777343 100644 --- a/examples/form/json_app_strict.cpp +++ b/examples/form/json_app_strict.cpp @@ -1,5 +1,16 @@ -// ============================================================================ -// json_app_strict.cpp β€” JSON parser (App) strict example (Vix.cpp) +/** + * + * @file json_app_strict.cpp β€” JSON parser (App) strict example (Vix.cpp) + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ // ---------------------------------------------------------------------------- // Run: // vix run json_app_strict.cpp @@ -38,19 +49,19 @@ using namespace vix; int main() { - App app; + App app; - // STRICT JSON: Content-Type required + body required (allow_empty=false) - app.use("/json", middleware::app::json_dev( - /*max_bytes=*/256, - /*allow_empty=*/false, - /*require_content_type=*/true)); + // STRICT JSON: Content-Type required + body required (allow_empty=false) + app.use("/json", middleware::app::json_dev( + /*max_bytes=*/256, + /*allow_empty=*/false, + /*require_content_type=*/true)); - app.get("/", [](Request &, Response &res) - { res.send("POST /json requires a non-empty JSON body."); }); + app.get("/", [](Request &, Response &res) + { res.send("POST /json requires a non-empty JSON body."); }); - app.post("/json", [](Request &req, Response &res) - { + app.post("/json", [](Request &req, Response &res) + { auto &jb = req.state(); if (jb.value.contains("x")) @@ -58,6 +69,6 @@ int main() else res.status(200).send(jb.value.dump()); }); - app.run(8080); - return 0; + app.run(8080); + return 0; } diff --git a/examples/form/multipart_app_simple.cpp b/examples/form/multipart_app_simple.cpp index 60883c6..e7e832e 100644 --- a/examples/form/multipart_app_simple.cpp +++ b/examples/form/multipart_app_simple.cpp @@ -1,5 +1,16 @@ -// ============================================================================ -// multipart_app_simple.cpp β€” Multipart parser (App) simple example (Vix.cpp) +/** + * + * @file multipart_app_simple.cpp β€” Multipart parser (App) simple example (Vix.cpp) + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ // ---------------------------------------------------------------------------- // Run: // vix run multipart_app_simple.cpp @@ -33,22 +44,22 @@ using namespace vix; int main() { - App app; + App app; - app.use("/mp", middleware::app::cors_dev()); - app.use("/mp", middleware::app::multipart_save_dev("uploads")); + app.use("/mp", middleware::app::cors_dev()); + app.use("/mp", middleware::app::multipart_save_dev("uploads")); - app.options("/mp", [](Request &, Response &res) - { res.status(204).send(""); }); + app.options("/mp", [](Request &, Response &res) + { res.status(204).send(""); }); - app.get("/", [](Request &, Response &res) - { res.send("POST /mp multipart/form-data (saves files to ./uploads/)"); }); + app.get("/", [](Request &, Response &res) + { res.send("POST /mp multipart/form-data (saves files to ./uploads/)"); }); - app.post("/mp", [](Request &req, Response &res) - { + app.post("/mp", [](Request &req, Response &res) + { auto &form = req.state(); res.json(middleware::app::multipart_json(form)); }); - app.run(8080); - return 0; -} \ No newline at end of file + app.run(8080); + return 0; +} diff --git a/examples/group_builder/group_app_example.cpp b/examples/group_builder/group_app_example.cpp index fbc65e6..e9ca15b 100644 --- a/examples/group_builder/group_app_example.cpp +++ b/examples/group_builder/group_app_example.cpp @@ -1,5 +1,16 @@ -// ============================================================================ -// group_app_example.cpp β€” Groups + protect() demo (Vix.cpp) +/** + * + * @file group_app_example.cpp β€” Groups + protect() demo (Vix.cpp) + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ // ---------------------------------------------------------------------------- // Run: // vix run group_app_example.cpp @@ -22,15 +33,15 @@ using namespace vix; int main() { - App app; + App app; - // Root - app.get("/", [](Request &, Response &res) - { res.send("Welcome. Try /api/public, /api/secure, /api/admin/dashboard"); }); + // Root + app.get("/", [](Request &, Response &res) + { res.send("Welcome. Try /api/public, /api/secure, /api/admin/dashboard"); }); - // GROUP: /api - app.group("/api", [&](App::Group &api) - { + // GROUP: /api + app.group("/api", [&](App::Group &api) + { // Public API api.get("/public", [](Request &, Response &res) { @@ -67,21 +78,21 @@ int main() }); }); }); - // Help - std::cout - << "Vix Groups example running:\n" - << " http://localhost:8080/\n" - << " http://localhost:8080/api/public\n" - << " http://localhost:8080/api/secure\n" - << " http://localhost:8080/api/admin/dashboard\n\n" - << "API KEY:\n" - << " secret\n\n" - << "Try:\n" - << " curl -i http://localhost:8080/api/public\n" - << " curl -i http://localhost:8080/api/secure\n" - << " curl -i -H \"x-api-key: secret\" http://localhost:8080/api/secure\n" - << " curl -i \"http://localhost:8080/api/secure?api_key=secret\"\n"; + // Help + std::cout + << "Vix Groups example running:\n" + << " http://localhost:8080/\n" + << " http://localhost:8080/api/public\n" + << " http://localhost:8080/api/secure\n" + << " http://localhost:8080/api/admin/dashboard\n\n" + << "API KEY:\n" + << " secret\n\n" + << "Try:\n" + << " curl -i http://localhost:8080/api/public\n" + << " curl -i http://localhost:8080/api/secure\n" + << " curl -i -H \"x-api-key: secret\" http://localhost:8080/api/secure\n" + << " curl -i \"http://localhost:8080/api/secure?api_key=secret\"\n"; - app.run(8080); - return 0; + app.run(8080); + return 0; } diff --git a/examples/group_builder/group_builder_example.cpp b/examples/group_builder/group_builder_example.cpp index 0956478..918e217 100644 --- a/examples/group_builder/group_builder_example.cpp +++ b/examples/group_builder/group_builder_example.cpp @@ -1,5 +1,16 @@ -// ============================================================================ -// group_builder_example.cpp β€” group() builder style (Vix.cpp) +/** + * + * @file group_builder_example.cpp β€” group() builder style (Vix.cpp) + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ // ---------------------------------------------------------------------------- // Run: // vix run group_builder_example.cpp @@ -18,32 +29,32 @@ using namespace vix; int main() { - App app; + App app; - // Create /api group - auto api = app.group("/api"); + // Create /api group + auto api = app.group("/api"); - // Public endpoint - api.get("/public", [](Request &, Response &res) - { res.send("Public endpoint"); }); + // Public endpoint + api.get("/public", [](Request &, Response &res) + { res.send("Public endpoint"); }); - // πŸ” Protect all following /api routes with API key (DEV preset) - api.use(middleware::app::api_key_dev("secret")); + // πŸ” Protect all following /api routes with API key (DEV preset) + api.use(middleware::app::api_key_dev("secret")); - // Secure endpoint - api.get("/secure", [](Request &req, Response &res) - { + // Secure endpoint + api.get("/secure", [](Request &req, Response &res) + { auto &k = req.state(); res.json({ "ok", true, "api_key", k.value }); }); - std::cout - << "Running:\n" - << " http://localhost:8080/api/public\n" - << " http://localhost:8080/api/secure\n"; + std::cout + << "Running:\n" + << " http://localhost:8080/api/public\n" + << " http://localhost:8080/api/secure\n"; - app.run(8080); - return 0; + app.run(8080); + return 0; } diff --git a/examples/headers/headers_pipeline_demo.cpp b/examples/headers/headers_pipeline_demo.cpp index acb6511..3e0f0eb 100644 --- a/examples/headers/headers_pipeline_demo.cpp +++ b/examples/headers/headers_pipeline_demo.cpp @@ -1,5 +1,16 @@ -// ============================================================================ -// headers_pipeline_demo.cpp β€” Security headers pipeline demo (Vix.cpp) +/** + * + * @file headers_pipeline_demo.cpp β€” Security headers pipeline demo (Vix.cpp) + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ // ---------------------------------------------------------------------------- // Run: // vix run headers_pipeline_demo.cpp @@ -16,47 +27,47 @@ using namespace vix::middleware; static vix::vhttp::RawRequest make_req() { - namespace http = boost::beast::http; - vix::vhttp::RawRequest req{http::verb::get, "/x", 11}; - req.set(http::field::host, "localhost"); - req.prepare_payload(); - return req; + namespace http = boost::beast::http; + vix::vhttp::RawRequest req{http::verb::get, "/x", 11}; + req.set(http::field::host, "localhost"); + req.prepare_payload(); + return req; } int main() { - namespace http = boost::beast::http; + namespace http = boost::beast::http; - auto raw = make_req(); - http::response res; + auto raw = make_req(); + http::response res; - vix::vhttp::Request req(raw, {}); - vix::vhttp::ResponseWrapper w(res); + vix::vhttp::Request req(raw, {}); + vix::vhttp::ResponseWrapper w(res); - HttpPipeline p; + HttpPipeline p; - // Default headers() adds: - // - X-Content-Type-Options: nosniff - // - X-Frame-Options: DENY - // - Referrer-Policy: no-referrer - // - Permissions-Policy: ... - p.use(vix::middleware::security::headers()); + // Default headers() adds: + // - X-Content-Type-Options: nosniff + // - X-Frame-Options: DENY + // - Referrer-Policy: no-referrer + // - Permissions-Policy: ... + p.use(vix::middleware::security::headers()); - int final_calls = 0; - p.run(req, w, [&](Request &, Response &) - { + int final_calls = 0; + p.run(req, w, [&](Request &, Response &) + { final_calls++; w.ok().text("OK"); }); - assert(final_calls == 1); - assert(res.result_int() == 200); + assert(final_calls == 1); + assert(res.result_int() == 200); - // Must exist - assert(res.find("X-Content-Type-Options") != res.end()); - assert(res.find("X-Frame-Options") != res.end()); - assert(res.find("Referrer-Policy") != res.end()); - assert(res.find("Permissions-Policy") != res.end()); + // Must exist + assert(res.find("X-Content-Type-Options") != res.end()); + assert(res.find("X-Frame-Options") != res.end()); + assert(res.find("Referrer-Policy") != res.end()); + assert(res.find("Permissions-Policy") != res.end()); - std::cout << "[OK] security headers pipeline demo\n"; - return 0; + std::cout << "[OK] security headers pipeline demo\n"; + return 0; } diff --git a/examples/headers/security_cors_csrf_headers_server.cpp b/examples/headers/security_cors_csrf_headers_server.cpp index 488a6e8..bd89c7b 100644 --- a/examples/headers/security_cors_csrf_headers_server.cpp +++ b/examples/headers/security_cors_csrf_headers_server.cpp @@ -1,5 +1,16 @@ -// ============================================================================ -// security_cors_csrf_headers_server.cpp β€” CORS + CSRF + Security Headers (Vix.cpp) +/** + * + * @file security_cors_csrf_headers_server.cpp β€” CORS + CSRF + Security Headers (Vix.cpp) + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ // ---------------------------------------------------------------------------- // Goal (realistic app): // - OPTIONS handled by CORS middleware (preflight) @@ -54,41 +65,41 @@ using namespace vix; int main() { - App app; + App app; - // Apply on ALL /api/* - // Order matters: headers first, then CORS, then CSRF. - app.use("/api", middleware::app::security_headers_dev()); // HSTS off by default - app.use("/api", middleware::app::cors_dev({ - "http://localhost:5173", - "http://0.0.0.0:5173", - "https://example.com" // for your curl tests - })); - app.use("/api", middleware::app::csrf_dev("csrf_token", "x-csrf-token", false)); + // Apply on ALL /api/* + // Order matters: headers first, then CORS, then CSRF. + app.use("/api", middleware::app::security_headers_dev()); // HSTS off by default + app.use("/api", middleware::app::cors_dev({ + "http://localhost:5173", + "http://0.0.0.0:5173", + "https://example.com" // for your curl tests + })); + app.use("/api", middleware::app::csrf_dev("csrf_token", "x-csrf-token", false)); - // Explicit OPTIONS routes (lets CORS middleware answer preflight) - app.options("/api/update", [](Request &, Response &res) - { res.status(204).send(); }); + // Explicit OPTIONS routes (lets CORS middleware answer preflight) + app.options("/api/update", [](Request &, Response &res) + { res.status(204).send(); }); - app.options("/api/csrf", [](Request &, Response &res) - { res.status(204).send(); }); + app.options("/api/csrf", [](Request &, Response &res) + { res.status(204).send(); }); - // Routes - app.get("/api/csrf", [](Request &, Response &res) - { + // Routes + app.get("/api/csrf", [](Request &, Response &res) + { // For cross-origin cookie in browsers: HTTPS + SameSite=None; Secure // For local dev HTTP: SameSite=Lax is fine but cookie might not be sent cross-site. res.header("Set-Cookie", "csrf_token=abc; Path=/; SameSite=Lax"); res.header("X-Request-Id", "req_csrf_1"); res.json({"csrf_token", "abc"}); }); - app.post("/api/update", [](Request &, Response &res) - { + app.post("/api/update", [](Request &, Response &res) + { res.header("X-Request-Id", "req_update_1"); res.json({"ok", true, "message", "CORS βœ… + CSRF βœ… + HEADERS βœ…"}); }); - app.get("/", [](Request &, Response &res) - { res.send("public route"); }); + app.get("/", [](Request &, Response &res) + { res.send("public route"); }); - app.run(8080); + app.run(8080); } diff --git a/examples/headers/security_headers_server.cpp b/examples/headers/security_headers_server.cpp index 2ee773b..0d1d658 100644 --- a/examples/headers/security_headers_server.cpp +++ b/examples/headers/security_headers_server.cpp @@ -1,5 +1,16 @@ -// ============================================================================ -// security_headers_server.cpp β€” Security headers middleware example (Vix.cpp) +/** + * + * @file security_headers_server.cpp β€” Security headers middleware example (Vix.cpp) + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ // ---------------------------------------------------------------------------- // Goal: // - Apply security headers only on /api prefix @@ -20,17 +31,17 @@ using namespace vix; int main() { - App app; + App app; - // πŸ”’ Apply security headers only on /api - app.use("/api", middleware::app::security_headers_dev()); + // πŸ”’ Apply security headers only on /api + app.use("/api", middleware::app::security_headers_dev()); - app.get("/api/ping", [](Request &, Response &res) - { res.json({"ok", true, "message", "headers applied βœ…"}); }); + app.get("/api/ping", [](Request &, Response &res) + { res.json({"ok", true, "message", "headers applied βœ…"}); }); - // Public route (no forced headers) - app.get("/", [](Request &, Response &res) - { res.send("public route"); }); + // Public route (no forced headers) + app.get("/", [](Request &, Response &res) + { res.send("public route"); }); - app.run(8080); + app.run(8080); } diff --git a/examples/hello_routes.cpp b/examples/hello_routes.cpp index 4683cf3..35a20f2 100644 --- a/examples/hello_routes.cpp +++ b/examples/hello_routes.cpp @@ -1,5 +1,17 @@ +/** + * + * @file hello_routes.cpp + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ // ============================================================================ -// hello_routes.cpp β€” Minimal example (new Vix.cpp API) // GET /hello β†’ {"message":"Hello, Vix!"} // GET /user β†’ {"name":"Ada","tags":["c++","net","http"],"profile":{"id":42,"vip":true}} // GET /users/{id} β†’ 404 {"error":"User not found"} @@ -11,39 +23,36 @@ namespace J = vix::json; int main() { - App app; - - // Simple hello route - app.get("/hello", [](auto &, auto &res) - { res.json({"message", "Hello, Vix!"}); }); - - // Nested JSON using builders - app.get("/user", [](auto &, auto &res) - { - using namespace J; - res.json({ - "name", "Ada", - "tags", array({ "c++", "net", "http" }), - "profile", obj({ "id", 42, "vip", true }) - }); }); - - // Example with path param - app.get("/users/{id}", [](auto &, auto &res) - { res.status(4040).json({"error", "User not found"}); }); - - app.get("/hello", [](const Request &, Response &) - { return vix::json::o("message", "Hello", "id", 20); }); - - app.get("/txt", [](const Request &, Response &) - { - return "Hello world"; // const char* - }); - - app.get("/mix", [](Request &, Response &res) - { + App app; + + // Simple hello route + app.get("/", [](Request &, Response &res) + { res.json({"message", "Hello, Vix!"}); }); + + // Nested JSON using builders + app.get("/user", [](Request &, Response &res) + { res.json({"name", "Ada", + "tags", J::array({"c++", "net", "http"}), + "profile", J::obj({"id", 42, "vip", true})}); }); + + // Example with path param + app.get("/users/{id}", [](Request &, Response &res) + { res.status(4040).json({"error", "User not found"}); }); + + app.get("/hello", [](const Request &, Response &res) + { + res.set_status(200); + return vix::json::o("message", "Hello", "id", 20); }); + + app.get("/txt", [](const Request &, Response &) + { + return "Hello world"; // const char* + }); + + app.get("/mix", [](Request &, Response &res) + { res.status(201).send("Created"); return vix::json::o("ignored", true); }); - app.run(8080); - return 0; + app.run(8080); } diff --git a/examples/http/basic_get.cpp b/examples/http/basic_get.cpp index abce14f..13dcba0 100644 --- a/examples/http/basic_get.cpp +++ b/examples/http/basic_get.cpp @@ -1,28 +1,30 @@ -// -// examples/http/basic_get.cpp -// -// Minimal HTTP server using Vix.cpp. -// -// Demonstrates: -// - Creating a vix::App -// - Registering a simple GET route -// - Returning a JSON response -// - +/** + * + * @file examples/http/basic_get.cpp + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ #include using namespace vix; int main() { - App app; + App app; - // GET / - app.get("/", [](auto &, auto &res) - { res.json({"framework", "Vix.cpp", - "message", "Hello from basic_get.cpp πŸ‘‹"}); }); + // GET / + app.get("/", [](Request &, Response &res) + { res.json({"framework", "Vix.cpp", + "message", "Hello from basic_get.cpp πŸ‘‹"}); }); - // Start the HTTP server on port 8080 - app.run(8080); - return 0; + // Start the HTTP server on port 8080 + app.run(8080); + return 0; } diff --git a/examples/http/json_api.cpp b/examples/http/json_api.cpp index f1aab59..3e810e3 100644 --- a/examples/http/json_api.cpp +++ b/examples/http/json_api.cpp @@ -1,100 +1,92 @@ -// -// examples/http/json_api.cpp -// -// Simple JSON API example using Vix.cpp. -// -// Demonstrates: -// - A small "users" API under /api/users -// - GET /api/users β†’ returns a JSON array -// - POST /api/users β†’ accepts a JSON body and echoes basic info -// -// Note: -// This example uses nlohmann::json for parsing the request body. -// Make sure nlohmann/json.hpp is available in your include path. -// +/** + * + * @file examples/http/json_api.cpp + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ #include -#include using namespace vix; int main() { - App app; + App app; - // In-memory fake "database" - std::vector users = { - {{"id", 1}, {"name", "Alice"}, {"role", "admin"}}, - {{"id", 2}, {"name", "Bob"}, {"role", "user"}}}; + // In-memory fake "database" + std::vector users = { + {{"id", 1}, {"name", "Alice"}, {"role", "admin"}}, + {{"id", 2}, {"name", "Bob"}, {"role", "user"}}}; - // -------------------------------------------------------- - // GET /api/users - // - // Returns the full list of users as JSON. - // -------------------------------------------------------- - app.get("/api/users", [&users](auto &, auto &res) - { - json payload = { + // GET /api/users + // + // Returns the full list of users as JSON. + app.get("/api/users", [&users](Request &, Response &res) + { + json::Json payload = { {"count", users.size()}, {"items", users} }; res.json(payload); }); - // -------------------------------------------------------- - // POST /api/users - // - // Expects a JSON body like: - // { "name": "Charlie", "role": "user" } - // - // For simplicity, we: - // - parse the body - // - assign a new incremental id - // - push it into the in-memory vector - // - return the created user - // -------------------------------------------------------- - app.post("/api/users", [&users](auto &req, auto &res) - { + // POST /api/users + // + // Expects a JSON body like: + // { "name": "Charlie", "role": "user" } + // + // For simplicity, we: + // - parse the body + // - assign a new incremental id + // - push it into the in-memory vector + // - return the created user + app.post("/api/users", [&users](Request &req, Response &res) + { try { // req is typically a boost::beast::http::request const auto& body = req.body(); - auto data = json::parse(body); + json::Json data = json::Json::parse(body); // Generate a very simple new id int newId = users.empty() ? 1 : (users.back().value("id", 0) + 1); - json user = { + json::Json user = json::Json({ {"id", newId}, {"name", data.value("name", "unknown")}, {"role", data.value("role", "user")} - }; + }); users.push_back(user); - res.status(201).json({ + res.status(201).send(vix::json::kv({ {"message", "User created"}, {"user", user} - }); + })); } catch (const std::exception& e) { - res.status(400).json({ + res.status(400).send(vix::json::kv({ {"error", "Invalid JSON payload"}, {"details", e.what()} - }); + })); } }); - // -------------------------------------------------------- - // Root route: hint for trying the API - // -------------------------------------------------------- - app.get("/", [](auto &, auto &res) - { res.json({"api", "/api/users", - "hint1", "GET /api/users", - "hint2", "POST /api/users with JSON body {\"name\":\"Charlie\",\"role\":\"user\"}"}); }); + // Root route: hint for trying the API + app.get("/", [](Request &, Response &res) + { res.json({"api", "/api/users", + "hint1", "GET /api/users", + "hint2", "POST /api/users with JSON body {\"name\":\"Charlie\",\"role\":\"user\"}"}); }); - app.run(8080); - return 0; + app.run(8080); + return 0; } diff --git a/examples/http/json_builders_routes.cpp b/examples/http/json_builders_routes.cpp index 891d7b6..e2e3961 100644 --- a/examples/http/json_builders_routes.cpp +++ b/examples/http/json_builders_routes.cpp @@ -1,5 +1,17 @@ +/** + * + * @file examples/http/json_builders_routes.cpp + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ // ============================================================================ -// json_builders_routes.cpp β€” Minimal routes using Vix::json builders (new API) // GET /hello -> {"message":"Hello, World!"} // GET /users/{id} -> {"user":{"id":"","active":true}} // GET /roles -> {"roles":["admin","editor","viewer"]} @@ -13,16 +25,16 @@ namespace J = vix::json; int main() { - App app; + App app; - // GET /hello -> {"message": "Hello, World!"} - app.get("/hello", [](auto &, auto &res) - { res.json({"message", "Hello, World!"}); }); + // GET /hello -> {"message": "Hello, World!"} + app.get("/hello", [](Request &, Response &res) + { res.json({"message", "Hello, World!"}); }); - // GET /users/{id} -> {"user": {"id": "...", "active": true}} - app.get("/users/{id}", [](auto &, auto &res, auto ¶ms) - { - const std::string id = params["id"]; + // GET /users/{id} -> {"user": {"id": "...", "active": true}} + app.get("/users/{id}", [](Request &req, Response &res) + { + const std::string id = req.param("id"); res.json({ "user", J::obj({ "id", id, @@ -30,10 +42,10 @@ int main() }) }); }); - // GET /roles -> {"roles": ["admin", "editor", "viewer"]} - app.get("/roles", [](auto &, auto &res) - { res.json({"roles", J::array({"admin", "editor", "viewer"})}); }); + // GET /roles -> {"roles": ["admin", "editor", "viewer"]} + app.get("/roles", [](Request &, Response &res) + { res.json({"roles", J::array({"admin", "editor", "viewer"})}); }); - app.run(8080); - return 0; + app.run(8080); + return 0; } diff --git a/examples/http/now_server.cpp b/examples/http/now_server.cpp index 2cbe44c..5ef1278 100644 --- a/examples/http/now_server.cpp +++ b/examples/http/now_server.cpp @@ -1,5 +1,17 @@ +/** + * + * @file now_server.cpp β€” Demo route: current time in ISO 8601 and milliseconds + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ // ============================================================================ -// now_server.cpp β€” Demo route: current time in ISO 8601 and milliseconds // GET /now -> {"iso8601":"2025-10-09T10:34:12.123Z","ms":1696848852123} // ============================================================================ @@ -13,21 +25,21 @@ using namespace vix::utils; int main() { - auto &log = Logger::getInstance(); - log.setPattern("[%H:%M:%S.%e] [%^%l%$] %v"); - log.setLevel(Logger::Level::INFO); + auto &log = Logger::getInstance(); + log.setPattern("[%H:%M:%S.%e] [%^%l%$] %v"); + log.setLevel(Logger::Level::INFO); - const int port = utils::env_int("PORT", 8080); + const int port = utils::env_int("PORT", 8081); - App app; + App app; - // GET /now β†’ returns current ISO 8601 timestamp and epoch ms - app.get("/now", [](auto &, auto &res) - { res.json({"iso8601", utils::iso8601_now(), - "ms", static_cast(utils::now_ms())}); }); + // GET /now β†’ returns current ISO 8601 timestamp and epoch ms + app.get("/now", [](Request &, Response &res) + { res.json({"iso8601", utils::iso8601_now(), + "ms", static_cast(utils::now_ms())}); }); - log.log(Logger::Level::INFO, "Starting server on port {}", port); + log.log(Logger::Level::INFO, "Starting server on port {}", port); - app.run(port); - return 0; + app.run(port); + return 0; } diff --git a/examples/http/router_params.cpp b/examples/http/router_params.cpp index 91065d0..0633c32 100644 --- a/examples/http/router_params.cpp +++ b/examples/http/router_params.cpp @@ -1,13 +1,16 @@ -// -// examples/http/router_params.cpp -// -// Example demonstrating route parameters with Vix.cpp. -// -// Demonstrates: -// - Path parameters: /hello/{name} -// - Multiple parameters: /posts/{year}/{slug} -// - Returning JSON with dynamic values -// +/** + * + * @file examples/http/router_params.cpp + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ #include @@ -15,44 +18,46 @@ using namespace vix; int main() { - App app; - - // GET /hello/{name} - // - // Example: - // GET /hello/Alice -> { "greeting": "Hello Alice πŸ‘‹" } - // - app.get("/hello/{name}", [](auto &, auto &res, auto ¶ms) - { - const auto name = params["name"]; + App app; + + // GET /hello/{name} + // + // Example: + // GET /hello/Alice -> { "greeting": "Hello Alice πŸ‘‹" } + // + app.get("/hello/{name}", [](Request &req, Response &res) + { + const auto name = req.param("name"); res.json({ "greeting", "Hello " + name + " πŸ‘‹", "powered_by", "Vix.cpp" }); }); - // GET /posts/{year}/{slug} - // - // Example: - // GET /posts/2025/hello-world - // - app.get("/posts/{year}/{slug}", [](auto &, auto &res, auto ¶ms) - { - const auto year = params["year"]; - const auto slug = params["slug"]; - - res.json({ - "year", year, - "slug", slug, - "title", "Post: " + slug, - "message", "This is an example route with multiple params.", - "powered_by", "Vix.cpp" - }); }); - - // Optional: a root route for discoverability - app.get("/", [](auto &, auto &res) - { res.json({"routes", "/hello/{name}, /posts/{year}/{slug}", - "hint", "Try GET /hello/Alice or /posts/2025/hello-world"}); }); - - app.run(8080); - return 0; + // GET /posts/{year}/{slug} + // + // Example: + // GET /posts/2025/hello-world + // + app.get("/posts", [](Request &req, Response &res) + { + const auto year = req.query_value("year"); + const auto slug = req.query_value("slug"); + + res.json(json::kv({ + {"year", year}, + {"slug", slug}, + {"title", "Post: " + slug}, + {"message", "This is an example route with multiple params."}, + {"powered_by", "Vix.cpp"}, + {"params", req.params()}, + {"query", req.query()} + })); }); + + // Optional: a root route for discoverability + app.get("/", [](Request &, Response &res) + { res.json({"routes", "/hello/{name}, /posts/{year}/{slug}", + "hint", "Try GET /hello/Alice or /posts/2025/hello-world"}); }); + + app.run(8080); + return 0; } diff --git a/examples/http/trace_route.cpp b/examples/http/trace_route.cpp index 0dc5634..843898e 100644 --- a/examples/http/trace_route.cpp +++ b/examples/http/trace_route.cpp @@ -1,5 +1,18 @@ +/** + * + * @file examples/http/trace_route.cpp + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ + // ============================================================================ -// trace_route.cpp β€” Demo: contextual logging + request tracing (new Vix.cpp API) // GET /trace -> {"rid": "", "ok": true} // ============================================================================ @@ -13,23 +26,23 @@ using namespace vix::utils; int main() { - auto &log = Logger::getInstance(); - log.setPattern("[%Y-%m-%d %H:%M:%S.%e] [%^%l%$] %v"); - log.setLevel(Logger::Level::INFO); - log.setAsync(true); + auto &log = Logger::getInstance(); + log.setPattern("[%Y-%m-%d %H:%M:%S.%e] [%^%l%$] %v"); + log.setLevel(Logger::Level::INFO); + log.setAsync(true); - App app; + App app; - // GET /trace - app.get("/trace", [](auto &req, auto &res) - { + // GET /trace + app.get("/trace", [](Request &req, Response &res) + { Logger::Context ctx; ctx.request_id = utils::uuid4(); ctx.module = "trace_handler"; Logger::getInstance().setContext(ctx); std::string path(req.target().data(), req.target().size()); - std::string method(req.method_string().data(), req.method_string().size()); + std::string method(req.method().data(), req.method().size()); Logger::getInstance().logf( Logger::Level::INFO, @@ -43,6 +56,5 @@ int main() "ok", true }); }); - app.run(8080); - return 0; + app.run(8080); } diff --git a/examples/http_crud/CMakeLists.txt b/examples/http_crud/CMakeLists.txt index a87e249..33e4a7e 100644 --- a/examples/http_crud/CMakeLists.txt +++ b/examples/http_crud/CMakeLists.txt @@ -10,7 +10,6 @@ set(_HTTP_CRUD_EXAMPLES users_crud_internal.cpp users_crud.cpp repository_crud_full.cpp - validation_user_create.cpp ) foreach(EXAMPLE_SRC IN LISTS _HTTP_CRUD_EXAMPLES) diff --git a/examples/http_crud/batch_insert_tx.cpp b/examples/http_crud/batch_insert_tx.cpp new file mode 100644 index 0000000..7cdaeae --- /dev/null +++ b/examples/http_crud/batch_insert_tx.cpp @@ -0,0 +1,79 @@ +/** + * + * @file examples/http_crud/batch_insert_tx.cpp + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ + +#include +#include +#include + +#include +#include +#include + +using namespace vix::orm; + +int main(int argc, char **argv) +{ + std::string host = (argc > 1 ? argv[1] : "tcp://127.0.0.1:3306"); + std::string user = (argc > 2 ? argv[2] : "root"); + std::string pass = (argc > 3 ? argv[3] : ""); + std::string db = (argc > 4 ? argv[4] : "vixdb"); + + try + { + auto factory = make_mysql_factory(host, user, pass, db); + + PoolConfig cfg; + cfg.min = 1; + cfg.max = 8; + + ConnectionPool pool{factory, cfg}; + pool.warmup(); + + Transaction tx(pool); + auto &c = tx.conn(); + + auto st = c.prepare("INSERT INTO users(name,email,age) VALUES(?,?,?)"); + + struct Row + { + const char *name; + const char *email; + int age; + }; + + std::vector rows = { + {"Zoe", "zoe@example.com", 23}, + {"Mina", "mina@example.com", 31}, + {"Omar", "omar@example.com", 35}, + }; + + std::uint64_t total = 0; + for (const auto &r : rows) + { + st->bind(1, r.name); + st->bind(2, r.email); + st->bind(3, r.age); + total += st->exec(); + } + + tx.commit(); + std::cout << "[OK] inserted rows = " << total << "\n"; + return 0; + } + catch (const std::exception &e) + { + std::cerr << "[ERR] " << e.what() << "\n"; + return 1; + } +} diff --git a/examples/http_crud/delete_user.cpp b/examples/http_crud/delete_user.cpp index 1d33cd4..b883966 100644 --- a/examples/http_crud/delete_user.cpp +++ b/examples/http_crud/delete_user.cpp @@ -1,7 +1,16 @@ -// ============================================================================ -// delete_user.cpp β€” DELETE example (new Vix.cpp API) -// DELETE /users/{id} -> {"action":"delete","status":"deleted","user_id":""} -// ============================================================================ +/** + * + * @file examples/http_crud/delete_user.cpp + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ #include #include @@ -12,12 +21,12 @@ namespace J = vix::json; int main() { - App app; + App app; - // DELETE /users/{id} - app.del("/users/{id}", [](auto &, auto &res, auto ¶ms) - { - const std::string id = params["id"]; + // DELETE /users/{id} + app.del("/users/{id}", [](Request &req, Response &res) + { + const std::string id = req.param("id"); // In a real app you'd remove the resource from DB or memory here res.json({ @@ -26,6 +35,6 @@ int main() "user_id", id }); }); - app.run(8080); - return 0; + app.run(8080); + return 0; } diff --git a/examples/http_crud/error_handling.cpp b/examples/http_crud/error_handling.cpp new file mode 100644 index 0000000..0b320d1 --- /dev/null +++ b/examples/http_crud/error_handling.cpp @@ -0,0 +1,39 @@ +/** + * + * @file examples/http_crud/error_handling.cpp + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ +#include +#include + +using namespace vix::orm; + +int main(int argc, char **argv) +{ + (void)argc; + (void)argv; + try + { + // Intentionally wrong DB name to show error + auto raw = make_mysql_factory("tcp://127.0.0.1:3306", "root", "", "db_does_not_exist"); + //..... + std::cout << "[INFO] This message may not be reached if connection fails.\n"; + } + catch (const DBError &e) + { + std::cerr << "[DBError] " << e.what() << "\n"; + } + catch (const std::exception &e) + { + std::cerr << "[std::exception] " << e.what() << "\n"; + } + return 0; +} diff --git a/examples/http_crud/migrations/2025_10_10_000001_create_users.down.sql b/examples/http_crud/migrations/2025_10_10_000001_create_users.down.sql new file mode 100644 index 0000000..662db4d --- /dev/null +++ b/examples/http_crud/migrations/2025_10_10_000001_create_users.down.sql @@ -0,0 +1,2 @@ +-- Drop users table +DROP TABLE IF EXISTS users; diff --git a/examples/http_crud/migrations/2025_10_10_000001_create_users.up.sql b/examples/http_crud/migrations/2025_10_10_000001_create_users.up.sql new file mode 100644 index 0000000..4659050 --- /dev/null +++ b/examples/http_crud/migrations/2025_10_10_000001_create_users.up.sql @@ -0,0 +1,10 @@ +-- Create users table +CREATE TABLE IF NOT EXISTS users ( + id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + name VARCHAR(120) NOT NULL, + email VARCHAR(190) NOT NULL, + age INT NOT NULL, + + PRIMARY KEY (id), + UNIQUE KEY uq_users_email (email) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/examples/http_crud/migrations/2025_10_10_000002_create_orders.down.sql b/examples/http_crud/migrations/2025_10_10_000002_create_orders.down.sql new file mode 100644 index 0000000..1e94744 --- /dev/null +++ b/examples/http_crud/migrations/2025_10_10_000002_create_orders.down.sql @@ -0,0 +1,2 @@ +-- Drop orders table +DROP TABLE IF EXISTS orders; diff --git a/examples/http_crud/migrations/2025_10_10_000002_create_orders.up.sql b/examples/http_crud/migrations/2025_10_10_000002_create_orders.up.sql new file mode 100644 index 0000000..0d465bb --- /dev/null +++ b/examples/http_crud/migrations/2025_10_10_000002_create_orders.up.sql @@ -0,0 +1,14 @@ +-- Create orders table +CREATE TABLE IF NOT EXISTS orders ( + id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + user_id BIGINT UNSIGNED NOT NULL, + total DECIMAL(12,2) NOT NULL, + + PRIMARY KEY (id), + INDEX ix_orders_user_id (user_id), + + CONSTRAINT fk_orders_user + FOREIGN KEY (user_id) + REFERENCES users(id) + ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/examples/http_crud/post_create_user.cpp b/examples/http_crud/post_create_user.cpp index 4f3044f..61d1451 100644 --- a/examples/http_crud/post_create_user.cpp +++ b/examples/http_crud/post_create_user.cpp @@ -1,7 +1,16 @@ -// ============================================================================ -// post_create_user.cpp β€” POST example (new Vix.cpp API) -// POST /users -> {"action":"create","status":"created","user":{...}} -// ============================================================================ +/** + * + * @file examples/http_crud/post_create_user.cpp + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ #include #include @@ -12,14 +21,13 @@ namespace J = vix::json; int main() { - App app; + App app; - // POST /users - app.post("/users", [](auto &req, auto &res) - { + // POST /users + app.post("/users", [](Request &req, Response &res) + { try { - // Parse body as nlohmann::json for simplicity (still supported) - auto body = nlohmann::json::parse(req.body()); + auto body = json::Json::parse(req.body()); const std::string name = body.value("name", ""); const std::string email = body.value("email", ""); @@ -41,6 +49,5 @@ int main() }); } }); - app.run(8080); - return 0; + app.run(8080); } diff --git a/examples/http_crud/put_update_user.cpp b/examples/http_crud/put_update_user.cpp index 02c1502..bb8255e 100644 --- a/examples/http_crud/put_update_user.cpp +++ b/examples/http_crud/put_update_user.cpp @@ -1,7 +1,16 @@ -// ============================================================================ -// put_update_user.cpp β€” PUT example (new Vix.cpp API) -// PUT /users/{id} -> {"action":"update","status":"updated","user":{...}} -// ============================================================================ +/** + * + * @file examples/http_crud/put_update_user.cpp + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ #include #include @@ -12,16 +21,16 @@ namespace J = vix::json; int main() { - App app; + App app; - // PUT /users/{id} - app.put("/users/{id}", [](auto &req, auto &res, auto ¶ms) - { - const std::string id = params["id"]; + // PUT /users/{id} + app.put("/users/{id}", [](Request &req, Response &res) + { + const std::string id = req.param("id"); try { // Parsing with nlohmann::json for input is fine (Vix supports it internally) - auto body = nlohmann::json::parse(req.body()); + auto body = json::Json::parse(req.body()); const std::string name = body.value("name", ""); const std::string email = body.value("email", ""); @@ -44,6 +53,5 @@ int main() }); } }); - app.run(8080); - return 0; + app.run(8080); } diff --git a/examples/http_crud/querybuilder_update.cpp b/examples/http_crud/querybuilder_update.cpp new file mode 100644 index 0000000..604ee61 --- /dev/null +++ b/examples/http_crud/querybuilder_update.cpp @@ -0,0 +1,62 @@ +/** + * + * @file examples/http_crud/querybuilder_update.cpp + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ +#include +#include +#include + +#include +#include + +using namespace vix::orm; + +int main(int argc, char **argv) +{ + std::string host = (argc > 1 ? argv[1] : "tcp://127.0.0.1:3306"); + std::string user = (argc > 2 ? argv[2] : "root"); + std::string pass = (argc > 3 ? argv[3] : ""); + std::string db = (argc > 4 ? argv[4] : "vixdb"); + + try + { + auto factory = make_mysql_factory(host, user, pass, db); + + PoolConfig cfg; + cfg.min = 1; + cfg.max = 8; + + ConnectionPool pool{factory, cfg}; + pool.warmup(); + + QueryBuilder qb; + qb.raw("UPDATE users SET age=? WHERE email=?") + .param(29) + .param(std::string("gaspardkirira@example.com")); + + PooledConn pc(pool); + auto st = pc.get().prepare(qb.sql()); + + const auto &ps = qb.params(); + for (std::size_t i = 0; i < ps.size(); ++i) + st->bind(i + 1, ps[i]); + + auto affected = st->exec(); + std::cout << "[OK] affected rows = " << affected << "\n"; + return 0; + } + catch (const std::exception &e) + { + std::cerr << "[ERR] " << e.what() << "\n"; + return 1; + } +} diff --git a/examples/http_crud/repository_crud_full.cpp b/examples/http_crud/repository_crud_full.cpp index b7a75a7..8db701c 100644 --- a/examples/http_crud/repository_crud_full.cpp +++ b/examples/http_crud/repository_crud_full.cpp @@ -1,68 +1,101 @@ /** - * @file repository_crud_full.cpp - * @brief Example β€” Full CRUD except SELECT (pending ResultSet). + * + * @file examples/http_crud/repository_crud_full.cpp + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * */ - #include +#include +#include + #include #include +#include struct User { - std::int64_t id{}; - std::string name; - std::string email; - int age{}; + std::int64_t id{}; + std::string name; + std::string email; + int age{}; }; namespace vix::orm { - template <> - struct Mapper + template <> + struct Mapper + { + static User fromRow(const ResultRow &) { return {}; } // pending + + static std::vector> + toInsertParams(const User &u) { - static User fromRow(const ResultRow &) { return {}; } // pending - static std::vector> toInsertParams(const User &u) - { - return {{"name", u.name}, {"email", u.email}, {"age", u.age}}; - } - static std::vector> toUpdateParams(const User &u) - { - return {{"name", u.name}, {"email", u.email}, {"age", u.age}}; - } - }; -} // namespace Vix::orm + return { + {"name", u.name}, + {"email", u.email}, + {"age", u.age}, + }; + } + + static std::vector> + toUpdateParams(const User &u) + { + return { + {"name", u.name}, + {"email", u.email}, + {"age", u.age}, + }; + } + }; +} // namespace vix::orm int main(int argc, char **argv) { - using namespace Vix::orm; + using namespace vix::orm; - std::string host = (argc > 1 ? argv[1] : "tcp://127.0.0.1:3306"); - std::string user = (argc > 2 ? argv[2] : "root"); - std::string pass = (argc > 3 ? argv[3] : ""); - std::string db = (argc > 4 ? argv[4] : "vixdb"); + std::string host = (argc > 1 ? argv[1] : "tcp://127.0.0.1:3306"); + std::string user = (argc > 2 ? argv[2] : "root"); + std::string pass = (argc > 3 ? argv[3] : ""); + std::string db = (argc > 4 ? argv[4] : "vixdb"); - try - { - ConnectionPool pool{host, user, pass, db}; - BaseRepository repo{pool, "users"}; + try + { + auto factory = make_mysql_factory(host, user, pass, db); - // Create - std::int64_t id = static_cast(repo.create(User{0, "Bob", "gaspardkirira@example.com", 30})); - std::cout << "[OK] create β†’ id=" << id << "\n"; + PoolConfig cfg; + cfg.min = 1; + cfg.max = 8; - // Update - repo.updateById(id, User{id, "Adastra", "adastra@example.com", 31}); - std::cout << "[OK] update β†’ id=" << id << "\n"; + ConnectionPool pool{factory, cfg}; + pool.warmup(); - // Delete - repo.removeById(id); - std::cout << "[OK] delete β†’ id=" << id << "\n"; + BaseRepository repo{pool, "users"}; - return 0; - } - catch (const std::exception &e) - { - std::cerr << "[ERR] " << e.what() << "\n"; - return 1; - } + // Create + std::int64_t id = static_cast( + repo.create(User{0, "Bob", "gaspardkirira@example.com", 30})); + std::cout << "[OK] create β†’ id=" << id << "\n"; + + // Update + repo.updateById(id, User{id, "Adastra", "adastra@example.com", 31}); + std::cout << "[OK] update β†’ id=" << id << "\n"; + + // Delete + repo.removeById(id); + std::cout << "[OK] delete β†’ id=" << id << "\n"; + + return 0; + } + catch (const std::exception &e) + { + std::cerr << "[ERR] " << e.what() << "\n"; + return 1; + } } diff --git a/examples/http_crud/tx_unit_of_work.cpp b/examples/http_crud/tx_unit_of_work.cpp new file mode 100644 index 0000000..9196bcb --- /dev/null +++ b/examples/http_crud/tx_unit_of_work.cpp @@ -0,0 +1,71 @@ +/** + * + * @file examples/http_crud/tx_unit_of_work.cpp + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ + +#include +#include +#include + +#include +#include + +using namespace vix::orm; + +int main(int argc, char **argv) +{ + std::string host = (argc > 1 ? argv[1] : "tcp://127.0.0.1:3306"); + std::string user = (argc > 2 ? argv[2] : "root"); + std::string pass = (argc > 3 ? argv[3] : ""); + std::string db = (argc > 4 ? argv[4] : "vixdb"); + + try + { + auto factory = make_mysql_factory(host, user, pass, db); + + PoolConfig cfg; + cfg.min = 1; + cfg.max = 8; + + ConnectionPool pool{factory, cfg}; + pool.warmup(); + + UnitOfWork uow{pool}; + auto &c = uow.conn(); + + { + auto st = c.prepare("INSERT INTO users(name,email,age) VALUES(?,?,?)"); + st->bind(1, std::string("Alice")); + st->bind(2, std::string("alice@example.com")); + st->bind(3, 27); + st->exec(); + } + + const auto userId = c.lastInsertId(); + + { + auto st = c.prepare("INSERT INTO orders(user_id,total) VALUES(?,?)"); + st->bind(1, static_cast(userId)); + st->bind(2, 199.99); + st->exec(); + } + + uow.commit(); + std::cout << "[OK] user+order committed. user_id=" << userId << "\n"; + return 0; + } + catch (const std::exception &e) + { + std::cerr << "[ERR] " << e.what() << "\n"; + return 1; + } +} diff --git a/examples/http_crud/user_crud_with_validation.cpp b/examples/http_crud/user_crud_with_validation.cpp index dc721af..359b388 100644 --- a/examples/http_crud/user_crud_with_validation.cpp +++ b/examples/http_crud/user_crud_with_validation.cpp @@ -1,12 +1,16 @@ -// ============================================================================ -// user_crud_with_validation.cpp β€” Full CRUD + Validation (Vix.cpp, nouvelle API) -// ---------------------------------------------------------------------------- -// Routes: -// POST /users β†’ Create user (with validation) -// GET /users/{id} β†’ Read user -// PUT /users/{id} β†’ Update user -// DELETE /users/{id} β†’ Delete user -// ============================================================================ +/** + * + * @file examples/http_crud/user_crud_with_validation.cpp + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ #include // App, http, ResponseWrapper, etc. #include // Vix::json::token, obj(), array() @@ -28,10 +32,10 @@ using namespace vix::utils; // --------------------------- Data Model ------------------------------------- struct User { - std::string id; - std::string name; - std::string email; - int age{}; + std::string id; + std::string name; + std::string email; + int age{}; }; static std::mutex g_mtx; @@ -40,75 +44,75 @@ static std::unordered_map g_users; // --------------------------- Helpers ---------------------------------------- static J::kvs user_to_kvs(const User &u) { - return J::obj({"id", u.id, - "name", u.name, - "email", u.email, - "age", static_cast(u.age)}); + return J::obj({"id", u.id, + "name", u.name, + "email", u.email, + "age", static_cast(u.age)}); } static std::string to_string_safe(const njson &j) { - if (j.is_string()) - return j.get(); - if (j.is_number_integer()) - return std::to_string(j.get()); - if (j.is_number_unsigned()) - return std::to_string(j.get()); - if (j.is_number_float()) - return std::to_string(j.get()); - if (j.is_boolean()) - return j.get() ? "true" : "false"; - return {}; + if (j.is_string()) + return j.get(); + if (j.is_number_integer()) + return std::to_string(j.get()); + if (j.is_number_unsigned()) + return std::to_string(j.get()); + if (j.is_number_float()) + return std::to_string(j.get()); + if (j.is_boolean()) + return j.get() ? "true" : "false"; + return {}; } static bool parse_user(const njson &j, User &out) { - try - { - out.name = j.value("name", std::string{}); - out.email = j.value("email", std::string{}); + try + { + out.name = j.value("name", std::string{}); + out.email = j.value("email", std::string{}); - if (j.contains("age")) - { - if (j["age"].is_string()) - out.age = std::stoi(j["age"].get()); - else if (j["age"].is_number_integer()) - out.age = static_cast(j["age"].get()); - else if (j["age"].is_number_unsigned()) - out.age = static_cast(j["age"].get()); - else if (j["age"].is_number_float()) - out.age = static_cast(j["age"].get()); - else - out.age = 0; - } - else - { - out.age = 0; - } - return true; + if (j.contains("age")) + { + if (j["age"].is_string()) + out.age = std::stoi(j["age"].get()); + else if (j["age"].is_number_integer()) + out.age = static_cast(j["age"].get()); + else if (j["age"].is_number_unsigned()) + out.age = static_cast(j["age"].get()); + else if (j["age"].is_number_float()) + out.age = static_cast(j["age"].get()); + else + out.age = 0; } - catch (...) + else { - return false; + out.age = 0; } + return true; + } + catch (...) + { + return false; + } } static std::string gen_id_from_email(const std::string &email) { - const auto h = std::hash{}(email) & 0xFFFFFFull; - std::ostringstream oss; - oss << h; - return oss.str(); + const auto h = std::hash{}(email) & 0xFFFFFFull; + std::ostringstream oss; + oss << h; + return oss.str(); } // --------------------------- Main ------------------------------------------- int main() { - App app; + App app; - // CREATE (POST /users) - app.post("/users", [](auto &req, auto &res) - { + // CREATE (POST /users) + app.post("/users", [](Request &req, Response &res) + { njson body; try { body = njson::parse(req.body()); @@ -168,10 +172,10 @@ int main() "user", user_to_kvs(u) }); }); - // READ (GET /users/{id}) - app.get("/users/{id}", [](auto & /*req*/, auto &res, auto ¶ms) - { - const std::string id = params["id"]; + // READ (GET /users/{id}) + app.get("/users/{id}", [](Request &req, Response &res) + { + const std::string id = req.param("id"); std::lock_guard lock(g_mtx); auto it = g_users.find(id); if (it == g_users.end()) { @@ -184,10 +188,10 @@ int main() "user", user_to_kvs(it->second) }); }); - // UPDATE (PUT /users/{id}) - app.put("/users/{id}", [](auto &req, auto &res, auto ¶ms) - { - const std::string id = params["id"]; + // UPDATE (PUT /users/{id}) + app.put("/users/{id}", [](Request &req, Response &res) + { + const std::string id = req.param("id"); njson body; try { @@ -222,10 +226,10 @@ int main() "user", user_to_kvs(it->second) }); }); - // DELETE (DELETE /users/{id}) - app.del("/users/{id}", [](auto & /*req*/, auto &res, auto ¶ms) - { - const std::string id = params["id"]; + // DELETE (DELETE /users/{id}) + app.del("/users/{id}", [](Request &req, Response &res) + { + const std::string id = req.param("id"); std::lock_guard lock(g_mtx); const auto n = g_users.erase(id); if (!n) { @@ -239,7 +243,6 @@ int main() "user_id", id }); }); - // Lancement - app.run(8080); - return 0; + // Lancement + app.run(8080); } diff --git a/examples/http_crud/users_crud.cpp b/examples/http_crud/users_crud.cpp index bfeb194..964b560 100644 --- a/examples/http_crud/users_crud.cpp +++ b/examples/http_crud/users_crud.cpp @@ -1,152 +1,75 @@ /** - * @file users_crud.cpp - * @brief Example β€” Basic CRUD operations with Vix ORM. * - * This example demonstrates how to use the Vix ORM core classes - * (`ConnectionPool`, `BaseRepository`, and `Mapper`) to perform - * a simple `INSERT` on a `users` table. + * @file examples/http_crud/users_crud.cpp + * @author Gaspard Kirira * - * ─────────────────────────────────────────────────────────────────────────── - * Requirements - * ─────────────────────────────────────────────────────────────────────────── - * - MySQL server accessible on localhost or custom TCP host. - * - A table `users` with columns: + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. * - * ```sql - * CREATE TABLE users ( - * id BIGINT AUTO_INCREMENT PRIMARY KEY, - * name VARCHAR(255) NOT NULL, - * email VARCHAR(255) NOT NULL, - * age INT NOT NULL - * ); - * ``` + * Vix.cpp * - * - Compiled with: - * ```bash - * cmake -S .. -B build -DVIX_ORM_BUILD_EXAMPLES=ON -DVIX_ORM_USE_MYSQL=ON - * cmake --build build -j - * ./build/vix_orm_users tcp://127.0.0.1:3306 root password vixdb - * ``` - * - * ─────────────────────────────────────────────────────────────────────────── - * Example flow - * ─────────────────────────────────────────────────────────────────────────── - * 1. Define a C++ struct `User` representing the table. - * 2. Provide a `Mapper` specialization describing how to - * convert a `User` to SQL insert/update parameters. - * 3. Use a `ConnectionPool` to manage MySQL connections. - * 4. Create a `BaseRepository` and call `create()`. - * - * ─────────────────────────────────────────────────────────────────────────── - * Output example - * ─────────────────────────────────────────────────────────────────────────── - * ``` - * [OK] Insert user β†’ id=42 - * ``` */ - #include +#include +#include #include -#include +#include -// ============================================================================ -// Entity definition -// ---------------------------------------------------------------------------- -// Represents a single row in the `users` table. -// ============================================================================ -struct User -{ - std::int64_t id{}; - std::string name; - std::string email; - int age{}; -}; +using namespace vix::orm; -// ============================================================================ -// Mapper specialization for User -// ---------------------------------------------------------------------------- -// Defines how to convert between `User` objects and SQL parameters. -// Note: reading (fromRow) is omitted here since ResultSet adapter is pending. -// ============================================================================ -namespace vix::orm +int main(int argc, char **argv) { - template <> - struct Mapper - { - static User fromRow(const ResultRow &) - { - // Not implemented yet (ResultSet adapter pending) - return {}; - } + std::string host = (argc > 1 ? argv[1] : "tcp://127.0.0.1:3306"); + std::string user = (argc > 2 ? argv[2] : "root"); + std::string pass = (argc > 3 ? argv[3] : ""); + std::string db = (argc > 4 ? argv[4] : "vixdb"); - static std::vector> toInsertParams(const User &u) - { - return { - {"name", u.name}, - {"email", u.email}, - {"age", u.age}}; - } - - static std::vector> toUpdateParams(const User &u) - { - return { - {"name", u.name}, - {"email", u.email}, - {"age", u.age}}; - } - }; -} // namespace Vix::orm + try + { + auto factory = make_mysql_factory(host, user, pass, db); -// ============================================================================ -// Entry point -// ---------------------------------------------------------------------------- -// Demonstrates connecting, creating a repository, and inserting a record. -// ============================================================================ -int main(int argc, char **argv) -{ - using namespace Vix::orm; + PoolConfig cfg; + cfg.min = 1; + cfg.max = 8; - // ------------------------------------------------------------------------ - // 1. Retrieve DB credentials (defaults or CLI arguments) - // ------------------------------------------------------------------------ - std::string host = (argc > 1 ? argv[1] : "tcp://127.0.0.1:3306"); - std::string user = (argc > 2 ? argv[2] : "root"); - std::string pass = (argc > 3 ? argv[3] : ""); - std::string db = (argc > 4 ? argv[4] : "vixdb"); + ConnectionPool pool{factory, cfg}; + pool.warmup(); - try - { - // -------------------------------------------------------------------- - // 2. Initialize a connection pool - // -------------------------------------------------------------------- - ConnectionPool pool{host, user, pass, db}; + Transaction tx(pool); + auto &c = tx.conn(); - // -------------------------------------------------------------------- - // 3. Create repository for `users` - // -------------------------------------------------------------------- - BaseRepository users{pool, "users"}; + auto st = c.prepare("INSERT INTO users(name,email,age) VALUES(?,?,?)"); - // -------------------------------------------------------------------- - // 4. Insert a user (CREATE) - // -------------------------------------------------------------------- - std::uint64_t id = users.create(User{ - 0, - "Gaspard", - "gaspardkirira@outlook.com", - 28}); + struct Row + { + const char *name; + const char *email; + int age; + }; + std::vector rows = { + {"Zoe", "zoe@example.com", 23}, + {"Mina", "mina@example.com", 31}, + {"Omar", "omar@example.com", 35}, + }; - // -------------------------------------------------------------------- - // 5. Display success result - // -------------------------------------------------------------------- - std::cout << "[OK] Insert user β†’ id=" << id << "\n"; - return 0; - } - catch (const std::exception &e) + std::uint64_t total = 0; + for (const auto &r : rows) { - // -------------------------------------------------------------------- - // 6. Error handling (DB connection or execution failure) - // -------------------------------------------------------------------- - std::cerr << "[ERR] " << e.what() << "\n"; - return 1; + st->bind(1, r.name); + st->bind(2, r.email); + st->bind(3, r.age); + total += st->exec(); } + + tx.commit(); + std::cout << "[OK] inserted rows = " << total << "\n"; + return 0; + } + catch (const std::exception &e) + { + std::cerr << "[ERR] " << e.what() << "\n"; + return 1; + } } diff --git a/examples/http_crud/users_crud_internal.cpp b/examples/http_crud/users_crud_internal.cpp index 750a183..20a3cb8 100644 --- a/examples/http_crud/users_crud_internal.cpp +++ b/examples/http_crud/users_crud_internal.cpp @@ -1,133 +1,21 @@ /** - * @file users_crud_internal.cpp - * @brief Contributor documentation β€” internals & pedagogy for users_crud example. * - * This file intentionally defines **no linker-visible symbols**. It documents - * the rationale and teaching goals behind `examples/users_crud.cpp`, and serves - * as a guide for contributors who evolve the example or the underlying ORM. + * @file examples/http_crud/user_crud_internal.cpp + * @author Gaspard Kirira * - * ─────────────────────────────────────────────────────────────────────────── - * Contents - * ─────────────────────────────────────────────────────────────────────────── - * 1) Teaching goals of the example - * 2) Minimal domain model & Mapper - * 3) Repository wiring & SQL safety - * 4) Connection management (pool) & transactional hints - * 5) Error handling policy - * 6) Environment & schema pre-conditions - * 7) Extending the example (roadmap) + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. * - * ---------------------------------------------------------------------------- - * 1) Teaching goals of the example - * ---------------------------------------------------------------------------- - * The example is deliberately small and focused: - * - Show how to define an entity struct (`User`). - * - Show how to specialize `Mapper` with `toInsertParams` and `toUpdateParams`. - * - Demonstrate how to acquire a connection via `ConnectionPool`. - * - Demonstrate a simple `INSERT` with `BaseRepository::create`. + * Vix.cpp * - * It does NOT include: - * - Reading back rows (ResultSet adapter is introduced later). - * - Dynamic WHERE clauses, pagination, or joins (covered in advanced samples). - * - * ---------------------------------------------------------------------------- - * 2) Minimal domain model & Mapper - * ---------------------------------------------------------------------------- - * The domain struct is a plain struct with public fields; no macros or intrusive - * base classes are required (optional `Entity` marker exists for clarity). - * - * `Mapper` responsibilities in the example: - * - `toInsertParams(const User&)`: list of (column, value) for INSERT. - * - `toUpdateParams(const User&)`: similar list for UPDATE. - * - `fromRow(const ResultRow&)`: intentionally left unimplemented here to keep - * the early example focused; implemented in later ResultSet-centric examples. - * - * Invariants: - * - `toInsertParams` must exclude auto-increment `id`. - * - Column names must match the DB schema exactly (no escaping done by repo). - * - Values must be driver-supported `std::any` payloads (int, int64_t, unsigned, - * double, float, bool, const char*, std::string). - * - * ---------------------------------------------------------------------------- - * 3) Repository wiring & SQL safety - * ---------------------------------------------------------------------------- - * `BaseRepository` constructs parameterized SQL: - * INSERT INTO (col1,col2,...) VALUES (?, ?, ...) - * It binds parameters in the order returned by `Mapper::toInsertParams`. - * - * Security model: - * - All *values* are passed through `?` placeholders (safe binding). - * - Table and column identifiers are assumed trusted (application-owned). - * - * ---------------------------------------------------------------------------- - * 4) Connection management (pool) & transactional hints - * ---------------------------------------------------------------------------- - * The example uses `ConnectionPool` β†’ `PooledConn` automatically via the repo. - * For multi-step atomic flows, contributors should encourage explicit - * transactional scopes: - * - * @code - * Transaction tx(pool); - * auto& c = tx.conn(); - * auto st = c.prepare("INSERT ..."); - * // ... - * tx.commit(); - * @endcode - * - * or use `UnitOfWork` for grouping multiple repo calls: - * - * @code - * UnitOfWork uow(pool); - * // repoA(uow.conn()).create(...); - * // repoB(uow.conn()).update(...); - * uow.commit(); - * @endcode - * - * ---------------------------------------------------------------------------- - * 5) Error handling policy - * ---------------------------------------------------------------------------- - * - Driver failures propagate as `DBError` (see Errors.hpp). - * - The example catches `std::exception` at `main` and prints a concise error. - * - Contributors should avoid exposing secrets in error messages (no DSNs). - * - * ---------------------------------------------------------------------------- - * 6) Environment & schema pre-conditions - * ---------------------------------------------------------------------------- - * The example assumes: - * - A running MySQL instance (default host: `tcp://127.0.0.1:3306`). - * - Credentials passed via CLI or using defaults (root / empty password). - * - A database `vixdb` (or provided via argv) and a `users` table: - * - * CREATE TABLE users ( - * id BIGINT AUTO_INCREMENT PRIMARY KEY, - * name VARCHAR(255) NOT NULL, - * email VARCHAR(255) NOT NULL, - * age INT NOT NULL - * ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; - * - * Build & run: - * cmake -S .. -B build -DVIX_ORM_BUILD_EXAMPLES=ON -DVIX_ORM_USE_MYSQL=ON - * cmake --build build -j - * ./build/vix_orm_users tcp://127.0.0.1:3306 root "" vixdb - * - * ---------------------------------------------------------------------------- - * 7) Extending the example (roadmap) - * ---------------------------------------------------------------------------- - * Short term: - * - Implement `MySQLResultSet`/`MySQLResultRow` and switch the example to: - * auto u = repo.findById(id); - * std::cout << u->name << "\n"; - * - Add an UPDATE and DELETE example. - * - * Medium term: - * - Introduce `QueryBuilder` usage for WHERE clauses and pagination: - * qb.raw("SELECT id,name FROM users WHERE age >= ?").param(18); - * - Show `UnitOfWork` coordinating multiple repository actions. - * - * Long term: - * - Demonstrate batch inserts (multi-values) and upserts (ON DUPLICATE KEY). - * - Add a migration-driven setup in examples (MigrationsRunner + migrations). - * - Provide optional logging via spdlog (timings, affected rows). */ -// No includes or code needed; this TU is documentation-only. +#include + +int main() +{ + std::cout << "[OK] users_crud_internal placeholder\n"; + return 0; +} diff --git a/examples/http_crud/validation_user_create.cpp b/examples/http_crud/validation_user_create.cpp deleted file mode 100644 index 0b5a551..0000000 --- a/examples/http_crud/validation_user_create.cpp +++ /dev/null @@ -1,212 +0,0 @@ -// ============================================================================ -// users_crud_validated.cpp β€” CRUD + validation (new Vix.cpp API) -// ============================================================================ - -#include -#include -#include -#include - -#include -#include -#include -#include - -using namespace vix; -namespace J = vix::json; -using njson = nlohmann::json; -using namespace vix::utils; - -// --------------------------- Data Model ------------------------------------- -struct User -{ - std::string id; - std::string name; - std::string email; - int age{}; -}; - -static std::mutex g_mtx; -static std::unordered_map g_users; - -// --------------------------- JSON helpers (Simple API) ---------------------- -static J::kvs to_json(const User &u) -{ - return J::obj({"id", u.id, - "name", u.name, - "email", u.email, - "age", static_cast(u.age)}); -} - -static std::string j_to_string(const njson &j, const char *k) -{ - if (!j.contains(k)) - return {}; - const auto &v = j.at(k); - if (v.is_string()) - return v.get(); - if (v.is_number_integer()) - return std::to_string(v.get()); - if (v.is_number_unsigned()) - return std::to_string(v.get()); - if (v.is_number_float()) - return std::to_string(v.get()); - if (v.is_boolean()) - return v.get() ? "true" : "false"; - return v.dump(); // objet/array -> JSON string -} - -static bool parse_user(const njson &j, User &out) -{ - try - { - out.name = j.value("name", std::string{}); - out.email = j.value("email", std::string{}); - - if (j.contains("age")) - { - if (j["age"].is_string()) - out.age = std::stoi(j["age"].get()); - else if (j["age"].is_number_integer()) - out.age = static_cast(j["age"].get()); - else if (j["age"].is_number_unsigned()) - out.age = static_cast(j["age"].get()); - else if (j["age"].is_number_float()) - out.age = static_cast(j["age"].get()); - else - out.age = 0; - } - else - { - out.age = 0; - } - return true; - } - catch (...) - { - return false; - } -} - -// --------------------------- App ------------------------------------------- -int main() -{ - App app; - - // CREATE - app.post("/users", [](auto &req, auto &res) - { - njson body; - try { - body = njson::parse(req.body()); - } catch (...) { - res.status(http::status::bad_request).json({ "error", "Invalid JSON" }); - return; - } - - std::unordered_map data{ - {"name", j_to_string(body, "name")}, - {"email", j_to_string(body, "email")}, - {"age", j_to_string(body, "age")} - }; - - Schema sch{ - {"name", required("name")}, - {"age", num_range(1, 150, "Age")}, - {"email", match(R"(^[^@\s]+@[^@\s]+\.[^@\s]+$)", "Invalid email")} - }; - - auto r = validate_map(data, sch); - if (r.is_err()) { - // Construire {"errors": {field: message, ...}} sans nlohmann - std::vector flat; - flat.reserve(r.error().size() * 2); - for (const auto& kv : r.error()) { - flat.emplace_back(kv.first); // clΓ© - flat.emplace_back(kv.second); // valeur - } - - // J::obj(std::move(flat)) crΓ©e un objet JSON Γ  partir de la liste plate - res.status(400).json({ - "errors", J::obj(std::move(flat)) - }); - return; - } - - User u; - if (!parse_user(body, u)) { - res.status(400).json({ "error", "Invalid fields" }); - return; - } - - // Simple ID from email hash - u.id = std::to_string(std::hash{}(u.email) & 0xFFFFFF); - - { - std::lock_guard lock(g_mtx); - g_users[u.id] = u; - } - - res.status(http::status::created).json({ - "status", "created", - "user", to_json(u) - }); }); - - // READ - app.get("/users/{id}", [](auto &, auto &res, auto ¶ms) - { - const std::string id = params["id"]; - std::lock_guard lock(g_mtx); - auto it = g_users.find(id); - if (it == g_users.end()) { - res.status(http::status::not_found).json({ "error", "Not found" }); - return; - } - res.json({ "user", to_json(it->second) }); }); - - // UPDATE - app.put("/users/{id}", [](auto &req, auto &res, auto ¶ms) - { - const std::string id = params["id"]; - - njson body; - try { - body = njson::parse(req.body()); - } catch (...) { - res.status(http::status::bad_request).json({ "error", "Invalid JSON" }); - return; - } - - std::lock_guard lock(g_mtx); - auto it = g_users.find(id); - if (it == g_users.end()) { - res.status(http::status::not_found).json({ "error", "Not found" }); - return; - } - - if (body.contains("name")) it->second.name = body["name"].get(); - if (body.contains("email")) it->second.email = body["email"].get(); - if (body.contains("age")) { - if (body["age"].is_string()) it->second.age = std::stoi(body["age"].get()); - else if (body["age"].is_number_integer()) it->second.age = static_cast(body["age"].get()); - else if (body["age"].is_number_unsigned()) it->second.age = static_cast(body["age"].get()); - else if (body["age"].is_number_float()) it->second.age = static_cast(body["age"].get()); - } - - res.json({ "status", "updated", "user", to_json(it->second) }); }); - - // DELETE - app.del("/users/{id}", [](auto &, auto &res, auto ¶ms) - { - const std::string id = params["id"]; - std::lock_guard lock(g_mtx); - const auto n = g_users.erase(id); - if (!n) { - res.status(http::status::not_found).json({ "error", "Not found" }); - return; - } - res.json({ "status", "deleted", "user_id", id }); }); - - app.run(8080); - return 0; -} diff --git a/examples/http_ws/main_basic.cpp b/examples/http_ws/main_basic.cpp index 438318d..109cef3 100644 --- a/examples/http_ws/main_basic.cpp +++ b/examples/http_ws/main_basic.cpp @@ -1,21 +1,16 @@ -// -// examples/http_ws/main_basic.cpp -// -// Basic example demonstrating how to run HTTP + WebSocket -// together using Vix.cpp's high-level helpers. -// -// This example uses: -// - make_http_and_ws() β†’ constructs App + WebSocket server -// - run_http_and_ws() β†’ automatically runs both runtimes -// -// HTTP: -// GET / -// GET /hello/{name} -// -// WebSocket: -// Broadcasts a welcome message on connection -// Echoes typed chat messages to all clients -// +/** + * + * @file examples/http_ws/main_basic.cpp + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ #include #include @@ -24,36 +19,26 @@ using namespace vix; int main() { - // ------------------------------------------------------------ - // 1) Construct the HTTP App + WebSocket Server together - // ------------------------------------------------------------ - // The config file path can be omitted; if omitted, - // Vix will automatically look for config/config.json. - // - auto bundle = vix::make_http_and_ws("config/config.json"); - auto &[app, ws] = bundle; + // Construct the HTTP App + WebSocket Server together + // The config file path can be omitted; if omitted, + // Vix will automatically look for config/config.json. + // + auto bundle = vix::make_http_and_ws("config/config.json"); + auto &[app, ws] = bundle; - // ------------------------------------------------------------ - // 2) Register HTTP routes - // ------------------------------------------------------------ + // GET / + app.get("/", [](Request &, Response &res) + { res.json({"framework", "Vix.cpp", + "message", "HTTP + WebSocket example (basic) πŸš€"}); }); - // GET / - app.get("/", [](auto &, auto &res) - { res.json({"framework", "Vix.cpp", - "message", "HTTP + WebSocket example (basic) πŸš€"}); }); + // GET /hello/{name} + app.get("/hello/{name}", [](Request &req, Response &res) + { res.json({"greeting", "Hello " + req.param("name") + " πŸ‘‹", + "powered_by", "Vix.cpp"}); }); - // GET /hello/{name} - app.get("/hello/{name}", [](auto &, auto &res, auto ¶ms) - { res.json({"greeting", "Hello " + params["name"] + " πŸ‘‹", - "powered_by", "Vix.cpp"}); }); - - // ------------------------------------------------------------ - // 3) Register WebSocket event handlers - // ------------------------------------------------------------ - - // When a new WebSocket connection opens: - ws.on_open([&ws](auto &session) - { + // Register WebSocket event handlers + ws.on_open([&ws](auto &session) + { (void)session; ws.broadcast_json("chat.system", { @@ -61,11 +46,12 @@ int main() "text", "Welcome to Vix WebSocket! πŸ‘‹" }); }); - // When a typed message is received: - ws.on_typed_message([&ws](auto &session, - const std::string &type, - const vix::json::kvs &payload) - { + // When a typed message is received: + ws.on_typed_message( + [&ws](auto &session, + const std::string &type, + const vix::json::kvs &payload) + { (void)session; // Basic chat echo example @@ -73,15 +59,13 @@ int main() ws.broadcast_json("chat.message", payload); } }); - // ------------------------------------------------------------ - // 4) Start HTTP + WebSocket together - // ------------------------------------------------------------ - // This function: - // - runs the WebSocket server in a background thread - // - installs a shutdown callback on the HTTP server - // - blocks on app.run(port) - // - vix::run_http_and_ws(app, ws, 8080); + // 4) Start HTTP + WebSocket together + // This function: + // - runs the WebSocket server in a background thread + // - installs a shutdown callback on the HTTP server + // - blocks on app.run(port) + // + vix::run_http_and_ws(app, ws, 8080); - return 0; + return 0; } diff --git a/examples/http_ws/main_chat.cpp b/examples/http_ws/main_chat.cpp index 98856cb..bacdedb 100644 --- a/examples/http_ws/main_chat.cpp +++ b/examples/http_ws/main_chat.cpp @@ -1,15 +1,16 @@ -// -// examples/http_ws/main_chat.cpp -// -// Chat-style example on top of HTTP + WebSocket. -// -// Demonstrates: -// - HTTP JSON endpoints -// - WebSocket typed messages for chat: -// * "chat.join" β†’ user joined -// * "chat.message" β†’ broadcast chat message -// * "chat.typing" β†’ typing indicator -// +/** + * + * @file examples/http_ws/main_chat.cpp + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ #include #include @@ -18,40 +19,34 @@ using namespace vix; int main() { - vix::serve_http_and_ws( - "config/config.json", 8080, - [](auto &app, auto &ws) - { - // -------------------------------------------------------- - // HTTP: basic API for client bootstrapping - // -------------------------------------------------------- + vix::serve_http_and_ws( + "config/config.json", 8080, + [](App &app, auto &ws) + { + // HTTP: basic API for client bootstrapping + app.get("/", [](Request &, Response &res) + { res.json({"name", "Vix Chat Example", + "description", "HTTP + WebSocket powered chat server", + "framework", "Vix.cpp"}); }); - app.get("/", [](auto &, auto &res) - { res.json({"name", "Vix Chat Example", - "description", "HTTP + WebSocket powered chat server", - "framework", "Vix.cpp"}); }); + app.get("/health", [](auto &, auto &res) + { res.json({"status", "ok", + "service", "chat", + "version", "1.0.0"}); }); - app.get("/health", [](auto &, auto &res) - { res.json({"status", "ok", - "service", "chat", - "version", "1.0.0"}); }); - - // -------------------------------------------------------- - // WebSocket: chat protocol - // - // Client messages are expected as: - // { - // "type": "chat.join" | "chat.message" | "chat.typing", - // "payload": { - // "user": "Alice", - // "text": "...", // for chat.message - // "room": "general" // optional - // } - // } - // -------------------------------------------------------- - - ws.on_open([&ws](auto &session) - { + // WebSocket: chat protocol + // + // Client messages are expected as: + // { + // "type": "chat.join" | "chat.message" | "chat.typing", + // "payload": { + // "user": "Alice", + // "text": "...", // for chat.message + // "room": "general" // optional + // } + // } + ws.on_open([&ws](auto &session) + { (void)session; // Notify everybody that a new connection is here @@ -60,37 +55,42 @@ int main() "text", "A new user connected to the chat πŸ‘‹" }); }); - ws.on_typed_message( - [&ws](auto &session, - const std::string &type, - const vix::json::kvs &payload) - { - (void)session; + ws.on_typed_message( + [&ws](auto &session, + const std::string &type, + const vix::json::kvs &payload) + { + (void)session; + + if (type == "chat.join") + { + nlohmann::json j = vix::websocket::detail::ws_kvs_to_nlohmann(payload); + std::string user = j.at("user"); - if (type == "chat.join") - { - // Example: { "user": "Alice", "room": "general" } - ws.broadcast_json("chat.system", {"user", payload.at("user"), - "text", payload.at("user") + " joined the chat πŸš€"}); - } - else if (type == "chat.message") - { - // Example: { "user": "Alice", "text": "Hello!" } - ws.broadcast_json("chat.message", payload); - } - else if (type == "chat.typing") - { - // Example: { "user": "Alice" } - ws.broadcast_json("chat.typing", payload); - } - else - { - // Unknown type β†’ optional debug - ws.broadcast_json("chat.unknown", {"info", "Unknown message type", - "type", type}); - } - }); - }); + // Example: { "user": "Alice", "room": "general" } + ws.broadcast_json("chat.system", + {"user", user, + "text", user + " joined the chat πŸš€"}); + } + else if (type == "chat.message") + { + // Example: { "user": "Alice", "text": "Hello!" } + ws.broadcast_json("chat.message", payload); + } + else if (type == "chat.typing") + { + // Example: { "user": "Alice" } + ws.broadcast_json("chat.typing", payload); + } + else + { + // Unknown type β†’ optional debug + ws.broadcast_json("chat.unknown", + {"info", "Unknown message type", + "type", type}); + } + }); + }); - return 0; + return 0; } diff --git a/examples/http_ws/main_minimal.cpp b/examples/http_ws/main_minimal.cpp index cc534b0..8ec3819 100644 --- a/examples/http_ws/main_minimal.cpp +++ b/examples/http_ws/main_minimal.cpp @@ -1,10 +1,16 @@ -// -// examples/http_ws/main_minimal.cpp -// -// Minimal HTTP + WebSocket example. -// -// This is intentionally tiny, to show the shape of the API. -// +/** + * + * @file examples/http_ws/main_minimal.cpp + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ #include #include @@ -13,9 +19,9 @@ using namespace vix; int main() { - // Use default config path "config/config.json" and port 8080 - vix::serve_http_and_ws([](auto &app, auto &ws) - { + // Use default config path "config/config.json" and port 8080 + vix::serve_http_and_ws([](auto &app, auto &ws) + { // Minimal HTTP route app.get("/", [](auto&, auto& res) { res.json({ @@ -37,5 +43,5 @@ int main() } }); }); - return 0; + return 0; } diff --git a/examples/http_ws/main_runtime.cpp b/examples/http_ws/main_runtime.cpp index 371811a..61ca109 100644 --- a/examples/http_ws/main_runtime.cpp +++ b/examples/http_ws/main_runtime.cpp @@ -1,12 +1,16 @@ -// -// examples/http_ws/main_runtime.cpp -// -// High-level example using vix::serve_http_and_ws(). -// -// This is the most friendly API for Node.js / Python developers: -// - One call to serve_http_and_ws(config, port, lambda) -// - Inside the lambda you configure HTTP (app) and WebSocket (ws). -// +/** + * + * @file examples/http_ws/main_runtime.cpp + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ #include #include @@ -15,30 +19,24 @@ using namespace vix; int main() { - vix::serve_http_and_ws( - "config/config.json", 8080, - [](auto &app, auto &ws) - { - // -------------------------------------------------------- - // HTTP routes (Express / FastAPI style) - // -------------------------------------------------------- + vix::serve_http_and_ws( + "config/config.json", 8080, + [](auto &app, auto &ws) + { + // HTTP routes + app.get("/", [](auto &, auto &res) + { res.json({"framework", "Vix.cpp", + "mode", "runtime", + "message", "HTTP + WebSocket via serve_http_and_ws() πŸš€"}); }); - app.get("/", [](auto &, auto &res) - { res.json({"framework", "Vix.cpp", - "mode", "runtime", - "message", "HTTP + WebSocket via serve_http_and_ws() πŸš€"}); }); + app.get("/hello/{name}", [](Request &req, Response &res) + { res.json({"greeting", "Hello " + req.param("name") + " πŸ‘‹", + "powered_by", "Vix.cpp"}); }); - app.get("/hello/{name}", [](auto &, auto &res, auto ¶ms) - { res.json({"greeting", "Hello " + params["name"] + " πŸ‘‹", - "powered_by", "Vix.cpp"}); }); - - // -------------------------------------------------------- - // WebSocket handlers - // -------------------------------------------------------- - - // Fired when a client opens a WebSocket connection - ws.on_open([&ws](auto &session) - { + // WebSocket handlers + // Fired when a client opens a WebSocket connection + ws.on_open([&ws](auto &session) + { (void)session; ws.broadcast_json("chat.system", { @@ -46,21 +44,21 @@ int main() "text", "Welcome to the runtime example! πŸ‘‹" }); }); - // Fired on every typed message: { "type": "...", "payload": {...} } - ws.on_typed_message( - [&ws](auto &session, - const std::string &type, - const vix::json::kvs &payload) - { - (void)session; + // Fired on every typed message: { "type": "...", "payload": {...} } + ws.on_typed_message( + [&ws](auto &session, + const std::string &type, + const vix::json::kvs &payload) + { + (void)session; - if (type == "chat.message") - { - // Echo / broadcast chat messages to all clients - ws.broadcast_json("chat.message", payload); - } - }); - }); + if (type == "chat.message") + { + // Echo / broadcast chat messages to all clients + ws.broadcast_json("chat.message", payload); + } + }); + }); - return 0; + return 0; } diff --git a/examples/ip_filter/ip_filter_pipeline_demo.cpp b/examples/ip_filter/ip_filter_pipeline_demo.cpp index 5512502..51086d6 100644 --- a/examples/ip_filter/ip_filter_pipeline_demo.cpp +++ b/examples/ip_filter/ip_filter_pipeline_demo.cpp @@ -1,9 +1,16 @@ -// ============================================================================ -// ip_filter_pipeline_demo.cpp β€” IP filter pipeline demo (Vix.cpp) -// ---------------------------------------------------------------------------- -// Run: -// vix run ip_filter_pipeline_demo.cpp -// ============================================================================ +/** + * + * @file examples/ip_filter/ip_filter_pipeline_demo.cpp + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ #include #include @@ -17,96 +24,90 @@ using namespace vix::middleware; static vix::vhttp::RawRequest make_req(std::string ip) { - namespace http = boost::beast::http; - vix::vhttp::RawRequest req{http::verb::get, "/x", 11}; - req.set(http::field::host, "localhost"); - req.set("X-Forwarded-For", std::move(ip)); - req.prepare_payload(); - return req; + namespace http = boost::beast::http; + vix::vhttp::RawRequest req{http::verb::get, "/x", 11}; + req.set(http::field::host, "localhost"); + req.set("X-Forwarded-For", std::move(ip)); + req.prepare_payload(); + return req; } int main() { - namespace http = boost::beast::http; + namespace http = boost::beast::http; - // ----------------------------- - // Case 1: deny list blocks - // ----------------------------- - { - auto raw = make_req("1.2.3.4"); - http::response res; + // Case 1: deny list blocks + { + auto raw = make_req("1.2.3.4"); + http::response res; - vix::vhttp::Request req(raw, {}); - vix::vhttp::ResponseWrapper w(res); + vix::vhttp::Request req(raw, {}); + vix::vhttp::ResponseWrapper w(res); - vix::middleware::security::IpFilterOptions opt; - opt.deny = {"1.2.3.4"}; + vix::middleware::security::IpFilterOptions opt; + opt.deny = {"1.2.3.4"}; - HttpPipeline p; - p.use(vix::middleware::security::ip_filter(opt)); + HttpPipeline p; + p.use(vix::middleware::security::ip_filter(opt)); - int final_calls = 0; - p.run(req, w, [&](Request &, Response &) - { + int final_calls = 0; + p.run(req, w, [&](Request &, Response &) + { final_calls++; w.ok().text("OK"); }); - assert(final_calls == 0); - assert(res.result_int() == 403); - } + assert(final_calls == 0); + assert(res.result_int() == 403); + } - // ----------------------------- - // Case 2: allow list lets through - // ----------------------------- - { - auto raw = make_req("9.9.9.9"); - http::response res; + // Case 2: allow list lets through + { + auto raw = make_req("9.9.9.9"); + http::response res; - vix::vhttp::Request req(raw, {}); - vix::vhttp::ResponseWrapper w(res); + vix::vhttp::Request req(raw, {}); + vix::vhttp::ResponseWrapper w(res); - vix::middleware::security::IpFilterOptions opt; - opt.allow = {"9.9.9.9"}; + vix::middleware::security::IpFilterOptions opt; + opt.allow = {"9.9.9.9"}; - HttpPipeline p; - p.use(vix::middleware::security::ip_filter(opt)); + HttpPipeline p; + p.use(vix::middleware::security::ip_filter(opt)); - int final_calls = 0; - p.run(req, w, [&](Request &, Response &) - { + int final_calls = 0; + p.run(req, w, [&](Request &, Response &) + { final_calls++; w.ok().text("OK"); }); - assert(final_calls == 1); - assert(res.result_int() == 200); - } + assert(final_calls == 1); + assert(res.result_int() == 200); + } - // ----------------------------- - // Case 3: allow list blocks others - // ----------------------------- - { - auto raw = make_req("2.2.2.2"); - http::response res; + // Case 3: allow list blocks others + { + auto raw = make_req("2.2.2.2"); + http::response res; - vix::vhttp::Request req(raw, {}); - vix::vhttp::ResponseWrapper w(res); + vix::vhttp::Request req(raw, {}); + vix::vhttp::ResponseWrapper w(res); - vix::middleware::security::IpFilterOptions opt; - opt.allow = {"9.9.9.9"}; + vix::middleware::security::IpFilterOptions opt; + opt.allow = {"9.9.9.9"}; - HttpPipeline p; - p.use(vix::middleware::security::ip_filter(opt)); + HttpPipeline p; + p.use(vix::middleware::security::ip_filter(opt)); - int final_calls = 0; - p.run(req, w, [&](Request &, Response &) - { + int final_calls = 0; + p.run(req, w, [&](Request &, Response &) + { final_calls++; w.ok().text("OK"); }); - assert(final_calls == 0); - assert(res.result_int() == 403); - } + assert(final_calls == 0); + assert(res.result_int() == 403); + } - std::cout << "[OK] ip_filter pipeline demo\n"; - return 0; + std::cout << "[OK] ip_filter pipeline demo\n"; + return 0; } diff --git a/examples/ip_filter/ip_filter_server.cpp b/examples/ip_filter/ip_filter_server.cpp index f3e5fe7..e201f1d 100644 --- a/examples/ip_filter/ip_filter_server.cpp +++ b/examples/ip_filter/ip_filter_server.cpp @@ -1,5 +1,16 @@ -// ============================================================================ -// ip_filter_server.cpp β€” IP filter middleware example (Vix.cpp) +/** + * + * @file ip_filter_server.cpp β€” IP filter middleware example (Vix.cpp) + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ // ---------------------------------------------------------------------------- // Goal: // - Protect /api/* using ip_filter() @@ -25,10 +36,6 @@ // // # X-Forwarded-For with chain: "client, proxy1, proxy2" // curl -i http://localhost:8080/api/hello -H "X-Forwarded-For: 10.0.0.1, 127.0.0.1" -// ============================================================================ - -// ============================================================================ -// ip_filter_server.cpp β€” IP filter middleware example (Vix.cpp) // ---------------------------------------------------------------------------- // Run: // vix run ip_filter_server.cpp @@ -40,25 +47,25 @@ using namespace vix; int main() { - App app; + App app; - // Apply on /api/* - app.use("/api", middleware::app::ip_filter_allow_deny_dev( - "x-forwarded-for", - {"10.0.0.1", "127.0.0.1"}, // allow - {"9.9.9.9"}, // deny (priority) - true // fallback to x-real-ip, etc. - )); + // Apply on /api/* + app.use("/api", middleware::app::ip_filter_allow_deny_dev( + "x-forwarded-for", + {"10.0.0.1", "127.0.0.1"}, // allow + {"9.9.9.9"}, // deny (priority) + true // fallback to x-real-ip, etc. + )); - // Routes - app.get("/", [](Request &, Response &res) - { res.send("public route"); }); + // Routes + app.get("/", [](Request &, Response &res) + { res.send("public route"); }); - app.get("/api/hello", [](Request &req, Response &res) - { res.json({"ok", true, - "message", "Hello from /api/hello", - "x_forwarded_for", req.header("x-forwarded-for"), - "x_real_ip", req.header("x-real-ip")}); }); + app.get("/api/hello", [](Request &req, Response &res) + { res.json({"ok", true, + "message", "Hello from /api/hello", + "x_forwarded_for", req.header("x-forwarded-for"), + "x_real_ip", req.header("x-real-ip")}); }); - app.run(8080); + app.run(8080); } diff --git a/examples/main.cpp b/examples/main.cpp index d34b497..4553101 100644 --- a/examples/main.cpp +++ b/examples/main.cpp @@ -1,30 +1,36 @@ -// ============================================================================ -// main.cpp β€” Quick Start Example (Vix.cpp) -// --------------------------------------------------------------------------- -// A minimal HTTP server built with Vix.cpp. -// Run β†’ ./main -// Then visit β†’ http://localhost:8080/hello -// ============================================================================ +/** + * + * @file main.cpp + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ #include using namespace vix; int main() { - App app; + App app; - app.get("/", [](auto &, auto &res) - { - res.send("ok"); // light - }); + app.get("/", [](Request &, Response &res) + { + res.send("ok"); // light + }); - app.get_heavy("/users", [](auto &, auto &res) - { + app.get_heavy("/users", [](Request &, Response &res) + { // DB query (heavy) -> executor res.send("users"); }); - app.get("/users/{id}", [](Request &req, Response &res) - { + app.get("/users/{id}", [](Request &req, Response &res) + { auto id = req.param("id"); auto page = req.query_value("page", "1"); @@ -34,8 +40,8 @@ int main() "page", page }); }); - app.post("/echo/{id}", [](Request &req, Response &res) - { + app.post("/echo/{id}", [](Request &req, Response &res) + { const auto &body = req.json(); res.json({ @@ -45,5 +51,5 @@ int main() "params", req.params() }); }); - app.run(8080); + app.run(8080); } diff --git a/examples/middleware_http/http_cache_example.cpp b/examples/middleware_http/http_cache_example.cpp index 8804c19..f2cee1c 100644 --- a/examples/middleware_http/http_cache_example.cpp +++ b/examples/middleware_http/http_cache_example.cpp @@ -1,3 +1,16 @@ +/** + * + * @file examples/middleware_http/http_cache_example.cpp + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ #include #include @@ -5,23 +18,25 @@ using namespace vix; int main() { - App app; + App app; - app.use("/api", middleware::app::http_cache({.ttl_ms = 30'000, - .allow_bypass = true, - .bypass_header = "x-vix-cache", - .bypass_value = "bypass"})); + app.use("/api", + middleware::app::http_cache( + {.ttl_ms = 30'000, + .allow_bypass = true, + .bypass_header = "x-vix-cache", + .bypass_value = "bypass"})); - app.get("/api/users", [](Request &req, Response &res) - { res.json({"ok", true, - "page", req.query_value("page", "1"), - "source", "origin"}); }); + app.get("/api/users", [](Request &req, Response &res) + { res.json({"ok", true, + "page", req.query_value("page", "1"), + "source", "origin"}); }); - app.get("/", [](Request, Response res) - { res.send("Welcome !"); }); + app.get("/", [](Request, Response res) + { res.send("Welcome !"); }); - app.get("/hello", [](Request, Response res) - { res.status(200).send("Hello, World"); }); + app.get("/hello", [](Request, Response res) + { res.status(200).send("Hello, World"); }); - app.run(8080); + app.run(8080); } diff --git a/examples/rate_limit/rate_limit_pipeline_demo.cpp b/examples/rate_limit/rate_limit_pipeline_demo.cpp index 6d75e6e..26b84b7 100644 --- a/examples/rate_limit/rate_limit_pipeline_demo.cpp +++ b/examples/rate_limit/rate_limit_pipeline_demo.cpp @@ -1,5 +1,16 @@ -// ============================================================================ -// rate_limit_pipeline_demo.cpp β€” Rate limit pipeline demo (Vix.cpp) +/** + * + * @file rate_limit_pipeline_demo.cpp β€” Rate limit pipeline demo (Vix.cpp) + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ // ---------------------------------------------------------------------------- // Run: // vix run rate_limit_pipeline_demo.cpp @@ -17,68 +28,68 @@ using namespace vix::middleware; static vix::vhttp::RawRequest make_req() { - namespace http = boost::beast::http; - vix::vhttp::RawRequest req{http::verb::get, "/api/x", 11}; - req.set(http::field::host, "localhost"); - req.set("x-forwarded-for", "1.2.3.4"); - req.prepare_payload(); - return req; + namespace http = boost::beast::http; + vix::vhttp::RawRequest req{http::verb::get, "/api/x", 11}; + req.set(http::field::host, "localhost"); + req.set("x-forwarded-for", "1.2.3.4"); + req.prepare_payload(); + return req; } int main() { - namespace http = boost::beast::http; + namespace http = boost::beast::http; - vix::middleware::security::RateLimitOptions opt{}; - opt.capacity = 2.0; - opt.refill_per_sec = 0.0; - opt.add_headers = true; + vix::middleware::security::RateLimitOptions opt{}; + opt.capacity = 2.0; + opt.refill_per_sec = 0.0; + opt.add_headers = true; - HttpPipeline p; + HttpPipeline p; - auto shared = std::make_shared(); - p.services().provide(shared); + auto shared = std::make_shared(); + p.services().provide(shared); - p.use(vix::middleware::security::rate_limit(opt)); + p.use(vix::middleware::security::rate_limit(opt)); - auto run_once = [&](http::response &res) - { - auto raw = make_req(); - vix::vhttp::Request req(raw, {}); - vix::vhttp::ResponseWrapper w(res); + auto run_once = [&](http::response &res) + { + auto raw = make_req(); + vix::vhttp::Request req(raw, {}); + vix::vhttp::ResponseWrapper w(res); - p.run(req, w, [&](Request &, Response &) - { w.ok().text("OK"); }); - }; + p.run(req, w, [&](Request &, Response &) + { w.ok().text("OK"); }); + }; - // 1) OK - { - http::response res; - run_once(res); - assert(res.result_int() == 200); - assert(res.body() == "OK"); - assert(!res["X-RateLimit-Limit"].empty()); - assert(!res["X-RateLimit-Remaining"].empty()); - } + // 1) OK + { + http::response res; + run_once(res); + assert(res.result_int() == 200); + assert(res.body() == "OK"); + assert(!res["X-RateLimit-Limit"].empty()); + assert(!res["X-RateLimit-Remaining"].empty()); + } - // 2) OK - { - http::response res; - run_once(res); - assert(res.result_int() == 200); - assert(res.body() == "OK"); - } + // 2) OK + { + http::response res; + run_once(res); + assert(res.result_int() == 200); + assert(res.body() == "OK"); + } - // 3) BLOCKED - { - http::response res; - run_once(res); - assert(res.result_int() == 429); - assert(res.body().find("rate_limited") != std::string::npos); - assert(!res["Retry-After"].empty()); - assert(res["X-RateLimit-Remaining"] == "0"); - } + // 3) BLOCKED + { + http::response res; + run_once(res); + assert(res.result_int() == 429); + assert(res.body().find("rate_limited") != std::string::npos); + assert(!res["Retry-After"].empty()); + assert(res["X-RateLimit-Remaining"] == "0"); + } - std::cout << "[OK] rate_limit pipeline demo\n"; - return 0; + std::cout << "[OK] rate_limit pipeline demo\n"; + return 0; } diff --git a/examples/rate_limit/rate_limit_server.cpp b/examples/rate_limit/rate_limit_server.cpp index e2e4f30..3d0f03a 100644 --- a/examples/rate_limit/rate_limit_server.cpp +++ b/examples/rate_limit/rate_limit_server.cpp @@ -1,5 +1,16 @@ -// ============================================================================ -// rate_limit_server.cpp β€” Rate limit server demo (Vix.cpp) +/** + * + * @file rate_limit_server.cpp β€” Rate limit server demo (Vix.cpp) + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ // ---------------------------------------------------------------------------- // Run: // vix run rate_limit_server.cpp @@ -15,16 +26,16 @@ using namespace vix; int main() { - App app; + App app; - // burst=5, refill=0 => easy to trigger - app.use("/api", middleware::app::rate_limit_custom_dev(5.0, 0.0)); + // burst=5, refill=0 => easy to trigger + app.use("/api", middleware::app::rate_limit_custom_dev(5.0, 0.0)); - app.get("/", [](Request &, Response &res) - { res.send("public route"); }); + app.get("/", [](Request &, Response &res) + { res.send("public route"); }); - app.get("/api/ping", [](Request &req, Response &res) - { res.json({"ok", true, "msg", "pong", "xff", req.header("x-forwarded-for")}); }); + app.get("/api/ping", [](Request &req, Response &res) + { res.json({"ok", true, "msg", "pong", "xff", req.header("x-forwarded-for")}); }); - app.run(8080); + app.run(8080); } diff --git a/examples/rate_limit/security_cors_ip_rate_server.cpp b/examples/rate_limit/security_cors_ip_rate_server.cpp index c47904f..924144c 100644 --- a/examples/rate_limit/security_cors_ip_rate_server.cpp +++ b/examples/rate_limit/security_cors_ip_rate_server.cpp @@ -1,83 +1,18 @@ -// ============================================================================ -// security_cors_ip_rate_server.cpp β€” CORS + IP Filter + Rate Limit (Vix.cpp) -// ---------------------------------------------------------------------------- -// FIX: -// - Add explicit OPTIONS routes so middleware can attach CORS headers. -// - Use X-Vix-Ip consistently for browser demo. -// - Rate limit key uses x-vix-ip. -// ---------------------------------------------------------------------------- -// Run: -// vix run security_cors_ip_rate_server.cpp -// ============================================================================ -#include -#include - -using namespace vix; - -static void register_options_routes(App &app) -{ - // Without explicit OPTIONS routes, Vix core may auto-return 204 - // BEFORE middleware runs => preflight has no CORS headers. - - app.options("/api/ping", [](Request &, Response &res) - { - res.header("X-OPTIONS-HIT", "ping"); - res.status(204).send(); }); - - app.options("/api/echo", [](Request &, Response &res) - { - res.header("X-OPTIONS-HIT", "echo"); - res.status(204).send(); }); -} - -int main() -{ - App app; - - // --------------------------------------------------------------------- - // Apply all on /api prefix (ORDER MATTERS) - // --------------------------------------------------------------------- - app.use("/api", middleware::app::cors_ip_demo()); - app.use("/api", middleware::app::ip_filter_dev("x-vix-ip", {"1.2.3.4"})); - app.use("/api", middleware::app::rate_limit_custom_dev( - 5.0, // capacity (burst) - 0.0, // refill_per_sec (demo: easy to trigger) - "x-vix-ip" // key header (must match IP filter header) - )); - - // Public - app.get("/", [](Request &, Response &res) - { res.send("public route"); }); - - // Critical for browser preflight headers - register_options_routes(app); - - app.get("/api/ping", [](Request &req, Response &res) - { - res.header("X-Request-Id", "req_ping_1"); - res.json({ - "ok", true, - "msg", "pong", - "ip", req.header("x-vix-ip") - }); }); - - app.post("/api/echo", [](Request &req, Response &res) - { - res.header("X-Request-Id", "req_echo_1"); - res.json({ - "ok", true, - "msg", "echo", - "content_type", req.header("content-type") - }); }); - - app.run(8080); -} - -/* +/** + * + * @file security_cors_ip_rate_server.cpp β€” CORS + IP Filter + Rate Limit (Vix.cpp) + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * =============================================================================== CURL TESTS =============================================================================== - # 0) Run server vix run security_cors_ip_rate_server.cpp @@ -110,3 +45,65 @@ for i in $(seq 1 6); do -H "X-Forwarded-For: 9.9.9.9" done */ + +#include +#include + +using namespace vix; + +static void register_options_routes(App &app) +{ + // Without explicit OPTIONS routes, Vix core may auto-return 204 + // BEFORE middleware runs => preflight has no CORS headers. + + app.options("/api/ping", [](Request &, Response &res) + { + res.header("X-OPTIONS-HIT", "ping"); + res.status(204).send(); }); + + app.options("/api/echo", [](Request &, Response &res) + { + res.header("X-OPTIONS-HIT", "echo"); + res.status(204).send(); }); +} + +int main() +{ + App app; + + // Apply all on /api prefix (ORDER MATTERS) + app.use("/api", middleware::app::cors_ip_demo()); + app.use("/api", middleware::app::ip_filter_dev("x-vix-ip", {"1.2.3.4"})); + app.use("/api", middleware::app::rate_limit_custom_dev( + 5.0, // capacity (burst) + 0.0, // refill_per_sec (demo: easy to trigger) + "x-vix-ip" // key header (must match IP filter header) + )); + + // Public + app.get("/", [](Request &, Response &res) + { res.send("public route"); }); + + // Critical for browser preflight headers + register_options_routes(app); + + app.get("/api/ping", [](Request &req, Response &res) + { + res.header("X-Request-Id", "req_ping_1"); + res.json({ + "ok", true, + "msg", "pong", + "ip", req.header("x-vix-ip") + }); }); + + app.post("/api/echo", [](Request &req, Response &res) + { + res.header("X-Request-Id", "req_echo_1"); + res.json({ + "ok", true, + "msg", "echo", + "content_type", req.header("content-type") + }); }); + + app.run(8080); +} diff --git a/examples/static_files/static_files_app_simple.cpp b/examples/static_files/static_files_app_simple.cpp index 0dff971..8fbc6dd 100644 --- a/examples/static_files/static_files_app_simple.cpp +++ b/examples/static_files/static_files_app_simple.cpp @@ -1,3 +1,16 @@ +/** + * + * @file static_files_app_simple.cpp + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ #include #include @@ -11,41 +24,38 @@ using namespace vix; static std::filesystem::path source_dir() { - // __FILE__ = /home/softadastra/dev/tmp/static_files_app_simple.cpp - return std::filesystem::path(__FILE__).parent_path(); + // __FILE__ = /home/softadastra/dev/tmp/static_files_app_simple.cpp + return std::filesystem::path(__FILE__).parent_path(); } int main() { - // Dossier public Γ  cΓ΄tΓ© du .cpp (stable mΓͺme si vix run change le cwd) - std::filesystem::path root = source_dir() / "public"; - std::filesystem::create_directories(root); - - // Fichiers de test - { - std::ofstream(root / "index.html") << "

OK

"; - } - { - std::ofstream(root / "hello.txt") << "hello"; - } - - App app; - - // Adapter Pipeline middleware -> App middleware - app.use(vix::middleware::app::adapt_ctx( - vix::middleware::performance::static_files( - root, - { - .mount = "/", - .index_file = "index.html", - .add_cache_control = true, - .cache_control = "public, max-age=3600", - .fallthrough = true, - }))); - - app.get("/api/ping", [](Request &, Response &res) - { res.json({"ok", true}); }); - - std::cout << "Static root: " << root << "\n"; - app.run(8080); + std::filesystem::path root = source_dir() / "public"; + std::filesystem::create_directories(root); + + { + std::ofstream(root / "index.html") << "

OK

"; + } + { + std::ofstream(root / "hello.txt") << "hello"; + } + + App app; + + app.use(vix::middleware::app::adapt_ctx( + vix::middleware::performance::static_files( + root, + { + .mount = "/", + .index_file = "index.html", + .add_cache_control = true, + .cache_control = "public, max-age=3600", + .fallthrough = true, + }))); + + app.get("/api/ping", [](Request &, Response &res) + { res.json({"ok", true}); }); + + std::cout << "Static root: " << root << "\n"; + app.run(8080); } diff --git a/examples/websocket/advanced/src/client.cpp b/examples/websocket/advanced/src/client.cpp index 7fdbc3d..14a3e00 100644 --- a/examples/websocket/advanced/src/client.cpp +++ b/examples/websocket/advanced/src/client.cpp @@ -1,6 +1,14 @@ /** * @file client.cpp * @brief Advanced interactive WebSocket client example for Vix.cpp + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp * * This example demonstrates how to build a fully interactive, terminal-based * WebSocket client using the Vix.cpp WebSocket module. It is designed to show @@ -89,8 +97,8 @@ using Clock = std::chrono::steady_clock; // Petit helper local : starts_with bool starts_with(const std::string &s, const std::string &prefix) { - return s.size() >= prefix.size() && - s.compare(0, prefix.size(), prefix) == 0; + return s.size() >= prefix.size() && + s.compare(0, prefix.size(), prefix) == 0; } /** @@ -102,16 +110,16 @@ ClientPtr create_chat_client(std::string host, std::string user, std::string room) { - using ws::JsonMessage; + using ws::JsonMessage; - auto client = ws::Client::create(std::move(host), - std::move(port), - std::move(path)); + auto client = ws::Client::create(std::move(host), + std::move(port), + std::move(path)); - // ───────────── Handlers ───────────── + // ───────────── Handlers ───────────── - client->on_open([client, user, room] - { + client->on_open([client, user, room] + { std::cout << "[client] Connected βœ…" << std::endl; client->send( @@ -121,8 +129,8 @@ ClientPtr create_chat_client(std::string host, "user", user, }); }); - client->on_message([](const std::string &msg) - { + client->on_message([](const std::string &msg) + { auto jm = JsonMessage::parse(msg); if (!jm) @@ -136,7 +144,7 @@ ClientPtr create_chat_client(std::string host, if (type == "chat.system") { std::string text = jm->get_string("text"); - std::string roomName = jm->get_string("room"); + std::string roomName = jm->get_string("room"); if (!roomName.empty()) { @@ -170,17 +178,17 @@ ClientPtr create_chat_client(std::string host, std::cout << msg << std::endl; } }); - client->on_close([]() - { std::cout << "[client] Disconnected." << std::endl; }); + client->on_close([]() + { std::cout << "[client] Disconnected." << std::endl; }); - client->on_error([](const boost::system::error_code &ec) - { std::cerr << "[client] error: " << ec.message() << std::endl; }); + client->on_error([](const boost::system::error_code &ec) + { std::cerr << "[client] error: " << ec.message() << std::endl; }); - // Auto-reconnect + heartbeat - client->enable_auto_reconnect(true, std::chrono::seconds(3)); - client->enable_heartbeat(std::chrono::seconds(20)); + // Auto-reconnect + heartbeat + client->enable_auto_reconnect(true, std::chrono::seconds(3)); + client->enable_heartbeat(std::chrono::seconds(20)); - return client; + return client; } /** @@ -188,103 +196,103 @@ ClientPtr create_chat_client(std::string host, */ void run_chat_cli(ClientPtr client, std::string user, std::string room) { - std::cout << "Type messages, /join , /leave, /quit\n"; + std::cout << "Type messages, /join , /leave, /quit\n"; + + for (std::string line; std::getline(std::cin, line);) + { + if (line == "/quit") + break; + + // /join + if (starts_with(line, "/join ")) + { + std::string newRoom = line.substr(6); + if (newRoom.empty()) + { + std::cout << "[client] Usage: /join \n"; + continue; + } + + // Leave ancienne room + client->send( + "chat.leave", + { + "room", + room, + "user", + user, + }); + + room = newRoom; + + // Join nouvelle room + client->send( + "chat.join", + { + "room", + room, + "user", + user, + }); + + std::cout << "[client] Switched to room: " << room << "\n"; + continue; + } + + // /leave (reste connectΓ©, mais ne participe plus Γ  la room) + if (line == "/leave") + { + client->send( + "chat.leave", + { + "room", + room, + "user", + user, + }); + + std::cout << "[client] Left room: " << room << "\n"; + continue; + } - for (std::string line; std::getline(std::cin, line);) + // Message normal β†’ chat.message dans la room courante + if (!line.empty()) { - if (line == "/quit") - break; - - // /join - if (starts_with(line, "/join ")) - { - std::string newRoom = line.substr(6); - if (newRoom.empty()) - { - std::cout << "[client] Usage: /join \n"; - continue; - } - - // Leave ancienne room - client->send( - "chat.leave", - { - "room", - room, - "user", - user, - }); - - room = newRoom; - - // Join nouvelle room - client->send( - "chat.join", - { - "room", - room, - "user", - user, - }); - - std::cout << "[client] Switched to room: " << room << "\n"; - continue; - } - - // /leave (reste connectΓ©, mais ne participe plus Γ  la room) - if (line == "/leave") - { - client->send( - "chat.leave", - { - "room", - room, - "user", - user, - }); - - std::cout << "[client] Left room: " << room << "\n"; - continue; - } - - // Message normal β†’ chat.message dans la room courante - if (!line.empty()) - { - client->send( - "chat.message", - { - "room", - room, - "user", - user, - "text", - line, - }); - } + client->send( + "chat.message", + { + "room", + room, + "user", + user, + "text", + line, + }); } + } } int main() { - // ───────────── Prompt user + room ───────────── - std::cout << "Pseudo: "; - std::string user; - std::getline(std::cin, user); - if (user.empty()) - user = "anonymous"; + // ───────────── Prompt user + room ───────────── + std::cout << "Pseudo: "; + std::string user; + std::getline(std::cin, user); + if (user.empty()) + user = "anonymous"; - std::cout << "Room (ex: general): "; - std::string room; - std::getline(std::cin, room); - if (room.empty()) - room = "general"; + std::cout << "Room (ex: general): "; + std::string room; + std::getline(std::cin, room); + if (room.empty()) + room = "general"; - auto client = create_chat_client("localhost", "9090", "/", user, room); + auto client = create_chat_client("localhost", "9090", "/", user, room); - client->connect(); + client->connect(); - run_chat_cli(client, user, room); + run_chat_cli(client, user, room); - client->close(); - return 0; + client->close(); + return 0; } diff --git a/examples/websocket/advanced/src/server.cpp b/examples/websocket/advanced/src/server.cpp index de2b1ac..e061f0c 100644 --- a/examples/websocket/advanced/src/server.cpp +++ b/examples/websocket/advanced/src/server.cpp @@ -1,6 +1,14 @@ /** * @file server.cpp * @brief Advanced WebSocket server example for Vix.cpp + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp * * This example demonstrates a fully–featured, production-style WebSocket * server using the Vix.cpp runtime. It showcases how to combine: @@ -99,383 +107,383 @@ int main() { - using vix::websocket::App; - using vix::websocket::JsonMessage; - using vix::websocket::LongPollingBridge; - using vix::websocket::LongPollingManager; - using vix::websocket::Session; - using vix::websocket::WebSocketMetrics; - using vix::websocket::detail::ws_kvs_to_nlohmann; - - namespace http = boost::beast::http; - using njson = nlohmann::json; + using vix::websocket::App; + using vix::websocket::JsonMessage; + using vix::websocket::LongPollingBridge; + using vix::websocket::LongPollingManager; + using vix::websocket::Session; + using vix::websocket::WebSocketMetrics; + using vix::websocket::detail::ws_kvs_to_nlohmann; + + namespace http = boost::beast::http; + using njson = nlohmann::json; + + App wsApp{"config/config.json"}; + auto &ws = wsApp.server(); + + WebSocketMetrics metrics; + + std::thread metricsThread([&metrics]() + { vix::websocket::run_metrics_http_exporter( + metrics, + "0.0.0.0", + 9100); }); + metricsThread.detach(); + + vix::websocket::SqliteMessageStore store{"chat_messages.db"}; + constexpr std::size_t HISTORY_LIMIT = 50; + + auto resolver = [](const JsonMessage &msg) + { + if (!msg.room.empty()) + return std::string{"room:"} + msg.room; + return std::string{"broadcast"}; + }; + + auto httpToWs = [&ws](const JsonMessage &msg) + { + if (!msg.room.empty()) + ws.broadcast_room_json(msg.room, msg.type, msg.payload); + else + ws.broadcast_json(msg.type, msg.payload); + }; + + auto lpBridge = std::make_shared( + &metrics, + std::chrono::seconds{60}, // TTL + 256, // max buffer / session + resolver, + httpToWs); + + ws.attach_long_polling_bridge(lpBridge); + + ws.on_open( + [&store, &metrics](Session &session) + { + (void)session; + + metrics.connections_total.fetch_add(1, std::memory_order_relaxed); + metrics.connections_active.fetch_add(1, std::memory_order_relaxed); + + vix::json::kvs payload{ + "user", + "server", + "text", + "Welcome to Softadastra Chat πŸ‘‹", + }; + + JsonMessage msg; + msg.kind = "system"; + msg.type = "chat.system"; + msg.room = ""; + msg.payload = payload; + + store.append(msg); + session.send_text(JsonMessage::serialize(msg)); + }); + + (void)wsApp.ws( + "/chat", + [&ws, &store, &metrics](Session &session, + const std::string &type, + const vix::json::kvs &payload) + { + (void)session; + + metrics.messages_in_total.fetch_add(1, std::memory_order_relaxed); + + njson j = ws_kvs_to_nlohmann(payload); + + // 1) JOIN + if (type == "chat.join") + { + std::string room = j.value("room", ""); + std::string user = j.value("user", "anonymous"); - App wsApp{"config/config.json"}; - auto &ws = wsApp.server(); + if (!room.empty()) + { + ws.join_room(session, room); - WebSocketMetrics metrics; + auto history = store.list_by_room(room, HISTORY_LIMIT, std::nullopt); + for (auto msg : history) + { + if (msg.kind.empty()) + msg.kind = "history"; - std::thread metricsThread([&metrics]() - { vix::websocket::run_metrics_http_exporter( - metrics, - "0.0.0.0", - 9100); }); - metricsThread.detach(); + session.send_text(JsonMessage::serialize(msg)); + metrics.messages_out_total.fetch_add(1, std::memory_order_relaxed); + } - vix::websocket::SqliteMessageStore store{"chat_messages.db"}; - constexpr std::size_t HISTORY_LIMIT = 50; + vix::json::kvs sysPayload{ + "room", + room, + "text", + user + " joined the room", + }; - auto resolver = [](const JsonMessage &msg) - { - if (!msg.room.empty()) - return std::string{"room:"} + msg.room; - return std::string{"broadcast"}; - }; + JsonMessage sysMsg; + sysMsg.kind = "system"; + sysMsg.type = "chat.system"; + sysMsg.room = room; + sysMsg.payload = sysPayload; + + store.append(sysMsg); + ws.broadcast_room_json(room, sysMsg.type, sysMsg.payload); + metrics.messages_out_total.fetch_add(1, std::memory_order_relaxed); + } + return; + } - auto httpToWs = [&ws](const JsonMessage &msg) - { - if (!msg.room.empty()) - ws.broadcast_room_json(msg.room, msg.type, msg.payload); - else - ws.broadcast_json(msg.type, msg.payload); - }; - - auto lpBridge = std::make_shared( - &metrics, - std::chrono::seconds{60}, // TTL - 256, // max buffer / session - resolver, - httpToWs); - - ws.attach_long_polling_bridge(lpBridge); - - ws.on_open( - [&store, &metrics](Session &session) + // 2) LEAVE + if (type == "chat.leave") { - (void)session; + std::string room = j.value("room", ""); + std::string user = j.value("user", "anonymous"); - metrics.connections_total.fetch_add(1, std::memory_order_relaxed); - metrics.connections_active.fetch_add(1, std::memory_order_relaxed); + if (!room.empty()) + { + ws.leave_room(session, room); - vix::json::kvs payload{ - "user", - "server", + vix::json::kvs sysPayload{ + "room", + room, "text", - "Welcome to Softadastra Chat πŸ‘‹", + user + " left the room", }; JsonMessage msg; msg.kind = "system"; msg.type = "chat.system"; - msg.room = ""; - msg.payload = payload; + msg.room = room; + msg.payload = sysPayload; store.append(msg); - session.send_text(JsonMessage::serialize(msg)); - }); + ws.broadcast_room_json(room, msg.type, msg.payload); + metrics.messages_out_total.fetch_add(1, std::memory_order_relaxed); + } + return; + } - (void)wsApp.ws( - "/chat", - [&ws, &store, &metrics](Session &session, - const std::string &type, - const vix::json::kvs &payload) + // 3) MESSAGE + if (type == "chat.message") { - (void)session; - - metrics.messages_in_total.fetch_add(1, std::memory_order_relaxed); - - njson j = ws_kvs_to_nlohmann(payload); - - // 1) JOIN - if (type == "chat.join") - { - std::string room = j.value("room", ""); - std::string user = j.value("user", "anonymous"); - - if (!room.empty()) - { - ws.join_room(session, room); - - auto history = store.list_by_room(room, HISTORY_LIMIT, std::nullopt); - for (auto msg : history) - { - if (msg.kind.empty()) - msg.kind = "history"; - - session.send_text(JsonMessage::serialize(msg)); - metrics.messages_out_total.fetch_add(1, std::memory_order_relaxed); - } - - vix::json::kvs sysPayload{ - "room", - room, - "text", - user + " joined the room", - }; - - JsonMessage sysMsg; - sysMsg.kind = "system"; - sysMsg.type = "chat.system"; - sysMsg.room = room; - sysMsg.payload = sysPayload; - - store.append(sysMsg); - ws.broadcast_room_json(room, sysMsg.type, sysMsg.payload); - metrics.messages_out_total.fetch_add(1, std::memory_order_relaxed); - } - return; - } - - // 2) LEAVE - if (type == "chat.leave") - { - std::string room = j.value("room", ""); - std::string user = j.value("user", "anonymous"); - - if (!room.empty()) - { - ws.leave_room(session, room); - - vix::json::kvs sysPayload{ - "room", - room, - "text", - user + " left the room", - }; - - JsonMessage msg; - msg.kind = "system"; - msg.type = "chat.system"; - msg.room = room; - msg.payload = sysPayload; - - store.append(msg); - ws.broadcast_room_json(room, msg.type, msg.payload); - metrics.messages_out_total.fetch_add(1, std::memory_order_relaxed); - } - return; - } - - // 3) MESSAGE - if (type == "chat.message") - { - std::string room = j.value("room", ""); - std::string user = j.value("user", "anonymous"); - std::string text = j.value("text", ""); - - if (!room.empty() && !text.empty()) - { - vix::json::kvs msgPayload{ - "room", - room, - "user", - user, - "text", - text, - }; - - JsonMessage msg; - msg.kind = "event"; - msg.type = "chat.message"; - msg.room = room; - msg.payload = msgPayload; - - store.append(msg); - ws.broadcast_room_json(room, msg.type, msg.payload); - metrics.messages_out_total.fetch_add(1, std::memory_order_relaxed); - return; - } - } + std::string room = j.value("room", ""); + std::string user = j.value("user", "anonymous"); + std::string text = j.value("text", ""); + + if (!room.empty() && !text.empty()) + { + vix::json::kvs msgPayload{ + "room", + room, + "user", + user, + "text", + text, + }; - // 4) Fallback global - { - JsonMessage msg; - msg.kind = "event"; - msg.type = type; - msg.room = ""; - msg.payload = payload; - - store.append(msg); - ws.broadcast_json(type, payload); - metrics.messages_out_total.fetch_add(1, std::memory_order_relaxed); - } - }); + JsonMessage msg; + msg.kind = "event"; + msg.type = "chat.message"; + msg.room = room; + msg.payload = msgPayload; - // ───────────────────────────────────────────── - // 7) HTTP App : /ws/poll + /ws/send (LongPolling) - // ───────────────────────────────────────────── - vix::App httpApp; + store.append(msg); + ws.broadcast_room_json(room, msg.type, msg.payload); + metrics.messages_out_total.fetch_add(1, std::memory_order_relaxed); + return; + } + } - // Helper local pour lire ?name=value dans la target Beast - auto get_query_param = [](const http::request &req, - std::string_view key) -> std::optional + // 4) Fallback global + { + JsonMessage msg; + msg.kind = "event"; + msg.type = type; + msg.room = ""; + msg.payload = payload; + + store.append(msg); + ws.broadcast_json(type, payload); + metrics.messages_out_total.fetch_add(1, std::memory_order_relaxed); + } + }); + + // ───────────────────────────────────────────── + // 7) HTTP App : /ws/poll + /ws/send (LongPolling) + // ───────────────────────────────────────────── + vix::App httpApp; + + // Helper local pour lire ?name=value dans la target Beast + auto get_query_param = [](const http::request &req, + std::string_view key) -> std::optional + { + std::string target = std::string(req.target()); + auto pos = target.find('?'); + if (pos == std::string::npos) + return std::nullopt; + + std::string query = target.substr(pos + 1); + std::size_t start = 0; + + while (start < query.size()) { - std::string target = std::string(req.target()); - auto pos = target.find('?'); - if (pos == std::string::npos) - return std::nullopt; - - std::string query = target.substr(pos + 1); - std::size_t start = 0; - - while (start < query.size()) + auto amp = query.find('&', start); + auto part = query.substr( + start, + (amp == std::string::npos) ? std::string::npos : (amp - start)); + + auto eq = part.find('='); + if (eq != std::string::npos) + { + std::string k = part.substr(0, eq); + std::string v = part.substr(eq + 1); + if (k == key) { - auto amp = query.find('&', start); - auto part = query.substr( - start, - (amp == std::string::npos) ? std::string::npos : (amp - start)); - - auto eq = part.find('='); - if (eq != std::string::npos) - { - std::string k = part.substr(0, eq); - std::string v = part.substr(eq + 1); - if (k == key) - { - return v; - } - } - - if (amp == std::string::npos) - break; - start = amp + 1; + return v; } - return std::nullopt; - }; - - // GET /ws/poll β†’ retourne un array JSON de JsonMessage - httpApp.get( - "/ws/poll", - [lpBridge, &get_query_param](const http::request &req, - vix::vhttp::ResponseWrapper &res) + } + + if (amp == std::string::npos) + break; + start = amp + 1; + } + return std::nullopt; + }; + + // GET /ws/poll β†’ retourne un array JSON de JsonMessage + httpApp.get( + "/ws/poll", + [lpBridge, &get_query_param](const http::request &req, + vix::vhttp::ResponseWrapper &res) + { + auto sessionIdOpt = get_query_param(req, "session_id"); + if (!sessionIdOpt || sessionIdOpt->empty()) { - auto sessionIdOpt = get_query_param(req, "session_id"); - if (!sessionIdOpt || sessionIdOpt->empty()) - { - res.status(http::status::bad_request).json({ - "error", - "missing_session_id", - }); - return; - } + res.status(http::status::bad_request).json({ + "error", + "missing_session_id", + }); + return; + } - std::string sessionId = *sessionIdOpt; + std::string sessionId = *sessionIdOpt; - std::size_t maxMessages = 50; - if (auto maxStrOpt = get_query_param(req, "max")) - { - try - { - maxMessages = static_cast(std::stoul(*maxStrOpt)); - } - catch (...) - { - // on garde 50 - } - } + std::size_t maxMessages = 50; + if (auto maxStrOpt = get_query_param(req, "max")) + { + try + { + maxMessages = static_cast(std::stoul(*maxStrOpt)); + } + catch (...) + { + // on garde 50 + } + } - auto messages = lpBridge->poll(sessionId, maxMessages, true); - auto body = vix::websocket::json_messages_to_nlohmann_array(messages); + auto messages = lpBridge->poll(sessionId, maxMessages, true); + auto body = vix::websocket::json_messages_to_nlohmann_array(messages); - res.status(http::status::ok).json(body); - }); + res.status(http::status::ok).json(body); + }); - // POST /ws/send β†’ HTTP -> LP (et via httpToWs β†’ WS + rooms) - httpApp.post( - "/ws/send", - [lpBridge](const http::request &req, - vix::vhttp::ResponseWrapper &res) + // POST /ws/send β†’ HTTP -> LP (et via httpToWs β†’ WS + rooms) + httpApp.post( + "/ws/send", + [lpBridge](const http::request &req, + vix::vhttp::ResponseWrapper &res) + { + njson j; + try { - njson j; - try - { - j = njson::parse(req.body()); - } - catch (...) - { - res.status(http::status::bad_request).json({ - "error", - "invalid_json_body", - }); - return; - } + j = njson::parse(req.body()); + } + catch (...) + { + res.status(http::status::bad_request).json({ + "error", + "invalid_json_body", + }); + return; + } - std::string sessionId = j.value("session_id", std::string{}); - std::string type = j.value("type", std::string{}); - std::string room = j.value("room", std::string{}); + std::string sessionId = j.value("session_id", std::string{}); + std::string type = j.value("type", std::string{}); + std::string room = j.value("room", std::string{}); - if (type.empty()) - { - res.status(http::status::bad_request).json({ - "error", - "missing_type", - }); - return; - } + if (type.empty()) + { + res.status(http::status::bad_request).json({ + "error", + "missing_type", + }); + return; + } - // Si pas de session_id fourni : - if (sessionId.empty()) - { - if (!room.empty()) - { - sessionId = std::string{"room:"} + room; - } - else - { - sessionId = "broadcast"; - } - } + // Si pas de session_id fourni : + if (sessionId.empty()) + { + if (!room.empty()) + { + sessionId = std::string{"room:"} + room; + } + else + { + sessionId = "broadcast"; + } + } - JsonMessage msg; - msg.type = type; - msg.room = room; + JsonMessage msg; + msg.type = type; + msg.room = room; - if (j.contains("payload")) - { - msg.payload = vix::websocket::detail::nlohmann_payload_to_kvs(j["payload"]); - } + if (j.contains("payload")) + { + msg.payload = vix::websocket::detail::nlohmann_payload_to_kvs(j["payload"]); + } - lpBridge->send_from_http(sessionId, msg); + lpBridge->send_from_http(sessionId, msg); - res.status(http::status::accepted).json({ - "status", - "queued", - "session_id", - sessionId, - }); + res.status(http::status::accepted).json({ + "status", + "queued", + "session_id", + sessionId, }); - - // (Optionnel) /health simple - httpApp.get( - "/health", - [](auto &, vix::vhttp::ResponseWrapper &res) - { - res.status(http::status::ok).json({ - "status", - "ok", - }); + }); + + // (Optionnel) /health simple + httpApp.get( + "/health", + [](auto &, vix::vhttp::ResponseWrapper &res) + { + res.status(http::status::ok).json({ + "status", + "ok", }); + }); - // ───────────────────────────────────────────── - // 8) Lancement WS + HTTP - // ───────────────────────────────────────────── + // ───────────────────────────────────────────── + // 8) Lancement WS + HTTP + // ───────────────────────────────────────────── - // Thread dΓ©diΓ© WebSocket - std::thread wsThread{[&wsApp]() - { wsApp.run_blocking(); }}; + // Thread dΓ©diΓ© WebSocket + std::thread wsThread{[&wsApp]() + { wsApp.run_blocking(); }}; - // Hook shutdown : quand HTTP reΓ§oit SIGINT/SIGTERM, il sort de run() - // et on coupe proprement le WS. - httpApp.set_shutdown_callback([&wsApp, &wsThread]() - { + // Hook shutdown : quand HTTP reΓ§oit SIGINT/SIGTERM, il sort de run() + // et on coupe proprement le WS. + httpApp.set_shutdown_callback([&wsApp, &wsThread]() + { wsApp.stop(); if (wsThread.joinable()) { wsThread.join(); } }); - // HTTP bloquant sur 8080 - httpApp.run(8080); + // HTTP bloquant sur 8080 + httpApp.run(8080); - return 0; + return 0; } diff --git a/examples/websocket/chat_room.cpp b/examples/websocket/chat_room.cpp index 0a51c93..c745dd2 100644 --- a/examples/websocket/chat_room.cpp +++ b/examples/websocket/chat_room.cpp @@ -1,6 +1,16 @@ -// -// examples/websocket/chat_room.cpp -// +/** + * + * @file chat_rom.cpp + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ // Chat-room style WebSocket server. // // This example demonstrates how to design a simple room-based protocol @@ -50,113 +60,113 @@ int main() { - using vix::websocket::Server; - - // ------------------------------------------------------------ - // 1) Load config - // ------------------------------------------------------------ - vix::config::Config cfg{"config/config.json"}; + using vix::websocket::Server; - // ------------------------------------------------------------ - // 2) Thread pool for async WebSocket processing - // ------------------------------------------------------------ - auto exec = vix::experimental::make_threadpool_executor( - 4, // min threads - 8, // max threads - 0 // default priority - ); + // ------------------------------------------------------------ + // 1) Load config + // ------------------------------------------------------------ + vix::config::Config cfg{"config/config.json"}; - // ------------------------------------------------------------ - // 3) Construct the WebSocket server - // ------------------------------------------------------------ - Server ws(cfg, std::move(exec)); - - // ------------------------------------------------------------ - // 4) On new connection - // ------------------------------------------------------------ - ws.on_open( - [&ws](auto &session) - { - (void)session; + // ------------------------------------------------------------ + // 2) Thread pool for async WebSocket processing + // ------------------------------------------------------------ + auto exec = vix::experimental::make_threadpool_executor( + 4, // min threads + 8, // max threads + 0 // default priority + ); - ws.broadcast_json( - "room.system", - { - "user", - "server", - "text", - "A new user connected to the chat room server πŸ‘‹", - }); - }); + // ------------------------------------------------------------ + // 3) Construct the WebSocket server + // ------------------------------------------------------------ + Server ws(cfg, std::move(exec)); - // ------------------------------------------------------------ - // 5) Typed messages for room protocol - // ------------------------------------------------------------ - ws.on_typed_message( - [&ws](auto &session, - const std::string &type, - const vix::json::kvs &payload) - { - (void)session; + // ------------------------------------------------------------ + // 4) On new connection + // ------------------------------------------------------------ + ws.on_open( + [&ws](auto &session) + { + (void)session; - // Small helper: extract a string value from kvs by key - auto get_string = [](const vix::json::kvs &kv, - const std::string &key) -> std::string + ws.broadcast_json( + "room.system", { - const auto &flat = kv.flat; + "user", + "server", + "text", + "A new user connected to the chat room server πŸ‘‹", + }); + }); - // kvs is stored as [key, value, key, value, ...] - for (std::size_t i = 0; i + 1 < flat.size(); i += 2) - { - // Try to read the key token as string - if (auto keyStr = std::get_if(&flat[i].v)) - { - if (*keyStr == key) - { - // Try to read the value token as string - if (auto valStr = std::get_if(&flat[i + 1].v)) - return *valStr; - } - } - } - return {}; - }; + // ------------------------------------------------------------ + // 5) Typed messages for room protocol + // ------------------------------------------------------------ + ws.on_typed_message( + [&ws](auto &session, + const std::string &type, + const vix::json::kvs &payload) + { + (void)session; - if (type == "room.join") - { - // payload: { "user": "Alice", "room": "general" } - const std::string user = get_string(payload, "user"); - const std::string room = get_string(payload, "room"); - const std::string text = user + " joined room " + room; + // Small helper: extract a string value from kvs by key + auto get_string = [](const vix::json::kvs &kv, + const std::string &key) -> std::string + { + const auto &flat = kv.flat; - ws.broadcast_json("room.system", - {"user", user, - "room", room, - "text", text}); - } - else if (type == "room.message") + // kvs is stored as [key, value, key, value, ...] + for (std::size_t i = 0; i + 1 < flat.size(); i += 2) + { + // Try to read the key token as string + if (auto keyStr = std::get_if(&flat[i].v)) { - // payload: { "user": "Alice", "room": "general", "text": "..." } - // Broadcast to all clients; each client can filter by "room". - ws.broadcast_json("room.message", payload); + if (*keyStr == key) + { + // Try to read the value token as string + if (auto valStr = std::get_if(&flat[i + 1].v)) + return *valStr; + } } - else if (type == "room.typing") - { - // payload: { "user": "Alice", "room": "general" } - ws.broadcast_json("room.typing", payload); - } - else - { - ws.broadcast_json("room.unknown", - {"type", type, - "info", "Unknown room message type"}); - } - }); + } + return {}; + }; + + if (type == "room.join") + { + // payload: { "user": "Alice", "room": "general" } + const std::string user = get_string(payload, "user"); + const std::string room = get_string(payload, "room"); + const std::string text = user + " joined room " + room; + + ws.broadcast_json("room.system", + {"user", user, + "room", room, + "text", text}); + } + else if (type == "room.message") + { + // payload: { "user": "Alice", "room": "general", "text": "..." } + // Broadcast to all clients; each client can filter by "room". + ws.broadcast_json("room.message", payload); + } + else if (type == "room.typing") + { + // payload: { "user": "Alice", "room": "general" } + ws.broadcast_json("room.typing", payload); + } + else + { + ws.broadcast_json("room.unknown", + {"type", type, + "info", "Unknown room message type"}); + } + }); - // ------------------------------------------------------------ - // 6) Run server (blocking) - // ------------------------------------------------------------ - ws.listen_blocking(); + // ------------------------------------------------------------ + // 6) Run server (blocking) + // ------------------------------------------------------------ + ws.listen_blocking(); - return 0; + return 0; } diff --git a/examples/websocket/simple/src/minimal_ws_server.cpp b/examples/websocket/simple/src/minimal_ws_server.cpp index ba41652..3a2b507 100644 --- a/examples/websocket/simple/src/minimal_ws_server.cpp +++ b/examples/websocket/simple/src/minimal_ws_server.cpp @@ -1,4 +1,17 @@ -#include // ws::App, ws::Server, ws::Session +/** + * + * @file minimal_ws_server.cpp + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ +#include #include #include @@ -9,14 +22,14 @@ namespace ws = vix::websocket; int main() { - ws::App app{"config/config.json"}; - auto &server = app.server(); + ws::App app{"config/config.json"}; + auto &server = app.server(); - std::cout << "[minimal] WebSocket server starting on port " - << server.port() << std::endl; + std::cout << "[minimal] WebSocket server starting on port " + << server.port() << std::endl; - server.on_open([](ws::Session &session) - { + server.on_open([](ws::Session &session) + { vix::json::kvs payload{ "message", std::string{"Welcome to minimal Vix WebSocket πŸ‘‹"}, }; @@ -28,22 +41,22 @@ int main() std::cout << "[minimal] New session opened, welcome sent" << std::endl; }); - (void)app.ws( - "/chat", - [&server](ws::Session &session, - const std::string &type, - const vix::json::kvs &payload) - { - (void)session; + (void)app.ws( + "/chat", + [&server](ws::Session &session, + const std::string &type, + const vix::json::kvs &payload) + { + (void)session; - if (type == "chat.message") - { - server.broadcast_json("chat.message", payload); - } - }); + if (type == "chat.message") + { + server.broadcast_json("chat.message", payload); + } + }); - // 4) Boucle bloquante - app.run_blocking(); + // 4) Boucle bloquante + app.run_blocking(); - return 0; -} \ No newline at end of file + return 0; +} diff --git a/examples/websocket/simple/src/simple_client.cpp b/examples/websocket/simple/src/simple_client.cpp index 7e03263..5a15394 100644 --- a/examples/websocket/simple/src/simple_client.cpp +++ b/examples/websocket/simple/src/simple_client.cpp @@ -1,6 +1,15 @@ /** * @file simple_client.cpp * @brief Minimal WebSocket client example for Vix.cpp + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * * * This example demonstrates the simplest possible interactive WebSocket * client built with the Vix.cpp WebSocket module. It connects to a server, @@ -61,16 +70,16 @@ int main() { - using vix::websocket::Client; - using vix::websocket::JsonMessage; + using vix::websocket::Client; + using vix::websocket::JsonMessage; - auto client = Client::create("localhost", "9090", "/"); + auto client = Client::create("localhost", "9090", "/"); - client->on_open([] - { std::cout << "[client] Connected βœ…" << std::endl; }); + client->on_open([] + { std::cout << "[client] Connected βœ…" << std::endl; }); - client->on_message([](const std::string &msg) - { + client->on_message([](const std::string &msg) + { auto jm = JsonMessage::parse(msg); if (!jm) @@ -99,42 +108,42 @@ int main() std::cout << msg << std::endl; } }); - client->on_close([] - { std::cout << "[client] Disconnected." << std::endl; }); + client->on_close([] + { std::cout << "[client] Disconnected." << std::endl; }); - client->on_error([](const boost::system::error_code &ec) - { std::cerr << "[client] error: " << ec.message() << std::endl; }); + client->on_error([](const boost::system::error_code &ec) + { std::cerr << "[client] error: " << ec.message() << std::endl; }); - client->enable_auto_reconnect(true, std::chrono::seconds(3)); - client->enable_heartbeat(std::chrono::seconds(20)); + client->enable_auto_reconnect(true, std::chrono::seconds(3)); + client->enable_heartbeat(std::chrono::seconds(20)); - client->connect(); + client->connect(); - // Prompt username - std::cout << "Pseudo: "; - std::string user; - std::getline(std::cin, user); - if (user.empty()) - user = "anonymous"; + // Prompt username + std::cout << "Pseudo: "; + std::string user; + std::getline(std::cin, user); + if (user.empty()) + user = "anonymous"; - std::cout << "Type messages, /quit to exit\n"; + std::cout << "Type messages, /quit to exit\n"; - // Message loop - for (std::string line; std::getline(std::cin, line);) - { - if (line == "/quit") - break; + // Message loop + for (std::string line; std::getline(std::cin, line);) + { + if (line == "/quit") + break; - client->send( - "chat.message", - { - "user", - user, - "text", - line, - }); - } - - client->close(); - return 0; + client->send( + "chat.message", + { + "user", + user, + "text", + line, + }); + } + + client->close(); + return 0; } diff --git a/examples/websocket/simple/src/simple_server.cpp b/examples/websocket/simple/src/simple_server.cpp index 9be7146..539841b 100644 --- a/examples/websocket/simple/src/simple_server.cpp +++ b/examples/websocket/simple/src/simple_server.cpp @@ -1,6 +1,14 @@ /** * @file simple_server.cpp * @brief Minimal WebSocket server example for Vix.cpp + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp * * This file provides a compact, beginner-friendly demonstration of how to * start a WebSocket server using the Vix.cpp runtime. It serves as the @@ -64,49 +72,49 @@ int main() { - using vix::websocket::Server; + using vix::websocket::Server; - // Load configuration from config/config.json - vix::config::Config cfg{"config/config.json"}; + // Load configuration from config/config.json + vix::config::Config cfg{"config/config.json"}; - // Thread pool for async work - auto exec = vix::experimental::make_threadpool_executor( - 4, // min threads - 8, // max threads - 0 // default priority - ); + // Thread pool for async work + auto exec = vix::experimental::make_threadpool_executor( + 4, // min threads + 8, // max threads + 0 // default priority + ); - Server ws(cfg, std::move(exec)); + Server ws(cfg, std::move(exec)); - // On new connection: broadcast a welcome system message - ws.on_open( - [&ws](auto &session) - { - (void)session; + // On new connection: broadcast a welcome system message + ws.on_open( + [&ws](auto &session) + { + (void)session; - ws.broadcast_json( - "chat.system", - { - "user", - "server", - "text", - "welcome to Softadastra Chat πŸ‘‹", - }); - }); + ws.broadcast_json( + "chat.system", + { + "user", + "server", + "text", + "welcome to Softadastra Chat πŸ‘‹", + }); + }); - // On typed message: echo chat messages to everyone - ws.on_typed_message( - [&ws](auto &session, - const std::string &type, - const vix::json::kvs &payload) - { - (void)session; + // On typed message: echo chat messages to everyone + ws.on_typed_message( + [&ws](auto &session, + const std::string &type, + const vix::json::kvs &payload) + { + (void)session; - if (type == "chat.message") - { - ws.broadcast_json("chat.message", payload); - } - }); + if (type == "chat.message") + { + ws.broadcast_json("chat.message", payload); + } + }); - ws.listen_blocking(); -} \ No newline at end of file + ws.listen_blocking(); +} diff --git a/examples/websocket/simple_client.cpp b/examples/websocket/simple_client.cpp index 96363ad..6123a6b 100644 --- a/examples/websocket/simple_client.cpp +++ b/examples/websocket/simple_client.cpp @@ -1,6 +1,16 @@ -// -// examples/websocket/simple_server.cpp -// +/** + * + * @file simple_client.cpp + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ // Minimal WebSocket server using Vix.cpp. // // This example demonstrates: @@ -21,77 +31,65 @@ int main() { - using vix::websocket::Server; + using vix::websocket::Server; - // ------------------------------------------------------------ - // 1) Load configuration - // ------------------------------------------------------------ - // - // The Config loader will try to find "config/config.json" - // relative to the project root or the current working directory. - // - vix::config::Config cfg{"config/config.json"}; + // 1) Load configuration + // + // The Config loader will try to find "config/config.json" + // relative to the project root or the current working directory. + // + vix::config::Config cfg{"config/config.json"}; - // ------------------------------------------------------------ - // 2) Create a thread pool executor for the WebSocket server - // ------------------------------------------------------------ - auto exec = vix::experimental::make_threadpool_executor( - 4, // min threads - 8, // max threads - 0 // default priority - ); + // 2) Create a thread pool executor for the WebSocket server + auto exec = vix::experimental::make_threadpool_executor( + 4, // min threads + 8, // max threads + 0 // default priority + ); - // ------------------------------------------------------------ - // 3) Construct the WebSocket server - // ------------------------------------------------------------ - Server ws(cfg, std::move(exec)); + // 3) Construct the WebSocket server + Server ws(cfg, std::move(exec)); - // ------------------------------------------------------------ - // 4) On new connection - // ------------------------------------------------------------ - ws.on_open( - [&ws](auto &session) - { - (void)session; + // 4) On new connection + ws.on_open( + [&ws](auto &session) + { + (void)session; - ws.broadcast_json( - "chat.system", - { - "user", - "server", - "text", - "Welcome to the simple WebSocket server πŸ‘‹", - }); - }); + ws.broadcast_json( + "chat.system", + { + "user", + "server", + "text", + "Welcome to the simple WebSocket server πŸ‘‹", + }); + }); - // ------------------------------------------------------------ - // 5) On typed message - // ------------------------------------------------------------ - ws.on_typed_message( - [&ws](auto &session, - const std::string &type, - const vix::json::kvs &payload) - { - (void)session; + // 5) On typed message + ws.on_typed_message( + [&ws](auto &session, + const std::string &type, + const vix::json::kvs &payload) + { + (void)session; - if (type == "chat.message") - { - // echo / broadcast chat messages - ws.broadcast_json("chat.message", payload); - } - else - { - // optional: broadcast unknown message types for debugging - ws.broadcast_json("chat.unknown", - {"type", type, - "info", "Unknown message type"}); - } - }); + if (type == "chat.message") + { + // echo / broadcast chat messages + ws.broadcast_json("chat.message", payload); + } + else + { + // optional: broadcast unknown message types for debugging + ws.broadcast_json("chat.unknown", + {"type", type, + "info", "Unknown message type"}); + } + }); - // ------------------------------------------------------------ - // 6) Start the WebSocket server (blocking) - // ------------------------------------------------------------ - ws.listen_blocking(); + // 6) Start the WebSocket server (blocking) + ws.listen_blocking(); - return 0; + return 0; } diff --git a/examples/websocket/simple_server.cpp b/examples/websocket/simple_server.cpp index 96363ad..4f24fa0 100644 --- a/examples/websocket/simple_server.cpp +++ b/examples/websocket/simple_server.cpp @@ -1,6 +1,16 @@ -// -// examples/websocket/simple_server.cpp -// +/** + * + * @file simple_server.cpp + * @author Gaspard Kirira + * + * Copyright 2025, Gaspard Kirira. All rights reserved. + * https://github.com/vixcpp/vix + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Vix.cpp + * + */ // Minimal WebSocket server using Vix.cpp. // // This example demonstrates: @@ -21,77 +31,65 @@ int main() { - using vix::websocket::Server; + using vix::websocket::Server; - // ------------------------------------------------------------ - // 1) Load configuration - // ------------------------------------------------------------ - // - // The Config loader will try to find "config/config.json" - // relative to the project root or the current working directory. - // - vix::config::Config cfg{"config/config.json"}; + // 1) Load configuration + // + // The Config loader will try to find "config/config.json" + // relative to the project root or the current working directory. + // + vix::config::Config cfg{"config/config.json"}; - // ------------------------------------------------------------ - // 2) Create a thread pool executor for the WebSocket server - // ------------------------------------------------------------ - auto exec = vix::experimental::make_threadpool_executor( - 4, // min threads - 8, // max threads - 0 // default priority - ); + // 2) Create a thread pool executor for the WebSocket server + auto exec = vix::experimental::make_threadpool_executor( + 4, // min threads + 8, // max threads + 0 // default priority + ); - // ------------------------------------------------------------ - // 3) Construct the WebSocket server - // ------------------------------------------------------------ - Server ws(cfg, std::move(exec)); + // 3) Construct the WebSocket server + Server ws(cfg, std::move(exec)); - // ------------------------------------------------------------ - // 4) On new connection - // ------------------------------------------------------------ - ws.on_open( - [&ws](auto &session) - { - (void)session; + // 4) On new connection + ws.on_open( + [&ws](auto &session) + { + (void)session; - ws.broadcast_json( - "chat.system", - { - "user", - "server", - "text", - "Welcome to the simple WebSocket server πŸ‘‹", - }); - }); + ws.broadcast_json( + "chat.system", + { + "user", + "server", + "text", + "Welcome to the simple WebSocket server πŸ‘‹", + }); + }); - // ------------------------------------------------------------ - // 5) On typed message - // ------------------------------------------------------------ - ws.on_typed_message( - [&ws](auto &session, - const std::string &type, - const vix::json::kvs &payload) - { - (void)session; + // 5) On typed message + ws.on_typed_message( + [&ws](auto &session, + const std::string &type, + const vix::json::kvs &payload) + { + (void)session; - if (type == "chat.message") - { - // echo / broadcast chat messages - ws.broadcast_json("chat.message", payload); - } - else - { - // optional: broadcast unknown message types for debugging - ws.broadcast_json("chat.unknown", - {"type", type, - "info", "Unknown message type"}); - } - }); + if (type == "chat.message") + { + // echo / broadcast chat messages + ws.broadcast_json("chat.message", payload); + } + else + { + // optional: broadcast unknown message types for debugging + ws.broadcast_json("chat.unknown", + {"type", type, + "info", "Unknown message type"}); + } + }); - // ------------------------------------------------------------ - // 6) Start the WebSocket server (blocking) - // ------------------------------------------------------------ - ws.listen_blocking(); + // 6) Start the WebSocket server (blocking) + ws.listen_blocking(); - return 0; + return 0; } diff --git a/modules/cache b/modules/cache index 8f71da9..4e602bb 160000 --- a/modules/cache +++ b/modules/cache @@ -1 +1 @@ -Subproject commit 8f71da92b4f8a014e5a8373495115ca163ec56e4 +Subproject commit 4e602bb26b76944f574ef90befc54902b9d427be diff --git a/modules/cli b/modules/cli index d7a5969..c7ab88c 160000 --- a/modules/cli +++ b/modules/cli @@ -1 +1 @@ -Subproject commit d7a5969e5f75bf436c87ee445d7f179710e36596 +Subproject commit c7ab88c99af8af1b237a00e52ab87f4f808280bc diff --git a/modules/core b/modules/core index de6dfe2..cb37164 160000 --- a/modules/core +++ b/modules/core @@ -1 +1 @@ -Subproject commit de6dfe206fa918d1370fde8028ad07e419e79142 +Subproject commit cb37164d311259f61e25c5edd16fadbfe826c8cb diff --git a/modules/json b/modules/json index 1515b15..e051202 160000 --- a/modules/json +++ b/modules/json @@ -1 +1 @@ -Subproject commit 1515b15b0d2e4f71365ae7442d1486e44252861e +Subproject commit e0512028d027e36309360ed6d0671290f1234767 diff --git a/modules/middleware b/modules/middleware index 5a05c51..62108f3 160000 --- a/modules/middleware +++ b/modules/middleware @@ -1 +1 @@ -Subproject commit 5a05c512cd63eaca30e63638f71a6582e4248433 +Subproject commit 62108f30b1550dc0e581633ea6d1a25552c952d7 diff --git a/modules/net b/modules/net index bff0221..ae15113 160000 --- a/modules/net +++ b/modules/net @@ -1 +1 @@ -Subproject commit bff0221ad7bdafa8ecf897e0bb0e562f8b6a57e2 +Subproject commit ae15113f8913d11e3aa22acb88ff74a9f1b53f33 diff --git a/modules/orm b/modules/orm index b7271e4..e63da9b 160000 --- a/modules/orm +++ b/modules/orm @@ -1 +1 @@ -Subproject commit b7271e49219a03d32b38fca646a9aa2bad47873b +Subproject commit e63da9b21138a87620dd80c0074b066717083c4b diff --git a/modules/p2p b/modules/p2p index e70c865..b37254a 160000 --- a/modules/p2p +++ b/modules/p2p @@ -1 +1 @@ -Subproject commit e70c86520eb3f366d0af6e74f5d69515a2e2b22f +Subproject commit b37254a7b8c0db39cd887496a9b00a736299ae9c diff --git a/modules/sync b/modules/sync index 87fd89e..602af19 160000 --- a/modules/sync +++ b/modules/sync @@ -1 +1 @@ -Subproject commit 87fd89e571c7093e823a8ff24dc06477aaf26ecd +Subproject commit 602af1979a08cde93484a8c67bb14ba467bfd959 diff --git a/modules/utils b/modules/utils index 0de8931..271ee72 160000 --- a/modules/utils +++ b/modules/utils @@ -1 +1 @@ -Subproject commit 0de8931cc06a47461135575f7b0c30d7281b9a89 +Subproject commit 271ee7235b9c0dca408a0396b44484b275938ace diff --git a/modules/websocket b/modules/websocket index e7d8df4..dbd9a3a 160000 --- a/modules/websocket +++ b/modules/websocket @@ -1 +1 @@ -Subproject commit e7d8df4dd202ca25a6b0cc37e664cc6230c9f6db +Subproject commit dbd9a3a888d4a92b1c639891b89282d79247016a