diff --git a/buf/validate/conformance/runner.cc b/buf/validate/conformance/runner.cc index 8508a46..f54beef 100644 --- a/buf/validate/conformance/runner.cc +++ b/buf/validate/conformance/runner.cc @@ -69,7 +69,7 @@ harness::TestResult TestRunner::runTestCase(const google::protobuf::Message& mes break; } } else if (violations_or.value().violations_size() > 0) { - *result.mutable_validation_error() = std::move(violations_or).value(); + *result.mutable_validation_error() = violations_or->proto(); } else { result.set_success(true); } diff --git a/buf/validate/internal/BUILD.bazel b/buf/validate/internal/BUILD.bazel index 3cd409e..444c18d 100644 --- a/buf/validate/internal/BUILD.bazel +++ b/buf/validate/internal/BUILD.bazel @@ -31,11 +31,23 @@ cc_library( ], ) +cc_library( + name = "proto_field", + hdrs = ["proto_field.h"], + deps = [ + "@com_github_bufbuild_protovalidate//proto/protovalidate/buf/validate:validate_proto_cc", + "@com_google_absl//absl/status", + "@com_google_cel_cpp//eval/public:cel_value", + "@com_google_protobuf//:protobuf", + ], +) + cc_library( name = "constraint_rules", hdrs = ["constraint_rules.h"], deps = [ "@com_github_bufbuild_protovalidate//proto/protovalidate/buf/validate:validate_proto_cc", + ":proto_field", "@com_google_absl//absl/status", "@com_google_cel_cpp//eval/public:cel_value", "@com_google_protobuf//:protobuf", diff --git a/buf/validate/internal/cel_constraint_rules.cc b/buf/validate/internal/cel_constraint_rules.cc index 33c35d7..ce9eb9c 100644 --- a/buf/validate/internal/cel_constraint_rules.cc +++ b/buf/validate/internal/cel_constraint_rules.cc @@ -30,7 +30,7 @@ absl::Status ProcessConstraint( ConstraintContext& ctx, const google::api::expr::runtime::BaseActivation& activation, const CompiledConstraint& expr) { - auto result_or = expr.expr->Evaluate(activation, ctx.arena); + auto result_or= expr.expr->Evaluate(activation, ctx.arena); if (!result_or.ok()) { return result_or.status(); } @@ -38,22 +38,24 @@ absl::Status ProcessConstraint( if (result.IsBool()) { if (!result.BoolOrDie()) { // Add violation with the constraint message. - Violation& violation = *ctx.violations.add_violations(); + Violation violation; violation.set_message(expr.constraint.message()); violation.set_constraint_id(expr.constraint.id()); if (expr.rulePath.has_value()) { *violation.mutable_rule() = *expr.rulePath; } + ctx.violations.emplace_back(violation, absl::nullopt, absl::nullopt); } } else if (result.IsString()) { if (!result.StringOrDie().value().empty()) { // Add violation with custom message. - Violation& violation = *ctx.violations.add_violations(); + Violation violation; violation.set_message(std::string(result.StringOrDie().value())); violation.set_constraint_id(expr.constraint.id()); if (expr.rulePath.has_value()) { *violation.mutable_rule() = *expr.rulePath; } + ctx.violations.emplace_back(violation, absl::nullopt, absl::nullopt); } } else if (result.IsError()) { const cel::runtime::CelError& error = *result.ErrorOrDie(); @@ -126,11 +128,15 @@ absl::Status CelConstraintRules::ValidateCel( absl::Status status = absl::OkStatus(); for (const auto& expr : exprs_) { - if (rules_.IsMessage() && expr.rule) { + if (rules_.IsMessage() && expr.rule != nullptr) { activation.InsertValue( "rule", ProtoFieldToCelValue(rules_.MessageOrDie(), expr.rule, ctx.arena)); } + int pos = ctx.violations.size(); status = ProcessConstraint(ctx, activation, expr); + if (rules_.IsMessage() && expr.rule != nullptr && ctx.violations.size() > pos) { + ctx.setRuleValue(ProtoField{rules_.MessageOrDie(), expr.rule}, pos); + } if (ctx.shouldReturn(status)) { break; } diff --git a/buf/validate/internal/constraint_rules.h b/buf/validate/internal/constraint_rules.h index 4e57026..5dc66c1 100644 --- a/buf/validate/internal/constraint_rules.h +++ b/buf/validate/internal/constraint_rules.h @@ -15,12 +15,38 @@ #pragma once #include "absl/status/status.h" +#include "absl/strings/escaping.h" #include "buf/validate/validate.pb.h" +#include "buf/validate/internal/proto_field.h" #include "eval/public/cel_value.h" #include "google/protobuf/arena.h" #include "google/protobuf/message.h" namespace buf::validate::internal { +inline std::string fieldPathString(const FieldPath &path); + +/// ConstraintViolation is a wrapper for the protobuf Violation that provides additional in-memory +/// information, specifically, references to the in-memory values for the field and rule. +class ConstraintViolation { + friend struct ConstraintContext; + + public: + ConstraintViolation( + Violation proto, + const absl::optional& fieldValue, + const absl::optional& ruleValue) + : proto_{std::move(proto)}, fieldValue_{fieldValue}, ruleValue_{ruleValue} {} + + [[nodiscard]] const Violation& proto() const { return proto_; } + [[nodiscard]] absl::optional field_value() const { return fieldValue_; } + [[nodiscard]] absl::optional rule_value() const { return ruleValue_; } + + private: + Violation proto_; + absl::optional fieldValue_; + absl::optional ruleValue_; +}; + struct ConstraintContext { ConstraintContext() : failFast(false), arena(nullptr) {} ConstraintContext(const ConstraintContext&) = delete; @@ -28,26 +54,58 @@ struct ConstraintContext { bool failFast; google::protobuf::Arena* arena; - Violations violations; + std::vector violations; [[nodiscard]] bool shouldReturn(const absl::Status status) { - return !status.ok() || (failFast && violations.violations_size() > 0); + return !status.ok() || (failFast && !violations.empty()); } void appendFieldPathElement(const FieldPathElement &element, int start) { - for (int i = start; i < violations.violations_size(); i++) { - auto* violation = violations.mutable_violations(i); - *violation->mutable_field()->mutable_elements()->Add() = element; + for (int i = start; i < violations.size(); i++) { + *violations[i].proto_.mutable_field()->mutable_elements()->Add() = element; } } void appendRulePathElement(std::initializer_list suffix, int start) { - for (int i = start; i < violations.violations_size(); i++) { - auto* violation = violations.mutable_violations(i); - auto* elements = violation->mutable_rule()->mutable_elements(); + for (int i = start; i < violations.size(); i++) { + auto* elements = violations[i].proto_.mutable_rule()->mutable_elements(); std::copy(suffix.begin(), suffix.end(), RepeatedPtrFieldBackInserter(elements)); } } + + void setFieldValue(ProtoField field, int start) { + for (int i = start; i < violations.size(); i++) { + violations[i].fieldValue_ = field; + } + } + + void setRuleValue(ProtoField rule, int start) { + for (int i = start; i < violations.size(); i++) { + violations[i].ruleValue_ = rule; + } + } + + void setForKey(int start) { + for (int i = start; i < violations.size(); i++) { + violations[i].proto_.set_for_key(true); + } + } + + void finalize() { + for (ConstraintViolation& violation : violations) { + if (violation.proto().has_field()) { + std::reverse( + violation.proto_.mutable_field()->mutable_elements()->begin(), + violation.proto_.mutable_field()->mutable_elements()->end()); + *violation.proto_.mutable_field_path() = internal::fieldPathString(violation.proto().field()); + } + if (violation.proto().has_rule()) { + std::reverse( + violation.proto_.mutable_rule()->mutable_elements()->begin(), + violation.proto_.mutable_rule()->mutable_elements()->end()); + } + } + } }; class ConstraintRules { @@ -62,4 +120,33 @@ class ConstraintRules { ConstraintContext& ctx, const google::protobuf::Message& message) const = 0; }; +inline std::string fieldPathString(const FieldPath &path) { + std::string result; + for (const FieldPathElement& element : path.elements()) { + if (!result.empty()) { + result += '.'; + } + switch (element.subscript_case()) { + case FieldPathElement::kIndex: + absl::StrAppend(&result, element.field_name(), "[", std::to_string(element.index()), "]"); + break; + case FieldPathElement::kBoolKey: + absl::StrAppend(&result, element.field_name(), element.bool_key() ? "[true]" : "[false]"); + break; + case FieldPathElement::kIntKey: + absl::StrAppend(&result, element.field_name(), "[", std::to_string(element.int_key()), "]"); + break; + case FieldPathElement::kUintKey: + absl::StrAppend(&result, element.field_name(), "[", std::to_string(element.uint_key()), "]"); + break; + case FieldPathElement::kStringKey: + absl::StrAppend(&result, element.field_name(), "[\"", absl::CEscape(element.string_key()), "\"]"); + break; + case FieldPathElement::SUBSCRIPT_NOT_SET: + absl::StrAppend(&result, element.field_name()); + } + } + return result; +} + } // namespace buf::validate::internal diff --git a/buf/validate/internal/constraints.cc b/buf/validate/internal/constraints.cc index 34ef104..a828473 100644 --- a/buf/validate/internal/constraints.cc +++ b/buf/validate/internal/constraints.cc @@ -121,6 +121,9 @@ absl::Status MessageConstraintRules::Validate( absl::Status FieldConstraintRules::Validate( ConstraintContext& ctx, const google::protobuf::Message& message) const { + static const google::protobuf::FieldDescriptor* requiredField = + FieldConstraints::descriptor()->FindFieldByNumber( + FieldConstraints::kRequiredFieldNumber); google::api::expr::runtime::Activation activation; cel::runtime::CelValue result; std::string subPath; @@ -132,12 +135,16 @@ absl::Status FieldConstraintRules::Validate( if (ignoreEmpty_) { return absl::OkStatus(); } else if (required_) { - auto& violation = *ctx.violations.add_violations(); + Violation violation; *violation.mutable_constraint_id() = "required"; *violation.mutable_message() = "value is required"; *violation.mutable_field()->mutable_elements()->Add() = fieldPathElement(field_); *violation.mutable_rule()->mutable_elements()->Add() = staticFieldPathElement(); + ctx.violations.emplace_back( + std::move(violation), + ProtoField{&message, field_}, + ProtoField{&fieldConstraints_, requiredField}); } } } else if (field_->is_repeated()) { @@ -148,23 +155,31 @@ absl::Status FieldConstraintRules::Validate( if (ignoreEmpty_) { return absl::OkStatus(); } else if (required_) { - auto& violation = *ctx.violations.add_violations(); + Violation violation; *violation.mutable_constraint_id() = "required"; *violation.mutable_message() = "value is required"; *violation.mutable_field()->mutable_elements()->Add() = fieldPathElement(field_); *violation.mutable_rule()->mutable_elements()->Add() = staticFieldPathElement(); + ctx.violations.emplace_back( + std::move(violation), + ProtoField{&message, field_}, + ProtoField{&fieldConstraints_, requiredField}); } } } else { if (!message.GetReflection()->HasField(message, field_)) { if (required_) { - auto& violation = *ctx.violations.add_violations(); + Violation violation; *violation.mutable_constraint_id() = "required"; *violation.mutable_message() = "value is required"; *violation.mutable_field()->mutable_elements()->Add() = fieldPathElement(field_); *violation.mutable_rule()->mutable_elements()->Add() = staticFieldPathElement(); + ctx.violations.emplace_back( + std::move(violation), + ProtoField{&message, field_}, + ProtoField{&fieldConstraints_, requiredField}); return absl::OkStatus(); } else if (ignoreEmpty_) { return absl::OkStatus(); @@ -174,7 +189,7 @@ absl::Status FieldConstraintRules::Validate( if (anyRules_ != nullptr && field_->cpp_type() == google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE) { const auto& anyMsg = message.GetReflection()->GetMessage(message, field_); - auto status = ValidateAny(ctx, field_, anyMsg); + auto status = ValidateAny(ctx, ProtoField{&message, field_}, anyMsg); if (!status.ok()) { return status; } @@ -190,32 +205,40 @@ absl::Status FieldConstraintRules::Validate( } } activation.InsertValue("this", result); - int pos = ctx.violations.violations_size(); + int pos = ctx.violations.size(); auto status = ValidateCel(ctx, activation); if (!status.ok()) { return status; } - if (ctx.violations.violations_size() > pos) { + if (ctx.violations.size() > pos) { auto element = fieldPathElement(field_); ctx.appendFieldPathElement(element, pos); + ctx.setFieldValue(ProtoField{&message, field_}, pos); } return status; } absl::Status EnumConstraintRules::Validate( ConstraintContext& ctx, const google::protobuf::Message& message) const { + static const google::protobuf::FieldDescriptor* definedOnlyField = + EnumRules::descriptor()->FindFieldByNumber( + EnumRules::kDefinedOnlyFieldNumber); if (auto status = Base::Validate(ctx, message); ctx.shouldReturn(status)) { return status; } if (definedOnly_) { auto value = message.GetReflection()->GetEnumValue(message, field_); if (field_->enum_type()->FindValueByNumber(value) == nullptr) { - auto& violation = *ctx.violations.add_violations(); + Violation violation; *violation.mutable_constraint_id() = "enum.defined_only"; *violation.mutable_message() = "enum value must be defined"; *violation.mutable_field()->mutable_elements()->Add() = fieldPathElement(field_); *violation.mutable_rule()->mutable_elements()->Add() = fieldPathElement(EnumRules::descriptor()->FindFieldByNumber(EnumRules::kDefinedOnlyFieldNumber)); *violation.mutable_rule()->mutable_elements()->Add() = fieldPathElement(FieldConstraints::descriptor()->FindFieldByNumber(FieldConstraints::kEnumFieldNumber)); + ctx.violations.emplace_back( + std::move(violation), + ProtoField{&message, field_}, + ProtoField{&fieldConstraints_.enum_(), definedOnlyField}); } } return absl::OkStatus(); @@ -240,13 +263,13 @@ absl::Status RepeatedConstraintRules::Validate( } cel::runtime::Activation activation; activation.InsertValue("this", item); - int pos = ctx.violations.violations_size(); + int pos = ctx.violations.size(); status = itemRules_->ValidateCel(ctx, activation); if (itemRules_->getAnyRules() != nullptr) { const auto& anyMsg = message.GetReflection()->GetRepeatedMessage(message, field_, i); - status = itemRules_->ValidateAny(ctx, nullptr, anyMsg); + status = itemRules_->ValidateAny(ctx, ProtoField{&message, field_, i}, anyMsg); } - if (ctx.violations.violations_size() > pos) { + if (ctx.violations.size() > pos) { FieldPathElement element = fieldPathElement(field_); element.set_index(i); ctx.appendFieldPathElement(element, pos); @@ -254,6 +277,7 @@ absl::Status RepeatedConstraintRules::Validate( fieldPathElement(RepeatedRules::descriptor()->FindFieldByNumber(RepeatedRules::kItemsFieldNumber)), fieldPathElement(FieldConstraints::descriptor()->FindFieldByNumber(FieldConstraints::kRepeatedFieldNumber)), }, pos); + ctx.setFieldValue(ProtoField{&message, field_, i}, pos); } if (ctx.shouldReturn(status)) { return status; @@ -279,7 +303,7 @@ absl::Status MapConstraintRules::Validate( const auto& keys = *std::move(keys_or).value(); for (int i = 0; i < mapVal.size(); i++) { const auto& elemMsg = message.GetReflection()->GetRepeatedMessage(message, field_, i); - int pos = ctx.violations.violations_size(); + int pos = ctx.violations.size(); auto key = keys[i]; if (keyRules_ != nullptr) { if (!keyRules_->getIgnoreEmpty() || !isEmptyItem(key)) { @@ -288,14 +312,13 @@ absl::Status MapConstraintRules::Validate( if (!status.ok()) { return status; } - if (ctx.violations.violations_size() > pos) { + if (ctx.violations.size() > pos) { ctx.appendRulePathElement({ fieldPathElement(MapRules::descriptor()->FindFieldByNumber(MapRules::kKeysFieldNumber)), fieldPathElement(FieldConstraints::descriptor()->FindFieldByNumber(FieldConstraints::kMapFieldNumber)), }, pos); - for (int j = pos; j < ctx.violations.violations_size(); j++) { - ctx.violations.mutable_violations(j)->set_for_key(true); - } + ctx.setFieldValue(ProtoField{&elemMsg, keyField}, pos); + ctx.setForKey(pos); } activation.RemoveValueEntry("this"); } @@ -304,21 +327,22 @@ absl::Status MapConstraintRules::Validate( auto value = *mapVal[key]; if (!valueRules_->getIgnoreEmpty() || !isEmptyItem(value)) { activation.InsertValue("this", value); - int valuePos = ctx.violations.violations_size(); + int valuePos = ctx.violations.size(); status = valueRules_->ValidateCel(ctx, activation); if (!status.ok()) { return status; } - if (ctx.violations.violations_size() > valuePos) { + if (ctx.violations.size() > valuePos) { ctx.appendRulePathElement({ fieldPathElement(MapRules::descriptor()->FindFieldByNumber(MapRules::kValuesFieldNumber)), fieldPathElement(FieldConstraints::descriptor()->FindFieldByNumber(FieldConstraints::kMapFieldNumber)), }, valuePos); + ctx.setFieldValue(ProtoField{&elemMsg, valueField}, pos); } activation.RemoveValueEntry("this"); } } - if (ctx.violations.violations_size() > pos) { + if (ctx.violations.size() > pos) { FieldPathElement element = fieldPathElement(field_); if (auto status = setPathElementMapKey(&element, elemMsg, keyField, valueField); !status.ok()) { @@ -335,8 +359,14 @@ absl::Status MapConstraintRules::Validate( absl::Status FieldConstraintRules::ValidateAny( ConstraintContext& ctx, - const google::protobuf::FieldDescriptor* field, + const ProtoField& field, const google::protobuf::Message& anyMsg) const { + static const google::protobuf::FieldDescriptor* anyInField = + AnyRules::descriptor()->FindFieldByNumber( + AnyRules::kInFieldNumber); + static const google::protobuf::FieldDescriptor* anyNotInField = + AnyRules::descriptor()->FindFieldByNumber( + AnyRules::kNotInFieldNumber); const auto* typeUriField = anyMsg.GetDescriptor()->FindFieldByName("type_url"); if (typeUriField == nullptr || typeUriField->type() != google::protobuf::FieldDescriptor::TYPE_STRING) { @@ -353,26 +383,38 @@ absl::Status FieldConstraintRules::ValidateAny( } } if (!found) { - auto& violation = *ctx.violations.add_violations(); + Violation violation; *violation.mutable_constraint_id() = "any.in"; *violation.mutable_message() = "type URL must be in the allow list"; - if (field != nullptr) { - *violation.mutable_field()->mutable_elements()->Add() = fieldPathElement(field); + if (field.index() == -1) { + *violation.mutable_field()->mutable_elements()->Add() = fieldPathElement(field.descriptor()); } - *violation.mutable_rule()->mutable_elements()->Add() = fieldPathElement(AnyRules::descriptor()->FindFieldByNumber(AnyRules::kInFieldNumber)); - *violation.mutable_rule()->mutable_elements()->Add() = fieldPathElement(FieldConstraints::descriptor()->FindFieldByNumber(FieldConstraints::kAnyFieldNumber)); + *violation.mutable_rule()->mutable_elements()->Add() = + staticFieldPathElement(); + *violation.mutable_rule()->mutable_elements()->Add() = + staticFieldPathElement(); + ctx.violations.emplace_back( + std::move(violation), + field, + ProtoField{&fieldConstraints_.any(), anyInField}); } } for (const auto& block : anyRules_->not_in()) { if (block == typeUri) { - auto& violation = *ctx.violations.add_violations(); + Violation violation; *violation.mutable_constraint_id() = "any.not_in"; *violation.mutable_message() = "type URL must not be in the block list"; - if (field != nullptr) { - *violation.mutable_field()->mutable_elements()->Add() = fieldPathElement(field); + if (field.index() == -1) { + *violation.mutable_field()->mutable_elements()->Add() = fieldPathElement(field.descriptor()); } - *violation.mutable_rule()->mutable_elements()->Add() = fieldPathElement(AnyRules::descriptor()->FindFieldByNumber(AnyRules::kNotInFieldNumber)); - *violation.mutable_rule()->mutable_elements()->Add() = fieldPathElement(FieldConstraints::descriptor()->FindFieldByNumber(FieldConstraints::kAnyFieldNumber)); + *violation.mutable_rule()->mutable_elements()->Add() = + staticFieldPathElement(); + *violation.mutable_rule()->mutable_elements()->Add() = + staticFieldPathElement(); + ctx.violations.emplace_back( + std::move(violation), + field, + ProtoField{&fieldConstraints_.any(), anyNotInField}); break; } } @@ -384,10 +426,11 @@ absl::Status OneofConstraintRules::Validate( const google::protobuf::Message& message) const { if (required_) { if (!message.GetReflection()->HasOneof(message, oneof_)) { - auto& violation = *ctx.violations.add_violations(); + Violation violation; *violation.mutable_constraint_id() = "required"; *violation.mutable_message() = "exactly one field is required in oneof"; *violation.mutable_field()->mutable_elements()->Add() = oneofPathElement(*oneof_); + ctx.violations.emplace_back(std::move(violation), absl::nullopt, absl::nullopt); } } return absl::OkStatus(); diff --git a/buf/validate/internal/constraints.h b/buf/validate/internal/constraints.h index 05f4df0..ac62279 100644 --- a/buf/validate/internal/constraints.h +++ b/buf/validate/internal/constraints.h @@ -40,7 +40,8 @@ class FieldConstraintRules : public CelConstraintRules { const google::protobuf::FieldDescriptor* desc, const FieldConstraints& field, const AnyRules* anyRules = nullptr) - : field_(desc), + : fieldConstraints_(field), + field_(desc), mapEntryField_(desc->containing_type()->options().map_entry()), ignoreEmpty_(field.ignore() == IGNORE_IF_DEFAULT_VALUE || field.ignore() == IGNORE_IF_UNPOPULATED || @@ -56,7 +57,7 @@ class FieldConstraintRules : public CelConstraintRules { absl::Status ValidateAny( ConstraintContext& ctx, - const google::protobuf::FieldDescriptor* field, + const ProtoField& field, const google::protobuf::Message& anyMsg) const; [[nodiscard]] const AnyRules* getAnyRules() const { return anyRules_; } @@ -66,6 +67,7 @@ class FieldConstraintRules : public CelConstraintRules { [[nodiscard]] bool getIgnoreDefault() const { return ignoreDefault_; } protected: + const FieldConstraints& fieldConstraints_; const google::protobuf::FieldDescriptor* field_ = nullptr; bool mapEntryField_ = false; bool ignoreEmpty_ = false; @@ -214,33 +216,4 @@ inline absl::Status setPathElementMapKey( return {}; } -inline std::string fieldPathString(const FieldPath &path) { - std::string result; - for (const FieldPathElement& element : path.elements()) { - if (!result.empty()) { - result += '.'; - } - switch (element.subscript_case()) { - case FieldPathElement::kIndex: - absl::StrAppend(&result, element.field_name(), "[", std::to_string(element.index()), "]"); - break; - case FieldPathElement::kBoolKey: - absl::StrAppend(&result, element.field_name(), element.bool_key() ? "[true]" : "[false]"); - break; - case FieldPathElement::kIntKey: - absl::StrAppend(&result, element.field_name(), "[", std::to_string(element.int_key()), "]"); - break; - case FieldPathElement::kUintKey: - absl::StrAppend(&result, element.field_name(), "[", std::to_string(element.uint_key()), "]"); - break; - case FieldPathElement::kStringKey: - absl::StrAppend(&result, element.field_name(), "[\"", absl::CEscape(element.string_key()), "\"]"); - break; - case FieldPathElement::SUBSCRIPT_NOT_SET: - absl::StrAppend(&result, element.field_name()); - } - } - return result; -} - } // namespace buf::validate::internal diff --git a/buf/validate/internal/constraints_test.cc b/buf/validate/internal/constraints_test.cc index 96f12c1..545ccf2 100644 --- a/buf/validate/internal/constraints_test.cc +++ b/buf/validate/internal/constraints_test.cc @@ -53,14 +53,14 @@ class ExpressionTest : public testing::Test { absl::Status Validate( google::api::expr::runtime::Activation& activation, - std::vector& violations) { + std::vector& violations) { ConstraintContext ctx; ctx.arena = &arena_; auto status = constraints_->ValidateCel(ctx, activation); if (!status.ok()) { return status; } - for (const auto& violation : ctx.violations.violations()) { + for (auto& violation : ctx.violations) { violations.push_back(violation); } return absl::OkStatus(); @@ -72,12 +72,12 @@ TEST_F(ExpressionTest, BoolResult) { ASSERT_TRUE(AddConstraint("false", "always fails", "always-fails").ok()); cel::runtime::Activation ctx; - std::vector violations; + std::vector violations; auto status = Validate(ctx, violations); ASSERT_TRUE(status.ok()) << status; ASSERT_EQ(violations.size(), 1); - EXPECT_EQ(violations[0].message(), "always fails"); - EXPECT_EQ(violations[0].constraint_id(), "always-fails"); + EXPECT_EQ(violations[0].proto().message(), "always fails"); + EXPECT_EQ(violations[0].proto().constraint_id(), "always-fails"); } TEST_F(ExpressionTest, StringResult) { @@ -85,18 +85,18 @@ TEST_F(ExpressionTest, StringResult) { ASSERT_TRUE(AddConstraint("'error'", "always fails", "always-fails").ok()); cel::runtime::Activation ctx; - std::vector violations; + std::vector violations; auto status = Validate(ctx, violations); ASSERT_TRUE(status.ok()) << status; ASSERT_EQ(violations.size(), 1); - EXPECT_EQ(violations[0].message(), "error"); - EXPECT_EQ(violations[0].constraint_id(), "always-fails"); + EXPECT_EQ(violations[0].proto().message(), "error"); + EXPECT_EQ(violations[0].proto().constraint_id(), "always-fails"); } TEST_F(ExpressionTest, Error) { ASSERT_TRUE(AddConstraint("1/0", "always fails", "always-fails").ok()); cel::runtime::Activation ctx; - std::vector violations; + std::vector violations; auto status = Validate(ctx, violations); ASSERT_FALSE(status.ok()) << status; EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument); @@ -106,7 +106,7 @@ TEST_F(ExpressionTest, Error) { TEST_F(ExpressionTest, BadType) { ASSERT_TRUE(AddConstraint("1", "always fails", "always-fails").ok()); cel::runtime::Activation ctx; - std::vector violations; + std::vector violations; auto status = Validate(ctx, violations); ASSERT_FALSE(status.ok()) << status; EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument); diff --git a/buf/validate/internal/proto_field.h b/buf/validate/internal/proto_field.h new file mode 100644 index 0000000..6c1e25e --- /dev/null +++ b/buf/validate/internal/proto_field.h @@ -0,0 +1,155 @@ +// Copyright 2023 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include + +#include "absl/status/status.h" +#include "buf/validate/validate.pb.h" +#include "google/protobuf/arena.h" +#include "google/protobuf/message.h" + +namespace buf::validate::internal { + +/// ProtoField represents a field value. This may be a scalar field on a message, a repeated or map +/// field on a message, or an item within a repeated or map field on a message. +class ProtoField { + public: + /// Constructs a new ProtoField representing the field specified by descriptor under the provided + /// message. The provided descriptor should be contained by the provided message, although the + /// field need not be present. If the field is a repeated or map field, index may be specified to + /// point to a specific index; otherwise, the sentinel value -1 (default) should be provided. + ProtoField( + const google::protobuf::Message* message, + const google::protobuf::FieldDescriptor* descriptor, + int index = -1) + : message_{message}, descriptor_{descriptor}, index_{index} {} + + [[nodiscard]] const google::protobuf::Message* message() const { return message_; } + + [[nodiscard]] const google::protobuf::FieldDescriptor* descriptor() const { return descriptor_; } + + [[nodiscard]] int index() const { return index_; } + + /// Returns true if this ProtoField instance refers to a repeated or map field, rather than a + /// singular value. + [[nodiscard]] bool is_repeated() const { return descriptor_->is_repeated() && index_ == -1; } + + /// If this ProtoField references a repeated field, returns the number of items in the repeated + /// field. Otherwise, always returns zero. + [[nodiscard]] int size() const { + if (!is_repeated()) { + return 0; + } + return message_->GetReflection()->FieldSize(*message_, descriptor_); + } + + /// If this ProtoField references a repeated field, and the provided index is a valid index into + /// this repeated field, returns a ProtoField referencing the specific item at the provided index. + [[nodiscard]] absl::optional at(int index) const { + if (index < 0 || !is_repeated() || + index >= message_->GetReflection()->FieldSize(*message_, descriptor_)) { + return absl::nullopt; + } + return ProtoField{message_, descriptor_, index}; + } + + using Value = absl::variant< + absl::monostate, + int64_t, + uint64_t, + double, + bool, + std::string, + const google::protobuf::Message*>; + + /// Returns a variant representing the value of this ProtoField, if this field references a + /// singular value. If is_repeated() returns true, you must instead call at(...) to get a specific + /// item within the repeated field or map; this method will return the monostate value otherwise. + [[nodiscard]] Value variant() const { + if (is_repeated()) { + return absl::monostate{}; + } + if (!descriptor_->is_repeated() && + !message_->GetReflection()->HasField(*message_, descriptor_)) { + return absl::monostate{}; + } + switch (descriptor_->cpp_type()) { + case google::protobuf::FieldDescriptor::CPPTYPE_INT32: + if (index_ != -1) { + return static_cast( + message_->GetReflection()->GetRepeatedInt32(*message_, descriptor_, index_)); + } + return static_cast(message_->GetReflection()->GetInt32(*message_, descriptor_)); + case google::protobuf::FieldDescriptor::CPPTYPE_INT64: + if (index_ != -1) { + return message_->GetReflection()->GetRepeatedInt64(*message_, descriptor_, index_); + } + return message_->GetReflection()->GetInt64(*message_, descriptor_); + case google::protobuf::FieldDescriptor::CPPTYPE_ENUM: + if (index_ != -1) { + return static_cast( + message_->GetReflection()->GetRepeatedEnumValue(*message_, descriptor_, index_)); + } + return static_cast( + message_->GetReflection()->GetEnumValue(*message_, descriptor_)); + case google::protobuf::FieldDescriptor::CPPTYPE_UINT32: + if (index_ != -1) { + return static_cast( + message_->GetReflection()->GetRepeatedUInt32(*message_, descriptor_, index_)); + } + return static_cast(message_->GetReflection()->GetUInt32(*message_, descriptor_)); + case google::protobuf::FieldDescriptor::CPPTYPE_UINT64: + if (index_ != -1) { + return message_->GetReflection()->GetRepeatedUInt64(*message_, descriptor_, index_); + } + return message_->GetReflection()->GetUInt64(*message_, descriptor_); + case google::protobuf::FieldDescriptor::CPPTYPE_FLOAT: + if (index_ != -1) { + return static_cast( + message_->GetReflection()->GetRepeatedFloat(*message_, descriptor_, index_)); + } + return static_cast(message_->GetReflection()->GetFloat(*message_, descriptor_)); + case google::protobuf::FieldDescriptor::CPPTYPE_DOUBLE: + if (index_ != -1) { + return message_->GetReflection()->GetRepeatedDouble(*message_, descriptor_, index_); + } + return message_->GetReflection()->GetDouble(*message_, descriptor_); + case google::protobuf::FieldDescriptor::CPPTYPE_BOOL: + if (index_ != -1) { + return message_->GetReflection()->GetRepeatedBool(*message_, descriptor_, index_); + } + return message_->GetReflection()->GetBool(*message_, descriptor_); + case google::protobuf::FieldDescriptor::CPPTYPE_STRING: + if (index_ != -1) { + return message_->GetReflection()->GetRepeatedString(*message_, descriptor_, index_); + } + return message_->GetReflection()->GetString(*message_, descriptor_); + case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE: + if (index_ != -1) { + return &message_->GetReflection()->GetRepeatedMessage(*message_, descriptor_, index_); + } + return &message_->GetReflection()->GetMessage(*message_, descriptor_); + } + return absl::monostate{}; + } + + private: + const google::protobuf::Message* message_; + const google::protobuf::FieldDescriptor* descriptor_; + int index_; +}; + +} // namespace buf::validate::internal diff --git a/buf/validate/validator.cc b/buf/validate/validator.cc index 093a2fb..654f96b 100644 --- a/buf/validate/validator.cc +++ b/buf/validate/validator.cc @@ -70,9 +70,9 @@ absl::Status Validator::ValidateFields( for (int i = 0; i < size; i++) { const auto& elemMsg = message.GetReflection()->GetRepeatedMessage(message, field, i); const auto& valueMsg = elemMsg.GetReflection()->GetMessage(elemMsg, valueField); - int pos = ctx.violations.violations_size(); + int pos = ctx.violations.size(); auto status = ValidateMessage(ctx, valueMsg); - if (pos < ctx.violations.violations_size()) { + if (pos < ctx.violations.size()) { FieldPathElement element = internal::fieldPathElement(field); if (auto status = internal::setPathElementMapKey(&element, elemMsg, keyField, valueField); !status.ok()) { @@ -87,10 +87,10 @@ absl::Status Validator::ValidateFields( } else if (field->is_repeated()) { int size = message.GetReflection()->FieldSize(message, field); for (int i = 0; i < size; i++) { - int pos = ctx.violations.violations_size(); + int pos = ctx.violations.size(); const auto& subMsg = message.GetReflection()->GetRepeatedMessage(message, field, i); auto status = ValidateMessage(ctx, subMsg); - if (pos < ctx.violations.violations_size()) { + if (pos < ctx.violations.size()) { FieldPathElement element = internal::fieldPathElement(field); element.set_index(i); ctx.appendFieldPathElement(element, pos); @@ -101,9 +101,9 @@ absl::Status Validator::ValidateFields( } } else { const auto& subMsg = message.GetReflection()->GetMessage(message, field); - int pos = ctx.violations.violations_size(); + int pos = ctx.violations.size(); auto status = ValidateMessage(ctx, subMsg); - if (pos < ctx.violations.violations_size()) { + if (pos < ctx.violations.size()) { ctx.appendFieldPathElement(internal::fieldPathElement(field), pos); } if (ctx.shouldReturn(status)) { @@ -114,7 +114,7 @@ absl::Status Validator::ValidateFields( return absl::OkStatus(); } -absl::StatusOr Validator::Validate(const google::protobuf::Message& message) { +absl::StatusOr Validator::Validate(const google::protobuf::Message& message) { internal::ConstraintContext ctx; ctx.failFast = failFast_; ctx.arena = arena_; @@ -122,20 +122,8 @@ absl::StatusOr Validator::Validate(const google::protobuf::Message& if (!status.ok()) { return status; } - for (Violation& violation : *ctx.violations.mutable_violations()) { - if (violation.has_field()) { - std::reverse( - violation.mutable_field()->mutable_elements()->begin(), - violation.mutable_field()->mutable_elements()->end()); - *violation.mutable_field_path() = internal::fieldPathString(violation.field()); - } - if (violation.has_rule()) { - std::reverse( - violation.mutable_rule()->mutable_elements()->begin(), - violation.mutable_rule()->mutable_elements()->end()); - } - } - return std::move(ctx.violations); + ctx.finalize(); + return ValidationResult{std::move(ctx.violations)}; } absl::StatusOr> ValidatorFactory::New() { diff --git a/buf/validate/validator.h b/buf/validate/validator.h index 921fdcd..2106afd 100644 --- a/buf/validate/validator.h +++ b/buf/validate/validator.h @@ -16,6 +16,7 @@ #include #include +#include #include "buf/validate/validate.pb.h" #include "buf/validate/internal/constraints.h" @@ -26,8 +27,39 @@ namespace buf::validate { +using internal::ConstraintViolation; +using internal::ProtoField; + class ValidatorFactory; +/// The ValidationResult class contains information about the validation. +class ValidationResult { + public: + ValidationResult(std::vector violations) + : violations_{std::move(violations)} {} + + [[nodiscard]] Violations proto() { + Violations proto{}; + std::transform( + violations_.begin(), + violations_.end(), + RepeatedPtrFieldBackInserter(proto.mutable_violations()), + [](ConstraintViolation& violation) { return violation.proto(); }); + return proto; + } + + [[nodiscard]] bool success() const { return violations_.empty(); } + + [[nodiscard]] const std::vector& violations() const { return violations_; } + + [[nodiscard]] ConstraintViolation violations(int i) const { return violations_.at(i); } + + [[nodiscard]] int violations_size() const { return violations_.size(); } + + private: + std::vector violations_; +}; + /// A validator is a non-thread safe object that can be used to validate /// google.protobuf.Message objects. /// @@ -37,10 +69,10 @@ class Validator { public: /// Validate a message. /// - /// An empty Violations object is returned, if the message passes validation. - /// If the message fails validation, a Violations object with the violations is returned. + /// A ValidationResult with no violations is returned, if the message passes validation. + /// If the message fails validation, a ViolationResult with the violations is returned. /// If there is an error while validating, a Status with the error is returned. - absl::StatusOr Validate(const google::protobuf::Message& message); + absl::StatusOr Validate(const google::protobuf::Message& message); // Move only. Validator(const Validator&) = delete; @@ -122,7 +154,6 @@ class ValidatorFactory { ValidatorFactory() = default; const internal::Constraints* GetMessageConstraints(const google::protobuf::Descriptor* desc); - const internal::Constraints& AddConstraints(const google::protobuf::Descriptor* desc); }; } // namespace buf::validate diff --git a/buf/validate/validator_test.cc b/buf/validate/validator_test.cc index 6a084a9..2d1ecc4 100644 --- a/buf/validate/validator_test.cc +++ b/buf/validate/validator_test.cc @@ -23,12 +23,24 @@ #include "eval/public/builtin_func_registrar.h" #include "eval/public/cel_expr_builder_factory.h" #include "gtest/gtest.h" +#include "gmock/gmock.h" #include "parser/parser.h" namespace buf::validate { namespace cel = google::api::expr; namespace { +using ::testing::Matcher; +using ::testing::VariantWith; + +Matcher FieldValueOf(Matcher matcher) { + return ResultOf([](ProtoField field) { return field.variant(); }, matcher); +} + +Matcher FieldIndex(int index, Matcher matcher) { + return ResultOf([index](ProtoField field) { return field.at(index); }, Optional(matcher)); +} + TEST(ValidatorTest, ParseAndEval) { std::string input = "1 + 2"; auto pexpr_or = cel::parser::Parse(input); @@ -67,9 +79,9 @@ TEST(ValidatorTest, ValidateBool) { auto violations_or = validator.Validate(bool_const_false); ASSERT_TRUE(violations_or.ok()) << violations_or.status(); ASSERT_EQ(violations_or.value().violations_size(), 1); - EXPECT_EQ(violations_or.value().violations(0).field_path(), "val"); - EXPECT_EQ(violations_or.value().violations(0).constraint_id(), "bool.const"); - EXPECT_EQ(violations_or.value().violations(0).message(), "value must equal false"); + EXPECT_EQ(violations_or.value().violations(0).proto().field_path(), "val"); + EXPECT_EQ(violations_or.value().violations(0).proto().constraint_id(), "bool.const"); + EXPECT_EQ(violations_or.value().violations(0).proto().message(), "value must equal false"); } TEST(ValidatorTest, ValidateStrRepeatedUniqueSuccess) { @@ -129,9 +141,15 @@ TEST(ValidatorTest, ValidateRelativeURIFailure) { auto violations_or = validator.Validate(str_uri); ASSERT_TRUE(violations_or.ok()) << violations_or.status(); EXPECT_EQ(violations_or.value().violations_size(), 1); - EXPECT_EQ(violations_or.value().violations(0).field_path(), "val"); - EXPECT_EQ(violations_or.value().violations(0).constraint_id(), "string.uri"); - EXPECT_EQ(violations_or.value().violations(0).message(), "value must be a valid URI"); + EXPECT_EQ(violations_or.value().violations(0).proto().field_path(), "val"); + EXPECT_EQ(violations_or.value().violations(0).proto().constraint_id(), "string.uri"); + EXPECT_EQ(violations_or.value().violations(0).proto().message(), "value must be a valid URI"); + EXPECT_THAT( + violations_or.value().violations(0).field_value(), + Optional(FieldValueOf(VariantWith("/foo/bar?baz=quux")))); + EXPECT_THAT( + violations_or.value().violations(0).rule_value(), + Optional(FieldValueOf(VariantWith(true)))); } TEST(ValidatorTest, ValidateAbsoluteURIRefWithQueryStringSuccess) { @@ -145,11 +163,6 @@ TEST(ValidatorTest, ValidateAbsoluteURIRefWithQueryStringSuccess) { auto violations_or = validator.Validate(str_uri_ref); ASSERT_TRUE(violations_or.ok()) << violations_or.status(); EXPECT_EQ(violations_or.value().violations_size(), 0); - for (const auto& violation : violations_or.value().violations()) { - EXPECT_EQ(violation.field_path(), ""); - EXPECT_EQ(violation.constraint_id(), ""); - EXPECT_EQ(violation.message(), ""); - } } TEST(ValidatorTest, ValidateAbsoluteURIRefSuccess) { @@ -189,9 +202,15 @@ TEST(ValidatorTest, ValidateBadURIRefFailure) { auto violations_or = validator.Validate(str_uri_ref); ASSERT_TRUE(violations_or.ok()) << violations_or.status(); EXPECT_EQ(violations_or.value().violations_size(), 1); - EXPECT_EQ(violations_or.value().violations(0).field_path(), "val"); - EXPECT_EQ(violations_or.value().violations(0).constraint_id(), "string.uri_ref"); - EXPECT_EQ(violations_or.value().violations(0).message(), "value must be a valid URI"); + EXPECT_EQ(violations_or.value().violations(0).proto().field_path(), "val"); + EXPECT_EQ(violations_or.value().violations(0).proto().constraint_id(), "string.uri_ref"); + EXPECT_EQ(violations_or.value().violations(0).proto().message(), "value must be a valid URI"); + EXPECT_THAT( + violations_or.value().violations(0).field_value(), + Optional(FieldValueOf(VariantWith("!@#$%^&*")))); + EXPECT_THAT( + violations_or.value().violations(0).rule_value(), + Optional(FieldValueOf(VariantWith(true)))); } TEST(ValidatorTest, ValidateStrRepeatedUniqueFailure) { @@ -206,10 +225,19 @@ TEST(ValidatorTest, ValidateStrRepeatedUniqueFailure) { auto violations_or = validator.Validate(str_repeated); ASSERT_TRUE(violations_or.ok()) << violations_or.status(); EXPECT_EQ(violations_or.value().violations_size(), 1); - EXPECT_EQ(violations_or.value().violations(0).field_path(), "val"); - EXPECT_EQ(violations_or.value().violations(0).constraint_id(), "repeated.unique"); + EXPECT_EQ(violations_or.value().violations(0).proto().field_path(), "val"); + EXPECT_EQ(violations_or.value().violations(0).proto().constraint_id(), "repeated.unique"); EXPECT_EQ( - violations_or.value().violations(0).message(), "repeated value must contain unique items"); + violations_or.value().violations(0).proto().message(), + "repeated value must contain unique items"); + EXPECT_THAT( + violations_or.value().violations(0).field_value(), + Optional(AllOf( + FieldIndex(0, FieldValueOf(VariantWith("1"))), + FieldIndex(1, FieldValueOf(VariantWith("1")))))); + EXPECT_THAT( + violations_or.value().violations(0).rule_value(), + Optional(FieldValueOf(VariantWith(true)))); } TEST(ValidatorTest, ValidateStringContainsFailure) { @@ -223,10 +251,10 @@ TEST(ValidatorTest, ValidateStringContainsFailure) { auto violations_or = validator.Validate(str_contains); ASSERT_TRUE(violations_or.ok()) << violations_or.status(); EXPECT_EQ(violations_or.value().violations_size(), 1); - EXPECT_EQ(violations_or.value().violations(0).field_path(), "val"); - EXPECT_EQ(violations_or.value().violations(0).constraint_id(), "string.contains"); + EXPECT_EQ(violations_or.value().violations(0).proto().field_path(), "val"); + EXPECT_EQ(violations_or.value().violations(0).proto().constraint_id(), "string.contains"); EXPECT_EQ( - violations_or.value().violations(0).message(), "value does not contain substring `bar`"); + violations_or.value().violations(0).proto().message(), "value does not contain substring `bar`"); } TEST(ValidatorTest, ValidateStringContainsSuccess) { @@ -253,9 +281,9 @@ TEST(ValidatorTest, ValidateBytesContainsFailure) { auto violations_or = validator.Validate(bytes_contains); ASSERT_TRUE(violations_or.ok()) << violations_or.status(); EXPECT_EQ(violations_or.value().violations_size(), 1); - EXPECT_EQ(violations_or.value().violations(0).field_path(), "val"); - EXPECT_EQ(violations_or.value().violations(0).constraint_id(), "bytes.contains"); - EXPECT_EQ(violations_or.value().violations(0).message(), "value does not contain 626172"); + EXPECT_EQ(violations_or.value().violations(0).proto().field_path(), "val"); + EXPECT_EQ(violations_or.value().violations(0).proto().constraint_id(), "bytes.contains"); + EXPECT_EQ(violations_or.value().violations(0).proto().message(), "value does not contain 626172"); } TEST(ValidatorTest, ValidateBytesContainsSuccess) { @@ -282,9 +310,9 @@ TEST(ValidatorTest, ValidateStartsWithFailure) { auto violations_or = validator.Validate(str_starts_with); ASSERT_TRUE(violations_or.ok()) << violations_or.status(); EXPECT_EQ(violations_or.value().violations_size(), 1); - EXPECT_EQ(violations_or.value().violations(0).field_path(), "val"); - EXPECT_EQ(violations_or.value().violations(0).constraint_id(), "string.prefix"); - EXPECT_EQ(violations_or.value().violations(0).message(), "value does not have prefix `foo`"); + EXPECT_EQ(violations_or.value().violations(0).proto().field_path(), "val"); + EXPECT_EQ(violations_or.value().violations(0).proto().constraint_id(), "string.prefix"); + EXPECT_EQ(violations_or.value().violations(0).proto().message(), "value does not have prefix `foo`"); } TEST(ValidatorTest, ValidateStartsWithSuccess) { @@ -311,9 +339,9 @@ TEST(ValidatorTest, ValidateEndsWithFailure) { auto violations_or = validator.Validate(str_ends_with); ASSERT_TRUE(violations_or.ok()) << violations_or.status(); EXPECT_EQ(violations_or.value().violations_size(), 1); - EXPECT_EQ(violations_or.value().violations(0).field_path(), "val"); - EXPECT_EQ(violations_or.value().violations(0).constraint_id(), "string.suffix"); - EXPECT_EQ(violations_or.value().violations(0).message(), "value does not have suffix `baz`"); + EXPECT_EQ(violations_or.value().violations(0).proto().field_path(), "val"); + EXPECT_EQ(violations_or.value().violations(0).proto().constraint_id(), "string.suffix"); + EXPECT_EQ(violations_or.value().violations(0).proto().message(), "value does not have suffix `baz`"); } TEST(ValidatorTest, ValidateHostnameSuccess) { @@ -340,8 +368,8 @@ TEST(ValidatorTest, ValidateGarbageHostnameFailure) { auto violations_or = validator.Validate(str_hostname); ASSERT_TRUE(violations_or.ok()) << violations_or.status(); EXPECT_EQ(violations_or.value().violations_size(), 1); - EXPECT_EQ(violations_or.value().violations(0).field_path(), "val"); - EXPECT_EQ(violations_or.value().violations(0).constraint_id(), "string.hostname"); + EXPECT_EQ(violations_or.value().violations(0).proto().field_path(), "val"); + EXPECT_EQ(violations_or.value().violations(0).proto().constraint_id(), "string.hostname"); } TEST(ValidatorTest, ValidateHostnameFailure) { @@ -355,8 +383,8 @@ TEST(ValidatorTest, ValidateHostnameFailure) { auto violations_or = validator.Validate(str_hostname); ASSERT_TRUE(violations_or.ok()) << violations_or.status(); EXPECT_EQ(violations_or.value().violations_size(), 1); - EXPECT_EQ(violations_or.value().violations(0).field_path(), "val"); - EXPECT_EQ(violations_or.value().violations(0).constraint_id(), "string.hostname"); + EXPECT_EQ(violations_or.value().violations(0).proto().field_path(), "val"); + EXPECT_EQ(violations_or.value().violations(0).proto().constraint_id(), "string.hostname"); } TEST(ValidatorTest, ValidateHostnameDoubleDotFailure) { @@ -370,8 +398,8 @@ TEST(ValidatorTest, ValidateHostnameDoubleDotFailure) { auto violations_or = validator.Validate(str_hostname); ASSERT_TRUE(violations_or.ok()) << violations_or.status(); EXPECT_EQ(violations_or.value().violations_size(), 1); - EXPECT_EQ(violations_or.value().violations(0).field_path(), "val"); - EXPECT_EQ(violations_or.value().violations(0).constraint_id(), "string.hostname"); + EXPECT_EQ(violations_or.value().violations(0).proto().field_path(), "val"); + EXPECT_EQ(violations_or.value().violations(0).proto().constraint_id(), "string.hostname"); } TEST(ValidatorTest, ValidateEndsWithSuccess) { @@ -398,15 +426,15 @@ TEST(ValidatorTest, MessageConstraint) { auto violations_or = validator.Validate(message_expressions); ASSERT_TRUE(violations_or.ok()) << violations_or.status(); ASSERT_EQ(violations_or.value().violations_size(), 3); - EXPECT_EQ(violations_or.value().violations(0).field_path(), ""); - EXPECT_EQ(violations_or.value().violations(0).constraint_id(), "message_expression_scalar"); - EXPECT_EQ(violations_or.value().violations(0).message(), "a must be less than b"); - EXPECT_EQ(violations_or.value().violations(1).field_path(), ""); - EXPECT_EQ(violations_or.value().violations(1).constraint_id(), "message_expression_enum"); - EXPECT_EQ(violations_or.value().violations(1).message(), "c must not equal d"); - EXPECT_EQ(violations_or.value().violations(2).field_path(), "e"); - EXPECT_EQ(violations_or.value().violations(2).constraint_id(), "message_expression_nested"); - EXPECT_EQ(violations_or.value().violations(2).message(), "a must be greater than b"); + EXPECT_EQ(violations_or.value().violations(0).proto().field_path(), ""); + EXPECT_EQ(violations_or.value().violations(0).proto().constraint_id(), "message_expression_scalar"); + EXPECT_EQ(violations_or.value().violations(0).proto().message(), "a must be less than b"); + EXPECT_EQ(violations_or.value().violations(1).proto().field_path(), ""); + EXPECT_EQ(violations_or.value().violations(1).proto().constraint_id(), "message_expression_enum"); + EXPECT_EQ(violations_or.value().violations(1).proto().message(), "c must not equal d"); + EXPECT_EQ(violations_or.value().violations(2).proto().field_path(), "e"); + EXPECT_EQ(violations_or.value().violations(2).proto().constraint_id(), "message_expression_nested"); + EXPECT_EQ(violations_or.value().violations(2).proto().message(), "a must be greater than b"); } } // namespace