$darkmode
interface.hpp
Go to the documentation of this file.
1 /*
2  * Copyright 2020 Robert Bosch GmbH
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  * SPDX-License-Identifier: Apache-2.0
17  */
24 #pragma once
25 
26 #include <memory> // for shared_ptr<>
27 #include <optional> // for optional<>
28 #include <string> // for string
29 #include <type_traits> // for enable_if_t<>, is_base_of<>
30 #include <utility> // for move
31 
32 #include <fable/conf.hpp> // for Conf
33 #include <fable/error.hpp> // for SchemaError
34 #include <fable/fable_fwd.hpp> // for Confable
35 #include <fable/json.hpp> // for Json
36 
37 namespace fable::schema {
38 
67 class Interface {
68  protected:
69  Interface() = default;
70  Interface(const Interface&) = default;
71  Interface(Interface&&) noexcept = default;
72  Interface& operator=(const Interface&) = default;
73  Interface& operator=(Interface&&) noexcept = default;
74 
75  public:
76  virtual ~Interface() noexcept = default;
77 
84  [[nodiscard]] virtual std::unique_ptr<Interface> clone() const = 0;
85 
92  [[nodiscard]] virtual bool is_variant() const { return false; }
93 
102  [[nodiscard]] virtual JsonType type() const = 0;
103 
120  [[nodiscard]] virtual std::string type_string() const = 0;
121 
125  [[nodiscard]] virtual bool is_required() const = 0;
126 
130  [[nodiscard]] virtual const std::string& description() const = 0;
131 
135  virtual void set_description(std::string s) = 0;
136 
161  [[nodiscard]] virtual Json usage() const = 0;
162 
189  [[nodiscard]] virtual Json json_schema() const = 0;
190 
204  virtual bool validate(const Conf& c, std::optional<SchemaError>& error) const = 0;
205 
218  virtual void validate_or_throw(const Conf& c) const final {
219  if (auto err = fail(c); err) {
220  throw std::move(*err);
221  }
222  }
223 
237  [[nodiscard]] virtual std::optional<SchemaError> fail(const Conf& c) const final {
238  std::optional<SchemaError> err;
239  validate(c, err);
240  return err;
241  }
242 
246  [[nodiscard]] virtual bool is_valid(const Conf& c) const final { return !fail(c); }
247 
254  [[nodiscard]] virtual Json to_json() const {
255  Json j;
256  to_json(j);
257  return j;
258  }
259 
266  virtual void to_json(Json&) const = 0;
267 
273  virtual void from_conf(const Conf&) = 0;
274 
281  virtual void reset_ptr() = 0;
282 };
283 
292 template <typename S>
293 using enable_if_schema_t = std::enable_if_t<std::is_base_of_v<Interface, S>>;
294 
295 // ------------------------------------------------------------------------- //
296 
297 class Box final : public Interface {
298  public: // Constructors
299  Box() = default;
300  Box(const Box&) = default;
301  Box(Box&&) noexcept = default;
302  Box& operator=(const Box&) = default;
303  Box& operator=(Box&&) noexcept = default;
304  ~Box() noexcept override = default;
305 
306  Box(std::unique_ptr<Interface> i) : impl_(std::move(i)) { assert(impl_); } // NOLINT
307  Box(std::shared_ptr<Interface> i) : impl_(std::move(i)) { assert(impl_); } // NOLINT
308 
309  public: // Special
313  [[nodiscard]] std::shared_ptr<Interface> get() { return impl_; }
314 
324  template <typename T>
325  [[nodiscard]] std::shared_ptr<T> as() const {
326  assert(impl_ != nullptr);
327  auto downcast_ptr = std::dynamic_pointer_cast<T>(impl_);
328  if (downcast_ptr == nullptr) {
329  // If you can't figure out the type of this even from this error, set
330  // a breakpoint here in gdb and run:
331  //
332  // p dynamic_cast<Interface*>(impl_.get())
333  //
334  // That will give you something like:
335  //
336  // $8 = (fable::schema::Interface *) 0x5555559dfd30 <
337  // vtable for fable::schema::FromConfable<
338  // (anonymous namespace)::NormalDistribution<double>, 0
339  // >
340  // +16>
341  //
342  // Which is much more specific, since the output of FromConfable and
343  // Struct can be equivalent, for example.
344  throw SchemaError{Conf{}, this->json_schema(),
345  "cannot dynamic_pointer_cast to type T, got nullptr"};
346  }
347  return downcast_ptr;
348  }
349 
356  template <typename T>
357  [[nodiscard]] std::shared_ptr<T> as_unsafe() const {
358  return std::dynamic_pointer_cast<T>(impl_);
359  }
360 
361  [[nodiscard]] Box reset_pointer() && {
362  reset_ptr();
363  return std::move(*this);
364  }
365 
366  public: // Overrides
367  using Interface::to_json;
368  [[nodiscard]] std::unique_ptr<Interface> clone() const override { return impl_->clone(); }
369  [[nodiscard]] JsonType type() const override { return impl_->type(); }
370  [[nodiscard]] std::string type_string() const override { return impl_->type_string(); }
371  [[nodiscard]] bool is_required() const override { return impl_->is_required(); }
372  [[nodiscard]] const std::string& description() const override { return impl_->description(); }
373  void set_description(std::string s) override { return impl_->set_description(std::move(s)); }
374  [[nodiscard]] Json usage() const override { return impl_->usage(); }
375  [[nodiscard]] Json json_schema() const override { return impl_->json_schema(); };
376  bool validate(const Conf& c, std::optional<SchemaError>& err) const override {
377  return impl_->validate(c, err);
378  }
379  void to_json(Json& j) const override { impl_->to_json(j); }
380  void from_conf(const Conf& c) override { impl_->from_conf(c); }
381  void reset_ptr() override { impl_->reset_ptr(); }
382 
383  friend void to_json(Json& j, const Box& b) { b.impl_->to_json(j); }
384 
385  private:
386  std::shared_ptr<Interface> impl_{nullptr};
387 };
388 
389 // ------------------------------------------------------------------------- //
390 
397 template <typename CRTP>
398 class Base : public Interface {
399  protected:
400  Base() = default;
401  Base(const Base<CRTP>&) = default;
402  Base(Base<CRTP>&&) noexcept = default;
403  Base<CRTP>& operator=(const Base<CRTP>&) = default;
404  Base<CRTP>& operator=(Base<CRTP>&&) noexcept = default;
405 
406  Base(JsonType t, std::string desc) : type_(t), desc_(std::move(desc)) {}
407  explicit Base(JsonType t) : type_(t) {}
408  explicit Base(std::string desc) : desc_(std::move(desc)) {}
409 
410  public:
411  ~Base() noexcept override = default;
412 
413  [[nodiscard]] std::unique_ptr<Interface> clone() const override {
414  return std::make_unique<CRTP>(static_cast<CRTP const&>(*this));
415  }
416  [[nodiscard]] operator Box() const { return Box{this->clone()}; }
417 
418  [[nodiscard]] JsonType type() const override { return type_; }
419  [[nodiscard]] std::string type_string() const override { return to_string(type_); }
420 
421  [[nodiscard]] Json usage() const override {
422  const auto* required = required_ ? "!" : "";
423  if (desc_.empty()) {
424  return type_string() + required;
425  }
426  return fmt::format("{}{} :: {}", type_string(), required, desc_);
427  }
428 
429  [[nodiscard]] bool is_required() const override { return required_; }
430  [[nodiscard]] CRTP require() && {
431  required_ = true;
432  return std::move(*dynamic_cast<CRTP*>(this));
433  }
434  [[nodiscard]] CRTP required(bool value) && {
435  required_ = value;
436  return std::move(*dynamic_cast<CRTP*>(this));
437  }
438 
439  [[nodiscard]] CRTP reset_pointer() && {
440  reset_ptr();
441  return std::move(*dynamic_cast<CRTP*>(this));
442  }
443 
444  [[nodiscard]] bool has_description() const { return !desc_.empty(); }
445  void set_description(std::string s) override { desc_ = std::move(s); }
446  [[nodiscard]] const std::string& description() const override { return desc_; }
447  [[nodiscard]] CRTP description(std::string desc) && {
448  desc_ = std::move(desc);
449  return std::move(*dynamic_cast<CRTP*>(this));
450  }
451 
452  protected:
459  bool validate_type(const Conf& c, std::optional<SchemaError>& err) const {
460  if (c->type() != type_) {
461  if (c->type() == JsonType::number_unsigned && type_ == JsonType::number_integer) {
462  return true;
463  }
464 
465  return this->set_error(err, c, "require type {}, got {}", type_string(),
466  to_string(c->type()));
467  }
468  return true;
469  }
470 
471  template <typename... Args>
472  [[nodiscard]] SchemaError error(const Conf& c, std::string_view format, Args&&... args) const {
473  return SchemaError{c, this->json_schema(), format, std::forward<Args>(args)...};
474  }
475 
476  [[nodiscard]] SchemaError error(const ConfError& e) const {
477  return SchemaError{e, this->json_schema()};
478  }
479 
480  [[nodiscard]] SchemaError wrong_type(const Conf& c) const {
481  return error(error::WrongType(c, type_));
482  }
483 
484  template <typename... Args>
485  bool set_error(std::optional<SchemaError>& err, const Conf& c, std::string_view format,
486  Args&&... args) const {
487  err.emplace(this->error(c, format, std::forward<Args>(args)...));
488  return false;
489  }
490 
491  bool set_error(std::optional<SchemaError>& err, const ConfError& e) const {
492  err.emplace(this->error(e));
493  return false;
494  }
495 
496  bool set_error(std::optional<SchemaError>& err, SchemaError&& e) const {
497  err.emplace(std::move(e));
498  return false;
499  }
500 
501  bool set_wrong_type(std::optional<SchemaError>& err, const Conf& c) const {
502  err.emplace(this->wrong_type(c));
503  return false;
504  }
505 
506  void augment_schema(Json& j) const {
507  if (!desc_.empty()) {
508  j["description"] = desc_;
509  }
510  }
511 
512  protected:
513  JsonType type_{JsonType::null};
514  bool required_{false};
515  std::string desc_{};
516 };
517 
518 // ------------------------------------------------------------------------- //
519 
528 template <typename T>
529 using enable_if_confable_t = std::enable_if_t<std::is_base_of_v<Confable, T>>;
530 
539 template <typename T>
540 using enable_if_not_confable_t = std::enable_if_t<!std::is_base_of_v<Confable, T>>;
541 
542 template <typename T, typename S = std::string, std::enable_if_t<std::is_base_of_v<Confable, T>, int> = 0>
543 auto make_prototype(S&& desc = "");
544 
545 template <typename T, typename S = std::string, std::enable_if_t<!std::is_base_of_v<Confable, T>, int> = 0>
546 auto make_prototype(S&& desc = "");
547 
548 } // namespace fable::schema
Definition: conf.hpp:82
Definition: error.hpp:149
Definition: interface.hpp:398
bool is_required() const override
Definition: interface.hpp:429
const std::string & description() const override
Definition: interface.hpp:446
Json usage() const override
Definition: interface.hpp:421
JsonType type() const override
Definition: interface.hpp:418
std::unique_ptr< Interface > clone() const override
Definition: interface.hpp:413
bool validate_type(const Conf &c, std::optional< SchemaError > &err) const
Definition: interface.hpp:459
std::string type_string() const override
Definition: interface.hpp:419
void set_description(std::string s) override
Definition: interface.hpp:445
Definition: interface.hpp:297
void from_conf(const Conf &c) override
Definition: interface.hpp:380
Json json_schema() const override
Definition: interface.hpp:375
std::shared_ptr< T > as_unsafe() const
Definition: interface.hpp:357
std::string type_string() const override
Definition: interface.hpp:370
void to_json(Json &j) const override
Definition: interface.hpp:379
void reset_ptr() override
Definition: interface.hpp:381
virtual Json to_json() const
Definition: interface.hpp:254
Json usage() const override
Definition: interface.hpp:374
bool is_required() const override
Definition: interface.hpp:371
std::shared_ptr< T > as() const
Definition: interface.hpp:325
std::unique_ptr< Interface > clone() const override
Definition: interface.hpp:368
std::shared_ptr< Interface > get()
Definition: interface.hpp:313
void set_description(std::string s) override
Definition: interface.hpp:373
JsonType type() const override
Definition: interface.hpp:369
bool validate(const Conf &c, std::optional< SchemaError > &err) const override
Definition: interface.hpp:376
const std::string & description() const override
Definition: interface.hpp:372
Definition: interface.hpp:67
virtual void validate_or_throw(const Conf &c) const final
Definition: interface.hpp:218
virtual Json json_schema() const =0
virtual bool validate(const Conf &c, std::optional< SchemaError > &error) const =0
virtual std::optional< SchemaError > fail(const Conf &c) const final
Definition: interface.hpp:237
virtual Json to_json() const
Definition: interface.hpp:254
virtual void from_conf(const Conf &)=0
virtual const std::string & description() const =0
virtual bool is_variant() const
Definition: interface.hpp:92
virtual bool is_required() const =0
virtual JsonType type() const =0
virtual bool is_valid(const Conf &c) const final
Definition: interface.hpp:246
virtual void reset_ptr()=0
virtual std::string type_string() const =0
virtual void set_description(std::string s)=0
virtual std::unique_ptr< Interface > clone() const =0
virtual void to_json(Json &) const =0
virtual Json usage() const =0
nlohmann::json Json
Definition: fable_fwd.hpp:35
nlohmann::json::value_t JsonType
Definition: json.hpp:78