use kcode_k1_web_code_document::{CodeDocument, DocumentError};
use kcode_k1_web_package::SourcePackage;
use std::{
error::Error,
fmt::{Display, Formatter},
sync::Arc,
};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum DocumentPart {
Documentation,
Code,
}
#[derive(Clone)]
pub struct SessionScope {
identity: Arc<()>,
}
#[derive(Clone)]
pub struct OpenedLibrary {
scope: Arc<()>,
user: [u8; 12],
source: Arc<SourcePackage>,
document: CodeDocument,
}
impl OpenedLibrary {
pub const fn document(&self) -> &CodeDocument {
&self.document
}
}
#[derive(Clone)]
pub struct CheckEvidence {
scope: Arc<()>,
user: [u8; 12],
source: Arc<SourcePackage>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum SessionError {
ForeignHandle,
StaleSource,
InvalidDocument(String),
}
impl Display for SessionError {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::ForeignHandle => formatter.write_str("foreign handle"),
Self::StaleSource => formatter.write_str("stale source"),
Self::InvalidDocument(message) => formatter.write_str(message),
}
}
}
impl Error for SessionError {}
impl From<DocumentError> for SessionError {
fn from(error: DocumentError) -> Self {
Self::InvalidDocument(error.to_string())
}
}
impl Default for SessionScope {
fn default() -> Self {
Self::new()
}
}
impl SessionScope {
pub fn new() -> Self {
Self {
identity: Arc::new(()),
}
}
pub fn recover(
&self,
user: [u8; 12],
source: Arc<SourcePackage>,
) -> Result<OpenedLibrary, SessionError> {
self.open(user, source)
}
pub fn refresh(
&self,
user: [u8; 12],
source: Arc<SourcePackage>,
) -> Result<OpenedLibrary, SessionError> {
self.open(user, source)
}
pub fn validate(
&self,
user: [u8; 12],
opened: &OpenedLibrary,
current: &SourcePackage,
) -> Result<(), SessionError> {
if !Arc::ptr_eq(&self.identity, &opened.scope) || opened.user != user {
return Err(SessionError::ForeignHandle);
}
if opened.source.as_ref() != current {
return Err(SessionError::StaleSource);
}
Ok(())
}
pub fn replacement(
&self,
user: [u8; 12],
opened: &OpenedLibrary,
current: &SourcePackage,
part: DocumentPart,
replacement: String,
) -> Result<SourcePackage, SessionError> {
self.validate(user, opened, current)?;
let document = opened.document();
let (documentation, code) = match part {
DocumentPart::Documentation => (replacement.into_bytes(), document.code().to_vec()),
DocumentPart::Code => (document.documentation().to_vec(), replacement.into_bytes()),
};
let replacement = CodeDocument::new(
document.id().clone(),
documentation,
document.language(),
code,
)?;
replacement.to_source_package().map_err(Into::into)
}
pub fn mint_evidence(
&self,
user: [u8; 12],
opened: &OpenedLibrary,
current: &SourcePackage,
) -> Result<CheckEvidence, SessionError> {
self.validate(user, opened, current)?;
Ok(CheckEvidence {
scope: Arc::clone(&self.identity),
user,
source: Arc::clone(&opened.source),
})
}
pub fn evidence_matches(
&self,
user: [u8; 12],
opened: &OpenedLibrary,
current: &SourcePackage,
evidence: &CheckEvidence,
) -> Result<bool, SessionError> {
self.validate(user, opened, current)?;
Ok(Arc::ptr_eq(&self.identity, &evidence.scope)
&& evidence.user == user
&& evidence.source.as_ref() == current)
}
fn open(
&self,
user: [u8; 12],
source: Arc<SourcePackage>,
) -> Result<OpenedLibrary, SessionError> {
let document = CodeDocument::from_source_package(source.as_ref())?;
Ok(OpenedLibrary {
scope: Arc::clone(&self.identity),
user,
source,
document,
})
}
}