///|
/// An authorization result that changed between two policy versions.
pub(all) struct AccessChange {
  request : Request
  previous : Decision
  current : Decision
} derive(Debug, Eq)

///|
/// Whether the replacement policy grants or removes access for this request.
pub(all) enum AccessChangeKind {
  NewlyAllowed
  NewlyDenied
} derive(Debug, Eq)

///|
pub fn AccessChange::kind(self : AccessChange) -> AccessChangeKind {
  if self.current.allowed {
    NewlyAllowed
  } else {
    NewlyDenied
  }
}

///|
/// Evaluate the same request corpus against two policies and return only
/// changes in the access decision. Results retain the corpus order.
pub fn Policy::access_changes(
  self : Policy,
  replacement : Policy,
  requests : Array[Request],
) -> Array[AccessChange] {
  let changes : Array[AccessChange] = []
  for request in requests {
    let previous = self.authorize(request)
    let current = replacement.authorize(request)
    if previous.allowed != current.allowed {
      changes.push({ request, previous, current })
    }
  }
  changes
}

///|
/// Serialize an access regression as a request plus the two decisions.
pub fn AccessChange::to_json(self : AccessChange) -> Json {
  Json::object({
    "kind": Json::string(
      match self.kind() {
        NewlyAllowed => "newly_allowed"
        NewlyDenied => "newly_denied"
      },
    ),
    "request": self.request.to_json(),
    "previous": self.previous.to_json(),
    "current": self.current.to_json(),
  })
}