Skip to content

Commit

Permalink
Introduce Issue and IssueCollector types for collecting issues encoun…
Browse files Browse the repository at this point in the history
…tered during expression planning.

PiperOrigin-RevId: 576634566
  • Loading branch information
jnthntatum authored and copybara-github committed Oct 27, 2023
1 parent 1b92d73 commit 07a158c
Show file tree
Hide file tree
Showing 5 changed files with 274 additions and 0 deletions.
9 changes: 9 additions & 0 deletions runtime/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -416,3 +416,12 @@ cc_test(
"@com_google_protobuf//:protobuf",
],
)

cc_library(
name = "runtime_issue",
hdrs = ["runtime_issue.h"],
deps = [
"//base:kind",
"@com_google_absl//absl/status",
],
)
20 changes: 20 additions & 0 deletions runtime/internal/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,23 @@ cc_library(
"@com_google_absl//absl/time",
],
)

cc_library(
name = "issue_collector",
hdrs = ["issue_collector.h"],
deps = [
"//runtime:runtime_issue",
"@com_google_absl//absl/status",
],
)

cc_test(
name = "issue_collector_test",
srcs = ["issue_collector_test.cc"],
deps = [
":issue_collector",
"//internal:testing",
"//runtime:runtime_issue",
"@com_google_absl//absl/status",
],
)
63 changes: 63 additions & 0 deletions runtime/internal/issue_collector.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright 2023 Google LLC
//
// 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
//
// https://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.
#ifndef THIRD_PARTY_CEL_CPP_RUNTIME_INTERNAL_ISSUE_COLLECTOR_H_
#define THIRD_PARTY_CEL_CPP_RUNTIME_INTERNAL_ISSUE_COLLECTOR_H_

#include <utility>
#include <vector>

#include "absl/status/status.h"
#include "runtime/runtime_issue.h"

namespace cel::runtime_internal {

// IssueCollector collects issues and reports absl::Status according to the
// configured severity limit.
class IssueCollector {
public:
// Args:
// severity: inclusive limit for issues to return as non-ok absl::Status.
explicit IssueCollector(RuntimeIssue::Severity severity_limit)
: severity_limit_(severity_limit) {}

// move-only.
IssueCollector(const IssueCollector&) = delete;
IssueCollector& operator=(const IssueCollector&) = delete;
IssueCollector(IssueCollector&&) = default;
IssueCollector& operator=(IssueCollector&&) = default;

// Collect an Issue.
// Returns a status according to the IssueCollector's policy and the given
// Issue.
// The Issue is always added to issues, regardless of whether AddIssue returns
// a non-ok status.
absl::Status AddIssue(RuntimeIssue issue) {
issues_.push_back(std::move(issue));
if (issues_.back().severity() >= severity_limit_) {
return issues_.back().ToStatus();
}
return absl::OkStatus();
}

const std::vector<RuntimeIssue>& issues() const { return issues_; }
std::vector<RuntimeIssue> ExtractIssues() { return std::move(issues_); }

private:
RuntimeIssue::Severity severity_limit_;
std::vector<RuntimeIssue> issues_;
};

} // namespace cel::runtime_internal

#endif // THIRD_PARTY_CEL_CPP_RUNTIME_INTERNAL_ISSUE_COLLECTOR_H_
94 changes: 94 additions & 0 deletions runtime/internal/issue_collector_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Copyright 2023 Google LLC
//
// 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
//
// https://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.
#include "runtime/internal/issue_collector.h"

#include "absl/status/status.h"
#include "internal/testing.h"
#include "runtime/runtime_issue.h"

namespace cel::runtime_internal {
namespace {

using testing::ElementsAre;
using testing::Truly;
using cel::internal::StatusIs;

template <typename Matcher, typename T>
bool ApplyMatcher(Matcher m, const T& t) {
return static_cast<testing::Matcher<T>>(m).Matches(t);
}

TEST(IssueCollector, CollectsIssues) {
IssueCollector issue_collector(RuntimeIssue::Severity::kError);

EXPECT_THAT(issue_collector.AddIssue(
RuntimeIssue::CreateError(absl::InvalidArgumentError("e1"))),
StatusIs(absl::StatusCode::kInvalidArgument, "e1"));
ASSERT_OK(issue_collector.AddIssue(RuntimeIssue::CreateWarning(
absl::InvalidArgumentError("w1"),
RuntimeIssue::ErrorCode::kNoMatchingOverload)));

EXPECT_THAT(
issue_collector.issues(),
ElementsAre(
Truly([](const RuntimeIssue& issue) {
return issue.severity() == RuntimeIssue::Severity::kError &&
issue.error_code() == RuntimeIssue::ErrorCode::kOther &&
ApplyMatcher(
StatusIs(absl::StatusCode::kInvalidArgument, "e1"),
issue.ToStatus());
}),
Truly([](const RuntimeIssue& issue) {
return issue.severity() == RuntimeIssue::Severity::kWarning &&
issue.error_code() ==
RuntimeIssue::ErrorCode::kNoMatchingOverload &&
ApplyMatcher(
StatusIs(absl::StatusCode::kInvalidArgument, "w1"),
issue.ToStatus());
})));
}

TEST(IssueCollector, ReturnsStatusAtLimit) {
IssueCollector issue_collector(RuntimeIssue::Severity::kWarning);

EXPECT_THAT(issue_collector.AddIssue(
RuntimeIssue::CreateError(absl::InvalidArgumentError("e1"))),
StatusIs(absl::StatusCode::kInvalidArgument, "e1"));

EXPECT_THAT(issue_collector.AddIssue(RuntimeIssue::CreateWarning(
absl::InvalidArgumentError("w1"),
RuntimeIssue::ErrorCode::kNoMatchingOverload)),
StatusIs(absl::StatusCode::kInvalidArgument, "w1"));

EXPECT_THAT(
issue_collector.issues(),
ElementsAre(
Truly([](const RuntimeIssue& issue) {
return issue.severity() == RuntimeIssue::Severity::kError &&
issue.error_code() == RuntimeIssue::ErrorCode::kOther &&
ApplyMatcher(
StatusIs(absl::StatusCode::kInvalidArgument, "e1"),
issue.ToStatus());
}),
Truly([](const RuntimeIssue& issue) {
return issue.severity() == RuntimeIssue::Severity::kWarning &&
issue.error_code() ==
RuntimeIssue::ErrorCode::kNoMatchingOverload &&
ApplyMatcher(
StatusIs(absl::StatusCode::kInvalidArgument, "w1"),
issue.ToStatus());
})));
}
} // namespace
} // namespace cel::runtime_internal
88 changes: 88 additions & 0 deletions runtime/runtime_issue.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright 2023 Google LLC
//
// 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
//
// https://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.

#ifndef THIRD_PARTY_CEL_CPP_RUNTIME_RUNTIME_ISSUE_H_
#define THIRD_PARTY_CEL_CPP_RUNTIME_RUNTIME_ISSUE_H_

#include <utility>

#include "absl/status/status.h"

namespace cel {

// Represents an issue with a given CEL expression.
//
// The error details are represented as an absl::Status for compatibility
// reasons, but users should not depend on this.
class RuntimeIssue {
public:
// Severity of the RuntimeIssue.
//
// Can be used to determine whether to continue program planning or return
// early.
enum class Severity {
// The issue may lead to runtime errors in evaluation.
kWarning = 0,
// The expression is invalid or unsupported.
kError = 1,
// Arbitrary max value above Error.
kNotForUseWithExhaustiveSwitchStatements = 15
};

// Code for well-known runtime error kinds.
enum class ErrorCode {
// Overload not provided for given function call signature.
kNoMatchingOverload,
// Field access refers to unknown field for given type.
kNoSuchField,
// Other error outside the canonical set.
kOther,
};

static RuntimeIssue CreateError(absl::Status status,
ErrorCode error_code = ErrorCode::kOther) {
return RuntimeIssue(std::move(status), Severity::kError, error_code);
}

static RuntimeIssue CreateWarning(absl::Status status,
ErrorCode error_code = ErrorCode::kOther) {
return RuntimeIssue(std::move(status), Severity::kWarning, error_code);
}

RuntimeIssue(const RuntimeIssue& other) = default;
RuntimeIssue& operator=(const RuntimeIssue& other) = default;
RuntimeIssue(RuntimeIssue&& other) = default;
RuntimeIssue& operator=(RuntimeIssue&& other) = default;

Severity severity() const { return severity_; }

ErrorCode error_code() const { return error_code_; }

const absl::Status& ToStatus() const& { return status_; }
absl::Status ToStatus() && { return std::move(status_); }

private:
RuntimeIssue(absl::Status status, Severity severity, ErrorCode error_code)
: status_(std::move(status)),
error_code_(error_code),
severity_(severity) {}

absl::Status status_;
ErrorCode error_code_;
Severity severity_;
};

} // namespace cel

#endif // THIRD_PARTY_CEL_CPP_RUNTIME_RUNTIME_ISSUE_H_

0 comments on commit 07a158c

Please # to comment.