Directory: | cvmfs/ |
---|---|
File: | cvmfs/json_document_write.h |
Date: | 2025-10-19 02:35:28 |
Exec | Total | Coverage | |
---|---|---|---|
Lines: | 70 | 92 | 76.1% |
Branches: | 43 | 101 | 42.6% |
Line | Branch | Exec | Source |
---|---|---|---|
1 | /** | ||
2 | * This file is part of the CernVM File System. | ||
3 | */ | ||
4 | |||
5 | #ifndef CVMFS_JSON_DOCUMENT_WRITE_H_ | ||
6 | #define CVMFS_JSON_DOCUMENT_WRITE_H_ | ||
7 | |||
8 | #include <cstdio> | ||
9 | #include <string> | ||
10 | #include <vector> | ||
11 | |||
12 | #include "util/exception.h" | ||
13 | #include "util/string.h" | ||
14 | |||
15 | #ifdef CVMFS_NAMESPACE_GUARD | ||
16 | namespace CVMFS_NAMESPACE_GUARD { | ||
17 | #endif | ||
18 | |||
19 | /** | ||
20 | * This class is used for marshalling JSON objects to strings. | ||
21 | * | ||
22 | * When creating simple objects is sufficient to call the `Add()` methods to add | ||
23 | * new key - values to the final JSON. | ||
24 | * | ||
25 | * When creating complex objects, (an object that contains another object) is | ||
26 | * necessary to create first the nested object and then add it to the final | ||
27 | * object with the `AddJsonObject`. This will take care of all the escaping. | ||
28 | */ | ||
29 | class JsonStringGenerator { | ||
30 | enum JsonVariant { | ||
31 | kString, | ||
32 | kInteger, | ||
33 | kFloat, | ||
34 | kJsonObject | ||
35 | }; | ||
36 | |||
37 | struct JsonEntry { | ||
38 | JsonVariant variant; | ||
39 | std::string key_escaped; | ||
40 | std::string str_val_escaped; | ||
41 | int64_t int_val; | ||
42 | float float_val; | ||
43 | |||
44 | 550 | JsonEntry(const std::string &key_escaped, const std::string &val) | |
45 | 550 | : variant(kString) | |
46 | 550 | , key_escaped(key_escaped) | |
47 |
1/2✓ Branch 1 taken 550 times.
✗ Branch 2 not taken.
|
550 | , str_val_escaped(val) |
48 | 550 | , int_val(0) | |
49 | 550 | , float_val(0.0) { } | |
50 | |||
51 | 96 | JsonEntry(const std::string &key_escaped, const std::string &val, | |
52 | const JsonVariant variant) | ||
53 | 96 | : variant(variant) | |
54 | 96 | , key_escaped(key_escaped) | |
55 |
1/2✓ Branch 1 taken 96 times.
✗ Branch 2 not taken.
|
96 | , str_val_escaped(val) |
56 | 96 | , int_val(0) | |
57 | 96 | , float_val(0.0) { } | |
58 | |||
59 | 40 | JsonEntry(const std::string &key_escaped, const int val) | |
60 | 40 | : variant(kInteger) | |
61 | 40 | , key_escaped(key_escaped) | |
62 | 40 | , str_val_escaped() | |
63 | 40 | , int_val(val) | |
64 | 40 | , float_val(0.0) { } | |
65 | |||
66 | JsonEntry(const std::string &key_escaped, const float val) | ||
67 | : variant(kFloat) | ||
68 | , key_escaped(key_escaped) | ||
69 | , str_val_escaped() | ||
70 | , int_val(0) | ||
71 | , float_val(val) { } | ||
72 | |||
73 | 99 | JsonEntry(const std::string &key_escaped, const int64_t val) | |
74 | 99 | : variant(kInteger) | |
75 | 99 | , key_escaped(key_escaped) | |
76 | 99 | , str_val_escaped() | |
77 | 99 | , int_val(val) | |
78 | 99 | , float_val(0.0) { } | |
79 | |||
80 | 785 | std::string Format() const { | |
81 |
3/5✓ Branch 0 taken 550 times.
✓ Branch 1 taken 139 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 96 times.
✗ Branch 4 not taken.
|
785 | switch (variant) { |
82 | 550 | case kString: | |
83 |
3/6✓ Branch 2 taken 550 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 550 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 550 times.
✗ Branch 9 not taken.
|
1100 | return "\"" + key_escaped + "\":\"" + str_val_escaped + "\""; |
84 | 139 | case kInteger: | |
85 |
3/6✓ Branch 2 taken 139 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 139 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 139 times.
✗ Branch 9 not taken.
|
278 | return "\"" + key_escaped + "\":" + StringifyInt(int_val); |
86 | ✗ | case kFloat: | |
87 | ✗ | return "\"" + key_escaped + "\":" + StringifyDouble(float_val); | |
88 | 96 | case kJsonObject: | |
89 |
2/4✓ Branch 2 taken 96 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 96 times.
✗ Branch 6 not taken.
|
192 | return "\"" + key_escaped + "\":" + str_val_escaped; |
90 | ✗ | default: | |
91 | ✗ | PANIC(kLogStdout | kLogStderr, "JSON creation failed"); | |
92 | } | ||
93 | } | ||
94 | }; | ||
95 | |||
96 | public: | ||
97 | 550 | void Add(const std::string &key, const std::string &val) { | |
98 |
3/6✓ Branch 1 taken 550 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 550 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 550 times.
✗ Branch 8 not taken.
|
1100 | const JsonEntry entry(Escape(key), Escape(val)); |
99 |
1/2✓ Branch 1 taken 550 times.
✗ Branch 2 not taken.
|
550 | entries.push_back(entry); |
100 | 550 | } | |
101 | |||
102 | 40 | void Add(const std::string &key, const int val) { | |
103 |
2/4✓ Branch 1 taken 40 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 40 times.
✗ Branch 5 not taken.
|
40 | const JsonEntry entry(Escape(key), val); |
104 |
1/2✓ Branch 1 taken 40 times.
✗ Branch 2 not taken.
|
40 | entries.push_back(entry); |
105 | 40 | } | |
106 | |||
107 | void Add(const std::string &key, const float val) { | ||
108 | const JsonEntry entry(Escape(key), val); | ||
109 | entries.push_back(entry); | ||
110 | } | ||
111 | |||
112 | 99 | void Add(const std::string &key, const int64_t val) { | |
113 |
2/4✓ Branch 1 taken 99 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 99 times.
✗ Branch 5 not taken.
|
99 | const JsonEntry entry(Escape(key), val); |
114 |
1/2✓ Branch 1 taken 99 times.
✗ Branch 2 not taken.
|
99 | entries.push_back(entry); |
115 | 99 | } | |
116 | |||
117 | 96 | void AddJsonObject(const std::string &key, const std::string &json) { | |
118 | // we **do not escape** the value here | ||
119 |
2/4✓ Branch 1 taken 96 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 96 times.
✗ Branch 5 not taken.
|
96 | const JsonEntry entry(Escape(key), json, kJsonObject); |
120 |
1/2✓ Branch 1 taken 96 times.
✗ Branch 2 not taken.
|
96 | entries.push_back(entry); |
121 | 96 | } | |
122 | |||
123 | 382 | std::string GenerateString() const { | |
124 | 382 | std::string output; | |
125 | |||
126 |
1/2✓ Branch 1 taken 382 times.
✗ Branch 2 not taken.
|
382 | output += "{"; |
127 |
2/2✓ Branch 1 taken 785 times.
✓ Branch 2 taken 382 times.
|
1167 | for (size_t i = 0u; i < this->entries.size(); ++i) { |
128 |
2/4✓ Branch 2 taken 785 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 785 times.
✗ Branch 6 not taken.
|
785 | output += this->entries[i].Format(); |
129 |
2/2✓ Branch 1 taken 466 times.
✓ Branch 2 taken 319 times.
|
785 | if (i < this->entries.size() - 1) { |
130 |
1/2✓ Branch 1 taken 466 times.
✗ Branch 2 not taken.
|
466 | output += ','; |
131 | } | ||
132 | } | ||
133 |
2/4✓ Branch 2 taken 382 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 382 times.
✗ Branch 6 not taken.
|
382 | output += std::string("}"); |
134 | 382 | return output; | |
135 | } | ||
136 | |||
137 | 66 | void Clear() { entries.clear(); } | |
138 | |||
139 | private: | ||
140 | // this escape procedure is not as complete as it should be. | ||
141 | // we should manage ALL control chars from '\x00' to '\x1f' | ||
142 | // however this are the one that we can expect to happen | ||
143 | // More info: https://stackoverflow.com/a/33799784/869271 | ||
144 | 1335 | const std::string Escape(const std::string &input) const { | |
145 | 1335 | std::string result; | |
146 |
1/2✓ Branch 2 taken 1335 times.
✗ Branch 3 not taken.
|
1335 | result.reserve(input.size()); |
147 |
2/2✓ Branch 1 taken 26418 times.
✓ Branch 2 taken 1335 times.
|
27753 | for (size_t i = 0; i < input.size(); i++) { |
148 |
2/8✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 40 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✓ Branch 8 taken 26378 times.
|
26418 | switch (input[i]) { |
149 | ✗ | case '"': | |
150 | ✗ | result.append("\\\""); | |
151 | ✗ | break; | |
152 | ✗ | case '\\': | |
153 | ✗ | result.append("\\\\"); | |
154 | ✗ | break; | |
155 | ✗ | case '\b': | |
156 | ✗ | result.append("\\b"); | |
157 | ✗ | break; | |
158 | ✗ | case '\f': | |
159 | ✗ | result.append("\\f"); | |
160 | ✗ | break; | |
161 | 40 | case '\n': | |
162 |
1/2✓ Branch 1 taken 40 times.
✗ Branch 2 not taken.
|
40 | result.append("\\n"); |
163 | 40 | break; | |
164 | ✗ | case '\r': | |
165 | ✗ | result.append("\\r"); | |
166 | ✗ | break; | |
167 | ✗ | case '\t': | |
168 | ✗ | result.append("\\t"); | |
169 | ✗ | break; | |
170 | 26378 | default: | |
171 |
1/2✓ Branch 2 taken 26378 times.
✗ Branch 3 not taken.
|
26378 | result.push_back(input[i]); |
172 | 26378 | break; | |
173 | } | ||
174 | } | ||
175 | 1335 | return result; | |
176 | } | ||
177 | |||
178 | std::vector<JsonEntry> entries; | ||
179 | }; | ||
180 | |||
181 | #ifdef CVMFS_NAMESPACE_GUARD | ||
182 | } // namespace CVMFS_NAMESPACE_GUARD | ||
183 | #endif | ||
184 | |||
185 | #endif // CVMFS_JSON_DOCUMENT_WRITE_H_ | ||
186 |