Coverage Report

Created: 2023-06-07 06:59

/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