|
| 1 | +/// \file RPageStorageS3.cxx |
| 2 | +/// \author Jas Mehta <jasmehta805@gmail.com> |
| 3 | +/// \date 2026-06-01 |
| 4 | + |
| 5 | +/************************************************************************* |
| 6 | + * Copyright (C) 1995-2026, Rene Brun and Fons Rademakers. * |
| 7 | + * All rights reserved. * |
| 8 | + * * |
| 9 | + * For the licensing terms see $ROOTSYS/LICENSE. * |
| 10 | + * For the list of contributors see $ROOTSYS/README/CREDITS. * |
| 11 | + *************************************************************************/ |
| 12 | + |
| 13 | +#include <ROOT/RPageStorageS3.hxx> |
| 14 | + |
| 15 | +#include <nlohmann/json.hpp> |
| 16 | + |
| 17 | +#include <string> |
| 18 | + |
| 19 | +/// Field-by-field equality check across all 14 anchor members. |
| 20 | +/// Used to verify round-trip correctness in tests. |
| 21 | +bool ROOT::Experimental::Internal::RNTupleAnchorS3::operator==(const RNTupleAnchorS3 &other) const |
| 22 | +{ |
| 23 | + return fVersionAnchor == other.fVersionAnchor && fVersionEpoch == other.fVersionEpoch && |
| 24 | + fVersionMajor == other.fVersionMajor && fVersionMinor == other.fVersionMinor && |
| 25 | + fVersionPatch == other.fVersionPatch && fUrlTemplate == other.fUrlTemplate && |
| 26 | + fHeaderObjId == other.fHeaderObjId && fHeaderOffset == other.fHeaderOffset && |
| 27 | + fNBytesHeader == other.fNBytesHeader && fLenHeader == other.fLenHeader && |
| 28 | + fFooterObjId == other.fFooterObjId && fFooterOffset == other.fFooterOffset && |
| 29 | + fNBytesFooter == other.fNBytesFooter && fLenFooter == other.fLenFooter; |
| 30 | +} |
| 31 | + |
| 32 | +/// Serialize the anchor to a pretty-printed JSON string (2-space indent). |
| 33 | +/// nlohmann/json handles type conversion, string escaping, and uint64 precision. |
| 34 | +/// The output is suitable for direct upload to S3 as the anchor object. |
| 35 | +std::string ROOT::Experimental::Internal::RNTupleAnchorS3::ToJSON() const |
| 36 | +{ |
| 37 | + nlohmann::json jsonAnchor; |
| 38 | + jsonAnchor["anchor_version"] = fVersionAnchor; |
| 39 | + jsonAnchor["format_version_epoch"] = fVersionEpoch; |
| 40 | + jsonAnchor["format_version_major"] = fVersionMajor; |
| 41 | + jsonAnchor["format_version_minor"] = fVersionMinor; |
| 42 | + jsonAnchor["format_version_patch"] = fVersionPatch; |
| 43 | + jsonAnchor["url_template"] = fUrlTemplate; |
| 44 | + jsonAnchor["header_obj_id"] = fHeaderObjId; |
| 45 | + jsonAnchor["header_offset"] = fHeaderOffset; |
| 46 | + jsonAnchor["nbytes_header"] = fNBytesHeader; |
| 47 | + jsonAnchor["len_header"] = fLenHeader; |
| 48 | + jsonAnchor["footer_obj_id"] = fFooterObjId; |
| 49 | + jsonAnchor["footer_offset"] = fFooterOffset; |
| 50 | + jsonAnchor["nbytes_footer"] = fNBytesFooter; |
| 51 | + jsonAnchor["len_footer"] = fLenFooter; |
| 52 | + return jsonAnchor.dump(2); |
| 53 | +} |
| 54 | + |
| 55 | +/// Construct an anchor from a JSON string. |
| 56 | +/// The anchor version is checked first; if it does not match the current version, |
| 57 | +/// parsing fails immediately. All remaining fields are extracted with jsonAnchor.at() |
| 58 | +/// which throws on missing keys or type mismatches. |
| 59 | +ROOT::RResult<ROOT::Experimental::Internal::RNTupleAnchorS3> |
| 60 | +ROOT::Experimental::Internal::RNTupleAnchorS3::FromJSON(const std::string &json) |
| 61 | +{ |
| 62 | + nlohmann::json jsonAnchor; |
| 63 | + try { |
| 64 | + jsonAnchor = nlohmann::json::parse(json); |
| 65 | + } catch (const nlohmann::json::parse_error &e) { |
| 66 | + return R__FAIL("cannot parse S3 anchor JSON: " + std::string(e.what())); |
| 67 | + } |
| 68 | + |
| 69 | + RNTupleAnchorS3 anchor; |
| 70 | + |
| 71 | + try { |
| 72 | + anchor.fVersionAnchor = jsonAnchor.at("anchor_version").get<std::uint32_t>(); |
| 73 | + } catch (const nlohmann::json::exception &e) { |
| 74 | + return R__FAIL("missing or invalid 'anchor_version' in S3 anchor: " + std::string(e.what())); |
| 75 | + } |
| 76 | + |
| 77 | + if (anchor.fVersionAnchor != RNTupleAnchorS3().fVersionAnchor) |
| 78 | + return R__FAIL("unsupported S3 anchor version: " + std::to_string(anchor.fVersionAnchor)); |
| 79 | + |
| 80 | + try { |
| 81 | + anchor.fVersionEpoch = jsonAnchor.at("format_version_epoch").get<std::uint16_t>(); |
| 82 | + anchor.fVersionMajor = jsonAnchor.at("format_version_major").get<std::uint16_t>(); |
| 83 | + anchor.fVersionMinor = jsonAnchor.at("format_version_minor").get<std::uint16_t>(); |
| 84 | + anchor.fVersionPatch = jsonAnchor.at("format_version_patch").get<std::uint16_t>(); |
| 85 | + anchor.fUrlTemplate = jsonAnchor.at("url_template").get<std::string>(); |
| 86 | + anchor.fHeaderObjId = jsonAnchor.at("header_obj_id").get<std::uint64_t>(); |
| 87 | + anchor.fHeaderOffset = jsonAnchor.at("header_offset").get<std::uint64_t>(); |
| 88 | + anchor.fNBytesHeader = jsonAnchor.at("nbytes_header").get<std::uint64_t>(); |
| 89 | + anchor.fLenHeader = jsonAnchor.at("len_header").get<std::uint64_t>(); |
| 90 | + anchor.fFooterObjId = jsonAnchor.at("footer_obj_id").get<std::uint64_t>(); |
| 91 | + anchor.fFooterOffset = jsonAnchor.at("footer_offset").get<std::uint64_t>(); |
| 92 | + anchor.fNBytesFooter = jsonAnchor.at("nbytes_footer").get<std::uint64_t>(); |
| 93 | + anchor.fLenFooter = jsonAnchor.at("len_footer").get<std::uint64_t>(); |
| 94 | + } catch (const nlohmann::json::exception &e) { |
| 95 | + return R__FAIL("missing or invalid field in S3 anchor: " + std::string(e.what())); |
| 96 | + } |
| 97 | + |
| 98 | + return anchor; |
| 99 | +} |
0 commit comments