30class simple_yaml_parser {
32 using yaml_map = std::map<std::string, std::string>;
33 using yaml_list = std::vector<std::string>;
35 explicit simple_yaml_parser(std::string_view content) {
39 [[nodiscard]]
auto get_string(
const std::string& path,
40 const std::string& default_value =
"") const
43 return it !=
values_.end() ? it->second : default_value;
46 [[nodiscard]]
auto get_int(
const std::string& path,
int default_value = 0) const
48 auto value = get_string(path);
49 if (value.empty())
return default_value;
51 return std::stoi(value);
57 [[nodiscard]]
auto get_bool(
const std::string& path,
bool default_value =
false) const
59 auto value = get_string(path);
60 if (value.empty())
return default_value;
61 std::transform(value.begin(), value.end(), value.begin(), ::tolower);
62 return value ==
"true" || value ==
"yes" || value ==
"on" || value ==
"1";
65 [[nodiscard]]
auto get_list(
const std::string& path)
const
66 -> std::vector<std::string> {
67 auto it =
lists_.find(path);
68 return it !=
lists_.end() ? it->second : std::vector<std::string>{};
72 void parse(std::string_view content) {
73 std::istringstream stream{std::string(content)};
75 std::vector<std::pair<int, std::string>> path_stack;
76 std::string current_list_path;
78 while (std::getline(stream, line)) {
80 if (
line.empty() ||
line.find_first_not_of(
" \t\r\n") == std::string::npos) {
85 auto non_ws =
line.find_first_not_of(
" \t");
86 if (non_ws != std::string::npos && line[non_ws] ==
'#') {
93 if (c ==
' ') indent++;
94 else if (c ==
'\t') indent += 2;
99 auto trimmed = trim(line);
102 if (trimmed.starts_with(
"- ")) {
103 auto item = trim(trimmed.substr(2));
104 item = strip_quotes(item);
105 if (!current_list_path.empty()) {
106 lists_[current_list_path].push_back(item);
112 current_list_path.clear();
115 auto colon_pos = trimmed.find(
':');
116 if (colon_pos == std::string::npos)
continue;
118 std::string key = trim(trimmed.substr(0, colon_pos));
119 std::string value = (colon_pos + 1 < trimmed.size())
120 ? trim(trimmed.substr(colon_pos + 1))
124 while (!path_stack.empty() && path_stack.back().first >= indent) {
125 path_stack.pop_back();
129 std::string full_path;
130 for (
const auto& [_, segment] : path_stack) {
131 full_path += segment +
".";
137 path_stack.emplace_back(indent, key);
138 current_list_path = full_path;
141 value = strip_quotes(value);
147 [[nodiscard]]
static auto trim(std::string_view str) -> std::string {
148 auto start = str.find_first_not_of(
" \t\r\n");
149 if (start == std::string::npos)
return "";
150 auto end = str.find_last_not_of(
" \t\r\n");
151 return std::string(str.substr(start, end - start + 1));
154 [[nodiscard]]
static auto strip_quotes(std::string_view str) -> std::string {
155 if (str.length() >= 2) {
156 if ((str.front() ==
'"' && str.back() ==
'"') ||
157 (str.front() ==
'\'' && str.back() ==
'\'')) {
158 return std::string(str.substr(1, str.length() - 2));
161 return std::string(str);
172 std::ifstream file(path);
173 if (!file.is_open()) {
175 kcenon::common::error_info(
"Failed to open configuration file: " + path.string()));
180 return load_from_string(
buffer.str());
186 simple_yaml_parser parser(yaml_content);
191 config.
server.
port =
static_cast<std::uint16_t
>(
192 parser.get_int(
"server.port", config.
server.
port));
194 parser.get_int(
"server.max_associations",
197 parser.get_int(
"server.idle_timeout_seconds",
207 parser.get_bool(
"server.tls.require_client_cert",
false);
222 parser.get_bool(
"security.access_control.enabled",
true);
224 parser.get_string(
"security.access_control.default_role",
"viewer");
226 parser.get_list(
"security.allowed_ae_titles");
228 parser.get_bool(
"security.anonymization.auto_anonymize",
false);
230 auto profile_str = parser.get_string(
"security.anonymization.profile",
"basic");
241 parser.get_int(
"monitoring.health_check_interval_seconds", 30));
245 config.
logging.
level = parser.get_string(
"logging.level",
"info");
249 auto validation = validate(config);
250 if (!validation.is_ok()) {
252 kcenon::common::error_info(validation.error().message));
257 }
catch (
const std::exception& e) {
259 kcenon::common::error_info(std::string(
"Configuration parsing error: ") + e.what()));
301 -> kcenon::common::VoidResult {
303 if (config.server.ae_title.empty()) {
304 return kcenon::common::VoidResult::err(
305 kcenon::common::error_info(
"AE Title cannot be empty"));
307 if (config.server.ae_title.length() > 16) {
308 return kcenon::common::VoidResult::err(
309 kcenon::common::error_info(
"AE Title exceeds maximum length of 16 characters"));
313 if (config.server.port == 0) {
314 return kcenon::common::VoidResult::err(
315 kcenon::common::error_info(
"Server port cannot be 0"));
319 if (config.rest_api.enabled && config.rest_api.port == 0) {
320 return kcenon::common::VoidResult::err(
321 kcenon::common::error_info(
"REST API port cannot be 0"));
325 if (config.server.tls.enabled) {
326 if (config.server.tls.certificate.empty()) {
327 return kcenon::common::VoidResult::err(
328 kcenon::common::error_info(
"TLS enabled but no certificate specified"));
330 if (config.server.tls.private_key.empty()) {
331 return kcenon::common::VoidResult::err(
332 kcenon::common::error_info(
"TLS enabled but no private key specified"));
337 const std::vector<std::string> valid_schemes = {
338 "uid_flat",
"uid_hierarchical",
"date_based"
340 auto scheme_it = std::find(valid_schemes.begin(), valid_schemes.end(),
341 config.storage.naming_scheme);
342 if (scheme_it == valid_schemes.end()) {
343 return kcenon::common::VoidResult::err(
344 kcenon::common::error_info(
"Invalid naming scheme: " + config.storage.naming_scheme));
348 const std::vector<std::string> valid_policies = {
"reject",
"replace",
"rename"};
349 auto policy_it = std::find(valid_policies.begin(), valid_policies.end(),
350 config.storage.duplicate_policy);
351 if (policy_it == valid_policies.end()) {
352 return kcenon::common::VoidResult::err(
353 kcenon::common::error_info(
"Invalid duplicate policy: " + config.storage.duplicate_policy));
356 return kcenon::common::VoidResult::ok(std::monostate{});
364 auto v = trim(value);
365 std::transform(v.begin(), v.end(), v.begin(), ::tolower);
366 return v ==
"true" || v ==
"yes" || v ==
"on" || v ==
"1";
370 auto v = trim(value);
380 auto v = trim(value);
381 std::transform(v.begin(), v.end(), v.begin(), ::tolower);
383 if (v ==
"basic" || v ==
"basic_profile") {
386 if (v ==
"hipaa" || v ==
"hipaa_safe_harbor") {
389 if (v ==
"retain_longitudinal" || v ==
"retain_longitudinal_full_dates") {
392 if (v ==
"retain_device" || v ==
"retain_device_identity" ||
393 v ==
"retain_patient" || v ==
"retain_patient_characteristics") {
405 auto start = str.find_first_not_of(
" \t\r\n");
406 if (start == std::string::npos)
return "";
407 auto end = str.find_last_not_of(
" \t\r\n");
408 return std::string(str.substr(start, end - start + 1));
static auto create_default() -> production_config
Create a default configuration.
static auto parse_int(std::string_view value) -> int
Parse an integer value.
static auto load(const std::filesystem::path &path) -> kcenon::common::Result< production_config >
Load configuration from a YAML file.
static auto parse_anonymization_profile(std::string_view value) -> security::anonymization_profile
Parse anonymization profile.
static auto parse_bool(std::string_view value) -> bool
Parse a boolean value.
static auto validate(const production_config &config) -> kcenon::common::VoidResult
Validate a configuration.
static auto parse_value(std::string_view value) -> std::string
Parse a YAML value (simple types)
static auto load_from_string(std::string_view yaml_content) -> kcenon::common::Result< production_config >
Load configuration from a YAML string.
static auto trim(std::string_view str) -> std::string
Trim whitespace from string.
std::map< std::string, yaml_list > lists_
YAML configuration loader for Production PACS.
anonymization_profile
DICOM de-identification profiles based on PS3.15 Annex E.
@ hipaa_safe_harbor
HIPAA Safe Harbor - 18 identifier removal.
@ gdpr_compliant
GDPR Compliant - European data protection.
@ retain_longitudinal
Retain Longitudinal - Preserve temporal relationships.
@ retain_patient_characteristics
Retain Patient Characteristics.
@ basic
Basic Profile - Remove direct identifiers.
std::string default_role
Default role for unknown AE titles.
bool enabled
Enable access control enforcement.
security::anonymization_profile profile
Anonymization profile to use.
bool auto_anonymize
Automatically anonymize incoming images.
std::filesystem::path path
Path to SQLite database file.
std::filesystem::path audit_log_path
Path to audit log file.
std::string level
Log level: "debug", "info", "warn", "error".
std::chrono::seconds health_check_interval
Interval between health checks (seconds)
bool metrics_enabled
Enable Prometheus-style metrics endpoint.
Complete production PACS configuration.
monitoring_config monitoring
Monitoring settings.
logging_config logging
Logging settings.
storage_config storage
Storage settings.
rest_api_config rest_api
REST API settings.
security_config security
Security settings.
server_config server
DICOM server settings.
std::uint16_t port
HTTP port for REST API.
bool cors_enabled
Enable CORS for web clients.
bool enabled
Enable REST API server.
std::vector< std::string > allowed_ae_titles
Allowed AE titles (supports wildcards with *)
access_control_config access_control
Access control settings.
anonymization_config anonymization
Anonymization settings.
std::uint16_t port
TCP port for DICOM connections.
tls_config tls
TLS configuration.
std::size_t max_associations
Maximum concurrent associations.
std::chrono::seconds idle_timeout
Idle timeout for associations (seconds)
std::string ae_title
Application Entity title (max 16 characters)
std::string duplicate_policy
Duplicate handling: "reject", "replace", "rename".
std::string naming_scheme
Naming scheme: "uid_flat", "uid_hierarchical", "date_based".
database_config database
Database configuration.
std::filesystem::path root_path
Root path for DICOM file storage.
std::filesystem::path ca_certificate
Path to CA certificate file (PEM format)
std::string min_version
Minimum TLS version ("1.2" or "1.3")
std::filesystem::path certificate
Path to server certificate file (PEM format)
bool require_client_cert
Require client certificate (mutual TLS)
std::filesystem::path private_key
Path to private key file (PEM format)
bool enabled
Enable TLS for DICOM connections.