#[cfg(feature = "http-server")]
use std::cell::RefCell;
#[cfg(feature = "http-server")]
use serde_json::Value;
#[cfg(feature = "http-server")]
use super::{DERIVED_NOT_RECORDED, HANDLE_NOT_FOLLOWABLE, KeptWhole, NO_PROJECT};
use super::{
UNKNOWN_LINEAGE, carries_recovery_stub, note_kept_whole, protected, shorten_with_copy,
};
#[cfg(feature = "http-server")]
use crate::request_project;
#[cfg(feature = "http-server")]
use lean_ctx::core::archive::delivered;
#[cfg(feature = "http-server")]
pub(crate) const STUB_NOT_ADMISSIBLE: &str = "recovery.stub_not_admissible";
pub(crate) const NOT_ENGINE_TEXT: &str = UNKNOWN_LINEAGE;
pub(crate) fn shortened(original: &str, candidate: String) -> Option<String> {
if !protected() {
return Some(candidate);
}
if carries_recovery_stub(original) {
return None;
}
shorten_with_copy(original, &candidate)
}
#[cfg(feature = "http-server")]
pub(crate) fn rewrite(text: &mut String, shorten: impl FnOnce(&mut String)) {
if !protected() {
shorten(text);
return;
}
let mut candidate = text.clone();
shorten(&mut candidate);
if candidate != *text
&& let Some(sent) = shortened(text, candidate)
{
*text = sent;
}
}
pub(crate) fn keeps_whole(block: &str, code: &'static str) -> bool {
if !protected() {
return false;
}
note_kept_whole(block, code);
true
}
#[cfg(feature = "http-server")]
pub(crate) fn rewrapped(outer: &str, rewritten: String, parts: &[String]) -> Option<String> {
if !protected() {
return Some(rewritten);
}
let Some(project) = request_project::current_source_project() else {
note_kept_whole(outer, NO_PROJECT);
return None;
};
let recorded = request_project::in_project(&project, || {
let known = |text: &str| {
delivered::lineage_of(&delivered::digest(text)).filter(delivered::Lineage::is_complete)
};
let mut lineage = known(outer)?;
for part in parts {
lineage.join(known(part)?);
}
Some(delivered::record_derived(
&delivered::digest(&rewritten),
&lineage,
))
});
match recorded {
Some(true) => {
note_issued(outer, &rewritten);
Some(rewritten)
}
Some(false) => {
note_kept_whole(outer, DERIVED_NOT_RECORDED);
None
}
None => {
note_kept_whole(outer, UNKNOWN_LINEAGE);
None
}
}
}
#[derive(Debug)]
#[cfg(feature = "http-server")]
struct Stub {
sent: String,
original: String,
}
#[derive(Debug, Default)]
#[cfg(feature = "http-server")]
pub(crate) struct Issued {
stubs: Vec<Stub>,
}
#[cfg(feature = "http-server")]
thread_local! {
static ISSUED: RefCell<Option<Issued>> = const { RefCell::new(None) };
}
#[cfg(feature = "http-server")]
pub(crate) fn collecting<T>(so_far: &mut Issued, pass: impl FnOnce() -> T) -> T {
struct Restore(Option<Issued>);
impl Drop for Restore {
fn drop(&mut self) {
let outer = self.0.take();
ISSUED.with(|slot| *slot.borrow_mut() = outer);
}
}
let restore = Restore(ISSUED.with(|slot| slot.replace(Some(std::mem::take(so_far)))));
let result = pass();
if let Some(known) = ISSUED.with(|slot| slot.borrow_mut().take()) {
*so_far = known;
}
drop(restore);
result
}
pub(super) fn note_issued(original: &str, sent: &str) {
#[cfg(not(feature = "http-server"))]
let _ = (original, sent);
#[cfg(feature = "http-server")]
ISSUED.with(|slot| {
if let Some(issued) = slot.borrow_mut().as_mut() {
issued.stubs.push(Stub {
sent: sent.to_owned(),
original: original.to_owned(),
});
}
});
}
#[cfg(feature = "http-server")]
impl Issued {
pub(crate) fn settle(&self, doc: &mut Value, followable: bool, kept: &mut KeptWhole) -> bool {
!followable
&& !self.stubs.is_empty()
&& self.put_back(doc, None, HANDLE_NOT_FOLLOWABLE, kept)
}
pub(crate) fn restore_unadmitted(
&self,
sent: &Value,
admitted: Option<&Value>,
kept: &mut KeptWhole,
) -> Option<Value> {
if self.stubs.is_empty() {
return None;
}
let mut restored = sent.clone();
self.put_back(
&mut restored,
Some(admitted.unwrap_or(&NOWHERE)),
STUB_NOT_ADMISSIBLE,
kept,
)
.then_some(restored)
}
fn put_back(
&self,
node: &mut Value,
admitted: Option<&Value>,
code: &'static str,
kept: &mut KeptWhole,
) -> bool {
match node {
Value::String(text) => {
let Some(stub) = self.stubs.iter().find(|stub| stub.sent == *text) else {
return false;
};
if admitted.and_then(Value::as_str) == Some(text.as_str()) {
return false;
}
kept.note(&stub.original, code);
text.clone_from(&stub.original);
true
}
Value::Array(items) => {
let mut changed = false;
for (index, item) in items.iter_mut().enumerate() {
let place = admitted.map(|admitted| admitted.get(index).unwrap_or(&NOWHERE));
changed |= self.put_back(item, place, code, kept);
}
changed
}
Value::Object(fields) => {
let mut changed = false;
for (key, field) in fields.iter_mut() {
let place = admitted.map(|admitted| admitted.get(key).unwrap_or(&NOWHERE));
changed |= self.put_back(field, place, code, kept);
}
changed
}
_ => false,
}
}
}
#[cfg(feature = "http-server")]
static NOWHERE: Value = Value::Null;