ifw-daq  2.1.0-pre1
IFW Data Acquisition modules
dpSpec.cpp
Go to the documentation of this file.
1 /**
2  * @file
3  * @ingroup daq_dpm_libmerge
4  * @copyright ESO - European Southern Observatory
5  */
6 #include <daq/dpSpec.hpp>
7 
8 #include <fmt/format.h>
9 
10 #include <daq/fits/json.hpp>
11 #include <iostream>
12 
14 using JsonPointer = nlohmann::json_pointer<Json>;
15 
16 namespace daq {
17 
18 template <class... Args>
19 DpSpecError MakeParseException(JsonPointer const& ptr, Args&&... args) {
20  return DpSpecError(
21  (fmt::format("({}) {}", ptr.to_string(), fmt::format(std::forward<Args>(args)...)))
22  .c_str());
23 }
24 
25 template <char const*>
26 DpSpecError MakeParseException(JsonPointer const& ptr, char const* str) {
27  return DpSpecError(fmt::format("({}) {}", ptr.to_string(), str).c_str());
28 }
29 
31  return DpSpecError(fmt::format("({}) {}", ptr.to_string(), "mandatory value missing").c_str());
32 }
33 
34 DpSpecError
35 MakeWrongTypeException(JsonPointer const& ptr, char const* expected_type, char const* actual_type) {
36  return DpSpecError(
37  fmt::format("({}) {}",
38  ptr.to_string(),
39  fmt::format("must be a {}, but is {}", expected_type, actual_type))
40  .c_str());
41 }
42 
44  char const* known_variants,
45  char const* actual_variant) {
46  return DpSpecError(
47  fmt::format("({}) {}",
48  ptr.to_string(),
49  fmt::format("must be one of {}, but is '{}'", known_variants, actual_variant))
50  .c_str());
51 }
52 
53 template <class T>
54 T GetMember(Json const& json,
55  char const* name,
56  JsonPointer const& breadcrumb,
57  bool allow_empty = true);
58 
59 template <>
60 std::string GetMember<std::string>(Json const& json,
61  char const* name,
62  JsonPointer const& breadcrumb,
63  bool allow_empty) {
64  if (!json.contains(name)) {
65  throw MakeValueMissingException(breadcrumb / name);
66  }
67  auto const& value_json = json[name];
68  if (!value_json.is_string()) {
69  throw MakeWrongTypeException(breadcrumb / name, "string", value_json.type_name());
70  }
71  auto value = value_json.get<std::string>();
72  if (value.empty() && !allow_empty) {
73  throw MakeParseException(breadcrumb / name, "empty string is invalid");
74  }
75  return value;
76 }
77 
78 void AssertIsObject(Json const& json, JsonPointer const& breadcrumb) {
79  if (!json.is_object()) {
80  throw MakeWrongTypeException(breadcrumb, "object", json.type_name());
81  }
82 }
83 
84 void AssertIsArray(Json const& json, JsonPointer const& breadcrumb) {
85  if (!json.is_array()) {
86  throw MakeWrongTypeException(breadcrumb, "array", json.type_name());
87  }
88 }
89 
90 std::vector<std::string> ParseArrayOfString(Json const& json, JsonPointer const& breadcrumb) {
91  AssertIsArray(json, breadcrumb);
92  std::vector<std::string> result;
93  auto index = 0u;
94  for (auto const& value_json : json) {
95  if (!value_json.is_string()) {
96  throw MakeWrongTypeException(breadcrumb / index, "string", value_json.type_name());
97  }
98  auto value = value_json.get<std::string>();
99  if (value.empty()) {
100  throw MakeParseException(breadcrumb / index, "empty string is invalid");
101  }
102  result.emplace_back(std::move(value));
103  index++;
104  }
105  return result;
106 }
107 
108 DpSpec::Filter ParseFilter(Json const& json, JsonPointer const& breadcrumb) {
109  AssertIsObject(json, breadcrumb);
110  if (!json.contains("selectionPatterns")) {
111  throw MakeValueMissingException(breadcrumb / "selectionPatterns");
112  }
113  DpSpec::Filter filter;
114  filter.selection_patterns =
115  ParseArrayOfString(json["selectionPatterns"], breadcrumb / "selectionPatterns");
116  return filter;
117 }
118 
120  AssertIsObject(json, breadcrumb);
121  if (!json.contains("selectionPatterns")) {
122  throw MakeValueMissingException(breadcrumb / "selectionPatterns");
123  }
124  DpSpec::Transform transform;
125  transform.selection_patterns =
126  ParseArrayOfString(json["selectionPatterns"], breadcrumb / "selectionPatterns");
127  transform.regex = GetMember<std::string>(json, "regex", breadcrumb);
128  transform.format = GetMember<std::string>(json, "format", breadcrumb);
129  return transform;
130 }
131 
133  AssertIsArray(json, breadcrumb);
134  DpSpec::KeywordRules result;
135  auto index = 0u;
136  for (auto const& json_element : json) {
137  auto type = GetMember<std::string>(json_element, "type", breadcrumb);
138  if (type == "filter") {
139  result.emplace_back(ParseFilter(json_element, breadcrumb));
140  } else if (type == "transform") {
141  result.emplace_back(ParseTransform(json_element, breadcrumb));
142  } else {
144  breadcrumb / "type", "'filter' or 'transform'", type.c_str());
145  }
146  index++;
147  }
148  return result;
149 }
150 
151 DpSpec::SourceFitsKeywords
152 ParseSourceFitsKeywords(Json const& json, JsonPointer const& breadcrumb) {
153  AssertIsObject(json, breadcrumb);
155  obj.source_name = GetMember<std::string>(json, "sourceName", breadcrumb);
156  // Optional keywordRules
157  if (json.contains("keywordRules")) {
158  obj.keyword_rules = ParseKeywordRules(json["keywordRules"], breadcrumb / "keywordRules");
159  }
160  if (!json.contains("keywords")) {
161  throw MakeValueMissingException(breadcrumb / "keywords");
162  }
163  try {
164  auto keywords = fits::ParseJsonKeywords(json["keywords"], breadcrumb / "keywords");
165  obj.keywords = std::move(keywords);
166  return obj;
167  } catch (std::exception const& err) {
168  std::throw_with_nested(
169  MakeParseException(breadcrumb / "keywords", "failed to parse keywords"));
170  }
171 }
172 
174  AssertIsObject(json, breadcrumb);
176  obj.source_name = GetMember<std::string>(json, "sourceName", breadcrumb);
177  obj.origin = GetMember<std::string>(json, "origin", breadcrumb);
178  // Optional keywordRules
179  if (json.contains("keywordRules")) {
180  obj.keyword_rules = ParseKeywordRules(json["keywordRules"], breadcrumb / "keywordRules");
181  }
182  return obj;
183 }
184 
185 DpSpec::Target ParseTarget(Json const& json, JsonPointer const& breadcrumb) {
186  AssertIsObject(json, breadcrumb);
187  DpSpec::Target obj{};
188  obj.file_id = GetMember<std::string>(json, "fileId", breadcrumb);
189  if (json.contains("source")) {
190  obj.source = ParseSourceFitsFile(json["source"], breadcrumb / "source");
191  }
192  if (json.contains("filePrefix")) {
193  obj.file_prefix = GetMember<std::string>(json, "filePrefix", breadcrumb, true);
194  }
195  return obj;
196 }
197 
199  AssertIsObject(json, breadcrumb);
200  auto type = GetMember<std::string>(json, "type", breadcrumb);
201  if (type == "fitsKeywords") {
202  return ParseSourceFitsKeywords(json, breadcrumb);
203  } else if (type == "fitsFile") {
204  return ParseSourceFitsFile(json, breadcrumb);
205  } else {
207  breadcrumb / "type", "'fitsKeywords' or 'fitsFile'", type.c_str());
208  }
209 }
210 
212  try {
213  JsonPointer breadcrumb("");
214  AssertIsObject(json, breadcrumb);
215 
216  DpSpec spec{};
217  spec.id = GetMember<std::string>(json, "id", breadcrumb);
218  if (!json.contains("target")) {
219  throw MakeValueMissingException(breadcrumb / "target");
220  }
221  spec.target = ParseTarget(json["target"], breadcrumb / "target");
222 
223  if (json.contains("sources")) {
224  auto const& json_sources = json["sources"];
225  if (!json_sources.is_array()) {
227  breadcrumb / "sources", "array", json_sources.type_name());
228  }
229 
230  auto index = 0u;
231  for (auto const& json_source : json_sources) {
232  spec.sources.emplace_back(ParseSource(json_source, breadcrumb / "sources" / index));
233  index++;
234  }
235  }
236  // Check that there's at least one source
237  if (!spec.target.source && spec.sources.empty()) {
238  throw MakeParseException(
239  breadcrumb / "sources",
240  "At least one source must be provided if '/target/source' is not used");
241  }
242  return spec;
243  } catch (DpSpecError const&) {
244  throw;
245  } catch (...) { // GCOVR_EXCL_START
246  std::throw_with_nested(DpSpecError("Unknown parsing error"));
247  // GCOVR_EXCL_STOP
248  }
249 }
250 
251 Origin ParseSourceOrigin(std::string const& origin_str) {
252  Origin origin;
253  // origin str may contain a 'user@' component which we don't care about
254  auto start_pos = origin_str.find('@');
255  if (start_pos == std::string::npos) {
256  // no '@' found -> start at 0
257  start_pos = 0u;
258  } else {
259  if (start_pos > origin_str.size()) {
260  throw std::invalid_argument(
261  fmt::format("Invalid format of origin string: '{}'", origin_str));
262  }
263  // skip '@' sign
264  start_pos++;
265  }
266 
267  auto pos = origin_str.find(':', start_pos);
268  if (pos != std::string::npos) {
269  // Split at 'pos'
270  origin.host = origin_str.substr(start_pos, pos - start_pos);
271  origin.path = origin_str.substr(pos + 1, std::string::npos);
272  } else {
273  origin.path = origin_str.substr(start_pos);
274  }
275  if (origin.path.empty()) {
276  throw std::invalid_argument(
277  fmt::format("Invalid format of origin string: '{}'", origin_str));
278  }
279  return origin;
280 }
281 
282 std::string Origin::RsyncPath() const {
283  if (host.empty()) {
284  return path;
285  }
286  return (host + std::string(":") + path.native());
287 }
288 
289 } // namespace daq
daq::DpSpec::SourceFitsFile
Definition: dpSpec.hpp:45
daq::DpSpec::Transform
Definition: dpSpec.hpp:32
json.hpp
Contains data structure for FITS keywords.
daq::DpSpec::Transform::format
std::string format
Definition: dpSpec.hpp:35
daq::DpSpec
Close representation of the JSON structure but with stronger types.
Definition: dpSpec.hpp:28
daq::GetMember
T GetMember(Json const &json, char const *name, JsonPointer const &breadcrumb, bool allow_empty=true)
daq::DpSpecError
Definition: dpSpec.hpp:21
daq::DpSpec::Target::file_id
std::string file_id
Definition: dpSpec.hpp:51
daq::ParseFilter
DpSpec::Filter ParseFilter(Json const &json, JsonPointer const &breadcrumb)
Definition: dpSpec.cpp:108
dpSpec.hpp
daq::AssertIsArray
void AssertIsArray(Json const &json, JsonPointer const &breadcrumb)
Definition: dpSpec.cpp:84
daq::Origin::RsyncPath
std::string RsyncPath() const
Definition: dpSpec.cpp:282
daq::Origin::path
std::filesystem::path path
Definition: dpSpec.hpp:83
daq::MakeWrongTypeException
DpSpecError MakeWrongTypeException(JsonPointer const &ptr, char const *expected_type, char const *actual_type)
Definition: dpSpec.cpp:35
daq::ParseDpSpec
DpSpec ParseDpSpec(Json const &json)
Parse JSON to construct the DpSpec structure.
Definition: dpSpec.cpp:211
daq::DpSpec::SourceFitsKeywords::source_name
std::string source_name
Definition: dpSpec.hpp:41
Json
nlohmann::json Json
Definition: json.cpp:18
daq
Definition: asyncProcess.cpp:15
daq::MakeUnknownVariantException
DpSpecError MakeUnknownVariantException(JsonPointer const &ptr, char const *known_variants, char const *actual_variant)
Definition: dpSpec.cpp:43
daq::ParseSourceFitsFile
DpSpec::SourceFitsFile ParseSourceFitsFile(Json const &json, JsonPointer const &breadcrumb)
Definition: dpSpec.cpp:173
daq::DpSpec::SourceFitsKeywords
Definition: dpSpec.hpp:40
daq::DpSpec::id
std::string id
Definition: dpSpec.hpp:61
JsonPointer
nlohmann::json_pointer< Json > JsonPointer
Definition: json.cpp:19
daq::DpSpec::SourceFitsKeywords::keywords
fits::KeywordVector keywords
Definition: dpSpec.hpp:42
daq::DpSpec::KeywordRules
std::vector< KeywordRule > KeywordRules
Definition: dpSpec.hpp:38
daq::DpSpec::Filter
Definition: dpSpec.hpp:29
daq::ParseKeywordRules
DpSpec::KeywordRules ParseKeywordRules(Json const &json, JsonPointer const &breadcrumb)
Definition: dpSpec.cpp:132
daq::ParseSource
DpSpec::SourceTypes ParseSource(Json const &json, JsonPointer const &breadcrumb)
Definition: dpSpec.cpp:198
daq::DpSpec::SourceFitsFile::keyword_rules
KeywordRules keyword_rules
Definition: dpSpec.hpp:48
daq::DpSpec::SourceTypes
std::variant< SourceFitsKeywords, SourceFitsFile > SourceTypes
Definition: dpSpec.hpp:59
daq::Origin::host
std::string host
Definition: dpSpec.hpp:82
daq::DpSpec::Transform::selection_patterns
std::vector< std::string > selection_patterns
Definition: dpSpec.hpp:33
daq::DpSpec::SourceFitsKeywords::keyword_rules
KeywordRules keyword_rules
Definition: dpSpec.hpp:43
daq::ParseTarget
DpSpec::Target ParseTarget(Json const &json, JsonPointer const &breadcrumb)
Definition: dpSpec.cpp:185
daq::ParseSourceFitsKeywords
DpSpec::SourceFitsKeywords ParseSourceFitsKeywords(Json const &json, JsonPointer const &breadcrumb)
Definition: dpSpec.cpp:152
daq::Origin
Describes parsed origin string into its components "host" and "path".
Definition: dpSpec.hpp:77
daq::ParseTransform
DpSpec::Transform ParseTransform(Json const &json, JsonPointer const &breadcrumb)
Definition: dpSpec.cpp:119
daq::AssertIsObject
void AssertIsObject(Json const &json, JsonPointer const &breadcrumb)
Definition: dpSpec.cpp:78
daq::MakeValueMissingException
DpSpecError MakeValueMissingException(JsonPointer const &ptr)
Definition: dpSpec.cpp:30
daq::DpSpec::Transform::regex
std::string regex
Definition: dpSpec.hpp:34
daq::DpSpec::Target
Definition: dpSpec.hpp:50
daq::DpSpec::SourceFitsFile::source_name
std::string source_name
Definition: dpSpec.hpp:46
daq::DpSpec::Filter::selection_patterns
std::vector< std::string > selection_patterns
Definition: dpSpec.hpp:30
daq::DpSpec::SourceFitsFile::origin
std::string origin
Definition: dpSpec.hpp:47
daq::fits::ParseJsonKeywords
std::vector< KeywordVariant > ParseJsonKeywords(char const *keywords)
Parse and return FITS keywords.
Definition: json.cpp:124
daq::MakeParseException
DpSpecError MakeParseException(JsonPointer const &ptr, Args &&... args)
Definition: dpSpec.cpp:19
daq::ParseSourceOrigin
Origin ParseSourceOrigin(std::string const &origin_str)
Parse origin string from DpSpec into component parts.
Definition: dpSpec.cpp:251
daq::ParseArrayOfString
std::vector< std::string > ParseArrayOfString(Json const &json, JsonPointer const &breadcrumb)
Definition: dpSpec.cpp:90
daq::json
nlohmann::json json
Definition: json.cpp:20