Skip to main content

ng_net/
app_protocol.rs

1// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
2// All rights reserved.
3// Licensed under the Apache License, Version 2.0
4// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
5// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
6// at your option. All files in the project carrying such
7// notice may not be copied, modified, or distributed except
8// according to those terms.
9
10//! App Protocol (between LocalBroker and Verifier)
11
12use lazy_static::lazy_static;
13use regex::Regex;
14use serde::{Deserialize, Serialize};
15
16use ng_repo::errors::NgError;
17#[allow(unused_imports)]
18use ng_repo::log::*;
19use ng_repo::repo::CommitInfo;
20use ng_repo::types::*;
21use ng_repo::utils::{decode_digest, decode_key, decode_sym_key};
22use ng_repo::utils::{decode_overlayid, display_timestamp_local};
23
24use crate::types::*;
25
26lazy_static! {
27    #[doc(hidden)]
28    static ref RE_FILE_READ_CAP: Regex =
29        Regex::new(r"^did:ng:j:([A-Za-z0-9-_]*):k:([A-Za-z0-9-_]*)$").unwrap();
30    #[doc(hidden)]
31    static ref RE_REPO_O: Regex =
32        Regex::new(r"^did:ng:o:([A-Za-z0-9-_]*)$").unwrap();
33    #[doc(hidden)]
34    static ref RE_REPO: Regex =
35        Regex::new(r"^did:ng:o:([A-Za-z0-9-_]*):v:([A-Za-z0-9-_]*)$").unwrap();
36    #[doc(hidden)]
37    static ref RE_BRANCH: Regex =
38        Regex::new(r"^did:ng:o:([A-Za-z0-9-_]*):v:([A-Za-z0-9-_]*):b:([A-Za-z0-9-_]*)$").unwrap();
39    #[doc(hidden)]
40    static ref RE_NAMED_BRANCH_OR_COMMIT: Regex =
41        Regex::new(r"^did:ng:o:([A-Za-z0-9-_]*):v:([A-Za-z0-9-_]*):a:([A-Za-z0-9-_%]*)$").unwrap(); //TODO: allow international chars. disallow digit as first char
42    #[doc(hidden)]
43    static ref RE_OBJECTS: Regex =
44        Regex::new(r"^did:ng(?::o:([A-Za-z0-9-_]{44}))?:v:([A-Za-z0-9-_]{44})((?::[cj]:[A-Za-z0-9-_]{44}:k:[A-Za-z0-9-_]{44})+)(?::s:([A-Za-z0-9-_]{44}):k:([A-Za-z0-9-_]{44}))?:l:([A-Za-z0-9-_]*)$").unwrap();
45    #[doc(hidden)]
46    static ref RE_OBJECT_READ_CAPS: Regex =
47        Regex::new(r":[cj]:([A-Za-z0-9-_]{44}):k:([A-Za-z0-9-_]{44})").unwrap();
48
49}
50
51#[derive(Clone, Debug, Serialize, Deserialize)]
52pub enum AppFetchContentV0 {
53    Get, // does not subscribe.
54    Subscribe,
55    Update,
56    ReadQuery,
57    WriteQuery,
58    RdfDump,
59    History,
60    SignatureStatus,
61    SignatureRequest,
62    SignedSnapshotRequest,
63    Header,
64    //Invoke,
65}
66
67impl AppFetchContentV0 {
68    pub fn get_or_subscribe(subscribe: bool) -> Self {
69        if !subscribe {
70            AppFetchContentV0::Get
71        } else {
72            AppFetchContentV0::Subscribe
73        }
74    }
75}
76
77#[derive(Clone, Debug, Serialize, Deserialize)]
78pub enum NgAccessV0 {
79    ReadCap(ReadCap),
80    Token(Digest),
81    #[serde(with = "serde_bytes")]
82    ExtRequest(Vec<u8>),
83    Key(BlockKey),
84    Inbox(PubKey),
85}
86
87#[derive(Clone, Debug, Serialize, Deserialize)]
88pub enum TargetBranchV0 {
89    Chat,
90    Stream,
91    Comments,
92    BackLinks,
93    Context,
94    BranchId(BranchId),
95    Named(String),          // branch or commit
96    Commits(Vec<ObjectId>), // only possible if access to their branch is given. must belong to the same branch.
97}
98
99impl TargetBranchV0 {
100    pub fn is_valid_for_sparql_update(&self) -> bool {
101        match self {
102            Self::Commits(_) => false,
103            _ => true,
104        }
105    }
106    pub fn is_valid_for_discrete_update(&self) -> bool {
107        match self {
108            Self::BranchId(_) => true,
109            //TODO: add Named(s) is s is a branch => true
110            _ => false,
111        }
112    }
113    pub fn branch_id(&self) -> &BranchId {
114        match self {
115            Self::BranchId(id) => id,
116            _ => panic!("not a TargetBranchV0::BranchId"),
117        }
118    }
119}
120
121#[derive(Clone, Debug, Serialize, Deserialize)]
122pub enum NuriTargetV0 {
123    UserSite, // targets the whole data set of the user
124
125    PublicStore,
126    ProtectedStore,
127    PrivateStore,
128    AllDialogs,
129    Dialog(String), // shortname of a Dialog
130    AllGroups,
131    Group(String), // shortname of a Group
132
133    Repo(RepoId),
134
135    None,
136}
137
138impl NuriTargetV0 {
139    pub fn is_valid_for_sparql_update(&self) -> bool {
140        match self {
141            Self::UserSite | Self::AllDialogs | Self::AllGroups => false,
142            _ => true,
143        }
144    }
145    pub fn is_valid_for_discrete_update(&self) -> bool {
146        match self {
147            Self::UserSite | Self::AllDialogs | Self::AllGroups | Self::None => false,
148            _ => true,
149        }
150    }
151    pub fn is_repo_id(&self) -> bool {
152        match self {
153            Self::Repo(_) => true,
154            _ => false,
155        }
156    }
157    pub fn repo_id(&self) -> &RepoId {
158        match self {
159            Self::Repo(id) => id,
160            _ => panic!("not a NuriTargetV0::Repo"),
161        }
162    }
163}
164
165#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
166pub struct CommitInfoJs {
167    pub past: Vec<String>,
168    pub key: String,
169    pub signature: Option<String>,
170    pub author: String,
171    pub timestamp: String,
172    pub final_consistency: bool,
173    pub commit_type: CommitType,
174    pub branch: Option<String>,
175    pub x: u32,
176    pub y: u32,
177}
178
179impl From<&CommitInfo> for CommitInfoJs {
180    fn from(info: &CommitInfo) -> Self {
181        CommitInfoJs {
182            past: info.past.iter().map(|objid| objid.to_string()).collect(),
183            key: info.key.to_string(),
184            signature: info.signature.as_ref().map(|s| NuriV0::signature_ref(&s)),
185            author: info.author.clone(),
186            timestamp: display_timestamp_local(info.timestamp),
187            final_consistency: info.final_consistency,
188            commit_type: info.commit_type.clone(),
189            branch: info.branch.map(|b| b.to_string()),
190            x: info.x,
191            y: info.y,
192        }
193    }
194}
195
196const DID_PREFIX: &str = "did:ng";
197
198#[derive(Clone, Debug, Serialize, Deserialize)]
199pub struct NuriV0 {
200    pub identity: Option<UserId>, // None for personal identity
201    pub target: NuriTargetV0,
202    pub entire_store: bool, // If it is a store, will include all the docs belonging to the store
203
204    pub objects: Vec<ObjectRef>, // used only for FileGet. // cannot be used for queries. only to download an object (file,commit..)
205    pub signature: Option<ObjectRef>,
206
207    pub branch: Option<TargetBranchV0>, // if None, the main branch is chosen
208    pub overlay: Option<OverlayLink>,
209
210    pub access: Vec<NgAccessV0>,
211    pub topic: Option<TopicId>,
212    pub locator: Option<Locator>,
213}
214
215impl NuriV0 {
216    pub fn new_empty() -> Self {
217        NuriV0 {
218            identity: None,
219            target: NuriTargetV0::None,
220            entire_store: false,
221            objects: vec![],
222            signature: None,
223            branch: None,
224            overlay: None,
225            access: vec![],
226            topic: None,
227            locator: None,
228        }
229    }
230    pub fn copy_target_from(&mut self, nuri: &NuriV0) {
231        self.target = nuri.target.clone();
232    }
233    pub fn commit_graph_name(commit_id: &ObjectId, overlay_id: &OverlayId) -> String {
234        format!("{DID_PREFIX}:c:{commit_id}:v:{overlay_id}")
235    }
236
237    pub fn commit_graph_name_from_base64(commit_base64: &String, overlay_id: &OverlayId) -> String {
238        format!("{DID_PREFIX}:c:{commit_base64}:v:{overlay_id}")
239    }
240
241    pub fn from_store_repo(store_repo: &StoreRepo) -> Self {
242        NuriV0 {
243            identity: None,
244            target: NuriTargetV0::Repo(store_repo.repo_id().clone()),
245            entire_store: false,
246            objects: vec![],
247            signature: None,
248            branch: None,
249            overlay: None,
250            access: vec![],
251            topic: None,
252            locator: None,
253        }
254    }
255
256    pub fn to_store_nuri_string(store_id: &RepoId) -> String {
257        let overlay_id = OverlayId::outer(store_id);
258        format!("o:{store_id}:v:{overlay_id}")
259    }
260
261    pub fn repo_graph_name(repo_id: &RepoId, overlay_id: &OverlayId) -> String {
262        format!("{DID_PREFIX}:o:{repo_id}:v:{overlay_id}")
263    }
264
265    pub fn branch_repo_graph_name(
266        branch_id: &BranchId,
267        repo_id: &RepoId,
268        overlay_id: &OverlayId,
269    ) -> String {
270        format!("{DID_PREFIX}:o:{repo_id}:v:{overlay_id}:b:{branch_id}")
271    }
272
273    pub fn repo_skolem(
274        repo_id: &RepoId,
275        peer_id: &Vec<u8>,
276        random: u128,
277    ) -> Result<String, NgError> {
278        let mut arr = Vec::with_capacity(32);
279        arr.extend_from_slice(peer_id);
280        arr.extend_from_slice(&random.to_be_bytes());
281        let sko: SymKey = arr.as_slice().try_into()?;
282        Ok(format!("{DID_PREFIX}:o:{repo_id}:u:{sko}"))
283    }
284
285    pub fn repo(&self) -> String {
286        Self::repo_id(self.target.repo_id())
287    }
288
289    pub fn repo_id(repo_id: &RepoId) -> String {
290        format!("{DID_PREFIX}:o:{}", repo_id)
291    }
292
293    pub fn overlay_id(overlay_id: &OverlayId) -> String {
294        format!("{DID_PREFIX}:v:{overlay_id}")
295    }
296
297    pub fn topic_id(topic_id: &TopicId) -> String {
298        format!("{DID_PREFIX}:h:{topic_id}")
299    }
300
301    pub fn branch_id(branch_id: &BranchId) -> String {
302        format!("{DID_PREFIX}:b:{branch_id}")
303    }
304
305    pub fn branch_id_from_base64(branch_base64: &String) -> String {
306        format!("{DID_PREFIX}:b:{branch_base64}")
307    }
308
309    pub fn object_ref(obj_ref: &ObjectRef) -> String {
310        format!("{DID_PREFIX}:{}", obj_ref.object_nuri())
311    }
312
313    pub fn signature_ref(obj_ref: &ObjectRef) -> String {
314        format!("s:{}:k:{}", obj_ref.id, obj_ref.key)
315    }
316
317    pub fn token(token: &Digest) -> String {
318        format!("{DID_PREFIX}:n:{token}")
319    }
320
321    pub fn tokenized_commit(repo_id: &RepoId, commit_id: &ObjectId) -> String {
322        format!("{DID_PREFIX}:o:{repo_id}:t:{commit_id}")
323    }
324
325    pub fn commit(repo_id: &RepoId, commit_id: &ObjectId) -> String {
326        format!("{DID_PREFIX}:o:{repo_id}:c:{commit_id}")
327    }
328
329    pub fn locator(locator: &Locator) -> String {
330        format!("l:{locator}")
331    }
332
333    pub fn is_branch_identifier(&self) -> bool {
334        self.locator.is_none()
335            && self.topic.is_none()
336            && self.access.is_empty()
337            && self.overlay.as_ref().map_or(false, |o| o.is_outer())
338            && self
339                .branch
340                .as_ref()
341                .map_or(true, |b| b.is_valid_for_sparql_update())
342            && self.objects.is_empty()
343            && self.signature.is_none()
344            && !self.entire_store
345            && self.target.is_repo_id()
346    }
347
348    pub fn is_valid_for_sparql_update(&self) -> bool {
349        self.objects.is_empty()
350            && self.signature.is_none()
351            && self.entire_store == false
352            && self.target.is_valid_for_sparql_update()
353            && self
354                .branch
355                .as_ref()
356                .map_or(true, |b| b.is_valid_for_sparql_update())
357    }
358    pub fn is_valid_for_discrete_update(&self) -> bool {
359        self.objects.is_empty()
360            && self.signature.is_none()
361            && self.entire_store == false
362            && self.target.is_valid_for_discrete_update()
363            && self
364                .branch
365                .as_ref()
366                .map_or(true, |b| b.is_valid_for_discrete_update())
367    }
368    pub fn new_repo_target_from_string(repo_id_string: String) -> Result<Self, NgError> {
369        let repo_id: RepoId = repo_id_string.as_str().try_into()?;
370        Ok(Self {
371            identity: None,
372            target: NuriTargetV0::Repo(repo_id),
373            entire_store: false,
374            objects: vec![],
375            signature: None,
376            branch: None,
377            overlay: None,
378            access: vec![],
379            topic: None,
380            locator: None,
381        })
382    }
383
384    pub fn new_from_obj_ref(obj_ref: &ObjectRef) -> Self {
385        Self {
386            identity: None,
387            target: NuriTargetV0::None,
388            entire_store: false,
389            objects: vec![obj_ref.clone()],
390            signature: None,
391            branch: None,
392            overlay: None,
393            access: vec![],
394            topic: None,
395            locator: None,
396        }
397    }
398
399    pub fn new_private_store_target() -> Self {
400        Self {
401            identity: None,
402            target: NuriTargetV0::PrivateStore,
403            entire_store: false,
404            objects: vec![],
405            signature: None,
406            branch: None,
407            overlay: None,
408            access: vec![],
409            topic: None,
410            locator: None,
411        }
412    }
413    pub fn new_entire_user_site() -> Self {
414        Self {
415            identity: None,
416            target: NuriTargetV0::UserSite,
417            entire_store: false,
418            objects: vec![],
419            signature: None,
420            branch: None,
421            overlay: None,
422            access: vec![],
423            topic: None,
424            locator: None,
425        }
426    }
427    pub fn new_for_readcaps(from: &str) -> Result<Self, NgError> {
428        let c = RE_OBJECTS.captures(from);
429        if let Some(c) = c {
430            let target = c.get(1).map_or(NuriTargetV0::None, |repo_match| {
431                if let Ok(id) = decode_key(repo_match.as_str()) {
432                    NuriTargetV0::Repo(id)
433                } else {
434                    NuriTargetV0::None
435                }
436            });
437            let overlay_id = decode_overlayid(c.get(2).ok_or(NgError::InvalidNuri)?.as_str())?;
438            let read_caps = c.get(3).ok_or(NgError::InvalidNuri)?.as_str();
439            let sign_obj_id = c.get(4).map(|c| decode_digest(c.as_str()));
440            let sign_obj_key = c.get(5).map(|c| decode_sym_key(c.as_str()));
441            let locator =
442                TryInto::<Locator>::try_into(c.get(6).ok_or(NgError::InvalidNuri)?.as_str())?;
443            let signature = if sign_obj_id.is_some() && sign_obj_key.is_some() {
444                Some(ObjectRef::from_id_key(
445                    sign_obj_id.unwrap()?,
446                    sign_obj_key.unwrap()?,
447                ))
448            } else {
449                None
450            };
451
452            let objects = RE_OBJECT_READ_CAPS
453                .captures_iter(read_caps)
454                .map(|c| {
455                    Ok(ObjectRef::from_id_key(
456                        decode_digest(c.get(1).ok_or(NgError::InvalidNuri)?.as_str())?,
457                        decode_sym_key(c.get(2).ok_or(NgError::InvalidNuri)?.as_str())?,
458                    ))
459                })
460                .collect::<Result<Vec<ObjectRef>, NgError>>()?;
461
462            if objects.len() < 1 {
463                return Err(NgError::InvalidNuri);
464            }
465
466            Ok(Self {
467                identity: None,
468                target,
469                entire_store: false,
470                objects,
471                signature,
472                branch: None,
473                overlay: Some(overlay_id.into()),
474                access: vec![],
475                topic: None,
476                locator: Some(locator),
477            })
478        } else {
479            Err(NgError::InvalidNuri)
480        }
481    }
482
483    pub fn new_from(from: &String) -> Result<Self, NgError> {
484        let c = RE_REPO_O.captures(from);
485
486        if c.is_some() && c.as_ref().unwrap().get(1).is_some() {
487            let cap = c.unwrap();
488            let o = cap.get(1).unwrap().as_str();
489
490            let repo_id = decode_key(o)?;
491            Ok(Self {
492                identity: None,
493                target: NuriTargetV0::Repo(repo_id),
494                entire_store: false,
495                objects: vec![],
496                signature: None,
497                branch: None,
498                overlay: None,
499                access: vec![],
500                topic: None,
501                locator: None,
502            })
503        } else {
504            let c = RE_FILE_READ_CAP.captures(from);
505            if c.is_some()
506                && c.as_ref().unwrap().get(1).is_some()
507                && c.as_ref().unwrap().get(2).is_some()
508            {
509                let cap = c.unwrap();
510                let j = cap.get(1).unwrap().as_str();
511                let k = cap.get(2).unwrap().as_str();
512                let id = decode_digest(j)?;
513                let key = decode_sym_key(k)?;
514                Ok(Self {
515                    identity: None,
516                    target: NuriTargetV0::None,
517                    entire_store: false,
518                    objects: vec![ObjectRef::from_id_key(id, key)],
519                    signature: None,
520                    branch: None,
521                    overlay: None,
522                    access: vec![],
523                    topic: None,
524                    locator: None,
525                })
526            } else {
527                let c = RE_REPO.captures(from);
528
529                if c.is_some()
530                    && c.as_ref().unwrap().get(1).is_some()
531                    && c.as_ref().unwrap().get(2).is_some()
532                {
533                    let cap = c.unwrap();
534                    let o = cap.get(1).unwrap().as_str();
535
536                    let v = cap.get(2).unwrap().as_str();
537                    let repo_id = decode_key(o)?;
538                    let overlay_id = decode_overlayid(v)?;
539                    Ok(Self {
540                        identity: None,
541                        target: NuriTargetV0::Repo(repo_id),
542                        entire_store: false,
543                        objects: vec![],
544                        signature: None,
545                        branch: None,
546                        overlay: Some(overlay_id.into()),
547                        access: vec![],
548                        topic: None,
549                        locator: None,
550                    })
551                } else {
552                    let c = RE_BRANCH.captures(from);
553
554                    if c.is_some()
555                        && c.as_ref().unwrap().get(1).is_some()
556                        && c.as_ref().unwrap().get(2).is_some()
557                        && c.as_ref().unwrap().get(3).is_some()
558                    {
559                        let cap = c.unwrap();
560                        let o = cap.get(1).unwrap().as_str();
561                        let v = cap.get(2).unwrap().as_str();
562                        let b = cap.get(3).unwrap().as_str();
563                        let repo_id = decode_key(o)?;
564                        let overlay_id = decode_overlayid(v)?;
565                        let branch_id = decode_key(b)?;
566                        Ok(Self {
567                            identity: None,
568                            target: NuriTargetV0::Repo(repo_id),
569                            entire_store: false,
570                            objects: vec![],
571                            signature: None,
572                            branch: Some(TargetBranchV0::BranchId(branch_id)),
573                            overlay: Some(overlay_id.into()),
574                            access: vec![],
575                            topic: None,
576                            locator: None,
577                        })
578                    } else {
579                        Err(NgError::InvalidNuri)
580                    }
581                }
582            }
583        }
584    }
585}
586
587#[derive(Clone, Debug, Serialize, Deserialize)]
588pub enum AppRequestCommandV0 {
589    Fetch(AppFetchContentV0),
590    Pin,
591    UnPin,
592    Delete,
593    Create,
594    FileGet, // needs the Nuri of branch/doc/store AND ObjectId
595    FilePut, // needs the Nuri of branch/doc/store
596    Header,
597}
598
599impl AppRequestCommandV0 {
600    pub fn is_stream(&self) -> bool {
601        match self {
602            Self::Fetch(AppFetchContentV0::Subscribe) | Self::FileGet => true,
603            Self::FilePut
604            | Self::Create
605            | Self::Delete
606            | Self::UnPin
607            | Self::Pin
608            | Self::Header
609            | Self::Fetch(_) => false,
610        }
611    }
612    pub fn new_read_query() -> Self {
613        AppRequestCommandV0::Fetch(AppFetchContentV0::ReadQuery)
614    }
615    pub fn new_write_query() -> Self {
616        AppRequestCommandV0::Fetch(AppFetchContentV0::WriteQuery)
617    }
618    pub fn new_update() -> Self {
619        AppRequestCommandV0::Fetch(AppFetchContentV0::Update)
620    }
621    pub fn new_rdf_dump() -> Self {
622        AppRequestCommandV0::Fetch(AppFetchContentV0::RdfDump)
623    }
624    pub fn new_history() -> Self {
625        AppRequestCommandV0::Fetch(AppFetchContentV0::History)
626    }
627    pub fn new_signature_status() -> Self {
628        AppRequestCommandV0::Fetch(AppFetchContentV0::SignatureStatus)
629    }
630    pub fn new_signature_request() -> Self {
631        AppRequestCommandV0::Fetch(AppFetchContentV0::SignatureRequest)
632    }
633    pub fn new_signed_snapshot_request() -> Self {
634        AppRequestCommandV0::Fetch(AppFetchContentV0::SignedSnapshotRequest)
635    }
636    pub fn new_create() -> Self {
637        AppRequestCommandV0::Create
638    }
639    pub fn new_header() -> Self {
640        AppRequestCommandV0::Header
641    }
642    pub fn new_fetch_header() -> Self {
643        AppRequestCommandV0::Fetch(AppFetchContentV0::Header)
644    }
645}
646
647#[derive(Clone, Debug, Serialize, Deserialize)]
648pub struct AppRequestV0 {
649    pub command: AppRequestCommandV0,
650
651    pub nuri: NuriV0,
652
653    pub payload: Option<AppRequestPayload>,
654
655    pub session_id: u64,
656}
657
658#[derive(Clone, Debug, Serialize, Deserialize)]
659pub enum AppRequest {
660    V0(AppRequestV0),
661}
662
663impl AppRequest {
664    pub fn set_session_id(&mut self, session_id: u64) {
665        match self {
666            Self::V0(v0) => v0.session_id = session_id,
667        }
668    }
669    pub fn session_id(&self) -> u64 {
670        match self {
671            Self::V0(v0) => v0.session_id,
672        }
673    }
674    pub fn command(&self) -> &AppRequestCommandV0 {
675        match self {
676            Self::V0(v0) => &v0.command,
677        }
678    }
679    pub fn new(
680        command: AppRequestCommandV0,
681        nuri: NuriV0,
682        payload: Option<AppRequestPayload>,
683    ) -> Self {
684        AppRequest::V0(AppRequestV0 {
685            command,
686            nuri,
687            payload,
688            session_id: 0,
689        })
690    }
691
692    pub fn doc_fetch_repo_subscribe(repo_o: String) -> Result<Self, NgError> {
693        Ok(AppRequest::new(
694            AppRequestCommandV0::Fetch(AppFetchContentV0::get_or_subscribe(true)),
695            NuriV0::new_from(&repo_o)?,
696            None,
697        ))
698    }
699}
700
701#[derive(Clone, Debug, Serialize, Deserialize)]
702pub struct AppSessionStopV0 {
703    pub session_id: u64,
704    pub force_close: bool,
705}
706
707#[derive(Clone, Debug, Serialize, Deserialize)]
708pub enum AppSessionStop {
709    V0(AppSessionStopV0),
710}
711impl AppSessionStop {
712    pub fn session_id(&self) -> u64 {
713        match self {
714            Self::V0(v0) => v0.session_id,
715        }
716    }
717    pub fn is_force_close(&self) -> bool {
718        match self {
719            Self::V0(v0) => v0.force_close,
720        }
721    }
722}
723#[derive(Clone, Debug, Serialize, Deserialize)]
724pub struct AppSessionStartV0 {
725    pub session_id: u64,
726
727    pub credentials: Option<Credentials>,
728
729    pub user_id: UserId,
730
731    pub detach: bool,
732}
733
734#[derive(Clone, Debug, Serialize, Deserialize)]
735pub enum AppSessionStart {
736    V0(AppSessionStartV0),
737}
738
739#[derive(Clone, Debug, Serialize, Deserialize)]
740pub struct AppSessionStartResponseV0 {
741    pub private_store: RepoId,
742    pub protected_store: RepoId,
743    pub public_store: RepoId,
744}
745
746#[derive(Clone, Debug, Serialize, Deserialize)]
747pub enum AppSessionStartResponse {
748    V0(AppSessionStartResponseV0),
749}
750
751impl AppSessionStart {
752    pub fn session_id(&self) -> u64 {
753        match self {
754            Self::V0(v0) => v0.session_id,
755        }
756    }
757    pub fn credentials(&self) -> &Option<Credentials> {
758        match self {
759            Self::V0(v0) => &v0.credentials,
760        }
761    }
762    pub fn user_id(&self) -> &UserId {
763        match self {
764            Self::V0(v0) => &v0.user_id,
765        }
766    }
767}
768
769#[derive(Clone, Debug, Serialize, Deserialize)]
770pub enum DocQuery {
771    V0 {
772        sparql: String,
773        base: Option<String>,
774    },
775}
776
777#[derive(Clone, Debug, Serialize, Deserialize)]
778pub struct GraphUpdate {
779    // serialization of Vec<Triple>
780    #[serde(with = "serde_bytes")]
781    pub inserts: Vec<u8>,
782    // serialization of Vec<Triple>
783    #[serde(with = "serde_bytes")]
784    pub removes: Vec<u8>,
785}
786
787#[derive(Clone, Debug, Serialize, Deserialize)]
788pub enum DiscreteUpdate {
789    /// A yrs::Update
790    #[serde(with = "serde_bytes")]
791    YMap(Vec<u8>),
792    #[serde(with = "serde_bytes")]
793    YArray(Vec<u8>),
794    #[serde(with = "serde_bytes")]
795    YXml(Vec<u8>),
796    #[serde(with = "serde_bytes")]
797    YText(Vec<u8>),
798    /// An automerge::Change.raw_bytes()
799    #[serde(with = "serde_bytes")]
800    Automerge(Vec<u8>),
801}
802
803impl DiscreteUpdate {
804    pub fn from(crdt: String, update: Vec<u8>) -> Self {
805        match crdt.as_str() {
806            "YMap" => Self::YMap(update),
807            "YArray" => Self::YArray(update),
808            "YXml" => Self::YXml(update),
809            "YText" => Self::YText(update),
810            "Automerge" => Self::Automerge(update),
811            _ => panic!("wrong crdt type"),
812        }
813    }
814}
815
816#[derive(Clone, Debug, Serialize, Deserialize)]
817pub struct DocUpdate {
818    pub heads: Vec<ObjectId>,
819    pub graph: Option<GraphUpdate>,
820    pub discrete: Option<DiscreteUpdate>,
821}
822
823#[derive(Clone, Debug, Serialize, Deserialize)]
824pub struct DocAddFile {
825    pub filename: Option<String>,
826    pub object: ObjectRef,
827}
828
829#[derive(Clone, Debug, Serialize, Deserialize)]
830pub struct DocHeader {
831    pub title: Option<String>,
832    pub about: Option<String>,
833}
834
835#[derive(Clone, Debug, Serialize, Deserialize)]
836pub enum DocCreateDestination {
837    Store,
838    Stream,
839    MagicCarpet,
840}
841
842impl DocCreateDestination {
843    pub fn from(s: String) -> Result<Self, NgError> {
844        Ok(match s.as_str() {
845            "store" => Self::Store,
846            "stream" => Self::Stream,
847            "mc" => Self::MagicCarpet,
848            _ => return Err(NgError::InvalidArgument),
849        })
850    }
851}
852
853#[derive(Clone, Debug, Serialize, Deserialize)]
854pub struct DocCreate {
855    pub class: BranchCrdt,
856    pub destination: DocCreateDestination,
857}
858
859#[derive(Clone, Debug, Serialize, Deserialize)]
860pub struct DocDelete {
861    /// Nuri of doc to delete
862    nuri: String,
863}
864
865#[derive(Clone, Debug, Serialize, Deserialize)]
866pub enum AppRequestPayloadV0 {
867    Create(DocCreate),
868    Query(DocQuery),
869    Update(DocUpdate),
870    AddFile(DocAddFile),
871
872    Delete(DocDelete),
873
874    SmallFilePut(SmallFile),
875    RandomAccessFilePut(String), // content_type (iana media type)
876    RandomAccessFilePutChunk((u32, serde_bytes::ByteBuf)), // end the upload with an empty vec
877
878    Header(DocHeader),
879    //RemoveFile
880    //Invoke(InvokeArguments),
881}
882
883#[derive(Clone, Debug, Serialize, Deserialize)]
884pub enum AppRequestPayload {
885    V0(AppRequestPayloadV0),
886}
887
888impl AppRequestPayload {
889    pub fn new_sparql_query(sparql: String, base: Option<String>) -> Self {
890        AppRequestPayload::V0(AppRequestPayloadV0::Query(DocQuery::V0 { sparql, base }))
891    }
892    pub fn new_header(title: Option<String>, about: Option<String>) -> Self {
893        AppRequestPayload::V0(AppRequestPayloadV0::Header(DocHeader { title, about }))
894    }
895    pub fn new_discrete_update(
896        head_strings: Vec<String>,
897        crdt: String,
898        update: Vec<u8>,
899    ) -> Result<Self, NgError> {
900        let mut heads = Vec::with_capacity(head_strings.len());
901        for head in head_strings {
902            heads.push(decode_digest(&head)?);
903        }
904        let discrete = Some(DiscreteUpdate::from(crdt, update));
905        Ok(AppRequestPayload::V0(AppRequestPayloadV0::Update(
906            DocUpdate {
907                heads,
908                graph: None,
909                discrete,
910            },
911        )))
912    }
913}
914
915#[derive(Clone, Debug, Serialize, Deserialize)]
916pub enum DiscretePatch {
917    /// A yrs::Update
918    #[serde(with = "serde_bytes")]
919    YMap(Vec<u8>),
920    #[serde(with = "serde_bytes")]
921    YArray(Vec<u8>),
922    #[serde(with = "serde_bytes")]
923    YXml(Vec<u8>),
924    #[serde(with = "serde_bytes")]
925    YText(Vec<u8>),
926    /// An automerge::Change.raw_bytes() or a concatenation of several.
927    #[serde(with = "serde_bytes")]
928    Automerge(Vec<u8>),
929}
930
931#[derive(Clone, Debug, Serialize, Deserialize)]
932pub struct GraphPatch {
933    // serialization of Vec<Triple>
934    #[serde(with = "serde_bytes")]
935    pub inserts: Vec<u8>,
936    // serialization of Vec<Triple>
937    #[serde(with = "serde_bytes")]
938    pub removes: Vec<u8>,
939}
940
941#[derive(Clone, Debug, Serialize, Deserialize)]
942pub enum DiscreteState {
943    /// A yrs::Update
944    #[serde(with = "serde_bytes")]
945    YMap(Vec<u8>),
946    #[serde(with = "serde_bytes")]
947    YArray(Vec<u8>),
948    #[serde(with = "serde_bytes")]
949    YXml(Vec<u8>),
950    #[serde(with = "serde_bytes")]
951    YText(Vec<u8>),
952    // the output of Automerge::save()
953    #[serde(with = "serde_bytes")]
954    Automerge(Vec<u8>),
955}
956
957#[derive(Clone, Debug, Serialize, Deserialize)]
958pub struct GraphState {
959    // serialization of Vec<Triple>
960    #[serde(with = "serde_bytes")]
961    pub triples: Vec<u8>,
962}
963
964#[derive(Clone, Debug, Serialize, Deserialize)]
965pub struct AppState {
966    pub heads: Vec<ObjectId>,
967    pub head_keys: Vec<ObjectKey>,
968    pub graph: Option<GraphState>, // there is always a graph present in the branch. but it might not have been asked in the request
969    pub discrete: Option<DiscreteState>,
970    pub files: Vec<FileName>,
971}
972
973#[derive(Clone, Debug, Serialize, Deserialize)]
974pub struct AppHistory {
975    pub history: Vec<(ObjectId, CommitInfo)>,
976    pub swimlane_state: Vec<Option<ObjectId>>,
977}
978
979#[derive(Clone, Debug, Serialize, Deserialize)]
980pub struct AppHistoryJs {
981    pub history: Vec<(String, CommitInfoJs)>,
982    pub swimlane_state: Vec<Option<String>>,
983}
984
985impl AppHistory {
986    pub fn to_js(&self) -> AppHistoryJs {
987        AppHistoryJs {
988            history: Vec::from_iter(
989                self.history
990                    .iter()
991                    .map(|(id, info)| (id.to_string(), info.into())),
992            ),
993            swimlane_state: Vec::from_iter(
994                self.swimlane_state
995                    .iter()
996                    .map(|lane| lane.map_or(None, |b| Some(b.to_string()))),
997            ),
998        }
999    }
1000}
1001
1002#[derive(Clone, Debug, Serialize, Deserialize)]
1003pub enum OtherPatch {
1004    FileAdd(FileName),
1005    FileRemove(ObjectId),
1006    AsyncSignature((String, Vec<String>)),
1007    Snapshot(ObjectRef),
1008    Compact(ObjectRef),
1009    Other,
1010}
1011
1012#[derive(Clone, Debug, Serialize, Deserialize)]
1013pub struct AppPatch {
1014    pub commit_id: String,
1015    pub commit_info: CommitInfoJs,
1016    // or graph, or discrete, or both, or other.
1017    pub graph: Option<GraphPatch>,
1018    pub discrete: Option<DiscretePatch>,
1019    pub other: Option<OtherPatch>,
1020}
1021
1022#[derive(Clone, Debug, Serialize, Deserialize)]
1023pub struct FileName {
1024    pub name: Option<String>,
1025    pub reference: ObjectRef,
1026    pub nuri: String,
1027}
1028
1029#[derive(Clone, Debug, Serialize, Deserialize)]
1030pub struct FileMetaV0 {
1031    pub content_type: String,
1032    pub size: u64,
1033}
1034
1035#[derive(Clone, Debug, Serialize, Deserialize)]
1036pub struct AppTabStoreInfo {
1037    pub repo: Option<StoreRepo>, //+
1038    pub overlay: Option<String>, //+
1039    pub has_outer: Option<String>,
1040    pub store_type: Option<String>, //+
1041    pub readcap: Option<String>,
1042    pub is_member: Option<String>,
1043    pub inner: Option<String>,
1044    pub title: Option<String>,
1045    pub icon: Option<String>,
1046    pub description: Option<String>,
1047}
1048
1049#[derive(Clone, Debug, Serialize, Deserialize)]
1050pub struct AppTabDocInfo {
1051    pub nuri: Option<String>,      //+
1052    pub is_store: Option<bool>,    //+
1053    pub is_member: Option<String>, //+
1054    pub title: Option<String>,
1055    pub icon: Option<String>,
1056    pub description: Option<String>,
1057    pub authors: Option<Vec<String>>,
1058    pub inbox: Option<String>,
1059    pub can_edit: Option<bool>, //+
1060                                //TODO stream
1061                                //TODO live_editors
1062                                //TODO branches
1063}
1064
1065impl AppTabDocInfo {
1066    pub fn new() -> Self {
1067        AppTabDocInfo {
1068            nuri: None,
1069            is_store: None,
1070            is_member: None,
1071            title: None,
1072            icon: None,
1073            description: None,
1074            authors: None,
1075            inbox: None,
1076            can_edit: None,
1077        }
1078    }
1079}
1080
1081#[derive(Clone, Debug, Serialize, Deserialize)]
1082pub struct AppTabBranchInfo {
1083    pub id: Option<String>,      //+
1084    pub readcap: Option<String>, //+
1085    pub comment_branch: Option<String>,
1086    pub class: Option<String>, //+
1087}
1088
1089#[derive(Clone, Debug, Serialize, Deserialize)]
1090pub struct AppTabInfo {
1091    pub branch: Option<AppTabBranchInfo>,
1092    pub doc: Option<AppTabDocInfo>,
1093    pub store: Option<AppTabStoreInfo>,
1094}
1095
1096#[derive(Clone, Debug, Serialize, Deserialize)]
1097pub struct AppHeader {
1098    pub about: Option<String>,
1099    pub title: Option<String>,
1100    pub class: Option<String>,
1101}
1102
1103#[derive(Clone, Debug, Serialize, Deserialize)]
1104pub enum AppResponseV0 {
1105    SessionStart(AppSessionStartResponse),
1106    TabInfo(AppTabInfo),
1107    State(AppState),
1108    Patch(AppPatch),
1109    History(AppHistory),
1110    SignatureStatus(Vec<(String, Option<String>, bool)>),
1111    Text(String),
1112    //File(FileName),
1113    FileUploading(u32),
1114    FileUploaded(ObjectRef),
1115    #[serde(with = "serde_bytes")]
1116    FileBinary(Vec<u8>),
1117    FileMeta(FileMetaV0),
1118    #[serde(with = "serde_bytes")]
1119    QueryResult(Vec<u8>), // a serialized [SPARQL Query Results JSON Format](https://www.w3.org/TR/sparql11-results-json/)
1120    #[serde(with = "serde_bytes")]
1121    Graph(Vec<u8>), // a serde serialization of a list of triples. can be transformed on the client side to RDF-JS data model, or JSON-LD, or else (Turtle,...) http://rdf.js.org/data-model-spec/
1122    Ok,
1123    True,
1124    False,
1125    Error(String),
1126    EndOfStream,
1127    Nuri(String),
1128    Header(AppHeader),
1129    Commits(Vec<String>),
1130}
1131
1132#[derive(Clone, Debug, Serialize, Deserialize)]
1133pub enum AppResponse {
1134    V0(AppResponseV0),
1135}
1136
1137impl AppResponse {
1138    pub fn error(err: String) -> Self {
1139        AppResponse::V0(AppResponseV0::Error(err))
1140    }
1141    pub fn ok() -> Self {
1142        AppResponse::V0(AppResponseV0::Ok)
1143    }
1144    pub fn commits(commits: Vec<String>) -> Self {
1145        AppResponse::V0(AppResponseV0::Commits(commits))
1146    }
1147}