endbasic_client/lib.rs
1// EndBASIC
2// Copyright 2021 Julio Merino
3//
4// Licensed under the Apache License, Version 2.0 (the "License"); you may not
5// use this file except in compliance with the License. You may obtain a copy
6// of the License at:
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13// License for the specific language governing permissions and limitations
14// under the License.
15
16//! EndBASIC service client.
17
18use async_trait::async_trait;
19use endbasic_std::storage::{DiskSpace, FileAcls};
20use serde::{Deserialize, Serialize};
21use std::io;
22
23mod cloud;
24pub use cloud::CloudService;
25mod cmds;
26pub use cmds::add_all;
27mod drive;
28pub(crate) use drive::CloudDriveFactory;
29#[cfg(test)]
30pub(crate) mod testutils;
31
32/// Base address of the production REST API.
33pub const PROD_API_ADDRESS: &str = "https://service.endbasic.dev/";
34
35/// Wrapper over `DiskSpace` to implement (de)serialization.
36#[derive(Debug, Deserialize)]
37#[cfg_attr(test, derive(PartialEq, Serialize))]
38struct SerdeDiskSpace {
39 bytes: u64,
40 files: u64,
41}
42
43impl From<DiskSpace> for SerdeDiskSpace {
44 fn from(ds: DiskSpace) -> Self {
45 SerdeDiskSpace { bytes: ds.bytes, files: ds.files }
46 }
47}
48
49impl From<SerdeDiskSpace> for DiskSpace {
50 fn from(sds: SerdeDiskSpace) -> Self {
51 DiskSpace { bytes: sds.bytes, files: sds.files }
52 }
53}
54
55/// An opaque access token obtained during authentication and used for all subsequent requests
56/// against the server.
57#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
58#[cfg_attr(test, derive(Serialize))]
59pub struct AccessToken(String);
60
61impl AccessToken {
62 /// Creates a new access token based on the raw `token` string.
63 #[cfg(test)]
64 pub(crate) fn new<S: Into<String>>(token: S) -> Self {
65 Self(token.into())
66 }
67
68 /// Obtains the textual representation of the token so that it can be sent back to the server.
69 pub(crate) fn as_str(&self) -> &str {
70 &self.0
71 }
72}
73
74/// Representation of the details of an error response.
75#[derive(Deserialize)]
76#[cfg_attr(test, derive(Debug, Serialize))]
77pub struct ErrorResponse {
78 pub(crate) message: String,
79}
80
81/// Representation of a login response.
82#[derive(Deserialize)]
83#[cfg_attr(test, derive(Debug, Serialize))]
84pub struct LoginResponse {
85 pub(crate) access_token: AccessToken,
86 motd: Vec<String>,
87}
88
89/// Representation of a single directory entry as returned by the server.
90#[derive(Deserialize)]
91#[cfg_attr(test, derive(Debug, Serialize))]
92pub struct DirectoryEntry {
93 filename: String,
94 mtime: u64,
95 length: u64,
96}
97
98/// Representation of a directory enumeration response.
99#[derive(Deserialize)]
100#[cfg_attr(test, derive(Debug, Serialize))]
101pub struct GetFilesResponse {
102 files: Vec<DirectoryEntry>,
103 disk_quota: Option<SerdeDiskSpace>,
104 disk_free: Option<SerdeDiskSpace>,
105}
106
107/// Representation of a signup request.
108#[derive(Debug, Default, Eq, PartialEq, Serialize)]
109#[cfg_attr(test, derive(Deserialize))]
110pub struct SignupRequest {
111 username: String,
112 password: String,
113 email: String,
114 promotional_email: bool,
115}
116
117/// Abstract interface to interact with an EndBASIC service server.
118#[async_trait(?Send)]
119pub trait Service {
120 /// Interactively creates an account based on the details provided in `request`.
121 async fn signup(&mut self, request: &SignupRequest) -> io::Result<()>;
122
123 /// Sends an authentication request to the service with `username` and `password` to obtain an
124 /// access token for the session.
125 ///
126 /// If logging is successful, the access token is cached for future retrieval.
127 async fn login(&mut self, username: &str, password: &str) -> io::Result<LoginResponse>;
128
129 /// Logs out from the service and clears the access token from this object.
130 async fn logout(&mut self) -> io::Result<()>;
131
132 /// Checks if there is an active session against the service.
133 fn is_logged_in(&self) -> bool;
134
135 /// Returns the logged in username if there is an active session.
136 fn logged_in_username(&self) -> Option<String>;
137
138 /// Sends a request to the server to obtain the list of files owned by `username` with a
139 /// previously-acquired `access_token`.
140 async fn get_files(&mut self, username: &str) -> io::Result<GetFilesResponse>;
141
142 /// Sends a request to the server to obtain the contents of `filename` owned by `username` with a
143 /// previously-acquired `access_token`.
144 async fn get_file(&mut self, username: &str, filename: &str) -> io::Result<Vec<u8>>;
145
146 /// Sends a request to the server to obtain the ACLs of `filename` owned by `username` with a
147 /// previously-acquired `access_token`.
148 async fn get_file_acls(&mut self, username: &str, filename: &str) -> io::Result<FileAcls>;
149
150 /// Sends a request to the server to update the contents of `filename` owned by `username` as
151 /// specified in `content` with a previously-acquired `access_token`.
152 async fn patch_file_content(
153 &mut self,
154 username: &str,
155 filename: &str,
156 content: Vec<u8>,
157 ) -> io::Result<()>;
158
159 /// Sends a request to the server to update the ACLs of `filename` owned by `username` as
160 /// specified in `add` and `remove` with a previously-acquired `access_token`.
161 async fn patch_file_acls(
162 &mut self,
163 username: &str,
164 filename: &str,
165 add: &FileAcls,
166 remove: &FileAcls,
167 ) -> io::Result<()>;
168
169 /// Sends a request to the server to delete `filename` owned by `username` with a
170 /// previously-acquired `access_token`.
171 async fn delete_file(&mut self, username: &str, filename: &str) -> io::Result<()>;
172}