241 const std::string shown(text);
242 if (text.size() != 7 && text.size() != 9) {
243 return absl::InvalidArgumentError(
244 absl::StrFormat(
"color \"%s\" must be #RRGGBB or #RRGGBBAA", shown));
246 if (text[0] !=
'#') {
247 return absl::InvalidArgumentError(
248 absl::StrFormat(
"color \"%s\" must start with #", shown));
251 for (
size_t i = 1; i < text.size(); ++i) {
252 const char c = text[i];
254 if (c >=
'0' && c <=
'9')
256 if (c >=
'a' && c <=
'f')
257 digit = c -
'a' + 10;
258 if (c >=
'A' && c <=
'F')
259 digit = c -
'A' + 10;
261 return absl::InvalidArgumentError(
262 absl::StrFormat(
"color \"%s\" has a non-hex digit", shown));
264 value = (value << 4) | static_cast<uint32_t>(digit);
266 if (text.size() == 7) {
267 value = (value << 8) | 0xFF;
273 std::string_view json_text,
int max_room_id) {
274 json root = json::parse(json_text.begin(), json_text.end(),
nullptr,
276 if (root.is_discarded()) {
277 return absl::InvalidArgumentError(
"proposal overlay is not valid JSON");
279 if (!root.is_object()) {
280 return Invalid(
"root",
"must be an object");
283 return Invalid(
"root",
284 absl::StrFormat(
"\"format\" must be \"%s\"",
287 auto version = root.find(
"version");
288 if (version == root.end() || !version->is_number_integer() ||
290 return Invalid(
"root",
291 absl::StrFormat(
"unsupported \"version\" (expected %d)",
296 overlay.
title = OptionalString(root,
"title");
297 overlay.
status = OptionalString(root,
"status");
298 if (
auto notes = root.find(
"notes");
299 notes != root.end() && notes->is_array()) {
300 for (
const auto& note : *notes) {
301 if (note.is_string())
302 overlay.
notes.push_back(note.get<std::string>());
306 auto rooms = root.find(
"rooms");
307 if (rooms == root.end() || !rooms->is_array() || rooms->empty()) {
308 return Invalid(
"root",
"\"rooms\" must be a non-empty list");
310 std::set<int> room_ids;
311 for (
size_t i = 0; i < rooms->size(); ++i) {
312 const std::string where = absl::StrFormat(
"rooms[%d]", i);
313 const json& entry = (*rooms)[i];
315 entry.is_object() && entry.contains(
"room") ? entry[
"room"] : entry;
316 auto room = ParseRoomId(
id, where, max_room_id);
318 return room.status();
319 if (!room_ids.insert(*room).second) {
320 return Invalid(where,
321 absl::StrFormat(
"room 0x%X is listed twice", *room));
323 overlay.
rooms.push_back(
324 {*room, entry.is_object() ? OptionalString(entry,
"label") :
""});
327 auto layers = root.find(
"layers");
328 if (layers == root.end() || !layers->is_array()) {
329 return Invalid(
"root",
"\"layers\" must be a list");
331 std::set<std::string> layer_ids;
332 for (
size_t li = 0; li < layers->size(); ++li) {
333 const json& entry = (*layers)[li];
334 std::string where = absl::StrFormat(
"layers[%d]", li);
335 if (!entry.is_object()) {
336 return Invalid(where,
"must be an object");
339 layer.
id = OptionalString(entry,
"id");
340 if (layer.
id.empty()) {
341 return Invalid(where,
"missing \"id\"");
343 if (!layer_ids.insert(layer.
id).second) {
344 return Invalid(where,
345 absl::StrFormat(
"duplicate layer id \"%s\"", layer.
id));
347 where = absl::StrFormat(
"layer \"%s\"", layer.
id);
348 layer.
label = OptionalString(entry,
"label");
349 if (layer.
label.empty())
351 if (
auto color = entry.find(
"color"); color != entry.end()) {
352 if (!color->is_string()) {
353 return Invalid(where,
"\"color\" must be a string");
357 return Invalid(where, std::string(rgba.status().message()));
361 if (
auto visible = entry.find(
"visible");
362 visible != entry.end() && visible->is_boolean()) {
363 layer.
visible = visible->get<
bool>();
365 auto shapes = entry.find(
"shapes");
366 if (shapes == entry.end() || !shapes->is_array()) {
367 return Invalid(where,
"\"shapes\" must be a list");
369 for (
size_t si = 0; si < shapes->size(); ++si) {
371 ParseShape((*shapes)[si], absl::StrFormat(
"%s shapes[%d]", where, si),
372 room_ids, max_room_id);
374 return shape.status();
375 layer.
shapes.push_back(std::move(*shape));
377 overlay.
layers.push_back(std::move(layer));
379 if (
auto steps = root.find(
"steps"); steps != root.end()) {
380 if (!steps->is_array())
381 return Invalid(
"root",
"steps must be a list");
382 std::set<std::string> step_ids;
383 for (
size_t i = 0; i < steps->size(); ++i) {
384 const auto& entry = (*steps)[i];
385 const std::string where = absl::StrFormat(
"steps[%d]", i);
386 if (!entry.is_object())
387 return Invalid(where,
"must be an object");
389 step.
id = OptionalString(entry,
"id");
390 if (step.
id.empty() || !step_ids.insert(step.
id).second)
391 return Invalid(where,
"step id must be non-empty and unique");
392 step.
label = OptionalString(entry,
"label");
393 if (step.
label.empty())
395 step.
detail = OptionalString(entry,
"detail");
396 auto refs = entry.find(
"layers");
397 if (refs == entry.end() || !refs->is_array() || refs->empty())
398 return Invalid(where,
"layers must be a non-empty list of layer ids");
399 std::set<std::string> referenced;
400 for (
const auto& ref : *refs) {
401 if (!ref.is_string())
402 return Invalid(where,
"layer reference must be a string");
403 const auto id = ref.get<std::string>();
404 if (!layer_ids.contains(
id))
405 return Invalid(where, absl::StrFormat(
"unknown layer id \"%s\"",
id));
406 if (!referenced.insert(
id).second)
407 return Invalid(where,
408 absl::StrFormat(
"duplicate layer id \"%s\"",
id));
411 overlay.
steps.push_back(std::move(step));