use std::path::Path;
use super::{Observation, Visibility};
use crate::error::Result;
use crate::gh::Unanswered;
use crate::host::Hosting;
use crate::policy::{self, RulesSource};
use crate::registry::{Identity, Registry};
use crate::rules::RulesFile;
use crate::store;
pub fn declared(
file: &RulesFile,
source: &RulesSource,
key: &str,
checkout: &Path,
) -> Option<Visibility> {
let normalized = store::normalize(key);
policy::resolve(file, source, &normalized, checkout)
.visibility
.map(Visibility::from)
}
pub fn recorded(identity: &Identity, declared: Option<Visibility>) -> Visibility {
declared.unwrap_or(identity.visibility)
}
pub fn checkout_of<'r>(registry: &'r Registry, key: &str) -> Option<&'r Path> {
registry
.checkouts
.values()
.find(|checkout| checkout.identity == key)
.map(|checkout| checkout.path.as_path())
}
pub fn refresh_named(hosting: &dyn Hosting, repo: &str) -> Result<Visibility> {
let registry = store::load()?;
let resolution = store::resolve(®istry, repo)?;
refresh(hosting, &resolution.key)
}
pub fn refresh(hosting: &dyn Hosting, key: &str) -> Result<Visibility> {
Ok(match refreshed(hosting, key)? {
Refreshed::Answered(visibility) | Refreshed::Unconfirmed(visibility) => visibility,
Refreshed::Unanswered(_) => Visibility::Unknown,
})
}
#[derive(Debug)]
pub enum Screening {
Screened,
Unscreened,
Unanswered(Unanswered),
}
pub fn screening(hosting: &dyn Hosting, key: &str) -> Result<Screening> {
let hosted = store::normalize(key).hosted.is_some();
Ok(match refreshed(hosting, key)? {
Refreshed::Unanswered(unanswered) => Screening::Unanswered(unanswered),
Refreshed::Answered(visibility) if visibility.effective() != Visibility::Public => {
Screening::Unscreened
}
Refreshed::Unconfirmed(visibility)
if visibility.effective() != Visibility::Public && !hosted =>
{
Screening::Unscreened
}
Refreshed::Answered(_) | Refreshed::Unconfirmed(_) => Screening::Screened,
})
}
pub fn screens_writes_unasked(key: &str) -> Result<bool> {
Ok(screens_writes_as_recorded(key)?.unwrap_or_else(|| store::normalize(key).hosted.is_some()))
}
pub fn screening_unless_recorded(hosting: &dyn Hosting, key: &str) -> Result<Screening> {
match screens_writes_as_recorded(key)? {
Some(true) => Ok(Screening::Screened),
Some(false) => Ok(Screening::Unscreened),
None => screening(hosting, key),
}
}
fn screens_writes_as_recorded(key: &str) -> Result<Option<bool>> {
let registry = store::load()?;
let (file, source) = policy::load(®istry)?;
let checkout = checkout_of(®istry, key)
.map(Path::to_path_buf)
.unwrap_or_default();
if let Some(declared) = declared(&file, &source, key, &checkout) {
return Ok(Some(declared.effective() == Visibility::Public));
}
Ok(match registry.identities.get(key) {
Some(identity)
if !identity.observation.is_unknown() && !identity.visibility.is_unknown() =>
{
Some(identity.visibility.effective() == Visibility::Public)
}
_ => None,
})
}
enum Refreshed {
Answered(Visibility),
Unconfirmed(Visibility),
Unanswered(Unanswered),
}
fn refreshed(hosting: &dyn Hosting, key: &str) -> Result<Refreshed> {
let registry = store::load()?;
let (file, source) = policy::load(®istry)?;
let checkout = checkout_of(®istry, key)
.map(Path::to_path_buf)
.unwrap_or_default();
if let Some(declared) = declared(&file, &source, key, &checkout) {
record(key, declared, Observation::Override)?;
return Ok(Refreshed::Answered(declared));
}
let Some(slug) = crate::gh::slug(key) else {
let recorded = registry
.identities
.get(key)
.map(|identity| identity.visibility)
.unwrap_or_default();
return Ok(Refreshed::Unconfirmed(recorded));
};
let answered = hosting.for_repo(&slug).and_then(|host| host.visibility());
if let Some(unanswered) = answered.as_ref().err().and_then(Unanswered::of) {
return Ok(Refreshed::Unanswered(unanswered));
}
match answered {
Ok(visibility) if !visibility.is_unknown() => {
record(key, visibility, Observation::Host)?;
Ok(Refreshed::Answered(visibility))
}
_ => {
let unchanged = registry.identities.get(key).is_some_and(|identity| {
identity.visibility.is_unknown() && identity.observation.is_unknown()
});
if !unchanged {
record(key, Visibility::Unknown, Observation::Unknown)?;
}
Ok(Refreshed::Unconfirmed(Visibility::Unknown))
}
}
}
fn record(key: &str, visibility: Visibility, observation: Observation) -> Result<()> {
if !store::load()?.identities.contains_key(key) {
return Ok(());
}
store::update(|registry| {
if let Some(identity) = registry.identities.get_mut(key) {
identity.visibility = visibility;
identity.observation = observation;
identity.observed_at = match observation {
Observation::Unknown => None,
Observation::Host | Observation::Override => Some(crate::ids::timestamp()),
};
}
Ok(())
})
}