Endpoint catalog
An elaborate example that combines parsing, validation and traversal to emit a Markdown catalog of every endpoint in a spec — ready to paste into a README, wiki, or static site.
What it produces
For an OpenAPI file with 3 paths and 7 operations, the program prints:
markdown
# Orders API — Endpoint catalog
_Version 1.0.0 · generated by swaggercpp 0.2.1_
## `/orders`
### `GET /orders` — listOrders
Paginated list of orders.
**Parameters**
| Name | In | Required | Type | Description |
|--------|-------|----------|---------|-------------------|
| limit | query | no | integer | Page size (≤100). |
| cursor | query | no | string | Opaque cursor. |
**Responses**
| Status | Description |
|--------|--------------------|
| 200 | Page of orders. |
| 400 | Invalid cursor. |
### `POST /orders` — createOrder
Create a new order.
...Code
cpp
#include <filesystem>
#include <iostream>
#include <sstream>
#include "swaggercpp/swaggercpp.hpp"
namespace sc = swaggercpp;
struct CatalogVisitor : sc::DocumentVisitor {
std::ostringstream out;
std::string current_path;
void enter_document(const sc::Document& doc) override {
out << "# " << doc.info.title << " — Endpoint catalog\n\n"
<< "_Version " << doc.info.version
<< " · generated by swaggercpp_\n\n";
}
void enter_path_item(std::string_view path, const sc::PathItem&) override {
current_path = path;
out << "## `" << path << "`\n\n";
}
void enter_operation(std::string_view path,
sc::HttpMethod method,
const sc::Operation& op) override {
const auto name = op.operation_id.value_or("(anonymous)");
const auto upper = std::string(sc::to_string(method));
std::string METHOD; METHOD.reserve(upper.size());
for (char c : upper) METHOD.push_back(static_cast<char>(std::toupper(c)));
out << "### `" << METHOD << ' ' << path << "` — " << name << "\n\n";
if (op.summary) out << op.summary.value() << "\n\n";
if (op.description) out << op.description.value() << "\n\n";
if (!op.parameters.empty()) {
out << "**Parameters**\n\n"
<< "| Name | In | Required | Type | Description |\n"
<< "|------|----|----------|------|-------------|\n";
for (const auto& p : op.parameters) {
out << "| `" << p.name << "` | " << sc::to_string(p.in)
<< " | " << (p.required ? "yes" : "no")
<< " | " << (p.schema && p.schema->type ? *p.schema->type : "-")
<< " | " << p.description.value_or("")
<< " |\n";
}
out << '\n';
}
if (!op.responses.empty()) {
out << "**Responses**\n\n"
<< "| Status | Description |\n"
<< "|--------|-------------|\n";
for (const auto& [code, resp] : op.responses) {
out << "| " << code << " | " << resp.description << " |\n";
}
out << '\n';
}
}
};
int main(int argc, char** argv) {
if (argc < 2) {
std::cerr << "usage: catalog <openapi.yaml>\n";
return 2;
}
auto r = sc::DocumentReader::read_file(argv[1]);
if (!r) {
for (const auto& i : r.issues())
std::cerr << i.path << ": " << i.message << '\n';
return 1;
}
sc::DocumentValidator validator;
if (!validator.validate(r.value()).ok()) {
std::cerr << "Spec has validation errors; refusing to emit catalog.\n";
return 3;
}
CatalogVisitor v;
sc::DocumentWalker::walk(r.value(), v);
std::cout << v.out.str();
}How it uses swaggercpp
DocumentReader::read_file— parse (YAML or JSON).DocumentValidator::validate— refuse to emit a catalog for a broken spec.DocumentVisitor+DocumentWalker::walk— traverse deterministically.to_string(HttpMethod)/to_string(ParameterLocation)— stable, lowercase spec tokens.std::optional/value_or— graceful defaults for missing summaries and descriptions.
Ideas for extensions
- Emit HTML instead of Markdown.
- Group by tag (
Operation::tags) and sort within each group. - Inline component schemas when a parameter has
$ref. - Persist to disk with
DocumentWriter::write_filefor a companion JSON export.
See also
- DocumentWalker API
- DocumentValidator API
- Traverse a document — simpler traversal