userver: /data/code/userver/libraries/grpc-protovalidate/src/grpc-protovalidate/validate.cpp Source File
Loading...
Searching...
No Matches
validate.cpp
1#include <userver/grpc-protovalidate/validate.hpp>
2
3#include <stdexcept>
4#include <string>
5#include <utility>
6#include <vector>
7
8#include <algorithm>
9
10#include <fmt/core.h>
11#include <google/protobuf/arena.h>
12#include <google/protobuf/message.h>
13#include <google/protobuf/repeated_ptr_field.h>
14#include <google/rpc/status.pb.h>
15#include <grpcpp/support/status.h>
16
17#include <grpc-protovalidate/impl/utils.hpp>
18#include <userver/compiler/thread_local.hpp>
19#include <userver/grpc-protovalidate/buf_validate.hpp>
20#include <userver/ugrpc/status_utils.hpp>
21#include <userver/utils/assert.hpp>
22
23USERVER_NAMESPACE_BEGIN
24
25namespace grpc_protovalidate {
26
27namespace {
28
29compiler::ThreadLocal kValidatorFactory = [] { return impl::CreateProtoValidatorFactory(); };
30
31} // namespace
32
33ValidationError::ValidationError(Type type, std::string description, std::string message_name)
34 : type_(type),
35 description_(fmt::format("Message '{}' validation error: {}", message_name, std::move(description))),
36 result_(std::nullopt),
37 message_name_(std::move(message_name))
38{}
39
40ValidationError::ValidationError(buf::validate::ValidationResult result, std::string message_name)
41 : type_(Type::kRule),
42 description_(fmt::format(
43 "Message '{}' validation error: {} constraint(s) violated",
44 message_name,
45 result.violations_size()
46 )),
47 result_(std::move(result)),
48 message_name_(std::move(message_name))
49{}
50
51ValidationError::Type ValidationError::GetType() const { return type_; }
52
53const std::string& ValidationError::GetMessageName() const { return message_name_; }
54
55const std::string& ValidationError::GetDescription() const { return description_; }
56
57const std::vector<buf::validate::RuleViolation>& ValidationError::GetViolations() const {
58 if (GetType() == Type::kInternal || !result_.has_value()) {
59 static constexpr std::vector<buf::validate::RuleViolation> kNoViolations;
60 return kNoViolations;
61 }
62 return result_->violations();
63}
64
65grpc::Status ValidationError::GetGrpcStatus(bool include_violations, grpc::StatusCode rule_violation_status) const {
66 google::rpc::Status gstatus;
67 gstatus.set_message(GetDescription());
68 switch (GetType()) {
69 case Type::kInternal:
70 gstatus.set_code(grpc::StatusCode::INTERNAL);
71 break;
72 case Type::kRule: {
73 gstatus.set_code(rule_violation_status);
74 break;
75 }
76 }
77 if (include_violations) {
78 gstatus.add_details()->PackFrom(MakeViolationsProto());
79 }
80 return ugrpc::ToGrpcStatus(gstatus);
81}
82
83buf::validate::Violations ValidationError::MakeViolationsProto() const {
84 buf::validate::Violations proto;
85 std::ranges::transform(
86 GetViolations(),
87 RepeatedPtrFieldBackInserter(proto.mutable_violations()),
88 [](const buf::validate::RuleViolation& violation) { return violation.proto(); }
89 );
90 return proto;
91}
92
93logging::LogHelper& operator<<(logging::LogHelper& lh, const ValidationError& error) {
94 lh << error.GetDescription();
95 for (const buf::validate::RuleViolation& violation : error.GetViolations()) {
96 lh << violation.proto();
97 }
98 return lh;
99}
100
101ValidationResult::ValidationResult(ValidationError error)
102 : error_(std::move(error))
103{}
104
105bool ValidationResult::IsSuccess() const { return !error_.has_value(); }
106
107const ValidationError& ValidationResult::GetError() const& {
108 if (IsSuccess()) {
109 throw std::logic_error("Requested error for success validation result");
110 }
111 return error_.value();
112}
113
114ValidationError&& ValidationResult::GetError() && {
115 if (IsSuccess()) {
116 throw std::logic_error("Requested error for success validation result");
117 }
118 return std::move(error_).value();
119}
120
121ValidationResult ValidateMessage(const google::protobuf::Message& message, const ValidationParams& params) {
122 auto validator_factory = kValidatorFactory.Use();
123 google::protobuf::Arena arena;
124 auto validator = (*validator_factory)->NewValidator(&arena, params.fail_fast);
125 auto result = validator.Validate(message);
126 if (!result.ok()) {
127 return ValidationError(
128 ValidationError::Type::kInternal,
129 fmt::format(
130 "internal protovalidate error (check constraints syntax in the proto file) - {}",
131 result.status().ToString()
132 ),
133 message.GetTypeName()
134 );
135 }
136 if (result->violations_size() != 0) {
137 return ValidationError(result.value(), message.GetTypeName());
138 }
139 return ValidationResult();
140}
141
142} // namespace grpc_protovalidate
143
144USERVER_NAMESPACE_END