• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mbits-os / quick_dra / 22038263728

15 Feb 2026 03:31PM UTC coverage: 93.378% (+6.4%) from 87.008%
22038263728

Pull #39

github

web-flow
Merge 74a89bbd4 into 7d773e4d9
Pull Request #39: testing

58 of 67 new or added lines in 23 files covered. (86.57%)

75 existing lines in 12 files now uncovered.

4033 of 4319 relevant lines covered (93.38%)

1107.01 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.49
/libs/libmodels/src/models/parser_impl.cpp
1
// Copyright (c) 2026 midnightBITS
2
// This code is licensed under MIT license (see LICENSE for details)
3

4
#include <fmt/format.h>
5
#include <fmt/std.h>
6
#include <cmath>
7
#include <fstream>
8
#include <quick_dra/base/str.hpp>
9
#include <quick_dra/models/project_reader.hpp>
10

11
namespace quick_dra::v1 {
12
        namespace {
13
                static constexpr auto app_name = "Quick-DRA"sv;
14
        }
15

16
        std::optional<config> config::parse_yaml(
21✔
17
            std::filesystem::path const& path) {
1✔
18
                return parser::parse_yaml_file<config>(path, app_name);
64✔
19
        }
43✔
20

21
        bool config::postprocess() { return version == kVersion; }
22✔
22

23
        template <typename Named>
24
        bool parse_and_validate_name(Named& named) noexcept {
43✔
25
                auto const name = split_sv(named.last_name, ", "_sep, 1);
127✔
26
                if (name.size() != 2) return false;
43✔
27
                named.first_name = strip_sv(name[1]);
43✔
28
                named.last_name = strip_sv(name[0]);
127✔
29
                return !(named.last_name.empty() || named.first_name.empty());
43✔
30
        }
127✔
31

32
        bool payer_t::postprocess() {
22✔
33
                kind.clear();
64✔
34
                document.clear();
64✔
35

42✔
36
                if (id_card) {
22✔
37
                        if (passport) {
38
                                return false;
×
39
                        }
40
                        kind = "1"sv;
41
                        document = std::move(*id_card);
×
42
                }
43

42✔
44
                if (passport) {
22✔
45
                        kind = "2"sv;
64✔
46
                        document = std::move(*passport);
64✔
47
                }
42✔
48

42✔
49
                return parse_and_validate_name(*this) &&
43✔
50
                       !(social_id.empty() || tax_id.empty() || kind.empty() ||
21✔
51
                         document.empty());
42✔
52
        }
43✔
53

54
        bool insured_t::postprocess() {
22✔
55
                if (!parse_and_validate_name(*this)) return false;
22✔
56

42✔
57
                kind.clear();
22✔
58
                document.clear();
64✔
59

42✔
60
                if (social_id) {
22✔
61
                        if (id_card || passport) {
22✔
62
                                return false;
×
63
                        }
64
                        kind = "P"sv;
22✔
65
                        document = std::move(*social_id);
64✔
66
                }
42✔
67

42✔
68
                if (id_card) {
22✔
69
                        if (social_id || passport) {
70
                                return false;
×
71
                        }
72
                        kind = "1"sv;
73
                        document = std::move(*id_card);
×
74
                }
75

42✔
76
                if (passport) {
22✔
77
                        if (social_id || id_card) {
78
                                return false;
×
79
                        }
80
                        kind = "2"sv;
81
                        document = std::move(*passport);
×
82
                }
83

42✔
84
                if (kind.empty() || document.empty()) return false;
22✔
85
                return true;
22✔
86
        }
43✔
87

88
        std::optional<templates> templates::parse_yaml(
21✔
89
            std::filesystem::path const& path) {
1✔
90
                return parser::parse_yaml_file<templates>(path, app_name);
64✔
91
        }
43✔
92

93
        bool templates::validate() noexcept {
94
                for (auto& [kedu, report] : reports) {
×
95
                        if (kedu.empty()) return false;
96
                        for (auto& section : report) {
97
                                if (!section.validate()) return false;
98
                        }
99
                }
100
                return true;
101
        }
102

103
        bool report_section::validate() noexcept {
104
                if (id.empty()) return false;
105

106
                struct validator {
107
                        bool operator()(std::string const& str) const noexcept {
108
                                return !str.empty();
×
109
                        }
110
                        bool operator()(
111
                            std::vector<std::string> const& vec) const noexcept {
112
                                if (vec.empty()) return false;
113
                                for (auto const& str : vec) {
114
                                        if (str.empty()) return false;
115
                                }
116
                                return true;
117
                        }
118
                } value{};
119

120
                for (auto const& [_, field] : fields) {
121
                        if (!std::visit(value, field)) return false;
122
                }
123
                return true;
124
        }
125

126
        std::optional<tax_config> tax_config::parse_yaml(
21✔
127
            std::filesystem::path const& path) {
1✔
128
                return parser::parse_yaml_file<tax_config>(path, app_name);
64✔
129
        }
43✔
130

131
        std::optional<tax_config> tax_config::parse_from_text(
21✔
132
            std::string const& text,
133
            std::string const& path) {
1✔
134
                return parser::parse_yaml_text<tax_config>(text, path);
64✔
135
        }
43✔
136

137
        bool tax_config::postprocess() { return version == kVersion; }
43✔
138

139
        void tax_config::merge(tax_config&& newer) {
43✔
140
                scale.merge(std::move(newer.scale));
127✔
141
                minimal_pay.merge(std::move(newer.minimal_pay));
127✔
142
                costs_of_obtaining.merge(std::move(newer.costs_of_obtaining));
127✔
143
                contributions.merge(std::move(newer.contributions));
127✔
144
        }
127✔
145
}  // namespace quick_dra::v1
146

147
namespace quick_dra::v1::partial {
148
        load_status config::load(std::filesystem::path const& path) {
148✔
149
                std::error_code ec{};
442✔
150
                if (!std::filesystem::exists(path, ec) || ec) {
148✔
151
                        return load_status::file_not_found;
163✔
152
                }
108✔
153

186✔
154
                auto result = load_status::errors_encountered;
94✔
155

186✔
156
                auto object = parser::parse_yaml_file<config>(
218✔
157
                    path, [&]() { result = load_status::file_not_readable; });
94✔
158
                if (!object) {
94✔
159
                        return result;
×
160
                }
161

186✔
162
                *this = std::move(*object);
94✔
163
                auto const [needed, present] = count_loaded();
280✔
164
                return needed < present ? needed == 0 ? load_status::empty
94✔
165
                                                      : load_status::partially_loaded
166
                                        : load_status::fully_loaded;
93✔
167
        }
280✔
168

169
        config config::load_partial(std::filesystem::path const& path,
147✔
170
                                    bool writeable) {
1✔
171
                partial::config cfg{};
442✔
172
                auto const load = cfg.load(path);
442✔
173
                switch (load) {
442✔
174
                        case load_status::file_not_found:
54✔
175
                                if (writeable) {
55✔
176
                                        fmt::print(
188✔
177
                                            stderr,
102✔
178
                                            "Quick-DRA: file {} will be created as needed.\n",
102✔
179
                                            path);
102✔
180
                                }
102✔
181
                                break;
163✔
182
                        case load_status::file_not_readable:
UNCOV
183
                                fmt::print(stderr, "Quick-DRA: error: could not read {}\n",
×
184
                                           path);
UNCOV
185
                                std::exit(1);
×
186
                        case load_status::errors_encountered:
UNCOV
187
                                fmt::print(stderr, "Quick-DRA: error: {} needs to be updated\n",
×
188
                                           path);
UNCOV
189
                                std::exit(1);
×
190
                        default:
93✔
191
                                break;
279✔
192
                }
294✔
193
                return cfg;
148✔
194
        }
295✔
195

196
        bool config::store(std::filesystem::path const& path) {
115✔
197
                prepare_for_write();
343✔
198
                ryml::Tree tree{};
343✔
199
                auto ref = tree.rootref();
343✔
200
                yaml::write_value(ref, *this);
343✔
201

228✔
202
                ryml::csubstr output = ryml::emit_yaml(
457✔
203
                    tree, tree.root_id(), ryml::substr{}, /*error_on_excess*/ false);
418✔
204

228✔
205
                std::vector<char> buf(output.len);
419✔
206
                output = ryml::emit_yaml(tree, tree.root_id(), ryml::to_substr(buf),
381✔
207
                                         /*error_on_excess*/ true);
418✔
208

228✔
209
                std::ofstream out{path, std::ios::out | std::ios::binary};
343✔
210
                if (!out) {
115✔
211
                        return false;
10✔
212
                }
6✔
213

222✔
214
                out.write(output.data(), static_cast<std::streamsize>(output.size()));
260✔
215
                return true;
334✔
216
        }
343✔
217

218
        bool config::postprocess() {
94✔
219
                if (version && *version != kVersion) version = std::nullopt;
94✔
220
                if (!insured) insured.emplace();
94✔
221
                return true;
280✔
222
        }
187✔
223

224
        void config::preprocess() {
115✔
225
                if (!version) version = static_cast<unsigned short>(kVersion);
115✔
226
                if (!insured) insured.emplace();
115✔
227
        }
343✔
228

229
        template <typename Named>
230
        void parse_name(Named& named) noexcept {
163✔
231
                if (!named.last_name) return;
271✔
232

324✔
233
                auto const name = split_s(*named.last_name, ", "_sep, 1);
163✔
234
                if (name.size() == 2) {
163✔
235
                        named.first_name.emplace(strip_sv(name[1]));
487✔
236
                        named.last_name.emplace(strip_sv(name[0]));
487✔
237
                        if (!(named.last_name->empty() || named.first_name->empty())) {
163✔
238
                                return;
487✔
239
                        }
324✔
240
                }
324✔
241

242
                named.first_name = std::nullopt;
UNCOV
243
                named.last_name = std::nullopt;
×
244
        }
162✔
245

246
        template <typename Named>
247
        void preprocess_name(Named& named) noexcept {
187✔
248
                if (!named.last_name || !named.first_name) {
187✔
UNCOV
249
                        named.last_name = std::nullopt;
×
UNCOV
250
                        return;
×
251
                }
252

372✔
253
                named.last_name =
435✔
254
                    fmt::format("{}, {}", *named.last_name, *named.first_name);
620✔
255
                named.first_name = std::nullopt;
559✔
256
        }
373✔
257

258
        void payer_t::postprocess_document_kind() noexcept {
31✔
259
                kind = std::nullopt;
91✔
260
                document = std::nullopt;
91✔
261

60✔
262
                if (id_card) {
31✔
263
                        kind = "1"sv;
55✔
264
                        document = std::move(*id_card);
55✔
265
                } else if (passport) {
49✔
266
                        kind = "2"sv;
28✔
267
                        document = std::move(*passport);
28✔
268
                }
18✔
269
        }
91✔
270

271
        bool payer_t::postprocess() {
7✔
272
                postprocess_document_kind();
19✔
273

12✔
274
                parse_name(*this);
19✔
275

12✔
276
#define NULLIFY(STR)           \
12✔
277
        if (STR && STR->empty()) { \
278
                STR.reset();           \
312✔
279
        }
312✔
280

12✔
281
                NULLIFY(social_id);
7✔
282
                NULLIFY(tax_id);
7✔
283
                NULLIFY(kind);
7✔
284
                NULLIFY(document);
7✔
285
                return true;
19✔
286
        }
13✔
287

288
        void payer_t::preprocess() {
13✔
289
                preprocess_name(*this);
37✔
290
                preprocess_document_kind();
37✔
291
        }
37✔
292

293
        void payer_t::preprocess_document_kind() noexcept {
37✔
294
                auto kind_ = kind.value_or(""s);
109✔
295
                auto document_ = document.value_or(""s);
109✔
296

72✔
297
                id_card = std::nullopt;
109✔
298
                passport = std::nullopt;
109✔
299
                kind = std::nullopt;
109✔
300
                document = std::nullopt;
109✔
301

72✔
302
                if (kind_ == "1"sv) {
37✔
303
                        id_card = std::move(document_);
55✔
304
                } else if (kind_ == "2"sv) {
55✔
305
                        passport = std::move(document_);
46✔
306
                }
30✔
307
        }
109✔
308

309
        void insured_t::postprocess_document_kind() noexcept {
349✔
310
                kind = std::nullopt;
1,045✔
311
                document = std::nullopt;
1,045✔
312

696✔
313
                if (id_card) {
349✔
314
                        kind = "1"sv;
514✔
315
                        document = std::move(*id_card);
514✔
316
                } else if (passport) {
532✔
317
                        kind = "2"sv;
334✔
318
                        document = std::move(*passport);
334✔
319
                } else if (social_id) {
289✔
320
                        kind = "P"sv;
199✔
321
                        document = std::move(*social_id);
199✔
322
                }
132✔
323
        }
1,045✔
324

325
        bool insured_t::postprocess() {
157✔
326
                postprocess_document_kind();
469✔
327
                parse_name(*this);
469✔
328

312✔
329
                NULLIFY(id_card);
157✔
330
                NULLIFY(passport);
157✔
331
                NULLIFY(social_id);
157✔
332
                NULLIFY(kind);
157✔
333
                NULLIFY(document);
157✔
334

312✔
335
                return true;
469✔
336
        }
313✔
337

338
        void insured_t::preprocess() {
175✔
339
                preprocess_name(*this);
523✔
340
                preprocess_document_kind();
523✔
341
        }
523✔
342

343
        void insured_t::preprocess_document_kind() noexcept {
367✔
344
                auto kind_ = kind.value_or(""s);
1,099✔
345
                auto document_ = document.value_or(""s);
1,099✔
346

732✔
347
                id_card = std::nullopt;
1,099✔
348
                passport = std::nullopt;
1,099✔
349
                social_id = std::nullopt;
1,099✔
350
                kind = std::nullopt;
1,099✔
351
                document = std::nullopt;
1,099✔
352

732✔
353
                if (kind_ == "1"sv) {
367✔
354
                        id_card = std::move(document_);
541✔
355
                } else if (kind_ == "2"sv) {
559✔
356
                        passport = std::move(document_);
361✔
357
                } else if (kind_ == "P"sv) {
307✔
358
                        social_id = std::move(document_);
199✔
359
                }
132✔
360
        }
1,099✔
361
}  // namespace quick_dra::v1::partial
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc