Arachne 1.0
Arachne - the perpetual stitcher of Wikidata entities.
Loading...
Searching...
No Matches
arachne::pheidippides Namespace Reference

Namespaces

namespace  anonymous_namespace{hardened_transport.cpp}
namespace  anonymous_namespace{transport.cpp}

Classes

class  hardened_transport
struct  http_header
struct  redirect_policy
struct  body_artifact_reference
struct  fetch_request_v1
struct  acquired_artifact_v1
class  transport

Enumerations

enum class  http_method { get , post }
enum class  transport_operation {
  bulk_snapshot , incremental_harvest , point_lookup , resume_download ,
  backend_read , external_write
}
enum class  freshness_policy { fresh_required , cache_allowed , stale_allowed , offline_only }
enum class  delivery_mode {
  fetched , cache_validated , stale , resumed ,
  offline
}
enum class  transport_status {
  delivered , invalid_request , unsafe_artifact_ref , disallowed_host ,
  redirect_rejected , redirect_limit_exceeded , timed_out , response_too_large ,
  network_error , storage_error , artifact_exists , door_policy_rejected ,
  cache_miss , checksum_mismatch , retry_budget_exhausted , admission_timeout
}

Functions

std::string_view to_string (transport_operation value) noexcept
std::string_view to_string (freshness_policy value) noexcept
std::string_view to_string (delivery_mode value) noexcept
fetch_request_v1 from_contract (const nlohmann::json &document)
nlohmann::ordered_json to_contract (const acquired_artifact_v1 &artifact)

Enumeration Type Documentation

◆ delivery_mode

◆ freshness_policy

Enumerator
fresh_required 
cache_allowed 
stale_allowed 
offline_only 

Definition at line 24 of file transport.hpp.

◆ http_method

Enumerator
get 
post 

Definition at line 15 of file transport.hpp.

◆ transport_operation

◆ transport_status

Enumerator
delivered 
invalid_request 
unsafe_artifact_ref 
disallowed_host 
redirect_rejected 
redirect_limit_exceeded 
timed_out 
response_too_large 
network_error 
storage_error 
artifact_exists 
door_policy_rejected 
cache_miss 
checksum_mismatch 
retry_budget_exhausted 
admission_timeout 

Definition at line 97 of file transport.hpp.

97 {
104 timed_out,
114};

Function Documentation

◆ from_contract()

fetch_request_v1 arachne::pheidippides::from_contract ( const nlohmann::json & document)
nodiscard

Validate and decode the canonical JSON boundary contract.

Definition at line 1341 of file transport.cpp.

1341 {
1343 const auto validation = arachnespace::contracts::validate(
1344 contract_name::fetch_request, document
1345 );
1346 if (!validation) {
1347 throw std::invalid_argument(validation_message(validation));
1348 }
1349
1350 try {
1351 fetch_request_v1 request;
1352 request.contract = document.at("contract").get<std::string>();
1353 request.format_version
1354 = document.at("format_version").get<std::uint32_t>();
1355 request.request_id = document.at("request_id").get<std::string>();
1356 request.door_id = document.value("door_id", std::string {});
1357 request.endpoint_id = document.value("endpoint_id", std::string {});
1358 const std::string operation
1359 = document.value("operation", std::string("point_lookup"));
1360 if (operation == "bulk_snapshot") {
1361 request.operation = transport_operation::bulk_snapshot;
1362 } else if (operation == "incremental_harvest") {
1363 request.operation = transport_operation::incremental_harvest;
1364 } else if (operation == "resume_download") {
1365 request.operation = transport_operation::resume_download;
1366 } else if (operation == "backend_read") {
1367 request.operation = transport_operation::backend_read;
1368 } else if (operation == "external_write") {
1369 request.operation = transport_operation::external_write;
1370 }
1371 const std::string freshness
1372 = document.value("freshness_policy", std::string("fresh_required"));
1373 if (freshness == "cache_allowed") {
1374 request.freshness = freshness_policy::cache_allowed;
1375 } else if (freshness == "stale_allowed") {
1376 request.freshness = freshness_policy::stale_allowed;
1377 } else if (freshness == "offline_only") {
1378 request.freshness = freshness_policy::offline_only;
1379 }
1380 request.idempotency_key
1381 = document.value("idempotency_key", std::string {});
1382 request.url = document.at("locator").get<std::string>();
1383 request.target_artifact_ref
1384 = document.at("output_ref").get<std::string>();
1385
1386 const std::string method = document.at("method").get<std::string>();
1387 request.method
1388 = method == "POST" ? http_method::post : http_method::get;
1389
1390 if (const auto headers = document.find("headers");
1391 headers != document.end()) {
1392 for (const auto& [name, value] : headers->items()) {
1393 request.headers.push_back({ name, value.get<std::string>() });
1394 }
1395 }
1396 if (const auto expected = document.find("expected");
1397 expected != document.end()) {
1398 if (const auto maximum_bytes = expected->find("maximum_bytes");
1399 maximum_bytes != expected->end()) {
1400 request.max_bytes = maximum_bytes->get<std::uint64_t>();
1401 }
1402 if (const auto timeout = expected->find("timeout_ms");
1403 timeout != expected->end()) {
1404 request.timeout
1405 = std::chrono::milliseconds(timeout->get<std::int64_t>());
1406 }
1407 if (const auto timeout = expected->find("connect_timeout_ms");
1408 timeout != expected->end()) {
1409 request.connect_timeout
1410 = std::chrono::milliseconds(timeout->get<std::int64_t>());
1411 }
1412 if (const auto timeout = expected->find("read_timeout_ms");
1413 timeout != expected->end()) {
1414 request.read_timeout
1415 = std::chrono::milliseconds(timeout->get<std::int64_t>());
1416 }
1417 if (const auto timeout = expected->find("write_timeout_ms");
1418 timeout != expected->end()) {
1419 request.write_timeout
1420 = std::chrono::milliseconds(timeout->get<std::int64_t>());
1421 }
1422 if (const auto checksum = expected->find("sha256");
1423 checksum != expected->end()) {
1424 request.expected_sha256 = checksum->get<std::string>();
1425 }
1426 }
1427 if (const auto retry = document.find("retry");
1428 retry != document.end()) {
1429 if (const auto attempts = retry->find("maximum_attempts");
1430 attempts != retry->end()) {
1431 request.maximum_attempts = attempts->get<std::size_t>();
1432 }
1433 if (const auto delay = retry->find("initial_delay_ms");
1434 delay != retry->end()) {
1435 request.initial_retry_delay
1436 = std::chrono::milliseconds(delay->get<std::int64_t>());
1437 }
1438 if (const auto delay = retry->find("maximum_delay_ms");
1439 delay != retry->end()) {
1440 request.maximum_retry_delay
1441 = std::chrono::milliseconds(delay->get<std::int64_t>());
1442 }
1443 if (const auto budget = retry->find("total_delay_budget_ms");
1444 budget != retry->end()) {
1445 request.total_retry_delay_budget
1446 = std::chrono::milliseconds(budget->get<std::int64_t>());
1447 }
1448 request.respect_retry_after
1449 = retry->value("respect_retry_after", true);
1450 }
1451 if (const auto policy = document.find("redirect_policy");
1452 policy != document.end()) {
1453 request.redirects.follow = policy->at("follow").get<bool>();
1454 request.redirects.maximum_redirects
1455 = policy->at("maximum_redirects").get<std::size_t>();
1456 request.redirects.allow_https_to_http
1457 = policy->at("allow_https_to_http").get<bool>();
1458 request.redirects.allowed_hosts
1459 = policy->at("allowed_hosts").get<std::vector<std::string>>();
1460 }
1461 if (const auto artifact = document.find("body_artifact");
1462 artifact != document.end()) {
1463 request.body_artifact = body_artifact_reference {
1464 .storage_ref = artifact->at("storage_ref").get<std::string>(),
1465 .sha256 = artifact->at("sha256").get<std::string>(),
1466 .byte_length = artifact->at("byte_length").get<std::uint64_t>(),
1467 };
1468 }
1469 if (const auto artifact = document.find("resume_artifact");
1470 artifact != document.end()) {
1471 request.resume_artifact = body_artifact_reference {
1472 .storage_ref = artifact->at("storage_ref").get<std::string>(),
1473 .sha256 = artifact->at("sha256").get<std::string>(),
1474 .byte_length = artifact->at("byte_length").get<std::uint64_t>(),
1475 };
1476 }
1477 return request;
1478 } catch (const nlohmann::json::exception& exception) {
1479 throw std::invalid_argument(
1480 std::string("cannot decode fetch_request_v1: ") + exception.what()
1481 );
1482 }
1483}
json artifact(std::string storage_ref, std::string sha256, std::uintmax_t byte_length, std::string media_type)
Definition store.cpp:1484
validation_result validate(const nlohmann::json &document)

References backend_read, bulk_snapshot, cache_allowed, arachne::pheidippides::fetch_request_v1::door_id, arachne::pheidippides::fetch_request_v1::endpoint_id, external_write, arachnespace::contracts::fetch_request, arachne::pheidippides::fetch_request_v1::format_version, arachne::pheidippides::fetch_request_v1::freshness, get, arachne::pheidippides::fetch_request_v1::idempotency_key, incremental_harvest, arachne::pheidippides::fetch_request_v1::max_bytes, arachne::pheidippides::fetch_request_v1::maximum_attempts, arachne::pheidippides::fetch_request_v1::method, offline_only, arachne::pheidippides::fetch_request_v1::operation, post, arachne::pheidippides::fetch_request_v1::request_id, arachne::pheidippides::fetch_request_v1::respect_retry_after, resume_download, stale_allowed, arachne::pheidippides::fetch_request_v1::target_artifact_ref, and arachne::pheidippides::fetch_request_v1::url.

◆ to_contract()

nlohmann::ordered_json arachne::pheidippides::to_contract ( const acquired_artifact_v1 & artifact)
nodiscard

Encode a transport result as the canonical acquired-artifact contract.

Definition at line 1485 of file transport.cpp.

1485 {
1486 using ordered_json = nlohmann::ordered_json;
1487 ordered_json document = ordered_json::object();
1488 document["contract"] = "acquired_artifact_v1";
1489 document["format_version"] = 1;
1490 document["artifact_id"] = artifact.artifact_id.empty()
1491 ? artifact_id_for(artifact.request_id)
1492 : artifact.artifact_id;
1493 document["request_id"] = artifact.request_id;
1494 if (!artifact.door_id.empty()) {
1495 document["door_id"] = artifact.door_id;
1496 }
1497 document["operation"] = std::string(to_string(artifact.operation));
1498 document["source_locator"]
1499 = receipt_safe_text(redact_url_query(artifact.source_url));
1500
1501 ordered_json transport_metadata = ordered_json::object();
1502 transport_metadata["status"]
1503 = artifact.delivered() ? "delivered" : "failed";
1504 transport_metadata["attempts"] = artifact.attempts;
1505 transport_metadata["delivery_mode"]
1506 = std::string(to_string(artifact.delivered_via));
1507 if (artifact.retry_after) {
1508 transport_metadata["retry_after_ms"] = artifact.retry_after->count();
1509 }
1510 if (!artifact.delivered()) {
1511 transport_metadata["error_code"]
1512 = std::string(status_name(artifact.status));
1513 transport_metadata["error_message"] = artifact.error_message.empty()
1514 ? "transport failed without additional detail"
1515 : receipt_safe_text(artifact.error_message);
1516 }
1517 document["transport"] = std::move(transport_metadata);
1518
1519 ordered_json response_metadata = ordered_json::object();
1520 response_metadata["status_code"] = artifact.http_status;
1521 if (!artifact.effective_url.empty()) {
1522 response_metadata["effective_url"]
1524 }
1525 response_metadata["headers"] = ordered_json::array();
1526 for (const auto& header : artifact.response_headers) {
1527 response_metadata["headers"].push_back(
1528 { { "name", receipt_safe_text(header.name) },
1529 { "value",
1530 sensitive_header_name(header.name)
1531 ? "[REDACTED]"
1532 : receipt_safe_text(header.value) } }
1533 );
1534 }
1535 response_metadata["redirect_chain"] = ordered_json::array();
1536 for (const auto& redirect : artifact.redirect_chain) {
1537 response_metadata["redirect_chain"].push_back(
1539 );
1540 }
1541 response_metadata["started_at"] = rfc3339(artifact.started_at);
1542 response_metadata["completed_at"] = rfc3339(artifact.completed_at);
1543 document["response_metadata"] = std::move(response_metadata);
1544
1545 if (artifact.delivered()) {
1546 ordered_json artifact_reference = ordered_json::object();
1547 artifact_reference["storage_ref"] = artifact.artifact_ref;
1548 artifact_reference["sha256"] = artifact.sha256;
1549 artifact_reference["byte_length"] = artifact.byte_count;
1550 if (const auto content_type
1551 = response_content_type(artifact.response_headers)) {
1552 artifact_reference["media_type"] = receipt_safe_text(*content_type);
1553 }
1554 document["artifact"] = std::move(artifact_reference);
1555 }
1556 document["acquired_at"] = rfc3339(artifact.completed_at);
1557
1558 const nlohmann::json validation_document = document;
1559 const auto validation = arachnespace::contracts::validate(
1561 validation_document
1562 );
1563 if (!validation) {
1564 throw std::invalid_argument(validation_message(validation));
1565 }
1566 return document;
1567}
nlohmann::ordered_json ordered_json
Definition main.cpp:46
std::string receipt_safe_text(const std::string_view value)
std::string redact_url_query(const std::string &url)
std::string rfc3339(const std::chrono::system_clock::time_point time)
std::optional< std::string > response_content_type(const std::vector< http_header > &headers)
bool sensitive_header_name(const std::string_view name)
std::string validation_message(const arachnespace::contracts::validation_result &validation)
std::string_view to_string(transport_operation value) noexcept

References arachnespace::contracts::acquired_artifact, arachne::pheidippides::acquired_artifact_v1::artifact_ref, arachne::pheidippides::acquired_artifact_v1::attempts, arachne::pheidippides::acquired_artifact_v1::byte_count, arachne::pheidippides::acquired_artifact_v1::delivered(), arachne::pheidippides::acquired_artifact_v1::door_id, arachne::pheidippides::acquired_artifact_v1::effective_url, arachne::pheidippides::acquired_artifact_v1::http_status, arachne::pheidippides::acquired_artifact_v1::request_id, and arachne::pheidippides::acquired_artifact_v1::sha256.

Here is the call graph for this function:

◆ to_string() [1/3]

std::string_view arachne::pheidippides::to_string ( delivery_mode value)
nodiscardnoexcept

Definition at line 1325 of file transport.cpp.

1325 {
1326 switch (value) {
1328 return "fetched";
1330 return "cache_validated";
1332 return "stale";
1334 return "resumed";
1336 return "offline";
1337 }
1338 return "fetched";
1339}

References cache_validated, fetched, offline, resumed, and stale.

◆ to_string() [2/3]

std::string_view arachne::pheidippides::to_string ( freshness_policy value)
nodiscardnoexcept

Definition at line 1311 of file transport.cpp.

1311 {
1312 switch (value) {
1314 return "fresh_required";
1316 return "cache_allowed";
1318 return "stale_allowed";
1320 return "offline_only";
1321 }
1322 return "fresh_required";
1323}

References cache_allowed, fresh_required, offline_only, and stale_allowed.

◆ to_string() [3/3]

std::string_view arachne::pheidippides::to_string ( transport_operation value)
nodiscardnoexcept

Definition at line 1293 of file transport.cpp.

1293 {
1294 switch (value) {
1296 return "bulk_snapshot";
1298 return "incremental_harvest";
1300 return "point_lookup";
1302 return "resume_download";
1304 return "backend_read";
1306 return "external_write";
1307 }
1308 return "point_lookup";
1309}

References backend_read, bulk_snapshot, external_write, incremental_harvest, point_lookup, and resume_download.