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

mbits-os / quick_dra / 22263246436

21 Feb 2026 07:51PM UTC coverage: 98.659%. Remained the same
22263246436

push

github

mzdun
ci: adding coverage comment to GitHub PR

4562 of 4624 relevant lines covered (98.66%)

2266.46 hits per line

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

93.44
/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
#include <string>
11
#include <utility>
12
#include <vector>
13

14
namespace quick_dra::v1 {
15
        namespace {
16
                static constexpr auto app_name = "Quick-DRA"sv;
17
        }
18

19
        std::optional<config> config::parse_yaml(
39✔
20
            std::filesystem::path const& path) {
1✔
21
                return parser::parse_yaml_file<config>(path, app_name);
157✔
22
        }
118✔
23

24
        bool config::postprocess() { return version == kVersion; }
37✔
25

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

35
        bool payer_t::postprocess() {
37✔
36
                kind.clear();
145✔
37
                document.clear();
145✔
38

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

108✔
47
                if (passport) {
37✔
48
                        kind = "2"sv;
145✔
49
                        document = std::move(*passport);
145✔
50
                }
108✔
51

108✔
52
                return parse_and_validate_name(*this) &&
73✔
53
                       !(social_id.empty() || tax_id.empty() || kind.empty() ||
36✔
54
                         document.empty());
72✔
55
        }
109✔
56

57
        bool insured_t::postprocess() {
37✔
58
                if (!parse_and_validate_name(*this)) return false;
37✔
59

108✔
60
                kind.clear();
37✔
61
                document.clear();
145✔
62

108✔
63
                if (social_id) {
37✔
64
                        if (id_card || passport) {
37✔
65
                                return false;
×
66
                        }
67
                        kind = "P"sv;
37✔
68
                        document = std::move(*social_id);
145✔
69
                }
108✔
70

108✔
71
                if (id_card) {
37✔
72
                        if (social_id || passport) {
73
                                return false;
×
74
                        }
75
                        kind = "1"sv;
76
                        document = std::move(*id_card);
×
77
                }
78

108✔
79
                if (passport) {
37✔
80
                        if (social_id || id_card) {
81
                                return false;
×
82
                        }
83
                        kind = "2"sv;
84
                        document = std::move(*passport);
×
85
                }
86

108✔
87
                if (kind.empty() || document.empty()) return false;
37✔
88
                return true;
37✔
89
        }
109✔
90

91
        std::optional<templates> templates::parse_yaml(
33✔
92
            std::filesystem::path const& path) {
1✔
93
                return parser::parse_yaml_file<templates>(path, app_name);
133✔
94
        }
100✔
95

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

106
        bool report_section::validate() noexcept {
107
                if (id.empty()) return false;
108

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

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

129
        std::optional<tax_config> tax_config::parse_yaml(
39✔
130
            std::filesystem::path const& path) {
1✔
131
                return parser::parse_yaml_file<tax_config>(path, app_name);
157✔
132
        }
118✔
133

134
        std::optional<tax_config> tax_config::parse_from_text(
33✔
135
            std::string const& text,
136
            std::string const& path) {
1✔
137
                return parser::parse_yaml_text<tax_config>(text, path);
133✔
138
        }
100✔
139

140
        bool tax_config::postprocess() { return version == kVersion; }
67✔
141

142
        void tax_config::merge(tax_config&& newer) {
67✔
143
                scale.merge(std::move(newer.scale));
265✔
144
                minimal_pay.merge(std::move(newer.minimal_pay));
265✔
145
                costs_of_obtaining.merge(std::move(newer.costs_of_obtaining));
265✔
146
                contributions.merge(std::move(newer.contributions));
265✔
147
        }
265✔
148
}  // namespace quick_dra::v1
149

150
namespace quick_dra::v1::partial {
151
        load_status config::load(std::filesystem::path const& path) {
319✔
152
                std::error_code ec{};
1,273✔
153
                if (!std::filesystem::exists(path, ec) || ec) {
319✔
154
                        return load_status::file_not_found;
385✔
155
                }
288✔
156

666✔
157
                auto result = load_status::errors_encountered;
223✔
158

666✔
159
                auto object = parser::parse_yaml_file<config>(
741✔
160
                    path, [&]() { result = load_status::file_not_readable; });
223✔
161
                if (!object) {
223✔
162
                        return result;
×
163
                }
164

666✔
165
                *this = std::move(*object);
223✔
166
                return load_status::loaded;
889✔
167
        }
889✔
168

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

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

738✔
202
                ryml::csubstr output = ryml::emit_yaml(
1,231✔
203
                    tree, tree.root_id(), ryml::substr{}, /*error_on_excess*/ false);
1,148✔
204

738✔
205
                std::vector<char> buf(output.len);
1,149✔
206
                output = ryml::emit_yaml(tree, tree.root_id(), ryml::to_substr(buf),
1,067✔
207
                                         /*error_on_excess*/ true);
1,148✔
208

738✔
209
                std::ofstream out{path, std::ios::out | std::ios::binary};
985✔
210
                if (!out) {
247✔
211
                        return false;
49✔
212
                }
36✔
213

702✔
214
                out.write(output.data(), static_cast<std::streamsize>(output.size()));
547✔
215
                return true;
937✔
216
        }
985✔
217

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

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

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

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

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

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

1,323✔
253
                named.last_name =
1,030✔
254
                    fmt::format("{}, {}", *named.last_name, *named.first_name);
1,911✔
255
                named.first_name = std::nullopt;
1,765✔
256
        }
1,324✔
257

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

162✔
262
                if (id_card) {
55✔
263
                        kind = "1"sv;
133✔
264
                        document = std::move(*id_card);
133✔
265
                } else if (passport) {
121✔
266
                        kind = "2"sv;
49✔
267
                        document = std::move(*passport);
49✔
268
                }
36✔
269
        }
217✔
270

271
        bool payer_t::postprocess() {
16✔
272
                postprocess_document_kind();
61✔
273

45✔
274
                parse_name(*this);
61✔
275

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

45✔
281
                NULLIFY(social_id);
16✔
282
                NULLIFY(tax_id);
16✔
283
                NULLIFY(kind);
16✔
284
                NULLIFY(document);
16✔
285
                return true;
61✔
286
        }
46✔
287

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

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

180✔
297
                id_card = std::nullopt;
241✔
298
                passport = std::nullopt;
241✔
299
                kind = std::nullopt;
241✔
300
                document = std::nullopt;
241✔
301

180✔
302
                if (kind_ == "1"sv) {
61✔
303
                        id_card = std::move(document_);
121✔
304
                } else if (kind_ == "2"sv) {
121✔
305
                        passport = std::move(document_);
85✔
306
                }
63✔
307
        }
241✔
308

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

2,511✔
313
                if (id_card) {
838✔
314
                        kind = "1"sv;
1,489✔
315
                        document = std::move(*id_card);
1,489✔
316
                } else if (passport) {
1,861✔
317
                        kind = "2"sv;
1,069✔
318
                        document = std::move(*passport);
1,069✔
319
                } else if (social_id) {
1,000✔
320
                        kind = "P"sv;
793✔
321
                        document = std::move(*social_id);
793✔
322
                }
594✔
323
        }
3,349✔
324

325
        bool insured_t::postprocess() {
397✔
326
                postprocess_document_kind();
1,585✔
327
                parse_name(*this);
1,585✔
328

1,188✔
329
                NULLIFY(id_card);
397✔
330
                NULLIFY(passport);
397✔
331
                NULLIFY(social_id);
397✔
332
                NULLIFY(kind);
397✔
333
                NULLIFY(document);
397✔
334

1,188✔
335
                return true;
1,585✔
336
        }
1,189✔
337

338
        void insured_t::preprocess() {
424✔
339
                preprocess_name(*this);
1,693✔
340
                preprocess_document_kind();
1,693✔
341
        }
1,693✔
342

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

2,601✔
347
                id_card = std::nullopt;
3,469✔
348
                passport = std::nullopt;
3,469✔
349
                social_id = std::nullopt;
3,469✔
350
                kind = std::nullopt;
3,469✔
351
                document = std::nullopt;
3,469✔
352

2,601✔
353
                if (kind_ == "1"sv) {
868✔
354
                        id_card = std::move(document_);
1,561✔
355
                } else if (kind_ == "2"sv) {
1,909✔
356
                        passport = std::move(document_);
1,117✔
357
                } else if (kind_ == "P"sv) {
1,036✔
358
                        social_id = std::move(document_);
793✔
359
                }
594✔
360
        }
3,469✔
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