///|
/// A named authorization example with an expected allow or deny result.
pub(all) struct PolicyCase {
name : String
request : Request
expected_allowed : Bool
} derive(Debug, Eq)
///|
pub fn PolicyCase::PolicyCase(
name~ : String,
request~ : Request,
expected_allowed~ : Bool,
) -> PolicyCase {
{ name, request, expected_allowed }
}
///|
/// The actual decision and pass/fail status for a policy case.
pub(all) struct PolicyCaseResult {
name : String
request : Request
expected_allowed : Bool
decision : Decision
passed : Bool
} derive(Debug, Eq)
///|
/// Ordered case results and aggregate pass/fail counts.
pub(all) struct PolicySuiteResult {
results : Array[PolicyCaseResult]
passed : Int
failed : Int
} derive(Debug, Eq)
///|
/// Evaluate named examples against a policy. Every case is evaluated even
/// after a failure, so a suite reports all regressions in one run.
pub fn Policy::run_cases(
self : Policy,
cases : Array[PolicyCase],
) -> PolicySuiteResult {
let results : Array[PolicyCaseResult] = []
let mut passed = 0
let mut failed = 0
for case in cases {
let decision = self.authorize(case.request)
let success = decision.allowed == case.expected_allowed
if success {
passed += 1
} else {
failed += 1
}
results.push({
name: case.name,
request: case.request,
expected_allowed: case.expected_allowed,
decision,
passed: success,
})
}
{ results, passed, failed }
}
///|
fn PolicyCaseResult::to_json(self : PolicyCaseResult) -> Json {
Json::object({
"name": Json::string(self.name),
"request": self.request.to_json(),
"expected_allowed": Json::boolean(self.expected_allowed),
"decision": self.decision.to_json(),
"passed": Json::boolean(self.passed),
})
}
///|
/// Serialize a suite result for CLI and CI consumers.
pub fn PolicySuiteResult::to_json(self : PolicySuiteResult) -> Json {
Json::object({
"passed": Json::number(Double::from_int(self.passed)),
"failed": Json::number(Double::from_int(self.failed)),
"results": Json::array([ for result in self.results => result.to_json() ]),
})
}
///|
fn decode_case_expected(value : Json) -> Bool raise PolicyParseError {
match value {
True => true
False => false
_ =>
raise ExpectedType(field="cases[].expected_allowed", expected="boolean")
}
}
///|
/// Decode a JSON array of named authorization examples. Each member requires
/// `name`, `request`, and `expected_allowed` fields; duplicate names fail.
pub fn cases_from_json(
source : String,
) -> Array[PolicyCase] raise PolicyParseError {
let root = @json.parse(source) catch {
error => raise InvalidJson(error.to_string())
}
let entries = expect_array(root, field="cases")
let cases : Array[PolicyCase] = []
let names : Map[String, Bool] = {}
for entry in entries {
let object = expect_object(entry, field="cases[]")
reject_unknown_fields(
object,
["name", "request", "expected_allowed"],
"cases[]",
)
let name = expect_string(
required_field(object, "name"),
field="cases[].name",
)
if name == "" {
raise InvalidPolicy("cases[].name must not be empty")
}
if names.contains(name) {
raise InvalidPolicy("duplicate case name: " + name)
}
names[name] = true
cases.push(
PolicyCase(
name~,
request=decode_request(required_field(object, "request")),
expected_allowed=decode_case_expected(
required_field(object, "expected_allowed"),
),
),
)
}
cases
}