#![forbid(unsafe_code)]
use kcode_k1_transaction_id::TxId;
use kcode_k1_web_code_document::{CodeDocument, DocumentError};
use semver::Version;
use std::{
error::Error,
fmt::{Display, Formatter},
sync::Arc,
};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum DocumentPart {
Documentation,
Code,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum OpenedSource {
Published(Version),
Unpublished { id: TxId, revision: TxId },
}
impl OpenedSource {
pub fn text(&self) -> String {
match self {
Self::Published(value) => value.to_string(),
Self::Unpublished { id, .. } => id.to_string(),
}
}
pub const fn unpublished_id(&self) -> Option<TxId> {
match self {
Self::Published(_) => None,
Self::Unpublished { id, .. } => Some(*id),
}
}
pub const fn revision(&self) -> Option<TxId> {
match self {
Self::Published(_) => None,
Self::Unpublished { revision, .. } => Some(*revision),
}
}
pub fn published_version(&self) -> Option<&Version> {
match self {
Self::Published(value) => Some(value),
Self::Unpublished { .. } => None,
}
}
}
#[derive(Clone)]
pub struct SessionScope {
identity: Arc<()>,
}
#[derive(Clone)]
pub struct OpenedLibrary {
scope: Arc<()>,
authority: [u8; 12],
document: CodeDocument,
source: OpenedSource,
}
impl OpenedLibrary {
pub const fn document(&self) -> &CodeDocument {
&self.document
}
pub const fn source(&self) -> &OpenedSource {
&self.source
}
pub fn version_text(&self) -> String {
self.source.text()
}
}
#[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(value) => formatter.write_str(value),
}
}
}
impl Error for SessionError {}
impl From<DocumentError> for SessionError {
fn from(value: DocumentError) -> Self {
Self::InvalidDocument(value.to_string())
}
}
impl Default for SessionScope {
fn default() -> Self {
Self::new()
}
}
impl SessionScope {
pub fn new() -> Self {
Self {
identity: Arc::new(()),
}
}
pub fn open(
&self,
authority: [u8; 12],
document: CodeDocument,
source: OpenedSource,
) -> Result<OpenedLibrary, SessionError> {
if document.family().authority().transaction_id().as_bytes() != &authority {
return Err(SessionError::ForeignHandle);
}
Ok(OpenedLibrary {
scope: self.identity.clone(),
authority,
document,
source,
})
}
pub fn refresh(
&self,
authority: [u8; 12],
document: CodeDocument,
source: OpenedSource,
) -> Result<OpenedLibrary, SessionError> {
self.open(authority, document, source)
}
pub fn validate(
&self,
authority: [u8; 12],
opened: &OpenedLibrary,
document: &CodeDocument,
source: &OpenedSource,
) -> Result<(), SessionError> {
if !Arc::ptr_eq(&self.identity, &opened.scope) || opened.authority != authority {
return Err(SessionError::ForeignHandle);
}
if &opened.document != document || &opened.source != source {
return Err(SessionError::StaleSource);
}
Ok(())
}
pub fn replacement(
&self,
authority: [u8; 12],
opened: &OpenedLibrary,
document: &CodeDocument,
source: &OpenedSource,
part: DocumentPart,
contents: String,
) -> Result<CodeDocument, SessionError> {
self.validate(authority, opened, document, source)?;
let (documentation, code) = match part {
DocumentPart::Documentation => (contents.into_bytes(), document.code().to_vec()),
DocumentPart::Code => (document.documentation().to_vec(), contents.into_bytes()),
};
CodeDocument::new(
document.family().clone(),
documentation,
document.language(),
code,
)
.map_err(Into::into)
}
}