///|
pub struct ReceiverState {
guard_state : GuardState
admission_state : AdmissionState
audit_records : Array[AuditRecord]
} derive(Eq, Debug)
///|
pub fn ReceiverState::empty() -> ReceiverState {
{
guard_state: GuardState::empty(),
admission_state: AdmissionState::empty(),
audit_records: [],
}
}
///|
pub struct ReceiverConfig {
guard_config : GuardConfig
admission_rule : AdmissionRule
validate_payload : Bool
} derive(Eq, Debug)
///|
pub fn ReceiverConfig::default() -> ReceiverConfig {
{
guard_config: GuardConfig::default(),
admission_rule: admission_rule_for("github"),
validate_payload: true,
}
}
///|
pub struct ReceiverOutput {
pipeline : GuardPipelineOutput
plan : HandlingPlan
payload_errors : Array[PayloadValidationError]
admission : AdmissionDecision
state : ReceiverState
} derive(Eq, Debug)
///|
fn append_audit(
records : Array[AuditRecord],
output : GuardPipelineOutput,
) -> Array[AuditRecord] {
let previous = if records.length() == 0 {
"root"
} else {
records[records.length() - 1].digest
}
let record = audit_record(records.length(), previous, output)
Array::makei(records.length() + 1, i => {
if i < records.length() {
records[i]
} else {
record
}
})
}
///|
pub fn receive_webhook(
request : WebhookRequest,
config : ReceiverConfig,
state : ReceiverState,
) -> ReceiverOutput {
let admission = check_admission(
request.provider,
config.guard_config.policy.now,
config.admission_rule,
state.admission_state,
)
if !admission.allowed {
let rejected = VerificationResult::rejected(
config.guard_config.policy.provider.name,
provider_event_type(request, config.guard_config.policy.provider),
provider_event_id(request, config.guard_config.policy.provider),
[GuardDiagnostic::new(ReplayDetected, "admission blocked request")],
)
let pipeline = {
verification: rejected,
route: RouteDecision::miss("admission blocked"),
risk: score_result(rejected),
state: state.guard_state,
}
let plan = HandlingPlan::new(
[HandlerAction::new(RetryLater, "", "admission blocked")],
retry_response("admission blocked"),
)
{
pipeline,
plan,
payload_errors: [],
admission,
state: {
guard_state: state.guard_state,
admission_state: admission.state,
audit_records: append_audit(state.audit_records, pipeline),
},
}
} else {
let guard_config = config.guard_config.with_state(state.guard_state)
let pipeline = run_guard_pipeline(request, guard_config)
let payload_errors = if config.validate_payload &&
pipeline.verification.is_ok() {
validate_request_payload(request, config.guard_config.policy.provider)
} else {
[]
}
let plan = if payload_errors.length() > 0 {
HandlingPlan::new(
[HandlerAction::new(Reject, "", "payload schema failed")],
rejected_response("payload_schema"),
)
} else {
plan_for_output(pipeline)
}
{
pipeline,
plan,
payload_errors,
admission,
state: {
guard_state: pipeline.state,
admission_state: admission.state,
audit_records: append_audit(state.audit_records, pipeline),
},
}
}
}
///|
pub fn receiver_output_text(output : ReceiverOutput) -> String {
let builder = StringBuilder::new()
builder.write_string(pipeline_output_text(output.pipeline))
if output.payload_errors.length() > 0 {
builder.write_string("\npayload_errors=\n")
builder.write_string(payload_error_text(output.payload_errors))
}
builder.write_string("\nplan=\n")
builder.write_string(plan_text(output.plan))
builder.to_string()
}
///|
pub fn receive_batch(
requests : Array[WebhookRequest],
config : ReceiverConfig,
) -> Array[ReceiverOutput] {
let mut state = ReceiverState::empty()
Array::makei(requests.length(), i => {
let output = receive_webhook(requests[i], config, state)
state = output.state
output
})
}
///|
pub fn receiver_batch_report(outputs : Array[ReceiverOutput]) -> String {
let builder = StringBuilder::new()
builder.write_string("| index | status | route | response |\n")
builder.write_string("| ---: | --- | --- | ---: |\n")
for i in 0..