Skip to main content

kcode_k1_web_code_session/
lib.rs

1#![forbid(unsafe_code)]
2
3use kcode_k1_transaction_id::TxId;
4use kcode_k1_web_code_document::{CodeDocument, DocumentError};
5use semver::Version;
6use std::{
7    error::Error,
8    fmt::{Display, Formatter},
9    sync::Arc,
10};
11
12#[derive(Clone, Copy, Debug, Eq, PartialEq)]
13pub enum DocumentPart {
14    Documentation,
15    Code,
16}
17
18#[derive(Clone, Debug, Eq, PartialEq)]
19pub enum OpenedSource {
20    Published(Version),
21    Unpublished { id: TxId, revision: TxId },
22}
23
24impl OpenedSource {
25    pub fn text(&self) -> String {
26        match self {
27            Self::Published(value) => value.to_string(),
28            Self::Unpublished { id, .. } => id.to_string(),
29        }
30    }
31
32    pub const fn unpublished_id(&self) -> Option<TxId> {
33        match self {
34            Self::Published(_) => None,
35            Self::Unpublished { id, .. } => Some(*id),
36        }
37    }
38
39    pub const fn revision(&self) -> Option<TxId> {
40        match self {
41            Self::Published(_) => None,
42            Self::Unpublished { revision, .. } => Some(*revision),
43        }
44    }
45
46    pub fn published_version(&self) -> Option<&Version> {
47        match self {
48            Self::Published(value) => Some(value),
49            Self::Unpublished { .. } => None,
50        }
51    }
52}
53
54#[derive(Clone)]
55pub struct SessionScope {
56    identity: Arc<()>,
57}
58
59#[derive(Clone)]
60pub struct OpenedLibrary {
61    scope: Arc<()>,
62    authority: [u8; 12],
63    document: CodeDocument,
64    source: OpenedSource,
65}
66
67impl OpenedLibrary {
68    pub const fn document(&self) -> &CodeDocument {
69        &self.document
70    }
71
72    pub const fn source(&self) -> &OpenedSource {
73        &self.source
74    }
75
76    pub fn version_text(&self) -> String {
77        self.source.text()
78    }
79}
80
81#[derive(Clone, Debug, Eq, PartialEq)]
82pub enum SessionError {
83    ForeignHandle,
84    StaleSource,
85    InvalidDocument(String),
86}
87
88impl Display for SessionError {
89    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
90        match self {
91            Self::ForeignHandle => formatter.write_str("foreign handle"),
92            Self::StaleSource => formatter.write_str("stale source"),
93            Self::InvalidDocument(value) => formatter.write_str(value),
94        }
95    }
96}
97
98impl Error for SessionError {}
99
100impl From<DocumentError> for SessionError {
101    fn from(value: DocumentError) -> Self {
102        Self::InvalidDocument(value.to_string())
103    }
104}
105
106impl Default for SessionScope {
107    fn default() -> Self {
108        Self::new()
109    }
110}
111
112impl SessionScope {
113    pub fn new() -> Self {
114        Self {
115            identity: Arc::new(()),
116        }
117    }
118
119    pub fn open(
120        &self,
121        authority: [u8; 12],
122        document: CodeDocument,
123        source: OpenedSource,
124    ) -> Result<OpenedLibrary, SessionError> {
125        if document.family().authority().transaction_id().as_bytes() != &authority {
126            return Err(SessionError::ForeignHandle);
127        }
128        Ok(OpenedLibrary {
129            scope: self.identity.clone(),
130            authority,
131            document,
132            source,
133        })
134    }
135
136    pub fn refresh(
137        &self,
138        authority: [u8; 12],
139        document: CodeDocument,
140        source: OpenedSource,
141    ) -> Result<OpenedLibrary, SessionError> {
142        self.open(authority, document, source)
143    }
144
145    pub fn validate(
146        &self,
147        authority: [u8; 12],
148        opened: &OpenedLibrary,
149        document: &CodeDocument,
150        source: &OpenedSource,
151    ) -> Result<(), SessionError> {
152        if !Arc::ptr_eq(&self.identity, &opened.scope) || opened.authority != authority {
153            return Err(SessionError::ForeignHandle);
154        }
155        if &opened.document != document || &opened.source != source {
156            return Err(SessionError::StaleSource);
157        }
158        Ok(())
159    }
160
161    pub fn replacement(
162        &self,
163        authority: [u8; 12],
164        opened: &OpenedLibrary,
165        document: &CodeDocument,
166        source: &OpenedSource,
167        part: DocumentPart,
168        contents: String,
169    ) -> Result<CodeDocument, SessionError> {
170        self.validate(authority, opened, document, source)?;
171        let (documentation, code) = match part {
172            DocumentPart::Documentation => (contents.into_bytes(), document.code().to_vec()),
173            DocumentPart::Code => (document.documentation().to_vec(), contents.into_bytes()),
174        };
175        CodeDocument::new(
176            document.family().clone(),
177            documentation,
178            document.language(),
179            code,
180        )
181        .map_err(Into::into)
182    }
183}