#include #include #include #include #include #include #include #include #include #include #include #include namespace { constexpr auto json_options = modulus::json_config::options{ .category = "luma", .invalid_argument_code = luma::detail::error_invalid_argument, .not_found_code = luma::detail::error_not_found}; auto invalid_display(std::string message) -> std::expected { assert(!message.empty()); return luma::detail::fail(luma::detail::error_invalid_argument, std::move(message)); } auto invalid_touch(std::string message) -> std::expected { assert(!message.empty()); return luma::detail::fail(luma::detail::error_invalid_argument, std::move(message)); } auto invalid_application(std::string message) -> std::expected { assert(!message.empty()); return luma::detail::fail(luma::detail::error_invalid_argument, std::move(message)); } } auto luma::validate(luma::display_config const &config) -> std::expected { if (config.width <= 0) { return luma::detail::fail(luma::detail::error_invalid_argument, "display.width must be positive"); } if (config.height <= 0) { return luma::detail::fail(luma::detail::error_invalid_argument, "display.height must be positive"); } if (config.target_fps <= 0) { return luma::detail::fail(luma::detail::error_invalid_argument, "display.target_fps must be positive"); } if (config.idle_fps <= 0) { return luma::detail::fail(luma::detail::error_invalid_argument, "display.idle_fps must be positive"); } if (config.idle_fps > config.target_fps) { return luma::detail::fail(luma::detail::error_invalid_argument, "display.idle_fps must be less than or equal to display.target_fps"); } return {}; } auto luma::validate(luma::touch_config const &config) -> std::expected { if (config.max_points <= 0) { return luma::detail::fail(luma::detail::error_invalid_argument, "touch.max_points must be positive"); } return {}; } auto luma::validate(luma::application_config const &config) -> std::expected { if (config.title.empty()) { return luma::detail::fail(luma::detail::error_invalid_argument, "title must not be empty"); } if (auto status = luma::validate(config.display); !status) { return modulus::fail(status.error()); } if (auto status = luma::validate(config.touch); !status) { return modulus::fail(status.error()); } if (auto status = luma::validate(config.theme); !status) { return modulus::fail(status.error()); } return {}; } auto luma::parse_display_config(boost::json::value const &value) -> std::expected { auto object = modulus::json_config::require_object(value, "display", json_options); if (!object) { return invalid_display(std::string{object.error().message()}); } auto unknown = modulus::json_config::reject_unknown_fields(**object, { "width", "height", "target_fps", "idle_fps", "fullscreen", }, "display", json_options); if (!unknown) { return invalid_display(std::string{unknown.error().message()}); } auto config = luma::display_config{}; auto width = modulus::json_config::optional_int32_field(**object, "width", config.width, std::numeric_limits::min(), std::numeric_limits::max(), json_options); auto height = modulus::json_config::optional_int32_field(**object, "height", config.height, std::numeric_limits::min(), std::numeric_limits::max(), json_options); auto target_fps = modulus::json_config::optional_int32_field(**object, "target_fps", config.target_fps, std::numeric_limits::min(), std::numeric_limits::max(), json_options); auto idle_fps = modulus::json_config::optional_int32_field(**object, "idle_fps", config.idle_fps, std::numeric_limits::min(), std::numeric_limits::max(), json_options); auto fullscreen = modulus::json_config::optional_bool_field(**object, "fullscreen", config.fullscreen, json_options); if (!width) { return invalid_display(std::string{width.error().message()}); } if (!height) { return invalid_display(std::string{height.error().message()}); } if (!target_fps) { return invalid_display(std::string{target_fps.error().message()}); } if (!idle_fps) { return invalid_display(std::string{idle_fps.error().message()}); } if (!fullscreen) { return invalid_display(std::string{fullscreen.error().message()}); } config.width = *width; config.height = *height; config.target_fps = *target_fps; config.idle_fps = *idle_fps; config.fullscreen = *fullscreen; if (auto status = luma::validate(config); !status) { return invalid_display(std::string{status.error().message()}); } return modulus::success(config); } auto luma::parse_touch_config(boost::json::value const &value) -> std::expected { auto object = modulus::json_config::require_object(value, "touch", json_options); if (!object) { return invalid_touch(std::string{object.error().message()}); } auto unknown = modulus::json_config::reject_unknown_fields(**object, {"max_points"}, "touch", json_options); if (!unknown) { return invalid_touch(std::string{unknown.error().message()}); } auto config = luma::touch_config{}; auto max_points = modulus::json_config::optional_int32_field(**object, "max_points", config.max_points, std::numeric_limits::min(), std::numeric_limits::max(), json_options); if (!max_points) { return invalid_touch(std::string{max_points.error().message()}); } config.max_points = *max_points; if (auto status = luma::validate(config); !status) { return invalid_touch(std::string{status.error().message()}); } return modulus::success(config); } auto luma::parse_application_config(boost::json::value const &value) -> std::expected { auto object = modulus::json_config::require_object(value, "application config", json_options); if (!object) { return invalid_application(std::string{object.error().message()}); } auto unknown = modulus::json_config::reject_unknown_fields(**object, { "title", "display", "touch", "theme", }, "application config", json_options); if (!unknown) { return invalid_application(std::string{unknown.error().message()}); } auto config = luma::application_config{}; auto title = modulus::json_config::optional_string_field(**object, "title", config.title, json_options); if (!title) { return invalid_application(std::string{title.error().message()}); } config.title = *title; if (auto const *display = (*object)->if_contains("display")) { auto parsed = luma::parse_display_config(*display); if (!parsed) { return invalid_application(std::string{parsed.error().message()}); } config.display = *parsed; } if (auto const *touch = (*object)->if_contains("touch")) { auto parsed = luma::parse_touch_config(*touch); if (!parsed) { return invalid_application(std::string{parsed.error().message()}); } config.touch = *parsed; } if (auto const *theme = (*object)->if_contains("theme")) { auto parsed = luma::parse_theme(*theme); if (!parsed) { return invalid_application(std::string{parsed.error().message()}); } config.theme = *parsed; } if (auto status = luma::validate(config); !status) { return invalid_application(std::string{status.error().message()}); } return modulus::success(config); } auto luma::load_application_config(std::string const &path) -> std::expected { auto value = modulus::json_config::load_value(path, json_options); if (!value) { return modulus::fail(value.error()); } return luma::parse_application_config(*value); } auto luma::to_json(luma::display_config const &config) -> boost::json::value { return boost::json::object{ { "width", config.width, }, { "height", config.height, }, { "target_fps", config.target_fps, }, { "idle_fps", config.idle_fps, }, { "fullscreen", config.fullscreen, }, }; } auto luma::to_json(luma::touch_config const &config) -> boost::json::value { return boost::json::object{ { "max_points", config.max_points, }, }; } auto luma::to_json(luma::application_config const &config) -> boost::json::value { return boost::json::object{ { "title", config.title, }, { "display", luma::to_json(config.display), }, { "touch", luma::to_json(config.touch), }, { "theme", luma::to_json(config.theme), }, }; }