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

mbits-os / quick_dra / 22241471141

20 Feb 2026 09:14PM UTC coverage: 98.4% (-0.3%) from 98.659%
22241471141

Pull #42

github

web-flow
Merge 592ea7635 into 56bca2562
Pull Request #42: ci: adding coverage comment to GitHub PR

4306 of 4376 relevant lines covered (98.4%)

2394.2 hits per line

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

92.94
/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(
39✔
17
            std::filesystem::path const& path) {
18
                return parser::parse_yaml_file<config>(path, app_name);
156✔
19
        }
117✔
20

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

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

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

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

108✔
44
                if (passport) {
36✔
45
                        kind = "2"sv;
144✔
46
                        document = std::move(*passport);
144✔
47
                }
108✔
48

108✔
49
                return parse_and_validate_name(*this) &&
72✔
50
                       !(social_id.empty() || tax_id.empty() || kind.empty() ||
36✔
51
                         document.empty());
72✔
52
        }
108✔
53

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

108✔
57
                kind.clear();
36✔
58
                document.clear();
144✔
59

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

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

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

108✔
84
                if (kind.empty() || document.empty()) return false;
36✔
85
                return true;
36✔
86
        }
108✔
87

88
        std::optional<templates> templates::parse_yaml(
33✔
89
            std::filesystem::path const& path) {
90
                return parser::parse_yaml_file<templates>(path, app_name);
132✔
91
        }
99✔
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(
39✔
127
            std::filesystem::path const& path) {
128
                return parser::parse_yaml_file<tax_config>(path, app_name);
156✔
129
        }
117✔
130

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

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

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

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

666✔
154
                auto result = load_status::errors_encountered;
222✔
155

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

666✔
162
                *this = std::move(*object);
222✔
163
                return load_status::loaded;
888✔
164
        }
888✔
165

166
        config config::load_partial(std::filesystem::path const& path,
318✔
167
                                    bool writeable) {
168
                partial::config cfg{};
1,272✔
169
                auto const load = cfg.load(path);
1,272✔
170
                switch (load) {
1,272✔
171
                        case load_status::file_not_found:
96✔
172
                                if (writeable) {
96✔
173
                                        fmt::print(
434✔
174
                                            stderr,
279✔
175
                                            "Quick-DRA: file {} will be created as needed.\n",
279✔
176
                                            path);
279✔
177
                                }
279✔
178
                                break;
384✔
179
                        case load_status::file_not_readable:
180
                                fmt::print(stderr, "Quick-DRA: error: could not read {}\n",
×
181
                                           path);
182
                                std::exit(1);
×
183
                        case load_status::errors_encountered:
184
                                fmt::print(stderr, "Quick-DRA: error: {} needs to be updated\n",
×
185
                                           path);
186
                                std::exit(1);
×
187
                        default:
222✔
188
                                break;
888✔
189
                }
954✔
190
                return cfg;
318✔
191
        }
954✔
192

193
        bool config::store(std::filesystem::path const& path) {
246✔
194
                prepare_for_write();
984✔
195
                ryml::Tree tree{};
984✔
196
                auto ref = tree.rootref();
984✔
197
                yaml::write_value(ref, *this);
984✔
198

738✔
199
                ryml::csubstr output = ryml::emit_yaml(
1,230✔
200
                    tree, tree.root_id(), ryml::substr{}, /*error_on_excess*/ false);
1,148✔
201

738✔
202
                std::vector<char> buf(output.len);
1,148✔
203
                output = ryml::emit_yaml(tree, tree.root_id(), ryml::to_substr(buf),
1,066✔
204
                                         /*error_on_excess*/ true);
1,148✔
205

738✔
206
                std::ofstream out{path, std::ios::out | std::ios::binary};
984✔
207
                if (!out) {
246✔
208
                        return false;
48✔
209
                }
36✔
210

702✔
211
                out.write(output.data(), static_cast<std::streamsize>(output.size()));
546✔
212
                return true;
936✔
213
        }
984✔
214

215
        bool config::postprocess() {
222✔
216
                if (version && *version != kVersion) version = std::nullopt;
222✔
217
                if (!insured) insured.emplace();
222✔
218
                return true;
888✔
219
        }
666✔
220

221
        void config::preprocess() {
246✔
222
                if (!version) version = static_cast<unsigned short>(kVersion);
246✔
223
                if (!insured) insured.emplace();
246✔
224
        }
984✔
225

226
        template <typename Named>
227
        void parse_name(Named& named) noexcept {
411✔
228
                if (!named.last_name) return;
685✔
229

1,233✔
230
                auto const name = split_s(*named.last_name, ", "_sep, 1);
411✔
231
                if (name.size() == 2) {
411✔
232
                        named.first_name.emplace(strip_sv(name[1]));
1,644✔
233
                        named.last_name.emplace(strip_sv(name[0]));
1,644✔
234
                        if (!(named.last_name->empty() || named.first_name->empty())) {
411✔
235
                                return;
1,644✔
236
                        }
1,233✔
237
                }
1,233✔
238

239
                named.first_name = std::nullopt;
240
                named.last_name = std::nullopt;
×
241
        }
411✔
242

243
        template <typename Named>
244
        void preprocess_name(Named& named) noexcept {
441✔
245
                if (!named.last_name || !named.first_name) {
441✔
246
                        named.last_name = std::nullopt;
×
247
                        return;
×
248
                }
249

1,323✔
250
                named.last_name =
1,029✔
251
                    fmt::format("{}, {}", *named.last_name, *named.first_name);
1,911✔
252
                named.first_name = std::nullopt;
1,764✔
253
        }
1,323✔
254

255
        void payer_t::postprocess_document_kind() noexcept {
54✔
256
                kind = std::nullopt;
216✔
257
                document = std::nullopt;
216✔
258

162✔
259
                if (id_card) {
54✔
260
                        kind = "1"sv;
132✔
261
                        document = std::move(*id_card);
132✔
262
                } else if (passport) {
120✔
263
                        kind = "2"sv;
48✔
264
                        document = std::move(*passport);
48✔
265
                }
36✔
266
        }
216✔
267

268
        bool payer_t::postprocess() {
15✔
269
                postprocess_document_kind();
60✔
270

45✔
271
                parse_name(*this);
60✔
272

45✔
273
#define NULLIFY(STR)           \
45✔
274
        if (STR && STR->empty()) { \
275
                STR.reset();           \
1,188✔
276
        }
1,188✔
277

45✔
278
                NULLIFY(social_id);
15✔
279
                NULLIFY(tax_id);
15✔
280
                NULLIFY(kind);
15✔
281
                NULLIFY(document);
15✔
282
                return true;
60✔
283
        }
45✔
284

285
        void payer_t::preprocess() {
18✔
286
                preprocess_name(*this);
72✔
287
                preprocess_document_kind();
72✔
288
        }
72✔
289

290
        void payer_t::preprocess_document_kind() noexcept {
60✔
291
                auto kind_ = kind.value_or(""s);
240✔
292
                auto document_ = document.value_or(""s);
240✔
293

180✔
294
                id_card = std::nullopt;
240✔
295
                passport = std::nullopt;
240✔
296
                kind = std::nullopt;
240✔
297
                document = std::nullopt;
240✔
298

180✔
299
                if (kind_ == "1"sv) {
60✔
300
                        id_card = std::move(document_);
120✔
301
                } else if (kind_ == "2"sv) {
120✔
302
                        passport = std::move(document_);
84✔
303
                }
63✔
304
        }
240✔
305

306
        void insured_t::postprocess_document_kind() noexcept {
837✔
307
                kind = std::nullopt;
3,348✔
308
                document = std::nullopt;
3,348✔
309

2,511✔
310
                if (id_card) {
837✔
311
                        kind = "1"sv;
1,488✔
312
                        document = std::move(*id_card);
1,488✔
313
                } else if (passport) {
1,860✔
314
                        kind = "2"sv;
1,068✔
315
                        document = std::move(*passport);
1,068✔
316
                } else if (social_id) {
999✔
317
                        kind = "P"sv;
792✔
318
                        document = std::move(*social_id);
792✔
319
                }
594✔
320
        }
3,348✔
321

322
        bool insured_t::postprocess() {
396✔
323
                postprocess_document_kind();
1,584✔
324
                parse_name(*this);
1,584✔
325

1,188✔
326
                NULLIFY(id_card);
396✔
327
                NULLIFY(passport);
396✔
328
                NULLIFY(social_id);
396✔
329
                NULLIFY(kind);
396✔
330
                NULLIFY(document);
396✔
331

1,188✔
332
                return true;
1,584✔
333
        }
1,188✔
334

335
        void insured_t::preprocess() {
423✔
336
                preprocess_name(*this);
1,692✔
337
                preprocess_document_kind();
1,692✔
338
        }
1,692✔
339

340
        void insured_t::preprocess_document_kind() noexcept {
867✔
341
                auto kind_ = kind.value_or(""s);
3,468✔
342
                auto document_ = document.value_or(""s);
3,468✔
343

2,601✔
344
                id_card = std::nullopt;
3,468✔
345
                passport = std::nullopt;
3,468✔
346
                social_id = std::nullopt;
3,468✔
347
                kind = std::nullopt;
3,468✔
348
                document = std::nullopt;
3,468✔
349

2,601✔
350
                if (kind_ == "1"sv) {
867✔
351
                        id_card = std::move(document_);
1,560✔
352
                } else if (kind_ == "2"sv) {
1,908✔
353
                        passport = std::move(document_);
1,116✔
354
                } else if (kind_ == "P"sv) {
1,035✔
355
                        social_id = std::move(document_);
792✔
356
                }
594✔
357
        }
3,468✔
358
}  // 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