Skip to content

Commit e6b94dc

Browse files
committed
fable: Change validate to a non-throwing interface
BREAKING CHANGE: Previous use of `Schema::validate(const Conf&)` should now use `Schema::validate_or_throw(const Conf&)`. BREAKING CHANGE: Custom schemas should change their implementation of `validate(const Conf&)` to `bool validate(const Conf&, std::optional<SchemaError>`. See the changes in this commit to see how the built-in schemas are modified for examples of how to change your implementation.
1 parent e4ae6b7 commit e6b94dc

34 files changed

+421
-236
lines changed

engine/src/main_stack.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ void merge_stack(const StackOptions& opt, Stack& s, const std::string& filepath)
6969
}
7070

7171
s.from_conf(c);
72-
s.validate();
72+
s.validate_self();
7373
};
7474

7575
if (!opt.error) {

engine/src/stack.cpp

+22-7
Original file line numberDiff line numberDiff line change
@@ -604,25 +604,40 @@ void Stack::from_conf(const Conf& _conf, size_t depth) {
604604
}
605605

606606
// Apply everything else.
607-
this->schema().validate(c);
607+
this->schema().validate_or_throw(c);
608608
this->schema().from_conf(c);
609609
this->reset_schema();
610610
}
611611

612-
void Stack::validate(const Conf& c) const {
612+
void Stack::validate_self() const {
613+
check_consistency();
614+
check_defaults();
615+
}
616+
617+
void Stack::validate_or_throw(const Conf& c) const {
613618
Stack copy(*this);
614619
copy.from_conf(c);
615-
copy.validate();
620+
copy.validate_self();
616621
}
617622

618-
void Stack::validate() const {
619-
check_consistency();
620-
check_defaults();
623+
bool Stack::validate(const Conf &c, std::optional<SchemaError> &err) const {
624+
Stack copy(*this);
625+
try {
626+
copy.from_conf(c);
627+
copy.validate_self();
628+
} catch (fable::SchemaError& e) {
629+
err.emplace(e);
630+
return false;
631+
} catch (Error& e) {
632+
err.emplace(SchemaError(c, schema().json_schema(), "{}", e.what()));
633+
return false;
634+
}
635+
return true;
621636
}
622637

623638
bool Stack::is_valid() const {
624639
try {
625-
validate();
640+
validate_self();
626641
return true;
627642
} catch (...) {
628643
// TODO(ben): Replace ... with specific error classes

engine/src/stack.hpp

+15-10
Original file line numberDiff line numberDiff line change
@@ -178,13 +178,17 @@ struct LoggingConf : public Confable {
178178
};
179179
}
180180

181-
void validate(const Conf& c) const override {
181+
bool validate(const Conf& c, std::optional<SchemaError>& err) const override {
182182
const auto& s = this->schema();
183-
s.validate(c);
183+
if (!s.validate(c, err)) {
184+
return false;
185+
}
184186
if (!c.has("pattern") && !c.has("level")) {
185-
throw SchemaError(c, s.json_schema(),
186-
"require at least one of 'pattern' or 'level' properties");
187+
err.emplace(SchemaError(c, s.json_schema(),
188+
"require at least one of 'pattern' or 'level' properties"));
189+
return false;
187190
}
191+
return true;
188192
}
189193
};
190194

@@ -756,9 +760,9 @@ class VehicleSchema : public fable::schema::Base<VehicleSchema> {
756760
return v.schema().json_schema();
757761
}
758762

759-
void validate(const Conf& c) const override {
763+
bool validate(const Conf& c, std::optional<SchemaError>& err) const override {
760764
VehicleConf v{components_};
761-
v.schema().validate(c);
765+
return v.schema().validate(c, err);
762766
}
763767

764768
Json serialize(const Type& x) const { return x.to_json(); }
@@ -1018,7 +1022,7 @@ class Stack : public Confable {
10181022
* consistency and correctness (but not necessarily completeness, except for
10191023
* any references made) of the entire configuration.
10201024
*/
1021-
void validate() const;
1025+
void validate_self() const;
10221026

10231027
/**
10241028
* Return true if this configuration would be valid.
@@ -1081,13 +1085,14 @@ class Stack : public Confable {
10811085
/**
10821086
* Validate a configuration.
10831087
*
1084-
* This cannot normally be done with `schema().validate(c)`, because plugins
1088+
* This cannot normally be done with `schema().validate()`, because plugins
10851089
* needed to be loaded in order to validate sections of the schema. This
10861090
* requires partial application of the schema.
10871091
*
1088-
* This should be equivalent to from_conf() followed by validate().
1092+
* This should be equivalent to from_conf() followed by validate_self().
10891093
*/
1090-
void validate(const Conf& c) const override;
1094+
bool validate(const Conf& c, std::optional<SchemaError>& err) const override;
1095+
void validate_or_throw(const Conf& c) const override;
10911096

10921097
void to_json(Json& j) const override;
10931098
void from_conf(const Conf& c) override { from_conf(c, 0); }

fable/include/fable/confable.hpp

+20-2
Original file line numberDiff line numberDiff line change
@@ -79,14 +79,32 @@ class Confable {
7979
/**
8080
* Validate a Conf without applying it.
8181
*
82+
* By default, this uses the validate_or_throw() method on schema(). If you want to
83+
* guarantee anything extending beyond what's possible with schema, you can
84+
* do that here.
85+
*
86+
* This method should NOT call from_conf without also overriding from_conf
87+
* to prevent infinite recursion.
88+
*/
89+
virtual void validate_or_throw(const Conf& c) const;
90+
91+
/**
92+
* Validate a Conf without applying it and without throwing.
93+
*
8294
* By default, this uses the validate() method on schema(). If you want to
8395
* guarantee anything extending beyond what's possible with schema, you can
8496
* do that here.
8597
*
86-
* This methodshould NOT call from_conf without also overriding from_conf
98+
* This method should NOT call from_conf without also overriding from_conf
8799
* to prevent infinite recursion.
100+
*
101+
* This method should not reset err unless the method returns false.
102+
*
103+
* \param c JSON to validate
104+
* \param err reference to store error in
105+
* \return true if validate successful
88106
*/
89-
virtual void validate(const Conf& c) const;
107+
virtual bool validate(const Conf& c, std::optional<SchemaError>& err) const;
90108

91109
/**
92110
* Deserialize a Confable from a Conf.

fable/include/fable/schema.hpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ class Schema : public schema::Interface {
254254
void set_description(std::string s) override { return impl_->set_description(std::move(s)); }
255255
Json usage() const override { return impl_->usage(); }
256256
Json json_schema() const override { return impl_->json_schema(); }
257-
void validate(const Conf& c) const override { impl_->validate(c); }
257+
bool validate(const Conf& c, std::optional<SchemaError>& err) const override { return impl_->validate(c, err); }
258258
void to_json(Json& j) const override { impl_->to_json(j); }
259259
void from_conf(const Conf& c) override { impl_->from_conf(c); }
260260
void reset_ptr() override { impl_->reset_ptr(); }

fable/include/fable/schema/array.hpp

+29-19
Original file line numberDiff line numberDiff line change
@@ -169,19 +169,23 @@ class Array : public Base<Array<T, N, P>> {
169169
return j;
170170
}
171171

172-
void validate(const Conf& c) const override {
172+
bool validate(const Conf& c, std::optional<SchemaError>& err) const override {
173173
if (option_require_all_) {
174-
this->validate_type(c);
175-
this->validate_array(c);
176-
return;
174+
if (!this->validate_type(c, err)) {
175+
return false;
176+
}
177+
if (!this->validate_array(c, err)) {
178+
return false;
179+
}
180+
return true;
177181
}
178182

179183
if (c->type() == JsonType::array) {
180-
this->validate_array(c);
184+
return this->validate_array(c, err);
181185
} else if (c->type() == JsonType::object) {
182-
this->validate_object(c);
186+
return this->validate_object(c, err);
183187
} else {
184-
this->throw_wrong_type(c);
188+
return this->set_wrong_type(err, c);
185189
}
186190
}
187191

@@ -233,7 +237,7 @@ class Array : public Base<Array<T, N, P>> {
233237
} else if (c->type() == JsonType::object) {
234238
this->deserialize_from_object(v, c);
235239
} else {
236-
this->throw_wrong_type(c);
240+
throw this->wrong_type(c);
237241
}
238242
}
239243

@@ -273,14 +277,17 @@ class Array : public Base<Array<T, N, P>> {
273277
* That is, all elements in the array must be set, no less and
274278
* no more.
275279
*/
276-
void validate_array(const Conf& c) const {
280+
bool validate_array(const Conf& c, std::optional<SchemaError>& err) const {
277281
assert(c->type() == JsonType::array);
278282
if (c->size() != N) {
279-
this->throw_error(c, "require exactly {} items in array, got {}", N, c->size());
283+
return this->set_error(err, c, "require exactly {} items in array, got {}", N, c->size());
280284
}
281285
for (const auto& x : c.to_array()) {
282-
prototype_.validate(x);
286+
if (!prototype_.validate(x, err)) {
287+
return false;
288+
}
283289
}
290+
return true;
284291
}
285292

286293
/**
@@ -304,13 +311,16 @@ class Array : public Base<Array<T, N, P>> {
304311
* less than the size N of the array. Otherwise an error is thrown.
305312
* More than one index can be set at once. Unset indexes are left as is.
306313
*/
307-
void validate_object(const Conf& c) const {
314+
bool validate_object(const Conf& c, std::optional<SchemaError>& err) const {
308315
assert(c->type() == JsonType::object);
309316
for (const auto& kv : c->items()) {
310317
const auto& key = kv.key();
311318
this->parse_index(c, key);
312-
prototype_.validate(c.at(key));
319+
if (prototype_.validate(c.at(key), err)) {
320+
return false;
321+
}
313322
}
323+
return true;
314324
}
315325

316326
/**
@@ -329,25 +339,25 @@ class Array : public Base<Array<T, N, P>> {
329339
// We'd like to just be able to use std::stoul, but unfortunately
330340
// the standard library seems to think strings like "234x" are ok.
331341
if (s.empty()) {
332-
this->throw_error(c, "invalid index key in object, require integer, got ''");
342+
throw this->error(c, "invalid index key in object, require integer, got ''");
333343
} else if (s.size() > 1 && s[0] == '0') {
334-
this->throw_error(c, "invalid index key in object, require base-10 value, got '{}'", s);
344+
throw this->error(c, "invalid index key in object, require base-10 value, got '{}'", s);
335345
}
336346
for (char ch : s) {
337347
if (ch < '0' || ch > '9') {
338-
this->throw_error(c, "invalid index key in object, require integer, got '{}'", s);
348+
throw this->error(c, "invalid index key in object, require integer, got '{}'", s);
339349
}
340350
}
341351
size_t idx = std::stoul(s);
342352
if (idx >= N) {
343-
this->throw_error(c, "out-of-range index key in object, require < {}, got '{}'", N, s);
353+
throw this->error(c, "out-of-range index key in object, require < {}, got '{}'", N, s);
344354
}
345355
return idx;
346356
}
347357

348-
[[noreturn]] void throw_wrong_type(const Conf& c) const {
358+
[[nodiscard]] SchemaError wrong_type(const Conf& c) const {
349359
std::string got = to_string(c->type());
350-
this->throw_error(c, "property must have type array or object, got {}", got);
360+
return this->error(c, "property must have type array or object, got {}", got);
351361
}
352362

353363
void deserialize_from_object(Type& array, const Conf& c) const {

fable/include/fable/schema/boolean.hpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class Boolean : public Base<Boolean> {
3939

4040
public: // Overrides
4141
Json json_schema() const override;
42-
void validate(const Conf& c) const override;
42+
bool validate(const Conf& c, std::optional<SchemaError>& err) const override;
4343
using Interface::to_json;
4444
void to_json(Json& j) const override;
4545
void from_conf(const Conf& c) override;

fable/include/fable/schema/confable.hpp

+3-3
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,11 @@ class FromConfable : public Base<FromConfable<T>> {
6363
return j;
6464
}
6565

66-
void validate(const Conf& c) const override {
66+
bool validate(const Conf& c, std::optional<SchemaError>& err) const override {
6767
if (ptr_ == nullptr) {
68-
schema_.validate(c);
68+
return schema_.validate(c, err);
6969
} else {
70-
ptr_->validate(c);
70+
return ptr_->validate(c, err);
7171
}
7272
}
7373

fable/include/fable/schema/const.hpp

+6-5
Original file line numberDiff line numberDiff line change
@@ -57,29 +57,30 @@ class Const : public Base<Const<T, P>> {
5757
return j;
5858
}
5959

60-
void validate(const Conf& c) const override {
60+
bool validate(const Conf& c, std::optional<SchemaError>& err) const override {
6161
Type tmp = prototype_.deserialize(c);
6262
if (tmp != constant_) {
63-
this->throw_error(c, "expected const value {}, got {}", constant_, tmp);
63+
return this->set_error(err, c, "expected const value {}, got {}", constant_, tmp);
6464
}
65+
return true;
6566
}
6667

6768
using Interface::to_json;
6869
void to_json(Json& j) const override { j = serialize(constant_); }
6970

70-
void from_conf(const Conf& c) override { validate(c); }
71+
void from_conf(const Conf& c) override { this->validate_or_throw(c); }
7172

7273
Json serialize(const Type& x) const { return prototype_.serialize(x); }
7374

7475
Type deserialize(const Conf& c) const {
75-
validate(c);
76+
this->validate_or_throw(c);
7677
return constant_;
7778
}
7879

7980
void serialize_into(Json& j, const Type& x) const { prototype_.serialize_into(j, x); }
8081

8182
void deserialize_into(const Conf& c, Type& x) const {
82-
validate(c);
83+
this->validate_or_throw(c);
8384
x = constant_;
8485
}
8586

fable/include/fable/schema/custom.hpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ class CustomDeserializer : public schema::Interface {
6565
void set_description(std::string s) override { return impl_->set_description(std::move(s)); }
6666
Json usage() const override { return impl_->usage(); }
6767
Json json_schema() const override { return impl_->json_schema(); };
68-
void validate(const Conf& c) const override { impl_->validate(c); }
68+
bool validate(const Conf& c, std::optional<SchemaError>& err) const override { return impl_->validate(c, err); }
6969
void to_json(Json& j) const override { impl_->to_json(j); }
7070

7171
void from_conf(const Conf& c) override {

0 commit comments

Comments
 (0)