Skip to content

Commit 80206f7

Browse files
committed
Break out ConfigResult
1 parent 792a29a commit 80206f7

File tree

10 files changed

+131
-129
lines changed

10 files changed

+131
-129
lines changed

include/config/config_result.h

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/** @file
2+
3+
Generic configuration parsing result type.
4+
5+
@section license License
6+
7+
Licensed to the Apache Software Foundation (ASF) under one
8+
or more contributor license agreements. See the NOTICE file
9+
distributed with this work for additional information
10+
regarding copyright ownership. The ASF licenses this file
11+
to you under the Apache License, Version 2.0 (the
12+
"License"); you may not use this file except in compliance
13+
with the License. You may obtain a copy of the License at
14+
15+
http://www.apache.org/licenses/LICENSE-2.0
16+
17+
Unless required by applicable law or agreed to in writing, software
18+
distributed under the License is distributed on an "AS IS" BASIS,
19+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20+
See the License for the specific language governing permissions and
21+
limitations under the License.
22+
*/
23+
24+
#pragma once
25+
26+
#include "swoc/Errata.h"
27+
28+
namespace config
29+
{
30+
31+
/**
32+
* Result of a configuration parse operation.
33+
*
34+
* This template is the standard return type for all configuration parsers in
35+
* the config library. It bundles a parsed configuration value with an errata
36+
* that may contain warnings or errors encountered during parsing.
37+
*
38+
* Design rationale:
39+
* - Parsers can return partial results (value populated) even when warnings
40+
* are present, allowing callers to decide how to handle degraded configs.
41+
* - The ok() method checks whether parsing succeeded without errors, but
42+
* callers should also inspect the errata for warnings.
43+
* - This type is reused across all configuration file types (ssl_multicert,
44+
* etc.) to provide a consistent API.
45+
*
46+
* Example usage:
47+
* @code
48+
* config::SSLMultiCertParser parser;
49+
* auto result = parser.parse("/path/to/ssl_multicert.yaml");
50+
* if (!result.ok()) {
51+
* // Handle error
52+
* return;
53+
* }
54+
* for (const auto& entry : result.value) {
55+
* // Use parsed entries
56+
* }
57+
* @endcode
58+
*
59+
* @tparam T The configuration type (e.g., SSLMultiCertConfig).
60+
*/
61+
template <typename T> struct ConfigResult {
62+
T value; ///< The parsed configuration value.
63+
swoc::Errata errata; ///< Errors or warnings from parsing.
64+
65+
/**
66+
* Check if parsing succeeded without errors.
67+
*
68+
* @return true if no errors occurred, false otherwise.
69+
*/
70+
bool
71+
ok() const
72+
{
73+
return errata.is_ok();
74+
}
75+
};
76+
77+
} // namespace config

include/config/ssl_multicert.h

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
#include <string_view>
2929
#include <vector>
3030

31-
#include "swoc/Errata.h"
31+
#include "config/config_result.h"
3232

3333
namespace config
3434
{
@@ -52,27 +52,6 @@ struct SSLMultiCertEntry {
5252
/// A configuration is a vector of certificate entries.
5353
using SSLMultiCertConfig = std::vector<SSLMultiCertEntry>;
5454

55-
/**
56-
* Result of a configuration parse operation.
57-
*
58-
* @tparam T The configuration type.
59-
*/
60-
template <typename T> struct ConfigResult {
61-
T value; ///< The parsed configuration value.
62-
swoc::Errata errata; ///< Errors or warnings from parsing.
63-
64-
/**
65-
* Check if parsing succeeded without errors.
66-
*
67-
* @return true if no errors occurred, false otherwise.
68-
*/
69-
bool
70-
ok() const
71-
{
72-
return errata.is_ok();
73-
}
74-
};
75-
7655
/**
7756
* Parser for ssl_multicert configuration files.
7857
*

src/config/CMakeLists.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
#
1616
#######################
1717

18-
set(CONFIG_PUBLIC_HEADERS ${PROJECT_SOURCE_DIR}/include/config/ssl_multicert.h)
18+
set(CONFIG_PUBLIC_HEADERS ${PROJECT_SOURCE_DIR}/include/config/config_result.h
19+
${PROJECT_SOURCE_DIR}/include/config/ssl_multicert.h
20+
)
1921

2022
add_library(tsconfig ssl_multicert.cc)
2123

src/config/README.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@ used by various ATS components including `traffic_server` and `traffic_ctl`.
55

66
## Architecture Overview
77

8-
Each configuration file type (ssl_multicert, logging, sni, etc.) follows the
9-
same pattern:
8+
Each configuration file type (ssl_multicert, etc.) follows the same pattern:
109

1110
```
1211
include/config/<config_name>.h - Header with data types, parser, marshaller
@@ -73,7 +72,7 @@ The parser automatically detects the configuration format:
7372

7473
## Adding a New Configuration Type
7574

76-
To add support for a new configuration (e.g., `logging.yaml`):
75+
To add support for a new configuration (maybe, `logging.yaml`):
7776

7877
1. Create `include/config/logging.h`:
7978
- Define `LoggingEntry` struct with fields matching the config

src/config/ssl_multicert.cc

Lines changed: 35 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
#include <cctype>
2828
#include <exception>
2929
#include <set>
30-
#include <sstream>
3130

3231
#include <yaml-cpp/yaml.h>
3332

@@ -100,43 +99,36 @@ parse_legacy_line(swoc::TextView line)
10099
return result;
101100
}
102101

103-
/// Escape a string for YAML output if needed.
104-
std::string
105-
yaml_escape(std::string const &value)
102+
/// Emit a single SSLMultiCertEntry to a YAML::Emitter.
103+
void
104+
emit_entry(YAML::Emitter &emitter, config::SSLMultiCertEntry const &entry)
106105
{
107-
if (value.empty()) {
108-
return "\"\"";
109-
}
106+
emitter << YAML::BeginMap;
110107

111-
bool needs_quotes = false;
112-
if (value[0] == '*' || value[0] == '!' || value[0] == '&' || value[0] == '{' || value[0] == '}' || value[0] == '[' ||
113-
value[0] == ']' || value[0] == ',' || value[0] == '#' || value[0] == '?' || value[0] == '-' || value[0] == ':' ||
114-
value[0] == '>' || value[0] == '|' || value[0] == '@' || value[0] == '`' || value[0] == '"' || value[0] == '\'') {
115-
needs_quotes = true;
116-
} else if (value.find(':') != std::string::npos || value.find('#') != std::string::npos) {
117-
needs_quotes = true;
118-
} else if (value == "true" || value == "false" || value == "yes" || value == "no" || value == "null" || value == "True" ||
119-
value == "False" || value == "Yes" || value == "No" || value == "Null") {
120-
needs_quotes = true;
121-
}
108+
auto write_field = [&](char const *key, std::string const &value) {
109+
if (!value.empty()) {
110+
emitter << YAML::Key << key << YAML::Value << value;
111+
}
112+
};
122113

123-
if (needs_quotes) {
124-
std::string escaped;
125-
escaped.reserve(value.size() + 2);
126-
escaped += '"';
127-
for (char c : value) {
128-
if (c == '\\') {
129-
escaped += "\\\\";
130-
} else if (c == '"') {
131-
escaped += "\\\"";
132-
} else {
133-
escaped += c;
134-
}
114+
auto write_int_field = [&](char const *key, std::optional<int> const &value) {
115+
if (value.has_value()) {
116+
emitter << YAML::Key << key << YAML::Value << value.value();
135117
}
136-
escaped += '"';
137-
return escaped;
138-
}
139-
return value;
118+
};
119+
120+
write_field(KEY_SSL_CERT_NAME, entry.ssl_cert_name);
121+
write_field(KEY_DEST_IP, entry.dest_ip);
122+
write_field(KEY_SSL_KEY_NAME, entry.ssl_key_name);
123+
write_field(KEY_SSL_CA_NAME, entry.ssl_ca_name);
124+
write_field(KEY_SSL_OCSP_NAME, entry.ssl_ocsp_name);
125+
write_field(KEY_SSL_KEY_DIALOG, entry.ssl_key_dialog);
126+
write_field(KEY_DEST_FQDN, entry.dest_fqdn);
127+
write_field(KEY_ACTION, entry.action);
128+
write_int_field(KEY_SSL_TICKET_ENABLED, entry.ssl_ticket_enabled);
129+
write_int_field(KEY_SSL_TICKET_NUMBER, entry.ssl_ticket_number);
130+
131+
emitter << YAML::EndMap;
140132
}
141133

142134
} // namespace
@@ -268,9 +260,8 @@ SSLMultiCertParser::parse_yaml(std::string_view content)
268260
return {result, swoc::Errata("expected 'ssl_multicert' to be a sequence")};
269261
}
270262

271-
for (auto it = entries.begin(); it != entries.end(); ++it) {
272-
YAML::Node entry_node = *it;
273-
auto const mark = entry_node.Mark();
263+
for (auto const &entry_node : entries) {
264+
auto const mark = entry_node.Mark();
274265
if (!entry_node.IsMap()) {
275266
return {result, swoc::Errata(ERRATA_ERROR_SEV, "Expected ssl_multicert entries to be maps at line {}, column {}", mark.line,
276267
mark.column)};
@@ -352,41 +343,16 @@ SSLMultiCertParser::parse_legacy(std::string_view content)
352343
std::string
353344
SSLMultiCertMarshaller::to_yaml(SSLMultiCertConfig const &config)
354345
{
355-
std::ostringstream out;
356-
out << "ssl_multicert:\n";
346+
YAML::Emitter yaml;
347+
yaml << YAML::BeginMap;
348+
yaml << YAML::Key << KEY_SSL_MULTICERT << YAML::Value << YAML::BeginSeq;
357349

358350
for (auto const &entry : config) {
359-
bool first = true;
360-
361-
auto write_field = [&](char const *key, std::string const &value) {
362-
if (value.empty()) {
363-
return;
364-
}
365-
out << (first ? " - " : " ") << key << ": " << yaml_escape(value) << "\n";
366-
first = false;
367-
};
368-
369-
auto write_int_field = [&](char const *key, std::optional<int> const &value) {
370-
if (!value.has_value()) {
371-
return;
372-
}
373-
out << (first ? " - " : " ") << key << ": " << value.value() << "\n";
374-
first = false;
375-
};
376-
377-
write_field(KEY_SSL_CERT_NAME, entry.ssl_cert_name);
378-
write_field(KEY_DEST_IP, entry.dest_ip);
379-
write_field(KEY_SSL_KEY_NAME, entry.ssl_key_name);
380-
write_field(KEY_SSL_CA_NAME, entry.ssl_ca_name);
381-
write_field(KEY_SSL_OCSP_NAME, entry.ssl_ocsp_name);
382-
write_field(KEY_SSL_KEY_DIALOG, entry.ssl_key_dialog);
383-
write_field(KEY_DEST_FQDN, entry.dest_fqdn);
384-
write_field(KEY_ACTION, entry.action);
385-
write_int_field(KEY_SSL_TICKET_ENABLED, entry.ssl_ticket_enabled);
386-
write_int_field(KEY_SSL_TICKET_NUMBER, entry.ssl_ticket_number);
351+
emit_entry(yaml, entry);
387352
}
388353

389-
return out.str();
354+
yaml << YAML::EndSeq << YAML::EndMap;
355+
return yaml.c_str();
390356
}
391357

392358
std::string
@@ -398,32 +364,7 @@ SSLMultiCertMarshaller::to_json(SSLMultiCertConfig const &config)
398364
json << YAML::Key << KEY_SSL_MULTICERT << YAML::Value << YAML::BeginSeq;
399365

400366
for (auto const &entry : config) {
401-
json << YAML::BeginMap;
402-
403-
auto write_field = [&](char const *key, std::string const &value) {
404-
if (!value.empty()) {
405-
json << YAML::Key << key << YAML::Value << value;
406-
}
407-
};
408-
409-
auto write_int_field = [&](char const *key, std::optional<int> const &value) {
410-
if (value.has_value()) {
411-
json << YAML::Key << key << YAML::Value << value.value();
412-
}
413-
};
414-
415-
write_field(KEY_SSL_CERT_NAME, entry.ssl_cert_name);
416-
write_field(KEY_DEST_IP, entry.dest_ip);
417-
write_field(KEY_SSL_KEY_NAME, entry.ssl_key_name);
418-
write_field(KEY_SSL_CA_NAME, entry.ssl_ca_name);
419-
write_field(KEY_SSL_OCSP_NAME, entry.ssl_ocsp_name);
420-
write_field(KEY_SSL_KEY_DIALOG, entry.ssl_key_dialog);
421-
write_field(KEY_DEST_FQDN, entry.dest_fqdn);
422-
write_field(KEY_ACTION, entry.action);
423-
write_int_field(KEY_SSL_TICKET_ENABLED, entry.ssl_ticket_enabled);
424-
write_int_field(KEY_SSL_TICKET_NUMBER, entry.ssl_ticket_number);
425-
426-
json << YAML::EndMap;
367+
emit_entry(json, entry);
427368
}
428369

429370
json << YAML::EndSeq << YAML::EndMap;

src/config/unit_tests/test_ssl_multicert.cc

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ TEST_CASE("SSLMultiCertMarshaller produces valid JSON", "[ssl_multicert][marshal
297297
CHECK(json.find(']') != std::string::npos);
298298
}
299299

300-
TEST_CASE("SSLMultiCertMarshaller escapes special characters", "[ssl_multicert][marshaller][escaping]")
300+
TEST_CASE("SSLMultiCertMarshaller handles special characters", "[ssl_multicert][marshaller][escaping]")
301301
{
302302
SSLMultiCertConfig config;
303303

@@ -309,11 +309,16 @@ TEST_CASE("SSLMultiCertMarshaller escapes special characters", "[ssl_multicert][
309309

310310
SSLMultiCertMarshaller marshaller;
311311

312-
SECTION("YAML escapes quotes")
312+
SECTION("YAML output contains the field and can be re-parsed")
313313
{
314314
std::string yaml = marshaller.to_yaml(config);
315315
CHECK(yaml.find("ssl_key_dialog:") != std::string::npos);
316-
CHECK(yaml.find("\\\"with quotes\\\"") != std::string::npos);
316+
317+
// Verify round-trip preserves the value.
318+
auto result = parse_content(yaml, "test.yaml");
319+
REQUIRE(result.ok());
320+
REQUIRE(result.value.size() == 1);
321+
CHECK(result.value[0].ssl_key_dialog == "exec:/path/to/script \"with quotes\"");
317322
}
318323

319324
SECTION("JSON escapes quotes")

src/traffic_ctl/ConvertConfigCommand.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,14 +66,14 @@ ConvertConfigCommand::convert_ssl_multicert()
6666

6767
// Write to output file or stdout if output is "-".
6868
if (_output_file == "-") {
69-
std::cout << yaml_output;
69+
std::cout << yaml_output << '\n';
7070
} else {
7171
std::ofstream out(_output_file);
7272
if (!out) {
7373
_printer->write_output("Failed to open output file '" + _output_file + "' for writing");
7474
return;
7575
}
76-
out << yaml_output;
76+
out << yaml_output << '\n';
7777
out.close();
7878
_printer->write_output("Converted " + _input_file + " -> " + _output_file);
7979
}

tests/gold_tests/traffic_ctl/convert_ssl_multicert/gold/full.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ ssl_multicert:
55
ssl_ticket_enabled: 1
66
ssl_ticket_number: 3
77
- ssl_cert_name: ecdsa.pem,rsa.pem
8-
dest_ip: "10.0.0.1:443"
8+
dest_ip: 10.0.0.1:443
99
ssl_key_name: ecdsa.key,rsa.key
1010
ssl_ca_name: ca.pem
1111
ssl_ocsp_name: ocsp.der
1212
- ssl_cert_name: encrypted.pem
1313
dest_ip: "*"
14-
ssl_key_dialog: "exec:/usr/bin/getpass foo"
14+
ssl_key_dialog: exec:/usr/bin/getpass foo
1515
- dest_ip: 192.168.1.1
1616
action: tunnel
1717
- ssl_cert_name: wildcard.pem
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
ssl_multicert:
22
- ssl_cert_name: server.pem
33
dest_ip: "*"
4-
ssl_key_dialog: "exec:/usr/bin/getpass arg1 'arg 2'"
4+
ssl_key_dialog: exec:/usr/bin/getpass arg1 'arg 2'
55
- ssl_cert_name: another.pem
66
dest_ip: "[::1]:8443"

tests/gold_tests/traffic_ctl/show_ssl_multicert/gold/show_yaml.gold

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,3 @@ ssl_multicert:
22
- ssl_cert_name: server.pem
33
dest_ip: "*"
44
ssl_key_name: server.key
5-

0 commit comments

Comments
 (0)