takane
Validators for ArtifactDB file formats
Loading...
Searching...
No Matches
simple_list.hpp
Go to the documentation of this file.
1#ifndef TAKANE_SIMPLE_LIST_HPP
2#define TAKANE_SIMPLE_LIST_HPP
3
4#include "H5Cpp.h"
5#include "uzuki2/uzuki2.hpp"
6#include "byteme/byteme.hpp"
7#include "sanisizer/sanisizer.hpp"
8
9#include <string>
10#include <stdexcept>
11#include <filesystem>
12#include <cstddef>
13#include <optional>
14#include <cmath>
15#include <exception>
16
17#include "utils_public.hpp"
18#include "utils_other.hpp"
19
25namespace takane {
26
30void validate(const std::filesystem::path&, const Options&);
31
32inline std::string extract_simple_list_format(const JsonObjectMap& map) {
33 auto fIt = map.find("format");
34 if (fIt == map.end()) {
35 return "hdf5";
36 }
37 const auto& val = fIt->second;
38 if (val->type() != millijson::STRING) {
39 throw std::runtime_error("expected a JSON string");
40 }
41 return reinterpret_cast<millijson::String*>(val.get())->value();
42}
43
44inline std::optional<std::size_t> extract_simple_list_length(const JsonObjectMap& map) {
45 std::optional<std::size_t> output;
46 auto lIt = map.find("length");
47 if (lIt != map.end()) {
48 const auto& val = lIt->second;
49 if (val->type() != millijson::NUMBER) {
50 throw std::runtime_error("expected a JSON number");
51 }
52 const auto num = reinterpret_cast<millijson::Number*>(val.get())->value();
53 if (num != std::trunc(num)) {
54 throw std::runtime_error("expected an integer");
55 }
56 output = sanisizer::from_float<std::size_t>(num);
57 }
58 return output;
59}
69inline void validate_simple_list(const std::filesystem::path& path, const ObjectMetadata& metadata, const Options& options) {
70 const std::string type_name = "simple_list"; // use a separate variable to avoid dangling reference warnings from GCC.
71
72 ritsuko::Version version;
73 std::string format;
74 std::optional<std::size_t> expected_length;
75 try {
76 const auto& metamap = extract_json_object(metadata.other, type_name);
77 try {
78 const std::string& vstring = extract_json_version_string(metamap);
79 version = ritsuko::parse_version_string(vstring.c_str(), vstring.size(), /* skip_patch = */ true);
80 if (version.major != 1) {
81 throw std::runtime_error("unsupported version string '" + vstring + "'");
82 }
83 } catch (...) {
84 std::throw_with_nested(std::runtime_error("failed to validate 'version'"));
85 }
86
87 try {
88 format = extract_simple_list_format(metamap);
89 if (format != "json.gz" && format != "hdf5") {
90 throw std::runtime_error("unknown format '" + format + "'");
91 }
92 } catch (...) {
93 std::throw_with_nested(std::runtime_error("failed to validate 'format'"));
94 }
95
96 try {
97 expected_length = extract_simple_list_length(metamap);
98 } catch (...) {
99 std::throw_with_nested(std::runtime_error("failed to validate 'length'"));
100 }
101 } catch (...) {
102 std::throw_with_nested(std::runtime_error("failed to validate '" + type_name + "' in the object metadata"));
103 }
104
105 auto other_dir = path / "other_contents";
106 std::size_t num_external = 0;
107 if (std::filesystem::exists(other_dir)) {
108 auto status = std::filesystem::status(other_dir);
109 if (status.type() != std::filesystem::file_type::directory) {
110 throw std::runtime_error("expected 'other_contents' to be a directory");
111 }
112
113 num_external = count_directory_entries(other_dir);
114 for (I<decltype(num_external)> e = 0; e < num_external; ++e) {
115 auto epath = other_dir / ritsuko::itoa(e);
116 if (!std::filesystem::exists(epath)) {
117 throw std::runtime_error("expected an external list object at '" + std::filesystem::relative(epath, path).string() + "'");
118 }
119 try {
120 validate(epath, options);
121 } catch (...) {
122 std::throw_with_nested(std::runtime_error("failed to validate external list object at '" + std::filesystem::relative(epath, path).string() + "'"));
123 }
124 }
125 }
126
127 I<decltype(std::declval<uzuki2::List>().size())> len;
128 if (format == "json.gz") {
129 try {
130 uzuki2::json::Options opt;
131 opt.parallel = options.parallel_reads;
132 auto gzreader = open_reader<byteme::GzipFileReader>(path / "list_contents.json.gz", byteme::GzipFileReaderOptions());
133 auto loaded = uzuki2::json::parse<uzuki2::DummyProvisioner>(*gzreader, uzuki2::DummyExternals(num_external), opt);
134 len = reinterpret_cast<const uzuki2::List*>(loaded.get())->size();
135 } catch (...) {
136 std::throw_with_nested(std::runtime_error("failed to validate 'list_contents.json.gz'"));
137 }
138 } else {
139 try {
140 H5::H5File handle(path / "list_contents.h5", H5F_ACC_RDONLY);
141 auto ghandle = handle.openGroup(type_name);
142 auto loaded = uzuki2::hdf5::parse<uzuki2::DummyProvisioner>(ghandle, uzuki2::DummyExternals(num_external), {});
143 len = reinterpret_cast<const uzuki2::List*>(loaded.get())->size();
144 } catch (...) {
145 std::throw_with_nested(std::runtime_error("failed to validate '" + type_name + "' in 'list_contents.h5'"));
146 }
147 }
148
149 if (version.ge(1, 1, 0)) {
150 if (expected_length.has_value() && *expected_length != len) {
151 throw std::runtime_error("value of '/" + type_name + "/length' from the object metadata differs from the length of the list");
152 }
153 }
154}
155
162inline std::size_t height_of_simple_list(const std::filesystem::path& path, const ObjectMetadata& metadata, const Options& options) {
163 const std::string type_name = "simple_list"; // use a separate variable to avoid dangling reference warnings from GCC.
164
165 const auto& metamap = extract_json_object(metadata.other, type_name);
166 auto len_info = extract_simple_list_length(metamap);
167 if (len_info.has_value()) {
168 return *len_info;
169 }
170
171 std::string format = extract_simple_list_format(metamap);
172 if (format == "hdf5") {
173 H5::H5File handle(path / "list_contents.h5", H5F_ACC_RDONLY);
174 auto lhandle = handle.openGroup("simple_list");
175 auto vhandle = lhandle.openGroup("data");
176 return sanisizer::cast<std::size_t>(vhandle.getNumObjs());
177
178 } else {
179 // Not much choice but to parse the entire list here. We do so using the
180 // dummy, which still has enough self-awareness to hold its own length.
181 auto other_dir = path / "other_contents";
182 std::size_t num_external = 0;
183 if (std::filesystem::exists(other_dir)) {
184 num_external = count_directory_entries(other_dir);
185 }
186
187 uzuki2::json::Options opt;
188 opt.parallel = options.parallel_reads;
189 auto gzreader = open_reader<byteme::GzipFileReader>(path / "list_contents.json.gz", byteme::GzipFileReaderOptions());
190 auto ptr = uzuki2::json::parse<uzuki2::DummyProvisioner>(*gzreader, uzuki2::DummyExternals(num_external), opt);
191 return sanisizer::cast<std::size_t>(reinterpret_cast<const uzuki2::List*>(ptr.get())->size());
192 }
193}
194
195}
196
197#endif
takane validation functions.
Definition _derived_from.hpp:15
std::size_t height_of_simple_list(const std::filesystem::path &path, const ObjectMetadata &metadata, const Options &options)
Definition simple_list.hpp:162
void validate(const std::filesystem::path &path, const ObjectMetadata &metadata, const Options &options)
Definition _validate.hpp:107
void validate_simple_list(const std::filesystem::path &path, const ObjectMetadata &metadata, const Options &options)
Definition simple_list.hpp:69
Object metadata, including the type and other fields.
Definition utils_public.hpp:29
std::unordered_map< std::string, std::shared_ptr< millijson::Base > > other
Definition utils_public.hpp:38
Validation options.
Definition utils_public.hpp:99
bool parallel_reads
Definition utils_public.hpp:103
Exported utilities.