/src/valijson/include/valijson/subschema.hpp
Line | Count | Source (jump to first uncovered line) |
1 | | #pragma once |
2 | | |
3 | | #include <functional> |
4 | | #include <memory> |
5 | | #include <vector> |
6 | | |
7 | | #include <valijson/constraints/constraint.hpp> |
8 | | #include <valijson/internal/optional.hpp> |
9 | | #include <valijson/exceptions.hpp> |
10 | | |
11 | | namespace valijson { |
12 | | |
13 | | /** |
14 | | * Represents a sub-schema within a JSON Schema |
15 | | * |
16 | | * While all JSON Schemas have at least one sub-schema, the root, some will |
17 | | * have additional sub-schemas that are defined as part of constraints that are |
18 | | * included in the schema. For example, a 'oneOf' constraint maintains a set of |
19 | | * references to one or more nested sub-schemas. As per the definition of a |
20 | | * oneOf constraint, a document is valid within that constraint if it validates |
21 | | * against one of the nested sub-schemas. |
22 | | */ |
23 | | class Subschema |
24 | | { |
25 | | public: |
26 | | |
27 | | /// Typedef for custom new-/malloc-like function |
28 | | typedef void * (*CustomAlloc)(size_t size); |
29 | | |
30 | | /// Typedef for custom free-like function |
31 | | typedef void (*CustomFree)(void *); |
32 | | |
33 | | /// Typedef the Constraint class into the local namespace for convenience |
34 | | typedef constraints::Constraint Constraint; |
35 | | |
36 | | /// Typedef for a function that can be applied to each of the Constraint |
37 | | /// instances owned by a Schema. |
38 | | typedef std::function<bool (const Constraint &)> ApplyFunction; |
39 | | |
40 | | // Disable copy construction |
41 | | Subschema(const Subschema &) = delete; |
42 | | |
43 | | // Disable copy assignment |
44 | | Subschema & operator=(const Subschema &) = delete; |
45 | | |
46 | | /** |
47 | | * @brief Construct a new Subschema object |
48 | | */ |
49 | | Subschema() |
50 | 103 | : m_allocFn([](size_t size) { return ::operator new(size, std::nothrow); }) |
51 | | , m_freeFn(::operator delete) |
52 | 206 | , m_alwaysInvalid(false) { } |
53 | | |
54 | | /** |
55 | | * @brief Construct a new Subschema using custom memory management |
56 | | * functions |
57 | | * |
58 | | * @param allocFn malloc- or new-like function to allocate memory |
59 | | * within Schema, such as for Subschema instances |
60 | | * @param freeFn free-like function to free memory allocated with |
61 | | * the `customAlloc` function |
62 | | */ |
63 | | Subschema(CustomAlloc allocFn, CustomFree freeFn) |
64 | | : m_allocFn(allocFn) |
65 | | , m_freeFn(freeFn) |
66 | | , m_alwaysInvalid(false) |
67 | 0 | { |
68 | 0 | // explicitly initialise optionals. See: https://github.com/tristanpenman/valijson/issues/124 |
69 | 0 | m_description = opt::nullopt; |
70 | 0 | m_id = opt::nullopt; |
71 | 0 | m_title = opt::nullopt; |
72 | 0 | } |
73 | | |
74 | | /** |
75 | | * @brief Clean up and free all memory managed by the Subschema |
76 | | */ |
77 | | virtual ~Subschema() |
78 | 206 | { |
79 | 206 | #if VALIJSON_USE_EXCEPTIONS |
80 | 206 | try { |
81 | 206 | #endif |
82 | 206 | m_constraints.clear(); |
83 | 206 | #if VALIJSON_USE_EXCEPTIONS |
84 | 206 | } catch (const std::exception &e) { |
85 | 0 | fprintf(stderr, "Caught an exception in Subschema destructor: %s", |
86 | 0 | e.what()); |
87 | 0 | } |
88 | 206 | #endif |
89 | 206 | } |
90 | | |
91 | | /** |
92 | | * @brief Add a constraint to this sub-schema |
93 | | * |
94 | | * The constraint will be copied before being added to the list of |
95 | | * constraints for this Subschema. Note that constraints will be copied |
96 | | * only as deep as references to other Subschemas - e.g. copies of |
97 | | * constraints that refer to sub-schemas, will continue to refer to the |
98 | | * same Subschema instances. |
99 | | * |
100 | | * @param constraint Reference to the constraint to copy |
101 | | */ |
102 | | void addConstraint(const Constraint &constraint) |
103 | 0 | { |
104 | | // the vector allocation might throw but the constraint memory will be taken care of anyways |
105 | 0 | m_constraints.push_back(constraint.clone(m_allocFn, m_freeFn)); |
106 | 0 | } |
107 | | |
108 | | /** |
109 | | * @brief Invoke a function on each child Constraint |
110 | | * |
111 | | * This function will apply the callback function to each constraint in |
112 | | * the Subschema, even if one of the invocations returns \c false. However, |
113 | | * if one or more invocations of the callback function return \c false, |
114 | | * this function will also return \c false. |
115 | | * |
116 | | * @returns \c true if all invocations of the callback function are |
117 | | * successful, \c false otherwise |
118 | | */ |
119 | | bool apply(ApplyFunction &applyFunction) const |
120 | 0 | { |
121 | 0 | bool allTrue = true; |
122 | 0 | for (auto &&constraint : m_constraints) { |
123 | 0 | allTrue = applyFunction(*constraint) && allTrue; |
124 | 0 | } |
125 | 0 |
|
126 | 0 | return allTrue; |
127 | 0 | } |
128 | | |
129 | | /** |
130 | | * @brief Invoke a function on each child Constraint |
131 | | * |
132 | | * This is a stricter version of the apply() function that will return |
133 | | * immediately if any of the invocations of the callback function return |
134 | | * \c false. |
135 | | * |
136 | | * @returns \c true if all invocations of the callback function are |
137 | | * successful, \c false otherwise |
138 | | */ |
139 | | bool applyStrict(ApplyFunction &applyFunction) const |
140 | 0 | { |
141 | 0 | for (auto &&constraint : m_constraints) { |
142 | 0 | if (!applyFunction(*constraint)) { |
143 | 0 | return false; |
144 | 0 | } |
145 | 0 | } |
146 | 0 |
|
147 | 0 | return true; |
148 | 0 | } |
149 | | |
150 | | bool getAlwaysInvalid() const |
151 | 0 | { |
152 | 0 | return m_alwaysInvalid; |
153 | 0 | } |
154 | | |
155 | | /** |
156 | | * @brief Get the description associated with this sub-schema |
157 | | * |
158 | | * @throws std::runtime_error if a description has not been set |
159 | | * |
160 | | * @returns string containing sub-schema description |
161 | | */ |
162 | | std::string getDescription() const |
163 | 0 | { |
164 | 0 | if (m_description) { |
165 | 0 | return *m_description; |
166 | 0 | } |
167 | 0 |
|
168 | 0 | throwRuntimeError("Schema does not have a description"); |
169 | 0 | } |
170 | | |
171 | | /** |
172 | | * @brief Get the ID associated with this sub-schema |
173 | | * |
174 | | * @throws std::runtime_error if an ID has not been set |
175 | | * |
176 | | * @returns string containing sub-schema ID |
177 | | */ |
178 | | std::string getId() const |
179 | 0 | { |
180 | 0 | if (m_id) { |
181 | 0 | return *m_id; |
182 | 0 | } |
183 | 0 |
|
184 | 0 | throwRuntimeError("Schema does not have an ID"); |
185 | 0 | } |
186 | | |
187 | | /** |
188 | | * @brief Get the title associated with this sub-schema |
189 | | * |
190 | | * @throws std::runtime_error if a title has not been set |
191 | | * |
192 | | * @returns string containing sub-schema title |
193 | | */ |
194 | | std::string getTitle() const |
195 | 0 | { |
196 | 0 | if (m_title) { |
197 | 0 | return *m_title; |
198 | 0 | } |
199 | 0 |
|
200 | 0 | throwRuntimeError("Schema does not have a title"); |
201 | 0 | } |
202 | | |
203 | | /** |
204 | | * @brief Check whether this sub-schema has a description |
205 | | * |
206 | | * @return boolean value |
207 | | */ |
208 | | bool hasDescription() const |
209 | 0 | { |
210 | 0 | return static_cast<bool>(m_description); |
211 | 0 | } |
212 | | |
213 | | /** |
214 | | * @brief Check whether this sub-schema has an ID |
215 | | * |
216 | | * @return boolean value |
217 | | */ |
218 | | bool hasId() const |
219 | 0 | { |
220 | 0 | return static_cast<bool>(m_id); |
221 | 0 | } |
222 | | |
223 | | /** |
224 | | * @brief Check whether this sub-schema has a title |
225 | | * |
226 | | * @return boolean value |
227 | | */ |
228 | | bool hasTitle() const |
229 | 0 | { |
230 | 0 | return static_cast<bool>(m_title); |
231 | 0 | } |
232 | | |
233 | | void setAlwaysInvalid(bool value) |
234 | 0 | { |
235 | 0 | m_alwaysInvalid = value; |
236 | 0 | } |
237 | | |
238 | | /** |
239 | | * @brief Set the description for this sub-schema |
240 | | * |
241 | | * The description will not be used for validation, but may be used as part |
242 | | * of the user interface for interacting with schemas and sub-schemas. As |
243 | | * an example, it may be used as part of the validation error descriptions |
244 | | * that are produced by the Validator and ValidationVisitor classes. |
245 | | * |
246 | | * @param description new description |
247 | | */ |
248 | | void setDescription(const std::string &description) |
249 | 0 | { |
250 | 0 | m_description = description; |
251 | 0 | } |
252 | | |
253 | | void setId(const std::string &id) |
254 | 0 | { |
255 | 0 | m_id = id; |
256 | 0 | } |
257 | | |
258 | | /** |
259 | | * @brief Set the title for this sub-schema |
260 | | * |
261 | | * The title will not be used for validation, but may be used as part |
262 | | * of the user interface for interacting with schemas and sub-schema. As an |
263 | | * example, it may be used as part of the validation error descriptions |
264 | | * that are produced by the Validator and ValidationVisitor classes. |
265 | | * |
266 | | * @param title new title |
267 | | */ |
268 | | void setTitle(const std::string &title) |
269 | 0 | { |
270 | 0 | m_title = title; |
271 | 0 | } |
272 | | |
273 | | protected: |
274 | | |
275 | | CustomAlloc m_allocFn; |
276 | | |
277 | | CustomFree m_freeFn; |
278 | | |
279 | | private: |
280 | | |
281 | | bool m_alwaysInvalid; |
282 | | |
283 | | /// List of pointers to constraints that apply to this schema. |
284 | | std::vector<Constraint::OwningPointer> m_constraints; |
285 | | |
286 | | /// Schema description (optional) |
287 | | opt::optional<std::string> m_description; |
288 | | |
289 | | /// Id to apply when resolving the schema URI |
290 | | opt::optional<std::string> m_id; |
291 | | |
292 | | /// Title string associated with the schema (optional) |
293 | | opt::optional<std::string> m_title; |
294 | | }; |
295 | | |
296 | | } // namespace valijson |