Program Listing for File config_loader.cpp

Program Listing for File config_loader.cpp#

↰ Return to documentation for file (Src/config_loader.cpp)

module;

#include <expected>
#include <filesystem>
#include <fstream>
#include <limits>
#include <sstream>
#include <string>

module kataglyphis.config_loader;

import nlohmann.json;

namespace kataglyphis::config {

using json = nlohmann::json;

namespace {

    auto assign_optional_string(const json &object, const char *key, std::string &target)
      -> std::expected<void, ConfigError>
    {
        const auto it = object.find(key);
        if (it == object.end()) { return {}; }
        if (!it->is_string()) { return std::unexpected(ConfigError::InvalidValue); }

        target = it->template get<std::string>();
        return {};
    }

    auto assign_optional_uint(const json &object, const char *key, std::uint32_t &target)
      -> std::expected<void, ConfigError>
    {
        const auto it = object.find(key);
        if (it == object.end()) { return {}; }
        // get<std::uint32_t>() would wrap a larger value silently.
        if (!it->is_number_unsigned()
            || it->template get<std::uint64_t>() > std::numeric_limits<std::uint32_t>::max()) {
            return std::unexpected(ConfigError::InvalidValue);
        }

        target = it->template get<std::uint32_t>();
        return {};
    }

    auto assign_optional_string_array(const json &object, const char *key, std::vector<std::string> &target)
      -> std::expected<void, ConfigError>
    {
        const auto it = object.find(key);
        if (it == object.end()) { return {}; }
        if (!it->is_array()) { return std::unexpected(ConfigError::InvalidValue); }

        target.clear();
        target.reserve(it->size());
        for (const auto &value : *it) {
            if (!value.is_string()) { return std::unexpected(ConfigError::InvalidValue); }
            target.push_back(value.template get<std::string>());
        }

        return {};
    }

    template<typename Parser>
    auto parse_optional_object(const json &object, const char *key, Parser &&parser) -> std::expected<void, ConfigError>
    {
        const auto it = object.find(key);
        if (it == object.end()) { return {}; }
        if (!it->is_object()) { return std::unexpected(ConfigError::InvalidValue); }

        return parser(*it);
    }

}// namespace

auto get_default_webrtc_config() -> WebRTCConfig
{
    WebRTCConfig config;
    config.stun_servers.emplace_back("stun:stun.l.google.com:19302");
    return config;
}

auto parse_webrtc_config(const std::string &json_content) -> std::expected<WebRTCConfig, ConfigError>
{
    auto j = json::parse(json_content, nullptr, false);
    if (j.is_discarded()) { return std::unexpected(ConfigError::ParseError); }
    // Valid JSON that is not an object has none of the keys, and would otherwise pass as all defaults.
    if (!j.is_object()) { return std::unexpected(ConfigError::InvalidValue); }

    WebRTCConfig config;
    if (auto result = assign_optional_string(j, "signalingServerUrl", config.signaling_server_url); !result) {
        return std::unexpected(result.error());
    }
    if (auto result = assign_optional_uint(j, "reconnectionTimeoutMs", config.reconnection_timeout_ms); !result) {
        return std::unexpected(result.error());
    }
    if (auto result = assign_optional_string_array(j, "stunServers", config.stun_servers); !result) {
        return std::unexpected(result.error());
    }
    if (auto result = assign_optional_string_array(j, "turnServers", config.turn_servers); !result) {
        return std::unexpected(result.error());
    }

    if (auto result = parse_optional_object(j,
          "video",
          [&](const json &video) -> std::expected<void, ConfigError> {
              if (auto value = assign_optional_uint(video, "defaultWidth", config.video.default_width); !value) {
                  return std::unexpected(value.error());
              }
              if (auto value = assign_optional_uint(video, "defaultHeight", config.video.default_height); !value) {
                  return std::unexpected(value.error());
              }
              if (auto value = assign_optional_uint(video, "defaultFramerate", config.video.default_framerate);
                !value) {
                  return std::unexpected(value.error());
              }
              if (auto value = assign_optional_uint(video, "defaultBitrateKbps", config.video.default_bitrate_kbps);
                !value) {
                  return std::unexpected(value.error());
              }
              return {};
          });
      !result) {
        return std::unexpected(result.error());
    }

    if (auto result = parse_optional_object(j,
          "texture",
          [&](const json &texture) -> std::expected<void, ConfigError> {
              if (auto value = assign_optional_uint(texture, "width", config.texture.width); !value) {
                  return std::unexpected(value.error());
              }
              if (auto value = assign_optional_uint(texture, "height", config.texture.height); !value) {
                  return std::unexpected(value.error());
              }
              return {};
          });
      !result) {
        return std::unexpected(result.error());
    }

    if (auto result = parse_optional_object(j,
          "android",
          [&](const json &android) -> std::expected<void, ConfigError> {
              if (auto value = assign_optional_uint(android, "width", config.android.width); !value) {
                  return std::unexpected(value.error());
              }
              if (auto value = assign_optional_uint(android, "height", config.android.height); !value) {
                  return std::unexpected(value.error());
              }
              if (auto value = assign_optional_uint(android, "fps", config.android.fps); !value) {
                  return std::unexpected(value.error());
              }
              return {};
          });
      !result) {
        return std::unexpected(result.error());
    }

    if (auto result = parse_optional_object(j,
          "stream",
          [&](const json &stream) -> std::expected<void, ConfigError> {
              if (auto value = assign_optional_string(stream, "source", config.stream.source); !value) {
                  return std::unexpected(value.error());
              }
              if (auto value = assign_optional_string(stream, "encoder", config.stream.encoder); !value) {
                  return std::unexpected(value.error());
              }
              if (auto value = assign_optional_string(stream, "device", config.stream.device); !value) {
                  return std::unexpected(value.error());
              }
              if (auto value = assign_optional_string(stream, "cameraId", config.stream.camera_id); !value) {
                  return std::unexpected(value.error());
              }
              if (auto value = assign_optional_string(stream, "inputPath", config.stream.input_path); !value) {
                  return std::unexpected(value.error());
              }
              if (auto value = assign_optional_string(stream, "inputUri", config.stream.input_uri); !value) {
                  return std::unexpected(value.error());
              }
              if (auto value = assign_optional_string(stream, "peerId", config.stream.peer_id); !value) {
                  return std::unexpected(value.error());
              }
              if (auto value = assign_optional_string(stream, "producerId", config.stream.producer_id); !value) {
                  return std::unexpected(value.error());
              }
              return {};
          });
      !result) {
        return std::unexpected(result.error());
    }

    return config;
}

auto load_webrtc_config(const std::filesystem::path &config_path) -> std::expected<WebRTCConfig, ConfigError>
{

    if (!std::filesystem::exists(config_path)) { return std::unexpected(ConfigError::FileNotFound); }

    std::ifstream file(config_path);
    if (!file.is_open()) { return std::unexpected(ConfigError::FileNotFound); }

    std::stringstream buffer;
    buffer << file.rdbuf();

    return parse_webrtc_config(buffer.str());
}

}// namespace kataglyphis::config