Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Introduce Issue and IssueCollector types for collecting issues encountered during expression planning. #356

Merged
merged 1 commit into from
Oct 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions runtime/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -416,3 +416,9 @@ cc_test(
"@com_google_protobuf//:protobuf",
],
)

cc_library(
name = "runtime_issue",
hdrs = ["runtime_issue.h"],
deps = ["@com_google_absl//absl/status"],
)
21 changes: 21 additions & 0 deletions runtime/internal/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,24 @@ 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",
"@com_google_absl//absl/types:span",
],
)

cc_test(
name = "issue_collector_test",
srcs = ["issue_collector_test.cc"],
deps = [
":issue_collector",
"//internal:testing",
"//runtime:runtime_issue",
"@com_google_absl//absl/status",
],
)
64 changes: 64 additions & 0 deletions runtime/internal/issue_collector.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// 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 "absl/types/span.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();
}

absl::Span<const 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_