Skip to content

Commit

Permalink
Add ValidationRuleBuilder to make it easier to write custom validatio…
Browse files Browse the repository at this point in the history
…n rules for use with FHIRPath validation.

PiperOrigin-RevId: 316206889
  • Loading branch information
aaronnash authored and nickgeorge committed Jun 15, 2020
1 parent d8ef3f3 commit 265316e
Show file tree
Hide file tree
Showing 5 changed files with 277 additions and 1 deletion.
29 changes: 29 additions & 0 deletions cc/google/fhir/fhir_path/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,22 @@ cc_library(
],
)

cc_library(
name = "fhir_path_validation_rule",
srcs = [
"fhir_path_validation_rule.cc",
],
hdrs = [
"fhir_path_validation_rule.h",
],
strip_include_prefix = "//cc/",
deps = [
":fhir_path_validation",
"@com_google_absl//absl/container:flat_hash_set",
"@com_google_absl//absl/hash",
],
)

cc_library(
name = "r4_fhir_path_validation",
srcs = [
Expand Down Expand Up @@ -216,6 +232,19 @@ cc_test(
],
)

cc_test(
name = "fhir_path_validation_rule_test",
size = "small",
srcs = [
"fhir_path_validation_rule_test.cc",
],
deps = [
":fhir_path_validation",
":fhir_path_validation_rule",
"@com_google_googletest//:gtest_main",
],
)

cc_test(
name = "utils_test",
size = "small",
Expand Down
9 changes: 8 additions & 1 deletion cc/google/fhir/fhir_path/fhir_path_validation.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,13 @@ class ValidationResult {
const StatusOr<bool> result_;
};

// A ValidationRule is a function that takes a ValidationResult and returns
// false if the resource under consideration should be considered invalid and
// true in all other cases. In order for a resource to be considered valid, from
// the perspective of FHIRPath, this function must return true for all
// ValidationResult objects for that resource.
using ValidationRule = std::function<bool(const ValidationResult&)>;

// Class the holds the results of evaluating all FHIRPath constraints defined
// on a particular resource.
class ValidationResults {
Expand All @@ -97,7 +104,7 @@ class ValidationResults {
//
// See ValidationResults::StrictValidationFn and
// ValidationResults::RelaxedValidationFn for common definitions of validity.
bool IsValid(std::function<bool(const ValidationResult&)> validation_fn =
bool IsValid(ValidationRule validation_fn =
&ValidationResults::StrictValidationFn) const;

// Returns Status::OK or the status of the first constraint violation
Expand Down
43 changes: 43 additions & 0 deletions cc/google/fhir/fhir_path/fhir_path_validation_rule.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright 2020 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
//
// http: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 "google/fhir/fhir_path/fhir_path_validation_rule.h"

namespace google::fhir::fhir_path {

ValidationRuleBuilder& ValidationRuleBuilder::IgnoringConstraint(
const std::string& constraint_path, const std::string& constraint) {
ignored_constraints_.emplace(
std::pair<std::string, std::string>(constraint_path, constraint));
return *this;
}

ValidationRuleBuilder& ValidationRuleBuilder::IgnoringConstraints(
const absl::flat_hash_set<std::pair<std::string, std::string>>&
constraints) {
ignored_constraints_.insert(constraints.begin(), constraints.end());
return *this;
}

ValidationRule ValidationRuleBuilder::CustomValidation(
ValidationRule validation_fn) const {
return [validation_fn, ignored_constraints = this->ignored_constraints_](
const ValidationResult& result) {
return validation_fn(result) ||
ignored_constraints.contains(
{result.ConstraintPath(), result.Constraint()});
};
}

} // namespace google::fhir::fhir_path
98 changes: 98 additions & 0 deletions cc/google/fhir/fhir_path/fhir_path_validation_rule.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// Copyright 2020 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
//
// http: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 GOOGLE_FHIR_FHIR_PATH_FHIR_PATH_VALIDATION_RULE_H_
#define GOOGLE_FHIR_FHIR_PATH_FHIR_PATH_VALIDATION_RULE_H_

#include "absl/container/flat_hash_set.h"
#include "absl/hash/hash.h"
#include "google/fhir/fhir_path/fhir_path_validation.h"

namespace google::fhir::fhir_path {

// A lightweight builder for creating custom validation functions.
//
// Example:
// auto validation_rule_function = ValidationRuleBuilder()
// .IgnoringConstraint("Patient.contact", "name.exists()...")
// .IgorningConstraints({
// {"Bundle.entry", "fullUrl.contains('/_history/').not()"},
// {"Bundle", "type = 'document' implies (timestamp.hasValue())"}
// })
// .StrictValidation();
//
// ValidationResults validation_results = [...];
// bool is_valid = validation_results.IsValid(validation_rule_function);
class ValidationRuleBuilder {
public:
// Causes all constraints that match the FHIRPath expression specified by
// "constraint" and located at "constraint_path", a FHIRPath expression to the
// constrained node, to be considered valid regardless of the actual result
// of evaluating the constraint on matching nodes.
//
// Example: IgnoringConstraint("Patient.contact", "name.exists()...")
ValidationRuleBuilder& IgnoringConstraint(const std::string& constraint_path,
const std::string& constraint);

// Causes a set of {constraint_path, constraint} pairs to be considered valid
// regardless of the actual result of evaluating the constraints on matching
// nodes.
//
// See also: IgnoringConstraint(...)
//
// Example: IgorningConstraints({
// {"Bundle.entry", "fullUrl.contains('/_history/').not()"},
// {"Bundle", "type = 'document' implies (timestamp.hasValue())"}
// })
ValidationRuleBuilder& IgnoringConstraints(
const absl::flat_hash_set<std::pair<std::string, std::string>>&
constraints);

// Returns the configured ValidationRule. If all preconditions are met (e.g.
// not an ignored constraint), the rule returns true if the result of the
// constraint's evaluation is true. False if the constraint was unmet or
// failed to evaluate to a boolean. If the constraint matches an ignored
// constraint, the rule returns true.
ValidationRule StrictValidation() const {
return CustomValidation(ValidationResults::StrictValidationFn);
}

// Returns the configured ValidationRule. If all preconditions are met (e.g.
// not an ignored constraint), the rule returns the result of the constraint's
// evaluation, if it evaluated to a boolean. Otherwise returns true. Common
// causes of an expression failing to evaluate to a boolean could be:
// - the constraint uses a portion of FHIRPath not currently supported by
// this library
// - the constraint is not a valid FHIRPath expression
// - the constraint does not yield a boolean value
// If the constraint matches an ignored constraint, the rule returns true.
ValidationRule RelaxedValidation() const {
return CustomValidation(ValidationResults::RelaxedValidationFn);
}

// Returns the configured ValidationRule. If all preconditions are met (e.g.
// not an ignored constraint), the rule returns the result of calling the
// provided validation function. If the constraint matches an ignored
// constraint, the rule returns true.
ValidationRule CustomValidation(ValidationRule validation_fn) const;

private:
absl::flat_hash_set<std::pair<std::string, std::string>,
absl::Hash<std::pair<std::string, std::string>>>
ignored_constraints_;
};

} // namespace google::fhir::fhir_path

#endif // GOOGLE_FHIR_FHIR_PATH_FHIR_PATH_VALIDATION_RULE_H_
99 changes: 99 additions & 0 deletions cc/google/fhir/fhir_path/fhir_path_validation_rule_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// Copyright 2020 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
//
// http: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 "google/fhir/fhir_path/fhir_path_validation_rule.h"

#include "gtest/gtest.h"

namespace google::fhir::fhir_path {

namespace {

bool FalseFunction(const ValidationResult& result) { return false; }
bool TrueFunction(const ValidationResult& result) { return true; }

TEST(ValidationRuleBuilder, TrueResultNotIgnored) {
ValidationResult result("constraint_path", "node_path", "expression", true);
const ValidationRuleBuilder builder;

EXPECT_TRUE(builder.StrictValidation()(result));
EXPECT_TRUE(builder.RelaxedValidation()(result));
EXPECT_FALSE(builder.CustomValidation(FalseFunction)(result));
EXPECT_TRUE(builder.CustomValidation(TrueFunction)(result));
}

TEST(ValidationRuleBuilder, TrueResultIgnored) {
ValidationResult result("constraint_path", "node_path", "expression", true);

const ValidationRuleBuilder builder =
ValidationRuleBuilder().IgnoringConstraint("constraint_path",
"expression");

EXPECT_TRUE(builder.StrictValidation()(result));
EXPECT_TRUE(builder.RelaxedValidation()(result));
EXPECT_TRUE(builder.CustomValidation(FalseFunction)(result));
EXPECT_TRUE(builder.CustomValidation(TrueFunction)(result));
}

TEST(ValidationRuleBuilder, FalseResultNotIgnored) {
ValidationResult result("constraint_path", "node_path", "expression", false);
const ValidationRuleBuilder builder;

EXPECT_FALSE(builder.StrictValidation()(result));
EXPECT_FALSE(builder.RelaxedValidation()(result));
EXPECT_FALSE(builder.CustomValidation(FalseFunction)(result));
EXPECT_TRUE(builder.CustomValidation(TrueFunction)(result));
}

TEST(ValidationRuleBuilder, FalseResultIgnored) {
ValidationResult result("constraint_path", "node_path", "expression", false);

const ValidationRuleBuilder builder =
ValidationRuleBuilder().IgnoringConstraint("constraint_path",
"expression");

EXPECT_TRUE(builder.StrictValidation()(result));
EXPECT_TRUE(builder.RelaxedValidation()(result));
EXPECT_TRUE(builder.CustomValidation(FalseFunction)(result));
EXPECT_TRUE(builder.CustomValidation(TrueFunction)(result));
}

TEST(ValidationRuleBuilder, ErrorResultNotIgnored) {
ValidationResult result("constraint_path", "node_path", "expression",
absl::InvalidArgumentError("foo"));
const ValidationRuleBuilder builder;

EXPECT_FALSE(builder.StrictValidation()(result));
EXPECT_TRUE(builder.RelaxedValidation()(result));
EXPECT_FALSE(builder.CustomValidation(FalseFunction)(result));
EXPECT_TRUE(builder.CustomValidation(TrueFunction)(result));
}

TEST(ValidationRuleBuilder, ErrorResultIgnored) {
ValidationResult result("constraint_path", "node_path", "expression",
absl::InvalidArgumentError("foo"));

const ValidationRuleBuilder builder =
ValidationRuleBuilder().IgnoringConstraint("constraint_path",
"expression");

EXPECT_TRUE(builder.StrictValidation()(result));
EXPECT_TRUE(builder.RelaxedValidation()(result));
EXPECT_TRUE(builder.CustomValidation(FalseFunction)(result));
EXPECT_TRUE(builder.CustomValidation(TrueFunction)(result));
}

} // namespace

} // namespace google::fhir::fhir_path

0 comments on commit 265316e

Please sign in to comment.