Files
logos-libp2p-module/tutorial/tutorial_0_introduction.cpp
T

211 lines
7.1 KiB
C++

/// # Tutorial 0: Introduction and Common Patterns
///
/// Welcome to the first `logos-libp2p-module` tutorial!
///
/// Before creating nodes or connecting peers, it helps to understand the
/// conventions used by every tutorial in this series.
///
/// The C++ wrapper (`logos-libp2p-module`) exposes a single main class, `Libp2pModuleImpl`.
/// Most methods on that class return the Logos result type. In C++ module code that
/// type is named `StdLogosResult`.
///
/// `StdLogosResult` has three fields:
///
/// | Field | Meaning |
/// |-------|---------|
/// | `success` | `true` when the call completed successfully |
/// | `value` | a `nlohmann::json` value returned by the call |
/// | `error` | a diagnostic string when `success == false` |
///
/// ## Always Check `success` First
///
/// Every tutorial follows the same rule: check unsuccessful outcomes before
/// using returned data.
///
/// ```cpp
/// StdLogosResult info = node.peerInfo();
/// if (!info.success) {
/// fprintf(stderr, "Failed to get peer info: %s\n",
/// info.error.c_str());
/// return 1;
/// }
/// ```
///
/// This keeps the code predictable: errors are handled immediately, and the
/// rest of the step can assume the operation succeeded.
///
/// ## Tutorial Executables Fail Fast
///
/// These programs are examples, not long-running services. When a required
/// operation fails, they print a useful error message to `stderr` and return
/// `1`. Successful tutorials print progress to `stdout` and return `0`.
///
/// ## Controlling libp2p Logs
///
/// The wrapped libp2p binding can emit runtime logs. Tutorials set the log
/// level to `"fatal"` by default so `stdout` only shows the tutorial's
/// own progress messages during normal runs. Choose another level such as
/// `"info"`, `"debug"`, or `"trace"` when you need more
/// detail while debugging:
///
/// ```cpp
/// setLogLevel("debug");
/// // ... or
/// Libp2pModuleImpl::setLogLevel("debug");
/// ```
///
/// The level names are `"none"`, `"trace"`, `"debug"`, `"info"`, `"notice"`,
/// `"warn"`, `"error"` and `"fatal"`; any other name returns a failed result
/// and leaves the level as it was.
///
/// Log levels are inclusive minimum thresholds: `"trace"` emits trace
/// and above, `"debug"` emits debug and above, and so on.
/// `"none"` is the lowest threshold, so it emits all logs; it does not
/// disable logging. Use `"fatal"` for the quietest built-in threshold.
///
/// In your own application, `"error"` is often a useful default.
/// It keeps normal output quiet while still surfacing conditions
/// that may indicate libp2p is misbehaving or that your integration code needs an
/// adjustment. Some error logs describe remote-peer behavior, retries, or
/// recoverable internal state, so they may not require any action from your side.
///
/// ## Convert JSON Values Explicitly
///
/// The `value` field is JSON. Convert it to the C++ type you need at the
/// point where you use it:
///
/// ```cpp
/// std::string peerId = info.value["peerId"].get<std::string>();
///
/// for (const nlohmann::json& addr : info.value["addrs"]) {
/// printf(" %s\n", addr.get<std::string>().c_str());
/// }
/// ```
///
/// Scalar values use `get<T>()`. Objects are accessed by key. Arrays are
/// iterated with range-for loops.
///
/// Tutorial code assumes returned JSON values have the documented type, so it
/// uses and converts values directly instead of checking the JSON type first.
///
/// ## Binary Data May Be Encoded
///
/// Some APIs carry arbitrary bytes, such as stream reads, DHT values, public
/// keys, or service discovery records. Those values may be returned as strings
/// encoded for JSON transport. The tutorials decode them before comparing or
/// printing human-readable payloads.
///
/// -----------
#include <cstdio>
#include <string>
#include "plugin.h"
int main()
{
printf("=== Tutorial 0: Introduction and Common Patterns ===\n\n");
// Silence logs from wrapped library.
setLogLevel("fatal");
/// ## Step 1: Create and start a node
///
/// Even the first real operation returns a result. Check it before moving on.
Libp2pModuleImpl node;
StdLogosResult startRes = node.start();
if (!startRes.success) {
fprintf(stderr, "Failed to start node: %s\n",
startRes.error.c_str());
return 1;
}
printf("Node started\n");
/// ## Step 2: Use scalar JSON values
///
/// Some calls return a single string, number, or boolean in `value`.
StdLogosResult version = node.getNodeInfo("Version");
if (!version.success) {
fprintf(stderr, "Failed to get module version: %s\n",
version.error.c_str());
return 1;
}
printf("Module version: %s\n",
version.value.get<std::string>().c_str());
/// ## Step 3: Use JSON objects and arrays
///
/// `peerInfo()` returns a JSON object. The `peerId` field is a string, while
/// `addrs` is an array because a node may listen on multiple addresses.
StdLogosResult info = node.peerInfo();
if (!info.success) {
fprintf(stderr, "Failed to get peer info: %s\n",
info.error.c_str());
return 1;
}
std::string peerId = info.value["peerId"].get<std::string>();
printf("Peer ID: %s\n", peerId.c_str());
printf("Listening addresses:\n");
for (const nlohmann::json& addr : info.value["addrs"]) {
printf(" %s\n", addr.get<std::string>().c_str());
}
/// ## Step 4: Stop cleanly
///
/// Production code should always attempt to close resources gracefully first.
///
/// The tutorials do not always process every cleanup outcome, such as failures
/// while stopping a node or closing streams, in order to keep the example code
/// focused and easy to follow.
StdLogosResult stopRes = node.stop();
if (!stopRes.success) {
fprintf(stderr, "Failed to stop node: %s\n",
stopRes.error.c_str());
return 1;
}
printf("\n=== Tutorial 0 Complete ===\n");
return 0;
}
/// ## Summary
///
/// In this tutorial you learned the conventions used everywhere else:
/// - Most calls return `StdLogosResult`
/// - Check `success` before reading `value`
/// - Print `error` when a call fails
/// - Convert JSON values with `get<T>()`, object keys, or array iteration
/// - Return `1` from tutorial executables when required operations fail
/// ## Run tutorial
///
/// Run this tutorial now to check that your environment is set up and the
/// tutorial executable has already been built:
///
/// ```bash
/// ./build/tutorial/tutorial_0_introduction
/// ```
///
/// If that command fails because the tutorial has not been built yet, build the
/// tutorials with this command and then run it again:
///
/// ```bash
/// nix --extra-experimental-features 'nix-command flakes' develop --command ./tutorial/build_tutorials.sh
/// ```
/// ## Exercise: Enable libp2p debug logs
///
/// Change this tutorial's log level from `"fatal"` to `"debug"`:
///
/// ```cpp
/// setLogLevel("debug");
/// ```
///
/// Compile the tutorial binaries again, using the same command from above.
///
/// Run tutorial 0 again. You should now see libp2p debug logs in `stdout`
/// alongside the tutorial's own progress messages.