oatpp / oatpp

🌱Light and powerful C++ web framework for highly scalable and resource-efficient web application. It's zero-dependency and easy-portable.
https://oatpp.io/
Apache License 2.0
7.73k stars 1.3k forks source link

[RFC] Endpoint: DTO Validators #388

Open bamkrs opened 3 years ago

bamkrs commented 3 years ago

Those could be implemented very easily:

1. User-Defined Validator

Could be a nice class with static functions i.E.

namespace MyValidators {
  class FooDtoValidator {
  public:
    static oatpp::Response validate(oatpp::Object<FooDto> foo) {
      OATPP_HTTP_ASSERT(foo->bar > 0 && foo->bar < 130, Status::CODE_400, "Not a valid bar!);
      return nullptr;
    }
  }
}

2. BODY_DTO extension

Extend the BODY_DTO macro with a third parameter that accepts the validator

#define OATPP_MACRO_API_CONTROLLER_BODY_DTO(TYPE, NAME, VALIDATOR) \
if(!getDefaultObjectMapper()) { \
  return ApiController::handleError(Status::CODE_500, "ObjectMapper was NOT set. Can't deserialize the request body."); \
} \
const auto& NAME = \
__request->readBodyToDto<TYPE>(getDefaultObjectMapper().get()); \
if(!NAME) { \
  return ApiController::handleError(Status::CODE_400, "Missing valid body parameter '" NAME "'"); \
} \
const oatpp::Response& invalid = VALIDATOR(NAME); \
if (!invalid) { \
  return invalid; \
}

3. Have an endpoint use the validator

ENDPOINT("PUT", "foos", putFoos,
         BODY_DTO(oatpp::Object<FooDto>, foo, MyValidators::FooDtoValidator::validate))
{
  return createDtoResponse(Status::CODE_200, m_userService.getUserById(userId));
}

Its a very simple API which won't kill our performance. It has some drawbacks (i.E. relying on static functions) but there are workarounds for this to. Or we could come up with some Interface were we could pass actual instances with.

lganzzzo commented 3 years ago

Let's have an abstract base BodyValidator

class BodyValidator {
public:
  virtual ~BodyValidator() = default;
  virtual oatpp::OutgoingResponse validate(const oatpp::Void& polymorph) = 0;
}

Thus we can have predefined validators and we can inherit other validators:

class NotNull : public BodyValidator {
public:
  oatpp::OutgoingResponse validate(const oatpp::Void& polymorph) override {

    auto dispatcher = static_cast<const oatpp::data::mapping::type::__class::AbstractObject::PolymorphicDispatcher*>(polymorph.valueType->polymorphicDispatcher);
    auto fields = dispatcher->getProperties()->getList();
    auto object = static_cast<oatpp::BaseObject*>(polymorph.get());

    for (auto const& field : fields) {
      auto value = field->get(object);
      OATPP_HTTP_ASSERT(value, Status::CODE_400, oatpp::String("Field '" + field->name + "' not set."));
    }
  }
}