Coverage Report

Created: 2026-08-31 07:17

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/valijson/include/valijson/subschema.hpp
Line
Count
Source
1
#pragma once
2
3
#include <functional>
4
#include <memory>
5
#include <optional>
6
#include <vector>
7
8
#include <valijson/constraints/constraint.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 Move construct a new Subschema
48
     *
49
     * @param other Subschema that is moved into the new Subschema
50
     */
51
    Subschema(Subschema &&other)
52
      : m_allocFn(other.m_allocFn),
53
        m_freeFn(other.m_freeFn),
54
        m_alwaysInvalid(std::move(other.m_alwaysInvalid)),
55
        m_constraints(std::move(other.m_constraints)),
56
        m_description(std::move(other.m_description)),
57
        m_id(std::move(other.m_id)),
58
0
        m_title(std::move(other.m_title)) { }
59
60
    /**
61
     * @brief Move assign a Subschema
62
     *
63
     * @param other Subschema that is move assigned to this Subschema
64
     * @return Subschema&
65
     */
66
    Subschema & operator=(Subschema &&other)
67
0
    {
68
0
        // Swaps all members
69
0
        std::swap(m_allocFn, other.m_allocFn);
70
0
        std::swap(m_freeFn, other.m_freeFn);
71
0
        std::swap(m_alwaysInvalid, other.m_alwaysInvalid);
72
0
        std::swap(m_constraints, other.m_constraints);
73
0
        std::swap(m_description, other.m_description);
74
0
        std::swap(m_id, other.m_id);
75
0
        std::swap(m_title, other.m_title);
76
0
77
0
        return *this;
78
0
    }
79
80
    /**
81
     * @brief  Construct a new Subschema object
82
     */
83
    Subschema()
84
624k
      : m_allocFn([](size_t size) { return ::operator new(size, std::nothrow); })
85
462k
      , m_freeFn(::operator delete)
86
462k
      , m_alwaysInvalid(false) { }
87
88
    /**
89
     * @brief  Construct a new Subschema using custom memory management
90
     *         functions
91
     *
92
     * @param  allocFn  malloc- or new-like function to allocate memory
93
     *                  within Schema, such as for Subschema instances
94
     * @param  freeFn   free-like function to free memory allocated with
95
     *                  the `customAlloc` function
96
     */
97
    Subschema(CustomAlloc allocFn, CustomFree freeFn)
98
      : m_allocFn(allocFn)
99
      , m_freeFn(freeFn)
100
      , m_alwaysInvalid(false)
101
0
    {
102
0
        // explicitly initialise optionals. See: https://github.com/tristanpenman/valijson/issues/124
103
0
        m_description = std::nullopt;
104
0
        m_id = std::nullopt;
105
0
        m_title = std::nullopt;
106
0
    }
107
108
    /**
109
     * @brief  Clean up and free all memory managed by the Subschema
110
     */
111
    virtual ~Subschema()
112
462k
    {
113
462k
#if VALIJSON_USE_EXCEPTIONS
114
462k
        try {
115
462k
#endif
116
462k
            m_constraints.clear();
117
462k
#if VALIJSON_USE_EXCEPTIONS
118
462k
        } catch (const std::exception &e) {
119
0
            fprintf(stderr, "Caught an exception in Subschema destructor: %s",
120
0
                    e.what());
121
0
        }
122
462k
#endif
123
462k
    }
124
125
    /**
126
     * @brief  Add a constraint to this sub-schema
127
     *
128
     * The constraint will be copied before being added to the list of
129
     * constraints for this Subschema. Note that constraints will be copied
130
     * only as deep as references to other Subschemas - e.g. copies of
131
     * constraints that refer to sub-schemas, will continue to refer to the
132
     * same Subschema instances.
133
     *
134
     * @param  constraint  Reference to the constraint to copy
135
     */
136
    void addConstraint(const Constraint &constraint)
137
211k
    {
138
        // the vector allocation might throw but the constraint memory will be taken care of anyways
139
211k
        m_constraints.push_back(constraint.clone(m_allocFn, m_freeFn));
140
211k
    }
141
142
    /**
143
     * @brief  Invoke a function on each child Constraint
144
     *
145
     * This function will apply the callback function to each constraint in
146
     * the Subschema, even if one of the invocations returns \c false. However,
147
     * if one or more invocations of the callback function return \c false,
148
     * this function will also return \c false.
149
     *
150
     * @returns  \c true if all invocations of the callback function are
151
     *           successful, \c false otherwise
152
     */
153
    bool apply(ApplyFunction &applyFunction) const
154
1.03M
    {
155
1.03M
        bool allTrue = true;
156
1.03M
        for (auto &&constraint : m_constraints) {
157
            // Even if an application fails, we want to continue checking the
158
            // schema. In that case we set allTrue to false, and then fall
159
            // through to the next constraint
160
922k
            if (!applyFunction(*constraint)) {
161
77.9k
                allTrue = false;
162
77.9k
            }
163
922k
        }
164
165
1.03M
        return allTrue;
166
1.03M
    }
167
168
    /**
169
     * @brief  Invoke a function on each child Constraint
170
     *
171
     * This is a stricter version of the apply() function that will return
172
     * immediately if any of the invocations of the callback function return
173
     * \c false.
174
     *
175
     * @returns  \c true if all invocations of the callback function are
176
     *           successful, \c false otherwise
177
     */
178
    bool applyStrict(ApplyFunction &applyFunction) const
179
71.1k
    {
180
71.1k
        for (auto &&constraint : m_constraints) {
181
57.3k
            if (!applyFunction(*constraint)) {
182
30.4k
                return false;
183
30.4k
            }
184
57.3k
        }
185
186
40.6k
        return true;
187
71.1k
    }
188
189
    bool getAlwaysInvalid() const
190
1.10M
    {
191
1.10M
        return m_alwaysInvalid;
192
1.10M
    }
193
194
    /**
195
     * @brief  Get the description associated with this sub-schema
196
     *
197
     * @throws  std::runtime_error if a description has not been set
198
     *
199
     * @returns  string containing sub-schema description
200
     */
201
    std::string getDescription() const
202
0
    {
203
0
        if (m_description) {
204
0
            return *m_description;
205
0
        }
206
0
207
0
        throwRuntimeError("Schema does not have a description");
208
0
    }
209
210
    /**
211
     * @brief  Get the ID associated with this sub-schema
212
     *
213
     * @throws  std::runtime_error if an ID has not been set
214
     *
215
     * @returns  string containing sub-schema ID
216
     */
217
    std::string getId() const
218
0
    {
219
0
        if (m_id) {
220
0
            return *m_id;
221
0
        }
222
0
223
0
        throwRuntimeError("Schema does not have an ID");
224
0
    }
225
226
    /**
227
     * @brief  Get the title associated with this sub-schema
228
     *
229
     * @throws  std::runtime_error if a title has not been set
230
     *
231
     * @returns  string containing sub-schema title
232
     */
233
    std::string getTitle() const
234
0
    {
235
0
        if (m_title) {
236
0
            return *m_title;
237
0
        }
238
0
239
0
        throwRuntimeError("Schema does not have a title");
240
0
    }
241
242
    /**
243
     * @brief  Check whether this sub-schema has a description
244
     *
245
     * @return boolean value
246
     */
247
    bool hasDescription() const
248
0
    {
249
0
        return static_cast<bool>(m_description);
250
0
    }
251
252
    /**
253
     * @brief  Check whether this sub-schema has an ID
254
     *
255
     * @return  boolean value
256
     */
257
    bool hasId() const
258
0
    {
259
0
        return static_cast<bool>(m_id);
260
0
    }
261
262
    /**
263
     * @brief  Check whether this sub-schema has a title
264
     *
265
     * @return  boolean value
266
     */
267
    bool hasTitle() const
268
0
    {
269
0
        return static_cast<bool>(m_title);
270
0
    }
271
272
    void setAlwaysInvalid(bool value)
273
1.85k
    {
274
1.85k
        m_alwaysInvalid = value;
275
1.85k
    }
276
277
    /**
278
     * @brief  Set the description for this sub-schema
279
     *
280
     * The description will not be used for validation, but may be used as part
281
     * of the user interface for interacting with schemas and sub-schemas. As
282
     * an example, it may be used as part of the validation error descriptions
283
     * that are produced by the Validator and ValidationVisitor classes.
284
     *
285
     * @param  description  new description
286
     */
287
    void setDescription(const std::string &description)
288
2.08k
    {
289
2.08k
        m_description = description;
290
2.08k
    }
291
292
    void setId(const std::string &id)
293
20.9k
    {
294
20.9k
        m_id = id;
295
20.9k
    }
296
297
    /**
298
     * @brief  Set the title for this sub-schema
299
     *
300
     * The title will not be used for validation, but may be used as part
301
     * of the user interface for interacting with schemas and sub-schema. As an
302
     * example, it may be used as part of the validation error descriptions
303
     * that are produced by the Validator and ValidationVisitor classes.
304
     *
305
     * @param  title  new title
306
     */
307
    void setTitle(const std::string &title)
308
2.64k
    {
309
2.64k
        m_title = title;
310
2.64k
    }
311
312
protected:
313
314
    CustomAlloc m_allocFn;
315
316
    CustomFree m_freeFn;
317
318
private:
319
320
    bool m_alwaysInvalid;
321
322
    /// List of pointers to constraints that apply to this schema.
323
    std::vector<Constraint::OwningPointer> m_constraints;
324
325
    /// Schema description (optional)
326
    std::optional<std::string> m_description;
327
328
    /// ID to apply when resolving the schema URI
329
    std::optional<std::string> m_id;
330
331
    /// Title string associated with the schema (optional)
332
    std::optional<std::string> m_title;
333
};
334
335
} // namespace valijson